diff --git a/toolkit/devtools/server/docs/actor-e10s-handling.md b/toolkit/devtools/server/docs/actor-e10s-handling.md new file mode 100644 index 000000000000..5c09556ce6b2 --- /dev/null +++ b/toolkit/devtools/server/docs/actor-e10s-handling.md @@ -0,0 +1,102 @@ +# How to handle E10S in actors + +In multi-process environments, most devtools actors are created and initialized in the child content process, to be able to access the resources they are exposing to the toolbox. But sometimes, these actors need to access things in the parent process too. Here's why and how. + +## Use case and examples + +Some actors need to exchange messages between the parent and the child process (typically when some components aren't available in the child process). + +E.g. the **director-manager** needs to ask the list of installed **director scripts** from +the **director-registry** running in the parent process. + +To that end, there's a parent/child setup mechanism at `DebuggerServer` level that can be used. + +When the actor is loaded for the first time in the `DebuggerServer` running in the child process, it may decide to run a setup procedure to load a module in the parent process with which to communicate. + +E.g. in the **director-registry**: + +``` + const {DebuggerServer} = require("devtools/server/main"); + + // Setup the child<->parent communication only if the actor module + // is running in a child process. + if (DebuggerServer.isInChildProcess) { + setupChildProcess(); + } + + function setupChildProcess() { + DebuggerServer.setupInParent({ + module: "devtools/server/actors/director-registry", + setupParent: "setupParentProcess" + }); + // ... + } +``` + +The `setupChildProcess` helper defined and used in the previous example uses the `DebuggerServer.setupInParent` to run a given setup function in the parent process Debugger Server, e.g. in the **director-registry** module. + +With this, the `DebuggerServer` running in the parent process will require the requested module (**director-registry**) and call its `setupParentProcess` function (which should be exported on the module). + +The `setupParentProcess` function will receive a parameter that contains a reference to the **MessageManager** and a prefix that should be used to send/receive messages between the child and parent processes. + +See below an example implementation of a `setupParent` function in the parent process: + +``` +let gTrackedMessageManager = new Set(); +exports.setupParentProcess = function setupParentProcess({ mm, prefix }) { + // Prevent multiple subscriptions on the same messagemanager. + if (gTrackedMessageManager.has(mm)) { return; } + gTrackedMessageManager.add(mm); + + // Start listening for messages from the actor in the child process. + mm.addMessageListener("debug:some-message-name", handleChildRequest); + + function handleChildRequest(msg) { + switch (msg.json.method) { + case "get": + return doGetInParentProcess(msg.json.args[0]); + break; + case "list": + return doListInParentProcess(); + break; + default: + console.error("Unknown method name", msg.json.method); + throw new Error("Unknown method name"); + } + } + + // Listen to the disconnection message to clean-up. + DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected); + + function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) { + // filter out not subscribed message managers + if (disconnected_mm !== mm || !gTrackedMessageManager.has(mm)) { + return; + } + + gTrackedMessageManager.delete(mm); + + // unregister for director-script requests handlers from the parent process (if any) + mm.removeMessageListener("debug:director-registry-request", handleChildRequest); + } +``` + +The `DebuggerServer` emits "disconnected-from-child:CHILDID" events to give the actor modules the chance to cleanup their handlers registered on the disconnected message manager. + +## Summary of the setup flow + +In the child process: + +* The `DebuggerServer` loads an actor module, +* the actor module checks `DebuggerServer.isInChildProcess` to know whether it runs in a child process or not, +* the actor module then uses the `DebuggerServer.setupInParent` helper to start setting up a parent-process counterpart, +* the `DebuggerServer.setupInParent` helper asks the parent process to run the required module's setup function, +* the actor module uses the `DebuggerServer.parentMessageManager.sendSyncMessage` and `DebuggerServer.parentMessageManager.addMessageListener` helpers to send or listen to message. + +In the parent process: + +* The DebuggerServer receives the `DebuggerServer.setupInParent` request, +* tries to load the required module, +* tries to call the `module[setupParent]` function with the frame message manager and the prefix as parameters `{ mm, prefix }`, +* the `setupParent` function then uses the mm to subscribe the messagemanager events, +* the `setupParent` function also uses the DebuggerServer object to subscribe *once* to the `"disconnected-from-child:PREFIX"` event to unsubscribe from messagemanager events. \ No newline at end of file diff --git a/toolkit/devtools/server/docs/actor-registration.md b/toolkit/devtools/server/docs/actor-registration.md new file mode 100644 index 000000000000..3de80f49026d --- /dev/null +++ b/toolkit/devtools/server/docs/actor-registration.md @@ -0,0 +1,41 @@ +# How to register an actor + +## Tab actors vs. global actors + +Tab actors are the most common types of actors. That's the type of actors you will most probably be adding. + +Tab actors target a document, this could be a tab in Firefox, an app on B2G or a remote document in Firefox for Android/Safari/Chrome for Android (via Valence). + +Global actors however are for the rest, for things not related to any particular document but instead for things global to the whole Firefox/B2G/Chrome/Safari intance the toolbox is connected to (e.g. the preference actor). + +## The DebuggerServer.registerModule function + +To register a tab actor: + +``` +DebuggerServer.registerModule("devtools/server/actors/webconsole", { + prefix: "console", + constructor: "WebConsoleActor", + type: { tab: true } +}); +``` + +To register a global actor: + +``` +DebuggerServer.registerModule("devtools/server/actors/webapps", { + prefix: "webapps", + constructor: "WebappsActor", + type: { global: true } +}); +``` + +If you are adding a new built-in devtools actor, you should be registering it using `DebuggerServer.registerModule` in `addBrowserActors` or `addTabActors` in `/toolkit/devtools/server/main.js`. + +If you are adding a new actor from an add-on, you should call `DebuggerServer.registerModule` directly from your add-on code. + +## A note about lazy registration + +The `DebuggerServer` loads and creates all of the actors lazily to keep the initial memory usage down (which is extremely important on lower end devices). + +It becomes especially important when debugging apps on b2g or pages with e10s when there are more than one process, because that's when we need to spawn a `DebuggerServer` per process (it may not be immediately obvious that the server in the main process is mostly only here for piping messages to the actors in the child process). diff --git a/toolkit/devtools/server/docs/lazy-actor-modules.md b/toolkit/devtools/server/docs/lazy-actor-modules.md deleted file mode 100644 index 52fbd75e7175..000000000000 --- a/toolkit/devtools/server/docs/lazy-actor-modules.md +++ /dev/null @@ -1,116 +0,0 @@ -Lazy Actor Modules and E10S setup ---------------------------------- - -The **DebuggerServer** loads and creates most of the actors lazily to keep -the initial memory usage down (which is extremely important on lower end devices). - -## Register a lazy global/tab actor module - -register a global actor: - -```js - DebuggerServer.registerModule("devtools/server/actors/webapps", { - prefix: "webapps", - constructor: "WebappsActor", - type: { global: true } - }); -``` - -register a tab actor: - -```js - DebuggerServer.registerModule("devtools/server/actors/webconsole", { - prefix: "console", - constructor: "WebConsoleActor", - type: { tab: true } - }); -``` - -## E10S Setup - -Some of the actor modules needs to exchange messages between the parent and child processes. - -E.g. the **director-manager** needs to ask the list installed **director scripts** from -the **director-registry** running in the parent process) and the parent/child setup -is lazily directed by the **DebuggerServer**. - -When the actor is loaded for the first time in the the **DebuggerServer** running in the -child process, it has the chances to run its setup procedure, e.g. in the **director-registry**: - -```js -... -const {DebuggerServer} = require("devtools/server/main"); - -... - -// skip child setup if this actor module is not running in a child process -if (DebuggerServer.isInChildProcess) { - setupChildProcess(); -} -... -``` - -The above setupChildProcess helper will use the **DebuggerServer.setupInParent** -to start a setup process in the parent process Debugger Server, e.g. in the the **director-registry**: - -```js -function setupChildProcess() { - const { sendSyncMessage } = DebuggerServer.parentMessageManager; - - DebuggerServer.setupInParent({ - module: "devtools/server/actors/director-registry", - setupParent: "setupParentProcess" - }); - - ... -``` - -in the parent process, the **DebuggerServer** will require the requested module -and call the **setupParent** exported helper with the **MessageManager** -connected to the child process as parameter, e.g. in the **director-registry**: - -```js -/** - * E10S parent/child setup helpers - */ - -let gTrackedMessageManager = new Set(); - -exports.setupParentProcess = function setupParentProcess({ mm, prefix }) { - if (gTrackedMessageManager.has(mm)) { return; } - gTrackedMessageManager.add(mm); - - // listen for director-script requests from the child process - mm.addMessageListener("debug:director-registry-request", handleChildRequest); - - // time to unsubscribe from the disconnected message manager - DebuggerServer.once("disconnected-from-child:" + prefix, handleMessageManagerDisconnected); - - function handleMessageManagerDisconnected(evt, { mm: disconnected_mm }) { - ... - } -``` - -The DebuggerServer emits "disconnected-from-child:CHILDID" events to give the actor modules -the chance to cleanup their handlers registered on the disconnected message manager. - -## E10S setup flow - -In the child process: -- DebuggerServer loads an actor module - - the actor module check DebuggerServer.isInChildProcess - - the actor module calls the DebuggerServer.setupInParent helper - - the DebuggerServer.setupInParent helper asks to the parent process - to run the required setup - - the actor module use the DebuggerServer.parentMessageManager.sendSyncMessage, - DebuggerServer.parentMessageManager.addMessageListener helpers to send requests - or to subscribe message handlers - -In the parent process: -- The DebuggerServer receives the DebuggerServer.setupInParent request -- it tries to load the required module -- it tries to call the **mod[setupParent]** method with the frame message manager and the prefix - in the json parameter **{ mm, prefix }** - - the module setupParent helper use the mm to subscribe the messagemanager events - - the module setupParent helper use the DebuggerServer object to subscribe *once* the - **"disconnected-from-child:PREFIX"** event (needed to unsubscribe the messagemanager events)