_configuration.sys.mjs (16046B)
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 { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs"; 6 7 const lazy = {}; 8 9 ChromeUtils.defineESModuleGetters(lazy, { 10 ContextDescriptorType: 11 "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs", 12 RootMessageHandler: 13 "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs", 14 WindowGlobalMessageHandler: 15 "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs", 16 }); 17 18 /** 19 * Internal module to set the configuration on the newly created navigables. 20 */ 21 class _ConfigurationModule extends WindowGlobalBiDiModule { 22 #geolocationConfiguration; 23 #localeOverride; 24 #preloadScripts; 25 #resolveBlockerPromise; 26 #screenOrientationOverride; 27 #screenSettingsOverride; 28 #timezoneOverride; 29 #userAgentOverride; 30 #viewportConfiguration; 31 32 constructor(messageHandler) { 33 super(messageHandler); 34 35 this.#geolocationConfiguration = undefined; 36 this.#localeOverride = null; 37 this.#preloadScripts = new Set(); 38 this.#screenOrientationOverride = undefined; 39 this.#screenSettingsOverride = undefined; 40 this.#timezoneOverride = null; 41 this.#userAgentOverride = null; 42 this.#viewportConfiguration = new Map(); 43 44 Services.obs.addObserver(this, "document-element-inserted"); 45 } 46 47 destroy() { 48 // Unblock the document parsing. 49 if (this.#resolveBlockerPromise) { 50 this.#resolveBlockerPromise(); 51 } 52 53 Services.obs.removeObserver(this, "document-element-inserted"); 54 55 this.#preloadScripts = null; 56 this.#viewportConfiguration = null; 57 } 58 59 async observe(subject, topic) { 60 if (topic === "document-element-inserted") { 61 const window = subject?.defaultView; 62 // Ignore events without a window. 63 if (window !== this.messageHandler.window) { 64 return; 65 } 66 67 // Do nothing if there is no configuration to apply. 68 if ( 69 this.#preloadScripts.size === 0 && 70 this.#viewportConfiguration.size === 0 && 71 this.#geolocationConfiguration === undefined && 72 this.#localeOverride === null && 73 this.#screenOrientationOverride === undefined && 74 this.#screenSettingsOverride === undefined && 75 this.#timezoneOverride === null && 76 this.#userAgentOverride === null 77 ) { 78 this.#onConfigurationComplete(window); 79 return; 80 } 81 82 // Block document parsing. 83 const blockerPromise = new Promise(resolve => { 84 this.#resolveBlockerPromise = resolve; 85 }); 86 window.document.blockParsing(blockerPromise); 87 88 // Usually rendering is blocked until layout is started implicitly (by 89 // end of parsing) or explicitly. Since we block the implicit 90 // initialization and some code we call may block on it (like waiting for 91 // requestAnimationFrame or viewport dimensions), we initialize it 92 // explicitly here by forcing a layout flush. Note that this will cause 93 // flashes of unstyled content, but that was already the case before 94 // bug 1958942. 95 window.document.documentElement.getBoundingClientRect(); 96 97 if (this.#geolocationConfiguration !== undefined) { 98 await this.messageHandler.handleCommand({ 99 moduleName: "emulation", 100 commandName: "_setGeolocationOverride", 101 destination: { 102 type: lazy.WindowGlobalMessageHandler.type, 103 id: this.messageHandler.context.id, 104 }, 105 params: { 106 coordinates: this.#geolocationConfiguration, 107 }, 108 }); 109 } 110 111 if (this.#localeOverride !== null) { 112 await this.messageHandler.forwardCommand({ 113 moduleName: "emulation", 114 commandName: "_setLocaleForBrowsingContext", 115 destination: { 116 type: lazy.RootMessageHandler.type, 117 }, 118 params: { 119 context: this.messageHandler.context, 120 value: this.#localeOverride, 121 }, 122 }); 123 } 124 125 // Compare with `undefined`, since `null` value is used as a reset value. 126 if (this.#screenSettingsOverride !== undefined) { 127 await this.messageHandler.forwardCommand({ 128 moduleName: "emulation", 129 commandName: "_setScreenSettingsOverride", 130 destination: { 131 type: lazy.RootMessageHandler.type, 132 }, 133 params: { 134 context: this.messageHandler.context, 135 value: this.#screenSettingsOverride, 136 }, 137 }); 138 } 139 140 if (this.#timezoneOverride !== null) { 141 await this.messageHandler.forwardCommand({ 142 moduleName: "emulation", 143 commandName: "_setTimezoneOverride", 144 destination: { 145 type: lazy.RootMessageHandler.type, 146 }, 147 params: { 148 context: this.messageHandler.context, 149 value: this.#timezoneOverride, 150 }, 151 }); 152 } 153 154 if (this.#userAgentOverride !== null) { 155 await this.messageHandler.forwardCommand({ 156 moduleName: "emulation", 157 commandName: "_setUserAgentOverride", 158 destination: { 159 type: lazy.RootMessageHandler.type, 160 }, 161 params: { 162 context: this.messageHandler.context, 163 value: this.#userAgentOverride, 164 }, 165 }); 166 } 167 168 if (this.#screenOrientationOverride !== undefined) { 169 await this.messageHandler.forwardCommand({ 170 moduleName: "emulation", 171 commandName: "_setEmulatedScreenOrientation", 172 destination: { 173 type: lazy.RootMessageHandler.type, 174 }, 175 params: { 176 context: this.messageHandler.context, 177 value: this.#screenOrientationOverride, 178 }, 179 }); 180 } 181 182 if (this.#viewportConfiguration.size !== 0) { 183 await this.messageHandler.forwardCommand({ 184 moduleName: "browsingContext", 185 commandName: "_updateNavigableViewport", 186 destination: { 187 type: lazy.RootMessageHandler.type, 188 }, 189 params: { 190 navigable: this.messageHandler.context, 191 viewportOverride: Object.fromEntries(this.#viewportConfiguration), 192 }, 193 }); 194 } 195 196 if (this.#preloadScripts.size !== 0) { 197 await this.messageHandler.handleCommand({ 198 moduleName: "script", 199 commandName: "_evaluatePreloadScripts", 200 destination: { 201 type: lazy.WindowGlobalMessageHandler.type, 202 id: this.messageHandler.context.id, 203 }, 204 params: { 205 scripts: this.#preloadScripts, 206 }, 207 }); 208 } 209 210 // Continue script parsing. 211 this.#resolveBlockerPromise(); 212 this.#onConfigurationComplete(window); 213 } 214 } 215 216 /** 217 * Check if the provided value matches the provided type. 218 * 219 * @param {*} value 220 * The value to verify. 221 * @param {string} type 222 * The type to match. 223 * 224 * @returns {boolean} 225 * Returns true if the value type is the same as 226 * the provided type. False, otherwise. 227 */ 228 #isOfType(value, type) { 229 if (type === "object") { 230 return typeof value === "object" && value !== null; 231 } 232 233 return typeof value === type; 234 } 235 236 /** 237 * For some emulations a value set per a browsing context overrides 238 * a value set per a user context or set globally. And a value set per 239 * a user context overrides a global value. 240 * 241 * @param {string} type 242 * The type to verify that the value was set. 243 * @param {*} contextValue 244 * The override value set per browsing context. 245 * @param {*} userContextValue 246 * The override value set per user context. 247 * @param {*} globalValue 248 * The override value set globally. 249 * 250 * @returns {*} 251 * Returns the override value which should be applied. 252 */ 253 #findCorrectOverrideValue(type, contextValue, userContextValue, globalValue) { 254 if (this.#isOfType(contextValue, type)) { 255 return contextValue; 256 } 257 if (this.#isOfType(userContextValue, type)) { 258 return userContextValue; 259 } 260 if (this.#isOfType(globalValue, type)) { 261 return globalValue; 262 } 263 return null; 264 } 265 266 async #onConfigurationComplete(window) { 267 // parser blocking doesn't work for initial about:blank, so ensure 268 // browsing_context.create waits for configuration to complete 269 if (window.document.isInitialDocument) { 270 await this.messageHandler.forwardCommand({ 271 moduleName: "browsingContext", 272 commandName: "_onConfigurationComplete", 273 destination: { 274 type: lazy.RootMessageHandler.type, 275 }, 276 params: { 277 navigable: this.messageHandler.context, 278 }, 279 }); 280 } 281 } 282 283 #updatePreloadScripts(sessionData) { 284 this.#preloadScripts.clear(); 285 286 for (const { contextDescriptor, value } of sessionData) { 287 if (!this.messageHandler.matchesContext(contextDescriptor)) { 288 continue; 289 } 290 291 this.#preloadScripts.add(value); 292 } 293 } 294 295 /** 296 * Internal commands 297 */ 298 299 _applySessionData(params) { 300 const { category, sessionData } = params; 301 302 if (category === "preload-script") { 303 this.#updatePreloadScripts(sessionData); 304 } 305 306 // The following overrides apply only to top-level traversables. 307 if ( 308 [ 309 "geolocation-override", 310 "locale-override", 311 "screen-orientation-override", 312 "screen-settings-override", 313 "timezone-override", 314 "user-agent-override", 315 "viewport-overrides", 316 ].includes(category) && 317 !this.messageHandler.context.parent 318 ) { 319 let geolocationOverridePerContext = null; 320 let geolocationOverridePerUserContext = null; 321 322 let localeOverridePerContext = null; 323 let localeOverridePerUserContext = null; 324 325 let screenOrientationOverridePerContext = null; 326 let screenOrientationOverridePerUserContext = null; 327 328 let screenSettingsOverridePerContext = null; 329 let screenSettingsOverridePerUserContext = null; 330 331 let timezoneOverridePerContext = null; 332 let timezoneOverridePerUserContext = null; 333 334 let userAgentOverrideGlobal = null; 335 let userAgentOverridePerUserContext = null; 336 let userAgentOverridePerContext = null; 337 338 for (const { contextDescriptor, value } of sessionData) { 339 if (!this.messageHandler.matchesContext(contextDescriptor)) { 340 continue; 341 } 342 343 switch (category) { 344 case "geolocation-override": { 345 switch (contextDescriptor.type) { 346 case lazy.ContextDescriptorType.TopBrowsingContext: { 347 geolocationOverridePerContext = value; 348 break; 349 } 350 case lazy.ContextDescriptorType.UserContext: { 351 geolocationOverridePerUserContext = value; 352 break; 353 } 354 } 355 break; 356 } 357 case "viewport-overrides": { 358 if (value.viewport !== undefined) { 359 this.#viewportConfiguration.set("viewport", value.viewport); 360 } 361 362 if (value.devicePixelRatio !== undefined) { 363 this.#viewportConfiguration.set( 364 "devicePixelRatio", 365 value.devicePixelRatio 366 ); 367 } 368 break; 369 } 370 case "locale-override": { 371 switch (contextDescriptor.type) { 372 case lazy.ContextDescriptorType.TopBrowsingContext: { 373 localeOverridePerContext = value; 374 break; 375 } 376 case lazy.ContextDescriptorType.UserContext: { 377 localeOverridePerUserContext = value; 378 break; 379 } 380 } 381 break; 382 } 383 case "screen-orientation-override": { 384 switch (contextDescriptor.type) { 385 case lazy.ContextDescriptorType.TopBrowsingContext: { 386 screenOrientationOverridePerContext = value; 387 break; 388 } 389 case lazy.ContextDescriptorType.UserContext: { 390 screenOrientationOverridePerUserContext = value; 391 break; 392 } 393 } 394 break; 395 } 396 case "screen-settings-override": { 397 switch (contextDescriptor.type) { 398 case lazy.ContextDescriptorType.TopBrowsingContext: { 399 screenSettingsOverridePerContext = value; 400 break; 401 } 402 case lazy.ContextDescriptorType.UserContext: { 403 screenSettingsOverridePerUserContext = value; 404 break; 405 } 406 } 407 break; 408 } 409 case "timezone-override": { 410 switch (contextDescriptor.type) { 411 case lazy.ContextDescriptorType.TopBrowsingContext: { 412 timezoneOverridePerContext = value; 413 break; 414 } 415 case lazy.ContextDescriptorType.UserContext: { 416 timezoneOverridePerUserContext = value; 417 break; 418 } 419 } 420 break; 421 } 422 case "user-agent-override": { 423 switch (contextDescriptor.type) { 424 case lazy.ContextDescriptorType.TopBrowsingContext: { 425 userAgentOverridePerContext = value; 426 break; 427 } 428 case lazy.ContextDescriptorType.UserContext: { 429 userAgentOverridePerUserContext = value; 430 break; 431 } 432 case lazy.ContextDescriptorType.All: { 433 userAgentOverrideGlobal = value; 434 } 435 } 436 break; 437 } 438 } 439 } 440 441 // For the following emulations on the previous step, we found session items 442 // that would apply an override for a browsing context,a user context, and in some cases globally. 443 // Now from these items we have to choose the one that would take precedence. 444 // The order is the user context item overrides the global one, and the browsing context overrides the user context item. 445 switch (category) { 446 case "geolocation-override": { 447 this.#geolocationConfiguration = this.#findCorrectOverrideValue( 448 "object", 449 geolocationOverridePerContext, 450 geolocationOverridePerUserContext 451 ); 452 break; 453 } 454 case "locale-override": { 455 this.#localeOverride = this.#findCorrectOverrideValue( 456 "string", 457 localeOverridePerContext, 458 localeOverridePerUserContext 459 ); 460 break; 461 } 462 case "screen-orientation-override": { 463 this.#screenOrientationOverride = this.#findCorrectOverrideValue( 464 "object", 465 screenOrientationOverridePerContext, 466 screenOrientationOverridePerUserContext 467 ); 468 469 break; 470 } 471 case "screen-settings-override": { 472 this.#screenSettingsOverride = this.#findCorrectOverrideValue( 473 "object", 474 screenSettingsOverridePerContext, 475 screenSettingsOverridePerUserContext 476 ); 477 478 break; 479 } 480 case "timezone-override": { 481 this.#timezoneOverride = this.#findCorrectOverrideValue( 482 "string", 483 timezoneOverridePerContext, 484 timezoneOverridePerUserContext 485 ); 486 487 break; 488 } 489 case "user-agent-override": { 490 this.#userAgentOverride = this.#findCorrectOverrideValue( 491 "string", 492 userAgentOverridePerContext, 493 userAgentOverridePerUserContext, 494 userAgentOverrideGlobal 495 ); 496 497 break; 498 } 499 } 500 } 501 } 502 } 503 504 export const _configuration = _ConfigurationModule;