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