diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-15 03:35:49 +0000 |
commit | d8bbc7858622b6d9c278469aab701ca0b609cddf (patch) | |
tree | eff41dc61d9f714852212739e6b3738b82a2af87 /devtools/server/connectors/js-window-actor | |
parent | Releasing progress-linux version 125.0.3-1~progress7.99u1. (diff) | |
download | firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip |
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/server/connectors/js-window-actor')
6 files changed, 0 insertions, 1941 deletions
diff --git a/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs deleted file mode 100644 index acb5e97110..0000000000 --- a/devtools/server/connectors/js-window-actor/DevToolsFrameChild.sys.mjs +++ /dev/null @@ -1,710 +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 * as Loader from "resource://devtools/shared/loader/Loader.sys.mjs"; - -const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - isWindowGlobalPartOfContext: - "resource://devtools/server/actors/watcher/browsing-context-helpers.sys.mjs", - releaseDistinctSystemPrincipalLoader: - "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", - TargetActorRegistry: - "resource://devtools/server/actors/targets/target-actor-registry.sys.mjs", - useDistinctSystemPrincipalLoader: - "resource://devtools/shared/loader/DistinctSystemPrincipalLoader.sys.mjs", - WindowGlobalLogger: - "resource://devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs", -}); - -const isEveryFrameTargetEnabled = Services.prefs.getBoolPref( - "devtools.every-frame-target.enabled", - false -); - -// Name of the attribute into which we save data in `sharedData` object. -const SHARED_DATA_KEY_NAME = "DevTools:watchedPerWatcher"; - -// If true, log info about WindowGlobal's being created. -const DEBUG = false; -function logWindowGlobal(windowGlobal, message) { - if (!DEBUG) { - return; - } - lazy.WindowGlobalLogger.logWindowGlobal(windowGlobal, message); -} - -export class DevToolsFrameChild 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 - // - actor: the WindowGlobalTargetActor instance - this._connections = new Map(); - - EventEmitter.decorate(this); - - // Set the following preference on the constructor, so that we can easily - // toggle these preferences on and off from tests and have the new value being picked up. - - // bfcache-in-parent changes significantly how navigation behaves. - // We may start reusing previously existing WindowGlobal and so reuse - // previous set of JSWindowActor pairs (i.e. DevToolsFrameParent/DevToolsFrameChild). - // When enabled, regular navigations may also change and spawn new BrowsingContexts. - // If the page we navigate from supports being stored in bfcache, - // the navigation will use a new BrowsingContext. And so force spawning - // a new top-level target. - ChromeUtils.defineLazyGetter( - this, - "isBfcacheInParentEnabled", - () => - Services.appinfo.sessionHistoryInParent && - Services.prefs.getBoolPref("fission.bfcacheInParent", false) - ); - } - - /** - * Try to instantiate new target actors for the current WindowGlobal, and that, - * for all the currently registered Watcher actors. - * - * Read the `sharedData` to get metadata about all registered watcher actors. - * If these watcher actors are interested in the current WindowGlobal, - * instantiate a new dedicated target actor for each of the watchers. - * - * @param Object options - * @param Boolean options.isBFCache - * True, if the request to instantiate a new target comes from a bfcache navigation. - * i.e. when we receive a pageshow event with persisted=true. - * This will be true regardless of bfcacheInParent being enabled or disabled. - * @param Boolean options.ignoreIfExisting - * By default to false. If true is passed, we avoid instantiating a target actor - * if one already exists for this windowGlobal. - */ - instantiate({ isBFCache = false, ignoreIfExisting = false } = {}) { - 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 BrowsingContext, but `sharedData` is empty about watched targets" - ); - } - - // Create one Target actor for each prefix/client which listen to frames - for (const [watcherActorID, sessionData] of sessionDataByWatcherActor) { - const { connectionPrefix, sessionContext } = sessionData; - // For bfcache navigations, we only create new targets when bfcacheInParent is enabled, - // as this would be the only case where new DocShells will be created. This requires us to spawn a - // new WindowGlobalTargetActor as one such actor is bound to a unique DocShell. - const forceAcceptTopLevelTarget = - isBFCache && this.isBfcacheInParentEnabled; - if ( - sessionData.targets?.includes("frame") && - lazy.isWindowGlobalPartOfContext(this.manager, sessionContext, { - forceAcceptTopLevelTarget, - }) - ) { - // If this was triggered because of a navigation, we want to retrieve the existing - // target we were debugging so we can destroy it before creating the new target. - // This is important because we had cases where the destruction of an old target - // was unsetting a flag on the **new** target document, breaking the toolbox (See Bug 1721398). - - // We're checking for an existing target given a watcherActorID + browserId + browsingContext - // Note that a target switch might create a new browsing context, so we wouldn't - // retrieve the existing target here. We are okay with this as: - // - this shouldn't happen much - // - in such case we weren't seeing the issue of Bug 1721398 (the old target can't access the new document) - const existingTarget = this._findTargetActor({ - watcherActorID, - sessionContext, - browsingContextId: this.manager.browsingContext.id, - }); - - // See comment in handleEvent(DOMDocElementInserted) to know why we try to - // create targets if none already exists - if (existingTarget && ignoreIfExisting) { - continue; - } - - // Bail if there is already an existing WindowGlobalTargetActor which wasn't - // created from a JSWIndowActor. - // This means we are reloading or navigating (same-process) a Target - // which has not been created using the Watcher, but from the client (most likely - // the initial target of a local-tab toolbox). - // However, we force overriding the first message manager based target in case of - // BFCache navigations. - if ( - existingTarget && - !existingTarget.createdFromJsWindowActor && - !isBFCache - ) { - continue; - } - - // If we decide to instantiate a new target and there was one before, - // first destroy the previous one. - // Otherwise its destroy sequence will be executed *after* the new one - // is being initialized and may easily revert changes made against platform API. - // (typically toggle platform boolean attributes back to default…) - if (existingTarget) { - existingTarget.destroy({ isTargetSwitching: true }); - } - - this._createTargetActor({ - watcherActorID, - parentConnectionPrefix: connectionPrefix, - sessionData, - isDocumentCreation: true, - }); - } - } - } - - /** - * Instantiate a new WindowGlobalTarget for the given connection. - * - * @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 - * All data managed by the Watcher Actor and WatcherRegistry.sys.mjs, containing - * target types, resources types to be listened as well as breakpoints and any - * other data meant to be shared across processes and threads. - * @param Boolean options.isDocumentCreation - * Set to true if the function is called from `instantiate`, i.e. when we're - * handling a new document being created. - * @param Boolean options.fromInstantiateAlreadyAvailable - * Set to true if the function is called from handling `DevToolsFrameParent:instantiate-already-available` - * query. - */ - _createTargetActor({ - watcherActorID, - parentConnectionPrefix, - sessionData, - isDocumentCreation, - fromInstantiateAlreadyAvailable, - }) { - if (this._connections.get(watcherActorID)) { - // If this function is called as a result of a `DevToolsFrameParent:instantiate-already-available` - // message, we might have a legitimate race condition: - // In frame-helper, we want to create the initial targets for a given browser element. - // It might happen that the `DevToolsFrameParent:instantiate-already-available` is - // aborted if the page navigates (and the document is destroyed) while the query is still pending. - // In such case, frame-helper will try to send a new message. In the meantime, - // the DevToolsFrameChild `DOMWindowCreated` handler may already have been called and - // the new target already created. - // We don't want to throw in such case, as our end-goal, having a target for the document, - // is achieved. - if (fromInstantiateAlreadyAvailable) { - return; - } - throw new Error( - "DevToolsFrameChild _createTargetActor was called more than once" + - ` for the same Watcher (Actor ID: "${watcherActorID}")` - ); - } - - // 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 - // XXX: WindowGlobal's innerWindowId should be unique across processes, I think. So that should be safe? - // (this.manager == WindowGlobalChild interface) - const forwardingPrefix = - parentConnectionPrefix + "windowGlobal" + this.manager.innerWindowId; - - logWindowGlobal( - this.manager, - "Instantiate WindowGlobalTarget with prefix: " + forwardingPrefix - ); - - const { connection, targetActor } = this._createConnectionAndActor( - forwardingPrefix, - sessionData - ); - const form = targetActor.form(); - // Ensure unregistering and destroying the related DevToolsServerConnection+Transport - // on both content and parent process JSWindowActors. - targetActor.once("destroyed", options => { - // This will destroy the content process one - this._destroyTargetActor(watcherActorID, options); - // And this will destroy the parent process one - try { - this.sendAsyncMessage("DevToolsFrameChild:destroy", { - actors: [ - { - watcherActorID, - form, - }, - ], - options, - }); - } catch (e) { - // Ignore exception when the JSWindowActorChild has already been destroyed. - // We often try to emit this message while the WindowGlobal is in the process of being - // destroyed. We eagerly destroy the target actor during reloads, - // just before the WindowGlobal starts destroying, but sendAsyncMessage - // doesn't have time to complete and throws. - if ( - !e.message.includes("JSWindowActorChild cannot send at the moment") - ) { - throw e; - } - } - }); - this._connections.set(watcherActorID, { - connection, - actor: targetActor, - }); - - // Immediately queue a message for the parent process, - // in order to ensure that the JSWindowActorTransport is instantiated - // before any packet is sent from the content process. - // As the order of messages is guaranteed to be delivered in the order they - // were queued, we don't have to wait for anything around this sendAsyncMessage call. - // In theory, the WindowGlobalTargetActor may emit events in its constructor. - // If it does, such RDP packets may be lost. - // The important point here is to send this message before processing the sessionData, - // which will start the Watcher and start emitting resources on the target actor. - this.sendAsyncMessage("DevToolsFrameChild:connectFromContent", { - watcherActorID, - forwardingPrefix, - actor: targetActor.form(), - }); - - // Pass initialization data to the target actor - for (const type in sessionData) { - // `sessionData` will also contain `browserId` as well as entries with empty arrays, - // which shouldn't be processed. - const entries = sessionData[type]; - if (!Array.isArray(entries) || !entries.length) { - continue; - } - targetActor.addOrSetSessionDataEntry( - type, - entries, - isDocumentCreation, - "set" - ); - } - } - - /** - * @param {string} watcherActorID - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ - _destroyTargetActor(watcherActorID, options) { - const connectionInfo = this._connections.get(watcherActorID); - // This connection has already been cleaned? - if (!connectionInfo) { - throw new Error( - `Trying to destroy a target actor that doesn't exists, or has already been destroyed. Watcher Actor ID:${watcherActorID}` - ); - } - connectionInfo.connection.close(options); - this._connections.delete(watcherActorID); - if (this._connections.size == 0) { - this.didDestroy(options); - } - } - - _createConnectionAndActor(forwardingPrefix, sessionData) { - this.useCustomLoader = this.document.nodePrincipal.isSystemPrincipal; - - if (!this.loader) { - // When debugging chrome pages, use a new dedicated loader, using a distinct chrome compartment. - this.loader = this.useCustomLoader - ? lazy.useDistinctSystemPrincipalLoader(this) - : Loader; - } - const { DevToolsServer } = this.loader.require( - "resource://devtools/server/devtools-server.js" - ); - - const { WindowGlobalTargetActor } = this.loader.require( - "resource://devtools/server/actors/targets/window-global.js" - ); - - DevToolsServer.init(); - - // We want a special server without any root actor and only target-scoped actors. - // We are going to spawn a WindowGlobalTargetActor 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 - ); - - // In the case of the browser toolbox, tab's BrowsingContext don't have - // any parent BC and shouldn't be considered as top-level. - // This is why we check for browserId's. - const browsingContext = this.manager.browsingContext; - const isTopLevelTarget = - !browsingContext.parent && - browsingContext.browserId == sessionData.sessionContext.browserId; - - // Create the actual target actor. - const targetActor = new WindowGlobalTargetActor(connection, { - docShell: this.docShell, - // Targets created from the server side, via Watcher actor and DevToolsFrame JSWindow - // actor pair are following WindowGlobal lifecycle. i.e. will be destroyed on any - // type of navigation/reload. - followWindowGlobalLifeCycle: true, - isTopLevelTarget, - ignoreSubFrames: isEveryFrameTargetEnabled, - sessionContext: sessionData.sessionContext, - }); - // There is no root actor in content processes and so - // the target actor can't be managed by it, but we do have to manage - // the actor to have it working and be registered in the DevToolsServerConnection. - // We make it manage itself and become a top level actor. - targetActor.manage(targetActor); - targetActor.createdFromJsWindowActor = true; - - return { connection, targetActor }; - } - - /** - * Supported Queries - */ - - sendPacket(packet, prefix) { - this.sendAsyncMessage("DevToolsFrameChild:packet", { packet, prefix }); - } - - /** - * JsWindowActor API - */ - - async sendQuery(msg, args) { - try { - const res = await super.sendQuery(msg, args); - return res; - } catch (e) { - console.error("Failed to sendQuery in DevToolsFrameChild", msg); - console.error(e.toString()); - throw e; - } - } - - receiveMessage(message) { - // Assert that the message is intended for this window global, - // except for "packet" messages which use a dedicated routing - if ( - message.name != "DevToolsFrameParent:packet" && - message.data.sessionContext.type == "browser-element" - ) { - 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, - { - forceAcceptTopLevelTarget: true, - } - ) - ) { - throw new Error( - "Mismatch between DevToolsFrameParent and DevToolsFrameChild " + - (this.manager.browsingContext.browserId == browserId - ? "window global shouldn't be notified (isWindowGlobalPartOfContext mismatch)" - : `expected browsing context with browserId ${browserId}, but got ${this.manager.browsingContext.browserId}`) - ); - } - } - switch (message.name) { - case "DevToolsFrameParent:instantiate-already-available": { - const { watcherActorID, connectionPrefix, sessionData } = message.data; - - return this._createTargetActor({ - watcherActorID, - parentConnectionPrefix: connectionPrefix, - sessionData, - fromInstantiateAlreadyAvailable: true, - }); - } - case "DevToolsFrameParent:destroy": { - const { watcherActorID, options } = message.data; - return this._destroyTargetActor(watcherActorID, options); - } - case "DevToolsFrameParent:addOrSetSessionDataEntry": { - const { watcherActorID, sessionContext, type, entries, updateType } = - message.data; - return this._addOrSetSessionDataEntry( - watcherActorID, - sessionContext, - type, - entries, - updateType - ); - } - case "DevToolsFrameParent:removeSessionDataEntry": { - const { watcherActorID, sessionContext, type, entries } = message.data; - return this._removeSessionDataEntry( - watcherActorID, - sessionContext, - type, - entries - ); - } - case "DevToolsFrameParent:packet": - return this.emit("packet-received", message); - default: - throw new Error( - "Unsupported message in DevToolsFrameParent: " + message.name - ); - } - } - - /** - * Return an existing target given a WatcherActor id, a browserId and an optional - * browsing context id. - * /!\ Note that we may have multiple targets for a given (watcherActorId, browserId) couple, - * for example if we have 2 remote iframes sharing the same origin, which is why you - * might want to pass a specific browsing context id to filter the list down. - * - * @param {Object} options - * @param {Object} options.watcherActorID - * @param {Object} options.sessionContext - * @param {Object} options.browsingContextId: Optional browsing context id to narrow the - * search to a specific browsing context. - * - * @returns {WindowGlobalTargetActor|null} - */ - _findTargetActor({ watcherActorID, sessionContext, browsingContextId }) { - // First let's check if a target was created for this watcher actor in this specific - // DevToolsFrameChild instance. - const connectionInfo = this._connections.get(watcherActorID); - const targetActor = connectionInfo ? connectionInfo.actor : null; - if (targetActor) { - return targetActor; - } - - // If we couldn't find such target, we want to see if a target was created for this - // (watcherActorId,browserId, {browsingContextId}) in another DevToolsFrameChild instance. - // This might be the case if we're navigating to a new page with server side target - // enabled and we want to retrieve the target of the page we're navigating from. - if ( - lazy.isWindowGlobalPartOfContext(this.manager, sessionContext, { - forceAcceptTopLevelTarget: true, - }) - ) { - // Ensure retrieving the one target actor related to this connection. - // This allows to distinguish actors created for various toolboxes. - // For ex, regular toolbox versus browser console versus browser toolbox - const connectionPrefix = watcherActorID.replace(/watcher\d+$/, ""); - const targetActors = lazy.TargetActorRegistry.getTargetActors( - sessionContext, - connectionPrefix - ); - - if (!browsingContextId) { - return targetActors[0] || null; - } - return targetActors.find( - actor => actor.browsingContextID === browsingContextId - ); - } - return null; - } - - _addOrSetSessionDataEntry( - watcherActorID, - sessionContext, - type, - entries, - updateType - ) { - // /!\ We may have an issue here as there could be multiple targets for a given - // (watcherActorID,browserId) pair. - // This should be clarified as part of Bug 1725623. - const targetActor = this._findTargetActor({ - watcherActorID, - sessionContext, - }); - - if (!targetActor) { - throw new Error( - `No target actor for this Watcher Actor ID:"${watcherActorID}" / BrowserId:${sessionContext.browserId}` - ); - } - return targetActor.addOrSetSessionDataEntry( - type, - entries, - false, - updateType - ); - } - - _removeSessionDataEntry(watcherActorID, sessionContext, type, entries) { - // /!\ We may have an issue here as there could be multiple targets for a given - // (watcherActorID,browserId) pair. - // This should be clarified as part of Bug 1725623. - const targetActor = this._findTargetActor({ - watcherActorID, - sessionContext, - }); - // By the time we are calling this, the target may already have been destroyed. - if (!targetActor) { - return null; - } - return targetActor.removeSessionDataEntry(type, entries); - } - - handleEvent({ type, persisted, target }) { - // Ignore any event that may fire for children WindowGlobals/documents - if (target != this.document) { - return; - } - - // DOMWindowCreated is registered from FrameWatcher via `ActorManagerParent.addJSWindowActors` - // as a DOM event to be listened to and so is fired by JS Window Actor code platform code. - if (type == "DOMWindowCreated") { - this.instantiate(); - return; - } - // We might have ignored the DOMWindowCreated event because it was the initial about:blank document. - // But when loading same-process iframes, we reuse the WindowGlobal of the about:bank document - // to load the actual URL loaded in the iframe. This means we won't have a new DOMWindowCreated - // for the actual document. There is a DOMDocElementInserted fired just after, that we can catch - // to create a target for same-process iframes. - // This means that we still do not create any target for the initial documents. - // It is complex to instantiate targets for initial documents because: - // - it would mean spawning two targets for the same WindowGlobal and sharing the same innerWindowId - // - or have WindowGlobalTargets to handle more than one document (it would mean reusing will-navigate/window-ready events - // both on client and server) - if (type == "DOMDocElementInserted") { - this.instantiate({ ignoreIfExisting: true }); - return; - } - - // If persisted=true, this is a BFCache navigation. - // - // With Fission enabled and bfcacheInParent, BFCache navigations will spawn a new DocShell - // in the same process: - // * the previous page won't be destroyed, its JSWindowActor will stay alive (`didDestroy` won't be called) - // and a 'pagehide' with persisted=true will be emitted on it. - // * the new page page won't emit any DOMWindowCreated, but instead a pageshow with persisted=true - // will be emitted. - - if (type === "pageshow" && persisted) { - // Notify all bfcache navigations, even the one for which we don't create a new target - // as that's being useful for parent process storage resource watchers. - this.sendAsyncMessage("DevToolsFrameChild:bf-cache-navigation-pageshow"); - - // Here we are going to re-instantiate a target that got destroyed before while processing a pagehide event. - // We force instantiating a new top level target, within `instantiate()` even if server targets are disabled. - // But we only do that if bfcacheInParent is enabled. Otherwise for same-process, same-docshell bfcache navigation, - // we don't want to spawn new targets. - this.instantiate({ - isBFCache: true, - }); - return; - } - - if (type === "pagehide" && persisted) { - // Notify all bfcache navigations, even the one for which we don't create a new target - // as that's being useful for parent process storage resource watchers. - this.sendAsyncMessage("DevToolsFrameChild:bf-cache-navigation-pagehide"); - - // We might navigate away for the first top level target, - // which isn't using JSWindowActor (it still uses messages manager and is created by the client, via TabDescriptor.getTarget). - // We have to unregister it from the TargetActorRegistry, otherwise, - // if we navigate back to it, the next DOMWindowCreated won't create a new target for it. - 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 BrowsingContext, but `sharedData` is empty about watched targets" - ); - } - - const actors = []; - // A flag to know if the following for loop ended up destroying all the actors. - // It may not be the case if one Watcher isn't having server target switching enabled. - let allActorsAreDestroyed = true; - for (const [watcherActorID, sessionData] of sessionDataByWatcherActor) { - const { sessionContext } = sessionData; - - // /!\ We may have an issue here as there could be multiple targets for a given - // (watcherActorID,browserId) pair. - // This should be clarified as part of Bug 1725623. - const existingTarget = this._findTargetActor({ - watcherActorID, - sessionContext, - }); - - if (!existingTarget) { - continue; - } - - // Use `originalWindow` as `window` can be set when a document was selected from - // the iframe picker in the toolbox toolbar. - if (existingTarget.originalWindow.document != target) { - throw new Error("Existing target actor is for a distinct document"); - } - // Do not do anything if both bfcache in parent and server targets are disabled - // As history navigations will be handled within the same DocShell and by the - // same WindowGlobalTargetActor. The actor will listen to pageshow/pagehide by itself. - // We should not destroy any target. - if ( - !this.isBfcacheInParentEnabled && - !sessionContext.isServerTargetSwitchingEnabled - ) { - allActorsAreDestroyed = false; - continue; - } - - actors.push({ - watcherActorID, - form: existingTarget.form(), - }); - existingTarget.destroy(); - } - - if (actors.length) { - // The most important is to unregister the actor from TargetActorRegistry, - // so that it is no longer present in the list when new DOMWindowCreated fires. - // This will also help notify the client that the target has been destroyed. - // And if we navigate back to this target, the client will receive the same target actor ID, - // so that it is really important to destroy it correctly on both server and client. - this.sendAsyncMessage("DevToolsFrameChild:destroy", { actors }); - } - - if (allActorsAreDestroyed) { - // Completely clear this JSWindow Actor. - // Do this after having called _findTargetActor, - // as it would clear the registered target actors. - this.didDestroy(); - } - } - } - - didDestroy(options) { - logWindowGlobal(this.manager, "Destroy WindowGlobalTarget"); - for (const { connection } of this._connections.values()) { - connection.close(options); - } - this._connections.clear(); - - if (this.loader) { - if (this.useCustomLoader) { - lazy.releaseDistinctSystemPrincipalLoader(this); - } - this.loader = null; - } - } -} diff --git a/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs deleted file mode 100644 index 31750d58e4..0000000000 --- a/devtools/server/connectors/js-window-actor/DevToolsFrameParent.sys.mjs +++ /dev/null @@ -1,277 +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 { loader } from "resource://devtools/shared/loader/Loader.sys.mjs"; -import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; - -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. - { global: "shared" } -); - -const lazy = {}; - -loader.lazyRequireGetter( - lazy, - "JsWindowActorTransport", - "resource://devtools/shared/transport/js-window-actor-transport.js", - true -); - -export class DevToolsFrameParent extends JSWindowActorParent { - constructor() { - super(); - - // Map of DevToolsServerConnection's used to forward the messages from/to - // the client. The connections run in the parent process, as this code. We - // may have more than one when there is more than one client debugging the - // same frame. For example, a content toolbox and the browser toolbox. - // - // The map is indexed by the connection prefix. - // The values are objects containing the following properties: - // - actor: the frame target actor(as a form) - // - connection: the DevToolsServerConnection used to communicate with the - // frame target actor - // - prefix: the forwarding prefix used by the connection to know - // how to forward packets to the frame target - // - transport: the JsWindowActorTransport - // - // Reminder about prefixes: all DevToolsServerConnections have a `prefix` - // which can be considered as a kind of id. On top of this, parent process - // DevToolsServerConnections also have forwarding prefixes because they are - // responsible for forwarding messages to content process connections. - this._connections = new Map(); - - this._onConnectionClosed = this._onConnectionClosed.bind(this); - EventEmitter.decorate(this); - } - - /** - * Request the content process to create the Frame Target if there is one - * already available that matches the Browsing Context ID - */ - async instantiateTarget({ - watcherActorID, - connectionPrefix, - sessionContext, - sessionData, - }) { - await this.sendQuery("DevToolsFrameParent:instantiate-already-available", { - watcherActorID, - connectionPrefix, - sessionContext, - sessionData, - }); - } - - /** - * @param {object} arg - * @param {object} arg.sessionContext - * @param {object} arg.options - * @param {boolean} arg.options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ - destroyTarget({ watcherActorID, sessionContext, options }) { - this.sendAsyncMessage("DevToolsFrameParent:destroy", { - watcherActorID, - sessionContext, - options, - }); - } - - /** - * Communicate to the content process that some data have been added. - */ - async addOrSetSessionDataEntry({ - watcherActorID, - sessionContext, - type, - entries, - updateType, - }) { - try { - await this.sendQuery("DevToolsFrameParent:addOrSetSessionDataEntry", { - watcherActorID, - sessionContext, - type, - entries, - updateType, - }); - } catch (e) { - console.warn( - "Failed to add session data entry for frame targets in browsing context", - this.browsingContext.id - ); - console.warn(e); - } - } - - /** - * Communicate to the content process that some data have been removed. - */ - removeSessionDataEntry({ watcherActorID, sessionContext, type, entries }) { - this.sendAsyncMessage("DevToolsFrameParent:removeSessionDataEntry", { - watcherActorID, - sessionContext, - type, - entries, - }); - } - - connectFromContent({ watcherActorID, forwardingPrefix, actor }) { - const watcher = WatcherRegistry.getWatcher(watcherActorID); - - if (!watcher) { - throw new Error( - `Watcher Actor with ID '${watcherActorID}' can't be found.` - ); - } - const connection = watcher.conn; - - connection.on("closed", this._onConnectionClosed); - - // Create a js-window-actor based transport. - const transport = new lazy.JsWindowActorTransport(this, forwardingPrefix); - transport.hooks = { - onPacket: connection.send.bind(connection), - onTransportClosed() {}, - }; - transport.ready(); - - connection.setForwarding(forwardingPrefix, transport); - - this._connections.set(watcher.conn.prefix, { - watcher, - connection, - // This prefix is the prefix of the DevToolsServerConnection, running - // in the content process, for which we should forward packets to, based on its prefix. - // While `watcher.connection` is also a DevToolsServerConnection, but from this process, - // the parent process. It is the one receiving Client packets and the one, from which - // we should forward packets from. - forwardingPrefix, - transport, - actor, - }); - - watcher.notifyTargetAvailable(actor); - } - - _onConnectionClosed(status, connectionPrefix) { - this._unregisterWatcher(connectionPrefix); - } - - /** - * Given a watcher connection prefix, unregister everything related to the Watcher - * in this JSWindowActor. - * - * @param {String} connectionPrefix - * The connection prefix of the watcher to unregister - */ - async _unregisterWatcher(connectionPrefix) { - const connectionInfo = this._connections.get(connectionPrefix); - if (!connectionInfo) { - return; - } - const { forwardingPrefix, transport, connection } = connectionInfo; - this._connections.delete(connectionPrefix); - - connection.off("closed", this._onConnectionClosed); - if (transport) { - // If we have a child transport, the actor has already - // been created. We need to stop using this transport. - transport.close(); - } - - connection.cancelForwarding(forwardingPrefix); - } - - /** - * Destroy everything that we did related to the current WindowGlobal that - * this JSWindow Actor represents: - * - close all transports that were used as bridge to communicate with the - * DevToolsFrameChild, running in the content process - * - unregister these transports from DevToolsServer (cancelForwarding) - * - notify the client, via the WatcherActor that all related targets, - * one per client/connection are all destroyed - * - * Note that with bfcacheInParent, we may reuse a JSWindowActor pair after closing all connections. - * This is can happen outside of the destruction of the actor. - * We may reuse a DevToolsFrameParent and DevToolsFrameChild pair. - * When navigating away, we will destroy them and call this method. - * Then when navigating back, we will reuse the same instances. - * So that we should be careful to keep the class fully function and only clear all its state. - * - * @param {object} options - * @param {boolean} options.isModeSwitching - * true when this is called as the result of a change to the devtools.browsertoolbox.scope pref - */ - _closeAllConnections(options) { - for (const { actor, watcher } of this._connections.values()) { - watcher.notifyTargetDestroyed(actor, options); - this._unregisterWatcher(watcher.conn.prefix); - } - this._connections.clear(); - } - - /** - * Supported Queries - */ - - sendPacket(packet, prefix) { - this.sendAsyncMessage("DevToolsFrameParent:packet", { packet, prefix }); - } - - /** - * JsWindowActor API - */ - - receiveMessage(message) { - switch (message.name) { - case "DevToolsFrameChild:connectFromContent": - return this.connectFromContent(message.data); - case "DevToolsFrameChild:packet": - return this.emit("packet-received", message); - case "DevToolsFrameChild:destroy": - for (const { form, watcherActorID } of message.data.actors) { - const watcher = WatcherRegistry.getWatcher(watcherActorID); - // As we instruct to destroy all targets when the watcher is destroyed, - // we may easily receive the target destruction notification *after* - // the watcher has been removed from the registry. - if (watcher) { - watcher.notifyTargetDestroyed(form, message.data.options); - this._unregisterWatcher(watcher.conn.prefix); - } - } - return null; - case "DevToolsFrameChild:bf-cache-navigation-pageshow": - for (const watcherActor of WatcherRegistry.getWatchersForBrowserId( - this.browsingContext.browserId - )) { - watcherActor.emit("bf-cache-navigation-pageshow", { - windowGlobal: this.browsingContext.currentWindowGlobal, - }); - } - return null; - case "DevToolsFrameChild:bf-cache-navigation-pagehide": - for (const watcherActor of WatcherRegistry.getWatchersForBrowserId( - this.browsingContext.browserId - )) { - watcherActor.emit("bf-cache-navigation-pagehide", { - windowGlobal: this.browsingContext.currentWindowGlobal, - }); - } - return null; - default: - throw new Error( - "Unsupported message in DevToolsFrameParent: " + message.name - ); - } - } - - didDestroy() { - this._closeAllConnections(); - } -} 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, - }) - ); - }); -} diff --git a/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs b/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs deleted file mode 100644 index cb9bffc2ca..0000000000 --- a/devtools/server/connectors/js-window-actor/DevToolsWorkerParent.sys.mjs +++ /dev/null @@ -1,294 +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 { loader } from "resource://devtools/shared/loader/Loader.sys.mjs"; -import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs"; - -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. - { global: "shared" } -); - -const lazy = {}; - -loader.lazyRequireGetter( - lazy, - "JsWindowActorTransport", - "resource://devtools/shared/transport/js-window-actor-transport.js", - true -); - -export class DevToolsWorkerParent extends JSWindowActorParent { - constructor() { - super(); - - this._destroyed = false; - - // Map of DevToolsServerConnection's used to forward the messages from/to - // the client. The connections run in the parent process, as this code. We - // may have more than one when there is more than one client debugging the - // same worker. For example, a content toolbox and the browser toolbox. - // - // The map is indexed by the connection prefix, and the values are object with the - // following properties: - // - watcher: The WatcherActor - // - actors: A Map of the worker target actors form, indexed by WorkerTarget actorID - // - transport: the JsWindowActorTransport - // - // Reminder about prefixes: all DevToolsServerConnections have a `prefix` - // which can be considered as a kind of id. On top of this, parent process - // DevToolsServerConnections also have forwarding prefixes because they are - // responsible for forwarding messages to content process connections. - this._connections = new Map(); - - this._onConnectionClosed = this._onConnectionClosed.bind(this); - EventEmitter.decorate(this); - } - - /** - * Request the content process to create Worker Targets if workers matching the context - * are already available. - */ - async instantiateWorkerTargets({ - watcherActorID, - connectionPrefix, - sessionContext, - sessionData, - }) { - try { - await this.sendQuery( - "DevToolsWorkerParent:instantiate-already-available", - { - watcherActorID, - connectionPrefix, - sessionContext, - sessionData, - } - ); - } catch (e) { - console.warn( - "Failed to create DevTools Worker target for browsingContext", - this.browsingContext.id, - "and watcher actor id", - watcherActorID - ); - console.warn(e); - } - } - - destroyWorkerTargets({ watcherActorID, sessionContext }) { - return this.sendAsyncMessage("DevToolsWorkerParent:destroy", { - watcherActorID, - sessionContext, - }); - } - - /** - * Communicate to the content process that some data have been added. - */ - async addOrSetSessionDataEntry({ - watcherActorID, - sessionContext, - type, - entries, - updateType, - }) { - try { - await this.sendQuery("DevToolsWorkerParent:addOrSetSessionDataEntry", { - watcherActorID, - sessionContext, - type, - entries, - updateType, - }); - } catch (e) { - console.warn( - "Failed to add session data entry for worker targets in browsing context", - this.browsingContext.id, - "and watcher actor id", - watcherActorID - ); - console.warn(e); - } - } - - /** - * Communicate to the content process that some data have been removed. - */ - removeSessionDataEntry({ watcherActorID, sessionContext, type, entries }) { - this.sendAsyncMessage("DevToolsWorkerParent:removeSessionDataEntry", { - watcherActorID, - sessionContext, - type, - entries, - }); - } - - workerTargetAvailable({ - watcherActorID, - forwardingPrefix, - workerTargetForm, - }) { - if (this._destroyed) { - return; - } - - const watcher = WatcherRegistry.getWatcher(watcherActorID); - - if (!watcher) { - throw new Error( - `Watcher Actor with ID '${watcherActorID}' can't be found.` - ); - } - - const connection = watcher.conn; - const { prefix } = connection; - if (!this._connections.has(prefix)) { - connection.on("closed", this._onConnectionClosed); - - // Create a js-window-actor based transport. - const transport = new lazy.JsWindowActorTransport(this, forwardingPrefix); - transport.hooks = { - onPacket: connection.send.bind(connection), - onTransportClosed() {}, - }; - transport.ready(); - - connection.setForwarding(forwardingPrefix, transport); - - this._connections.set(prefix, { - watcher, - transport, - actors: new Map(), - }); - } - - const workerTargetActorId = workerTargetForm.actor; - this._connections - .get(prefix) - .actors.set(workerTargetActorId, workerTargetForm); - watcher.notifyTargetAvailable(workerTargetForm); - } - - workerTargetDestroyed({ watcherActorID, workerTargetForm }) { - const watcher = WatcherRegistry.getWatcher(watcherActorID); - - if (!watcher) { - throw new Error( - `Watcher Actor with ID '${watcherActorID}' can't be found.` - ); - } - - const connection = watcher.conn; - const { prefix } = connection; - if (!this._connections.has(prefix)) { - return; - } - - const workerTargetActorId = workerTargetForm.actor; - const { actors } = this._connections.get(prefix); - if (!actors.has(workerTargetActorId)) { - return; - } - - actors.delete(workerTargetActorId); - watcher.notifyTargetDestroyed(workerTargetForm); - } - - _onConnectionClosed(status, prefix) { - this._unregisterWatcher(prefix); - } - - async _unregisterWatcher(connectionPrefix) { - const connectionInfo = this._connections.get(connectionPrefix); - if (!connectionInfo) { - return; - } - - const { watcher, transport } = connectionInfo; - const connection = watcher.conn; - - connection.off("closed", this._onConnectionClosed); - if (transport) { - // If we have a child transport, the actor has already - // been created. We need to stop using this transport. - connection.cancelForwarding(transport._prefix); - transport.close(); - } - - this._connections.delete(connectionPrefix); - - if (!this._connections.size) { - this._destroy(); - } - } - - _destroy() { - if (this._destroyed) { - return; - } - this._destroyed = true; - - for (const { actors, watcher } of this._connections.values()) { - for (const actor of actors.values()) { - watcher.notifyTargetDestroyed(actor); - } - - this._unregisterWatcher(watcher.conn.prefix); - } - } - - /** - * 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 (JSWindow)actor is no - * > longer able to receive any more messages. - */ - didDestroy() { - this._destroy(); - } - - /** - * Supported Queries - */ - - async sendPacket(packet, prefix) { - return this.sendAsyncMessage("DevToolsWorkerParent:packet", { - packet, - prefix, - }); - } - - /** - * JsWindowActor API - */ - - async sendQuery(msg, args) { - try { - const res = await super.sendQuery(msg, args); - return res; - } catch (e) { - console.error("Failed to sendQuery in DevToolsWorkerParent", msg, e); - throw e; - } - } - - receiveMessage(message) { - switch (message.name) { - case "DevToolsWorkerChild:workerTargetAvailable": - return this.workerTargetAvailable(message.data); - case "DevToolsWorkerChild:workerTargetDestroyed": - return this.workerTargetDestroyed(message.data); - case "DevToolsWorkerChild:packet": - return this.emit("packet-received", message); - default: - throw new Error( - "Unsupported message in DevToolsWorkerParent: " + message.name - ); - } - } -} diff --git a/devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs b/devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs deleted file mode 100644 index ae15c030fe..0000000000 --- a/devtools/server/connectors/js-window-actor/WindowGlobalLogger.sys.mjs +++ /dev/null @@ -1,76 +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/. */ - -function getWindowGlobalUri(windowGlobal) { - let windowGlobalUri = ""; - - if (windowGlobal.documentURI) { - // If windowGlobal is a WindowGlobalParent documentURI should be available. - windowGlobalUri = windowGlobal.documentURI.spec; - } else if (windowGlobal.browsingContext?.window) { - // If windowGlobal is a WindowGlobalChild, this code runs in the same - // process as the document and we can directly access the window.location - // object. - windowGlobalUri = windowGlobal.browsingContext.window.location.href; - if (!windowGlobalUri) { - windowGlobalUri = - windowGlobal.browsingContext.window.document.documentURI; - } - } - - return windowGlobalUri; -} - -export const WindowGlobalLogger = { - /** - * This logger can run from the content or parent process, and windowGlobal - * will either be of type `WindowGlobalParent` or `WindowGlobalChild`. - * - * The interface for each type can be found in WindowGlobalActors.webidl - * (https://searchfox.org/mozilla-central/source/dom/chrome-webidl/WindowGlobalActors.webidl) - * - * @param {WindowGlobalParent|WindowGlobalChild} windowGlobal - * The window global to log. See WindowGlobalActors.webidl for details - * about the types. - * @param {String} message - * A custom message that will be displayed at the beginning of the log. - */ - logWindowGlobal(windowGlobal, message) { - const { browsingContext } = windowGlobal; - const { parent } = browsingContext; - const windowGlobalUri = getWindowGlobalUri(windowGlobal); - const isInitialDocument = - "isInitialDocument" in windowGlobal - ? windowGlobal.isInitialDocument - : windowGlobal.browsingContext.window?.document.isInitialDocument; - - const details = []; - details.push( - "BrowsingContext.browserId: " + browsingContext.browserId, - "BrowsingContext.id: " + browsingContext.id, - "innerWindowId: " + windowGlobal.innerWindowId, - "opener.id: " + browsingContext.opener?.id, - "pid: " + windowGlobal.osPid, - "isClosed: " + windowGlobal.isClosed, - "isInProcess: " + windowGlobal.isInProcess, - "isCurrentGlobal: " + windowGlobal.isCurrentGlobal, - "isProcessRoot: " + windowGlobal.isProcessRoot, - "currentRemoteType: " + browsingContext.currentRemoteType, - "hasParent: " + (parent ? parent.id : "no"), - "uri: " + (windowGlobalUri ? windowGlobalUri : "no uri"), - "isProcessRoot: " + windowGlobal.isProcessRoot, - "BrowsingContext.isContent: " + windowGlobal.browsingContext.isContent, - "isInitialDocument: " + isInitialDocument - ); - - const header = "[WindowGlobalLogger] " + message; - - // Use a padding for multiline display. - const padding = " "; - const formattedDetails = details.map(s => padding + s); - const detailsString = formattedDetails.join("\n"); - - dump(header + "\n" + detailsString + "\n"); - }, -}; diff --git a/devtools/server/connectors/js-window-actor/moz.build b/devtools/server/connectors/js-window-actor/moz.build deleted file mode 100644 index faaaa8dd54..0000000000 --- a/devtools/server/connectors/js-window-actor/moz.build +++ /dev/null @@ -1,13 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# 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/. - -DevToolsModules( - "DevToolsFrameChild.sys.mjs", - "DevToolsFrameParent.sys.mjs", - "DevToolsWorkerChild.sys.mjs", - "DevToolsWorkerParent.sys.mjs", - "WindowGlobalLogger.sys.mjs", -) |