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