michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. michael@0: */ michael@0: michael@0: 'use strict'; michael@0: michael@0: const { Cc, Ci, Cu, ChromeWorker } = require("chrome"); michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: const { EventTarget } = require("sdk/event/target"); michael@0: const { emit, off } = require("sdk/event/core"); michael@0: const { Class } = require("sdk/core/heritage"); michael@0: const Environment = require("sdk/system/environment").env; michael@0: const Runtime = require("sdk/system/runtime"); michael@0: const Self = require("sdk/self"); michael@0: const URL = require("sdk/url"); michael@0: const Subprocess = require("subprocess"); michael@0: const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); michael@0: const Prefs = require("sdk/simple-prefs").prefs; michael@0: michael@0: const { rootURI: ROOT_URI } = require('@loader/options'); michael@0: const PROFILE_URL = ROOT_URI + "profile/"; michael@0: const BIN_URL = ROOT_URI + "b2g/"; michael@0: michael@0: // Log subprocess error and debug messages to the console. This logs messages michael@0: // for all consumers of the API. We trim the messages because they sometimes michael@0: // have trailing newlines. And note that registerLogHandler actually registers michael@0: // an error handler, despite its name. michael@0: Subprocess.registerLogHandler( michael@0: function(s) console.error("subprocess: " + s.trim()) michael@0: ); michael@0: Subprocess.registerDebugHandler( michael@0: function(s) console.debug("subprocess: " + s.trim()) michael@0: ); michael@0: michael@0: exports.SimulatorProcess = Class({ michael@0: extends: EventTarget, michael@0: initialize: function initialize(options) { michael@0: EventTarget.prototype.initialize.call(this, options); michael@0: michael@0: this.on("stdout", function onStdout(data) console.log(data.trim())); michael@0: this.on("stderr", function onStderr(data) console.error(data.trim())); michael@0: }, michael@0: michael@0: // check if b2g is running michael@0: get isRunning() !!this.process, michael@0: michael@0: /** michael@0: * Start the process and connect the debugger client. michael@0: */ michael@0: run: function() { michael@0: // kill before start if already running michael@0: if (this.process != null) { michael@0: this.process michael@0: .kill() michael@0: .then(this.run.bind(this)); michael@0: return; michael@0: } michael@0: michael@0: // resolve b2g binaries path (raise exception if not found) michael@0: let b2gExecutable = this.b2gExecutable; michael@0: michael@0: this.once("stdout", function () { michael@0: if (Runtime.OS == "Darwin") { michael@0: console.debug("WORKAROUND run osascript to show b2g-desktop window"+ michael@0: " on Runtime.OS=='Darwin'"); michael@0: // Escape double quotes and escape characters for use in AppleScript. michael@0: let path = b2gExecutable.path michael@0: .replace(/\\/g, "\\\\").replace(/\"/g, '\\"'); michael@0: michael@0: Subprocess.call({ michael@0: command: "/usr/bin/osascript", michael@0: arguments: ["-e", 'tell application "' + path + '" to activate'], michael@0: }); michael@0: } michael@0: }); michael@0: michael@0: let environment; michael@0: if (Runtime.OS == "Linux") { michael@0: environment = ["TMPDIR=" + Services.dirsvc.get("TmpD",Ci.nsIFile).path]; michael@0: if ("DISPLAY" in Environment) { michael@0: environment.push("DISPLAY=" + Environment.DISPLAY); michael@0: } michael@0: } michael@0: michael@0: // spawn a b2g instance michael@0: this.process = Subprocess.call({ michael@0: command: b2gExecutable, michael@0: arguments: this.b2gArguments, michael@0: environment: environment, michael@0: michael@0: // emit stdout event michael@0: stdout: (function(data) { michael@0: emit(this, "stdout", data); michael@0: }).bind(this), michael@0: michael@0: // emit stderr event michael@0: stderr: (function(data) { michael@0: emit(this, "stderr", data); michael@0: }).bind(this), michael@0: michael@0: // on b2g instance exit, reset tracked process, remoteDebuggerPort and michael@0: // shuttingDown flag, then finally emit an exit event michael@0: done: (function(result) { michael@0: console.log(this.b2gFilename + " terminated with " + result.exitCode); michael@0: this.process = null; michael@0: emit(this, "exit", result.exitCode); michael@0: }).bind(this) michael@0: }); michael@0: }, michael@0: michael@0: // request a b2g instance kill michael@0: kill: function() { michael@0: let deferred = promise.defer(); michael@0: if (this.process) { michael@0: this.once("exit", (exitCode) => { michael@0: this.shuttingDown = false; michael@0: deferred.resolve(exitCode); michael@0: }); michael@0: if (!this.shuttingDown) { michael@0: this.shuttingDown = true; michael@0: emit(this, "kill", null); michael@0: this.process.kill(); michael@0: } michael@0: return deferred.promise; michael@0: } else { michael@0: return promise.resolve(undefined); michael@0: } michael@0: }, michael@0: michael@0: // compute current b2g filename michael@0: get b2gFilename() { michael@0: return this._executable ? this._executableFilename : "B2G"; michael@0: }, michael@0: michael@0: // compute current b2g file handle michael@0: get b2gExecutable() { michael@0: if (this._executable) { michael@0: return this._executable; michael@0: } michael@0: michael@0: if (Prefs.customRuntime) { michael@0: this._executable = Prefs.customRuntime; michael@0: this._executableFilename = "Custom runtime"; michael@0: return this._executable; michael@0: } michael@0: michael@0: let bin = URL.toFilename(BIN_URL); michael@0: let executables = { michael@0: WINNT: "b2g-bin.exe", michael@0: Darwin: "B2G.app/Contents/MacOS/b2g-bin", michael@0: Linux: "b2g-bin", michael@0: }; michael@0: michael@0: let path = bin; michael@0: path += Runtime.OS == "WINNT" ? "\\" : "/"; michael@0: path += executables[Runtime.OS]; michael@0: console.log("simulator path: " + path); michael@0: michael@0: let executable = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); michael@0: executable.initWithPath(path); michael@0: michael@0: if (!executable.exists()) { michael@0: // B2G binaries not found michael@0: throw Error("b2g-desktop Executable not found."); michael@0: } michael@0: michael@0: this._executable = executable; michael@0: this._executableFilename = "b2g-bin"; michael@0: michael@0: return executable; michael@0: }, michael@0: michael@0: // compute b2g CLI arguments michael@0: get b2gArguments() { michael@0: let args = []; michael@0: michael@0: let profile = Prefs.gaiaProfile || URL.toFilename(PROFILE_URL); michael@0: args.push("-profile", profile); michael@0: console.log("profile", profile); michael@0: michael@0: // NOTE: push dbgport option on the b2g-desktop commandline michael@0: args.push("-start-debugger-server", "" + this.remoteDebuggerPort); michael@0: michael@0: // Ignore eventual zombie instances of b2g that are left over michael@0: args.push("-no-remote"); michael@0: michael@0: return args; michael@0: }, michael@0: }); michael@0: