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/shared/Permissions.sys.mjs",
33 pprint: "chrome://remote/content/shared/Format.sys.mjs",
34 print: "chrome://remote/content/shared/PDF.sys.mjs",
35 PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
37 "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
39 "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
41 "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
42 quit: "chrome://remote/content/shared/Browser.sys.mjs",
43 reftest: "chrome://remote/content/marionette/reftest.sys.mjs",
44 registerCommandsActor:
45 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
46 RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
47 ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
48 TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
49 TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
50 Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
51 unregisterCommandsActor:
52 "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
53 waitForInitialNavigationCompleted:
54 "chrome://remote/content/shared/Navigate.sys.mjs",
55 webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs",
56 WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
57 WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
58 windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
59 WindowState: "chrome://remote/content/marionette/browser.sys.mjs",
62 ChromeUtils.defineLazyGetter(lazy, "logger", () =>
63 lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
66 ChromeUtils.defineLazyGetter(lazy, "prefAsyncEventsEnabled", () =>
67 Services.prefs.getBoolPref("remote.events.async.enabled", false)
70 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
72 ChromeUtils.defineLazyGetter(
74 "supportedStrategies",
77 lazy.dom.Strategy.ClassName,
78 lazy.dom.Strategy.Selector,
80 lazy.dom.Strategy.Name,
81 lazy.dom.Strategy.LinkText,
82 lazy.dom.Strategy.PartialLinkText,
83 lazy.dom.Strategy.TagName,
84 lazy.dom.Strategy.XPath,
88 // Timeout used to abort fullscreen, maximize, and minimize
89 // commands if no window manager is present.
90 const TIMEOUT_NO_WINDOW_MANAGER = 5000;
92 // Observer topic to wait for until the browser window is ready.
93 const TOPIC_BROWSER_READY = "browser-delayed-startup-finished";
94 // Observer topic to perform clean up when application quit is requested.
95 const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested";
98 * The Marionette WebDriver services provides a standard conforming
99 * implementation of the W3C WebDriver specification.
101 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
106 * Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
107 * in chrome space and mediates calls to the current browsing context's actor.
109 * Throughout this prototype, functions with the argument <var>cmd</var>'s
110 * documentation refers to the contents of the <code>cmd.parameter</code>
115 * @param {MarionetteServer} server
116 * The instance of Marionette server.
118 export function GeckoDriver(server) {
119 this._server = server;
122 this._currentSession = null;
124 // Flag to indicate a WebDriver HTTP session
125 this._sessionConfigFlags = new Set([lazy.WebDriverSession.SESSION_FLAG_HTTP]);
127 // Flag to indicate that the application is shutting down
128 this._isShuttingDown = false;
132 // points to current browser
133 this.curBrowser = null;
134 // top-most chrome window
135 this.mainFrame = null;
137 // Use content context by default
138 this.context = lazy.Context.Content;
140 // used for modal dialogs
142 this.promptListener = null;
146 * The current context decides if commands are executed in chrome- or
149 Object.defineProperty(GeckoDriver.prototype, "context", {
151 return this._context;
155 this._context = lazy.Context.fromString(context);
160 * The current WebDriver Session.
162 Object.defineProperty(GeckoDriver.prototype, "currentSession", {
164 if (lazy.RemoteAgent.webDriverBiDi) {
165 return lazy.RemoteAgent.webDriverBiDi.session;
168 return this._currentSession;
173 * Returns the current URL of the ChromeWindow or content browser,
174 * depending on context.
177 * Read-only property containing the currently loaded URL.
179 Object.defineProperty(GeckoDriver.prototype, "currentURL", {
181 const browsingContext = this.getBrowsingContext({ top: true });
182 return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
187 * Returns the title of the ChromeWindow or content browser,
188 * depending on context.
191 * Read-only property containing the title of the loaded URL.
193 Object.defineProperty(GeckoDriver.prototype, "title", {
195 const browsingContext = this.getBrowsingContext({ top: true });
196 return browsingContext.currentWindowGlobal.documentTitle;
200 Object.defineProperty(GeckoDriver.prototype, "windowType", {
202 return this.curBrowser.window.document.documentElement.getAttribute(
208 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
210 "nsISupportsWeakReference",
214 * Callback used to observe the closing of modal dialogs
215 * during the session's lifetime.
217 GeckoDriver.prototype.handleClosedModalDialog = function () {
222 * Callback used to observe the creation of new modal dialogs
223 * during the session's lifetime.
225 GeckoDriver.prototype.handleOpenModalDialog = function (eventName, data) {
226 this.dialog = data.prompt;
228 if (this.dialog.promptType === "beforeunload" && !this.currentSession?.bidi) {
229 // Only implicitly accept the prompt when its not a BiDi session.
230 lazy.logger.trace(`Implicitly accepted "beforeunload" prompt`);
231 this.dialog.accept();
235 if (!this._isShuttingDown) {
236 this.getActor().notifyDialogOpened(this.dialog);
241 * Get the current URL.
243 * @param {object} options
244 * @param {boolean=} options.top
245 * If set to true return the window's top-level URL,
246 * otherwise the one from the currently selected frame. Defaults to true.
247 * @see https://w3c.github.io/webdriver/#get-current-url
249 GeckoDriver.prototype._getCurrentURL = function (options = {}) {
250 if (options.top === undefined) {
253 const browsingContext = this.getBrowsingContext(options);
254 return new URL(browsingContext.currentURI.spec);
258 * Get the current "MarionetteCommands" parent actor.
260 * @param {object} options
261 * @param {boolean=} options.top
262 * If set to true use the window's top-level browsing context for the actor,
263 * otherwise the one from the currently selected frame. Defaults to false.
265 * @returns {MarionetteCommandsParent}
268 GeckoDriver.prototype.getActor = function (options = {}) {
269 return lazy.getMarionetteCommandsActorProxy(() =>
270 this.getBrowsingContext(options)
275 * Get the selected BrowsingContext for the current context.
277 * @param {object} options
278 * @param {Context=} options.context
279 * Context (content or chrome) for which to retrieve the browsing context.
280 * Defaults to the current one.
281 * @param {boolean=} options.parent
282 * If set to true return the window's parent browsing context,
283 * otherwise the one from the currently selected frame. Defaults to false.
284 * @param {boolean=} options.top
285 * If set to true return the window's top-level browsing context,
286 * otherwise the one from the currently selected frame. Defaults to false.
288 * @returns {BrowsingContext}
289 * The browsing context, or `null` if none is available
291 GeckoDriver.prototype.getBrowsingContext = function (options = {}) {
292 const { context = this.context, parent = false, top = false } = options;
294 let browsingContext = null;
295 if (context === lazy.Context.Chrome) {
296 browsingContext = this.currentSession?.chromeBrowsingContext;
298 browsingContext = this.currentSession?.contentBrowsingContext;
301 if (browsingContext && parent) {
302 browsingContext = browsingContext.parent;
305 if (browsingContext && top) {
306 browsingContext = browsingContext.top;
309 return browsingContext;
313 * Get the currently selected window.
315 * It will return the outer {@link ChromeWindow} previously selected by
316 * window handle through {@link #switchToWindow}, or the first window that
319 * @param {object} options
320 * @param {Context=} options.context
321 * Optional name of the context to use for finding the window.
322 * It will be required if a command always needs a specific context,
323 * whether which context is currently set. Defaults to the current
326 * @returns {ChromeWindow}
327 * The current top-level browsing context.
329 GeckoDriver.prototype.getCurrentWindow = function (options = {}) {
330 const { context = this.context } = options;
334 case lazy.Context.Chrome:
335 if (this.curBrowser) {
336 win = this.curBrowser.window;
340 case lazy.Context.Content:
341 if (this.curBrowser && this.curBrowser.contentBrowser) {
342 win = this.curBrowser.window;
350 GeckoDriver.prototype.isReftestBrowser = function (element) {
354 element.tagName === "xul:browser" &&
355 element.parentElement &&
356 element.parentElement.id === "reftest"
361 * Create a new browsing context for window and add to known browsers.
363 * @param {ChromeWindow} win
364 * Window for which we will create a browsing context.
367 * Returns the unique server-assigned ID of the window.
369 GeckoDriver.prototype.addBrowser = function (win) {
370 let context = new lazy.browser.Context(win, this);
371 let winId = lazy.windowManager.getIdForWindow(win);
373 this.browsers[winId] = context;
374 this.curBrowser = this.browsers[winId];
378 * Handles registration of new content browsers. Depending on
379 * their type they are either accepted or ignored.
381 * @param {XULBrowser} browserElement
383 GeckoDriver.prototype.registerBrowser = function (browserElement) {
384 // We want to ignore frames that are XUL browsers that aren't in the "main"
385 // tabbrowser, but accept things on Fennec (which doesn't have a
386 // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
387 // as well as XUL frames. Ideally this should be cleaned up and we should
388 // keep track of browsers a different way.
390 !lazy.AppInfo.isFirefox ||
391 browserElement.namespaceURI != XUL_NS ||
392 browserElement.nodeName != "browser" ||
393 browserElement.getTabBrowser()
395 this.curBrowser.register(browserElement);
400 * Create a new WebDriver session.
402 * @param {object} cmd
403 * @param {Record<string, *>=} cmd.parameters
404 * JSON Object containing any of the recognised capabilities as listed
405 * on the `WebDriverSession` class.
408 * Session ID and capabilities offered by the WebDriver service.
410 * @throws {SessionNotCreatedError}
411 * If, for whatever reason, a session could not be created.
413 GeckoDriver.prototype.newSession = async function (cmd) {
414 if (this.currentSession) {
415 throw new lazy.error.SessionNotCreatedError(
416 "Maximum number of active sessions"
420 const { parameters: capabilities } = cmd;
423 if (lazy.RemoteAgent.webDriverBiDi) {
424 // If the WebDriver BiDi protocol is active always use the Remote Agent
425 // to handle the WebDriver session.
426 await lazy.RemoteAgent.webDriverBiDi.createSession(
428 this._sessionConfigFlags
431 // If it's not the case then Marionette itself needs to handle it, and
432 // has to nullify the "webSocketUrl" capability.
433 this._currentSession = new lazy.WebDriverSession(
435 this._sessionConfigFlags
437 this._currentSession.capabilities.delete("webSocketUrl");
440 // Don't wait for the initial window when Marionette is in windowless mode
441 if (!this.currentSession.capabilities.get("moz:windowless")) {
442 // Creating a WebDriver session too early can cause issues with
443 // clients in not being able to find any available window handle.
444 // Also when closing the application while it's still starting up can
445 // cause shutdown hangs. As such Marionette will return a new session
446 // once the initial application window has finished initializing.
447 lazy.logger.debug(`Waiting for initial application window`);
448 await lazy.Marionette.browserStartupFinished;
451 await lazy.windowManager.waitForInitialApplicationWindowLoaded();
453 if (lazy.MarionettePrefs.clickToStart) {
454 Services.prompt.alert(
457 "Click to start execution of marionette tests"
461 this.addBrowser(appWin);
462 this.mainFrame = appWin;
464 // Setup observer for modal dialogs
465 this.promptListener = new lazy.PromptListener(() => this.curBrowser);
466 this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
467 this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
468 this.promptListener.startListening();
470 for (let win of lazy.windowManager.windows) {
471 this.registerWindow(win, { registerBrowsers: true });
474 if (this.mainFrame) {
475 this.currentSession.chromeBrowsingContext =
476 this.mainFrame.browsingContext;
477 this.mainFrame.focus();
480 if (this.curBrowser.tab) {
481 const browsingContext = this.curBrowser.contentBrowser.browsingContext;
482 this.currentSession.contentBrowsingContext = browsingContext;
484 // Bug 1838381 - Only use a longer unload timeout for desktop, because
485 // on Android only the initial document is loaded, and loading a
486 // specific page during startup doesn't succeed.
488 if (!lazy.AppInfo.isAndroid) {
489 options.unloadTimeout = 5000;
492 await lazy.waitForInitialNavigationCompleted(
493 browsingContext.webProgress,
497 this.curBrowser.contentBrowser.focus();
500 // Check if there is already an open dialog for the selected browser window.
501 this.dialog = lazy.modal.findPrompt(this.curBrowser);
504 lazy.registerCommandsActor(this.currentSession.id);
505 lazy.enableEventsActor();
507 Services.obs.addObserver(this, TOPIC_BROWSER_READY);
509 throw new lazy.error.SessionNotCreatedError(e);
513 sessionId: this.currentSession.id,
514 capabilities: this.currentSession.capabilities,
519 * Start observing the specified window.
521 * @param {ChromeWindow} win
522 * Chrome window to register event listeners for.
523 * @param {object=} options
524 * @param {boolean=} options.registerBrowsers
525 * If true, register all content browsers of found tabs. Defaults to false.
527 GeckoDriver.prototype.registerWindow = function (win, options = {}) {
528 const { registerBrowsers = false } = options;
529 const tabBrowser = lazy.TabManager.getTabBrowser(win);
531 if (registerBrowsers && tabBrowser) {
532 for (const tab of tabBrowser.tabs) {
533 const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
534 this.registerBrowser(contentBrowser);
538 // Listen for any kind of top-level process switch
539 tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
543 * Stop observing the specified window.
545 * @param {ChromeWindow} win
546 * Chrome window to unregister event listeners for.
548 GeckoDriver.prototype.stopObservingWindow = function (win) {
549 const tabBrowser = lazy.TabManager.getTabBrowser(win);
551 tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
554 GeckoDriver.prototype.handleEvent = function ({ target, type }) {
556 case "XULFrameLoaderCreated":
557 if (target === this.curBrowser.contentBrowser) {
559 "Remoteness change detected. Set new top-level browsing context " +
560 `to ${target.browsingContext.id}`
563 this.currentSession.contentBrowsingContext = target.browsingContext;
569 GeckoDriver.prototype.observe = async function (subject, topic) {
571 case TOPIC_BROWSER_READY:
572 this.registerWindow(subject);
575 case TOPIC_QUIT_APPLICATION_REQUESTED:
576 // Run Marionette specific cleanup steps before allowing
577 // the application to shutdown
578 await this._server.setAcceptConnections(false);
579 this.deleteSession();
585 * Send the current session's capabilities to the client.
587 * Capabilities informs the client of which WebDriver features are
588 * supported by Firefox and Marionette. They are immutable for the
589 * length of the session.
591 * The return value is an immutable map of string keys
592 * ("capabilities") to values, which may be of types boolean,
593 * numerical or string.
595 GeckoDriver.prototype.getSessionCapabilities = function () {
596 return { capabilities: this.currentSession.capabilities };
600 * Sets the context of the subsequent commands.
602 * All subsequent requests to commands that in some way involve
603 * interaction with a browsing context will target the chosen browsing
606 * @param {object} cmd
607 * @param {string} cmd.parameters.value
608 * Name of the context to be switched to. Must be one of "chrome" or
611 * @throws {InvalidArgumentError}
612 * If <var>value</var> is not a string.
613 * @throws {WebDriverError}
614 * If <var>value</var> is not a valid browsing context.
616 GeckoDriver.prototype.setContext = function (cmd) {
617 let value = lazy.assert.string(
618 cmd.parameters.value,
619 lazy.pprint`Expected "value" to be a string, got ${cmd.parameters.value}`
622 this.context = value;
626 * Gets the context type that is Marionette's current target for
627 * browsing context scoped commands.
629 * You may choose a context through the {@link #setContext} command.
631 * The default browsing context is {@link Context.Content}.
636 GeckoDriver.prototype.getContext = function () {
641 * Executes a JavaScript function in the context of the current browsing
642 * context, if in content space, or in chrome space otherwise, and returns
643 * the return value of the function.
645 * It is important to note that if the <var>sandboxName</var> parameter
646 * is left undefined, the script will be evaluated in a mutable sandbox,
647 * causing any change it makes on the global state of the document to have
648 * lasting side-effects.
650 * @param {object} cmd
651 * @param {string} cmd.parameters.script
652 * Script to evaluate as a function body.
653 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
654 * Arguments exposed to the script in <code>arguments</code>.
655 * The array items must be serialisable to the WebDriver protocol.
656 * @param {string=} cmd.parameters.sandbox
657 * Name of the sandbox to evaluate the script in. The sandbox is
658 * cached for later re-use on the same Window object if
659 * <var>newSandbox</var> is false. If he parameter is undefined,
660 * the script is evaluated in a mutable sandbox. If the parameter
661 * is "system", it will be evaluted in a sandbox with elevated system
662 * privileges, equivalent to chrome space.
663 * @param {boolean=} cmd.parameters.newSandbox
664 * Forces the script to be evaluated in a fresh sandbox. Note that if
665 * it is undefined, the script will normally be evaluted in a fresh
667 * @param {string=} cmd.parameters.filename
668 * Filename of the client's program where this script is evaluated.
669 * @param {number=} cmd.parameters.line
670 * Line in the client's program where this script is evaluated.
672 * @returns {(string|boolean|number|object|WebReference)}
673 * Return value from the script, or null which signifies either the
674 * JavaScript notion of null or undefined.
676 * @throws {JavaScriptError}
677 * If an {@link Error} was thrown whilst evaluating the script.
678 * @throws {NoSuchElementError}
679 * If an element that was passed as part of <var>args</var> is unknown.
680 * @throws {NoSuchWindowError}
681 * Browsing context has been discarded.
682 * @throws {ScriptTimeoutError}
683 * If the script was interrupted due to reaching the session's
685 * @throws {StaleElementReferenceError}
686 * If an element that was passed as part of <var>args</var> or that is
687 * returned as result has gone stale.
689 GeckoDriver.prototype.executeScript = function (cmd) {
690 let { script, args } = cmd.parameters;
692 script: cmd.parameters.script,
693 args: cmd.parameters.args,
694 sandboxName: cmd.parameters.sandbox,
695 newSandbox: cmd.parameters.newSandbox,
696 file: cmd.parameters.filename,
697 line: cmd.parameters.line,
700 return this.execute_(script, args, opts);
704 * Executes a JavaScript function in the context of the current browsing
705 * context, if in content space, or in chrome space otherwise, and returns
706 * the object passed to the callback.
708 * The callback is always the last argument to the <var>arguments</var>
709 * list passed to the function scope of the script. It can be retrieved
713 * let callback = arguments[arguments.length - 1];
715 * // "foo" is returned
718 * It is important to note that if the <var>sandboxName</var> parameter
719 * is left undefined, the script will be evaluated in a mutable sandbox,
720 * causing any change it makes on the global state of the document to have
721 * lasting side-effects.
723 * @param {object} cmd
724 * @param {string} cmd.parameters.script
725 * Script to evaluate as a function body.
726 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
727 * Arguments exposed to the script in <code>arguments</code>.
728 * The array items must be serialisable to the WebDriver protocol.
729 * @param {string=} cmd.parameters.sandbox
730 * Name of the sandbox to evaluate the script in. The sandbox is
731 * cached for later re-use on the same Window object if
732 * <var>newSandbox</var> is false. If the parameter is undefined,
733 * the script is evaluated in a mutable sandbox. If the parameter
734 * is "system", it will be evaluted in a sandbox with elevated system
735 * privileges, equivalent to chrome space.
736 * @param {boolean=} cmd.parameters.newSandbox
737 * Forces the script to be evaluated in a fresh sandbox. Note that if
738 * it is undefined, the script will normally be evaluted in a fresh
740 * @param {string=} cmd.parameters.filename
741 * Filename of the client's program where this script is evaluated.
742 * @param {number=} cmd.parameters.line
743 * Line in the client's program where this script is evaluated.
745 * @returns {(string|boolean|number|object|WebReference)}
746 * Return value from the script, or null which signifies either the
747 * JavaScript notion of null or undefined.
749 * @throws {JavaScriptError}
750 * If an Error was thrown whilst evaluating the script.
751 * @throws {NoSuchElementError}
752 * If an element that was passed as part of <var>args</var> is unknown.
753 * @throws {NoSuchWindowError}
754 * Browsing context has been discarded.
755 * @throws {ScriptTimeoutError}
756 * If the script was interrupted due to reaching the session's
758 * @throws {StaleElementReferenceError}
759 * If an element that was passed as part of <var>args</var> or that is
760 * returned as result has gone stale.
762 GeckoDriver.prototype.executeAsyncScript = function (cmd) {
763 let { script, args } = cmd.parameters;
765 script: cmd.parameters.script,
766 args: cmd.parameters.args,
767 sandboxName: cmd.parameters.sandbox,
768 newSandbox: cmd.parameters.newSandbox,
769 file: cmd.parameters.filename,
770 line: cmd.parameters.line,
774 return this.execute_(script, args, opts);
777 GeckoDriver.prototype.execute_ = async function (
788 lazy.assert.open(this.getBrowsingContext());
789 await this._handleUserPrompts();
793 lazy.pprint`Expected "script" to be a string, got ${script}`
797 lazy.pprint`Expected "args" to be an array, got ${args}`
799 if (sandboxName !== null) {
802 lazy.pprint`Expected "sandboxName" to be a string, got ${sandboxName}`
807 lazy.pprint`Expected "newSandbox" to be boolean, got ${newSandbox}`
811 lazy.pprint`Expected "file" to be a string, got ${file}`
815 lazy.pprint`Expected "line" to be a number, got ${line}`
819 timeout: this.currentSession.timeouts.script,
827 return this.getActor().executeScript(script, args, opts);
831 * Navigate to given URL.
833 * Navigates the current browsing context to the given URL and waits for
834 * the document to load or the session's page timeout duration to elapse
837 * The command will return with a failure if there is an error loading
838 * the document or the URL is blocked. This can occur if it fails to
839 * reach host, the URL is malformed, or if there is a certificate issue
840 * to name some examples.
842 * The document is considered successfully loaded when the
843 * DOMContentLoaded event on the frame element associated with the
844 * current window triggers and document.readyState is "complete".
846 * In chrome context it will change the current window's location to
847 * the supplied URL and wait until document.readyState equals "complete"
848 * or the page timeout duration has elapsed.
850 * @param {object} cmd
851 * @param {string} cmd.parameters.url
852 * URL to navigate to.
854 * @throws {NoSuchWindowError}
855 * Top-level browsing context has been discarded.
856 * @throws {UnexpectedAlertOpenError}
857 * A modal dialog is open, blocking this operation.
858 * @throws {UnsupportedOperationError}
859 * Not available in current context.
861 GeckoDriver.prototype.navigateTo = async function (cmd) {
862 lazy.assert.content(this.context);
863 const browsingContext = lazy.assert.open(
864 this.getBrowsingContext({ top: true })
866 await this._handleUserPrompts();
870 validURL = new URL(cmd.parameters.url);
872 throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
875 // Switch to the top-level browsing context before navigating
876 this.currentSession.contentBrowsingContext = browsingContext;
878 const loadEventExpected = lazy.navigate.isLoadEventExpected(
879 this._getCurrentURL(),
885 await lazy.navigate.waitForNavigationCompleted(
888 lazy.navigate.navigateTo(browsingContext, validURL);
890 { loadEventExpected }
893 this.curBrowser.contentBrowser.focus();
897 * Get a string representing the current URL.
899 * On Desktop this returns a string representation of the URL of the
900 * current top level browsing context. This is equivalent to
901 * document.location.href.
903 * When in the context of the chrome, this returns the canonical URL
904 * of the current resource.
906 * @throws {NoSuchWindowError}
907 * Top-level browsing context has been discarded.
908 * @throws {UnexpectedAlertOpenError}
909 * A modal dialog is open, blocking this operation.
911 GeckoDriver.prototype.getCurrentUrl = async function () {
912 lazy.assert.open(this.getBrowsingContext({ top: true }));
913 await this._handleUserPrompts();
915 return this._getCurrentURL().href;
919 * Gets the current title of the window.
922 * Document title of the top-level browsing context.
924 * @throws {NoSuchWindowError}
925 * Top-level browsing context has been discarded.
926 * @throws {UnexpectedAlertOpenError}
927 * A modal dialog is open, blocking this operation.
929 GeckoDriver.prototype.getTitle = async function () {
930 lazy.assert.open(this.getBrowsingContext({ top: true }));
931 await this._handleUserPrompts();
937 * Gets the current type of the window.
942 * @throws {NoSuchWindowError}
943 * Top-level browsing context has been discarded.
945 GeckoDriver.prototype.getWindowType = function () {
946 lazy.assert.open(this.getBrowsingContext({ top: true }));
948 return this.windowType;
952 * Gets the page source of the content document.
955 * String serialisation of the DOM of the current browsing context's
958 * @throws {NoSuchWindowError}
959 * Browsing context has been discarded.
960 * @throws {UnexpectedAlertOpenError}
961 * A modal dialog is open, blocking this operation.
963 GeckoDriver.prototype.getPageSource = async function () {
964 lazy.assert.open(this.getBrowsingContext());
965 await this._handleUserPrompts();
967 return this.getActor().getPageSource();
971 * Cause the browser to traverse one step backward in the joint history
972 * of the current browsing context.
974 * @throws {NoSuchWindowError}
975 * Top-level browsing context has been discarded.
976 * @throws {UnexpectedAlertOpenError}
977 * A modal dialog is open, blocking this operation.
978 * @throws {UnsupportedOperationError}
979 * Not available in current context.
981 GeckoDriver.prototype.goBack = async function () {
982 lazy.assert.content(this.context);
983 const browsingContext = lazy.assert.open(
984 this.getBrowsingContext({ top: true })
986 await this._handleUserPrompts();
988 // If there is no history, just return
989 if (!browsingContext.embedderElement?.canGoBack) {
993 await lazy.navigate.waitForNavigationCompleted(this, () => {
994 browsingContext.goBack();
999 * Cause the browser to traverse one step forward in the joint history
1000 * of the current browsing context.
1002 * @throws {NoSuchWindowError}
1003 * Top-level browsing context has been discarded.
1004 * @throws {UnexpectedAlertOpenError}
1005 * A modal dialog is open, blocking this operation.
1006 * @throws {UnsupportedOperationError}
1007 * Not available in current context.
1009 GeckoDriver.prototype.goForward = async function () {
1010 lazy.assert.content(this.context);
1011 const browsingContext = lazy.assert.open(
1012 this.getBrowsingContext({ top: true })
1014 await this._handleUserPrompts();
1016 // If there is no history, just return
1017 if (!browsingContext.embedderElement?.canGoForward) {
1021 await lazy.navigate.waitForNavigationCompleted(this, () => {
1022 browsingContext.goForward();
1027 * Causes the browser to reload the page in current top-level browsing
1030 * @throws {NoSuchWindowError}
1031 * Top-level browsing context has been discarded.
1032 * @throws {UnexpectedAlertOpenError}
1033 * A modal dialog is open, blocking this operation.
1034 * @throws {UnsupportedOperationError}
1035 * Not available in current context.
1037 GeckoDriver.prototype.refresh = async function () {
1038 lazy.assert.content(this.context);
1039 const browsingContext = lazy.assert.open(
1040 this.getBrowsingContext({ top: true })
1042 await this._handleUserPrompts();
1044 // Switch to the top-level browsing context before navigating
1045 this.currentSession.contentBrowsingContext = browsingContext;
1047 await lazy.navigate.waitForNavigationCompleted(this, () => {
1048 lazy.navigate.refresh(browsingContext);
1053 * Get the current window's handle. On desktop this typically corresponds
1054 * to the currently selected tab.
1056 * For chrome scope it returns the window identifier for the current chrome
1057 * window for tests interested in managing the chrome window and tab separately.
1059 * Return an opaque server-assigned identifier to this window that
1060 * uniquely identifies it within this Marionette instance. This can
1061 * be used to switch to this window at a later point.
1064 * Unique window handle.
1066 * @throws {NoSuchWindowError}
1067 * Top-level browsing context has been discarded.
1069 GeckoDriver.prototype.getWindowHandle = function () {
1070 lazy.assert.open(this.getBrowsingContext({ top: true }));
1072 if (this.context == lazy.Context.Chrome) {
1073 return lazy.windowManager.getIdForWindow(this.curBrowser.window);
1075 return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
1079 * Get a list of top-level browsing contexts. On desktop this typically
1080 * corresponds to the set of open tabs for browser windows, or the window
1081 * itself for non-browser chrome windows.
1083 * For chrome scope it returns identifiers for each open chrome window for
1084 * tests interested in managing a set of chrome windows and tabs separately.
1086 * Each window handle is assigned by the server and is guaranteed unique,
1087 * however the return array does not have a specified ordering.
1089 * @returns {Array.<string>}
1090 * Unique window handles.
1092 GeckoDriver.prototype.getWindowHandles = function () {
1093 if (this.context == lazy.Context.Chrome) {
1094 return lazy.windowManager.chromeWindowHandles.map(String);
1096 return lazy.TabManager.allBrowserUniqueIds.map(String);
1100 * Get the current position and size of the browser window currently in focus.
1102 * Will return the current browser window size in pixels. Refers to
1103 * window outerWidth and outerHeight values, which include scroll bars,
1106 * @returns {Record<string, number>}
1107 * Object with |x| and |y| coordinates, and |width| and |height|
1108 * of browser window.
1110 * @throws {NoSuchWindowError}
1111 * Top-level browsing context has been discarded.
1112 * @throws {UnexpectedAlertOpenError}
1113 * A modal dialog is open, blocking this operation.
1115 GeckoDriver.prototype.getWindowRect = async function () {
1116 lazy.assert.open(this.getBrowsingContext({ top: true }));
1117 await this._handleUserPrompts();
1119 return this.curBrowser.rect;
1123 * Set the window position and size of the browser on the operating
1124 * system window manager.
1126 * The supplied `width` and `height` values refer to the window `outerWidth`
1127 * and `outerHeight` values, which include browser chrome and OS-level
1130 * @param {object} cmd
1131 * @param {number} cmd.parameters.x
1132 * X coordinate of the top/left of the window that it will be
1134 * @param {number} cmd.parameters.y
1135 * Y coordinate of the top/left of the window that it will be
1137 * @param {number} cmd.parameters.width
1138 * Width to resize the window to.
1139 * @param {number} cmd.parameters.height
1140 * Height to resize the window to.
1142 * @returns {Record<string, number>}
1143 * Object with `x` and `y` coordinates and `width` and `height`
1146 * @throws {NoSuchWindowError}
1147 * Top-level browsing context has been discarded.
1148 * @throws {UnexpectedAlertOpenError}
1149 * A modal dialog is open, blocking this operation.
1150 * @throws {UnsupportedOperationError}
1151 * Not applicable to application.
1153 GeckoDriver.prototype.setWindowRect = async function (cmd) {
1154 lazy.assert.desktop();
1155 lazy.assert.open(this.getBrowsingContext({ top: true }));
1156 await this._handleUserPrompts();
1158 const { x = null, y = null, width = null, height = null } = cmd.parameters;
1160 lazy.assert.integer(
1162 lazy.pprint`Expected "x" to be an integer value, got ${x}`
1166 lazy.assert.integer(
1168 lazy.pprint`Expected "y" to be an integer value, got ${y}`
1171 if (height !== null) {
1172 lazy.assert.positiveInteger(
1174 lazy.pprint`Expected "height" to be a positive integer value, got ${height}`
1177 if (width !== null) {
1178 lazy.assert.positiveInteger(
1180 lazy.pprint`Expected "width" to be a positive integer value, got ${width}`
1184 const win = this.getCurrentWindow();
1185 switch (lazy.WindowState.from(win.windowState)) {
1186 case lazy.WindowState.Fullscreen:
1187 await exitFullscreen(win);
1190 case lazy.WindowState.Maximized:
1191 case lazy.WindowState.Minimized:
1192 await restoreWindow(win);
1196 function geometryMatches() {
1200 (win.outerWidth !== width || win.outerHeight !== height)
1204 // Wayland doesn't support getting the window position.
1206 !lazy.AppInfo.isWayland &&
1209 (win.screenX !== x || win.screenY !== y)
1213 lazy.logger.trace(`Requested window geometry matches`);
1217 if (!geometryMatches()) {
1218 // There might be more than one resize or MozUpdateWindowPos event due
1219 // to previous geometry changes, such as from restoreWindow(), so
1220 // wait longer if window geometry does not match.
1221 const options = { checkFn: geometryMatches, timeout: 500 };
1222 const promises = [];
1223 if (width !== null && height !== null) {
1224 promises.push(new lazy.EventPromise(win, "resize", options));
1225 win.resizeTo(width, height);
1227 // Wayland doesn't support setting the window position.
1228 if (lazy.AppInfo.isWayland !== "wayland" && x !== null && y !== null) {
1230 new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
1235 await Promise.race(promises);
1237 if (e instanceof lazy.error.TimeoutError) {
1238 // The operating system might not honor the move or resize, in which
1239 // case assume that geometry will have been adjusted "as close as
1240 // possible" to that requested. There may be no event received if the
1241 // geometry is already as close as possible.
1248 return this.curBrowser.rect;
1252 * Switch current top-level browsing context by name or server-assigned
1253 * ID. Searches for windows by name, then ID. Content windows take
1256 * @param {object} cmd
1257 * @param {string} cmd.parameters.handle
1258 * Handle of the window to switch to.
1259 * @param {boolean=} cmd.parameters.focus
1260 * A boolean value which determines whether to focus
1261 * the window. Defaults to true.
1263 * @throws {InvalidArgumentError}
1264 * If <var>handle</var> is not a string or <var>focus</var> not a boolean.
1265 * @throws {NoSuchWindowError}
1266 * Top-level browsing context has been discarded.
1268 GeckoDriver.prototype.switchToWindow = async function (cmd) {
1269 const { focus = true, handle } = cmd.parameters;
1273 lazy.pprint`Expected "handle" to be a string, got ${handle}`
1275 lazy.assert.boolean(
1277 lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
1280 const found = lazy.windowManager.findWindowByHandle(handle);
1282 let selected = false;
1285 await this.setWindowHandle(found, focus);
1288 lazy.logger.error(e);
1293 throw new lazy.error.NoSuchWindowError(
1294 `Unable to locate window: ${handle}`
1300 * Switch the marionette window to a given window. If the browser in
1301 * the window is unregistered, register that browser and wait for
1302 * the registration is complete. If |focus| is true then set the focus
1305 * @param {object} winProperties
1306 * Object containing window properties such as returned from
1307 * :js:func:`GeckoDriver#getWindowProperties`
1308 * @param {boolean=} focus
1309 * A boolean value which determines whether to focus the window.
1312 GeckoDriver.prototype.setWindowHandle = async function (
1316 if (!(winProperties.id in this.browsers)) {
1317 // Initialise Marionette if the current chrome window has not been seen
1318 // before. Also register the initial tab, if one exists.
1319 this.addBrowser(winProperties.win);
1320 this.mainFrame = winProperties.win;
1322 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1324 if (!winProperties.hasTabBrowser) {
1325 this.currentSession.contentBrowsingContext = null;
1327 const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
1329 // For chrome windows such as a reftest window, `getTabBrowser` is not
1330 // a tabbrowser, it is the content browser which should be used here.
1331 const contentBrowser = tabBrowser.tabs
1332 ? tabBrowser.selectedBrowser
1335 this.currentSession.contentBrowsingContext =
1336 contentBrowser.browsingContext;
1337 this.registerBrowser(contentBrowser);
1340 // Otherwise switch to the known chrome window
1341 this.curBrowser = this.browsers[winProperties.id];
1342 this.mainFrame = this.curBrowser.window;
1344 // Activate the tab if it's a content window.
1346 if (winProperties.hasTabBrowser) {
1347 tab = await this.curBrowser.switchToTab(
1348 winProperties.tabIndex,
1354 this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1355 this.currentSession.contentBrowsingContext =
1356 tab?.linkedBrowser.browsingContext;
1359 // Check for an existing dialog for the new window
1360 this.dialog = lazy.modal.findPrompt(this.curBrowser);
1362 // If there is an open window modal dialog the underlying chrome window
1363 // cannot be focused.
1364 if (focus && !this.dialog?.isWindowModal) {
1365 await this.curBrowser.focusWindow();
1370 * Set the current browsing context for future commands to the parent
1371 * of the current browsing context.
1373 * @throws {NoSuchWindowError}
1374 * Browsing context has been discarded.
1375 * @throws {UnexpectedAlertOpenError}
1376 * A modal dialog is open, blocking this operation.
1378 GeckoDriver.prototype.switchToParentFrame = async function () {
1379 let browsingContext = this.getBrowsingContext();
1380 if (browsingContext && !browsingContext.parent) {
1384 browsingContext = lazy.assert.open(browsingContext?.parent);
1386 this.currentSession.contentBrowsingContext = browsingContext;
1390 * Switch to a given frame within the current window.
1392 * @param {object} cmd
1393 * @param {(string | object)=} cmd.parameters.element
1394 * A web element reference of the frame or its element id.
1395 * @param {number=} cmd.parameters.id
1396 * The index of the frame to switch to.
1397 * If both element and id are not defined, switch to top-level frame.
1399 * @throws {NoSuchElementError}
1400 * If element represented by reference <var>element</var> is unknown.
1401 * @throws {NoSuchWindowError}
1402 * Browsing context has been discarded.
1403 * @throws {StaleElementReferenceError}
1404 * If element represented by reference <var>element</var> has gone stale.
1405 * @throws {UnexpectedAlertOpenError}
1406 * A modal dialog is open, blocking this operation.
1408 GeckoDriver.prototype.switchToFrame = async function (cmd) {
1409 const { element: el, id } = cmd.parameters;
1411 if (typeof id == "number") {
1412 lazy.assert.unsignedShort(
1414 lazy.pprint`Expected "id" to be an unsigned short, got ${id}`
1418 const top = id == null && el == null;
1419 lazy.assert.open(this.getBrowsingContext({ top }));
1420 await this._handleUserPrompts();
1422 // Bug 1495063: Elements should be passed as WebReference reference
1424 if (typeof el == "string") {
1425 byFrame = lazy.WebElement.fromUUID(el).toJSON();
1430 // If the current context changed during the switchToFrame call, attempt to
1431 // call switchToFrame again until the browsing context remains stable.
1432 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1786640#c11
1433 let browsingContext;
1434 for (let i = 0; i < 5; i++) {
1435 const currentBrowsingContext = this.currentSession.contentBrowsingContext;
1436 ({ browsingContext } = await this.getActor({ top }).switchToFrame(
1440 if (currentBrowsingContext == this.currentSession.contentBrowsingContext) {
1445 this.currentSession.contentBrowsingContext = browsingContext;
1448 GeckoDriver.prototype.getTimeouts = function () {
1449 return this.currentSession.timeouts;
1453 * Set timeout for page loading, searching, and scripts.
1455 * @param {object} cmd
1456 * @param {Record<string, number>} cmd.parameters
1457 * Dictionary of timeout types and their new value, where all timeout
1458 * types are optional.
1460 * @throws {InvalidArgumentError}
1461 * If timeout type key is unknown, or the value provided with it is
1464 GeckoDriver.prototype.setTimeouts = function (cmd) {
1465 // merge with existing timeouts
1466 let merged = Object.assign(
1467 this.currentSession.timeouts.toJSON(),
1471 this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
1475 * Perform a series of grouped actions at the specified points in time.
1477 * @param {object} cmd
1478 * @param {Array<?>} cmd.parameters.actions
1479 * Array of objects that each represent an action sequence.
1481 * @throws {NoSuchElementError}
1482 * If an element that is used as part of the action chain is unknown.
1483 * @throws {NoSuchWindowError}
1484 * Browsing context has been discarded.
1485 * @throws {StaleElementReferenceError}
1486 * If an element that is used as part of the action chain has gone stale.
1487 * @throws {UnexpectedAlertOpenError}
1488 * A modal dialog is open, blocking this operation.
1489 * @throws {UnsupportedOperationError}
1490 * Not yet available in current context.
1492 GeckoDriver.prototype.performActions = async function (cmd) {
1493 lazy.assert.open(this.getBrowsingContext());
1494 await this._handleUserPrompts();
1496 const actions = cmd.parameters.actions;
1497 await this.getActor().performActions(actions, lazy.prefAsyncEventsEnabled);
1501 * Release all the keys and pointer buttons that are currently depressed.
1503 * @throws {NoSuchWindowError}
1504 * Browsing context has been discarded.
1505 * @throws {UnexpectedAlertOpenError}
1506 * A modal dialog is open, blocking this operation.
1507 * @throws {UnsupportedOperationError}
1508 * Not available in current context.
1510 GeckoDriver.prototype.releaseActions = async function () {
1511 lazy.assert.open(this.getBrowsingContext());
1512 await this._handleUserPrompts();
1514 await this.getActor().releaseActions(lazy.prefAsyncEventsEnabled);
1518 * Find an element using the indicated search strategy.
1520 * @param {object} cmd
1521 * @param {string=} cmd.parameters.element
1522 * Web element reference ID to the element that will be used as start node.
1523 * @param {string} cmd.parameters.using
1524 * Indicates which search method to use.
1525 * @param {string} cmd.parameters.value
1526 * Value the client is looking for.
1528 * @returns {WebElement}
1529 * Return the found element.
1531 * @throws {NoSuchElementError}
1532 * If element represented by reference <var>element</var> is unknown.
1533 * @throws {NoSuchWindowError}
1534 * Browsing context has been discarded.
1535 * @throws {StaleElementReferenceError}
1536 * If element represented by reference <var>element</var> has gone stale.
1537 * @throws {UnexpectedAlertOpenError}
1538 * A modal dialog is open, blocking this operation.
1540 GeckoDriver.prototype.findElement = async function (cmd) {
1541 const { element: el, using, value } = cmd.parameters;
1543 if (!lazy.supportedStrategies.has(using)) {
1544 throw new lazy.error.InvalidSelectorError(
1545 `Strategy not supported: ${using}`
1549 lazy.assert.defined(value);
1550 lazy.assert.open(this.getBrowsingContext());
1551 await this._handleUserPrompts();
1554 if (typeof el != "undefined") {
1555 startNode = lazy.WebElement.fromUUID(el).toJSON();
1560 timeout: this.currentSession.timeouts.implicit,
1564 return this.getActor().findElement(using, value, opts);
1568 * Find an element within shadow root using the indicated search strategy.
1570 * @param {object} cmd
1571 * @param {string} cmd.parameters.shadowRoot
1572 * Shadow root reference ID.
1573 * @param {string} cmd.parameters.using
1574 * Indicates which search method to use.
1575 * @param {string} cmd.parameters.value
1576 * Value the client is looking for.
1578 * @returns {WebElement}
1579 * Return the found element.
1581 * @throws {DetachedShadowRootError}
1582 * If shadow root represented by reference <var>id</var> is
1583 * no longer attached to the DOM.
1584 * @throws {NoSuchElementError}
1585 * If the element which is looked for with <var>value</var> was
1587 * @throws {NoSuchShadowRoot}
1588 * If shadow root represented by reference <var>shadowRoot</var> is unknown.
1589 * @throws {NoSuchWindowError}
1590 * Browsing context has been discarded.
1591 * @throws {UnexpectedAlertOpenError}
1592 * A modal dialog is open, blocking this operation.
1594 GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) {
1595 const { shadowRoot, using, value } = cmd.parameters;
1597 if (!lazy.supportedStrategies.has(using)) {
1598 throw new lazy.error.InvalidSelectorError(
1599 `Strategy not supported: ${using}`
1603 lazy.assert.defined(value);
1604 lazy.assert.open(this.getBrowsingContext());
1605 await this._handleUserPrompts();
1609 startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
1610 timeout: this.currentSession.timeouts.implicit,
1613 return this.getActor().findElement(using, value, opts);
1617 * Find elements using the indicated search strategy.
1619 * @param {object} cmd
1620 * @param {string=} cmd.parameters.element
1621 * Web element reference ID to the element that will be used as start node.
1622 * @param {string} cmd.parameters.using
1623 * Indicates which search method to use.
1624 * @param {string} cmd.parameters.value
1625 * Value the client is looking for.
1627 * @returns {Array<WebElement>}
1628 * Return the array of found elements.
1630 * @throws {NoSuchElementError}
1631 * If element represented by reference <var>element</var> is unknown.
1632 * @throws {NoSuchWindowError}
1633 * Browsing context has been discarded.
1634 * @throws {StaleElementReferenceError}
1635 * If element represented by reference <var>element</var> has gone stale.
1636 * @throws {UnexpectedAlertOpenError}
1637 * A modal dialog is open, blocking this operation.
1639 GeckoDriver.prototype.findElements = async function (cmd) {
1640 const { element: el, using, value } = cmd.parameters;
1642 if (!lazy.supportedStrategies.has(using)) {
1643 throw new lazy.error.InvalidSelectorError(
1644 `Strategy not supported: ${using}`
1648 lazy.assert.defined(value);
1649 lazy.assert.open(this.getBrowsingContext());
1650 await this._handleUserPrompts();
1653 if (typeof el != "undefined") {
1654 startNode = lazy.WebElement.fromUUID(el).toJSON();
1659 timeout: this.currentSession.timeouts.implicit,
1663 return this.getActor().findElements(using, value, opts);
1667 * Find elements within shadow root using the indicated search strategy.
1669 * @param {object} cmd
1670 * @param {string} cmd.parameters.shadowRoot
1671 * Shadow root reference ID.
1672 * @param {string} cmd.parameters.using
1673 * Indicates which search method to use.
1674 * @param {string} cmd.parameters.value
1675 * Value the client is looking for.
1677 * @returns {Array<WebElement>}
1678 * Return the array of found elements.
1680 * @throws {DetachedShadowRootError}
1681 * If shadow root represented by reference <var>id</var> is
1682 * no longer attached to the DOM.
1683 * @throws {NoSuchShadowRoot}
1684 * If shadow root represented by reference <var>shadowRoot</var> is unknown.
1685 * @throws {NoSuchWindowError}
1686 * Browsing context has been discarded.
1687 * @throws {UnexpectedAlertOpenError}
1688 * A modal dialog is open, blocking this operation.
1690 GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) {
1691 const { shadowRoot, using, value } = cmd.parameters;
1693 if (!lazy.supportedStrategies.has(using)) {
1694 throw new lazy.error.InvalidSelectorError(
1695 `Strategy not supported: ${using}`
1699 lazy.assert.defined(value);
1700 lazy.assert.open(this.getBrowsingContext());
1701 await this._handleUserPrompts();
1705 startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
1706 timeout: this.currentSession.timeouts.implicit,
1709 return this.getActor().findElements(using, value, opts);
1713 * Return the shadow root of an element in the document.
1715 * @param {object} cmd
1716 * @param {id} cmd.parameters.id
1717 * A web element id reference.
1718 * @returns {ShadowRoot}
1719 * ShadowRoot of the element.
1721 * @throws {InvalidArgumentError}
1722 * If element <var>id</var> is not a string.
1723 * @throws {NoSuchElementError}
1724 * If element represented by reference <var>id</var> is unknown.
1725 * @throws {NoSuchShadowRoot}
1726 * Element does not have a shadow root attached.
1727 * @throws {NoSuchWindowError}
1728 * Browsing context has been discarded.
1729 * @throws {StaleElementReferenceError}
1730 * If element represented by reference <var>id</var> has gone stale.
1731 * @throws {UnexpectedAlertOpenError}
1732 * A modal dialog is open, blocking this operation.
1733 * @throws {UnsupportedOperationError}
1734 * Not available in chrome current context.
1736 GeckoDriver.prototype.getShadowRoot = async function (cmd) {
1737 // Bug 1743541: Add support for chrome scope.
1738 lazy.assert.content(this.context);
1739 lazy.assert.open(this.getBrowsingContext());
1740 await this._handleUserPrompts();
1742 let id = lazy.assert.string(
1744 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1746 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1748 return this.getActor().getShadowRoot(webEl);
1752 * Return the active element in the document.
1754 * @returns {WebReference}
1755 * Active element of the current browsing context's document
1756 * element, if the document element is non-null.
1758 * @throws {NoSuchElementError}
1759 * If the document does not have an active element, i.e. if
1760 * its document element has been deleted.
1761 * @throws {NoSuchWindowError}
1762 * Browsing context has been discarded.
1763 * @throws {UnexpectedAlertOpenError}
1764 * A modal dialog is open, blocking this operation.
1765 * @throws {UnsupportedOperationError}
1766 * Not available in chrome context.
1768 GeckoDriver.prototype.getActiveElement = async function () {
1769 lazy.assert.content(this.context);
1770 lazy.assert.open(this.getBrowsingContext());
1771 await this._handleUserPrompts();
1773 return this.getActor().getActiveElement();
1777 * Send click event to element.
1779 * @param {object} cmd
1780 * @param {string} cmd.parameters.id
1781 * Reference ID to the element that will be clicked.
1783 * @throws {InvalidArgumentError}
1784 * If element <var>id</var> is not a string.
1785 * @throws {NoSuchElementError}
1786 * If element represented by reference <var>id</var> is unknown.
1787 * @throws {NoSuchWindowError}
1788 * Browsing context has been discarded.
1789 * @throws {StaleElementReferenceError}
1790 * If element represented by reference <var>id</var> has gone stale.
1791 * @throws {UnexpectedAlertOpenError}
1792 * A modal dialog is open, blocking this operation.
1794 GeckoDriver.prototype.clickElement = async function (cmd) {
1795 const browsingContext = lazy.assert.open(this.getBrowsingContext());
1796 await this._handleUserPrompts();
1798 let id = lazy.assert.string(
1800 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1802 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1804 const actor = this.getActor();
1806 const loadEventExpected = lazy.navigate.isLoadEventExpected(
1807 this._getCurrentURL(),
1810 target: await actor.getElementAttribute(webEl, "target"),
1814 await lazy.navigate.waitForNavigationCompleted(
1816 () => actor.clickElement(webEl, this.currentSession.capabilities),
1819 // The click might trigger a navigation, so don't count on it.
1820 requireBeforeUnload: false,
1826 * Get a given attribute of an element.
1828 * @param {object} cmd
1829 * @param {string} cmd.parameters.id
1830 * Web element reference ID to the element that will be inspected.
1831 * @param {string} cmd.parameters.name
1832 * Name of the attribute which value to retrieve.
1835 * Value of the attribute.
1837 * @throws {InvalidArgumentError}
1838 * If <var>id</var> or <var>name</var> are not strings.
1839 * @throws {NoSuchElementError}
1840 * If element represented by reference <var>id</var> is unknown.
1841 * @throws {NoSuchWindowError}
1842 * Browsing context has been discarded.
1843 * @throws {StaleElementReferenceError}
1844 * If element represented by reference <var>id</var> has gone stale.
1845 * @throws {UnexpectedAlertOpenError}
1846 * A modal dialog is open, blocking this operation.
1848 GeckoDriver.prototype.getElementAttribute = async function (cmd) {
1849 lazy.assert.open(this.getBrowsingContext());
1850 await this._handleUserPrompts();
1852 const id = lazy.assert.string(
1854 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1856 const name = lazy.assert.string(
1857 cmd.parameters.name,
1858 lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
1860 const webEl = lazy.WebElement.fromUUID(id).toJSON();
1862 return this.getActor().getElementAttribute(webEl, name);
1866 * Returns the value of a property associated with given element.
1868 * @param {object} cmd
1869 * @param {string} cmd.parameters.id
1870 * Web element reference ID to the element that will be inspected.
1871 * @param {string} cmd.parameters.name
1872 * Name of the property which value to retrieve.
1875 * Value of the property.
1877 * @throws {InvalidArgumentError}
1878 * If <var>id</var> or <var>name</var> are not strings.
1879 * @throws {NoSuchElementError}
1880 * If element represented by reference <var>id</var> is unknown.
1881 * @throws {NoSuchWindowError}
1882 * Browsing context has been discarded.
1883 * @throws {StaleElementReferenceError}
1884 * If element represented by reference <var>id</var> has gone stale.
1885 * @throws {UnexpectedAlertOpenError}
1886 * A modal dialog is open, blocking this operation.
1888 GeckoDriver.prototype.getElementProperty = async function (cmd) {
1889 lazy.assert.open(this.getBrowsingContext());
1890 await this._handleUserPrompts();
1892 const id = lazy.assert.string(
1894 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1896 const name = lazy.assert.string(
1897 cmd.parameters.name,
1898 lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
1900 const webEl = lazy.WebElement.fromUUID(id).toJSON();
1902 return this.getActor().getElementProperty(webEl, name);
1906 * Get the text of an element, if any. Includes the text of all child
1909 * @param {object} cmd
1910 * @param {string} cmd.parameters.id
1911 * Reference ID to the element that will be inspected.
1914 * Element's text "as rendered".
1916 * @throws {InvalidArgumentError}
1917 * If <var>id</var> is not a string.
1918 * @throws {NoSuchElementError}
1919 * If element represented by reference <var>id</var> is unknown.
1920 * @throws {NoSuchWindowError}
1921 * Browsing context has been discarded.
1922 * @throws {StaleElementReferenceError}
1923 * If element represented by reference <var>id</var> has gone stale.
1924 * @throws {UnexpectedAlertOpenError}
1925 * A modal dialog is open, blocking this operation.
1927 GeckoDriver.prototype.getElementText = async function (cmd) {
1928 lazy.assert.open(this.getBrowsingContext());
1929 await this._handleUserPrompts();
1931 let id = lazy.assert.string(
1933 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1935 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1937 return this.getActor().getElementText(webEl);
1941 * Get the tag name of the element.
1943 * @param {object} cmd
1944 * @param {string} cmd.parameters.id
1945 * Reference ID to the element that will be inspected.
1948 * Local tag name of element.
1950 * @throws {InvalidArgumentError}
1951 * If <var>id</var> is not a string.
1952 * @throws {NoSuchElementError}
1953 * If element represented by reference <var>id</var> is unknown.
1954 * @throws {NoSuchWindowError}
1955 * Browsing context has been discarded.
1956 * @throws {StaleElementReferenceError}
1957 * If element represented by reference <var>id</var> has gone stale.
1958 * @throws {UnexpectedAlertOpenError}
1959 * A modal dialog is open, blocking this operation.
1961 GeckoDriver.prototype.getElementTagName = async function (cmd) {
1962 lazy.assert.open(this.getBrowsingContext());
1963 await this._handleUserPrompts();
1965 let id = lazy.assert.string(
1967 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1969 let webEl = lazy.WebElement.fromUUID(id).toJSON();
1971 return this.getActor().getElementTagName(webEl);
1975 * Check if element is displayed.
1977 * @param {object} cmd
1978 * @param {string} cmd.parameters.id
1979 * Reference ID to the element that will be inspected.
1981 * @returns {boolean}
1982 * True if displayed, false otherwise.
1984 * @throws {InvalidArgumentError}
1985 * If <var>id</var> is not a string.
1986 * @throws {NoSuchElementError}
1987 * If element represented by reference <var>id</var> is unknown.
1988 * @throws {NoSuchWindowError}
1989 * Browsing context has been discarded.
1990 * @throws {UnexpectedAlertOpenError}
1991 * A modal dialog is open, blocking this operation.
1993 GeckoDriver.prototype.isElementDisplayed = async function (cmd) {
1994 lazy.assert.open(this.getBrowsingContext());
1995 await this._handleUserPrompts();
1997 let id = lazy.assert.string(
1999 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2001 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2003 return this.getActor().isElementDisplayed(
2005 this.currentSession.capabilities
2010 * Return the property of the computed style of an element.
2012 * @param {object} cmd
2013 * @param {string} cmd.parameters.id
2014 * Reference ID to the element that will be checked.
2015 * @param {string} cmd.parameters.propertyName
2016 * CSS rule that is being requested.
2019 * Value of |propertyName|.
2021 * @throws {InvalidArgumentError}
2022 * If <var>id</var> or <var>propertyName</var> are not strings.
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.getElementValueOfCssProperty = async function (cmd) {
2033 lazy.assert.open(this.getBrowsingContext());
2034 await this._handleUserPrompts();
2036 let id = lazy.assert.string(
2038 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2040 let prop = lazy.assert.string(
2041 cmd.parameters.propertyName,
2042 lazy.pprint`Expected "propertyName" to be a string, got ${cmd.parameters.propertyName}`
2044 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2046 return this.getActor().getElementValueOfCssProperty(webEl, prop);
2050 * Check if element is enabled.
2052 * @param {object} cmd
2053 * @param {string} cmd.parameters.id
2054 * Reference ID to the element that will be checked.
2056 * @returns {boolean}
2057 * True if enabled, false if disabled.
2059 * @throws {InvalidArgumentError}
2060 * If <var>id</var> is not a string.
2061 * @throws {NoSuchElementError}
2062 * If element represented by reference <var>id</var> is unknown.
2063 * @throws {NoSuchWindowError}
2064 * Browsing context has been discarded.
2065 * @throws {StaleElementReferenceError}
2066 * If element represented by reference <var>id</var> has gone stale.
2067 * @throws {UnexpectedAlertOpenError}
2068 * A modal dialog is open, blocking this operation.
2070 GeckoDriver.prototype.isElementEnabled = async function (cmd) {
2071 lazy.assert.open(this.getBrowsingContext());
2072 await this._handleUserPrompts();
2074 let id = lazy.assert.string(
2076 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2078 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2080 return this.getActor().isElementEnabled(
2082 this.currentSession.capabilities
2087 * Check if element is selected.
2089 * @param {object} cmd
2090 * @param {string} cmd.parameters.id
2091 * Reference ID to the element that will be checked.
2093 * @returns {boolean}
2094 * True if selected, false if unselected.
2096 * @throws {InvalidArgumentError}
2097 * If <var>id</var> is not a string.
2098 * @throws {NoSuchElementError}
2099 * If element represented by reference <var>id</var> is unknown.
2100 * @throws {NoSuchWindowError}
2101 * Browsing context has been discarded.
2102 * @throws {UnexpectedAlertOpenError}
2103 * A modal dialog is open, blocking this operation.
2105 GeckoDriver.prototype.isElementSelected = async function (cmd) {
2106 lazy.assert.open(this.getBrowsingContext());
2107 await this._handleUserPrompts();
2109 let id = lazy.assert.string(
2111 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2113 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2115 return this.getActor().isElementSelected(
2117 this.currentSession.capabilities
2122 * @throws {InvalidArgumentError}
2123 * If <var>id</var> is not a string.
2124 * @throws {NoSuchElementError}
2125 * If element represented by reference <var>id</var> is unknown.
2126 * @throws {NoSuchWindowError}
2127 * Browsing context has been discarded.
2128 * @throws {StaleElementReferenceError}
2129 * If element represented by reference <var>id</var> has gone stale.
2130 * @throws {UnexpectedAlertOpenError}
2131 * A modal dialog is open, blocking this operation.
2133 GeckoDriver.prototype.getElementRect = async function (cmd) {
2134 lazy.assert.open(this.getBrowsingContext());
2135 await this._handleUserPrompts();
2137 let id = lazy.assert.string(
2139 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2141 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2143 return this.getActor().getElementRect(webEl);
2147 * Send key presses to element after focusing on it.
2149 * @param {object} cmd
2150 * @param {string} cmd.parameters.id
2151 * Reference ID to the element that will be checked.
2152 * @param {string} cmd.parameters.text
2153 * Value to send to the element.
2155 * @throws {InvalidArgumentError}
2156 * If <var>id</var> or <var>text</var> are not strings.
2157 * @throws {NoSuchElementError}
2158 * If element represented by reference <var>id</var> is unknown.
2159 * @throws {NoSuchWindowError}
2160 * Browsing context has been discarded.
2161 * @throws {StaleElementReferenceError}
2162 * If element represented by reference <var>id</var> has gone stale.
2163 * @throws {UnexpectedAlertOpenError}
2164 * A modal dialog is open, blocking this operation.
2166 GeckoDriver.prototype.sendKeysToElement = async function (cmd) {
2167 lazy.assert.open(this.getBrowsingContext());
2168 await this._handleUserPrompts();
2170 let id = lazy.assert.string(
2172 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2174 let text = lazy.assert.string(
2175 cmd.parameters.text,
2176 lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}`
2178 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2180 return this.getActor().sendKeysToElement(
2183 this.currentSession.capabilities
2188 * Clear the text of an element.
2190 * @param {object} cmd
2191 * @param {string} cmd.parameters.id
2192 * Reference ID to the element that will be cleared.
2194 * @throws {InvalidArgumentError}
2195 * If <var>id</var> is not a string.
2196 * @throws {NoSuchElementError}
2197 * If element represented by reference <var>id</var> is unknown.
2198 * @throws {NoSuchWindowError}
2199 * Browsing context has been discarded.
2200 * @throws {StaleElementReferenceError}
2201 * If element represented by reference <var>id</var> has gone stale.
2202 * @throws {UnexpectedAlertOpenError}
2203 * A modal dialog is open, blocking this operation.
2205 GeckoDriver.prototype.clearElement = async function (cmd) {
2206 lazy.assert.open(this.getBrowsingContext());
2207 await this._handleUserPrompts();
2209 let id = lazy.assert.string(
2211 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
2213 let webEl = lazy.WebElement.fromUUID(id).toJSON();
2215 await this.getActor().clearElement(webEl);
2219 * Add a single cookie to the cookie store associated with the active
2220 * document's address.
2222 * @param {object} cmd
2223 * @param {Map.<string, (string|number|boolean)>} cmd.parameters.cookie
2226 * @throws {InvalidCookieDomainError}
2227 * If <var>cookie</var> is for a different domain than the active
2229 * @throws {NoSuchWindowError}
2230 * Bbrowsing context has been discarded.
2231 * @throws {UnexpectedAlertOpenError}
2232 * A modal dialog is open, blocking this operation.
2233 * @throws {UnsupportedOperationError}
2234 * Not available in current context.
2236 GeckoDriver.prototype.addCookie = async function (cmd) {
2237 lazy.assert.content(this.context);
2238 lazy.assert.open(this.getBrowsingContext());
2239 await this._handleUserPrompts();
2241 let { protocol, hostname } = this._getCurrentURL({ top: false });
2243 const networkSchemes = ["http:", "https:"];
2244 if (!networkSchemes.includes(protocol)) {
2245 throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
2248 let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
2250 lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
2254 * Get all the cookies for the current domain.
2256 * This is the equivalent of calling <code>document.cookie</code> and
2257 * parsing the result.
2259 * @throws {NoSuchWindowError}
2260 * Browsing context has been discarded.
2261 * @throws {UnexpectedAlertOpenError}
2262 * A modal dialog is open, blocking this operation.
2263 * @throws {UnsupportedOperationError}
2264 * Not available in current context.
2266 GeckoDriver.prototype.getCookies = async function () {
2267 lazy.assert.content(this.context);
2268 lazy.assert.open(this.getBrowsingContext());
2269 await this._handleUserPrompts();
2271 let { hostname, pathname } = this._getCurrentURL({ top: false });
2272 return [...lazy.cookie.iter(hostname, pathname)];
2276 * Delete all cookies that are visible to a document.
2278 * @throws {NoSuchWindowError}
2279 * Browsing context has been discarded.
2280 * @throws {UnexpectedAlertOpenError}
2281 * A modal dialog is open, blocking this operation.
2282 * @throws {UnsupportedOperationError}
2283 * Not available in current context.
2285 GeckoDriver.prototype.deleteAllCookies = async function () {
2286 lazy.assert.content(this.context);
2287 lazy.assert.open(this.getBrowsingContext());
2288 await this._handleUserPrompts();
2290 let { hostname, pathname } = this._getCurrentURL({ top: false });
2291 for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
2292 lazy.cookie.remove(toDelete);
2297 * Delete a cookie by name.
2299 * @throws {NoSuchWindowError}
2300 * Browsing context has been discarded.
2301 * @throws {UnexpectedAlertOpenError}
2302 * A modal dialog is open, blocking this operation.
2303 * @throws {UnsupportedOperationError}
2304 * Not available in current context.
2306 GeckoDriver.prototype.deleteCookie = async function (cmd) {
2307 lazy.assert.content(this.context);
2308 lazy.assert.open(this.getBrowsingContext());
2309 await this._handleUserPrompts();
2311 let { hostname, pathname } = this._getCurrentURL({ top: false });
2312 let name = lazy.assert.string(
2313 cmd.parameters.name,
2314 lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
2316 for (let c of lazy.cookie.iter(hostname, pathname)) {
2317 if (c.name === name) {
2318 lazy.cookie.remove(c);
2324 * Open a new top-level browsing context.
2326 * @param {object} cmd
2327 * @param {string=} cmd.parameters.type
2328 * Optional type of the new top-level browsing context. Can be one of
2329 * `tab` or `window`. Defaults to `tab`.
2330 * @param {boolean=} cmd.parameters.focus
2331 * Optional flag if the new top-level browsing context should be opened
2332 * in foreground (focused) or background (not focused). Defaults to false.
2333 * @param {boolean=} cmd.parameters.private
2334 * Optional flag, which gets only evaluated for type `window`. True if the
2335 * new top-level browsing context should be a private window.
2336 * Defaults to false.
2338 * @returns {Record<string, string>}
2339 * Handle and type of the new browsing context.
2341 * @throws {NoSuchWindowError}
2342 * Top-level browsing context has been discarded.
2343 * @throws {UnexpectedAlertOpenError}
2344 * A modal dialog is open, blocking this operation.
2346 GeckoDriver.prototype.newWindow = async function (cmd) {
2347 lazy.assert.open(this.getBrowsingContext({ top: true }));
2348 await this._handleUserPrompts();
2351 if (typeof cmd.parameters.focus != "undefined") {
2352 focus = lazy.assert.boolean(
2353 cmd.parameters.focus,
2354 lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
2358 let isPrivate = false;
2359 if (typeof cmd.parameters.private != "undefined") {
2360 isPrivate = lazy.assert.boolean(
2361 cmd.parameters.private,
2362 lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
2367 if (typeof cmd.parameters.type != "undefined") {
2368 type = lazy.assert.string(
2369 cmd.parameters.type,
2370 lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
2374 // If an invalid or no type has been specified default to a tab.
2375 // On Android always use a new tab instead because the application has a
2376 // single window only.
2378 typeof type == "undefined" ||
2379 !["tab", "window"].includes(type) ||
2380 lazy.AppInfo.isAndroid
2389 let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
2390 contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
2394 // To not fail if a new type gets added in the future, make opening
2395 // a new tab the default action.
2396 let tab = await this.curBrowser.openTab(focus);
2397 contentBrowser = lazy.TabManager.getBrowserForTab(tab);
2401 // Actors need the new window to be loaded to safely execute queries.
2402 // Wait until the initial page load has been finished.
2403 await lazy.waitForInitialNavigationCompleted(
2404 contentBrowser.browsingContext.webProgress,
2406 unloadTimeout: 5000,
2410 const id = lazy.TabManager.getIdForBrowser(contentBrowser);
2412 return { handle: id.toString(), type };
2416 * Close the currently selected tab/window.
2418 * With multiple open tabs present the currently selected tab will
2419 * be closed. Otherwise the window itself will be closed. If it is the
2420 * last window currently open, the window will not be closed to prevent
2421 * a shutdown of the application. Instead the returned list of window
2424 * @returns {Array.<string>}
2425 * Unique window handles of remaining windows.
2427 * @throws {NoSuchWindowError}
2428 * Top-level browsing context has been discarded.
2429 * @throws {UnexpectedAlertOpenError}
2430 * A modal dialog is open, blocking this operation.
2432 GeckoDriver.prototype.close = async function () {
2434 this.getBrowsingContext({ context: lazy.Context.Content, top: true })
2436 await this._handleUserPrompts();
2438 // If there is only one window left, do not close unless windowless mode is
2439 // enabled. Instead return a faked empty array of window handles.
2440 // This will instruct geckodriver to terminate the application.
2442 lazy.TabManager.getTabCount() === 1 &&
2443 !this.currentSession.capabilities.get("moz:windowless")
2448 await this.curBrowser.closeTab();
2449 this.currentSession.contentBrowsingContext = null;
2451 return lazy.TabManager.allBrowserUniqueIds.map(String);
2455 * Close the currently selected chrome window.
2457 * If it is the last window currently open, the chrome window will not be
2458 * closed to prevent a shutdown of the application. Instead the returned
2459 * list of chrome window handles is empty.
2461 * @returns {Array.<string>}
2462 * Unique chrome window handles of remaining chrome windows.
2464 * @throws {NoSuchWindowError}
2465 * Top-level browsing context has been discarded.
2467 GeckoDriver.prototype.closeChromeWindow = async function () {
2468 lazy.assert.desktop();
2470 this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
2475 // eslint-disable-next-line
2476 for (let _ of lazy.windowManager.windows) {
2480 // If there is only one window left, do not close unless windowless mode is
2481 // enabled. Instead return a faked empty array of window handles.
2482 // This will instruct geckodriver to terminate the application.
2483 if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
2487 await this.curBrowser.closeWindow();
2488 this.currentSession.chromeBrowsingContext = null;
2489 this.currentSession.contentBrowsingContext = null;
2491 return lazy.windowManager.chromeWindowHandles.map(String);
2494 /** Delete Marionette session. */
2495 GeckoDriver.prototype.deleteSession = function () {
2496 if (!this.currentSession) {
2500 for (let win of lazy.windowManager.windows) {
2501 this.stopObservingWindow(win);
2504 // reset to the top-most frame
2505 this.mainFrame = null;
2507 if (!this._isShuttingDown && this.promptListener) {
2508 // Do not stop the prompt listener when quitting the browser to
2509 // allow us to also accept beforeunload prompts during shutdown.
2510 this.promptListener.stopListening();
2511 this.promptListener = null;
2515 Services.obs.removeObserver(this, TOPIC_BROWSER_READY);
2517 lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`);
2520 // Always unregister actors after all other observers
2521 // and listeners have been removed.
2522 lazy.unregisterCommandsActor();
2523 // MarionetteEvents actors are only disabled to avoid IPC errors if there are
2524 // in flight events being forwarded from the content process to the parent
2526 lazy.disableEventsActor();
2528 if (lazy.RemoteAgent.webDriverBiDi) {
2529 lazy.RemoteAgent.webDriverBiDi.deleteSession();
2531 this.currentSession.destroy();
2532 this._currentSession = null;
2537 * Takes a screenshot of a web element, current frame, or viewport.
2539 * The screen capture is returned as a lossless PNG image encoded as
2542 * If called in the content context, the |id| argument is not null and
2543 * refers to a present and visible web element's ID, the capture area will
2544 * be limited to the bounding box of that element. Otherwise, the capture
2545 * area will be the bounding box of the current frame.
2547 * If called in the chrome context, the screenshot will always represent
2548 * the entire viewport.
2550 * @param {object} cmd
2551 * @param {string=} cmd.parameters.id
2552 * Optional web element reference to take a screenshot of.
2553 * If undefined, a screenshot will be taken of the document element.
2554 * @param {boolean=} cmd.parameters.full
2555 * True to take a screenshot of the entire document element. Is only
2556 * considered if <var>id</var> is not defined. Defaults to true.
2557 * @param {boolean=} cmd.parameters.hash
2558 * True if the user requests a hash of the image data. Defaults to false.
2559 * @param {boolean=} cmd.parameters.scroll
2560 * Scroll to element if |id| is provided. Defaults to true.
2563 * If <var>hash</var> is false, PNG image encoded as Base64 encoded
2564 * string. If <var>hash</var> is true, hex digest of the SHA-256
2565 * hash of the Base64 encoded string.
2567 * @throws {NoSuchElementError}
2568 * If element represented by reference <var>id</var> is unknown.
2569 * @throws {NoSuchWindowError}
2570 * Browsing context has been discarded.
2571 * @throws {StaleElementReferenceError}
2572 * If element represented by reference <var>id</var> has gone stale.
2574 GeckoDriver.prototype.takeScreenshot = async function (cmd) {
2575 lazy.assert.open(this.getBrowsingContext({ top: true }));
2576 await this._handleUserPrompts();
2578 let { id, full, hash, scroll } = cmd.parameters;
2579 let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
2581 full = typeof full == "undefined" ? true : full;
2582 scroll = typeof scroll == "undefined" ? true : scroll;
2584 let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;
2586 // Only consider full screenshot if no element has been specified
2587 full = webEl ? false : full;
2589 return this.getActor().takeScreenshot(webEl, format, full, scroll);
2593 * Get the current browser orientation.
2595 * Will return one of the valid primary orientation values
2596 * portrait-primary, landscape-primary, portrait-secondary, or
2597 * landscape-secondary.
2599 * @throws {NoSuchWindowError}
2600 * Top-level browsing context has been discarded.
2602 GeckoDriver.prototype.getScreenOrientation = function () {
2603 lazy.assert.mobile();
2604 lazy.assert.open(this.getBrowsingContext({ top: true }));
2606 const win = this.getCurrentWindow();
2608 return win.screen.orientation.type;
2612 * Set the current browser orientation.
2614 * The supplied orientation should be given as one of the valid
2615 * orientation values. If the orientation is unknown, an error will
2618 * Valid orientations are "portrait" and "landscape", which fall
2619 * back to "portrait-primary" and "landscape-primary" respectively,
2620 * and "portrait-secondary" as well as "landscape-secondary".
2622 * @throws {NoSuchWindowError}
2623 * Top-level browsing context has been discarded.
2625 GeckoDriver.prototype.setScreenOrientation = async function (cmd) {
2626 lazy.assert.mobile();
2627 lazy.assert.open(this.getBrowsingContext({ top: true }));
2633 "landscape-primary",
2634 "portrait-secondary",
2635 "landscape-secondary",
2638 let or = String(cmd.parameters.orientation);
2639 lazy.assert.string(or, lazy.pprint`Expected "or" to be a string, got ${or}`);
2640 let mozOr = or.toLowerCase();
2641 if (!ors.includes(mozOr)) {
2642 throw new lazy.error.InvalidArgumentError(
2643 `Unknown screen orientation: ${or}`
2647 const win = this.getCurrentWindow();
2650 await win.screen.orientation.lock(mozOr);
2652 throw new lazy.error.WebDriverError(
2653 `Unable to set screen orientation: ${or}`
2659 * Synchronously minimizes the user agent window as if the user pressed
2660 * the minimize button.
2662 * No action is taken if the window is already minimized.
2664 * Not supported on Fennec.
2666 * @returns {Record<string, number>}
2667 * Window rect and window state.
2669 * @throws {NoSuchWindowError}
2670 * Top-level browsing context has been discarded.
2671 * @throws {UnexpectedAlertOpenError}
2672 * A modal dialog is open, blocking this operation.
2673 * @throws {UnsupportedOperationError}
2674 * Not available for current application.
2676 GeckoDriver.prototype.minimizeWindow = async function () {
2677 lazy.assert.desktop();
2678 lazy.assert.open(this.getBrowsingContext({ top: true }));
2679 await this._handleUserPrompts();
2681 const win = this.getCurrentWindow();
2682 switch (lazy.WindowState.from(win.windowState)) {
2683 case lazy.WindowState.Fullscreen:
2684 await exitFullscreen(win);
2687 case lazy.WindowState.Maximized:
2688 await restoreWindow(win);
2692 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
2694 // Use a timed promise to abort if no window manager is present
2695 await new lazy.TimedPromise(
2697 cb = new lazy.DebounceCallback(resolve);
2698 win.addEventListener("sizemodechange", cb);
2701 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2703 win.removeEventListener("sizemodechange", cb);
2704 await new lazy.IdlePromise(win);
2707 return this.curBrowser.rect;
2711 * Synchronously maximizes the user agent window as if the user pressed
2712 * the maximize button.
2714 * No action is taken if the window is already maximized.
2716 * Not supported on Fennec.
2718 * @returns {Record<string, number>}
2721 * @throws {NoSuchWindowError}
2722 * Top-level browsing context has been discarded.
2723 * @throws {UnexpectedAlertOpenError}
2724 * A modal dialog is open, blocking this operation.
2725 * @throws {UnsupportedOperationError}
2726 * Not available for current application.
2728 GeckoDriver.prototype.maximizeWindow = async function () {
2729 lazy.assert.desktop();
2730 lazy.assert.open(this.getBrowsingContext({ top: true }));
2731 await this._handleUserPrompts();
2733 const win = this.getCurrentWindow();
2734 switch (lazy.WindowState.from(win.windowState)) {
2735 case lazy.WindowState.Fullscreen:
2736 await exitFullscreen(win);
2739 case lazy.WindowState.Minimized:
2740 await restoreWindow(win);
2744 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
2746 // Use a timed promise to abort if no window manager is present
2747 await new lazy.TimedPromise(
2749 cb = new lazy.DebounceCallback(resolve);
2750 win.addEventListener("sizemodechange", cb);
2753 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2755 win.removeEventListener("sizemodechange", cb);
2756 await new lazy.IdlePromise(win);
2759 return this.curBrowser.rect;
2763 * Synchronously sets the user agent window to full screen as if the user
2764 * had done "View > Enter Full Screen".
2766 * No action is taken if the window is already in full screen mode.
2768 * Not supported on Fennec.
2770 * @returns {Map.<string, number>}
2773 * @throws {NoSuchWindowError}
2774 * Top-level browsing context has been discarded.
2775 * @throws {UnexpectedAlertOpenError}
2776 * A modal dialog is open, blocking this operation.
2777 * @throws {UnsupportedOperationError}
2778 * Not available for current application.
2780 GeckoDriver.prototype.fullscreenWindow = async function () {
2781 lazy.assert.desktop();
2782 lazy.assert.open(this.getBrowsingContext({ top: true }));
2783 await this._handleUserPrompts();
2785 const win = this.getCurrentWindow();
2786 switch (lazy.WindowState.from(win.windowState)) {
2787 case lazy.WindowState.Maximized:
2788 case lazy.WindowState.Minimized:
2789 await restoreWindow(win);
2793 if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
2795 // Use a timed promise to abort if no window manager is present
2796 await new lazy.TimedPromise(
2798 cb = new lazy.DebounceCallback(resolve);
2799 win.addEventListener("sizemodechange", cb);
2800 win.fullScreen = true;
2802 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2804 win.removeEventListener("sizemodechange", cb);
2806 await new lazy.IdlePromise(win);
2808 return this.curBrowser.rect;
2812 * Dismisses a currently displayed modal dialogs, or returns no such alert if
2813 * no modal is displayed.
2815 * @throws {NoSuchAlertError}
2816 * If there is no current user prompt.
2817 * @throws {NoSuchWindowError}
2818 * Top-level browsing context has been discarded.
2820 GeckoDriver.prototype.dismissDialog = async function () {
2821 lazy.assert.open(this.getBrowsingContext({ top: true }));
2822 this._checkIfAlertIsPresent();
2824 const dialogClosed = this.promptListener.dialogClosed();
2825 this.dialog.dismiss();
2828 const win = this.getCurrentWindow();
2829 await new lazy.IdlePromise(win);
2833 * Accepts a currently displayed dialog modal, or returns no such alert if
2834 * no modal is displayed.
2836 * @throws {NoSuchAlertError}
2837 * If there is no current user prompt.
2838 * @throws {NoSuchWindowError}
2839 * Top-level browsing context has been discarded.
2841 GeckoDriver.prototype.acceptDialog = async function () {
2842 lazy.assert.open(this.getBrowsingContext({ top: true }));
2843 this._checkIfAlertIsPresent();
2845 const dialogClosed = this.promptListener.dialogClosed();
2846 this.dialog.accept();
2849 const win = this.getCurrentWindow();
2850 await new lazy.IdlePromise(win);
2854 * Returns the message shown in a currently displayed modal, or returns
2855 * a no such alert error if no modal is currently displayed.
2857 * @throws {NoSuchAlertError}
2858 * If there is no current user prompt.
2859 * @throws {NoSuchWindowError}
2860 * Top-level browsing context has been discarded.
2862 GeckoDriver.prototype.getTextFromDialog = async function () {
2863 lazy.assert.open(this.getBrowsingContext({ top: true }));
2864 this._checkIfAlertIsPresent();
2865 const text = await this.dialog.getText();
2870 * Set the user prompt's value field.
2872 * Sends keys to the input field of a currently displayed modal, or
2873 * returns a no such alert error if no modal is currently displayed. If
2874 * a modal dialog is currently displayed but has no means for text input,
2875 * an element not visible error is returned.
2877 * @param {object} cmd
2878 * @param {string} cmd.parameters.text
2879 * Input to the user prompt's value field.
2881 * @throws {ElementNotInteractableError}
2882 * If the current user prompt is an alert or confirm.
2883 * @throws {NoSuchAlertError}
2884 * If there is no current user prompt.
2885 * @throws {NoSuchWindowError}
2886 * Top-level browsing context has been discarded.
2887 * @throws {UnsupportedOperationError}
2888 * If the current user prompt is something other than an alert,
2889 * confirm, or a prompt.
2891 GeckoDriver.prototype.sendKeysToDialog = async function (cmd) {
2892 lazy.assert.open(this.getBrowsingContext({ top: true }));
2893 this._checkIfAlertIsPresent();
2895 let text = lazy.assert.string(
2896 cmd.parameters.text,
2897 lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}`
2899 let promptType = this.dialog.args.promptType;
2901 switch (promptType) {
2904 throw new lazy.error.ElementNotInteractableError(
2905 `User prompt of type ${promptType} is not interactable`
2910 await this.dismissDialog();
2911 throw new lazy.error.UnsupportedOperationError(
2912 `User prompt of type ${promptType} is not supported`
2915 this.dialog.text = text;
2918 GeckoDriver.prototype._checkIfAlertIsPresent = function () {
2919 if (!this.dialog || !this.dialog.isOpen) {
2920 throw new lazy.error.NoSuchAlertError();
2924 GeckoDriver.prototype._handleUserPrompts = async function () {
2925 if (!this.dialog || !this.dialog.isOpen) {
2929 const promptType = this.dialog.promptType;
2930 const textContent = await this.dialog.getText();
2932 if (promptType === "beforeunload" && !this.currentSession.bidi) {
2933 // In an HTTP-only session, this prompt will be automatically accepted.
2934 // Since this occurs asynchronously, we need to wait until it closes
2935 // to prevent race conditions, particularly in slow builds.
2936 await lazy.PollPromise((resolve, reject) => {
2937 this.dialog?.isOpen ? reject() : resolve();
2942 let type = lazy.PromptTypes.Default;
2943 switch (promptType) {
2945 type = lazy.PromptTypes.Alert;
2947 case "beforeunload":
2948 type = lazy.PromptTypes.BeforeUnload;
2951 type = lazy.PromptTypes.Confirm;
2954 type = lazy.PromptTypes.Prompt;
2958 const userPromptHandler = this.currentSession.userPromptHandler;
2959 const handlerConfig = userPromptHandler.getPromptHandler(type);
2961 switch (handlerConfig.handler) {
2962 case lazy.PromptHandlers.Accept:
2963 await this.acceptDialog();
2965 case lazy.PromptHandlers.Dismiss:
2966 await this.dismissDialog();
2968 case lazy.PromptHandlers.Ignore:
2972 if (handlerConfig.notify) {
2973 throw new lazy.error.UnexpectedAlertOpenError(
2974 `Unexpected ${promptType} dialog detected. Performed handler "${handlerConfig.handler}". Dialog text: ${textContent}`,
2983 * Enables or disables accepting new socket connections.
2985 * By calling this method with `false` the server will not accept any
2986 * further connections, but existing connections will not be forcible
2987 * closed. Use `true` to re-enable accepting connections.
2989 * Please note that when closing the connection via the client you can
2990 * end-up in a non-recoverable state if it hasn't been enabled before.
2992 * This method is used for custom in application shutdowns via
2993 * marionette.quit() or marionette.restart(), like File -> Quit.
2995 * @param {object} cmd
2996 * @param {boolean} cmd.parameters.value
2997 * True if the server should accept new socket connections.
2999 GeckoDriver.prototype.acceptConnections = async function (cmd) {
3000 lazy.assert.boolean(
3001 cmd.parameters.value,
3002 lazy.pprint`Expected "value" to be a boolean, got ${cmd.parameters.value}`
3004 await this._server.setAcceptConnections(cmd.parameters.value);
3008 * Quits the application with the provided flags.
3010 * Marionette will stop accepting new connections before ending the
3011 * current session, and finally attempting to quit the application.
3013 * Optional {@link nsIAppStartup} flags may be provided as
3014 * an array of masks, and these will be combined by ORing
3015 * them with a bitmask. The available masks are defined in
3016 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
3018 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
3019 * flag may be bit-wise combined with one of the *Quit flags to cause
3020 * the application to restart after it quits.
3022 * @param {object} cmd
3023 * @param {Array.<string>=} cmd.parameters.flags
3024 * Constant name of masks to pass to |Services.startup.quit|.
3025 * If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
3026 * @param {boolean=} cmd.parameters.safeMode
3027 * Optional flag to indicate that the application has to
3028 * be restarted in safe mode.
3030 * @returns {Record<string,boolean>}
3031 * Dictionary containing information that explains the shutdown reason.
3032 * The value for `cause` contains the shutdown kind like "shutdown" or
3033 * "restart", while `forced` will indicate if it was a normal or forced
3034 * shutdown of the application. "in_app" is always set to indicate that
3035 * it is a shutdown triggered from within the application.
3037 * @throws {InvalidArgumentError}
3038 * If <var>flags</var> contains unknown or incompatible flags,
3039 * for example multiple Quit flags.
3041 GeckoDriver.prototype.quit = async function (cmd) {
3042 const { flags = [], safeMode = false } = cmd.parameters;
3046 lazy.pprint`Expected "flags" to be an array, got ${flags}`
3048 lazy.assert.boolean(
3050 lazy.pprint`Expected "safeMode" to be a boolean, got ${safeMode}`
3053 if (safeMode && !flags.includes("eRestart")) {
3054 throw new lazy.error.InvalidArgumentError(
3055 `"safeMode" only works with restart flag`
3059 // Register handler to run Marionette specific shutdown code.
3060 Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
3062 let quitApplicationResponse;
3064 this._isShuttingDown = true;
3065 quitApplicationResponse = await lazy.quit(
3068 this.currentSession.capabilities.get("moz:windowless")
3071 this._isShuttingDown = false;
3072 if (e instanceof TypeError) {
3073 throw new lazy.error.InvalidArgumentError(e.message);
3075 throw new lazy.error.UnsupportedOperationError(e.message);
3077 Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
3080 return quitApplicationResponse;
3083 GeckoDriver.prototype.installAddon = function (cmd) {
3084 lazy.assert.desktop();
3086 let path = cmd.parameters.path;
3087 let temp = cmd.parameters.temporary || false;
3089 typeof path == "undefined" ||
3090 typeof path != "string" ||
3091 typeof temp != "boolean"
3093 throw new lazy.error.InvalidArgumentError();
3096 return lazy.Addon.install(path, temp);
3099 GeckoDriver.prototype.uninstallAddon = function (cmd) {
3100 lazy.assert.desktop();
3102 let id = cmd.parameters.id;
3103 if (typeof id == "undefined" || typeof id != "string") {
3104 throw new lazy.error.InvalidArgumentError();
3107 return lazy.Addon.uninstall(id);
3111 * Retrieve the localized string for the specified entity id.
3114 * localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
3116 * @param {object} cmd
3117 * @param {Array.<string>} cmd.parameters.urls
3118 * Array of .dtd URLs.
3119 * @param {string} cmd.parameters.id
3120 * The ID of the entity to retrieve the localized string for.
3123 * The localized string for the requested entity.
3125 GeckoDriver.prototype.localizeEntity = function (cmd) {
3126 let { urls, id } = cmd.parameters;
3128 if (!Array.isArray(urls)) {
3129 throw new lazy.error.InvalidArgumentError(
3130 "Value of `urls` should be of type 'Array'"
3133 if (typeof id != "string") {
3134 throw new lazy.error.InvalidArgumentError(
3135 "Value of `id` should be of type 'string'"
3139 return lazy.l10n.localizeEntity(urls, id);
3143 * Retrieve the localized string for the specified property id.
3148 * ["chrome://global/locale/findbar.properties"], "FastFind");
3150 * @param {object} cmd
3151 * @param {Array.<string>} cmd.parameters.urls
3152 * Array of .properties URLs.
3153 * @param {string} cmd.parameters.id
3154 * The ID of the property to retrieve the localized string for.
3157 * The localized string for the requested property.
3159 GeckoDriver.prototype.localizeProperty = function (cmd) {
3160 let { urls, id } = cmd.parameters;
3162 if (!Array.isArray(urls)) {
3163 throw new lazy.error.InvalidArgumentError(
3164 "Value of `urls` should be of type 'Array'"
3167 if (typeof id != "string") {
3168 throw new lazy.error.InvalidArgumentError(
3169 "Value of `id` should be of type 'string'"
3173 return lazy.l10n.localizeProperty(urls, id);
3177 * Initialize the reftest mode
3179 GeckoDriver.prototype.setupReftest = async function (cmd) {
3180 if (this._reftest) {
3181 throw new lazy.error.UnsupportedOperationError(
3182 "Called reftest:setup with a reftest session already active"
3188 screenshot = "unexpected",
3191 if (!["always", "fail", "unexpected"].includes(screenshot)) {
3192 throw new lazy.error.InvalidArgumentError(
3193 "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
3197 this._reftest = new lazy.reftest.Runner(this);
3198 this._reftest.setup(urlCount, screenshot, isPrint);
3201 /** Run a reftest. */
3202 GeckoDriver.prototype.runReftest = function (cmd) {
3203 let { test, references, expected, timeout, width, height, pageRanges } =
3206 if (!this._reftest) {
3207 throw new lazy.error.UnsupportedOperationError(
3208 "Called reftest:run before reftest:start"
3214 lazy.pprint`Expected "test" to be a string, got ${test}`
3218 lazy.pprint`Expected "expected" to be a string, got ${expected}`
3222 lazy.pprint`Expected "references" to be an array, got ${references}`
3225 return this._reftest.run(
3237 * End a reftest run.
3239 * Closes the reftest window (without changing the current window handle),
3240 * and removes cached canvases.
3242 GeckoDriver.prototype.teardownReftest = function () {
3243 if (!this._reftest) {
3244 throw new lazy.error.UnsupportedOperationError(
3245 "Called reftest:teardown before reftest:start"
3249 this._reftest.teardown();
3250 this._reftest = null;
3254 * Print page as PDF.
3256 * @param {object} cmd
3257 * @param {boolean=} cmd.parameters.background
3258 * Whether or not to print background colors and images.
3259 * Defaults to false, which prints without background graphics.
3260 * @param {number=} cmd.parameters.margin.bottom
3261 * Bottom margin in cm. Defaults to 1cm (~0.4 inches).
3262 * @param {number=} cmd.parameters.margin.left
3263 * Left margin in cm. Defaults to 1cm (~0.4 inches).
3264 * @param {number=} cmd.parameters.margin.right
3265 * Right margin in cm. Defaults to 1cm (~0.4 inches).
3266 * @param {number=} cmd.parameters.margin.top
3267 * Top margin in cm. Defaults to 1cm (~0.4 inches).
3268 * @param {('landscape'|'portrait')=} cmd.parameters.options.orientation
3269 * Paper orientation. Defaults to 'portrait'.
3270 * @param {Array.<string|number>=} cmd.parameters.pageRanges
3271 * Paper ranges to print, e.g., ['1-5', 8, '11-13'].
3272 * Defaults to the empty array, which means print all pages.
3273 * @param {number=} cmd.parameters.page.height
3274 * Paper height in cm. Defaults to US letter height (27.94cm / 11 inches)
3275 * @param {number=} cmd.parameters.page.width
3276 * Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches)
3277 * @param {number=} cmd.parameters.scale
3278 * Scale of the webpage rendering. Defaults to 1.0.
3279 * @param {boolean=} cmd.parameters.shrinkToFit
3280 * Whether or not to override page size as defined by CSS.
3281 * Defaults to true, in which case the content will be scaled
3282 * to fit the paper size.
3285 * Base64 encoded PDF representing printed document
3287 * @throws {NoSuchWindowError}
3288 * Top-level browsing context has been discarded.
3289 * @throws {UnexpectedAlertOpenError}
3290 * A modal dialog is open, blocking this operation.
3291 * @throws {UnsupportedOperationError}
3292 * Not available in chrome context.
3294 GeckoDriver.prototype.print = async function (cmd) {
3295 lazy.assert.content(this.context);
3296 lazy.assert.open(this.getBrowsingContext({ top: true }));
3297 await this._handleUserPrompts();
3299 const settings = lazy.print.addDefaultSettings(cmd.parameters);
3300 for (const prop of ["top", "bottom", "left", "right"]) {
3301 lazy.assert.positiveNumber(
3302 settings.margin[prop],
3303 lazy.pprint`Expected "margin.${prop}" to be a positive number, got ${settings.margin[prop]}`
3306 for (const prop of ["width", "height"]) {
3307 lazy.assert.positiveNumber(
3308 settings.page[prop],
3309 lazy.pprint`Expected "page.${prop}" to be a positive number, got ${settings.page[prop]}`
3312 lazy.assert.positiveNumber(
3314 lazy.pprint`Expected "scale" to be a positive number, got ${settings.scale}`
3318 s >= lazy.print.minScaleValue &&
3319 settings.scale <= lazy.print.maxScaleValue,
3320 lazy.pprint`scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
3322 lazy.assert.boolean(
3323 settings.shrinkToFit,
3324 lazy.pprint`Expected "shrinkToFit" to be a boolean, got ${settings.shrinkToFit}`
3327 orientation => lazy.print.defaults.orientationValue.includes(orientation),
3328 lazy.pprint`orientation ${
3329 settings.orientation
3330 } doesn't match allowed values "${lazy.print.defaults.orientationValue.join(
3333 )(settings.orientation);
3334 lazy.assert.boolean(
3335 settings.background,
3336 lazy.pprint`Expected "background" to be a boolean, got ${settings.background}`
3339 settings.pageRanges,
3340 lazy.pprint`Expected "pageRanges" to be an array, got ${settings.pageRanges}`
3343 const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext;
3344 const printSettings = await lazy.print.getPrintSettings(settings);
3345 const binaryString = await lazy.print.printToBinaryString(
3350 return btoa(binaryString);
3353 GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
3358 hasUserVerification,
3365 lazy.pprint`Expected "protocol" to be a string, got ${protocol}`
3369 lazy.pprint`Expected "transport" to be a string, got ${transport}`
3371 lazy.assert.boolean(
3373 lazy.pprint`Expected "hasResidentKey" to be a boolean, got ${hasResidentKey}`
3375 lazy.assert.boolean(
3376 hasUserVerification,
3377 lazy.pprint`Expected "hasUserVerification" to be a boolean, got ${hasUserVerification}`
3379 lazy.assert.boolean(
3381 lazy.pprint`Expected "isUserConsenting" to be a boolean, got ${isUserConsenting}`
3383 lazy.assert.boolean(
3385 lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}`
3388 return lazy.webauthn.addVirtualAuthenticator(
3392 hasUserVerification,
3398 GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
3399 const { authenticatorId } = cmd.parameters;
3401 lazy.assert.positiveInteger(
3403 lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
3406 lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
3409 GeckoDriver.prototype.addCredential = function (cmd) {
3413 isResidentCredential,
3420 lazy.assert.positiveInteger(
3422 lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
3426 lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}`
3428 lazy.assert.boolean(
3429 isResidentCredential,
3430 lazy.pprint`Expected "isResidentCredential" to be a boolean, got ${isResidentCredential}`
3434 lazy.pprint`Expected "rpId" to be a string, got ${rpId}`
3438 lazy.pprint`Expected "privateKey" to be a string, got ${privateKey}`
3443 lazy.pprint`Expected "userHandle" to be a string, got ${userHandle}`
3448 lazy.pprint`Expected "signCount" to be a number, got ${signCount}`
3451 lazy.webauthn.addCredential(
3454 isResidentCredential,
3462 GeckoDriver.prototype.getCredentials = function (cmd) {
3463 const { authenticatorId } = cmd.parameters;
3465 lazy.assert.positiveInteger(
3467 lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
3470 return lazy.webauthn.getCredentials(authenticatorId);
3473 GeckoDriver.prototype.removeCredential = function (cmd) {
3474 const { authenticatorId, credentialId } = cmd.parameters;
3476 lazy.assert.positiveInteger(
3478 lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
3482 lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}`
3485 lazy.webauthn.removeCredential(authenticatorId, credentialId);
3488 GeckoDriver.prototype.removeAllCredentials = function (cmd) {
3489 const { authenticatorId } = cmd.parameters;
3491 lazy.assert.positiveInteger(
3493 lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
3496 lazy.webauthn.removeAllCredentials(authenticatorId);
3499 GeckoDriver.prototype.setUserVerified = function (cmd) {
3500 const { authenticatorId, isUserVerified } = cmd.parameters;
3502 lazy.assert.positiveInteger(
3504 lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
3506 lazy.assert.boolean(
3508 lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}`
3511 lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
3514 GeckoDriver.prototype.setPermission = async function (cmd) {
3515 const { descriptor, state, oneRealm = false } = cmd.parameters;
3516 const browsingContext = lazy.assert.open(this.getBrowsingContext());
3518 lazy.permissions.validateDescriptor(descriptor);
3519 lazy.permissions.validateState(state);
3524 await this.curBrowser.window.navigator.permissions.parseSetParameters({
3529 throw new lazy.error.InvalidArgumentError(`setPermission: ${err.message}`);
3532 lazy.assert.boolean(
3534 lazy.pprint`Expected "oneRealm" to be a boolean, got ${oneRealm}`
3537 let origin = browsingContext.currentURI.prePath;
3539 // storage-access is a special case.
3540 if (descriptor.name === "storage-access") {
3541 origin = browsingContext.top.currentURI.prePath;
3544 type: lazy.permissions.getStorageAccessPermissionsType(
3545 browsingContext.currentWindowGlobal.documentURI
3550 lazy.permissions.set(params, state, origin);
3554 * Determines the Accessibility label for this element.
3556 * @param {object} cmd
3557 * @param {string} cmd.parameters.id
3558 * Web element reference ID to the element for which the accessibility label
3562 * The Accessibility label for this element
3564 GeckoDriver.prototype.getComputedLabel = async function (cmd) {
3565 lazy.assert.open(this.getBrowsingContext());
3566 await this._handleUserPrompts();
3568 let id = lazy.assert.string(
3570 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
3572 let webEl = lazy.WebElement.fromUUID(id).toJSON();
3574 return this.getActor().getComputedLabel(webEl);
3578 * Determines the Accessibility role for this element.
3580 * @param {object} cmd
3581 * @param {string} cmd.parameters.id
3582 * Web element reference ID to the element for which the accessibility role
3586 * The Accessibility role for this element
3588 GeckoDriver.prototype.getComputedRole = async function (cmd) {
3589 lazy.assert.open(this.getBrowsingContext());
3590 await this._handleUserPrompts();
3592 let id = lazy.assert.string(
3594 lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
3596 let webEl = lazy.WebElement.fromUUID(id).toJSON();
3597 return this.getActor().getComputedRole(webEl);
3600 GeckoDriver.prototype.commands = {
3601 // Marionette service
3602 "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
3603 "Marionette:GetContext": GeckoDriver.prototype.getContext,
3604 "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
3605 "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
3606 "Marionette:Quit": GeckoDriver.prototype.quit,
3607 "Marionette:SetContext": GeckoDriver.prototype.setContext,
3608 "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
3611 "Addon:Install": GeckoDriver.prototype.installAddon,
3612 "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
3615 "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
3616 "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
3619 "reftest:setup": GeckoDriver.prototype.setupReftest,
3620 "reftest:run": GeckoDriver.prototype.runReftest,
3621 "reftest:teardown": GeckoDriver.prototype.teardownReftest,
3623 // WebDriver service
3624 "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
3625 // deprecated, no longer used since the geckodriver 0.30.0 release
3626 "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
3627 "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
3628 "WebDriver:Back": GeckoDriver.prototype.goBack,
3629 "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
3630 "WebDriver:CloseWindow": GeckoDriver.prototype.close,
3631 "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
3632 "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
3633 "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
3634 "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
3635 "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
3636 "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
3637 "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
3638 "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
3639 "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
3640 "WebDriver:FindElement": GeckoDriver.prototype.findElement,
3641 "WebDriver:FindElementFromShadowRoot":
3642 GeckoDriver.prototype.findElementFromShadowRoot,
3643 "WebDriver:FindElements": GeckoDriver.prototype.findElements,
3644 "WebDriver:FindElementsFromShadowRoot":
3645 GeckoDriver.prototype.findElementsFromShadowRoot,
3646 "WebDriver:Forward": GeckoDriver.prototype.goForward,
3647 "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
3648 "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
3649 "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
3650 "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
3651 "WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel,
3652 "WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole,
3653 "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
3654 "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
3655 "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
3656 "WebDriver:GetElementCSSValue":
3657 GeckoDriver.prototype.getElementValueOfCssProperty,
3658 "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
3659 "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
3660 "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
3661 "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
3662 "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
3663 "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
3664 "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
3665 "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
3666 "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
3667 "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
3668 "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
3669 "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
3670 "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
3671 "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
3672 "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
3673 "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
3674 "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
3675 "WebDriver:NewSession": GeckoDriver.prototype.newSession,
3676 "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
3677 "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
3678 "WebDriver:Print": GeckoDriver.prototype.print,
3679 "WebDriver:Refresh": GeckoDriver.prototype.refresh,
3680 "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
3681 "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
3682 "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
3683 "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
3684 "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
3685 "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
3686 "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
3687 "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
3688 "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
3691 "WebAuthn:AddVirtualAuthenticator":
3692 GeckoDriver.prototype.addVirtualAuthenticator,
3693 "WebAuthn:RemoveVirtualAuthenticator":
3694 GeckoDriver.prototype.removeVirtualAuthenticator,
3695 "WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
3696 "WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
3697 "WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
3698 "WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
3699 "WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
3702 async function exitFullscreen(win) {
3704 // Use a timed promise to abort if no window manager is present
3705 await new lazy.TimedPromise(
3707 cb = new lazy.DebounceCallback(resolve);
3708 win.addEventListener("sizemodechange", cb);
3709 win.fullScreen = false;
3711 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3713 win.removeEventListener("sizemodechange", cb);
3714 await new lazy.IdlePromise(win);
3717 async function restoreWindow(win) {
3719 if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
3722 // Use a timed promise to abort if no window manager is present
3723 await new lazy.TimedPromise(
3725 cb = new lazy.DebounceCallback(resolve);
3726 win.addEventListener("sizemodechange", cb);
3729 { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3731 win.removeEventListener("sizemodechange", cb);
3732 await new lazy.IdlePromise(win);