Realm.sys.mjs (9661B)
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 const lazy = {}; 6 ChromeUtils.defineESModuleGetters(lazy, { 7 addDebuggerToGlobal: "resource://gre/modules/jsdebugger.sys.mjs", 8 9 generateUUID: "chrome://remote/content/shared/UUID.sys.mjs", 10 }); 11 12 ChromeUtils.defineLazyGetter(lazy, "dbg", () => { 13 // eslint-disable-next-line mozilla/reject-globalThis-modification 14 lazy.addDebuggerToGlobal(globalThis); 15 return new Debugger(); 16 }); 17 18 /** 19 * @typedef {string} RealmType 20 */ 21 22 /** 23 * Enum of realm types. 24 * 25 * @readonly 26 * @enum {RealmType} 27 */ 28 export const RealmType = { 29 AudioWorklet: "audio-worklet", 30 DedicatedWorker: "dedicated-worker", 31 PaintWorklet: "paint-worklet", 32 ServiceWorker: "service-worker", 33 SharedWorker: "shared-worker", 34 Window: "window", 35 Worker: "worker", 36 Worklet: "worklet", 37 }; 38 39 /** 40 * Base class that wraps any kind of WebDriver BiDi realm. 41 */ 42 export class Realm { 43 #handleObjectMap; 44 #id; 45 46 constructor() { 47 this.#id = lazy.generateUUID(); 48 49 // Map of unique handles (UUIDs) to objects belonging to this realm. 50 this.#handleObjectMap = new Map(); 51 } 52 53 destroy() { 54 this.#handleObjectMap = null; 55 } 56 57 /** 58 * Get the browsing context of the realm instance. 59 */ 60 get browsingContext() { 61 return null; 62 } 63 64 /** 65 * Get the unique identifier of the realm instance. 66 * 67 * @returns {string} The unique identifier. 68 */ 69 get id() { 70 return this.#id; 71 } 72 73 /** 74 * A getter to get a realm origin. 75 * 76 * It's required to be implemented in the sub class. 77 */ 78 get origin() { 79 throw new Error("Not implemented"); 80 } 81 82 /** 83 * Ensure the provided object can be used within this realm. 84 85 * @param {object} obj 86 * Any non-primitive object. 87 88 * @returns {object} 89 * An object usable in the current realm. 90 */ 91 cloneIntoRealm(obj) { 92 return obj; 93 } 94 95 /** 96 * Remove the reference corresponding to the provided unique handle. 97 * 98 * @param {string} handle 99 * The unique handle of an object reference tracked in this realm. 100 */ 101 removeObjectHandle(handle) { 102 this.#handleObjectMap.delete(handle); 103 } 104 105 /** 106 * Get a new unique handle for the provided object, creating a strong 107 * reference on the object. 108 * 109 * @param {object} object 110 * Any non-primitive object. 111 * @returns {string} The unique handle created for this strong reference. 112 */ 113 getHandleForObject(object) { 114 const handle = lazy.generateUUID(); 115 this.#handleObjectMap.set(handle, object); 116 return handle; 117 } 118 119 /** 120 * Get the basic realm information. 121 * 122 * @returns {BaseRealmInfo} 123 */ 124 getInfo() { 125 return { 126 realm: this.#id, 127 origin: this.origin, 128 }; 129 } 130 131 /** 132 * Retrieve the object corresponding to the provided unique handle. 133 * 134 * @param {string} handle 135 * The unique handle of an object reference tracked in this realm. 136 * @returns {object} object 137 * Any non-primitive object. 138 */ 139 getObjectForHandle(handle) { 140 return this.#handleObjectMap.get(handle); 141 } 142 } 143 144 /** 145 * Wrapper for Window realms including sandbox objects. 146 */ 147 export class WindowRealm extends Realm { 148 #realmAutomationFeaturesEnabled; 149 #globalObject; 150 #globalObjectReference; 151 #isSandbox; 152 #sandboxName; 153 #userActivationEnabled; 154 #window; 155 156 static type = RealmType.Window; 157 158 /** 159 * 160 * @param {Window} window 161 * The window global to wrap. 162 * @param {object} options 163 * @param {string=} options.sandboxName 164 * Name of the sandbox to create if specified. Defaults to `null`. 165 */ 166 constructor(window, options = {}) { 167 const { sandboxName = null } = options; 168 169 super(); 170 171 this.#isSandbox = sandboxName !== null; 172 this.#sandboxName = sandboxName; 173 this.#window = window; 174 this.#globalObject = this.#isSandbox ? this.#createSandbox() : this.#window; 175 this.#globalObjectReference = lazy.dbg.makeGlobalObjectReference( 176 this.#globalObject 177 ); 178 this.#realmAutomationFeaturesEnabled = false; 179 this.#userActivationEnabled = false; 180 } 181 182 destroy() { 183 if (this.#realmAutomationFeaturesEnabled) { 184 lazy.dbg.disableAsyncStack(this.#globalObject); 185 lazy.dbg.disableUnlimitedStacksCapturing(this.#globalObject); 186 this.#realmAutomationFeaturesEnabled = false; 187 } 188 189 this.#globalObjectReference = null; 190 this.#globalObject = null; 191 this.#window = null; 192 193 super.destroy(); 194 } 195 196 get browsingContext() { 197 return this.#window.browsingContext; 198 } 199 200 get globalObject() { 201 return this.#globalObject; 202 } 203 204 get globalObjectReference() { 205 return this.#globalObjectReference; 206 } 207 208 get isSandbox() { 209 return this.#isSandbox; 210 } 211 212 get origin() { 213 return this.#window.origin; 214 } 215 216 get userActivationEnabled() { 217 return this.#userActivationEnabled; 218 } 219 220 set userActivationEnabled(enable) { 221 if (enable === this.#userActivationEnabled) { 222 return; 223 } 224 225 const document = this.#window.document; 226 if (enable) { 227 document.notifyUserGestureActivation(); 228 } else { 229 document.clearUserGestureActivation(); 230 } 231 232 this.#userActivationEnabled = enable; 233 } 234 235 #createDebuggerObject(obj) { 236 return this.#globalObjectReference.makeDebuggeeValue(obj); 237 } 238 239 #createSandbox() { 240 const win = this.#window; 241 const opts = { 242 sameZoneAs: win, 243 sandboxPrototype: win, 244 wantComponents: false, 245 wantXrays: true, 246 }; 247 248 return new Cu.Sandbox(win, opts); 249 } 250 251 #enableRealmAutomationFeatures() { 252 if (!this.#realmAutomationFeaturesEnabled) { 253 lazy.dbg.enableAsyncStack(this.#globalObject); 254 lazy.dbg.enableUnlimitedStacksCapturing(this.#globalObject); 255 this.#realmAutomationFeaturesEnabled = true; 256 } 257 } 258 259 /** 260 * Clone the provided object into the scope of this Realm (either a window 261 * global, or a sandbox). 262 * 263 * @param {object} obj 264 * Any non-primitive object. 265 * 266 * @returns {object} 267 * The cloned object. 268 */ 269 cloneIntoRealm(obj) { 270 return Cu.cloneInto(obj, this.#globalObject, { cloneFunctions: true }); 271 } 272 273 /** 274 * Evaluates a provided expression in the context of the current realm. 275 * 276 * @param {string} expression 277 * The expression to evaluate. 278 * 279 * @returns {object} 280 * - evaluationStatus {EvaluationStatus} One of "normal", "throw". 281 * - exceptionDetails {ExceptionDetails=} the details of the exception if 282 * the evaluation status was "throw". 283 * - result {RemoteValue=} the result of the evaluation serialized as a 284 * RemoteValue if the evaluation status was "normal". 285 */ 286 executeInGlobal(expression) { 287 this.#enableRealmAutomationFeatures(); 288 return this.#globalObjectReference.executeInGlobal(expression, { 289 bypassCSP: true, 290 url: this.#window.document.baseURI, 291 }); 292 } 293 294 /** 295 * Call a function in the context of the current realm. 296 * 297 * @param {string} functionDeclaration 298 * The body of the function to call. 299 * @param {Array<object>} functionArguments 300 * The arguments to pass to the function call. 301 * @param {object} thisParameter 302 * The value of the `this` keyword for the function call. 303 * 304 * @returns {object} 305 * - evaluationStatus {EvaluationStatus} One of "normal", "throw". 306 * - exceptionDetails {ExceptionDetails=} the details of the exception if 307 * the evaluation status was "throw". 308 * - result {RemoteValue=} the result of the evaluation serialized as a 309 * RemoteValue if the evaluation status was "normal". 310 */ 311 executeInGlobalWithBindings( 312 functionDeclaration, 313 functionArguments, 314 thisParameter 315 ) { 316 this.#enableRealmAutomationFeatures(); 317 const expression = `(${functionDeclaration}).apply(__bidi_this, __bidi_args)`; 318 319 const args = this.cloneIntoRealm([]); 320 for (const arg of functionArguments) { 321 args.push(arg); 322 } 323 324 return this.#globalObjectReference.executeInGlobalWithBindings( 325 expression, 326 { 327 __bidi_args: this.#createDebuggerObject(args), 328 __bidi_this: this.#createDebuggerObject(thisParameter), 329 }, 330 { 331 bypassCSP: true, 332 url: this.#window.document.baseURI, 333 } 334 ); 335 } 336 337 /** 338 * Get the realm information. 339 * 340 * @returns {object} 341 * - context {BrowsingContext} The browsing context, associated with the realm. 342 * - id {string} The realm unique identifier. 343 * - origin {string} The serialization of an origin. 344 * - sandbox {string=} The name of the sandbox. 345 * - type {RealmType.Window} The window realm type. 346 */ 347 getInfo() { 348 const baseInfo = super.getInfo(); 349 const info = { 350 ...baseInfo, 351 context: this.#window.browsingContext, 352 type: WindowRealm.type, 353 }; 354 355 if (this.#isSandbox) { 356 info.sandbox = this.#sandboxName; 357 } 358 359 return info; 360 } 361 362 /** 363 * Log an error caused by a script evaluation. 364 * 365 * @param {string} message 366 * The error message. 367 * @param {Stack} stack 368 * The JavaScript stack trace. 369 */ 370 reportError(message, stack) { 371 const { column, line } = stack; 372 373 const scriptErrorClass = Cc["@mozilla.org/scripterror;1"]; 374 const scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError); 375 376 scriptError.initWithWindowID( 377 message, 378 this.#window.document.baseURI, 379 line, 380 column, 381 Ci.nsIScriptError.errorFlag, 382 "content javascript", 383 this.#window.windowGlobalChild.innerWindowId 384 ); 385 Services.console.logMessage(scriptError); 386 } 387 }