Bug 1107888 - e10s support for dynamic actor registration. r=ochameau
authorJan Odvarko <odvarko@gmail.com>
Fri, 05 Dec 2014 15:40:10 -0800
changeset 222244 11b650affda02eaed069e4615571154a210961aa
parent 222243 ff084cf799446340125a44ff0008d34d6b0e0568
child 222245 b8bf3fc7743761a73d90bd1462ee903765f75572
push id10671
push userryanvm@gmail.com
push dateTue, 06 Jan 2015 17:10:43 +0000
treeherderfx-team@7a8b80930bd1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1107888
milestone37.0a1
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);