Bug 910184 - Use a separate server for Browser Debugger. r=vporof
authorJ. Ryan Stinnett <jryans@gmail.com>
Wed, 28 Aug 2013 16:19:34 +0200
changeset 144870 f0ce14507a7808648905e04a924a7bd36073cfcf
parent 144869 b968cfd447a0a2a0dc17e26a0ae5e59880e0ec73
child 144871 7f72c69e437b4c7a40b6d14559982b7c8652a9a2
push id2438
push userprouget@mozilla.com
push dateThu, 29 Aug 2013 10:13:35 +0000
treeherderfx-team@7f72c69e437b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs910184
milestone26.0a1
Bug 910184 - Use a separate server for Browser Debugger. r=vporof
browser/devtools/debugger/DebuggerProcess.jsm
toolkit/devtools/Loader.jsm
toolkit/devtools/server/dbg-server.jsm
toolkit/devtools/server/main.js
toolkit/devtools/server/transport.js
--- a/browser/devtools/debugger/DebuggerProcess.jsm
+++ b/browser/devtools/debugger/DebuggerProcess.jsm
@@ -6,20 +6,20 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 const DBG_XUL = "chrome://browser/content/devtools/debugger.xul";
 const CHROME_DEBUGGER_PROFILE_NAME = "-chrome-debugger";
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
-let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+Cu.import("resource://gre/modules/devtools/Loader.jsm");
+let require = devtools.require;
 let Telemetry = require("devtools/shared/telemetry");
 
 this.EXPORTED_SYMBOLS = ["BrowserDebuggerProcess"];
 
 /**
  * Constructor for creating a process that will hold a chrome debugger.
  *
  * @param function aOnClose [optional]
@@ -45,21 +45,31 @@ BrowserDebuggerProcess.init = function(a
   return new BrowserDebuggerProcess(aOnClose, aOnRun);
 };
 
 BrowserDebuggerProcess.prototype = {
   /**
    * Initializes the debugger server.
    */
   _initServer: function() {
-    if (!DebuggerServer.initialized) {
-      DebuggerServer.init();
-      DebuggerServer.addBrowserActors();
+    if (!this.loader) {
+      // 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.
+      this.loader = new DevToolsLoader();
+      this.loader.main("devtools/server/main");
+      this.debuggerServer = this.loader.DebuggerServer;
     }
-    DebuggerServer.openListener(Prefs.chromeDebuggingPort);
+
+    if (!this.debuggerServer.initialized) {
+      this.debuggerServer.init();
+      this.debuggerServer.addBrowserActors();
+    }
+    this.debuggerServer.openListener(Prefs.chromeDebuggingPort);
   },
 
   /**
    * Initializes a profile for the remote debugger process.
    */
   _initProfile: function() {
     let profileService = Cc["@mozilla.org/toolkit/profile-service;1"]
       .createInstance(Ci.nsIToolkitProfileService);
@@ -131,16 +141,18 @@ BrowserDebuggerProcess.prototype = {
   close: function() {
     if (this._dbgProcess.isRunning) {
       dumpn("Killing chrome debugging process...");
       this._dbgProcess.kill();
     }
 
     this._telemetry.toolClosed("jsbrowserdebugger");
 
+    this.debuggerServer.destroy();
+
     dumpn("Chrome debugger is now closed...");
     if (typeof this._closeCallback == "function") {
       this._closeCallback.call({}, this);
     }
   }
 };
 
 /**
--- a/toolkit/devtools/Loader.jsm
+++ b/toolkit/devtools/Loader.jsm
@@ -16,36 +16,36 @@ Cu.import("resource://gre/modules/Servic
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "console", "resource://gre/modules/devtools/Console.jsm");
 
 let loader = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}).Loader;
 let promise = Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js", {}).Promise;
 
-this.EXPORTED_SYMBOLS = ["devtools"];
+this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools"];
 
 /**
  * Providers are different strategies for loading the devtools.
  */
 
 let loaderGlobals = {
   btoa: btoa,
   console: console,
   _Iterator: Iterator,
   loader: {
     lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils),
     lazyImporter: XPCOMUtils.defineLazyModuleGetter.bind(XPCOMUtils),
     lazyServiceGetter: XPCOMUtils.defineLazyServiceGetter.bind(XPCOMUtils)
   }
-}
+};
 
 // Used when the tools should be loaded from the Firefox package itself (the default)
 var BuiltinProvider = {
-  load: function(done) {
+  load: function() {
     this.loader = new loader.Loader({
       modules: {
         "toolkit/loader": loader
       },
       paths: {
         "": "resource://gre/modules/commonjs/",
         "main": "resource:///modules/devtools/main.js",
         "devtools": "resource:///modules/devtools",
@@ -73,17 +73,17 @@ var BuiltinProvider = {
 // to different paths, it needs to write chrome.manifest files to override chrome urls
 // from the builtin tools.
 var SrcdirProvider = {
   fileURI: function(path) {
     let file = new FileUtils.File(path);
     return Services.io.newFileURI(file).spec;
   },
 
-  load: function(done) {
+  load: function() {
     let srcdir = Services.prefs.getComplexValue("devtools.loader.srcdir",
                                                 Ci.nsISupportsString);
     srcdir = OS.Path.normalize(srcdir.data.trim());
     let devtoolsDir = OS.Path.join(srcdir, "browser", "devtools");
     let devtoolsURI = this.fileURI(devtoolsDir);
     let toolkitURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools"));
     let serverURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "server"));
     let webconsoleURI = this.fileURI(OS.Path.join(srcdir, "toolkit", "devtools", "webconsole"));
@@ -177,19 +177,25 @@ var SrcdirProvider = {
       Components.manager.addBootstrappedManifestLocation(new FileUtils.File(dir));
     });
   }
 };
 
 /**
  * The main devtools API.
  * In addition to a few loader-related details, this object will also include all
- * exports from the main module.
+ * exports from the main module.  The standard instance of this loader is
+ * exported as |devtools| below, but if a fresh copy of the loader is needed,
+ * then a new one can also be created.
  */
-this.devtools = {
+this.DevToolsLoader = function DevToolsLoader() {
+  this._chooseProvider();
+};
+
+DevToolsLoader.prototype = {
   _provider: null,
 
   /**
    * Add a URI to the loader.
    * @param string id
    *    The module id that can be used within the loader to refer to this module.
    * @param string uri
    *    The URI to load as a module.
@@ -262,10 +268,10 @@ this.devtools = {
     events.emit("devtools-unloaded", {});
 
     this._provider.unload("reload");
     delete this._provider;
     this._chooseProvider();
   },
 };
 
-// Now load the tools.
-devtools._chooseProvider();
+// Export the standard instance of DevToolsLoader used by the tools.
+this.devtools = new DevToolsLoader();
--- a/toolkit/devtools/server/dbg-server.jsm
+++ b/toolkit/devtools/server/dbg-server.jsm
@@ -1,44 +1,26 @@
 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
+
 /**
  * Loads the remote debugging protocol code into a sandbox, in order to
  * shield it from the debuggee. This way, when debugging chrome globals,
  * debugger and debuggee will be in separate compartments.
  */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 
+const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+
 this.EXPORTED_SYMBOLS = ["DebuggerServer", "ActorPool"];
 
-var loadSubScript =
-  "function loadSubScript(aURL)\n" +
-  "{\n" +
-  "const Ci = Components.interfaces;\n" +
-  "const Cc = Components.classes;\n" +
-  "  try {\n" +
-  "    let loader = Cc[\"@mozilla.org/moz/jssubscript-loader;1\"]\n" +
-  "      .getService(Ci.mozIJSSubScriptLoader);\n" +
-  "    loader.loadSubScript(aURL, this);\n" +
-  "  } catch(e) {\n" +
-  "    dump(\"Error loading: \" + aURL + \": \" + e + \" - \" + e.stack + \"\\n\");\n" +
-  "    throw e;\n" +
-  "  }\n" +
-  "}";
+let server = devtools.require("devtools/server/main");
 
-// Load the debugging server in a sandbox with its own compartment.
-var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
-                      .createInstance(Ci.nsIPrincipal);
-
-var gGlobal = Cu.Sandbox(systemPrincipal);
-Cu.evalInSandbox(loadSubScript, gGlobal, "1.8");
-gGlobal.loadSubScript("resource://gre/modules/devtools/server/main.js");
-
-this.DebuggerServer = gGlobal.DebuggerServer;
-this.ActorPool = gGlobal.ActorPool;
+this.DebuggerServer = server.DebuggerServer;
+this.ActorPool = server.ActorPool;
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -5,32 +5,45 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 /**
  * Toolkit glue for the remote debugging protocol, loaded into the
  * debugging global.
  */
 
-const Ci = Components.interfaces;
-const Cc = Components.classes;
-const CC = Components.Constructor;
-const Cu = Components.utils;
-const Cr = Components.results;
+const { Ci, Cc, CC, Cu, Cr, components: Components } = require("chrome");
 const DBG_STRINGS_URI = "chrome://global/locale/devtools/debugger.properties";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 const promptConnections = Services.prefs.getBoolPref("devtools.debugger.prompt-connection");
 
 Cu.import("resource://gre/modules/jsdebugger.jsm");
 addDebuggerToGlobal(this);
 
+function loadSubScript(aURL)
+{
+  try {
+    let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+      .getService(Ci.mozIJSSubScriptLoader);
+    loader.loadSubScript(aURL, this);
+  } catch(e) {
+    let errorStr = "Error loading: " + aURL + ": " + e + " - " + e.stack + "\n";
+    dump(errorStr);
+    Cu.reportError(errorStr);
+    throw e;
+  }
+}
+
+let loaderRequire = require;
+this.require = null;
 loadSubScript.call(this, "resource://gre/modules/commonjs/sdk/core/promise.js");
+this.require = loaderRequire;
 
 Cu.import("resource://gre/modules/devtools/SourceMap.jsm");
 
 loadSubScript.call(this, "resource://gre/modules/devtools/DevToolsUtils.js");
 
 function dumpn(str) {
   if (wantLogging) {
     dump("DBG-SERVER: " + str + "\n");
@@ -254,19 +267,17 @@ var DebuggerServer = {
    *    'register' and 'unregister' functions.
    */
   registerModule: function(id) {
     if (id in gRegisteredModules) {
       throw new Error("Tried to register a module twice: " + id + "\n");
     }
 
     let moduleAPI = ModuleAPI();
-
-    let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
-    let mod = devtools.require(id);
+    let mod = require(id);
     mod.register(moduleAPI);
     gRegisteredModules[id] = { module: mod, api: moduleAPI };
   },
 
   /**
    * Returns true if a module id has been registered.
    */
   isModuleRegistered: function(id) {
@@ -602,31 +613,34 @@ var DebuggerServer = {
       let handler = DebuggerServer.globalActorFactories[name];
       if (handler.name == aFunction.name) {
         delete DebuggerServer.globalActorFactories[name];
       }
     }
   }
 };
 
+exports.DebuggerServer = DebuggerServer;
 
 /**
  * Construct an ActorPool.
  *
  * ActorPools are actorID -> actor mapping and storage.  These are
  * used to accumulate and quickly dispose of groups of actors that
  * share a lifetime.
  */
 function ActorPool(aConnection)
 {
   this.conn = aConnection;
   this._cleanups = {};
   this._actors = {};
 }
 
+exports.ActorPool = ActorPool;
+
 ActorPool.prototype = {
   /**
    * Add an actor to the actor pool.  If the actor doesn't have an ID,
    * allocate one from the connection.
    *
    * @param aActor object
    *        The actor implementation.  If the object has a
    *        'disconnect' property, it will be called when the actor
--- a/toolkit/devtools/server/transport.js
+++ b/toolkit/devtools/server/transport.js
@@ -187,17 +187,21 @@ DebuggerTransport.prototype = {
       }
       dump(msg + "\n");
       return true;
     }
 
     dumpn("Got: " + packet);
     let self = this;
     Services.tm.currentThread.dispatch(makeInfallible(function() {
-      self.hooks.onPacket(parsed);
+      // Ensure the hooks are still around by the time this runs (they will go
+      // away when the transport is closed).
+      if (self.hooks) {
+        self.hooks.onPacket(parsed);
+      }
     }, "DebuggerTransport instance's this.hooks.onPacket"), 0);
 
     return true;
   }
 }
 
 
 /**