Bug 1814798 - pt 1. Add bool to enable/disable PHC at runtime r=glandium
[gecko.git] / remote / marionette / driver.sys.mjs
blob125bfa89d47c7a5506d46754096bb4f52bc008ee
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   atom: "chrome://remote/content/marionette/atom.sys.mjs",
12   browser: "chrome://remote/content/marionette/browser.sys.mjs",
13   capture: "chrome://remote/content/shared/Capture.sys.mjs",
14   Context: "chrome://remote/content/marionette/browser.sys.mjs",
15   cookie: "chrome://remote/content/marionette/cookie.sys.mjs",
16   DebounceCallback: "chrome://remote/content/marionette/sync.sys.mjs",
17   disableEventsActor:
18     "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
19   dom: "chrome://remote/content/shared/DOM.sys.mjs",
20   enableEventsActor:
21     "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
22   error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
23   EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
24   getMarionetteCommandsActorProxy:
25     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
26   IdlePromise: "chrome://remote/content/marionette/sync.sys.mjs",
27   l10n: "chrome://remote/content/marionette/l10n.sys.mjs",
28   Log: "chrome://remote/content/shared/Log.sys.mjs",
29   Marionette: "chrome://remote/content/components/Marionette.sys.mjs",
30   MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
31   modal: "chrome://remote/content/shared/Prompt.sys.mjs",
32   navigate: "chrome://remote/content/marionette/navigate.sys.mjs",
33   permissions: "chrome://remote/content/marionette/permissions.sys.mjs",
34   pprint: "chrome://remote/content/shared/Format.sys.mjs",
35   print: "chrome://remote/content/shared/PDF.sys.mjs",
36   PromptListener:
37     "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
38   quit: "chrome://remote/content/shared/Browser.sys.mjs",
39   reftest: "chrome://remote/content/marionette/reftest.sys.mjs",
40   registerCommandsActor:
41     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
42   RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
43   ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
44   TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
45   TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
46   Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
47   UnhandledPromptBehavior:
48     "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
49   unregisterCommandsActor:
50     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
51   waitForInitialNavigationCompleted:
52     "chrome://remote/content/shared/Navigate.sys.mjs",
53   webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs",
54   WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
55   WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
56   windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
57   WindowState: "chrome://remote/content/marionette/browser.sys.mjs",
58 });
60 ChromeUtils.defineLazyGetter(lazy, "logger", () =>
61   lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
64 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
66 ChromeUtils.defineLazyGetter(
67   lazy,
68   "supportedStrategies",
69   () =>
70     new Set([
71       lazy.dom.Strategy.ClassName,
72       lazy.dom.Strategy.Selector,
73       lazy.dom.Strategy.ID,
74       lazy.dom.Strategy.Name,
75       lazy.dom.Strategy.LinkText,
76       lazy.dom.Strategy.PartialLinkText,
77       lazy.dom.Strategy.TagName,
78       lazy.dom.Strategy.XPath,
79     ])
82 // Timeout used to abort fullscreen, maximize, and minimize
83 // commands if no window manager is present.
84 const TIMEOUT_NO_WINDOW_MANAGER = 5000;
86 // Observer topic to wait for until the browser window is ready.
87 const TOPIC_BROWSER_READY = "browser-delayed-startup-finished";
88 // Observer topic to perform clean up when application quit is requested.
89 const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested";
91 /**
92  * The Marionette WebDriver services provides a standard conforming
93  * implementation of the W3C WebDriver specification.
94  *
95  * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
96  * @namespace driver
97  */
99 /**
100  * Implements (parts of) the W3C WebDriver protocol.  GeckoDriver lives
101  * in chrome space and mediates calls to the current browsing context's actor.
103  * Throughout this prototype, functions with the argument <var>cmd</var>'s
104  * documentation refers to the contents of the <code>cmd.parameter</code>
105  * object.
107  * @class GeckoDriver
109  * @param {MarionetteServer} server
110  *     The instance of Marionette server.
111  */
112 export function GeckoDriver(server) {
113   this._server = server;
115   // WebDriver Session
116   this._currentSession = null;
118   this.browsers = {};
120   // points to current browser
121   this.curBrowser = null;
122   // top-most chrome window
123   this.mainFrame = null;
125   // Use content context by default
126   this.context = lazy.Context.Content;
128   // used for modal dialogs
129   this.dialog = null;
130   this.promptListener = null;
134  * The current context decides if commands are executed in chrome- or
135  * content space.
136  */
137 Object.defineProperty(GeckoDriver.prototype, "context", {
138   get() {
139     return this._context;
140   },
142   set(context) {
143     this._context = lazy.Context.fromString(context);
144   },
148  * The current WebDriver Session.
149  */
150 Object.defineProperty(GeckoDriver.prototype, "currentSession", {
151   get() {
152     if (lazy.RemoteAgent.webDriverBiDi) {
153       return lazy.RemoteAgent.webDriverBiDi.session;
154     }
156     return this._currentSession;
157   },
161  * Returns the current URL of the ChromeWindow or content browser,
162  * depending on context.
164  * @returns {URL}
165  *     Read-only property containing the currently loaded URL.
166  */
167 Object.defineProperty(GeckoDriver.prototype, "currentURL", {
168   get() {
169     const browsingContext = this.getBrowsingContext({ top: true });
170     return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
171   },
175  * Returns the title of the ChromeWindow or content browser,
176  * depending on context.
178  * @returns {string}
179  *     Read-only property containing the title of the loaded URL.
180  */
181 Object.defineProperty(GeckoDriver.prototype, "title", {
182   get() {
183     const browsingContext = this.getBrowsingContext({ top: true });
184     return browsingContext.currentWindowGlobal.documentTitle;
185   },
188 Object.defineProperty(GeckoDriver.prototype, "windowType", {
189   get() {
190     return this.curBrowser.window.document.documentElement.getAttribute(
191       "windowtype"
192     );
193   },
196 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
197   "nsIObserver",
198   "nsISupportsWeakReference",
202  * Callback used to observe the closing of modal dialogs
203  * during the session's lifetime.
204  */
205 GeckoDriver.prototype.handleClosedModalDialog = function () {
206   this.dialog = null;
210  * Callback used to observe the creation of new modal dialogs
211  * during the session's lifetime.
212  */
213 GeckoDriver.prototype.handleOpenModalDialog = function (eventName, data) {
214   this.dialog = data.prompt;
215   this.getActor().notifyDialogOpened();
219  * Get the current visible URL.
220  */
221 GeckoDriver.prototype._getCurrentURL = function () {
222   const browsingContext = this.getBrowsingContext({ top: true });
223   return new URL(browsingContext.currentURI.spec);
227  * Get the current "MarionetteCommands" parent actor.
229  * @param {object} options
230  * @param {boolean=} options.top
231  *     If set to true use the window's top-level browsing context for the actor,
232  *     otherwise the one from the currently selected frame. Defaults to false.
234  * @returns {MarionetteCommandsParent}
235  *     The parent actor.
236  */
237 GeckoDriver.prototype.getActor = function (options = {}) {
238   return lazy.getMarionetteCommandsActorProxy(() =>
239     this.getBrowsingContext(options)
240   );
244  * Get the selected BrowsingContext for the current context.
246  * @param {object} options
247  * @param {Context=} options.context
248  *     Context (content or chrome) for which to retrieve the browsing context.
249  *     Defaults to the current one.
250  * @param {boolean=} options.parent
251  *     If set to true return the window's parent browsing context,
252  *     otherwise the one from the currently selected frame. Defaults to false.
253  * @param {boolean=} options.top
254  *     If set to true return the window's top-level browsing context,
255  *     otherwise the one from the currently selected frame. Defaults to false.
257  * @returns {BrowsingContext}
258  *     The browsing context, or `null` if none is available
259  */
260 GeckoDriver.prototype.getBrowsingContext = function (options = {}) {
261   const { context = this.context, parent = false, top = false } = options;
263   let browsingContext = null;
264   if (context === lazy.Context.Chrome) {
265     browsingContext = this.currentSession?.chromeBrowsingContext;
266   } else {
267     browsingContext = this.currentSession?.contentBrowsingContext;
268   }
270   if (browsingContext && parent) {
271     browsingContext = browsingContext.parent;
272   }
274   if (browsingContext && top) {
275     browsingContext = browsingContext.top;
276   }
278   return browsingContext;
282  * Get the currently selected window.
284  * It will return the outer {@link ChromeWindow} previously selected by
285  * window handle through {@link #switchToWindow}, or the first window that
286  * was registered.
288  * @param {object} options
289  * @param {Context=} options.context
290  *     Optional name of the context to use for finding the window.
291  *     It will be required if a command always needs a specific context,
292  *     whether which context is currently set. Defaults to the current
293  *     context.
295  * @returns {ChromeWindow}
296  *     The current top-level browsing context.
297  */
298 GeckoDriver.prototype.getCurrentWindow = function (options = {}) {
299   const { context = this.context } = options;
301   let win = null;
302   switch (context) {
303     case lazy.Context.Chrome:
304       if (this.curBrowser) {
305         win = this.curBrowser.window;
306       }
307       break;
309     case lazy.Context.Content:
310       if (this.curBrowser && this.curBrowser.contentBrowser) {
311         win = this.curBrowser.window;
312       }
313       break;
314   }
316   return win;
319 GeckoDriver.prototype.isReftestBrowser = function (element) {
320   return (
321     this._reftest &&
322     element &&
323     element.tagName === "xul:browser" &&
324     element.parentElement &&
325     element.parentElement.id === "reftest"
326   );
330  * Create a new browsing context for window and add to known browsers.
332  * @param {ChromeWindow} win
333  *     Window for which we will create a browsing context.
335  * @returns {string}
336  *     Returns the unique server-assigned ID of the window.
337  */
338 GeckoDriver.prototype.addBrowser = function (win) {
339   let context = new lazy.browser.Context(win, this);
340   let winId = lazy.windowManager.getIdForWindow(win);
342   this.browsers[winId] = context;
343   this.curBrowser = this.browsers[winId];
347  * Recursively get all labeled text.
349  * @param {Element} el
350  *     The parent element.
351  * @param {Array.<string>} lines
352  *      Array that holds the text lines.
353  */
354 GeckoDriver.prototype.getVisibleText = function (el, lines) {
355   try {
356     if (lazy.atom.isElementDisplayed(el, this.getCurrentWindow())) {
357       if (el.value) {
358         lines.push(el.value);
359       }
360       for (let child in el.childNodes) {
361         this.getVisibleText(el.childNodes[child], lines);
362       }
363     }
364   } catch (e) {
365     if (el.nodeName == "#text") {
366       lines.push(el.textContent);
367     }
368   }
372  * Handles registration of new content browsers.  Depending on
373  * their type they are either accepted or ignored.
375  * @param {XULBrowser} browserElement
376  */
377 GeckoDriver.prototype.registerBrowser = function (browserElement) {
378   // We want to ignore frames that are XUL browsers that aren't in the "main"
379   // tabbrowser, but accept things on Fennec (which doesn't have a
380   // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
381   // as well as XUL frames. Ideally this should be cleaned up and we should
382   // keep track of browsers a different way.
383   if (
384     !lazy.AppInfo.isFirefox ||
385     browserElement.namespaceURI != XUL_NS ||
386     browserElement.nodeName != "browser" ||
387     browserElement.getTabBrowser()
388   ) {
389     this.curBrowser.register(browserElement);
390   }
394  * Create a new WebDriver session.
396  * @param {object} cmd
397  * @param {Object<string, *>=} cmd.parameters
398  *     JSON Object containing any of the recognised capabilities as listed
399  *     on the `WebDriverSession` class.
401  * @returns {object}
402  *     Session ID and capabilities offered by the WebDriver service.
404  * @throws {SessionNotCreatedError}
405  *     If, for whatever reason, a session could not be created.
406  */
407 GeckoDriver.prototype.newSession = async function (cmd) {
408   if (this.currentSession) {
409     throw new lazy.error.SessionNotCreatedError(
410       "Maximum number of active sessions"
411     );
412   }
414   const { parameters: capabilities } = cmd;
416   try {
417     // If the WebDriver BiDi protocol is active always use the Remote Agent
418     // to handle the WebDriver session. If it's not the case then Marionette
419     // itself needs to handle it, and has to nullify the "webSocketUrl"
420     // capability.
421     if (lazy.RemoteAgent.webDriverBiDi) {
422       await lazy.RemoteAgent.webDriverBiDi.createSession(capabilities);
423     } else {
424       this._currentSession = new lazy.WebDriverSession(capabilities);
425       this._currentSession.capabilities.delete("webSocketUrl");
426     }
428     // Don't wait for the initial window when Marionette is in windowless mode
429     if (!this.currentSession.capabilities.get("moz:windowless")) {
430       // Creating a WebDriver session too early can cause issues with
431       // clients in not being able to find any available window handle.
432       // Also when closing the application while it's still starting up can
433       // cause shutdown hangs. As such Marionette will return a new session
434       // once the initial application window has finished initializing.
435       lazy.logger.debug(`Waiting for initial application window`);
436       await lazy.Marionette.browserStartupFinished;
438       const appWin =
439         await lazy.windowManager.waitForInitialApplicationWindowLoaded();
441       if (lazy.MarionettePrefs.clickToStart) {
442         Services.prompt.alert(
443           appWin,
444           "",
445           "Click to start execution of marionette tests"
446         );
447       }
449       this.addBrowser(appWin);
450       this.mainFrame = appWin;
452       // Setup observer for modal dialogs
453       this.promptListener = new lazy.PromptListener(() => this.curBrowser);
454       this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
455       this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
456       this.promptListener.startListening();
458       for (let win of lazy.windowManager.windows) {
459         this.registerWindow(win, { registerBrowsers: true });
460       }
462       if (this.mainFrame) {
463         this.currentSession.chromeBrowsingContext =
464           this.mainFrame.browsingContext;
465         this.mainFrame.focus();
466       }
468       if (this.curBrowser.tab) {
469         const browsingContext = this.curBrowser.contentBrowser.browsingContext;
470         this.currentSession.contentBrowsingContext = browsingContext;
472         // Bug 1838381 - Only use a longer unload timeout for desktop, because
473         // on Android only the initial document is loaded, and loading a
474         // specific page during startup doesn't succeed.
475         const options = {};
476         if (!lazy.AppInfo.isAndroid) {
477           options.unloadTimeout = 5000;
478         }
480         await lazy.waitForInitialNavigationCompleted(
481           browsingContext.webProgress,
482           options
483         );
485         this.curBrowser.contentBrowser.focus();
486       }
488       // Check if there is already an open dialog for the selected browser window.
489       this.dialog = lazy.modal.findPrompt(this.curBrowser);
490     }
492     lazy.registerCommandsActor(this.currentSession.id);
493     lazy.enableEventsActor();
495     Services.obs.addObserver(this, TOPIC_BROWSER_READY);
496   } catch (e) {
497     throw new lazy.error.SessionNotCreatedError(e);
498   }
500   return {
501     sessionId: this.currentSession.id,
502     capabilities: this.currentSession.capabilities,
503   };
507  * Start observing the specified window.
509  * @param {ChromeWindow} win
510  *     Chrome window to register event listeners for.
511  * @param {object=} options
512  * @param {boolean=} options.registerBrowsers
513  *     If true, register all content browsers of found tabs. Defaults to false.
514  */
515 GeckoDriver.prototype.registerWindow = function (win, options = {}) {
516   const { registerBrowsers = false } = options;
517   const tabBrowser = lazy.TabManager.getTabBrowser(win);
519   if (registerBrowsers && tabBrowser) {
520     for (const tab of tabBrowser.tabs) {
521       const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
522       this.registerBrowser(contentBrowser);
523     }
524   }
526   // Listen for any kind of top-level process switch
527   tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
531  * Stop observing the specified window.
533  * @param {ChromeWindow} win
534  *     Chrome window to unregister event listeners for.
535  */
536 GeckoDriver.prototype.stopObservingWindow = function (win) {
537   const tabBrowser = lazy.TabManager.getTabBrowser(win);
539   tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
542 GeckoDriver.prototype.handleEvent = function ({ target, type }) {
543   switch (type) {
544     case "XULFrameLoaderCreated":
545       if (target === this.curBrowser.contentBrowser) {
546         lazy.logger.trace(
547           "Remoteness change detected. Set new top-level browsing context " +
548             `to ${target.browsingContext.id}`
549         );
551         this.currentSession.contentBrowsingContext = target.browsingContext;
552       }
553       break;
554   }
557 GeckoDriver.prototype.observe = async function (subject, topic, data) {
558   switch (topic) {
559     case TOPIC_BROWSER_READY:
560       this.registerWindow(subject);
561       break;
563     case TOPIC_QUIT_APPLICATION_REQUESTED:
564       // Run Marionette specific cleanup steps before allowing
565       // the application to shutdown
566       await this._server.setAcceptConnections(false);
567       this.deleteSession();
568       break;
569   }
573  * Send the current session's capabilities to the client.
575  * Capabilities informs the client of which WebDriver features are
576  * supported by Firefox and Marionette.  They are immutable for the
577  * length of the session.
579  * The return value is an immutable map of string keys
580  * ("capabilities") to values, which may be of types boolean,
581  * numerical or string.
582  */
583 GeckoDriver.prototype.getSessionCapabilities = function () {
584   return { capabilities: this.currentSession.capabilities };
588  * Sets the context of the subsequent commands.
590  * All subsequent requests to commands that in some way involve
591  * interaction with a browsing context will target the chosen browsing
592  * context.
594  * @param {object} cmd
595  * @param {string} cmd.parameters.value
596  *     Name of the context to be switched to.  Must be one of "chrome" or
597  *     "content".
599  * @throws {InvalidArgumentError}
600  *     If <var>value</var> is not a string.
601  * @throws {WebDriverError}
602  *     If <var>value</var> is not a valid browsing context.
603  */
604 GeckoDriver.prototype.setContext = function (cmd) {
605   let value = lazy.assert.string(cmd.parameters.value);
607   this.context = value;
611  * Gets the context type that is Marionette's current target for
612  * browsing context scoped commands.
614  * You may choose a context through the {@link #setContext} command.
616  * The default browsing context is {@link Context.Content}.
618  * @returns {Context}
619  *     Current context.
620  */
621 GeckoDriver.prototype.getContext = function () {
622   return this.context;
626  * Executes a JavaScript function in the context of the current browsing
627  * context, if in content space, or in chrome space otherwise, and returns
628  * the return value of the function.
630  * It is important to note that if the <var>sandboxName</var> parameter
631  * is left undefined, the script will be evaluated in a mutable sandbox,
632  * causing any change it makes on the global state of the document to have
633  * lasting side-effects.
635  * @param {object} cmd
636  * @param {string} cmd.parameters.script
637  *     Script to evaluate as a function body.
638  * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
639  *     Arguments exposed to the script in <code>arguments</code>.
640  *     The array items must be serialisable to the WebDriver protocol.
641  * @param {string=} cmd.parameters.sandbox
642  *     Name of the sandbox to evaluate the script in.  The sandbox is
643  *     cached for later re-use on the same Window object if
644  *     <var>newSandbox</var> is false.  If he parameter is undefined,
645  *     the script is evaluated in a mutable sandbox.  If the parameter
646  *     is "system", it will be evaluted in a sandbox with elevated system
647  *     privileges, equivalent to chrome space.
648  * @param {boolean=} cmd.parameters.newSandbox
649  *     Forces the script to be evaluated in a fresh sandbox.  Note that if
650  *     it is undefined, the script will normally be evaluted in a fresh
651  *     sandbox.
652  * @param {string=} cmd.parameters.filename
653  *     Filename of the client's program where this script is evaluated.
654  * @param {number=} cmd.parameters.line
655  *     Line in the client's program where this script is evaluated.
657  * @returns {(string|boolean|number|object|WebReference)}
658  *     Return value from the script, or null which signifies either the
659  *     JavaScript notion of null or undefined.
661  * @throws {JavaScriptError}
662  *     If an {@link Error} was thrown whilst evaluating the script.
663  * @throws {NoSuchElementError}
664  *     If an element that was passed as part of <var>args</var> is unknown.
665  * @throws {NoSuchWindowError}
666  *     Browsing context has been discarded.
667  * @throws {ScriptTimeoutError}
668  *     If the script was interrupted due to reaching the session's
669  *     script timeout.
670  * @throws {StaleElementReferenceError}
671  *     If an element that was passed as part of <var>args</var> or that is
672  *     returned as result has gone stale.
673  */
674 GeckoDriver.prototype.executeScript = function (cmd) {
675   let { script, args } = cmd.parameters;
676   let opts = {
677     script: cmd.parameters.script,
678     args: cmd.parameters.args,
679     sandboxName: cmd.parameters.sandbox,
680     newSandbox: cmd.parameters.newSandbox,
681     file: cmd.parameters.filename,
682     line: cmd.parameters.line,
683   };
685   return this.execute_(script, args, opts);
689  * Executes a JavaScript function in the context of the current browsing
690  * context, if in content space, or in chrome space otherwise, and returns
691  * the object passed to the callback.
693  * The callback is always the last argument to the <var>arguments</var>
694  * list passed to the function scope of the script.  It can be retrieved
695  * as such:
697  * <pre><code>
698  *     let callback = arguments[arguments.length - 1];
699  *     callback("foo");
700  *     // "foo" is returned
701  * </code></pre>
703  * It is important to note that if the <var>sandboxName</var> parameter
704  * is left undefined, the script will be evaluated in a mutable sandbox,
705  * causing any change it makes on the global state of the document to have
706  * lasting side-effects.
708  * @param {object} cmd
709  * @param {string} cmd.parameters.script
710  *     Script to evaluate as a function body.
711  * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
712  *     Arguments exposed to the script in <code>arguments</code>.
713  *     The array items must be serialisable to the WebDriver protocol.
714  * @param {string=} cmd.parameters.sandbox
715  *     Name of the sandbox to evaluate the script in.  The sandbox is
716  *     cached for later re-use on the same Window object if
717  *     <var>newSandbox</var> is false.  If the parameter is undefined,
718  *     the script is evaluated in a mutable sandbox.  If the parameter
719  *     is "system", it will be evaluted in a sandbox with elevated system
720  *     privileges, equivalent to chrome space.
721  * @param {boolean=} cmd.parameters.newSandbox
722  *     Forces the script to be evaluated in a fresh sandbox.  Note that if
723  *     it is undefined, the script will normally be evaluted in a fresh
724  *     sandbox.
725  * @param {string=} cmd.parameters.filename
726  *     Filename of the client's program where this script is evaluated.
727  * @param {number=} cmd.parameters.line
728  *     Line in the client's program where this script is evaluated.
730  * @returns {(string|boolean|number|object|WebReference)}
731  *     Return value from the script, or null which signifies either the
732  *     JavaScript notion of null or undefined.
734  * @throws {JavaScriptError}
735  *     If an Error was thrown whilst evaluating the script.
736  * @throws {NoSuchElementError}
737  *     If an element that was passed as part of <var>args</var> is unknown.
738  * @throws {NoSuchWindowError}
739  *     Browsing context has been discarded.
740  * @throws {ScriptTimeoutError}
741  *     If the script was interrupted due to reaching the session's
742  *     script timeout.
743  * @throws {StaleElementReferenceError}
744  *     If an element that was passed as part of <var>args</var> or that is
745  *     returned as result has gone stale.
746  */
747 GeckoDriver.prototype.executeAsyncScript = function (cmd) {
748   let { script, args } = cmd.parameters;
749   let opts = {
750     script: cmd.parameters.script,
751     args: cmd.parameters.args,
752     sandboxName: cmd.parameters.sandbox,
753     newSandbox: cmd.parameters.newSandbox,
754     file: cmd.parameters.filename,
755     line: cmd.parameters.line,
756     async: true,
757   };
759   return this.execute_(script, args, opts);
762 GeckoDriver.prototype.execute_ = async function (
763   script,
764   args = [],
765   {
766     sandboxName = null,
767     newSandbox = false,
768     file = "",
769     line = 0,
770     async = false,
771   } = {}
772 ) {
773   lazy.assert.open(this.getBrowsingContext());
774   await this._handleUserPrompts();
776   lazy.assert.string(
777     script,
778     lazy.pprint`Expected "script" to be a string: ${script}`
779   );
780   lazy.assert.array(
781     args,
782     lazy.pprint`Expected script args to be an array: ${args}`
783   );
784   if (sandboxName !== null) {
785     lazy.assert.string(
786       sandboxName,
787       lazy.pprint`Expected sandbox name to be a string: ${sandboxName}`
788     );
789   }
790   lazy.assert.boolean(
791     newSandbox,
792     lazy.pprint`Expected newSandbox to be boolean: ${newSandbox}`
793   );
794   lazy.assert.string(file, lazy.pprint`Expected file to be a string: ${file}`);
795   lazy.assert.number(line, lazy.pprint`Expected line to be a number: ${line}`);
797   let opts = {
798     timeout: this.currentSession.timeouts.script,
799     sandboxName,
800     newSandbox,
801     file,
802     line,
803     async,
804   };
806   return this.getActor().executeScript(script, args, opts);
810  * Navigate to given URL.
812  * Navigates the current browsing context to the given URL and waits for
813  * the document to load or the session's page timeout duration to elapse
814  * before returning.
816  * The command will return with a failure if there is an error loading
817  * the document or the URL is blocked.  This can occur if it fails to
818  * reach host, the URL is malformed, or if there is a certificate issue
819  * to name some examples.
821  * The document is considered successfully loaded when the
822  * DOMContentLoaded event on the frame element associated with the
823  * current window triggers and document.readyState is "complete".
825  * In chrome context it will change the current window's location to
826  * the supplied URL and wait until document.readyState equals "complete"
827  * or the page timeout duration has elapsed.
829  * @param {object} cmd
830  * @param {string} cmd.parameters.url
831  *     URL to navigate to.
833  * @throws {NoSuchWindowError}
834  *     Top-level browsing context has been discarded.
835  * @throws {UnexpectedAlertOpenError}
836  *     A modal dialog is open, blocking this operation.
837  * @throws {UnsupportedOperationError}
838  *     Not available in current context.
839  */
840 GeckoDriver.prototype.navigateTo = async function (cmd) {
841   lazy.assert.content(this.context);
842   const browsingContext = lazy.assert.open(
843     this.getBrowsingContext({ top: true })
844   );
845   await this._handleUserPrompts();
847   let validURL;
848   try {
849     validURL = new URL(cmd.parameters.url);
850   } catch (e) {
851     throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
852   }
854   // Switch to the top-level browsing context before navigating
855   this.currentSession.contentBrowsingContext = browsingContext;
857   const loadEventExpected = lazy.navigate.isLoadEventExpected(
858     this._getCurrentURL(),
859     {
860       future: validURL,
861     }
862   );
864   await lazy.navigate.waitForNavigationCompleted(
865     this,
866     () => {
867       lazy.navigate.navigateTo(browsingContext, validURL);
868     },
869     { loadEventExpected }
870   );
872   this.curBrowser.contentBrowser.focus();
876  * Get a string representing the current URL.
878  * On Desktop this returns a string representation of the URL of the
879  * current top level browsing context.  This is equivalent to
880  * document.location.href.
882  * When in the context of the chrome, this returns the canonical URL
883  * of the current resource.
885  * @throws {NoSuchWindowError}
886  *     Top-level browsing context has been discarded.
887  * @throws {UnexpectedAlertOpenError}
888  *     A modal dialog is open, blocking this operation.
889  */
890 GeckoDriver.prototype.getCurrentUrl = async function () {
891   lazy.assert.open(this.getBrowsingContext({ top: true }));
892   await this._handleUserPrompts();
894   return this._getCurrentURL().href;
898  * Gets the current title of the window.
900  * @returns {string}
901  *     Document title of the top-level browsing context.
903  * @throws {NoSuchWindowError}
904  *     Top-level browsing context has been discarded.
905  * @throws {UnexpectedAlertOpenError}
906  *     A modal dialog is open, blocking this operation.
907  */
908 GeckoDriver.prototype.getTitle = async function () {
909   lazy.assert.open(this.getBrowsingContext({ top: true }));
910   await this._handleUserPrompts();
912   return this.title;
916  * Gets the current type of the window.
918  * @returns {string}
919  *     Type of window
921  * @throws {NoSuchWindowError}
922  *     Top-level browsing context has been discarded.
923  */
924 GeckoDriver.prototype.getWindowType = function () {
925   lazy.assert.open(this.getBrowsingContext({ top: true }));
927   return this.windowType;
931  * Gets the page source of the content document.
933  * @returns {string}
934  *     String serialisation of the DOM of the current browsing context's
935  *     active document.
937  * @throws {NoSuchWindowError}
938  *     Browsing context has been discarded.
939  * @throws {UnexpectedAlertOpenError}
940  *     A modal dialog is open, blocking this operation.
941  */
942 GeckoDriver.prototype.getPageSource = async function () {
943   lazy.assert.open(this.getBrowsingContext());
944   await this._handleUserPrompts();
946   return this.getActor().getPageSource();
950  * Cause the browser to traverse one step backward in the joint history
951  * of the current browsing context.
953  * @throws {NoSuchWindowError}
954  *     Top-level browsing context has been discarded.
955  * @throws {UnexpectedAlertOpenError}
956  *     A modal dialog is open, blocking this operation.
957  * @throws {UnsupportedOperationError}
958  *     Not available in current context.
959  */
960 GeckoDriver.prototype.goBack = async function () {
961   lazy.assert.content(this.context);
962   const browsingContext = lazy.assert.open(
963     this.getBrowsingContext({ top: true })
964   );
965   await this._handleUserPrompts();
967   // If there is no history, just return
968   if (!browsingContext.embedderElement?.canGoBack) {
969     return;
970   }
972   await lazy.navigate.waitForNavigationCompleted(this, () => {
973     browsingContext.goBack();
974   });
978  * Cause the browser to traverse one step forward in the joint history
979  * of the current browsing context.
981  * @throws {NoSuchWindowError}
982  *     Top-level browsing context has been discarded.
983  * @throws {UnexpectedAlertOpenError}
984  *     A modal dialog is open, blocking this operation.
985  * @throws {UnsupportedOperationError}
986  *     Not available in current context.
987  */
988 GeckoDriver.prototype.goForward = async function () {
989   lazy.assert.content(this.context);
990   const browsingContext = lazy.assert.open(
991     this.getBrowsingContext({ top: true })
992   );
993   await this._handleUserPrompts();
995   // If there is no history, just return
996   if (!browsingContext.embedderElement?.canGoForward) {
997     return;
998   }
1000   await lazy.navigate.waitForNavigationCompleted(this, () => {
1001     browsingContext.goForward();
1002   });
1006  * Causes the browser to reload the page in current top-level browsing
1007  * context.
1009  * @throws {NoSuchWindowError}
1010  *     Top-level browsing context has been discarded.
1011  * @throws {UnexpectedAlertOpenError}
1012  *     A modal dialog is open, blocking this operation.
1013  * @throws {UnsupportedOperationError}
1014  *     Not available in current context.
1015  */
1016 GeckoDriver.prototype.refresh = async function () {
1017   lazy.assert.content(this.context);
1018   const browsingContext = lazy.assert.open(
1019     this.getBrowsingContext({ top: true })
1020   );
1021   await this._handleUserPrompts();
1023   // Switch to the top-level browsing context before navigating
1024   this.currentSession.contentBrowsingContext = browsingContext;
1026   await lazy.navigate.waitForNavigationCompleted(this, () => {
1027     lazy.navigate.refresh(browsingContext);
1028   });
1032  * Get the current window's handle. On desktop this typically corresponds
1033  * to the currently selected tab.
1035  * For chrome scope it returns the window identifier for the current chrome
1036  * window for tests interested in managing the chrome window and tab separately.
1038  * Return an opaque server-assigned identifier to this window that
1039  * uniquely identifies it within this Marionette instance.  This can
1040  * be used to switch to this window at a later point.
1042  * @returns {string}
1043  *     Unique window handle.
1045  * @throws {NoSuchWindowError}
1046  *     Top-level browsing context has been discarded.
1047  */
1048 GeckoDriver.prototype.getWindowHandle = function () {
1049   lazy.assert.open(this.getBrowsingContext({ top: true }));
1051   if (this.context == lazy.Context.Chrome) {
1052     return lazy.windowManager.getIdForWindow(this.curBrowser.window);
1053   }
1054   return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
1058  * Get a list of top-level browsing contexts. On desktop this typically
1059  * corresponds to the set of open tabs for browser windows, or the window
1060  * itself for non-browser chrome windows.
1062  * For chrome scope it returns identifiers for each open chrome window for
1063  * tests interested in managing a set of chrome windows and tabs separately.
1065  * Each window handle is assigned by the server and is guaranteed unique,
1066  * however the return array does not have a specified ordering.
1068  * @returns {Array.<string>}
1069  *     Unique window handles.
1070  */
1071 GeckoDriver.prototype.getWindowHandles = function () {
1072   if (this.context == lazy.Context.Chrome) {
1073     return lazy.windowManager.chromeWindowHandles.map(String);
1074   }
1075   return lazy.TabManager.allBrowserUniqueIds.map(String);
1079  * Get the current position and size of the browser window currently in focus.
1081  * Will return the current browser window size in pixels. Refers to
1082  * window outerWidth and outerHeight values, which include scroll bars,
1083  * title bars, etc.
1085  * @returns {Object<string, number>}
1086  *     Object with |x| and |y| coordinates, and |width| and |height|
1087  *     of browser window.
1089  * @throws {NoSuchWindowError}
1090  *     Top-level browsing context has been discarded.
1091  * @throws {UnexpectedAlertOpenError}
1092  *     A modal dialog is open, blocking this operation.
1093  */
1094 GeckoDriver.prototype.getWindowRect = async function () {
1095   lazy.assert.open(this.getBrowsingContext({ top: true }));
1096   await this._handleUserPrompts();
1098   return this.curBrowser.rect;
1102  * Set the window position and size of the browser on the operating
1103  * system window manager.
1105  * The supplied `width` and `height` values refer to the window `outerWidth`
1106  * and `outerHeight` values, which include browser chrome and OS-level
1107  * window borders.
1109  * @param {object} cmd
1110  * @param {number} cmd.parameters.x
1111  *     X coordinate of the top/left of the window that it will be
1112  *     moved to.
1113  * @param {number} cmd.parameters.y
1114  *     Y coordinate of the top/left of the window that it will be
1115  *     moved to.
1116  * @param {number} cmd.parameters.width
1117  *     Width to resize the window to.
1118  * @param {number} cmd.parameters.height
1119  *     Height to resize the window to.
1121  * @returns {Object<string, number>}
1122  *     Object with `x` and `y` coordinates and `width` and `height`
1123  *     dimensions.
1125  * @throws {NoSuchWindowError}
1126  *     Top-level browsing context has been discarded.
1127  * @throws {UnexpectedAlertOpenError}
1128  *     A modal dialog is open, blocking this operation.
1129  * @throws {UnsupportedOperationError}
1130  *     Not applicable to application.
1131  */
1132 GeckoDriver.prototype.setWindowRect = async function (cmd) {
1133   lazy.assert.desktop();
1134   lazy.assert.open(this.getBrowsingContext({ top: true }));
1135   await this._handleUserPrompts();
1137   const { x = null, y = null, width = null, height = null } = cmd.parameters;
1138   if (x !== null) {
1139     lazy.assert.integer(x);
1140   }
1141   if (y !== null) {
1142     lazy.assert.integer(y);
1143   }
1144   if (height !== null) {
1145     lazy.assert.positiveInteger(height);
1146   }
1147   if (width !== null) {
1148     lazy.assert.positiveInteger(width);
1149   }
1151   const win = this.getCurrentWindow();
1152   switch (lazy.WindowState.from(win.windowState)) {
1153     case lazy.WindowState.Fullscreen:
1154       await exitFullscreen(win);
1155       break;
1157     case lazy.WindowState.Maximized:
1158     case lazy.WindowState.Minimized:
1159       await restoreWindow(win);
1160       break;
1161   }
1163   function geometryMatches() {
1164     if (
1165       width !== null &&
1166       height !== null &&
1167       (win.outerWidth !== width || win.outerHeight !== height)
1168     ) {
1169       return false;
1170     }
1171     if (x !== null && y !== null && (win.screenX !== x || win.screenY !== y)) {
1172       return false;
1173     }
1174     lazy.logger.trace(`Requested window geometry matches`);
1175     return true;
1176   }
1178   if (!geometryMatches()) {
1179     // There might be more than one resize or MozUpdateWindowPos event due
1180     // to previous geometry changes, such as from restoreWindow(), so
1181     // wait longer if window geometry does not match.
1182     const options = { checkFn: geometryMatches, timeout: 500 };
1183     const promises = [];
1184     if (width !== null && height !== null) {
1185       promises.push(new lazy.EventPromise(win, "resize", options));
1186       win.resizeTo(width, height);
1187     }
1188     if (x !== null && y !== null) {
1189       promises.push(
1190         new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
1191       );
1192       win.moveTo(x, y);
1193     }
1194     try {
1195       await Promise.race(promises);
1196     } catch (e) {
1197       if (e instanceof lazy.error.TimeoutError) {
1198         // The operating system might not honor the move or resize, in which
1199         // case assume that geometry will have been adjusted "as close as
1200         // possible" to that requested.  There may be no event received if the
1201         // geometry is already as close as possible.
1202       } else {
1203         throw e;
1204       }
1205     }
1206   }
1208   return this.curBrowser.rect;
1212  * Switch current top-level browsing context by name or server-assigned
1213  * ID.  Searches for windows by name, then ID.  Content windows take
1214  * precedence.
1216  * @param {object} cmd
1217  * @param {string} cmd.parameters.handle
1218  *     Handle of the window to switch to.
1219  * @param {boolean=} cmd.parameters.focus
1220  *     A boolean value which determines whether to focus
1221  *     the window. Defaults to true.
1223  * @throws {InvalidArgumentError}
1224  *     If <var>handle</var> is not a string or <var>focus</var> not a boolean.
1225  * @throws {NoSuchWindowError}
1226  *     Top-level browsing context has been discarded.
1227  */
1228 GeckoDriver.prototype.switchToWindow = async function (cmd) {
1229   const { focus = true, handle } = cmd.parameters;
1231   lazy.assert.string(
1232     handle,
1233     lazy.pprint`Expected "handle" to be a string, got ${handle}`
1234   );
1235   lazy.assert.boolean(
1236     focus,
1237     lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
1238   );
1240   const found = lazy.windowManager.findWindowByHandle(handle);
1242   let selected = false;
1243   if (found) {
1244     try {
1245       await this.setWindowHandle(found, focus);
1246       selected = true;
1247     } catch (e) {
1248       lazy.logger.error(e);
1249     }
1250   }
1252   if (!selected) {
1253     throw new lazy.error.NoSuchWindowError(
1254       `Unable to locate window: ${handle}`
1255     );
1256   }
1260  * Switch the marionette window to a given window. If the browser in
1261  * the window is unregistered, register that browser and wait for
1262  * the registration is complete. If |focus| is true then set the focus
1263  * on the window.
1265  * @param {object} winProperties
1266  *     Object containing window properties such as returned from
1267  *     :js:func:`GeckoDriver#getWindowProperties`
1268  * @param {boolean=} focus
1269  *     A boolean value which determines whether to focus the window.
1270  *     Defaults to true.
1271  */
1272 GeckoDriver.prototype.setWindowHandle = async function (
1273   winProperties,
1274   focus = true
1275 ) {
1276   if (!(winProperties.id in this.browsers)) {
1277     // Initialise Marionette if the current chrome window has not been seen
1278     // before. Also register the initial tab, if one exists.
1279     this.addBrowser(winProperties.win);
1280     this.mainFrame = winProperties.win;
1282     this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1284     if (!winProperties.hasTabBrowser) {
1285       this.currentSession.contentBrowsingContext = null;
1286     } else {
1287       const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
1289       // For chrome windows such as a reftest window, `getTabBrowser` is not
1290       // a tabbrowser, it is the content browser which should be used here.
1291       const contentBrowser = tabBrowser.tabs
1292         ? tabBrowser.selectedBrowser
1293         : tabBrowser;
1295       this.currentSession.contentBrowsingContext =
1296         contentBrowser.browsingContext;
1297       this.registerBrowser(contentBrowser);
1298     }
1299   } else {
1300     // Otherwise switch to the known chrome window
1301     this.curBrowser = this.browsers[winProperties.id];
1302     this.mainFrame = this.curBrowser.window;
1304     // Activate the tab if it's a content window.
1305     let tab = null;
1306     if (winProperties.hasTabBrowser) {
1307       tab = await this.curBrowser.switchToTab(
1308         winProperties.tabIndex,
1309         winProperties.win,
1310         focus
1311       );
1312     }
1314     this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1315     this.currentSession.contentBrowsingContext =
1316       tab?.linkedBrowser.browsingContext;
1317   }
1319   // Check for an existing dialog for the new window
1320   this.dialog = lazy.modal.findPrompt(this.curBrowser);
1322   // If there is an open window modal dialog the underlying chrome window
1323   // cannot be focused.
1324   if (focus && !this.dialog?.isWindowModal) {
1325     await this.curBrowser.focusWindow();
1326   }
1330  * Set the current browsing context for future commands to the parent
1331  * of the current browsing context.
1333  * @throws {NoSuchWindowError}
1334  *     Browsing context has been discarded.
1335  * @throws {UnexpectedAlertOpenError}
1336  *     A modal dialog is open, blocking this operation.
1337  */
1338 GeckoDriver.prototype.switchToParentFrame = async function () {
1339   let browsingContext = this.getBrowsingContext();
1340   if (browsingContext && !browsingContext.parent) {
1341     return;
1342   }
1344   browsingContext = lazy.assert.open(browsingContext?.parent);
1346   this.currentSession.contentBrowsingContext = browsingContext;
1350  * Switch to a given frame within the current window.
1352  * @param {object} cmd
1353  * @param {(string | object)=} cmd.parameters.element
1354  *     A web element reference of the frame or its element id.
1355  * @param {number=} cmd.parameters.id
1356  *     The index of the frame to switch to.
1357  *     If both element and id are not defined, switch to top-level frame.
1359  * @throws {NoSuchElementError}
1360  *     If element represented by reference <var>element</var> is unknown.
1361  * @throws {NoSuchWindowError}
1362  *     Browsing context has been discarded.
1363  * @throws {StaleElementReferenceError}
1364  *     If element represented by reference <var>element</var> has gone stale.
1365  * @throws {UnexpectedAlertOpenError}
1366  *     A modal dialog is open, blocking this operation.
1367  */
1368 GeckoDriver.prototype.switchToFrame = async function (cmd) {
1369   const { element: el, id } = cmd.parameters;
1371   if (typeof id == "number") {
1372     lazy.assert.unsignedShort(
1373       id,
1374       `Expected id to be unsigned short, got ${id}`
1375     );
1376   }
1378   const top = id == null && el == null;
1379   lazy.assert.open(this.getBrowsingContext({ top }));
1380   await this._handleUserPrompts();
1382   // Bug 1495063: Elements should be passed as WebReference reference
1383   let byFrame;
1384   if (typeof el == "string") {
1385     byFrame = lazy.WebElement.fromUUID(el).toJSON();
1386   } else if (el) {
1387     byFrame = el;
1388   }
1390   const { browsingContext } = await this.getActor({ top }).switchToFrame(
1391     byFrame || id
1392   );
1394   this.currentSession.contentBrowsingContext = browsingContext;
1397 GeckoDriver.prototype.getTimeouts = function () {
1398   return this.currentSession.timeouts;
1402  * Set timeout for page loading, searching, and scripts.
1404  * @param {object} cmd
1405  * @param {Object<string, number>} cmd.parameters
1406  *     Dictionary of timeout types and their new value, where all timeout
1407  *     types are optional.
1409  * @throws {InvalidArgumentError}
1410  *     If timeout type key is unknown, or the value provided with it is
1411  *     not an integer.
1412  */
1413 GeckoDriver.prototype.setTimeouts = function (cmd) {
1414   // merge with existing timeouts
1415   let merged = Object.assign(
1416     this.currentSession.timeouts.toJSON(),
1417     cmd.parameters
1418   );
1420   this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
1424  * Perform a series of grouped actions at the specified points in time.
1426  * @param {object} cmd
1427  * @param {Array<?>} cmd.parameters.actions
1428  *     Array of objects that each represent an action sequence.
1430  * @throws {NoSuchElementError}
1431  *     If an element that is used as part of the action chain is unknown.
1432  * @throws {NoSuchWindowError}
1433  *     Browsing context has been discarded.
1434  * @throws {StaleElementReferenceError}
1435  *     If an element that is used as part of the action chain has gone stale.
1436  * @throws {UnexpectedAlertOpenError}
1437  *     A modal dialog is open, blocking this operation.
1438  * @throws {UnsupportedOperationError}
1439  *     Not yet available in current context.
1440  */
1441 GeckoDriver.prototype.performActions = async function (cmd) {
1442   lazy.assert.open(this.getBrowsingContext());
1443   await this._handleUserPrompts();
1445   const actions = cmd.parameters.actions;
1446   await this.getActor().performActions(actions);
1450  * Release all the keys and pointer buttons that are currently depressed.
1452  * @throws {NoSuchWindowError}
1453  *     Browsing context has been discarded.
1454  * @throws {UnexpectedAlertOpenError}
1455  *     A modal dialog is open, blocking this operation.
1456  * @throws {UnsupportedOperationError}
1457  *     Not available in current context.
1458  */
1459 GeckoDriver.prototype.releaseActions = async function () {
1460   lazy.assert.open(this.getBrowsingContext());
1461   await this._handleUserPrompts();
1463   await this.getActor().releaseActions();
1467  * Find an element using the indicated search strategy.
1469  * @param {object} cmd
1470  * @param {string=} cmd.parameters.element
1471  *     Web element reference ID to the element that will be used as start node.
1472  * @param {string} cmd.parameters.using
1473  *     Indicates which search method to use.
1474  * @param {string} cmd.parameters.value
1475  *     Value the client is looking for.
1477  * @returns {WebElement}
1478  *     Return the found element.
1480  * @throws {NoSuchElementError}
1481  *     If element represented by reference <var>element</var> is unknown.
1482  * @throws {NoSuchWindowError}
1483  *     Browsing context has been discarded.
1484  * @throws {StaleElementReferenceError}
1485  *     If element represented by reference <var>element</var> has gone stale.
1486  * @throws {UnexpectedAlertOpenError}
1487  *     A modal dialog is open, blocking this operation.
1488  */
1489 GeckoDriver.prototype.findElement = async function (cmd) {
1490   const { element: el, using, value } = cmd.parameters;
1492   if (!lazy.supportedStrategies.has(using)) {
1493     throw new lazy.error.InvalidSelectorError(
1494       `Strategy not supported: ${using}`
1495     );
1496   }
1498   lazy.assert.defined(value);
1499   lazy.assert.open(this.getBrowsingContext());
1500   await this._handleUserPrompts();
1502   let startNode;
1503   if (typeof el != "undefined") {
1504     startNode = lazy.WebElement.fromUUID(el).toJSON();
1505   }
1507   let opts = {
1508     startNode,
1509     timeout: this.currentSession.timeouts.implicit,
1510     all: false,
1511   };
1513   return this.getActor().findElement(using, value, opts);
1517  * Find an element within shadow root using the indicated search strategy.
1519  * @param {object} cmd
1520  * @param {string} cmd.parameters.shadowRoot
1521  *     Shadow root reference ID.
1522  * @param {string} cmd.parameters.using
1523  *     Indicates which search method to use.
1524  * @param {string} cmd.parameters.value
1525  *     Value the client is looking for.
1527  * @returns {WebElement}
1528  *     Return the found element.
1530  * @throws {DetachedShadowRootError}
1531  *     If shadow root represented by reference <var>id</var> is
1532  *     no longer attached to the DOM.
1533  * @throws {NoSuchElementError}
1534  *     If the element which is looked for with <var>value</var> was
1535  *     not found.
1536  * @throws {NoSuchShadowRoot}
1537  *     If shadow root represented by reference <var>shadowRoot</var> is unknown.
1538  * @throws {NoSuchWindowError}
1539  *     Browsing context has been discarded.
1540  * @throws {UnexpectedAlertOpenError}
1541  *     A modal dialog is open, blocking this operation.
1542  */
1543 GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) {
1544   const { shadowRoot, using, value } = cmd.parameters;
1546   if (!lazy.supportedStrategies.has(using)) {
1547     throw new lazy.error.InvalidSelectorError(
1548       `Strategy not supported: ${using}`
1549     );
1550   }
1552   lazy.assert.defined(value);
1553   lazy.assert.open(this.getBrowsingContext());
1554   await this._handleUserPrompts();
1556   const opts = {
1557     all: false,
1558     startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
1559     timeout: this.currentSession.timeouts.implicit,
1560   };
1562   return this.getActor().findElement(using, value, opts);
1566  * Find elements using the indicated search strategy.
1568  * @param {object} cmd
1569  * @param {string=} cmd.parameters.element
1570  *     Web element reference ID to the element that will be used as start node.
1571  * @param {string} cmd.parameters.using
1572  *     Indicates which search method to use.
1573  * @param {string} cmd.parameters.value
1574  *     Value the client is looking for.
1576  * @returns {Array<WebElement>}
1577  *     Return the array of found elements.
1579  * @throws {NoSuchElementError}
1580  *     If element represented by reference <var>element</var> is unknown.
1581  * @throws {NoSuchWindowError}
1582  *     Browsing context has been discarded.
1583  * @throws {StaleElementReferenceError}
1584  *     If element represented by reference <var>element</var> has gone stale.
1585  * @throws {UnexpectedAlertOpenError}
1586  *     A modal dialog is open, blocking this operation.
1587  */
1588 GeckoDriver.prototype.findElements = async function (cmd) {
1589   const { element: el, using, value } = cmd.parameters;
1591   if (!lazy.supportedStrategies.has(using)) {
1592     throw new lazy.error.InvalidSelectorError(
1593       `Strategy not supported: ${using}`
1594     );
1595   }
1597   lazy.assert.defined(value);
1598   lazy.assert.open(this.getBrowsingContext());
1599   await this._handleUserPrompts();
1601   let startNode;
1602   if (typeof el != "undefined") {
1603     startNode = lazy.WebElement.fromUUID(el).toJSON();
1604   }
1606   let opts = {
1607     startNode,
1608     timeout: this.currentSession.timeouts.implicit,
1609     all: true,
1610   };
1612   return this.getActor().findElements(using, value, opts);
1616  * Find elements within shadow root using the indicated search strategy.
1618  * @param {object} cmd
1619  * @param {string} cmd.parameters.shadowRoot
1620  *     Shadow root reference ID.
1621  * @param {string} cmd.parameters.using
1622  *     Indicates which search method to use.
1623  * @param {string} cmd.parameters.value
1624  *     Value the client is looking for.
1626  * @returns {Array<WebElement>}
1627  *     Return the array of found elements.
1629  * @throws {DetachedShadowRootError}
1630  *     If shadow root represented by reference <var>id</var> is
1631  *     no longer attached to the DOM.
1632  * @throws {NoSuchShadowRoot}
1633  *     If shadow root represented by reference <var>shadowRoot</var> is unknown.
1634  * @throws {NoSuchWindowError}
1635  *     Browsing context has been discarded.
1636  * @throws {UnexpectedAlertOpenError}
1637  *     A modal dialog is open, blocking this operation.
1638  */
1639 GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) {
1640   const { shadowRoot, using, value } = cmd.parameters;
1642   if (!lazy.supportedStrategies.has(using)) {
1643     throw new lazy.error.InvalidSelectorError(
1644       `Strategy not supported: ${using}`
1645     );
1646   }
1648   lazy.assert.defined(value);
1649   lazy.assert.open(this.getBrowsingContext());
1650   await this._handleUserPrompts();
1652   const opts = {
1653     all: true,
1654     startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
1655     timeout: this.currentSession.timeouts.implicit,
1656   };
1658   return this.getActor().findElements(using, value, opts);
1662  * Return the shadow root of an element in the document.
1664  * @param {object} cmd
1665  * @param {id} cmd.parameters.id
1666  *     A web element id reference.
1667  * @returns {ShadowRoot}
1668  *     ShadowRoot of the element.
1670  * @throws {InvalidArgumentError}
1671  *     If element <var>id</var> is not a string.
1672  * @throws {NoSuchElementError}
1673  *     If element represented by reference <var>id</var> is unknown.
1674  * @throws {NoSuchShadowRoot}
1675  *     Element does not have a shadow root attached.
1676  * @throws {NoSuchWindowError}
1677  *     Browsing context has been discarded.
1678  * @throws {StaleElementReferenceError}
1679  *     If element represented by reference <var>id</var> has gone stale.
1680  * @throws {UnexpectedAlertOpenError}
1681  *     A modal dialog is open, blocking this operation.
1682  * @throws {UnsupportedOperationError}
1683  *     Not available in chrome current context.
1684  */
1685 GeckoDriver.prototype.getShadowRoot = async function (cmd) {
1686   // Bug 1743541: Add support for chrome scope.
1687   lazy.assert.content(this.context);
1688   lazy.assert.open(this.getBrowsingContext());
1689   await this._handleUserPrompts();
1691   let id = lazy.assert.string(
1692     cmd.parameters.id,
1693     lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1694   );
1695   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1697   return this.getActor().getShadowRoot(webEl);
1701  * Return the active element in the document.
1703  * @returns {WebReference}
1704  *     Active element of the current browsing context's document
1705  *     element, if the document element is non-null.
1707  * @throws {NoSuchElementError}
1708  *     If the document does not have an active element, i.e. if
1709  *     its document element has been deleted.
1710  * @throws {NoSuchWindowError}
1711  *     Browsing context has been discarded.
1712  * @throws {UnexpectedAlertOpenError}
1713  *     A modal dialog is open, blocking this operation.
1714  * @throws {UnsupportedOperationError}
1715  *     Not available in chrome context.
1716  */
1717 GeckoDriver.prototype.getActiveElement = async function () {
1718   lazy.assert.content(this.context);
1719   lazy.assert.open(this.getBrowsingContext());
1720   await this._handleUserPrompts();
1722   return this.getActor().getActiveElement();
1726  * Send click event to element.
1728  * @param {object} cmd
1729  * @param {string} cmd.parameters.id
1730  *     Reference ID to the element that will be clicked.
1732  * @throws {InvalidArgumentError}
1733  *     If element <var>id</var> is not a string.
1734  * @throws {NoSuchElementError}
1735  *     If element represented by reference <var>id</var> is unknown.
1736  * @throws {NoSuchWindowError}
1737  *     Browsing context has been discarded.
1738  * @throws {StaleElementReferenceError}
1739  *     If element represented by reference <var>id</var> has gone stale.
1740  * @throws {UnexpectedAlertOpenError}
1741  *     A modal dialog is open, blocking this operation.
1742  */
1743 GeckoDriver.prototype.clickElement = async function (cmd) {
1744   const browsingContext = lazy.assert.open(this.getBrowsingContext());
1745   await this._handleUserPrompts();
1747   let id = lazy.assert.string(cmd.parameters.id);
1748   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1750   const actor = this.getActor();
1752   const loadEventExpected = lazy.navigate.isLoadEventExpected(
1753     this._getCurrentURL(),
1754     {
1755       browsingContext,
1756       target: await actor.getElementAttribute(webEl, "target"),
1757     }
1758   );
1760   await lazy.navigate.waitForNavigationCompleted(
1761     this,
1762     () => actor.clickElement(webEl, this.currentSession.capabilities),
1763     {
1764       loadEventExpected,
1765       // The click might trigger a navigation, so don't count on it.
1766       requireBeforeUnload: false,
1767     }
1768   );
1772  * Get a given attribute of an element.
1774  * @param {object} cmd
1775  * @param {string} cmd.parameters.id
1776  *     Web element reference ID to the element that will be inspected.
1777  * @param {string} cmd.parameters.name
1778  *     Name of the attribute which value to retrieve.
1780  * @returns {string}
1781  *     Value of the attribute.
1783  * @throws {InvalidArgumentError}
1784  *     If <var>id</var> or <var>name</var> are not strings.
1785  * @throws {NoSuchElementError}
1786  *     If element represented by reference <var>id</var> is unknown.
1787  * @throws {NoSuchWindowError}
1788  *     Browsing context has been discarded.
1789  * @throws {StaleElementReferenceError}
1790  *     If element represented by reference <var>id</var> has gone stale.
1791  * @throws {UnexpectedAlertOpenError}
1792  *     A modal dialog is open, blocking this operation.
1793  */
1794 GeckoDriver.prototype.getElementAttribute = async function (cmd) {
1795   lazy.assert.open(this.getBrowsingContext());
1796   await this._handleUserPrompts();
1798   const id = lazy.assert.string(cmd.parameters.id);
1799   const name = lazy.assert.string(cmd.parameters.name);
1800   const webEl = lazy.WebElement.fromUUID(id).toJSON();
1802   return this.getActor().getElementAttribute(webEl, name);
1806  * Returns the value of a property associated with given element.
1808  * @param {object} cmd
1809  * @param {string} cmd.parameters.id
1810  *     Web element reference ID to the element that will be inspected.
1811  * @param {string} cmd.parameters.name
1812  *     Name of the property which value to retrieve.
1814  * @returns {string}
1815  *     Value of the property.
1817  * @throws {InvalidArgumentError}
1818  *     If <var>id</var> or <var>name</var> are not strings.
1819  * @throws {NoSuchElementError}
1820  *     If element represented by reference <var>id</var> is unknown.
1821  * @throws {NoSuchWindowError}
1822  *     Browsing context has been discarded.
1823  * @throws {StaleElementReferenceError}
1824  *     If element represented by reference <var>id</var> has gone stale.
1825  * @throws {UnexpectedAlertOpenError}
1826  *     A modal dialog is open, blocking this operation.
1827  */
1828 GeckoDriver.prototype.getElementProperty = async function (cmd) {
1829   lazy.assert.open(this.getBrowsingContext());
1830   await this._handleUserPrompts();
1832   const id = lazy.assert.string(cmd.parameters.id);
1833   const name = lazy.assert.string(cmd.parameters.name);
1834   const webEl = lazy.WebElement.fromUUID(id).toJSON();
1836   return this.getActor().getElementProperty(webEl, name);
1840  * Get the text of an element, if any.  Includes the text of all child
1841  * elements.
1843  * @param {object} cmd
1844  * @param {string} cmd.parameters.id
1845  *     Reference ID to the element that will be inspected.
1847  * @returns {string}
1848  *     Element's text "as rendered".
1850  * @throws {InvalidArgumentError}
1851  *     If <var>id</var> is not a string.
1852  * @throws {NoSuchElementError}
1853  *     If element represented by reference <var>id</var> is unknown.
1854  * @throws {NoSuchWindowError}
1855  *     Browsing context has been discarded.
1856  * @throws {StaleElementReferenceError}
1857  *     If element represented by reference <var>id</var> has gone stale.
1858  * @throws {UnexpectedAlertOpenError}
1859  *     A modal dialog is open, blocking this operation.
1860  */
1861 GeckoDriver.prototype.getElementText = async function (cmd) {
1862   lazy.assert.open(this.getBrowsingContext());
1863   await this._handleUserPrompts();
1865   let id = lazy.assert.string(cmd.parameters.id);
1866   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1868   return this.getActor().getElementText(webEl);
1872  * Get the tag name of the element.
1874  * @param {object} cmd
1875  * @param {string} cmd.parameters.id
1876  *     Reference ID to the element that will be inspected.
1878  * @returns {string}
1879  *     Local tag name of element.
1881  * @throws {InvalidArgumentError}
1882  *     If <var>id</var> is not a string.
1883  * @throws {NoSuchElementError}
1884  *     If element represented by reference <var>id</var> is unknown.
1885  * @throws {NoSuchWindowError}
1886  *     Browsing context has been discarded.
1887  * @throws {StaleElementReferenceError}
1888  *     If element represented by reference <var>id</var> has gone stale.
1889  * @throws {UnexpectedAlertOpenError}
1890  *     A modal dialog is open, blocking this operation.
1891  */
1892 GeckoDriver.prototype.getElementTagName = async function (cmd) {
1893   lazy.assert.open(this.getBrowsingContext());
1894   await this._handleUserPrompts();
1896   let id = lazy.assert.string(cmd.parameters.id);
1897   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1899   return this.getActor().getElementTagName(webEl);
1903  * Check if element is displayed.
1905  * @param {object} cmd
1906  * @param {string} cmd.parameters.id
1907  *     Reference ID to the element that will be inspected.
1909  * @returns {boolean}
1910  *     True if displayed, false otherwise.
1912  * @throws {InvalidArgumentError}
1913  *     If <var>id</var> is not a string.
1914  * @throws {NoSuchElementError}
1915  *     If element represented by reference <var>id</var> is unknown.
1916  * @throws {NoSuchWindowError}
1917  *     Browsing context has been discarded.
1918  * @throws {UnexpectedAlertOpenError}
1919  *     A modal dialog is open, blocking this operation.
1920  */
1921 GeckoDriver.prototype.isElementDisplayed = async function (cmd) {
1922   lazy.assert.open(this.getBrowsingContext());
1923   await this._handleUserPrompts();
1925   let id = lazy.assert.string(cmd.parameters.id);
1926   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1928   return this.getActor().isElementDisplayed(
1929     webEl,
1930     this.currentSession.capabilities
1931   );
1935  * Return the property of the computed style of an element.
1937  * @param {object} cmd
1938  * @param {string} cmd.parameters.id
1939  *     Reference ID to the element that will be checked.
1940  * @param {string} cmd.parameters.propertyName
1941  *     CSS rule that is being requested.
1943  * @returns {string}
1944  *     Value of |propertyName|.
1946  * @throws {InvalidArgumentError}
1947  *     If <var>id</var> or <var>propertyName</var> are not strings.
1948  * @throws {NoSuchElementError}
1949  *     If element represented by reference <var>id</var> is unknown.
1950  * @throws {NoSuchWindowError}
1951  *     Browsing context has been discarded.
1952  * @throws {StaleElementReferenceError}
1953  *     If element represented by reference <var>id</var> has gone stale.
1954  * @throws {UnexpectedAlertOpenError}
1955  *     A modal dialog is open, blocking this operation.
1956  */
1957 GeckoDriver.prototype.getElementValueOfCssProperty = async function (cmd) {
1958   lazy.assert.open(this.getBrowsingContext());
1959   await this._handleUserPrompts();
1961   let id = lazy.assert.string(cmd.parameters.id);
1962   let prop = lazy.assert.string(cmd.parameters.propertyName);
1963   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1965   return this.getActor().getElementValueOfCssProperty(webEl, prop);
1969  * Check if element is enabled.
1971  * @param {object} cmd
1972  * @param {string} cmd.parameters.id
1973  *     Reference ID to the element that will be checked.
1975  * @returns {boolean}
1976  *     True if enabled, false if disabled.
1978  * @throws {InvalidArgumentError}
1979  *     If <var>id</var> is not a string.
1980  * @throws {NoSuchElementError}
1981  *     If element represented by reference <var>id</var> is unknown.
1982  * @throws {NoSuchWindowError}
1983  *     Browsing context has been discarded.
1984  * @throws {StaleElementReferenceError}
1985  *     If element represented by reference <var>id</var> has gone stale.
1986  * @throws {UnexpectedAlertOpenError}
1987  *     A modal dialog is open, blocking this operation.
1988  */
1989 GeckoDriver.prototype.isElementEnabled = async function (cmd) {
1990   lazy.assert.open(this.getBrowsingContext());
1991   await this._handleUserPrompts();
1993   let id = lazy.assert.string(cmd.parameters.id);
1994   let webEl = lazy.WebElement.fromUUID(id).toJSON();
1996   return this.getActor().isElementEnabled(
1997     webEl,
1998     this.currentSession.capabilities
1999   );
2003  * Check if element is selected.
2005  * @param {object} cmd
2006  * @param {string} cmd.parameters.id
2007  *     Reference ID to the element that will be checked.
2009  * @returns {boolean}
2010  *     True if selected, false if unselected.
2012  * @throws {InvalidArgumentError}
2013  *     If <var>id</var> is not a string.
2014  * @throws {NoSuchElementError}
2015  *     If element represented by reference <var>id</var> is unknown.
2016  * @throws {NoSuchWindowError}
2017  *     Browsing context has been discarded.
2018  * @throws {UnexpectedAlertOpenError}
2019  *     A modal dialog is open, blocking this operation.
2020  */
2021 GeckoDriver.prototype.isElementSelected = async function (cmd) {
2022   lazy.assert.open(this.getBrowsingContext());
2023   await this._handleUserPrompts();
2025   let id = lazy.assert.string(cmd.parameters.id);
2026   let webEl = lazy.WebElement.fromUUID(id).toJSON();
2028   return this.getActor().isElementSelected(
2029     webEl,
2030     this.currentSession.capabilities
2031   );
2035  * @throws {InvalidArgumentError}
2036  *     If <var>id</var> is not a string.
2037  * @throws {NoSuchElementError}
2038  *     If element represented by reference <var>id</var> is unknown.
2039  * @throws {NoSuchWindowError}
2040  *     Browsing context has been discarded.
2041  * @throws {StaleElementReferenceError}
2042  *     If element represented by reference <var>id</var> has gone stale.
2043  * @throws {UnexpectedAlertOpenError}
2044  *     A modal dialog is open, blocking this operation.
2045  */
2046 GeckoDriver.prototype.getElementRect = async function (cmd) {
2047   lazy.assert.open(this.getBrowsingContext());
2048   await this._handleUserPrompts();
2050   let id = lazy.assert.string(cmd.parameters.id);
2051   let webEl = lazy.WebElement.fromUUID(id).toJSON();
2053   return this.getActor().getElementRect(webEl);
2057  * Send key presses to element after focusing on it.
2059  * @param {object} cmd
2060  * @param {string} cmd.parameters.id
2061  *     Reference ID to the element that will be checked.
2062  * @param {string} cmd.parameters.text
2063  *     Value to send to the element.
2065  * @throws {InvalidArgumentError}
2066  *     If <var>id</var> or <var>text</var> are not strings.
2067  * @throws {NoSuchElementError}
2068  *     If element represented by reference <var>id</var> is unknown.
2069  * @throws {NoSuchWindowError}
2070  *     Browsing context has been discarded.
2071  * @throws {StaleElementReferenceError}
2072  *     If element represented by reference <var>id</var> has gone stale.
2073  * @throws {UnexpectedAlertOpenError}
2074  *     A modal dialog is open, blocking this operation.
2075  */
2076 GeckoDriver.prototype.sendKeysToElement = async function (cmd) {
2077   lazy.assert.open(this.getBrowsingContext());
2078   await this._handleUserPrompts();
2080   let id = lazy.assert.string(cmd.parameters.id);
2081   let text = lazy.assert.string(cmd.parameters.text);
2082   let webEl = lazy.WebElement.fromUUID(id).toJSON();
2084   return this.getActor().sendKeysToElement(
2085     webEl,
2086     text,
2087     this.currentSession.capabilities
2088   );
2092  * Clear the text of an element.
2094  * @param {object} cmd
2095  * @param {string} cmd.parameters.id
2096  *     Reference ID to the element that will be cleared.
2098  * @throws {InvalidArgumentError}
2099  *     If <var>id</var> is not a string.
2100  * @throws {NoSuchElementError}
2101  *     If element represented by reference <var>id</var> is unknown.
2102  * @throws {NoSuchWindowError}
2103  *     Browsing context has been discarded.
2104  * @throws {StaleElementReferenceError}
2105  *     If element represented by reference <var>id</var> has gone stale.
2106  * @throws {UnexpectedAlertOpenError}
2107  *     A modal dialog is open, blocking this operation.
2108  */
2109 GeckoDriver.prototype.clearElement = async function (cmd) {
2110   lazy.assert.open(this.getBrowsingContext());
2111   await this._handleUserPrompts();
2113   let id = lazy.assert.string(cmd.parameters.id);
2114   let webEl = lazy.WebElement.fromUUID(id).toJSON();
2116   await this.getActor().clearElement(webEl);
2120  * Add a single cookie to the cookie store associated with the active
2121  * document's address.
2123  * @param {object} cmd
2124  * @param {Map.<string, (string|number|boolean)>} cmd.parameters.cookie
2125  *     Cookie object.
2127  * @throws {InvalidCookieDomainError}
2128  *     If <var>cookie</var> is for a different domain than the active
2129  *     document's host.
2130  * @throws {NoSuchWindowError}
2131  *     Bbrowsing context has been discarded.
2132  * @throws {UnexpectedAlertOpenError}
2133  *     A modal dialog is open, blocking this operation.
2134  * @throws {UnsupportedOperationError}
2135  *     Not available in current context.
2136  */
2137 GeckoDriver.prototype.addCookie = async function (cmd) {
2138   lazy.assert.content(this.context);
2139   lazy.assert.open(this.getBrowsingContext());
2140   await this._handleUserPrompts();
2142   let { protocol, hostname } = this._getCurrentURL();
2144   const networkSchemes = ["http:", "https:"];
2145   if (!networkSchemes.includes(protocol)) {
2146     throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
2147   }
2149   let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
2151   lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
2155  * Get all the cookies for the current domain.
2157  * This is the equivalent of calling <code>document.cookie</code> and
2158  * parsing the result.
2160  * @throws {NoSuchWindowError}
2161  *     Browsing context has been discarded.
2162  * @throws {UnexpectedAlertOpenError}
2163  *     A modal dialog is open, blocking this operation.
2164  * @throws {UnsupportedOperationError}
2165  *     Not available in current context.
2166  */
2167 GeckoDriver.prototype.getCookies = async function () {
2168   lazy.assert.content(this.context);
2169   lazy.assert.open(this.getBrowsingContext());
2170   await this._handleUserPrompts();
2172   let { hostname, pathname } = this._getCurrentURL();
2173   return [...lazy.cookie.iter(hostname, pathname)];
2177  * Delete all cookies that are visible to a document.
2179  * @throws {NoSuchWindowError}
2180  *     Browsing context has been discarded.
2181  * @throws {UnexpectedAlertOpenError}
2182  *     A modal dialog is open, blocking this operation.
2183  * @throws {UnsupportedOperationError}
2184  *     Not available in current context.
2185  */
2186 GeckoDriver.prototype.deleteAllCookies = async function () {
2187   lazy.assert.content(this.context);
2188   lazy.assert.open(this.getBrowsingContext());
2189   await this._handleUserPrompts();
2191   let { hostname, pathname } = this._getCurrentURL();
2192   for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
2193     lazy.cookie.remove(toDelete);
2194   }
2198  * Delete a cookie by name.
2200  * @throws {NoSuchWindowError}
2201  *     Browsing context has been discarded.
2202  * @throws {UnexpectedAlertOpenError}
2203  *     A modal dialog is open, blocking this operation.
2204  * @throws {UnsupportedOperationError}
2205  *     Not available in current context.
2206  */
2207 GeckoDriver.prototype.deleteCookie = async function (cmd) {
2208   lazy.assert.content(this.context);
2209   lazy.assert.open(this.getBrowsingContext());
2210   await this._handleUserPrompts();
2212   let { hostname, pathname } = this._getCurrentURL();
2213   let name = lazy.assert.string(cmd.parameters.name);
2214   for (let c of lazy.cookie.iter(hostname, pathname)) {
2215     if (c.name === name) {
2216       lazy.cookie.remove(c);
2217     }
2218   }
2222  * Open a new top-level browsing context.
2224  * @param {object} cmd
2225  * @param {string=} cmd.parameters.type
2226  *     Optional type of the new top-level browsing context. Can be one of
2227  *     `tab` or `window`. Defaults to `tab`.
2228  * @param {boolean=} cmd.parameters.focus
2229  *     Optional flag if the new top-level browsing context should be opened
2230  *     in foreground (focused) or background (not focused). Defaults to false.
2231  * @param {boolean=} cmd.parameters.private
2232  *     Optional flag, which gets only evaluated for type `window`. True if the
2233  *     new top-level browsing context should be a private window.
2234  *     Defaults to false.
2236  * @returns {Object<string, string>}
2237  *     Handle and type of the new browsing context.
2239  * @throws {NoSuchWindowError}
2240  *     Top-level browsing context has been discarded.
2241  * @throws {UnexpectedAlertOpenError}
2242  *     A modal dialog is open, blocking this operation.
2243  */
2244 GeckoDriver.prototype.newWindow = async function (cmd) {
2245   lazy.assert.open(this.getBrowsingContext({ top: true }));
2246   await this._handleUserPrompts();
2248   let focus = false;
2249   if (typeof cmd.parameters.focus != "undefined") {
2250     focus = lazy.assert.boolean(
2251       cmd.parameters.focus,
2252       lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
2253     );
2254   }
2256   let isPrivate = false;
2257   if (typeof cmd.parameters.private != "undefined") {
2258     isPrivate = lazy.assert.boolean(
2259       cmd.parameters.private,
2260       lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
2261     );
2262   }
2264   let type;
2265   if (typeof cmd.parameters.type != "undefined") {
2266     type = lazy.assert.string(
2267       cmd.parameters.type,
2268       lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
2269     );
2270   }
2272   // If an invalid or no type has been specified default to a tab.
2273   if (typeof type == "undefined" || !["tab", "window"].includes(type)) {
2274     type = "tab";
2275   }
2277   let contentBrowser;
2279   switch (type) {
2280     case "window":
2281       let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
2282       contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
2283       break;
2285     default:
2286       // To not fail if a new type gets added in the future, make opening
2287       // a new tab the default action.
2288       let tab = await this.curBrowser.openTab(focus);
2289       contentBrowser = lazy.TabManager.getBrowserForTab(tab);
2290   }
2292   // Actors need the new window to be loaded to safely execute queries.
2293   // Wait until the initial page load has been finished.
2294   await lazy.waitForInitialNavigationCompleted(
2295     contentBrowser.browsingContext.webProgress,
2296     {
2297       unloadTimeout: 5000,
2298     }
2299   );
2301   const id = lazy.TabManager.getIdForBrowser(contentBrowser);
2303   return { handle: id.toString(), type };
2307  * Close the currently selected tab/window.
2309  * With multiple open tabs present the currently selected tab will
2310  * be closed.  Otherwise the window itself will be closed. If it is the
2311  * last window currently open, the window will not be closed to prevent
2312  * a shutdown of the application. Instead the returned list of window
2313  * handles is empty.
2315  * @returns {Array.<string>}
2316  *     Unique window handles of remaining windows.
2318  * @throws {NoSuchWindowError}
2319  *     Top-level browsing context has been discarded.
2320  * @throws {UnexpectedAlertOpenError}
2321  *     A modal dialog is open, blocking this operation.
2322  */
2323 GeckoDriver.prototype.close = async function () {
2324   lazy.assert.open(
2325     this.getBrowsingContext({ context: lazy.Context.Content, top: true })
2326   );
2327   await this._handleUserPrompts();
2329   // If there is only one window left, do not close unless windowless mode is
2330   // enabled. Instead return a faked empty array of window handles.
2331   // This will instruct geckodriver to terminate the application.
2332   if (
2333     lazy.TabManager.getTabCount() === 1 &&
2334     !this.currentSession.capabilities.get("moz:windowless")
2335   ) {
2336     return [];
2337   }
2339   await this.curBrowser.closeTab();
2340   this.currentSession.contentBrowsingContext = null;
2342   return lazy.TabManager.allBrowserUniqueIds.map(String);
2346  * Close the currently selected chrome window.
2348  * If it is the last window currently open, the chrome window will not be
2349  * closed to prevent a shutdown of the application. Instead the returned
2350  * list of chrome window handles is empty.
2352  * @returns {Array.<string>}
2353  *     Unique chrome window handles of remaining chrome windows.
2355  * @throws {NoSuchWindowError}
2356  *     Top-level browsing context has been discarded.
2357  */
2358 GeckoDriver.prototype.closeChromeWindow = async function () {
2359   lazy.assert.desktop();
2360   lazy.assert.open(
2361     this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
2362   );
2364   let nwins = 0;
2366   // eslint-disable-next-line
2367   for (let _ of lazy.windowManager.windows) {
2368     nwins++;
2369   }
2371   // If there is only one window left, do not close unless windowless mode is
2372   // enabled. Instead return a faked empty array of window handles.
2373   // This will instruct geckodriver to terminate the application.
2374   if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
2375     return [];
2376   }
2378   await this.curBrowser.closeWindow();
2379   this.currentSession.chromeBrowsingContext = null;
2380   this.currentSession.contentBrowsingContext = null;
2382   return lazy.windowManager.chromeWindowHandles.map(String);
2385 /** Delete Marionette session. */
2386 GeckoDriver.prototype.deleteSession = function () {
2387   if (!this.currentSession) {
2388     return;
2389   }
2391   for (let win of lazy.windowManager.windows) {
2392     this.stopObservingWindow(win);
2393   }
2395   // reset to the top-most frame
2396   this.mainFrame = null;
2398   if (this.promptListener) {
2399     this.promptListener.stopListening();
2400     this.promptListener = null;
2401   }
2403   try {
2404     Services.obs.removeObserver(this, TOPIC_BROWSER_READY);
2405   } catch (e) {
2406     lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`);
2407   }
2409   // Always unregister actors after all other observers
2410   // and listeners have been removed.
2411   lazy.unregisterCommandsActor();
2412   // MarionetteEvents actors are only disabled to avoid IPC errors if there are
2413   // in flight events being forwarded from the content process to the parent
2414   // process.
2415   lazy.disableEventsActor();
2417   if (lazy.RemoteAgent.webDriverBiDi) {
2418     lazy.RemoteAgent.webDriverBiDi.deleteSession();
2419   } else {
2420     this.currentSession.destroy();
2421     this._currentSession = null;
2422   }
2426  * Takes a screenshot of a web element, current frame, or viewport.
2428  * The screen capture is returned as a lossless PNG image encoded as
2429  * a base 64 string.
2431  * If called in the content context, the |id| argument is not null and
2432  * refers to a present and visible web element's ID, the capture area will
2433  * be limited to the bounding box of that element.  Otherwise, the capture
2434  * area will be the bounding box of the current frame.
2436  * If called in the chrome context, the screenshot will always represent
2437  * the entire viewport.
2439  * @param {object} cmd
2440  * @param {string=} cmd.parameters.id
2441  *     Optional web element reference to take a screenshot of.
2442  *     If undefined, a screenshot will be taken of the document element.
2443  * @param {boolean=} cmd.parameters.full
2444  *     True to take a screenshot of the entire document element. Is only
2445  *     considered if <var>id</var> is not defined. Defaults to true.
2446  * @param {boolean=} cmd.parameters.hash
2447  *     True if the user requests a hash of the image data. Defaults to false.
2448  * @param {boolean=} cmd.parameters.scroll
2449  *     Scroll to element if |id| is provided. Defaults to true.
2451  * @returns {string}
2452  *     If <var>hash</var> is false, PNG image encoded as Base64 encoded
2453  *     string.  If <var>hash</var> is true, hex digest of the SHA-256
2454  *     hash of the Base64 encoded string.
2456  * @throws {NoSuchElementError}
2457  *     If element represented by reference <var>id</var> is unknown.
2458  * @throws {NoSuchWindowError}
2459  *     Browsing context has been discarded.
2460  * @throws {StaleElementReferenceError}
2461  *     If element represented by reference <var>id</var> has gone stale.
2462  */
2463 GeckoDriver.prototype.takeScreenshot = async function (cmd) {
2464   lazy.assert.open(this.getBrowsingContext({ top: true }));
2465   await this._handleUserPrompts();
2467   let { id, full, hash, scroll } = cmd.parameters;
2468   let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
2470   full = typeof full == "undefined" ? true : full;
2471   scroll = typeof scroll == "undefined" ? true : scroll;
2473   let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;
2475   // Only consider full screenshot if no element has been specified
2476   full = webEl ? false : full;
2478   return this.getActor().takeScreenshot(webEl, format, full, scroll);
2482  * Get the current browser orientation.
2484  * Will return one of the valid primary orientation values
2485  * portrait-primary, landscape-primary, portrait-secondary, or
2486  * landscape-secondary.
2488  * @throws {NoSuchWindowError}
2489  *     Top-level browsing context has been discarded.
2490  */
2491 GeckoDriver.prototype.getScreenOrientation = function () {
2492   lazy.assert.mobile();
2493   lazy.assert.open(this.getBrowsingContext({ top: true }));
2495   const win = this.getCurrentWindow();
2497   return win.screen.orientation.type;
2501  * Set the current browser orientation.
2503  * The supplied orientation should be given as one of the valid
2504  * orientation values.  If the orientation is unknown, an error will
2505  * be raised.
2507  * Valid orientations are "portrait" and "landscape", which fall
2508  * back to "portrait-primary" and "landscape-primary" respectively,
2509  * and "portrait-secondary" as well as "landscape-secondary".
2511  * @throws {NoSuchWindowError}
2512  *     Top-level browsing context has been discarded.
2513  */
2514 GeckoDriver.prototype.setScreenOrientation = async function (cmd) {
2515   lazy.assert.mobile();
2516   lazy.assert.open(this.getBrowsingContext({ top: true }));
2518   const ors = [
2519     "portrait",
2520     "landscape",
2521     "portrait-primary",
2522     "landscape-primary",
2523     "portrait-secondary",
2524     "landscape-secondary",
2525   ];
2527   let or = String(cmd.parameters.orientation);
2528   lazy.assert.string(or);
2529   let mozOr = or.toLowerCase();
2530   if (!ors.includes(mozOr)) {
2531     throw new lazy.error.InvalidArgumentError(
2532       `Unknown screen orientation: ${or}`
2533     );
2534   }
2536   const win = this.getCurrentWindow();
2538   try {
2539     await win.screen.orientation.lock(mozOr);
2540   } catch (e) {
2541     throw new lazy.error.WebDriverError(
2542       `Unable to set screen orientation: ${or}`
2543     );
2544   }
2548  * Synchronously minimizes the user agent window as if the user pressed
2549  * the minimize button.
2551  * No action is taken if the window is already minimized.
2553  * Not supported on Fennec.
2555  * @returns {Object<string, number>}
2556  *     Window rect and window state.
2558  * @throws {NoSuchWindowError}
2559  *     Top-level browsing context has been discarded.
2560  * @throws {UnexpectedAlertOpenError}
2561  *     A modal dialog is open, blocking this operation.
2562  * @throws {UnsupportedOperationError}
2563  *     Not available for current application.
2564  */
2565 GeckoDriver.prototype.minimizeWindow = async function () {
2566   lazy.assert.desktop();
2567   lazy.assert.open(this.getBrowsingContext({ top: true }));
2568   await this._handleUserPrompts();
2570   const win = this.getCurrentWindow();
2571   switch (lazy.WindowState.from(win.windowState)) {
2572     case lazy.WindowState.Fullscreen:
2573       await exitFullscreen(win);
2574       break;
2576     case lazy.WindowState.Maximized:
2577       await restoreWindow(win);
2578       break;
2579   }
2581   if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
2582     let cb;
2583     // Use a timed promise to abort if no window manager is present
2584     await new lazy.TimedPromise(
2585       resolve => {
2586         cb = new lazy.DebounceCallback(resolve);
2587         win.addEventListener("sizemodechange", cb);
2588         win.minimize();
2589       },
2590       { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2591     );
2592     win.removeEventListener("sizemodechange", cb);
2593     await new lazy.IdlePromise(win);
2594   }
2596   return this.curBrowser.rect;
2600  * Synchronously maximizes the user agent window as if the user pressed
2601  * the maximize button.
2603  * No action is taken if the window is already maximized.
2605  * Not supported on Fennec.
2607  * @returns {Object<string, number>}
2608  *     Window rect.
2610  * @throws {NoSuchWindowError}
2611  *     Top-level browsing context has been discarded.
2612  * @throws {UnexpectedAlertOpenError}
2613  *     A modal dialog is open, blocking this operation.
2614  * @throws {UnsupportedOperationError}
2615  *     Not available for current application.
2616  */
2617 GeckoDriver.prototype.maximizeWindow = async function () {
2618   lazy.assert.desktop();
2619   lazy.assert.open(this.getBrowsingContext({ top: true }));
2620   await this._handleUserPrompts();
2622   const win = this.getCurrentWindow();
2623   switch (lazy.WindowState.from(win.windowState)) {
2624     case lazy.WindowState.Fullscreen:
2625       await exitFullscreen(win);
2626       break;
2628     case lazy.WindowState.Minimized:
2629       await restoreWindow(win);
2630       break;
2631   }
2633   if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
2634     let cb;
2635     // Use a timed promise to abort if no window manager is present
2636     await new lazy.TimedPromise(
2637       resolve => {
2638         cb = new lazy.DebounceCallback(resolve);
2639         win.addEventListener("sizemodechange", cb);
2640         win.maximize();
2641       },
2642       { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2643     );
2644     win.removeEventListener("sizemodechange", cb);
2645     await new lazy.IdlePromise(win);
2646   }
2648   return this.curBrowser.rect;
2652  * Synchronously sets the user agent window to full screen as if the user
2653  * had done "View > Enter Full Screen".
2655  * No action is taken if the window is already in full screen mode.
2657  * Not supported on Fennec.
2659  * @returns {Map.<string, number>}
2660  *     Window rect.
2662  * @throws {NoSuchWindowError}
2663  *     Top-level browsing context has been discarded.
2664  * @throws {UnexpectedAlertOpenError}
2665  *     A modal dialog is open, blocking this operation.
2666  * @throws {UnsupportedOperationError}
2667  *     Not available for current application.
2668  */
2669 GeckoDriver.prototype.fullscreenWindow = async function () {
2670   lazy.assert.desktop();
2671   lazy.assert.open(this.getBrowsingContext({ top: true }));
2672   await this._handleUserPrompts();
2674   const win = this.getCurrentWindow();
2675   switch (lazy.WindowState.from(win.windowState)) {
2676     case lazy.WindowState.Maximized:
2677     case lazy.WindowState.Minimized:
2678       await restoreWindow(win);
2679       break;
2680   }
2682   if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
2683     let cb;
2684     // Use a timed promise to abort if no window manager is present
2685     await new lazy.TimedPromise(
2686       resolve => {
2687         cb = new lazy.DebounceCallback(resolve);
2688         win.addEventListener("sizemodechange", cb);
2689         win.fullScreen = true;
2690       },
2691       { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2692     );
2693     win.removeEventListener("sizemodechange", cb);
2694   }
2695   await new lazy.IdlePromise(win);
2697   return this.curBrowser.rect;
2701  * Dismisses a currently displayed modal dialogs, or returns no such alert if
2702  * no modal is displayed.
2704  * @throws {NoSuchAlertError}
2705  *     If there is no current user prompt.
2706  * @throws {NoSuchWindowError}
2707  *     Top-level browsing context has been discarded.
2708  */
2709 GeckoDriver.prototype.dismissDialog = async function () {
2710   lazy.assert.open(this.getBrowsingContext({ top: true }));
2711   this._checkIfAlertIsPresent();
2713   const dialogClosed = this.promptListener.dialogClosed();
2714   this.dialog.dismiss();
2715   await dialogClosed;
2717   const win = this.getCurrentWindow();
2718   await new lazy.IdlePromise(win);
2722  * Accepts a currently displayed dialog modal, or returns no such alert if
2723  * no modal is displayed.
2725  * @throws {NoSuchAlertError}
2726  *     If there is no current user prompt.
2727  * @throws {NoSuchWindowError}
2728  *     Top-level browsing context has been discarded.
2729  */
2730 GeckoDriver.prototype.acceptDialog = async function () {
2731   lazy.assert.open(this.getBrowsingContext({ top: true }));
2732   this._checkIfAlertIsPresent();
2734   const dialogClosed = this.promptListener.dialogClosed();
2735   this.dialog.accept();
2736   await dialogClosed;
2738   const win = this.getCurrentWindow();
2739   await new lazy.IdlePromise(win);
2743  * Returns the message shown in a currently displayed modal, or returns
2744  * a no such alert error if no modal is currently displayed.
2746  * @throws {NoSuchAlertError}
2747  *     If there is no current user prompt.
2748  * @throws {NoSuchWindowError}
2749  *     Top-level browsing context has been discarded.
2750  */
2751 GeckoDriver.prototype.getTextFromDialog = async function () {
2752   lazy.assert.open(this.getBrowsingContext({ top: true }));
2753   this._checkIfAlertIsPresent();
2754   const text = await this.dialog.getText();
2755   return text;
2759  * Set the user prompt's value field.
2761  * Sends keys to the input field of a currently displayed modal, or
2762  * returns a no such alert error if no modal is currently displayed. If
2763  * a modal dialog is currently displayed but has no means for text input,
2764  * an element not visible error is returned.
2766  * @param {object} cmd
2767  * @param {string} cmd.parameters.text
2768  *     Input to the user prompt's value field.
2770  * @throws {ElementNotInteractableError}
2771  *     If the current user prompt is an alert or confirm.
2772  * @throws {NoSuchAlertError}
2773  *     If there is no current user prompt.
2774  * @throws {NoSuchWindowError}
2775  *     Top-level browsing context has been discarded.
2776  * @throws {UnsupportedOperationError}
2777  *     If the current user prompt is something other than an alert,
2778  *     confirm, or a prompt.
2779  */
2780 GeckoDriver.prototype.sendKeysToDialog = async function (cmd) {
2781   lazy.assert.open(this.getBrowsingContext({ top: true }));
2782   this._checkIfAlertIsPresent();
2784   let text = lazy.assert.string(cmd.parameters.text);
2785   let promptType = this.dialog.args.promptType;
2787   switch (promptType) {
2788     case "alert":
2789     case "confirm":
2790       throw new lazy.error.ElementNotInteractableError(
2791         `User prompt of type ${promptType} is not interactable`
2792       );
2793     case "prompt":
2794       break;
2795     default:
2796       await this.dismissDialog();
2797       throw new lazy.error.UnsupportedOperationError(
2798         `User prompt of type ${promptType} is not supported`
2799       );
2800   }
2801   this.dialog.text = text;
2804 GeckoDriver.prototype._checkIfAlertIsPresent = function () {
2805   if (!this.dialog || !this.dialog.isOpen) {
2806     throw new lazy.error.NoSuchAlertError();
2807   }
2810 GeckoDriver.prototype._handleUserPrompts = async function () {
2811   if (!this.dialog || !this.dialog.isOpen) {
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     quitApplicationResponse = await lazy.quit(
2923       flags,
2924       safeMode,
2925       this.currentSession.capabilities.get("moz:windowless")
2926     );
2927   } catch (e) {
2928     if (e instanceof TypeError) {
2929       throw new lazy.error.InvalidArgumentError(e.message);
2930     }
2932     throw new lazy.error.UnsupportedOperationError(e.message);
2933   } finally {
2934     Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
2935   }
2937   return quitApplicationResponse;
2940 GeckoDriver.prototype.installAddon = function (cmd) {
2941   lazy.assert.desktop();
2943   let path = cmd.parameters.path;
2944   let temp = cmd.parameters.temporary || false;
2945   if (
2946     typeof path == "undefined" ||
2947     typeof path != "string" ||
2948     typeof temp != "boolean"
2949   ) {
2950     throw new lazy.error.InvalidArgumentError();
2951   }
2953   return lazy.Addon.install(path, temp);
2956 GeckoDriver.prototype.uninstallAddon = function (cmd) {
2957   lazy.assert.desktop();
2959   let id = cmd.parameters.id;
2960   if (typeof id == "undefined" || typeof id != "string") {
2961     throw new lazy.error.InvalidArgumentError();
2962   }
2964   return lazy.Addon.uninstall(id);
2968  * Retrieve the localized string for the specified entity id.
2970  * Example:
2971  *     localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
2973  * @param {object} cmd
2974  * @param {Array.<string>} cmd.parameters.urls
2975  *     Array of .dtd URLs.
2976  * @param {string} cmd.parameters.id
2977  *     The ID of the entity to retrieve the localized string for.
2979  * @returns {string}
2980  *     The localized string for the requested entity.
2981  */
2982 GeckoDriver.prototype.localizeEntity = function (cmd) {
2983   let { urls, id } = cmd.parameters;
2985   if (!Array.isArray(urls)) {
2986     throw new lazy.error.InvalidArgumentError(
2987       "Value of `urls` should be of type 'Array'"
2988     );
2989   }
2990   if (typeof id != "string") {
2991     throw new lazy.error.InvalidArgumentError(
2992       "Value of `id` should be of type 'string'"
2993     );
2994   }
2996   return lazy.l10n.localizeEntity(urls, id);
3000  * Retrieve the localized string for the specified property id.
3002  * Example:
3004  *     localizeProperty(
3005  *         ["chrome://global/locale/findbar.properties"], "FastFind");
3007  * @param {object} cmd
3008  * @param {Array.<string>} cmd.parameters.urls
3009  *     Array of .properties URLs.
3010  * @param {string} cmd.parameters.id
3011  *     The ID of the property to retrieve the localized string for.
3013  * @returns {string}
3014  *     The localized string for the requested property.
3015  */
3016 GeckoDriver.prototype.localizeProperty = function (cmd) {
3017   let { urls, id } = cmd.parameters;
3019   if (!Array.isArray(urls)) {
3020     throw new lazy.error.InvalidArgumentError(
3021       "Value of `urls` should be of type 'Array'"
3022     );
3023   }
3024   if (typeof id != "string") {
3025     throw new lazy.error.InvalidArgumentError(
3026       "Value of `id` should be of type 'string'"
3027     );
3028   }
3030   return lazy.l10n.localizeProperty(urls, id);
3034  * Initialize the reftest mode
3035  */
3036 GeckoDriver.prototype.setupReftest = async function (cmd) {
3037   if (this._reftest) {
3038     throw new lazy.error.UnsupportedOperationError(
3039       "Called reftest:setup with a reftest session already active"
3040     );
3041   }
3043   let {
3044     urlCount = {},
3045     screenshot = "unexpected",
3046     isPrint = false,
3047   } = cmd.parameters;
3048   if (!["always", "fail", "unexpected"].includes(screenshot)) {
3049     throw new lazy.error.InvalidArgumentError(
3050       "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
3051     );
3052   }
3054   this._reftest = new lazy.reftest.Runner(this);
3055   this._reftest.setup(urlCount, screenshot, isPrint);
3058 /** Run a reftest. */
3059 GeckoDriver.prototype.runReftest = function (cmd) {
3060   let { test, references, expected, timeout, width, height, pageRanges } =
3061     cmd.parameters;
3063   if (!this._reftest) {
3064     throw new lazy.error.UnsupportedOperationError(
3065       "Called reftest:run before reftest:start"
3066     );
3067   }
3069   lazy.assert.string(test);
3070   lazy.assert.string(expected);
3071   lazy.assert.array(references);
3073   return this._reftest.run(
3074     test,
3075     references,
3076     expected,
3077     timeout,
3078     pageRanges,
3079     width,
3080     height
3081   );
3085  * End a reftest run.
3087  * Closes the reftest window (without changing the current window handle),
3088  * and removes cached canvases.
3089  */
3090 GeckoDriver.prototype.teardownReftest = function () {
3091   if (!this._reftest) {
3092     throw new lazy.error.UnsupportedOperationError(
3093       "Called reftest:teardown before reftest:start"
3094     );
3095   }
3097   this._reftest.teardown();
3098   this._reftest = null;
3102  * Print page as PDF.
3104  * @param {object} cmd
3105  * @param {boolean=} cmd.parameters.background
3106  *     Whether or not to print background colors and images.
3107  *     Defaults to false, which prints without background graphics.
3108  * @param {number=} cmd.parameters.margin.bottom
3109  *     Bottom margin in cm. Defaults to 1cm (~0.4 inches).
3110  * @param {number=} cmd.parameters.margin.left
3111  *     Left margin in cm. Defaults to 1cm (~0.4 inches).
3112  * @param {number=} cmd.parameters.margin.right
3113  *     Right margin in cm. Defaults to 1cm (~0.4 inches).
3114  * @param {number=} cmd.parameters.margin.top
3115  *     Top margin in cm. Defaults to 1cm (~0.4 inches).
3116  * @param {('landscape'|'portrait')=} cmd.parameters.options.orientation
3117  *     Paper orientation. Defaults to 'portrait'.
3118  * @param {Array.<string|number>=} cmd.parameters.pageRanges
3119  *     Paper ranges to print, e.g., ['1-5', 8, '11-13'].
3120  *     Defaults to the empty array, which means print all pages.
3121  * @param {number=} cmd.parameters.page.height
3122  *     Paper height in cm. Defaults to US letter height (27.94cm / 11 inches)
3123  * @param {number=} cmd.parameters.page.width
3124  *     Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches)
3125  * @param {number=} cmd.parameters.scale
3126  *     Scale of the webpage rendering. Defaults to 1.0.
3127  * @param {boolean=} cmd.parameters.shrinkToFit
3128  *     Whether or not to override page size as defined by CSS.
3129  *     Defaults to true, in which case the content will be scaled
3130  *     to fit the paper size.
3132  * @returns {string}
3133  *     Base64 encoded PDF representing printed document
3135  * @throws {NoSuchWindowError}
3136  *     Top-level browsing context has been discarded.
3137  * @throws {UnexpectedAlertOpenError}
3138  *     A modal dialog is open, blocking this operation.
3139  * @throws {UnsupportedOperationError}
3140  *     Not available in chrome context.
3141  */
3142 GeckoDriver.prototype.print = async function (cmd) {
3143   lazy.assert.content(this.context);
3144   lazy.assert.open(this.getBrowsingContext({ top: true }));
3145   await this._handleUserPrompts();
3147   const settings = lazy.print.addDefaultSettings(cmd.parameters);
3148   for (const prop of ["top", "bottom", "left", "right"]) {
3149     lazy.assert.positiveNumber(
3150       settings.margin[prop],
3151       lazy.pprint`margin.${prop} is not a positive number`
3152     );
3153   }
3154   for (const prop of ["width", "height"]) {
3155     lazy.assert.positiveNumber(
3156       settings.page[prop],
3157       lazy.pprint`page.${prop} is not a positive number`
3158     );
3159   }
3160   lazy.assert.positiveNumber(
3161     settings.scale,
3162     `scale ${settings.scale} is not a positive number`
3163   );
3164   lazy.assert.that(
3165     s =>
3166       s >= lazy.print.minScaleValue &&
3167       settings.scale <= lazy.print.maxScaleValue,
3168     `scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
3169   )(settings.scale);
3170   lazy.assert.boolean(settings.shrinkToFit);
3171   lazy.assert.that(
3172     orientation => lazy.print.defaults.orientationValue.includes(orientation),
3173     `orientation ${
3174       settings.orientation
3175     } doesn't match allowed values "${lazy.print.defaults.orientationValue.join(
3176       "/"
3177     )}"`
3178   )(settings.orientation);
3179   lazy.assert.boolean(settings.background);
3180   lazy.assert.array(settings.pageRanges);
3182   const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext;
3183   const printSettings = await lazy.print.getPrintSettings(settings);
3184   const binaryString = await lazy.print.printToBinaryString(
3185     browsingContext,
3186     printSettings
3187   );
3189   return btoa(binaryString);
3192 GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
3193   const {
3194     protocol,
3195     transport,
3196     hasResidentKey,
3197     hasUserVerification,
3198     isUserConsenting,
3199     isUserVerified,
3200   } = cmd.parameters;
3202   lazy.assert.string(
3203     protocol,
3204     "addVirtualAuthenticator: protocol must be a string"
3205   );
3206   lazy.assert.string(
3207     transport,
3208     "addVirtualAuthenticator: transport must be a string"
3209   );
3210   lazy.assert.boolean(
3211     hasResidentKey,
3212     "addVirtualAuthenticator: hasResidentKey must be a boolean"
3213   );
3214   lazy.assert.boolean(
3215     hasUserVerification,
3216     "addVirtualAuthenticator: hasUserVerification must be a boolean"
3217   );
3218   lazy.assert.boolean(
3219     isUserConsenting,
3220     "addVirtualAuthenticator: isUserConsenting must be a boolean"
3221   );
3222   lazy.assert.boolean(
3223     isUserVerified,
3224     "addVirtualAuthenticator: isUserVerified must be a boolean"
3225   );
3227   return lazy.webauthn.addVirtualAuthenticator(
3228     protocol,
3229     transport,
3230     hasResidentKey,
3231     hasUserVerification,
3232     isUserConsenting,
3233     isUserVerified
3234   );
3237 GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
3238   const { authenticatorId } = cmd.parameters;
3240   lazy.assert.positiveInteger(
3241     authenticatorId,
3242     "removeVirtualAuthenticator: authenticatorId must be a positiveInteger"
3243   );
3245   lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
3248 GeckoDriver.prototype.addCredential = function (cmd) {
3249   const {
3250     authenticatorId,
3251     credentialId,
3252     isResidentCredential,
3253     rpId,
3254     privateKey,
3255     userHandle,
3256     signCount,
3257   } = cmd.parameters;
3259   lazy.assert.positiveInteger(
3260     authenticatorId,
3261     "addCredential: authenticatorId must be a positiveInteger"
3262   );
3263   lazy.assert.string(
3264     credentialId,
3265     "addCredential: credentialId must be a string"
3266   );
3267   lazy.assert.boolean(
3268     isResidentCredential,
3269     "addCredential: isResidentCredential must be a boolean"
3270   );
3271   lazy.assert.string(rpId, "addCredential: rpId must be a string");
3272   lazy.assert.string(privateKey, "addCredential: privateKey must be a string");
3273   if (userHandle) {
3274     lazy.assert.string(
3275       userHandle,
3276       "addCredential: userHandle must be a string if present"
3277     );
3278   }
3279   lazy.assert.number(signCount, "addCredential: signCount must be a number");
3281   lazy.webauthn.addCredential(
3282     authenticatorId,
3283     credentialId,
3284     isResidentCredential,
3285     rpId,
3286     privateKey,
3287     userHandle,
3288     signCount
3289   );
3292 GeckoDriver.prototype.getCredentials = function (cmd) {
3293   const { authenticatorId } = cmd.parameters;
3295   lazy.assert.positiveInteger(
3296     authenticatorId,
3297     "getCredentials: authenticatorId must be a positiveInteger"
3298   );
3300   return lazy.webauthn.getCredentials(authenticatorId);
3303 GeckoDriver.prototype.removeCredential = function (cmd) {
3304   const { authenticatorId, credentialId } = cmd.parameters;
3306   lazy.assert.positiveInteger(
3307     authenticatorId,
3308     "removeCredential: authenticatorId must be a positiveInteger"
3309   );
3310   lazy.assert.string(
3311     credentialId,
3312     "removeCredential: credentialId must be a string"
3313   );
3315   lazy.webauthn.removeCredential(authenticatorId, credentialId);
3318 GeckoDriver.prototype.removeAllCredentials = function (cmd) {
3319   const { authenticatorId } = cmd.parameters;
3321   lazy.assert.positiveInteger(
3322     authenticatorId,
3323     "removeAllCredentials: authenticatorId must be a positiveInteger"
3324   );
3326   lazy.webauthn.removeAllCredentials(authenticatorId);
3329 GeckoDriver.prototype.setUserVerified = function (cmd) {
3330   const { authenticatorId, isUserVerified } = cmd.parameters;
3332   lazy.assert.positiveInteger(
3333     authenticatorId,
3334     "setUserVerified: authenticatorId must be a positiveInteger"
3335   );
3336   lazy.assert.boolean(
3337     isUserVerified,
3338     "setUserVerified: isUserVerified must be a boolean"
3339   );
3341   lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
3344 GeckoDriver.prototype.setPermission = async function (cmd) {
3345   const { descriptor, state, oneRealm = false } = cmd.parameters;
3347   lazy.assert.boolean(oneRealm);
3348   lazy.assert.that(
3349     state => ["granted", "denied", "prompt"].includes(state),
3350     `state is ${state}, expected "granted", "denied", or "prompt"`
3351   )(state);
3353   lazy.permissions.set(descriptor, state, oneRealm);
3357  * Determines the Accessibility label for this element.
3359  * @param {object} cmd
3360  * @param {string} cmd.parameters.id
3361  *     Web element reference ID to the element for which the accessibility label
3362  *     will be returned.
3364  * @returns {string}
3365  *     The Accessibility label for this element
3366  */
3367 GeckoDriver.prototype.getComputedLabel = async function (cmd) {
3368   lazy.assert.open(this.getBrowsingContext());
3369   await this._handleUserPrompts();
3371   let id = lazy.assert.string(cmd.parameters.id);
3372   let webEl = lazy.WebElement.fromUUID(id).toJSON();
3374   return this.getActor().getComputedLabel(webEl);
3378  * Determines the Accessibility role for this element.
3380  * @param {object} cmd
3381  * @param {string} cmd.parameters.id
3382  *     Web element reference ID to the element for which the accessibility role
3383  *     will be returned.
3385  * @returns {string}
3386  *     The Accessibility role for this element
3387  */
3388 GeckoDriver.prototype.getComputedRole = async function (cmd) {
3389   lazy.assert.open(this.getBrowsingContext());
3390   await this._handleUserPrompts();
3392   let id = lazy.assert.string(cmd.parameters.id);
3393   let webEl = lazy.WebElement.fromUUID(id).toJSON();
3394   return this.getActor().getComputedRole(webEl);
3397 GeckoDriver.prototype.commands = {
3398   // Marionette service
3399   "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
3400   "Marionette:GetContext": GeckoDriver.prototype.getContext,
3401   "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
3402   "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
3403   "Marionette:Quit": GeckoDriver.prototype.quit,
3404   "Marionette:SetContext": GeckoDriver.prototype.setContext,
3405   "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
3407   // Addon service
3408   "Addon:Install": GeckoDriver.prototype.installAddon,
3409   "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
3411   // L10n service
3412   "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
3413   "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
3415   // Reftest service
3416   "reftest:setup": GeckoDriver.prototype.setupReftest,
3417   "reftest:run": GeckoDriver.prototype.runReftest,
3418   "reftest:teardown": GeckoDriver.prototype.teardownReftest,
3420   // WebDriver service
3421   "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
3422   // deprecated, no longer used since the geckodriver 0.30.0 release
3423   "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
3424   "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
3425   "WebDriver:Back": GeckoDriver.prototype.goBack,
3426   "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
3427   "WebDriver:CloseWindow": GeckoDriver.prototype.close,
3428   "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
3429   "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
3430   "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
3431   "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
3432   "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
3433   "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
3434   "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
3435   "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
3436   "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
3437   "WebDriver:FindElement": GeckoDriver.prototype.findElement,
3438   "WebDriver:FindElementFromShadowRoot":
3439     GeckoDriver.prototype.findElementFromShadowRoot,
3440   "WebDriver:FindElements": GeckoDriver.prototype.findElements,
3441   "WebDriver:FindElementsFromShadowRoot":
3442     GeckoDriver.prototype.findElementsFromShadowRoot,
3443   "WebDriver:Forward": GeckoDriver.prototype.goForward,
3444   "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
3445   "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
3446   "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
3447   "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
3448   "WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel,
3449   "WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole,
3450   "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
3451   "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
3452   "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
3453   "WebDriver:GetElementCSSValue":
3454     GeckoDriver.prototype.getElementValueOfCssProperty,
3455   "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
3456   "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
3457   "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
3458   "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
3459   "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
3460   "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
3461   "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
3462   "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
3463   "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
3464   "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
3465   "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
3466   "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
3467   "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
3468   "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
3469   "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
3470   "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
3471   "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
3472   "WebDriver:NewSession": GeckoDriver.prototype.newSession,
3473   "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
3474   "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
3475   "WebDriver:Print": GeckoDriver.prototype.print,
3476   "WebDriver:Refresh": GeckoDriver.prototype.refresh,
3477   "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
3478   "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
3479   "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
3480   "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
3481   "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
3482   "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
3483   "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
3484   "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
3485   "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
3487   // WebAuthn
3488   "WebAuthn:AddVirtualAuthenticator":
3489     GeckoDriver.prototype.addVirtualAuthenticator,
3490   "WebAuthn:RemoveVirtualAuthenticator":
3491     GeckoDriver.prototype.removeVirtualAuthenticator,
3492   "WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
3493   "WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
3494   "WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
3495   "WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
3496   "WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
3499 async function exitFullscreen(win) {
3500   let cb;
3501   // Use a timed promise to abort if no window manager is present
3502   await new lazy.TimedPromise(
3503     resolve => {
3504       cb = new lazy.DebounceCallback(resolve);
3505       win.addEventListener("sizemodechange", cb);
3506       win.fullScreen = false;
3507     },
3508     { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3509   );
3510   win.removeEventListener("sizemodechange", cb);
3511   await new lazy.IdlePromise(win);
3514 async function restoreWindow(win) {
3515   let cb;
3516   if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
3517     return;
3518   }
3519   // Use a timed promise to abort if no window manager is present
3520   await new lazy.TimedPromise(
3521     resolve => {
3522       cb = new lazy.DebounceCallback(resolve);
3523       win.addEventListener("sizemodechange", cb);
3524       win.restore();
3525     },
3526     { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3527   );
3528   win.removeEventListener("sizemodechange", cb);
3529   await new lazy.IdlePromise(win);