Bug 1107888 - e10s support for dynamic actor registration. r=ochameau
authorJan Odvarko <odvarko@gmail.com>
Fri, 05 Dec 2014 15:40:10 -0800
changeset 248216 11b650affda02eaed069e4615571154a210961aa
parent 248215 ff084cf799446340125a44ff0008d34d6b0e0568
child 248217 b8bf3fc7743761a73d90bd1462ee903765f75572
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1107888
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1107888 - e10s support for dynamic actor registration. r=ochameau
toolkit/devtools/server/actors/actor-registry.js
toolkit/devtools/server/actors/utils/actor-registry-utils.js
toolkit/devtools/server/child.js
toolkit/devtools/server/main.js
toolkit/devtools/server/moz.build
toolkit/devtools/server/tests/unit/test_actor-registry-actor.js
--- a/toolkit/devtools/server/actors/actor-registry.js
+++ b/toolkit/devtools/server/actors/actor-registry.js
@@ -6,40 +6,36 @@
 
 const protocol = require("devtools/server/protocol");
 const { method, custom, Arg, Option, RetVal } = protocol;
 
 const { Cu, CC, components } = require("chrome");
 const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 const Services = require("Services");
 const { DebuggerServer } = require("devtools/server/main");
-const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+const ActorRegistryUtils = require("devtools/server/actors/utils/actor-registry-utils");
+const { registerActor, unregisterActor } = ActorRegistryUtils;
+
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 
 /**
  * The ActorActor gives you a handle to an actor you've dynamically
  * registered and allows you to unregister it.
  */
 const ActorActor = protocol.ActorClass({
   typeName: "actorActor",
 
   initialize: function (conn, options) {
     protocol.Actor.prototype.initialize.call(this, conn);
 
     this.options = options;
   },
 
   unregister: method(function () {
-    if (this.options.tab) {
-      DebuggerServer.removeTabActor(this.options);
-    }
-
-    if (this.options.global) {
-      DebuggerServer.removeGlobalActor(this.options);
-    }
+    unregisterActor(this.options);
   }, {
     request: {},
     response: {}
   })
 });
 
 const ActorActorFront = protocol.FrontClass(ActorActor, {
   initialize: function (client, form) {
@@ -56,38 +52,19 @@ exports.ActorActorFront = ActorActorFron
 const ActorRegistryActor = protocol.ActorClass({
   typeName: "actorRegistry",
 
   initialize: function (conn) {
     protocol.Actor.prototype.initialize.call(this, conn);
   },
 
   registerActor: method(function (sourceText, fileName, options) {
-    const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
-    const sandbox = Cu.Sandbox(principal);
-    const exports = sandbox.exports = {};
-    sandbox.require = require;
-
-    Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1);
-
-    let { prefix, constructor, type } = options;
+    registerActor(sourceText, fileName, options);
 
-    if (type.global) {
-      DebuggerServer.addGlobalActor({
-        constructorName: constructor,
-        constructorFun: sandbox[constructor]
-      }, prefix);
-    }
-
-    if (type.tab) {
-      DebuggerServer.addTabActor({
-        constructorName: constructor,
-        constructorFun: sandbox[constructor]
-      }, prefix);
-    }
+    let { constructor, type } = options;
 
     return ActorActor(this.conn, {
       name: constructor,
       tab: type.tab,
       global: type.global
     });
   }, {
     request: {
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/utils/actor-registry-utils.js
@@ -0,0 +1,74 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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";
+
+let { Cu, CC, Ci, Cc } = require("chrome");
+
+const { DebuggerServer } = require("devtools/server/main");
+
+/**
+ * Support for actor registration. Main used by ActorRegistryActor
+ * for dynamic registration of new actors.
+ *
+ * @param sourceText {String} Source of the actor implementation
+ * @param fileName {String} URL of the actor module (for proper stack traces)
+ * @param options {Object} Configuration object
+ */
+exports.registerActor = function(sourceText, fileName, options) {
+  const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
+  const sandbox = Cu.Sandbox(principal);
+  const exports = sandbox.exports = {};
+  sandbox.require = require;
+
+  Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1);
+
+  let { prefix, constructor, type } = options;
+
+  if (type.global && !DebuggerServer.globalActorFactories.hasOwnProperty(prefix)) {
+    DebuggerServer.addGlobalActor({
+      constructorName: constructor,
+      constructorFun: sandbox[constructor]
+    }, prefix);
+  }
+
+  if (type.tab && !DebuggerServer.tabActorFactories.hasOwnProperty(prefix)) {
+    DebuggerServer.addTabActor({
+      constructorName: constructor,
+      constructorFun: sandbox[constructor]
+    }, prefix);
+  }
+
+  // Also register in all child processes in case the current scope
+  // is chrome parent process.
+  if (!DebuggerServer.isInChildProcess) {
+    DebuggerServer.setupInChild({
+      module: "devtools/server/actors/utils/actor-registry-utils",
+      setupChild: "registerActor",
+      args: [sourceText, fileName, options]
+    });
+  }
+}
+
+exports.unregisterActor = function(options) {
+  if (options.tab) {
+    DebuggerServer.removeTabActor(options);
+  }
+
+  if (options.global) {
+    DebuggerServer.removeGlobalActor(options);
+  }
+
+  // Also unregister it from all child processes in case the current
+  // scope is chrome parent process.
+  if (!DebuggerServer.isInChildProcess) {
+    DebuggerServer.setupInChild({
+      module: "devtools/server/actors/utils/actor-registry-utils",
+      setupChild: "unregisterActor",
+      args: [options]
+    });
+  }
+}
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -9,17 +9,19 @@ try {
 let chromeGlobal = this;
 
 // Encapsulate in its own scope to allows loading this frame script
 // more than once.
 (function () {
   let Cu = Components.utils;
   let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
   const DevToolsUtils = devtools.require("devtools/toolkit/DevToolsUtils.js");
-  const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+  const { dumpn } = DevToolsUtils;
+  const { DebuggerServer, ActorPool } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+
   if (!DebuggerServer.childID) {
     DebuggerServer.childID = 1;
   }
 
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
 
     // message manager helpers provided for actor module parent/child message exchange
@@ -52,16 +54,46 @@ let chromeGlobal = this;
     actorPool.addActor(actor);
     conn.addActorPool(actorPool);
 
     sendAsyncMessage("debug:actor", {actor: actor.form(), childID: id});
   });
 
   addMessageListener("debug:connect", onConnect);
 
+  // Allows executing module setup helper from the parent process.
+  // See also: DebuggerServer.setupInChild()
+  let onSetupInChild = DevToolsUtils.makeInfallible(msg => {
+    let { module, setupChild, args } = msg.data;
+    let m, fn;
+
+    try {
+      m = devtools.require(module);
+
+      if (!setupChild in m) {
+        dumpn("ERROR: module '" + module + "' does not export '" +
+              setupChild + "'");
+        return false;
+      }
+
+      m[setupChild].apply(m, args);
+
+      return true;
+    } catch(e) {
+      let error_msg = "exception during actor module setup running in the child process: ";
+      DevToolsUtils.reportException(error_msg + e);
+      dumpn("ERROR: " + error_msg + " \n\t module: '" + module +
+            "' \n\t setupChild: '" + setupChild + "'\n" +
+            DevToolsUtils.safeErrorString(e));
+      return false;
+    }
+  });
+
+  addMessageListener("debug:setup-in-child", onSetupInChild);
+
   let onDisconnect = DevToolsUtils.makeInfallible(function (msg) {
     removeMessageListener("debug:disconnect", onDisconnect);
 
     // Call DebuggerServerConnection.close to destroy all child actors
     // (It should end up calling DebuggerServerConnection.onClosed
     // that would actually cleanup all actor pools)
     let childID = msg.data.childID;
     let conn = connections.get(childID);
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -726,16 +726,41 @@ var DebuggerServer = {
    * Check if the caller is running in a content child process.
    *
    * @return boolean
    *         true if the caller is running in a content
    */
   get isInChildProcess() !!this.parentMessageManager,
 
   /**
+   * In a chrome parent process, ask all content child processes
+   * to execute a given module setup helper.
+   *
+   * @param module
+   *        The module to be required
+   * @param setupChild
+   *        The name of the setup helper exported by the above module
+   *        (setup helper signature: function ({mm}) { ... })
+   */
+  setupInChild: function({ module, setupChild, args }) {
+    if (this.isInChildProcess) {
+      return;
+    }
+
+    const gMessageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+      getService(Ci.nsIMessageListenerManager);
+
+    gMessageManager.broadcastAsyncMessage("debug:setup-in-child", {
+      module: module,
+      setupChild: setupChild,
+      args: args,
+    });
+  },
+
+  /**
    * In a content child process, ask the DebuggerServer in the parent process
    * to execute a given module setup helper.
    *
    * @param module
    *        The module to be required
    * @param setupParent
    *        The name of the setup helper exported by the above module
    *        (setup helper signature: function ({mm}) { ... })
@@ -824,16 +849,18 @@ var DebuggerServer = {
 
       dumpn("establishing forwarding for app with prefix " + prefix);
 
       actor = msg.json.actor;
 
       let { NetworkMonitorManager } = require("devtools/toolkit/webconsole/network-monitor");
       netMonitor = new NetworkMonitorManager(aFrame, actor.actor);
 
+      events.emit(DebuggerServer, "new-child-process", { mm: mm });
+
       deferred.resolve(actor);
     }).bind(this);
     mm.addMessageListener("debug:actor", onActorCreated);
 
     let onMessageManagerDisconnect = DevToolsUtils.makeInfallible(function (subject, topic, data) {
       if (subject == mm) {
         Services.obs.removeObserver(onMessageManagerDisconnect, topic);
 
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -65,16 +65,17 @@ EXTRA_JS_MODULES.devtools.server.actors 
     'actors/webapps.js',
     'actors/webaudio.js',
     'actors/webbrowser.js',
     'actors/webconsole.js',
     'actors/webgl.js',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors.utils += [
+    'actors/utils/actor-registry-utils.js',
     'actors/utils/automation-timeline.js',
     'actors/utils/make-debugger.js',
     'actors/utils/map-uri-to-addon-id.js',
     'actors/utils/ScriptStore.js',
     'actors/utils/stack.js',
 ]
 
 FAIL_ON_WARNINGS = True
--- a/toolkit/devtools/server/tests/unit/test_actor-registry-actor.js
+++ b/toolkit/devtools/server/tests/unit/test_actor-registry-actor.js
@@ -60,17 +60,17 @@ function talkToNewActor() {
   });
 }
 
 function unregisterNewActor() {
   gActorFront
     .unregister()
     .then(testActorIsUnregistered)
     .then(null, e => {
-      DevToolsUtils.reportException("registerNewActor", e)
+      DevToolsUtils.reportException("unregisterNewActor", e)
       do_check_true(false);
     });
 }
 
 function testActorIsUnregistered() {
   gClient.listTabs(({ helloActor }) => {
     do_check_true(!helloActor);