RootMessageHandler.sys.mjs (6631B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { MessageHandler } from "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 NavigationManager: "chrome://remote/content/shared/NavigationManager.sys.mjs", 11 RootTransport: 12 "chrome://remote/content/shared/messagehandler/transports/RootTransport.sys.mjs", 13 SessionData: 14 "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs", 15 SessionDataMethod: 16 "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs", 17 WindowGlobalMessageHandler: 18 "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs", 19 }); 20 21 /** 22 * A RootMessageHandler is the root node of a MessageHandler network. It lives 23 * in the parent process. It can forward commands to MessageHandlers in other 24 * layers (at the moment WindowGlobalMessageHandlers in content processes). 25 */ 26 export class RootMessageHandler extends MessageHandler { 27 #navigationManager; 28 #realms; 29 #rootTransport; 30 #sessionData; 31 32 /** 33 * Returns the RootMessageHandler module path. 34 * 35 * @returns {string} 36 */ 37 static get modulePath() { 38 return "root"; 39 } 40 41 /** 42 * Returns the RootMessageHandler type. 43 * 44 * @returns {string} 45 */ 46 static get type() { 47 return "ROOT"; 48 } 49 50 /** 51 * The ROOT MessageHandler is unique for a given MessageHandler network 52 * (ie for a given sessionId). Reuse the type as context id here. 53 */ 54 static getIdFromContext() { 55 return RootMessageHandler.type; 56 } 57 58 /** 59 * Create a new RootMessageHandler instance. 60 * 61 * @param {string} sessionId 62 * ID of the session the handler is used for. 63 */ 64 constructor(sessionId) { 65 super(sessionId, null); 66 67 this.#rootTransport = new lazy.RootTransport(this); 68 this.#sessionData = new lazy.SessionData(this); 69 this.#navigationManager = new lazy.NavigationManager(); 70 this.#navigationManager.startMonitoring(); 71 72 // Map with inner window ids as keys, and sets of realm ids, associated with 73 // this window as values. 74 this.#realms = new Map(); 75 // In the general case, we don't get notified that realms got destroyed, 76 // because there is no communication between content and parent process at this moment, 77 // so we have to listen to the this notification to clean up the internal 78 // map and trigger the events. 79 Services.obs.addObserver(this, "window-global-destroyed"); 80 } 81 82 get navigationManager() { 83 return this.#navigationManager; 84 } 85 86 get realms() { 87 return this.#realms; 88 } 89 90 get sessionData() { 91 return this.#sessionData; 92 } 93 94 destroy() { 95 this.#sessionData.destroy(); 96 this.#navigationManager.destroy(); 97 98 Services.obs.removeObserver(this, "window-global-destroyed"); 99 this.#realms = null; 100 101 super.destroy(); 102 } 103 104 /** 105 * Add new session data items of a given module, category and 106 * contextDescriptor. 107 * 108 * Forwards the call to the SessionData instance owned by this 109 * RootMessageHandler and propagates the information via a command to existing 110 * MessageHandlers. 111 */ 112 addSessionDataItem(sessionData = {}) { 113 sessionData.method = lazy.SessionDataMethod.Add; 114 return this.updateSessionData([sessionData]); 115 } 116 117 emitEvent(name, eventPayload, contextInfo) { 118 // Intercept realm created and destroyed events to update internal map. 119 if (name === "realm-created") { 120 this.#onRealmCreated(eventPayload); 121 } 122 // We receive this events in the case of moving the page to BFCache. 123 if (name === "windowglobal-pagehide") { 124 this.#cleanUpRealmsForWindow( 125 eventPayload.innerWindowId, 126 eventPayload.context 127 ); 128 } 129 130 super.emitEvent(name, eventPayload, contextInfo); 131 } 132 133 /** 134 * Emit a public protocol event. This event will be sent over to the client. 135 * 136 * @param {string} name 137 * Name of the event. Protocol level events should be of the 138 * form [module name].[event name]. 139 * @param {object} data 140 * The event's data. 141 */ 142 emitProtocolEvent(name, data) { 143 this.emit("message-handler-protocol-event", { 144 name, 145 data, 146 sessionId: this.sessionId, 147 }); 148 } 149 150 /** 151 * Forward the provided command to WINDOW_GLOBAL MessageHandlers via the 152 * RootTransport. 153 * 154 * @param {Command} command 155 * The command to forward. See type definition in MessageHandler.js 156 * @returns {Promise} 157 * Returns a promise that resolves with the result of the command. 158 */ 159 forwardCommand(command) { 160 switch (command.destination.type) { 161 case lazy.WindowGlobalMessageHandler.type: 162 return this.#rootTransport.forwardCommand(command); 163 default: 164 throw new Error( 165 `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".` 166 ); 167 } 168 } 169 170 matchesContext() { 171 return true; 172 } 173 174 observe(subject, topic) { 175 if (topic !== "window-global-destroyed") { 176 return; 177 } 178 179 this.#cleanUpRealmsForWindow( 180 subject.innerWindowId, 181 subject.browsingContext 182 ); 183 } 184 185 /** 186 * Remove session data items of a given module, category and 187 * contextDescriptor. 188 * 189 * Forwards the call to the SessionData instance owned by this 190 * RootMessageHandler and propagates the information via a command to existing 191 * MessageHandlers. 192 */ 193 removeSessionDataItem(sessionData = {}) { 194 sessionData.method = lazy.SessionDataMethod.Remove; 195 return this.updateSessionData([sessionData]); 196 } 197 198 /** 199 * Update session data items of a given module, category and 200 * contextDescriptor. 201 * 202 * Forwards the call to the SessionData instance owned by this 203 * RootMessageHandler. 204 */ 205 async updateSessionData(sessionData = []) { 206 await this.#sessionData.updateSessionData(sessionData); 207 } 208 209 #cleanUpRealmsForWindow(innerWindowId, context) { 210 const realms = this.#realms.get(innerWindowId); 211 212 if (!realms) { 213 return; 214 } 215 216 realms.forEach(realm => { 217 this.#realms.get(innerWindowId).delete(realm); 218 219 this.emitEvent("realm-destroyed", { 220 context, 221 realm, 222 }); 223 }); 224 225 this.#realms.delete(innerWindowId); 226 } 227 228 #onRealmCreated = data => { 229 const { innerWindowId, realmInfo } = data; 230 231 if (!this.#realms.has(innerWindowId)) { 232 this.#realms.set(innerWindowId, new Set()); 233 } 234 235 this.#realms.get(innerWindowId).add(realmInfo.realm); 236 }; 237 }