diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:13:27 +0000 |
commit | 40a355a42d4a9444dc753c04c6608dade2f06a23 (patch) | |
tree | 871fc667d2de662f171103ce5ec067014ef85e61 /devtools/server/actors/watcher | |
parent | Adding upstream version 124.0.1. (diff) | |
download | firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.tar.xz firefox-40a355a42d4a9444dc753c04c6608dade2f06a23.zip |
Adding upstream version 125.0.1.upstream/125.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/actors/watcher')
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 = { |