browser/devtools/framework/ToolboxProcess.jsm

changeset 0
6474c204b198
     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);

mercurial