/* 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 EventEmitter = require("devtools/shared/event-emitter"); const promise = require("promise"); const Subprocess = require("sdk/system/child_process/subprocess"); const Services = require("Services"); loader.lazyGetter(this, "OS", () => { const Runtime = require("sdk/system/runtime"); switch (Runtime.OS) { case "Darwin": return "mac64"; case "Linux": if (Runtime.XPCOMABI.indexOf("x86_64") === 0) { return "linux64"; } else { return "linux32"; } case "WINNT": return "win32"; default: return ""; } }); function SimulatorProcess() {} SimulatorProcess.prototype = { // Check if B2G is running. get isRunning() { return !!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."); } // Ensure Gaia profile exists. let gaia = this.gaiaProfile; if (!gaia || !gaia.exists()) { throw Error("Gaia profile directory 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'], }); } }); let logHandler = (e, data) => this.log(e, data.trim()); this.on("stdout", logHandler); this.on("stderr", logHandler); this.once("exit", () => { this.off("stdout", logHandler); this.off("stderr", logHandler); }); let environment; if (OS.indexOf("linux") > -1) { environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path]; ["DISPLAY", "XAUTHORITY"].forEach(key => { if (key in Environment) { environment.push(key + "=" + Environment[key]); } }); } // 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 = []; // Gaia profile. args.push("-profile", this.gaiaProfile.path); // Debugger server port. let port = parseInt(this.options.port); args.push("-start-debugger-server", "" + port); // Screen size. let width = parseInt(this.options.width); let height = parseInt(this.options.height); if (width && height) { args.push("-screen", width + "x" + height); } // Ignore eventual zombie instances of b2g that are left over. args.push("-no-remote"); // If we are running a simulator based on Mulet, // we have to override the default chrome URL // in order to prevent the Browser UI to appear. if (this.b2gBinary.leafName.includes("firefox")) { args.push("-chrome", "chrome://b2g/content/shell.html"); } return args; }, }; EventEmitter.decorate(SimulatorProcess.prototype); function CustomSimulatorProcess(options) { this.options = options; } var 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; } var 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) { file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file; file.append("b2g"); let binaries = { win32: "b2g-bin.exe", mac64: "B2G.app/Contents/MacOS/b2g-bin", linux32: "b2g-bin", linux64: "b2g-bin", }; binaries[OS].split("/").forEach(node => file.append(node)); } // If the binary doesn't exists, it may be because of a simulator // based on mulet, which has a different binary name. if (!file.exists()) { file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file; file.append("firefox"); let binaries = { win32: "firefox.exe", mac64: "FirefoxNightly.app/Contents/MacOS/firefox-bin", linux32: "firefox-bin", linux64: "firefox-bin", }; binaries[OS].split("/").forEach(node => file.append(node)); } return file; } }); // Compute Gaia profile file handle. Object.defineProperty(ASPp, "gaiaProfile", { get: function () { let file; // Custom profile from simulator configuration. if (this.options.gaiaProfile) { file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(this.options.gaiaProfile); return file; } // Custom profile from addon prefs. try { let pref = "extensions." + this.addon.id + ".gaiaProfile"; file = Services.prefs.getComplexValue(pref, Ci.nsIFile); return file; } catch (e) {} // Default profile from addon. 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; } var 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) { file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file; let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_"); file.append("resources"); file.append("fxos_" + version + "_simulator"); file.append("data"); file.append(OS == "linux32" ? "linux" : OS); let binaries = { win32: "b2g/b2g-bin.exe", mac64: "B2G.app/Contents/MacOS/b2g-bin", linux32: "b2g/b2g-bin", linux64: "b2g/b2g-bin", }; binaries[OS].split("/").forEach(node => file.append(node)); } return file; } }); // Compute B2G CLI arguments. Object.defineProperty(OASPp, "args", { get: function () { let args = []; // Gaia profile. args.push("-profile", this.gaiaProfile.path); // Debugger server port. let port = parseInt(this.options.port); args.push("-dbgport", "" + port); // Ignore eventual zombie instances of b2g that are left over. args.push("-no-remote"); return args; } }); exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;