From d8bbc7858622b6d9c278469aab701ca0b609cddf Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:35:49 +0200 Subject: Merging upstream version 126.0. Signed-off-by: Daniel Baumann --- .../js-window-actor/DevToolsWorkerChild.sys.mjs | 571 --------------------- 1 file changed, 571 deletions(-) delete mode 100644 devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs (limited to 'devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs') diff --git a/devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs deleted file mode 100644 index 6bbe4140c3..0000000000 --- a/devtools/server/connectors/js-window-actor/DevToolsWorkerChild.sys.mjs +++ /dev/null @@ -1,571 +0,0 @@ -/* 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/. */ - -import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; - -import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; - -const lazy = {}; - -XPCOMUtils.defineLazyServiceGetter( - lazy, - "wdm", - "@mozilla.org/dom/workers/workerdebuggermanager;1", - "nsIWorkerDebuggerManager" -); - -ChromeUtils.defineLazyGetter(lazy, "Loader", () => - ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs") -); - -ChromeUtils.defineLazyGetter(lazy, "DevToolsUtils", () => - lazy.Loader.require("resource://devtools/shared/DevToolsUtils.js") -); -XPCOMUtils.defineLazyModuleGetters(lazy, { - SessionDataHelpers: - "resource://devtools/server/actors/watcher/SessionDataHelpers.jsm", -}); -ChromeUtils.defineESModuleGetters(lazy, { - isWindowGlobalPartOfContext: - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", -}); - -// Name of the attribute into which we save data in `sharedData` object. -const SHARED_DATA_KEY_NAME = "DevTools:watchedPerWatcher"; - -export class DevToolsWorkerChild extends JSWindowActorChild { - constructor() { - super(); - - // The map is indexed by the Watcher Actor ID. - // The values are objects containing the following properties: - // - connection: the DevToolsServerConnection itself - // - workers: An array of object containing the following properties: - // - dbg: A WorkerDebuggerInstance - // - workerTargetForm: The associated worker target instance form - // - workerThreadServerForwardingPrefix: The prefix used to forward events to the - // worker target on the worker thread (). - // - forwardingPrefix: Prefix used by the JSWindowActorTransport pair to communicate - // between content and parent processes. - // - sessionData: Data (targets, resources, …) the watcher wants to be notified about. - // See WatcherRegistry.getSessionData to see the full list of properties. - this._connections = new Map(); - - EventEmitter.decorate(this); - } - - _onWorkerRegistered(dbg) { - if (!this._shouldHandleWorker(dbg)) { - return; - } - - for (const [watcherActorID, { connection, forwardingPrefix }] of this - ._connections) { - this._createWorkerTargetActor({ - dbg, - connection, - forwardingPrefix, - watcherActorID, - }); - } - } - - _onWorkerUnregistered(dbg) { - for (const [watcherActorID, { workers, forwardingPrefix }] of this - ._connections) { - // Check if the worker registration was handled for this watcherActorID. - const unregisteredActorIndex = workers.findIndex(worker => { - try { - // Accessing the WorkerDebugger id might throw (NS_ERROR_UNEXPECTED). - return worker.dbg.id === dbg.id; - } catch (e) { - return false; - } - }); - if (unregisteredActorIndex === -1) { - continue; - } - - const { workerTargetForm, transport } = workers[unregisteredActorIndex]; - transport.close(); - - try { - this.sendAsyncMessage("DevToolsWorkerChild:workerTargetDestroyed", { - watcherActorID, - forwardingPrefix, - workerTargetForm, - }); - } catch (e) { - return; - } - - workers.splice(unregisteredActorIndex, 1); - } - } - - onDOMWindowCreated() { - const { sharedData } = Services.cpmm; - const sessionDataByWatcherActor = sharedData.get(SHARED_DATA_KEY_NAME); - if (!sessionDataByWatcherActor) { - throw new Error( - "Request to instantiate the target(s) for the Worker, but `sharedData` is empty about watched targets" - ); - } - - // Create one Target actor for each prefix/client which listen to workers - for (const [watcherActorID, sessionData] of sessionDataByWatcherActor) { - const { targets, connectionPrefix, sessionContext } = sessionData; - if ( - targets?.includes("worker") && - lazy.isWindowGlobalPartOfContext(this.manager, sessionContext, { - acceptInitialDocument: true, - forceAcceptTopLevelTarget: true, - acceptSameProcessIframes: true, - }) - ) { - this._watchWorkerTargets({ - watcherActorID, - parentConnectionPrefix: connectionPrefix, - sessionData, - }); - } - } - } - - /** - * Function handling messages sent by DevToolsWorkerParent (part of JSWindowActor API). - * - * @param {Object} message - * @param {String} message.name - * @param {*} message.data - */ - receiveMessage(message) { - // All messages pass `sessionContext` (except packet) and are expected - // to match isWindowGlobalPartOfContext result. - if (message.name != "DevToolsWorkerParent:packet") { - const { browserId } = message.data.sessionContext; - // Re-check here, just to ensure that both parent and content processes agree - // on what should or should not be watched. - if ( - this.manager.browsingContext.browserId != browserId && - !lazy.isWindowGlobalPartOfContext( - this.manager, - message.data.sessionContext, - { - acceptInitialDocument: true, - } - ) - ) { - throw new Error( - "Mismatch between DevToolsWorkerParent and DevToolsWorkerChild " + - (this.manager.browsingContext.browserId == browserId - ? "window global shouldn't be notified (isWindowGlobalPartOfContext mismatch)" - : `expected browsing context with ID ${browserId}, but got ${this.manager.browsingContext.browserId}`) - ); - } - } - - switch (message.name) { - case "DevToolsWorkerParent:instantiate-already-available": { - const { watcherActorID, connectionPrefix, sessionData } = message.data; - - return this._watchWorkerTargets({ - watcherActorID, - parentConnectionPrefix: connectionPrefix, - sessionData, - }); - } - case "DevToolsWorkerParent:destroy": { - const { watcherActorID } = message.data; - return this._destroyTargetActors(watcherActorID); - } - case "DevToolsWorkerParent:addOrSetSessionDataEntry": { - const { watcherActorID, type, entries, updateType } = message.data; - return this._addOrSetSessionDataEntry( - watcherActorID, - type, - entries, - updateType - ); - } - case "DevToolsWorkerParent:removeSessionDataEntry": { - const { watcherActorID, type, entries } = message.data; - return this._removeSessionDataEntry(watcherActorID, type, entries); - } - case "DevToolsWorkerParent:packet": - return this.emit("packet-received", message); - default: - throw new Error( - "Unsupported message in DevToolsWorkerParent: " + message.name - ); - } - } - - /** - * Instantiate targets for existing workers, watch for worker registration and listen - * for resources on those workers, for given connection and context. Targets are sent - * to the DevToolsWorkerParent via the DevToolsWorkerChild:workerTargetAvailable message. - * - * @param {Object} options - * @param {String} options.watcherActorID: The ID of the WatcherActor who requested to - * observe and create these target actors. - * @param {String} options.parentConnectionPrefix: The prefix of the DevToolsServerConnection - * of the Watcher Actor. This is used to compute a unique ID for the target actor. - * @param {Object} options.sessionData: Data (targets, resources, …) the watcher wants - * to be notified about. See WatcherRegistry.getSessionData to see the full list - * of properties. - */ - async _watchWorkerTargets({ - watcherActorID, - parentConnectionPrefix, - sessionData, - }) { - if (this._connections.has(watcherActorID)) { - throw new Error( - "DevToolsWorkerChild _watchWorkerTargets was called more than once" + - ` for the same Watcher (Actor ID: "${watcherActorID}")` - ); - } - - // Listen for new workers that will be spawned. - if (!this._workerDebuggerListener) { - this._workerDebuggerListener = { - onRegister: this._onWorkerRegistered.bind(this), - onUnregister: this._onWorkerUnregistered.bind(this), - }; - lazy.wdm.addListener(this._workerDebuggerListener); - } - - // Compute a unique prefix, just for this WindowGlobal, - // which will be used to create a JSWindowActorTransport pair between content and parent processes. - // This is slightly hacky as we typicaly compute Prefix and Actor ID via `DevToolsServerConnection.allocID()`, - // but here, we can't have access to any DevTools connection as we are really early in the content process startup - // WindowGlobalChild's innerWindowId should be unique across processes, so it should be safe? - // (this.manager == WindowGlobalChild interface) - const forwardingPrefix = - parentConnectionPrefix + "workerGlobal" + this.manager.innerWindowId; - - const connection = this._createConnection(forwardingPrefix); - - this._connections.set(watcherActorID, { - connection, - workers: [], - forwardingPrefix, - sessionData, - }); - - const promises = []; - for (const dbg of lazy.wdm.getWorkerDebuggerEnumerator()) { - if (!this._shouldHandleWorker(dbg)) { - continue; - } - promises.push( - this._createWorkerTargetActor({ - dbg, - connection, - forwardingPrefix, - watcherActorID, - }) - ); - } - await Promise.all(promises); - } - - _createConnection(forwardingPrefix) { - const { DevToolsServer } = lazy.Loader.require( - "resource://devtools/server/devtools-server.js" - ); - - DevToolsServer.init(); - - // We want a special server without any root actor and only target-scoped actors. - // We are going to spawn a WorkerTargetActor instance in the next few lines, - // it is going to act like a root actor without being one. - DevToolsServer.registerActors({ target: true }); - - const connection = DevToolsServer.connectToParentWindowActor( - this, - forwardingPrefix - ); - - return connection; - } - - /** - * Indicates whether or not we should handle the worker debugger - * - * @param {WorkerDebugger} dbg: The worker debugger we want to check. - * @returns {Boolean} - */ - _shouldHandleWorker(dbg) { - // We only want to create targets for non-closed dedicated worker, in the same document - return ( - lazy.DevToolsUtils.isWorkerDebuggerAlive(dbg) && - dbg.type === Ci.nsIWorkerDebugger.TYPE_DEDICATED && - dbg.windowIDs.includes(this.manager.innerWindowId) - ); - } - - async _createWorkerTargetActor({ - dbg, - connection, - forwardingPrefix, - watcherActorID, - }) { - // Prevent the debuggee from executing in this worker until the client has - // finished attaching to it. This call will throw if the debugger is already "registered" - // (i.e. if this is called outside of the register listener) - // See https://searchfox.org/mozilla-central/rev/84922363f4014eae684aabc4f1d06380066494c5/dom/workers/nsIWorkerDebugger.idl#55-66 - try { - dbg.setDebuggerReady(false); - } catch (e) {} - - const watcherConnectionData = this._connections.get(watcherActorID); - const { sessionData } = watcherConnectionData; - const workerThreadServerForwardingPrefix = - connection.allocID("workerTarget"); - - // Create the actual worker target actor, in the worker thread. - const { connectToWorker } = lazy.Loader.require( - "resource://devtools/server/connectors/worker-connector.js" - ); - - const onConnectToWorker = connectToWorker( - connection, - dbg, - workerThreadServerForwardingPrefix, - { - sessionData, - sessionContext: sessionData.sessionContext, - } - ); - - try { - await onConnectToWorker; - } catch (e) { - // onConnectToWorker can reject if the Worker Debugger is closed; so we only want to - // resume the debugger if it is not closed (otherwise it can cause crashes). - if (!dbg.isClosed) { - dbg.setDebuggerReady(true); - } - return; - } - - const { workerTargetForm, transport } = await onConnectToWorker; - - try { - this.sendAsyncMessage("DevToolsWorkerChild:workerTargetAvailable", { - watcherActorID, - forwardingPrefix, - workerTargetForm, - }); - } catch (e) { - // If there was an error while sending the message, we are not going to use this - // connection to communicate with the worker. - transport.close(); - return; - } - - // Only add data to the connection if we successfully send the - // workerTargetAvailable message. - watcherConnectionData.workers.push({ - dbg, - transport, - workerTargetForm, - workerThreadServerForwardingPrefix, - }); - } - - _destroyTargetActors(watcherActorID) { - const watcherConnectionData = this._connections.get(watcherActorID); - this._connections.delete(watcherActorID); - - // This connection has already been cleaned? - if (!watcherConnectionData) { - console.error( - `Trying to destroy a target actor that doesn't exists, or has already been destroyed. Watcher Actor ID:${watcherActorID}` - ); - return; - } - - for (const { - dbg, - transport, - workerThreadServerForwardingPrefix, - } of watcherConnectionData.workers) { - try { - if (lazy.DevToolsUtils.isWorkerDebuggerAlive(dbg)) { - dbg.postMessage( - JSON.stringify({ - type: "disconnect", - forwardingPrefix: workerThreadServerForwardingPrefix, - }) - ); - } - } catch (e) {} - - transport.close(); - } - - watcherConnectionData.connection.close(); - } - - async sendPacket(packet, prefix) { - return this.sendAsyncMessage("DevToolsWorkerChild:packet", { - packet, - prefix, - }); - } - - async _addOrSetSessionDataEntry(watcherActorID, type, entries, updateType) { - const watcherConnectionData = this._connections.get(watcherActorID); - if (!watcherConnectionData) { - return; - } - - lazy.SessionDataHelpers.addOrSetSessionDataEntry( - watcherConnectionData.sessionData, - type, - entries, - updateType - ); - - const promises = []; - for (const { - dbg, - workerThreadServerForwardingPrefix, - } of watcherConnectionData.workers) { - promises.push( - addOrSetSessionDataEntryInWorkerTarget({ - dbg, - workerThreadServerForwardingPrefix, - type, - entries, - updateType, - }) - ); - } - await Promise.all(promises); - } - - _removeSessionDataEntry(watcherActorID, type, entries) { - const watcherConnectionData = this._connections.get(watcherActorID); - - if (!watcherConnectionData) { - return; - } - - lazy.SessionDataHelpers.removeSessionDataEntry( - watcherConnectionData.sessionData, - type, - entries - ); - - for (const { - dbg, - workerThreadServerForwardingPrefix, - } of watcherConnectionData.workers) { - if (lazy.DevToolsUtils.isWorkerDebuggerAlive(dbg)) { - dbg.postMessage( - JSON.stringify({ - type: "remove-session-data-entry", - forwardingPrefix: workerThreadServerForwardingPrefix, - dataEntryType: type, - entries, - }) - ); - } - } - } - - handleEvent({ type }) { - // DOMWindowCreated is registered from the WatcherRegistry via `ActorManagerParent.addJSWindowActors` - // as a DOM event to be listened to and so is fired by JSWindowActor platform code. - if (type == "DOMWindowCreated") { - this.onDOMWindowCreated(); - } - } - - _removeExistingWorkerDebuggerListener() { - if (this._workerDebuggerListener) { - lazy.wdm.removeListener(this._workerDebuggerListener); - this._workerDebuggerListener = null; - } - } - - /** - * Part of JSActor API - * https://searchfox.org/mozilla-central/rev/d9f92154813fbd4a528453c33886dc3a74f27abb/dom/chrome-webidl/JSActor.webidl#41-42,52 - * - * > The didDestroy method, if present, will be called after the actor is no - * > longer able to receive any more messages. - */ - didDestroy() { - this._removeExistingWorkerDebuggerListener(); - - for (const [watcherActorID, watcherConnectionData] of this._connections) { - const { connection } = watcherConnectionData; - this._destroyTargetActors(watcherActorID); - - connection.close(); - } - - this._connections.clear(); - } -} - -/** - * Communicate the type and entries to the Worker Target actor, via the WorkerDebugger. - * - * @param {WorkerDebugger} dbg - * @param {String} workerThreadServerForwardingPrefix - * @param {String} type - * Session data type name - * @param {Array} entries - * Session data entries to add or set. - * @param {String} updateType - * Either "add" or "set", to control if we should only add some items, - * or replace the whole data set with the new entries. - * @returns {Promise} Returns a Promise that resolves once the data entry were handled - * by the worker target. - */ -function addOrSetSessionDataEntryInWorkerTarget({ - dbg, - workerThreadServerForwardingPrefix, - type, - entries, - updateType, -}) { - if (!lazy.DevToolsUtils.isWorkerDebuggerAlive(dbg)) { - return Promise.resolve(); - } - - return new Promise(resolve => { - // Wait until we're notified by the worker that the resources are watched. - // This is important so we know existing resources were handled. - const listener = { - onMessage: message => { - message = JSON.parse(message); - if (message.type === "session-data-entry-added-or-set") { - resolve(); - dbg.removeListener(listener); - } - }, - // Resolve if the worker is being destroyed so we don't have a dangling promise. - onClose: () => resolve(), - }; - - dbg.addListener(listener); - - dbg.postMessage( - JSON.stringify({ - type: "add-or-set-session-data-entry", - forwardingPrefix: workerThreadServerForwardingPrefix, - dataEntryType: type, - entries, - updateType, - }) - ); - }); -} -- cgit v1.2.3