Skip to content
This repository has been archived by the owner on Jan 19, 2022. It is now read-only.

Commit

Permalink
Convert to packed add-on
Browse files Browse the repository at this point in the history
Firefox 59 no longer supports unpacked add-ons, which we relied on for launching
the included binaries.  Packed mode is now enabled and we manually unpack the
binaries as needed to the local profile directory.
  • Loading branch information
jryans committed Feb 23, 2018
1 parent 0eed32c commit 364b1fc
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 127 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FILES=adb.js adb-*.js bootstrap.js device.js devtools-import.js devtools-require.js events.js fastboot.js main.js scanner.js unload.js
FILES=adb.js adb-*.js binary-manager.js bootstrap.js device.js devtools-import.js devtools-require.js events.js fastboot.js main.js scanner.js unload.js
ADDON_NAME=adbhelper
ADDON_VERSION=0.11.3pre
XPI_NAME=$(ADDON_NAME)-$(ADDON_VERSION)
Expand Down
117 changes: 47 additions & 70 deletions adb.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
const { Cc, Ci, Cu } = require("chrome");
const events = require("./events");
const client = require("./adb-client");
const { getFileForBinary } = require("./binary-manager");
const { setTimeout } = Cu.import("resource://gre/modules/Timer.jsm", {});
const { Subprocess } = Cu.import("resource://gre/modules/Subprocess.jsm", {});
const { PromiseUtils } = Cu.import("resource://gre/modules/PromiseUtils.jsm", {});
Expand All @@ -21,8 +22,6 @@ const env = Cc["@mozilla.org/process/environment;1"].
const { OS } = require("resource://gre/modules/osfile.jsm");
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});

const {XPCOMABI} = Services.appinfo;

// When loaded as a CommonJS module, get the TextEncoder and TextDecoder
// interfaces from the Services JavaScript Module, since they aren't defined
// in a CommonJS module by default.
Expand Down Expand Up @@ -56,75 +55,54 @@ const ADB = {
ready = newVal;
},

