summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/watcher
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/watcher')
-rw-r--r--devtools/server/actors/watcher/WatcherRegistry.sys.mjs84
-rw-r--r--devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js26
-rw-r--r--devtools/server/actors/watcher/target-helpers/frame-helper.js11
-rw-r--r--devtools/server/actors/watcher/target-helpers/moz.build1
-rw-r--r--devtools/server/actors/watcher/target-helpers/process-helper.js372
5 files changed, 155 insertions, 339 deletions
diff --git a/devtools/server/actors/watcher/WatcherRegistry.sys.mjs b/devtools/server/actors/watcher/WatcherRegistry.sys.mjs
index 1068a253c9..ac8bc7f0c8 100644
--- a/devtools/server/actors/watcher/WatcherRegistry.sys.mjs
+++ b/devtools/server/actors/watcher/WatcherRegistry.sys.mjs
@@ -180,6 +180,9 @@ export const WatcherRegistry = {
// Register the JS Window Actor the first time we start watching for something (e.g. resource, target, …).
registerJSWindowActor();
+ if (sessionData?.targets?.includes("process")) {
+ registerJSProcessActor();
+ }
persistMapToSharedData();
},
@@ -245,7 +248,17 @@ export const WatcherRegistry = {
unregisterWatcher(watcher) {
sessionDataByWatcherActor.delete(watcher.actorID);
watcherActors.delete(watcher.actorID);
- this.maybeUnregisteringJSWindowActor();
+ this.maybeUnregisterJSActors();
+ },
+
+ /**
+ * Unregister the JS Actors if there is no more DevTools code observing any target/resource.
+ */
+ maybeUnregisterJSActors() {
+ if (sessionDataByWatcherActor.size == 0) {
+ unregisterJSWindowActor();
+ unregisterJSProcessActor();
+ }
},
/**
@@ -319,15 +332,6 @@ export const WatcherRegistry = {
resourceTypes
);
},
-
- /**
- * Unregister the JS Window Actor if there is no more DevTools code observing any target/resource.
- */
- maybeUnregisteringJSWindowActor() {
- if (sessionDataByWatcherActor.size == 0) {
- unregisterJSWindowActor();
- }
- },
};
// Boolean flag to know if the DevToolsFrame JS Window Actor is currently registered
@@ -395,3 +399,63 @@ function unregisterJSWindowActor() {
ChromeUtils.unregisterWindowActor(JSWindowActorName);
}
}
+
+// Boolean flag to know if the DevToolsProcess JS Process Actor is currently registered
+let isJSProcessActorRegistered = false;
+
+const JSProcessActorConfig = {
+ parent: {
+ esModuleURI:
+ "resource://devtools/server/connectors/js-process-actor/DevToolsProcessParent.sys.mjs",
+ },
+ child: {
+ esModuleURI:
+ "resource://devtools/server/connectors/js-process-actor/DevToolsProcessChild.sys.mjs",
+ // There is no good observer service notification we can listen to to instantiate the JSProcess Actor
+ // reliably as soon as the process start.
+ // So manually spawn our JSProcessActor from a process script emitting a custom observer service notification...
+ observers: ["init-devtools-content-process-actor"],
+ },
+ // The parent process is handled very differently from content processes
+ // This uses the ParentProcessTarget which inherits from BrowsingContextTarget
+ // and is, for now, manually created by the descriptor as the top level target.
+ includeParent: false,
+
+ // This JS Process Actor is used to bootstrap DevTools code debugging the privileged code
+ // in content processes. The privileged code runs in the "shared JSM global" (See mozJSModuleLoader).
+ // DevTools modules should be loaded in a distinct global in order to be able to debug this privileged code.
+ // There is a strong requirement in spidermonkey for the debuggee and debugger to be using distinct compartments.
+ // This flag will force both parent and child modules to be loaded via a dedicated loader (See mozJSModuleLoader::GetOrCreateDevToolsLoader)
+ //
+ // Note that as a side effect, it makes these modules and all their dependencies to be invisible to the debugger.
+ loadInDevToolsLoader: true,
+};
+
+const PROCESS_SCRIPT_URL =
+ "resource://devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js";
+
+function registerJSProcessActor() {
+ if (isJSProcessActorRegistered) {
+ return;
+ }
+ isJSProcessActorRegistered = true;
+ ChromeUtils.registerProcessActor("DevToolsProcess", JSProcessActorConfig);
+
+ // There is no good observer service notification we can listen to to instantiate the JSProcess Actor
+ // as soon as the process start.
+ // So manually spawn our JSProcessActor from a process script emitting a custom observer service notification...
+ Services.ppmm.loadProcessScript(PROCESS_SCRIPT_URL, true);
+}
+
+function unregisterJSProcessActor() {
+ if (!isJSProcessActorRegistered) {
+ return;
+ }
+ isJSProcessActorRegistered = false;
+ try {
+ ChromeUtils.unregisterProcessActor("DevToolsProcess");
+ } catch (e) {
+ // If any pending query was still ongoing, this would throw
+ }
+ Services.ppmm.removeDelayedProcessScript(PROCESS_SCRIPT_URL);
+}
diff --git a/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js b/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js
new file mode 100644
index 0000000000..1765bcc66c
--- /dev/null
+++ b/devtools/server/actors/watcher/target-helpers/content-process-jsprocessactor-startup.js
@@ -0,0 +1,26 @@
+/* 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";
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+/*
+ We can't spawn the JSProcessActor right away and have to spin the event loop.
+ Otherwise it isn't registered yet and isn't listening to observer service.
+ Could it be the reason why JSProcessActor aren't spawn via process actor option's child.observers notifications ??
+*/
+setTimeout(function () {
+ /*
+ This notification is registered in DevToolsServiceWorker JS process actor's options's `observers` attribute
+ and will force the JS Process actor to be instantiated in all processes.
+ */
+ Services.obs.notifyObservers(null, "init-devtools-content-process-actor");
+ /*
+ Instead of using observer service, we could also manually call some method of the actor:
+ ChromeUtils.domProcessChild.getActor("DevToolsProcess").observe(null, "foo");
+ */
+}, 0);
diff --git a/devtools/server/actors/watcher/target-helpers/frame-helper.js b/devtools/server/actors/watcher/target-helpers/frame-helper.js
index 0e6f4f80d3..18d4d8f92e 100644
--- a/devtools/server/actors/watcher/target-helpers/frame-helper.js
+++ b/devtools/server/actors/watcher/target-helpers/frame-helper.js
@@ -6,14 +6,13 @@
const { WatcherRegistry } = ChromeUtils.importESModule(
"resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs",
- {
- // WatcherRegistry needs to be a true singleton and loads ActorManagerParent
- // which also has to be a true singleton.
- loadInDevToolsLoader: false,
- }
+ // WatcherRegistry needs to be a true singleton and loads ActorManagerParent
+ // which also has to be a true singleton.
+ { global: "shared" }
);
const { WindowGlobalLogger } = ChromeUtils.importESModule(
- "resource://devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs"
+ "resource://devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs",
+ { global: "contextual" }
);
const Targets = require("resource://devtools/server/actors/targets/index.js");
diff --git a/devtools/server/actors/watcher/target-helpers/moz.build b/devtools/server/actors/watcher/target-helpers/moz.build
index b7c8983590..3b00f0ef47 100644
--- a/devtools/server/actors/watcher/target-helpers/moz.build
+++ b/devtools/server/actors/watcher/target-helpers/moz.build
@@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
+ "content-process-jsprocessactor-startup.js",
"frame-helper.js",
"process-helper.js",
"service-worker-helper.js",
diff --git a/devtools/server/actors/watcher/target-helpers/process-helper.js b/devtools/server/actors/watcher/target-helpers/process-helper.js
index 8895d7ed66..e36f0a204c 100644
--- a/devtools/server/actors/watcher/target-helpers/process-helper.js
+++ b/devtools/server/actors/watcher/target-helpers/process-helper.js
@@ -4,205 +4,36 @@
"use strict";
-const { WatcherRegistry } = ChromeUtils.importESModule(
- "resource://devtools/server/actors/watcher/WatcherRegistry.sys.mjs",
- {
- // WatcherRegistry needs to be a true singleton and loads ActorManagerParent
- // which also has to be a true singleton.
- loadInDevToolsLoader: false,
- }
-);
-
-loader.lazyRequireGetter(
- this,
- "ChildDebuggerTransport",
- "resource://devtools/shared/transport/child-transport.js",
- true
-);
-
-const CONTENT_PROCESS_SCRIPT =
- "resource://devtools/server/startup/content-process-script.js";
-
/**
- * Map a MessageManager key to an Array of ContentProcessTargetActor "description" objects.
- * A single MessageManager might be linked to several ContentProcessTargetActors if there are several
- * Watcher actors instantiated on the DevToolsServer, via a single connection (in theory), but rather
- * via distinct connections (ex: a content toolbox and the browser toolbox).
- * Note that if we spawn two DevToolsServer, this module will be instantiated twice.
+ * Return the list of all DOM Processes except the one for the parent process
*
- * Each ContentProcessTargetActor "description" object is structured as follows
- * - {Object} actor: form of the content process target actor
- * - {String} prefix: forwarding prefix used to redirect all packet to the right content process's transport
- * - {ChildDebuggerTransport} childTransport: Transport forwarding all packets to the target's content process
- * - {WatcherActor} watcher: The Watcher actor for which we instantiated this content process target actor
+ * @return Array<nsIDOMProcessParent>
*/
-const actors = new WeakMap();
-
-// Save the list of all watcher actors that are watching for processes
-const watchers = new Set();
-
-function onContentProcessActorCreated(msg) {
- const { watcherActorID, prefix, actor } = msg.data;
- const watcher = WatcherRegistry.getWatcher(watcherActorID);
- if (!watcher) {
- throw new Error(
- `Receiving a content process actor without a watcher actor ${watcherActorID}`
- );
- }
- // Ignore watchers of other connections.
- // We may have two browser toolbox connected to the same process.
- // This will spawn two distinct Watcher actor and two distinct process target helper module.
- // Avoid processing the event many times, otherwise we will notify about the same target
- // multiple times.
- if (!watchers.has(watcher)) {
- return;
- }
- const messageManager = msg.target;
- const connection = watcher.conn;
-
- // Pipe Debugger message from/to parent/child via the message manager
- const childTransport = new ChildDebuggerTransport(messageManager, prefix);
- childTransport.hooks = {
- onPacket: connection.send.bind(connection),
- };
- childTransport.ready();
-
- connection.setForwarding(prefix, childTransport);
-
- const list = actors.get(messageManager) || [];
- list.push({
- prefix,
- childTransport,
- actor,
- watcher,
- });
- actors.set(messageManager, list);
-
- watcher.notifyTargetAvailable(actor);
-}
-
-function onContentProcessActorDestroyed(msg) {
- const { watcherActorID } = msg.data;
- const watcher = WatcherRegistry.getWatcher(watcherActorID);
- if (!watcher) {
- throw new Error(
- `Receiving a content process actor destruction without a watcher actor ${watcherActorID}`
- );
- }
- // Ignore watchers of other connections.
- // We may have two browser toolbox connected to the same process.
- // This will spawn two distinct Watcher actor and two distinct process target helper module.
- // Avoid processing the event many times, otherwise we will notify about the same target
- // multiple times.
- if (!watchers.has(watcher)) {
- return;
- }
- const messageManager = msg.target;
- unregisterWatcherForMessageManager(watcher, messageManager);
-}
-
-function onMessageManagerClose(messageManager, topic, data) {
- const list = actors.get(messageManager);
- if (!list || !list.length) {
- return;
- }
- for (const { prefix, childTransport, actor, watcher } of list) {
- watcher.notifyTargetDestroyed(actor);
-
- // If we have a child transport, the actor has already
- // been created. We need to stop using this message manager.
- childTransport.close();
- watcher.conn.cancelForwarding(prefix);
- }
- actors.delete(messageManager);
-}
-
-/**
- * Unregister everything created for a given watcher against a precise message manager:
- * - clear up things from `actors` WeakMap,
- * - notify all related target actors as being destroyed,
- * - close all DevTools Transports being created for each Message Manager.
- *
- * @param {WatcherActor} watcher
- * @param {MessageManager}
- * @param {object} options
- * @param {boolean} options.isModeSwitching
- * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
- */
-function unregisterWatcherForMessageManager(watcher, messageManager, options) {
- const targetActorDescriptions = actors.get(messageManager);
- if (!targetActorDescriptions || !targetActorDescriptions.length) {
- return;
- }
-
- // Destroy all transports related to this watcher and tells the client to purge all related actors
- const matchingTargetActorDescriptions = targetActorDescriptions.filter(
- item => item.watcher === watcher
+function getAllContentProcesses() {
+ return ChromeUtils.getAllDOMProcesses().filter(
+ process => process.childID !== 0
);
- for (const {
- prefix,
- childTransport,
- actor,
- } of matchingTargetActorDescriptions) {
- watcher.notifyTargetDestroyed(actor, options);
-
- childTransport.close();
- watcher.conn.cancelForwarding(prefix);
- }
-
- // Then update global `actors` WeakMap by stripping all data about this watcher
- const remainingTargetActorDescriptions = targetActorDescriptions.filter(
- item => item.watcher !== watcher
- );
- if (!remainingTargetActorDescriptions.length) {
- actors.delete(messageManager);
- } else {
- actors.set(messageManager, remainingTargetActorDescriptions);
- }
}
/**
- * Destroy everything related to a given watcher that has been created in this module:
- * (See unregisterWatcherForMessageManager)
+ * Instantiate all Content Process targets in all the DOM Processes.
*
* @param {WatcherActor} watcher
- * @param {object} options
- * @param {boolean} options.isModeSwitching
- * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
*/
-function closeWatcherTransports(watcher, options) {
- for (let i = 0; i < Services.ppmm.childCount; i++) {
- const messageManager = Services.ppmm.getChildAt(i);
- unregisterWatcherForMessageManager(watcher, messageManager, options);
- }
-}
-
-function maybeRegisterMessageListeners(watcher) {
- const sizeBefore = watchers.size;
- watchers.add(watcher);
- if (sizeBefore == 0 && watchers.size == 1) {
- Services.ppmm.addMessageListener(
- "debug:content-process-actor",
- onContentProcessActorCreated
- );
- Services.ppmm.addMessageListener(
- "debug:content-process-actor-destroyed",
- onContentProcessActorDestroyed
+async function createTargets(watcher) {
+ const promises = [];
+ for (const domProcess of getAllContentProcesses()) {
+ const processActor = domProcess.getActor("DevToolsProcess");
+ promises.push(
+ processActor.instantiateTarget({
+ watcherActorID: watcher.actorID,
+ connectionPrefix: watcher.conn.prefix,
+ sessionContext: watcher.sessionContext,
+ sessionData: watcher.sessionData,
+ })
);
- Services.obs.addObserver(onMessageManagerClose, "message-manager-close");
-
- // Load the content process server startup script only once,
- // otherwise it will be evaluated twice, listen to events twice and create
- // target actors twice.
- // We may try to load it twice when opening one Browser Toolbox via about:debugging
- // and another regular Browser Toolbox. Both will spawn a WatcherActor and watch for processes.
- const isContentProcessScripLoaded = Services.ppmm
- .getDelayedProcessScripts()
- .some(([uri]) => uri === CONTENT_PROCESS_SCRIPT);
- if (!isContentProcessScripLoaded) {
- Services.ppmm.loadProcessScript(CONTENT_PROCESS_SCRIPT, true);
- }
}
+ await Promise.all(promises);
}
/**
@@ -211,96 +42,16 @@ function maybeRegisterMessageListeners(watcher) {
* @param {boolean} options.isModeSwitching
* true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
*/
-function maybeUnregisterMessageListeners(watcher, options = {}) {
- const sizeBefore = watchers.size;
- watchers.delete(watcher);
- closeWatcherTransports(watcher, options);
-
- if (sizeBefore == 1 && watchers.size == 0) {
- Services.ppmm.removeMessageListener(
- "debug:content-process-actor",
- onContentProcessActorCreated
- );
- Services.ppmm.removeMessageListener(
- "debug:content-process-actor-destroyed",
- onContentProcessActorDestroyed
- );
- Services.obs.removeObserver(onMessageManagerClose, "message-manager-close");
-
- // We inconditionally remove the process script, while we should only remove it
- // once the last DevToolsServer stop watching for processes.
- // We might have many server, using distinct loaders, so that this module
- // will be spawn many times and we should remove the script only once the last
- // module unregister the last watcher of all.
- Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_SCRIPT);
-
- Services.ppmm.broadcastAsyncMessage("debug:destroy-process-script", {
- options,
+function destroyTargets(watcher, options) {
+ for (const domProcess of getAllContentProcesses()) {
+ const processActor = domProcess.getActor("DevToolsProcess");
+ processActor.destroyTarget({
+ watcherActorID: watcher.actorID,
+ isModeSwitching: options.isModeSwitching,
});
}
}
-async function createTargets(watcher) {
- // XXX: Should this move to WatcherRegistry??
- maybeRegisterMessageListeners(watcher);
-
- // Bug 1648499: This could be simplified when migrating to JSProcessActor by using sendQuery.
- // For now, hack into WatcherActor in order to know when we created one target
- // actor for each existing content process.
- // Also, we substract one as the parent process has a message manager and is counted
- // in `childCount`, but we ignore it from the process script and it won't reply.
- let contentProcessCount = Services.ppmm.childCount - 1;
- if (contentProcessCount == 0) {
- return;
- }
- const onTargetsCreated = new Promise(resolve => {
- let receivedTargetCount = 0;
- const listener = () => {
- receivedTargetCount++;
- mayBeResolve();
- };
- watcher.on("target-available-form", listener);
- const onContentProcessClosed = () => {
- // Update the content process count as one has been just destroyed
- contentProcessCount--;
- mayBeResolve();
- };
- Services.obs.addObserver(onContentProcessClosed, "message-manager-close");
- function mayBeResolve() {
- if (receivedTargetCount >= contentProcessCount) {
- watcher.off("target-available-form", listener);
- Services.obs.removeObserver(
- onContentProcessClosed,
- "message-manager-close"
- );
- resolve();
- }
- }
- });
-
- Services.ppmm.broadcastAsyncMessage("debug:instantiate-already-available", {
- watcherActorID: watcher.actorID,
- connectionPrefix: watcher.conn.prefix,
- sessionData: watcher.sessionData,
- });
-
- await onTargetsCreated;
-}
-
-/**
- * @param {WatcherActor} watcher
- * @param {object} options
- * @param {boolean} options.isModeSwitching
- * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref
- */
-function destroyTargets(watcher, options) {
- maybeUnregisterMessageListeners(watcher, options);
-
- Services.ppmm.broadcastAsyncMessage("debug:destroy-target", {
- watcherActorID: watcher.actorID,
- });
-}
-
/**
* Go over all existing content processes in order to communicate about new data entries
*
@@ -321,51 +72,19 @@ async function addOrSetSessionDataEntry({
entries,
updateType,
}) {
- let expectedCount = Services.ppmm.childCount - 1;
- if (expectedCount == 0) {
- return;
- }
- const onAllReplied = new Promise(resolve => {
- let count = 0;
- const listener = msg => {
- if (msg.data.watcherActorID != watcher.actorID) {
- return;
- }
- count++;
- maybeResolve();
- };
- Services.ppmm.addMessageListener(
- "debug:add-or-set-session-data-entry-done",
- listener
+ const promises = [];
+ for (const domProcess of getAllContentProcesses()) {
+ const processActor = domProcess.getActor("DevToolsProcess");
+ promises.push(
+ processActor.addOrSetSessionDataEntry({
+ watcherActorID: watcher.actorID,
+ type,
+ entries,
+ updateType,
+ })
);
- const onContentProcessClosed = (messageManager, topic, data) => {
- expectedCount--;
- maybeResolve();
- };
- const maybeResolve = () => {
- if (count == expectedCount) {
- Services.ppmm.removeMessageListener(
- "debug:add-or-set-session-data-entry-done",
- listener
- );
- Services.obs.removeObserver(
- onContentProcessClosed,
- "message-manager-close"
- );
- resolve();
- }
- };
- Services.obs.addObserver(onContentProcessClosed, "message-manager-close");
- });
-
- Services.ppmm.broadcastAsyncMessage("debug:add-or-set-session-data-entry", {
- watcherActorID: watcher.actorID,
- type,
- entries,
- updateType,
- });
-
- await onAllReplied;
+ }
+ await Promise.all(promises);
}
/**
@@ -373,12 +92,19 @@ async function addOrSetSessionDataEntry({
*
* See addOrSetSessionDataEntry for argument documentation.
*/
-function removeSessionDataEntry({ watcher, type, entries }) {
- Services.ppmm.broadcastAsyncMessage("debug:remove-session-data-entry", {
- watcherActorID: watcher.actorID,
- type,
- entries,
- });
+async function removeSessionDataEntry({ watcher, type, entries }) {
+ const promises = [];
+ for (const domProcess of getAllContentProcesses()) {
+ const processActor = domProcess.getActor("DevToolsProcess");
+ promises.push(
+ processActor.removeSessionDataEntry({
+ watcherActorID: watcher.actorID,
+ type,
+ entries,
+ })
+ );
+ }
+ await Promise.all(promises);
}
module.exports = {