1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/framework/ToolboxProcess.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,272 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +"use strict"; 1.10 + 1.11 +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; 1.12 + 1.13 +const DBG_XUL = "chrome://browser/content/devtools/framework/toolbox-process-window.xul"; 1.14 +const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger"; 1.15 + 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); 1.18 + 1.19 +Cu.import("resource://gre/modules/devtools/Loader.jsm"); 1.20 +let require = devtools.require; 1.21 +let Telemetry = require("devtools/shared/telemetry"); 1.22 +let EventEmitter = require("devtools/toolkit/event-emitter"); 1.23 +const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); 1.24 + 1.25 +this.EXPORTED_SYMBOLS = ["BrowserToolboxProcess"]; 1.26 + 1.27 +let processes = Set(); 1.28 + 1.29 +/** 1.30 + * Constructor for creating a process that will hold a chrome toolbox. 1.31 + * 1.32 + * @param function aOnClose [optional] 1.33 + * A function called when the process stops running. 1.34 + * @param function aOnRun [optional] 1.35 + * A function called when the process starts running. 1.36 + * @param object aOptions [optional] 1.37 + * An object with properties for configuring BrowserToolboxProcess. 1.38 + */ 1.39 +this.BrowserToolboxProcess = function BrowserToolboxProcess(aOnClose, aOnRun, aOptions) { 1.40 + let emitter = new EventEmitter(); 1.41 + this.on = emitter.on.bind(emitter); 1.42 + this.off = emitter.off.bind(emitter); 1.43 + this.once = emitter.once.bind(emitter); 1.44 + // Forward any events to the shared emitter. 1.45 + this.emit = function(...args) { 1.46 + emitter.emit(...args); 1.47 + BrowserToolboxProcess.emit(...args); 1.48 + } 1.49 + 1.50 + // If first argument is an object, use those properties instead of 1.51 + // all three arguments 1.52 + if (typeof aOnClose === "object") { 1.53 + if (aOnClose.onClose) { 1.54 + this.on("close", aOnClose.onClose); 1.55 + } 1.56 + if (aOnClose.onRun) { 1.57 + this.on("run", aOnClose.onRun); 1.58 + } 1.59 + this._options = aOnClose; 1.60 + } else { 1.61 + if (aOnClose) { 1.62 + this.on("close", aOnClose); 1.63 + } 1.64 + if (aOnRun) { 1.65 + this.on("run", aOnRun); 1.66 + } 1.67 + this._options = aOptions || {}; 1.68 + } 1.69 + 1.70 + this._telemetry = new Telemetry(); 1.71 + 1.72 + this.close = this.close.bind(this); 1.73 + Services.obs.addObserver(this.close, "quit-application", false); 1.74 + this._initServer(); 1.75 + this._initProfile(); 1.76 + this._create(); 1.77 + 1.78 + processes.add(this); 1.79 +}; 1.80 + 1.81 +EventEmitter.decorate(BrowserToolboxProcess); 1.82 + 1.83 +/** 1.84 + * Initializes and starts a chrome toolbox process. 1.85 + * @return object 1.86 + */ 1.87 +BrowserToolboxProcess.init = function(aOnClose, aOnRun, aOptions) { 1.88 + return new BrowserToolboxProcess(aOnClose, aOnRun, aOptions); 1.89 +}; 1.90 + 1.91 +/** 1.92 + * Passes a set of options to the BrowserAddonActors for the given ID. 1.93 + * 1.94 + * @param aId string 1.95 + * The ID of the add-on to pass the options to 1.96 + * @param aOptions object 1.97 + * The options. 1.98 + * @return a promise that will be resolved when complete. 1.99 + */ 1.100 +BrowserToolboxProcess.setAddonOptions = function DSC_setAddonOptions(aId, aOptions) { 1.101 + let promises = []; 1.102 + 1.103 + for (let process of processes.values()) { 1.104 + promises.push(process.debuggerServer.setAddonOptions(aId, aOptions)); 1.105 + } 1.106 + 1.107 + return promise.all(promises); 1.108 +}; 1.109 + 1.110 +BrowserToolboxProcess.prototype = { 1.111 + /** 1.112 + * Initializes the debugger server. 1.113 + */ 1.114 + _initServer: function() { 1.115 + dumpn("Initializing the chrome toolbox server."); 1.116 + 1.117 + if (!this.loader) { 1.118 + // Create a separate loader instance, so that we can be sure to receive a 1.119 + // separate instance of the DebuggingServer from the rest of the devtools. 1.120 + // This allows us to safely use the tools against even the actors and 1.121 + // DebuggingServer itself, especially since we can mark this loader as 1.122 + // invisible to the debugger (unlike the usual loader settings). 1.123 + this.loader = new DevToolsLoader(); 1.124 + this.loader.invisibleToDebugger = true; 1.125 + this.loader.main("devtools/server/main"); 1.126 + this.debuggerServer = this.loader.DebuggerServer; 1.127 + dumpn("Created a separate loader instance for the DebuggerServer."); 1.128 + 1.129 + // Forward interesting events. 1.130 + this.debuggerServer.on("connectionchange", this.emit.bind(this)); 1.131 + } 1.132 + 1.133 + if (!this.debuggerServer.initialized) { 1.134 + this.debuggerServer.init(); 1.135 + this.debuggerServer.addBrowserActors(); 1.136 + dumpn("initialized and added the browser actors for the DebuggerServer."); 1.137 + } 1.138 + 1.139 + this.debuggerServer.openListener(Prefs.chromeDebuggingPort); 1.140 + 1.141 + dumpn("Finished initializing the chrome toolbox server."); 1.142 + dumpn("Started listening on port: " + Prefs.chromeDebuggingPort); 1.143 + }, 1.144 + 1.145 + /** 1.146 + * Initializes a profile for the remote debugger process. 1.147 + */ 1.148 + _initProfile: function() { 1.149 + dumpn("Initializing the chrome toolbox user profile."); 1.150 + 1.151 + let profileService = Cc["@mozilla.org/toolkit/profile-service;1"] 1.152 + .createInstance(Ci.nsIToolkitProfileService); 1.153 + 1.154 + let profileName; 1.155 + try { 1.156 + // Attempt to get the required chrome debugging profile name string. 1.157 + profileName = profileService.selectedProfile.name + CHROME_DEBUGGER_PROFILE_NAME; 1.158 + dumpn("Using chrome toolbox profile name: " + profileName); 1.159 + } catch (e) { 1.160 + // Requested profile string could not be retrieved. 1.161 + profileName = CHROME_DEBUGGER_PROFILE_NAME; 1.162 + let msg = "Querying the current profile failed. " + e.name + ": " + e.message; 1.163 + dumpn(msg); 1.164 + Cu.reportError(msg); 1.165 + } 1.166 + 1.167 + let profileObject; 1.168 + try { 1.169 + // Attempt to get the required chrome debugging profile toolkit object. 1.170 + profileObject = profileService.getProfileByName(profileName); 1.171 + dumpn("Using chrome toolbox profile object: " + profileObject); 1.172 + 1.173 + // The profile exists but the corresponding folder may have been deleted. 1.174 + var enumerator = Services.dirsvc.get("ProfD", Ci.nsIFile).parent.directoryEntries; 1.175 + while (enumerator.hasMoreElements()) { 1.176 + let profileDir = enumerator.getNext().QueryInterface(Ci.nsIFile); 1.177 + if (profileDir.leafName.contains(profileName)) { 1.178 + // Requested profile was found and the folder exists. 1.179 + this._dbgProfile = profileObject; 1.180 + return; 1.181 + } 1.182 + } 1.183 + // Requested profile was found but the folder was deleted. Cleanup needed. 1.184 + profileObject.remove(true); 1.185 + dumpn("The already existing chrome toolbox profile was invalid."); 1.186 + } catch (e) { 1.187 + // Requested profile object was not found. 1.188 + let msg = "Creating a profile failed. " + e.name + ": " + e.message; 1.189 + dumpn(msg); 1.190 + Cu.reportError(msg); 1.191 + } 1.192 + 1.193 + // Create a new chrome debugging profile. 1.194 + this._dbgProfile = profileService.createProfile(null, profileName); 1.195 + profileService.flush(); 1.196 + 1.197 + dumpn("Finished creating the chrome toolbox user profile."); 1.198 + dumpn("Flushed profile service with: " + profileName); 1.199 + }, 1.200 + 1.201 + /** 1.202 + * Creates and initializes the profile & process for the remote debugger. 1.203 + */ 1.204 + _create: function() { 1.205 + dumpn("Initializing chrome debugging process."); 1.206 + let process = this._dbgProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); 1.207 + process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile)); 1.208 + 1.209 + let xulURI = DBG_XUL; 1.210 + 1.211 + if (this._options.addonID) { 1.212 + xulURI += "?addonID=" + this._options.addonID; 1.213 + } 1.214 + 1.215 + dumpn("Running chrome debugging process."); 1.216 + let args = ["-no-remote", "-foreground", "-P", this._dbgProfile.name, "-chrome", xulURI]; 1.217 + 1.218 + process.runwAsync(args, args.length, { observe: () => this.close() }); 1.219 + 1.220 + this._telemetry.toolOpened("jsbrowserdebugger"); 1.221 + 1.222 + dumpn("Chrome toolbox is now running..."); 1.223 + this.emit("run", this); 1.224 + }, 1.225 + 1.226 + /** 1.227 + * Closes the remote debugging server and kills the toolbox process. 1.228 + */ 1.229 + close: function() { 1.230 + if (this.closed) { 1.231 + return; 1.232 + } 1.233 + 1.234 + dumpn("Cleaning up the chrome debugging process."); 1.235 + Services.obs.removeObserver(this.close, "quit-application"); 1.236 + 1.237 + if (this._dbgProcess.isRunning) { 1.238 + this._dbgProcess.kill(); 1.239 + } 1.240 + 1.241 + this._telemetry.toolClosed("jsbrowserdebugger"); 1.242 + if (this.debuggerServer) { 1.243 + this.debuggerServer.destroy(); 1.244 + } 1.245 + 1.246 + dumpn("Chrome toolbox is now closed..."); 1.247 + this.closed = true; 1.248 + this.emit("close", this); 1.249 + processes.delete(this); 1.250 + } 1.251 +}; 1.252 + 1.253 +/** 1.254 + * Shortcuts for accessing various debugger preferences. 1.255 + */ 1.256 +let Prefs = new ViewHelpers.Prefs("devtools.debugger", { 1.257 + chromeDebuggingHost: ["Char", "chrome-debugging-host"], 1.258 + chromeDebuggingPort: ["Int", "chrome-debugging-port"] 1.259 +}); 1.260 + 1.261 +/** 1.262 + * Helper method for debugging. 1.263 + * @param string 1.264 + */ 1.265 +function dumpn(str) { 1.266 + if (wantLogging) { 1.267 + dump("DBG-FRONTEND: " + str + "\n"); 1.268 + } 1.269 +} 1.270 + 1.271 +let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log"); 1.272 + 1.273 +Services.prefs.addObserver("devtools.debugger.log", { 1.274 + observe: (...args) => wantLogging = Services.prefs.getBoolPref(args.pop()) 1.275 +}, false);