init: function adb_init() {
console.log("init");
let platform = Services.appinfo.OS;

let uri = "resource://adbhelperatmozilla.org/";

let bin;
switch (platform) {
case "Linux":
let platform = XPCOMABI.indexOf("x86_64") == 0 ? "linux64" : "linux";
bin = uri + platform + "/adb";
break;
case "Darwin":
bin = uri + "mac64/adb";
break;
case "WINNT":
bin = uri + "win32/adb.exe";
break;
default:
console.log("Unsupported platform : " + platform);
return;
get adbFilePromise() {
if (this._adbFilePromise) {
return this._adbFilePromise;
}

let url = Services.io.newURI(bin).QueryInterface(Ci.nsIFileURL);
this._adb = url.file;
this._adbFilePromise = getFileForBinary("adb");
return this._adbFilePromise;
},

// We startup by launching adb in server mode, and setting
// the tcp socket preference to |true|
start: function adb_start() {
let deferred = PromiseUtils.defer();

let onSuccessfulStart = (function onSuccessfulStart() {
Services.obs.notifyObservers(null, "adb-ready");
this.ready = true;
deferred.resolve();
}).bind(this);
start() {
return new Promise(async (resolve, reject) => {
let onSuccessfulStart = () => {
Services.obs.notifyObservers(null, "adb-ready");
this.ready = true;
resolve();
};

require("./adb-running-checker").check().then((function(isAdbRunning) {
if (isAdbRunning) {
this.didRunInitially = false;
console.log("Found ADB process running, not restarting");
onSuccessfulStart();
return;
let isAdbRunning = await require("./adb-running-checker").check();
if (isAdbRunning) {
this.didRunInitially = false;
console.log("Found ADB process running, not restarting");
onSuccessfulStart();
return;
}
console.log("Didn't find ADB process running, restarting");

this.didRunInitially = true;
let process = Cc["@mozilla.org/process/util;1"]
.createInstance(Ci.nsIProcess);
let adbFile = await this.adbFilePromise;
process.init(adbFile);
let params = ["start-server"];
let self = this;
process.runAsync(params, params.length, {
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "process-finished":
onSuccessfulStart();
break;
case "process-failed":
self.ready = false;
reject();
break;
}
}
console.log("Didn't find ADB process running, restarting");

this.didRunInitially = true;
let process = Cc["@mozilla.org/process/util;1"]
.createInstance(Ci.nsIProcess);
process.init(this._adb);
let params = ["start-server"];
let self = this;
process.runAsync(params, params.length, {
observe(aSubject, aTopic, aData) {
switch (aTopic) {
case "process-finished":
onSuccessfulStart();
break;
case "process-failed":
self.ready = false;
deferred.reject();
break;
}
}
}, false);
}).bind(this));

return deferred.promise;
}, false);
});
},

/**
Expand All @@ -146,21 +124,22 @@ const ADB = {
* Kill the ADB server. We do this by running ADB again, passing it
* the "kill-server" argument.
*
* @param {Boolean} aSync
* @param {Boolean} sync
* Whether or not to kill the server synchronously. In general,
* this should be false. But on Windows, an add-on may fail to update
* if its copy of ADB is running when Firefox tries to update it.
* So add-ons who observe their own updates and kill the ADB server
* beforehand should do so synchronously on Windows to make sure
* the update doesn't race the killing.
*/
kill: function adb_kill(aSync) {
async kill(sync) {
let process = Cc["@mozilla.org/process/util;1"]
.createInstance(Ci.nsIProcess);
process.init(this._adb);
let adbFile = await this.adbFilePromise;
process.init(adbFile);
let params = ["kill-server"];

if (aSync) {
if (sync) {
process.run(true, params, params.length);
console.log("adb kill-server: " + process.exitValue);
this.ready = false;
Expand Down Expand Up @@ -1037,6 +1016,4 @@ const ADB = {
}
};

ADB.init();

module.exports = ADB;
171 changes: 171 additions & 0 deletions binary-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { Cu } = require("chrome");
const { OS } = require("resource://gre/modules/osfile.jsm");
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
const { TextDecoder } =
Cu.getGlobalForObject(Cu.import("resource://gre/modules/Services.jsm", {}));
const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");

const ADDON_ROOT_PATH = "resource://adbhelperatmozilla.org";
// Use the "local" profile directory, since it's meant for temporary storage.
const UNPACKED_ROOT_PATH = OS.Path.join(OS.Constants.Path.localProfileDir, "adbhelper");
const MANIFEST = "manifest.json";

function getPlatformDir() {
let { OS: platform, XPCOMABI } = Services.appinfo;
switch (platform) {
case "Linux":
return XPCOMABI.indexOf("x86_64") == 0 ? "linux64" : "linux";
case "Darwin":
return "mac64";
case "WINNT":
return "win32";
default:
throw new Error("Unsupported platform : " + platform);
}
}

/**
* Read the manifest from inside the add-on.
* Uses NetUtil since data is packed inside the add-on, not a local file.
*/
async function getManifestFromAddon() {
return new Promise((resolve, reject) => {
NetUtil.asyncFetch({
uri: `${ADDON_ROOT_PATH}/${getPlatformDir()}/${MANIFEST}`,
loadUsingSystemPrincipal: true
}, (input) => {
let data;
try {
let string = NetUtil.readInputStreamToString(input, input.available());
data = JSON.parse(string);
} catch (e) {
reject(new Error("Could not read manifest in add-on"));
return;
}
resolve(data);
});
});
}

/**
* Read the manifest from the unpacked binary directory.
* Uses OS.File since this is a local file.
*/
async function getManifestFromUnpacked() {
let dirPath = OS.Path.join(UNPACKED_ROOT_PATH, getPlatformDir());
let manifestPath = OS.Path.join(dirPath, MANIFEST);
if (!await OS.File.exists(manifestPath)) {
throw new Error("Manifest doesn't exist at unpacked path");
}
let binary = await OS.File.read(manifestPath);
let json = new TextDecoder().decode(binary);
let data;
try {
data = JSON.parse(json);
} catch (e) {
throw new Error("Could not read unpacked manifest");
}
return data;
}

/**
* Unpack file from the add-on.
* Uses NetUtil to read and write, since it's required for reading.
*
* @param {string} file
* The base name of the file, such as "adb".
* @param {object} options
* Object with the properties:
* - exec {boolean}
* Whether to mark the file as executable.
*/
async function unpackFile(file, { exec }) {
// Assumes that destination dir already exists.
let filePath = OS.Path.join(UNPACKED_ROOT_PATH, getPlatformDir(), file);
await new Promise((resolve, reject) => {
NetUtil.asyncFetch({
uri: `${ADDON_ROOT_PATH}/${getPlatformDir()}/${file}`,
loadUsingSystemPrincipal: true
}, (input) => {
try {
// Since we have to use NetUtil to read, probably it's okay to use for
// writing, rather than bouncing to OS.File...?
let outputFile = new FileUtils.File(filePath);
let output = FileUtils.openAtomicFileOutputStream(outputFile);
NetUtil.asyncCopy(input, output, resolve);
} catch (e) {
reject(new Error(`Could not unpack file ${file} in add-on: ${e}`));
}
});
});
// Mark binaries as executable.
if (exec) {
await OS.File.setPermissions(filePath, { unixMode: 0o744 });
}
}

/**
* Check state of binary unpacking, including the location and manifest.
*/
async function isUnpacked() {
let dirPath = OS.Path.join(UNPACKED_ROOT_PATH, getPlatformDir());
let manifestPath = OS.Path.join(dirPath, MANIFEST);
if (!await OS.File.exists(manifestPath)) {
console.log("Needs unpacking, no manifest found");
return false;
}
let addonManifest = await getManifestFromAddon();
let unpackedManifest = await getManifestFromUnpacked();
if (addonManifest.version != unpackedManifest.version) {
console.log(
`Needs unpacking, add-on version ${addonManifest.version} != ` +
`unpacked version ${unpackedManifest.version}`
);
return false;
}
console.log("Already unpacked");
return true;
}

/**
* Unpack binaries for the current OS along with the manifest.
*/
async function unpack() {
let dirPath = OS.Path.join(UNPACKED_ROOT_PATH, getPlatformDir());
await OS.File.makeDir(dirPath, { from: OS.Constants.Path.localProfileDir });
let manifest = await getManifestFromAddon();
for (let file of manifest.files) {
await unpackFile(file, { exec: true });
}
await unpackFile(MANIFEST, { exec: false });
}

/**
* Get a file object for a given named binary that was packed in this add-on.
*
* @param {string} name
* Base name of the binary, such as "adb".
* @return {nsIFile}
* File object for the binary.
*/
async function getFileForBinary(name) {
if (!await isUnpacked()) {
await unpack();
}
let path = OS.Path.join(UNPACKED_ROOT_PATH, getPlatformDir(), name);
let { OS: platform } = Services.appinfo;
if (platform == "WINNT") {
path += ".exe";
}
console.log(`Binary path: ${path}`);
return new FileUtils.File(path);
}

exports.getFileForBinary = getFileForBinary;
14 changes: 9 additions & 5 deletions bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ Cu.import("resource://gre/modules/Services.jsm");
const REASON = [ "unknown", "startup", "shutdown", "enable", "disable",
"install", "uninstall", "upgrade", "downgrade" ];

// Usefull piece of code from :bent
// Useful piece of code from :bent
// http://mxr.mozilla.org/mozilla-central/source/dom/workers/test/extensions/bootstrap/bootstrap.js
function registerAddonResourceHandler(data) {
let file = data.installPath;
let fileuri = file.isDirectory() ?
Services.io.newFileURI(file) :
Services.io.newURI("jar:" + file.path + "!/");
let fileURI = Services.io.newFileURI(file);
if (!file.isDirectory()) {
fileURI = Services.io.newURI("jar:" + fileURI.spec + "!/");
}
let resourceName = encodeURIComponent(data.id.replace("@", "at"));

Services.io.getProtocolHandler("resource").
QueryInterface(Ci.nsIResProtocolHandler).
setSubstitution(resourceName, fileuri);
setSubstitution(resourceName, fileURI);

return "resource://" + resourceName + "/";
}
Expand Down Expand Up @@ -120,6 +121,9 @@ function startup(data, reason) {
loader = Loader(loaderOptions);
let require_ = Require(loader, { id: "./addon" });
mainModule = require_("./main");

// TODO: debugging, remove?
this.require = require_;
}

function shutdown(data, reasonCode) {
Expand Down
Loading

0 comments on commit 364b1fc

Please sign in to comment.