Bug 1745595 wait for expected geometry after move or resize r=whimboo,webdriver-reviewers
[gecko.git] / remote / marionette / driver.js
blob2ddf2d84ed74899fbaf22097ebab943581ccbf40
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 "use strict";
7 const EXPORTED_SYMBOLS = ["GeckoDriver"];
9 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
10 const { XPCOMUtils } = ChromeUtils.import(
11   "resource://gre/modules/XPCOMUtils.jsm"
14 const lazy = {};
16 XPCOMUtils.defineLazyModuleGetters(lazy, {
17   Addon: "chrome://remote/content/marionette/addon.js",
18   AppInfo: "chrome://remote/content/marionette/appinfo.js",
19   assert: "chrome://remote/content/shared/webdriver/Assert.jsm",
20   atom: "chrome://remote/content/marionette/atom.js",
21   browser: "chrome://remote/content/marionette/browser.js",
22   capture: "chrome://remote/content/marionette/capture.js",
23   clearActionInputState:
24     "chrome://remote/content/marionette/actors/MarionetteCommandsChild.jsm",
25   clearElementIdCache:
26     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
27   Context: "chrome://remote/content/marionette/browser.js",
28   cookie: "chrome://remote/content/marionette/cookie.js",
29   DebounceCallback: "chrome://remote/content/marionette/sync.js",
30   disableEventsActor:
31     "chrome://remote/content/marionette/actors/MarionetteEventsParent.jsm",
32   element: "chrome://remote/content/marionette/element.js",
33   enableEventsActor:
34     "chrome://remote/content/marionette/actors/MarionetteEventsParent.jsm",
35   error: "chrome://remote/content/shared/webdriver/Errors.jsm",
36   EventPromise: "chrome://remote/content/shared/Sync.jsm",
37   getMarionetteCommandsActorProxy:
38     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
39   IdlePromise: "chrome://remote/content/marionette/sync.js",
40   l10n: "chrome://remote/content/marionette/l10n.js",
41   Log: "chrome://remote/content/shared/Log.jsm",
42   Marionette: "chrome://remote/content/components/Marionette.jsm",
43   MarionettePrefs: "chrome://remote/content/marionette/prefs.js",
44   modal: "chrome://remote/content/marionette/modal.js",
45   navigate: "chrome://remote/content/marionette/navigate.js",
46   permissions: "chrome://remote/content/marionette/permissions.js",
47   PollPromise: "chrome://remote/content/marionette/sync.js",
48   pprint: "chrome://remote/content/shared/Format.jsm",
49   print: "chrome://remote/content/shared/PDF.jsm",
50   reftest: "chrome://remote/content/marionette/reftest.js",
51   registerCommandsActor:
52     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
53   RemoteAgent: "chrome://remote/content/components/RemoteAgent.jsm",
54   TabManager: "chrome://remote/content/shared/TabManager.jsm",
55   TimedPromise: "chrome://remote/content/marionette/sync.js",
56   Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.jsm",
57   UnhandledPromptBehavior:
58     "chrome://remote/content/shared/webdriver/Capabilities.jsm",
59   unregisterCommandsActor:
60     "chrome://remote/content/marionette/actors/MarionetteCommandsParent.jsm",
61   waitForInitialNavigationCompleted:
62     "chrome://remote/content/shared/Navigate.jsm",
63   waitForObserverTopic: "chrome://remote/content/marionette/sync.js",
64   WebDriverSession: "chrome://remote/content/shared/webdriver/Session.jsm",
65   WebElement: "chrome://remote/content/marionette/element.js",
66   WebElementEventTarget: "chrome://remote/content/marionette/dom.js",
67   windowManager: "chrome://remote/content/shared/WindowManager.jsm",
68   WindowState: "chrome://remote/content/marionette/browser.js",
69 });
71 XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
72   lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
75 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
77 const SUPPORTED_STRATEGIES = new Set([
78   lazy.element.Strategy.ClassName,
79   lazy.element.Strategy.Selector,
80   lazy.element.Strategy.ID,
81   lazy.element.Strategy.Name,
82   lazy.element.Strategy.LinkText,
83   lazy.element.Strategy.PartialLinkText,
84   lazy.element.Strategy.TagName,
85   lazy.element.Strategy.XPath,
86 ]);
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 /**
93  * The Marionette WebDriver services provides a standard conforming
94  * implementation of the W3C WebDriver specification.
95  *
96  * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
97  * @namespace driver
98  */
101  * Implements (parts of) the W3C WebDriver protocol.  GeckoDriver lives
102  * in chrome space and mediates calls to the current browsing context's actor.
104  * Throughout this prototype, functions with the argument <var>cmd</var>'s
105  * documentation refers to the contents of the <code>cmd.parameter</code>
106  * object.
108  * @class GeckoDriver
110  * @param {MarionetteServer} server
111  *     The instance of Marionette server.
112  */
113 function GeckoDriver(server) {
114   this._server = server;
116   // WebDriver Session
117   this._currentSession = null;
119   this.browsers = {};
121   // points to current browser
122   this.curBrowser = null;
123   // top-most chrome window
124   this.mainFrame = null;
126   // Use content context by default
127   this.context = lazy.Context.Content;
129   // used for modal dialogs or tab modal alerts
130   this.dialog = null;
131   this.dialogObserver = null;
135  * The current context decides if commands are executed in chrome- or
136  * content space.
137  */
138 Object.defineProperty(GeckoDriver.prototype, "context", {
139   get() {
140     return this._context;
141   },
143   set(context) {
144     this._context = lazy.Context.fromString(context);
145   },
149  * The current WebDriver Session.
150  */
151 Object.defineProperty(GeckoDriver.prototype, "currentSession", {
152   get() {
153     if (lazy.RemoteAgent.webDriverBiDi) {
154       return lazy.RemoteAgent.webDriverBiDi.session;
155     }
157     return this._currentSession;
158   },
162  * Returns the current URL of the ChromeWindow or content browser,
163  * depending on context.
165  * @return {URL}
166  *     Read-only property containing the currently loaded URL.
167  */
168 Object.defineProperty(GeckoDriver.prototype, "currentURL", {
169   get() {
170     const browsingContext = this.getBrowsingContext({ top: true });
171     return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
172   },
176  * Returns the title of the ChromeWindow or content browser,
177  * depending on context.
179  * @return {string}
180  *     Read-only property containing the title of the loaded URL.
181  */
182 Object.defineProperty(GeckoDriver.prototype, "title", {
183   get() {
184     const browsingContext = this.getBrowsingContext({ top: true });
185     return browsingContext.currentWindowGlobal.documentTitle;
186   },
189 Object.defineProperty(GeckoDriver.prototype, "windowType", {
190   get() {
191     return this.curBrowser.window.document.documentElement.getAttribute(
192       "windowtype"
193     );
194   },
197 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
198   "nsIObserver",
199   "nsISupportsWeakReference",
203  * Callback used to observe the creation of new modal or tab modal dialogs
204  * during the session's lifetime.
205  */
206 GeckoDriver.prototype.handleModalDialog = function(action, dialog) {
207   if (!this.currentSession) {
208     return;
209   }
211   if (action === lazy.modal.ACTION_OPENED) {
212     this.dialog = new lazy.modal.Dialog(() => this.curBrowser, dialog);
213     this.getActor().notifyDialogOpened();
214   } else if (action === lazy.modal.ACTION_CLOSED) {
215     this.dialog = null;
216   }
220  * Get the current visible URL.
221  */
222 GeckoDriver.prototype._getCurrentURL = function() {
223   const browsingContext = this.getBrowsingContext({ top: true });
224   return new URL(browsingContext.currentURI.spec);
228  * Get the current "MarionetteCommands" parent actor.
230  * @param {Object} options
231  * @param {boolean=} options.top
232  *     If set to true use the window's top-level browsing context for the actor,
233  *     otherwise the one from the currently selected frame. Defaults to false.
235  * @returns {MarionetteCommandsParent}
236  *     The parent actor.
237  */
238 GeckoDriver.prototype.getActor = function(options = {}) {
239   return lazy.getMarionetteCommandsActorProxy(() =>
240     this.getBrowsingContext(options)
241   );
245  * Get the selected BrowsingContext for the current context.
247  * @param {Object} options
248  * @param {Context=} options.context
249  *     Context (content or chrome) for which to retrieve the browsing context.
250  *     Defaults to the current one.
251  * @param {boolean=} options.parent
252  *     If set to true return the window's parent browsing context,
253  *     otherwise the one from the currently selected frame. Defaults to false.
254  * @param {boolean=} options.top
255  *     If set to true return the window's top-level browsing context,
256  *     otherwise the one from the currently selected frame. Defaults to false.
258  * @return {BrowsingContext}
259  *     The browsing context, or `null` if none is available
260  */
261 GeckoDriver.prototype.getBrowsingContext = function(options = {}) {
262   const { context = this.context, parent = false, top = false } = options;
264   let browsingContext = null;
265   if (context === lazy.Context.Chrome) {
266     browsingContext = this.currentSession?.chromeBrowsingContext;
267   } else {
268     browsingContext = this.currentSession?.contentBrowsingContext;
269   }
271   if (browsingContext && parent) {
272     browsingContext = browsingContext.parent;
273   }
275   if (browsingContext && top) {
276     browsingContext = browsingContext.top;
277   }
279   return browsingContext;
283  * Get the currently selected window.
285  * It will return the outer {@link ChromeWindow} previously selected by
286  * window handle through {@link #switchToWindow}, or the first window that
287  * was registered.
289  * @param {Object} options
290  * @param {Context=} options.context
291  *     Optional name of the context to use for finding the window.
292  *     It will be required if a command always needs a specific context,
293  *     whether which context is currently set. Defaults to the current
294  *     context.
296  * @return {ChromeWindow}
297  *     The current top-level browsing context.
298  */
299 GeckoDriver.prototype.getCurrentWindow = function(options = {}) {
300   const { context = this.context } = options;
302   let win = null;
303   switch (context) {
304     case lazy.Context.Chrome:
305       if (this.curBrowser) {
306         win = this.curBrowser.window;
307       }
308       break;
310     case lazy.Context.Content:
311       if (this.curBrowser && this.curBrowser.contentBrowser) {
312         win = this.curBrowser.window;
313       }
314       break;
315   }
317   return win;
320 GeckoDriver.prototype.isReftestBrowser = function(element) {
321   return (
322     this._reftest &&
323     element &&
324     element.tagName === "xul:browser" &&
325     element.parentElement &&
326     element.parentElement.id === "reftest"
327   );
331  * Create a new browsing context for window and add to known browsers.
333  * @param {ChromeWindow} win
334  *     Window for which we will create a browsing context.
336  * @return {string}
337  *     Returns the unique server-assigned ID of the window.
338  */
339 GeckoDriver.prototype.addBrowser = function(win) {
340   let context = new lazy.browser.Context(win, this);
341   let winId = lazy.windowManager.getIdForWindow(win);
343   this.browsers[winId] = context;
344   this.curBrowser = this.browsers[winId];
348  * Recursively get all labeled text.
350  * @param {Element} el
351  *     The parent element.
352  * @param {Array.<string>} lines
353  *      Array that holds the text lines.
354  */
355 GeckoDriver.prototype.getVisibleText = function(el, lines) {
356   try {
357     if (lazy.atom.isElementDisplayed(el, this.getCurrentWindow())) {
358       if (el.value) {
359         lines.push(el.value);
360       }
361       for (let child in el.childNodes) {
362         this.getVisibleText(el.childNodes[child], lines);
363       }
364     }
365   } catch (e) {
366     if (el.nodeName == "#text") {
367       lines.push(el.textContent);
368     }
369   }
373  * Handles registration of new content browsers.  Depending on
374  * their type they are either accepted or ignored.
376  * @param {xul:browser} browserElement
377  */
378 GeckoDriver.prototype.registerBrowser = function(browserElement) {
379   // We want to ignore frames that are XUL browsers that aren't in the "main"
380   // tabbrowser, but accept things on Fennec (which doesn't have a
381   // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
382   // as well as XUL frames. Ideally this should be cleaned up and we should
383   // keep track of browsers a different way.
384   if (
385     !lazy.AppInfo.isFirefox ||
386     browserElement.namespaceURI != XUL_NS ||
387     browserElement.nodeName != "browser" ||
388     browserElement.getTabBrowser()
389   ) {
390     this.curBrowser.register(browserElement);
391   }
395  * Create a new WebDriver session.
397  * @param {Object} cmd
398  * @param {Object.<string, *>=} cmd.parameters
399  *     JSON Object containing any of the recognised capabilities as listed
400  *     on the `WebDriverSession` class.
402  * @return {Object}
403  *     Session ID and capabilities offered by the WebDriver service.
405  * @throws {SessionNotCreatedError}
406  *     If, for whatever reason, a session could not be created.
407  */
408 GeckoDriver.prototype.newSession = async function(cmd) {
409   if (this.currentSession) {
410     throw new lazy.error.SessionNotCreatedError(
411       "Maximum number of active sessions"
412     );
413   }
415   const { parameters: capabilities } = cmd;
417   try {
418     // If the WebDriver BiDi protocol is active always use the Remote Agent
419     // to handle the WebDriver session. If it's not the case then Marionette
420     // itself needs to handle it, and has to nullify the "webSocketUrl"
421     // capability.
422     if (lazy.RemoteAgent.webDriverBiDi) {
423       await lazy.RemoteAgent.webDriverBiDi.createSession(capabilities);
424     } else {
425       this._currentSession = new lazy.WebDriverSession(capabilities);
426       this._currentSession.capabilities.delete("webSocketUrl");
427     }
429     // Don't wait for the initial window when Marionette is in windowless mode
430     if (!this.currentSession.capabilities.get("moz:windowless")) {
431       // Creating a WebDriver session too early can cause issues with
432       // clients in not being able to find any available window handle.
433       // Also when closing the application while it's still starting up can
434       // cause shutdown hangs. As such Marionette will return a new session
435       // once the initial application window has finished initializing.
436       lazy.logger.debug(`Waiting for initial application window`);
437       await lazy.Marionette.browserStartupFinished;
439       const appWin = await lazy.windowManager.waitForInitialApplicationWindowLoaded();
441       if (lazy.MarionettePrefs.clickToStart) {
442         Services.prompt.alert(
443           appWin,
444           "",
445           "Click to start execution of marionette tests"
446         );
447       }
449       this.addBrowser(appWin);
450       this.mainFrame = appWin;
452       // Setup observer for modal dialogs
453       this.dialogObserver = new lazy.modal.DialogObserver(
454         () => this.curBrowser
455       );
456       this.dialogObserver.add(this.handleModalDialog.bind(this));
458       for (let win of lazy.windowManager.windows) {
459         const tabBrowser = lazy.TabManager.getTabBrowser(win);
461         if (tabBrowser) {
462           for (const tab of tabBrowser.tabs) {
463             const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
464             this.registerBrowser(contentBrowser);
465           }
466         }
468         this.registerListenersForWindow(win);
469       }
471       if (this.mainFrame) {
472         this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
473         this.mainFrame.focus();
474       }
476       if (this.curBrowser.tab) {
477         const browsingContext = this.curBrowser.contentBrowser.browsingContext;
478         this.currentSession.contentBrowsingContext = browsingContext;
480         await lazy.waitForInitialNavigationCompleted(
481           browsingContext.webProgress
482         );
484         this.curBrowser.contentBrowser.focus();
485       }
487       // Check if there is already an open dialog for the selected browser window.
488       this.dialog = lazy.modal.findModalDialogs(this.curBrowser);
489     }
491     lazy.registerCommandsActor();
492     lazy.enableEventsActor();
494     Services.obs.addObserver(this, "browser-delayed-startup-finished");
495   } catch (e) {
496     throw new lazy.error.SessionNotCreatedError(e);
497   }
499   return {
500     sessionId: this.currentSession.id,
501     capabilities: this.currentSession.capabilities,
502   };
506  * Register event listeners for the specified window.
508  * @param {ChromeWindow} win
509  *     Chrome window to register event listeners for.
510  */
511 GeckoDriver.prototype.registerListenersForWindow = function(win) {
512   const tabBrowser = lazy.TabManager.getTabBrowser(win);
514   // Listen for any kind of top-level process switch
515   tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
519  * Unregister event listeners for the specified window.
521  * @param {ChromeWindow} win
522  *     Chrome window to unregister event listeners for.
523  */
524 GeckoDriver.prototype.unregisterListenersForWindow = function(win) {
525   const tabBrowser = lazy.TabManager.getTabBrowser(win);
527   tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
530 GeckoDriver.prototype.handleEvent = function({ target, type }) {
531   switch (type) {
532     case "XULFrameLoaderCreated":
533       if (target === this.curBrowser.contentBrowser) {
534         lazy.logger.trace(
535           "Remoteness change detected. Set new top-level browsing context " +
536             `to ${target.browsingContext.id}`
537         );
539         this.currentSession.contentBrowsingContext = target.browsingContext;
540       }
541       break;
542   }
545 GeckoDriver.prototype.observe = function(subject, topic, data) {
546   switch (topic) {
547     case "browser-delayed-startup-finished":
548       this.registerListenersForWindow(subject);
549       break;
550   }
554  * Send the current session's capabilities to the client.
556  * Capabilities informs the client of which WebDriver features are
557  * supported by Firefox and Marionette.  They are immutable for the
558  * length of the session.
560  * The return value is an immutable map of string keys
561  * ("capabilities") to values, which may be of types boolean,
562  * numerical or string.
563  */
564 GeckoDriver.prototype.getSessionCapabilities = function() {
565   return { capabilities: this.currentSession.capabilities };
569  * Sets the context of the subsequent commands.
571  * All subsequent requests to commands that in some way involve
572  * interaction with a browsing context will target the chosen browsing
573  * context.
575  * @param {string} value
576  *     Name of the context to be switched to.  Must be one of "chrome" or
577  *     "content".
579  * @throws {InvalidArgumentError}
580  *     If <var>value</var> is not a string.
581  * @throws {WebDriverError}
582  *     If <var>value</var> is not a valid browsing context.
583  */
584 GeckoDriver.prototype.setContext = function(cmd) {
585   let value = lazy.assert.string(cmd.parameters.value);
587   this.context = value;
591  * Gets the context type that is Marionette's current target for
592  * browsing context scoped commands.
594  * You may choose a context through the {@link #setContext} command.
596  * The default browsing context is {@link Context.Content}.
598  * @return {Context}
599  *     Current context.
600  */
601 GeckoDriver.prototype.getContext = function() {
602   return this.context;
606  * Executes a JavaScript function in the context of the current browsing
607  * context, if in content space, or in chrome space otherwise, and returns
608  * the return value of the function.
610  * It is important to note that if the <var>sandboxName</var> parameter
611  * is left undefined, the script will be evaluated in a mutable sandbox,
612  * causing any change it makes on the global state of the document to have
613  * lasting side-effects.
615  * @param {string} script
616  *     Script to evaluate as a function body.
617  * @param {Array.<(string|boolean|number|object|WebElement)>} args
618  *     Arguments exposed to the script in <code>arguments</code>.
619  *     The array items must be serialisable to the WebDriver protocol.
620  * @param {string=} sandbox
621  *     Name of the sandbox to evaluate the script in.  The sandbox is
622  *     cached for later re-use on the same Window object if
623  *     <var>newSandbox</var> is false.  If he parameter is undefined,
624  *     the script is evaluated in a mutable sandbox.  If the parameter
625  *     is "system", it will be evaluted in a sandbox with elevated system
626  *     privileges, equivalent to chrome space.
627  * @param {boolean=} newSandbox
628  *     Forces the script to be evaluated in a fresh sandbox.  Note that if
629  *     it is undefined, the script will normally be evaluted in a fresh
630  *     sandbox.
631  * @param {string=} filename
632  *     Filename of the client's program where this script is evaluated.
633  * @param {number=} line
634  *     Line in the client's program where this script is evaluated.
636  * @return {(string|boolean|number|object|WebElement)}
637  *     Return value from the script, or null which signifies either the
638  *     JavaScript notion of null or undefined.
640  * @throws {JavaScriptError}
641  *     If an {@link Error} was thrown whilst evaluating the script.
642  * @throws {NoSuchWindowError}
643  *     Browsing context has been discarded.
644  * @throws {ScriptTimeoutError}
645  *     If the script was interrupted due to reaching the session's
646  *     script timeout.
647  */
648 GeckoDriver.prototype.executeScript = async function(cmd) {
649   let { script, args } = cmd.parameters;
650   let opts = {
651     script: cmd.parameters.script,
652     args: cmd.parameters.args,
653     sandboxName: cmd.parameters.sandbox,
654     newSandbox: cmd.parameters.newSandbox,
655     file: cmd.parameters.filename,
656     line: cmd.parameters.line,
657   };
659   return { value: await this.execute_(script, args, opts) };
663  * Executes a JavaScript function in the context of the current browsing
664  * context, if in content space, or in chrome space otherwise, and returns
665  * the object passed to the callback.
667  * The callback is always the last argument to the <var>arguments</var>
668  * list passed to the function scope of the script.  It can be retrieved
669  * as such:
671  * <pre><code>
672  *     let callback = arguments[arguments.length - 1];
673  *     callback("foo");
674  *     // "foo" is returned
675  * </code></pre>
677  * It is important to note that if the <var>sandboxName</var> parameter
678  * is left undefined, the script will be evaluated in a mutable sandbox,
679  * causing any change it makes on the global state of the document to have
680  * lasting side-effects.
682  * @param {string} script
683  *     Script to evaluate as a function body.
684  * @param {Array.<(string|boolean|number|object|WebElement)>} args
685  *     Arguments exposed to the script in <code>arguments</code>.
686  *     The array items must be serialisable to the WebDriver protocol.
687  * @param {string=} sandbox
688  *     Name of the sandbox to evaluate the script in.  The sandbox is
689  *     cached for later re-use on the same Window object if
690  *     <var>newSandbox</var> is false.  If the parameter is undefined,
691  *     the script is evaluated in a mutable sandbox.  If the parameter
692  *     is "system", it will be evaluted in a sandbox with elevated system
693  *     privileges, equivalent to chrome space.
694  * @param {boolean=} newSandbox
695  *     Forces the script to be evaluated in a fresh sandbox.  Note that if
696  *     it is undefined, the script will normally be evaluted in a fresh
697  *     sandbox.
698  * @param {string=} filename
699  *     Filename of the client's program where this script is evaluated.
700  * @param {number=} line
701  *     Line in the client's program where this script is evaluated.
703  * @return {(string|boolean|number|object|WebElement)}
704  *     Return value from the script, or null which signifies either the
705  *     JavaScript notion of null or undefined.
707  * @throws {JavaScriptError}
708  *     If an Error was thrown whilst evaluating the script.
709  * @throws {NoSuchWindowError}
710  *     Browsing context has been discarded.
711  * @throws {ScriptTimeoutError}
712  *     If the script was interrupted due to reaching the session's
713  *     script timeout.
714  */
715 GeckoDriver.prototype.executeAsyncScript = async function(cmd) {
716   let { script, args } = cmd.parameters;
717   let opts = {
718     script: cmd.parameters.script,
719     args: cmd.parameters.args,
720     sandboxName: cmd.parameters.sandbox,
721     newSandbox: cmd.parameters.newSandbox,
722     file: cmd.parameters.filename,
723     line: cmd.parameters.line,
724     async: true,
725   };
727   return { value: await this.execute_(script, args, opts) };
730 GeckoDriver.prototype.execute_ = async function(
731   script,
732   args = [],
733   {
734     sandboxName = null,
735     newSandbox = false,
736     file = "",
737     line = 0,
738     async = false,
739   } = {}
740 ) {
741   lazy.assert.open(this.getBrowsingContext());
742   await this._handleUserPrompts();
744   lazy.assert.string(
745     script,
746     lazy.pprint`Expected "script" to be a string: ${script}`
747   );
748   lazy.assert.array(
749     args,
750     lazy.pprint`Expected script args to be an array: ${args}`
751   );
752   if (sandboxName !== null) {
753     lazy.assert.string(
754       sandboxName,
755       lazy.pprint`Expected sandbox name to be a string: ${sandboxName}`
756     );
757   }
758   lazy.assert.boolean(
759     newSandbox,
760     lazy.pprint`Expected newSandbox to be boolean: ${newSandbox}`
761   );
762   lazy.assert.string(file, lazy.pprint`Expected file to be a string: ${file}`);
763   lazy.assert.number(line, lazy.pprint`Expected line to be a number: ${line}`);
765   let opts = {
766     timeout: this.currentSession.timeouts.script,
767     sandboxName,
768     newSandbox,
769     file,
770     line,
771     async,
772   };
774   return this.getActor().executeScript(script, args, opts);
778  * Navigate to given URL.
780  * Navigates the current browsing context to the given URL and waits for
781  * the document to load or the session's page timeout duration to elapse
782  * before returning.
784  * The command will return with a failure if there is an error loading
785  * the document or the URL is blocked.  This can occur if it fails to
786  * reach host, the URL is malformed, or if there is a certificate issue
787  * to name some examples.
789  * The document is considered successfully loaded when the
790  * DOMContentLoaded event on the frame element associated with the
791  * current window triggers and document.readyState is "complete".
793  * In chrome context it will change the current window's location to
794  * the supplied URL and wait until document.readyState equals "complete"
795  * or the page timeout duration has elapsed.
797  * @param {string} url
798  *     URL to navigate to.
800  * @throws {NoSuchWindowError}
801  *     Top-level browsing context has been discarded.
802  * @throws {UnexpectedAlertOpenError}
803  *     A modal dialog is open, blocking this operation.
804  * @throws {UnsupportedOperationError}
805  *     Not available in current context.
806  */
807 GeckoDriver.prototype.navigateTo = async function(cmd) {
808   lazy.assert.content(this.context);
809   const browsingContext = lazy.assert.open(
810     this.getBrowsingContext({ top: true })
811   );
812   await this._handleUserPrompts();
814   let validURL;
815   try {
816     validURL = new URL(cmd.parameters.url);
817   } catch (e) {
818     throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
819   }
821   // Switch to the top-level browsing context before navigating
822   this.currentSession.contentBrowsingContext = browsingContext;
824   const loadEventExpected = lazy.navigate.isLoadEventExpected(
825     this._getCurrentURL(),
826     {
827       future: validURL,
828     }
829   );
831   await lazy.navigate.waitForNavigationCompleted(
832     this,
833     () => {
834       lazy.navigate.navigateTo(browsingContext, validURL);
835     },
836     { loadEventExpected }
837   );
839   this.curBrowser.contentBrowser.focus();
843  * Get a string representing the current URL.
845  * On Desktop this returns a string representation of the URL of the
846  * current top level browsing context.  This is equivalent to
847  * document.location.href.
849  * When in the context of the chrome, this returns the canonical URL
850  * of the current resource.
852  * @throws {NoSuchWindowError}
853  *     Top-level browsing context has been discarded.
854  * @throws {UnexpectedAlertOpenError}
855  *     A modal dialog is open, blocking this operation.
856  */
857 GeckoDriver.prototype.getCurrentUrl = async function() {
858   lazy.assert.open(this.getBrowsingContext({ top: true }));
859   await this._handleUserPrompts();
861   return this._getCurrentURL().href;
865  * Gets the current title of the window.
867  * @return {string}
868  *     Document title of the top-level browsing context.
870  * @throws {NoSuchWindowError}
871  *     Top-level browsing context has been discarded.
872  * @throws {UnexpectedAlertOpenError}
873  *     A modal dialog is open, blocking this operation.
874  */
875 GeckoDriver.prototype.getTitle = async function() {
876   lazy.assert.open(this.getBrowsingContext({ top: true }));
877   await this._handleUserPrompts();
879   return this.title;
883  * Gets the current type of the window.
885  * @return {string}
886  *     Type of window
888  * @throws {NoSuchWindowError}
889  *     Top-level browsing context has been discarded.
890  */
891 GeckoDriver.prototype.getWindowType = function() {
892   lazy.assert.open(this.getBrowsingContext({ top: true }));
894   return this.windowType;
898  * Gets the page source of the content document.
900  * @return {string}
901  *     String serialisation of the DOM of the current browsing context's
902  *     active document.
904  * @throws {NoSuchWindowError}
905  *     Browsing context has been discarded.
906  * @throws {UnexpectedAlertOpenError}
907  *     A modal dialog is open, blocking this operation.
908  */
909 GeckoDriver.prototype.getPageSource = async function() {
910   lazy.assert.open(this.getBrowsingContext());
911   await this._handleUserPrompts();
913   return this.getActor().getPageSource();
917  * Cause the browser to traverse one step backward in the joint history
918  * of the current browsing context.
920  * @throws {NoSuchWindowError}
921  *     Top-level browsing context has been discarded.
922  * @throws {UnexpectedAlertOpenError}
923  *     A modal dialog is open, blocking this operation.
924  * @throws {UnsupportedOperationError}
925  *     Not available in current context.
926  */
927 GeckoDriver.prototype.goBack = async function() {
928   lazy.assert.content(this.context);
929   const browsingContext = lazy.assert.open(
930     this.getBrowsingContext({ top: true })
931   );
932   await this._handleUserPrompts();
934   // If there is no history, just return
935   if (!browsingContext.embedderElement?.canGoBack) {
936     return;
937   }
939   await lazy.navigate.waitForNavigationCompleted(this, () => {
940     browsingContext.goBack();
941   });
945  * Cause the browser to traverse one step forward in the joint history
946  * of the current browsing context.
948  * @throws {NoSuchWindowError}
949  *     Top-level browsing context has been discarded.
950  * @throws {UnexpectedAlertOpenError}
951  *     A modal dialog is open, blocking this operation.
952  * @throws {UnsupportedOperationError}
953  *     Not available in current context.
954  */
955 GeckoDriver.prototype.goForward = async function() {
956   lazy.assert.content(this.context);
957   const browsingContext = lazy.assert.open(
958     this.getBrowsingContext({ top: true })
959   );
960   await this._handleUserPrompts();
962   // If there is no history, just return
963   if (!browsingContext.embedderElement?.canGoForward) {
964     return;
965   }
967   await lazy.navigate.waitForNavigationCompleted(this, () => {
968     browsingContext.goForward();
969   });
973  * Causes the browser to reload the page in current top-level browsing
974  * context.
976  * @throws {NoSuchWindowError}
977  *     Top-level browsing context has been discarded.
978  * @throws {UnexpectedAlertOpenError}
979  *     A modal dialog is open, blocking this operation.
980  * @throws {UnsupportedOperationError}
981  *     Not available in current context.
982  */
983 GeckoDriver.prototype.refresh = async function() {
984   lazy.assert.content(this.context);
985   const browsingContext = lazy.assert.open(
986     this.getBrowsingContext({ top: true })
987   );
988   await this._handleUserPrompts();
990   // Switch to the top-level browsing context before navigating
991   this.currentSession.contentBrowsingContext = browsingContext;
993   await lazy.navigate.waitForNavigationCompleted(this, () => {
994     lazy.navigate.refresh(browsingContext);
995   });
999  * Get the current window's handle. On desktop this typically corresponds
1000  * to the currently selected tab.
1002  * For chrome scope it returns the window identifier for the current chrome
1003  * window for tests interested in managing the chrome window and tab separately.
1005  * Return an opaque server-assigned identifier to this window that
1006  * uniquely identifies it within this Marionette instance.  This can
1007  * be used to switch to this window at a later point.
1009  * @return {string}
1010  *     Unique window handle.
1012  * @throws {NoSuchWindowError}
1013  *     Top-level browsing context has been discarded.
1014  */
1015 GeckoDriver.prototype.getWindowHandle = function() {
1016   lazy.assert.open(this.getBrowsingContext({ top: true }));
1018   if (this.context == lazy.Context.Chrome) {
1019     return lazy.windowManager.getIdForWindow(this.curBrowser.window);
1020   }
1021   return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
1025  * Get a list of top-level browsing contexts. On desktop this typically
1026  * corresponds to the set of open tabs for browser windows, or the window
1027  * itself for non-browser chrome windows.
1029  * For chrome scope it returns identifiers for each open chrome window for
1030  * tests interested in managing a set of chrome windows and tabs separately.
1032  * Each window handle is assigned by the server and is guaranteed unique,
1033  * however the return array does not have a specified ordering.
1035  * @return {Array.<string>}
1036  *     Unique window handles.
1037  */
1038 GeckoDriver.prototype.getWindowHandles = function() {
1039   if (this.context == lazy.Context.Chrome) {
1040     return lazy.windowManager.chromeWindowHandles.map(String);
1041   }
1042   return lazy.TabManager.allBrowserUniqueIds.map(String);
1046  * Get the current position and size of the browser window currently in focus.
1048  * Will return the current browser window size in pixels. Refers to
1049  * window outerWidth and outerHeight values, which include scroll bars,
1050  * title bars, etc.
1052  * @return {Object.<string, number>}
1053  *     Object with |x| and |y| coordinates, and |width| and |height|
1054  *     of browser window.
1056  * @throws {NoSuchWindowError}
1057  *     Top-level browsing context has been discarded.
1058  * @throws {UnexpectedAlertOpenError}
1059  *     A modal dialog is open, blocking this operation.
1060  */
1061 GeckoDriver.prototype.getWindowRect = async function() {
1062   lazy.assert.open(this.getBrowsingContext({ top: true }));
1063   await this._handleUserPrompts();
1065   return this.curBrowser.rect;
1069  * Set the window position and size of the browser on the operating
1070  * system window manager.
1072  * The supplied `width` and `height` values refer to the window `outerWidth`
1073  * and `outerHeight` values, which include browser chrome and OS-level
1074  * window borders.
1076  * @param {number} x
1077  *     X coordinate of the top/left of the window that it will be
1078  *     moved to.
1079  * @param {number} y
1080  *     Y coordinate of the top/left of the window that it will be
1081  *     moved to.
1082  * @param {number} width
1083  *     Width to resize the window to.
1084  * @param {number} height
1085  *     Height to resize the window to.
1087  * @return {Object.<string, number>}
1088  *     Object with `x` and `y` coordinates and `width` and `height`
1089  *     dimensions.
1091  * @throws {NoSuchWindowError}
1092  *     Top-level browsing context has been discarded.
1093  * @throws {UnexpectedAlertOpenError}
1094  *     A modal dialog is open, blocking this operation.
1095  * @throws {UnsupportedOperationError}
1096  *     Not applicable to application.
1097  */
1098 GeckoDriver.prototype.setWindowRect = async function(cmd) {
1099   lazy.assert.desktop();
1100   lazy.assert.open(this.getBrowsingContext({ top: true }));
1101   await this._handleUserPrompts();
1103   const { x = null, y = null, width = null, height = null } = cmd.parameters;
1104   if (x !== null) {
1105     lazy.assert.integer(x);
1106   }
1107   if (y !== null) {
1108     lazy.assert.integer(y);
1109   }
1110   if (height !== null) {
1111     lazy.assert.positiveInteger(height);
1112   }
1113   if (width !== null) {
1114     lazy.assert.positiveInteger(width);
1115   }
1117   const win = this.getCurrentWindow();
1118   switch (lazy.WindowState.from(win.windowState)) {
1119     case lazy.WindowState.Fullscreen:
1120       await exitFullscreen(win);
1121       break;
1123     case lazy.WindowState.Maximized:
1124     case lazy.WindowState.Minimized:
1125       await restoreWindow(win);
1126       break;
1127   }
1129   function geometryMatches() {
1130     if (
1131       width !== null &&
1132       height !== null &&
1133       (win.outerWidth !== width || win.outerHeight !== height)
1134     ) {
1135       return false;
1136     }
1137     if (x !== null && y !== null && (win.screenX !== x || win.screenY !== y)) {
1138       return false;
1139     }
1140     lazy.logger.trace(`Requested window geometry matches`);
1141     return true;
1142   }
1144   if (!geometryMatches()) {
1145     // There might be more than one resize or MozUpdateWindowPos event due
1146     // to previous geometry changes, such as from restoreWindow(), so
1147     // wait longer if window geometry does not match.
1148     const options = { checkFn: geometryMatches, timeout: 500 };
1149     const promises = [];
1150     if (width !== null && height !== null) {
1151       promises.push(new lazy.EventPromise(win, "resize", options));
1152       win.resizeTo(width, height);
1153     }
1154     if (x !== null && y !== null) {
1155       promises.push(
1156         new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
1157       );
1158       win.moveTo(x, y);
1159     }
1160     try {
1161       await Promise.race(promises);
1162     } catch (e) {
1163       if (e instanceof lazy.error.TimeoutError) {
1164         // The operating system might not honor the move or resize, in which
1165         // case assume that geometry will have been adjusted "as close as
1166         // possible" to that requested.  There may be no event received if the
1167         // geometry is already as close as possible.
1168       } else {
1169         throw e;
1170       }
1171     }
1172   }
1174   return this.curBrowser.rect;
1178  * Switch current top-level browsing context by name or server-assigned
1179  * ID.  Searches for windows by name, then ID.  Content windows take
1180  * precedence.
1182  * @param {string} handle
1183  *     Handle of the window to switch to.
1184  * @param {boolean=} focus
1185  *     A boolean value which determines whether to focus
1186  *     the window. Defaults to true.
1188  * @throws {NoSuchWindowError}
1189  *     Top-level browsing context has been discarded.
1190  */
1191 GeckoDriver.prototype.switchToWindow = async function(cmd) {
1192   const { focus = true, handle } = cmd.parameters;
1194   lazy.assert.string(
1195     handle,
1196     lazy.pprint`Expected "handle" to be a string, got ${handle}`
1197   );
1198   lazy.assert.boolean(
1199     focus,
1200     lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
1201   );
1203   const found = lazy.windowManager.findWindowByHandle(handle);
1205   let selected = false;
1206   if (found) {
1207     try {
1208       await this.setWindowHandle(found, focus);
1209       selected = true;
1210     } catch (e) {
1211       lazy.logger.error(e);
1212     }
1213   }
1215   if (!selected) {
1216     throw new lazy.error.NoSuchWindowError(
1217       `Unable to locate window: ${handle}`
1218     );
1219   }
1223  * Switch the marionette window to a given window. If the browser in
1224  * the window is unregistered, register that browser and wait for
1225  * the registration is complete. If |focus| is true then set the focus
1226  * on the window.
1228  * @param {Object} winProperties
1229  *     Object containing window properties such as returned from
1230  *     :js:func:`GeckoDriver#getWindowProperties`
1231  * @param {boolean=} focus
1232  *     A boolean value which determines whether to focus the window.
1233  *     Defaults to true.
1234  */
1235 GeckoDriver.prototype.setWindowHandle = async function(
1236   winProperties,
1237   focus = true
1238 ) {
1239   if (!(winProperties.id in this.browsers)) {
1240     // Initialise Marionette if the current chrome window has not been seen
1241     // before. Also register the initial tab, if one exists.
1242     this.addBrowser(winProperties.win);
1243     this.mainFrame = winProperties.win;
1245     this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1247     if (!winProperties.hasTabBrowser) {
1248       this.currentSession.contentBrowsingContext = null;
1249     } else {
1250       const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
1252       // For chrome windows such as a reftest window, `getTabBrowser` is not
1253       // a tabbrowser, it is the content browser which should be used here.
1254       const contentBrowser = tabBrowser.tabs
1255         ? tabBrowser.selectedBrowser
1256         : tabBrowser;
1258       this.currentSession.contentBrowsingContext =
1259         contentBrowser.browsingContext;
1260       this.registerBrowser(contentBrowser);
1261     }
1262   } else {
1263     // Otherwise switch to the known chrome window
1264     this.curBrowser = this.browsers[winProperties.id];
1265     this.mainFrame = this.curBrowser.window;
1267     // Activate the tab if it's a content window.
1268     let tab = null;
1269     if (winProperties.hasTabBrowser) {
1270       tab = await this.curBrowser.switchToTab(
1271         winProperties.tabIndex,
1272         winProperties.win,
1273         focus
1274       );
1275     }
1277     this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
1278     this.currentSession.contentBrowsingContext =
1279       tab?.linkedBrowser.browsingContext;
1280   }
1282   // Check for existing dialogs for the new window
1283   this.dialog = lazy.modal.findModalDialogs(this.curBrowser);
1285   // If there is an open window modal dialog the underlying chrome window
1286   // cannot be focused.
1287   if (focus && !this.dialog?.isWindowModal) {
1288     await this.curBrowser.focusWindow();
1289   }
1293  * Set the current browsing context for future commands to the parent
1294  * of the current browsing context.
1296  * @throws {NoSuchWindowError}
1297  *     Browsing context has been discarded.
1298  * @throws {UnexpectedAlertOpenError}
1299  *     A modal dialog is open, blocking this operation.
1300  */
1301 GeckoDriver.prototype.switchToParentFrame = async function() {
1302   let browsingContext = this.getBrowsingContext();
1303   if (browsingContext && !browsingContext.parent) {
1304     return;
1305   }
1307   browsingContext = lazy.assert.open(browsingContext?.parent);
1309   this.currentSession.contentBrowsingContext = browsingContext;
1313  * Switch to a given frame within the current window.
1315  * @param {(string|Object)=} element
1316  *     A web element reference of the frame or its element id.
1317  * @param {number=} id
1318  *     The index of the frame to switch to.
1319  *     If both element and id are not defined, switch to top-level frame.
1321  * @throws {NoSuchWindowError}
1322  *     Browsing context has been discarded.
1323  * @throws {UnexpectedAlertOpenError}
1324  *     A modal dialog is open, blocking this operation.
1325  */
1326 GeckoDriver.prototype.switchToFrame = async function(cmd) {
1327   const { element: el, id } = cmd.parameters;
1329   if (typeof id == "number") {
1330     lazy.assert.unsignedShort(
1331       id,
1332       `Expected id to be unsigned short, got ${id}`
1333     );
1334   }
1336   const top = id == null && el == null;
1337   lazy.assert.open(this.getBrowsingContext({ top }));
1338   await this._handleUserPrompts();
1340   // Bug 1495063: Elements should be passed as WebElement reference
1341   let byFrame;
1342   if (typeof el == "string") {
1343     byFrame = lazy.WebElement.fromUUID(el, this.context);
1344   } else if (el) {
1345     byFrame = lazy.WebElement.fromJSON(el);
1346   }
1348   const { browsingContext } = await this.getActor({ top }).switchToFrame(
1349     byFrame || id
1350   );
1352   this.currentSession.contentBrowsingContext = browsingContext;
1355 GeckoDriver.prototype.getTimeouts = function() {
1356   return this.currentSession.timeouts;
1360  * Set timeout for page loading, searching, and scripts.
1362  * @param {Object.<string, number>}
1363  *     Dictionary of timeout types and their new value, where all timeout
1364  *     types are optional.
1366  * @throws {InvalidArgumentError}
1367  *     If timeout type key is unknown, or the value provided with it is
1368  *     not an integer.
1369  */
1370 GeckoDriver.prototype.setTimeouts = function(cmd) {
1371   // merge with existing timeouts
1372   let merged = Object.assign(
1373     this.currentSession.timeouts.toJSON(),
1374     cmd.parameters
1375   );
1377   this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
1380 /** Single tap. */
1381 GeckoDriver.prototype.singleTap = async function(cmd) {
1382   lazy.assert.open(this.getBrowsingContext());
1384   let { id, x, y } = cmd.parameters;
1385   let webEl = lazy.WebElement.fromUUID(id, this.context);
1387   await this.getActor().singleTap(
1388     webEl,
1389     x,
1390     y,
1391     this.currentSession.capabilities
1392   );
1396  * Perform a series of grouped actions at the specified points in time.
1398  * @param {Array.<?>} actions
1399  *     Array of objects that each represent an action sequence.
1401  * @throws {NoSuchWindowError}
1402  *     Browsing context has been discarded.
1403  * @throws {UnexpectedAlertOpenError}
1404  *     A modal dialog is open, blocking this operation.
1405  * @throws {UnsupportedOperationError}
1406  *     Not yet available in current context.
1407  */
1408 GeckoDriver.prototype.performActions = async function(cmd) {
1409   lazy.assert.open(this.getBrowsingContext());
1410   await this._handleUserPrompts();
1412   const actions = cmd.parameters.actions;
1414   await this.getActor().performActions(
1415     actions,
1416     this.currentSession.capabilities
1417   );
1421  * Release all the keys and pointer buttons that are currently depressed.
1423  * @throws {NoSuchWindowError}
1424  *     Browsing context has been discarded.
1425  * @throws {UnexpectedAlertOpenError}
1426  *     A modal dialog is open, blocking this operation.
1427  * @throws {UnsupportedOperationError}
1428  *     Not available in current context.
1429  */
1430 GeckoDriver.prototype.releaseActions = async function() {
1431   lazy.assert.open(this.getBrowsingContext());
1432   await this._handleUserPrompts();
1434   await this.getActor().releaseActions();
1438  * Find an element using the indicated search strategy.
1440  * @param {string} using
1441  *     Indicates which search method to use.
1442  * @param {string} value
1443  *     Value the client is looking for.
1445  * @throws {NoSuchWindowError}
1446  *     Browsing context has been discarded.
1447  * @throws {UnexpectedAlertOpenError}
1448  *     A modal dialog is open, blocking this operation.
1449  */
1450 GeckoDriver.prototype.findElement = async function(cmd) {
1451   const { element: el, using, value } = cmd.parameters;
1453   if (!SUPPORTED_STRATEGIES.has(using)) {
1454     throw new lazy.error.InvalidSelectorError(
1455       `Strategy not supported: ${using}`
1456     );
1457   }
1459   lazy.assert.open(this.getBrowsingContext());
1460   await this._handleUserPrompts();
1462   let startNode;
1463   if (typeof el != "undefined") {
1464     startNode = lazy.WebElement.fromUUID(el, this.context);
1465   }
1467   let opts = {
1468     startNode,
1469     timeout: this.currentSession.timeouts.implicit,
1470     all: false,
1471   };
1473   return this.getActor().findElement(using, value, opts);
1477  * Find elements using the indicated search strategy.
1479  * @param {string} using
1480  *     Indicates which search method to use.
1481  * @param {string} value
1482  *     Value the client is looking for.
1484  * @throws {NoSuchWindowError}
1485  *     Browsing context has been discarded.
1486  */
1487 GeckoDriver.prototype.findElements = async function(cmd) {
1488   const { element: el, using, value } = cmd.parameters;
1490   if (!SUPPORTED_STRATEGIES.has(using)) {
1491     throw new lazy.error.InvalidSelectorError(
1492       `Strategy not supported: ${using}`
1493     );
1494   }
1496   lazy.assert.open(this.getBrowsingContext());
1497   await this._handleUserPrompts();
1499   let startNode;
1500   if (typeof el != "undefined") {
1501     startNode = lazy.WebElement.fromUUID(el, this.context);
1502   }
1504   let opts = {
1505     startNode,
1506     timeout: this.currentSession.timeouts.implicit,
1507     all: true,
1508   };
1510   return this.getActor().findElements(using, value, opts);
1514  * Return the shadow root of an element in the document.
1516  * @param {id}
1517  *     A web element id reference.
1518  * @return {ShadowRoot}
1519  *     ShadowRoot of the element.
1521  * @throws {InvalidArgumentError}
1522  *     If element <var>id</var> is not a string.
1523  * @throws {NoSuchElementError}
1524  *     If element represented by reference <var>id</var> is unknown.
1525  * @throws {NoSuchShadowRoot}
1526  *     Element does not have a shadow root attached.
1527  * @throws {NoSuchWindowError}
1528  *     Browsing context has been discarded.
1529  * @throws {UnexpectedAlertOpenError}
1530  *     A modal dialog is open, blocking this operation.
1531  * @throws {UnsupportedOperationError}
1532  *     Not available in chrome current context.
1533  */
1534 GeckoDriver.prototype.getShadowRoot = async function(cmd) {
1535   // Bug 1743541: Add support for chrome scope.
1536   lazy.assert.content(this.context);
1537   lazy.assert.open(this.getBrowsingContext());
1538   await this._handleUserPrompts();
1540   let id = lazy.assert.string(
1541     cmd.parameters.id,
1542     lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
1543   );
1544   let webEl = lazy.WebElement.fromUUID(id, this.context);
1546   return this.getActor().getShadowRoot(webEl);
1550  * Return the active element in the document.
1552  * @return {WebElement}
1553  *     Active element of the current browsing context's document
1554  *     element, if the document element is non-null.
1556  * @throws {NoSuchElementError}
1557  *     If the document does not have an active element, i.e. if
1558  *     its document element has been deleted.
1559  * @throws {NoSuchWindowError}
1560  *     Browsing context has been discarded.
1561  * @throws {UnexpectedAlertOpenError}
1562  *     A modal dialog is open, blocking this operation.
1563  * @throws {UnsupportedOperationError}
1564  *     Not available in current context.
1565  */
1566 GeckoDriver.prototype.getActiveElement = async function() {
1567   lazy.assert.content(this.context);
1568   lazy.assert.open(this.getBrowsingContext());
1569   await this._handleUserPrompts();
1571   return this.getActor().getActiveElement();
1575  * Send click event to element.
1577  * @param {string} id
1578  *     Reference ID to the element that will be clicked.
1580  * @throws {InvalidArgumentError}
1581  *     If element <var>id</var> is not a string.
1582  * @throws {NoSuchElementError}
1583  *     If element represented by reference <var>id</var> is unknown.
1584  * @throws {NoSuchWindowError}
1585  *     Browsing context has been discarded.
1586  * @throws {UnexpectedAlertOpenError}
1587  *     A modal dialog is open, blocking this operation.
1588  */
1589 GeckoDriver.prototype.clickElement = async function(cmd) {
1590   const browsingContext = lazy.assert.open(this.getBrowsingContext());
1591   await this._handleUserPrompts();
1593   let id = lazy.assert.string(cmd.parameters.id);
1594   let webEl = lazy.WebElement.fromUUID(id, this.context);
1596   const actor = this.getActor();
1598   const loadEventExpected = lazy.navigate.isLoadEventExpected(
1599     this._getCurrentURL(),
1600     {
1601       browsingContext,
1602       target: await actor.getElementAttribute(webEl, "target"),
1603     }
1604   );
1606   await lazy.navigate.waitForNavigationCompleted(
1607     this,
1608     () => actor.clickElement(webEl, this.currentSession.capabilities),
1609     {
1610       loadEventExpected,
1611       // The click might trigger a navigation, so don't count on it.
1612       requireBeforeUnload: false,
1613     }
1614   );
1618  * Get a given attribute of an element.
1620  * @param {string} id
1621  *     Web element reference ID to the element that will be inspected.
1622  * @param {string} name
1623  *     Name of the attribute which value to retrieve.
1625  * @return {string}
1626  *     Value of the attribute.
1628  * @throws {InvalidArgumentError}
1629  *     If <var>id</var> or <var>name</var> are not strings.
1630  * @throws {NoSuchElementError}
1631  *     If element represented by reference <var>id</var> is unknown.
1632  * @throws {NoSuchWindowError}
1633  *     Browsing context has been discarded.
1634  * @throws {UnexpectedAlertOpenError}
1635  *     A modal dialog is open, blocking this operation.
1636  */
1637 GeckoDriver.prototype.getElementAttribute = async function(cmd) {
1638   lazy.assert.open(this.getBrowsingContext());
1639   await this._handleUserPrompts();
1641   const id = lazy.assert.string(cmd.parameters.id);
1642   const name = lazy.assert.string(cmd.parameters.name);
1643   const webEl = lazy.WebElement.fromUUID(id, this.context);
1645   return this.getActor().getElementAttribute(webEl, name);
1649  * Returns the value of a property associated with given element.
1651  * @param {string} id
1652  *     Web element reference ID to the element that will be inspected.
1653  * @param {string} name
1654  *     Name of the property which value to retrieve.
1656  * @return {string}
1657  *     Value of the property.
1659  * @throws {InvalidArgumentError}
1660  *     If <var>id</var> or <var>name</var> are not strings.
1661  * @throws {NoSuchElementError}
1662  *     If element represented by reference <var>id</var> is unknown.
1663  * @throws {NoSuchWindowError}
1664  *     Browsing context has been discarded.
1665  * @throws {UnexpectedAlertOpenError}
1666  *     A modal dialog is open, blocking this operation.
1667  */
1668 GeckoDriver.prototype.getElementProperty = async function(cmd) {
1669   lazy.assert.open(this.getBrowsingContext());
1670   await this._handleUserPrompts();
1672   const id = lazy.assert.string(cmd.parameters.id);
1673   const name = lazy.assert.string(cmd.parameters.name);
1674   const webEl = lazy.WebElement.fromUUID(id, this.context);
1676   return this.getActor().getElementProperty(webEl, name);
1680  * Get the text of an element, if any.  Includes the text of all child
1681  * elements.
1683  * @param {string} id
1684  *     Reference ID to the element that will be inspected.
1686  * @return {string}
1687  *     Element's text "as rendered".
1689  * @throws {InvalidArgumentError}
1690  *     If <var>id</var> is not a string.
1691  * @throws {NoSuchElementError}
1692  *     If element represented by reference <var>id</var> is unknown.
1693  * @throws {NoSuchWindowError}
1694  *     Browsing context has been discarded.
1695  * @throws {UnexpectedAlertOpenError}
1696  *     A modal dialog is open, blocking this operation.
1697  */
1698 GeckoDriver.prototype.getElementText = async function(cmd) {
1699   lazy.assert.open(this.getBrowsingContext());
1700   await this._handleUserPrompts();
1702   let id = lazy.assert.string(cmd.parameters.id);
1703   let webEl = lazy.WebElement.fromUUID(id, this.context);
1705   return this.getActor().getElementText(webEl);
1709  * Get the tag name of the element.
1711  * @param {string} id
1712  *     Reference ID to the element that will be inspected.
1714  * @return {string}
1715  *     Local tag name of element.
1717  * @throws {InvalidArgumentError}
1718  *     If <var>id</var> is not a string.
1719  * @throws {NoSuchElementError}
1720  *     If element represented by reference <var>id</var> is unknown.
1721  * @throws {NoSuchWindowError}
1722  *     Browsing context has been discarded.
1723  * @throws {UnexpectedAlertOpenError}
1724  *     A modal dialog is open, blocking this operation.
1725  */
1726 GeckoDriver.prototype.getElementTagName = async function(cmd) {
1727   lazy.assert.open(this.getBrowsingContext());
1728   await this._handleUserPrompts();
1730   let id = lazy.assert.string(cmd.parameters.id);
1731   let webEl = lazy.WebElement.fromUUID(id, this.context);
1733   return this.getActor().getElementTagName(webEl);
1737  * Check if element is displayed.
1739  * @param {string} id
1740  *     Reference ID to the element that will be inspected.
1742  * @return {boolean}
1743  *     True if displayed, false otherwise.
1745  * @throws {InvalidArgumentError}
1746  *     If <var>id</var> is not a string.
1747  * @throws {NoSuchElementError}
1748  *     If element represented by reference <var>id</var> is unknown.
1749  * @throws {NoSuchWindowError}
1750  *     Browsing context has been discarded.
1751  * @throws {UnexpectedAlertOpenError}
1752  *     A modal dialog is open, blocking this operation.
1753  */
1754 GeckoDriver.prototype.isElementDisplayed = async function(cmd) {
1755   lazy.assert.open(this.getBrowsingContext());
1756   await this._handleUserPrompts();
1758   let id = lazy.assert.string(cmd.parameters.id);
1759   let webEl = lazy.WebElement.fromUUID(id, this.context);
1761   return this.getActor().isElementDisplayed(
1762     webEl,
1763     this.currentSession.capabilities
1764   );
1768  * Return the property of the computed style of an element.
1770  * @param {string} id
1771  *     Reference ID to the element that will be checked.
1772  * @param {string} propertyName
1773  *     CSS rule that is being requested.
1775  * @return {string}
1776  *     Value of |propertyName|.
1778  * @throws {InvalidArgumentError}
1779  *     If <var>id</var> or <var>propertyName</var> are not strings.
1780  * @throws {NoSuchElementError}
1781  *     If element represented by reference <var>id</var> is unknown.
1782  * @throws {NoSuchWindowError}
1783  *     Browsing context has been discarded.
1784  * @throws {UnexpectedAlertOpenError}
1785  *     A modal dialog is open, blocking this operation.
1786  */
1787 GeckoDriver.prototype.getElementValueOfCssProperty = async function(cmd) {
1788   lazy.assert.open(this.getBrowsingContext());
1789   await this._handleUserPrompts();
1791   let id = lazy.assert.string(cmd.parameters.id);
1792   let prop = lazy.assert.string(cmd.parameters.propertyName);
1793   let webEl = lazy.WebElement.fromUUID(id, this.context);
1795   return this.getActor().getElementValueOfCssProperty(webEl, prop);
1799  * Check if element is enabled.
1801  * @param {string} id
1802  *     Reference ID to the element that will be checked.
1804  * @return {boolean}
1805  *     True if enabled, false if disabled.
1807  * @throws {InvalidArgumentError}
1808  *     If <var>id</var> is not a string.
1809  * @throws {NoSuchElementError}
1810  *     If element represented by reference <var>id</var> is unknown.
1811  * @throws {NoSuchWindowError}
1812  *     Browsing context has been discarded.
1813  * @throws {UnexpectedAlertOpenError}
1814  *     A modal dialog is open, blocking this operation.
1815  */
1816 GeckoDriver.prototype.isElementEnabled = async function(cmd) {
1817   lazy.assert.open(this.getBrowsingContext());
1818   await this._handleUserPrompts();
1820   let id = lazy.assert.string(cmd.parameters.id);
1821   let webEl = lazy.WebElement.fromUUID(id, this.context);
1823   return this.getActor().isElementEnabled(
1824     webEl,
1825     this.currentSession.capabilities
1826   );
1830  * Check if element is selected.
1832  * @param {string} id
1833  *     Reference ID to the element that will be checked.
1835  * @return {boolean}
1836  *     True if selected, false if unselected.
1838  * @throws {InvalidArgumentError}
1839  *     If <var>id</var> is not a string.
1840  * @throws {NoSuchElementError}
1841  *     If element represented by reference <var>id</var> is unknown.
1842  * @throws {NoSuchWindowError}
1843  *     Browsing context has been discarded.
1844  * @throws {UnexpectedAlertOpenError}
1845  *     A modal dialog is open, blocking this operation.
1846  */
1847 GeckoDriver.prototype.isElementSelected = async function(cmd) {
1848   lazy.assert.open(this.getBrowsingContext());
1849   await this._handleUserPrompts();
1851   let id = lazy.assert.string(cmd.parameters.id);
1852   let webEl = lazy.WebElement.fromUUID(id, this.context);
1854   return this.getActor().isElementSelected(
1855     webEl,
1856     this.currentSession.capabilities
1857   );
1861  * @throws {InvalidArgumentError}
1862  *     If <var>id</var> is not a string.
1863  * @throws {NoSuchElementError}
1864  *     If element represented by reference <var>id</var> is unknown.
1865  * @throws {NoSuchWindowError}
1866  *     Browsing context has been discarded.
1867  * @throws {UnexpectedAlertOpenError}
1868  *     A modal dialog is open, blocking this operation.
1869  */
1870 GeckoDriver.prototype.getElementRect = async function(cmd) {
1871   lazy.assert.open(this.getBrowsingContext());
1872   await this._handleUserPrompts();
1874   let id = lazy.assert.string(cmd.parameters.id);
1875   let webEl = lazy.WebElement.fromUUID(id, this.context);
1877   return this.getActor().getElementRect(webEl);
1881  * Send key presses to element after focusing on it.
1883  * @param {string} id
1884  *     Reference ID to the element that will be checked.
1885  * @param {string} text
1886  *     Value to send to the element.
1888  * @throws {InvalidArgumentError}
1889  *     If `id` or `text` are not strings.
1890  * @throws {NoSuchElementError}
1891  *     If element represented by reference `id` is unknown.
1892  * @throws {NoSuchWindowError}
1893  *     Browsing context has been discarded.
1894  * @throws {UnexpectedAlertOpenError}
1895  *     A modal dialog is open, blocking this operation.
1896  */
1897 GeckoDriver.prototype.sendKeysToElement = async function(cmd) {
1898   lazy.assert.open(this.getBrowsingContext());
1899   await this._handleUserPrompts();
1901   let id = lazy.assert.string(cmd.parameters.id);
1902   let text = lazy.assert.string(cmd.parameters.text);
1903   let webEl = lazy.WebElement.fromUUID(id, this.context);
1905   return this.getActor().sendKeysToElement(
1906     webEl,
1907     text,
1908     this.currentSession.capabilities
1909   );
1913  * Clear the text of an element.
1915  * @param {string} id
1916  *     Reference ID to the element that will be cleared.
1918  * @throws {InvalidArgumentError}
1919  *     If <var>id</var> is not a string.
1920  * @throws {NoSuchElementError}
1921  *     If element represented by reference <var>id</var> is unknown.
1922  * @throws {NoSuchWindowError}
1923  *     Browsing context has been discarded.
1924  * @throws {UnexpectedAlertOpenError}
1925  *     A modal dialog is open, blocking this operation.
1926  */
1927 GeckoDriver.prototype.clearElement = async function(cmd) {
1928   lazy.assert.open(this.getBrowsingContext());
1929   await this._handleUserPrompts();
1931   let id = lazy.assert.string(cmd.parameters.id);
1932   let webEl = lazy.WebElement.fromUUID(id, this.context);
1934   await this.getActor().clearElement(webEl);
1938  * Add a single cookie to the cookie store associated with the active
1939  * document's address.
1941  * @param {Map.<string, (string|number|boolean)> cookie
1942  *     Cookie object.
1944  * @throws {InvalidCookieDomainError}
1945  *     If <var>cookie</var> is for a different domain than the active
1946  *     document's host.
1947  * @throws {NoSuchWindowError}
1948  *     Bbrowsing context has been discarded.
1949  * @throws {UnexpectedAlertOpenError}
1950  *     A modal dialog is open, blocking this operation.
1951  * @throws {UnsupportedOperationError}
1952  *     Not available in current context.
1953  */
1954 GeckoDriver.prototype.addCookie = async function(cmd) {
1955   lazy.assert.content(this.context);
1956   lazy.assert.open(this.getBrowsingContext());
1957   await this._handleUserPrompts();
1959   let { protocol, hostname } = this._getCurrentURL();
1961   const networkSchemes = ["http:", "https:"];
1962   if (!networkSchemes.includes(protocol)) {
1963     throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
1964   }
1966   let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
1968   lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
1972  * Get all the cookies for the current domain.
1974  * This is the equivalent of calling <code>document.cookie</code> and
1975  * parsing the result.
1977  * @throws {NoSuchWindowError}
1978  *     Browsing context has been discarded.
1979  * @throws {UnexpectedAlertOpenError}
1980  *     A modal dialog is open, blocking this operation.
1981  * @throws {UnsupportedOperationError}
1982  *     Not available in current context.
1983  */
1984 GeckoDriver.prototype.getCookies = async function() {
1985   lazy.assert.content(this.context);
1986   lazy.assert.open(this.getBrowsingContext());
1987   await this._handleUserPrompts();
1989   let { hostname, pathname } = this._getCurrentURL();
1990   return [...lazy.cookie.iter(hostname, pathname)];
1994  * Delete all cookies that are visible to a document.
1996  * @throws {NoSuchWindowError}
1997  *     Browsing context has been discarded.
1998  * @throws {UnexpectedAlertOpenError}
1999  *     A modal dialog is open, blocking this operation.
2000  * @throws {UnsupportedOperationError}
2001  *     Not available in current context.
2002  */
2003 GeckoDriver.prototype.deleteAllCookies = async function() {
2004   lazy.assert.content(this.context);
2005   lazy.assert.open(this.getBrowsingContext());
2006   await this._handleUserPrompts();
2008   let { hostname, pathname } = this._getCurrentURL();
2009   for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
2010     lazy.cookie.remove(toDelete);
2011   }
2015  * Delete a cookie by name.
2017  * @throws {NoSuchWindowError}
2018  *     Browsing context has been discarded.
2019  * @throws {UnexpectedAlertOpenError}
2020  *     A modal dialog is open, blocking this operation.
2021  * @throws {UnsupportedOperationError}
2022  *     Not available in current context.
2023  */
2024 GeckoDriver.prototype.deleteCookie = async function(cmd) {
2025   lazy.assert.content(this.context);
2026   lazy.assert.open(this.getBrowsingContext());
2027   await this._handleUserPrompts();
2029   let { hostname, pathname } = this._getCurrentURL();
2030   let name = lazy.assert.string(cmd.parameters.name);
2031   for (let c of lazy.cookie.iter(hostname, pathname)) {
2032     if (c.name === name) {
2033       lazy.cookie.remove(c);
2034     }
2035   }
2039  * Open a new top-level browsing context.
2041  * @param {string=} type
2042  *     Optional type of the new top-level browsing context. Can be one of
2043  *     `tab` or `window`. Defaults to `tab`.
2044  * @param {boolean=} focus
2045  *     Optional flag if the new top-level browsing context should be opened
2046  *     in foreground (focused) or background (not focused). Defaults to false.
2047  * @param {boolean=} private
2048  *     Optional flag, which gets only evaluated for type `window`. True if the
2049  *     new top-level browsing context should be a private window.
2050  *     Defaults to false.
2052  * @return {Object.<string, string>}
2053  *     Handle and type of the new browsing context.
2055  * @throws {NoSuchWindowError}
2056  *     Top-level browsing context has been discarded.
2057  */
2058 GeckoDriver.prototype.newWindow = async function(cmd) {
2059   lazy.assert.open(this.getBrowsingContext({ top: true }));
2060   await this._handleUserPrompts();
2062   let focus = false;
2063   if (typeof cmd.parameters.focus != "undefined") {
2064     focus = lazy.assert.boolean(
2065       cmd.parameters.focus,
2066       lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
2067     );
2068   }
2070   let isPrivate = false;
2071   if (typeof cmd.parameters.private != "undefined") {
2072     isPrivate = lazy.assert.boolean(
2073       cmd.parameters.private,
2074       lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
2075     );
2076   }
2078   let type;
2079   if (typeof cmd.parameters.type != "undefined") {
2080     type = lazy.assert.string(
2081       cmd.parameters.type,
2082       lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
2083     );
2084   }
2086   // If an invalid or no type has been specified default to a tab.
2087   if (typeof type == "undefined" || !["tab", "window"].includes(type)) {
2088     type = "tab";
2089   }
2091   let contentBrowser;
2093   switch (type) {
2094     case "window":
2095       let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
2096       contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
2097       break;
2099     default:
2100       // To not fail if a new type gets added in the future, make opening
2101       // a new tab the default action.
2102       let tab = await this.curBrowser.openTab(focus);
2103       contentBrowser = lazy.TabManager.getBrowserForTab(tab);
2104   }
2106   // Actors need the new window to be loaded to safely execute queries.
2107   // Wait until the initial page load has been finished.
2108   await lazy.waitForInitialNavigationCompleted(
2109     contentBrowser.browsingContext.webProgress
2110   );
2112   const id = lazy.TabManager.getIdForBrowser(contentBrowser);
2114   return { handle: id.toString(), type };
2118  * Close the currently selected tab/window.
2120  * With multiple open tabs present the currently selected tab will
2121  * be closed.  Otherwise the window itself will be closed. If it is the
2122  * last window currently open, the window will not be closed to prevent
2123  * a shutdown of the application. Instead the returned list of window
2124  * handles is empty.
2126  * @return {Array.<string>}
2127  *     Unique window handles of remaining windows.
2129  * @throws {NoSuchWindowError}
2130  *     Top-level browsing context has been discarded.
2131  * @throws {UnexpectedAlertOpenError}
2132  *     A modal dialog is open, blocking this operation.
2133  */
2134 GeckoDriver.prototype.close = async function() {
2135   lazy.assert.open(
2136     this.getBrowsingContext({ context: lazy.Context.Content, top: true })
2137   );
2138   await this._handleUserPrompts();
2140   // If there is only one window left, do not close unless windowless mode is
2141   // enabled. Instead return a faked empty array of window handles.
2142   // This will instruct geckodriver to terminate the application.
2143   if (
2144     lazy.TabManager.getTabCount() === 1 &&
2145     !this.currentSession.capabilities.get("moz:windowless")
2146   ) {
2147     return [];
2148   }
2150   await this.curBrowser.closeTab();
2151   this.currentSession.contentBrowsingContext = null;
2153   return lazy.TabManager.allBrowserUniqueIds.map(String);
2157  * Close the currently selected chrome window.
2159  * If it is the last window currently open, the chrome window will not be
2160  * closed to prevent a shutdown of the application. Instead the returned
2161  * list of chrome window handles is empty.
2163  * @return {Array.<string>}
2164  *     Unique chrome window handles of remaining chrome windows.
2166  * @throws {NoSuchWindowError}
2167  *     Top-level browsing context has been discarded.
2168  */
2169 GeckoDriver.prototype.closeChromeWindow = async function() {
2170   lazy.assert.desktop();
2171   lazy.assert.open(
2172     this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
2173   );
2175   let nwins = 0;
2177   // eslint-disable-next-line
2178   for (let _ of lazy.windowManager.windows) {
2179     nwins++;
2180   }
2182   // If there is only one window left, do not close unless windowless mode is
2183   // enabled. Instead return a faked empty array of window handles.
2184   // This will instruct geckodriver to terminate the application.
2185   if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
2186     return [];
2187   }
2189   await this.curBrowser.closeWindow();
2190   this.currentSession.chromeBrowsingContext = null;
2191   this.currentSession.contentBrowsingContext = null;
2193   return lazy.windowManager.chromeWindowHandles.map(String);
2196 /** Delete Marionette session. */
2197 GeckoDriver.prototype.deleteSession = function() {
2198   if (!this.currentSession) {
2199     return;
2200   }
2202   for (let win of lazy.windowManager.windows) {
2203     this.unregisterListenersForWindow(win);
2204   }
2206   // reset to the top-most frame
2207   this.mainFrame = null;
2209   if (this.dialogObserver) {
2210     this.dialogObserver.cleanup();
2211     this.dialogObserver = null;
2212   }
2214   Services.obs.removeObserver(this, "browser-delayed-startup-finished");
2216   lazy.clearActionInputState();
2217   lazy.clearElementIdCache();
2219   // Always unregister actors after all other observers
2220   // and listeners have been removed.
2221   lazy.unregisterCommandsActor();
2222   // MarionetteEvents actors are only disabled to avoid IPC errors if there are
2223   // in flight events being forwarded from the content process to the parent
2224   // process.
2225   lazy.disableEventsActor();
2227   if (lazy.RemoteAgent.webDriverBiDi) {
2228     lazy.RemoteAgent.webDriverBiDi.deleteSession();
2229   } else {
2230     this.currentSession.destroy();
2231     this._currentSession = null;
2232   }
2236  * Takes a screenshot of a web element, current frame, or viewport.
2238  * The screen capture is returned as a lossless PNG image encoded as
2239  * a base 64 string.
2241  * If called in the content context, the |id| argument is not null and
2242  * refers to a present and visible web element's ID, the capture area will
2243  * be limited to the bounding box of that element.  Otherwise, the capture
2244  * area will be the bounding box of the current frame.
2246  * If called in the chrome context, the screenshot will always represent
2247  * the entire viewport.
2249  * @param {string=} id
2250  *     Optional web element reference to take a screenshot of.
2251  *     If undefined, a screenshot will be taken of the document element.
2252  * @param {boolean=} full
2253  *     True to take a screenshot of the entire document element. Is only
2254  *     considered if <var>id</var> is not defined. Defaults to true.
2255  * @param {boolean=} hash
2256  *     True if the user requests a hash of the image data. Defaults to false.
2257  * @param {boolean=} scroll
2258  *     Scroll to element if |id| is provided. Defaults to true.
2260  * @return {string}
2261  *     If <var>hash</var> is false, PNG image encoded as Base64 encoded
2262  *     string.  If <var>hash</var> is true, hex digest of the SHA-256
2263  *     hash of the Base64 encoded string.
2265  * @throws {NoSuchWindowError}
2266  *     Top-level browsing context has been discarded.
2267  */
2268 GeckoDriver.prototype.takeScreenshot = async function(cmd) {
2269   lazy.assert.open(this.getBrowsingContext({ top: true }));
2270   await this._handleUserPrompts();
2272   let { id, full, hash, scroll } = cmd.parameters;
2273   let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
2275   full = typeof full == "undefined" ? true : full;
2276   scroll = typeof scroll == "undefined" ? true : scroll;
2278   let webEl = id ? lazy.WebElement.fromUUID(id, this.context) : null;
2280   // Only consider full screenshot if no element has been specified
2281   full = webEl ? false : full;
2283   return this.getActor().takeScreenshot(webEl, format, full, scroll);
2287  * Get the current browser orientation.
2289  * Will return one of the valid primary orientation values
2290  * portrait-primary, landscape-primary, portrait-secondary, or
2291  * landscape-secondary.
2293  * @throws {NoSuchWindowError}
2294  *     Top-level browsing context has been discarded.
2295  */
2296 GeckoDriver.prototype.getScreenOrientation = function() {
2297   lazy.assert.mobile();
2298   lazy.assert.open(this.getBrowsingContext({ top: true }));
2300   const win = this.getCurrentWindow();
2302   return win.screen.mozOrientation;
2306  * Set the current browser orientation.
2308  * The supplied orientation should be given as one of the valid
2309  * orientation values.  If the orientation is unknown, an error will
2310  * be raised.
2312  * Valid orientations are "portrait" and "landscape", which fall
2313  * back to "portrait-primary" and "landscape-primary" respectively,
2314  * and "portrait-secondary" as well as "landscape-secondary".
2316  * @throws {NoSuchWindowError}
2317  *     Top-level browsing context has been discarded.
2318  */
2319 GeckoDriver.prototype.setScreenOrientation = function(cmd) {
2320   lazy.assert.mobile();
2321   lazy.assert.open(this.getBrowsingContext({ top: true }));
2323   const ors = [
2324     "portrait",
2325     "landscape",
2326     "portrait-primary",
2327     "landscape-primary",
2328     "portrait-secondary",
2329     "landscape-secondary",
2330   ];
2332   let or = String(cmd.parameters.orientation);
2333   lazy.assert.string(or);
2334   let mozOr = or.toLowerCase();
2335   if (!ors.includes(mozOr)) {
2336     throw new lazy.error.InvalidArgumentError(
2337       `Unknown screen orientation: ${or}`
2338     );
2339   }
2341   const win = this.getCurrentWindow();
2342   if (!win.screen.mozLockOrientation(mozOr)) {
2343     throw new lazy.error.WebDriverError(
2344       `Unable to set screen orientation: ${or}`
2345     );
2346   }
2350  * Synchronously minimizes the user agent window as if the user pressed
2351  * the minimize button.
2353  * No action is taken if the window is already minimized.
2355  * Not supported on Fennec.
2357  * @return {Object.<string, number>}
2358  *     Window rect and window state.
2360  * @throws {NoSuchWindowError}
2361  *     Top-level browsing context has been discarded.
2362  * @throws {UnexpectedAlertOpenError}
2363  *     A modal dialog is open, blocking this operation.
2364  * @throws {UnsupportedOperationError}
2365  *     Not available for current application.
2366  */
2367 GeckoDriver.prototype.minimizeWindow = async function() {
2368   lazy.assert.desktop();
2369   lazy.assert.open(this.getBrowsingContext({ top: true }));
2370   await this._handleUserPrompts();
2372   const win = this.getCurrentWindow();
2373   switch (lazy.WindowState.from(win.windowState)) {
2374     case lazy.WindowState.Fullscreen:
2375       await exitFullscreen(win);
2376       break;
2378     case lazy.WindowState.Maximized:
2379       await restoreWindow(win);
2380       break;
2381   }
2383   if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
2384     let cb;
2385     let observer = new lazy.WebElementEventTarget(
2386       this.curBrowser.messageManager
2387     );
2388     // Use a timed promise to abort if no window manager is present
2389     await new lazy.TimedPromise(
2390       resolve => {
2391         cb = new lazy.DebounceCallback(resolve);
2392         observer.addEventListener("visibilitychange", cb);
2393         win.minimize();
2394       },
2395       { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2396     );
2397     observer.removeEventListener("visibilitychange", cb);
2398     await new lazy.IdlePromise(win);
2399   }
2401   return this.curBrowser.rect;
2405  * Synchronously maximizes the user agent window as if the user pressed
2406  * the maximize button.
2408  * No action is taken if the window is already maximized.
2410  * Not supported on Fennec.
2412  * @return {Object.<string, number>}
2413  *     Window rect.
2415  * @throws {NoSuchWindowError}
2416  *     Top-level browsing context has been discarded.
2417  * @throws {UnexpectedAlertOpenError}
2418  *     A modal dialog is open, blocking this operation.
2419  * @throws {UnsupportedOperationError}
2420  *     Not available for current application.
2421  */
2422 GeckoDriver.prototype.maximizeWindow = async function() {
2423   lazy.assert.desktop();
2424   lazy.assert.open(this.getBrowsingContext({ top: true }));
2425   await this._handleUserPrompts();
2427   const win = this.getCurrentWindow();
2428   switch (lazy.WindowState.from(win.windowState)) {
2429     case lazy.WindowState.Fullscreen:
2430       await exitFullscreen(win);
2431       break;
2433     case lazy.WindowState.Minimized:
2434       await restoreWindow(win);
2435       break;
2436   }
2438   if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
2439     let cb;
2440     // Use a timed promise to abort if no window manager is present
2441     await new lazy.TimedPromise(
2442       resolve => {
2443         cb = new lazy.DebounceCallback(resolve);
2444         win.addEventListener("sizemodechange", cb);
2445         win.maximize();
2446       },
2447       { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2448     );
2449     win.removeEventListener("sizemodechange", cb);
2450     await new lazy.IdlePromise(win);
2451   }
2453   return this.curBrowser.rect;
2457  * Synchronously sets the user agent window to full screen as if the user
2458  * had done "View > Enter Full Screen".
2460  * No action is taken if the window is already in full screen mode.
2462  * Not supported on Fennec.
2464  * @return {Map.<string, number>}
2465  *     Window rect.
2467  * @throws {NoSuchWindowError}
2468  *     Top-level browsing context has been discarded.
2469  * @throws {UnexpectedAlertOpenError}
2470  *     A modal dialog is open, blocking this operation.
2471  * @throws {UnsupportedOperationError}
2472  *     Not available for current application.
2473  */
2474 GeckoDriver.prototype.fullscreenWindow = async function() {
2475   lazy.assert.desktop();
2476   lazy.assert.open(this.getBrowsingContext({ top: true }));
2477   await this._handleUserPrompts();
2479   const win = this.getCurrentWindow();
2480   switch (lazy.WindowState.from(win.windowState)) {
2481     case lazy.WindowState.Maximized:
2482     case lazy.WindowState.Minimized:
2483       await restoreWindow(win);
2484       break;
2485   }
2487   if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
2488     let cb;
2489     // Use a timed promise to abort if no window manager is present
2490     await new lazy.TimedPromise(
2491       resolve => {
2492         cb = new lazy.DebounceCallback(resolve);
2493         win.addEventListener("sizemodechange", cb);
2494         win.fullScreen = true;
2495       },
2496       { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
2497     );
2498     win.removeEventListener("sizemodechange", cb);
2499   }
2500   await new lazy.IdlePromise(win);
2502   return this.curBrowser.rect;
2506  * Dismisses a currently displayed tab modal, or returns no such alert if
2507  * no modal is displayed.
2509  * @throws {NoSuchWindowError}
2510  *     Top-level browsing context has been discarded.
2511  */
2512 GeckoDriver.prototype.dismissDialog = async function() {
2513   lazy.assert.open(this.getBrowsingContext({ top: true }));
2514   this._checkIfAlertIsPresent();
2516   const dialogClosed = this.dialogObserver.dialogClosed();
2517   this.dialog.dismiss();
2518   await dialogClosed;
2520   const win = this.getCurrentWindow();
2521   await new lazy.IdlePromise(win);
2525  * Accepts a currently displayed tab modal, or returns no such alert if
2526  * no modal is displayed.
2528  * @throws {NoSuchWindowError}
2529  *     Top-level browsing context has been discarded.
2530  */
2531 GeckoDriver.prototype.acceptDialog = async function() {
2532   lazy.assert.open(this.getBrowsingContext({ top: true }));
2533   this._checkIfAlertIsPresent();
2535   const dialogClosed = this.dialogObserver.dialogClosed();
2536   this.dialog.accept();
2537   await dialogClosed;
2539   const win = this.getCurrentWindow();
2540   await new lazy.IdlePromise(win);
2544  * Returns the message shown in a currently displayed modal, or returns
2545  * a no such alert error if no modal is currently displayed.
2547  * @throws {NoSuchWindowError}
2548  *     Top-level browsing context has been discarded.
2549  */
2550 GeckoDriver.prototype.getTextFromDialog = function() {
2551   lazy.assert.open(this.getBrowsingContext({ top: true }));
2552   this._checkIfAlertIsPresent();
2553   return this.dialog.text;
2557  * Set the user prompt's value field.
2559  * Sends keys to the input field of a currently displayed modal, or
2560  * returns a no such alert error if no modal is currently displayed. If
2561  * a tab modal is currently displayed but has no means for text input,
2562  * an element not visible error is returned.
2564  * @param {string} text
2565  *     Input to the user prompt's value field.
2567  * @throws {ElementNotInteractableError}
2568  *     If the current user prompt is an alert or confirm.
2569  * @throws {NoSuchAlertError}
2570  *     If there is no current user prompt.
2571  * @throws {NoSuchWindowError}
2572  *     Top-level browsing context has been discarded.
2573  * @throws {UnsupportedOperationError}
2574  *     If the current user prompt is something other than an alert,
2575  *     confirm, or a prompt.
2576  */
2577 GeckoDriver.prototype.sendKeysToDialog = async function(cmd) {
2578   lazy.assert.open(this.getBrowsingContext({ top: true }));
2579   this._checkIfAlertIsPresent();
2581   let text = lazy.assert.string(cmd.parameters.text);
2582   let promptType = this.dialog.args.promptType;
2584   switch (promptType) {
2585     case "alert":
2586     case "confirm":
2587       throw new lazy.error.ElementNotInteractableError(
2588         `User prompt of type ${promptType} is not interactable`
2589       );
2590     case "prompt":
2591       break;
2592     default:
2593       await this.dismissDialog();
2594       throw new lazy.error.UnsupportedOperationError(
2595         `User prompt of type ${promptType} is not supported`
2596       );
2597   }
2598   this.dialog.text = text;
2601 GeckoDriver.prototype._checkIfAlertIsPresent = function() {
2602   if (!this.dialog || !this.dialog.isOpen) {
2603     throw new lazy.error.NoSuchAlertError();
2604   }
2607 GeckoDriver.prototype._handleUserPrompts = async function() {
2608   if (!this.dialog || !this.dialog.isOpen) {
2609     return;
2610   }
2612   let textContent = this.dialog.text;
2614   const behavior = this.currentSession.unhandledPromptBehavior;
2615   switch (behavior) {
2616     case lazy.UnhandledPromptBehavior.Accept:
2617       await this.acceptDialog();
2618       break;
2620     case lazy.UnhandledPromptBehavior.AcceptAndNotify:
2621       await this.acceptDialog();
2622       throw new lazy.error.UnexpectedAlertOpenError(
2623         `Accepted user prompt dialog: ${textContent}`
2624       );
2626     case lazy.UnhandledPromptBehavior.Dismiss:
2627       await this.dismissDialog();
2628       break;
2630     case lazy.UnhandledPromptBehavior.DismissAndNotify:
2631       await this.dismissDialog();
2632       throw new lazy.error.UnexpectedAlertOpenError(
2633         `Dismissed user prompt dialog: ${textContent}`
2634       );
2636     case lazy.UnhandledPromptBehavior.Ignore:
2637       throw new lazy.error.UnexpectedAlertOpenError(
2638         "Encountered unhandled user prompt dialog"
2639       );
2641     default:
2642       throw new TypeError(`Unknown unhandledPromptBehavior "${behavior}"`);
2643   }
2647  * Enables or disables accepting new socket connections.
2649  * By calling this method with `false` the server will not accept any
2650  * further connections, but existing connections will not be forcible
2651  * closed. Use `true` to re-enable accepting connections.
2653  * Please note that when closing the connection via the client you can
2654  * end-up in a non-recoverable state if it hasn't been enabled before.
2656  * This method is used for custom in application shutdowns via
2657  * marionette.quit() or marionette.restart(), like File -> Quit.
2659  * @param {boolean} state
2660  *     True if the server should accept new socket connections.
2661  */
2662 GeckoDriver.prototype.acceptConnections = function(cmd) {
2663   lazy.assert.boolean(cmd.parameters.value);
2664   this._server.acceptConnections = cmd.parameters.value;
2668  * Quits the application with the provided flags.
2670  * Marionette will stop accepting new connections before ending the
2671  * current session, and finally attempting to quit the application.
2673  * Optional {@link nsIAppStartup} flags may be provided as
2674  * an array of masks, and these will be combined by ORing
2675  * them with a bitmask.  The available masks are defined in
2676  * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
2678  * Crucially, only one of the *Quit flags can be specified. The |eRestart|
2679  * flag may be bit-wise combined with one of the *Quit flags to cause
2680  * the application to restart after it quits.
2682  * @param {Array.<string>=} flags
2683  *     Constant name of masks to pass to |Services.startup.quit|.
2684  *     If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
2686  * @param {boolean=} safeMode
2687  *     Optional flag to indicate that the application has to
2688  *     be restarted in safe mode.
2690  * @return {Object<string,boolean>}
2691  *     Dictionary containing information that explains the shutdown reason.
2692  *     The value for `cause` contains the shutdown kind like "shutdown" or
2693  *     "restart", while `forced` will indicate if it was a normal or forced
2694  *     shutdown of the application.
2696  * @throws {InvalidArgumentError}
2697  *     If <var>flags</var> contains unknown or incompatible flags,
2698  *     for example multiple Quit flags.
2699  */
2700 GeckoDriver.prototype.quit = async function(cmd) {
2701   const { flags = [], safeMode = false } = cmd.parameters;
2702   const quits = ["eConsiderQuit", "eAttemptQuit", "eForceQuit"];
2704   lazy.assert.array(flags, `Expected "flags" to be an array`);
2705   lazy.assert.boolean(safeMode, `Expected "safeMode" to be a boolean`);
2707   if (safeMode && !flags.includes("eRestart")) {
2708     throw new lazy.error.InvalidArgumentError(
2709       `"safeMode" only works with restart flag`
2710     );
2711   }
2713   if (flags.includes("eSilently")) {
2714     if (!this.currentSession.capabilities.get("moz:windowless")) {
2715       throw new lazy.error.UnsupportedOperationError(
2716         `Silent restarts only allowed with "moz:windowless" capability set`
2717       );
2718     }
2719     if (!flags.includes("eRestart")) {
2720       throw new lazy.error.InvalidArgumentError(
2721         `"silently" only works with restart flag`
2722       );
2723     }
2724   }
2726   let quitSeen;
2727   let mode = 0;
2728   if (flags.length > 0) {
2729     for (let k of flags) {
2730       lazy.assert.in(k, Ci.nsIAppStartup);
2732       if (quits.includes(k)) {
2733         if (quitSeen) {
2734           throw new lazy.error.InvalidArgumentError(
2735             `${k} cannot be combined with ${quitSeen}`
2736           );
2737         }
2738         quitSeen = k;
2739       }
2741       mode |= Ci.nsIAppStartup[k];
2742     }
2743   }
2745   if (!quitSeen) {
2746     mode |= Ci.nsIAppStartup.eAttemptQuit;
2747   }
2749   this._server.acceptConnections = false;
2750   this.deleteSession();
2752   // Notify all windows that an application quit has been requested.
2753   const cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
2754     Ci.nsISupportsPRBool
2755   );
2756   Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
2758   // If the shutdown of the application is prevented force quit it instead.
2759   if (cancelQuit.data) {
2760     mode |= Ci.nsIAppStartup.eForceQuit;
2761   }
2763   // delay response until the application is about to quit
2764   let quitApplication = lazy.waitForObserverTopic("quit-application");
2766   if (safeMode) {
2767     Services.startup.restartInSafeMode(mode);
2768   } else {
2769     Services.startup.quit(mode);
2770   }
2772   return {
2773     cause: (await quitApplication).data,
2774     forced: cancelQuit.data,
2775   };
2778 GeckoDriver.prototype.installAddon = function(cmd) {
2779   lazy.assert.desktop();
2781   let path = cmd.parameters.path;
2782   let temp = cmd.parameters.temporary || false;
2783   if (
2784     typeof path == "undefined" ||
2785     typeof path != "string" ||
2786     typeof temp != "boolean"
2787   ) {
2788     throw new lazy.error.InvalidArgumentError();
2789   }
2791   return lazy.Addon.install(path, temp);
2794 GeckoDriver.prototype.uninstallAddon = function(cmd) {
2795   lazy.assert.desktop();
2797   let id = cmd.parameters.id;
2798   if (typeof id == "undefined" || typeof id != "string") {
2799     throw new lazy.error.InvalidArgumentError();
2800   }
2802   return lazy.Addon.uninstall(id);
2806  * Retrieve the localized string for the specified entity id.
2808  * Example:
2809  *     localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
2811  * @param {Array.<string>} urls
2812  *     Array of .dtd URLs.
2813  * @param {string} id
2814  *     The ID of the entity to retrieve the localized string for.
2816  * @return {string}
2817  *     The localized string for the requested entity.
2818  */
2819 GeckoDriver.prototype.localizeEntity = function(cmd) {
2820   let { urls, id } = cmd.parameters;
2822   if (!Array.isArray(urls)) {
2823     throw new lazy.error.InvalidArgumentError(
2824       "Value of `urls` should be of type 'Array'"
2825     );
2826   }
2827   if (typeof id != "string") {
2828     throw new lazy.error.InvalidArgumentError(
2829       "Value of `id` should be of type 'string'"
2830     );
2831   }
2833   return lazy.l10n.localizeEntity(urls, id);
2837  * Retrieve the localized string for the specified property id.
2839  * Example:
2841  *     localizeProperty(
2842  *         ["chrome://global/locale/findbar.properties"], "FastFind");
2844  * @param {Array.<string>} urls
2845  *     Array of .properties URLs.
2846  * @param {string} id
2847  *     The ID of the property to retrieve the localized string for.
2849  * @return {string}
2850  *     The localized string for the requested property.
2851  */
2852 GeckoDriver.prototype.localizeProperty = function(cmd) {
2853   let { urls, id } = cmd.parameters;
2855   if (!Array.isArray(urls)) {
2856     throw new lazy.error.InvalidArgumentError(
2857       "Value of `urls` should be of type 'Array'"
2858     );
2859   }
2860   if (typeof id != "string") {
2861     throw new lazy.error.InvalidArgumentError(
2862       "Value of `id` should be of type 'string'"
2863     );
2864   }
2866   return lazy.l10n.localizeProperty(urls, id);
2870  * Initialize the reftest mode
2871  */
2872 GeckoDriver.prototype.setupReftest = async function(cmd) {
2873   if (this._reftest) {
2874     throw new lazy.error.UnsupportedOperationError(
2875       "Called reftest:setup with a reftest session already active"
2876     );
2877   }
2879   let {
2880     urlCount = {},
2881     screenshot = "unexpected",
2882     isPrint = false,
2883   } = cmd.parameters;
2884   if (!["always", "fail", "unexpected"].includes(screenshot)) {
2885     throw new lazy.error.InvalidArgumentError(
2886       "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
2887     );
2888   }
2890   this._reftest = new lazy.reftest.Runner(this);
2891   this._reftest.setup(urlCount, screenshot, isPrint);
2894 /** Run a reftest. */
2895 GeckoDriver.prototype.runReftest = async function(cmd) {
2896   let {
2897     test,
2898     references,
2899     expected,
2900     timeout,
2901     width,
2902     height,
2903     pageRanges,
2904   } = cmd.parameters;
2906   if (!this._reftest) {
2907     throw new lazy.error.UnsupportedOperationError(
2908       "Called reftest:run before reftest:start"
2909     );
2910   }
2912   lazy.assert.string(test);
2913   lazy.assert.string(expected);
2914   lazy.assert.array(references);
2916   return {
2917     value: await this._reftest.run(
2918       test,
2919       references,
2920       expected,
2921       timeout,
2922       pageRanges,
2923       width,
2924       height
2925     ),
2926   };
2930  * End a reftest run.
2932  * Closes the reftest window (without changing the current window handle),
2933  * and removes cached canvases.
2934  */
2935 GeckoDriver.prototype.teardownReftest = function() {
2936   if (!this._reftest) {
2937     throw new lazy.error.UnsupportedOperationError(
2938       "Called reftest:teardown before reftest:start"
2939     );
2940   }
2942   this._reftest.teardown();
2943   this._reftest = null;
2947  * Print page as PDF.
2949  * @param {boolean=} landscape
2950  *     Paper orientation. Defaults to false.
2951  * @param {number=} margin.bottom
2952  *     Bottom margin in cm. Defaults to 1cm (~0.4 inches).
2953  * @param {number=} margin.left
2954  *     Left margin in cm. Defaults to 1cm (~0.4 inches).
2955  * @param {number=} margin.right
2956  *     Right margin in cm. Defaults to 1cm (~0.4 inches).
2957  * @param {number=} margin.top
2958  *     Top margin in cm. Defaults to 1cm (~0.4 inches).
2959  * @param {Array.<string|number>=} pageRanges
2960  *     Paper ranges to print, e.g., ['1-5', 8, '11-13'].
2961  *     Defaults to the empty array, which means print all pages.
2962  * @param {number=} page.height
2963  *     Paper height in cm. Defaults to US letter height (11 inches / 27.94cm)
2964  * @param {number=} page.width
2965  *     Paper width in cm. Defaults to US letter width (8.5 inches / 21.59cm)
2966  * @param {boolean=} shrinkToFit
2967  *     Whether or not to override page size as defined by CSS.
2968  *     Defaults to true, in which case the content will be scaled
2969  *     to fit the paper size.
2970  * @param {boolean=} printBackground
2971  *     Print background graphics. Defaults to false.
2972  * @param {number=} scale
2973  *     Scale of the webpage rendering. Defaults to 1.
2975  * @return {string}
2976  *     Base64 encoded PDF representing printed document
2978  * @throws {NoSuchWindowError}
2979  *     Top-level browsing context has been discarded.
2980  */
2981 GeckoDriver.prototype.print = async function(cmd) {
2982   lazy.assert.content(this.context);
2983   lazy.assert.open(this.getBrowsingContext({ top: true }));
2984   await this._handleUserPrompts();
2986   const settings = lazy.print.addDefaultSettings(cmd.parameters);
2987   for (let prop of ["top", "bottom", "left", "right"]) {
2988     lazy.assert.positiveNumber(
2989       settings.margin[prop],
2990       lazy.pprint`margin.${prop} is not a positive number`
2991     );
2992   }
2993   for (let prop of ["width", "height"]) {
2994     lazy.assert.positiveNumber(
2995       settings.page[prop],
2996       lazy.pprint`page.${prop} is not a positive number`
2997     );
2998   }
2999   lazy.assert.positiveNumber(
3000     settings.scale,
3001     `scale ${settings.scale} is not a positive number`
3002   );
3003   lazy.assert.that(
3004     s =>
3005       s >= lazy.print.minScaleValue &&
3006       settings.scale <= lazy.print.maxScaleValue,
3007     `scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
3008   )(settings.scale);
3009   lazy.assert.boolean(settings.shrinkToFit);
3010   lazy.assert.boolean(settings.landscape);
3011   lazy.assert.boolean(settings.printBackground);
3012   lazy.assert.array(settings.pageRanges);
3014   const linkedBrowser = this.curBrowser.tab.linkedBrowser;
3015   const filePath = await lazy.print.printToFile(linkedBrowser, settings);
3017   // return all data as a base64 encoded string
3018   let bytes;
3019   try {
3020     bytes = await IOUtils.read(filePath);
3021   } finally {
3022     await IOUtils.remove(filePath);
3023   }
3025   // Each UCS2 character has an upper byte of 0 and a lower byte matching
3026   // the binary data. Splitting the file into chunks to avoid hitting the
3027   // internal argument length limit.
3028   const chunks = [];
3029   // This is the largest power of 2 smaller than MAX_ARGS_LENGTH defined in Spidermonkey
3030   const argLengthLimit = 262144;
3032   for (let offset = 0; offset < bytes.length; offset += argLengthLimit) {
3033     const chunkData = bytes.subarray(offset, offset + argLengthLimit);
3035     chunks.push(String.fromCharCode.apply(null, chunkData));
3036   }
3038   return {
3039     value: btoa(chunks.join("")),
3040   };
3043 GeckoDriver.prototype.setPermission = async function(cmd) {
3044   const { descriptor, state, oneRealm = false } = cmd.parameters;
3046   lazy.assert.boolean(oneRealm);
3047   lazy.assert.that(
3048     state => ["granted", "denied", "prompt"].includes(state),
3049     `state is ${state}, expected "granted", "denied", or "prompt"`
3050   )(state);
3052   lazy.permissions.set(descriptor, state, oneRealm);
3055 GeckoDriver.prototype.commands = {
3056   // Marionette service
3057   "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
3058   "Marionette:GetContext": GeckoDriver.prototype.getContext,
3059   "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
3060   "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
3061   "Marionette:Quit": GeckoDriver.prototype.quit,
3062   "Marionette:SetContext": GeckoDriver.prototype.setContext,
3063   "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
3064   "Marionette:SingleTap": GeckoDriver.prototype.singleTap,
3066   // Addon service
3067   "Addon:Install": GeckoDriver.prototype.installAddon,
3068   "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
3070   // L10n service
3071   "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
3072   "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
3074   // Reftest service
3075   "reftest:setup": GeckoDriver.prototype.setupReftest,
3076   "reftest:run": GeckoDriver.prototype.runReftest,
3077   "reftest:teardown": GeckoDriver.prototype.teardownReftest,
3079   // WebDriver service
3080   "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
3081   // deprecated, no longer used since the geckodriver 0.30.0 release
3082   "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
3083   "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
3084   "WebDriver:Back": GeckoDriver.prototype.goBack,
3085   "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
3086   "WebDriver:CloseWindow": GeckoDriver.prototype.close,
3087   "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
3088   "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
3089   "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
3090   "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
3091   "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
3092   "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
3093   "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
3094   "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
3095   "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
3096   "WebDriver:FindElement": GeckoDriver.prototype.findElement,
3097   "WebDriver:FindElements": GeckoDriver.prototype.findElements,
3098   "WebDriver:Forward": GeckoDriver.prototype.goForward,
3099   "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
3100   "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
3101   "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
3102   "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
3103   "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
3104   "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
3105   "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
3106   "WebDriver:GetElementCSSValue":
3107     GeckoDriver.prototype.getElementValueOfCssProperty,
3108   "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
3109   "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
3110   "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
3111   "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
3112   "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
3113   "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
3114   "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
3115   "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
3116   "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
3117   "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
3118   "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
3119   "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
3120   "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
3121   "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
3122   "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
3123   "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
3124   "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
3125   "WebDriver:NewSession": GeckoDriver.prototype.newSession,
3126   "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
3127   "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
3128   "WebDriver:Print": GeckoDriver.prototype.print,
3129   "WebDriver:Refresh": GeckoDriver.prototype.refresh,
3130   "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
3131   "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
3132   "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
3133   "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
3134   "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
3135   "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
3136   "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
3137   "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
3138   "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
3141 async function exitFullscreen(win) {
3142   let cb;
3143   // Use a timed promise to abort if no window manager is present
3144   await new lazy.TimedPromise(
3145     resolve => {
3146       cb = new lazy.DebounceCallback(resolve);
3147       win.addEventListener("sizemodechange", cb);
3148       win.fullScreen = false;
3149     },
3150     { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
3151   );
3152   win.removeEventListener("sizemodechange", cb);
3155 async function restoreWindow(win) {
3156   win.restore();
3157   // Use a poll promise to abort if no window manager is present
3158   await new lazy.PollPromise(
3159     (resolve, reject) => {
3160       if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
3161         resolve();
3162       } else {
3163         reject();
3164       }
3165     },
3166     { timeout: TIMEOUT_NO_WINDOW_MANAGER }
3167   );