Bug 1090949 - Give WebIDE full control over simulator addons. r=ochameau

This commit is contained in:
Jan Keromnes 2015-02-12 08:55:00 -05:00
parent 1d632a96b3
commit 0ed0ebd3ae
5 changed files with 392 additions and 21 deletions

View File

@ -5,9 +5,9 @@
const {Cu, Ci} = require("chrome");
const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
const {Connection} = require("devtools/client/connection-manager");
const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
const {Simulators} = require("devtools/webide/simulators");
const discovery = require("devtools/toolkit/discovery/discovery");
const EventEmitter = require("devtools/toolkit/event-emitter");
const promise = require("promise");
@ -193,14 +193,12 @@ let SimulatorScanner = {
enable() {
this._updateRuntimes = this._updateRuntimes.bind(this);
Simulator.on("register", this._updateRuntimes);
Simulator.on("unregister", this._updateRuntimes);
Simulators.on("updated", this._updateRuntimes);
this._updateRuntimes();
},
disable() {
Simulator.off("register", this._updateRuntimes);
Simulator.off("unregister", this._updateRuntimes);
Simulators.off("updated", this._updateRuntimes);
},
_emitUpdated() {
@ -208,11 +206,13 @@ let SimulatorScanner = {
},
_updateRuntimes() {
this._runtimes = [];
for (let name of Simulator.availableNames()) {
this._runtimes.push(new SimulatorRuntime(name));
}
this._emitUpdated();
Simulators.getAll().then(simulators => {
this._runtimes = [];
for (let simulator of simulators) {
this._runtimes.push(new SimulatorRuntime(simulator));
}
this._emitUpdated();
});
},
scan() {
@ -542,28 +542,26 @@ WiFiRuntime.prototype = {
// For testing use only
exports._WiFiRuntime = WiFiRuntime;
function SimulatorRuntime(name) {
this.name = name;
function SimulatorRuntime(simulator) {
this.simulator = simulator;
}
SimulatorRuntime.prototype = {
type: RuntimeTypes.SIMULATOR,
connect: function(connection) {
let port = ConnectionManager.getFreeTCPPort();
let simulator = Simulator.getByName(this.name);
if (!simulator || !simulator.launch) {
return promise.reject(new Error("Can't find simulator: " + this.name));
}
return simulator.launch({port: port}).then(() => {
return this.simulator.launch().then(port => {
connection.host = "localhost";
connection.port = port;
connection.keepConnecting = true;
connection.once(Connection.Events.DISCONNECTED, simulator.close);
connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
connection.connect();
});
},
get id() {
return this.name;
return this.simulator.id;
},
get name() {
return this.simulator.name;
},
};

View File

@ -0,0 +1,273 @@
/* 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 { Cc, Ci, Cu } = require("chrome");
const Environment = require("sdk/system/environment").env;
const Subprocess = require("sdk/system/child_process/subprocess");
const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
let OS = "";
if (platform.indexOf("Win") != -1) {
OS = "win32";
} else if (platform.indexOf("Mac") != -1) {
OS = "mac64";
} else if (platform.indexOf("Linux") != -1) {
if (platform.indexOf("x86_64") != -1) {
OS = "linux64";
} else {
OS = "linux32";
}
}
function SimulatorProcess() {}
SimulatorProcess.prototype = {
// Check if B2G is running.
get isRunning() !!this.process,
// Start the process and connect the debugger client.
run() {
// Resolve B2G binary.
let b2g = this.b2gBinary;
if (!b2g || !b2g.exists()) {
throw Error("B2G executable not found.");
}
this.once("stdout", function () {
if (OS == "mac64") {
console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
// Escape double quotes and escape characters for use in AppleScript.
let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
Subprocess.call({
command: "/usr/bin/osascript",
arguments: ["-e", 'tell application "' + path + '" to activate'],
});
}
});
this.on("stdout", (e, data) => this.log(e, data.trim()));
this.on("stderr", (e, data) => this.log(e, data.trim()));
let environment;
if (OS.indexOf("linux") > -1) {
environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
if ("DISPLAY" in Environment) {
environment.push("DISPLAY=" + Environment.DISPLAY);
}
}
// Spawn a B2G instance.
this.process = Subprocess.call({
command: b2g,
arguments: this.args,
environment: environment,
stdout: data => this.emit("stdout", data),
stderr: data => this.emit("stderr", data),
// On B2G instance exit, reset tracked process, remote debugger port and
// shuttingDown flag, then finally emit an exit event.
done: result => {
console.log("B2G terminated with " + result.exitCode);
this.process = null;
this.emit("exit", result.exitCode);
}
});
},
// Request a B2G instance kill.
kill() {
let deferred = promise.defer();
if (this.process) {
this.once("exit", (e, exitCode) => {
this.shuttingDown = false;
deferred.resolve(exitCode);
});
if (!this.shuttingDown) {
this.shuttingDown = true;
this.emit("kill", null);
this.process.kill();
}
return deferred.promise;
} else {
return promise.resolve(undefined);
}
},
// Maybe log output messages.
log(level, message) {
if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
return;
}
if (level === "stderr" || level === "error") {
console.error(message);
return;
}
console.log(message);
},
// Compute B2G CLI arguments.
get args() {
let args = [];
let gaia = this.gaiaProfile;
if (!gaia || !gaia.exists()) {
throw Error("Gaia profile directory not found.");
}
args.push("-profile", gaia.path);
args.push("-start-debugger-server", "" + this.options.port);
// Ignore eventual zombie instances of b2g that are left over.
args.push("-no-remote");
return args;
},
};
EventEmitter.decorate(SimulatorProcess.prototype);
function CustomSimulatorProcess(options) {
this.options = options;
}
let CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
// Compute B2G binary file handle.
Object.defineProperty(CSPp, "b2gBinary", {
get: function() {
let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
file.initWithPath(this.options.b2gBinary);
return file;
}
});
// Compute Gaia profile file handle.
Object.defineProperty(CSPp, "gaiaProfile", {
get: function() {
let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
file.initWithPath(this.options.gaiaProfile);
return file;
}
});
exports.CustomSimulatorProcess = CustomSimulatorProcess;
function AddonSimulatorProcess(addon, options) {
this.addon = addon;
this.options = options;
}
let ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
// Compute B2G binary file handle.
Object.defineProperty(ASPp, "b2gBinary", {
get: function() {
let file;
try {
let pref = "extensions." + this.addon.id + ".customRuntime";
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!file) {
let binaries = {
win32: "b2g-bin.exe",
mac64: "B2G.app/Contents/MacOS/b2g-bin",
linux32: "b2g-bin",
linux64: "b2g-bin",
};
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
file.append("b2g");
file.append(binaries[OS]);
}
return file;
}
});
// Compute Gaia profile file handle.
Object.defineProperty(ASPp, "gaiaProfile", {
get: function() {
let file;
try {
let pref = "extensions." + this.addon.id + ".gaiaProfile";
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!file) {
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
file.append("profile");
}
return file;
}
});
exports.AddonSimulatorProcess = AddonSimulatorProcess;
function OldAddonSimulatorProcess(addon, options) {
this.addon = addon;
this.options = options;
}
let OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
// Compute B2G binary file handle.
Object.defineProperty(OASPp, "b2gBinary", {
get: function() {
let file;
try {
let pref = "extensions." + this.addon.id + ".customRuntime";
file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
} catch(e) {}
if (!file) {
let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
file.append("resources");
file.append("fxos_" + version + "_simulator");
file.append("data");
file.append(OS == "linux32" ? "linux" : OS);
if (OS == "mac64") {
file.append("B2G.app");
file.append("Contents");
file.append("MacOS");
} else {
file.append("b2g");
}
file.append("b2g-bin" + (OS == "win32" ? ".exe" : ""));
}
return file;
}
});
// Compute B2G CLI arguments.
Object.defineProperty(OASPp, "args", {
get: function() {
let args = [];
let gaia = this.gaiaProfile;
if (!gaia || !gaia.exists()) {
throw Error("Gaia profile directory not found.");
}
args.push("-profile", gaia.path);
args.push("-dbgport", "" + this.options.port);
// Ignore eventual zombie instances of b2g that are left over.
args.push("-no-remote");
return args;
}
});
exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;

View File

@ -0,0 +1,96 @@
/* 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/. */
const { Cu } = require("chrome");
const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const { ConnectionManager } = require("devtools/client/connection-manager");
const { AddonSimulatorProcess, OldAddonSimulatorProcess } = require("devtools/webide/simulator-process");
const promise = require("promise");
const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
let Simulators = {
// TODO (Bug 1090949) Don't generate this list from installed simulator
// addons, but instead implement a persistent list of user-configured
// simulators.
getAll() {
let deferred = promise.defer();
AddonManager.getAllAddons(addons => {
let simulators = [];
for (let addon of addons) {
if (SimulatorRegExp.exec(addon.id)) {
simulators.push(new Simulator(addon));
}
}
// Sort simulators alphabetically by name.
simulators.sort((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
});
deferred.resolve(simulators);
});
return deferred.promise;
},
}
EventEmitter.decorate(Simulators);
exports.Simulators = Simulators;
function update() {
Simulators.emit("updated");
}
AddonManager.addAddonListener({
onEnabled: update,
onDisabled: update,
onInstalled: update,
onUninstalled: update
});
function Simulator(addon) {
this.addon = addon;
}
Simulator.prototype = {
launch() {
// Close already opened simulation.
if (this.process) {
return this.kill().then(this.launch.bind(this));
}
let options = {
port: ConnectionManager.getFreeTCPPort()
};
if (this.version <= "1.3") {
// Support older simulator addons.
this.process = new OldAddonSimulatorProcess(this.addon, options);
} else {
this.process = new AddonSimulatorProcess(this.addon, options);
}
this.process.run();
return promise.resolve(options.port);
},
kill() {
let process = this.process;
if (!process) {
return promise.resolve();
}
this.process = null;
return process.kill();
},
get id() {
return this.addon.id;
},
get name() {
return this.addon.name.replace(" Simulator", "");
},
get version() {
return this.name.match(/\d+\.\d+/)[0];
},
};

View File

@ -20,6 +20,8 @@ EXTRA_JS_MODULES.devtools.webide += [
'modules/config-view.js',
'modules/remote-resources.js',
'modules/runtimes.js',
'modules/simulator-process.js',
'modules/simulators.js',
'modules/tab-store.js',
'modules/utils.js'
]

View File

@ -13,6 +13,7 @@ pref("devtools.webide.enableLocalRuntime", false);
pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\.org$");
pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
@ -20,6 +21,7 @@ pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
pref("devtools.webide.lastConnectedRuntime", "");
pref("devtools.webide.lastSelectedProject", "");
pref("devtools.webide.logSimulatorOutput", false);
pref("devtools.webide.widget.autoinstall", true);
#ifdef MOZ_DEV_EDITION
pref("devtools.webide.widget.enabled", true);