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/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 Addon: "chrome://remote/content/marionette/addon.sys.mjs",
9 AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
10 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
11 browser: "chrome://remote/content/marionette/browser.sys.mjs",
12 capture: "chrome://remote/content/shared/Capture.sys.mjs",
13 Context: "chrome://remote/content/marionette/browser.sys.mjs",
14 cookie: "chrome://remote/content/marionette/cookie.sys.mjs",
15 DebounceCallback: "chrome://remote/content/marionette/sync.sys.mjs",
17 "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
18 dom: "chrome://remote/content/shared/DOM.sys.mjs",
20 "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
21 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
22 EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
23 getMarionetteCommandsActorProxy:
24 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
25 IdlePromise: "chrome://remote/content/marionette/sync.sys.mjs",
26 l10n: "chrome://remote/content/marionette/l10n.sys.mjs",
27 Log: "chrome://remote/content/shared/Log.sys.mjs",
28 Marionette: "chrome://remote/content/components/Marionette.sys.mjs",
29 MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
30 modal: "chrome://remote/content/shared/Prompt.sys.mjs",
31 navigate: "chrome://remote/content/marionette/navigate.sys.mjs",
32 permissions: "chrome://remote/content/marionette/permissions.sys.mjs",
33 pprint: "chrome://remote/content/shared/Format.sys.mjs",
34 print: "chrome://remote/content/shared/PDF.sys.mjs",
36 "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
37 quit: "chrome://remote/content/shared/Browser.sys.mjs",
38 reftest: "chrome://remote/content/marionette/reftest.sys.mjs",
39 registerCommandsActor:
40 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
41 RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
42 ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
43 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
44 TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
45 Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
46 UnhandledPromptBehavior:
47 "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
48 unregisterCommandsActor:
49 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
50 waitForInitialNavigationCompleted:
51 "chrome://remote/content/shared/Navigate.sys.mjs",
52 webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs",
53 WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
54 WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
55 windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
56 WindowState: "chrome://remote/content/marionette/browser.sys.mjs",
59 ChromeUtils.defineLazyGetter(lazy, "logger", () =>
60 lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
63 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
65 ChromeUtils.defineLazyGetter(
67 "supportedStrategies",
70 lazy.dom.Strategy.ClassName,
71 lazy.dom.Strategy.Selector,
73 lazy.dom.Strategy.Name,
74 lazy.dom.Strategy.LinkText,
75 lazy.dom.Strategy.PartialLinkText,
76 lazy.dom.Strategy.TagName,
77 lazy.dom.Strategy.XPath,
81 // Timeout used to abort fullscreen, maximize, and minimize
82 // commands if no window manager is present.
83 const TIMEOUT_NO_WINDOW_MANAGER = 5000;
85 // Observer topic to wait for until the browser window is ready.
86 const TOPIC_BROWSER_READY = "browser-delayed-startup-finished";
87 // Observer topic to perform clean up when application quit is requested.
88 const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested";
91 * The Marionette WebDriver services provides a standard conforming
92 * implementation of the W3C WebDriver specification.
94 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
99 * Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
100 * in chrome space and mediates calls to the current browsing context's actor.
102 * Throughout this prototype, functions with the argument <var>cmd</var>'s
103 * documentation refers to the contents of the <code>cmd.parameter</code>
108 * @param {MarionetteServer} server
109 * The instance of Marionette server.
111 export function GeckoDriver(server) {
112 this._server = server;
115 this._currentSession = null;
117 // Flag to indicate that the application is shutting down
118 this._isShuttingDown = false;
122 // points to current browser
123 this.curBrowser = null;
124 // top-most chrome window
125 this.mainFrame = null;
127 // Use content context by default
128 this.context = lazy.Context.Content;
130 // used for modal dialogs
132 this.promptListener = null;
136 * The current context decides if commands are executed in chrome- or
139 Object.defineProperty(GeckoDriver.prototype, "context", {
141 return this._context;
145 this._context = lazy.Context.fromString(context);
150 * The current WebDriver Session.
152 Object.defineProperty(GeckoDriver.prototype, "currentSession", {
154 if (lazy.RemoteAgent.webDriverBiDi) {
155 return lazy.RemoteAgent.webDriverBiDi.session;
158 return this._currentSession;
163 * Returns the current URL of the ChromeWindow or content browser,
164 * depending on context.
167 * Read-only property containing the currently loaded URL.
169 Object.defineProperty(GeckoDriver.prototype, "currentURL", {
171 const browsingContext = this.getBrowsingContext({ top: true });
172 return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
177 * Returns the title of the ChromeWindow or content browser,
178 * depending on context.
181 * Read-only property containing the title of the loaded URL.
183 Object.defineProperty(GeckoDriver.prototype, "title", {
185 const browsingContext = this.getBrowsingContext({ top: true });
186 return browsingContext.currentWindowGlobal.documentTitle;
190 Object.defineProperty(GeckoDriver.prototype, "windowType", {
192 return this.curBrowser.window.document.documentElement.getAttribute(
198 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
200 "nsISupportsWeakReference",
204 * Callback used to observe the closing of modal dialogs
205 * during the session's lifetime.
207 GeckoDriver.prototype.handleClosedModalDialog = function () {
212 * Callback used to observe the creation of new modal dialogs
213 * during the session's lifetime.
215 GeckoDriver.prototype.handleOpenModalDialog = function (eventName, data) {
216 this.dialog = data.prompt;
218 if (this.dialog.promptType === "beforeunload") {
219 lazy.logger.trace(`Implicitly accepted "beforeunload" prompt`);
220 this.dialog.accept();
224 if (!this._isShuttingDown) {
225 this.getActor().notifyDialogOpened(this.dialog);
230 * Get the current visible URL.
232 GeckoDriver.prototype._getCurrentURL = function () {
233 const browsingContext = this.getBrowsingContext({ top: true });
234 return new URL(browsingContext.currentURI.spec);
238 * Get the current "MarionetteCommands" parent actor.
240 * @param {object} options
241 * @param {boolean=} options.top
242 * If set to true use the window's top-level browsing context for the actor,
243 * otherwise the one from the currently selected frame. Defaults to false.
245 * @returns {MarionetteCommandsParent}
248 GeckoDriver.prototype.getActor = function (options = {}) {
249 return lazy.getMarionetteCommandsActorProxy(() =>
250 this.getBrowsingContext(options)
255 * Get the selected BrowsingContext for the current context.
257 * @param {object} options
258 * @param {Context=} options.context
259 * Context (content or chrome) for which to retrieve the browsing context.
260 * Defaults to the current one.
261 * @param {boolean=} options.parent
262 * If set to true return the window's parent browsing context,
263 * otherwise the one from the currently selected frame. Defaults to false.
264 * @param {boolean=} options.top
265 * If set to true return the window's top-level browsing context,
266 * otherwise the one from the currently selected frame. Defaults to false.
268 * @returns {BrowsingContext}
269 * The browsing context, or `null` if none is available
271 GeckoDriver.prototype.getBrowsingContext = function (options = {}) {
272 const { context = this.context, parent = false, top = false } = options;
274 let browsingContext = null;
275 if (context === lazy.Context.Chrome) {
276 browsingContext = this.currentSession?.chromeBrowsingContext;
278 browsingContext = this.currentSession?.contentBrowsingContext;
281 if (browsingContext && parent) {
282 browsingContext = browsingContext.parent;
285 if (browsingContext && top) {
286 browsingContext = browsingContext.top;
289 return browsingContext;
293 * Get the currently selected window.
295 * It will return the outer {@link ChromeWindow} previously selected by
296 * window handle through {@link #switchToWindow}, or the first window that
299 * @param {object} options
300 * @param {Context=} options.context
301 * Optional name of the context to use for finding the window.
302 * It will be required if a command always needs a specific context,
303 * whether which context is currently set. Defaults to the current
306 * @returns {ChromeWindow}
307 * The current top-level browsing context.
309 GeckoDriver.prototype.getCurrentWindow = function (options = {}) {
310 const { context = this.context } = options;
314 case lazy.Context.Chrome:
315 if (this.curBrowser) {
316 win = this.curBrowser.window;
320 case lazy.Context.Content:
321 if (this.curBrowser && this.curBrowser.contentBrowser) {
322 win = this.curBrowser.window;
330 GeckoDriver.prototype.isReftestBrowser = function (element) {
334 element.tagName === "xul:browser" &&
335 element.parentElement &&
336 element.parentElement.id === "reftest"
341 * Create a new browsing context for window and add to known browsers.
343 * @param {ChromeWindow} win
344 * Window for which we will create a browsing context.
347 * Returns the unique server-assigned ID of the window.
349 GeckoDriver.prototype.addBrowser = function (win) {
350 let context = new lazy.browser.Context(win, this);
351 let winId = lazy.windowManager.getIdForWindow(win);
353 this.browsers[winId] = context;
354 this.curBrowser = this.browsers[winId];
358 * Handles registration of new content browsers. Depending on
359 * their type they are either accepted or ignored.
361 * @param {XULBrowser} browserElement
363 GeckoDriver.prototype.registerBrowser = function (browserElement) {
364 // We want to ignore frames that are XUL browsers that aren't in the "main"
365 // tabbrowser, but accept things on Fennec (which doesn't have a
366 // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
367 // as well as XUL frames. Ideally this should be cleaned up and we should
368 // keep track of browsers a different way.
370 !lazy.AppInfo.isFirefox ||
371 browserElement.namespaceURI != XUL_NS ||
372 browserElement.nodeName != "browser" ||
373 browserElement.getTabBrowser()
375 this.curBrowser.register(browserElement);
380 * Create a new WebDriver session.
382 * @param {object} cmd
383 * @param {Object<string, *>=} cmd.parameters
384 * JSON Object containing any of the recognised capabilities as listed
385 * on the `WebDriverSession` class.
388 * Session ID and capabilities offered by the WebDriver service.
390 * @throws {SessionNotCreatedError}
391 * If, for whatever reason, a session could not be created.
393 GeckoDriver.prototype.newSession = async function (cmd) {
394 if (this.currentSession) {
395 throw new lazy.error.SessionNotCreatedError(
396 "Maximum number of active sessions"
400 const { parameters: capabilities } = cmd;
403 // If the WebDriver BiDi protocol is active always use the Remote Agent
404 // to handle the WebDriver session. If it's not the case then Marionette
405 // itself needs to handle it, and has to nullify the "webSocketUrl"
407 if (lazy.RemoteAgent.webDriverBiDi) {
408 await lazy.RemoteAgent.webDriverBiDi.createSession(capabilities);
410 this._currentSession = new lazy.WebDriverSession(capabilities);
411 this._currentSession.capabilities.delete("webSocketUrl");
414 // Don't wait for the initial window when Marionette is in windowless mode
415 if (!this.currentSession.capabilities.get("moz:windowless")) {
416 // Creating a WebDriver session too early can cause issues with
417 // clients in not being able to find any available window handle.
418 // Also when closing the application while it's still starting up can
419 // cause shutdown hangs. As such Marionette will return a new session
420 // once the initial application window has finished initializing.
421 lazy.logger.debug(`Waiting for initial application window`);
422 await lazy.Marionette.browserStartupFinished;
425 await lazy.windowManager.waitForInitialApplicationWindowLoaded();
427 if (lazy.MarionettePrefs.clickToStart) {
428 Services.prompt.alert(
431 "Click to start execution of marionette tests"
435 this.addBrowser(appWin);
436 this.mainFrame = appWin;
438 // Setup observer for modal dialogs
439 this.promptListener = new lazy.PromptListener(() => this.curBrowser);
440 this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
441 this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
442 this.promptListener.startListening();
444 for (let win of lazy.windowManager.windows) {
445 this.registerWindow(win, { registerBrowsers: true });
448 if (this.mainFrame) {
449 this.currentSession.chromeBrowsingContext =
450 this.mainFrame.browsingContext;
451 this.mainFrame.focus();
454 if (this.curBrowser.tab) {
455 const browsingContext = this.curBrowser.contentBrowser.browsingContext;
456 this.currentSession.contentBrowsingContext = browsingContext;
458 // Bug 1838381 - Only use a longer unload timeout for desktop, because
459 // on Android only the initial document is loaded, and loading a
460 // specific page during startup doesn't succeed.
462 if (!lazy.AppInfo.isAndroid) {
463 options.unloadTimeout = 5000;
466 await lazy.waitForInitialNavigationCompleted(
467 browsingContext.webProgress,
471 this.curBrowser.contentBrowser.focus();
474 // Check if there is already an open dialog for the selected browser window.
475 this.dialog = lazy.modal.findPrompt(this.curBrowser);
478 lazy.registerCommandsActor(this.currentSession.id);
479 lazy.enableEventsActor();
481 Services.obs.addObserver(this, TOPIC_BROWSER_READY);
483 throw new lazy.error.SessionNotCreatedError(e);
487 sessionId: this.currentSession.id,
488 capabilities: this.currentSession.capabilities,
493 * Start observing the specified window.
495 * @param {ChromeWindow} win
496 * Chrome window to register event listeners for.
497 * @param {object=} options
498 * @param {boolean=} options.registerBrowsers
499 * If true, register all content browsers of found tabs. Defaults to false.
501 GeckoDriver.prototype.registerWindow = function (win, options = {}) {
502 const { registerBrowsers = false } = options;
503 const tabBrowser = lazy.TabManager.getTabBrowser(win);
505 if (registerBrowsers && tabBrowser) {
506 for (const tab of tabBrowser.tabs) {
507 const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
508 this.registerBrowser(contentBrowser);
512 // Listen for any kind of top-level process switch
513 tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
517 * Stop observing the specified window.
519 * @param {ChromeWindow} win
520 * Chrome window to unregister event listeners for.
522 GeckoDriver.prototype.stopObservingWindow = function (win) {
523 const tabBrowser = lazy.TabManager.getTabBrowser(win);
525 tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
528 GeckoDriver.prototype.handleEvent = function ({ target, type }) {
530 case "XULFrameLoaderCreated":
531 if (target === this.curBrowser.contentBrowser) {
533 "Remoteness change detected. Set new top-level browsing context " +
534 `to ${target.browsingContext.id}`
537 this.currentSession.contentBrowsingContext = target.browsingContext;
543 GeckoDriver.prototype.observe = async function (subject, topic) {
545 case TOPIC_BROWSER_READY:
546 this.registerWindow(subject);
549 case TOPIC_QUIT_APPLICATION_REQUESTED:
550 // Run Marionette specific cleanup steps before allowing
551 // the application to shutdown
552 await this._server.setAcceptConnections(false);
553 this.deleteSession();
559 * Send the current session's capabilities to the client.
561 * Capabilities informs the client of which WebDriver features are
562 * supported by Firefox and Marionette. They are immutable for the
563 * length of the session.
565 * The return value is an immutable map of string keys
566 * ("capabilities") to values, which may be of types boolean,
567 * numerical or string.
569 GeckoDriver.prototype.getSessionCapabilities = function () {
570 return { capabilities: this.currentSession.capabilities };
574 * Sets the context of the subsequent commands.
576 * All subsequent requests to commands that in some way involve
577 * interaction with a browsing context will target the chosen browsing
580 * @param {object} cmd
581 * @param {string} cmd.parameters.value
582 * Name of the context to be switched to. Must be one of "chrome" or
585 * @throws {InvalidArgumentError}
586 * If <var>value</var> is not a string.
587 * @throws {WebDriverError}
588 * If <var>value</var> is not a valid browsing context.
590 GeckoDriver.prototype.setContext = function (cmd) {
591 let value = lazy.assert.string(cmd.parameters.value);
593 this.context = value;
597 * Gets the context type that is Marionette's current target for
598 * browsing context scoped commands.
600 * You may choose a context through the {@link #setContext} command.
602 * The default browsing context is {@link Context.Content}.
607 GeckoDriver.prototype.getContext = function () {
612 * Executes a JavaScript function in the context of the current browsing
613 * context, if in content space, or in chrome space otherwise, and returns
614 * the return value of the function.
616 * It is important to note that if the <var>sandboxName</var> parameter
617 * is left undefined, the script will be evaluated in a mutable sandbox,
618 * causing any change it makes on the global state of the document to have
619 * lasting side-effects.
621 * @param {object} cmd
622 * @param {string} cmd.parameters.script
623 * Script to evaluate as a function body.
624 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
625 * Arguments exposed to the script in <code>arguments</code>.
626 * The array items must be serialisable to the WebDriver protocol.
627 * @param {string=} cmd.parameters.sandbox
628 * Name of the sandbox to evaluate the script in. The sandbox is
629 * cached for later re-use on the same Window object if
630 * <var>newSandbox</var> is false. If he parameter is undefined,
631 * the script is evaluated in a mutable sandbox. If the parameter
632 * is "system", it will be evaluted in a sandbox with elevated system
633 * privileges, equivalent to chrome space.
634 * @param {boolean=} cmd.parameters.newSandbox
635 * Forces the script to be evaluated in a fresh sandbox. Note that if
636 * it is undefined, the script will normally be evaluted in a fresh
638 * @param {string=} cmd.parameters.filename
639 * Filename of the client's program where this script is evaluated.
640 * @param {number=} cmd.parameters.line
641 * Line in the client's program where this script is evaluated.
643 * @returns {(string|boolean|number|object|WebReference)}
644 * Return value from the script, or null which signifies either the
645 * JavaScript notion of null or undefined.
647 * @throws {JavaScriptError}
648 * If an {@link Error} was thrown whilst evaluating the script.
649 * @throws {NoSuchElementError}
650 * If an element that was passed as part of <var>args</var> is unknown.
651 * @throws {NoSuchWindowError}
652 * Browsing context has been discarded.
653 * @throws {ScriptTimeoutError}
654 * If the script was interrupted due to reaching the session's
656 * @throws {StaleElementReferenceError}
657 * If an element that was passed as part of <var>args</var> or that is
658 * returned as result has gone stale.
660 GeckoDriver.prototype.executeScript = function (cmd) {
661 let { script, args } = cmd.parameters;
663 script: cmd.parameters.script,
664 args: cmd.parameters.args,
665 sandboxName: cmd.parameters.sandbox,
666 newSandbox: cmd.parameters.newSandbox,
667 file: cmd.parameters.filename,
668 line: cmd.parameters.line,
671 return this.execute_(script, args, opts);
675 * Executes a JavaScript function in the context of the current browsing
676 * context, if in content space, or in chrome space otherwise, and returns
677 * the object passed to the callback.
679 * The callback is always the last argument to the <var>arguments</var>
680 * list passed to the function scope of the script. It can be retrieved
684 * let callback = arguments[arguments.length - 1];
686 * // "foo" is returned
689 * It is important to note that if the <var>sandboxName</var> parameter
690 * is left undefined, the script will be evaluated in a mutable sandbox,
691 * causing any change it makes on the global state of the document to have
692 * lasting side-effects.
694 * @param {object} cmd
695 * @param {string} cmd.parameters.script
696 * Script to evaluate as a function body.
697 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
698 * Arguments exposed to the script in <code>arguments</code>.
699 * The array items must be serialisable to the WebDriver protocol.
700 * @param {string=} cmd.parameters.sandbox
701 * Name of the sandbox to evaluate the script in. The sandbox is
702 * cached for later re-use on the same Window object if
703 * <var>newSandbox</var> is false. If the parameter is undefined,
704 * the script is evaluated in a mutable sandbox. If the parameter
705 * is "system", it will be evaluted in a sandbox with elevated system
706 * privileges, equivalent to chrome space.
707 * @param {boolean=} cmd.parameters.newSandbox
708 * Forces the script to be evaluated in a fresh sandbox. Note that if
709 * it is undefined, the script will normally be evaluted in a fresh
711 * @param {string=} cmd.parameters.filename
712 * Filename of the client's program where this script is evaluated.
713 * @param {number=} cmd.parameters.line
714 * Line in the client's program where this script is evaluated.
716 * @returns {(string|boolean|number|object|WebReference)}
717 * Return value from the script, or null which signifies either the
718 * JavaScript notion of null or undefined.
720 * @throws {JavaScriptError}
721 * If an Error was thrown whilst evaluating the script.
722 * @throws {NoSuchElementError}
723 * If an element that was passed as part of <var>args</var> is unknown.
724 * @throws {NoSuchWindowError}
725 * Browsing context has been discarded.
726 * @throws {ScriptTimeoutError}
727 * If the script was interrupted due to reaching the session's
729 * @throws {StaleElementReferenceError}
730 * If an element that was passed as part of <var>args</var> or that is
731 * returned as result has gone stale.
733 GeckoDriver.prototype.executeAsyncScript = function (cmd) {
734 let { script, args } = cmd.parameters;
736 script: cmd.parameters.script,
737 args: cmd.parameters.args,
738 sandboxName: cmd.parameters.sandbox,
739 newSandbox: cmd.parameters.newSandbox,
740 file: cmd.parameters.filename,
741 line: cmd.parameters.line,
745 return this.execute_(script, args, opts);
748 GeckoDriver.prototype.execute_ = async function (
759 lazy.assert.open(this.getBrowsingContext());
760 await this._handleUserPrompts();
764 lazy.pprint`Expected "script" to be a string: ${script}`
768 lazy.pprint`Expected script args to be an array: ${args}`
770 if (sandboxName !== null) {
773 lazy.pprint`Expected sandbox name to be a string: ${sandboxName}`
778 lazy.pprint`Expected newSandbox to be boolean: ${newSandbox}`
780 lazy.assert.string(file, lazy.pprint`Expected file to be a string: ${file}`);
781 lazy.assert.number(line, lazy.pprint`Expected line to be a number: ${line}`);
784 timeout: this.currentSession.timeouts.script,
792 return this.getActor().executeScript(script, args, opts);
796 * Navigate to given URL.
798 * Navigates the current browsing context to the given URL and waits for
799 * the document to load or the session's page timeout duration to elapse
802 * The command will return with a failure if there is an error loading
803 * the document or the URL is blocked. This can occur if it fails to
804 * reach host, the URL is malformed, or if there is a certificate issue
805 * to name some examples.
807 * The document is considered successfully loaded when the
808 * DOMContentLoaded event on the frame element associated with the
809 * current window triggers and document.readyState is "complete".
811 * In chrome context it will change the current window's location to
812 * the supplied URL and wait until document.readyState equals "complete"
813 * or the page timeout duration has elapsed.
815 * @param {object} cmd
816 * @param {string} cmd.parameters.url
817 * URL to navigate to.
819 * @throws {NoSuchWindowError}
820 * Top-level browsing context has been discarded.
821 * @throws {UnexpectedAlertOpenError}
822 * A modal dialog is open, blocking this operation.
823 * @throws {UnsupportedOperationError}
824 * Not available in current context.
826 GeckoDriver.prototype.navigateTo = async function (cmd) {
827 lazy.assert.content(this.context);
828 const browsingContext = lazy.assert.open(
829 this.getBrowsingContext({ top: true })
831 await this._handleUserPrompts();
835 validURL = new URL(cmd.parameters.url);
837 throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
840 // Switch to the top-level browsing context before navigating
841 this.currentSession.contentBrowsingContext = browsingContext;
843 const loadEventExpected = lazy.navigate.isLoadEventExpected(
844 this._getCurrentURL(),
850 await lazy.navigate.waitForNavigationCompleted(
853 lazy.navigate.navigateTo(browsingContext, validURL);
855 { loadEventExpected }
858 this.curBrowser.contentBrowser.focus();
862 * Get a string representing the current URL.
864 * On Desktop this returns a string representation of the URL of the
865 * current top level browsing context. This is equivalent to
866 * document.location.href.
868 * When in the context of the chrome, this returns the canonical URL
869 * of the current resource.
871 * @throws {NoSuchWindowError}
872 * Top-level browsing context has been discarded.
873 * @throws {UnexpectedAlertOpenError}
874 * A modal dialog is open, blocking this operation.
876 GeckoDriver.prototype.getCurrentUrl = async function () {
877 lazy.assert.open(this.getBrowsingContext({ top: true }));
878 await this._handleUserPrompts();
880 return this._getCurrentURL().href;
884 * Gets the current title of the window.
887 * Document title of the top-level browsing context.
889 * @throws {NoSuchWindowError}
890 * Top-level browsing context has been discarded.
891 * @throws {UnexpectedAlertOpenError}
892 * A modal dialog is open, blocking this operation.
894 GeckoDriver.prototype.getTitle = async function () {
895 lazy.assert.open(this.getBrowsingContext({ top: true }));
896 await this._handleUserPrompts();
902 * Gets the current type of the window.
907 * @throws {NoSuchWindowError}
908 * Top-level browsing context has been discarded.
910 GeckoDriver.prototype.getWindowType = function () {
911 lazy.assert.open(this.getBrowsingContext({ top: true }));
913 return this.windowType;
917 * Gets the page source of the content document.
920 * String serialisation of the DOM of the current browsing context's
923 * @throws {NoSuchWindowError}
924 * Browsing context has been discarded.
925 * @throws {UnexpectedAlertOpenError}
926 * A modal dialog is open, blocking this operation.
928 GeckoDriver.prototype.getPageSource = async function () {
929 lazy.assert.open(this.getBrowsingContext());
930 await this._handleUserPrompts();
932 return this.getActor().getPageSource();
936 * Cause the browser to traverse one step backward in the joint history
937 * of the current browsing context.
939 * @throws {NoSuchWindowError}
940 * Top-level browsing context has been discarded.
941 * @throws {UnexpectedAlertOpenError}
942 * A modal dialog is open, blocking this operation.
943 * @throws {UnsupportedOperationError}
944 * Not available in current context.
946 GeckoDriver.prototype.goBack = async function () {
947 lazy.assert.content(this.context);
948 const browsingContext = lazy.assert.open(
949 this.getBrowsingContext({ top: true })
951 await this._handleUserPrompts();
953 // If there is no history, just return
954 if (!browsingContext.embedderElement?.canGoBack) {
958 await lazy.navigate.waitForNavigationCompleted(this, () => {
959 browsingContext.goBack();
964 * Cause the browser to traverse one step forward in the joint history
965 * of the current browsing context.
967 * @throws {NoSuchWindowError}
968 * Top-level browsing context has been discarded.
969 * @throws {UnexpectedAlertOpenError}
970 * A modal dialog is open, blocking this operation.
971 * @throws {UnsupportedOperationError}
972 * Not available in current context.
974 GeckoDriver.prototype.goForward = async function () {
975 lazy.assert.content(this.context);
976 const browsingContext = lazy.assert.open(
977 this.getBrowsingContext({ top: true })
979 await this._handleUserPrompts();
981 // If there is no history, just return
982 if (!browsingContext.embedderElement?.canGoForward) {
986 await lazy.navigate.waitForNavigationCompleted(this, () => {
987 browsingContext.goForward();
992 * Causes the browser to reload the page in current top-level browsing
995 * @throws {NoSuchWindowError}
996 * Top-level browsing context has been discarded.
997 * @throws {UnexpectedAlertOpenError}
998 * A modal dialog is open, blocking this operation.
999 * @throws {UnsupportedOperationError}
1000 * Not available in current context.
1002 GeckoDriver.prototype.refresh = async function () {
1003 lazy.assert.content(this.context);
1004 const browsingContext = lazy.assert.open(
1005 this.getBrowsingContext({ top: true })
1007 await this._handleUserPrompts();
1009 // Switch to the top-level browsing context before navigating
1010 this.currentSession.contentBrowsingContext = browsingContext;
1012 await lazy.navigate.waitForNavigationCompleted(this, () => {
1013 lazy.navigate.refresh(browsingContext);
1018 * Get the current window's handle. On desktop this typically corresponds
1019 * to the currently selected tab.
1021 * For chrome scope it returns the window identifier for the current chrome
1022 * window for tests interested in managing the chrome window and tab separately.
1024 * Return an opaque server-assigned identifier to this window that
1025 * uniquely identifies it within this Marionette instance. This can
1026 * be used to switch to this window at a later point.
1029 * Unique window handle.
1031 * @throws {NoSuchWindowError}
1032 * Top-level browsing context has been discarded.
1034 GeckoDriver.prototype.getWindowHandle = function () {
1035 lazy.assert.open(this.getBrowsingContext({ top: true }));
1037 if (this.context == lazy.Context.Chrome) {
1038 return lazy.windowManager.getIdForWindow(this.curBrowser.window);
1040 return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
1044 * Get a list of top-level browsing contexts. On desktop this typically
1045 * corresponds to the set of open tabs for browser windows, or the window
1046 * itself for non-browser chrome windows.
1048 * For chrome scope it returns identifiers for each open chrome window for
1049 * tests interested in managing a set of chrome windows and tabs separately.
1051 * Each window handle is assigned by the server and is guaranteed unique,
1052 * however the return array does not have a specified ordering.
1054 * @returns {Array.<string>}
1055 * Unique window handles.
1057 GeckoDriver.prototype.getWindowHandles = function () {
1058 if (this.context == lazy.Context.Chrome) {
1059 return lazy.windowManager.chromeWindowHandles.map(String);
1061 return lazy.TabManager.allBrowserUniqueIds.map(String);
1065 * Get the current position and size of the browser window currently in focus.
1067 * Will return the current browser window size in pixels. Refers to
1068 * window outerWidth and outerHeight values, which include scroll bars,
1071 * @returns {Object<string, number>}
1072 * Object with |x| and |y| coordinates, and |width| and |height|
1073 * of browser window.
1075 * @throws {NoSuchWindowError}
1076 * Top-level browsing context has been discarded.
1077 * @throws {UnexpectedAlertOpenError}
1078 * A modal dialog is open, blocking this operation.
1080 GeckoDriver.prototype.getWindowRect = async function () {
1081 lazy.assert.open(this.getBrowsingContext({ top: true }));
1082 await this._handleUserPrompts();
1084 return this.curBrowser.rect;
1088 * Set the window position and size of the browser on the operating
1089 * system window manager.
1091 * The supplied `width` and `height` values refer to the window `outerWidth`
1092 * and `outerHeight` values, which include browser chrome and OS-level
1095 * @param {object} cmd
1096 * @param {number} cmd.parameters.x
1097 * X coordinate of the top/left of the window that it will be
1099 * @param {number} cmd.parameters.y
1100 * Y coordinate of the top/left of the window that it will be
1102 * @param {number} cmd.parameters.width
1103 * Width to resize the window to.
1104 * @param {number} cmd.parameters.height
1105 * Height to resize the window to.
1107 * @returns {Object<string, number>}
1108 * Object with `x` and `y` coordinates and `width` and `height`
1111 * @throws {NoSuchWindowError}
1112 * Top-level browsing context has been discarded.
1113 * @throws {UnexpectedAlertOpenError}
1114 * A modal dialog is open, blocking this operation.
1115 * @throws {UnsupportedOperationError}
1116 * Not applicable to application.
1118 GeckoDriver.prototype.setWindowRect = async function (cmd) {
1119 lazy.assert.desktop();
1120 lazy.assert.open(this.getBrowsingContext({ top: true }));
1121 await this._handleUserPrompts();
1123 const { x = null, y = null, width = null, height = null } = cmd.parameters;
1125 lazy.assert.integer(x);
1128 lazy.assert.integer(y);
1130 if (height !== null) {
1131 lazy.assert.positiveInteger(height);
1133 if (width !== null) {
1134 lazy.assert.positiveInteger(width);
1137 const win = this.getCurrentWindow();
1138 switch (lazy.WindowState.from(win.windowState)) {
1139 case lazy.WindowState.Fullscreen:
1140 await exitFullscreen(win);
1143 case lazy.WindowState.Maximized:
1144 case lazy.WindowState.Minimized:
1145 await restoreWindow(win);
1149 function geometryMatches() {
1153 (win.outerWidth !== width || win.outerHeight !== height)
1157 if (x !== null && y !== null && (win.screenX !== x || win.screenY !== y)) {
1160 lazy.logger.trace(`Requested window geometry matches`);
1164 if (!geometryMatches()) {
1165 // There might be more than one resize or MozUpdateWindowPos event due
1166 // to previous geometry changes, such as from restoreWindow(), so
1167 // wait longer if window geometry does not match.
1168 const options = { checkFn: geometryMatches, timeout: 500 };
1169 const promises = [];
1170 if (width !== null && height !== null) {
1171 promises.push(new lazy.EventPromise(win, "resize", options));
1172 win.resizeTo(width, height);
1174 if (x !== null && y !== null) {
1176 new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
1181 await Promise.race(promises);
1183 if (e instanceof lazy.error.TimeoutError) {
1184 // The operating system might not honor the move or resize, in which
1185 // case assume that geometry will have been adjusted "as close as
1186 // possible" to that requested. There may be no event received if the
1187 // geometry is already as close as possible.
1194 return this.curBrowser.rect;
1198 * Switch current top-level browsing context by name or server-assigned
1199 * ID. Searches for windows by name, then ID. Content windows take
1202 * @param {object} cmd
1203 * @param {string} cmd.parameters.handle
1204 * Handle of the window to switch to.
1205 * @param {boolean=} cmd.parameters.focus
1206 * A boolean value which determines whether to focus
1207 * the window. Defaults to true.
1209 * @throws {InvalidArgumentError}
1210 * If <var>handle</var> is not a string or <var>focus</var> not a boolean.
1211 * @throws {NoSuchWindowError}
1212 * Top-level browsing context has been discarded.
1214 GeckoDriver.prototype.switchToWindow = async function (cmd) {
1215 const { focus = true, handle } = cmd.parameters;
1219 lazy.pprint`Expected "handle" to be a string, got ${handle}`
1221 lazy.assert.boolean(
1223 lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
1226 const found = lazy.windowManager.findWindowByHandle(handle);
1228 let selected = false;
1231 await this.setWindowHandle(found, focus);
1234 lazy.logger.error(e);
1239 throw new lazy.error.NoSuchWindowError(
1240 `Unable to locate window: ${handle}`
1246 * Switch the marionette window to a given window. If the browser in
1247 * the window is unregistered, register that browser and wait for
1248 * the registration is complete. If |focus| is true then set the focus
1251 * @param {object} winProperties
1252 * Object containing window properties such as returned from
1253 * :js:func:`GeckoDriver#getWindowProperties`
1254 * @param {boolean=} focus
1255 * A boolean value which determines whether to focus the window.
1258 GeckoDriver.prototype.setWindowHandle = async function (
1262 if (!(winProperties.id in this.browsers)) {
1263 // Initialise Marionette if the current chrome window has not been seen
1264 // before. Also register the initial tab, if one exists.
1265 this.addBrowser(winProperties.win);
1266 this.mainFrame = winProperties.win;
1268 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1270 if (!winProperties.hasTabBrowser) {
1271 this.currentSession.contentBrowsingContext = null;
1273 const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
1275 // For chrome windows such as a reftest window, `getTabBrowser` is not
1276 // a tabbrowser, it is the content browser which should be used here.
1277 const contentBrowser = tabBrowser.tabs
1278 ? tabBrowser.selectedBrowser
1281 this.currentSession.contentBrowsingContext =
1282 contentBrowser.browsingContext;
1283 this.registerBrowser(contentBrowser);
1286 // Otherwise switch to the known chrome window
1287 this.curBrowser = this.browsers[winProperties.id];
1288 this.mainFrame = this.curBrowser.window;
1290 // Activate the tab if it's a content window.
1292 if (winProperties.hasTabBrowser) {
1293 tab = await this.curBrowser.switchToTab(
1294 winProperties.tabIndex,
1300 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1301 this.currentSession.contentBrowsingContext =
1302 tab?.linkedBrowser.browsingContext;
1305 // Check for an existing dialog for the new window
1306 this.dialog = lazy.modal.findPrompt(this.curBrowser);
1308 // If there is an open window modal dialog the underlying chrome window
1309 // cannot be focused.
1310 if (focus && !this.dialog?.isWindowModal) {
1311 await this.curBrowser.focusWindow();
1316 * Set the current browsing context for future commands to the parent
1317 * of the current browsing context.
1319 * @throws {NoSuchWindowError}
1320 * Browsing context has been discarded.
1321 * @throws {UnexpectedAlertOpenError}
1322 * A modal dialog is open, blocking this operation.
1324 GeckoDriver.prototype.switchToParentFrame = async function () {
1325 let browsingContext = this.getBrowsingContext();
1326 if (browsingContext && !browsingContext.parent) {
1330 browsingContext = lazy.assert.open(browsingContext?.parent);
1332 this.currentSession.contentBrowsingContext = browsingContext;
1336 * Switch to a given frame within the current window.
1338 * @param {object} cmd
1339 * @param {(string | object)=} cmd.parameters.element
1340 * A web element reference of the frame or its element id.
1341 * @param {number=} cmd.parameters.id
1342 * The index of the frame to switch to.
1343 * If both element and id are not defined, switch to top-level frame.
1345 * @throws {NoSuchElementError}
1346 * If element represented by reference <var>element</var> is unknown.
1347 * @throws {NoSuchWindowError}
1348 * Browsing context has been discarded.
1349 * @throws {StaleElementReferenceError}
1350 * If element represented by reference <var>element</var> has gone stale.
1351 * @throws {UnexpectedAlertOpenError}
1352 * A modal dialog is open, blocking this operation.
1354 GeckoDriver.prototype.switchToFrame = async function (cmd) {
1355 const { element: el, id } = cmd.parameters;
1357 if (typeof id == "number") {
1358 lazy.assert.unsignedShort(
1360 `Expected id to be unsigned short, got ${id}`
1364 const top = id == null && el == null;
1365 lazy.assert.open(this.getBrowsingContext({ top }));
1366 await this._handleUserPrompts();
1368 // Bug 1495063: Elements should be passed as WebReference reference
1370 if (typeof el == "string") {
1371 byFrame = lazy.WebElement.fromUUID(el).toJSON();
1376 const { browsingContext } = await this.getActor({ top }).switchToFrame(
1380 this.currentSession.contentBrowsingContext = browsingContext;
1383 GeckoDriver.prototype.getTimeouts = function () {
1384 return this.currentSession.timeouts;
1388 * Set timeout for page loading, searching, and scripts.
1390 * @param {object} cmd
1391 * @param {Object<string, number>} cmd.parameters
1392 * Dictionary of timeout types and their new value, where all timeout
1393 * types are optional.
1395 * @throws {InvalidArgumentError}
1396 * If timeout type key is unknown, or the value provided with it is
1399 GeckoDriver.prototype.setTimeouts = function (cmd) {
1400 // merge with existing timeouts
1401 let merged = Object.assign(
1402 this.currentSession.timeouts.toJSON(),
1406 this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
1410 * Perform a series of grouped actions at the specified points in time.
1412 * @param {object} cmd
1413 * @param {Array<?>} cmd.parameters.actions
1414 * Array of objects that each represent an action sequence.
1416 * @throws {NoSuchElementError}
1417 * If an element that is used as part of the action chain is unknown.
1418 * @throws {NoSuchWindowError}
1419 * Browsing context has been discarded.
1420 * @throws {StaleElementReferenceError}
1421 * If an element that is used as part of the action chain has gone stale.
1422 * @throws {UnexpectedAlertOpenError}
1423 * A modal dialog is open, blocking this operation.
1424 * @throws {UnsupportedOperationError}
1425 * Not yet available in current context.
1427 GeckoDriver.prototype.performActions = async function (cmd) {
1428 lazy.assert.open(this.getBrowsingContext());
1429 await this._handleUserPrompts();
1431 const actions = cmd.parameters.actions;
1432 await this.getActor().performActions(actions);
1436 * Release all the keys and pointer buttons that are currently depressed.
1438 * @throws {NoSuchWindowError}
1439 * Browsing context has been discarded.
1440 * @throws {UnexpectedAlertOpenError}
1441 * A modal dialog is open, blocking this operation.
1442 * @throws {UnsupportedOperationError}
1443 * Not available in current context.
1445 GeckoDriver.prototype.releaseActions = async function () {
1446 lazy.assert.open(this.getBrowsingContext());
1447 await this._handleUserPrompts();
1449 await this.getActor().releaseActions();
1453 * Find an element using the indicated search strategy.
1455 * @param {object} cmd
1456 * @param {string=} cmd.parameters.element
1457 * Web element reference ID to the element that will be used as start node.
1458 * @param {string} cmd.parameters.using
1459 * Indicates which search method to use.
1460 * @param {string} cmd.parameters.value
1461 * Value the client is looking for.
1463 * @returns {WebElement}
1464 * Return the found element.
1466 * @throws {NoSuchElementError}
1467 * If element represented by reference <var>element</var> is unknown.
1468 * @throws {NoSuchWindowError}
1469 * Browsing context has been discarded.
1470 * @throws {StaleElementReferenceError}
1471 * If element represented by reference <var>element</var> has gone stale.
1472 * @throws {UnexpectedAlertOpenError}
1473 * A modal dialog is open, blocking this operation.
1475 GeckoDriver.prototype.findElement = async function (cmd) {
1476 const { element: el, using, value } = cmd.parameters;
1478 if (!lazy.supportedStrategies.has(using)) {
1479 throw new lazy.error.InvalidSelectorError(
1480 `Strategy not supported: ${using}`
1484 lazy.assert.defined(value);
1485 lazy.assert.open(this.getBrowsingContext());
1486 await this._handleUserPrompts();
1489 if (typeof el != "undefined") {
1490 startNode = lazy.WebElement.fromUUID(el).toJSON();
1495 timeout: this.currentSession.timeouts.implicit,
1499 return this.getActor().findElement(using, value, opts);
1503 * Find an element within shadow root using the indicated search strategy.
1505 * @param {object} cmd
1506 * @param {string} cmd.parameters.shadowRoot
1507 * Shadow root reference ID.
1508 * @param {string} cmd.parameters.using
1509 * Indicates which search method to use.
1510 * @param {string} cmd.parameters.value
1511 * Value the client is looking for.
1513 * @returns {WebElement}
1514 * Return the found element.
1516 * @throws {DetachedShadowRootError}
1517 * If shadow root represented by reference <var>id</var> is
1518 * no longer attached to the DOM.
1519 * @throws {NoSuchElementError}
1520 * If the element which is looked for with <var>value</var> was
1522 * @throws {NoSuchShadowRoot}
1523 * If shadow root represented by reference <var>shadowRoot</var> is unknown.
1524 * @throws {NoSuchWindowError}
1525 * Browsing context has been discarded.
1526 * @throws {UnexpectedAlertOpenError}
1527 * A modal dialog is open, blocking this operation.
1529 GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) {
1530 const { shadowRoot, using, value } = cmd.parameters;
1532 if (!lazy.supportedStrategies.has(using)) {
1533 throw new lazy.error.InvalidSelectorError(
1534 `Strategy not supported: ${using}`
1538 lazy.assert.defined(value);
1539 lazy.assert.open(this.getBrowsingContext());
1540 await this._handleUserPrompts();
1544 startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
1545 timeout: this.currentSession.timeouts.implicit,
1548 return this.getActor().findElement(using, value, opts);
1552 * Find elements using the indicated search strategy.
1554 * @param {object} cmd
1555 * @param {string=} cmd.parameters.element
1556 * Web element reference ID to the element that will be used as start node.
1557 * @param {string} cmd.parameters.using
1558 * Indicates which search method to use.
1559 * @param {string} cmd.parameters.value
1560 * Value the client is looking for.
1562 * @returns {Array<WebElement>}
1563 * Return the array of found elements.
1565 * @throws {NoSuchElementError}
1566 * If element represented by reference <var>element</var> is unknown.
1567 * @throws {NoSuchWindowError}
1568 * Browsing context has been discarded.
1569 * @throws {StaleElementReferenceError}
1570 * If element represented by reference <var>element</var> has gone stale.
1571 * @throws {UnexpectedAlertOpenError}
1572 * A modal dialog is open, blocking this operation.
1574 GeckoDriver.prototype.findElements = async function (cmd) {
1575 const { element: el, using, value } = cmd.parameters;
1577 if (!lazy.supportedStrategies.has(using)) {
1578 throw new lazy.error.InvalidSelectorError(
1579 `Strategy not supported: ${using}`
1583 lazy.assert.defined(value);
1584 lazy.assert.open(this.getBrowsingContext());
1585 await this._handleUserPrompts();
1588 if (typeof el != "undefined") {
1589 startNode = lazy.WebElement.fromUUID(el).toJSON();
1594 timeout: this.currentSession.timeouts.implicit,
1598 return this.getActor().findElements(using, value, opts);
1602 * Find elements within shadow root using the indicated search strategy.
1604 * @param {object} cmd
1605 * @param {string} cmd.parameters.shadowRoot
1606 * Shadow root reference ID.
1607 * @param {string} cmd.parameters.using
1608 * Indicates which search method to use.
1609 * @param {string} cmd.parameters.value
1610 * Value the client is looking for.
1612 * @returns {Array<WebElement>}
1613 * Return the array of found elements.
1615 * @throws {DetachedShadowRootError}
1616 * If shadow root represented by reference <var>id</var> is
1617 * no longer attached to the DOM.
1618 * @throws {NoSuchShadowRoot}
1619 * If shadow root represented by reference <var>shadowRoot</var> is unknown.
1620 * @throws {NoSuchWindowError}
1621 * Browsing context has been discarded.
1622 * @throws {UnexpectedAlertOpenError}
1623 * A modal dialog is open, blocking this operation.
1625 GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) {
1626 const { shadowRoot, using, value } = cmd.parameters;
1628 if (!lazy.supportedStrategies.has(using)) {
1629 throw new lazy.error.InvalidSelectorError(
1630 `Strategy not supported: ${using}`
1634 lazy.assert.defined(value);
1635 lazy.assert.open(this.getBrowsingContext());
1636 await this._handleUserPrompts();
1640 startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
1641 timeout: this.currentSession.timeouts.implicit,
1644 return this.getActor().findElements(using, value, opts);
1648 * Return the shadow root of an element in the document.
1650 * @param {object} cmd
1651 * @param {id} cmd.parameters.id
1652 * A web element id reference.
1653 * @returns {ShadowRoot}
1654 * ShadowRoot of the element.
1656 * @throws {InvalidArgumentError}
1657 * If element <var>id</var> is not a string.
1658 * @throws {NoSuchElementError}
1659 * If element represented by reference <var>id</var> is unknown.
1660 * @throws {NoSuchShadowRoot}
1661 * Element does not have a shadow root attached.
1662 * @throws {NoSuchWindowError}
1663 * Browsing context has been discarded.
1664 * @throws {StaleElementReferenceError}
1665 * If element represented by reference <var>id</var> has gone stale.
1666 * @throws {UnexpectedAlertOpenError}
1667 * A modal dialog is open, blocking this operation.
1668 * @throws {UnsupportedOperationError}
1669 * Not available in chrome current context.
1671 GeckoDriver.prototype.getShadowRoot = async function (cmd) {
1672 // Bug 1743541: Add support for chrome scope.
1673 lazy.assert.content(this.context);
1674 lazy.assert.open(this.getBrowsingContext());
1675 await this._handleUserPrompts();
1677 let id = lazy.assert.string(
1679 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1681 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1683 return this.getActor().getShadowRoot(webEl);
1687 * Return the active element in the document.
1689 * @returns {WebReference}
1690 * Active element of the current browsing context's document
1691 * element, if the document element is non-null.
1693 * @throws {NoSuchElementError}
1694 * If the document does not have an active element, i.e. if
1695 * its document element has been deleted.
1696 * @throws {NoSuchWindowError}
1697 * Browsing context has been discarded.
1698 * @throws {UnexpectedAlertOpenError}
1699 * A modal dialog is open, blocking this operation.
1700 * @throws {UnsupportedOperationError}
1701 * Not available in chrome context.
1703 GeckoDriver.prototype.getActiveElement = async function () {
1704 lazy.assert.content(this.context);
1705 lazy.assert.open(this.getBrowsingContext());
1706 await this._handleUserPrompts();
1708 return this.getActor().getActiveElement();
1712 * Send click event to element.
1714 * @param {object} cmd
1715 * @param {string} cmd.parameters.id
1716 * Reference ID to the element that will be clicked.
1718 * @throws {InvalidArgumentError}
1719 * If element <var>id</var> is not a string.
1720 * @throws {NoSuchElementError}
1721 * If element represented by reference <var>id</var> is unknown.
1722 * @throws {NoSuchWindowError}
1723 * Browsing context has been discarded.
1724 * @throws {StaleElementReferenceError}
1725 * If element represented by reference <var>id</var> has gone stale.
1726 * @throws {UnexpectedAlertOpenError}
1727 * A modal dialog is open, blocking this operation.
1729 GeckoDriver.prototype.clickElement = async function (cmd) {
1730 const browsingContext = lazy.assert.open(this.getBrowsingContext());
1731 await this._handleUserPrompts();
1733 let id = lazy.assert.string(cmd.parameters.id);
1734 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1736 const actor = this.getActor();
1738 const loadEventExpected = lazy.navigate.isLoadEventExpected(
1739 this._getCurrentURL(),
1742 target: await actor.getElementAttribute(webEl, "target"),
1746 await lazy.navigate.waitForNavigationCompleted(
1748 () => actor.clickElement(webEl, this.currentSession.capabilities),
1751 // The click might trigger a navigation, so don't count on it.
1752 requireBeforeUnload: false,
1758 * Get a given attribute of an element.
1760 * @param {object} cmd
1761 * @param {string} cmd.parameters.id
1762 * Web element reference ID to the element that will be inspected.
1763 * @param {string} cmd.parameters.name
1764 * Name of the attribute which value to retrieve.
1767 * Value of the attribute.
1769 * @throws {InvalidArgumentError}
1770 * If <var>id</var> or <var>name</var> are not strings.
1771 * @throws {NoSuchElementError}
1772 * If element represented by reference <var>id</var> is unknown.
1773 * @throws {NoSuchWindowError}
1774 * Browsing context has been discarded.
1775 * @throws {StaleElementReferenceError}
1776 * If element represented by reference <var>id</var> has gone stale.
1777 * @throws {UnexpectedAlertOpenError}
1778 * A modal dialog is open, blocking this operation.
1780 GeckoDriver.prototype.getElementAttribute = async function (cmd) {
1781 lazy.assert.open(this.getBrowsingContext());
1782 await this._handleUserPrompts();
1784 const id = lazy.assert.string(cmd.parameters.id);
1785 const name = lazy.assert.string(cmd.parameters.name);
1786 const webEl = lazy.WebElement.fromUUID(id).toJSON();
1788 return this.getActor().getElementAttribute(webEl, name);
1792 * Returns the value of a property associated with given element.
1794 * @param {object} cmd
1795 * @param {string} cmd.parameters.id
1796 * Web element reference ID to the element that will be inspected.
1797 * @param {string} cmd.parameters.name
1798 * Name of the property which value to retrieve.
1801 * Value of the property.
1803 * @throws {InvalidArgumentError}
1804 * If <var>id</var> or <var>name</var> are not strings.
1805 * @throws {NoSuchElementError}
1806 * If element represented by reference <var>id</var> is unknown.
1807 * @throws {NoSuchWindowError}
1808 * Browsing context has been discarded.
1809 * @throws {StaleElementReferenceError}
1810 * If element represented by reference <var>id</var> has gone stale.
1811 * @throws {UnexpectedAlertOpenError}
1812 * A modal dialog is open, blocking this operation.
1814 GeckoDriver.prototype.getElementProperty = async function (cmd) {
1815 lazy.assert.open(this.getBrowsingContext());
1816 await this._handleUserPrompts();
1818 const id = lazy.assert.string(cmd.parameters.id);
1819 const name = lazy.assert.string(cmd.parameters.name);
1820 const webEl = lazy.WebElement.fromUUID(id).toJSON();
1822 return this.getActor().getElementProperty(webEl, name);
1826 * Get the text of an element, if any. Includes the text of all child
1829 * @param {object} cmd
1830 * @param {string} cmd.parameters.id
1831 * Reference ID to the element that will be inspected.
1834 * Element's text "as rendered".
1836 * @throws {InvalidArgumentError}
1837 * If <var>id</var> is not a string.
1838 * @throws {NoSuchElementError}
1839 * If element represented by reference <var>id</var> is unknown.
1840 * @throws {NoSuchWindowError}
1841 * Browsing context has been discarded.
1842 * @throws {StaleElementReferenceError}
1843 * If element represented by reference <var>id</var> has gone stale.
1844 * @throws {UnexpectedAlertOpenError}
1845 * A modal dialog is open, blocking this operation.
1847 GeckoDriver.prototype.getElementText = async function (cmd) {
1848 lazy.assert.open(this.getBrowsingContext());
1849 await this._handleUserPrompts();
1851 let id = lazy.assert.string(cmd.parameters.id);
1852 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1854 return this.getActor().getElementText(webEl);
1858 * Get the tag name of the element.
1860 * @param {object} cmd
1861 * @param {string} cmd.parameters.id
1862 * Reference ID to the element that will be inspected.
1865 * Local tag name of element.
1867 * @throws {InvalidArgumentError}
1868 * If <var>id</var> is not a string.
1869 * @throws {NoSuchElementError}
1870 * If element represented by reference <var>id</var> is unknown.
1871 * @throws {NoSuchWindowError}
1872 * Browsing context has been discarded.
1873 * @throws {StaleElementReferenceError}
1874 * If element represented by reference <var>id</var> has gone stale.
1875 * @throws {UnexpectedAlertOpenError}
1876 * A modal dialog is open, blocking this operation.
1878 GeckoDriver.prototype.getElementTagName = async function (cmd) {
1879 lazy.assert.open(this.getBrowsingContext());
1880 await this._handleUserPrompts();
1882 let id = lazy.assert.string(cmd.parameters.id);
1883 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1885 return this.getActor().getElementTagName(webEl);
1889 * Check if element is displayed.
1891 * @param {object} cmd
1892 * @param {string} cmd.parameters.id
1893 * Reference ID to the element that will be inspected.
1895 * @returns {boolean}
1896 * True if displayed, false otherwise.
1898 * @throws {InvalidArgumentError}
1899 * If <var>id</var> is not a string.
1900 * @throws {NoSuchElementError}
1901 * If element represented by reference <var>id</var> is unknown.
1902 * @throws {NoSuchWindowError}
1903 * Browsing context has been discarded.
1904 * @throws {UnexpectedAlertOpenError}
1905 * A modal dialog is open, blocking this operation.
1907 GeckoDriver.prototype.isElementDisplayed = async function (cmd) {
1908 lazy.assert.open(this.getBrowsingContext());
1909 await this._handleUserPrompts();
1911 let id = lazy.assert.string(cmd.parameters.id);
1912 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1914 return this.getActor().isElementDisplayed(
1916 this.currentSession.capabilities
1921 * Return the property of the computed style of an element.
1923 * @param {object} cmd
1924 * @param {string} cmd.parameters.id
1925 * Reference ID to the element that will be checked.
1926 * @param {string} cmd.parameters.propertyName
1927 * CSS rule that is being requested.
1930 * Value of |propertyName|.
1932 * @throws {InvalidArgumentError}
1933 * If <var>id</var> or <var>propertyName</var> are not strings.
1934 * @throws {NoSuchElementError}
1935 * If element represented by reference <var>id</var> is unknown.
1936 * @throws {NoSuchWindowError}
1937 * Browsing context has been discarded.
1938 * @throws {StaleElementReferenceError}
1939 * If element represented by reference <var>id</var> has gone stale.
1940 * @throws {UnexpectedAlertOpenError}
1941 * A modal dialog is open, blocking this operation.
1943 GeckoDriver.prototype.getElementValueOfCssProperty = async function (cmd) {
1944 lazy.assert.open(this.getBrowsingContext());
1945 await this._handleUserPrompts();
1947 let id = lazy.assert.string(cmd.parameters.id);
1948 let prop = lazy.assert.string(cmd.parameters.propertyName);
1949 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1951 return this.getActor().getElementValueOfCssProperty(webEl, prop);
1955 * Check if element is enabled.
1957 * @param {object} cmd
1958 * @param {string} cmd.parameters.id
1959 * Reference ID to the element that will be checked.
1961 * @returns {boolean}
1962 * True if enabled, false if disabled.
1964 * @throws {InvalidArgumentError}
1965 * If <var>id</var> is not a string.
1966 * @throws {NoSuchElementError}
1967 * If element represented by reference <var>id</var> is unknown.
1968 * @throws {NoSuchWindowError}
1969 * Browsing context has been discarded.
1970 * @throws {StaleElementReferenceError}
1971 * If element represented by reference <var>id</var> has gone stale.
1972 * @throws {UnexpectedAlertOpenError}
1973 * A modal dialog is open, blocking this operation.
1975 GeckoDriver.prototype.isElementEnabled = async function (cmd) {
1976 lazy.assert.open(this.getBrowsingContext());
1977 await this._handleUserPrompts();
1979 let id = lazy.assert.string(cmd.parameters.id);
1980 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1982 return this.getActor().isElementEnabled(
1984 this.currentSession.capabilities
1989 * Check if element is selected.
1991 * @param {object} cmd
1992 * @param {string} cmd.parameters.id
1993 * Reference ID to the element that will be checked.
1995 * @returns {boolean}
1996 * True if selected, false if unselected.
1998 * @throws {InvalidArgumentError}
1999 * If <var>id</var> is not a string.
2000 * @throws {NoSuchElementError}
2001 * If element represented by reference <var>id</var> is unknown.
2002 * @throws {NoSuchWindowError}
2003 * Browsing context has been discarded.
2004 * @throws {UnexpectedAlertOpenError}
2005 * A modal dialog is open, blocking this operation.
2007 GeckoDriver.prototype.isElementSelected = async function (cmd) {
2008 lazy.assert.open(this.getBrowsingContext());
2009 await this._handleUserPrompts();
2011 let id = lazy.assert.string(cmd.parameters.id);
2012 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2014 return this.getActor().isElementSelected(
2016 this.currentSession.capabilities
2021 * @throws {InvalidArgumentError}
2022 * If <var>id</var> is not a string.
2023 * @throws {NoSuchElementError}
2024 * If element represented by reference <var>id</var> is unknown.
2025 * @throws {NoSuchWindowError}
2026 * Browsing context has been discarded.
2027 * @throws {StaleElementReferenceError}
2028 * If element represented by reference <var>id</var> has gone stale.
2029 * @throws {UnexpectedAlertOpenError}
2030 * A modal dialog is open, blocking this operation.
2032 GeckoDriver.prototype.getElementRect = async function (cmd) {
2033 lazy.assert.open(this.getBrowsingContext());
2034 await this._handleUserPrompts();
2036 let id = lazy.assert.string(cmd.parameters.id);
2037 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2039 return this.getActor().getElementRect(webEl);
2043 * Send key presses to element after focusing on it.
2045 * @param {object} cmd
2046 * @param {string} cmd.parameters.id
2047 * Reference ID to the element that will be checked.
2048 * @param {string} cmd.parameters.text
2049 * Value to send to the element.
2051 * @throws {InvalidArgumentError}
2052 * If <var>id</var> or <var>text</var> are not strings.
2053 * @throws {NoSuchElementError}
2054 * If element represented by reference <var>id</var> is unknown.
2055 * @throws {NoSuchWindowError}
2056 * Browsing context has been discarded.
2057 * @throws {StaleElementReferenceError}
2058 * If element represented by reference <var>id</var> has gone stale.
2059 * @throws {UnexpectedAlertOpenError}
2060 * A modal dialog is open, blocking this operation.
2062 GeckoDriver.prototype.sendKeysToElement = async function (cmd) {
2063 lazy.assert.open(this.getBrowsingContext());
2064 await this._handleUserPrompts();
2066 let id = lazy.assert.string(cmd.parameters.id);
2067 let text = lazy.assert.string(cmd.parameters.text);
2068 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2070 return this.getActor().sendKeysToElement(
2073 this.currentSession.capabilities
2078 * Clear the text of an element.
2080 * @param {object} cmd
2081 * @param {string} cmd.parameters.id
2082 * Reference ID to the element that will be cleared.
2084 * @throws {InvalidArgumentError}
2085 * If <var>id</var> is not a string.
2086 * @throws {NoSuchElementError}
2087 * If element represented by reference <var>id</var> is unknown.
2088 * @throws {NoSuchWindowError}
2089 * Browsing context has been discarded.
2090 * @throws {StaleElementReferenceError}
2091 * If element represented by reference <var>id</var> has gone stale.
2092 * @throws {UnexpectedAlertOpenError}
2093 * A modal dialog is open, blocking this operation.
2095 GeckoDriver.prototype.clearElement = async function (cmd) {
2096 lazy.assert.open(this.getBrowsingContext());
2097 await this._handleUserPrompts();
2099 let id = lazy.assert.string(cmd.parameters.id);
2100 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2102 await this.getActor().clearElement(webEl);
2106 * Add a single cookie to the cookie store associated with the active
2107 * document's address.
2109 * @param {object} cmd
2110 * @param {Map.<string, (string|number|boolean)>} cmd.parameters.cookie
2113 * @throws {InvalidCookieDomainError}
2114 * If <var>cookie</var> is for a different domain than the active
2116 * @throws {NoSuchWindowError}
2117 * Bbrowsing context has been discarded.
2118 * @throws {UnexpectedAlertOpenError}
2119 * A modal dialog is open, blocking this operation.
2120 * @throws {UnsupportedOperationError}
2121 * Not available in current context.
2123 GeckoDriver.prototype.addCookie = async function (cmd) {
2124 lazy.assert.content(this.context);
2125 lazy.assert.open(this.getBrowsingContext());
2126 await this._handleUserPrompts();
2128 let { protocol, hostname } = this._getCurrentURL();
2130 const networkSchemes = ["http:", "https:"];
2131 if (!networkSchemes.includes(protocol)) {
2132 throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
2135 let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
2137 lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
2141 * Get all the cookies for the current domain.
2143 * This is the equivalent of calling <code>document.cookie</code> and
2144 * parsing the result.
2146 * @throws {NoSuchWindowError}
2147 * Browsing context has been discarded.
2148 * @throws {UnexpectedAlertOpenError}
2149 * A modal dialog is open, blocking this operation.
2150 * @throws {UnsupportedOperationError}
2151 * Not available in current context.
2153 GeckoDriver.prototype.getCookies = async function () {
2154 lazy.assert.content(this.context);
2155 lazy.assert.open(this.getBrowsingContext());
2156 await this._handleUserPrompts();
2158 let { hostname, pathname } = this._getCurrentURL();
2159 return [...lazy.cookie.iter(hostname, pathname)];
2163 * Delete all cookies that are visible to a document.
2165 * @throws {NoSuchWindowError}
2166 * Browsing context has been discarded.
2167 * @throws {UnexpectedAlertOpenError}
2168 * A modal dialog is open, blocking this operation.
2169 * @throws {UnsupportedOperationError}
2170 * Not available in current context.
2172 GeckoDriver.prototype.deleteAllCookies = async function () {
2173 lazy.assert.content(this.context);
2174 lazy.assert.open(this.getBrowsingContext());
2175 await this._handleUserPrompts();
2177 let { hostname, pathname } = this._getCurrentURL();
2178 for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
2179 lazy.cookie.remove(toDelete);
2184 * Delete a cookie by name.
2186 * @throws {NoSuchWindowError}
2187 * Browsing context has been discarded.
2188 * @throws {UnexpectedAlertOpenError}
2189 * A modal dialog is open, blocking this operation.
2190 * @throws {UnsupportedOperationError}
2191 * Not available in current context.
2193 GeckoDriver.prototype.deleteCookie = async function (cmd) {
2194 lazy.assert.content(this.context);
2195 lazy.assert.open(this.getBrowsingContext());
2196 await this._handleUserPrompts();
2198 let { hostname, pathname } = this._getCurrentURL();
2199 let name = lazy.assert.string(cmd.parameters.name);
2200 for (let c of lazy.cookie.iter(hostname, pathname)) {
2201 if (c.name === name) {
2202 lazy.cookie.remove(c);
2208 * Open a new top-level browsing context.
2210 * @param {object} cmd
2211 * @param {string=} cmd.parameters.type
2212 * Optional type of the new top-level browsing context. Can be one of
2213 * `tab` or `window`. Defaults to `tab`.
2214 * @param {boolean=} cmd.parameters.focus
2215 * Optional flag if the new top-level browsing context should be opened
2216 * in foreground (focused) or background (not focused). Defaults to false.
2217 * @param {boolean=} cmd.parameters.private
2218 * Optional flag, which gets only evaluated for type `window`. True if the
2219 * new top-level browsing context should be a private window.
2220 * Defaults to false.
2222 * @returns {Object<string, string>}
2223 * Handle and type of the new browsing context.
2225 * @throws {NoSuchWindowError}
2226 * Top-level browsing context has been discarded.
2227 * @throws {UnexpectedAlertOpenError}
2228 * A modal dialog is open, blocking this operation.
2230 GeckoDriver.prototype.newWindow = async function (cmd) {
2231 lazy.assert.open(this.getBrowsingContext({ top: true }));
2232 await this._handleUserPrompts();
2235 if (typeof cmd.parameters.focus != "undefined") {
2236 focus = lazy.assert.boolean(
2237 cmd.parameters.focus,
2238 lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
2242 let isPrivate = false;
2243 if (typeof cmd.parameters.private != "undefined") {
2244 isPrivate = lazy.assert.boolean(
2245 cmd.parameters.private,
2246 lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
2251 if (typeof cmd.parameters.type != "undefined") {
2252 type = lazy.assert.string(
2253 cmd.parameters.type,
2254 lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
2258 // If an invalid or no type has been specified default to a tab.
2259 // On Android always use a new tab instead because the application has a
2260 // single window only.
2262 typeof type == "undefined" ||
2263 !["tab", "window"].includes(type) ||
2264 lazy.AppInfo.isAndroid
2273 let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
2274 contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
2278 // To not fail if a new type gets added in the future, make opening
2279 // a new tab the default action.
2280 let tab = await this.curBrowser.openTab(focus);
2281 contentBrowser = lazy.TabManager.getBrowserForTab(tab);
2284 // Actors need the new window to be loaded to safely execute queries.
2285 // Wait until the initial page load has been finished.
2286 await lazy.waitForInitialNavigationCompleted(
2287 contentBrowser.browsingContext.webProgress,
2289 unloadTimeout: 5000,
2293 const id = lazy.TabManager.getIdForBrowser(contentBrowser);
2295 return { handle: id.toString(), type };
2299 * Close the currently selected tab/window.
2301 * With multiple open tabs present the currently selected tab will
2302 * be closed. Otherwise the window itself will be closed. If it is the
2303 * last window currently open, the window will not be closed to prevent
2304 * a shutdown of the application. Instead the returned list of window
2307 * @returns {Array.<string>}
2308 * Unique window handles of remaining windows.
2310 * @throws {NoSuchWindowError}
2311 * Top-level browsing context has been discarded.
2312 * @throws {UnexpectedAlertOpenError}
2313 * A modal dialog is open, blocking this operation.
2315 GeckoDriver.prototype.close = async function () {
2317 this.getBrowsingContext({ context: lazy.Context.Content, top: true })
2319 await this._handleUserPrompts();
2321 // If there is only one window left, do not close unless windowless mode is
2322 // enabled. Instead return a faked empty array of window handles.
2323 // This will instruct geckodriver to terminate the application.
2325 lazy.TabManager.getTabCount() === 1 &&
2326 !this.currentSession.capabilities.get("moz:windowless")
2331 await this.curBrowser.closeTab();
2332 this.currentSession.contentBrowsingContext = null;
2334 return lazy.TabManager.allBrowserUniqueIds.map(String);
2338 * Close the currently selected chrome window.
2340 * If it is the last window currently open, the chrome window will not be
2341 * closed to prevent a shutdown of the application. Instead the returned
2342 * list of chrome window handles is empty.
2344 * @returns {Array.<string>}
2345 * Unique chrome window handles of remaining chrome windows.
2347 * @throws {NoSuchWindowError}
2348 * Top-level browsing context has been discarded.
2350 GeckoDriver.prototype.closeChromeWindow = async function () {
2351 lazy.assert.desktop();
2353 this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
2358 // eslint-disable-next-line
2359 for (let _ of lazy.windowManager.windows) {
2363 // If there is only one window left, do not close unless windowless mode is
2364 // enabled. Instead return a faked empty array of window handles.
2365 // This will instruct geckodriver to terminate the application.
2366 if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
2370 await this.curBrowser.closeWindow();
2371 this.currentSession.chromeBrowsingContext = null;
2372 this.currentSession.contentBrowsingContext = null;
2374 return lazy.windowManager.chromeWindowHandles.map(String);
2377 /** Delete Marionette session. */
2378 GeckoDriver.prototype.deleteSession = function () {
2379 if (!this.currentSession) {
2383 for (let win of lazy.windowManager.windows) {
2384 this.stopObservingWindow(win);
2387 // reset to the top-most frame
2388 this.mainFrame = null;
2390 if (!this._isShuttingDown && this.promptListener) {
2391 // Do not stop the prompt listener when quitting the browser to
2392 // allow us to also accept beforeunload prompts during shutdown.
2393 this.promptListener.stopListening();
2394 this.promptListener = null;
2398 Services.obs.removeObserver(this, TOPIC_BROWSER_READY);
2400 lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`);
2403 // Always unregister actors after all other observers
2404 // and listeners have been removed.
2405 lazy.unregisterCommandsActor();
2406 // MarionetteEvents actors are only disabled to avoid IPC errors if there are
2407 // in flight events being forwarded from the content process to the parent
2409 lazy.disableEventsActor();
2411 if (lazy.RemoteAgent.webDriverBiDi) {
2412 lazy.RemoteAgent.webDriverBiDi.deleteSession();
2414 this.currentSession.destroy();
2415 this._currentSession = null;
2420 * Takes a screenshot of a web element, current frame, or viewport.
2422 * The screen capture is returned as a lossless PNG image encoded as
2425 * If called in the content context, the |id| argument is not null and
2426 * refers to a present and visible web element's ID, the capture area will
2427 * be limited to the bounding box of that element. Otherwise, the capture
2428 * area will be the bounding box of the current frame.
2430 * If called in the chrome context, the screenshot will always represent
2431 * the entire viewport.
2433 * @param {object} cmd
2434 * @param {string=} cmd.parameters.id
2435 * Optional web element reference to take a screenshot of.
2436 * If undefined, a screenshot will be taken of the document element.
2437 * @param {boolean=} cmd.parameters.full
2438 * True to take a screenshot of the entire document element. Is only
2439 * considered if <var>id</var> is not defined. Defaults to true.
2440 * @param {boolean=} cmd.parameters.hash
2441 * True if the user requests a hash of the image data. Defaults to false.
2442 * @param {boolean=} cmd.parameters.scroll
2443 * Scroll to element if |id| is provided. Defaults to true.
2446 * If <var>hash</var> is false, PNG image encoded as Base64 encoded
2447 * string. If <var>hash</var> is true, hex digest of the SHA-256
2448 * hash of the Base64 encoded string.
2450 * @throws {NoSuchElementError}
2451 * If element represented by reference <var>id</var> is unknown.
2452 * @throws {NoSuchWindowError}
2453 * Browsing context has been discarded.
2454 * @throws {StaleElementReferenceError}
2455 * If element represented by reference <var>id</var> has gone stale.
2457 GeckoDriver.prototype.takeScreenshot = async function (cmd) {
2458 lazy.assert.open(this.getBrowsingContext({ top: true }));
2459 await this._handleUserPrompts();
2461 let { id, full, hash, scroll } = cmd.parameters;
2462 let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
2464 full = typeof full == "undefined" ? true : full;
2465 scroll = typeof scroll == "undefined" ? true : scroll;
2467 let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;
2469 // Only consider full screenshot if no element has been specified
2470 full = webEl ? false : full;
2472 return this.getActor().takeScreenshot(webEl, format, full, scroll);
2476 * Get the current browser orientation.
2478 * Will return one of the valid primary orientation values
2479 * portrait-primary, landscape-primary, portrait-secondary, or
2480 * landscape-secondary.
2482 * @throws {NoSuchWindowError}
2483 * Top-level browsing context has been discarded.
2485 GeckoDriver.prototype.getScreenOrientation = function () {
2486 lazy.assert.mobile();
2487 lazy.assert.open(this.getBrowsingContext({ top: true }));
2489 const win = this.getCurrentWindow();
2491 return win.screen.orientation.type;
2495 * Set the current browser orientation.
2497 * The supplied orientation should be given as one of the valid
2498 * orientation values. If the orientation is unknown, an error will
2501 * Valid orientations are "portrait" and "landscape", which fall
2502 * back to "portrait-primary" and "landscape-primary" respectively,
2503 * and "portrait-secondary" as well as "landscape-secondary".
2505 * @throws {NoSuchWindowError}
2506 * Top-level browsing context has been discarded.
2508 GeckoDriver.prototype.setScreenOrientation = async function (cmd) {
2509 lazy.assert.mobile();
2510 lazy.assert.open(this.getBrowsingContext({ top: true }));
2516 "landscape-primary",
2517 "portrait-secondary",
2518 "landscape-secondary",
2521 let or = String(cmd.parameters.orientation);
2522 lazy.assert.string(or);
2523 let mozOr = or.toLowerCase();
2524 if (!ors.includes(mozOr)) {
2525 throw new lazy.error.InvalidArgumentError(
2526 `Unknown screen orientation: ${or}`
2530 const win = this.getCurrentWindow();
2533 await win.screen.orientation.lock(mozOr);
2535 throw new lazy.error.WebDriverError(
2536 `Unable to set screen orientation: ${or}`
2542 * Synchronously minimizes the user agent window as if the user pressed
2543 * the minimize button.
2545 * No action is taken if the window is already minimized.
2547 * Not supported on Fennec.
2549 * @returns {Object<string, number>}
2550 * Window rect and window state.
2552 * @throws {NoSuchWindowError}
2553 * Top-level browsing context has been discarded.
2554 * @throws {UnexpectedAlertOpenError}
2555 * A modal dialog is open, blocking this operation.
2556 * @throws {UnsupportedOperationError}
2557 * Not available for current application.
2559 GeckoDriver.prototype.minimizeWindow = async function () {
2560 lazy.assert.desktop();
2561 lazy.assert.open(this.getBrowsingContext({ top: true }));
2562 await this._handleUserPrompts();
2564 const win = this.getCurrentWindow();
2565 switch (lazy.WindowState.from(win.windowState)) {
2566 case lazy.WindowState.Fullscreen:
2567 await exitFullscreen(win);
2570 case lazy.WindowState.Maximized:
2571 await restoreWindow(win);
2575 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
2577 // Use a timed promise to abort if no window manager is present
2578 await new lazy.TimedPromise(
2580 cb = new lazy.DebounceCallback(resolve);
2581 win.addEventListener("sizemodechange", cb);
2584 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2586 win.removeEventListener("sizemodechange", cb);
2587 await new lazy.IdlePromise(win);
2590 return this.curBrowser.rect;
2594 * Synchronously maximizes the user agent window as if the user pressed
2595 * the maximize button.
2597 * No action is taken if the window is already maximized.
2599 * Not supported on Fennec.
2601 * @returns {Object<string, number>}
2604 * @throws {NoSuchWindowError}
2605 * Top-level browsing context has been discarded.
2606 * @throws {UnexpectedAlertOpenError}
2607 * A modal dialog is open, blocking this operation.
2608 * @throws {UnsupportedOperationError}
2609 * Not available for current application.
2611 GeckoDriver.prototype.maximizeWindow = async function () {
2612 lazy.assert.desktop();
2613 lazy.assert.open(this.getBrowsingContext({ top: true }));
2614 await this._handleUserPrompts();
2616 const win = this.getCurrentWindow();
2617 switch (lazy.WindowState.from(win.windowState)) {
2618 case lazy.WindowState.Fullscreen:
2619 await exitFullscreen(win);
2622 case lazy.WindowState.Minimized:
2623 await restoreWindow(win);
2627 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
2629 // Use a timed promise to abort if no window manager is present
2630 await new lazy.TimedPromise(
2632 cb = new lazy.DebounceCallback(resolve);
2633 win.addEventListener("sizemodechange", cb);
2636 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2638 win.removeEventListener("sizemodechange", cb);
2639 await new lazy.IdlePromise(win);
2642 return this.curBrowser.rect;
2646 * Synchronously sets the user agent window to full screen as if the user
2647 * had done "View > Enter Full Screen".
2649 * No action is taken if the window is already in full screen mode.
2651 * Not supported on Fennec.
2653 * @returns {Map.<string, number>}
2656 * @throws {NoSuchWindowError}
2657 * Top-level browsing context has been discarded.
2658 * @throws {UnexpectedAlertOpenError}
2659 * A modal dialog is open, blocking this operation.
2660 * @throws {UnsupportedOperationError}
2661 * Not available for current application.
2663 GeckoDriver.prototype.fullscreenWindow = async function () {
2664 lazy.assert.desktop();
2665 lazy.assert.open(this.getBrowsingContext({ top: true }));
2666 await this._handleUserPrompts();
2668 const win = this.getCurrentWindow();
2669 switch (lazy.WindowState.from(win.windowState)) {
2670 case lazy.WindowState.Maximized:
2671 case lazy.WindowState.Minimized:
2672 await restoreWindow(win);
2676 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
2678 // Use a timed promise to abort if no window manager is present
2679 await new lazy.TimedPromise(
2681 cb = new lazy.DebounceCallback(resolve);
2682 win.addEventListener("sizemodechange", cb);
2683 win.fullScreen = true;
2685 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2687 win.removeEventListener("sizemodechange", cb);
2689 await new lazy.IdlePromise(win);
2691 return this.curBrowser.rect;
2695 * Dismisses a currently displayed modal dialogs, or returns no such alert if
2696 * no modal is displayed.
2698 * @throws {NoSuchAlertError}
2699 * If there is no current user prompt.
2700 * @throws {NoSuchWindowError}
2701 * Top-level browsing context has been discarded.
2703 GeckoDriver.prototype.dismissDialog = async function () {
2704 lazy.assert.open(this.getBrowsingContext({ top: true }));
2705 this._checkIfAlertIsPresent();
2707 const dialogClosed = this.promptListener.dialogClosed();
2708 this.dialog.dismiss();
2711 const win = this.getCurrentWindow();
2712 await new lazy.IdlePromise(win);
2716 * Accepts a currently displayed dialog modal, or returns no such alert if
2717 * no modal is displayed.
2719 * @throws {NoSuchAlertError}
2720 * If there is no current user prompt.
2721 * @throws {NoSuchWindowError}
2722 * Top-level browsing context has been discarded.
2724 GeckoDriver.prototype.acceptDialog = async function () {
2725 lazy.assert.open(this.getBrowsingContext({ top: true }));
2726 this._checkIfAlertIsPresent();
2728 const dialogClosed = this.promptListener.dialogClosed();
2729 this.dialog.accept();
2732 const win = this.getCurrentWindow();
2733 await new lazy.IdlePromise(win);
2737 * Returns the message shown in a currently displayed modal, or returns
2738 * a no such alert error if no modal is currently displayed.
2740 * @throws {NoSuchAlertError}
2741 * If there is no current user prompt.
2742 * @throws {NoSuchWindowError}
2743 * Top-level browsing context has been discarded.
2745 GeckoDriver.prototype.getTextFromDialog = async function () {
2746 lazy.assert.open(this.getBrowsingContext({ top: true }));
2747 this._checkIfAlertIsPresent();
2748 const text = await this.dialog.getText();
2753 * Set the user prompt's value field.
2755 * Sends keys to the input field of a currently displayed modal, or
2756 * returns a no such alert error if no modal is currently displayed. If
2757 * a modal dialog is currently displayed but has no means for text input,
2758 * an element not visible error is returned.
2760 * @param {object} cmd
2761 * @param {string} cmd.parameters.text
2762 * Input to the user prompt's value field.
2764 * @throws {ElementNotInteractableError}
2765 * If the current user prompt is an alert or confirm.
2766 * @throws {NoSuchAlertError}
2767 * If there is no current user prompt.
2768 * @throws {NoSuchWindowError}
2769 * Top-level browsing context has been discarded.
2770 * @throws {UnsupportedOperationError}
2771 * If the current user prompt is something other than an alert,
2772 * confirm, or a prompt.
2774 GeckoDriver.prototype.sendKeysToDialog = async function (cmd) {
2775 lazy.assert.open(this.getBrowsingContext({ top: true }));
2776 this._checkIfAlertIsPresent();
2778 let text = lazy.assert.string(cmd.parameters.text);
2779 let promptType = this.dialog.args.promptType;
2781 switch (promptType) {
2784 throw new lazy.error.ElementNotInteractableError(
2785 `User prompt of type ${promptType} is not interactable`
2790 await this.dismissDialog();
2791 throw new lazy.error.UnsupportedOperationError(
2792 `User prompt of type ${promptType} is not supported`
2795 this.dialog.text = text;
2798 GeckoDriver.prototype._checkIfAlertIsPresent = function () {
2799 if (!this.dialog || !this.dialog.isOpen) {
2800 throw new lazy.error.NoSuchAlertError();
2804 GeckoDriver.prototype._handleUserPrompts = async function () {
2805 if (!this.dialog || !this.dialog.isOpen) {
2809 if (this.dialog.promptType == "beforeunload") {
2810 // Wait until the "beforeunload" prompt has been accepted.
2811 await this.promptListener.dialogClosed();
2815 const textContent = await this.dialog.getText();
2817 const behavior = this.currentSession.unhandledPromptBehavior;
2819 case lazy.UnhandledPromptBehavior.Accept:
2820 await this.acceptDialog();
2823 case lazy.UnhandledPromptBehavior.AcceptAndNotify:
2824 await this.acceptDialog();
2825 throw new lazy.error.UnexpectedAlertOpenError(
2826 `Accepted user prompt dialog: ${textContent}`
2829 case lazy.UnhandledPromptBehavior.Dismiss:
2830 await this.dismissDialog();
2833 case lazy.UnhandledPromptBehavior.DismissAndNotify:
2834 await this.dismissDialog();
2835 throw new lazy.error.UnexpectedAlertOpenError(
2836 `Dismissed user prompt dialog: ${textContent}`
2839 case lazy.UnhandledPromptBehavior.Ignore:
2840 throw new lazy.error.UnexpectedAlertOpenError(
2841 "Encountered unhandled user prompt dialog"
2845 throw new TypeError(`Unknown unhandledPromptBehavior "${behavior}"`);
2850 * Enables or disables accepting new socket connections.
2852 * By calling this method with `false` the server will not accept any
2853 * further connections, but existing connections will not be forcible
2854 * closed. Use `true` to re-enable accepting connections.
2856 * Please note that when closing the connection via the client you can
2857 * end-up in a non-recoverable state if it hasn't been enabled before.
2859 * This method is used for custom in application shutdowns via
2860 * marionette.quit() or marionette.restart(), like File -> Quit.
2862 * @param {object} cmd
2863 * @param {boolean} cmd.parameters.value
2864 * True if the server should accept new socket connections.
2866 GeckoDriver.prototype.acceptConnections = async function (cmd) {
2867 lazy.assert.boolean(cmd.parameters.value);
2868 await this._server.setAcceptConnections(cmd.parameters.value);
2872 * Quits the application with the provided flags.
2874 * Marionette will stop accepting new connections before ending the
2875 * current session, and finally attempting to quit the application.
2877 * Optional {@link nsIAppStartup} flags may be provided as
2878 * an array of masks, and these will be combined by ORing
2879 * them with a bitmask. The available masks are defined in
2880 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
2882 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
2883 * flag may be bit-wise combined with one of the *Quit flags to cause
2884 * the application to restart after it quits.
2886 * @param {object} cmd
2887 * @param {Array.<string>=} cmd.parameters.flags
2888 * Constant name of masks to pass to |Services.startup.quit|.
2889 * If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
2890 * @param {boolean=} cmd.parameters.safeMode
2891 * Optional flag to indicate that the application has to
2892 * be restarted in safe mode.
2894 * @returns {Object<string,boolean>}
2895 * Dictionary containing information that explains the shutdown reason.
2896 * The value for `cause` contains the shutdown kind like "shutdown" or
2897 * "restart", while `forced` will indicate if it was a normal or forced
2898 * shutdown of the application. "in_app" is always set to indicate that
2899 * it is a shutdown triggered from within the application.
2901 * @throws {InvalidArgumentError}
2902 * If <var>flags</var> contains unknown or incompatible flags,
2903 * for example multiple Quit flags.
2905 GeckoDriver.prototype.quit = async function (cmd) {
2906 const { flags = [], safeMode = false } = cmd.parameters;
2908 lazy.assert.array(flags, `Expected "flags" to be an array`);
2909 lazy.assert.boolean(safeMode, `Expected "safeMode" to be a boolean`);
2911 if (safeMode && !flags.includes("eRestart")) {
2912 throw new lazy.error.InvalidArgumentError(
2913 `"safeMode" only works with restart flag`
2917 // Register handler to run Marionette specific shutdown code.
2918 Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
2920 let quitApplicationResponse;
2922 this._isShuttingDown = true;
2923 quitApplicationResponse = await lazy.quit(
2926 this.currentSession.capabilities.get("moz:windowless")
2929 this._isShuttingDown = false;
2930 if (e instanceof TypeError) {
2931 throw new lazy.error.InvalidArgumentError(e.message);
2933 throw new lazy.error.UnsupportedOperationError(e.message);
2935 Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
2938 return quitApplicationResponse;
2941 GeckoDriver.prototype.installAddon = function (cmd) {
2942 lazy.assert.desktop();
2944 let path = cmd.parameters.path;
2945 let temp = cmd.parameters.temporary || false;
2947 typeof path == "undefined" ||
2948 typeof path != "string" ||
2949 typeof temp != "boolean"
2951 throw new lazy.error.InvalidArgumentError();
2954 return lazy.Addon.install(path, temp);
2957 GeckoDriver.prototype.uninstallAddon = function (cmd) {
2958 lazy.assert.desktop();
2960 let id = cmd.parameters.id;
2961 if (typeof id == "undefined" || typeof id != "string") {
2962 throw new lazy.error.InvalidArgumentError();
2965 return lazy.Addon.uninstall(id);
2969 * Retrieve the localized string for the specified entity id.
2972 * localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
2974 * @param {object} cmd
2975 * @param {Array.<string>} cmd.parameters.urls
2976 * Array of .dtd URLs.
2977 * @param {string} cmd.parameters.id
2978 * The ID of the entity to retrieve the localized string for.
2981 * The localized string for the requested entity.
2983 GeckoDriver.prototype.localizeEntity = function (cmd) {
2984 let { urls, id } = cmd.parameters;
2986 if (!Array.isArray(urls)) {
2987 throw new lazy.error.InvalidArgumentError(
2988 "Value of `urls` should be of type 'Array'"
2991 if (typeof id != "string") {
2992 throw new lazy.error.InvalidArgumentError(
2993 "Value of `id` should be of type 'string'"
2997 return lazy.l10n.localizeEntity(urls, id);
3001 * Retrieve the localized string for the specified property id.
3006 * ["chrome://global/locale/findbar.properties"], "FastFind");
3008 * @param {object} cmd
3009 * @param {Array.<string>} cmd.parameters.urls
3010 * Array of .properties URLs.
3011 * @param {string} cmd.parameters.id
3012 * The ID of the property to retrieve the localized string for.
3015 * The localized string for the requested property.
3017 GeckoDriver.prototype.localizeProperty = function (cmd) {
3018 let { urls, id } = cmd.parameters;
3020 if (!Array.isArray(urls)) {
3021 throw new lazy.error.InvalidArgumentError(
3022 "Value of `urls` should be of type 'Array'"
3025 if (typeof id != "string") {
3026 throw new lazy.error.InvalidArgumentError(
3027 "Value of `id` should be of type 'string'"
3031 return lazy.l10n.localizeProperty(urls, id);
3035 * Initialize the reftest mode
3037 GeckoDriver.prototype.setupReftest = async function (cmd) {
3038 if (this._reftest) {
3039 throw new lazy.error.UnsupportedOperationError(
3040 "Called reftest:setup with a reftest session already active"
3046 screenshot = "unexpected",
3049 if (!["always", "fail", "unexpected"].includes(screenshot)) {
3050 throw new lazy.error.InvalidArgumentError(
3051 "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
3055 this._reftest = new lazy.reftest.Runner(this);
3056 this._reftest.setup(urlCount, screenshot, isPrint);
3059 /** Run a reftest. */
3060 GeckoDriver.prototype.runReftest = function (cmd) {
3061 let { test, references, expected, timeout, width, height, pageRanges } =
3064 if (!this._reftest) {
3065 throw new lazy.error.UnsupportedOperationError(
3066 "Called reftest:run before reftest:start"
3070 lazy.assert.string(test);
3071 lazy.assert.string(expected);
3072 lazy.assert.array(references);
3074 return this._reftest.run(
3086 * End a reftest run.
3088 * Closes the reftest window (without changing the current window handle),
3089 * and removes cached canvases.
3091 GeckoDriver.prototype.teardownReftest = function () {
3092 if (!this._reftest) {
3093 throw new lazy.error.UnsupportedOperationError(
3094 "Called reftest:teardown before reftest:start"
3098 this._reftest.teardown();
3099 this._reftest = null;
3103 * Print page as PDF.
3105 * @param {object} cmd
3106 * @param {boolean=} cmd.parameters.background
3107 * Whether or not to print background colors and images.
3108 * Defaults to false, which prints without background graphics.
3109 * @param {number=} cmd.parameters.margin.bottom
3110 * Bottom margin in cm. Defaults to 1cm (~0.4 inches).
3111 * @param {number=} cmd.parameters.margin.left
3112 * Left margin in cm. Defaults to 1cm (~0.4 inches).
3113 * @param {number=} cmd.parameters.margin.right
3114 * Right margin in cm. Defaults to 1cm (~0.4 inches).
3115 * @param {number=} cmd.parameters.margin.top
3116 * Top margin in cm. Defaults to 1cm (~0.4 inches).
3117 * @param {('landscape'|'portrait')=} cmd.parameters.options.orientation
3118 * Paper orientation. Defaults to 'portrait'.
3119 * @param {Array.<string|number>=} cmd.parameters.pageRanges
3120 * Paper ranges to print, e.g., ['1-5', 8, '11-13'].
3121 * Defaults to the empty array, which means print all pages.
3122 * @param {number=} cmd.parameters.page.height
3123 * Paper height in cm. Defaults to US letter height (27.94cm / 11 inches)
3124 * @param {number=} cmd.parameters.page.width
3125 * Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches)
3126 * @param {number=} cmd.parameters.scale
3127 * Scale of the webpage rendering. Defaults to 1.0.
3128 * @param {boolean=} cmd.parameters.shrinkToFit
3129 * Whether or not to override page size as defined by CSS.
3130 * Defaults to true, in which case the content will be scaled
3131 * to fit the paper size.
3134 * Base64 encoded PDF representing printed document
3136 * @throws {NoSuchWindowError}
3137 * Top-level browsing context has been discarded.
3138 * @throws {UnexpectedAlertOpenError}
3139 * A modal dialog is open, blocking this operation.
3140 * @throws {UnsupportedOperationError}
3141 * Not available in chrome context.
3143 GeckoDriver.prototype.print = async function (cmd) {
3144 lazy.assert.content(this.context);
3145 lazy.assert.open(this.getBrowsingContext({ top: true }));
3146 await this._handleUserPrompts();
3148 const settings = lazy.print.addDefaultSettings(cmd.parameters);
3149 for (const prop of ["top", "bottom", "left", "right"]) {
3150 lazy.assert.positiveNumber(
3151 settings.margin[prop],
3152 lazy.pprint`margin.${prop} is not a positive number`
3155 for (const prop of ["width", "height"]) {
3156 lazy.assert.positiveNumber(
3157 settings.page[prop],
3158 lazy.pprint`page.${prop} is not a positive number`
3161 lazy.assert.positiveNumber(
3163 `scale ${settings.scale} is not a positive number`
3167 s >= lazy.print.minScaleValue &&
3168 settings.scale <= lazy.print.maxScaleValue,
3169 `scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
3171 lazy.assert.boolean(settings.shrinkToFit);
3173 orientation => lazy.print.defaults.orientationValue.includes(orientation),
3175 settings.orientation
3176 } doesn't match allowed values "${lazy.print.defaults.orientationValue.join(
3179 )(settings.orientation);
3180 lazy.assert.boolean(settings.background);
3181 lazy.assert.array(settings.pageRanges);
3183 const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext;
3184 const printSettings = await lazy.print.getPrintSettings(settings);
3185 const binaryString = await lazy.print.printToBinaryString(
3190 return btoa(binaryString);
3193 GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
3198 hasUserVerification,
3205 "addVirtualAuthenticator: protocol must be a string"
3209 "addVirtualAuthenticator: transport must be a string"
3211 lazy.assert.boolean(
3213 "addVirtualAuthenticator: hasResidentKey must be a boolean"
3215 lazy.assert.boolean(
3216 hasUserVerification,
3217 "addVirtualAuthenticator: hasUserVerification must be a boolean"
3219 lazy.assert.boolean(
3221 "addVirtualAuthenticator: isUserConsenting must be a boolean"
3223 lazy.assert.boolean(
3225 "addVirtualAuthenticator: isUserVerified must be a boolean"
3228 return lazy.webauthn.addVirtualAuthenticator(
3232 hasUserVerification,
3238 GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
3239 const { authenticatorId } = cmd.parameters;
3241 lazy.assert.positiveInteger(
3243 "removeVirtualAuthenticator: authenticatorId must be a positiveInteger"
3246 lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
3249 GeckoDriver.prototype.addCredential = function (cmd) {
3253 isResidentCredential,
3260 lazy.assert.positiveInteger(
3262 "addCredential: authenticatorId must be a positiveInteger"
3266 "addCredential: credentialId must be a string"
3268 lazy.assert.boolean(
3269 isResidentCredential,
3270 "addCredential: isResidentCredential must be a boolean"
3272 lazy.assert.string(rpId, "addCredential: rpId must be a string");
3273 lazy.assert.string(privateKey, "addCredential: privateKey must be a string");
3277 "addCredential: userHandle must be a string if present"
3280 lazy.assert.number(signCount, "addCredential: signCount must be a number");
3282 lazy.webauthn.addCredential(
3285 isResidentCredential,
3293 GeckoDriver.prototype.getCredentials = function (cmd) {
3294 const { authenticatorId } = cmd.parameters;
3296 lazy.assert.positiveInteger(
3298 "getCredentials: authenticatorId must be a positiveInteger"
3301 return lazy.webauthn.getCredentials(authenticatorId);
3304 GeckoDriver.prototype.removeCredential = function (cmd) {
3305 const { authenticatorId, credentialId } = cmd.parameters;
3307 lazy.assert.positiveInteger(
3309 "removeCredential: authenticatorId must be a positiveInteger"
3313 "removeCredential: credentialId must be a string"
3316 lazy.webauthn.removeCredential(authenticatorId, credentialId);
3319 GeckoDriver.prototype.removeAllCredentials = function (cmd) {
3320 const { authenticatorId } = cmd.parameters;
3322 lazy.assert.positiveInteger(
3324 "removeAllCredentials: authenticatorId must be a positiveInteger"
3327 lazy.webauthn.removeAllCredentials(authenticatorId);
3330 GeckoDriver.prototype.setUserVerified = function (cmd) {
3331 const { authenticatorId, isUserVerified } = cmd.parameters;
3333 lazy.assert.positiveInteger(
3335 "setUserVerified: authenticatorId must be a positiveInteger"
3337 lazy.assert.boolean(
3339 "setUserVerified: isUserVerified must be a boolean"
3342 lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
3345 GeckoDriver.prototype.setPermission = async function (cmd) {
3346 const { descriptor, state, oneRealm = false } = cmd.parameters;
3347 const browsingContext = lazy.assert.open(this.getBrowsingContext());
3349 // XXX: We currently depend on camera/microphone tests throwing UnsupportedOperationError,
3350 // the fix is ongoing in bug 1609427.
3351 if (["camera", "microphone"].includes(descriptor.name)) {
3352 throw new lazy.error.UnsupportedOperationError(
3353 "setPermission: camera and microphone permissions are currently unsupported"
3357 // XXX: Allowing this permission causes timing related Android crash, see also bug 1878741
3358 if (descriptor.name === "notifications") {
3359 if (Services.prefs.getBoolPref("notification.prompt.testing", false)) {
3360 // Okay, do nothing. The notifications module will work without permission.
3363 throw new lazy.error.UnsupportedOperationError(
3364 "setPermission: expected notification.prompt.testing to be set"
3371 await this.curBrowser.window.navigator.permissions.parseSetParameters({
3376 throw new lazy.error.InvalidArgumentError(`setPermission: ${err.message}`);
3379 lazy.assert.boolean(oneRealm);
3381 lazy.permissions.set(params.type, params.state, oneRealm, browsingContext);
3385 * Determines the Accessibility label for this element.
3387 * @param {object} cmd
3388 * @param {string} cmd.parameters.id
3389 * Web element reference ID to the element for which the accessibility label
3393 * The Accessibility label for this element
3395 GeckoDriver.prototype.getComputedLabel = async function (cmd) {
3396 lazy.assert.open(this.getBrowsingContext());
3397 await this._handleUserPrompts();
3399 let id = lazy.assert.string(cmd.parameters.id);
3400 let webEl = lazy.WebElement.fromUUID(id).toJSON();
3402 return this.getActor().getComputedLabel(webEl);
3406 * Determines the Accessibility role for this element.
3408 * @param {object} cmd
3409 * @param {string} cmd.parameters.id
3410 * Web element reference ID to the element for which the accessibility role
3414 * The Accessibility role for this element
3416 GeckoDriver.prototype.getComputedRole = async function (cmd) {
3417 lazy.assert.open(this.getBrowsingContext());
3418 await this._handleUserPrompts();
3420 let id = lazy.assert.string(cmd.parameters.id);
3421 let webEl = lazy.WebElement.fromUUID(id).toJSON();
3422 return this.getActor().getComputedRole(webEl);
3425 GeckoDriver.prototype.commands = {
3426 // Marionette service
3427 "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
3428 "Marionette:GetContext": GeckoDriver.prototype.getContext,
3429 "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
3430 "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
3431 "Marionette:Quit": GeckoDriver.prototype.quit,
3432 "Marionette:SetContext": GeckoDriver.prototype.setContext,
3433 "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
3436 "Addon:Install": GeckoDriver.prototype.installAddon,
3437 "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
3440 "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
3441 "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
3444 "reftest:setup": GeckoDriver.prototype.setupReftest,
3445 "reftest:run": GeckoDriver.prototype.runReftest,
3446 "reftest:teardown": GeckoDriver.prototype.teardownReftest,
3448 // WebDriver service
3449 "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
3450 // deprecated, no longer used since the geckodriver 0.30.0 release
3451 "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
3452 "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
3453 "WebDriver:Back": GeckoDriver.prototype.goBack,
3454 "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
3455 "WebDriver:CloseWindow": GeckoDriver.prototype.close,
3456 "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
3457 "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
3458 "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
3459 "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
3460 "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
3461 "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
3462 "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
3463 "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
3464 "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
3465 "WebDriver:FindElement": GeckoDriver.prototype.findElement,
3466 "WebDriver:FindElementFromShadowRoot":
3467 GeckoDriver.prototype.findElementFromShadowRoot,
3468 "WebDriver:FindElements": GeckoDriver.prototype.findElements,
3469 "WebDriver:FindElementsFromShadowRoot":
3470 GeckoDriver.prototype.findElementsFromShadowRoot,
3471 "WebDriver:Forward": GeckoDriver.prototype.goForward,
3472 "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
3473 "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
3474 "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
3475 "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
3476 "WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel,
3477 "WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole,
3478 "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
3479 "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
3480 "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
3481 "WebDriver:GetElementCSSValue":
3482 GeckoDriver.prototype.getElementValueOfCssProperty,
3483 "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
3484 "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
3485 "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
3486 "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
3487 "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
3488 "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
3489 "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
3490 "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
3491 "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
3492 "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
3493 "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
3494 "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
3495 "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
3496 "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
3497 "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
3498 "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
3499 "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
3500 "WebDriver:NewSession": GeckoDriver.prototype.newSession,
3501 "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
3502 "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
3503 "WebDriver:Print": GeckoDriver.prototype.print,
3504 "WebDriver:Refresh": GeckoDriver.prototype.refresh,
3505 "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
3506 "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
3507 "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
3508 "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
3509 "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
3510 "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
3511 "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
3512 "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
3513 "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
3516 "WebAuthn:AddVirtualAuthenticator":
3517 GeckoDriver.prototype.addVirtualAuthenticator,
3518 "WebAuthn:RemoveVirtualAuthenticator":
3519 GeckoDriver.prototype.removeVirtualAuthenticator,
3520 "WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
3521 "WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
3522 "WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
3523 "WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
3524 "WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
3527 async function exitFullscreen(win) {
3529 // Use a timed promise to abort if no window manager is present
3530 await new lazy.TimedPromise(
3532 cb = new lazy.DebounceCallback(resolve);
3533 win.addEventListener("sizemodechange", cb);
3534 win.fullScreen = false;
3536 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3538 win.removeEventListener("sizemodechange", cb);
3539 await new lazy.IdlePromise(win);
3542 async function restoreWindow(win) {
3544 if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
3547 // Use a timed promise to abort if no window manager is present
3548 await new lazy.TimedPromise(
3550 cb = new lazy.DebounceCallback(resolve);
3551 win.addEventListener("sizemodechange", cb);
3554 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3556 win.removeEventListener("sizemodechange", cb);
3557 await new lazy.IdlePromise(win);