/* 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 { ContextDescriptorType, MessageHandler, } from "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", getMessageHandlerFrameChildActor: "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs", RootMessageHandler: "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs", WindowRealm: "chrome://remote/content/shared/Realm.sys.mjs", }); /** * A WindowGlobalMessageHandler is dedicated to debugging a single window * global. It follows the lifecycle of the corresponding window global and will * therefore not survive any navigation. This MessageHandler cannot forward * commands further to other MessageHandlers and represents a leaf node in a * MessageHandler network. */ export class WindowGlobalMessageHandler extends MessageHandler { #innerWindowId; #realms; constructor() { super(...arguments); this.#innerWindowId = this.context.window.windowGlobalChild.innerWindowId; // Maps sandbox names to instances of window realms, // the default realm is mapped to an empty string sandbox name. this.#realms = new Map([["", new lazy.WindowRealm(this.context.window)]]); } destroy() { for (const realm of this.#realms.values()) { realm.destroy(); } this.#realms = null; super.destroy(); } /** * Returns the WindowGlobalMessageHandler module path. * * @returns {string} */ static get modulePath() { return "windowglobal"; } /** * Returns the WindowGlobalMessageHandler type. * * @returns {string} */ static get type() { return "WINDOW_GLOBAL"; } /** * For WINDOW_GLOBAL MessageHandlers, `context` is a BrowsingContext, * and BrowsingContext.id can be used as the context id. * * @param {BrowsingContext} context * WindowGlobalMessageHandler contexts are expected to be * BrowsingContexts. * @returns {string} * The browsing context id. */ static getIdFromContext(context) { return context.id; } get innerWindowId() { return this.#innerWindowId; } get realms() { return this.#realms; } get window() { return this.context.window; } #getRealmFromSandboxName(sandboxName = null) { if (sandboxName === null || sandboxName === "") { return this.#realms.get(""); } if (this.#realms.has(sandboxName)) { return this.#realms.get(sandboxName); } const realm = new lazy.WindowRealm(this.context.window, { sandboxName, }); this.#realms.set(sandboxName, realm); return realm; } async applyInitialSessionDataItems(sessionDataItems) { if (!Array.isArray(sessionDataItems)) { return; } const destination = { type: WindowGlobalMessageHandler.type, }; // Create a Map with the structure moduleName -> category -> relevant session data items. const structuredUpdates = new Map(); for (const sessionDataItem of sessionDataItems) { const { category, contextDescriptor, moduleName } = sessionDataItem; if (!this.matchesContext(contextDescriptor)) { continue; } if (!structuredUpdates.has(moduleName)) { // Skip session data item if the module is not present // for the destination. if (!this.moduleCache.hasModuleClass(moduleName, destination)) { continue; } structuredUpdates.set(moduleName, new Map()); } if (!structuredUpdates.get(moduleName).has(category)) { structuredUpdates.get(moduleName).set(category, new Set()); } structuredUpdates.get(moduleName).get(category).add(sessionDataItem); } const sessionDataPromises = []; for (const [moduleName, categories] of structuredUpdates.entries()) { for (const [category, relevantSessionData] of categories.entries()) { sessionDataPromises.push( this.handleCommand({ moduleName, commandName: "_applySessionData", params: { category, initial: true, sessionData: Array.from(relevantSessionData), }, destination, }) ); } } await Promise.all(sessionDataPromises); // With the session data applied the handler is now ready to be used. this.emitEvent("window-global-handler-created", { contextId: this.contextId, innerWindowId: this.#innerWindowId, }); } forwardCommand(command) { switch (command.destination.type) { case lazy.RootMessageHandler.type: return lazy .getMessageHandlerFrameChildActor(this) .sendCommand(command, this.sessionId); default: throw new Error( `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".` ); } } /** * If realmId is null or not provided get the realm for * a given sandboxName, otherwise find the realm * in the cache with the realm id equal given realmId. * * @param {object} options * @param {string|null=} options.realmId * The realm id. * @param {string=} options.sandboxName * The name of sandbox * * @returns {Realm} * The realm object. */ getRealm(options = {}) { const { realmId = null, sandboxName } = options; if (realmId === null) { return this.#getRealmFromSandboxName(sandboxName); } const realm = Array.from(this.#realms.values()).find( realm => realm.id === realmId ); if (realm) { return realm; } throw new lazy.error.NoSuchFrameError(`Realm with id ${realmId} not found`); } matchesContext(contextDescriptor) { return ( contextDescriptor.type === ContextDescriptorType.All || (contextDescriptor.type === ContextDescriptorType.TopBrowsingContext && contextDescriptor.id === this.context.browserId) ); } /** * Send a command to the root MessageHandler. * * @param {Command} command * The command to send to the root MessageHandler. * @returns {Promise} * A promise which resolves with the return value of the command. */ sendRootCommand(command) { return this.handleCommand({ ...command, destination: { type: lazy.RootMessageHandler.type, }, }); } }