WindowGlobalMessageHandler.sys.mjs (7470B)
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 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 11 getMessageHandlerFrameChildActor: 12 "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs", 13 RootMessageHandler: 14 "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs", 15 WindowRealm: "chrome://remote/content/shared/Realm.sys.mjs", 16 }); 17 18 /** 19 * A WindowGlobalMessageHandler is dedicated to debugging a single window 20 * global. It follows the lifecycle of the corresponding window global and will 21 * therefore not survive any navigation. This MessageHandler cannot forward 22 * commands further to other MessageHandlers and represents a leaf node in a 23 * MessageHandler network. 24 */ 25 export class WindowGlobalMessageHandler extends MessageHandler { 26 #innerWindowId; 27 #realms; 28 29 constructor() { 30 super(...arguments); 31 32 this.#innerWindowId = this.context.window.windowGlobalChild.innerWindowId; 33 34 // Maps sandbox names to instances of window realms. 35 this.#realms = new Map(); 36 } 37 38 initialize(sessionDataItems) { 39 // Create the default realm, it is mapped to an empty string sandbox name. 40 this.#realms.set("", this.#createRealm()); 41 42 // This method, even though being async, is not awaited on purpose, 43 // since for now the sessionDataItems are passed in response to an event in a for loop. 44 this.#applyInitialSessionDataItems(sessionDataItems); 45 46 // With the session data applied the handler is now ready to be used. 47 this.emitEvent("window-global-handler-created", { 48 contextId: this.contextId, 49 innerWindowId: this.#innerWindowId, 50 }); 51 } 52 53 destroy() { 54 for (const realm of this.#realms.values()) { 55 realm.destroy(); 56 } 57 this.emitEvent("windowglobal-pagehide", { 58 context: this.context, 59 innerWindowId: this.innerWindowId, 60 }); 61 this.#realms = null; 62 63 super.destroy(); 64 } 65 66 /** 67 * Returns the WindowGlobalMessageHandler module path. 68 * 69 * @returns {string} 70 */ 71 static get modulePath() { 72 return "windowglobal"; 73 } 74 75 /** 76 * Returns the WindowGlobalMessageHandler type. 77 * 78 * @returns {string} 79 */ 80 static get type() { 81 return "WINDOW_GLOBAL"; 82 } 83 84 /** 85 * For WINDOW_GLOBAL MessageHandlers, `context` is a BrowsingContext, 86 * and BrowsingContext.id can be used as the context id. 87 * 88 * @param {BrowsingContext} context 89 * WindowGlobalMessageHandler contexts are expected to be 90 * BrowsingContexts. 91 * @returns {string} 92 * The browsing context id. 93 */ 94 static getIdFromContext(context) { 95 return context.id; 96 } 97 98 get innerWindowId() { 99 return this.#innerWindowId; 100 } 101 102 get realms() { 103 return this.#realms; 104 } 105 106 get window() { 107 return this.context.window; 108 } 109 110 #createRealm(sandboxName = null) { 111 const realm = new lazy.WindowRealm(this.context.window, { 112 sandboxName, 113 }); 114 115 this.emitEvent("realm-created", { 116 realmInfo: realm.getInfo(), 117 innerWindowId: this.innerWindowId, 118 }); 119 120 return realm; 121 } 122 123 #getRealmFromSandboxName(sandboxName = null) { 124 if (sandboxName === null || sandboxName === "") { 125 return this.#realms.get(""); 126 } 127 128 if (this.#realms.has(sandboxName)) { 129 return this.#realms.get(sandboxName); 130 } 131 132 const realm = this.#createRealm(sandboxName); 133 134 this.#realms.set(sandboxName, realm); 135 136 return realm; 137 } 138 139 async #applyInitialSessionDataItems(sessionDataItems) { 140 if (!Array.isArray(sessionDataItems)) { 141 return; 142 } 143 144 const destination = { 145 type: WindowGlobalMessageHandler.type, 146 }; 147 148 // Create a Map with the structure moduleName -> category -> relevant session data items. 149 const structuredUpdates = new Map(); 150 for (const sessionDataItem of sessionDataItems) { 151 const { category, contextDescriptor, moduleName } = sessionDataItem; 152 153 if (!this.matchesContext(contextDescriptor)) { 154 continue; 155 } 156 if (!structuredUpdates.has(moduleName)) { 157 // Skip session data item if the module is not present 158 // for the destination. 159 if (!this.moduleCache.hasModuleClass(moduleName, destination)) { 160 continue; 161 } 162 structuredUpdates.set(moduleName, new Map()); 163 } 164 165 if (!structuredUpdates.get(moduleName).has(category)) { 166 structuredUpdates.get(moduleName).set(category, new Set()); 167 } 168 169 structuredUpdates.get(moduleName).get(category).add(sessionDataItem); 170 } 171 172 const sessionDataPromises = []; 173 174 for (const [moduleName, categories] of structuredUpdates.entries()) { 175 for (const [category, relevantSessionData] of categories.entries()) { 176 sessionDataPromises.push( 177 this.handleCommand({ 178 moduleName, 179 commandName: "_applySessionData", 180 params: { 181 category, 182 sessionData: Array.from(relevantSessionData), 183 initial: true, 184 }, 185 destination, 186 }) 187 ); 188 } 189 } 190 191 await Promise.all(sessionDataPromises); 192 } 193 194 forwardCommand(command) { 195 switch (command.destination.type) { 196 case lazy.RootMessageHandler.type: 197 return lazy 198 .getMessageHandlerFrameChildActor(this) 199 .sendCommand(command, this.sessionId); 200 default: 201 throw new Error( 202 `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".` 203 ); 204 } 205 } 206 207 /** 208 * If <var>realmId</var> is null or not provided get the realm for 209 * a given <var>sandboxName</var>, otherwise find the realm 210 * in the cache with the realm id equal given <var>realmId</var>. 211 * 212 * @param {object} options 213 * @param {string|null=} options.realmId 214 * The realm id. 215 * @param {string=} options.sandboxName 216 * The name of sandbox 217 * 218 * @returns {Realm} 219 * The realm object. 220 */ 221 getRealm(options = {}) { 222 const { realmId = null, sandboxName } = options; 223 if (realmId === null) { 224 return this.#getRealmFromSandboxName(sandboxName); 225 } 226 227 const realm = Array.from(this.#realms.values()).find( 228 realm => realm.id === realmId 229 ); 230 231 if (realm) { 232 return realm; 233 } 234 235 throw new lazy.error.NoSuchFrameError(`Realm with id ${realmId} not found`); 236 } 237 238 /** 239 * Check if the context matches a provided context descriptor. 240 * 241 * @param {object} contextDescriptor 242 * A context descriptor. 243 * @returns {boolean} 244 * Return true if the context matches a provided context descriptor, 245 * false otherwise. 246 */ 247 matchesContext(contextDescriptor) { 248 return this.contextMatchesDescriptor(this.context, contextDescriptor); 249 } 250 251 /** 252 * Send a command to the root MessageHandler. 253 * 254 * @param {Command} command 255 * The command to send to the root MessageHandler. 256 * @returns {Promise} 257 * A promise which resolves with the return value of the command. 258 */ 259 sendRootCommand(command) { 260 return this.handleCommand({ 261 ...command, 262 destination: { 263 type: lazy.RootMessageHandler.type, 264 }, 265 }); 266 } 267 }