123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
- const DBG_XUL = "chrome://devtools/content/framework/toolbox-process-window.xul";
- const CHROME_DEBUGGER_PROFILE_NAME = "chrome_debugger_profile";
- const { require, DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
- const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
- XPCOMUtils.defineLazyGetter(this, "Telemetry", function () {
- return require("devtools/client/shared/telemetry");
- });
- XPCOMUtils.defineLazyGetter(this, "EventEmitter", function () {
- return require("devtools/shared/event-emitter");
- });
- const promise = require("promise");
- const Services = require("Services");
- this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
- var processes = new Set();
- /**
- * Constructor for creating a process that will hold a chrome toolbox.
- *
- * @param function aOnClose [optional]
- * A function called when the process stops running.
- * @param function aOnRun [optional]
- * A function called when the process starts running.
- * @param object aOptions [optional]
- * An object with properties for configuring BrowserToolboxProcess.
- */
- this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) {
- let emitter = new EventEmitter();
- this.on = emitter.on.bind(emitter);
- this.off = emitter.off.bind(emitter);
- this.once = emitter.once.bind(emitter);
- // Forward any events to the shared emitter.
- this.emit = function (...args) {
- emitter.emit(...args);
- BrowserToolboxProcess.emit(...args);
- };
- // If first argument is an object, use those properties instead of
- // all three arguments
- if (typeof aOnClose === "object") {
- if (aOnClose.onClose) {
- this.once("close", aOnClose.onClose);
- }
- if (aOnClose.onRun) {
- this.once("run", aOnClose.onRun);
- }
- this._options = aOnClose;
- } else {
- if (aOnClose) {
- this.once("close", aOnClose);
- }
- if (aOnRun) {
- this.once("run", aOnRun);
- }
- this._options = aOptions || {};
- }
- this._telemetry = new Telemetry();
- this.close = this.close.bind(this);
- Services.obs.addObserver(this.close, "quit-application", false);
- this._initServer();
- this._initProfile();
- this._create();
- processes.add(this);
- };
- EventEmitter.decorate(BrowserToolboxProcess);
- /**
- * Initializes and starts a chrome toolbox process.
- * @return object
- */
- BrowserToolboxProcess.init = function (aOnClose, aOnRun, aOptions) {
- return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions);
- };
- /**
- * Passes a set of options to the BrowserAddonActors for the given ID.
- *
- * @param aId string
- * The ID of the add-on to pass the options to
- * @param aOptions object
- * The options.
- * @return a promise that will be resolved when complete.
- */
- BrowserToolboxProcess.setAddonOptions = function DSC_setAddonOptions(aId, aOptions) {
- let promises = [];
- for (let process of processes.values()) {
- promises.push(process.debuggerServer.setAddonOptions(aId, aOptions));
- }
- return promise.all(promises);
- };
- BrowserToolboxProcess.prototype = {
- /**
- * Initializes the debugger server.
- */
- _initServer: function () {
- if (this.debuggerServer) {
- dumpn("The chrome toolbox server is already running.");
- return;
- }
- dumpn("Initializing the chrome toolbox server.");
- // Create a separate loader instance, so that we can be sure to receive a
- // separate instance of the DebuggingServer from the rest of the devtools.
- // This allows us to safely use the tools against even the actors and
- // DebuggingServer itself, especially since we can mark this loader as
- // invisible to the debugger (unlike the usual loader settings).
- this.loader = new DevToolsLoader();
- this.loader.invisibleToDebugger = true;
- let { DebuggerServer } = this.loader.require("devtools/server/main");
- this.debuggerServer = DebuggerServer;
- dumpn("Created a separate loader instance for the DebuggerServer.");
- // Forward interesting events.
- this.debuggerServer.on("connectionchange", this.emit);
- this.debuggerServer.init();
- this.debuggerServer.addBrowserActors();
- this.debuggerServer.allowChromeProcess = true;
- dumpn("initialized and added the browser actors for the DebuggerServer.");
- let chromeDebuggingPort =
- Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
- let chromeDebuggingWebSocket =
- Services.prefs.getBoolPref("devtools.debugger.chrome-debugging-websocket");
- let listener = this.debuggerServer.createListener();
- listener.portOrPath = chromeDebuggingPort;
- listener.webSocket = chromeDebuggingWebSocket;
- listener.open();
- dumpn("Finished initializing the chrome toolbox server.");
- dumpn("Started listening on port: " + chromeDebuggingPort);
- },
- /**
- * Initializes a profile for the remote debugger process.
- */
- _initProfile: function () {
- dumpn("Initializing the chrome toolbox user profile.");
- let debuggingProfileDir = Services.dirsvc.get("ProfLD", Ci.nsIFile);
- debuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
- try {
- debuggingProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
- } catch (ex) {
- // Don't re-copy over the prefs again if this profile already exists
- if (ex.result === Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
- this._dbgProfilePath = debuggingProfileDir.path;
- } else {
- dumpn("Error trying to create a profile directory, failing.");
- dumpn("Error: " + (ex.message || ex));
- }
- return;
- }
- this._dbgProfilePath = debuggingProfileDir.path;
- // We would like to copy prefs into this new profile...
- let prefsFile = debuggingProfileDir.clone();
- prefsFile.append("prefs.js");
- // ... but unfortunately, when we run tests, it seems the starting profile
- // clears out the prefs file before re-writing it, and in practice the
- // file is empty when we get here. So just copying doesn't work in that
- // case.
- // We could force a sync pref flush and then copy it... but if we're doing
- // that, we might as well just flush directly to the new profile, which
- // always works:
- Services.prefs.savePrefFile(prefsFile);
- dumpn("Finished creating the chrome toolbox user profile at: " + this._dbgProfilePath);
- },
- /**
- * Creates and initializes the profile & process for the remote debugger.
- */
- _create: function () {
- dumpn("Initializing chrome debugging process.");
- let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
- process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
- let xulURI = DBG_XUL;
- if (this._options.addonID) {
- xulURI += "?addonID=" + this._options.addonID;
- }
- dumpn("Running chrome debugging process.");
- let args = ["-no-remote", "-foreground", "-profile", this._dbgProfilePath, "-chrome", xulURI];
- // During local development, incremental builds can trigger the main process
- // to clear its startup cache with the "flag file" .purgecaches, but this
- // file is removed during app startup time, so we aren't able to know if it
- // was present in order to also clear the child profile's startup cache as
- // well.
- //
- // As an approximation of "isLocalBuild", check for an unofficial build.
- if (!Services.appinfo.isOfficial) {
- args.push("-purgecaches");
- }
- // Disable safe mode for the new process in case this was opened via the
- // keyboard shortcut.
- let nsIEnvironment = Components.classes["@mozilla.org/process/environment;1"].getService(Components.interfaces.nsIEnvironment);
- let originalValue = nsIEnvironment.get("MOZ_DISABLE_SAFE_MODE_KEY");
- nsIEnvironment.set("MOZ_DISABLE_SAFE_MODE_KEY", "1");
- process.runwAsync(args, args.length, { observe: () => this.close() });
- // Now that the process has started, it's safe to reset the env variable.
- nsIEnvironment.set("MOZ_DISABLE_SAFE_MODE_KEY", originalValue);
- this._telemetry.toolOpened("jsbrowserdebugger");
- dumpn("Chrome toolbox is now running...");
- this.emit("run", this);
- },
- /**
- * Closes the remote debugging server and kills the toolbox process.
- */
- close: function () {
- if (this.closed) {
- return;
- }
- dumpn("Cleaning up the chrome debugging process.");
- Services.obs.removeObserver(this.close, "quit-application");
- if (this._dbgProcess.isRunning) {
- this._dbgProcess.kill();
- }
- this._telemetry.toolClosed("jsbrowserdebugger");
- if (this.debuggerServer) {
- this.debuggerServer.off("connectionchange", this.emit);
- this.debuggerServer.destroy();
- this.debuggerServer = null;
- }
- dumpn("Chrome toolbox is now closed...");
- this.closed = true;
- this.emit("close", this);
- processes.delete(this);
- this._dbgProcess = null;
- this._options = null;
- if (this.loader) {
- this.loader.destroy();
- }
- this.loader = null;
- this._telemetry = null;
- }
- };
- /**
- * Helper method for debugging.
- * @param string
- */
- function dumpn(str) {
- if (wantLogging) {
- dump("DBG-FRONTEND: " + str + "\n");
- }
- }
- var wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
- Services.prefs.addObserver("devtools.debugger.log", {
- observe: (...args) => wantLogging = Services.prefs.getBoolPref(args.pop())
- }, false);
- Services.obs.notifyObservers(null, "ToolboxProcessLoaded", null);
|