driver.sys.mjs (133746B)
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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 actions: "chrome://remote/content/shared/webdriver/Actions.sys.mjs", 9 Addon: "chrome://remote/content/shared/Addon.sys.mjs", 10 AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs", 11 AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs", 12 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", 13 browser: "chrome://remote/content/marionette/browser.sys.mjs", 14 capture: "chrome://remote/content/shared/Capture.sys.mjs", 15 Context: "chrome://remote/content/marionette/browser.sys.mjs", 16 cookie: "chrome://remote/content/marionette/cookie.sys.mjs", 17 disableEventsActor: 18 "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs", 19 dom: "chrome://remote/content/shared/DOM.sys.mjs", 20 enableEventsActor: 21 "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs", 22 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 23 getMarionetteCommandsActorProxy: 24 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs", 25 l10n: "chrome://remote/content/marionette/l10n.sys.mjs", 26 Log: "chrome://remote/content/shared/Log.sys.mjs", 27 Marionette: "chrome://remote/content/components/Marionette.sys.mjs", 28 MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs", 29 modal: "chrome://remote/content/shared/Prompt.sys.mjs", 30 NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs", 31 navigate: "chrome://remote/content/marionette/navigate.sys.mjs", 32 permissions: "chrome://remote/content/shared/Permissions.sys.mjs", 33 pprint: "chrome://remote/content/shared/Format.sys.mjs", 34 print: "chrome://remote/content/shared/PDF.sys.mjs", 35 PollPromise: "chrome://remote/content/shared/Sync.sys.mjs", 36 PromptHandlers: 37 "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs", 38 PromptListener: 39 "chrome://remote/content/shared/listeners/PromptListener.sys.mjs", 40 PromptTypes: 41 "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs", 42 quit: "chrome://remote/content/shared/Browser.sys.mjs", 43 reftest: "chrome://remote/content/marionette/reftest.sys.mjs", 44 registerCommandsActor: 45 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs", 46 RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs", 47 ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs", 48 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs", 49 Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs", 50 truncate: "chrome://remote/content/shared/Format.sys.mjs", 51 unregisterCommandsActor: 52 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs", 53 waitForInitialNavigationCompleted: 54 "chrome://remote/content/shared/Navigate.sys.mjs", 55 webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs", 56 WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs", 57 WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs", 58 windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs", 59 WindowState: "chrome://remote/content/shared/WindowManager.sys.mjs", 60 }); 61 62 ChromeUtils.defineLazyGetter(lazy, "logger", () => 63 lazy.Log.get(lazy.Log.TYPES.MARIONETTE) 64 ); 65 66 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; 67 68 ChromeUtils.defineLazyGetter( 69 lazy, 70 "supportedStrategies", 71 () => 72 new Set([ 73 lazy.dom.Strategy.ClassName, 74 lazy.dom.Strategy.Selector, 75 lazy.dom.Strategy.ID, 76 lazy.dom.Strategy.Name, 77 lazy.dom.Strategy.LinkText, 78 lazy.dom.Strategy.PartialLinkText, 79 lazy.dom.Strategy.TagName, 80 lazy.dom.Strategy.XPath, 81 ]) 82 ); 83 84 // Observer topic to wait for until the browser window is ready. 85 const TOPIC_BROWSER_READY = "browser-delayed-startup-finished"; 86 // Observer topic to perform clean up when application quit is requested. 87 const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested"; 88 89 /** 90 * The Marionette WebDriver services provides a standard conforming 91 * implementation of the W3C WebDriver specification. 92 * 93 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html} 94 * @namespace driver 95 */ 96 97 class ActionsHelper { 98 #actionsOptions; 99 #driver; 100 101 constructor(driver) { 102 this.#driver = driver; 103 104 // Options for actions to pass through performActions and releaseActions. 105 this.#actionsOptions = { 106 // Callbacks as defined in the WebDriver specification. 107 getElementOrigin: this.getElementOrigin.bind(this), 108 isElementOrigin: this.isElementOrigin.bind(this), 109 110 // Custom callbacks. 111 assertInViewPort: this.assertInViewPort.bind(this), 112 dispatchEvent: this.dispatchEvent.bind(this), 113 getClientRects: this.getClientRects.bind(this), 114 getInViewCentrePoint: this.getInViewCentrePoint.bind(this), 115 toBrowserWindowCoordinates: this.toBrowserWindowCoordinates.bind(this), 116 }; 117 } 118 119 get actionsOptions() { 120 return this.#actionsOptions; 121 } 122 123 #getActor(browsingContext) { 124 return lazy.getMarionetteCommandsActorProxy(() => browsingContext); 125 } 126 127 /** 128 * Assert that the target coordinates are within the visible viewport. 129 * 130 * @param {Array.<number>} target 131 * Coordinates [x, y] of the target relative to the viewport. 132 * @param {BrowsingContext} browsingContext 133 * The browsing context to dispatch the event to. 134 * 135 * @returns {Promise<undefined>} 136 * Promise that rejects, if the coordinates are not within 137 * the visible viewport. 138 * 139 * @throws {MoveTargetOutOfBoundsError} 140 * If target is outside the viewport. 141 */ 142 assertInViewPort(target, browsingContext) { 143 return this.#getActor(browsingContext).assertInViewPort(target); 144 } 145 146 /** 147 * Dispatch an event. 148 * 149 * @param {string} eventName 150 * Name of the event to be dispatched. 151 * @param {BrowsingContext} browsingContext 152 * The browsing context to dispatch the event to. 153 * @param {object} details 154 * Details of the event to be dispatched. 155 * 156 * @returns {Promise} 157 * Promise that resolves once the event is dispatched. 158 */ 159 dispatchEvent(eventName, browsingContext, details) { 160 if ( 161 (eventName === "synthesizeWheelAtPoint" && 162 lazy.actions.useAsyncWheelEvents) || 163 (eventName == "synthesizeMouseAtPoint" && 164 lazy.actions.useAsyncMouseEvents) 165 ) { 166 browsingContext = browsingContext.topChromeWindow?.browsingContext; 167 details.eventData.asyncEnabled = true; 168 } 169 170 return this.#getActor(browsingContext).dispatchEvent(eventName, details); 171 } 172 173 /** 174 * Finalize an action command. 175 * 176 * @param {BrowsingContext} browsingContext 177 * The browsing context to dispatch the event to. 178 */ 179 async finalizeAction(browsingContext) { 180 try { 181 await this.#getActor(browsingContext).finalizeAction(); 182 } catch (e) { 183 // Ignore the error if the underlying browsing context is already gone. 184 if (e.name !== lazy.error.NoSuchWindowError.name) { 185 throw e; 186 } 187 } 188 } 189 190 /** 191 * Retrieves the WebElement reference of the origin. 192 * 193 * @param {ElementOrigin} origin 194 * Reference to the element origin of the action. 195 * @param {BrowsingContext} _browsingContext 196 * Not used by Marionette. 197 * 198 * @returns {WebElement} 199 * The WebElement reference. 200 */ 201 getElementOrigin(origin, _browsingContext) { 202 return origin; 203 } 204 205 /** 206 * Retrieve the list of client rects for the element. 207 * 208 * @param {WebElement} element 209 * The web element reference to retrieve the rects from. 210 * @param {BrowsingContext} browsingContext 211 * The browsing context to dispatch the event to. 212 * 213 * @returns {Promise<Array<Map.<string, number>>>} 214 * Promise that resolves to a list of DOMRect-like objects. 215 */ 216 getClientRects(element, browsingContext) { 217 return this.#getActor(browsingContext).getClientRects(element); 218 } 219 220 /** 221 * Retrieve the in-view center point for the rect and visible viewport. 222 * 223 * @param {DOMRect} rect 224 * Size and position of the rectangle to check. 225 * @param {BrowsingContext} browsingContext 226 * The browsing context to dispatch the event to. 227 * 228 * @returns {Promise<Map.<string, number>>} 229 * X and Y coordinates that denotes the in-view centre point of 230 * `rect`. 231 */ 232 getInViewCentrePoint(rect, browsingContext) { 233 return this.#getActor(browsingContext).getInViewCentrePoint(rect); 234 } 235 236 /** 237 * Retrieves the action's input state. 238 * 239 * @param {BrowsingContext} browsingContext 240 * The Browsing Context to retrieve the input state for. 241 * 242 * @returns {Actions.InputState} 243 * The action's input state. 244 */ 245 getInputState(browsingContext) { 246 // Bug 1821460: Fetch top-level browsing context. 247 let inputState = this.#driver._inputStates.get(browsingContext); 248 249 if (inputState === undefined) { 250 inputState = new lazy.actions.State(); 251 this.#driver._inputStates.set(browsingContext, inputState); 252 } 253 254 return inputState; 255 } 256 257 /** 258 * Checks if the given object is a valid element origin. 259 * 260 * @param {object} origin 261 * The object to check. 262 * 263 * @returns {boolean} 264 * True, if it's a WebElement. 265 */ 266 isElementOrigin(origin) { 267 return lazy.WebElement.Identifier in origin; 268 } 269 270 /** 271 * Resets the action's input state. 272 * 273 * @param {BrowsingContext} browsingContext 274 * The Browsing Context to reset the input state for. 275 */ 276 resetInputState(browsingContext) { 277 // Bug 1821460: Fetch top-level browsing context. 278 if (this.#driver._inputStates.has(browsingContext)) { 279 this.#driver._inputStates.delete(browsingContext); 280 } 281 } 282 283 /** 284 * Convert a position or rect in browser coordinates of CSS units. 285 * 286 * @param {object} position - Object with the coordinates to convert. 287 * @param {number} position.x - X coordinate. 288 * @param {number} position.y - Y coordinate. 289 * @param {BrowsingContext} browsingContext - The Browsing Context to convert the 290 * coordinates for. 291 */ 292 toBrowserWindowCoordinates(position, browsingContext) { 293 return this.#getActor(browsingContext).toBrowserWindowCoordinates(position); 294 } 295 } 296 297 /** 298 * Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives 299 * in chrome space and mediates calls to the current browsing context's actor. 300 * 301 * Throughout this prototype, functions with the argument <var>cmd</var>'s 302 * documentation refers to the contents of the <code>cmd.parameter</code> 303 * object. 304 * 305 * @class GeckoDriver 306 * 307 * @param {MarionetteServer} server 308 * The instance of Marionette server. 309 */ 310 export function GeckoDriver(server) { 311 this._server = server; 312 313 // WebDriver Session 314 this._currentSession = null; 315 316 // Flag to indicate a WebDriver HTTP session 317 this._sessionConfigFlags = new Set([lazy.WebDriverSession.SESSION_FLAG_HTTP]); 318 319 // Flag to indicate that the application is shutting down 320 this._isShuttingDown = false; 321 322 this.browsers = {}; 323 324 // points to current browser 325 this.curBrowser = null; 326 // top-most chrome window 327 this.mainFrame = null; 328 329 // Use content context by default 330 this.context = lazy.Context.Content; 331 332 // used for modal dialogs 333 this.dialog = null; 334 this.promptListener = null; 335 336 // Browsing context => input state. 337 // Bug 1821460: Move to WebDriver Session and share with Remote Agent. 338 this._inputStates = new WeakMap(); 339 340 this._actionsHelper = new ActionsHelper(this); 341 } 342 343 GeckoDriver.prototype._trace = function (message, browsingContext = null) { 344 if (browsingContext !== null) { 345 lazy.logger.trace(`[${browsingContext.id}] ${message}`); 346 } else { 347 lazy.logger.trace(message); 348 } 349 }; 350 351 /** 352 * The current context decides if commands are executed in chrome- or 353 * content space. 354 */ 355 Object.defineProperty(GeckoDriver.prototype, "context", { 356 get() { 357 return this._context; 358 }, 359 360 set(context) { 361 if (context === lazy.Context.Chrome) { 362 lazy.assert.hasSystemAccess(); 363 } 364 365 this._context = lazy.Context.fromString(context); 366 }, 367 }); 368 369 /** 370 * The current WebDriver Session. 371 */ 372 Object.defineProperty(GeckoDriver.prototype, "currentSession", { 373 get() { 374 if (lazy.RemoteAgent.webDriverBiDi) { 375 return lazy.RemoteAgent.webDriverBiDi.session; 376 } 377 378 return this._currentSession; 379 }, 380 }); 381 382 /** 383 * Returns the current URL of the ChromeWindow or content browser, 384 * depending on context. 385 * 386 * @returns {URL} 387 * Read-only property containing the currently loaded URL. 388 */ 389 Object.defineProperty(GeckoDriver.prototype, "currentURL", { 390 get() { 391 const browsingContext = this.getBrowsingContext({ top: true }); 392 return new URL(browsingContext.currentWindowGlobal.documentURI.spec); 393 }, 394 }); 395 396 /** 397 * Returns the title of the ChromeWindow or content browser, 398 * depending on context. 399 * 400 * @returns {string} 401 * Read-only property containing the title of the loaded URL. 402 */ 403 Object.defineProperty(GeckoDriver.prototype, "title", { 404 get() { 405 const browsingContext = this.getBrowsingContext({ top: true }); 406 return browsingContext.currentWindowGlobal.documentTitle; 407 }, 408 }); 409 410 Object.defineProperty(GeckoDriver.prototype, "windowType", { 411 get() { 412 return this.curBrowser.window.document.documentElement.getAttribute( 413 "windowtype" 414 ); 415 }, 416 }); 417 418 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([ 419 "nsIObserver", 420 "nsISupportsWeakReference", 421 ]); 422 423 /** 424 * Callback used to observe the closing of modal dialogs 425 * during the session's lifetime. 426 */ 427 GeckoDriver.prototype.handleClosedModalDialog = function (_eventName, data) { 428 const { contentBrowser, detail } = data; 429 430 this._trace( 431 `Prompt closed (type: "${detail.promptType}", accepted: "${detail.accepted}")`, 432 contentBrowser.browsingContext 433 ); 434 435 this.dialog = null; 436 }; 437 438 /** 439 * Callback used to observe the creation of new modal dialogs 440 * during the session's lifetime. 441 */ 442 GeckoDriver.prototype.handleOpenModalDialog = function (_eventName, data) { 443 const { contentBrowser, prompt } = data; 444 445 prompt.getText().then(text => { 446 // We need the text to identify a user prompt when it gets 447 // randomly opened. Because on Android the text is asynchronously 448 // retrieved lets delay the logging without making the handler async. 449 this._trace( 450 `Prompt opened (type: "${prompt.promptType}", text: "${text}")`, 451 contentBrowser.browsingContext 452 ); 453 }); 454 455 this.dialog = prompt; 456 457 if (this.dialog.promptType === "beforeunload" && !this.currentSession?.bidi) { 458 // Only implicitly accept the prompt when its not a BiDi session. 459 this._trace(`Implicitly accepted "beforeunload" prompt`); 460 this.dialog.accept(); 461 return; 462 } 463 464 if (!this._isShuttingDown) { 465 this.getActor().notifyDialogOpened(this.dialog); 466 } 467 }; 468 469 /** 470 * Get the current URL. 471 * 472 * @param {object} options 473 * @param {boolean=} options.top 474 * If set to true return the window's top-level URL, 475 * otherwise the one from the currently selected frame. Defaults to true. 476 * @see https://w3c.github.io/webdriver/#get-current-url 477 */ 478 GeckoDriver.prototype._getCurrentURL = function (options = {}) { 479 if (options.top === undefined) { 480 options.top = true; 481 } 482 const browsingContext = this.getBrowsingContext(options); 483 return new URL(browsingContext.currentURI.spec); 484 }; 485 486 /** 487 * Get the current "MarionetteCommands" parent actor. 488 * 489 * @param {object} options 490 * @param {boolean=} options.top 491 * If set to true use the window's top-level browsing context for the actor, 492 * otherwise the one from the currently selected frame. Defaults to false. 493 * 494 * @returns {MarionetteCommandsParent} 495 * The parent actor. 496 */ 497 GeckoDriver.prototype.getActor = function (options = {}) { 498 return lazy.getMarionetteCommandsActorProxy(() => 499 this.getBrowsingContext(options) 500 ); 501 }; 502 503 /** 504 * Get the selected BrowsingContext for the current context. 505 * 506 * @param {object} options 507 * @param {Context=} options.context 508 * Context (content or chrome) for which to retrieve the browsing context. 509 * Defaults to the current one. 510 * @param {boolean=} options.parent 511 * If set to true return the window's parent browsing context, 512 * otherwise the one from the currently selected frame. Defaults to false. 513 * @param {boolean=} options.top 514 * If set to true return the window's top-level browsing context, 515 * otherwise the one from the currently selected frame. Defaults to false. 516 * 517 * @returns {BrowsingContext} 518 * The browsing context, or `null` if none is available 519 */ 520 GeckoDriver.prototype.getBrowsingContext = function (options = {}) { 521 const { context = this.context, parent = false, top = false } = options; 522 523 let browsingContext = null; 524 if (context === lazy.Context.Chrome) { 525 browsingContext = this.currentSession?.chromeBrowsingContext; 526 } else { 527 browsingContext = this.currentSession?.contentBrowsingContext; 528 } 529 530 if (browsingContext && parent) { 531 browsingContext = browsingContext.parent; 532 } 533 534 if (browsingContext && top) { 535 browsingContext = browsingContext.top; 536 } 537 538 return browsingContext; 539 }; 540 541 /** 542 * Get the currently selected window. 543 * 544 * It will return the outer {@link ChromeWindow} previously selected by 545 * window handle through {@link #switchToWindow}, or the first window that 546 * was registered. 547 * 548 * @param {object} options 549 * @param {Context=} options.context 550 * Optional name of the context to use for finding the window. 551 * It will be required if a command always needs a specific context, 552 * whether which context is currently set. Defaults to the current 553 * context. 554 * 555 * @returns {ChromeWindow} 556 * The current top-level browsing context. 557 */ 558 GeckoDriver.prototype.getCurrentWindow = function (options = {}) { 559 const { context = this.context } = options; 560 561 let win = null; 562 switch (context) { 563 case lazy.Context.Chrome: 564 if (this.curBrowser) { 565 win = this.curBrowser.window; 566 } 567 break; 568 569 case lazy.Context.Content: 570 if (this.curBrowser && this.curBrowser.contentBrowser) { 571 win = this.curBrowser.window; 572 } 573 break; 574 } 575 576 return win; 577 }; 578 579 GeckoDriver.prototype.isReftestBrowser = function (element) { 580 return ( 581 this._reftest && 582 element && 583 element.tagName === "xul:browser" && 584 element.parentElement && 585 element.parentElement.id === "reftest" 586 ); 587 }; 588 589 /** 590 * Create a new browsing context for window and add to known browsers. 591 * 592 * @param {ChromeWindow} win 593 * Window for which we will create a browsing context. 594 * 595 * @returns {string} 596 * Returns the unique server-assigned ID of the window. 597 */ 598 GeckoDriver.prototype.addBrowser = function (win) { 599 let context = new lazy.browser.Context(win, this); 600 let winId = lazy.NavigableManager.getIdForBrowsingContext( 601 win.browsingContext 602 ); 603 604 this.browsers[winId] = context; 605 this.curBrowser = this.browsers[winId]; 606 }; 607 608 /** 609 * Handles registration of new content browsers. Depending on 610 * their type they are either accepted or ignored. 611 * 612 * @param {XULBrowser} browserElement 613 */ 614 GeckoDriver.prototype.registerBrowser = function (browserElement) { 615 // We want to ignore frames that are XUL browsers that aren't in the "main" 616 // tabbrowser, but accept things on Fennec (which doesn't have a 617 // xul:tabbrowser), and accept HTML iframes (because tests depend on it), 618 // as well as XUL frames. Ideally this should be cleaned up and we should 619 // keep track of browsers a different way. 620 if ( 621 !lazy.AppInfo.isFirefox || 622 browserElement.namespaceURI != XUL_NS || 623 browserElement.nodeName != "browser" || 624 browserElement.getTabBrowser() 625 ) { 626 this.curBrowser.register(browserElement); 627 } 628 }; 629 630 /** 631 * Create a new WebDriver session. 632 * 633 * @param {object} cmd 634 * @param {Record<string, *>=} cmd.parameters 635 * JSON Object containing any of the recognised capabilities as listed 636 * on the `WebDriverSession` class. 637 * 638 * @returns {object} 639 * Session ID and capabilities offered by the WebDriver service. 640 * 641 * @throws {SessionNotCreatedError} 642 * If, for whatever reason, a session could not be created. 643 */ 644 GeckoDriver.prototype.newSession = async function (cmd) { 645 if (this.currentSession) { 646 throw new lazy.error.SessionNotCreatedError( 647 "Maximum number of active sessions" 648 ); 649 } 650 651 const { parameters: capabilities } = cmd; 652 653 try { 654 if (lazy.RemoteAgent.webDriverBiDi) { 655 // If the WebDriver BiDi protocol is active always use the Remote Agent 656 // to handle the WebDriver session. 657 await lazy.RemoteAgent.webDriverBiDi.createSession( 658 capabilities, 659 this._sessionConfigFlags 660 ); 661 } else { 662 // If it's not the case then Marionette itself needs to handle it, and 663 // has to nullify the "webSocketUrl" capability. 664 this._currentSession = new lazy.WebDriverSession( 665 capabilities, 666 this._sessionConfigFlags 667 ); 668 this._currentSession.capabilities.delete("webSocketUrl"); 669 } 670 671 // Don't wait for the initial window when Marionette is in windowless mode 672 if (!this.currentSession.capabilities.get("moz:windowless")) { 673 // Creating a WebDriver session too early can cause issues with 674 // clients in not being able to find any available window handle. 675 // Also when closing the application while it's still starting up can 676 // cause shutdown hangs. As such Marionette will return a new session 677 // once the initial application window has finished initializing. 678 lazy.logger.debug(`Waiting for initial application window`); 679 await lazy.Marionette.browserStartupFinished; 680 681 // This call includes a fallback to "mail:3pane" as well. 682 const appWin = Services.wm.getMostRecentBrowserWindow(); 683 await lazy.windowManager.waitForChromeWindowLoaded(appWin); 684 685 if (lazy.MarionettePrefs.clickToStart) { 686 Services.prompt.alert( 687 appWin, 688 "", 689 "Click to start execution of marionette tests" 690 ); 691 } 692 693 this.addBrowser(appWin); 694 this.mainFrame = appWin; 695 696 // Setup observer for modal dialogs 697 this.promptListener = new lazy.PromptListener(() => this.curBrowser); 698 this.promptListener.on("closed", this.handleClosedModalDialog.bind(this)); 699 this.promptListener.on("opened", this.handleOpenModalDialog.bind(this)); 700 this.promptListener.startListening(); 701 702 for (let win of lazy.windowManager.windows) { 703 this.registerWindow(win, { registerBrowsers: true }); 704 } 705 706 if (this.mainFrame) { 707 this.currentSession.chromeBrowsingContext = 708 this.mainFrame.browsingContext; 709 this.mainFrame.focus(); 710 } 711 712 if (this.curBrowser.tab) { 713 const browsingContext = this.curBrowser.contentBrowser.browsingContext; 714 this.currentSession.contentBrowsingContext = browsingContext; 715 716 // Bug 1838381 - Only use a longer unload timeout for desktop, because 717 // on Android only the initial document is loaded, and loading a 718 // specific page during startup doesn't succeed. 719 const options = {}; 720 if (!lazy.AppInfo.isAndroid) { 721 options.unloadTimeout = 5000; 722 } 723 724 await lazy.waitForInitialNavigationCompleted( 725 browsingContext.webProgress, 726 options 727 ); 728 729 this.curBrowser.contentBrowser.focus(); 730 } 731 732 // Check if there is already an open dialog for the selected browser window. 733 this.dialog = lazy.modal.findPrompt(this.curBrowser); 734 } 735 736 lazy.registerCommandsActor(this.currentSession.id); 737 lazy.enableEventsActor(); 738 739 Services.obs.addObserver(this, TOPIC_BROWSER_READY); 740 } catch (e) { 741 throw new lazy.error.SessionNotCreatedError(e); 742 } 743 744 return { 745 sessionId: this.currentSession.id, 746 capabilities: this.currentSession.capabilities, 747 }; 748 }; 749 750 /** 751 * Start observing the specified window. 752 * 753 * @param {ChromeWindow} win 754 * Chrome window to register event listeners for. 755 * @param {object=} options 756 * @param {boolean=} options.registerBrowsers 757 * If true, register all content browsers of found tabs. Defaults to false. 758 */ 759 GeckoDriver.prototype.registerWindow = function (win, options = {}) { 760 const { registerBrowsers = false } = options; 761 const tabBrowser = lazy.TabManager.getTabBrowser(win); 762 763 if (registerBrowsers && tabBrowser) { 764 for (const tab of tabBrowser.tabs) { 765 const contentBrowser = lazy.TabManager.getBrowserForTab(tab); 766 this.registerBrowser(contentBrowser); 767 } 768 } 769 770 // Listen for any kind of top-level process switch 771 tabBrowser?.addEventListener("XULFrameLoaderCreated", this); 772 }; 773 774 /** 775 * Stop observing the specified window. 776 * 777 * @param {ChromeWindow} win 778 * Chrome window to unregister event listeners for. 779 */ 780 GeckoDriver.prototype.stopObservingWindow = function (win) { 781 const tabBrowser = lazy.TabManager.getTabBrowser(win); 782 783 tabBrowser?.removeEventListener("XULFrameLoaderCreated", this); 784 }; 785 786 GeckoDriver.prototype.handleEvent = function ({ target, type }) { 787 switch (type) { 788 case "XULFrameLoaderCreated": 789 if (target === this.curBrowser.contentBrowser) { 790 lazy.logger.trace( 791 "Remoteness change detected. Set new top-level browsing context " + 792 `to ${target.browsingContext.id}` 793 ); 794 795 this.currentSession.contentBrowsingContext = target.browsingContext; 796 } 797 break; 798 } 799 }; 800 801 GeckoDriver.prototype.observe = async function (subject, topic) { 802 switch (topic) { 803 case TOPIC_BROWSER_READY: 804 this.registerWindow(subject); 805 break; 806 807 case TOPIC_QUIT_APPLICATION_REQUESTED: 808 // Run Marionette specific cleanup steps before allowing 809 // the application to shutdown 810 await this._server.setAcceptConnections(false); 811 this.deleteSession(); 812 break; 813 } 814 }; 815 816 /** 817 * Send the current session's capabilities to the client. 818 * 819 * Capabilities informs the client of which WebDriver features are 820 * supported by Firefox and Marionette. They are immutable for the 821 * length of the session. 822 * 823 * The return value is an immutable map of string keys 824 * ("capabilities") to values, which may be of types boolean, 825 * numerical or string. 826 */ 827 GeckoDriver.prototype.getSessionCapabilities = function () { 828 return { capabilities: this.currentSession.capabilities }; 829 }; 830 831 /** 832 * Register a chrome protocol handler for a directory containing XHTML or XUL 833 * files, allowing them to be loaded via the chrome:// protocol. 834 * 835 * @param {obj} cmd 836 * @param {string} cmd.parameters.manifestPath 837 * The base manifest path for the entries. URL values are resolved 838 * relative to this path. 839 * @param {Array<Array<string, string, string>>} cmd.parameters.entries 840 * An array of arrays, each containing a registry entry (type, namespace, 841 * path, options) as it would appear in a chrome.manifest file. Only the 842 * following entry types are currently accepted: 843 * 844 * - "content" A URL entry. Must be a 3-element array. 845 * - "override" A URL override entry. Must be a 3-element array. 846 * - "locale" A locale package entry. Must be a 4-element array. 847 * 848 * @returns {string} id 849 * The identifier for the registered chrome protocol handler. 850 * 851 * @throws {InvalidArgumentError} 852 * If <var>id</var> is not a string. 853 * @throws {UnknownError} 854 * If there is no such registered chrome protocol handler. 855 */ 856 GeckoDriver.prototype.registerChromeHandler = function (cmd) { 857 const manifestPath = lazy.assert.string( 858 cmd.parameters.manifestPath, 859 lazy.pprint`Expected "path" to be a string, got ${cmd.parameters.manifestPath}` 860 ); 861 862 const entries = lazy.assert.array( 863 cmd.parameters.entries, 864 lazy.pprint`Expected "entries" to be an array, got ${cmd.parameters.entries}` 865 ); 866 entries.forEach(entry => { 867 const [type, namespace, directory, options] = lazy.assert.array( 868 entry, 869 lazy.pprint`Expected values of "entries" to be an array, got ${entries}` 870 ); 871 lazy.assert.string( 872 type, 873 lazy.pprint`Expected "type" of entry to be a string, got ${type}` 874 ); 875 lazy.assert.string( 876 namespace, 877 lazy.pprint`Expected "namespace" of entry to be a string, got ${namespace}` 878 ); 879 lazy.assert.string( 880 directory, 881 lazy.pprint`Expected "directory" of entry to be a string, got ${directory}` 882 ); 883 if (options !== undefined) { 884 lazy.assert.string( 885 options, 886 lazy.pprint`Expected "options" of entry to be a string, got ${options}` 887 ); 888 } 889 }); 890 891 lazy.assert.hasSystemAccess(); 892 893 return this.currentSession.registerChromeHandler(manifestPath, entries); 894 }; 895 896 /** 897 * Unregister a previously registered chrome protocol handler. 898 * 899 * @param {obj} cmd 900 * @param {string} cmd.parameters.id 901 * The identifier returned when the chrome handler was registered. 902 * 903 * @throws {InvalidArgumentError} 904 * If <var>id</var> is not a string. 905 * @throws {UnknownError} 906 * If there is no such registered chrome protocol handler. 907 */ 908 GeckoDriver.prototype.unregisterChromeHandler = function (cmd) { 909 const id = lazy.assert.string( 910 cmd.parameters.id, 911 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 912 ); 913 914 lazy.assert.hasSystemAccess(); 915 916 this.currentSession.unregisterChromeHandler(id); 917 }; 918 919 /** 920 * Sets the context of the subsequent commands. 921 * 922 * All subsequent requests to commands that in some way involve 923 * interaction with a browsing context will target the chosen browsing 924 * context. 925 * 926 * @param {object} cmd 927 * @param {string} cmd.parameters.value 928 * Name of the context to be switched to. Must be one of "chrome" or 929 * "content". 930 * 931 * @throws {InvalidArgumentError} 932 * If <var>value</var> is not a string. 933 * @throws {WebDriverError} 934 * If <var>value</var> is not a valid browsing context. 935 */ 936 GeckoDriver.prototype.setContext = function (cmd) { 937 let value = lazy.assert.string( 938 cmd.parameters.value, 939 lazy.pprint`Expected "value" to be a string, got ${cmd.parameters.value}` 940 ); 941 942 this.context = value; 943 }; 944 945 /** 946 * Gets the context type that is Marionette's current target for 947 * browsing context scoped commands. 948 * 949 * You may choose a context through the {@link #setContext} command. 950 * 951 * The default browsing context is {@link Context.Content}. 952 * 953 * @returns {Context} 954 * Current context. 955 */ 956 GeckoDriver.prototype.getContext = function () { 957 return this.context; 958 }; 959 960 /** 961 * Executes a JavaScript function in the context of the current browsing 962 * context, if in content space, or in chrome space otherwise, and returns 963 * the return value of the function. 964 * 965 * It is important to note that if the <var>sandboxName</var> parameter 966 * is left undefined, the script will be evaluated in a mutable sandbox, 967 * causing any change it makes on the global state of the document to have 968 * lasting side-effects. 969 * 970 * @param {object} cmd 971 * @param {string} cmd.parameters.script 972 * Script to evaluate as a function body. 973 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args 974 * Arguments exposed to the script in <code>arguments</code>. 975 * The array items must be serialisable to the WebDriver protocol. 976 * @param {string=} cmd.parameters.sandbox 977 * Name of the sandbox to evaluate the script in. The sandbox is 978 * cached for later reuse on the same Window object if 979 * <var>newSandbox</var> is false. If the parameter is undefined, 980 * the script is evaluated in a mutable sandbox. If the parameter 981 * is "system", it will be evaluated in a sandbox with elevated system 982 * privileges, equivalent to chrome space. 983 * @param {boolean=} cmd.parameters.newSandbox 984 * Forces the script to be evaluated in a fresh sandbox. Note that if 985 * it is undefined, the script will normally be evaluated in a fresh 986 * sandbox. 987 * @param {string=} cmd.parameters.filename 988 * Filename of the client's program where this script is evaluated. 989 * @param {number=} cmd.parameters.line 990 * Line in the client's program where this script is evaluated. 991 * 992 * @returns {(string|boolean|number|object|WebReference)} 993 * Return value from the script, or null which signifies either the 994 * JavaScript notion of null or undefined. 995 * 996 * @throws {JavaScriptError} 997 * If an {@link Error} was thrown whilst evaluating the script. 998 * @throws {NoSuchElementError} 999 * If an element that was passed as part of <var>args</var> is unknown. 1000 * @throws {NoSuchFrameError} 1001 * Child browsing context has been discarded. 1002 * @throws {NoSuchWindowError} 1003 * Top-level browsing context has been discarded. 1004 * @throws {ScriptTimeoutError} 1005 * If the script was interrupted due to reaching the session's 1006 * script timeout. 1007 * @throws {StaleElementReferenceError} 1008 * If an element that was passed as part of <var>args</var> or that is 1009 * returned as result has gone stale. 1010 */ 1011 GeckoDriver.prototype.executeScript = function (cmd) { 1012 let { script, args } = cmd.parameters; 1013 let opts = { 1014 script: cmd.parameters.script, 1015 args: cmd.parameters.args, 1016 sandboxName: cmd.parameters.sandbox, 1017 newSandbox: cmd.parameters.newSandbox, 1018 file: cmd.parameters.filename, 1019 line: cmd.parameters.line, 1020 }; 1021 1022 return this.execute_(script, args, opts); 1023 }; 1024 1025 /** 1026 * Executes a JavaScript function in the context of the current browsing 1027 * context, if in content space, or in chrome space otherwise, and returns 1028 * the object passed to the callback. 1029 * 1030 * The callback is always the last argument to the <var>arguments</var> 1031 * list passed to the function scope of the script. It can be retrieved 1032 * as such: 1033 * 1034 * <pre><code> 1035 * let callback = arguments[arguments.length - 1]; 1036 * callback("foo"); 1037 * // "foo" is returned 1038 * </code></pre> 1039 * 1040 * It is important to note that if the <var>sandboxName</var> parameter 1041 * is left undefined, the script will be evaluated in a mutable sandbox, 1042 * causing any change it makes on the global state of the document to have 1043 * lasting side-effects. 1044 * 1045 * @param {object} cmd 1046 * @param {string} cmd.parameters.script 1047 * Script to evaluate as a function body. 1048 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args 1049 * Arguments exposed to the script in <code>arguments</code>. 1050 * The array items must be serialisable to the WebDriver protocol. 1051 * @param {string=} cmd.parameters.sandbox 1052 * Name of the sandbox to evaluate the script in. The sandbox is 1053 * cached for later reuse on the same Window object if 1054 * <var>newSandbox</var> is false. If the parameter is undefined, 1055 * the script is evaluated in a mutable sandbox. If the parameter 1056 * is "system", it will be evaluated in a sandbox with elevated system 1057 * privileges, equivalent to chrome space. 1058 * @param {boolean=} cmd.parameters.newSandbox 1059 * Forces the script to be evaluated in a fresh sandbox. Note that if 1060 * it is undefined, the script will normally be evaluated in a fresh 1061 * sandbox. 1062 * @param {string=} cmd.parameters.filename 1063 * Filename of the client's program where this script is evaluated. 1064 * @param {number=} cmd.parameters.line 1065 * Line in the client's program where this script is evaluated. 1066 * 1067 * @returns {(string|boolean|number|object|WebReference)} 1068 * Return value from the script, or null which signifies either the 1069 * JavaScript notion of null or undefined. 1070 * 1071 * @throws {JavaScriptError} 1072 * If an Error was thrown whilst evaluating the script. 1073 * @throws {NoSuchElementError} 1074 * If an element that was passed as part of <var>args</var> is unknown. 1075 * @throws {NoSuchFrameError} 1076 * Child browsing context has been discarded. 1077 * @throws {NoSuchWindowError} 1078 * Top-level browsing context has been discarded. 1079 * @throws {ScriptTimeoutError} 1080 * If the script was interrupted due to reaching the session's 1081 * script timeout. 1082 * @throws {StaleElementReferenceError} 1083 * If an element that was passed as part of <var>args</var> or that is 1084 * returned as result has gone stale. 1085 */ 1086 GeckoDriver.prototype.executeAsyncScript = function (cmd) { 1087 let { script, args } = cmd.parameters; 1088 let opts = { 1089 script: cmd.parameters.script, 1090 args: cmd.parameters.args, 1091 sandboxName: cmd.parameters.sandbox, 1092 newSandbox: cmd.parameters.newSandbox, 1093 file: cmd.parameters.filename, 1094 line: cmd.parameters.line, 1095 async: true, 1096 }; 1097 1098 return this.execute_(script, args, opts); 1099 }; 1100 1101 GeckoDriver.prototype.execute_ = async function ( 1102 script, 1103 args = [], 1104 { 1105 sandboxName = null, 1106 newSandbox = false, 1107 file = "", 1108 line = 0, 1109 async = false, 1110 } = {} 1111 ) { 1112 lazy.assert.open(this.getBrowsingContext()); 1113 await this._handleUserPrompts(); 1114 1115 lazy.assert.string( 1116 script, 1117 lazy.pprint`Expected "script" to be a string, got ${script}` 1118 ); 1119 lazy.assert.array( 1120 args, 1121 lazy.pprint`Expected "args" to be an array, got ${args}` 1122 ); 1123 if (sandboxName !== null) { 1124 lazy.assert.string( 1125 sandboxName, 1126 lazy.pprint`Expected "sandboxName" to be a string, got ${sandboxName}` 1127 ); 1128 } 1129 lazy.assert.boolean( 1130 newSandbox, 1131 lazy.pprint`Expected "newSandbox" to be boolean, got ${newSandbox}` 1132 ); 1133 lazy.assert.string( 1134 file, 1135 lazy.pprint`Expected "file" to be a string, got ${file}` 1136 ); 1137 lazy.assert.number( 1138 line, 1139 lazy.pprint`Expected "line" to be a number, got ${line}` 1140 ); 1141 1142 let opts = { 1143 timeout: this.currentSession.timeouts.script, 1144 sandboxName, 1145 newSandbox, 1146 file, 1147 line, 1148 async, 1149 }; 1150 1151 return this.getActor().executeScript(script, args, opts); 1152 }; 1153 1154 /** 1155 * Navigate to given URL. 1156 * 1157 * Navigates the current browsing context to the given URL and waits for 1158 * the document to load or the session's page timeout duration to elapse 1159 * before returning. 1160 * 1161 * The command will return with a failure if there is an error loading 1162 * the document or the URL is blocked. This can occur if it fails to 1163 * reach host, the URL is malformed, or if there is a certificate issue 1164 * to name some examples. 1165 * 1166 * The document is considered successfully loaded when the 1167 * DOMContentLoaded event on the frame element associated with the 1168 * current window triggers and document.readyState is "complete". 1169 * 1170 * In chrome context it will change the current window's location to 1171 * the supplied URL and wait until document.readyState equals "complete" 1172 * or the page timeout duration has elapsed. 1173 * 1174 * @param {object} cmd 1175 * @param {string} cmd.parameters.url 1176 * URL to navigate to. 1177 * 1178 * @throws {NoSuchWindowError} 1179 * Top-level browsing context has been discarded. 1180 * @throws {UnexpectedAlertOpenError} 1181 * A modal dialog is open, blocking this operation. 1182 * @throws {UnsupportedOperationError} 1183 * Not available in current context. 1184 */ 1185 GeckoDriver.prototype.navigateTo = async function (cmd) { 1186 lazy.assert.content(this.context); 1187 const browsingContext = lazy.assert.open( 1188 this.getBrowsingContext({ top: true }) 1189 ); 1190 await this._handleUserPrompts(); 1191 1192 let { url } = cmd.parameters; 1193 1194 let validURL = URL.parse(url); 1195 if (!validURL) { 1196 throw new lazy.error.InvalidArgumentError( 1197 lazy.truncate`Expected "url" to be a valid URL, got ${url}` 1198 ); 1199 } 1200 1201 // Switch to the top-level browsing context before navigating 1202 this.currentSession.contentBrowsingContext = browsingContext; 1203 1204 const loadEventExpected = lazy.navigate.isLoadEventExpected( 1205 this._getCurrentURL(), 1206 { 1207 future: validURL, 1208 } 1209 ); 1210 1211 await lazy.navigate.waitForNavigationCompleted( 1212 this, 1213 () => { 1214 lazy.navigate.navigateTo(browsingContext, validURL); 1215 }, 1216 { loadEventExpected } 1217 ); 1218 1219 this.curBrowser.contentBrowser.focus(); 1220 }; 1221 1222 /** 1223 * Get a string representing the current URL. 1224 * 1225 * On Desktop this returns a string representation of the URL of the 1226 * current top level browsing context. This is equivalent to 1227 * document.location.href. 1228 * 1229 * When in the context of the chrome, this returns the canonical URL 1230 * of the current resource. 1231 * 1232 * @throws {NoSuchWindowError} 1233 * Top-level browsing context has been discarded. 1234 * @throws {UnexpectedAlertOpenError} 1235 * A modal dialog is open, blocking this operation. 1236 */ 1237 GeckoDriver.prototype.getCurrentUrl = async function () { 1238 lazy.assert.open(this.getBrowsingContext({ top: true })); 1239 await this._handleUserPrompts(); 1240 1241 return this._getCurrentURL().href; 1242 }; 1243 1244 /** 1245 * Gets the current title of the window. 1246 * 1247 * @returns {string} 1248 * Document title of the top-level browsing context. 1249 * 1250 * @throws {NoSuchWindowError} 1251 * Top-level browsing context has been discarded. 1252 * @throws {UnexpectedAlertOpenError} 1253 * A modal dialog is open, blocking this operation. 1254 */ 1255 GeckoDriver.prototype.getTitle = async function () { 1256 lazy.assert.open(this.getBrowsingContext({ top: true })); 1257 await this._handleUserPrompts(); 1258 1259 return this.title; 1260 }; 1261 1262 /** 1263 * Gets the current type of the window. 1264 * 1265 * @returns {string} 1266 * Type of window 1267 * 1268 * @throws {NoSuchWindowError} 1269 * Top-level browsing context has been discarded. 1270 */ 1271 GeckoDriver.prototype.getWindowType = function () { 1272 lazy.assert.open(this.getBrowsingContext({ top: true })); 1273 1274 return this.windowType; 1275 }; 1276 1277 /** 1278 * Gets the page source of the content document. 1279 * 1280 * @returns {string} 1281 * String serialisation of the DOM of the current browsing context's 1282 * active document. 1283 * 1284 * @throws {NoSuchWindowError} 1285 * Browsing context has been discarded. 1286 * @throws {UnexpectedAlertOpenError} 1287 * A modal dialog is open, blocking this operation. 1288 */ 1289 GeckoDriver.prototype.getPageSource = async function () { 1290 lazy.assert.open(this.getBrowsingContext()); 1291 await this._handleUserPrompts(); 1292 1293 return this.getActor().getPageSource(); 1294 }; 1295 1296 /** 1297 * Cause the browser to traverse one step backward in the joint history 1298 * of the current browsing context. 1299 * 1300 * @throws {NoSuchWindowError} 1301 * Top-level browsing context has been discarded. 1302 * @throws {UnexpectedAlertOpenError} 1303 * A modal dialog is open, blocking this operation. 1304 * @throws {UnsupportedOperationError} 1305 * Not available in current context. 1306 */ 1307 GeckoDriver.prototype.goBack = async function () { 1308 lazy.assert.content(this.context); 1309 const browsingContext = lazy.assert.open( 1310 this.getBrowsingContext({ top: true }) 1311 ); 1312 await this._handleUserPrompts(); 1313 1314 // If there is no history, just return 1315 if (!browsingContext.embedderElement?.canGoBackIgnoringUserInteraction) { 1316 return; 1317 } 1318 1319 await lazy.navigate.waitForNavigationCompleted(this, () => { 1320 browsingContext.goBack(); 1321 }); 1322 }; 1323 1324 /** 1325 * Cause the browser to traverse one step forward in the joint history 1326 * of the current browsing context. 1327 * 1328 * @throws {NoSuchWindowError} 1329 * Top-level browsing context has been discarded. 1330 * @throws {UnexpectedAlertOpenError} 1331 * A modal dialog is open, blocking this operation. 1332 * @throws {UnsupportedOperationError} 1333 * Not available in current context. 1334 */ 1335 GeckoDriver.prototype.goForward = async function () { 1336 lazy.assert.content(this.context); 1337 const browsingContext = lazy.assert.open( 1338 this.getBrowsingContext({ top: true }) 1339 ); 1340 await this._handleUserPrompts(); 1341 1342 // If there is no history, just return 1343 if (!browsingContext.embedderElement?.canGoForward) { 1344 return; 1345 } 1346 1347 await lazy.navigate.waitForNavigationCompleted(this, () => { 1348 browsingContext.goForward(); 1349 }); 1350 }; 1351 1352 /** 1353 * Causes the browser to reload the page in current top-level browsing 1354 * context. 1355 * 1356 * @throws {NoSuchWindowError} 1357 * Top-level browsing context has been discarded. 1358 * @throws {UnexpectedAlertOpenError} 1359 * A modal dialog is open, blocking this operation. 1360 * @throws {UnsupportedOperationError} 1361 * Not available in current context. 1362 */ 1363 GeckoDriver.prototype.refresh = async function () { 1364 lazy.assert.content(this.context); 1365 const browsingContext = lazy.assert.open( 1366 this.getBrowsingContext({ top: true }) 1367 ); 1368 await this._handleUserPrompts(); 1369 1370 // Switch to the top-level browsing context before navigating 1371 this.currentSession.contentBrowsingContext = browsingContext; 1372 1373 await lazy.navigate.waitForNavigationCompleted(this, () => { 1374 lazy.navigate.refresh(browsingContext); 1375 }); 1376 }; 1377 1378 /** 1379 * Get the current window's handle. On desktop this typically corresponds 1380 * to the currently selected tab. 1381 * 1382 * For chrome scope it returns the window identifier for the current chrome 1383 * window for tests interested in managing the chrome window and tab separately. 1384 * 1385 * Return an opaque server-assigned identifier to this window that 1386 * uniquely identifies it within this Marionette instance. This can 1387 * be used to switch to this window at a later point. 1388 * 1389 * @returns {string} 1390 * Unique window handle. 1391 * 1392 * @throws {NoSuchWindowError} 1393 * Top-level browsing context has been discarded. 1394 */ 1395 GeckoDriver.prototype.getWindowHandle = function () { 1396 lazy.assert.open(this.getBrowsingContext({ top: true })); 1397 1398 if (this.context == lazy.Context.Chrome) { 1399 return lazy.NavigableManager.getIdForBrowsingContext( 1400 this.currentSession.chromeBrowsingContext 1401 ); 1402 } 1403 1404 return this.curBrowser.contentBrowserId; 1405 }; 1406 1407 /** 1408 * Get a list of top-level browsing contexts. On desktop this typically 1409 * corresponds to the set of open tabs for browser windows, or the window 1410 * itself for non-browser chrome windows. 1411 * 1412 * For chrome scope it returns identifiers for each open chrome window for 1413 * tests interested in managing a set of chrome windows and tabs separately. 1414 * 1415 * Each window handle is assigned by the server and is guaranteed unique, 1416 * however the return array does not have a specified ordering. 1417 * 1418 * @returns {Array.<string>} 1419 * Unique window handles. 1420 */ 1421 GeckoDriver.prototype.getWindowHandles = function () { 1422 if (this.context == lazy.Context.Chrome) { 1423 return lazy.windowManager.windows.map(window => 1424 lazy.NavigableManager.getIdForBrowsingContext(window.browsingContext) 1425 ); 1426 } 1427 1428 return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser => 1429 lazy.NavigableManager.getIdForBrowser(browser) 1430 ); 1431 }; 1432 1433 /** 1434 * Get the current position and size of the browser window currently in focus. 1435 * 1436 * Will return the current browser window size in pixels. Refers to 1437 * window outerWidth and outerHeight values, which include scroll bars, 1438 * title bars, etc. 1439 * 1440 * @returns {Record<string, number>} 1441 * Object with |x| and |y| coordinates, and |width| and |height| 1442 * of browser window. 1443 * 1444 * @throws {NoSuchWindowError} 1445 * Top-level browsing context has been discarded. 1446 * @throws {UnexpectedAlertOpenError} 1447 * A modal dialog is open, blocking this operation. 1448 */ 1449 GeckoDriver.prototype.getWindowRect = async function () { 1450 lazy.assert.open(this.getBrowsingContext({ top: true })); 1451 await this._handleUserPrompts(); 1452 1453 return this.curBrowser.rect; 1454 }; 1455 1456 /** 1457 * Set the window position and size of the browser on the operating 1458 * system window manager. 1459 * 1460 * The supplied `width` and `height` values refer to the window `outerWidth` 1461 * and `outerHeight` values, which include browser chrome and OS-level 1462 * window borders. 1463 * 1464 * @param {object} cmd 1465 * @param {number} cmd.parameters.x 1466 * X coordinate of the top/left of the window that it will be 1467 * moved to. 1468 * @param {number} cmd.parameters.y 1469 * Y coordinate of the top/left of the window that it will be 1470 * moved to. 1471 * @param {number} cmd.parameters.width 1472 * Width to resize the window to. 1473 * @param {number} cmd.parameters.height 1474 * Height to resize the window to. 1475 * 1476 * @returns {Record<string, number>} 1477 * Object with `x` and `y` coordinates and `width` and `height` 1478 * dimensions. 1479 * 1480 * @throws {NoSuchWindowError} 1481 * Top-level browsing context has been discarded. 1482 * @throws {UnexpectedAlertOpenError} 1483 * A modal dialog is open, blocking this operation. 1484 * @throws {UnsupportedOperationError} 1485 * Not applicable to application. 1486 */ 1487 GeckoDriver.prototype.setWindowRect = async function (cmd) { 1488 lazy.assert.desktop(); 1489 lazy.assert.open(this.getBrowsingContext({ top: true })); 1490 await this._handleUserPrompts(); 1491 1492 const { x = null, y = null, width = null, height = null } = cmd.parameters; 1493 if (x !== null) { 1494 lazy.assert.integer( 1495 x, 1496 lazy.pprint`Expected "x" to be an integer value, got ${x}` 1497 ); 1498 } 1499 if (y !== null) { 1500 lazy.assert.integer( 1501 y, 1502 lazy.pprint`Expected "y" to be an integer value, got ${y}` 1503 ); 1504 } 1505 if (height !== null) { 1506 lazy.assert.positiveInteger( 1507 height, 1508 lazy.pprint`Expected "height" to be a positive integer value, got ${height}` 1509 ); 1510 } 1511 if (width !== null) { 1512 lazy.assert.positiveInteger( 1513 width, 1514 lazy.pprint`Expected "width" to be a positive integer value, got ${width}` 1515 ); 1516 } 1517 1518 const win = this.getCurrentWindow(); 1519 switch (lazy.WindowState.from(win.windowState)) { 1520 case lazy.WindowState.Fullscreen: 1521 await lazy.windowManager.setFullscreen(win, false); 1522 break; 1523 1524 case lazy.WindowState.Maximized: 1525 case lazy.WindowState.Minimized: 1526 await lazy.windowManager.restoreWindow(win); 1527 break; 1528 } 1529 1530 await lazy.windowManager.adjustWindowGeometry(win, x, y, width, height); 1531 1532 return this.curBrowser.rect; 1533 }; 1534 1535 /** 1536 * Find a specific window matching the provided window handle. 1537 * 1538 * @param {string} handle 1539 * The unique handle of either a chrome window or a content browser, as 1540 * returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`. 1541 * 1542 * @returns {object|null} 1543 * A window properties object, or `null` if a window cannot be found. 1544 *. @see :js:func:`WindowManager#getWindowProperties` 1545 */ 1546 GeckoDriver.prototype._findWindowByHandle = function (handle) { 1547 for (const win of lazy.windowManager.windows) { 1548 const chromeWindowId = lazy.NavigableManager.getIdForBrowsingContext( 1549 win.browsingContext 1550 ); 1551 if (chromeWindowId == handle) { 1552 return this.getWindowProperties(win); 1553 } 1554 1555 // Otherwise check if the chrome window has a tab browser, and that it 1556 // contains a tab with the wanted window handle. 1557 const tabBrowser = lazy.TabManager.getTabBrowser(win); 1558 if (tabBrowser && tabBrowser.tabs) { 1559 for (let i = 0; i < tabBrowser.tabs.length; ++i) { 1560 let contentBrowser = lazy.TabManager.getBrowserForTab( 1561 tabBrowser.tabs[i] 1562 ); 1563 let contentWindowId = 1564 lazy.NavigableManager.getIdForBrowser(contentBrowser); 1565 1566 if (contentWindowId == handle) { 1567 return this.getWindowProperties(win, { tabIndex: i }); 1568 } 1569 } 1570 } 1571 } 1572 1573 return null; 1574 }; 1575 1576 /** 1577 * Switch current top-level browsing context by name or server-assigned 1578 * ID. Searches for windows by name, then ID. Content windows take 1579 * precedence. 1580 * 1581 * @param {object} cmd 1582 * @param {string} cmd.parameters.handle 1583 * Handle of the window to switch to. 1584 * @param {boolean=} cmd.parameters.focus 1585 * A boolean value which determines whether to focus 1586 * the window. Defaults to true. 1587 * 1588 * @throws {InvalidArgumentError} 1589 * If <var>handle</var> is not a string or <var>focus</var> not a boolean. 1590 * @throws {NoSuchWindowError} 1591 * Top-level browsing context has been discarded. 1592 */ 1593 GeckoDriver.prototype.switchToWindow = async function (cmd) { 1594 const { focus = true, handle } = cmd.parameters; 1595 1596 lazy.assert.string( 1597 handle, 1598 lazy.pprint`Expected "handle" to be a string, got ${handle}` 1599 ); 1600 lazy.assert.boolean( 1601 focus, 1602 lazy.pprint`Expected "focus" to be a boolean, got ${focus}` 1603 ); 1604 1605 const found = this._findWindowByHandle(handle); 1606 1607 let selected = false; 1608 if (found) { 1609 try { 1610 await this.setWindowHandle(found, focus); 1611 selected = true; 1612 } catch (e) { 1613 lazy.logger.error(e); 1614 } 1615 } 1616 1617 if (!selected) { 1618 throw new lazy.error.NoSuchWindowError( 1619 `Unable to locate window: ${handle}` 1620 ); 1621 } 1622 }; 1623 1624 /** 1625 * A set of properties that describe a window and allow it to be uniquely 1626 * identified. The described window can either be a Chrome Window or a 1627 * Content Window. 1628 * 1629 * @typedef {object} WindowProperties 1630 * @property {Window} win 1631 * The Chrome Window containing the window. When describing 1632 * a Chrome Window, this is the window itself. 1633 * @property {string} id 1634 * The unique id of the containing Chrome Window. 1635 * @property {boolean} hasTabBrowser 1636 * `true` if the Chrome Window has a tabBrowser. 1637 * @property {number=} tabIndex 1638 * Optional, the index of the specific tab within the window. 1639 */ 1640 1641 /** 1642 * Returns a WindowProperties object, that can be used with :js:func:`GeckoDriver#setWindowHandle`. 1643 * 1644 * @param {Window} win 1645 * The Chrome Window for which we want to create a properties object. 1646 * @param {object=} options 1647 * @param {number} options.tabIndex 1648 * Tab index of a specific Content Window in the specified Chrome Window. 1649 * 1650 * @returns {WindowProperties} 1651 * A window properties object. 1652 */ 1653 GeckoDriver.prototype.getWindowProperties = function (win, options = {}) { 1654 const { tabIndex } = options; 1655 1656 if (!Window.isInstance(win)) { 1657 throw new TypeError("Invalid argument, expected a Window object"); 1658 } 1659 1660 return { 1661 win, 1662 id: lazy.NavigableManager.getIdForBrowsingContext(win.browsingContext), 1663 hasTabBrowser: !!lazy.TabManager.getTabBrowser(win), 1664 tabIndex, 1665 }; 1666 }; 1667 1668 /** 1669 * Switch the marionette window to a given window. If the browser in 1670 * the window is unregistered, register that browser and wait for 1671 * the registration is complete. If |focus| is true then set the focus 1672 * on the window. 1673 * 1674 * @param {object} winProperties 1675 * Object containing window properties such as returned from 1676 * :js:func:`GeckoDriver#getWindowProperties` 1677 * @param {boolean=} focus 1678 * A boolean value which determines whether to focus the window. 1679 * Defaults to true. 1680 */ 1681 GeckoDriver.prototype.setWindowHandle = async function ( 1682 winProperties, 1683 focus = true 1684 ) { 1685 if (!(winProperties.id in this.browsers)) { 1686 // Initialise Marionette if the current chrome window has not been seen 1687 // before. Also register the initial tab, if one exists. 1688 this.addBrowser(winProperties.win); 1689 this.mainFrame = winProperties.win; 1690 1691 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext; 1692 1693 if (!winProperties.hasTabBrowser) { 1694 this.currentSession.contentBrowsingContext = null; 1695 } else { 1696 const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win); 1697 1698 // For chrome windows such as a reftest window, `getTabBrowser` is not 1699 // a tabbrowser, it is the content browser which should be used here. 1700 const contentBrowser = tabBrowser.tabs 1701 ? tabBrowser.selectedBrowser 1702 : tabBrowser; 1703 1704 this.currentSession.contentBrowsingContext = 1705 contentBrowser.browsingContext; 1706 this.registerBrowser(contentBrowser); 1707 } 1708 } else { 1709 // Otherwise switch to the known chrome window 1710 this.curBrowser = this.browsers[winProperties.id]; 1711 this.mainFrame = this.curBrowser.window; 1712 1713 // Activate the tab if it's a content window. 1714 let tab = null; 1715 if (winProperties.hasTabBrowser) { 1716 tab = await this.curBrowser.switchToTab( 1717 winProperties.tabIndex, 1718 winProperties.win, 1719 focus 1720 ); 1721 } 1722 1723 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext; 1724 this.currentSession.contentBrowsingContext = 1725 tab?.linkedBrowser.browsingContext; 1726 } 1727 1728 // Check for an existing dialog for the new window 1729 this.dialog = lazy.modal.findPrompt(this.curBrowser); 1730 1731 // If there is an open window modal dialog the underlying chrome window 1732 // cannot be focused. 1733 if (focus && !this.dialog?.isWindowModal) { 1734 await this.curBrowser.focusWindow(); 1735 } 1736 }; 1737 1738 /** 1739 * Set the current browsing context for future commands to the parent 1740 * of the current browsing context. 1741 * 1742 * @throws {NoSuchWindowError} 1743 * Browsing context has been discarded. 1744 * @throws {UnexpectedAlertOpenError} 1745 * A modal dialog is open, blocking this operation. 1746 */ 1747 GeckoDriver.prototype.switchToParentFrame = async function () { 1748 let browsingContext = this.getBrowsingContext(); 1749 if (browsingContext && !browsingContext.parent) { 1750 return; 1751 } 1752 1753 browsingContext = lazy.assert.open(browsingContext?.parent); 1754 1755 this.currentSession.contentBrowsingContext = browsingContext; 1756 }; 1757 1758 /** 1759 * Switch to a given frame within the current window. 1760 * 1761 * @param {object} cmd 1762 * @param {(string | object)=} cmd.parameters.element 1763 * A web element reference of the frame or its element id. 1764 * @param {number=} cmd.parameters.id 1765 * The index of the frame to switch to. 1766 * If both element and id are not defined, switch to top-level frame. 1767 * 1768 * @throws {NoSuchElementError} 1769 * If element represented by reference <var>element</var> is unknown. 1770 * @throws {NoSuchWindowError} 1771 * Browsing context has been discarded. 1772 * @throws {StaleElementReferenceError} 1773 * If element represented by reference <var>element</var> has gone stale. 1774 * @throws {UnexpectedAlertOpenError} 1775 * A modal dialog is open, blocking this operation. 1776 */ 1777 GeckoDriver.prototype.switchToFrame = async function (cmd) { 1778 const { element: el, id } = cmd.parameters; 1779 1780 if (typeof id == "number") { 1781 lazy.assert.unsignedShort( 1782 id, 1783 lazy.pprint`Expected "id" to be an unsigned short, got ${id}` 1784 ); 1785 } 1786 1787 const top = id == null && el == null; 1788 lazy.assert.open(this.getBrowsingContext({ top })); 1789 await this._handleUserPrompts(); 1790 1791 // Bug 1495063: Elements should be passed as WebReference reference 1792 let byFrame; 1793 if (typeof el == "string") { 1794 byFrame = lazy.WebElement.fromUUID(el).toJSON(); 1795 } else if (el) { 1796 byFrame = el; 1797 } 1798 1799 // If the current context changed during the switchToFrame call, attempt to 1800 // call switchToFrame again until the browsing context remains stable. 1801 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1786640#c11 1802 let browsingContext; 1803 for (let i = 0; i < 5; i++) { 1804 const currentBrowsingContext = this.currentSession.contentBrowsingContext; 1805 ({ browsingContext } = await this.getActor({ top }).switchToFrame( 1806 byFrame || id 1807 )); 1808 1809 if (currentBrowsingContext == this.currentSession.contentBrowsingContext) { 1810 break; 1811 } 1812 } 1813 1814 this.currentSession.contentBrowsingContext = browsingContext; 1815 }; 1816 1817 GeckoDriver.prototype.getTimeouts = function () { 1818 return this.currentSession.timeouts; 1819 }; 1820 1821 /** 1822 * Set timeout for page loading, searching, and scripts. 1823 * 1824 * @param {object} cmd 1825 * @param {Record<string, number>} cmd.parameters 1826 * Dictionary of timeout types and their new value, where all timeout 1827 * types are optional. 1828 * 1829 * @throws {InvalidArgumentError} 1830 * If timeout type key is unknown, or the value provided with it is 1831 * not an integer. 1832 */ 1833 GeckoDriver.prototype.setTimeouts = function (cmd) { 1834 // merge with existing timeouts 1835 let merged = Object.assign( 1836 this.currentSession.timeouts.toJSON(), 1837 cmd.parameters 1838 ); 1839 1840 this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged); 1841 }; 1842 1843 /** 1844 * Perform a series of grouped actions at the specified points in time. 1845 * 1846 * @param {object} cmd 1847 * @param {Array<?>} cmd.parameters.actions 1848 * Array of objects that each represent an action sequence. 1849 * 1850 * @throws {NoSuchElementError} 1851 * If an element that is used as part of the action chain is unknown. 1852 * @throws {NoSuchWindowError} 1853 * Browsing context has been discarded. 1854 * @throws {StaleElementReferenceError} 1855 * If an element that is used as part of the action chain has gone stale. 1856 * @throws {UnexpectedAlertOpenError} 1857 * A modal dialog is open, blocking this operation. 1858 * @throws {UnsupportedOperationError} 1859 * Not yet available in current context. 1860 */ 1861 GeckoDriver.prototype.performActions = async function (cmd) { 1862 const { actions } = cmd.parameters; 1863 1864 const browsingContext = lazy.assert.open(this.getBrowsingContext()); 1865 await this._handleUserPrompts(); 1866 1867 // Bug 1821460: Fetch top-level browsing context. 1868 const inputState = this._actionsHelper.getInputState(browsingContext); 1869 const actionsOptions = { 1870 ...this._actionsHelper.actionsOptions, 1871 context: browsingContext, 1872 }; 1873 1874 const actionChain = await lazy.actions.Chain.fromJSON( 1875 inputState, 1876 actions, 1877 actionsOptions 1878 ); 1879 1880 // Enqueue to serialize access to input state. 1881 await inputState.enqueueAction(() => 1882 actionChain.dispatch(inputState, actionsOptions) 1883 ); 1884 1885 // Process async follow-up tasks in content before the reply is sent. 1886 await this._actionsHelper.finalizeAction(browsingContext); 1887 }; 1888 1889 /** 1890 * Release all the keys and pointer buttons that are currently depressed. 1891 * 1892 * @throws {NoSuchWindowError} 1893 * Browsing context has been discarded. 1894 * @throws {UnexpectedAlertOpenError} 1895 * A modal dialog is open, blocking this operation. 1896 * @throws {UnsupportedOperationError} 1897 * Not available in current context. 1898 */ 1899 GeckoDriver.prototype.releaseActions = async function () { 1900 const browsingContext = lazy.assert.open(this.getBrowsingContext()); 1901 await this._handleUserPrompts(); 1902 1903 // Bug 1821460: Fetch top-level browsing context. 1904 const inputState = this._actionsHelper.getInputState(browsingContext); 1905 const actionsOptions = { 1906 ...this._actionsHelper.actionsOptions, 1907 context: browsingContext, 1908 }; 1909 1910 // Enqueue to serialize access to input state. 1911 await inputState.enqueueAction(() => { 1912 const undoActions = inputState.inputCancelList.reverse(); 1913 return undoActions.dispatch(inputState, actionsOptions); 1914 }); 1915 1916 this._actionsHelper.resetInputState(browsingContext); 1917 1918 // Process async follow-up tasks in content before the reply is sent. 1919 await this._actionsHelper.finalizeAction(browsingContext); 1920 }; 1921 1922 /** 1923 * Find an element using the indicated search strategy. 1924 * 1925 * @param {object} cmd 1926 * @param {string=} cmd.parameters.element 1927 * Web element reference ID to the element that will be used as start node. 1928 * @param {string} cmd.parameters.using 1929 * Indicates which search method to use. 1930 * @param {string} cmd.parameters.value 1931 * Value the client is looking for. 1932 * 1933 * @returns {WebElement} 1934 * Return the found element. 1935 * 1936 * @throws {NoSuchElementError} 1937 * If element represented by reference <var>element</var> is unknown. 1938 * @throws {NoSuchWindowError} 1939 * Browsing context has been discarded. 1940 * @throws {StaleElementReferenceError} 1941 * If element represented by reference <var>element</var> has gone stale. 1942 * @throws {UnexpectedAlertOpenError} 1943 * A modal dialog is open, blocking this operation. 1944 */ 1945 GeckoDriver.prototype.findElement = async function (cmd) { 1946 const { element: el, using, value } = cmd.parameters; 1947 1948 if (!lazy.supportedStrategies.has(using)) { 1949 throw new lazy.error.InvalidSelectorError( 1950 `Strategy not supported: ${using}` 1951 ); 1952 } 1953 1954 lazy.assert.defined(value); 1955 lazy.assert.open(this.getBrowsingContext()); 1956 await this._handleUserPrompts(); 1957 1958 let startNode; 1959 if (typeof el != "undefined") { 1960 startNode = lazy.WebElement.fromUUID(el).toJSON(); 1961 } 1962 1963 let opts = { 1964 startNode, 1965 timeout: this.currentSession.timeouts.implicit, 1966 all: false, 1967 }; 1968 1969 return this.getActor().findElement(using, value, opts); 1970 }; 1971 1972 /** 1973 * Find an element within shadow root using the indicated search strategy. 1974 * 1975 * @param {object} cmd 1976 * @param {string} cmd.parameters.shadowRoot 1977 * Shadow root reference ID. 1978 * @param {string} cmd.parameters.using 1979 * Indicates which search method to use. 1980 * @param {string} cmd.parameters.value 1981 * Value the client is looking for. 1982 * 1983 * @returns {WebElement} 1984 * Return the found element. 1985 * 1986 * @throws {DetachedShadowRootError} 1987 * If shadow root represented by reference <var>id</var> is 1988 * no longer attached to the DOM. 1989 * @throws {NoSuchElementError} 1990 * If the element which is looked for with <var>value</var> was 1991 * not found. 1992 * @throws {NoSuchShadowRoot} 1993 * If shadow root represented by reference <var>shadowRoot</var> is unknown. 1994 * @throws {NoSuchWindowError} 1995 * Browsing context has been discarded. 1996 * @throws {UnexpectedAlertOpenError} 1997 * A modal dialog is open, blocking this operation. 1998 */ 1999 GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) { 2000 const { shadowRoot, using, value } = cmd.parameters; 2001 2002 if (!lazy.supportedStrategies.has(using)) { 2003 throw new lazy.error.InvalidSelectorError( 2004 `Strategy not supported: ${using}` 2005 ); 2006 } 2007 2008 lazy.assert.defined(value); 2009 lazy.assert.open(this.getBrowsingContext()); 2010 await this._handleUserPrompts(); 2011 2012 const opts = { 2013 all: false, 2014 startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(), 2015 timeout: this.currentSession.timeouts.implicit, 2016 }; 2017 2018 return this.getActor().findElement(using, value, opts); 2019 }; 2020 2021 /** 2022 * Find elements using the indicated search strategy. 2023 * 2024 * @param {object} cmd 2025 * @param {string=} cmd.parameters.element 2026 * Web element reference ID to the element that will be used as start node. 2027 * @param {string} cmd.parameters.using 2028 * Indicates which search method to use. 2029 * @param {string} cmd.parameters.value 2030 * Value the client is looking for. 2031 * 2032 * @returns {Array<WebElement>} 2033 * Return the array of found elements. 2034 * 2035 * @throws {NoSuchElementError} 2036 * If element represented by reference <var>element</var> is unknown. 2037 * @throws {NoSuchWindowError} 2038 * Browsing context has been discarded. 2039 * @throws {StaleElementReferenceError} 2040 * If element represented by reference <var>element</var> has gone stale. 2041 * @throws {UnexpectedAlertOpenError} 2042 * A modal dialog is open, blocking this operation. 2043 */ 2044 GeckoDriver.prototype.findElements = async function (cmd) { 2045 const { element: el, using, value } = cmd.parameters; 2046 2047 if (!lazy.supportedStrategies.has(using)) { 2048 throw new lazy.error.InvalidSelectorError( 2049 `Strategy not supported: ${using}` 2050 ); 2051 } 2052 2053 lazy.assert.defined(value); 2054 lazy.assert.open(this.getBrowsingContext()); 2055 await this._handleUserPrompts(); 2056 2057 let startNode; 2058 if (typeof el != "undefined") { 2059 startNode = lazy.WebElement.fromUUID(el).toJSON(); 2060 } 2061 2062 let opts = { 2063 startNode, 2064 timeout: this.currentSession.timeouts.implicit, 2065 all: true, 2066 }; 2067 2068 return this.getActor().findElements(using, value, opts); 2069 }; 2070 2071 /** 2072 * Find elements within shadow root using the indicated search strategy. 2073 * 2074 * @param {object} cmd 2075 * @param {string} cmd.parameters.shadowRoot 2076 * Shadow root reference ID. 2077 * @param {string} cmd.parameters.using 2078 * Indicates which search method to use. 2079 * @param {string} cmd.parameters.value 2080 * Value the client is looking for. 2081 * 2082 * @returns {Array<WebElement>} 2083 * Return the array of found elements. 2084 * 2085 * @throws {DetachedShadowRootError} 2086 * If shadow root represented by reference <var>id</var> is 2087 * no longer attached to the DOM. 2088 * @throws {NoSuchShadowRoot} 2089 * If shadow root represented by reference <var>shadowRoot</var> is unknown. 2090 * @throws {NoSuchWindowError} 2091 * Browsing context has been discarded. 2092 * @throws {UnexpectedAlertOpenError} 2093 * A modal dialog is open, blocking this operation. 2094 */ 2095 GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) { 2096 const { shadowRoot, using, value } = cmd.parameters; 2097 2098 if (!lazy.supportedStrategies.has(using)) { 2099 throw new lazy.error.InvalidSelectorError( 2100 `Strategy not supported: ${using}` 2101 ); 2102 } 2103 2104 lazy.assert.defined(value); 2105 lazy.assert.open(this.getBrowsingContext()); 2106 await this._handleUserPrompts(); 2107 2108 const opts = { 2109 all: true, 2110 startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(), 2111 timeout: this.currentSession.timeouts.implicit, 2112 }; 2113 2114 return this.getActor().findElements(using, value, opts); 2115 }; 2116 2117 /** 2118 * Return the shadow root of an element in the document. 2119 * 2120 * @param {object} cmd 2121 * @param {id} cmd.parameters.id 2122 * A web element id reference. 2123 * @returns {ShadowRoot} 2124 * ShadowRoot of the element. 2125 * 2126 * @throws {InvalidArgumentError} 2127 * If element <var>id</var> is not a string. 2128 * @throws {NoSuchElementError} 2129 * If element represented by reference <var>id</var> is unknown. 2130 * @throws {NoSuchShadowRoot} 2131 * Element does not have a shadow root attached. 2132 * @throws {NoSuchWindowError} 2133 * Browsing context has been discarded. 2134 * @throws {StaleElementReferenceError} 2135 * If element represented by reference <var>id</var> has gone stale. 2136 * @throws {UnexpectedAlertOpenError} 2137 * A modal dialog is open, blocking this operation. 2138 * @throws {UnsupportedOperationError} 2139 * Not available in chrome current context. 2140 */ 2141 GeckoDriver.prototype.getShadowRoot = async function (cmd) { 2142 // Bug 1743541: Add support for chrome scope. 2143 lazy.assert.content(this.context); 2144 lazy.assert.open(this.getBrowsingContext()); 2145 await this._handleUserPrompts(); 2146 2147 let id = lazy.assert.string( 2148 cmd.parameters.id, 2149 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2150 ); 2151 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2152 2153 return this.getActor().getShadowRoot(webEl); 2154 }; 2155 2156 /** 2157 * Return the active element in the document. 2158 * 2159 * @returns {WebReference} 2160 * Active element of the current browsing context's document 2161 * element, if the document element is non-null. 2162 * 2163 * @throws {NoSuchElementError} 2164 * If the document does not have an active element, i.e. if 2165 * its document element has been deleted. 2166 * @throws {NoSuchWindowError} 2167 * Browsing context has been discarded. 2168 * @throws {UnexpectedAlertOpenError} 2169 * A modal dialog is open, blocking this operation. 2170 * @throws {UnsupportedOperationError} 2171 * Not available in chrome context. 2172 */ 2173 GeckoDriver.prototype.getActiveElement = async function () { 2174 lazy.assert.content(this.context); 2175 lazy.assert.open(this.getBrowsingContext()); 2176 await this._handleUserPrompts(); 2177 2178 return this.getActor().getActiveElement(); 2179 }; 2180 2181 /** 2182 * Send click event to element. 2183 * 2184 * @param {object} cmd 2185 * @param {string} cmd.parameters.id 2186 * Reference ID to the element that will be clicked. 2187 * 2188 * @throws {InvalidArgumentError} 2189 * If element <var>id</var> is not a string. 2190 * @throws {NoSuchElementError} 2191 * If element represented by reference <var>id</var> is unknown. 2192 * @throws {NoSuchWindowError} 2193 * Browsing context has been discarded. 2194 * @throws {StaleElementReferenceError} 2195 * If element represented by reference <var>id</var> has gone stale. 2196 * @throws {UnexpectedAlertOpenError} 2197 * A modal dialog is open, blocking this operation. 2198 */ 2199 GeckoDriver.prototype.clickElement = async function (cmd) { 2200 const browsingContext = lazy.assert.open(this.getBrowsingContext()); 2201 await this._handleUserPrompts(); 2202 2203 let id = lazy.assert.string( 2204 cmd.parameters.id, 2205 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2206 ); 2207 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2208 2209 const actor = this.getActor(); 2210 2211 const loadEventExpected = lazy.navigate.isLoadEventExpected( 2212 this._getCurrentURL(), 2213 { 2214 browsingContext, 2215 target: await actor.getElementAttribute(webEl, "target"), 2216 } 2217 ); 2218 2219 await lazy.navigate.waitForNavigationCompleted( 2220 this, 2221 () => actor.clickElement(webEl, this.currentSession.capabilities), 2222 { 2223 loadEventExpected, 2224 // The click might trigger a navigation, so don't count on it. 2225 requireBeforeUnload: false, 2226 } 2227 ); 2228 }; 2229 2230 /** 2231 * Get a given attribute of an element. 2232 * 2233 * @param {object} cmd 2234 * @param {string} cmd.parameters.id 2235 * Web element reference ID to the element that will be inspected. 2236 * @param {string} cmd.parameters.name 2237 * Name of the attribute which value to retrieve. 2238 * 2239 * @returns {string} 2240 * Value of the attribute. 2241 * 2242 * @throws {InvalidArgumentError} 2243 * If <var>id</var> or <var>name</var> are not strings. 2244 * @throws {NoSuchElementError} 2245 * If element represented by reference <var>id</var> is unknown. 2246 * @throws {NoSuchWindowError} 2247 * Browsing context has been discarded. 2248 * @throws {StaleElementReferenceError} 2249 * If element represented by reference <var>id</var> has gone stale. 2250 * @throws {UnexpectedAlertOpenError} 2251 * A modal dialog is open, blocking this operation. 2252 */ 2253 GeckoDriver.prototype.getElementAttribute = async function (cmd) { 2254 lazy.assert.open(this.getBrowsingContext()); 2255 await this._handleUserPrompts(); 2256 2257 const id = lazy.assert.string( 2258 cmd.parameters.id, 2259 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2260 ); 2261 const name = lazy.assert.string( 2262 cmd.parameters.name, 2263 lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}` 2264 ); 2265 const webEl = lazy.WebElement.fromUUID(id).toJSON(); 2266 2267 return this.getActor().getElementAttribute(webEl, name); 2268 }; 2269 2270 /** 2271 * Returns the value of a property associated with given element. 2272 * 2273 * @param {object} cmd 2274 * @param {string} cmd.parameters.id 2275 * Web element reference ID to the element that will be inspected. 2276 * @param {string} cmd.parameters.name 2277 * Name of the property which value to retrieve. 2278 * 2279 * @returns {string} 2280 * Value of the property. 2281 * 2282 * @throws {InvalidArgumentError} 2283 * If <var>id</var> or <var>name</var> are not strings. 2284 * @throws {NoSuchElementError} 2285 * If element represented by reference <var>id</var> is unknown. 2286 * @throws {NoSuchWindowError} 2287 * Browsing context has been discarded. 2288 * @throws {StaleElementReferenceError} 2289 * If element represented by reference <var>id</var> has gone stale. 2290 * @throws {UnexpectedAlertOpenError} 2291 * A modal dialog is open, blocking this operation. 2292 */ 2293 GeckoDriver.prototype.getElementProperty = async function (cmd) { 2294 lazy.assert.open(this.getBrowsingContext()); 2295 await this._handleUserPrompts(); 2296 2297 const id = lazy.assert.string( 2298 cmd.parameters.id, 2299 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2300 ); 2301 const name = lazy.assert.string( 2302 cmd.parameters.name, 2303 lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}` 2304 ); 2305 const webEl = lazy.WebElement.fromUUID(id).toJSON(); 2306 2307 return this.getActor().getElementProperty(webEl, name); 2308 }; 2309 2310 /** 2311 * Get the text of an element, if any. Includes the text of all child 2312 * elements. 2313 * 2314 * @param {object} cmd 2315 * @param {string} cmd.parameters.id 2316 * Reference ID to the element that will be inspected. 2317 * 2318 * @returns {string} 2319 * Element's text "as rendered". 2320 * 2321 * @throws {InvalidArgumentError} 2322 * If <var>id</var> is not a string. 2323 * @throws {NoSuchElementError} 2324 * If element represented by reference <var>id</var> is unknown. 2325 * @throws {NoSuchWindowError} 2326 * Browsing context has been discarded. 2327 * @throws {StaleElementReferenceError} 2328 * If element represented by reference <var>id</var> has gone stale. 2329 * @throws {UnexpectedAlertOpenError} 2330 * A modal dialog is open, blocking this operation. 2331 */ 2332 GeckoDriver.prototype.getElementText = async function (cmd) { 2333 lazy.assert.open(this.getBrowsingContext()); 2334 await this._handleUserPrompts(); 2335 2336 let id = lazy.assert.string( 2337 cmd.parameters.id, 2338 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2339 ); 2340 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2341 2342 return this.getActor().getElementText(webEl); 2343 }; 2344 2345 /** 2346 * Get the tag name of the element. 2347 * 2348 * @param {object} cmd 2349 * @param {string} cmd.parameters.id 2350 * Reference ID to the element that will be inspected. 2351 * 2352 * @returns {string} 2353 * Local tag name of element. 2354 * 2355 * @throws {InvalidArgumentError} 2356 * If <var>id</var> is not a string. 2357 * @throws {NoSuchElementError} 2358 * If element represented by reference <var>id</var> is unknown. 2359 * @throws {NoSuchWindowError} 2360 * Browsing context has been discarded. 2361 * @throws {StaleElementReferenceError} 2362 * If element represented by reference <var>id</var> has gone stale. 2363 * @throws {UnexpectedAlertOpenError} 2364 * A modal dialog is open, blocking this operation. 2365 */ 2366 GeckoDriver.prototype.getElementTagName = async function (cmd) { 2367 lazy.assert.open(this.getBrowsingContext()); 2368 await this._handleUserPrompts(); 2369 2370 let id = lazy.assert.string( 2371 cmd.parameters.id, 2372 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2373 ); 2374 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2375 2376 return this.getActor().getElementTagName(webEl); 2377 }; 2378 2379 /** 2380 * Check if element is displayed. 2381 * 2382 * @param {object} cmd 2383 * @param {string} cmd.parameters.id 2384 * Reference ID to the element that will be inspected. 2385 * 2386 * @returns {boolean} 2387 * True if displayed, false otherwise. 2388 * 2389 * @throws {InvalidArgumentError} 2390 * If <var>id</var> is not a string. 2391 * @throws {NoSuchElementError} 2392 * If element represented by reference <var>id</var> is unknown. 2393 * @throws {NoSuchWindowError} 2394 * Browsing context has been discarded. 2395 * @throws {UnexpectedAlertOpenError} 2396 * A modal dialog is open, blocking this operation. 2397 */ 2398 GeckoDriver.prototype.isElementDisplayed = async function (cmd) { 2399 lazy.assert.open(this.getBrowsingContext()); 2400 await this._handleUserPrompts(); 2401 2402 let id = lazy.assert.string( 2403 cmd.parameters.id, 2404 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2405 ); 2406 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2407 2408 return this.getActor().isElementDisplayed( 2409 webEl, 2410 this.currentSession.capabilities 2411 ); 2412 }; 2413 2414 /** 2415 * Return the property of the computed style of an element. 2416 * 2417 * @param {object} cmd 2418 * @param {string} cmd.parameters.id 2419 * Reference ID to the element that will be checked. 2420 * @param {string} cmd.parameters.propertyName 2421 * CSS rule that is being requested. 2422 * 2423 * @returns {string} 2424 * Value of |propertyName|. 2425 * 2426 * @throws {InvalidArgumentError} 2427 * If <var>id</var> or <var>propertyName</var> are not strings. 2428 * @throws {NoSuchElementError} 2429 * If element represented by reference <var>id</var> is unknown. 2430 * @throws {NoSuchWindowError} 2431 * Browsing context has been discarded. 2432 * @throws {StaleElementReferenceError} 2433 * If element represented by reference <var>id</var> has gone stale. 2434 * @throws {UnexpectedAlertOpenError} 2435 * A modal dialog is open, blocking this operation. 2436 */ 2437 GeckoDriver.prototype.getElementValueOfCssProperty = async function (cmd) { 2438 lazy.assert.open(this.getBrowsingContext()); 2439 await this._handleUserPrompts(); 2440 2441 let id = lazy.assert.string( 2442 cmd.parameters.id, 2443 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2444 ); 2445 let prop = lazy.assert.string( 2446 cmd.parameters.propertyName, 2447 lazy.pprint`Expected "propertyName" to be a string, got ${cmd.parameters.propertyName}` 2448 ); 2449 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2450 2451 return this.getActor().getElementValueOfCssProperty(webEl, prop); 2452 }; 2453 2454 /** 2455 * Check if element is enabled. 2456 * 2457 * @param {object} cmd 2458 * @param {string} cmd.parameters.id 2459 * Reference ID to the element that will be checked. 2460 * 2461 * @returns {boolean} 2462 * True if enabled, false if disabled. 2463 * 2464 * @throws {InvalidArgumentError} 2465 * If <var>id</var> is not a string. 2466 * @throws {NoSuchElementError} 2467 * If element represented by reference <var>id</var> is unknown. 2468 * @throws {NoSuchWindowError} 2469 * Browsing context has been discarded. 2470 * @throws {StaleElementReferenceError} 2471 * If element represented by reference <var>id</var> has gone stale. 2472 * @throws {UnexpectedAlertOpenError} 2473 * A modal dialog is open, blocking this operation. 2474 */ 2475 GeckoDriver.prototype.isElementEnabled = async function (cmd) { 2476 lazy.assert.open(this.getBrowsingContext()); 2477 await this._handleUserPrompts(); 2478 2479 let id = lazy.assert.string( 2480 cmd.parameters.id, 2481 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2482 ); 2483 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2484 2485 return this.getActor().isElementEnabled( 2486 webEl, 2487 this.currentSession.capabilities 2488 ); 2489 }; 2490 2491 /** 2492 * Check if element is selected. 2493 * 2494 * @param {object} cmd 2495 * @param {string} cmd.parameters.id 2496 * Reference ID to the element that will be checked. 2497 * 2498 * @returns {boolean} 2499 * True if selected, false if unselected. 2500 * 2501 * @throws {InvalidArgumentError} 2502 * If <var>id</var> is not a string. 2503 * @throws {NoSuchElementError} 2504 * If element represented by reference <var>id</var> is unknown. 2505 * @throws {NoSuchWindowError} 2506 * Browsing context has been discarded. 2507 * @throws {UnexpectedAlertOpenError} 2508 * A modal dialog is open, blocking this operation. 2509 */ 2510 GeckoDriver.prototype.isElementSelected = async function (cmd) { 2511 lazy.assert.open(this.getBrowsingContext()); 2512 await this._handleUserPrompts(); 2513 2514 let id = lazy.assert.string( 2515 cmd.parameters.id, 2516 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2517 ); 2518 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2519 2520 return this.getActor().isElementSelected( 2521 webEl, 2522 this.currentSession.capabilities 2523 ); 2524 }; 2525 2526 /** 2527 * @throws {InvalidArgumentError} 2528 * If <var>id</var> is not a string. 2529 * @throws {NoSuchElementError} 2530 * If element represented by reference <var>id</var> is unknown. 2531 * @throws {NoSuchWindowError} 2532 * Browsing context has been discarded. 2533 * @throws {StaleElementReferenceError} 2534 * If element represented by reference <var>id</var> has gone stale. 2535 * @throws {UnexpectedAlertOpenError} 2536 * A modal dialog is open, blocking this operation. 2537 */ 2538 GeckoDriver.prototype.getElementRect = async function (cmd) { 2539 lazy.assert.open(this.getBrowsingContext()); 2540 await this._handleUserPrompts(); 2541 2542 let id = lazy.assert.string( 2543 cmd.parameters.id, 2544 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2545 ); 2546 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2547 2548 return this.getActor().getElementRect(webEl); 2549 }; 2550 2551 /** 2552 * Send key presses to element after focusing on it. 2553 * 2554 * @param {object} cmd 2555 * @param {string} cmd.parameters.id 2556 * Reference ID to the element that will be checked. 2557 * @param {string} cmd.parameters.text 2558 * Value to send to the element. 2559 * 2560 * @throws {InvalidArgumentError} 2561 * If <var>id</var> or <var>text</var> are not strings. 2562 * @throws {NoSuchElementError} 2563 * If element represented by reference <var>id</var> is unknown. 2564 * @throws {NoSuchWindowError} 2565 * Browsing context has been discarded. 2566 * @throws {StaleElementReferenceError} 2567 * If element represented by reference <var>id</var> has gone stale. 2568 * @throws {UnexpectedAlertOpenError} 2569 * A modal dialog is open, blocking this operation. 2570 */ 2571 GeckoDriver.prototype.sendKeysToElement = async function (cmd) { 2572 lazy.assert.open(this.getBrowsingContext()); 2573 await this._handleUserPrompts(); 2574 2575 let id = lazy.assert.string( 2576 cmd.parameters.id, 2577 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2578 ); 2579 let text = lazy.assert.string( 2580 cmd.parameters.text, 2581 lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}` 2582 ); 2583 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2584 2585 return this.getActor().sendKeysToElement( 2586 webEl, 2587 text, 2588 this.currentSession.capabilities 2589 ); 2590 }; 2591 2592 /** 2593 * Clear the text of an element. 2594 * 2595 * @param {object} cmd 2596 * @param {string} cmd.parameters.id 2597 * Reference ID to the element that will be cleared. 2598 * 2599 * @throws {InvalidArgumentError} 2600 * If <var>id</var> is not a string. 2601 * @throws {NoSuchElementError} 2602 * If element represented by reference <var>id</var> is unknown. 2603 * @throws {NoSuchWindowError} 2604 * Browsing context has been discarded. 2605 * @throws {StaleElementReferenceError} 2606 * If element represented by reference <var>id</var> has gone stale. 2607 * @throws {UnexpectedAlertOpenError} 2608 * A modal dialog is open, blocking this operation. 2609 */ 2610 GeckoDriver.prototype.clearElement = async function (cmd) { 2611 lazy.assert.open(this.getBrowsingContext()); 2612 await this._handleUserPrompts(); 2613 2614 let id = lazy.assert.string( 2615 cmd.parameters.id, 2616 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 2617 ); 2618 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 2619 2620 await this.getActor().clearElement(webEl); 2621 }; 2622 2623 /** 2624 * Add a single cookie to the cookie store associated with the active 2625 * document's address. 2626 * 2627 * @param {object} cmd 2628 * @param {Map.<string, (string|number|boolean)>} cmd.parameters.cookie 2629 * Cookie object. 2630 * 2631 * @throws {InvalidCookieDomainError} 2632 * If <var>cookie</var> is for a different domain than the active 2633 * document's host. 2634 * @throws {NoSuchWindowError} 2635 * Bbrowsing context has been discarded. 2636 * @throws {UnexpectedAlertOpenError} 2637 * A modal dialog is open, blocking this operation. 2638 * @throws {UnsupportedOperationError} 2639 * Not available in current context. 2640 */ 2641 GeckoDriver.prototype.addCookie = async function (cmd) { 2642 lazy.assert.content(this.context); 2643 lazy.assert.open(this.getBrowsingContext()); 2644 await this._handleUserPrompts(); 2645 2646 let { protocol, hostname } = this._getCurrentURL({ top: false }); 2647 2648 const networkSchemes = ["http:", "https:"]; 2649 if (!networkSchemes.includes(protocol)) { 2650 throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse"); 2651 } 2652 2653 let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie); 2654 2655 lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol }); 2656 }; 2657 2658 /** 2659 * Get all the cookies for the current domain. 2660 * 2661 * This is the equivalent of calling <code>document.cookie</code> and 2662 * parsing the result. 2663 * 2664 * @throws {NoSuchWindowError} 2665 * Browsing context has been discarded. 2666 * @throws {UnexpectedAlertOpenError} 2667 * A modal dialog is open, blocking this operation. 2668 * @throws {UnsupportedOperationError} 2669 * Not available in current context. 2670 */ 2671 GeckoDriver.prototype.getCookies = async function () { 2672 lazy.assert.content(this.context); 2673 lazy.assert.open(this.getBrowsingContext()); 2674 await this._handleUserPrompts(); 2675 2676 let { hostname, pathname } = this._getCurrentURL({ top: false }); 2677 return [...lazy.cookie.iter(hostname, this.getBrowsingContext(), pathname)]; 2678 }; 2679 2680 /** 2681 * Delete all cookies that are visible to a document. 2682 * 2683 * @throws {NoSuchWindowError} 2684 * Browsing context has been discarded. 2685 * @throws {UnexpectedAlertOpenError} 2686 * A modal dialog is open, blocking this operation. 2687 * @throws {UnsupportedOperationError} 2688 * Not available in current context. 2689 */ 2690 GeckoDriver.prototype.deleteAllCookies = async function () { 2691 lazy.assert.content(this.context); 2692 lazy.assert.open(this.getBrowsingContext()); 2693 await this._handleUserPrompts(); 2694 2695 let { hostname, pathname } = this._getCurrentURL({ top: false }); 2696 for (let toDelete of lazy.cookie.iter( 2697 hostname, 2698 this.getBrowsingContext(), 2699 pathname 2700 )) { 2701 lazy.cookie.remove(toDelete); 2702 } 2703 }; 2704 2705 /** 2706 * Delete a cookie by name. 2707 * 2708 * @throws {NoSuchWindowError} 2709 * Browsing context has been discarded. 2710 * @throws {UnexpectedAlertOpenError} 2711 * A modal dialog is open, blocking this operation. 2712 * @throws {UnsupportedOperationError} 2713 * Not available in current context. 2714 */ 2715 GeckoDriver.prototype.deleteCookie = async function (cmd) { 2716 lazy.assert.content(this.context); 2717 lazy.assert.open(this.getBrowsingContext()); 2718 await this._handleUserPrompts(); 2719 2720 let { hostname, pathname } = this._getCurrentURL({ top: false }); 2721 let name = lazy.assert.string( 2722 cmd.parameters.name, 2723 lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}` 2724 ); 2725 for (let c of lazy.cookie.iter( 2726 hostname, 2727 this.getBrowsingContext(), 2728 pathname 2729 )) { 2730 if (c.name === name) { 2731 lazy.cookie.remove(c); 2732 } 2733 } 2734 }; 2735 2736 /** 2737 * Open a new top-level browsing context. 2738 * 2739 * @param {object} cmd 2740 * @param {string=} cmd.parameters.type 2741 * Optional type of the new top-level browsing context. Can be one of 2742 * `tab` or `window`. Defaults to `tab`. 2743 * @param {boolean=} cmd.parameters.focus 2744 * Optional flag if the new top-level browsing context should be opened 2745 * in foreground (focused) or background (not focused). Defaults to false. 2746 * @param {boolean=} cmd.parameters.private 2747 * Optional flag, which gets only evaluated for type `window`. True if the 2748 * new top-level browsing context should be a private window. 2749 * Defaults to false. 2750 * 2751 * @returns {Record<string, string>} 2752 * Handle and type of the new browsing context. 2753 * 2754 * @throws {NoSuchWindowError} 2755 * Top-level browsing context has been discarded. 2756 * @throws {UnexpectedAlertOpenError} 2757 * A modal dialog is open, blocking this operation. 2758 */ 2759 GeckoDriver.prototype.newWindow = async function (cmd) { 2760 lazy.assert.open(this.getBrowsingContext({ top: true })); 2761 await this._handleUserPrompts(); 2762 2763 let focus = false; 2764 if (typeof cmd.parameters.focus != "undefined") { 2765 focus = lazy.assert.boolean( 2766 cmd.parameters.focus, 2767 lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}` 2768 ); 2769 } 2770 2771 let isPrivate = false; 2772 if (typeof cmd.parameters.private != "undefined") { 2773 isPrivate = lazy.assert.boolean( 2774 cmd.parameters.private, 2775 lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}` 2776 ); 2777 } 2778 2779 let type; 2780 if (typeof cmd.parameters.type != "undefined") { 2781 type = lazy.assert.string( 2782 cmd.parameters.type, 2783 lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}` 2784 ); 2785 } 2786 2787 // If an invalid or no type has been specified default to a tab. 2788 // On Android always use a new tab instead because the application has a 2789 // single window only. 2790 if ( 2791 typeof type == "undefined" || 2792 !["tab", "window"].includes(type) || 2793 lazy.AppInfo.isAndroid 2794 ) { 2795 if (lazy.TabManager.supportsTabs()) { 2796 type = "tab"; 2797 } else if (lazy.windowManager.supportsWindows()) { 2798 type = "window"; 2799 } else { 2800 throw new lazy.error.UnsupportedOperationError( 2801 `Not supported in ${lazy.AppInfo.name}` 2802 ); 2803 } 2804 } 2805 2806 let contentBrowser; 2807 2808 switch (type) { 2809 case "window": { 2810 if (lazy.windowManager.supportsWindows()) { 2811 let win = await this.curBrowser.openBrowserWindow(focus, isPrivate); 2812 contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser; 2813 } else { 2814 throw new lazy.error.UnsupportedOperationError( 2815 `Not supported in ${lazy.AppInfo.name}` 2816 ); 2817 } 2818 break; 2819 } 2820 default: { 2821 // To not fail if a new type gets added in the future, make opening 2822 // a new tab the default action. 2823 if (lazy.TabManager.supportsTabs()) { 2824 let tab = await this.curBrowser.openTab(focus); 2825 contentBrowser = lazy.TabManager.getBrowserForTab(tab); 2826 } else { 2827 throw new lazy.error.UnsupportedOperationError( 2828 `Not supported in ${lazy.AppInfo.name}` 2829 ); 2830 } 2831 } 2832 } 2833 2834 // Actors need the new window to be loaded to safely execute queries. 2835 // Wait until the initial page load has been finished. 2836 await lazy.waitForInitialNavigationCompleted( 2837 contentBrowser.browsingContext.webProgress, 2838 { 2839 unloadTimeout: 5000, 2840 } 2841 ); 2842 2843 const id = lazy.NavigableManager.getIdForBrowser(contentBrowser); 2844 2845 return { handle: id.toString(), type }; 2846 }; 2847 2848 /** 2849 * Close the currently selected tab/window. 2850 * 2851 * With multiple open tabs present the currently selected tab will 2852 * be closed. Otherwise the window itself will be closed. If it is the 2853 * last window currently open, the window will not be closed to prevent 2854 * a shutdown of the application. Instead the returned list of window 2855 * handles is empty. 2856 * 2857 * @returns {Array.<string>} 2858 * Unique window handles of remaining windows. 2859 * 2860 * @throws {NoSuchWindowError} 2861 * Top-level browsing context has been discarded. 2862 * @throws {UnexpectedAlertOpenError} 2863 * A modal dialog is open, blocking this operation. 2864 */ 2865 GeckoDriver.prototype.close = async function () { 2866 lazy.assert.open( 2867 this.getBrowsingContext({ context: lazy.Context.Content, top: true }) 2868 ); 2869 await this._handleUserPrompts(); 2870 2871 // If there is only one window left, do not close unless windowless mode is 2872 // enabled. Instead return a faked empty array of window handles. 2873 // This will instruct geckodriver to terminate the application. 2874 if ( 2875 lazy.TabManager.getTabCount() === 1 && 2876 !this.currentSession.capabilities.get("moz:windowless") 2877 ) { 2878 return []; 2879 } 2880 2881 await this.curBrowser.closeTab(); 2882 this.currentSession.contentBrowsingContext = null; 2883 2884 return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser => 2885 lazy.NavigableManager.getIdForBrowser(browser) 2886 ); 2887 }; 2888 2889 /** 2890 * Close the currently selected chrome window. 2891 * 2892 * If it is the last window currently open, the chrome window will not be 2893 * closed to prevent a shutdown of the application. Instead the returned 2894 * list of chrome window handles is empty. 2895 * 2896 * @returns {Array.<string>} 2897 * Unique chrome window handles of remaining chrome windows. 2898 * 2899 * @throws {NoSuchWindowError} 2900 * Top-level browsing context has been discarded. 2901 */ 2902 GeckoDriver.prototype.closeChromeWindow = async function () { 2903 lazy.assert.desktop(); 2904 lazy.assert.open( 2905 this.getBrowsingContext({ context: lazy.Context.Chrome, top: true }) 2906 ); 2907 2908 let nwins = 0; 2909 2910 // eslint-disable-next-line 2911 for (let _ of lazy.windowManager.windows) { 2912 nwins++; 2913 } 2914 2915 // If there is only one window left, do not close unless windowless mode is 2916 // enabled. Instead return a faked empty array of window handles. 2917 // This will instruct geckodriver to terminate the application. 2918 if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) { 2919 return []; 2920 } 2921 2922 await this.curBrowser.closeWindow(); 2923 this.currentSession.chromeBrowsingContext = null; 2924 this.currentSession.contentBrowsingContext = null; 2925 2926 return lazy.windowManager.windows.map(window => 2927 lazy.NavigableManager.getIdForBrowsingContext(window.browsingContext) 2928 ); 2929 }; 2930 2931 /** Delete Marionette session. */ 2932 GeckoDriver.prototype.deleteSession = function () { 2933 if (!this.currentSession) { 2934 return; 2935 } 2936 2937 for (let win of lazy.windowManager.windows) { 2938 this.stopObservingWindow(win); 2939 } 2940 2941 // reset to the top-most frame 2942 this.mainFrame = null; 2943 2944 if (!this._isShuttingDown && this.promptListener) { 2945 // Do not stop the prompt listener when quitting the browser to 2946 // allow us to also accept beforeunload prompts during shutdown. 2947 this.promptListener.stopListening(); 2948 this.promptListener = null; 2949 } 2950 2951 try { 2952 Services.obs.removeObserver(this, TOPIC_BROWSER_READY); 2953 } catch (e) { 2954 lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`); 2955 } 2956 2957 // Always unregister actors after all other observers 2958 // and listeners have been removed. 2959 lazy.unregisterCommandsActor(); 2960 // MarionetteEvents actors are only disabled to avoid IPC errors if there are 2961 // in flight events being forwarded from the content process to the parent 2962 // process. 2963 lazy.disableEventsActor(); 2964 2965 if (lazy.RemoteAgent.webDriverBiDi) { 2966 lazy.RemoteAgent.webDriverBiDi.deleteSession(); 2967 } else { 2968 this.currentSession.destroy(); 2969 this._currentSession = null; 2970 } 2971 }; 2972 2973 /** 2974 * Takes a screenshot of a web element, current frame, or viewport. 2975 * 2976 * The screen capture is returned as a lossless PNG image encoded as 2977 * a base 64 string. 2978 * 2979 * If called in the content context, the |id| argument is not null and 2980 * refers to a present and visible web element's ID, the capture area will 2981 * be limited to the bounding box of that element. Otherwise, the capture 2982 * area will be the bounding box of the current frame. 2983 * 2984 * If called in the chrome context, the screenshot will always represent 2985 * the entire viewport. 2986 * 2987 * @param {object} cmd 2988 * @param {string=} cmd.parameters.id 2989 * Optional web element reference to take a screenshot of. 2990 * If undefined, a screenshot will be taken of the document element. 2991 * @param {boolean=} cmd.parameters.full 2992 * True to take a screenshot of the entire document element. Is only 2993 * considered if <var>id</var> is not defined. Defaults to true. 2994 * @param {boolean=} cmd.parameters.hash 2995 * True if the user requests a hash of the image data. Defaults to false. 2996 * @param {boolean=} cmd.parameters.scroll 2997 * Scroll to element if |id| is provided. Defaults to true. 2998 * 2999 * @returns {string} 3000 * If <var>hash</var> is false, PNG image encoded as Base64 encoded 3001 * string. If <var>hash</var> is true, hex digest of the SHA-256 3002 * hash of the Base64 encoded string. 3003 * 3004 * @throws {NoSuchElementError} 3005 * If element represented by reference <var>id</var> is unknown. 3006 * @throws {NoSuchWindowError} 3007 * Browsing context has been discarded. 3008 * @throws {StaleElementReferenceError} 3009 * If element represented by reference <var>id</var> has gone stale. 3010 */ 3011 GeckoDriver.prototype.takeScreenshot = async function (cmd) { 3012 lazy.assert.open(this.getBrowsingContext({ top: true })); 3013 await this._handleUserPrompts(); 3014 3015 let { id, full, hash, scroll } = cmd.parameters; 3016 let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64; 3017 3018 full = typeof full == "undefined" ? true : full; 3019 scroll = typeof scroll == "undefined" ? true : scroll; 3020 3021 let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null; 3022 3023 // Only consider full screenshot if no element has been specified 3024 full = webEl ? false : full; 3025 3026 return this.getActor().takeScreenshot(webEl, format, full, scroll); 3027 }; 3028 3029 /** 3030 * Get the current browser orientation. 3031 * 3032 * Will return one of the valid primary orientation values 3033 * portrait-primary, landscape-primary, portrait-secondary, or 3034 * landscape-secondary. 3035 * 3036 * @throws {NoSuchWindowError} 3037 * Top-level browsing context has been discarded. 3038 */ 3039 GeckoDriver.prototype.getScreenOrientation = function () { 3040 lazy.assert.mobile(); 3041 lazy.assert.open(this.getBrowsingContext({ top: true })); 3042 3043 const win = this.getCurrentWindow(); 3044 3045 return win.screen.orientation.type; 3046 }; 3047 3048 /** 3049 * Set the current browser orientation. 3050 * 3051 * The supplied orientation should be given as one of the valid 3052 * orientation values. If the orientation is unknown, an error will 3053 * be raised. 3054 * 3055 * Valid orientations are "portrait" and "landscape", which fall 3056 * back to "portrait-primary" and "landscape-primary" respectively, 3057 * and "portrait-secondary" as well as "landscape-secondary". 3058 * 3059 * @throws {NoSuchWindowError} 3060 * Top-level browsing context has been discarded. 3061 */ 3062 GeckoDriver.prototype.setScreenOrientation = async function (cmd) { 3063 lazy.assert.mobile(); 3064 lazy.assert.open(this.getBrowsingContext({ top: true })); 3065 3066 const ors = [ 3067 "portrait", 3068 "landscape", 3069 "portrait-primary", 3070 "landscape-primary", 3071 "portrait-secondary", 3072 "landscape-secondary", 3073 ]; 3074 3075 let or = String(cmd.parameters.orientation); 3076 lazy.assert.string(or, lazy.pprint`Expected "or" to be a string, got ${or}`); 3077 let mozOr = or.toLowerCase(); 3078 if (!ors.includes(mozOr)) { 3079 throw new lazy.error.InvalidArgumentError( 3080 `Unknown screen orientation: ${or}` 3081 ); 3082 } 3083 3084 const win = this.getCurrentWindow(); 3085 3086 try { 3087 await win.screen.orientation.lock(mozOr); 3088 } catch (e) { 3089 throw new lazy.error.WebDriverError( 3090 `Unable to set screen orientation: ${or}` 3091 ); 3092 } 3093 }; 3094 3095 /** 3096 * Synchronously minimizes the user agent window as if the user pressed 3097 * the minimize button. 3098 * 3099 * No action is taken if the window is already minimized. 3100 * 3101 * Not supported on Fennec. 3102 * 3103 * @returns {Record<string, number>} 3104 * Window rect and window state. 3105 * 3106 * @throws {NoSuchWindowError} 3107 * Top-level browsing context has been discarded. 3108 * @throws {UnexpectedAlertOpenError} 3109 * A modal dialog is open, blocking this operation. 3110 * @throws {UnsupportedOperationError} 3111 * Not available for current application. 3112 */ 3113 GeckoDriver.prototype.minimizeWindow = async function () { 3114 lazy.assert.desktop(); 3115 lazy.assert.open(this.getBrowsingContext({ top: true })); 3116 await this._handleUserPrompts(); 3117 3118 const win = this.getCurrentWindow(); 3119 switch (lazy.WindowState.from(win.windowState)) { 3120 case lazy.WindowState.Fullscreen: 3121 await lazy.windowManager.setFullscreen(win, false); 3122 break; 3123 3124 case lazy.WindowState.Maximized: 3125 await lazy.windowManager.restoreWindow(win); 3126 break; 3127 } 3128 3129 await lazy.windowManager.minimizeWindow(win); 3130 3131 return this.curBrowser.rect; 3132 }; 3133 3134 /** 3135 * Synchronously maximizes the user agent window as if the user pressed 3136 * the maximize button. 3137 * 3138 * No action is taken if the window is already maximized. 3139 * 3140 * Not supported on Fennec. 3141 * 3142 * @returns {Record<string, number>} 3143 * Window rect. 3144 * 3145 * @throws {NoSuchWindowError} 3146 * Top-level browsing context has been discarded. 3147 * @throws {UnexpectedAlertOpenError} 3148 * A modal dialog is open, blocking this operation. 3149 * @throws {UnsupportedOperationError} 3150 * Not available for current application. 3151 */ 3152 GeckoDriver.prototype.maximizeWindow = async function () { 3153 lazy.assert.desktop(); 3154 lazy.assert.open(this.getBrowsingContext({ top: true })); 3155 await this._handleUserPrompts(); 3156 3157 const win = this.getCurrentWindow(); 3158 switch (lazy.WindowState.from(win.windowState)) { 3159 case lazy.WindowState.Fullscreen: 3160 await lazy.windowManager.setFullscreen(win, false); 3161 break; 3162 3163 case lazy.WindowState.Minimized: 3164 await lazy.windowManager.restoreWindow(win); 3165 break; 3166 } 3167 3168 await lazy.windowManager.maximizeWindow(win); 3169 3170 return this.curBrowser.rect; 3171 }; 3172 3173 /** 3174 * Synchronously sets the user agent window to full screen as if the user 3175 * had done "View > Enter Full Screen". 3176 * 3177 * No action is taken if the window is already in full screen mode. 3178 * 3179 * Not supported on Fennec. 3180 * 3181 * @returns {Map.<string, number>} 3182 * Window rect. 3183 * 3184 * @throws {NoSuchWindowError} 3185 * Top-level browsing context has been discarded. 3186 * @throws {UnexpectedAlertOpenError} 3187 * A modal dialog is open, blocking this operation. 3188 * @throws {UnsupportedOperationError} 3189 * Not available for current application. 3190 */ 3191 GeckoDriver.prototype.fullscreenWindow = async function () { 3192 lazy.assert.desktop(); 3193 lazy.assert.open(this.getBrowsingContext({ top: true })); 3194 await this._handleUserPrompts(); 3195 3196 const win = this.getCurrentWindow(); 3197 switch (lazy.WindowState.from(win.windowState)) { 3198 case lazy.WindowState.Maximized: 3199 case lazy.WindowState.Minimized: 3200 await lazy.windowManager.restoreWindow(win); 3201 break; 3202 } 3203 3204 await lazy.windowManager.setFullscreen(win, true); 3205 3206 return this.curBrowser.rect; 3207 }; 3208 3209 /** 3210 * Dismisses a currently displayed modal dialogs, or returns no such alert if 3211 * no modal is displayed. 3212 * 3213 * @throws {NoSuchAlertError} 3214 * If there is no current user prompt. 3215 * @throws {NoSuchWindowError} 3216 * Top-level browsing context has been discarded. 3217 */ 3218 GeckoDriver.prototype.dismissDialog = async function () { 3219 lazy.assert.open(this.getBrowsingContext({ top: true })); 3220 this._checkIfAlertIsPresent(); 3221 3222 const dialogClosed = this.promptListener.dialogClosed(); 3223 this.dialog.dismiss(); 3224 await dialogClosed; 3225 3226 const win = this.getCurrentWindow(); 3227 await new lazy.AnimationFramePromise(win); 3228 }; 3229 3230 /** 3231 * Accepts a currently displayed dialog modal, or returns no such alert if 3232 * no modal is displayed. 3233 * 3234 * @throws {NoSuchAlertError} 3235 * If there is no current user prompt. 3236 * @throws {NoSuchWindowError} 3237 * Top-level browsing context has been discarded. 3238 */ 3239 GeckoDriver.prototype.acceptDialog = async function () { 3240 lazy.assert.open(this.getBrowsingContext({ top: true })); 3241 this._checkIfAlertIsPresent(); 3242 3243 const dialogClosed = this.promptListener.dialogClosed(); 3244 this.dialog.accept(); 3245 await dialogClosed; 3246 3247 const win = this.getCurrentWindow(); 3248 await new lazy.AnimationFramePromise(win); 3249 }; 3250 3251 /** 3252 * Returns the message shown in a currently displayed modal, or returns 3253 * a no such alert error if no modal is currently displayed. 3254 * 3255 * @throws {NoSuchAlertError} 3256 * If there is no current user prompt. 3257 * @throws {NoSuchWindowError} 3258 * Top-level browsing context has been discarded. 3259 */ 3260 GeckoDriver.prototype.getTextFromDialog = async function () { 3261 lazy.assert.open(this.getBrowsingContext({ top: true })); 3262 this._checkIfAlertIsPresent(); 3263 const text = await this.dialog.getText(); 3264 return text; 3265 }; 3266 3267 /** 3268 * Set the user prompt's value field. 3269 * 3270 * Sends keys to the input field of a currently displayed modal, or 3271 * returns a no such alert error if no modal is currently displayed. If 3272 * a modal dialog is currently displayed but has no means for text input, 3273 * an element not visible error is returned. 3274 * 3275 * @param {object} cmd 3276 * @param {string} cmd.parameters.text 3277 * Input to the user prompt's value field. 3278 * 3279 * @throws {ElementNotInteractableError} 3280 * If the current user prompt is an alert or confirm. 3281 * @throws {NoSuchAlertError} 3282 * If there is no current user prompt. 3283 * @throws {NoSuchWindowError} 3284 * Top-level browsing context has been discarded. 3285 * @throws {UnsupportedOperationError} 3286 * If the current user prompt is something other than an alert, 3287 * confirm, or a prompt. 3288 */ 3289 GeckoDriver.prototype.sendKeysToDialog = async function (cmd) { 3290 lazy.assert.open(this.getBrowsingContext({ top: true })); 3291 this._checkIfAlertIsPresent(); 3292 3293 let text = lazy.assert.string( 3294 cmd.parameters.text, 3295 lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}` 3296 ); 3297 let promptType = this.dialog.args.promptType; 3298 3299 switch (promptType) { 3300 case "alert": 3301 case "confirm": 3302 throw new lazy.error.ElementNotInteractableError( 3303 `User prompt of type ${promptType} is not interactable` 3304 ); 3305 case "prompt": 3306 break; 3307 default: 3308 await this.dismissDialog(); 3309 throw new lazy.error.UnsupportedOperationError( 3310 `User prompt of type ${promptType} is not supported` 3311 ); 3312 } 3313 this.dialog.text = text; 3314 }; 3315 3316 GeckoDriver.prototype._checkIfAlertIsPresent = function () { 3317 if (!this.dialog || !this.dialog.isOpen) { 3318 throw new lazy.error.NoSuchAlertError(); 3319 } 3320 }; 3321 3322 GeckoDriver.prototype._handleUserPrompts = async function () { 3323 if (!this.dialog || !this.dialog.isOpen) { 3324 return; 3325 } 3326 3327 const promptType = this.dialog.promptType; 3328 const textContent = await this.dialog.getText(); 3329 3330 if (promptType === "beforeunload" && !this.currentSession.bidi) { 3331 // In an HTTP-only session, this prompt will be automatically accepted. 3332 // Since this occurs asynchronously, we need to wait until it closes 3333 // to prevent race conditions, particularly in slow builds. 3334 await lazy.PollPromise((resolve, reject) => { 3335 this.dialog?.isOpen ? reject() : resolve(); 3336 }); 3337 return; 3338 } 3339 3340 let type = lazy.PromptTypes.Default; 3341 switch (promptType) { 3342 case "alert": 3343 type = lazy.PromptTypes.Alert; 3344 break; 3345 case "beforeunload": 3346 type = lazy.PromptTypes.BeforeUnload; 3347 break; 3348 case "confirm": 3349 type = lazy.PromptTypes.Confirm; 3350 break; 3351 case "prompt": 3352 type = lazy.PromptTypes.Prompt; 3353 break; 3354 } 3355 3356 const userPromptHandler = this.currentSession.userPromptHandler; 3357 const handlerConfig = userPromptHandler.getPromptHandler(type); 3358 3359 switch (handlerConfig.handler) { 3360 case lazy.PromptHandlers.Accept: 3361 await this.acceptDialog(); 3362 break; 3363 case lazy.PromptHandlers.Dismiss: 3364 await this.dismissDialog(); 3365 break; 3366 case lazy.PromptHandlers.Ignore: 3367 break; 3368 } 3369 3370 if (handlerConfig.notify) { 3371 throw new lazy.error.UnexpectedAlertOpenError( 3372 `Unexpected ${promptType} dialog detected. Performed handler "${handlerConfig.handler}"`, 3373 { 3374 text: textContent, 3375 } 3376 ); 3377 } 3378 }; 3379 3380 /** 3381 * Enables or disables accepting new socket connections. 3382 * 3383 * By calling this method with `false` the server will not accept any 3384 * further connections, but existing connections will not be forcible 3385 * closed. Use `true` to re-enable accepting connections. 3386 * 3387 * Please note that when closing the connection via the client you can 3388 * end-up in a non-recoverable state if it hasn't been enabled before. 3389 * 3390 * This method is used for custom in application shutdowns via 3391 * marionette.quit() or marionette.restart(), like File -> Quit. 3392 * 3393 * @param {object} cmd 3394 * @param {boolean} cmd.parameters.value 3395 * True if the server should accept new socket connections. 3396 */ 3397 GeckoDriver.prototype.acceptConnections = async function (cmd) { 3398 lazy.assert.boolean( 3399 cmd.parameters.value, 3400 lazy.pprint`Expected "value" to be a boolean, got ${cmd.parameters.value}` 3401 ); 3402 await this._server.setAcceptConnections(cmd.parameters.value); 3403 }; 3404 3405 /** 3406 * Quits the application with the provided flags. 3407 * 3408 * Marionette will stop accepting new connections before ending the 3409 * current session, and finally attempting to quit the application. 3410 * 3411 * Optional {@link nsIAppStartup} flags may be provided as 3412 * an array of masks, and these will be combined by ORing 3413 * them with a bitmask. The available masks are defined in 3414 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup. 3415 * 3416 * Crucially, only one of the *Quit flags can be specified. The |eRestart| 3417 * flag may be bit-wise combined with one of the *Quit flags to cause 3418 * the application to restart after it quits. 3419 * 3420 * @param {object} cmd 3421 * @param {Array.<string>=} cmd.parameters.flags 3422 * Constant name of masks to pass to |Services.startup.quit|. 3423 * If empty or undefined, |nsIAppStartup.eAttemptQuit| is used. 3424 * @param {boolean=} cmd.parameters.safeMode 3425 * Optional flag to indicate that the application has to 3426 * be restarted in safe mode. 3427 * 3428 * @returns {Record<string,boolean>} 3429 * Dictionary containing information that explains the shutdown reason. 3430 * The value for `cause` contains the shutdown kind like "shutdown" or 3431 * "restart", while `forced` will indicate if it was a normal or forced 3432 * shutdown of the application. "in_app" is always set to indicate that 3433 * it is a shutdown triggered from within the application. 3434 * 3435 * @throws {InvalidArgumentError} 3436 * If <var>flags</var> contains unknown or incompatible flags, 3437 * for example multiple Quit flags. 3438 */ 3439 GeckoDriver.prototype.quit = async function (cmd) { 3440 const { flags = [], safeMode = false } = cmd.parameters; 3441 3442 lazy.assert.array( 3443 flags, 3444 lazy.pprint`Expected "flags" to be an array, got ${flags}` 3445 ); 3446 lazy.assert.boolean( 3447 safeMode, 3448 lazy.pprint`Expected "safeMode" to be a boolean, got ${safeMode}` 3449 ); 3450 3451 if (safeMode && !flags.includes("eRestart")) { 3452 throw new lazy.error.InvalidArgumentError( 3453 `"safeMode" only works with restart flag` 3454 ); 3455 } 3456 3457 // Register handler to run Marionette specific shutdown code. 3458 Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED); 3459 3460 let quitApplicationResponse; 3461 try { 3462 this._isShuttingDown = true; 3463 quitApplicationResponse = await lazy.quit( 3464 flags, 3465 safeMode, 3466 this.currentSession.capabilities.get("moz:windowless") 3467 ); 3468 } catch (e) { 3469 this._isShuttingDown = false; 3470 if (e instanceof TypeError) { 3471 throw new lazy.error.InvalidArgumentError(e.message); 3472 } 3473 throw new lazy.error.UnsupportedOperationError(e.message); 3474 } finally { 3475 Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED); 3476 } 3477 3478 return quitApplicationResponse; 3479 }; 3480 3481 GeckoDriver.prototype.installAddon = function (cmd) { 3482 const { 3483 addon = null, 3484 allowPrivateBrowsing = false, 3485 path = null, 3486 temporary = false, 3487 } = cmd.parameters; 3488 3489 lazy.assert.boolean( 3490 allowPrivateBrowsing, 3491 lazy.pprint`Expected "allowPrivateBrowsing" to be a boolean, got ${allowPrivateBrowsing}` 3492 ); 3493 3494 lazy.assert.boolean( 3495 temporary, 3496 lazy.pprint`Expected "temporary" to be a boolean, got ${temporary}` 3497 ); 3498 3499 if (addon !== null) { 3500 if (path !== null) { 3501 throw new lazy.error.InvalidArgumentError( 3502 `Expected only one of "addon" or "path" to be specified` 3503 ); 3504 } 3505 3506 lazy.assert.string( 3507 addon, 3508 lazy.pprint`Expected "addon" to be a string, got ${addon}` 3509 ); 3510 3511 return lazy.Addon.installWithBase64(addon, temporary, allowPrivateBrowsing); 3512 } 3513 3514 if (path !== null) { 3515 lazy.assert.string( 3516 path, 3517 lazy.pprint`Expected "path" to be a string, got ${path}` 3518 ); 3519 3520 return lazy.Addon.installWithPath(path, temporary, allowPrivateBrowsing); 3521 } 3522 3523 throw new lazy.error.InvalidArgumentError( 3524 `Expected "addon" or "path" argument to be specified` 3525 ); 3526 }; 3527 3528 GeckoDriver.prototype.uninstallAddon = function (cmd) { 3529 let id = cmd.parameters.id; 3530 if (typeof id == "undefined" || typeof id != "string") { 3531 throw new lazy.error.InvalidArgumentError(); 3532 } 3533 3534 return lazy.Addon.uninstall(id); 3535 }; 3536 3537 /** 3538 * Retrieve the localized string for the specified property id. 3539 * 3540 * Example: 3541 * 3542 * localizeProperty( 3543 * ["chrome://global/locale/findbar.properties"], "FastFind"); 3544 * 3545 * @param {object} cmd 3546 * @param {Array.<string>} cmd.parameters.urls 3547 * Array of .properties URLs. 3548 * @param {string} cmd.parameters.id 3549 * The ID of the property to retrieve the localized string for. 3550 * 3551 * @returns {string} 3552 * The localized string for the requested property. 3553 */ 3554 GeckoDriver.prototype.localizeProperty = function (cmd) { 3555 let { urls, id } = cmd.parameters; 3556 3557 if (!Array.isArray(urls)) { 3558 throw new lazy.error.InvalidArgumentError( 3559 "Value of `urls` should be of type 'Array'" 3560 ); 3561 } 3562 if (typeof id != "string") { 3563 throw new lazy.error.InvalidArgumentError( 3564 "Value of `id` should be of type 'string'" 3565 ); 3566 } 3567 3568 return lazy.l10n.localizeProperty(urls, id); 3569 }; 3570 3571 /** 3572 * Initialize the reftest mode 3573 */ 3574 GeckoDriver.prototype.setupReftest = async function (cmd) { 3575 if (this._reftest) { 3576 throw new lazy.error.UnsupportedOperationError( 3577 "Called reftest:setup with a reftest session already active" 3578 ); 3579 } 3580 3581 let { 3582 urlCount = {}, 3583 screenshot = "unexpected", 3584 isPrint = false, 3585 cacheScreenshots = true, 3586 } = cmd.parameters; 3587 if (!["always", "fail", "unexpected"].includes(screenshot)) { 3588 throw new lazy.error.InvalidArgumentError( 3589 "Value of `screenshot` should be 'always', 'fail' or 'unexpected'" 3590 ); 3591 } 3592 3593 this._reftest = new lazy.reftest.Runner(this); 3594 this._reftest.setup(urlCount, screenshot, isPrint, cacheScreenshots); 3595 }; 3596 3597 /** Run a reftest. */ 3598 GeckoDriver.prototype.runReftest = function (cmd) { 3599 let { test, references, expected, timeout, width, height, pageRanges } = 3600 cmd.parameters; 3601 3602 if (!this._reftest) { 3603 throw new lazy.error.UnsupportedOperationError( 3604 "Called reftest:run before reftest:start" 3605 ); 3606 } 3607 3608 lazy.assert.string( 3609 test, 3610 lazy.pprint`Expected "test" to be a string, got ${test}` 3611 ); 3612 lazy.assert.string( 3613 expected, 3614 lazy.pprint`Expected "expected" to be a string, got ${expected}` 3615 ); 3616 lazy.assert.array( 3617 references, 3618 lazy.pprint`Expected "references" to be an array, got ${references}` 3619 ); 3620 3621 return this._reftest.run( 3622 test, 3623 references, 3624 expected, 3625 timeout, 3626 pageRanges, 3627 width, 3628 height 3629 ); 3630 }; 3631 3632 /** 3633 * End a reftest run. 3634 * 3635 * Closes the reftest window (without changing the current window handle), 3636 * and removes cached canvases. 3637 */ 3638 GeckoDriver.prototype.teardownReftest = function () { 3639 if (!this._reftest) { 3640 throw new lazy.error.UnsupportedOperationError( 3641 "Called reftest:teardown before reftest:start" 3642 ); 3643 } 3644 3645 this._reftest.teardown(); 3646 this._reftest = null; 3647 }; 3648 3649 /** 3650 * Implements the GenerateTestReport functionality of the Reporting API. 3651 * 3652 * @see https://w3c.github.io/reporting/#generate-test-report-command * 3653 * 3654 * @param {object} cmd 3655 * @param {string} cmd.parameters.message 3656 * The message contents of the report being generated. 3657 * @param {string=} cmd.parameters.group 3658 * The name of the reporting endpoint that the report should be sent to. 3659 * @see https://www.w3.org/TR/reporting-1/#endpoint 3660 * 3661 * @throws {InvalidArgumentError} 3662 * If a message argument wasn't passed in the parameters. 3663 */ 3664 3665 GeckoDriver.prototype.generateTestReport = async function (cmd) { 3666 const { message, group = "default" } = cmd.parameters; 3667 3668 lazy.assert.open(this.getBrowsingContext()); 3669 await this._handleUserPrompts(); 3670 3671 lazy.assert.string( 3672 message, 3673 lazy.pprint(`Expected "message" to be a string, got ${message}`) 3674 ); 3675 3676 lazy.assert.string( 3677 group, 3678 lazy.pprint(`Expected "group" to be a string, got ${group}`) 3679 ); 3680 3681 await this.getActor().generateTestReport(message, group); 3682 }; 3683 3684 /** 3685 * Print page as PDF. 3686 * 3687 * @param {object} cmd 3688 * @param {boolean=} cmd.parameters.background 3689 * Whether or not to print background colors and images. 3690 * Defaults to false, which prints without background graphics. 3691 * @param {number=} cmd.parameters.margin.bottom 3692 * Bottom margin in cm. Defaults to 1cm (~0.4 inches). 3693 * @param {number=} cmd.parameters.margin.left 3694 * Left margin in cm. Defaults to 1cm (~0.4 inches). 3695 * @param {number=} cmd.parameters.margin.right 3696 * Right margin in cm. Defaults to 1cm (~0.4 inches). 3697 * @param {number=} cmd.parameters.margin.top 3698 * Top margin in cm. Defaults to 1cm (~0.4 inches). 3699 * @param {('landscape'|'portrait')=} cmd.parameters.options.orientation 3700 * Paper orientation. Defaults to 'portrait'. 3701 * @param {Array.<string|number>=} cmd.parameters.pageRanges 3702 * Paper ranges to print, e.g., ['1-5', 8, '11-13']. 3703 * Defaults to the empty array, which means print all pages. 3704 * @param {number=} cmd.parameters.page.height 3705 * Paper height in cm. Defaults to US letter height (27.94cm / 11 inches) 3706 * @param {number=} cmd.parameters.page.width 3707 * Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches) 3708 * @param {number=} cmd.parameters.scale 3709 * Scale of the webpage rendering. Defaults to 1.0. 3710 * @param {boolean=} cmd.parameters.shrinkToFit 3711 * Whether or not to override page size as defined by CSS. 3712 * Defaults to true, in which case the content will be scaled 3713 * to fit the paper size. 3714 * 3715 * @returns {string} 3716 * Base64 encoded PDF representing printed document 3717 * 3718 * @throws {NoSuchWindowError} 3719 * Top-level browsing context has been discarded. 3720 * @throws {UnexpectedAlertOpenError} 3721 * A modal dialog is open, blocking this operation. 3722 * @throws {UnsupportedOperationError} 3723 * Not available in chrome context. 3724 */ 3725 GeckoDriver.prototype.print = async function (cmd) { 3726 lazy.assert.content(this.context); 3727 lazy.assert.open(this.getBrowsingContext({ top: true })); 3728 await this._handleUserPrompts(); 3729 3730 const settings = lazy.print.addDefaultSettings(cmd.parameters); 3731 for (const prop of ["top", "bottom", "left", "right"]) { 3732 lazy.assert.positiveNumber( 3733 settings.margin[prop], 3734 lazy.pprint`Expected "margin.${prop}" to be a positive number, got ${settings.margin[prop]}` 3735 ); 3736 } 3737 for (const prop of ["width", "height"]) { 3738 lazy.assert.positiveNumber( 3739 settings.page[prop], 3740 lazy.pprint`Expected "page.${prop}" to be a positive number, got ${settings.page[prop]}` 3741 ); 3742 } 3743 lazy.assert.positiveNumber( 3744 settings.scale, 3745 lazy.pprint`Expected "scale" to be a positive number, got ${settings.scale}` 3746 ); 3747 lazy.assert.that( 3748 s => 3749 s >= lazy.print.minScaleValue && 3750 settings.scale <= lazy.print.maxScaleValue, 3751 lazy.pprint`scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}` 3752 )(settings.scale); 3753 lazy.assert.boolean( 3754 settings.shrinkToFit, 3755 lazy.pprint`Expected "shrinkToFit" to be a boolean, got ${settings.shrinkToFit}` 3756 ); 3757 lazy.assert.that( 3758 orientation => lazy.print.defaults.orientationValue.includes(orientation), 3759 lazy.pprint`orientation ${ 3760 settings.orientation 3761 } doesn't match allowed values "${lazy.print.defaults.orientationValue.join( 3762 "/" 3763 )}"` 3764 )(settings.orientation); 3765 lazy.assert.boolean( 3766 settings.background, 3767 lazy.pprint`Expected "background" to be a boolean, got ${settings.background}` 3768 ); 3769 lazy.assert.array( 3770 settings.pageRanges, 3771 lazy.pprint`Expected "pageRanges" to be an array, got ${settings.pageRanges}` 3772 ); 3773 3774 const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext; 3775 const printSettings = await lazy.print.getPrintSettings(settings); 3776 const binaryString = await lazy.print.printToBinaryString( 3777 browsingContext, 3778 printSettings 3779 ); 3780 3781 return btoa(binaryString); 3782 }; 3783 3784 GeckoDriver.prototype.getGlobalPrivacyControl = function () { 3785 const gpc = Services.prefs.getBoolPref( 3786 "privacy.globalprivacycontrol.enabled", 3787 true 3788 ); 3789 return { gpc }; 3790 }; 3791 3792 GeckoDriver.prototype.setGlobalPrivacyControl = function (cmd) { 3793 const { gpc } = cmd.parameters; 3794 if (typeof gpc != "boolean") { 3795 throw new lazy.error.InvalidArgumentError( 3796 "Value of `gpc` should be of type 'boolean'" 3797 ); 3798 } 3799 Services.prefs.setBoolPref("privacy.globalprivacycontrol.enabled", gpc); 3800 return { gpc }; 3801 }; 3802 3803 GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) { 3804 const { 3805 protocol, 3806 transport, 3807 hasResidentKey, 3808 hasUserVerification, 3809 isUserConsenting, 3810 isUserVerified, 3811 } = cmd.parameters; 3812 3813 lazy.assert.string( 3814 protocol, 3815 lazy.pprint`Expected "protocol" to be a string, got ${protocol}` 3816 ); 3817 lazy.assert.string( 3818 transport, 3819 lazy.pprint`Expected "transport" to be a string, got ${transport}` 3820 ); 3821 lazy.assert.boolean( 3822 hasResidentKey, 3823 lazy.pprint`Expected "hasResidentKey" to be a boolean, got ${hasResidentKey}` 3824 ); 3825 lazy.assert.boolean( 3826 hasUserVerification, 3827 lazy.pprint`Expected "hasUserVerification" to be a boolean, got ${hasUserVerification}` 3828 ); 3829 lazy.assert.boolean( 3830 isUserConsenting, 3831 lazy.pprint`Expected "isUserConsenting" to be a boolean, got ${isUserConsenting}` 3832 ); 3833 lazy.assert.boolean( 3834 isUserVerified, 3835 lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}` 3836 ); 3837 3838 return lazy.webauthn.addVirtualAuthenticator( 3839 protocol, 3840 transport, 3841 hasResidentKey, 3842 hasUserVerification, 3843 isUserConsenting, 3844 isUserVerified 3845 ); 3846 }; 3847 3848 GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) { 3849 const { authenticatorId } = cmd.parameters; 3850 3851 lazy.assert.string( 3852 authenticatorId, 3853 lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}` 3854 ); 3855 3856 lazy.webauthn.removeVirtualAuthenticator(authenticatorId); 3857 }; 3858 3859 GeckoDriver.prototype.addCredential = function (cmd) { 3860 const { 3861 authenticatorId, 3862 credentialId, 3863 isResidentCredential, 3864 rpId, 3865 privateKey, 3866 userHandle, 3867 signCount, 3868 } = cmd.parameters; 3869 3870 lazy.assert.string( 3871 authenticatorId, 3872 lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}` 3873 ); 3874 lazy.assert.string( 3875 credentialId, 3876 lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}` 3877 ); 3878 lazy.assert.boolean( 3879 isResidentCredential, 3880 lazy.pprint`Expected "isResidentCredential" to be a boolean, got ${isResidentCredential}` 3881 ); 3882 lazy.assert.string( 3883 rpId, 3884 lazy.pprint`Expected "rpId" to be a string, got ${rpId}` 3885 ); 3886 lazy.assert.string( 3887 privateKey, 3888 lazy.pprint`Expected "privateKey" to be a string, got ${privateKey}` 3889 ); 3890 if (userHandle) { 3891 lazy.assert.string( 3892 userHandle, 3893 lazy.pprint`Expected "userHandle" to be a string, got ${userHandle}` 3894 ); 3895 } 3896 lazy.assert.number( 3897 signCount, 3898 lazy.pprint`Expected "signCount" to be a number, got ${signCount}` 3899 ); 3900 3901 lazy.webauthn.addCredential( 3902 authenticatorId, 3903 credentialId, 3904 isResidentCredential, 3905 rpId, 3906 privateKey, 3907 userHandle, 3908 signCount 3909 ); 3910 }; 3911 3912 GeckoDriver.prototype.getCredentials = function (cmd) { 3913 const { authenticatorId } = cmd.parameters; 3914 3915 lazy.assert.string( 3916 authenticatorId, 3917 lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}` 3918 ); 3919 3920 return lazy.webauthn.getCredentials(authenticatorId); 3921 }; 3922 3923 GeckoDriver.prototype.removeCredential = function (cmd) { 3924 const { authenticatorId, credentialId } = cmd.parameters; 3925 3926 lazy.assert.string( 3927 authenticatorId, 3928 lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}` 3929 ); 3930 lazy.assert.string( 3931 credentialId, 3932 lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}` 3933 ); 3934 3935 lazy.webauthn.removeCredential(authenticatorId, credentialId); 3936 }; 3937 3938 GeckoDriver.prototype.removeAllCredentials = function (cmd) { 3939 const { authenticatorId } = cmd.parameters; 3940 3941 lazy.assert.string( 3942 authenticatorId, 3943 lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}` 3944 ); 3945 3946 lazy.webauthn.removeAllCredentials(authenticatorId); 3947 }; 3948 3949 GeckoDriver.prototype.setUserVerified = function (cmd) { 3950 const { authenticatorId, isUserVerified } = cmd.parameters; 3951 3952 lazy.assert.string( 3953 authenticatorId, 3954 lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}` 3955 ); 3956 lazy.assert.boolean( 3957 isUserVerified, 3958 lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}` 3959 ); 3960 3961 lazy.webauthn.setUserVerified(authenticatorId, isUserVerified); 3962 }; 3963 3964 GeckoDriver.prototype.setPermission = async function (cmd) { 3965 const { descriptor, state, oneRealm = false } = cmd.parameters; 3966 const browsingContext = lazy.assert.open(this.getBrowsingContext()); 3967 3968 lazy.permissions.validateDescriptor(descriptor); 3969 lazy.permissions.validateState(state); 3970 3971 let params; 3972 try { 3973 params = 3974 await this.curBrowser.window.navigator.permissions.parseSetParameters({ 3975 descriptor, 3976 state, 3977 }); 3978 } catch (err) { 3979 throw new lazy.error.InvalidArgumentError(`setPermission: ${err.message}`); 3980 } 3981 3982 lazy.assert.boolean( 3983 oneRealm, 3984 lazy.pprint`Expected "oneRealm" to be a boolean, got ${oneRealm}` 3985 ); 3986 3987 let origin = browsingContext.currentURI.prePath; 3988 3989 // storage-access is a special case. 3990 if (descriptor.name === "storage-access") { 3991 origin = browsingContext.top.currentURI.prePath; 3992 3993 params = { 3994 type: lazy.permissions.getStorageAccessPermissionsType( 3995 browsingContext.currentWindowGlobal.documentURI 3996 ), 3997 }; 3998 } 3999 4000 lazy.permissions.set(params, state, origin); 4001 }; 4002 4003 /** 4004 * Determines the Accessibility label for this element. 4005 * 4006 * @param {object} cmd 4007 * @param {string} cmd.parameters.id 4008 * Web element reference ID to the element for which the accessibility label 4009 * will be returned. 4010 * 4011 * @returns {string} 4012 * The Accessibility label for this element 4013 */ 4014 GeckoDriver.prototype.getComputedLabel = async function (cmd) { 4015 lazy.assert.open(this.getBrowsingContext()); 4016 await this._handleUserPrompts(); 4017 4018 let id = lazy.assert.string( 4019 cmd.parameters.id, 4020 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 4021 ); 4022 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 4023 4024 return this.getActor().getComputedLabel(webEl); 4025 }; 4026 4027 /** 4028 * Determines the Accessibility role for this element. 4029 * 4030 * @param {object} cmd 4031 * @param {string} cmd.parameters.id 4032 * Web element reference ID to the element for which the accessibility role 4033 * will be returned. 4034 * 4035 * @returns {string} 4036 * The Accessibility role for this element 4037 */ 4038 GeckoDriver.prototype.getComputedRole = async function (cmd) { 4039 lazy.assert.open(this.getBrowsingContext()); 4040 await this._handleUserPrompts(); 4041 4042 let id = lazy.assert.string( 4043 cmd.parameters.id, 4044 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}` 4045 ); 4046 let webEl = lazy.WebElement.fromUUID(id).toJSON(); 4047 return this.getActor().getComputedRole(webEl); 4048 }; 4049 4050 GeckoDriver.prototype.commands = { 4051 // Marionette service 4052 "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections, 4053 "Marionette:GetContext": GeckoDriver.prototype.getContext, 4054 "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation, 4055 "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType, 4056 "Marionette:Quit": GeckoDriver.prototype.quit, 4057 "Marionette:RegisterChromeHandler": 4058 GeckoDriver.prototype.registerChromeHandler, 4059 "Marionette:UnregisterChromeHandler": 4060 GeckoDriver.prototype.unregisterChromeHandler, 4061 "Marionette:SetContext": GeckoDriver.prototype.setContext, 4062 "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation, 4063 4064 // Addon service 4065 "Addon:Install": GeckoDriver.prototype.installAddon, 4066 "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon, 4067 4068 // L10n service 4069 "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty, 4070 4071 // Reftest service 4072 "reftest:setup": GeckoDriver.prototype.setupReftest, 4073 "reftest:run": GeckoDriver.prototype.runReftest, 4074 "reftest:teardown": GeckoDriver.prototype.teardownReftest, 4075 4076 // WebDriver service 4077 "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog, 4078 // deprecated, no longer used since the geckodriver 0.30.0 release 4079 "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog, 4080 "WebDriver:AddCookie": GeckoDriver.prototype.addCookie, 4081 "WebDriver:Back": GeckoDriver.prototype.goBack, 4082 "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow, 4083 "WebDriver:CloseWindow": GeckoDriver.prototype.close, 4084 "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies, 4085 "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie, 4086 "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession, 4087 "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog, 4088 "WebDriver:ElementClear": GeckoDriver.prototype.clearElement, 4089 "WebDriver:ElementClick": GeckoDriver.prototype.clickElement, 4090 "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement, 4091 "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript, 4092 "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript, 4093 "WebDriver:FindElement": GeckoDriver.prototype.findElement, 4094 "WebDriver:FindElementFromShadowRoot": 4095 GeckoDriver.prototype.findElementFromShadowRoot, 4096 "WebDriver:FindElements": GeckoDriver.prototype.findElements, 4097 "WebDriver:FindElementsFromShadowRoot": 4098 GeckoDriver.prototype.findElementsFromShadowRoot, 4099 "WebDriver:Forward": GeckoDriver.prototype.goForward, 4100 "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow, 4101 "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement, 4102 "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog, 4103 "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities, 4104 "WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel, 4105 "WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole, 4106 "WebDriver:GetCookies": GeckoDriver.prototype.getCookies, 4107 "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl, 4108 "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute, 4109 "WebDriver:GetElementCSSValue": 4110 GeckoDriver.prototype.getElementValueOfCssProperty, 4111 "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty, 4112 "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect, 4113 "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName, 4114 "WebDriver:GetElementText": GeckoDriver.prototype.getElementText, 4115 "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource, 4116 "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot, 4117 "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts, 4118 "WebDriver:GetTitle": GeckoDriver.prototype.getTitle, 4119 "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle, 4120 "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles, 4121 "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect, 4122 "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed, 4123 "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled, 4124 "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected, 4125 "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow, 4126 "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow, 4127 "WebDriver:Navigate": GeckoDriver.prototype.navigateTo, 4128 "WebDriver:NewSession": GeckoDriver.prototype.newSession, 4129 "WebDriver:NewWindow": GeckoDriver.prototype.newWindow, 4130 "WebDriver:PerformActions": GeckoDriver.prototype.performActions, 4131 "WebDriver:Print": GeckoDriver.prototype.print, 4132 "WebDriver:Refresh": GeckoDriver.prototype.refresh, 4133 "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions, 4134 "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog, 4135 "WebDriver:SetPermission": GeckoDriver.prototype.setPermission, 4136 "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts, 4137 "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect, 4138 "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame, 4139 "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame, 4140 "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow, 4141 "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot, 4142 4143 // Global Privacy Control 4144 "GPC:GetGlobalPrivacyControl": GeckoDriver.prototype.getGlobalPrivacyControl, 4145 "GPC:SetGlobalPrivacyControl": GeckoDriver.prototype.setGlobalPrivacyControl, 4146 4147 // Reporting API test generation of reports 4148 "Reporting:GenerateTestReport": GeckoDriver.prototype.generateTestReport, 4149 4150 // WebAuthn 4151 "WebAuthn:AddVirtualAuthenticator": 4152 GeckoDriver.prototype.addVirtualAuthenticator, 4153 "WebAuthn:RemoveVirtualAuthenticator": 4154 GeckoDriver.prototype.removeVirtualAuthenticator, 4155 "WebAuthn:AddCredential": GeckoDriver.prototype.addCredential, 4156 "WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials, 4157 "WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential, 4158 "WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials, 4159 "WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified, 4160 };