browser/devtools/framework/ToolboxProcess.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6 "use strict";
michael@0 7
michael@0 8 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
michael@0 9
michael@0 10 const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul";
michael@0 11 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
michael@0 12
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
michael@0 15
michael@0 16 Cu.import("resource://gre/modules/devtools/Loader.jsm");
michael@0 17 let require = devtools.require;
michael@0 18 let Telemetry = require("devtools/shared/telemetry");
michael@0 19 let EventEmitter = require("devtools/toolkit/event-emitter");
michael@0 20 const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
michael@0 21
michael@0 22 this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"];
michael@0 23
michael@0 24 let processes = Set();
michael@0 25
michael@0 26 /**
michael@0 27 * Constructor for creating a process that will hold a chrome toolbox.
michael@0 28 *
michael@0 29 * @param function aOnClose [optional]
michael@0 30 * A function called when the process stops running.
michael@0 31 * @param function aOnRun [optional]
michael@0 32 * A function called when the process starts running.
michael@0 33 * @param object aOptions [optional]
michael@0 34 * An object with properties for configuring BrowserToolboxProcess.
michael@0 35 */
michael@0 36 this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) {
michael@0 37 let emitter = new EventEmitter();
michael@0 38 this.on = emitter.on.bind(emitter);
michael@0 39 this.off = emitter.off.bind(emitter);
michael@0 40 this.once = emitter.once.bind(emitter);
michael@0 41 // Forward any events to the shared emitter.
michael@0 42 this.emit = function(...args) {
michael@0 43 emitter.emit(...args);
michael@0 44 BrowserToolboxProcess.emit(...args);
michael@0 45 }
michael@0 46
michael@0 47 // If first argument is an object, use those properties instead of
michael@0 48 // all three arguments
michael@0 49 if (typeof aOnClose === "object") {
michael@0 50 if (aOnClose.onClose) {
michael@0 51 this.on("close", aOnClose.onClose);
michael@0 52 }
michael@0 53 if (aOnClose.onRun) {
michael@0 54 this.on("run", aOnClose.onRun);
michael@0 55 }
michael@0 56 this._options = aOnClose;
michael@0 57 } else {
michael@0 58 if (aOnClose) {
michael@0 59 this.on("close", aOnClose);
michael@0 60 }
michael@0 61 if (aOnRun) {
michael@0 62 this.on("run", aOnRun);
michael@0 63 }
michael@0 64 this._options = aOptions || {};
michael@0 65 }
michael@0 66
michael@0 67 this._telemetry = new Telemetry();
michael@0 68
michael@0 69 this.close = this.close.bind(this);
michael@0 70 Services.obs.addObserver(this.close, "quit-application", false);
michael@0 71 this._initServer();
michael@0 72 this._initProfile();
michael@0 73 this._create();
michael@0 74
michael@0 75 processes.add(this);
michael@0 76 };
michael@0 77
michael@0 78 EventEmitter.decorate(BrowserToolboxProcess);
michael@0 79
michael@0 80 /**
michael@0 81 * Initializes and starts a chrome toolbox process.
michael@0 82 * @return object
michael@0 83 */
michael@0 84 BrowserToolboxProcess.init = function(aOnClose, aOnRun, aOptions) {
michael@0 85 return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions);
michael@0 86 };
michael@0 87
michael@0 88 /**
michael@0 89 * Passes a set of options to the BrowserAddonActors for the given ID.
michael@0 90 *
michael@0 91 * @param aId string
michael@0 92 * The ID of the add-on to pass the options to
michael@0 93 * @param aOptions object
michael@0 94 * The options.
michael@0 95 * @return a promise that will be resolved when complete.
michael@0 96 */
michael@0 97 BrowserToolboxProcess.setAddonOptions = function DSC_setAddonOptions(aId, aOptions) {
michael@0 98 let promises = [];
michael@0 99
michael@0 100 for (let process of processes.values()) {
michael@0 101 promises.push(process.debuggerServer.setAddonOptions(aId, aOptions));
michael@0 102 }
michael@0 103
michael@0 104 return promise.all(promises);
michael@0 105 };
michael@0 106
michael@0 107 BrowserToolboxProcess.prototype = {
michael@0 108 /**
michael@0 109 * Initializes the debugger server.
michael@0 110 */
michael@0 111 _initServer: function() {
michael@0 112 dumpn("Initializing the chrome toolbox server.");
michael@0 113
michael@0 114 if (!this.loader) {
michael@0 115 // Create a separate loader instance, so that we can be sure to receive a
michael@0 116 // separate instance of the DebuggingServer from the rest of the devtools.
michael@0 117 // This allows us to safely use the tools against even the actors and
michael@0 118 // DebuggingServer itself, especially since we can mark this loader as
michael@0 119 // invisible to the debugger (unlike the usual loader settings).
michael@0 120 this.loader = new DevToolsLoader();
michael@0 121 this.loader.invisibleToDebugger = true;
michael@0 122 this.loader.main("devtools/server/main");
michael@0 123 this.debuggerServer = this.loader.DebuggerServer;
michael@0 124 dumpn("Created a separate loader instance for the DebuggerServer.");
michael@0 125
michael@0 126 // Forward interesting events.
michael@0 127 this.debuggerServer.on("connectionchange", this.emit.bind(this));
michael@0 128 }
michael@0 129
michael@0 130 if (!this.debuggerServer.initialized) {
michael@0 131 this.debuggerServer.init();
michael@0 132 this.debuggerServer.addBrowserActors();
michael@0 133 dumpn("initialized and added the browser actors for the DebuggerServer.");
michael@0 134 }
michael@0 135
michael@0 136 this.debuggerServer.openListener(Prefs.chromeDebuggingPort);
michael@0 137
michael@0 138 dumpn("Finished initializing the chrome toolbox server.");
michael@0 139 dumpn("Started listening on port: " + Prefs.chromeDebuggingPort);
michael@0 140 },
michael@0 141
michael@0 142 /**
michael@0 143 * Initializes a profile for the remote debugger process.
michael@0 144 */
michael@0 145 _initProfile: function() {
michael@0 146 dumpn("Initializing the chrome toolbox user profile.");
michael@0 147
michael@0 148 let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
michael@0 149 .createInstance(Ci.nsIToolkitProfileService);
michael@0 150
michael@0 151 let profileName;
michael@0 152 try {
michael@0 153 // Attempt to get the required chrome debugging profile name string.
michael@0 154 profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME;
michael@0 155 dumpn("Using chrome toolbox profile name: " + profileName);
michael@0 156 } catch (e) {
michael@0 157 // Requested profile string could not be retrieved.
michael@0 158 profileName = CHROME_DEBUGGER_PROFILE_NAME;
michael@0 159 let msg = "Querying the current profile failed. " + e.name + ": " + e.message;
michael@0 160 dumpn(msg);
michael@0 161 Cu.reportError(msg);
michael@0 162 }
michael@0 163
michael@0 164 let profileObject;
michael@0 165 try {
michael@0 166 // Attempt to get the required chrome debugging profile toolkit object.
michael@0 167 profileObject = profileService.getProfileByName(profileName);
michael@0 168 dumpn("Using chrome toolbox profile object: " + profileObject);
michael@0 169
michael@0 170 // The profile exists but the corresponding folder may have been deleted.
michael@0 171 var enumerator = Services.dirsvc.get("ProfD", Ci.nsIFile).parent.directoryEntries;
michael@0 172 while (enumerator.hasMoreElements()) {
michael@0 173 let profileDir = enumerator.getNext().QueryInterface(Ci.nsIFile);
michael@0 174 if (profileDir.leafName.contains(profileName)) {
michael@0 175 // Requested profile was found and the folder exists.
michael@0 176 this._dbgProfile = profileObject;
michael@0 177 return;
michael@0 178 }
michael@0 179 }
michael@0 180 // Requested profile was found but the folder was deleted. Cleanup needed.
michael@0 181 profileObject.remove(true);
michael@0 182 dumpn("The already existing chrome toolbox profile was invalid.");
michael@0 183 } catch (e) {
michael@0 184 // Requested profile object was not found.
michael@0 185 let msg = "Creating a profile failed. " + e.name + ": " + e.message;
michael@0 186 dumpn(msg);
michael@0 187 Cu.reportError(msg);
michael@0 188 }
michael@0 189
michael@0 190 // Create a new chrome debugging profile.
michael@0 191 this._dbgProfile = profileService.createProfile(null, profileName);
michael@0 192 profileService.flush();
michael@0 193
michael@0 194 dumpn("Finished creating the chrome toolbox user profile.");
michael@0 195 dumpn("Flushed profile service with: " + profileName);
michael@0 196 },
michael@0 197
michael@0 198 /**
michael@0 199 * Creates and initializes the profile & process for the remote debugger.
michael@0 200 */
michael@0 201 _create: function() {
michael@0 202 dumpn("Initializing chrome debugging process.");
michael@0 203 let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
michael@0 204 process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
michael@0 205
michael@0 206 let xulURI = DBG_XUL;
michael@0 207
michael@0 208 if (this._options.addonID) {
michael@0 209 xulURI += "?addonID=" + this._options.addonID;
michael@0 210 }
michael@0 211
michael@0 212 dumpn("Running chrome debugging process.");
michael@0 213 let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", xulURI];
michael@0 214
michael@0 215 process.runwAsync(args, args.length, { observe: () => this.close() });
michael@0 216
michael@0 217 this._telemetry.toolOpened("jsbrowserdebugger");
michael@0 218
michael@0 219 dumpn("Chrome toolbox is now running...");
michael@0 220 this.emit("run", this);
michael@0 221 },
michael@0 222
michael@0 223 /**
michael@0 224 * Closes the remote debugging server and kills the toolbox process.
michael@0 225 */
michael@0 226 close: function() {
michael@0 227 if (this.closed) {
michael@0 228 return;
michael@0 229 }
michael@0 230
michael@0 231 dumpn("Cleaning up the chrome debugging process.");
michael@0 232 Services.obs.removeObserver(this.close, "quit-application");
michael@0 233
michael@0 234 if (this._dbgProcess.isRunning) {
michael@0 235 this._dbgProcess.kill();
michael@0 236 }
michael@0 237
michael@0 238 this._telemetry.toolClosed("jsbrowserdebugger");
michael@0 239 if (this.debuggerServer) {
michael@0 240 this.debuggerServer.destroy();
michael@0 241 }
michael@0 242
michael@0 243 dumpn("Chrome toolbox is now closed...");
michael@0 244 this.closed = true;
michael@0 245 this.emit("close", this);
michael@0 246 processes.delete(this);
michael@0 247 }
michael@0 248 };
michael@0 249
michael@0 250 /**
michael@0 251 * Shortcuts for accessing various debugger preferences.
michael@0 252 */
michael@0 253 let Prefs = new ViewHelpers.Prefs("devtools.debugger", {
michael@0 254 chromeDebuggingHost: ["Char", "chrome-debugging-host"],
michael@0 255 chromeDebuggingPort: ["Int", "chrome-debugging-port"]
michael@0 256 });
michael@0 257
michael@0 258 /**
michael@0 259 * Helper method for debugging.
michael@0 260 * @param string
michael@0 261 */
michael@0 262 function dumpn(str) {
michael@0 263 if (wantLogging) {
michael@0 264 dump("DBG-FRONTEND: " + str + "\n");
michael@0 265 }
michael@0 266 }
michael@0 267
michael@0 268 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
michael@0 269
michael@0 270 Services.prefs.addObserver("devtools.debugger.log", {
michael@0 271 observe: (...args) => wantLogging = Services.prefs.getBoolPref(args.pop())
michael@0 272 }, false);

mercurial