/* 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 { MessageHandler } from "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { NavigationManager: "chrome://remote/content/shared/NavigationManager.sys.mjs", RootTransport: "chrome://remote/content/shared/messagehandler/transports/RootTransport.sys.mjs", SessionData: "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs", SessionDataMethod: "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs", WindowGlobalMessageHandler: "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs", }); /** * A RootMessageHandler is the root node of a MessageHandler network. It lives * in the parent process. It can forward commands to MessageHandlers in other * layers (at the moment WindowGlobalMessageHandlers in content processes). */ export class RootMessageHandler extends MessageHandler { #navigationManager; #realms; #rootTransport; #sessionData; /** * Returns the RootMessageHandler module path. * * @returns {string} */ static get modulePath() { return "root"; } /** * Returns the RootMessageHandler type. * * @returns {string} */ static get type() { return "ROOT"; } /** * The ROOT MessageHandler is unique for a given MessageHandler network * (ie for a given sessionId). Reuse the type as context id here. */ static getIdFromContext(context) { return RootMessageHandler.type; } /** * Create a new RootMessageHandler instance. * * @param {string} sessionId * ID of the session the handler is used for. */ constructor(sessionId) { super(sessionId, null); this.#rootTransport = new lazy.RootTransport(this); this.#sessionData = new lazy.SessionData(this); this.#navigationManager = new lazy.NavigationManager(); this.#navigationManager.startMonitoring(); // Map with inner window ids as keys, and sets of realm ids, assosiated with // this window as values. this.#realms = new Map(); // In the general case, we don't get notified that realms got destroyed, // because there is no communication between content and parent process at this moment, // so we have to listen to the this notification to clean up the internal // map and trigger the events. Services.obs.addObserver(this, "window-global-destroyed"); } get navigationManager() { return this.#navigationManager; } get realms() { return this.#realms; } get sessionData() { return this.#sessionData; } destroy() { this.#sessionData.destroy(); this.#navigationManager.destroy(); Services.obs.removeObserver(this, "window-global-destroyed"); this.#realms = null; super.destroy(); } /** * Add new session data items of a given module, category and * contextDescriptor. * * Forwards the call to the SessionData instance owned by this * RootMessageHandler and propagates the information via a command to existing * MessageHandlers. */ addSessionDataItem(sessionData = {}) { sessionData.method = lazy.SessionDataMethod.Add; return this.updateSessionData([sessionData]); } emitEvent(name, eventPayload, contextInfo) { // Intercept realm created and destroyed events to update internal map. if (name === "realm-created") { this.#onRealmCreated(eventPayload); } // We receive this events in the case of moving the page to BFCache. if (name === "windowglobal-pagehide") { this.#cleanUpRealmsForWindow( eventPayload.innerWindowId, eventPayload.context ); } super.emitEvent(name, eventPayload, contextInfo); } /** * Emit a public protocol event. This event will be sent over to the client. * * @param {string} name * Name of the event. Protocol level events should be of the * form [module name].[event name]. * @param {object} data * The event's data. */ emitProtocolEvent(name, data) { this.emit("message-handler-protocol-event", { name, data, sessionId: this.sessionId, }); } /** * Forward the provided command to WINDOW_GLOBAL MessageHandlers via the * RootTransport. * * @param {Command} command * The command to forward. See type definition in MessageHandler.js * @returns {Promise} * Returns a promise that resolves with the result of the command. */ forwardCommand(command) { switch (command.destination.type) { case lazy.WindowGlobalMessageHandler.type: return this.#rootTransport.forwardCommand(command); default: throw new Error( `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".` ); } } matchesContext() { return true; } observe(subject, topic) { if (topic !== "window-global-destroyed") { return; } this.#cleanUpRealmsForWindow( subject.innerWindowId, subject.browsingContext ); } /** * Remove session data items of a given module, category and * contextDescriptor. * * Forwards the call to the SessionData instance owned by this * RootMessageHandler and propagates the information via a command to existing * MessageHandlers. */ removeSessionDataItem(sessionData = {}) { sessionData.method = lazy.SessionDataMethod.Remove; return this.updateSessionData([sessionData]); } /** * Update session data items of a given module, category and * contextDescriptor. * * Forwards the call to the SessionData instance owned by this * RootMessageHandler. */ async updateSessionData(sessionData = []) { await this.#sessionData.updateSessionData(sessionData); } #cleanUpRealmsForWindow(innerWindowId, context) { const realms = this.#realms.get(innerWindowId); if (!realms) { return; } realms.forEach(realm => { this.#realms.get(innerWindowId).delete(realm); this.emitEvent("realm-destroyed", { context, realm, }); }); this.#realms.delete(innerWindowId); } #onRealmCreated = data => { const { innerWindowId, realmInfo } = data; if (!this.#realms.has(innerWindowId)) { this.#realms.set(innerWindowId, new Set()); } this.#realms.get(innerWindowId).add(realmInfo.realm); }; }