Bug 1107888 - e10s support for dynamic actor registration. r=ochameau
--- 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);