tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

driver.sys.mjs (133746B)


      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/. */
      4 
      5 const lazy = {};
      6 
      7 ChromeUtils.defineESModuleGetters(lazy, {
      8  actions: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
      9  Addon: "chrome://remote/content/shared/Addon.sys.mjs",
     10  AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
     11  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
     12  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
     13  browser: "chrome://remote/content/marionette/browser.sys.mjs",
     14  capture: "chrome://remote/content/shared/Capture.sys.mjs",
     15  Context: "chrome://remote/content/marionette/browser.sys.mjs",
     16  cookie: "chrome://remote/content/marionette/cookie.sys.mjs",
     17  disableEventsActor:
     18    "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
     19  dom: "chrome://remote/content/shared/DOM.sys.mjs",
     20  enableEventsActor:
     21    "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
     22  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
     23  getMarionetteCommandsActorProxy:
     24    "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
     25  l10n: "chrome://remote/content/marionette/l10n.sys.mjs",
     26  Log: "chrome://remote/content/shared/Log.sys.mjs",
     27  Marionette: "chrome://remote/content/components/Marionette.sys.mjs",
     28  MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
     29  modal: "chrome://remote/content/shared/Prompt.sys.mjs",
     30  NavigableManager: "chrome://remote/content/shared/NavigableManager.sys.mjs",
     31  navigate: "chrome://remote/content/marionette/navigate.sys.mjs",
     32  permissions: "chrome://remote/content/shared/Permissions.sys.mjs",
     33  pprint: "chrome://remote/content/shared/Format.sys.mjs",
     34  print: "chrome://remote/content/shared/PDF.sys.mjs",
     35  PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
     36  PromptHandlers:
     37    "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
     38  PromptListener:
     39    "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
     40  PromptTypes:
     41    "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
     42  quit: "chrome://remote/content/shared/Browser.sys.mjs",
     43  reftest: "chrome://remote/content/marionette/reftest.sys.mjs",
     44  registerCommandsActor:
     45    "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
     46  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
     47  ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
     48  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
     49  Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
     50  truncate: "chrome://remote/content/shared/Format.sys.mjs",
     51  unregisterCommandsActor:
     52    "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
     53  waitForInitialNavigationCompleted:
     54    "chrome://remote/content/shared/Navigate.sys.mjs",
     55  webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs",
     56  WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
     57  WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
     58  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
     59  WindowState: "chrome://remote/content/shared/WindowManager.sys.mjs",
     60 });
     61 
     62 ChromeUtils.defineLazyGetter(lazy, "logger", () =>
     63  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
     64 );
     65 
     66 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     67 
     68 ChromeUtils.defineLazyGetter(
     69  lazy,
     70  "supportedStrategies",
     71  () =>
     72    new Set([
     73      lazy.dom.Strategy.ClassName,
     74      lazy.dom.Strategy.Selector,
     75      lazy.dom.Strategy.ID,
     76      lazy.dom.Strategy.Name,
     77      lazy.dom.Strategy.LinkText,
     78      lazy.dom.Strategy.PartialLinkText,
     79      lazy.dom.Strategy.TagName,
     80      lazy.dom.Strategy.XPath,
     81    ])
     82 );
     83 
     84 // Observer topic to wait for until the browser window is ready.
     85 const TOPIC_BROWSER_READY = "browser-delayed-startup-finished";
     86 // Observer topic to perform clean up when application quit is requested.
     87 const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested";
     88 
     89 /**
     90 * The Marionette WebDriver services provides a standard conforming
     91 * implementation of the W3C WebDriver specification.
     92 *
     93 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
     94 * @namespace driver
     95 */
     96 
     97 class ActionsHelper {
     98  #actionsOptions;
     99  #driver;
    100 
    101  constructor(driver) {
    102    this.#driver = driver;
    103 
    104    // Options for actions to pass through performActions and releaseActions.
    105    this.#actionsOptions = {
    106      // Callbacks as defined in the WebDriver specification.
    107      getElementOrigin: this.getElementOrigin.bind(this),
    108      isElementOrigin: this.isElementOrigin.bind(this),
    109 
    110      // Custom callbacks.
    111      assertInViewPort: this.assertInViewPort.bind(this),
    112      dispatchEvent: this.dispatchEvent.bind(this),
    113      getClientRects: this.getClientRects.bind(this),
    114      getInViewCentrePoint: this.getInViewCentrePoint.bind(this),
    115      toBrowserWindowCoordinates: this.toBrowserWindowCoordinates.bind(this),
    116    };
    117  }
    118 
    119  get actionsOptions() {
    120    return this.#actionsOptions;
    121  }
    122 
    123  #getActor(browsingContext) {
    124    return lazy.getMarionetteCommandsActorProxy(() => browsingContext);
    125  }
    126 
    127  /**
    128   * Assert that the target coordinates are within the visible viewport.
    129   *
    130   * @param {Array.<number>} target
    131   *     Coordinates [x, y] of the target relative to the viewport.
    132   * @param {BrowsingContext} browsingContext
    133   *     The browsing context to dispatch the event to.
    134   *
    135   * @returns {Promise<undefined>}
    136   *     Promise that rejects, if the coordinates are not within
    137   *     the visible viewport.
    138   *
    139   * @throws {MoveTargetOutOfBoundsError}
    140   *     If target is outside the viewport.
    141   */
    142  assertInViewPort(target, browsingContext) {
    143    return this.#getActor(browsingContext).assertInViewPort(target);
    144  }
    145 
    146  /**
    147   * Dispatch an event.
    148   *
    149   * @param {string} eventName
    150   *     Name of the event to be dispatched.
    151   * @param {BrowsingContext} browsingContext
    152   *     The browsing context to dispatch the event to.
    153   * @param {object} details
    154   *     Details of the event to be dispatched.
    155   *
    156   * @returns {Promise}
    157   *     Promise that resolves once the event is dispatched.
    158   */
    159  dispatchEvent(eventName, browsingContext, details) {
    160    if (
    161      (eventName === "synthesizeWheelAtPoint" &&
    162        lazy.actions.useAsyncWheelEvents) ||
    163      (eventName == "synthesizeMouseAtPoint" &&
    164        lazy.actions.useAsyncMouseEvents)
    165    ) {
    166      browsingContext = browsingContext.topChromeWindow?.browsingContext;
    167      details.eventData.asyncEnabled = true;
    168    }
    169 
    170    return this.#getActor(browsingContext).dispatchEvent(eventName, details);
    171  }
    172 
    173  /**
    174   * Finalize an action command.
    175   *
    176   * @param {BrowsingContext} browsingContext
    177   *     The browsing context to dispatch the event to.
    178   */
    179  async finalizeAction(browsingContext) {
    180    try {
    181      await this.#getActor(browsingContext).finalizeAction();
    182    } catch (e) {
    183      // Ignore the error if the underlying browsing context is already gone.
    184      if (e.name !== lazy.error.NoSuchWindowError.name) {
    185        throw e;
    186      }
    187    }
    188  }
    189 
    190  /**
    191   * Retrieves the WebElement reference of the origin.
    192   *
    193   * @param {ElementOrigin} origin
    194   *     Reference to the element origin of the action.
    195   * @param {BrowsingContext} _browsingContext
    196   *     Not used by Marionette.
    197   *
    198   * @returns {WebElement}
    199   *     The WebElement reference.
    200   */
    201  getElementOrigin(origin, _browsingContext) {
    202    return origin;
    203  }
    204 
    205  /**
    206   * Retrieve the list of client rects for the element.
    207   *
    208   * @param {WebElement} element
    209   *     The web element reference to retrieve the rects from.
    210   * @param {BrowsingContext} browsingContext
    211   *     The browsing context to dispatch the event to.
    212   *
    213   * @returns {Promise<Array<Map.<string, number>>>}
    214   *     Promise that resolves to a list of DOMRect-like objects.
    215   */
    216  getClientRects(element, browsingContext) {
    217    return this.#getActor(browsingContext).getClientRects(element);
    218  }
    219 
    220  /**
    221   * Retrieve the in-view center point for the rect and visible viewport.
    222   *
    223   * @param {DOMRect} rect
    224   *     Size and position of the rectangle to check.
    225   * @param {BrowsingContext} browsingContext
    226   *     The browsing context to dispatch the event to.
    227   *
    228   * @returns {Promise<Map.<string, number>>}
    229   *     X and Y coordinates that denotes the in-view centre point of
    230   *     `rect`.
    231   */
    232  getInViewCentrePoint(rect, browsingContext) {
    233    return this.#getActor(browsingContext).getInViewCentrePoint(rect);
    234  }
    235 
    236  /**
    237   * Retrieves the action's input state.
    238   *
    239   * @param {BrowsingContext} browsingContext
    240   *     The Browsing Context to retrieve the input state for.
    241   *
    242   * @returns {Actions.InputState}
    243   *     The action's input state.
    244   */
    245  getInputState(browsingContext) {
    246    // Bug 1821460: Fetch top-level browsing context.
    247    let inputState = this.#driver._inputStates.get(browsingContext);
    248 
    249    if (inputState === undefined) {
    250      inputState = new lazy.actions.State();
    251      this.#driver._inputStates.set(browsingContext, inputState);
    252    }
    253 
    254    return inputState;
    255  }
    256 
    257  /**
    258   * Checks if the given object is a valid element origin.
    259   *
    260   * @param {object} origin
    261   *     The object to check.
    262   *
    263   * @returns {boolean}
    264   *     True, if it's a WebElement.
    265   */
    266  isElementOrigin(origin) {
    267    return lazy.WebElement.Identifier in origin;
    268  }
    269 
    270  /**
    271   * Resets the action's input state.
    272   *
    273   * @param {BrowsingContext} browsingContext
    274   *     The Browsing Context to reset the input state for.
    275   */
    276  resetInputState(browsingContext) {
    277    // Bug 1821460: Fetch top-level browsing context.
    278    if (this.#driver._inputStates.has(browsingContext)) {
    279      this.#driver._inputStates.delete(browsingContext);
    280    }
    281  }
    282 
    283  /**
    284   * Convert a position or rect in browser coordinates of CSS units.
    285   *
    286   * @param {object} position - Object with the coordinates to convert.
    287   * @param {number} position.x - X coordinate.
    288   * @param {number} position.y - Y coordinate.
    289   * @param {BrowsingContext} browsingContext - The Browsing Context to convert the
    290   *     coordinates for.
    291   */
    292  toBrowserWindowCoordinates(position, browsingContext) {
    293    return this.#getActor(browsingContext).toBrowserWindowCoordinates(position);
    294  }
    295 }
    296 
    297 /**
    298 * Implements (parts of) the W3C WebDriver protocol.  GeckoDriver lives
    299 * in chrome space and mediates calls to the current browsing context's actor.
    300 *
    301 * Throughout this prototype, functions with the argument <var>cmd</var>'s
    302 * documentation refers to the contents of the <code>cmd.parameter</code>
    303 * object.
    304 *
    305 * @class GeckoDriver
    306 *
    307 * @param {MarionetteServer} server
    308 *     The instance of Marionette server.
    309 */
    310 export function GeckoDriver(server) {
    311  this._server = server;
    312 
    313  // WebDriver Session
    314  this._currentSession = null;
    315 
    316  // Flag to indicate a WebDriver HTTP session
    317  this._sessionConfigFlags = new Set([lazy.WebDriverSession.SESSION_FLAG_HTTP]);
    318 
    319  // Flag to indicate that the application is shutting down
    320  this._isShuttingDown = false;
    321 
    322  this.browsers = {};
    323 
    324  // points to current browser
    325  this.curBrowser = null;
    326  // top-most chrome window
    327  this.mainFrame = null;
    328 
    329  // Use content context by default
    330  this.context = lazy.Context.Content;
    331 
    332  // used for modal dialogs
    333  this.dialog = null;
    334  this.promptListener = null;
    335 
    336  // Browsing context => input state.
    337  // Bug 1821460: Move to WebDriver Session and share with Remote Agent.
    338  this._inputStates = new WeakMap();
    339 
    340  this._actionsHelper = new ActionsHelper(this);
    341 }
    342 
    343 GeckoDriver.prototype._trace = function (message, browsingContext = null) {
    344  if (browsingContext !== null) {
    345    lazy.logger.trace(`[${browsingContext.id}] ${message}`);
    346  } else {
    347    lazy.logger.trace(message);
    348  }
    349 };
    350 
    351 /**
    352 * The current context decides if commands are executed in chrome- or
    353 * content space.
    354 */
    355 Object.defineProperty(GeckoDriver.prototype, "context", {
    356  get() {
    357    return this._context;
    358  },
    359 
    360  set(context) {
    361    if (context === lazy.Context.Chrome) {
    362      lazy.assert.hasSystemAccess();
    363    }
    364 
    365    this._context = lazy.Context.fromString(context);
    366  },
    367 });
    368 
    369 /**
    370 * The current WebDriver Session.
    371 */
    372 Object.defineProperty(GeckoDriver.prototype, "currentSession", {
    373  get() {
    374    if (lazy.RemoteAgent.webDriverBiDi) {
    375      return lazy.RemoteAgent.webDriverBiDi.session;
    376    }
    377 
    378    return this._currentSession;
    379  },
    380 });
    381 
    382 /**
    383 * Returns the current URL of the ChromeWindow or content browser,
    384 * depending on context.
    385 *
    386 * @returns {URL}
    387 *     Read-only property containing the currently loaded URL.
    388 */
    389 Object.defineProperty(GeckoDriver.prototype, "currentURL", {
    390  get() {
    391    const browsingContext = this.getBrowsingContext({ top: true });
    392    return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
    393  },
    394 });
    395 
    396 /**
    397 * Returns the title of the ChromeWindow or content browser,
    398 * depending on context.
    399 *
    400 * @returns {string}
    401 *     Read-only property containing the title of the loaded URL.
    402 */
    403 Object.defineProperty(GeckoDriver.prototype, "title", {
    404  get() {
    405    const browsingContext = this.getBrowsingContext({ top: true });
    406    return browsingContext.currentWindowGlobal.documentTitle;
    407  },
    408 });
    409 
    410 Object.defineProperty(GeckoDriver.prototype, "windowType", {
    411  get() {
    412    return this.curBrowser.window.document.documentElement.getAttribute(
    413      "windowtype"
    414    );
    415  },
    416 });
    417 
    418 GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
    419  "nsIObserver",
    420  "nsISupportsWeakReference",
    421 ]);
    422 
    423 /**
    424 * Callback used to observe the closing of modal dialogs
    425 * during the session's lifetime.
    426 */
    427 GeckoDriver.prototype.handleClosedModalDialog = function (_eventName, data) {
    428  const { contentBrowser, detail } = data;
    429 
    430  this._trace(
    431    `Prompt closed (type: "${detail.promptType}", accepted: "${detail.accepted}")`,
    432    contentBrowser.browsingContext
    433  );
    434 
    435  this.dialog = null;
    436 };
    437 
    438 /**
    439 * Callback used to observe the creation of new modal dialogs
    440 * during the session's lifetime.
    441 */
    442 GeckoDriver.prototype.handleOpenModalDialog = function (_eventName, data) {
    443  const { contentBrowser, prompt } = data;
    444 
    445  prompt.getText().then(text => {
    446    // We need the text to identify a user prompt when it gets
    447    // randomly opened. Because on Android the text is asynchronously
    448    // retrieved lets delay the logging without making the handler async.
    449    this._trace(
    450      `Prompt opened (type: "${prompt.promptType}", text: "${text}")`,
    451      contentBrowser.browsingContext
    452    );
    453  });
    454 
    455  this.dialog = prompt;
    456 
    457  if (this.dialog.promptType === "beforeunload" && !this.currentSession?.bidi) {
    458    // Only implicitly accept the prompt when its not a BiDi session.
    459    this._trace(`Implicitly accepted "beforeunload" prompt`);
    460    this.dialog.accept();
    461    return;
    462  }
    463 
    464  if (!this._isShuttingDown) {
    465    this.getActor().notifyDialogOpened(this.dialog);
    466  }
    467 };
    468 
    469 /**
    470 * Get the current URL.
    471 *
    472 * @param {object} options
    473 * @param {boolean=} options.top
    474 *     If set to true return the window's top-level URL,
    475 *     otherwise the one from the currently selected frame. Defaults to true.
    476 * @see https://w3c.github.io/webdriver/#get-current-url
    477 */
    478 GeckoDriver.prototype._getCurrentURL = function (options = {}) {
    479  if (options.top === undefined) {
    480    options.top = true;
    481  }
    482  const browsingContext = this.getBrowsingContext(options);
    483  return new URL(browsingContext.currentURI.spec);
    484 };
    485 
    486 /**
    487 * Get the current "MarionetteCommands" parent actor.
    488 *
    489 * @param {object} options
    490 * @param {boolean=} options.top
    491 *     If set to true use the window's top-level browsing context for the actor,
    492 *     otherwise the one from the currently selected frame. Defaults to false.
    493 *
    494 * @returns {MarionetteCommandsParent}
    495 *     The parent actor.
    496 */
    497 GeckoDriver.prototype.getActor = function (options = {}) {
    498  return lazy.getMarionetteCommandsActorProxy(() =>
    499    this.getBrowsingContext(options)
    500  );
    501 };
    502 
    503 /**
    504 * Get the selected BrowsingContext for the current context.
    505 *
    506 * @param {object} options
    507 * @param {Context=} options.context
    508 *     Context (content or chrome) for which to retrieve the browsing context.
    509 *     Defaults to the current one.
    510 * @param {boolean=} options.parent
    511 *     If set to true return the window's parent browsing context,
    512 *     otherwise the one from the currently selected frame. Defaults to false.
    513 * @param {boolean=} options.top
    514 *     If set to true return the window's top-level browsing context,
    515 *     otherwise the one from the currently selected frame. Defaults to false.
    516 *
    517 * @returns {BrowsingContext}
    518 *     The browsing context, or `null` if none is available
    519 */
    520 GeckoDriver.prototype.getBrowsingContext = function (options = {}) {
    521  const { context = this.context, parent = false, top = false } = options;
    522 
    523  let browsingContext = null;
    524  if (context === lazy.Context.Chrome) {
    525    browsingContext = this.currentSession?.chromeBrowsingContext;
    526  } else {
    527    browsingContext = this.currentSession?.contentBrowsingContext;
    528  }
    529 
    530  if (browsingContext && parent) {
    531    browsingContext = browsingContext.parent;
    532  }
    533 
    534  if (browsingContext && top) {
    535    browsingContext = browsingContext.top;
    536  }
    537 
    538  return browsingContext;
    539 };
    540 
    541 /**
    542 * Get the currently selected window.
    543 *
    544 * It will return the outer {@link ChromeWindow} previously selected by
    545 * window handle through {@link #switchToWindow}, or the first window that
    546 * was registered.
    547 *
    548 * @param {object} options
    549 * @param {Context=} options.context
    550 *     Optional name of the context to use for finding the window.
    551 *     It will be required if a command always needs a specific context,
    552 *     whether which context is currently set. Defaults to the current
    553 *     context.
    554 *
    555 * @returns {ChromeWindow}
    556 *     The current top-level browsing context.
    557 */
    558 GeckoDriver.prototype.getCurrentWindow = function (options = {}) {
    559  const { context = this.context } = options;
    560 
    561  let win = null;
    562  switch (context) {
    563    case lazy.Context.Chrome:
    564      if (this.curBrowser) {
    565        win = this.curBrowser.window;
    566      }
    567      break;
    568 
    569    case lazy.Context.Content:
    570      if (this.curBrowser && this.curBrowser.contentBrowser) {
    571        win = this.curBrowser.window;
    572      }
    573      break;
    574  }
    575 
    576  return win;
    577 };
    578 
    579 GeckoDriver.prototype.isReftestBrowser = function (element) {
    580  return (
    581    this._reftest &&
    582    element &&
    583    element.tagName === "xul:browser" &&
    584    element.parentElement &&
    585    element.parentElement.id === "reftest"
    586  );
    587 };
    588 
    589 /**
    590 * Create a new browsing context for window and add to known browsers.
    591 *
    592 * @param {ChromeWindow} win
    593 *     Window for which we will create a browsing context.
    594 *
    595 * @returns {string}
    596 *     Returns the unique server-assigned ID of the window.
    597 */
    598 GeckoDriver.prototype.addBrowser = function (win) {
    599  let context = new lazy.browser.Context(win, this);
    600  let winId = lazy.NavigableManager.getIdForBrowsingContext(
    601    win.browsingContext
    602  );
    603 
    604  this.browsers[winId] = context;
    605  this.curBrowser = this.browsers[winId];
    606 };
    607 
    608 /**
    609 * Handles registration of new content browsers.  Depending on
    610 * their type they are either accepted or ignored.
    611 *
    612 * @param {XULBrowser} browserElement
    613 */
    614 GeckoDriver.prototype.registerBrowser = function (browserElement) {
    615  // We want to ignore frames that are XUL browsers that aren't in the "main"
    616  // tabbrowser, but accept things on Fennec (which doesn't have a
    617  // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
    618  // as well as XUL frames. Ideally this should be cleaned up and we should
    619  // keep track of browsers a different way.
    620  if (
    621    !lazy.AppInfo.isFirefox ||
    622    browserElement.namespaceURI != XUL_NS ||
    623    browserElement.nodeName != "browser" ||
    624    browserElement.getTabBrowser()
    625  ) {
    626    this.curBrowser.register(browserElement);
    627  }
    628 };
    629 
    630 /**
    631 * Create a new WebDriver session.
    632 *
    633 * @param {object} cmd
    634 * @param {Record<string, *>=} cmd.parameters
    635 *     JSON Object containing any of the recognised capabilities as listed
    636 *     on the `WebDriverSession` class.
    637 *
    638 * @returns {object}
    639 *     Session ID and capabilities offered by the WebDriver service.
    640 *
    641 * @throws {SessionNotCreatedError}
    642 *     If, for whatever reason, a session could not be created.
    643 */
    644 GeckoDriver.prototype.newSession = async function (cmd) {
    645  if (this.currentSession) {
    646    throw new lazy.error.SessionNotCreatedError(
    647      "Maximum number of active sessions"
    648    );
    649  }
    650 
    651  const { parameters: capabilities } = cmd;
    652 
    653  try {
    654    if (lazy.RemoteAgent.webDriverBiDi) {
    655      // If the WebDriver BiDi protocol is active always use the Remote Agent
    656      // to handle the WebDriver session.
    657      await lazy.RemoteAgent.webDriverBiDi.createSession(
    658        capabilities,
    659        this._sessionConfigFlags
    660      );
    661    } else {
    662      // If it's not the case then Marionette itself needs to handle it, and
    663      // has to nullify the "webSocketUrl" capability.
    664      this._currentSession = new lazy.WebDriverSession(
    665        capabilities,
    666        this._sessionConfigFlags
    667      );
    668      this._currentSession.capabilities.delete("webSocketUrl");
    669    }
    670 
    671    // Don't wait for the initial window when Marionette is in windowless mode
    672    if (!this.currentSession.capabilities.get("moz:windowless")) {
    673      // Creating a WebDriver session too early can cause issues with
    674      // clients in not being able to find any available window handle.
    675      // Also when closing the application while it's still starting up can
    676      // cause shutdown hangs. As such Marionette will return a new session
    677      // once the initial application window has finished initializing.
    678      lazy.logger.debug(`Waiting for initial application window`);
    679      await lazy.Marionette.browserStartupFinished;
    680 
    681      // This call includes a fallback to "mail:3pane" as well.
    682      const appWin = Services.wm.getMostRecentBrowserWindow();
    683      await lazy.windowManager.waitForChromeWindowLoaded(appWin);
    684 
    685      if (lazy.MarionettePrefs.clickToStart) {
    686        Services.prompt.alert(
    687          appWin,
    688          "",
    689          "Click to start execution of marionette tests"
    690        );
    691      }
    692 
    693      this.addBrowser(appWin);
    694      this.mainFrame = appWin;
    695 
    696      // Setup observer for modal dialogs
    697      this.promptListener = new lazy.PromptListener(() => this.curBrowser);
    698      this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
    699      this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
    700      this.promptListener.startListening();
    701 
    702      for (let win of lazy.windowManager.windows) {
    703        this.registerWindow(win, { registerBrowsers: true });
    704      }
    705 
    706      if (this.mainFrame) {
    707        this.currentSession.chromeBrowsingContext =
    708          this.mainFrame.browsingContext;
    709        this.mainFrame.focus();
    710      }
    711 
    712      if (this.curBrowser.tab) {
    713        const browsingContext = this.curBrowser.contentBrowser.browsingContext;
    714        this.currentSession.contentBrowsingContext = browsingContext;
    715 
    716        // Bug 1838381 - Only use a longer unload timeout for desktop, because
    717        // on Android only the initial document is loaded, and loading a
    718        // specific page during startup doesn't succeed.
    719        const options = {};
    720        if (!lazy.AppInfo.isAndroid) {
    721          options.unloadTimeout = 5000;
    722        }
    723 
    724        await lazy.waitForInitialNavigationCompleted(
    725          browsingContext.webProgress,
    726          options
    727        );
    728 
    729        this.curBrowser.contentBrowser.focus();
    730      }
    731 
    732      // Check if there is already an open dialog for the selected browser window.
    733      this.dialog = lazy.modal.findPrompt(this.curBrowser);
    734    }
    735 
    736    lazy.registerCommandsActor(this.currentSession.id);
    737    lazy.enableEventsActor();
    738 
    739    Services.obs.addObserver(this, TOPIC_BROWSER_READY);
    740  } catch (e) {
    741    throw new lazy.error.SessionNotCreatedError(e);
    742  }
    743 
    744  return {
    745    sessionId: this.currentSession.id,
    746    capabilities: this.currentSession.capabilities,
    747  };
    748 };
    749 
    750 /**
    751 * Start observing the specified window.
    752 *
    753 * @param {ChromeWindow} win
    754 *     Chrome window to register event listeners for.
    755 * @param {object=} options
    756 * @param {boolean=} options.registerBrowsers
    757 *     If true, register all content browsers of found tabs. Defaults to false.
    758 */
    759 GeckoDriver.prototype.registerWindow = function (win, options = {}) {
    760  const { registerBrowsers = false } = options;
    761  const tabBrowser = lazy.TabManager.getTabBrowser(win);
    762 
    763  if (registerBrowsers && tabBrowser) {
    764    for (const tab of tabBrowser.tabs) {
    765      const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
    766      this.registerBrowser(contentBrowser);
    767    }
    768  }
    769 
    770  // Listen for any kind of top-level process switch
    771  tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
    772 };
    773 
    774 /**
    775 * Stop observing the specified window.
    776 *
    777 * @param {ChromeWindow} win
    778 *     Chrome window to unregister event listeners for.
    779 */
    780 GeckoDriver.prototype.stopObservingWindow = function (win) {
    781  const tabBrowser = lazy.TabManager.getTabBrowser(win);
    782 
    783  tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
    784 };
    785 
    786 GeckoDriver.prototype.handleEvent = function ({ target, type }) {
    787  switch (type) {
    788    case "XULFrameLoaderCreated":
    789      if (target === this.curBrowser.contentBrowser) {
    790        lazy.logger.trace(
    791          "Remoteness change detected. Set new top-level browsing context " +
    792            `to ${target.browsingContext.id}`
    793        );
    794 
    795        this.currentSession.contentBrowsingContext = target.browsingContext;
    796      }
    797      break;
    798  }
    799 };
    800 
    801 GeckoDriver.prototype.observe = async function (subject, topic) {
    802  switch (topic) {
    803    case TOPIC_BROWSER_READY:
    804      this.registerWindow(subject);
    805      break;
    806 
    807    case TOPIC_QUIT_APPLICATION_REQUESTED:
    808      // Run Marionette specific cleanup steps before allowing
    809      // the application to shutdown
    810      await this._server.setAcceptConnections(false);
    811      this.deleteSession();
    812      break;
    813  }
    814 };
    815 
    816 /**
    817 * Send the current session's capabilities to the client.
    818 *
    819 * Capabilities informs the client of which WebDriver features are
    820 * supported by Firefox and Marionette.  They are immutable for the
    821 * length of the session.
    822 *
    823 * The return value is an immutable map of string keys
    824 * ("capabilities") to values, which may be of types boolean,
    825 * numerical or string.
    826 */
    827 GeckoDriver.prototype.getSessionCapabilities = function () {
    828  return { capabilities: this.currentSession.capabilities };
    829 };
    830 
    831 /**
    832 * Register a chrome protocol handler for a directory containing XHTML or XUL
    833 * files, allowing them to be loaded via the chrome:// protocol.
    834 *
    835 * @param {obj} cmd
    836 * @param {string} cmd.parameters.manifestPath
    837 *     The base manifest path for the entries. URL values are resolved
    838 *     relative to this path.
    839 * @param {Array<Array<string, string, string>>} cmd.parameters.entries
    840 *     An array of arrays, each containing a registry entry (type, namespace,
    841 *     path, options) as it would appear in a chrome.manifest file. Only the
    842 *     following entry types are currently accepted:
    843 *
    844 *         - "content" A URL entry. Must be a 3-element array.
    845 *         - "override" A URL override entry. Must be a 3-element array.
    846 *         - "locale" A locale package entry. Must be a 4-element array.
    847 *
    848 * @returns {string} id
    849 *     The identifier for the registered chrome protocol handler.
    850 *
    851 * @throws {InvalidArgumentError}
    852 *     If <var>id</var> is not a string.
    853 * @throws {UnknownError}
    854 *     If there is no such registered chrome protocol handler.
    855 */
    856 GeckoDriver.prototype.registerChromeHandler = function (cmd) {
    857  const manifestPath = lazy.assert.string(
    858    cmd.parameters.manifestPath,
    859    lazy.pprint`Expected "path" to be a string, got ${cmd.parameters.manifestPath}`
    860  );
    861 
    862  const entries = lazy.assert.array(
    863    cmd.parameters.entries,
    864    lazy.pprint`Expected "entries" to be an array, got ${cmd.parameters.entries}`
    865  );
    866  entries.forEach(entry => {
    867    const [type, namespace, directory, options] = lazy.assert.array(
    868      entry,
    869      lazy.pprint`Expected values of "entries" to be an array, got ${entries}`
    870    );
    871    lazy.assert.string(
    872      type,
    873      lazy.pprint`Expected "type" of entry to be a string, got ${type}`
    874    );
    875    lazy.assert.string(
    876      namespace,
    877      lazy.pprint`Expected "namespace" of entry to be a string, got ${namespace}`
    878    );
    879    lazy.assert.string(
    880      directory,
    881      lazy.pprint`Expected "directory" of entry to be a string, got ${directory}`
    882    );
    883    if (options !== undefined) {
    884      lazy.assert.string(
    885        options,
    886        lazy.pprint`Expected "options" of entry to be a string, got ${options}`
    887      );
    888    }
    889  });
    890 
    891  lazy.assert.hasSystemAccess();
    892 
    893  return this.currentSession.registerChromeHandler(manifestPath, entries);
    894 };
    895 
    896 /**
    897 * Unregister a previously registered chrome protocol handler.
    898 *
    899 * @param {obj} cmd
    900 * @param {string} cmd.parameters.id
    901 *     The identifier returned when the chrome handler was registered.
    902 *
    903 * @throws {InvalidArgumentError}
    904 *     If <var>id</var> is not a string.
    905 * @throws {UnknownError}
    906 *     If there is no such registered chrome protocol handler.
    907 */
    908 GeckoDriver.prototype.unregisterChromeHandler = function (cmd) {
    909  const id = lazy.assert.string(
    910    cmd.parameters.id,
    911    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
    912  );
    913 
    914  lazy.assert.hasSystemAccess();
    915 
    916  this.currentSession.unregisterChromeHandler(id);
    917 };
    918 
    919 /**
    920 * Sets the context of the subsequent commands.
    921 *
    922 * All subsequent requests to commands that in some way involve
    923 * interaction with a browsing context will target the chosen browsing
    924 * context.
    925 *
    926 * @param {object} cmd
    927 * @param {string} cmd.parameters.value
    928 *     Name of the context to be switched to.  Must be one of "chrome" or
    929 *     "content".
    930 *
    931 * @throws {InvalidArgumentError}
    932 *     If <var>value</var> is not a string.
    933 * @throws {WebDriverError}
    934 *     If <var>value</var> is not a valid browsing context.
    935 */
    936 GeckoDriver.prototype.setContext = function (cmd) {
    937  let value = lazy.assert.string(
    938    cmd.parameters.value,
    939    lazy.pprint`Expected "value" to be a string, got ${cmd.parameters.value}`
    940  );
    941 
    942  this.context = value;
    943 };
    944 
    945 /**
    946 * Gets the context type that is Marionette's current target for
    947 * browsing context scoped commands.
    948 *
    949 * You may choose a context through the {@link #setContext} command.
    950 *
    951 * The default browsing context is {@link Context.Content}.
    952 *
    953 * @returns {Context}
    954 *     Current context.
    955 */
    956 GeckoDriver.prototype.getContext = function () {
    957  return this.context;
    958 };
    959 
    960 /**
    961 * Executes a JavaScript function in the context of the current browsing
    962 * context, if in content space, or in chrome space otherwise, and returns
    963 * the return value of the function.
    964 *
    965 * It is important to note that if the <var>sandboxName</var> parameter
    966 * is left undefined, the script will be evaluated in a mutable sandbox,
    967 * causing any change it makes on the global state of the document to have
    968 * lasting side-effects.
    969 *
    970 * @param {object} cmd
    971 * @param {string} cmd.parameters.script
    972 *     Script to evaluate as a function body.
    973 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
    974 *     Arguments exposed to the script in <code>arguments</code>.
    975 *     The array items must be serialisable to the WebDriver protocol.
    976 * @param {string=} cmd.parameters.sandbox
    977 *     Name of the sandbox to evaluate the script in.  The sandbox is
    978 *     cached for later reuse on the same Window object if
    979 *     <var>newSandbox</var> is false.  If the parameter is undefined,
    980 *     the script is evaluated in a mutable sandbox.  If the parameter
    981 *     is "system", it will be evaluated in a sandbox with elevated system
    982 *     privileges, equivalent to chrome space.
    983 * @param {boolean=} cmd.parameters.newSandbox
    984 *     Forces the script to be evaluated in a fresh sandbox.  Note that if
    985 *     it is undefined, the script will normally be evaluated in a fresh
    986 *     sandbox.
    987 * @param {string=} cmd.parameters.filename
    988 *     Filename of the client's program where this script is evaluated.
    989 * @param {number=} cmd.parameters.line
    990 *     Line in the client's program where this script is evaluated.
    991 *
    992 * @returns {(string|boolean|number|object|WebReference)}
    993 *     Return value from the script, or null which signifies either the
    994 *     JavaScript notion of null or undefined.
    995 *
    996 * @throws {JavaScriptError}
    997 *     If an {@link Error} was thrown whilst evaluating the script.
    998 * @throws {NoSuchElementError}
    999 *     If an element that was passed as part of <var>args</var> is unknown.
   1000 * @throws {NoSuchFrameError}
   1001 *     Child browsing context has been discarded.
   1002 * @throws {NoSuchWindowError}
   1003 *     Top-level browsing context has been discarded.
   1004 * @throws {ScriptTimeoutError}
   1005 *     If the script was interrupted due to reaching the session's
   1006 *     script timeout.
   1007 * @throws {StaleElementReferenceError}
   1008 *     If an element that was passed as part of <var>args</var> or that is
   1009 *     returned as result has gone stale.
   1010 */
   1011 GeckoDriver.prototype.executeScript = function (cmd) {
   1012  let { script, args } = cmd.parameters;
   1013  let opts = {
   1014    script: cmd.parameters.script,
   1015    args: cmd.parameters.args,
   1016    sandboxName: cmd.parameters.sandbox,
   1017    newSandbox: cmd.parameters.newSandbox,
   1018    file: cmd.parameters.filename,
   1019    line: cmd.parameters.line,
   1020  };
   1021 
   1022  return this.execute_(script, args, opts);
   1023 };
   1024 
   1025 /**
   1026 * Executes a JavaScript function in the context of the current browsing
   1027 * context, if in content space, or in chrome space otherwise, and returns
   1028 * the object passed to the callback.
   1029 *
   1030 * The callback is always the last argument to the <var>arguments</var>
   1031 * list passed to the function scope of the script.  It can be retrieved
   1032 * as such:
   1033 *
   1034 * <pre><code>
   1035 *     let callback = arguments[arguments.length - 1];
   1036 *     callback("foo");
   1037 *     // "foo" is returned
   1038 * </code></pre>
   1039 *
   1040 * It is important to note that if the <var>sandboxName</var> parameter
   1041 * is left undefined, the script will be evaluated in a mutable sandbox,
   1042 * causing any change it makes on the global state of the document to have
   1043 * lasting side-effects.
   1044 *
   1045 * @param {object} cmd
   1046 * @param {string} cmd.parameters.script
   1047 *     Script to evaluate as a function body.
   1048 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
   1049 *     Arguments exposed to the script in <code>arguments</code>.
   1050 *     The array items must be serialisable to the WebDriver protocol.
   1051 * @param {string=} cmd.parameters.sandbox
   1052 *     Name of the sandbox to evaluate the script in.  The sandbox is
   1053 *     cached for later reuse on the same Window object if
   1054 *     <var>newSandbox</var> is false.  If the parameter is undefined,
   1055 *     the script is evaluated in a mutable sandbox.  If the parameter
   1056 *     is "system", it will be evaluated in a sandbox with elevated system
   1057 *     privileges, equivalent to chrome space.
   1058 * @param {boolean=} cmd.parameters.newSandbox
   1059 *     Forces the script to be evaluated in a fresh sandbox.  Note that if
   1060 *     it is undefined, the script will normally be evaluated in a fresh
   1061 *     sandbox.
   1062 * @param {string=} cmd.parameters.filename
   1063 *     Filename of the client's program where this script is evaluated.
   1064 * @param {number=} cmd.parameters.line
   1065 *     Line in the client's program where this script is evaluated.
   1066 *
   1067 * @returns {(string|boolean|number|object|WebReference)}
   1068 *     Return value from the script, or null which signifies either the
   1069 *     JavaScript notion of null or undefined.
   1070 *
   1071 * @throws {JavaScriptError}
   1072 *     If an Error was thrown whilst evaluating the script.
   1073 * @throws {NoSuchElementError}
   1074 *     If an element that was passed as part of <var>args</var> is unknown.
   1075 * @throws {NoSuchFrameError}
   1076 *     Child browsing context has been discarded.
   1077 * @throws {NoSuchWindowError}
   1078 *     Top-level browsing context has been discarded.
   1079 * @throws {ScriptTimeoutError}
   1080 *     If the script was interrupted due to reaching the session's
   1081 *     script timeout.
   1082 * @throws {StaleElementReferenceError}
   1083 *     If an element that was passed as part of <var>args</var> or that is
   1084 *     returned as result has gone stale.
   1085 */
   1086 GeckoDriver.prototype.executeAsyncScript = function (cmd) {
   1087  let { script, args } = cmd.parameters;
   1088  let opts = {
   1089    script: cmd.parameters.script,
   1090    args: cmd.parameters.args,
   1091    sandboxName: cmd.parameters.sandbox,
   1092    newSandbox: cmd.parameters.newSandbox,
   1093    file: cmd.parameters.filename,
   1094    line: cmd.parameters.line,
   1095    async: true,
   1096  };
   1097 
   1098  return this.execute_(script, args, opts);
   1099 };
   1100 
   1101 GeckoDriver.prototype.execute_ = async function (
   1102  script,
   1103  args = [],
   1104  {
   1105    sandboxName = null,
   1106    newSandbox = false,
   1107    file = "",
   1108    line = 0,
   1109    async = false,
   1110  } = {}
   1111 ) {
   1112  lazy.assert.open(this.getBrowsingContext());
   1113  await this._handleUserPrompts();
   1114 
   1115  lazy.assert.string(
   1116    script,
   1117    lazy.pprint`Expected "script" to be a string, got ${script}`
   1118  );
   1119  lazy.assert.array(
   1120    args,
   1121    lazy.pprint`Expected "args" to be an array, got ${args}`
   1122  );
   1123  if (sandboxName !== null) {
   1124    lazy.assert.string(
   1125      sandboxName,
   1126      lazy.pprint`Expected "sandboxName" to be a string, got ${sandboxName}`
   1127    );
   1128  }
   1129  lazy.assert.boolean(
   1130    newSandbox,
   1131    lazy.pprint`Expected "newSandbox" to be boolean, got ${newSandbox}`
   1132  );
   1133  lazy.assert.string(
   1134    file,
   1135    lazy.pprint`Expected "file" to be a string, got ${file}`
   1136  );
   1137  lazy.assert.number(
   1138    line,
   1139    lazy.pprint`Expected "line" to be a number, got ${line}`
   1140  );
   1141 
   1142  let opts = {
   1143    timeout: this.currentSession.timeouts.script,
   1144    sandboxName,
   1145    newSandbox,
   1146    file,
   1147    line,
   1148    async,
   1149  };
   1150 
   1151  return this.getActor().executeScript(script, args, opts);
   1152 };
   1153 
   1154 /**
   1155 * Navigate to given URL.
   1156 *
   1157 * Navigates the current browsing context to the given URL and waits for
   1158 * the document to load or the session's page timeout duration to elapse
   1159 * before returning.
   1160 *
   1161 * The command will return with a failure if there is an error loading
   1162 * the document or the URL is blocked.  This can occur if it fails to
   1163 * reach host, the URL is malformed, or if there is a certificate issue
   1164 * to name some examples.
   1165 *
   1166 * The document is considered successfully loaded when the
   1167 * DOMContentLoaded event on the frame element associated with the
   1168 * current window triggers and document.readyState is "complete".
   1169 *
   1170 * In chrome context it will change the current window's location to
   1171 * the supplied URL and wait until document.readyState equals "complete"
   1172 * or the page timeout duration has elapsed.
   1173 *
   1174 * @param {object} cmd
   1175 * @param {string} cmd.parameters.url
   1176 *     URL to navigate to.
   1177 *
   1178 * @throws {NoSuchWindowError}
   1179 *     Top-level browsing context has been discarded.
   1180 * @throws {UnexpectedAlertOpenError}
   1181 *     A modal dialog is open, blocking this operation.
   1182 * @throws {UnsupportedOperationError}
   1183 *     Not available in current context.
   1184 */
   1185 GeckoDriver.prototype.navigateTo = async function (cmd) {
   1186  lazy.assert.content(this.context);
   1187  const browsingContext = lazy.assert.open(
   1188    this.getBrowsingContext({ top: true })
   1189  );
   1190  await this._handleUserPrompts();
   1191 
   1192  let { url } = cmd.parameters;
   1193 
   1194  let validURL = URL.parse(url);
   1195  if (!validURL) {
   1196    throw new lazy.error.InvalidArgumentError(
   1197      lazy.truncate`Expected "url" to be a valid URL, got ${url}`
   1198    );
   1199  }
   1200 
   1201  // Switch to the top-level browsing context before navigating
   1202  this.currentSession.contentBrowsingContext = browsingContext;
   1203 
   1204  const loadEventExpected = lazy.navigate.isLoadEventExpected(
   1205    this._getCurrentURL(),
   1206    {
   1207      future: validURL,
   1208    }
   1209  );
   1210 
   1211  await lazy.navigate.waitForNavigationCompleted(
   1212    this,
   1213    () => {
   1214      lazy.navigate.navigateTo(browsingContext, validURL);
   1215    },
   1216    { loadEventExpected }
   1217  );
   1218 
   1219  this.curBrowser.contentBrowser.focus();
   1220 };
   1221 
   1222 /**
   1223 * Get a string representing the current URL.
   1224 *
   1225 * On Desktop this returns a string representation of the URL of the
   1226 * current top level browsing context.  This is equivalent to
   1227 * document.location.href.
   1228 *
   1229 * When in the context of the chrome, this returns the canonical URL
   1230 * of the current resource.
   1231 *
   1232 * @throws {NoSuchWindowError}
   1233 *     Top-level browsing context has been discarded.
   1234 * @throws {UnexpectedAlertOpenError}
   1235 *     A modal dialog is open, blocking this operation.
   1236 */
   1237 GeckoDriver.prototype.getCurrentUrl = async function () {
   1238  lazy.assert.open(this.getBrowsingContext({ top: true }));
   1239  await this._handleUserPrompts();
   1240 
   1241  return this._getCurrentURL().href;
   1242 };
   1243 
   1244 /**
   1245 * Gets the current title of the window.
   1246 *
   1247 * @returns {string}
   1248 *     Document title of the top-level browsing context.
   1249 *
   1250 * @throws {NoSuchWindowError}
   1251 *     Top-level browsing context has been discarded.
   1252 * @throws {UnexpectedAlertOpenError}
   1253 *     A modal dialog is open, blocking this operation.
   1254 */
   1255 GeckoDriver.prototype.getTitle = async function () {
   1256  lazy.assert.open(this.getBrowsingContext({ top: true }));
   1257  await this._handleUserPrompts();
   1258 
   1259  return this.title;
   1260 };
   1261 
   1262 /**
   1263 * Gets the current type of the window.
   1264 *
   1265 * @returns {string}
   1266 *     Type of window
   1267 *
   1268 * @throws {NoSuchWindowError}
   1269 *     Top-level browsing context has been discarded.
   1270 */
   1271 GeckoDriver.prototype.getWindowType = function () {
   1272  lazy.assert.open(this.getBrowsingContext({ top: true }));
   1273 
   1274  return this.windowType;
   1275 };
   1276 
   1277 /**
   1278 * Gets the page source of the content document.
   1279 *
   1280 * @returns {string}
   1281 *     String serialisation of the DOM of the current browsing context's
   1282 *     active document.
   1283 *
   1284 * @throws {NoSuchWindowError}
   1285 *     Browsing context has been discarded.
   1286 * @throws {UnexpectedAlertOpenError}
   1287 *     A modal dialog is open, blocking this operation.
   1288 */
   1289 GeckoDriver.prototype.getPageSource = async function () {
   1290  lazy.assert.open(this.getBrowsingContext());
   1291  await this._handleUserPrompts();
   1292 
   1293  return this.getActor().getPageSource();
   1294 };
   1295 
   1296 /**
   1297 * Cause the browser to traverse one step backward in the joint history
   1298 * of the current browsing context.
   1299 *
   1300 * @throws {NoSuchWindowError}
   1301 *     Top-level browsing context has been discarded.
   1302 * @throws {UnexpectedAlertOpenError}
   1303 *     A modal dialog is open, blocking this operation.
   1304 * @throws {UnsupportedOperationError}
   1305 *     Not available in current context.
   1306 */
   1307 GeckoDriver.prototype.goBack = async function () {
   1308  lazy.assert.content(this.context);
   1309  const browsingContext = lazy.assert.open(
   1310    this.getBrowsingContext({ top: true })
   1311  );
   1312  await this._handleUserPrompts();
   1313 
   1314  // If there is no history, just return
   1315  if (!browsingContext.embedderElement?.canGoBackIgnoringUserInteraction) {
   1316    return;
   1317  }
   1318 
   1319  await lazy.navigate.waitForNavigationCompleted(this, () => {
   1320    browsingContext.goBack();
   1321  });
   1322 };
   1323 
   1324 /**
   1325 * Cause the browser to traverse one step forward in the joint history
   1326 * of the current browsing context.
   1327 *
   1328 * @throws {NoSuchWindowError}
   1329 *     Top-level browsing context has been discarded.
   1330 * @throws {UnexpectedAlertOpenError}
   1331 *     A modal dialog is open, blocking this operation.
   1332 * @throws {UnsupportedOperationError}
   1333 *     Not available in current context.
   1334 */
   1335 GeckoDriver.prototype.goForward = async function () {
   1336  lazy.assert.content(this.context);
   1337  const browsingContext = lazy.assert.open(
   1338    this.getBrowsingContext({ top: true })
   1339  );
   1340  await this._handleUserPrompts();
   1341 
   1342  // If there is no history, just return
   1343  if (!browsingContext.embedderElement?.canGoForward) {
   1344    return;
   1345  }
   1346 
   1347  await lazy.navigate.waitForNavigationCompleted(this, () => {
   1348    browsingContext.goForward();
   1349  });
   1350 };
   1351 
   1352 /**
   1353 * Causes the browser to reload the page in current top-level browsing
   1354 * context.
   1355 *
   1356 * @throws {NoSuchWindowError}
   1357 *     Top-level browsing context has been discarded.
   1358 * @throws {UnexpectedAlertOpenError}
   1359 *     A modal dialog is open, blocking this operation.
   1360 * @throws {UnsupportedOperationError}
   1361 *     Not available in current context.
   1362 */
   1363 GeckoDriver.prototype.refresh = async function () {
   1364  lazy.assert.content(this.context);
   1365  const browsingContext = lazy.assert.open(
   1366    this.getBrowsingContext({ top: true })
   1367  );
   1368  await this._handleUserPrompts();
   1369 
   1370  // Switch to the top-level browsing context before navigating
   1371  this.currentSession.contentBrowsingContext = browsingContext;
   1372 
   1373  await lazy.navigate.waitForNavigationCompleted(this, () => {
   1374    lazy.navigate.refresh(browsingContext);
   1375  });
   1376 };
   1377 
   1378 /**
   1379 * Get the current window's handle. On desktop this typically corresponds
   1380 * to the currently selected tab.
   1381 *
   1382 * For chrome scope it returns the window identifier for the current chrome
   1383 * window for tests interested in managing the chrome window and tab separately.
   1384 *
   1385 * Return an opaque server-assigned identifier to this window that
   1386 * uniquely identifies it within this Marionette instance.  This can
   1387 * be used to switch to this window at a later point.
   1388 *
   1389 * @returns {string}
   1390 *     Unique window handle.
   1391 *
   1392 * @throws {NoSuchWindowError}
   1393 *     Top-level browsing context has been discarded.
   1394 */
   1395 GeckoDriver.prototype.getWindowHandle = function () {
   1396  lazy.assert.open(this.getBrowsingContext({ top: true }));
   1397 
   1398  if (this.context == lazy.Context.Chrome) {
   1399    return lazy.NavigableManager.getIdForBrowsingContext(
   1400      this.currentSession.chromeBrowsingContext
   1401    );
   1402  }
   1403 
   1404  return this.curBrowser.contentBrowserId;
   1405 };
   1406 
   1407 /**
   1408 * Get a list of top-level browsing contexts. On desktop this typically
   1409 * corresponds to the set of open tabs for browser windows, or the window
   1410 * itself for non-browser chrome windows.
   1411 *
   1412 * For chrome scope it returns identifiers for each open chrome window for
   1413 * tests interested in managing a set of chrome windows and tabs separately.
   1414 *
   1415 * Each window handle is assigned by the server and is guaranteed unique,
   1416 * however the return array does not have a specified ordering.
   1417 *
   1418 * @returns {Array.<string>}
   1419 *     Unique window handles.
   1420 */
   1421 GeckoDriver.prototype.getWindowHandles = function () {
   1422  if (this.context == lazy.Context.Chrome) {
   1423    return lazy.windowManager.windows.map(window =>
   1424      lazy.NavigableManager.getIdForBrowsingContext(window.browsingContext)
   1425    );
   1426  }
   1427 
   1428  return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser =>
   1429    lazy.NavigableManager.getIdForBrowser(browser)
   1430  );
   1431 };
   1432 
   1433 /**
   1434 * Get the current position and size of the browser window currently in focus.
   1435 *
   1436 * Will return the current browser window size in pixels. Refers to
   1437 * window outerWidth and outerHeight values, which include scroll bars,
   1438 * title bars, etc.
   1439 *
   1440 * @returns {Record<string, number>}
   1441 *     Object with |x| and |y| coordinates, and |width| and |height|
   1442 *     of browser window.
   1443 *
   1444 * @throws {NoSuchWindowError}
   1445 *     Top-level browsing context has been discarded.
   1446 * @throws {UnexpectedAlertOpenError}
   1447 *     A modal dialog is open, blocking this operation.
   1448 */
   1449 GeckoDriver.prototype.getWindowRect = async function () {
   1450  lazy.assert.open(this.getBrowsingContext({ top: true }));
   1451  await this._handleUserPrompts();
   1452 
   1453  return this.curBrowser.rect;
   1454 };
   1455 
   1456 /**
   1457 * Set the window position and size of the browser on the operating
   1458 * system window manager.
   1459 *
   1460 * The supplied `width` and `height` values refer to the window `outerWidth`
   1461 * and `outerHeight` values, which include browser chrome and OS-level
   1462 * window borders.
   1463 *
   1464 * @param {object} cmd
   1465 * @param {number} cmd.parameters.x
   1466 *     X coordinate of the top/left of the window that it will be
   1467 *     moved to.
   1468 * @param {number} cmd.parameters.y
   1469 *     Y coordinate of the top/left of the window that it will be
   1470 *     moved to.
   1471 * @param {number} cmd.parameters.width
   1472 *     Width to resize the window to.
   1473 * @param {number} cmd.parameters.height
   1474 *     Height to resize the window to.
   1475 *
   1476 * @returns {Record<string, number>}
   1477 *     Object with `x` and `y` coordinates and `width` and `height`
   1478 *     dimensions.
   1479 *
   1480 * @throws {NoSuchWindowError}
   1481 *     Top-level browsing context has been discarded.
   1482 * @throws {UnexpectedAlertOpenError}
   1483 *     A modal dialog is open, blocking this operation.
   1484 * @throws {UnsupportedOperationError}
   1485 *     Not applicable to application.
   1486 */
   1487 GeckoDriver.prototype.setWindowRect = async function (cmd) {
   1488  lazy.assert.desktop();
   1489  lazy.assert.open(this.getBrowsingContext({ top: true }));
   1490  await this._handleUserPrompts();
   1491 
   1492  const { x = null, y = null, width = null, height = null } = cmd.parameters;
   1493  if (x !== null) {
   1494    lazy.assert.integer(
   1495      x,
   1496      lazy.pprint`Expected "x" to be an integer value, got ${x}`
   1497    );
   1498  }
   1499  if (y !== null) {
   1500    lazy.assert.integer(
   1501      y,
   1502      lazy.pprint`Expected "y" to be an integer value, got ${y}`
   1503    );
   1504  }
   1505  if (height !== null) {
   1506    lazy.assert.positiveInteger(
   1507      height,
   1508      lazy.pprint`Expected "height" to be a positive integer value, got ${height}`
   1509    );
   1510  }
   1511  if (width !== null) {
   1512    lazy.assert.positiveInteger(
   1513      width,
   1514      lazy.pprint`Expected "width" to be a positive integer value, got ${width}`
   1515    );
   1516  }
   1517 
   1518  const win = this.getCurrentWindow();
   1519  switch (lazy.WindowState.from(win.windowState)) {
   1520    case lazy.WindowState.Fullscreen:
   1521      await lazy.windowManager.setFullscreen(win, false);
   1522      break;
   1523 
   1524    case lazy.WindowState.Maximized:
   1525    case lazy.WindowState.Minimized:
   1526      await lazy.windowManager.restoreWindow(win);
   1527      break;
   1528  }
   1529 
   1530  await lazy.windowManager.adjustWindowGeometry(win, x, y, width, height);
   1531 
   1532  return this.curBrowser.rect;
   1533 };
   1534 
   1535 /**
   1536 * Find a specific window matching the provided window handle.
   1537 *
   1538 * @param {string} handle
   1539 *     The unique handle of either a chrome window or a content browser, as
   1540 *     returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`.
   1541 *
   1542 * @returns {object|null}
   1543 *     A window properties object, or `null` if a window cannot be found.
   1544 *.    @see :js:func:`WindowManager#getWindowProperties`
   1545 */
   1546 GeckoDriver.prototype._findWindowByHandle = function (handle) {
   1547  for (const win of lazy.windowManager.windows) {
   1548    const chromeWindowId = lazy.NavigableManager.getIdForBrowsingContext(
   1549      win.browsingContext
   1550    );
   1551    if (chromeWindowId == handle) {
   1552      return this.getWindowProperties(win);
   1553    }
   1554 
   1555    // Otherwise check if the chrome window has a tab browser, and that it
   1556    // contains a tab with the wanted window handle.
   1557    const tabBrowser = lazy.TabManager.getTabBrowser(win);
   1558    if (tabBrowser && tabBrowser.tabs) {
   1559      for (let i = 0; i < tabBrowser.tabs.length; ++i) {
   1560        let contentBrowser = lazy.TabManager.getBrowserForTab(
   1561          tabBrowser.tabs[i]
   1562        );
   1563        let contentWindowId =
   1564          lazy.NavigableManager.getIdForBrowser(contentBrowser);
   1565 
   1566        if (contentWindowId == handle) {
   1567          return this.getWindowProperties(win, { tabIndex: i });
   1568        }
   1569      }
   1570    }
   1571  }
   1572 
   1573  return null;
   1574 };
   1575 
   1576 /**
   1577 * Switch current top-level browsing context by name or server-assigned
   1578 * ID.  Searches for windows by name, then ID.  Content windows take
   1579 * precedence.
   1580 *
   1581 * @param {object} cmd
   1582 * @param {string} cmd.parameters.handle
   1583 *     Handle of the window to switch to.
   1584 * @param {boolean=} cmd.parameters.focus
   1585 *     A boolean value which determines whether to focus
   1586 *     the window. Defaults to true.
   1587 *
   1588 * @throws {InvalidArgumentError}
   1589 *     If <var>handle</var> is not a string or <var>focus</var> not a boolean.
   1590 * @throws {NoSuchWindowError}
   1591 *     Top-level browsing context has been discarded.
   1592 */
   1593 GeckoDriver.prototype.switchToWindow = async function (cmd) {
   1594  const { focus = true, handle } = cmd.parameters;
   1595 
   1596  lazy.assert.string(
   1597    handle,
   1598    lazy.pprint`Expected "handle" to be a string, got ${handle}`
   1599  );
   1600  lazy.assert.boolean(
   1601    focus,
   1602    lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
   1603  );
   1604 
   1605  const found = this._findWindowByHandle(handle);
   1606 
   1607  let selected = false;
   1608  if (found) {
   1609    try {
   1610      await this.setWindowHandle(found, focus);
   1611      selected = true;
   1612    } catch (e) {
   1613      lazy.logger.error(e);
   1614    }
   1615  }
   1616 
   1617  if (!selected) {
   1618    throw new lazy.error.NoSuchWindowError(
   1619      `Unable to locate window: ${handle}`
   1620    );
   1621  }
   1622 };
   1623 
   1624 /**
   1625 * A set of properties that describe a window and allow it to be uniquely
   1626 * identified. The described window can either be a Chrome Window or a
   1627 * Content Window.
   1628 *
   1629 * @typedef {object} WindowProperties
   1630 * @property {Window} win
   1631 *     The Chrome Window containing the window. When describing
   1632 *     a Chrome Window, this is the window itself.
   1633 * @property {string} id
   1634 *     The unique id of the containing Chrome Window.
   1635 * @property {boolean} hasTabBrowser
   1636 *     `true` if the Chrome Window has a tabBrowser.
   1637 * @property {number=} tabIndex
   1638 *     Optional, the index of the specific tab within the window.
   1639 */
   1640 
   1641 /**
   1642 * Returns a WindowProperties object, that can be used with :js:func:`GeckoDriver#setWindowHandle`.
   1643 *
   1644 * @param {Window} win
   1645 *     The Chrome Window for which we want to create a properties object.
   1646 * @param {object=} options
   1647 * @param {number} options.tabIndex
   1648 *     Tab index of a specific Content Window in the specified Chrome Window.
   1649 *
   1650 * @returns {WindowProperties}
   1651 *     A window properties object.
   1652 */
   1653 GeckoDriver.prototype.getWindowProperties = function (win, options = {}) {
   1654  const { tabIndex } = options;
   1655 
   1656  if (!Window.isInstance(win)) {
   1657    throw new TypeError("Invalid argument, expected a Window object");
   1658  }
   1659 
   1660  return {
   1661    win,
   1662    id: lazy.NavigableManager.getIdForBrowsingContext(win.browsingContext),
   1663    hasTabBrowser: !!lazy.TabManager.getTabBrowser(win),
   1664    tabIndex,
   1665  };
   1666 };
   1667 
   1668 /**
   1669 * Switch the marionette window to a given window. If the browser in
   1670 * the window is unregistered, register that browser and wait for
   1671 * the registration is complete. If |focus| is true then set the focus
   1672 * on the window.
   1673 *
   1674 * @param {object} winProperties
   1675 *     Object containing window properties such as returned from
   1676 *     :js:func:`GeckoDriver#getWindowProperties`
   1677 * @param {boolean=} focus
   1678 *     A boolean value which determines whether to focus the window.
   1679 *     Defaults to true.
   1680 */
   1681 GeckoDriver.prototype.setWindowHandle = async function (
   1682  winProperties,
   1683  focus = true
   1684 ) {
   1685  if (!(winProperties.id in this.browsers)) {
   1686    // Initialise Marionette if the current chrome window has not been seen
   1687    // before. Also register the initial tab, if one exists.
   1688    this.addBrowser(winProperties.win);
   1689    this.mainFrame = winProperties.win;
   1690 
   1691    this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
   1692 
   1693    if (!winProperties.hasTabBrowser) {
   1694      this.currentSession.contentBrowsingContext = null;
   1695    } else {
   1696      const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);
   1697 
   1698      // For chrome windows such as a reftest window, `getTabBrowser` is not
   1699      // a tabbrowser, it is the content browser which should be used here.
   1700      const contentBrowser = tabBrowser.tabs
   1701        ? tabBrowser.selectedBrowser
   1702        : tabBrowser;
   1703 
   1704      this.currentSession.contentBrowsingContext =
   1705        contentBrowser.browsingContext;
   1706      this.registerBrowser(contentBrowser);
   1707    }
   1708  } else {
   1709    // Otherwise switch to the known chrome window
   1710    this.curBrowser = this.browsers[winProperties.id];
   1711    this.mainFrame = this.curBrowser.window;
   1712 
   1713    // Activate the tab if it's a content window.
   1714    let tab = null;
   1715    if (winProperties.hasTabBrowser) {
   1716      tab = await this.curBrowser.switchToTab(
   1717        winProperties.tabIndex,
   1718        winProperties.win,
   1719        focus
   1720      );
   1721    }
   1722 
   1723    this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
   1724    this.currentSession.contentBrowsingContext =
   1725      tab?.linkedBrowser.browsingContext;
   1726  }
   1727 
   1728  // Check for an existing dialog for the new window
   1729  this.dialog = lazy.modal.findPrompt(this.curBrowser);
   1730 
   1731  // If there is an open window modal dialog the underlying chrome window
   1732  // cannot be focused.
   1733  if (focus && !this.dialog?.isWindowModal) {
   1734    await this.curBrowser.focusWindow();
   1735  }
   1736 };
   1737 
   1738 /**
   1739 * Set the current browsing context for future commands to the parent
   1740 * of the current browsing context.
   1741 *
   1742 * @throws {NoSuchWindowError}
   1743 *     Browsing context has been discarded.
   1744 * @throws {UnexpectedAlertOpenError}
   1745 *     A modal dialog is open, blocking this operation.
   1746 */
   1747 GeckoDriver.prototype.switchToParentFrame = async function () {
   1748  let browsingContext = this.getBrowsingContext();
   1749  if (browsingContext && !browsingContext.parent) {
   1750    return;
   1751  }
   1752 
   1753  browsingContext = lazy.assert.open(browsingContext?.parent);
   1754 
   1755  this.currentSession.contentBrowsingContext = browsingContext;
   1756 };
   1757 
   1758 /**
   1759 * Switch to a given frame within the current window.
   1760 *
   1761 * @param {object} cmd
   1762 * @param {(string | object)=} cmd.parameters.element
   1763 *     A web element reference of the frame or its element id.
   1764 * @param {number=} cmd.parameters.id
   1765 *     The index of the frame to switch to.
   1766 *     If both element and id are not defined, switch to top-level frame.
   1767 *
   1768 * @throws {NoSuchElementError}
   1769 *     If element represented by reference <var>element</var> is unknown.
   1770 * @throws {NoSuchWindowError}
   1771 *     Browsing context has been discarded.
   1772 * @throws {StaleElementReferenceError}
   1773 *     If element represented by reference <var>element</var> has gone stale.
   1774 * @throws {UnexpectedAlertOpenError}
   1775 *     A modal dialog is open, blocking this operation.
   1776 */
   1777 GeckoDriver.prototype.switchToFrame = async function (cmd) {
   1778  const { element: el, id } = cmd.parameters;
   1779 
   1780  if (typeof id == "number") {
   1781    lazy.assert.unsignedShort(
   1782      id,
   1783      lazy.pprint`Expected "id" to be an unsigned short, got ${id}`
   1784    );
   1785  }
   1786 
   1787  const top = id == null && el == null;
   1788  lazy.assert.open(this.getBrowsingContext({ top }));
   1789  await this._handleUserPrompts();
   1790 
   1791  // Bug 1495063: Elements should be passed as WebReference reference
   1792  let byFrame;
   1793  if (typeof el == "string") {
   1794    byFrame = lazy.WebElement.fromUUID(el).toJSON();
   1795  } else if (el) {
   1796    byFrame = el;
   1797  }
   1798 
   1799  // If the current context changed during the switchToFrame call, attempt to
   1800  // call switchToFrame again until the browsing context remains stable.
   1801  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1786640#c11
   1802  let browsingContext;
   1803  for (let i = 0; i < 5; i++) {
   1804    const currentBrowsingContext = this.currentSession.contentBrowsingContext;
   1805    ({ browsingContext } = await this.getActor({ top }).switchToFrame(
   1806      byFrame || id
   1807    ));
   1808 
   1809    if (currentBrowsingContext == this.currentSession.contentBrowsingContext) {
   1810      break;
   1811    }
   1812  }
   1813 
   1814  this.currentSession.contentBrowsingContext = browsingContext;
   1815 };
   1816 
   1817 GeckoDriver.prototype.getTimeouts = function () {
   1818  return this.currentSession.timeouts;
   1819 };
   1820 
   1821 /**
   1822 * Set timeout for page loading, searching, and scripts.
   1823 *
   1824 * @param {object} cmd
   1825 * @param {Record<string, number>} cmd.parameters
   1826 *     Dictionary of timeout types and their new value, where all timeout
   1827 *     types are optional.
   1828 *
   1829 * @throws {InvalidArgumentError}
   1830 *     If timeout type key is unknown, or the value provided with it is
   1831 *     not an integer.
   1832 */
   1833 GeckoDriver.prototype.setTimeouts = function (cmd) {
   1834  // merge with existing timeouts
   1835  let merged = Object.assign(
   1836    this.currentSession.timeouts.toJSON(),
   1837    cmd.parameters
   1838  );
   1839 
   1840  this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
   1841 };
   1842 
   1843 /**
   1844 * Perform a series of grouped actions at the specified points in time.
   1845 *
   1846 * @param {object} cmd
   1847 * @param {Array<?>} cmd.parameters.actions
   1848 *     Array of objects that each represent an action sequence.
   1849 *
   1850 * @throws {NoSuchElementError}
   1851 *     If an element that is used as part of the action chain is unknown.
   1852 * @throws {NoSuchWindowError}
   1853 *     Browsing context has been discarded.
   1854 * @throws {StaleElementReferenceError}
   1855 *     If an element that is used as part of the action chain has gone stale.
   1856 * @throws {UnexpectedAlertOpenError}
   1857 *     A modal dialog is open, blocking this operation.
   1858 * @throws {UnsupportedOperationError}
   1859 *     Not yet available in current context.
   1860 */
   1861 GeckoDriver.prototype.performActions = async function (cmd) {
   1862  const { actions } = cmd.parameters;
   1863 
   1864  const browsingContext = lazy.assert.open(this.getBrowsingContext());
   1865  await this._handleUserPrompts();
   1866 
   1867  // Bug 1821460: Fetch top-level browsing context.
   1868  const inputState = this._actionsHelper.getInputState(browsingContext);
   1869  const actionsOptions = {
   1870    ...this._actionsHelper.actionsOptions,
   1871    context: browsingContext,
   1872  };
   1873 
   1874  const actionChain = await lazy.actions.Chain.fromJSON(
   1875    inputState,
   1876    actions,
   1877    actionsOptions
   1878  );
   1879 
   1880  // Enqueue to serialize access to input state.
   1881  await inputState.enqueueAction(() =>
   1882    actionChain.dispatch(inputState, actionsOptions)
   1883  );
   1884 
   1885  // Process async follow-up tasks in content before the reply is sent.
   1886  await this._actionsHelper.finalizeAction(browsingContext);
   1887 };
   1888 
   1889 /**
   1890 * Release all the keys and pointer buttons that are currently depressed.
   1891 *
   1892 * @throws {NoSuchWindowError}
   1893 *     Browsing context has been discarded.
   1894 * @throws {UnexpectedAlertOpenError}
   1895 *     A modal dialog is open, blocking this operation.
   1896 * @throws {UnsupportedOperationError}
   1897 *     Not available in current context.
   1898 */
   1899 GeckoDriver.prototype.releaseActions = async function () {
   1900  const browsingContext = lazy.assert.open(this.getBrowsingContext());
   1901  await this._handleUserPrompts();
   1902 
   1903  // Bug 1821460: Fetch top-level browsing context.
   1904  const inputState = this._actionsHelper.getInputState(browsingContext);
   1905  const actionsOptions = {
   1906    ...this._actionsHelper.actionsOptions,
   1907    context: browsingContext,
   1908  };
   1909 
   1910  // Enqueue to serialize access to input state.
   1911  await inputState.enqueueAction(() => {
   1912    const undoActions = inputState.inputCancelList.reverse();
   1913    return undoActions.dispatch(inputState, actionsOptions);
   1914  });
   1915 
   1916  this._actionsHelper.resetInputState(browsingContext);
   1917 
   1918  // Process async follow-up tasks in content before the reply is sent.
   1919  await this._actionsHelper.finalizeAction(browsingContext);
   1920 };
   1921 
   1922 /**
   1923 * Find an element using the indicated search strategy.
   1924 *
   1925 * @param {object} cmd
   1926 * @param {string=} cmd.parameters.element
   1927 *     Web element reference ID to the element that will be used as start node.
   1928 * @param {string} cmd.parameters.using
   1929 *     Indicates which search method to use.
   1930 * @param {string} cmd.parameters.value
   1931 *     Value the client is looking for.
   1932 *
   1933 * @returns {WebElement}
   1934 *     Return the found element.
   1935 *
   1936 * @throws {NoSuchElementError}
   1937 *     If element represented by reference <var>element</var> is unknown.
   1938 * @throws {NoSuchWindowError}
   1939 *     Browsing context has been discarded.
   1940 * @throws {StaleElementReferenceError}
   1941 *     If element represented by reference <var>element</var> has gone stale.
   1942 * @throws {UnexpectedAlertOpenError}
   1943 *     A modal dialog is open, blocking this operation.
   1944 */
   1945 GeckoDriver.prototype.findElement = async function (cmd) {
   1946  const { element: el, using, value } = cmd.parameters;
   1947 
   1948  if (!lazy.supportedStrategies.has(using)) {
   1949    throw new lazy.error.InvalidSelectorError(
   1950      `Strategy not supported: ${using}`
   1951    );
   1952  }
   1953 
   1954  lazy.assert.defined(value);
   1955  lazy.assert.open(this.getBrowsingContext());
   1956  await this._handleUserPrompts();
   1957 
   1958  let startNode;
   1959  if (typeof el != "undefined") {
   1960    startNode = lazy.WebElement.fromUUID(el).toJSON();
   1961  }
   1962 
   1963  let opts = {
   1964    startNode,
   1965    timeout: this.currentSession.timeouts.implicit,
   1966    all: false,
   1967  };
   1968 
   1969  return this.getActor().findElement(using, value, opts);
   1970 };
   1971 
   1972 /**
   1973 * Find an element within shadow root using the indicated search strategy.
   1974 *
   1975 * @param {object} cmd
   1976 * @param {string} cmd.parameters.shadowRoot
   1977 *     Shadow root reference ID.
   1978 * @param {string} cmd.parameters.using
   1979 *     Indicates which search method to use.
   1980 * @param {string} cmd.parameters.value
   1981 *     Value the client is looking for.
   1982 *
   1983 * @returns {WebElement}
   1984 *     Return the found element.
   1985 *
   1986 * @throws {DetachedShadowRootError}
   1987 *     If shadow root represented by reference <var>id</var> is
   1988 *     no longer attached to the DOM.
   1989 * @throws {NoSuchElementError}
   1990 *     If the element which is looked for with <var>value</var> was
   1991 *     not found.
   1992 * @throws {NoSuchShadowRoot}
   1993 *     If shadow root represented by reference <var>shadowRoot</var> is unknown.
   1994 * @throws {NoSuchWindowError}
   1995 *     Browsing context has been discarded.
   1996 * @throws {UnexpectedAlertOpenError}
   1997 *     A modal dialog is open, blocking this operation.
   1998 */
   1999 GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) {
   2000  const { shadowRoot, using, value } = cmd.parameters;
   2001 
   2002  if (!lazy.supportedStrategies.has(using)) {
   2003    throw new lazy.error.InvalidSelectorError(
   2004      `Strategy not supported: ${using}`
   2005    );
   2006  }
   2007 
   2008  lazy.assert.defined(value);
   2009  lazy.assert.open(this.getBrowsingContext());
   2010  await this._handleUserPrompts();
   2011 
   2012  const opts = {
   2013    all: false,
   2014    startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
   2015    timeout: this.currentSession.timeouts.implicit,
   2016  };
   2017 
   2018  return this.getActor().findElement(using, value, opts);
   2019 };
   2020 
   2021 /**
   2022 * Find elements using the indicated search strategy.
   2023 *
   2024 * @param {object} cmd
   2025 * @param {string=} cmd.parameters.element
   2026 *     Web element reference ID to the element that will be used as start node.
   2027 * @param {string} cmd.parameters.using
   2028 *     Indicates which search method to use.
   2029 * @param {string} cmd.parameters.value
   2030 *     Value the client is looking for.
   2031 *
   2032 * @returns {Array<WebElement>}
   2033 *     Return the array of found elements.
   2034 *
   2035 * @throws {NoSuchElementError}
   2036 *     If element represented by reference <var>element</var> is unknown.
   2037 * @throws {NoSuchWindowError}
   2038 *     Browsing context has been discarded.
   2039 * @throws {StaleElementReferenceError}
   2040 *     If element represented by reference <var>element</var> has gone stale.
   2041 * @throws {UnexpectedAlertOpenError}
   2042 *     A modal dialog is open, blocking this operation.
   2043 */
   2044 GeckoDriver.prototype.findElements = async function (cmd) {
   2045  const { element: el, using, value } = cmd.parameters;
   2046 
   2047  if (!lazy.supportedStrategies.has(using)) {
   2048    throw new lazy.error.InvalidSelectorError(
   2049      `Strategy not supported: ${using}`
   2050    );
   2051  }
   2052 
   2053  lazy.assert.defined(value);
   2054  lazy.assert.open(this.getBrowsingContext());
   2055  await this._handleUserPrompts();
   2056 
   2057  let startNode;
   2058  if (typeof el != "undefined") {
   2059    startNode = lazy.WebElement.fromUUID(el).toJSON();
   2060  }
   2061 
   2062  let opts = {
   2063    startNode,
   2064    timeout: this.currentSession.timeouts.implicit,
   2065    all: true,
   2066  };
   2067 
   2068  return this.getActor().findElements(using, value, opts);
   2069 };
   2070 
   2071 /**
   2072 * Find elements within shadow root using the indicated search strategy.
   2073 *
   2074 * @param {object} cmd
   2075 * @param {string} cmd.parameters.shadowRoot
   2076 *     Shadow root reference ID.
   2077 * @param {string} cmd.parameters.using
   2078 *     Indicates which search method to use.
   2079 * @param {string} cmd.parameters.value
   2080 *     Value the client is looking for.
   2081 *
   2082 * @returns {Array<WebElement>}
   2083 *     Return the array of found elements.
   2084 *
   2085 * @throws {DetachedShadowRootError}
   2086 *     If shadow root represented by reference <var>id</var> is
   2087 *     no longer attached to the DOM.
   2088 * @throws {NoSuchShadowRoot}
   2089 *     If shadow root represented by reference <var>shadowRoot</var> is unknown.
   2090 * @throws {NoSuchWindowError}
   2091 *     Browsing context has been discarded.
   2092 * @throws {UnexpectedAlertOpenError}
   2093 *     A modal dialog is open, blocking this operation.
   2094 */
   2095 GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) {
   2096  const { shadowRoot, using, value } = cmd.parameters;
   2097 
   2098  if (!lazy.supportedStrategies.has(using)) {
   2099    throw new lazy.error.InvalidSelectorError(
   2100      `Strategy not supported: ${using}`
   2101    );
   2102  }
   2103 
   2104  lazy.assert.defined(value);
   2105  lazy.assert.open(this.getBrowsingContext());
   2106  await this._handleUserPrompts();
   2107 
   2108  const opts = {
   2109    all: true,
   2110    startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
   2111    timeout: this.currentSession.timeouts.implicit,
   2112  };
   2113 
   2114  return this.getActor().findElements(using, value, opts);
   2115 };
   2116 
   2117 /**
   2118 * Return the shadow root of an element in the document.
   2119 *
   2120 * @param {object} cmd
   2121 * @param {id} cmd.parameters.id
   2122 *     A web element id reference.
   2123 * @returns {ShadowRoot}
   2124 *     ShadowRoot of the element.
   2125 *
   2126 * @throws {InvalidArgumentError}
   2127 *     If element <var>id</var> is not a string.
   2128 * @throws {NoSuchElementError}
   2129 *     If element represented by reference <var>id</var> is unknown.
   2130 * @throws {NoSuchShadowRoot}
   2131 *     Element does not have a shadow root attached.
   2132 * @throws {NoSuchWindowError}
   2133 *     Browsing context has been discarded.
   2134 * @throws {StaleElementReferenceError}
   2135 *     If element represented by reference <var>id</var> has gone stale.
   2136 * @throws {UnexpectedAlertOpenError}
   2137 *     A modal dialog is open, blocking this operation.
   2138 * @throws {UnsupportedOperationError}
   2139 *     Not available in chrome current context.
   2140 */
   2141 GeckoDriver.prototype.getShadowRoot = async function (cmd) {
   2142  // Bug 1743541: Add support for chrome scope.
   2143  lazy.assert.content(this.context);
   2144  lazy.assert.open(this.getBrowsingContext());
   2145  await this._handleUserPrompts();
   2146 
   2147  let id = lazy.assert.string(
   2148    cmd.parameters.id,
   2149    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2150  );
   2151  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2152 
   2153  return this.getActor().getShadowRoot(webEl);
   2154 };
   2155 
   2156 /**
   2157 * Return the active element in the document.
   2158 *
   2159 * @returns {WebReference}
   2160 *     Active element of the current browsing context's document
   2161 *     element, if the document element is non-null.
   2162 *
   2163 * @throws {NoSuchElementError}
   2164 *     If the document does not have an active element, i.e. if
   2165 *     its document element has been deleted.
   2166 * @throws {NoSuchWindowError}
   2167 *     Browsing context has been discarded.
   2168 * @throws {UnexpectedAlertOpenError}
   2169 *     A modal dialog is open, blocking this operation.
   2170 * @throws {UnsupportedOperationError}
   2171 *     Not available in chrome context.
   2172 */
   2173 GeckoDriver.prototype.getActiveElement = async function () {
   2174  lazy.assert.content(this.context);
   2175  lazy.assert.open(this.getBrowsingContext());
   2176  await this._handleUserPrompts();
   2177 
   2178  return this.getActor().getActiveElement();
   2179 };
   2180 
   2181 /**
   2182 * Send click event to element.
   2183 *
   2184 * @param {object} cmd
   2185 * @param {string} cmd.parameters.id
   2186 *     Reference ID to the element that will be clicked.
   2187 *
   2188 * @throws {InvalidArgumentError}
   2189 *     If element <var>id</var> is not a string.
   2190 * @throws {NoSuchElementError}
   2191 *     If element represented by reference <var>id</var> is unknown.
   2192 * @throws {NoSuchWindowError}
   2193 *     Browsing context has been discarded.
   2194 * @throws {StaleElementReferenceError}
   2195 *     If element represented by reference <var>id</var> has gone stale.
   2196 * @throws {UnexpectedAlertOpenError}
   2197 *     A modal dialog is open, blocking this operation.
   2198 */
   2199 GeckoDriver.prototype.clickElement = async function (cmd) {
   2200  const browsingContext = lazy.assert.open(this.getBrowsingContext());
   2201  await this._handleUserPrompts();
   2202 
   2203  let id = lazy.assert.string(
   2204    cmd.parameters.id,
   2205    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2206  );
   2207  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2208 
   2209  const actor = this.getActor();
   2210 
   2211  const loadEventExpected = lazy.navigate.isLoadEventExpected(
   2212    this._getCurrentURL(),
   2213    {
   2214      browsingContext,
   2215      target: await actor.getElementAttribute(webEl, "target"),
   2216    }
   2217  );
   2218 
   2219  await lazy.navigate.waitForNavigationCompleted(
   2220    this,
   2221    () => actor.clickElement(webEl, this.currentSession.capabilities),
   2222    {
   2223      loadEventExpected,
   2224      // The click might trigger a navigation, so don't count on it.
   2225      requireBeforeUnload: false,
   2226    }
   2227  );
   2228 };
   2229 
   2230 /**
   2231 * Get a given attribute of an element.
   2232 *
   2233 * @param {object} cmd
   2234 * @param {string} cmd.parameters.id
   2235 *     Web element reference ID to the element that will be inspected.
   2236 * @param {string} cmd.parameters.name
   2237 *     Name of the attribute which value to retrieve.
   2238 *
   2239 * @returns {string}
   2240 *     Value of the attribute.
   2241 *
   2242 * @throws {InvalidArgumentError}
   2243 *     If <var>id</var> or <var>name</var> are not strings.
   2244 * @throws {NoSuchElementError}
   2245 *     If element represented by reference <var>id</var> is unknown.
   2246 * @throws {NoSuchWindowError}
   2247 *     Browsing context has been discarded.
   2248 * @throws {StaleElementReferenceError}
   2249 *     If element represented by reference <var>id</var> has gone stale.
   2250 * @throws {UnexpectedAlertOpenError}
   2251 *     A modal dialog is open, blocking this operation.
   2252 */
   2253 GeckoDriver.prototype.getElementAttribute = async function (cmd) {
   2254  lazy.assert.open(this.getBrowsingContext());
   2255  await this._handleUserPrompts();
   2256 
   2257  const id = lazy.assert.string(
   2258    cmd.parameters.id,
   2259    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2260  );
   2261  const name = lazy.assert.string(
   2262    cmd.parameters.name,
   2263    lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
   2264  );
   2265  const webEl = lazy.WebElement.fromUUID(id).toJSON();
   2266 
   2267  return this.getActor().getElementAttribute(webEl, name);
   2268 };
   2269 
   2270 /**
   2271 * Returns the value of a property associated with given element.
   2272 *
   2273 * @param {object} cmd
   2274 * @param {string} cmd.parameters.id
   2275 *     Web element reference ID to the element that will be inspected.
   2276 * @param {string} cmd.parameters.name
   2277 *     Name of the property which value to retrieve.
   2278 *
   2279 * @returns {string}
   2280 *     Value of the property.
   2281 *
   2282 * @throws {InvalidArgumentError}
   2283 *     If <var>id</var> or <var>name</var> are not strings.
   2284 * @throws {NoSuchElementError}
   2285 *     If element represented by reference <var>id</var> is unknown.
   2286 * @throws {NoSuchWindowError}
   2287 *     Browsing context has been discarded.
   2288 * @throws {StaleElementReferenceError}
   2289 *     If element represented by reference <var>id</var> has gone stale.
   2290 * @throws {UnexpectedAlertOpenError}
   2291 *     A modal dialog is open, blocking this operation.
   2292 */
   2293 GeckoDriver.prototype.getElementProperty = async function (cmd) {
   2294  lazy.assert.open(this.getBrowsingContext());
   2295  await this._handleUserPrompts();
   2296 
   2297  const id = lazy.assert.string(
   2298    cmd.parameters.id,
   2299    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2300  );
   2301  const name = lazy.assert.string(
   2302    cmd.parameters.name,
   2303    lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
   2304  );
   2305  const webEl = lazy.WebElement.fromUUID(id).toJSON();
   2306 
   2307  return this.getActor().getElementProperty(webEl, name);
   2308 };
   2309 
   2310 /**
   2311 * Get the text of an element, if any.  Includes the text of all child
   2312 * elements.
   2313 *
   2314 * @param {object} cmd
   2315 * @param {string} cmd.parameters.id
   2316 *     Reference ID to the element that will be inspected.
   2317 *
   2318 * @returns {string}
   2319 *     Element's text "as rendered".
   2320 *
   2321 * @throws {InvalidArgumentError}
   2322 *     If <var>id</var> is not a string.
   2323 * @throws {NoSuchElementError}
   2324 *     If element represented by reference <var>id</var> is unknown.
   2325 * @throws {NoSuchWindowError}
   2326 *     Browsing context has been discarded.
   2327 * @throws {StaleElementReferenceError}
   2328 *     If element represented by reference <var>id</var> has gone stale.
   2329 * @throws {UnexpectedAlertOpenError}
   2330 *     A modal dialog is open, blocking this operation.
   2331 */
   2332 GeckoDriver.prototype.getElementText = async function (cmd) {
   2333  lazy.assert.open(this.getBrowsingContext());
   2334  await this._handleUserPrompts();
   2335 
   2336  let id = lazy.assert.string(
   2337    cmd.parameters.id,
   2338    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2339  );
   2340  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2341 
   2342  return this.getActor().getElementText(webEl);
   2343 };
   2344 
   2345 /**
   2346 * Get the tag name of the element.
   2347 *
   2348 * @param {object} cmd
   2349 * @param {string} cmd.parameters.id
   2350 *     Reference ID to the element that will be inspected.
   2351 *
   2352 * @returns {string}
   2353 *     Local tag name of element.
   2354 *
   2355 * @throws {InvalidArgumentError}
   2356 *     If <var>id</var> is not a string.
   2357 * @throws {NoSuchElementError}
   2358 *     If element represented by reference <var>id</var> is unknown.
   2359 * @throws {NoSuchWindowError}
   2360 *     Browsing context has been discarded.
   2361 * @throws {StaleElementReferenceError}
   2362 *     If element represented by reference <var>id</var> has gone stale.
   2363 * @throws {UnexpectedAlertOpenError}
   2364 *     A modal dialog is open, blocking this operation.
   2365 */
   2366 GeckoDriver.prototype.getElementTagName = async function (cmd) {
   2367  lazy.assert.open(this.getBrowsingContext());
   2368  await this._handleUserPrompts();
   2369 
   2370  let id = lazy.assert.string(
   2371    cmd.parameters.id,
   2372    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2373  );
   2374  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2375 
   2376  return this.getActor().getElementTagName(webEl);
   2377 };
   2378 
   2379 /**
   2380 * Check if element is displayed.
   2381 *
   2382 * @param {object} cmd
   2383 * @param {string} cmd.parameters.id
   2384 *     Reference ID to the element that will be inspected.
   2385 *
   2386 * @returns {boolean}
   2387 *     True if displayed, false otherwise.
   2388 *
   2389 * @throws {InvalidArgumentError}
   2390 *     If <var>id</var> is not a string.
   2391 * @throws {NoSuchElementError}
   2392 *     If element represented by reference <var>id</var> is unknown.
   2393 * @throws {NoSuchWindowError}
   2394 *     Browsing context has been discarded.
   2395 * @throws {UnexpectedAlertOpenError}
   2396 *     A modal dialog is open, blocking this operation.
   2397 */
   2398 GeckoDriver.prototype.isElementDisplayed = async function (cmd) {
   2399  lazy.assert.open(this.getBrowsingContext());
   2400  await this._handleUserPrompts();
   2401 
   2402  let id = lazy.assert.string(
   2403    cmd.parameters.id,
   2404    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2405  );
   2406  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2407 
   2408  return this.getActor().isElementDisplayed(
   2409    webEl,
   2410    this.currentSession.capabilities
   2411  );
   2412 };
   2413 
   2414 /**
   2415 * Return the property of the computed style of an element.
   2416 *
   2417 * @param {object} cmd
   2418 * @param {string} cmd.parameters.id
   2419 *     Reference ID to the element that will be checked.
   2420 * @param {string} cmd.parameters.propertyName
   2421 *     CSS rule that is being requested.
   2422 *
   2423 * @returns {string}
   2424 *     Value of |propertyName|.
   2425 *
   2426 * @throws {InvalidArgumentError}
   2427 *     If <var>id</var> or <var>propertyName</var> are not strings.
   2428 * @throws {NoSuchElementError}
   2429 *     If element represented by reference <var>id</var> is unknown.
   2430 * @throws {NoSuchWindowError}
   2431 *     Browsing context has been discarded.
   2432 * @throws {StaleElementReferenceError}
   2433 *     If element represented by reference <var>id</var> has gone stale.
   2434 * @throws {UnexpectedAlertOpenError}
   2435 *     A modal dialog is open, blocking this operation.
   2436 */
   2437 GeckoDriver.prototype.getElementValueOfCssProperty = async function (cmd) {
   2438  lazy.assert.open(this.getBrowsingContext());
   2439  await this._handleUserPrompts();
   2440 
   2441  let id = lazy.assert.string(
   2442    cmd.parameters.id,
   2443    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2444  );
   2445  let prop = lazy.assert.string(
   2446    cmd.parameters.propertyName,
   2447    lazy.pprint`Expected "propertyName" to be a string, got ${cmd.parameters.propertyName}`
   2448  );
   2449  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2450 
   2451  return this.getActor().getElementValueOfCssProperty(webEl, prop);
   2452 };
   2453 
   2454 /**
   2455 * Check if element is enabled.
   2456 *
   2457 * @param {object} cmd
   2458 * @param {string} cmd.parameters.id
   2459 *     Reference ID to the element that will be checked.
   2460 *
   2461 * @returns {boolean}
   2462 *     True if enabled, false if disabled.
   2463 *
   2464 * @throws {InvalidArgumentError}
   2465 *     If <var>id</var> is not a string.
   2466 * @throws {NoSuchElementError}
   2467 *     If element represented by reference <var>id</var> is unknown.
   2468 * @throws {NoSuchWindowError}
   2469 *     Browsing context has been discarded.
   2470 * @throws {StaleElementReferenceError}
   2471 *     If element represented by reference <var>id</var> has gone stale.
   2472 * @throws {UnexpectedAlertOpenError}
   2473 *     A modal dialog is open, blocking this operation.
   2474 */
   2475 GeckoDriver.prototype.isElementEnabled = async function (cmd) {
   2476  lazy.assert.open(this.getBrowsingContext());
   2477  await this._handleUserPrompts();
   2478 
   2479  let id = lazy.assert.string(
   2480    cmd.parameters.id,
   2481    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2482  );
   2483  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2484 
   2485  return this.getActor().isElementEnabled(
   2486    webEl,
   2487    this.currentSession.capabilities
   2488  );
   2489 };
   2490 
   2491 /**
   2492 * Check if element is selected.
   2493 *
   2494 * @param {object} cmd
   2495 * @param {string} cmd.parameters.id
   2496 *     Reference ID to the element that will be checked.
   2497 *
   2498 * @returns {boolean}
   2499 *     True if selected, false if unselected.
   2500 *
   2501 * @throws {InvalidArgumentError}
   2502 *     If <var>id</var> is not a string.
   2503 * @throws {NoSuchElementError}
   2504 *     If element represented by reference <var>id</var> is unknown.
   2505 * @throws {NoSuchWindowError}
   2506 *     Browsing context has been discarded.
   2507 * @throws {UnexpectedAlertOpenError}
   2508 *     A modal dialog is open, blocking this operation.
   2509 */
   2510 GeckoDriver.prototype.isElementSelected = async function (cmd) {
   2511  lazy.assert.open(this.getBrowsingContext());
   2512  await this._handleUserPrompts();
   2513 
   2514  let id = lazy.assert.string(
   2515    cmd.parameters.id,
   2516    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2517  );
   2518  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2519 
   2520  return this.getActor().isElementSelected(
   2521    webEl,
   2522    this.currentSession.capabilities
   2523  );
   2524 };
   2525 
   2526 /**
   2527 * @throws {InvalidArgumentError}
   2528 *     If <var>id</var> is not a string.
   2529 * @throws {NoSuchElementError}
   2530 *     If element represented by reference <var>id</var> is unknown.
   2531 * @throws {NoSuchWindowError}
   2532 *     Browsing context has been discarded.
   2533 * @throws {StaleElementReferenceError}
   2534 *     If element represented by reference <var>id</var> has gone stale.
   2535 * @throws {UnexpectedAlertOpenError}
   2536 *     A modal dialog is open, blocking this operation.
   2537 */
   2538 GeckoDriver.prototype.getElementRect = async function (cmd) {
   2539  lazy.assert.open(this.getBrowsingContext());
   2540  await this._handleUserPrompts();
   2541 
   2542  let id = lazy.assert.string(
   2543    cmd.parameters.id,
   2544    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2545  );
   2546  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2547 
   2548  return this.getActor().getElementRect(webEl);
   2549 };
   2550 
   2551 /**
   2552 * Send key presses to element after focusing on it.
   2553 *
   2554 * @param {object} cmd
   2555 * @param {string} cmd.parameters.id
   2556 *     Reference ID to the element that will be checked.
   2557 * @param {string} cmd.parameters.text
   2558 *     Value to send to the element.
   2559 *
   2560 * @throws {InvalidArgumentError}
   2561 *     If <var>id</var> or <var>text</var> are not strings.
   2562 * @throws {NoSuchElementError}
   2563 *     If element represented by reference <var>id</var> is unknown.
   2564 * @throws {NoSuchWindowError}
   2565 *     Browsing context has been discarded.
   2566 * @throws {StaleElementReferenceError}
   2567 *     If element represented by reference <var>id</var> has gone stale.
   2568 * @throws {UnexpectedAlertOpenError}
   2569 *     A modal dialog is open, blocking this operation.
   2570 */
   2571 GeckoDriver.prototype.sendKeysToElement = async function (cmd) {
   2572  lazy.assert.open(this.getBrowsingContext());
   2573  await this._handleUserPrompts();
   2574 
   2575  let id = lazy.assert.string(
   2576    cmd.parameters.id,
   2577    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2578  );
   2579  let text = lazy.assert.string(
   2580    cmd.parameters.text,
   2581    lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}`
   2582  );
   2583  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2584 
   2585  return this.getActor().sendKeysToElement(
   2586    webEl,
   2587    text,
   2588    this.currentSession.capabilities
   2589  );
   2590 };
   2591 
   2592 /**
   2593 * Clear the text of an element.
   2594 *
   2595 * @param {object} cmd
   2596 * @param {string} cmd.parameters.id
   2597 *     Reference ID to the element that will be cleared.
   2598 *
   2599 * @throws {InvalidArgumentError}
   2600 *     If <var>id</var> is not a string.
   2601 * @throws {NoSuchElementError}
   2602 *     If element represented by reference <var>id</var> is unknown.
   2603 * @throws {NoSuchWindowError}
   2604 *     Browsing context has been discarded.
   2605 * @throws {StaleElementReferenceError}
   2606 *     If element represented by reference <var>id</var> has gone stale.
   2607 * @throws {UnexpectedAlertOpenError}
   2608 *     A modal dialog is open, blocking this operation.
   2609 */
   2610 GeckoDriver.prototype.clearElement = async function (cmd) {
   2611  lazy.assert.open(this.getBrowsingContext());
   2612  await this._handleUserPrompts();
   2613 
   2614  let id = lazy.assert.string(
   2615    cmd.parameters.id,
   2616    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   2617  );
   2618  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   2619 
   2620  await this.getActor().clearElement(webEl);
   2621 };
   2622 
   2623 /**
   2624 * Add a single cookie to the cookie store associated with the active
   2625 * document's address.
   2626 *
   2627 * @param {object} cmd
   2628 * @param {Map.<string, (string|number|boolean)>} cmd.parameters.cookie
   2629 *     Cookie object.
   2630 *
   2631 * @throws {InvalidCookieDomainError}
   2632 *     If <var>cookie</var> is for a different domain than the active
   2633 *     document's host.
   2634 * @throws {NoSuchWindowError}
   2635 *     Bbrowsing context has been discarded.
   2636 * @throws {UnexpectedAlertOpenError}
   2637 *     A modal dialog is open, blocking this operation.
   2638 * @throws {UnsupportedOperationError}
   2639 *     Not available in current context.
   2640 */
   2641 GeckoDriver.prototype.addCookie = async function (cmd) {
   2642  lazy.assert.content(this.context);
   2643  lazy.assert.open(this.getBrowsingContext());
   2644  await this._handleUserPrompts();
   2645 
   2646  let { protocol, hostname } = this._getCurrentURL({ top: false });
   2647 
   2648  const networkSchemes = ["http:", "https:"];
   2649  if (!networkSchemes.includes(protocol)) {
   2650    throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
   2651  }
   2652 
   2653  let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);
   2654 
   2655  lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
   2656 };
   2657 
   2658 /**
   2659 * Get all the cookies for the current domain.
   2660 *
   2661 * This is the equivalent of calling <code>document.cookie</code> and
   2662 * parsing the result.
   2663 *
   2664 * @throws {NoSuchWindowError}
   2665 *     Browsing context has been discarded.
   2666 * @throws {UnexpectedAlertOpenError}
   2667 *     A modal dialog is open, blocking this operation.
   2668 * @throws {UnsupportedOperationError}
   2669 *     Not available in current context.
   2670 */
   2671 GeckoDriver.prototype.getCookies = async function () {
   2672  lazy.assert.content(this.context);
   2673  lazy.assert.open(this.getBrowsingContext());
   2674  await this._handleUserPrompts();
   2675 
   2676  let { hostname, pathname } = this._getCurrentURL({ top: false });
   2677  return [...lazy.cookie.iter(hostname, this.getBrowsingContext(), pathname)];
   2678 };
   2679 
   2680 /**
   2681 * Delete all cookies that are visible to a document.
   2682 *
   2683 * @throws {NoSuchWindowError}
   2684 *     Browsing context has been discarded.
   2685 * @throws {UnexpectedAlertOpenError}
   2686 *     A modal dialog is open, blocking this operation.
   2687 * @throws {UnsupportedOperationError}
   2688 *     Not available in current context.
   2689 */
   2690 GeckoDriver.prototype.deleteAllCookies = async function () {
   2691  lazy.assert.content(this.context);
   2692  lazy.assert.open(this.getBrowsingContext());
   2693  await this._handleUserPrompts();
   2694 
   2695  let { hostname, pathname } = this._getCurrentURL({ top: false });
   2696  for (let toDelete of lazy.cookie.iter(
   2697    hostname,
   2698    this.getBrowsingContext(),
   2699    pathname
   2700  )) {
   2701    lazy.cookie.remove(toDelete);
   2702  }
   2703 };
   2704 
   2705 /**
   2706 * Delete a cookie by name.
   2707 *
   2708 * @throws {NoSuchWindowError}
   2709 *     Browsing context has been discarded.
   2710 * @throws {UnexpectedAlertOpenError}
   2711 *     A modal dialog is open, blocking this operation.
   2712 * @throws {UnsupportedOperationError}
   2713 *     Not available in current context.
   2714 */
   2715 GeckoDriver.prototype.deleteCookie = async function (cmd) {
   2716  lazy.assert.content(this.context);
   2717  lazy.assert.open(this.getBrowsingContext());
   2718  await this._handleUserPrompts();
   2719 
   2720  let { hostname, pathname } = this._getCurrentURL({ top: false });
   2721  let name = lazy.assert.string(
   2722    cmd.parameters.name,
   2723    lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
   2724  );
   2725  for (let c of lazy.cookie.iter(
   2726    hostname,
   2727    this.getBrowsingContext(),
   2728    pathname
   2729  )) {
   2730    if (c.name === name) {
   2731      lazy.cookie.remove(c);
   2732    }
   2733  }
   2734 };
   2735 
   2736 /**
   2737 * Open a new top-level browsing context.
   2738 *
   2739 * @param {object} cmd
   2740 * @param {string=} cmd.parameters.type
   2741 *     Optional type of the new top-level browsing context. Can be one of
   2742 *     `tab` or `window`. Defaults to `tab`.
   2743 * @param {boolean=} cmd.parameters.focus
   2744 *     Optional flag if the new top-level browsing context should be opened
   2745 *     in foreground (focused) or background (not focused). Defaults to false.
   2746 * @param {boolean=} cmd.parameters.private
   2747 *     Optional flag, which gets only evaluated for type `window`. True if the
   2748 *     new top-level browsing context should be a private window.
   2749 *     Defaults to false.
   2750 *
   2751 * @returns {Record<string, string>}
   2752 *     Handle and type of the new browsing context.
   2753 *
   2754 * @throws {NoSuchWindowError}
   2755 *     Top-level browsing context has been discarded.
   2756 * @throws {UnexpectedAlertOpenError}
   2757 *     A modal dialog is open, blocking this operation.
   2758 */
   2759 GeckoDriver.prototype.newWindow = async function (cmd) {
   2760  lazy.assert.open(this.getBrowsingContext({ top: true }));
   2761  await this._handleUserPrompts();
   2762 
   2763  let focus = false;
   2764  if (typeof cmd.parameters.focus != "undefined") {
   2765    focus = lazy.assert.boolean(
   2766      cmd.parameters.focus,
   2767      lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
   2768    );
   2769  }
   2770 
   2771  let isPrivate = false;
   2772  if (typeof cmd.parameters.private != "undefined") {
   2773    isPrivate = lazy.assert.boolean(
   2774      cmd.parameters.private,
   2775      lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
   2776    );
   2777  }
   2778 
   2779  let type;
   2780  if (typeof cmd.parameters.type != "undefined") {
   2781    type = lazy.assert.string(
   2782      cmd.parameters.type,
   2783      lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
   2784    );
   2785  }
   2786 
   2787  // If an invalid or no type has been specified default to a tab.
   2788  // On Android always use a new tab instead because the application has a
   2789  // single window only.
   2790  if (
   2791    typeof type == "undefined" ||
   2792    !["tab", "window"].includes(type) ||
   2793    lazy.AppInfo.isAndroid
   2794  ) {
   2795    if (lazy.TabManager.supportsTabs()) {
   2796      type = "tab";
   2797    } else if (lazy.windowManager.supportsWindows()) {
   2798      type = "window";
   2799    } else {
   2800      throw new lazy.error.UnsupportedOperationError(
   2801        `Not supported in ${lazy.AppInfo.name}`
   2802      );
   2803    }
   2804  }
   2805 
   2806  let contentBrowser;
   2807 
   2808  switch (type) {
   2809    case "window": {
   2810      if (lazy.windowManager.supportsWindows()) {
   2811        let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
   2812        contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
   2813      } else {
   2814        throw new lazy.error.UnsupportedOperationError(
   2815          `Not supported in ${lazy.AppInfo.name}`
   2816        );
   2817      }
   2818      break;
   2819    }
   2820    default: {
   2821      // To not fail if a new type gets added in the future, make opening
   2822      // a new tab the default action.
   2823      if (lazy.TabManager.supportsTabs()) {
   2824        let tab = await this.curBrowser.openTab(focus);
   2825        contentBrowser = lazy.TabManager.getBrowserForTab(tab);
   2826      } else {
   2827        throw new lazy.error.UnsupportedOperationError(
   2828          `Not supported in ${lazy.AppInfo.name}`
   2829        );
   2830      }
   2831    }
   2832  }
   2833 
   2834  // Actors need the new window to be loaded to safely execute queries.
   2835  // Wait until the initial page load has been finished.
   2836  await lazy.waitForInitialNavigationCompleted(
   2837    contentBrowser.browsingContext.webProgress,
   2838    {
   2839      unloadTimeout: 5000,
   2840    }
   2841  );
   2842 
   2843  const id = lazy.NavigableManager.getIdForBrowser(contentBrowser);
   2844 
   2845  return { handle: id.toString(), type };
   2846 };
   2847 
   2848 /**
   2849 * Close the currently selected tab/window.
   2850 *
   2851 * With multiple open tabs present the currently selected tab will
   2852 * be closed.  Otherwise the window itself will be closed. If it is the
   2853 * last window currently open, the window will not be closed to prevent
   2854 * a shutdown of the application. Instead the returned list of window
   2855 * handles is empty.
   2856 *
   2857 * @returns {Array.<string>}
   2858 *     Unique window handles of remaining windows.
   2859 *
   2860 * @throws {NoSuchWindowError}
   2861 *     Top-level browsing context has been discarded.
   2862 * @throws {UnexpectedAlertOpenError}
   2863 *     A modal dialog is open, blocking this operation.
   2864 */
   2865 GeckoDriver.prototype.close = async function () {
   2866  lazy.assert.open(
   2867    this.getBrowsingContext({ context: lazy.Context.Content, top: true })
   2868  );
   2869  await this._handleUserPrompts();
   2870 
   2871  // If there is only one window left, do not close unless windowless mode is
   2872  // enabled. Instead return a faked empty array of window handles.
   2873  // This will instruct geckodriver to terminate the application.
   2874  if (
   2875    lazy.TabManager.getTabCount() === 1 &&
   2876    !this.currentSession.capabilities.get("moz:windowless")
   2877  ) {
   2878    return [];
   2879  }
   2880 
   2881  await this.curBrowser.closeTab();
   2882  this.currentSession.contentBrowsingContext = null;
   2883 
   2884  return lazy.TabManager.getBrowsers({ unloaded: true }).map(browser =>
   2885    lazy.NavigableManager.getIdForBrowser(browser)
   2886  );
   2887 };
   2888 
   2889 /**
   2890 * Close the currently selected chrome window.
   2891 *
   2892 * If it is the last window currently open, the chrome window will not be
   2893 * closed to prevent a shutdown of the application. Instead the returned
   2894 * list of chrome window handles is empty.
   2895 *
   2896 * @returns {Array.<string>}
   2897 *     Unique chrome window handles of remaining chrome windows.
   2898 *
   2899 * @throws {NoSuchWindowError}
   2900 *     Top-level browsing context has been discarded.
   2901 */
   2902 GeckoDriver.prototype.closeChromeWindow = async function () {
   2903  lazy.assert.desktop();
   2904  lazy.assert.open(
   2905    this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
   2906  );
   2907 
   2908  let nwins = 0;
   2909 
   2910  // eslint-disable-next-line
   2911  for (let _ of lazy.windowManager.windows) {
   2912    nwins++;
   2913  }
   2914 
   2915  // If there is only one window left, do not close unless windowless mode is
   2916  // enabled. Instead return a faked empty array of window handles.
   2917  // This will instruct geckodriver to terminate the application.
   2918  if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
   2919    return [];
   2920  }
   2921 
   2922  await this.curBrowser.closeWindow();
   2923  this.currentSession.chromeBrowsingContext = null;
   2924  this.currentSession.contentBrowsingContext = null;
   2925 
   2926  return lazy.windowManager.windows.map(window =>
   2927    lazy.NavigableManager.getIdForBrowsingContext(window.browsingContext)
   2928  );
   2929 };
   2930 
   2931 /** Delete Marionette session. */
   2932 GeckoDriver.prototype.deleteSession = function () {
   2933  if (!this.currentSession) {
   2934    return;
   2935  }
   2936 
   2937  for (let win of lazy.windowManager.windows) {
   2938    this.stopObservingWindow(win);
   2939  }
   2940 
   2941  // reset to the top-most frame
   2942  this.mainFrame = null;
   2943 
   2944  if (!this._isShuttingDown && this.promptListener) {
   2945    // Do not stop the prompt listener when quitting the browser to
   2946    // allow us to also accept beforeunload prompts during shutdown.
   2947    this.promptListener.stopListening();
   2948    this.promptListener = null;
   2949  }
   2950 
   2951  try {
   2952    Services.obs.removeObserver(this, TOPIC_BROWSER_READY);
   2953  } catch (e) {
   2954    lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`);
   2955  }
   2956 
   2957  // Always unregister actors after all other observers
   2958  // and listeners have been removed.
   2959  lazy.unregisterCommandsActor();
   2960  // MarionetteEvents actors are only disabled to avoid IPC errors if there are
   2961  // in flight events being forwarded from the content process to the parent
   2962  // process.
   2963  lazy.disableEventsActor();
   2964 
   2965  if (lazy.RemoteAgent.webDriverBiDi) {
   2966    lazy.RemoteAgent.webDriverBiDi.deleteSession();
   2967  } else {
   2968    this.currentSession.destroy();
   2969    this._currentSession = null;
   2970  }
   2971 };
   2972 
   2973 /**
   2974 * Takes a screenshot of a web element, current frame, or viewport.
   2975 *
   2976 * The screen capture is returned as a lossless PNG image encoded as
   2977 * a base 64 string.
   2978 *
   2979 * If called in the content context, the |id| argument is not null and
   2980 * refers to a present and visible web element's ID, the capture area will
   2981 * be limited to the bounding box of that element.  Otherwise, the capture
   2982 * area will be the bounding box of the current frame.
   2983 *
   2984 * If called in the chrome context, the screenshot will always represent
   2985 * the entire viewport.
   2986 *
   2987 * @param {object} cmd
   2988 * @param {string=} cmd.parameters.id
   2989 *     Optional web element reference to take a screenshot of.
   2990 *     If undefined, a screenshot will be taken of the document element.
   2991 * @param {boolean=} cmd.parameters.full
   2992 *     True to take a screenshot of the entire document element. Is only
   2993 *     considered if <var>id</var> is not defined. Defaults to true.
   2994 * @param {boolean=} cmd.parameters.hash
   2995 *     True if the user requests a hash of the image data. Defaults to false.
   2996 * @param {boolean=} cmd.parameters.scroll
   2997 *     Scroll to element if |id| is provided. Defaults to true.
   2998 *
   2999 * @returns {string}
   3000 *     If <var>hash</var> is false, PNG image encoded as Base64 encoded
   3001 *     string.  If <var>hash</var> is true, hex digest of the SHA-256
   3002 *     hash of the Base64 encoded string.
   3003 *
   3004 * @throws {NoSuchElementError}
   3005 *     If element represented by reference <var>id</var> is unknown.
   3006 * @throws {NoSuchWindowError}
   3007 *     Browsing context has been discarded.
   3008 * @throws {StaleElementReferenceError}
   3009 *     If element represented by reference <var>id</var> has gone stale.
   3010 */
   3011 GeckoDriver.prototype.takeScreenshot = async function (cmd) {
   3012  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3013  await this._handleUserPrompts();
   3014 
   3015  let { id, full, hash, scroll } = cmd.parameters;
   3016  let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;
   3017 
   3018  full = typeof full == "undefined" ? true : full;
   3019  scroll = typeof scroll == "undefined" ? true : scroll;
   3020 
   3021  let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;
   3022 
   3023  // Only consider full screenshot if no element has been specified
   3024  full = webEl ? false : full;
   3025 
   3026  return this.getActor().takeScreenshot(webEl, format, full, scroll);
   3027 };
   3028 
   3029 /**
   3030 * Get the current browser orientation.
   3031 *
   3032 * Will return one of the valid primary orientation values
   3033 * portrait-primary, landscape-primary, portrait-secondary, or
   3034 * landscape-secondary.
   3035 *
   3036 * @throws {NoSuchWindowError}
   3037 *     Top-level browsing context has been discarded.
   3038 */
   3039 GeckoDriver.prototype.getScreenOrientation = function () {
   3040  lazy.assert.mobile();
   3041  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3042 
   3043  const win = this.getCurrentWindow();
   3044 
   3045  return win.screen.orientation.type;
   3046 };
   3047 
   3048 /**
   3049 * Set the current browser orientation.
   3050 *
   3051 * The supplied orientation should be given as one of the valid
   3052 * orientation values.  If the orientation is unknown, an error will
   3053 * be raised.
   3054 *
   3055 * Valid orientations are "portrait" and "landscape", which fall
   3056 * back to "portrait-primary" and "landscape-primary" respectively,
   3057 * and "portrait-secondary" as well as "landscape-secondary".
   3058 *
   3059 * @throws {NoSuchWindowError}
   3060 *     Top-level browsing context has been discarded.
   3061 */
   3062 GeckoDriver.prototype.setScreenOrientation = async function (cmd) {
   3063  lazy.assert.mobile();
   3064  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3065 
   3066  const ors = [
   3067    "portrait",
   3068    "landscape",
   3069    "portrait-primary",
   3070    "landscape-primary",
   3071    "portrait-secondary",
   3072    "landscape-secondary",
   3073  ];
   3074 
   3075  let or = String(cmd.parameters.orientation);
   3076  lazy.assert.string(or, lazy.pprint`Expected "or" to be a string, got ${or}`);
   3077  let mozOr = or.toLowerCase();
   3078  if (!ors.includes(mozOr)) {
   3079    throw new lazy.error.InvalidArgumentError(
   3080      `Unknown screen orientation: ${or}`
   3081    );
   3082  }
   3083 
   3084  const win = this.getCurrentWindow();
   3085 
   3086  try {
   3087    await win.screen.orientation.lock(mozOr);
   3088  } catch (e) {
   3089    throw new lazy.error.WebDriverError(
   3090      `Unable to set screen orientation: ${or}`
   3091    );
   3092  }
   3093 };
   3094 
   3095 /**
   3096 * Synchronously minimizes the user agent window as if the user pressed
   3097 * the minimize button.
   3098 *
   3099 * No action is taken if the window is already minimized.
   3100 *
   3101 * Not supported on Fennec.
   3102 *
   3103 * @returns {Record<string, number>}
   3104 *     Window rect and window state.
   3105 *
   3106 * @throws {NoSuchWindowError}
   3107 *     Top-level browsing context has been discarded.
   3108 * @throws {UnexpectedAlertOpenError}
   3109 *     A modal dialog is open, blocking this operation.
   3110 * @throws {UnsupportedOperationError}
   3111 *     Not available for current application.
   3112 */
   3113 GeckoDriver.prototype.minimizeWindow = async function () {
   3114  lazy.assert.desktop();
   3115  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3116  await this._handleUserPrompts();
   3117 
   3118  const win = this.getCurrentWindow();
   3119  switch (lazy.WindowState.from(win.windowState)) {
   3120    case lazy.WindowState.Fullscreen:
   3121      await lazy.windowManager.setFullscreen(win, false);
   3122      break;
   3123 
   3124    case lazy.WindowState.Maximized:
   3125      await lazy.windowManager.restoreWindow(win);
   3126      break;
   3127  }
   3128 
   3129  await lazy.windowManager.minimizeWindow(win);
   3130 
   3131  return this.curBrowser.rect;
   3132 };
   3133 
   3134 /**
   3135 * Synchronously maximizes the user agent window as if the user pressed
   3136 * the maximize button.
   3137 *
   3138 * No action is taken if the window is already maximized.
   3139 *
   3140 * Not supported on Fennec.
   3141 *
   3142 * @returns {Record<string, number>}
   3143 *     Window rect.
   3144 *
   3145 * @throws {NoSuchWindowError}
   3146 *     Top-level browsing context has been discarded.
   3147 * @throws {UnexpectedAlertOpenError}
   3148 *     A modal dialog is open, blocking this operation.
   3149 * @throws {UnsupportedOperationError}
   3150 *     Not available for current application.
   3151 */
   3152 GeckoDriver.prototype.maximizeWindow = async function () {
   3153  lazy.assert.desktop();
   3154  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3155  await this._handleUserPrompts();
   3156 
   3157  const win = this.getCurrentWindow();
   3158  switch (lazy.WindowState.from(win.windowState)) {
   3159    case lazy.WindowState.Fullscreen:
   3160      await lazy.windowManager.setFullscreen(win, false);
   3161      break;
   3162 
   3163    case lazy.WindowState.Minimized:
   3164      await lazy.windowManager.restoreWindow(win);
   3165      break;
   3166  }
   3167 
   3168  await lazy.windowManager.maximizeWindow(win);
   3169 
   3170  return this.curBrowser.rect;
   3171 };
   3172 
   3173 /**
   3174 * Synchronously sets the user agent window to full screen as if the user
   3175 * had done "View > Enter Full Screen".
   3176 *
   3177 * No action is taken if the window is already in full screen mode.
   3178 *
   3179 * Not supported on Fennec.
   3180 *
   3181 * @returns {Map.<string, number>}
   3182 *     Window rect.
   3183 *
   3184 * @throws {NoSuchWindowError}
   3185 *     Top-level browsing context has been discarded.
   3186 * @throws {UnexpectedAlertOpenError}
   3187 *     A modal dialog is open, blocking this operation.
   3188 * @throws {UnsupportedOperationError}
   3189 *     Not available for current application.
   3190 */
   3191 GeckoDriver.prototype.fullscreenWindow = async function () {
   3192  lazy.assert.desktop();
   3193  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3194  await this._handleUserPrompts();
   3195 
   3196  const win = this.getCurrentWindow();
   3197  switch (lazy.WindowState.from(win.windowState)) {
   3198    case lazy.WindowState.Maximized:
   3199    case lazy.WindowState.Minimized:
   3200      await lazy.windowManager.restoreWindow(win);
   3201      break;
   3202  }
   3203 
   3204  await lazy.windowManager.setFullscreen(win, true);
   3205 
   3206  return this.curBrowser.rect;
   3207 };
   3208 
   3209 /**
   3210 * Dismisses a currently displayed modal dialogs, or returns no such alert if
   3211 * no modal is displayed.
   3212 *
   3213 * @throws {NoSuchAlertError}
   3214 *     If there is no current user prompt.
   3215 * @throws {NoSuchWindowError}
   3216 *     Top-level browsing context has been discarded.
   3217 */
   3218 GeckoDriver.prototype.dismissDialog = async function () {
   3219  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3220  this._checkIfAlertIsPresent();
   3221 
   3222  const dialogClosed = this.promptListener.dialogClosed();
   3223  this.dialog.dismiss();
   3224  await dialogClosed;
   3225 
   3226  const win = this.getCurrentWindow();
   3227  await new lazy.AnimationFramePromise(win);
   3228 };
   3229 
   3230 /**
   3231 * Accepts a currently displayed dialog modal, or returns no such alert if
   3232 * no modal is displayed.
   3233 *
   3234 * @throws {NoSuchAlertError}
   3235 *     If there is no current user prompt.
   3236 * @throws {NoSuchWindowError}
   3237 *     Top-level browsing context has been discarded.
   3238 */
   3239 GeckoDriver.prototype.acceptDialog = async function () {
   3240  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3241  this._checkIfAlertIsPresent();
   3242 
   3243  const dialogClosed = this.promptListener.dialogClosed();
   3244  this.dialog.accept();
   3245  await dialogClosed;
   3246 
   3247  const win = this.getCurrentWindow();
   3248  await new lazy.AnimationFramePromise(win);
   3249 };
   3250 
   3251 /**
   3252 * Returns the message shown in a currently displayed modal, or returns
   3253 * a no such alert error if no modal is currently displayed.
   3254 *
   3255 * @throws {NoSuchAlertError}
   3256 *     If there is no current user prompt.
   3257 * @throws {NoSuchWindowError}
   3258 *     Top-level browsing context has been discarded.
   3259 */
   3260 GeckoDriver.prototype.getTextFromDialog = async function () {
   3261  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3262  this._checkIfAlertIsPresent();
   3263  const text = await this.dialog.getText();
   3264  return text;
   3265 };
   3266 
   3267 /**
   3268 * Set the user prompt's value field.
   3269 *
   3270 * Sends keys to the input field of a currently displayed modal, or
   3271 * returns a no such alert error if no modal is currently displayed. If
   3272 * a modal dialog is currently displayed but has no means for text input,
   3273 * an element not visible error is returned.
   3274 *
   3275 * @param {object} cmd
   3276 * @param {string} cmd.parameters.text
   3277 *     Input to the user prompt's value field.
   3278 *
   3279 * @throws {ElementNotInteractableError}
   3280 *     If the current user prompt is an alert or confirm.
   3281 * @throws {NoSuchAlertError}
   3282 *     If there is no current user prompt.
   3283 * @throws {NoSuchWindowError}
   3284 *     Top-level browsing context has been discarded.
   3285 * @throws {UnsupportedOperationError}
   3286 *     If the current user prompt is something other than an alert,
   3287 *     confirm, or a prompt.
   3288 */
   3289 GeckoDriver.prototype.sendKeysToDialog = async function (cmd) {
   3290  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3291  this._checkIfAlertIsPresent();
   3292 
   3293  let text = lazy.assert.string(
   3294    cmd.parameters.text,
   3295    lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}`
   3296  );
   3297  let promptType = this.dialog.args.promptType;
   3298 
   3299  switch (promptType) {
   3300    case "alert":
   3301    case "confirm":
   3302      throw new lazy.error.ElementNotInteractableError(
   3303        `User prompt of type ${promptType} is not interactable`
   3304      );
   3305    case "prompt":
   3306      break;
   3307    default:
   3308      await this.dismissDialog();
   3309      throw new lazy.error.UnsupportedOperationError(
   3310        `User prompt of type ${promptType} is not supported`
   3311      );
   3312  }
   3313  this.dialog.text = text;
   3314 };
   3315 
   3316 GeckoDriver.prototype._checkIfAlertIsPresent = function () {
   3317  if (!this.dialog || !this.dialog.isOpen) {
   3318    throw new lazy.error.NoSuchAlertError();
   3319  }
   3320 };
   3321 
   3322 GeckoDriver.prototype._handleUserPrompts = async function () {
   3323  if (!this.dialog || !this.dialog.isOpen) {
   3324    return;
   3325  }
   3326 
   3327  const promptType = this.dialog.promptType;
   3328  const textContent = await this.dialog.getText();
   3329 
   3330  if (promptType === "beforeunload" && !this.currentSession.bidi) {
   3331    // In an HTTP-only session, this prompt will be automatically accepted.
   3332    // Since this occurs asynchronously, we need to wait until it closes
   3333    // to prevent race conditions, particularly in slow builds.
   3334    await lazy.PollPromise((resolve, reject) => {
   3335      this.dialog?.isOpen ? reject() : resolve();
   3336    });
   3337    return;
   3338  }
   3339 
   3340  let type = lazy.PromptTypes.Default;
   3341  switch (promptType) {
   3342    case "alert":
   3343      type = lazy.PromptTypes.Alert;
   3344      break;
   3345    case "beforeunload":
   3346      type = lazy.PromptTypes.BeforeUnload;
   3347      break;
   3348    case "confirm":
   3349      type = lazy.PromptTypes.Confirm;
   3350      break;
   3351    case "prompt":
   3352      type = lazy.PromptTypes.Prompt;
   3353      break;
   3354  }
   3355 
   3356  const userPromptHandler = this.currentSession.userPromptHandler;
   3357  const handlerConfig = userPromptHandler.getPromptHandler(type);
   3358 
   3359  switch (handlerConfig.handler) {
   3360    case lazy.PromptHandlers.Accept:
   3361      await this.acceptDialog();
   3362      break;
   3363    case lazy.PromptHandlers.Dismiss:
   3364      await this.dismissDialog();
   3365      break;
   3366    case lazy.PromptHandlers.Ignore:
   3367      break;
   3368  }
   3369 
   3370  if (handlerConfig.notify) {
   3371    throw new lazy.error.UnexpectedAlertOpenError(
   3372      `Unexpected ${promptType} dialog detected. Performed handler "${handlerConfig.handler}"`,
   3373      {
   3374        text: textContent,
   3375      }
   3376    );
   3377  }
   3378 };
   3379 
   3380 /**
   3381 * Enables or disables accepting new socket connections.
   3382 *
   3383 * By calling this method with `false` the server will not accept any
   3384 * further connections, but existing connections will not be forcible
   3385 * closed. Use `true` to re-enable accepting connections.
   3386 *
   3387 * Please note that when closing the connection via the client you can
   3388 * end-up in a non-recoverable state if it hasn't been enabled before.
   3389 *
   3390 * This method is used for custom in application shutdowns via
   3391 * marionette.quit() or marionette.restart(), like File -> Quit.
   3392 *
   3393 * @param {object} cmd
   3394 * @param {boolean} cmd.parameters.value
   3395 *     True if the server should accept new socket connections.
   3396 */
   3397 GeckoDriver.prototype.acceptConnections = async function (cmd) {
   3398  lazy.assert.boolean(
   3399    cmd.parameters.value,
   3400    lazy.pprint`Expected "value" to be a boolean, got ${cmd.parameters.value}`
   3401  );
   3402  await this._server.setAcceptConnections(cmd.parameters.value);
   3403 };
   3404 
   3405 /**
   3406 * Quits the application with the provided flags.
   3407 *
   3408 * Marionette will stop accepting new connections before ending the
   3409 * current session, and finally attempting to quit the application.
   3410 *
   3411 * Optional {@link nsIAppStartup} flags may be provided as
   3412 * an array of masks, and these will be combined by ORing
   3413 * them with a bitmask.  The available masks are defined in
   3414 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
   3415 *
   3416 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
   3417 * flag may be bit-wise combined with one of the *Quit flags to cause
   3418 * the application to restart after it quits.
   3419 *
   3420 * @param {object} cmd
   3421 * @param {Array.<string>=} cmd.parameters.flags
   3422 *     Constant name of masks to pass to |Services.startup.quit|.
   3423 *     If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
   3424 * @param {boolean=} cmd.parameters.safeMode
   3425 *     Optional flag to indicate that the application has to
   3426 *     be restarted in safe mode.
   3427 *
   3428 * @returns {Record<string,boolean>}
   3429 *     Dictionary containing information that explains the shutdown reason.
   3430 *     The value for `cause` contains the shutdown kind like "shutdown" or
   3431 *     "restart", while `forced` will indicate if it was a normal or forced
   3432 *     shutdown of the application. "in_app" is always set to indicate that
   3433 *     it is a shutdown triggered from within the application.
   3434 *
   3435 * @throws {InvalidArgumentError}
   3436 *     If <var>flags</var> contains unknown or incompatible flags,
   3437 *     for example multiple Quit flags.
   3438 */
   3439 GeckoDriver.prototype.quit = async function (cmd) {
   3440  const { flags = [], safeMode = false } = cmd.parameters;
   3441 
   3442  lazy.assert.array(
   3443    flags,
   3444    lazy.pprint`Expected "flags" to be an array, got ${flags}`
   3445  );
   3446  lazy.assert.boolean(
   3447    safeMode,
   3448    lazy.pprint`Expected "safeMode" to be a boolean, got ${safeMode}`
   3449  );
   3450 
   3451  if (safeMode && !flags.includes("eRestart")) {
   3452    throw new lazy.error.InvalidArgumentError(
   3453      `"safeMode" only works with restart flag`
   3454    );
   3455  }
   3456 
   3457  // Register handler to run Marionette specific shutdown code.
   3458  Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
   3459 
   3460  let quitApplicationResponse;
   3461  try {
   3462    this._isShuttingDown = true;
   3463    quitApplicationResponse = await lazy.quit(
   3464      flags,
   3465      safeMode,
   3466      this.currentSession.capabilities.get("moz:windowless")
   3467    );
   3468  } catch (e) {
   3469    this._isShuttingDown = false;
   3470    if (e instanceof TypeError) {
   3471      throw new lazy.error.InvalidArgumentError(e.message);
   3472    }
   3473    throw new lazy.error.UnsupportedOperationError(e.message);
   3474  } finally {
   3475    Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
   3476  }
   3477 
   3478  return quitApplicationResponse;
   3479 };
   3480 
   3481 GeckoDriver.prototype.installAddon = function (cmd) {
   3482  const {
   3483    addon = null,
   3484    allowPrivateBrowsing = false,
   3485    path = null,
   3486    temporary = false,
   3487  } = cmd.parameters;
   3488 
   3489  lazy.assert.boolean(
   3490    allowPrivateBrowsing,
   3491    lazy.pprint`Expected "allowPrivateBrowsing" to be a boolean, got ${allowPrivateBrowsing}`
   3492  );
   3493 
   3494  lazy.assert.boolean(
   3495    temporary,
   3496    lazy.pprint`Expected "temporary" to be a boolean, got ${temporary}`
   3497  );
   3498 
   3499  if (addon !== null) {
   3500    if (path !== null) {
   3501      throw new lazy.error.InvalidArgumentError(
   3502        `Expected only one of "addon" or "path" to be specified`
   3503      );
   3504    }
   3505 
   3506    lazy.assert.string(
   3507      addon,
   3508      lazy.pprint`Expected "addon" to be a string, got ${addon}`
   3509    );
   3510 
   3511    return lazy.Addon.installWithBase64(addon, temporary, allowPrivateBrowsing);
   3512  }
   3513 
   3514  if (path !== null) {
   3515    lazy.assert.string(
   3516      path,
   3517      lazy.pprint`Expected "path" to be a string, got ${path}`
   3518    );
   3519 
   3520    return lazy.Addon.installWithPath(path, temporary, allowPrivateBrowsing);
   3521  }
   3522 
   3523  throw new lazy.error.InvalidArgumentError(
   3524    `Expected "addon" or "path" argument to be specified`
   3525  );
   3526 };
   3527 
   3528 GeckoDriver.prototype.uninstallAddon = function (cmd) {
   3529  let id = cmd.parameters.id;
   3530  if (typeof id == "undefined" || typeof id != "string") {
   3531    throw new lazy.error.InvalidArgumentError();
   3532  }
   3533 
   3534  return lazy.Addon.uninstall(id);
   3535 };
   3536 
   3537 /**
   3538 * Retrieve the localized string for the specified property id.
   3539 *
   3540 * Example:
   3541 *
   3542 *     localizeProperty(
   3543 *         ["chrome://global/locale/findbar.properties"], "FastFind");
   3544 *
   3545 * @param {object} cmd
   3546 * @param {Array.<string>} cmd.parameters.urls
   3547 *     Array of .properties URLs.
   3548 * @param {string} cmd.parameters.id
   3549 *     The ID of the property to retrieve the localized string for.
   3550 *
   3551 * @returns {string}
   3552 *     The localized string for the requested property.
   3553 */
   3554 GeckoDriver.prototype.localizeProperty = function (cmd) {
   3555  let { urls, id } = cmd.parameters;
   3556 
   3557  if (!Array.isArray(urls)) {
   3558    throw new lazy.error.InvalidArgumentError(
   3559      "Value of `urls` should be of type 'Array'"
   3560    );
   3561  }
   3562  if (typeof id != "string") {
   3563    throw new lazy.error.InvalidArgumentError(
   3564      "Value of `id` should be of type 'string'"
   3565    );
   3566  }
   3567 
   3568  return lazy.l10n.localizeProperty(urls, id);
   3569 };
   3570 
   3571 /**
   3572 * Initialize the reftest mode
   3573 */
   3574 GeckoDriver.prototype.setupReftest = async function (cmd) {
   3575  if (this._reftest) {
   3576    throw new lazy.error.UnsupportedOperationError(
   3577      "Called reftest:setup with a reftest session already active"
   3578    );
   3579  }
   3580 
   3581  let {
   3582    urlCount = {},
   3583    screenshot = "unexpected",
   3584    isPrint = false,
   3585    cacheScreenshots = true,
   3586  } = cmd.parameters;
   3587  if (!["always", "fail", "unexpected"].includes(screenshot)) {
   3588    throw new lazy.error.InvalidArgumentError(
   3589      "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
   3590    );
   3591  }
   3592 
   3593  this._reftest = new lazy.reftest.Runner(this);
   3594  this._reftest.setup(urlCount, screenshot, isPrint, cacheScreenshots);
   3595 };
   3596 
   3597 /** Run a reftest. */
   3598 GeckoDriver.prototype.runReftest = function (cmd) {
   3599  let { test, references, expected, timeout, width, height, pageRanges } =
   3600    cmd.parameters;
   3601 
   3602  if (!this._reftest) {
   3603    throw new lazy.error.UnsupportedOperationError(
   3604      "Called reftest:run before reftest:start"
   3605    );
   3606  }
   3607 
   3608  lazy.assert.string(
   3609    test,
   3610    lazy.pprint`Expected "test" to be a string, got ${test}`
   3611  );
   3612  lazy.assert.string(
   3613    expected,
   3614    lazy.pprint`Expected "expected" to be a string, got ${expected}`
   3615  );
   3616  lazy.assert.array(
   3617    references,
   3618    lazy.pprint`Expected "references" to be an array, got ${references}`
   3619  );
   3620 
   3621  return this._reftest.run(
   3622    test,
   3623    references,
   3624    expected,
   3625    timeout,
   3626    pageRanges,
   3627    width,
   3628    height
   3629  );
   3630 };
   3631 
   3632 /**
   3633 * End a reftest run.
   3634 *
   3635 * Closes the reftest window (without changing the current window handle),
   3636 * and removes cached canvases.
   3637 */
   3638 GeckoDriver.prototype.teardownReftest = function () {
   3639  if (!this._reftest) {
   3640    throw new lazy.error.UnsupportedOperationError(
   3641      "Called reftest:teardown before reftest:start"
   3642    );
   3643  }
   3644 
   3645  this._reftest.teardown();
   3646  this._reftest = null;
   3647 };
   3648 
   3649 /**
   3650 * Implements the GenerateTestReport functionality of the Reporting API.
   3651 *
   3652 * @see https://w3c.github.io/reporting/#generate-test-report-command *
   3653 *
   3654 * @param {object} cmd
   3655 * @param {string} cmd.parameters.message
   3656 *     The message contents of the report being generated.
   3657 * @param {string=} cmd.parameters.group
   3658 *     The name of the reporting endpoint that the report should be sent to.
   3659 *     @see https://www.w3.org/TR/reporting-1/#endpoint
   3660 *
   3661 * @throws {InvalidArgumentError}
   3662 *     If a message argument wasn't passed in the parameters.
   3663 */
   3664 
   3665 GeckoDriver.prototype.generateTestReport = async function (cmd) {
   3666  const { message, group = "default" } = cmd.parameters;
   3667 
   3668  lazy.assert.open(this.getBrowsingContext());
   3669  await this._handleUserPrompts();
   3670 
   3671  lazy.assert.string(
   3672    message,
   3673    lazy.pprint(`Expected "message" to be a string, got ${message}`)
   3674  );
   3675 
   3676  lazy.assert.string(
   3677    group,
   3678    lazy.pprint(`Expected "group" to be a string, got ${group}`)
   3679  );
   3680 
   3681  await this.getActor().generateTestReport(message, group);
   3682 };
   3683 
   3684 /**
   3685 * Print page as PDF.
   3686 *
   3687 * @param {object} cmd
   3688 * @param {boolean=} cmd.parameters.background
   3689 *     Whether or not to print background colors and images.
   3690 *     Defaults to false, which prints without background graphics.
   3691 * @param {number=} cmd.parameters.margin.bottom
   3692 *     Bottom margin in cm. Defaults to 1cm (~0.4 inches).
   3693 * @param {number=} cmd.parameters.margin.left
   3694 *     Left margin in cm. Defaults to 1cm (~0.4 inches).
   3695 * @param {number=} cmd.parameters.margin.right
   3696 *     Right margin in cm. Defaults to 1cm (~0.4 inches).
   3697 * @param {number=} cmd.parameters.margin.top
   3698 *     Top margin in cm. Defaults to 1cm (~0.4 inches).
   3699 * @param {('landscape'|'portrait')=} cmd.parameters.options.orientation
   3700 *     Paper orientation. Defaults to 'portrait'.
   3701 * @param {Array.<string|number>=} cmd.parameters.pageRanges
   3702 *     Paper ranges to print, e.g., ['1-5', 8, '11-13'].
   3703 *     Defaults to the empty array, which means print all pages.
   3704 * @param {number=} cmd.parameters.page.height
   3705 *     Paper height in cm. Defaults to US letter height (27.94cm / 11 inches)
   3706 * @param {number=} cmd.parameters.page.width
   3707 *     Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches)
   3708 * @param {number=} cmd.parameters.scale
   3709 *     Scale of the webpage rendering. Defaults to 1.0.
   3710 * @param {boolean=} cmd.parameters.shrinkToFit
   3711 *     Whether or not to override page size as defined by CSS.
   3712 *     Defaults to true, in which case the content will be scaled
   3713 *     to fit the paper size.
   3714 *
   3715 * @returns {string}
   3716 *     Base64 encoded PDF representing printed document
   3717 *
   3718 * @throws {NoSuchWindowError}
   3719 *     Top-level browsing context has been discarded.
   3720 * @throws {UnexpectedAlertOpenError}
   3721 *     A modal dialog is open, blocking this operation.
   3722 * @throws {UnsupportedOperationError}
   3723 *     Not available in chrome context.
   3724 */
   3725 GeckoDriver.prototype.print = async function (cmd) {
   3726  lazy.assert.content(this.context);
   3727  lazy.assert.open(this.getBrowsingContext({ top: true }));
   3728  await this._handleUserPrompts();
   3729 
   3730  const settings = lazy.print.addDefaultSettings(cmd.parameters);
   3731  for (const prop of ["top", "bottom", "left", "right"]) {
   3732    lazy.assert.positiveNumber(
   3733      settings.margin[prop],
   3734      lazy.pprint`Expected "margin.${prop}" to be a positive number, got ${settings.margin[prop]}`
   3735    );
   3736  }
   3737  for (const prop of ["width", "height"]) {
   3738    lazy.assert.positiveNumber(
   3739      settings.page[prop],
   3740      lazy.pprint`Expected "page.${prop}" to be a positive number, got ${settings.page[prop]}`
   3741    );
   3742  }
   3743  lazy.assert.positiveNumber(
   3744    settings.scale,
   3745    lazy.pprint`Expected "scale" to be a positive number, got ${settings.scale}`
   3746  );
   3747  lazy.assert.that(
   3748    s =>
   3749      s >= lazy.print.minScaleValue &&
   3750      settings.scale <= lazy.print.maxScaleValue,
   3751    lazy.pprint`scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
   3752  )(settings.scale);
   3753  lazy.assert.boolean(
   3754    settings.shrinkToFit,
   3755    lazy.pprint`Expected "shrinkToFit" to be a boolean, got ${settings.shrinkToFit}`
   3756  );
   3757  lazy.assert.that(
   3758    orientation => lazy.print.defaults.orientationValue.includes(orientation),
   3759    lazy.pprint`orientation ${
   3760      settings.orientation
   3761    } doesn't match allowed values "${lazy.print.defaults.orientationValue.join(
   3762      "/"
   3763    )}"`
   3764  )(settings.orientation);
   3765  lazy.assert.boolean(
   3766    settings.background,
   3767    lazy.pprint`Expected "background" to be a boolean, got ${settings.background}`
   3768  );
   3769  lazy.assert.array(
   3770    settings.pageRanges,
   3771    lazy.pprint`Expected "pageRanges" to be an array, got ${settings.pageRanges}`
   3772  );
   3773 
   3774  const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext;
   3775  const printSettings = await lazy.print.getPrintSettings(settings);
   3776  const binaryString = await lazy.print.printToBinaryString(
   3777    browsingContext,
   3778    printSettings
   3779  );
   3780 
   3781  return btoa(binaryString);
   3782 };
   3783 
   3784 GeckoDriver.prototype.getGlobalPrivacyControl = function () {
   3785  const gpc = Services.prefs.getBoolPref(
   3786    "privacy.globalprivacycontrol.enabled",
   3787    true
   3788  );
   3789  return { gpc };
   3790 };
   3791 
   3792 GeckoDriver.prototype.setGlobalPrivacyControl = function (cmd) {
   3793  const { gpc } = cmd.parameters;
   3794  if (typeof gpc != "boolean") {
   3795    throw new lazy.error.InvalidArgumentError(
   3796      "Value of `gpc` should be of type 'boolean'"
   3797    );
   3798  }
   3799  Services.prefs.setBoolPref("privacy.globalprivacycontrol.enabled", gpc);
   3800  return { gpc };
   3801 };
   3802 
   3803 GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
   3804  const {
   3805    protocol,
   3806    transport,
   3807    hasResidentKey,
   3808    hasUserVerification,
   3809    isUserConsenting,
   3810    isUserVerified,
   3811  } = cmd.parameters;
   3812 
   3813  lazy.assert.string(
   3814    protocol,
   3815    lazy.pprint`Expected "protocol" to be a string, got ${protocol}`
   3816  );
   3817  lazy.assert.string(
   3818    transport,
   3819    lazy.pprint`Expected "transport" to be a string, got ${transport}`
   3820  );
   3821  lazy.assert.boolean(
   3822    hasResidentKey,
   3823    lazy.pprint`Expected "hasResidentKey" to be a boolean, got ${hasResidentKey}`
   3824  );
   3825  lazy.assert.boolean(
   3826    hasUserVerification,
   3827    lazy.pprint`Expected "hasUserVerification" to be a boolean, got ${hasUserVerification}`
   3828  );
   3829  lazy.assert.boolean(
   3830    isUserConsenting,
   3831    lazy.pprint`Expected "isUserConsenting" to be a boolean, got ${isUserConsenting}`
   3832  );
   3833  lazy.assert.boolean(
   3834    isUserVerified,
   3835    lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}`
   3836  );
   3837 
   3838  return lazy.webauthn.addVirtualAuthenticator(
   3839    protocol,
   3840    transport,
   3841    hasResidentKey,
   3842    hasUserVerification,
   3843    isUserConsenting,
   3844    isUserVerified
   3845  );
   3846 };
   3847 
   3848 GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
   3849  const { authenticatorId } = cmd.parameters;
   3850 
   3851  lazy.assert.string(
   3852    authenticatorId,
   3853    lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}`
   3854  );
   3855 
   3856  lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
   3857 };
   3858 
   3859 GeckoDriver.prototype.addCredential = function (cmd) {
   3860  const {
   3861    authenticatorId,
   3862    credentialId,
   3863    isResidentCredential,
   3864    rpId,
   3865    privateKey,
   3866    userHandle,
   3867    signCount,
   3868  } = cmd.parameters;
   3869 
   3870  lazy.assert.string(
   3871    authenticatorId,
   3872    lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}`
   3873  );
   3874  lazy.assert.string(
   3875    credentialId,
   3876    lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}`
   3877  );
   3878  lazy.assert.boolean(
   3879    isResidentCredential,
   3880    lazy.pprint`Expected "isResidentCredential" to be a boolean, got ${isResidentCredential}`
   3881  );
   3882  lazy.assert.string(
   3883    rpId,
   3884    lazy.pprint`Expected "rpId" to be a string, got ${rpId}`
   3885  );
   3886  lazy.assert.string(
   3887    privateKey,
   3888    lazy.pprint`Expected "privateKey" to be a string, got ${privateKey}`
   3889  );
   3890  if (userHandle) {
   3891    lazy.assert.string(
   3892      userHandle,
   3893      lazy.pprint`Expected "userHandle" to be a string, got ${userHandle}`
   3894    );
   3895  }
   3896  lazy.assert.number(
   3897    signCount,
   3898    lazy.pprint`Expected "signCount" to be a number, got ${signCount}`
   3899  );
   3900 
   3901  lazy.webauthn.addCredential(
   3902    authenticatorId,
   3903    credentialId,
   3904    isResidentCredential,
   3905    rpId,
   3906    privateKey,
   3907    userHandle,
   3908    signCount
   3909  );
   3910 };
   3911 
   3912 GeckoDriver.prototype.getCredentials = function (cmd) {
   3913  const { authenticatorId } = cmd.parameters;
   3914 
   3915  lazy.assert.string(
   3916    authenticatorId,
   3917    lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}`
   3918  );
   3919 
   3920  return lazy.webauthn.getCredentials(authenticatorId);
   3921 };
   3922 
   3923 GeckoDriver.prototype.removeCredential = function (cmd) {
   3924  const { authenticatorId, credentialId } = cmd.parameters;
   3925 
   3926  lazy.assert.string(
   3927    authenticatorId,
   3928    lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}`
   3929  );
   3930  lazy.assert.string(
   3931    credentialId,
   3932    lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}`
   3933  );
   3934 
   3935  lazy.webauthn.removeCredential(authenticatorId, credentialId);
   3936 };
   3937 
   3938 GeckoDriver.prototype.removeAllCredentials = function (cmd) {
   3939  const { authenticatorId } = cmd.parameters;
   3940 
   3941  lazy.assert.string(
   3942    authenticatorId,
   3943    lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}`
   3944  );
   3945 
   3946  lazy.webauthn.removeAllCredentials(authenticatorId);
   3947 };
   3948 
   3949 GeckoDriver.prototype.setUserVerified = function (cmd) {
   3950  const { authenticatorId, isUserVerified } = cmd.parameters;
   3951 
   3952  lazy.assert.string(
   3953    authenticatorId,
   3954    lazy.pprint`Expected "authenticatorId" to be a string, got ${authenticatorId}`
   3955  );
   3956  lazy.assert.boolean(
   3957    isUserVerified,
   3958    lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}`
   3959  );
   3960 
   3961  lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
   3962 };
   3963 
   3964 GeckoDriver.prototype.setPermission = async function (cmd) {
   3965  const { descriptor, state, oneRealm = false } = cmd.parameters;
   3966  const browsingContext = lazy.assert.open(this.getBrowsingContext());
   3967 
   3968  lazy.permissions.validateDescriptor(descriptor);
   3969  lazy.permissions.validateState(state);
   3970 
   3971  let params;
   3972  try {
   3973    params =
   3974      await this.curBrowser.window.navigator.permissions.parseSetParameters({
   3975        descriptor,
   3976        state,
   3977      });
   3978  } catch (err) {
   3979    throw new lazy.error.InvalidArgumentError(`setPermission: ${err.message}`);
   3980  }
   3981 
   3982  lazy.assert.boolean(
   3983    oneRealm,
   3984    lazy.pprint`Expected "oneRealm" to be a boolean, got ${oneRealm}`
   3985  );
   3986 
   3987  let origin = browsingContext.currentURI.prePath;
   3988 
   3989  // storage-access is a special case.
   3990  if (descriptor.name === "storage-access") {
   3991    origin = browsingContext.top.currentURI.prePath;
   3992 
   3993    params = {
   3994      type: lazy.permissions.getStorageAccessPermissionsType(
   3995        browsingContext.currentWindowGlobal.documentURI
   3996      ),
   3997    };
   3998  }
   3999 
   4000  lazy.permissions.set(params, state, origin);
   4001 };
   4002 
   4003 /**
   4004 * Determines the Accessibility label for this element.
   4005 *
   4006 * @param {object} cmd
   4007 * @param {string} cmd.parameters.id
   4008 *     Web element reference ID to the element for which the accessibility label
   4009 *     will be returned.
   4010 *
   4011 * @returns {string}
   4012 *     The Accessibility label for this element
   4013 */
   4014 GeckoDriver.prototype.getComputedLabel = async function (cmd) {
   4015  lazy.assert.open(this.getBrowsingContext());
   4016  await this._handleUserPrompts();
   4017 
   4018  let id = lazy.assert.string(
   4019    cmd.parameters.id,
   4020    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   4021  );
   4022  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   4023 
   4024  return this.getActor().getComputedLabel(webEl);
   4025 };
   4026 
   4027 /**
   4028 * Determines the Accessibility role for this element.
   4029 *
   4030 * @param {object} cmd
   4031 * @param {string} cmd.parameters.id
   4032 *     Web element reference ID to the element for which the accessibility role
   4033 *     will be returned.
   4034 *
   4035 * @returns {string}
   4036 *     The Accessibility role for this element
   4037 */
   4038 GeckoDriver.prototype.getComputedRole = async function (cmd) {
   4039  lazy.assert.open(this.getBrowsingContext());
   4040  await this._handleUserPrompts();
   4041 
   4042  let id = lazy.assert.string(
   4043    cmd.parameters.id,
   4044    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
   4045  );
   4046  let webEl = lazy.WebElement.fromUUID(id).toJSON();
   4047  return this.getActor().getComputedRole(webEl);
   4048 };
   4049 
   4050 GeckoDriver.prototype.commands = {
   4051  // Marionette service
   4052  "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
   4053  "Marionette:GetContext": GeckoDriver.prototype.getContext,
   4054  "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
   4055  "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
   4056  "Marionette:Quit": GeckoDriver.prototype.quit,
   4057  "Marionette:RegisterChromeHandler":
   4058    GeckoDriver.prototype.registerChromeHandler,
   4059  "Marionette:UnregisterChromeHandler":
   4060    GeckoDriver.prototype.unregisterChromeHandler,
   4061  "Marionette:SetContext": GeckoDriver.prototype.setContext,
   4062  "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
   4063 
   4064  // Addon service
   4065  "Addon:Install": GeckoDriver.prototype.installAddon,
   4066  "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,
   4067 
   4068  // L10n service
   4069  "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,
   4070 
   4071  // Reftest service
   4072  "reftest:setup": GeckoDriver.prototype.setupReftest,
   4073  "reftest:run": GeckoDriver.prototype.runReftest,
   4074  "reftest:teardown": GeckoDriver.prototype.teardownReftest,
   4075 
   4076  // WebDriver service
   4077  "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
   4078  // deprecated, no longer used since the geckodriver 0.30.0 release
   4079  "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
   4080  "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
   4081  "WebDriver:Back": GeckoDriver.prototype.goBack,
   4082  "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
   4083  "WebDriver:CloseWindow": GeckoDriver.prototype.close,
   4084  "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
   4085  "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
   4086  "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
   4087  "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
   4088  "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
   4089  "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
   4090  "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
   4091  "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
   4092  "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
   4093  "WebDriver:FindElement": GeckoDriver.prototype.findElement,
   4094  "WebDriver:FindElementFromShadowRoot":
   4095    GeckoDriver.prototype.findElementFromShadowRoot,
   4096  "WebDriver:FindElements": GeckoDriver.prototype.findElements,
   4097  "WebDriver:FindElementsFromShadowRoot":
   4098    GeckoDriver.prototype.findElementsFromShadowRoot,
   4099  "WebDriver:Forward": GeckoDriver.prototype.goForward,
   4100  "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
   4101  "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
   4102  "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
   4103  "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
   4104  "WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel,
   4105  "WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole,
   4106  "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
   4107  "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
   4108  "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
   4109  "WebDriver:GetElementCSSValue":
   4110    GeckoDriver.prototype.getElementValueOfCssProperty,
   4111  "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
   4112  "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
   4113  "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
   4114  "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
   4115  "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
   4116  "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
   4117  "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
   4118  "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
   4119  "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
   4120  "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
   4121  "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
   4122  "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
   4123  "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
   4124  "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
   4125  "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
   4126  "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
   4127  "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
   4128  "WebDriver:NewSession": GeckoDriver.prototype.newSession,
   4129  "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
   4130  "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
   4131  "WebDriver:Print": GeckoDriver.prototype.print,
   4132  "WebDriver:Refresh": GeckoDriver.prototype.refresh,
   4133  "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
   4134  "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
   4135  "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
   4136  "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
   4137  "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
   4138  "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
   4139  "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
   4140  "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
   4141  "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,
   4142 
   4143  // Global Privacy Control
   4144  "GPC:GetGlobalPrivacyControl": GeckoDriver.prototype.getGlobalPrivacyControl,
   4145  "GPC:SetGlobalPrivacyControl": GeckoDriver.prototype.setGlobalPrivacyControl,
   4146 
   4147  // Reporting API test generation of reports
   4148  "Reporting:GenerateTestReport": GeckoDriver.prototype.generateTestReport,
   4149 
   4150  // WebAuthn
   4151  "WebAuthn:AddVirtualAuthenticator":
   4152    GeckoDriver.prototype.addVirtualAuthenticator,
   4153  "WebAuthn:RemoveVirtualAuthenticator":
   4154    GeckoDriver.prototype.removeVirtualAuthenticator,
   4155  "WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
   4156  "WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
   4157  "WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
   4158  "WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
   4159  "WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
   4160 };