From 9e3c08db40b8916968b9f30096c7be3f00ce9647 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 21 Apr 2024 13:44:51 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- remote/marionette/.eslintrc.js | 14 + remote/marionette/README | 20 + remote/marionette/accessibility.sys.mjs | 481 +++ .../actors/MarionetteCommandsChild.sys.mjs | 611 ++++ .../actors/MarionetteCommandsParent.sys.mjs | 383 +++ .../actors/MarionetteEventsChild.sys.mjs | 84 + .../actors/MarionetteEventsParent.sys.mjs | 115 + .../actors/MarionetteReftestChild.sys.mjs | 236 ++ .../actors/MarionetteReftestParent.sys.mjs | 85 + remote/marionette/addon.sys.mjs | 133 + remote/marionette/atom.sys.mjs | 305 ++ remote/marionette/browser.sys.mjs | 385 +++ remote/marionette/cert.sys.mjs | 61 + remote/marionette/chrome/reftest.xhtml | 8 + remote/marionette/chrome/test.xhtml | 61 + remote/marionette/chrome/test2.xhtml | 36 + remote/marionette/chrome/test_dialog.dtd | 7 + remote/marionette/chrome/test_dialog.properties | 7 + remote/marionette/chrome/test_dialog.xhtml | 37 + remote/marionette/chrome/test_menupopup.xhtml | 33 + remote/marionette/chrome/test_nested_iframe.xhtml | 8 + remote/marionette/chrome/test_no_xul.xhtml | 58 + remote/marionette/cookie.sys.mjs | 296 ++ remote/marionette/driver.sys.mjs | 3418 ++++++++++++++++++++ remote/marionette/element.sys.mjs | 1620 ++++++++++ remote/marionette/evaluate.sys.mjs | 356 ++ remote/marionette/event.sys.mjs | 317 ++ remote/marionette/interaction.sys.mjs | 782 +++++ remote/marionette/jar.mn | 52 + remote/marionette/json.sys.mjs | 249 ++ remote/marionette/l10n.sys.mjs | 103 + remote/marionette/legacyaction.sys.mjs | 640 ++++ remote/marionette/message.sys.mjs | 329 ++ remote/marionette/modal.sys.mjs | 377 +++ remote/marionette/moz.build | 10 + remote/marionette/navigate.sys.mjs | 428 +++ remote/marionette/packets.sys.mjs | 426 +++ remote/marionette/permissions.sys.mjs | 69 + remote/marionette/prefs.sys.mjs | 175 + remote/marionette/reftest-content.js | 65 + remote/marionette/reftest.sys.mjs | 907 ++++++ remote/marionette/server.sys.mjs | 462 +++ remote/marionette/stream-utils.sys.mjs | 256 ++ remote/marionette/sync.sys.mjs | 540 ++++ remote/marionette/test/README | 1 + remote/marionette/test/xpcshell/.eslintrc.js | 7 + remote/marionette/test/xpcshell/README | 16 + remote/marionette/test/xpcshell/head.js | 3 + remote/marionette/test/xpcshell/test_actors.js | 55 + remote/marionette/test/xpcshell/test_browser.js | 21 + remote/marionette/test/xpcshell/test_cookie.js | 362 +++ remote/marionette/test/xpcshell/test_element.js | 789 +++++ remote/marionette/test/xpcshell/test_json.js | 275 ++ remote/marionette/test/xpcshell/test_message.js | 245 ++ remote/marionette/test/xpcshell/test_modal.js | 113 + remote/marionette/test/xpcshell/test_navigate.js | 90 + remote/marionette/test/xpcshell/test_prefs.js | 98 + remote/marionette/test/xpcshell/test_sync.js | 419 +++ remote/marionette/test/xpcshell/xpcshell.ini | 18 + remote/marionette/transport.sys.mjs | 529 +++ 60 files changed, 18086 insertions(+) create mode 100644 remote/marionette/.eslintrc.js create mode 100644 remote/marionette/README create mode 100644 remote/marionette/accessibility.sys.mjs create mode 100644 remote/marionette/actors/MarionetteCommandsChild.sys.mjs create mode 100644 remote/marionette/actors/MarionetteCommandsParent.sys.mjs create mode 100644 remote/marionette/actors/MarionetteEventsChild.sys.mjs create mode 100644 remote/marionette/actors/MarionetteEventsParent.sys.mjs create mode 100644 remote/marionette/actors/MarionetteReftestChild.sys.mjs create mode 100644 remote/marionette/actors/MarionetteReftestParent.sys.mjs create mode 100644 remote/marionette/addon.sys.mjs create mode 100644 remote/marionette/atom.sys.mjs create mode 100644 remote/marionette/browser.sys.mjs create mode 100644 remote/marionette/cert.sys.mjs create mode 100644 remote/marionette/chrome/reftest.xhtml create mode 100644 remote/marionette/chrome/test.xhtml create mode 100644 remote/marionette/chrome/test2.xhtml create mode 100644 remote/marionette/chrome/test_dialog.dtd create mode 100644 remote/marionette/chrome/test_dialog.properties create mode 100644 remote/marionette/chrome/test_dialog.xhtml create mode 100644 remote/marionette/chrome/test_menupopup.xhtml create mode 100644 remote/marionette/chrome/test_nested_iframe.xhtml create mode 100644 remote/marionette/chrome/test_no_xul.xhtml create mode 100644 remote/marionette/cookie.sys.mjs create mode 100644 remote/marionette/driver.sys.mjs create mode 100644 remote/marionette/element.sys.mjs create mode 100644 remote/marionette/evaluate.sys.mjs create mode 100644 remote/marionette/event.sys.mjs create mode 100644 remote/marionette/interaction.sys.mjs create mode 100644 remote/marionette/jar.mn create mode 100644 remote/marionette/json.sys.mjs create mode 100644 remote/marionette/l10n.sys.mjs create mode 100644 remote/marionette/legacyaction.sys.mjs create mode 100644 remote/marionette/message.sys.mjs create mode 100644 remote/marionette/modal.sys.mjs create mode 100644 remote/marionette/moz.build create mode 100644 remote/marionette/navigate.sys.mjs create mode 100644 remote/marionette/packets.sys.mjs create mode 100644 remote/marionette/permissions.sys.mjs create mode 100644 remote/marionette/prefs.sys.mjs create mode 100644 remote/marionette/reftest-content.js create mode 100644 remote/marionette/reftest.sys.mjs create mode 100644 remote/marionette/server.sys.mjs create mode 100644 remote/marionette/stream-utils.sys.mjs create mode 100644 remote/marionette/sync.sys.mjs create mode 100644 remote/marionette/test/README create mode 100644 remote/marionette/test/xpcshell/.eslintrc.js create mode 100644 remote/marionette/test/xpcshell/README create mode 100644 remote/marionette/test/xpcshell/head.js create mode 100644 remote/marionette/test/xpcshell/test_actors.js create mode 100644 remote/marionette/test/xpcshell/test_browser.js create mode 100644 remote/marionette/test/xpcshell/test_cookie.js create mode 100644 remote/marionette/test/xpcshell/test_element.js create mode 100644 remote/marionette/test/xpcshell/test_json.js create mode 100644 remote/marionette/test/xpcshell/test_message.js create mode 100644 remote/marionette/test/xpcshell/test_modal.js create mode 100644 remote/marionette/test/xpcshell/test_navigate.js create mode 100644 remote/marionette/test/xpcshell/test_prefs.js create mode 100644 remote/marionette/test/xpcshell/test_sync.js create mode 100644 remote/marionette/test/xpcshell/xpcshell.ini create mode 100644 remote/marionette/transport.sys.mjs (limited to 'remote/marionette') diff --git a/remote/marionette/.eslintrc.js b/remote/marionette/.eslintrc.js new file mode 100644 index 0000000000..64a8883c43 --- /dev/null +++ b/remote/marionette/.eslintrc.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +// inherits from ../../tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js + +module.exports = { + rules: { + camelcase: ["error", { properties: "never" }], + "no-var": "error", + }, +}; diff --git a/remote/marionette/README b/remote/marionette/README new file mode 100644 index 0000000000..d077a5136c --- /dev/null +++ b/remote/marionette/README @@ -0,0 +1,20 @@ +Marionette [ ˌmarɪəˈnɛt] is + + * a puppet worked by strings: the bird bobs up and down like + a marionette; + + * a person who is easily manipulated or controlled: many officers + dismissed him as the mayor’s marionette; + + * the remote protocol that lets out-of-process programs communicate + with, instrument, and control Gecko-based browsers. + +Marionette provides interfaces for interacting with both the internal +JavaScript runtime and UI elements of Gecko-based browsers, such +as Firefox on desktop and mobile. It can control both the chrome- and content +documents, giving a high level of control and ability to replicate, +or emulate, user interaction. + +Head on to the Marionette documentation to find out more: + + https://firefox-source-docs.mozilla.org/testing/marionette/ diff --git a/remote/marionette/accessibility.sys.mjs b/remote/marionette/accessibility.sys.mjs new file mode 100644 index 0000000000..f21eaf5047 --- /dev/null +++ b/remote/marionette/accessibility.sys.mjs @@ -0,0 +1,481 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", + waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +XPCOMUtils.defineLazyGetter(lazy, "service", () => { + try { + return Cc["@mozilla.org/accessibilityService;1"].getService( + Ci.nsIAccessibilityService + ); + } catch (e) { + lazy.logger.warn("Accessibility module is not present"); + return undefined; + } +}); + +/** @namespace */ +export const accessibility = { + get service() { + return lazy.service; + }, +}; + +/** + * Accessible states used to check element"s state from the accessiblity API + * perspective. + * + * Note: if gecko is built with --disable-accessibility, the interfaces + * are not defined. This is why we use getters instead to be able to use + * these statically. + */ +accessibility.State = { + get Unavailable() { + return Ci.nsIAccessibleStates.STATE_UNAVAILABLE; + }, + get Focusable() { + return Ci.nsIAccessibleStates.STATE_FOCUSABLE; + }, + get Selectable() { + return Ci.nsIAccessibleStates.STATE_SELECTABLE; + }, + get Selected() { + return Ci.nsIAccessibleStates.STATE_SELECTED; + }, +}; + +/** + * Accessible object roles that support some action. + */ +accessibility.ActionableRoles = new Set([ + "checkbutton", + "check menu item", + "check rich option", + "combobox", + "combobox option", + "entry", + "key", + "link", + "listbox option", + "listbox rich option", + "menuitem", + "option", + "outlineitem", + "pagetab", + "pushbutton", + "radiobutton", + "radio menu item", + "rowheader", + "slider", + "spinbutton", + "switch", +]); + +/** + * Factory function that constructs a new {@code accessibility.Checks} + * object with enforced strictness or not. + */ +accessibility.get = function (strict = false) { + return new accessibility.Checks(!!strict); +}; + +/** + * Wait for the document accessibility state to be different from STATE_BUSY. + * + * @param {Document} doc + * The document to wait for. + * @returns {Promise} + * A promise which resolves when the document's accessibility state is no + * longer busy. + */ +function waitForDocumentAccessibility(doc) { + const documentAccessible = accessibility.service.getAccessibleFor(doc); + const state = {}; + documentAccessible.getState(state, {}); + if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) { + return Promise.resolve(); + } + + // Accessibility for the doc is busy, so wait for the state to change. + return lazy.waitForObserverTopic("accessible-event", { + checkFn: subject => { + // If event type does not match expected type, skip the event. + // If event's accessible does not match expected accessible, + // skip the event. + const event = subject.QueryInterface(Ci.nsIAccessibleEvent); + return ( + event.eventType === Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE && + event.accessible === documentAccessible + ); + }, + }); +} + +/** + * Retrieve the Accessible for the provided element. + * + * @param {Element} element + * The element for which we need to retrieve the accessible. + * + * @returns {nsIAccessible|null} + * The Accessible object corresponding to the provided element or null if + * the accessibility service is not available. + */ +accessibility.getAccessible = async function (element) { + if (!accessibility.service) { + return null; + } + + // First, wait for accessibility to be ready for the element's document. + await waitForDocumentAccessibility(element.ownerDocument); + + const acc = accessibility.service.getAccessibleFor(element); + if (acc) { + return acc; + } + + // The Accessible doesn't exist yet. This can happen because a11y tree + // mutations happen during refresh driver ticks. Stop the refresh driver from + // doing its regular ticks and force two refresh driver ticks: the first to + // let layout update and notify a11y, and the second to let a11y process + // updates. + const windowUtils = element.ownerGlobal.windowUtils; + windowUtils.advanceTimeAndRefresh(0); + windowUtils.advanceTimeAndRefresh(0); + // Go back to normal refresh driver ticks. + windowUtils.restoreNormalRefresh(); + return accessibility.service.getAccessibleFor(element); +}; + +/** + * Component responsible for interacting with platform accessibility + * API. + * + * Its methods serve as wrappers for testing content and chrome + * accessibility as well as accessibility of user interactions. + */ +accessibility.Checks = class { + /** + * @param {boolean} strict + * Flag indicating whether the accessibility issue should be logged + * or cause an error to be thrown. Default is to log to stdout. + */ + constructor(strict) { + this.strict = strict; + } + + /** + * Assert that the element has a corresponding accessible object, and retrieve + * this accessible. Note that if the accessibility.Checks component was + * created in non-strict mode, this helper will not attempt to resolve the + * accessible at all and will simply return null. + * + * @param {DOMElement|XULElement} element + * Element to get the accessible object for. + * @param {boolean=} mustHaveAccessible + * Flag indicating that the element must have an accessible object. + * Defaults to not require this. + * + * @returns {Promise.} + * Promise with an accessibility object for the given element. + */ + async assertAccessible(element, mustHaveAccessible = false) { + if (!this.strict) { + return null; + } + + const accessible = await accessibility.getAccessible(element); + if (!accessible && mustHaveAccessible) { + this.error("Element does not have an accessible object", element); + } + + return accessible; + } + + /** + * Test if the accessible has a role that supports some arbitrary + * action. + * + * @param {nsIAccessible} accessible + * Accessible object. + * + * @returns {boolean} + * True if an actionable role is found on the accessible, false + * otherwise. + */ + isActionableRole(accessible) { + return accessibility.ActionableRoles.has( + accessibility.service.getStringRole(accessible.role) + ); + } + + /** + * Test if an accessible has at least one action that it supports. + * + * @param {nsIAccessible} accessible + * Accessible object. + * + * @returns {boolean} + * True if the accessible has at least one supported action, + * false otherwise. + */ + hasActionCount(accessible) { + return accessible.actionCount > 0; + } + + /** + * Test if an accessible has a valid name. + * + * @param {nsIAccessible} accessible + * Accessible object. + * + * @returns {boolean} + * True if the accessible has a non-empty valid name, or false if + * this is not the case. + */ + hasValidName(accessible) { + return accessible.name && accessible.name.trim(); + } + + /** + * Test if an accessible has a {@code hidden} attribute. + * + * @param {nsIAccessible} accessible + * Accessible object. + * + * @returns {boolean} + * True if the accessible object has a {@code hidden} attribute, + * false otherwise. + */ + hasHiddenAttribute(accessible) { + let hidden = false; + try { + hidden = accessible.attributes.getStringProperty("hidden"); + } catch (e) {} + // if the property is missing, error will be thrown + return hidden && hidden === "true"; + } + + /** + * Verify if an accessible has a given state. + * Test if an accessible has a given state. + * + * @param {nsIAccessible} accessible + * Accessible object to test. + * @param {number} stateToMatch + * State to match. + * + * @returns {boolean} + * True if |accessible| has |stateToMatch|, false otherwise. + */ + matchState(accessible, stateToMatch) { + let state = {}; + accessible.getState(state, {}); + return !!(state.value & stateToMatch); + } + + /** + * Test if an accessible is hidden from the user. + * + * @param {nsIAccessible} accessible + * Accessible object. + * + * @returns {boolean} + * True if element is hidden from user, false otherwise. + */ + isHidden(accessible) { + if (!accessible) { + return true; + } + + while (accessible) { + if (this.hasHiddenAttribute(accessible)) { + return true; + } + accessible = accessible.parent; + } + return false; + } + + /** + * Test if the element's visible state corresponds to its accessibility + * API visibility. + * + * @param {nsIAccessible} accessible + * Accessible object. + * @param {DOMElement|XULElement} element + * Element associated with |accessible|. + * @param {boolean} visible + * Visibility state of |element|. + * + * @throws ElementNotAccessibleError + * If |element|'s visibility state does not correspond to + * |accessible|'s. + */ + assertVisible(accessible, element, visible) { + let hiddenAccessibility = this.isHidden(accessible); + + let message; + if (visible && hiddenAccessibility) { + message = + "Element is not currently visible via the accessibility API " + + "and may not be manipulated by it"; + } else if (!visible && !hiddenAccessibility) { + message = + "Element is currently only visible via the accessibility API " + + "and can be manipulated by it"; + } + this.error(message, element); + } + + /** + * Test if the element's unavailable accessibility state matches the + * enabled state. + * + * @param {nsIAccessible} accessible + * Accessible object. + * @param {DOMElement|XULElement} element + * Element associated with |accessible|. + * @param {boolean} enabled + * Enabled state of |element|. + * + * @throws ElementNotAccessibleError + * If |element|'s enabled state does not match |accessible|'s. + */ + assertEnabled(accessible, element, enabled) { + if (!accessible) { + return; + } + + let win = element.ownerGlobal; + let disabledAccessibility = this.matchState( + accessible, + accessibility.State.Unavailable + ); + let explorable = + win.getComputedStyle(element).getPropertyValue("pointer-events") !== + "none"; + + let message; + if (!explorable && !disabledAccessibility) { + message = + "Element is enabled but is not explorable via the " + + "accessibility API"; + } else if (enabled && disabledAccessibility) { + message = "Element is enabled but disabled via the accessibility API"; + } else if (!enabled && !disabledAccessibility) { + message = "Element is disabled but enabled via the accessibility API"; + } + this.error(message, element); + } + + /** + * Test if it is possible to activate an element with the accessibility + * API. + * + * @param {nsIAccessible} accessible + * Accessible object. + * @param {DOMElement|XULElement} element + * Element associated with |accessible|. + * + * @throws ElementNotAccessibleError + * If it is impossible to activate |element| with |accessible|. + */ + assertActionable(accessible, element) { + if (!accessible) { + return; + } + + let message; + if (!this.hasActionCount(accessible)) { + message = "Element does not support any accessible actions"; + } else if (!this.isActionableRole(accessible)) { + message = + "Element does not have a correct accessibility role " + + "and may not be manipulated via the accessibility API"; + } else if (!this.hasValidName(accessible)) { + message = "Element is missing an accessible name"; + } else if (!this.matchState(accessible, accessibility.State.Focusable)) { + message = "Element is not focusable via the accessibility API"; + } + + this.error(message, element); + } + + /** + * Test that an element's selected state corresponds to its + * accessibility API selected state. + * + * @param {nsIAccessible} accessible + * Accessible object. + * @param {DOMElement|XULElement} element + * Element associated with |accessible|. + * @param {boolean} selected + * The |element|s selected state. + * + * @throws ElementNotAccessibleError + * If |element|'s selected state does not correspond to + * |accessible|'s. + */ + assertSelected(accessible, element, selected) { + if (!accessible) { + return; + } + + // element is not selectable via the accessibility API + if (!this.matchState(accessible, accessibility.State.Selectable)) { + return; + } + + let selectedAccessibility = this.matchState( + accessible, + accessibility.State.Selected + ); + + let message; + if (selected && !selectedAccessibility) { + message = + "Element is selected but not selected via the accessibility API"; + } else if (!selected && selectedAccessibility) { + message = + "Element is not selected but selected via the accessibility API"; + } + this.error(message, element); + } + + /** + * Throw an error if strict accessibility checks are enforced and log + * the error to the log. + * + * @param {string} message + * @param {DOMElement|XULElement} element + * Element that caused an error. + * + * @throws ElementNotAccessibleError + * If |strict| is true. + */ + error(message, element) { + if (!message || !this.strict) { + return; + } + if (element) { + let { id, tagName, className } = element; + message += `: id: ${id}, tagName: ${tagName}, className: ${className}`; + } + + throw new lazy.error.ElementNotAccessibleError(message); + } +}; diff --git a/remote/marionette/actors/MarionetteCommandsChild.sys.mjs b/remote/marionette/actors/MarionetteCommandsChild.sys.mjs new file mode 100644 index 0000000000..b51e758cae --- /dev/null +++ b/remote/marionette/actors/MarionetteCommandsChild.sys.mjs @@ -0,0 +1,611 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-disable no-restricted-globals */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs", + action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs", + atom: "chrome://remote/content/marionette/atom.sys.mjs", + element: "chrome://remote/content/marionette/element.sys.mjs", + error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", + evaluate: "chrome://remote/content/marionette/evaluate.sys.mjs", + interaction: "chrome://remote/content/marionette/interaction.sys.mjs", + json: "chrome://remote/content/marionette/json.sys.mjs", + legacyaction: "chrome://remote/content/marionette/legacyaction.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", + sandbox: "chrome://remote/content/marionette/evaluate.sys.mjs", + Sandboxes: "chrome://remote/content/marionette/evaluate.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +export class MarionetteCommandsChild extends JSWindowActorChild { + #processActor; + + constructor() { + super(); + + this.#processActor = ChromeUtils.domProcessChild.getActor( + "WebDriverProcessData" + ); + + // sandbox storage and name of the current sandbox + this.sandboxes = new lazy.Sandboxes(() => this.document.defaultView); + // State of the input actions. This is specific to contexts and sessions + this.actionState = null; + } + + get innerWindowId() { + return this.manager.innerWindowId; + } + + /** + * Lazy getter to create a legacyaction Chain instance for touch events. + */ + get legacyactions() { + if (!this._legacyactions) { + this._legacyactions = new lazy.legacyaction.Chain(); + } + + return this._legacyactions; + } + + actorCreated() { + lazy.logger.trace( + `[${this.browsingContext.id}] MarionetteCommands actor created ` + + `for window id ${this.innerWindowId}` + ); + } + + didDestroy() { + lazy.logger.trace( + `[${this.browsingContext.id}] MarionetteCommands actor destroyed ` + + `for window id ${this.innerWindowId}` + ); + } + + async receiveMessage(msg) { + if (!this.contentWindow) { + throw new DOMException("Actor is no longer active", "InactiveActor"); + } + + try { + let result; + let waitForNextTick = false; + + const { name, data: serializedData } = msg; + const data = lazy.json.deserialize( + serializedData, + this.#processActor.getNodeCache(), + this.contentWindow + ); + + switch (name) { + case "MarionetteCommandsParent:clearElement": + this.clearElement(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:clickElement": + result = await this.clickElement(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:executeScript": + result = await this.executeScript(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:findElement": + result = await this.findElement(data); + break; + case "MarionetteCommandsParent:findElements": + result = await this.findElements(data); + break; + case "MarionetteCommandsParent:getActiveElement": + result = await this.getActiveElement(); + break; + case "MarionetteCommandsParent:getComputedLabel": + result = await this.getComputedLabel(data); + break; + case "MarionetteCommandsParent:getComputedRole": + result = await this.getComputedRole(data); + break; + case "MarionetteCommandsParent:getElementAttribute": + result = await this.getElementAttribute(data); + break; + case "MarionetteCommandsParent:getElementProperty": + result = await this.getElementProperty(data); + break; + case "MarionetteCommandsParent:getElementRect": + result = await this.getElementRect(data); + break; + case "MarionetteCommandsParent:getElementTagName": + result = await this.getElementTagName(data); + break; + case "MarionetteCommandsParent:getElementText": + result = await this.getElementText(data); + break; + case "MarionetteCommandsParent:getElementValueOfCssProperty": + result = await this.getElementValueOfCssProperty(data); + break; + case "MarionetteCommandsParent:getPageSource": + result = await this.getPageSource(); + break; + case "MarionetteCommandsParent:getScreenshotRect": + result = await this.getScreenshotRect(data); + break; + case "MarionetteCommandsParent:getShadowRoot": + result = await this.getShadowRoot(data); + break; + case "MarionetteCommandsParent:isElementDisplayed": + result = await this.isElementDisplayed(data); + break; + case "MarionetteCommandsParent:isElementEnabled": + result = await this.isElementEnabled(data); + break; + case "MarionetteCommandsParent:isElementSelected": + result = await this.isElementSelected(data); + break; + case "MarionetteCommandsParent:performActions": + result = await this.performActions(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:releaseActions": + result = await this.releaseActions(); + break; + case "MarionetteCommandsParent:sendKeysToElement": + result = await this.sendKeysToElement(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:singleTap": + result = await this.singleTap(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:switchToFrame": + result = await this.switchToFrame(data); + waitForNextTick = true; + break; + case "MarionetteCommandsParent:switchToParentFrame": + result = await this.switchToParentFrame(); + waitForNextTick = true; + break; + } + + // Inform the content process that the command has completed. It allows + // it to process async follow-up tasks before the reply is sent. + if (waitForNextTick) { + await new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); + } + + return { + data: lazy.json.clone(result, this.#processActor.getNodeCache()), + }; + } catch (e) { + // Always wrap errors as WebDriverError + return { error: lazy.error.wrap(e).toJSON() }; + } + } + + // Implementation of WebDriver commands + + /** Clear the text of an element. + * + * @param {object} options + * @param {Element} options.elem + */ + clearElement(options = {}) { + const { elem } = options; + + lazy.interaction.clearElement(elem); + } + + /** + * Click an element. + */ + async clickElement(options = {}) { + const { capabilities, elem } = options; + + return lazy.interaction.clickElement( + elem, + capabilities["moz:accessibilityChecks"], + capabilities["moz:webdriverClick"] + ); + } + + /** + * Executes a JavaScript function. + */ + async executeScript(options = {}) { + const { args, opts = {}, script } = options; + + let sb; + if (opts.sandboxName) { + sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox); + } else { + sb = lazy.sandbox.createMutable(this.document.defaultView); + } + + return lazy.evaluate.sandbox(sb, script, args, opts); + } + + /** + * Find an element in the current browsing context's document using the + * given search strategy. + * + * @param {object=} options + * @param {string} options.strategy + * @param {string} options.selector + * @param {object} options.opts + * @param {Element} options.opts.startNode + * + */ + async findElement(options = {}) { + const { strategy, selector, opts } = options; + + opts.all = false; + + const container = { frame: this.document.defaultView }; + return lazy.element.find(container, strategy, selector, opts); + } + + /** + * Find elements in the current browsing context's document using the + * given search strategy. + * + * @param {object=} options + * @param {string} options.strategy + * @param {string} options.selector + * @param {object} options.opts + * @param {Element} options.opts.startNode + * + */ + async findElements(options = {}) { + const { strategy, selector, opts } = options; + + opts.all = true; + + const container = { frame: this.document.defaultView }; + return lazy.element.find(container, strategy, selector, opts); + } + + /** + * Return the active element in the document. + */ + async getActiveElement() { + let elem = this.document.activeElement; + if (!elem) { + throw new lazy.error.NoSuchElementError(); + } + + return elem; + } + + /** + * Return the accessible label for a given element. + */ + async getComputedLabel(options = {}) { + const { elem } = options; + + const accessible = await lazy.accessibility.getAccessible(elem); + if (!accessible) { + return null; + } + + return accessible.name; + } + + /** + * Return the accessible role for a given element. + */ + async getComputedRole(options = {}) { + const { elem } = options; + + const accessible = await lazy.accessibility.getAccessible(elem); + if (!accessible) { + // If it's not in the a11y tree, it's probably presentational. + return "none"; + } + + return accessible.computedARIARole; + } + + /** + * Get the value of an attribute for the given element. + */ + async getElementAttribute(options = {}) { + const { name, elem } = options; + + if (lazy.element.isBooleanAttribute(elem, name)) { + if (elem.hasAttribute(name)) { + return "true"; + } + return null; + } + return elem.getAttribute(name); + } + + /** + * Get the value of a property for the given element. + */ + async getElementProperty(options = {}) { + const { name, elem } = options; + + // Waive Xrays to get unfiltered access to the untrusted element. + const el = Cu.waiveXrays(elem); + return typeof el[name] != "undefined" ? el[name] : null; + } + + /** + * Get the position and dimensions of the element. + */ + async getElementRect(options = {}) { + const { elem } = options; + + const rect = elem.getBoundingClientRect(); + return { + x: rect.x + this.document.defaultView.pageXOffset, + y: rect.y + this.document.defaultView.pageYOffset, + width: rect.width, + height: rect.height, + }; + } + + /** + * Get the tagName for the given element. + */ + async getElementTagName(options = {}) { + const { elem } = options; + + return elem.tagName.toLowerCase(); + } + + /** + * Get the text content for the given element. + */ + async getElementText(options = {}) { + const { elem } = options; + + try { + return lazy.atom.getElementText(elem, this.document.defaultView); + } catch (e) { + lazy.logger.warn(`Atom getElementText failed: "${e.message}"`); + + // Fallback in case the atom implementation is broken. + // As known so far this only happens for XML documents (bug 1794099). + return elem.textContent; + } + } + + /** + * Get the value of a css property for the given element. + */ + async getElementValueOfCssProperty(options = {}) { + const { name, elem } = options; + + const style = this.document.defaultView.getComputedStyle(elem); + return style.getPropertyValue(name); + } + + /** + * Get the source of the current browsing context's document. + */ + async getPageSource() { + return this.document.documentElement.outerHTML; + } + + /** + * Returns the rect of the element to screenshot. + * + * Because the screen capture takes place in the parent process the dimensions + * for the screenshot have to be determined in the appropriate child process. + * + * Also it takes care of scrolling an element into view if requested. + * + * @param {object} options + * @param {Element} options.elem + * Optional element to take a screenshot of. + * @param {boolean=} options.full + * True to take a screenshot of the entire document element. + * Defaults to true. + * @param {boolean=} options.scroll + * When elem is given, scroll it into view. + * Defaults to true. + * + * @returns {DOMRect} + * The area to take a snapshot from. + */ + async getScreenshotRect(options = {}) { + const { elem, full = true, scroll = true } = options; + const win = elem + ? this.document.defaultView + : this.browsingContext.top.window; + + let rect; + + if (elem) { + if (scroll) { + lazy.element.scrollIntoView(elem); + } + rect = this.getElementRect({ elem }); + } else if (full) { + const docEl = win.document.documentElement; + rect = new DOMRect(0, 0, docEl.scrollWidth, docEl.scrollHeight); + } else { + // viewport + rect = new DOMRect( + win.pageXOffset, + win.pageYOffset, + win.innerWidth, + win.innerHeight + ); + } + + return rect; + } + + /** + * Return the shadowRoot attached to an element + */ + async getShadowRoot(options = {}) { + const { elem } = options; + + return lazy.element.getShadowRoot(elem); + } + + /** + * Determine the element displayedness of the given web element. + */ + async isElementDisplayed(options = {}) { + const { capabilities, elem } = options; + + return lazy.interaction.isElementDisplayed( + elem, + capabilities["moz:accessibilityChecks"] + ); + } + + /** + * Check if element is enabled. + */ + async isElementEnabled(options = {}) { + const { capabilities, elem } = options; + + return lazy.interaction.isElementEnabled( + elem, + capabilities["moz:accessibilityChecks"] + ); + } + + /** + * Determine whether the referenced element is selected or not. + */ + async isElementSelected(options = {}) { + const { capabilities, elem } = options; + + return lazy.interaction.isElementSelected( + elem, + capabilities["moz:accessibilityChecks"] + ); + } + + /** + * Perform a series of grouped actions at the specified points in time. + * + * @param {object} options + * @param {object} options.actions + * Array of objects with each representing an action sequence. + * @param {object} options.capabilities + * Object with a list of WebDriver session capabilities. + */ + async performActions(options = {}) { + const { actions, capabilities } = options; + if (this.actionState === null) { + this.actionState = new lazy.action.State({ + specCompatPointerOrigin: + !capabilities["moz:useNonSpecCompliantPointerOrigin"], + }); + } + let actionChain = lazy.action.Chain.fromJSON(this.actionState, actions); + + await actionChain.dispatch(this.actionState, this.document.defaultView); + // Terminate the current wheel transaction if there is one. Wheel + // transactions should not live longer than a single action chain. + ChromeUtils.endWheelTransaction(); + } + + /** + * The release actions command is used to release all the keys and pointer + * buttons that are currently depressed. This causes events to be fired + * as if the state was released by an explicit series of actions. It also + * clears all the internal state of the virtual devices. + */ + async releaseActions() { + if (this.actionState === null) { + return; + } + await this.actionState.release(this.document.defaultView); + this.actionState = null; + } + + /* + * Send key presses to element after focusing on it. + */ + async sendKeysToElement(options = {}) { + const { capabilities, elem, text } = options; + + const opts = { + strictFileInteractability: capabilities.strictFileInteractability, + accessibilityChecks: capabilities["moz:accessibilityChecks"], + webdriverClick: capabilities["moz:webdriverClick"], + }; + + return lazy.interaction.sendKeysToElement(elem, text, opts); + } + + /** + * Perform a single tap. + */ + async singleTap(options = {}) { + const { capabilities, elem, x, y } = options; + return this.legacyactions.singleTap(elem, x, y, capabilities); + } + + /** + * Switch to the specified frame. + * + * @param {object=} options + * @param {(number|Element)=} options.id + * If it's a number treat it as the index for all the existing frames. + * If it's an Element switch to this specific frame. + * If not specified or `null` switch to the top-level browsing context. + */ + async switchToFrame(options = {}) { + const { id } = options; + + const childContexts = this.browsingContext.children; + let browsingContext; + + if (id == null) { + browsingContext = this.browsingContext.top; + } else if (typeof id == "number") { + if (id < 0 || id >= childContexts.length) { + throw new lazy.error.NoSuchFrameError( + `Unable to locate frame with index: ${id}` + ); + } + browsingContext = childContexts[id]; + } else { + const context = childContexts.find(context => { + return context.embedderElement === id; + }); + if (!context) { + throw new lazy.error.NoSuchFrameError( + `Unable to locate frame for element: ${id}` + ); + } + browsingContext = context; + } + + // For in-process iframes the window global is lazy-loaded for optimization + // reasons. As such force the currentWindowGlobal to be created so we always + // have a window (bug 1691348). + browsingContext.window; + + return { browsingContextId: browsingContext.id }; + } + + /** + * Switch to the parent frame. + */ + async switchToParentFrame() { + const browsingContext = this.browsingContext.parent || this.browsingContext; + + return { browsingContextId: browsingContext.id }; + } +} diff --git a/remote/marionette/actors/MarionetteCommandsParent.sys.mjs b/remote/marionette/actors/MarionetteCommandsParent.sys.mjs new file mode 100644 index 0000000000..2ab202acc0 --- /dev/null +++ b/remote/marionette/actors/MarionetteCommandsParent.sys.mjs @@ -0,0 +1,383 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + capture: "chrome://remote/content/shared/Capture.sys.mjs", + error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +export class MarionetteCommandsParent extends JSWindowActorParent { + actorCreated() { + this._resolveDialogOpened = null; + } + + dialogOpenedPromise() { + return new Promise(resolve => { + this._resolveDialogOpened = resolve; + }); + } + + async sendQuery(name, data) { + // return early if a dialog is opened + const result = await Promise.race([ + super.sendQuery(name, data), + this.dialogOpenedPromise(), + ]).finally(() => { + this._resolveDialogOpened = null; + }); + + if ("error" in result) { + throw lazy.error.WebDriverError.fromJSON(result.error); + } else { + return result.data; + } + } + + notifyDialogOpened() { + if (this._resolveDialogOpened) { + this._resolveDialogOpened({ data: null }); + } + } + + // Proxying methods for WebDriver commands + + clearElement(webEl) { + return this.sendQuery("MarionetteCommandsParent:clearElement", { + elem: webEl, + }); + } + + clickElement(webEl, capabilities) { + return this.sendQuery("MarionetteCommandsParent:clickElement", { + elem: webEl, + capabilities: capabilities.toJSON(), + }); + } + + async executeScript(script, args, opts) { + return this.sendQuery("MarionetteCommandsParent:executeScript", { + script, + args, + opts, + }); + } + + findElement(strategy, selector, opts) { + return this.sendQuery("MarionetteCommandsParent:findElement", { + strategy, + selector, + opts, + }); + } + + findElements(strategy, selector, opts) { + return this.sendQuery("MarionetteCommandsParent:findElements", { + strategy, + selector, + opts, + }); + } + + async getShadowRoot(webEl) { + return this.sendQuery("MarionetteCommandsParent:getShadowRoot", { + elem: webEl, + }); + } + + async getActiveElement() { + return this.sendQuery("MarionetteCommandsParent:getActiveElement"); + } + + async getComputedLabel(webEl) { + return this.sendQuery("MarionetteCommandsParent:getComputedLabel", { + elem: webEl, + }); + } + + async getComputedRole(webEl) { + return this.sendQuery("MarionetteCommandsParent:getComputedRole", { + elem: webEl, + }); + } + + async getElementAttribute(webEl, name) { + return this.sendQuery("MarionetteCommandsParent:getElementAttribute", { + elem: webEl, + name, + }); + } + + async getElementProperty(webEl, name) { + return this.sendQuery("MarionetteCommandsParent:getElementProperty", { + elem: webEl, + name, + }); + } + + async getElementRect(webEl) { + return this.sendQuery("MarionetteCommandsParent:getElementRect", { + elem: webEl, + }); + } + + async getElementTagName(webEl) { + return this.sendQuery("MarionetteCommandsParent:getElementTagName", { + elem: webEl, + }); + } + + async getElementText(webEl) { + return this.sendQuery("MarionetteCommandsParent:getElementText", { + elem: webEl, + }); + } + + async getElementValueOfCssProperty(webEl, name) { + return this.sendQuery( + "MarionetteCommandsParent:getElementValueOfCssProperty", + { + elem: webEl, + name, + } + ); + } + + async getPageSource() { + return this.sendQuery("MarionetteCommandsParent:getPageSource"); + } + + async isElementDisplayed(webEl, capabilities) { + return this.sendQuery("MarionetteCommandsParent:isElementDisplayed", { + capabilities: capabilities.toJSON(), + elem: webEl, + }); + } + + async isElementEnabled(webEl, capabilities) { + return this.sendQuery("MarionetteCommandsParent:isElementEnabled", { + capabilities: capabilities.toJSON(), + elem: webEl, + }); + } + + async isElementSelected(webEl, capabilities) { + return this.sendQuery("MarionetteCommandsParent:isElementSelected", { + capabilities: capabilities.toJSON(), + elem: webEl, + }); + } + + async sendKeysToElement(webEl, text, capabilities) { + return this.sendQuery("MarionetteCommandsParent:sendKeysToElement", { + capabilities: capabilities.toJSON(), + elem: webEl, + text, + }); + } + + async performActions(actions, capabilities) { + return this.sendQuery("MarionetteCommandsParent:performActions", { + actions, + capabilities: capabilities.toJSON(), + }); + } + + async releaseActions() { + return this.sendQuery("MarionetteCommandsParent:releaseActions"); + } + + async singleTap(webEl, x, y, capabilities) { + return this.sendQuery("MarionetteCommandsParent:singleTap", { + capabilities: capabilities.toJSON(), + elem: webEl, + x, + y, + }); + } + + async switchToFrame(id) { + const { browsingContextId } = await this.sendQuery( + "MarionetteCommandsParent:switchToFrame", + { id } + ); + + return { + browsingContext: BrowsingContext.get(browsingContextId), + }; + } + + async switchToParentFrame() { + const { browsingContextId } = await this.sendQuery( + "MarionetteCommandsParent:switchToParentFrame" + ); + + return { + browsingContext: BrowsingContext.get(browsingContextId), + }; + } + + async takeScreenshot(webEl, format, full, scroll) { + const rect = await this.sendQuery( + "MarionetteCommandsParent:getScreenshotRect", + { + elem: webEl, + full, + scroll, + } + ); + + // If no element has been specified use the top-level browsing context. + // Otherwise use the browsing context from the currently selected frame. + const browsingContext = webEl + ? this.browsingContext + : this.browsingContext.top; + + let canvas = await lazy.capture.canvas( + browsingContext.topChromeWindow, + browsingContext, + rect.x, + rect.y, + rect.width, + rect.height + ); + + switch (format) { + case lazy.capture.Format.Hash: + return lazy.capture.toHash(canvas); + + case lazy.capture.Format.Base64: + return lazy.capture.toBase64(canvas); + + default: + throw new TypeError(`Invalid capture format: ${format}`); + } + } +} + +/** + * Proxy that will dynamically create MarionetteCommands actors for a dynamically + * provided browsing context until the method can be fully executed by the + * JSWindowActor pair. + * + * @param {function(): BrowsingContext} browsingContextFn + * A function that returns the reference to the browsing context for which + * the query should run. + */ +export function getMarionetteCommandsActorProxy(browsingContextFn) { + const MAX_ATTEMPTS = 10; + + /** + * Methods which modify the content page cannot be retried safely. + * See Bug 1673345. + */ + const NO_RETRY_METHODS = [ + "clickElement", + "executeScript", + "performActions", + "releaseActions", + "sendKeysToElement", + "singleTap", + ]; + + return new Proxy( + {}, + { + get(target, methodName) { + return async (...args) => { + let attempts = 0; + while (true) { + try { + const browsingContext = browsingContextFn(); + if (!browsingContext) { + throw new DOMException( + "No BrowsingContext found", + "NoBrowsingContext" + ); + } + + // TODO: Scenarios where the window/tab got closed and + // currentWindowGlobal is null will be handled in Bug 1662808. + const actor = + browsingContext.currentWindowGlobal.getActor( + "MarionetteCommands" + ); + + const result = await actor[methodName](...args); + return result; + } catch (e) { + if (!["AbortError", "InactiveActor"].includes(e.name)) { + // Only retry when the JSWindowActor pair gets destroyed, or + // gets inactive eg. when the page is moved into bfcache. + throw e; + } + + if (NO_RETRY_METHODS.includes(methodName)) { + const browsingContextId = browsingContextFn()?.id; + lazy.logger.trace( + `[${browsingContextId}] Querying "${methodName}" failed with` + + ` ${e.name}, returning "null" as fallback` + ); + return null; + } + + if (++attempts > MAX_ATTEMPTS) { + const browsingContextId = browsingContextFn()?.id; + lazy.logger.trace( + `[${browsingContextId}] Querying "${methodName} "` + + `reached the limit of retry attempts (${MAX_ATTEMPTS})` + ); + throw e; + } + + lazy.logger.trace( + `Retrying "${methodName}", attempt: ${attempts}` + ); + } + } + }; + }, + } + ); +} + +/** + * Register the MarionetteCommands actor that holds all the commands. + */ +export function registerCommandsActor() { + try { + ChromeUtils.registerWindowActor("MarionetteCommands", { + kind: "JSWindowActor", + parent: { + esModuleURI: + "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs", + }, + child: { + esModuleURI: + "chrome://remote/content/marionette/actors/MarionetteCommandsChild.sys.mjs", + }, + + allFrames: true, + includeChrome: true, + }); + } catch (e) { + if (e.name === "NotSupportedError") { + lazy.logger.warn(`MarionetteCommands actor is already registered!`); + } else { + throw e; + } + } +} + +export function unregisterCommandsActor() { + ChromeUtils.unregisterWindowActor("MarionetteCommands"); +} diff --git a/remote/marionette/actors/MarionetteEventsChild.sys.mjs b/remote/marionette/actors/MarionetteEventsChild.sys.mjs new file mode 100644 index 0000000000..031215e5fc --- /dev/null +++ b/remote/marionette/actors/MarionetteEventsChild.sys.mjs @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* eslint-disable no-restricted-globals */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + event: "chrome://remote/content/marionette/event.sys.mjs", + Log: "chrome://remote/content/shared/Log.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +export class MarionetteEventsChild extends JSWindowActorChild { + get innerWindowId() { + return this.manager.innerWindowId; + } + + actorCreated() { + // Prevent the logger from being created if the current log level + // isn't set to 'trace'. This is important for a faster content process + // creation when Marionette is running. + if (lazy.Log.isTraceLevelOrOrMore) { + lazy.logger.trace( + `[${this.browsingContext.id}] MarionetteEvents actor created ` + + `for window id ${this.innerWindowId}` + ); + } + } + + handleEvent({ target, type }) { + if (!Services.cpmm.sharedData.get("MARIONETTE_EVENTS_ENABLED")) { + // The parent process will set MARIONETTE_EVENTS_ENABLED to false when + // the Marionette session ends to avoid unnecessary inter process + // communications + return; + } + + // Ignore invalid combinations of load events and document's readyState. + if ( + (type === "DOMContentLoaded" && target.readyState != "interactive") || + (type === "pageshow" && target.readyState != "complete") + ) { + lazy.logger.warn( + `Ignoring event '${type}' because document has an invalid ` + + `readyState of '${target.readyState}'.` + ); + return; + } + + switch (type) { + case "beforeunload": + case "DOMContentLoaded": + case "hashchange": + case "pagehide": + case "pageshow": + case "popstate": + this.sendAsyncMessage("MarionetteEventsChild:PageLoadEvent", { + browsingContext: this.browsingContext, + documentURI: target.documentURI, + readyState: target.readyState, + type, + windowId: this.innerWindowId, + }); + break; + + // Listen for click event to indicate one click has happened, so actions + // code can send dblclick event + case "click": + lazy.event.DoubleClickTracker.setClick(); + break; + case "dblclick": + case "unload": + lazy.event.DoubleClickTracker.resetClick(); + break; + } + } +} diff --git a/remote/marionette/actors/MarionetteEventsParent.sys.mjs b/remote/marionette/actors/MarionetteEventsParent.sys.mjs new file mode 100644 index 0000000000..4211f99e59 --- /dev/null +++ b/remote/marionette/actors/MarionetteEventsParent.sys.mjs @@ -0,0 +1,115 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs", + + Log: "chrome://remote/content/shared/Log.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +// Singleton to allow forwarding events to registered listeners. +export const EventDispatcher = { + init() { + lazy.EventEmitter.decorate(this); + }, +}; + +EventDispatcher.init(); + +export class MarionetteEventsParent extends JSWindowActorParent { + async receiveMessage(msg) { + const { name, data } = msg; + + let rv; + switch (name) { + case "MarionetteEventsChild:PageLoadEvent": + EventDispatcher.emit("page-load", data); + break; + } + + return rv; + } +} + +// Flag to check if the MarionetteEvents actors have already been registed. +let eventsActorRegistered = false; + +/** + * Register Events actors to listen for page load events via EventDispatcher. + */ +function registerEventsActor() { + if (eventsActorRegistered) { + return; + } + + try { + // Register the JSWindowActor pair for events as used by Marionette + ChromeUtils.registerWindowActor("MarionetteEvents", { + kind: "JSWindowActor", + parent: { + esModuleURI: + "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs", + }, + child: { + esModuleURI: + "chrome://remote/content/marionette/actors/MarionetteEventsChild.sys.mjs", + events: { + beforeunload: { capture: true }, + DOMContentLoaded: { mozSystemGroup: true }, + hashchange: { mozSystemGroup: true }, + pagehide: { mozSystemGroup: true }, + pageshow: { mozSystemGroup: true }, + // popstate doesn't bubble, as such use capturing phase + popstate: { capture: true, mozSystemGroup: true }, + + click: {}, + dblclick: {}, + unload: { capture: true, createActor: false }, + }, + }, + + allFrames: true, + includeChrome: true, + }); + + eventsActorRegistered = true; + } catch (e) { + if (e.name === "NotSupportedError") { + lazy.logger.warn(`MarionetteEvents actor is already registered!`); + } else { + throw e; + } + } +} + +/** + * Enable MarionetteEvents actors to start forwarding page load events from the + * child actor to the parent actor. Register the MarionetteEvents actor if necessary. + */ +export function enableEventsActor() { + // sharedData is replicated across processes and will be checked by + // MarionetteEventsChild before forward events to the parent actor. + Services.ppmm.sharedData.set("MARIONETTE_EVENTS_ENABLED", true); + // Request to immediately flush the data to the content processes to avoid races. + Services.ppmm.sharedData.flush(); + + registerEventsActor(); +} + +/** + * Disable MarionetteEvents actors to stop forwarding page load events from the + * child actor to the parent actor. + */ +export function disableEventsActor() { + Services.ppmm.sharedData.set("MARIONETTE_EVENTS_ENABLED", false); + Services.ppmm.sharedData.flush(); +} diff --git a/remote/marionette/actors/MarionetteReftestChild.sys.mjs b/remote/marionette/actors/MarionetteReftestChild.sys.mjs new file mode 100644 index 0000000000..262ddb2f22 --- /dev/null +++ b/remote/marionette/actors/MarionetteReftestChild.sys.mjs @@ -0,0 +1,236 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + setTimeout: "resource://gre/modules/Timer.sys.mjs", + + Log: "chrome://remote/content/shared/Log.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "logger", () => + lazy.Log.get(lazy.Log.TYPES.MARIONETTE) +); + +/** + * Child JSWindowActor to handle navigation for reftests relying on marionette. + */ +export class MarionetteReftestChild extends JSWindowActorChild { + constructor() { + super(); + + // This promise will resolve with the URL recorded in the "load" event + // handler. This URL will not be impacted by any hash modification that + // might be performed by the test script. + // The harness should be loaded before loading any test page, so the actors + // should be registered before the "load" event is received for a test page. + this._loadedURLPromise = new Promise( + r => (this._resolveLoadedURLPromise = r) + ); + } + + handleEvent(event) { + if (event.type == "load") { + const url = event.target.location.href; + lazy.logger.debug(`Handle load event with URL ${url}`); + this._resolveLoadedURLPromise(url); + } + } + + actorCreated() { + lazy.logger.trace( + `[${this.browsingContext.id}] Reftest actor created ` + + `for window id ${this.manager.innerWindowId}` + ); + } + + async receiveMessage(msg) { + const { name, data } = msg; + + let result; + switch (name) { + case "MarionetteReftestParent:flushRendering": + result = await this.flushRendering(data); + break; + case "MarionetteReftestParent:reftestWait": + result = await this.reftestWait(data); + break; + } + return result; + } + + /** + * Wait for a reftest page to be ready for screenshots: + * - wait for the loadedURL to be available (see handleEvent) + * - check if the URL matches the expected URL + * - if present, wait for the "reftest-wait" classname to be removed from the + * document element + * + * @param {object} options + * @param {string} options.url + * The expected test page URL + * @param {boolean} options.useRemote + * True when using e10s + * @returns {boolean} + * Returns true when the correct page is loaded and ready for + * screenshots. Returns false if the page loaded bug does not have the + * expected URL. + */ + async reftestWait(options = {}) { + const { url, useRemote } = options; + const loadedURL = await this._loadedURLPromise; + if (loadedURL !== url) { + lazy.logger.debug( + `Window URL does not match the expected URL "${loadedURL}" !== "${url}"` + ); + return false; + } + + const documentElement = this.document.documentElement; + const hasReftestWait = documentElement.classList.contains("reftest-wait"); + + lazy.logger.debug("Waiting for event loop to spin"); + await new Promise(resolve => lazy.setTimeout(resolve, 0)); + + await this.paintComplete({ useRemote, ignoreThrottledAnimations: true }); + + if (hasReftestWait) { + const event = new this.document.defaultView.Event("TestRendered", { + bubbles: true, + }); + documentElement.dispatchEvent(event); + lazy.logger.info("Emitted TestRendered event"); + await this.reftestWaitRemoved(); + await this.paintComplete({ useRemote, ignoreThrottledAnimations: false }); + } + if ( + this.document.defaultView.innerWidth < documentElement.scrollWidth || + this.document.defaultView.innerHeight < documentElement.scrollHeight + ) { + lazy.logger.warn( + `${url} overflows viewport (width: ${documentElement.scrollWidth}, height: ${documentElement.scrollHeight})` + ); + } + return true; + } + + paintComplete({ useRemote, ignoreThrottledAnimations }) { + lazy.logger.debug("Waiting for rendering"); + let windowUtils = this.document.defaultView.windowUtils; + return new Promise(resolve => { + let maybeResolve = () => { + this.flushRendering({ ignoreThrottledAnimations }); + if (useRemote) { + // Flush display (paint) + lazy.logger.debug("Force update of layer tree"); + windowUtils.updateLayerTree(); + } + + if (windowUtils.isMozAfterPaintPending) { + lazy.logger.debug("isMozAfterPaintPending: true"); + this.document.defaultView.addEventListener( + "MozAfterPaint", + maybeResolve, + { + once: true, + } + ); + } else { + // resolve at the start of the next frame in case of leftover paints + lazy.logger.debug("isMozAfterPaintPending: false"); + this.document.defaultView.requestAnimationFrame(() => { + this.document.defaultView.requestAnimationFrame(resolve); + }); + } + }; + maybeResolve(); + }); + } + + reftestWaitRemoved() { + lazy.logger.debug("Waiting for reftest-wait removal"); + return new Promise(resolve => { + const documentElement = this.document.documentElement; + let observer = new this.document.defaultView.MutationObserver(() => { + if (!documentElement.classList.contains("reftest-wait")) { + observer.disconnect(); + lazy.logger.debug("reftest-wait removed"); + lazy.setTimeout(resolve, 0); + } + }); + if (documentElement.classList.contains("reftest-wait")) { + observer.observe(documentElement, { attributes: true }); + } else { + lazy.setTimeout(resolve, 0); + } + }); + } + + /** + * Ensure layout is flushed in each frame + * + * @param {object} options + * @param {boolean} options.ignoreThrottledAnimations Don't flush + * the layout of throttled animations. We can end up in a + * situation where flushing a throttled animation causes + * mozAfterPaint events even when all rendering we care about + * should have ceased. See + * https://searchfox.org/mozilla-central/rev/d58860eb739af613774c942c3bb61754123e449b/layout/tools/reftest/reftest-content.js#723-729 + * for more detail. + */ + flushRendering(options = {}) { + let { ignoreThrottledAnimations } = options; + lazy.logger.debug( + `flushRendering ignoreThrottledAnimations:${ignoreThrottledAnimations}` + ); + let anyPendingPaintsGeneratedInDescendants = false; + + let windowUtils = this.document.defaultView.windowUtils; + + function flushWindow(win) { + let utils = win.windowUtils; + let afterPaintWasPending = utils.isMozAfterPaintPending; + + let root = win.document.documentElement; + if (root) { + try { + if (ignoreThrottledAnimations) { + utils.flushLayoutWithoutThrottledAnimations(); + } else { + root.getBoundingClientRect(); + } + } catch (e) { + lazy.logger.error("flushWindow failed", e); + } + } + + if (!afterPaintWasPending && utils.isMozAfterPaintPending) { + anyPendingPaintsGeneratedInDescendants = true; + } + + for (let i = 0; i < win.frames.length; ++i) { + // Skip remote frames, flushRendering will be called on their individual + // MarionetteReftest actor via _recursiveFlushRendering performed from + // the topmost MarionetteReftest actor. + if (!Cu.isRemoteProxy(win.frames[i])) { + flushWindow(win.frames[i]); + } + } + } + flushWindow(this.document.defaultView); + + if ( + anyPendingPaintsGeneratedInDescendants && + !windowUtils.isMozAfterPaintPending + ) { + lazy.logger.error( + "Descendant frame generated a MozAfterPaint event, " + + "but the root document doesn't have one!" + ); + } + } +} diff --git a/remote/marionette/actors/MarionetteReftestParent.sys.mjs b/remote/marionette/actors/MarionetteReftestParent.sys.mjs new file mode 100644 index 0000000000..86d40aa650 --- /dev/null +++ b/remote/marionette/actors/MarionetteReftestParent.sys.mjs @@ -0,0 +1,85 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Parent JSWindowActor to handle navigation for reftests relying on marionette. + */ +export class MarionetteReftestParent extends JSWindowActorParent { + /** + * Wait for the expected URL to be loaded. + * + * @param {string} url + * The expected url. + * @param {boolean} useRemote + * True if tests are running with e10s. + * @returns {boolean} true if the page is fully loaded with the expected url, + * false otherwise. + */ + async reftestWait(url, useRemote) { + try { + const isCorrectUrl = await this.sendQuery( + "MarionetteReftestParent:reftestWait", + { + url, + useRemote, + } + ); + + if (isCorrectUrl) { + // Trigger flush rendering for all remote frames. + await this._flushRenderingInSubtree({ + ignoreThrottledAnimations: false, + }); + } + + return isCorrectUrl; + } catch (e) { + if (e.name === "AbortError") { + // If the query is aborted, the window global is being destroyed, most + // likely because a navigation happened. + return false; + } + + // Other errors should not be swallowed. + throw e; + } + } + + /** + * Call flushRendering on all browsing contexts in the subtree. + * Each actor will flush rendering in all the same process frames. + */ + async _flushRenderingInSubtree({ ignoreThrottledAnimations }) { + const browsingContext = this.manager.browsingContext; + const contexts = browsingContext.getAllBrowsingContextsInSubtree(); + + await Promise.all( + contexts.map(async context => { + if (context === browsingContext) { + // Skip the top browsing context, for which flushRendering is + // already performed via the initial reftestWait call. + return; + } + + const windowGlobal = context.currentWindowGlobal; + if (!windowGlobal) { + // Bail out if there is no window attached to the current context. + return; + } + + if (!windowGlobal.isProcessRoot) { + // Bail out if this window global is not a process root. + // MarionetteReftestChild::flushRendering will flush all same process + // frames, so we only need to call flushRendering on process roots. + return; + } + + const reftestActor = windowGlobal.getActor("MarionetteReftest"); + await reftestActor.sendQuery("MarionetteReftestParent:flushRendering", { + ignoreThrottledAnimations, + }); + }) + ); + } +} diff --git a/remote/marionette/addon.sys.mjs b/remote/marionette/addon.sys.mjs new file mode 100644 index 0000000000..d1234a0880 --- /dev/null +++ b/remote/marionette/addon.sys.mjs @@ -0,0 +1,133 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", +}); + +// from https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager#AddonInstall_errors +const ERRORS = { + [-1]: "ERROR_NETWORK_FAILURE: A network error occured.", + [-2]: "ERROR_INCORECT_HASH: The downloaded file did not match the expected hash.", + [-3]: "ERROR_CORRUPT_FILE: The file appears to be corrupt.", + [-4]: "ERROR_FILE_ACCESS: There was an error accessing the filesystem.", + [-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.", +}; + +async function installAddon(file) { + let install = await lazy.AddonManager.getInstallForFile(file, null, { + source: "internal", + }); + + if (install.error) { + throw new lazy.error.UnknownError(ERRORS[install.error]); + } + + return install.install().catch(err => { + throw new lazy.error.UnknownError(ERRORS[install.error]); + }); +} + +/** Installs addons by path and uninstalls by ID. */ +export class Addon { + /** + * Install a Firefox addon. + * + * If the addon is restartless, it can be used right away. Otherwise a + * restart is required. + * + * Temporary addons will automatically be uninstalled on shutdown and + * do not need to be signed, though they must be restartless. + * + * @param {string} path + * Full path to the extension package archive. + * @param {boolean=} temporary + * True to install the addon temporarily, false (default) otherwise. + * + * @returns {Promise.} + * Addon ID. + * + * @throws {UnknownError} + * If there is a problem installing the addon. + */ + static async install(path, temporary = false) { + let addon; + let file; + + try { + file = new lazy.FileUtils.File(path); + } catch (e) { + throw new lazy.error.UnknownError(`Expected absolute path: ${e}`, e); + } + + if (!file.exists()) { + throw new lazy.error.UnknownError(`No such file or directory: ${path}`); + } + + try { + if (temporary) { + addon = await lazy.AddonManager.installTemporaryAddon(file); + } else { + addon = await installAddon(file); + } + } catch (e) { + throw new lazy.error.UnknownError( + `Could not install add-on: ${path}: ${e.message}`, + e + ); + } + + return addon.id; + } + + /** + * Uninstall a Firefox addon. + * + * If the addon is restartless it will be uninstalled right away. + * Otherwise, Firefox must be restarted for the change to take effect. + * + * @param {string} id + * ID of the addon to uninstall. + * + * @returns {Promise} + * + * @throws {UnknownError} + * If there is a problem uninstalling the addon. + */ + static async uninstall(id) { + let candidate = await lazy.AddonManager.getAddonByID(id); + if (candidate === null) { + // `AddonManager.getAddonByID` never rejects but instead + // returns `null` if the requested addon cannot be found. + throw new lazy.error.UnknownError(`Addon ${id} is not installed`); + } + + return new Promise(resolve => { + let listener = { + onOperationCancelled: addon => { + if (addon.id === candidate.id) { + lazy.AddonManager.removeAddonListener(listener); + throw new lazy.error.UnknownError( + `Uninstall of ${candidate.id} has been canceled` + ); + } + }, + + onUninstalled: addon => { + if (addon.id === candidate.id) { + lazy.AddonManager.removeAddonListener(listener); + resolve(); + } + }, + }; + + lazy.AddonManager.addAddonListener(listener); + candidate.uninstall(); + }); + } +} diff --git a/remote/marionette/atom.sys.mjs b/remote/marionette/atom.sys.mjs new file mode 100644 index 0000000000..70aa53b91c --- /dev/null +++ b/remote/marionette/atom.sys.mjs @@ -0,0 +1,305 @@ +// Copyright 2011-2017 Software Freedom Conservancy +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** @namespace */ +export const atom = {}; + +// Follow the instructions to export all the atoms: +// https://firefox-source-docs.mozilla.org/testing/marionette/SeleniumAtoms.html +// +// Built from SHA1: a6b161a159c3d581b130f03a2e6e35f577f38dec + +atom.getElementText = function(element, window){return (function(){var k=this||self;function aa(a){return"string"==typeof a}function ba(a,b){a=a.split(".");var c=k;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b} +function ca(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; +else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function da(a,b,c){return a.call.apply(a.bind,arguments)}function ea(a,b,c){if(!a)throw Error();if(2b?null:"string"===typeof a?a.charAt(b):a[b]}function qa(a){return Array.prototype.concat.apply([],arguments)}function ra(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};function sa(a){var b=a.length-1;return 0<=b&&a.indexOf(" ",b)==b}var ta=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(a)[1]};function ua(a,b){return ab?1:0};var r;a:{var va=k.navigator;if(va){var wa=va.userAgent;if(wa){r=wa;break a}}r=""}function u(a){return-1!=r.indexOf(a)};function xa(){return u("Firefox")||u("FxiOS")}function ya(){return(u("Chrome")||u("CriOS"))&&!u("Edge")};function za(a){return String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})};function Aa(){return u("iPhone")&&!u("iPod")&&!u("iPad")};function Ba(a,b){var c=Ca;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var Da=u("Opera"),w=u("Trident")||u("MSIE"),Ea=u("Edge"),Fa=u("Gecko")&&!(-1!=r.toLowerCase().indexOf("webkit")&&!u("Edge"))&&!(u("Trident")||u("MSIE"))&&!u("Edge"),Ga=-1!=r.toLowerCase().indexOf("webkit")&&!u("Edge");function Ha(){var a=k.document;return a?a.documentMode:void 0}var Ia; +a:{var Ja="",Ka=function(){var a=r;if(Fa)return/rv:([^\);]+)(\)|;)/.exec(a);if(Ea)return/Edge\/([\d\.]+)/.exec(a);if(w)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(Ga)return/WebKit\/(\S+)/.exec(a);if(Da)return/(?:Version)[ \/]?(\S+)/.exec(a)}();Ka&&(Ja=Ka?Ka[1]:"");if(w){var La=Ha();if(null!=La&&La>parseFloat(Ja)){Ia=String(La);break a}}Ia=Ja}var Ca={}; +function Ma(a){return Ba(a,function(){for(var b=0,c=ta(String(Ia)).split("."),d=ta(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f]=|\s+|./g,Ua=/^\s/;function y(a,b){return a.b[a.a+(b||0)]}function z(a){return a.b[a.a++]}function Va(a){return a.b.length<=a.a};function Wa(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}Wa.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};Wa.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};Wa.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function Xa(a,b){this.width=a;this.height=b}Xa.prototype.aspectRatio=function(){return this.width/this.height};Xa.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Xa.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Xa.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ya(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function Za(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a} +function $a(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(w&&!(9<=Number(Na))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?ab(a,b):!c&&Za(e,b)?-1*bb(a,b):!d&&Za(f,a)?bb(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=A(a);c=d.createRange(); +c.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function bb(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return ab(b,a)}function ab(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function A(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function cb(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null} +function db(a){this.a=a||k.document||document}db.prototype.getElementsByTagName=function(a,b){return(b||this.a).getElementsByTagName(String(a))};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(x&&"title"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),x&&"title"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b} +function C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Oa&&"class"==b&&(b="className");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function eb(a,b,c,d,e){return(x?fb:gb).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)} +function fb(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=ib(a);if("*"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)"*"==a&&"!"==b.tagName||e.add(b);return e}jb(a,b,c,d,e);return e} +function gb(a,b,c,d,e){b.getElementsByName&&d&&"name"==c&&!w?(b=b.getElementsByName(d),p(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),p(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?jb(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),p(b,function(f){C(f,c,d)&&e.add(f)}));return e} +function kb(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=ib(a);if("*"!=g&&(f=ka(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ka(f,function(h){return C(h,c,d)}));p(f,function(h){"*"==g&&("!"==h.tagName||"*"==g&&1!=h.nodeType)||e.add(h)});return e}return lb(a,b,c,d,e)}function lb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e} +function jb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),jb(a,b,c,d,e)}function ib(a){if(a instanceof G){if(8==a.b)return"!";if(null===a.b)return"*"}return a.f()};function E(){this.b=this.a=null;this.l=0}function mb(a){this.f=a;this.a=this.b=null}function nb(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof Pa&&g instanceof Pa&&e.a==g.a?(e=c,c=c.a,b=b.a):0<$a(c.f,b.f)?(e=b,b=b.a):(e=c,c=c.a);(e.b=d)?d.a=e:a.a=e;d=e;f++}for(e=c||b;e;)e.b=d,d=d.a=e,f++,e=e.a;a.b=d;a.l=f;return a}function ob(a,b){b=new mb(b);b.a=a.a;a.b?a.a.b=b:a.a=a.b=b;a.a=b;a.l++} +E.prototype.add=function(a){a=new mb(a);a.b=this.b;this.a?this.b.a=a:this.a=this.b=a;this.b=a;this.l++};function pb(a){return(a=a.a)?a.f:null}function qb(a){return(a=pb(a))?B(a):""}function H(a,b){return new rb(a,!!b)}function rb(a,b){this.f=a;this.b=(this.s=b)?a.b:a.a;this.a=null}function I(a){var b=a.b;if(null==b)return null;var c=a.a=b;a.b=a.s?b.b:b.a;return c.f};function J(a){this.i=a;this.b=this.g=!1;this.f=null}function K(a){return"\n "+a.toString().split("\n").join("\n ")}function sb(a,b){a.g=b}function tb(a,b){a.b=b}function L(a,b){a=a.a(b);return a instanceof E?+qb(a):+a}function O(a,b){a=a.a(b);return a instanceof E?qb(a):""+a}function ub(a,b){a=a.a(b);return a instanceof E?!!a.l:!!a};function vb(a,b,c){J.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==wb&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,u:b}):this.f={name:b.f.name,u:c})}m(vb,J); +function xb(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof E&&c instanceof E){b=H(b);for(d=I(b);d;d=I(b))for(e=H(c),f=I(e);f;f=I(e))if(a(B(d),B(f)))return!0;return!1}if(b instanceof E||c instanceof E){b instanceof E?(e=b,d=c):(e=c,d=b);f=H(e);for(var g=typeof d,h=I(f);h;h=I(f)){switch(g){case "number":h=+B(h);break;case "boolean":h=!!B(h);break;case "string":h=B(h);break;default:throw Error("Illegal primitive type for comparison.");}if(e==b&&a(h,d)||e==c&&a(d,h))return!0}return!1}return e?"boolean"== +typeof b||"boolean"==typeof c?a(!!b,!!c):"number"==typeof b||"number"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}vb.prototype.a=function(a){return this.c.m(this.h,this.o,a)};vb.prototype.toString=function(){var a="Binary Expression: "+this.c;a+=K(this.h);return a+=K(this.o)};function yb(a,b,c,d){this.I=a;this.D=b;this.i=c;this.m=d}yb.prototype.toString=function(){return this.I};var zb={}; +function P(a,b,c,d){if(zb.hasOwnProperty(a))throw Error("Binary operator already created: "+a);a=new yb(a,b,c,d);return zb[a.toString()]=a}P("div",6,1,function(a,b,c){return L(a,c)/L(b,c)});P("mod",6,1,function(a,b,c){return L(a,c)%L(b,c)});P("*",6,1,function(a,b,c){return L(a,c)*L(b,c)});P("+",5,1,function(a,b,c){return L(a,c)+L(b,c)});P("-",5,1,function(a,b,c){return L(a,c)-L(b,c)});P("<",4,2,function(a,b,c){return xb(function(d,e){return d",4,2,function(a,b,c){return xb(function(d,e){return d>e},a,b,c)});P("<=",4,2,function(a,b,c){return xb(function(d,e){return d<=e},a,b,c)});P(">=",4,2,function(a,b,c){return xb(function(d,e){return d>=e},a,b,c)});var wb=P("=",3,2,function(a,b,c){return xb(function(d,e){return d==e},a,b,c,!0)});P("!=",3,2,function(a,b,c){return xb(function(d,e){return d!=e},a,b,c,!0)});P("and",2,2,function(a,b,c){return ub(a,c)&&ub(b,c)});P("or",1,2,function(a,b,c){return ub(a,c)||ub(b,c)});function Ab(a,b){if(b.a.length&&4!=a.i)throw Error("Primary expression must evaluate to nodeset if filter has predicate(s).");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}m(Ab,J);Ab.prototype.a=function(a){a=this.c.a(a);return Bb(this.h,a)};Ab.prototype.toString=function(){var a="Filter:"+K(this.c);return a+=K(this.h)};function Cb(a,b){if(b.lengtha.B)throw Error("Function "+a.j+" expects at most "+a.B+" arguments, "+b.length+" given");a.H&&p(b,function(c,d){if(4!=c.i)throw Error("Argument "+d+" to function "+a.j+" is not of type Nodeset: "+c);});J.call(this,a.i);this.v=a;this.c=b;sb(this,a.g||na(b,function(c){return c.g}));tb(this,a.G&&!b.length||a.F&&!!b.length||na(b,function(c){return c.b}))} +m(Cb,J);Cb.prototype.a=function(a){return this.v.m.apply(null,qa(a,this.c))};Cb.prototype.toString=function(){var a="Function: "+this.v;if(this.c.length){var b=ma(this.c,function(c,d){return c+K(d)},"Arguments:");a+=K(b)}return a};function Db(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}Db.prototype.toString=function(){return this.j};var Eb={}; +function Q(a,b,c,d,e,f,g,h){if(Eb.hasOwnProperty(a))throw Error("Function already created: "+a+".");Eb[a]=new Db(a,b,c,d,e,f,g,h)}Q("boolean",2,!1,!1,function(a,b){return ub(b,a)},1);Q("ceiling",1,!1,!1,function(a,b){return Math.ceil(L(b,a))},1);Q("concat",3,!1,!1,function(a,b){return ma(ra(arguments,1),function(c,d){return c+O(d,a)},"")},2,null);Q("contains",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return-1!=b.indexOf(a)},2);Q("count",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0); +Q("false",2,!1,!1,function(){return!1},0);Q("floor",1,!1,!1,function(a,b){return Math.floor(L(b,a))},1);Q("id",4,!1,!1,function(a,b){function c(h){if(x){var l=e.all[h];if(l){if(l.nodeType&&h==l.id)return l;if(l.length)return pa(l,function(v){return h==v.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=O(b,a).split(/\s+/);var f=[];p(a,function(h){h=c(h);!h||0<=ja(f,h)||f.push(h)});f.sort($a);var g=new E;p(f,function(h){g.add(h)});return g},1); +Q("lang",2,!1,!1,function(){return!1},1);Q("last",1,!0,!1,function(a){if(1!=arguments.length)throw Error("Function last expects ()");return a.f},0);Q("local-name",3,!1,!0,function(a,b){return(a=b?pb(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():""},0,1,!0);Q("name",3,!1,!0,function(a,b){return(a=b?pb(b.a(a)):a.a)?a.nodeName.toLowerCase():""},0,1,!0);Q("namespace-uri",3,!0,!1,function(){return""},0,1,!0); +Q("normalize-space",3,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);Q("not",2,!1,!1,function(a,b){return!ub(b,a)},1);Q("number",1,!1,!0,function(a,b){return b?L(b,a):+B(a.a)},0,1);Q("position",1,!0,!1,function(a){return a.b},0);Q("round",1,!1,!1,function(a,b){return Math.round(L(b,a))},1);Q("starts-with",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return 0==b.lastIndexOf(a,0)},2);Q("string",3,!1,!0,function(a,b){return b?O(b,a):B(a.a)},0,1); +Q("string-length",1,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).length},0,1);Q("substring",3,!1,!1,function(a,b,c,d){c=L(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?L(d,a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=O(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);Q("substring-after",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2); +Q("substring-before",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);Q("sum",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);Q("translate",3,!1,!1,function(a,b,c,d){b=O(b,a);c=O(c,a);var e=O(d,a);a={};for(d=0;da.length)throw Error("Unclosed literal string");return new Gb(a)} +function fc(a){var b=[];if(Mb(y(a.a))){var c=z(a.a);var d=y(a.a);if("/"==c&&(Va(a.a)||"."!=d&&".."!=d&&"@"!=d&&"*"!=d&&!/(?![0-9])[\w]/.test(d)))return new Kb;d=new Kb;T(a,"Missing next location step.");c=gc(a,c);b.push(c)}else{a:{c=y(a.a);d=c.charAt(0);switch(d){case "$":throw Error("Variable reference not allowed in HTML XPath");case "(":z(a.a);c=Zb(a);T(a,'unclosed "("');cc(a,")");break;case '"':case "'":c=ec(a);break;default:if(isNaN(+c))if(!Fb(c)&&/(?![0-9])[\w]/.test(d)&&"("==y(a.a,1)){c=z(a.a); +c=Eb[c]||null;z(a.a);for(d=[];")"!=y(a.a);){T(a,"Missing function argument list.");d.push(Zb(a));if(","!=y(a.a))break;z(a.a)}T(a,"Unclosed function argument list.");dc(a);c=new Cb(c,d)}else{c=null;break a}else c=new Hb(+z(a.a))}"["==y(a.a)&&(d=new Pb(hc(a)),c=new Ab(c,d))}if(c)if(Mb(y(a.a)))d=c;else return c;else c=gc(a,"/"),d=new Lb,b.push(c)}for(;Mb(y(a.a));)c=z(a.a),T(a,"Missing next location step."),c=gc(a,c),b.push(c);return new Ib(d,b)} +function gc(a,b){if("/"!=b&&"//"!=b)throw Error('Step op should be "/" or "//"');if("."==y(a.a)){var c=new R(Vb,new G("node"));z(a.a);return c}if(".."==y(a.a))return c=new R(Ub,new G("node")),z(a.a),c;if("@"==y(a.a)){var d=Jb;z(a.a);T(a,"Missing attribute name")}else if("::"==y(a.a,1)){if(!/(?![0-9])[\w]/.test(y(a.a).charAt(0)))throw Error("Bad token: "+z(a.a));var e=z(a.a);d=Tb[e]||null;if(!d)throw Error("No axis with name: "+e);z(a.a);T(a,"Missing node name")}else d=Qb;e=y(a.a);if(/(?![0-9])[\w\*]/.test(e.charAt(0)))if("("== +y(a.a,1)){if(!Fb(e))throw Error("Invalid node type: "+e);e=z(a.a);if(!Fb(e))throw Error("Invalid type name: "+e);cc(a,"(");T(a,"Bad nodetype");var f=y(a.a).charAt(0),g=null;if('"'==f||"'"==f)g=ec(a);T(a,"Bad nodetype");dc(a);e=new G(e,g)}else if(e=z(a.a),f=e.indexOf(":"),-1==f)e=new F(e);else{g=e.substring(0,f);if("*"==g)var h="*";else if(h=a.b(g),!h)throw Error("Namespace prefix not declared: "+g);e=e.substr(f+1);e=new F(e,h)}else throw Error("Bad token: "+z(a.a));a=new Pb(hc(a),d.s);return c||new R(d, +e,a,"//"==b)}function hc(a){for(var b=[];"["==y(a.a);){z(a.a);T(a,"Missing predicate expression.");var c=Zb(a);b.push(c);T(a,"Unclosed predicate expression.");cc(a,"]")}return b}function bc(a){if("-"==y(a.a))return z(a.a),new Wb(bc(a));var b=fc(a);if("|"!=y(a.a))a=b;else{for(b=[b];"|"==z(a.a);)T(a,"Missing next union location path."),b.push(fc(a));a.a.a--;a=new Xb(b)}return a};function ic(a){switch(a.nodeType){case 1:return ha(jc,a);case 9:return ic(a.documentElement);case 11:case 10:case 6:case 12:return kc;default:return a.parentNode?ic(a.parentNode):kc}}function kc(){return null}function jc(a,b){if(a.prefix==b)return a.namespaceURI||"http://www.w3.org/1999/xhtml";var c=a.getAttributeNode("xmlns:"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?jc(a.parentNode,b):null};function lc(a,b){if(!a.length)throw Error("Empty XPath expression.");a=Sa(a);if(Va(a))throw Error("Invalid XPath expression.");b?"function"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Zb(new Yb(a,b));if(!Va(a))throw Error("Bad token: "+z(a));this.evaluate=function(d,e){d=c.a(new ia(d));return new U(d,e)}} +function U(a,b){if(0==b)if(a instanceof E)b=4;else if("string"==typeof a)b=2;else if("number"==typeof a)b=1;else if("boolean"==typeof a)b=3;else throw Error("Unexpected evaluation result.");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error("value could not be converted to the specified type");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?qb(a):""+a;break;case 1:this.numberValue=a instanceof E?+qb(a):+a;break;case 3:this.booleanValue=a instanceof E?0=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error("snapshotItem called with wrong result type");return g>=d.length|| +0>g?null:d[g]}}U.ANY_TYPE=0;U.NUMBER_TYPE=1;U.STRING_TYPE=2;U.BOOLEAN_TYPE=3;U.UNORDERED_NODE_ITERATOR_TYPE=4;U.ORDERED_NODE_ITERATOR_TYPE=5;U.UNORDERED_NODE_SNAPSHOT_TYPE=6;U.ORDERED_NODE_SNAPSHOT_TYPE=7;U.ANY_UNORDERED_NODE_TYPE=8;U.FIRST_ORDERED_NODE_TYPE=9;function mc(a){this.lookupNamespaceURI=ic(a)} +function nc(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=U,c.evaluate=function(d,e,f,g){return(new lc(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new lc(d,e)},c.createNSResolver=function(d){return new mc(d)}}ba("wgxpath.install",nc);ba("wgxpath.install",nc);var oc={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400", +darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc", +ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a", +lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1", +moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57", +seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var pc="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),qc=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,rc=/^#(?:[0-9a-f]{3}){1,2}$/i,sc=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,tc=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function uc(a,b){this.code=a;this.a=V[a]||vc;this.message=b||"";a=this.a.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}m(uc,Error);var vc="unknown error",V={15:"element not selectable",11:"element not visible"};V[31]=vc;V[30]=vc;V[24]="invalid cookie domain";V[29]="invalid element coordinates";V[12]="invalid element state"; +V[32]="invalid selector";V[51]="invalid selector";V[52]="invalid selector";V[17]="javascript error";V[405]="unsupported operation";V[34]="move target out of bounds";V[27]="no such alert";V[7]="no such element";V[8]="no such frame";V[23]="no such window";V[28]="script timeout";V[33]="session not created";V[10]="stale element reference";V[21]="timeout";V[25]="unable to set cookie";V[26]="unexpected alert open";V[13]=vc;V[9]="unknown command";var wc=xa(),xc=Aa()||u("iPod"),yc=u("iPad"),zc=u("Android")&&!(ya()||xa()||u("Opera")||u("Silk")),Ac=ya(),Bc=u("Safari")&&!(ya()||u("Coast")||u("Opera")||u("Edge")||u("Edg/")||u("OPR")||xa()||u("Silk")||u("Android"))&&!(Aa()||u("iPad")||u("iPod"));function Cc(a){return(a=a.exec(r))?a[1]:""}(function(){if(wc)return Cc(/Firefox\/([0-9.]+)/);if(w||Ea||Da)return Ia;if(Ac)return Aa()||u("iPad")||u("iPod")?Cc(/CriOS\/([0-9.]+)/):Cc(/Chrome\/([0-9.]+)/);if(Bc&&!(Aa()||u("iPad")||u("iPod")))return Cc(/Version\/([0-9.]+)/);if(xc||yc){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(r);if(a)return a[1]+"."+a[2]}else if(zc)return(a=Cc(/Android\s+([0-9.]+)/))?a:Cc(/Version\/([0-9.]+)/);return""})();var Dc=w&&!(9<=Number(Na));function W(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Ec=function(){var a={K:"http://www.w3.org/2000/svg"};return function(b){return a[b]||null}}(); +function Fc(a,b){var c=A(a);if(!c.documentElement)return null;(w||zc)&&nc(c?c.parentWindow||c.defaultView:window);try{var d=c.createNSResolver?c.createNSResolver(c.documentElement):Ec;if(w&&!Ma(7))return c.evaluate.call(c,b,a,d,9,null);if(!w||9<=Number(Na)){for(var e={},f=c.getElementsByTagName("*"),g=0;g=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(tc))if(b= +Number(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=oc[b.toLowerCase()];if(!c&&(c="#"==b.charAt(0)?b:"#"+b,4==c.length&&(c=c.replace(qc,"#$1$1$2$2$3$3")),!rc.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a} +function Kc(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&"function"==ca(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?void 0!==d?d:null:(a=Jc(a))?Kc(a,b):null} +function Lc(a,b,c){function d(g){var h=Mc(g);return 0=D.a+D.width;D=e.c>=D.b+D.height;if(M&&"hidden"==n.x||D&&"hidden"==n.y)return Z;if(M&&"visible"!=n.x||D&&"visible"!=n.y){if(v&&(n=d(a),e.f>=g.scrollWidth-n.x||e.a>=g.scrollHeight-n.y))return Z;e=Nc(a);return e==Z?Z:"scroll"}}}return"none"} +function Mc(a){var b=Oc(a);if(b)return b.rect;if(W(a,"HTML"))return a=A(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a="CSS1Compat"==a.compatMode?a.documentElement:a.body,a=new Xa(a.clientWidth,a.clientHeight),new X(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new X(0,0,0,0)}b=new X(c.left,c.top,c.right-c.left,c.bottom-c.top);w&&a.ownerDocument.body&&(a=A(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop); +return b}function Oc(a){var b=W(a,"MAP");if(!b&&!W(a,"AREA"))return null;var c=b?a:W(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=Gc('/descendant::*[@usemap = "#'+c.name+'"]',A(c)))&&(e=Mc(d),b||"default"==a.shape.toLowerCase()||(a=Sc(a),b=Math.min(Math.max(a.a,0),e.width),c=Math.min(Math.max(a.b,0),e.height),e=new X(b+e.a,c+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{image:d,rect:e||new X(0,0,0,0)}} +function Sc(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4==a.length){b=a[0];var c=a[1];return new X(b,c,a[2]-b,a[3]-c)}if("circle"==b&&3==a.length)return b=a[2],new X(a[0]-b,a[1]-b,2*b,2*b);if("poly"==b&&2b?null:"string"===typeof a?a.charAt(b):a[b]}function la(a){return Array.prototype.concat.apply([],arguments)}function ma(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};var t;a:{var na=k.navigator;if(na){var oa=na.userAgent;if(oa){t=oa;break a}}t=""}function u(a){return-1!=t.indexOf(a)};function pa(){return u("Firefox")||u("FxiOS")}function qa(){return(u("Chrome")||u("CriOS"))&&!u("Edge")};function ra(){return u("iPhone")&&!u("iPod")&&!u("iPad")};var sa=u("Opera"),v=u("Trident")||u("MSIE"),ta=u("Edge"),ua=u("Gecko")&&!(-1!=t.toLowerCase().indexOf("webkit")&&!u("Edge"))&&!(u("Trident")||u("MSIE"))&&!u("Edge"),va=-1!=t.toLowerCase().indexOf("webkit")&&!u("Edge");function wa(){var a=k.document;return a?a.documentMode:void 0}var xa; +a:{var ya="",za=function(){var a=t;if(ua)return/rv:([^\);]+)(\)|;)/.exec(a);if(ta)return/Edge\/([\d\.]+)/.exec(a);if(v)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(va)return/WebKit\/(\S+)/.exec(a);if(sa)return/(?:Version)[ \/]?(\S+)/.exec(a)}();za&&(ya=za?za[1]:"");if(v){var Aa=wa();if(null!=Aa&&Aa>parseFloat(ya)){xa=String(Aa);break a}}xa=ya}var Ba;Ba=k.document&&v?wa():void 0;var w=v&&!(9<=Number(Ba)),Ca=v&&!(8<=Number(Ba));function y(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function Da(a,b){var c=Ca&&"href"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new y(b,a,b.nodeName,c)};function Ea(a){this.b=a;this.a=0}function Fa(a){a=a.match(Ga);for(var b=0;b]=|\s+|./g,Ha=/^\s/;function z(a,b){return a.b[a.a+(b||0)]}function A(a){return a.b[a.a++]}function Ia(a){return a.b.length<=a.a};function Ja(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function Ka(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a} +function La(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(v&&!(9<=Number(Ba))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ma(a,b):!c&&Ka(e,b)?-1*Na(a,b):!d&&Ka(f,a)?Na(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=9==a.nodeType? +a:a.ownerDocument||a.document;c=d.createRange();c.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function Na(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return Ma(b,a)}function Ma(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function Oa(a,b){for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(w&&"title"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),w&&"title"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b} +function C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ca&&"class"==b&&(b="className");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function D(a,b,c,d,e){return(w?Pa:Qa).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)} +function Pa(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=Ra(a);if("*"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)"*"==a&&"!"==b.tagName||e.add(b);return e}Sa(a,b,c,d,e);return e} +function Qa(a,b,c,d,e){b.getElementsByName&&d&&"name"==c&&!v?(b=b.getElementsByName(d),n(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),n(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?Sa(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),n(b,function(f){C(f,c,d)&&e.add(f)}));return e} +function Ta(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=Ra(a);if("*"!=g&&(f=ja(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ja(f,function(h){return C(h,c,d)}));n(f,function(h){"*"==g&&("!"==h.tagName||"*"==g&&1!=h.nodeType)||e.add(h)});return e}return Ua(a,b,c,d,e)}function Ua(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e} +function Sa(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),Sa(a,b,c,d,e)}function Ra(a){if(a instanceof G){if(8==a.b)return"!";if(null===a.b)return"*"}return a.f()};function E(){this.b=this.a=null;this.l=0}function Va(a){this.f=a;this.a=this.b=null}function Wa(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof y&&g instanceof y&&e.a==g.a?(e=c,c=c.a,b=b.a):0",4,2,function(a,b,c){return P(function(d,e){return d>e},a,b,c)});Q("<=",4,2,function(a,b,c){return P(function(d,e){return d<=e},a,b,c)});Q(">=",4,2,function(a,b,c){return P(function(d,e){return d>=e},a,b,c)});var cb=Q("=",3,2,function(a,b,c){return P(function(d,e){return d==e},a,b,c,!0)});Q("!=",3,2,function(a,b,c){return P(function(d,e){return d!=e},a,b,c,!0)});Q("and",2,2,function(a,b,c){return N(a,c)&&N(b,c)});Q("or",1,2,function(a,b,c){return N(a,c)||N(b,c)});function fb(a,b){if(b.a.length&&4!=a.i)throw Error("Primary expression must evaluate to nodeset if filter has predicate(s).");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}l(fb,J);fb.prototype.a=function(a){a=this.c.a(a);return gb(this.h,a)};fb.prototype.toString=function(){var a="Filter:"+K(this.c);return a+=K(this.h)};function hb(a,b){if(b.lengtha.B)throw Error("Function "+a.j+" expects at most "+a.B+" arguments, "+b.length+" given");a.H&&n(b,function(c,d){if(4!=c.i)throw Error("Argument "+d+" to function "+a.j+" is not of type Nodeset: "+c);});J.call(this,a.i);this.v=a;this.c=b;ab(this,a.g||r(b,function(c){return c.g}));bb(this,a.G&&!b.length||a.F&&!!b.length||r(b,function(c){return c.b}))}l(hb,J); +hb.prototype.a=function(a){return this.v.m.apply(null,la(a,this.c))};hb.prototype.toString=function(){var a="Function: "+this.v;if(this.c.length){var b=p(this.c,function(c,d){return c+K(d)},"Arguments:");a+=K(b)}return a};function ib(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}ib.prototype.toString=function(){return this.j};var jb={}; +function R(a,b,c,d,e,f,g,h){if(jb.hasOwnProperty(a))throw Error("Function already created: "+a+".");jb[a]=new ib(a,b,c,d,e,f,g,h)}R("boolean",2,!1,!1,function(a,b){return N(b,a)},1);R("ceiling",1,!1,!1,function(a,b){return Math.ceil(L(b,a))},1);R("concat",3,!1,!1,function(a,b){return p(ma(arguments,1),function(c,d){return c+M(d,a)},"")},2,null);R("contains",2,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);return-1!=b.indexOf(a)},2);R("count",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0); +R("false",2,!1,!1,function(){return!1},0);R("floor",1,!1,!1,function(a,b){return Math.floor(L(b,a))},1);R("id",4,!1,!1,function(a,b){function c(h){if(w){var q=e.all[h];if(q){if(q.nodeType&&h==q.id)return q;if(q.length)return ka(q,function(x){return h==x.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=M(b,a).split(/\s+/);var f=[];n(a,function(h){h=c(h);!h||0<=ia(f,h)||f.push(h)});f.sort(La);var g=new E;n(f,function(h){g.add(h)});return g},1); +R("lang",2,!1,!1,function(){return!1},1);R("last",1,!0,!1,function(a){if(1!=arguments.length)throw Error("Function last expects ()");return a.f},0);R("local-name",3,!1,!0,function(a,b){return(a=b?Ya(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():""},0,1,!0);R("name",3,!1,!0,function(a,b){return(a=b?Ya(b.a(a)):a.a)?a.nodeName.toLowerCase():""},0,1,!0);R("namespace-uri",3,!0,!1,function(){return""},0,1,!0); +R("normalize-space",3,!1,!0,function(a,b){return(b?M(b,a):B(a.a)).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);R("not",2,!1,!1,function(a,b){return!N(b,a)},1);R("number",1,!1,!0,function(a,b){return b?L(b,a):+B(a.a)},0,1);R("position",1,!0,!1,function(a){return a.b},0);R("round",1,!1,!1,function(a,b){return Math.round(L(b,a))},1);R("starts-with",2,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);return 0==b.lastIndexOf(a,0)},2);R("string",3,!1,!0,function(a,b){return b?M(b,a):B(a.a)},0,1); +R("string-length",1,!1,!0,function(a,b){return(b?M(b,a):B(a.a)).length},0,1);R("substring",3,!1,!1,function(a,b,c,d){c=L(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?L(d,a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=M(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);R("substring-after",3,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2); +R("substring-before",3,!1,!1,function(a,b,c){b=M(b,a);a=M(c,a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);R("sum",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);R("translate",3,!1,!1,function(a,b,c,d){b=M(b,a);c=M(c,a);var e=M(d,a);a={};for(d=0;da.length)throw Error("Unclosed literal string");return new lb(a)} +function Ib(a){var b=[];if(qb(z(a.a))){var c=A(a.a);var d=z(a.a);if("/"==c&&(Ia(a.a)||"."!=d&&".."!=d&&"@"!=d&&"*"!=d&&!/(?![0-9])[\w]/.test(d)))return new S;d=new S;W(a,"Missing next location step.");c=Jb(a,c);b.push(c)}else{a:{c=z(a.a);d=c.charAt(0);switch(d){case "$":throw Error("Variable reference not allowed in HTML XPath");case "(":A(a.a);c=Db(a);W(a,'unclosed "("');Fb(a,")");break;case '"':case "'":c=Hb(a);break;default:if(isNaN(+c))if(!kb(c)&&/(?![0-9])[\w]/.test(d)&&"("==z(a.a,1)){c=A(a.a); +c=jb[c]||null;A(a.a);for(d=[];")"!=z(a.a);){W(a,"Missing function argument list.");d.push(Db(a));if(","!=z(a.a))break;A(a.a)}W(a,"Unclosed function argument list.");Gb(a);c=new hb(c,d)}else{c=null;break a}else c=new mb(+A(a.a))}"["==z(a.a)&&(d=new tb(Kb(a)),c=new fb(c,d))}if(c)if(qb(z(a.a)))d=c;else return c;else c=Jb(a,"/"),d=new pb,b.push(c)}for(;qb(z(a.a));)c=A(a.a),W(a,"Missing next location step."),c=Jb(a,c),b.push(c);return new nb(d,b)} +function Jb(a,b){if("/"!=b&&"//"!=b)throw Error('Step op should be "/" or "//"');if("."==z(a.a)){var c=new U(zb,new G("node"));A(a.a);return c}if(".."==z(a.a))return c=new U(yb,new G("node")),A(a.a),c;if("@"==z(a.a)){var d=ob;A(a.a);W(a,"Missing attribute name")}else if("::"==z(a.a,1)){if(!/(?![0-9])[\w]/.test(z(a.a).charAt(0)))throw Error("Bad token: "+A(a.a));var e=A(a.a);d=xb[e]||null;if(!d)throw Error("No axis with name: "+e);A(a.a);W(a,"Missing node name")}else d=ub;e=z(a.a);if(/(?![0-9])[\w\*]/.test(e.charAt(0)))if("("== +z(a.a,1)){if(!kb(e))throw Error("Invalid node type: "+e);e=A(a.a);if(!kb(e))throw Error("Invalid type name: "+e);Fb(a,"(");W(a,"Bad nodetype");var f=z(a.a).charAt(0),g=null;if('"'==f||"'"==f)g=Hb(a);W(a,"Bad nodetype");Gb(a);e=new G(e,g)}else if(e=A(a.a),f=e.indexOf(":"),-1==f)e=new F(e);else{g=e.substring(0,f);if("*"==g)var h="*";else if(h=a.b(g),!h)throw Error("Namespace prefix not declared: "+g);e=e.substr(f+1);e=new F(e,h)}else throw Error("Bad token: "+A(a.a));a=new tb(Kb(a),d.s);return c||new U(d, +e,a,"//"==b)}function Kb(a){for(var b=[];"["==z(a.a);){A(a.a);W(a,"Missing predicate expression.");var c=Db(a);b.push(c);W(a,"Unclosed predicate expression.");Fb(a,"]")}return b}function Eb(a){if("-"==z(a.a))return A(a.a),new Ab(Eb(a));var b=Ib(a);if("|"!=z(a.a))a=b;else{for(b=[b];"|"==A(a.a);)W(a,"Missing next union location path."),b.push(Ib(a));a.a.a--;a=new Bb(b)}return a};function Lb(a){switch(a.nodeType){case 1:return ha(Mb,a);case 9:return Lb(a.documentElement);case 11:case 10:case 6:case 12:return Nb;default:return a.parentNode?Lb(a.parentNode):Nb}}function Nb(){return null}function Mb(a,b){if(a.prefix==b)return a.namespaceURI||"http://www.w3.org/1999/xhtml";var c=a.getAttributeNode("xmlns:"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?Mb(a.parentNode,b):null};function Ob(a,b){if(!a.length)throw Error("Empty XPath expression.");a=Fa(a);if(Ia(a))throw Error("Invalid XPath expression.");b?"function"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Db(new Cb(a,b));if(!Ia(a))throw Error("Bad token: "+A(a));this.evaluate=function(d,e){d=c.a(new m(d));return new X(d,e)}} +function X(a,b){if(0==b)if(a instanceof E)b=4;else if("string"==typeof a)b=2;else if("number"==typeof a)b=1;else if("boolean"==typeof a)b=3;else throw Error("Unexpected evaluation result.");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error("value could not be converted to the specified type");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?Za(a):""+a;break;case 1:this.numberValue=a instanceof E?+Za(a):+a;break;case 3:this.booleanValue=a instanceof E?0=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error("snapshotItem called with wrong result type");return g>=d.length|| +0>g?null:d[g]}}X.ANY_TYPE=0;X.NUMBER_TYPE=1;X.STRING_TYPE=2;X.BOOLEAN_TYPE=3;X.UNORDERED_NODE_ITERATOR_TYPE=4;X.ORDERED_NODE_ITERATOR_TYPE=5;X.UNORDERED_NODE_SNAPSHOT_TYPE=6;X.ORDERED_NODE_SNAPSHOT_TYPE=7;X.ANY_UNORDERED_NODE_TYPE=8;X.FIRST_ORDERED_NODE_TYPE=9;function Pb(a){this.lookupNamespaceURI=Lb(a)} +function Qb(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=X,c.evaluate=function(d,e,f,g){return(new Ob(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new Ob(d,e)},c.createNSResolver=function(d){return new Pb(d)}}ba("wgxpath.install",Qb);ba("wgxpath.install",Qb);var Rb=pa(),Sb=ra()||u("iPod"),Tb=u("iPad"),Ub=u("Android")&&!(qa()||pa()||u("Opera")||u("Silk")),Vb=qa(),Wb=u("Safari")&&!(qa()||u("Coast")||u("Opera")||u("Edge")||u("Edg/")||u("OPR")||pa()||u("Silk")||u("Android"))&&!(ra()||u("iPad")||u("iPod"));function Y(a){return(a=a.exec(t))?a[1]:""}(function(){if(Rb)return Y(/Firefox\/([0-9.]+)/);if(v||ta||sa)return xa;if(Vb)return ra()||u("iPad")||u("iPod")?Y(/CriOS\/([0-9.]+)/):Y(/Chrome\/([0-9.]+)/);if(Wb&&!(ra()||u("iPad")||u("iPod")))return Y(/Version\/([0-9.]+)/);if(Sb||Tb){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(t);if(a)return a[1]+"."+a[2]}else if(Ub)return(a=Y(/Android\s+([0-9.]+)/))?a:Y(/Version\/([0-9.]+)/);return""})();function Z(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Xb="BUTTON INPUT OPTGROUP OPTION SELECT TEXTAREA".split(" ");function Yb(a){return r(Xb,function(b){return Z(a,b)})?a.disabled?!1:a.parentNode&&1==a.parentNode.nodeType&&Z(a,"OPTGROUP")||Z(a,"OPTION")?Yb(a.parentNode):!Oa(a,function(b){var c=b.parentNode;if(c&&Z(c,"FIELDSET")&&c.disabled){if(!Z(b,"LEGEND"))return!0;for(;b=void 0!==b.previousElementSibling?b.previousElementSibling:Ja(b.previousSibling);)if(Z(b,"LEGEND"))return!0}return!1}):!0};ba("_",Yb);; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);} + +atom.isElementDisplayed = function(element, window){return (function(){var k=this||self;function aa(a){return"string"==typeof a}function ba(a,b){a=a.split(".");var c=k;a[0]in c||"undefined"==typeof c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b} +function ca(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; +else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function da(a,b,c){return a.call.apply(a.bind,arguments)}function ea(a,b,c){if(!a)throw Error();if(2b?null:"string"===typeof a?a.charAt(b):a[b]} +function pa(a){return Array.prototype.concat.apply([],arguments)}function qa(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};var ra=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\s\xa0]*([\s\S]*?)[\s\xa0]*$/.exec(a)[1]};function sa(a,b){return ab?1:0};var t;a:{var ta=k.navigator;if(ta){var ua=ta.userAgent;if(ua){t=ua;break a}}t=""}function u(a){return-1!=t.indexOf(a)};function va(){return u("Firefox")||u("FxiOS")}function wa(){return(u("Chrome")||u("CriOS"))&&!u("Edge")};function xa(a){return String(a).replace(/\-([a-z])/g,function(b,c){return c.toUpperCase()})};function ya(){return u("iPhone")&&!u("iPod")&&!u("iPad")};function za(a,b){var c=Aa;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var Ba=u("Opera"),v=u("Trident")||u("MSIE"),Ca=u("Edge"),Da=u("Gecko")&&!(-1!=t.toLowerCase().indexOf("webkit")&&!u("Edge"))&&!(u("Trident")||u("MSIE"))&&!u("Edge"),Ea=-1!=t.toLowerCase().indexOf("webkit")&&!u("Edge");function Fa(){var a=k.document;return a?a.documentMode:void 0}var Ga; +a:{var Ha="",Ia=function(){var a=t;if(Da)return/rv:([^\);]+)(\)|;)/.exec(a);if(Ca)return/Edge\/([\d\.]+)/.exec(a);if(v)return/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/.exec(a);if(Ea)return/WebKit\/(\S+)/.exec(a);if(Ba)return/(?:Version)[ \/]?(\S+)/.exec(a)}();Ia&&(Ha=Ia?Ia[1]:"");if(v){var Ja=Fa();if(null!=Ja&&Ja>parseFloat(Ha)){Ga=String(Ja);break a}}Ga=Ha}var Aa={}; +function Ka(a){return za(a,function(){for(var b=0,c=ra(String(Ga)).split("."),d=ra(String(a)).split("."),e=Math.max(c.length,d.length),f=0;0==b&&f]=|\s+|./g,Sa=/^\s/;function y(a,b){return a.b[a.a+(b||0)]}function z(a){return a.b[a.a++]}function Ta(a){return a.b.length<=a.a};function Ua(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}Ua.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};Ua.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};Ua.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function Va(a,b){this.width=a;this.height=b}Va.prototype.aspectRatio=function(){return this.width/this.height};Va.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Va.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Va.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Wa(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if("undefined"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a} +function Xa(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(v&&!(9<=Number(La))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if("sourceIndex"in a||a.parentNode&&"sourceIndex"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ya(a,b):!c&&Wa(e,b)?-1*Za(a,b):!d&&Wa(f,a)?Za(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=A(a);c=d.createRange(); +c.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function Za(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return Ya(b,a)}function Ya(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function A(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function $a(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null} +function ab(a){this.a=a||k.document||document}ab.prototype.getElementsByTagName=function(a,b){return(b||this.a).getElementsByTagName(String(a))};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?"":b);if("string"!=typeof b)if(x&&"title"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b="";a;){do 1!=a.nodeType&&(b+=a.nodeValue),x&&"title"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b} +function C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ma&&"class"==b&&(b="className");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function bb(a,b,c,d,e){return(x?cb:db).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)} +function cb(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=eb(a);if("*"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)"*"==a&&"!"==b.tagName||e.add(b);return e}gb(a,b,c,d,e);return e} +function db(a,b,c,d,e){b.getElementsByName&&d&&"name"==c&&!v?(b=b.getElementsByName(d),n(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&"class"==c?(b=b.getElementsByClassName(d),n(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?gb(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),n(b,function(f){C(f,c,d)&&e.add(f)}));return e} +function hb(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=eb(a);if("*"!=g&&(f=ka(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ka(f,function(h){return C(h,c,d)}));n(f,function(h){"*"==g&&("!"==h.tagName||"*"==g&&1!=h.nodeType)||e.add(h)});return e}return ib(a,b,c,d,e)}function ib(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e} +function gb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),gb(a,b,c,d,e)}function eb(a){if(a instanceof G){if(8==a.b)return"!";if(null===a.b)return"*"}return a.f()};function E(){this.b=this.a=null;this.l=0}function jb(a){this.f=a;this.a=this.b=null}function kb(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof Na&&g instanceof Na&&e.a==g.a?(e=c,c=c.a,b=b.a):0",4,2,function(a,b,c){return ub(function(d,e){return d>e},a,b,c)});P("<=",4,2,function(a,b,c){return ub(function(d,e){return d<=e},a,b,c)});P(">=",4,2,function(a,b,c){return ub(function(d,e){return d>=e},a,b,c)});var tb=P("=",3,2,function(a,b,c){return ub(function(d,e){return d==e},a,b,c,!0)});P("!=",3,2,function(a,b,c){return ub(function(d,e){return d!=e},a,b,c,!0)});P("and",2,2,function(a,b,c){return rb(a,c)&&rb(b,c)});P("or",1,2,function(a,b,c){return rb(a,c)||rb(b,c)});function xb(a,b){if(b.a.length&&4!=a.i)throw Error("Primary expression must evaluate to nodeset if filter has predicate(s).");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}l(xb,J);xb.prototype.a=function(a){a=this.c.a(a);return yb(this.h,a)};xb.prototype.toString=function(){var a="Filter:"+K(this.c);return a+=K(this.h)};function zb(a,b){if(b.lengtha.B)throw Error("Function "+a.j+" expects at most "+a.B+" arguments, "+b.length+" given");a.H&&n(b,function(c,d){if(4!=c.i)throw Error("Argument "+d+" to function "+a.j+" is not of type Nodeset: "+c);});J.call(this,a.i);this.v=a;this.c=b;pb(this,a.g||ma(b,function(c){return c.g}));qb(this,a.G&&!b.length||a.F&&!!b.length||ma(b,function(c){return c.b}))} +l(zb,J);zb.prototype.a=function(a){return this.v.m.apply(null,pa(a,this.c))};zb.prototype.toString=function(){var a="Function: "+this.v;if(this.c.length){var b=la(this.c,function(c,d){return c+K(d)},"Arguments:");a+=K(b)}return a};function Ab(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}Ab.prototype.toString=function(){return this.j};var Bb={}; +function Q(a,b,c,d,e,f,g,h){if(Bb.hasOwnProperty(a))throw Error("Function already created: "+a+".");Bb[a]=new Ab(a,b,c,d,e,f,g,h)}Q("boolean",2,!1,!1,function(a,b){return rb(b,a)},1);Q("ceiling",1,!1,!1,function(a,b){return Math.ceil(N(b,a))},1);Q("concat",3,!1,!1,function(a,b){return la(qa(arguments,1),function(c,d){return c+O(d,a)},"")},2,null);Q("contains",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return-1!=b.indexOf(a)},2);Q("count",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0); +Q("false",2,!1,!1,function(){return!1},0);Q("floor",1,!1,!1,function(a,b){return Math.floor(N(b,a))},1);Q("id",4,!1,!1,function(a,b){function c(h){if(x){var m=e.all[h];if(m){if(m.nodeType&&h==m.id)return m;if(m.length)return oa(m,function(w){return h==w.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=O(b,a).split(/\s+/);var f=[];n(a,function(h){h=c(h);!h||0<=ja(f,h)||f.push(h)});f.sort(Xa);var g=new E;n(f,function(h){g.add(h)});return g},1); +Q("lang",2,!1,!1,function(){return!1},1);Q("last",1,!0,!1,function(a){if(1!=arguments.length)throw Error("Function last expects ()");return a.f},0);Q("local-name",3,!1,!0,function(a,b){return(a=b?mb(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():""},0,1,!0);Q("name",3,!1,!0,function(a,b){return(a=b?mb(b.a(a)):a.a)?a.nodeName.toLowerCase():""},0,1,!0);Q("namespace-uri",3,!0,!1,function(){return""},0,1,!0); +Q("normalize-space",3,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).replace(/[\s\xa0]+/g," ").replace(/^\s+|\s+$/g,"")},0,1);Q("not",2,!1,!1,function(a,b){return!rb(b,a)},1);Q("number",1,!1,!0,function(a,b){return b?N(b,a):+B(a.a)},0,1);Q("position",1,!0,!1,function(a){return a.b},0);Q("round",1,!1,!1,function(a,b){return Math.round(N(b,a))},1);Q("starts-with",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return 0==b.lastIndexOf(a,0)},2);Q("string",3,!1,!0,function(a,b){return b?O(b,a):B(a.a)},0,1); +Q("string-length",1,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).length},0,1);Q("substring",3,!1,!1,function(a,b,c,d){c=N(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return"";d=d?N(d,a):Infinity;if(isNaN(d)||-Infinity===d)return"";c=Math.round(c)-1;var e=Math.max(c,0);a=O(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);Q("substring-after",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);c=b.indexOf(a);return-1==c?"":b.substring(c+a.length)},2); +Q("substring-before",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);a=b.indexOf(a);return-1==a?"":b.substring(0,a)},2);Q("sum",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);Q("translate",3,!1,!1,function(a,b,c,d){b=O(b,a);c=O(c,a);var e=O(d,a);a={};for(d=0;da.length)throw Error("Unclosed literal string");return new Db(a)} +function cc(a){var b=[];if(Jb(y(a.a))){var c=z(a.a);var d=y(a.a);if("/"==c&&(Ta(a.a)||"."!=d&&".."!=d&&"@"!=d&&"*"!=d&&!/(?![0-9])[\w]/.test(d)))return new Hb;d=new Hb;T(a,"Missing next location step.");c=dc(a,c);b.push(c)}else{a:{c=y(a.a);d=c.charAt(0);switch(d){case "$":throw Error("Variable reference not allowed in HTML XPath");case "(":z(a.a);c=Yb(a);T(a,'unclosed "("');$b(a,")");break;case '"':case "'":c=bc(a);break;default:if(isNaN(+c))if(!Cb(c)&&/(?![0-9])[\w]/.test(d)&&"("==y(a.a,1)){c=z(a.a); +c=Bb[c]||null;z(a.a);for(d=[];")"!=y(a.a);){T(a,"Missing function argument list.");d.push(Yb(a));if(","!=y(a.a))break;z(a.a)}T(a,"Unclosed function argument list.");ac(a);c=new zb(c,d)}else{c=null;break a}else c=new Eb(+z(a.a))}"["==y(a.a)&&(d=new Mb(ec(a)),c=new xb(c,d))}if(c)if(Jb(y(a.a)))d=c;else return c;else c=dc(a,"/"),d=new Ib,b.push(c)}for(;Jb(y(a.a));)c=z(a.a),T(a,"Missing next location step."),c=dc(a,c),b.push(c);return new Fb(d,b)} +function dc(a,b){if("/"!=b&&"//"!=b)throw Error('Step op should be "/" or "//"');if("."==y(a.a)){var c=new R(Sb,new G("node"));z(a.a);return c}if(".."==y(a.a))return c=new R(Rb,new G("node")),z(a.a),c;if("@"==y(a.a)){var d=Gb;z(a.a);T(a,"Missing attribute name")}else if("::"==y(a.a,1)){if(!/(?![0-9])[\w]/.test(y(a.a).charAt(0)))throw Error("Bad token: "+z(a.a));var e=z(a.a);d=Qb[e]||null;if(!d)throw Error("No axis with name: "+e);z(a.a);T(a,"Missing node name")}else d=Nb;e=y(a.a);if(/(?![0-9])[\w\*]/.test(e.charAt(0)))if("("== +y(a.a,1)){if(!Cb(e))throw Error("Invalid node type: "+e);e=z(a.a);if(!Cb(e))throw Error("Invalid type name: "+e);$b(a,"(");T(a,"Bad nodetype");var f=y(a.a).charAt(0),g=null;if('"'==f||"'"==f)g=bc(a);T(a,"Bad nodetype");ac(a);e=new G(e,g)}else if(e=z(a.a),f=e.indexOf(":"),-1==f)e=new F(e);else{g=e.substring(0,f);if("*"==g)var h="*";else if(h=a.b(g),!h)throw Error("Namespace prefix not declared: "+g);e=e.substr(f+1);e=new F(e,h)}else throw Error("Bad token: "+z(a.a));a=new Mb(ec(a),d.s);return c||new R(d, +e,a,"//"==b)}function ec(a){for(var b=[];"["==y(a.a);){z(a.a);T(a,"Missing predicate expression.");var c=Yb(a);b.push(c);T(a,"Unclosed predicate expression.");$b(a,"]")}return b}function Zb(a){if("-"==y(a.a))return z(a.a),new Tb(Zb(a));var b=cc(a);if("|"!=y(a.a))a=b;else{for(b=[b];"|"==z(a.a);)T(a,"Missing next union location path."),b.push(cc(a));a.a.a--;a=new Ub(b)}return a};function fc(a){switch(a.nodeType){case 1:return ha(gc,a);case 9:return fc(a.documentElement);case 11:case 10:case 6:case 12:return hc;default:return a.parentNode?fc(a.parentNode):hc}}function hc(){return null}function gc(a,b){if(a.prefix==b)return a.namespaceURI||"http://www.w3.org/1999/xhtml";var c=a.getAttributeNode("xmlns:"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?gc(a.parentNode,b):null};function ic(a,b){if(!a.length)throw Error("Empty XPath expression.");a=Qa(a);if(Ta(a))throw Error("Invalid XPath expression.");b?"function"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Yb(new Vb(a,b));if(!Ta(a))throw Error("Bad token: "+z(a));this.evaluate=function(d,e){d=c.a(new ia(d));return new U(d,e)}} +function U(a,b){if(0==b)if(a instanceof E)b=4;else if("string"==typeof a)b=2;else if("number"==typeof a)b=1;else if("boolean"==typeof a)b=3;else throw Error("Unexpected evaluation result.");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error("value could not be converted to the specified type");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?nb(a):""+a;break;case 1:this.numberValue=a instanceof E?+nb(a):+a;break;case 3:this.booleanValue=a instanceof E?0=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error("snapshotItem called with wrong result type");return g>=d.length|| +0>g?null:d[g]}}U.ANY_TYPE=0;U.NUMBER_TYPE=1;U.STRING_TYPE=2;U.BOOLEAN_TYPE=3;U.UNORDERED_NODE_ITERATOR_TYPE=4;U.ORDERED_NODE_ITERATOR_TYPE=5;U.UNORDERED_NODE_SNAPSHOT_TYPE=6;U.ORDERED_NODE_SNAPSHOT_TYPE=7;U.ANY_UNORDERED_NODE_TYPE=8;U.FIRST_ORDERED_NODE_TYPE=9;function jc(a){this.lookupNamespaceURI=fc(a)} +function kc(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=U,c.evaluate=function(d,e,f,g){return(new ic(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new ic(d,e)},c.createNSResolver=function(d){return new jc(d)}}ba("wgxpath.install",kc);ba("wgxpath.install",kc);var lc={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400", +darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc", +ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a", +lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1", +moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57", +seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};var mc="backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor".split(" "),nc=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,oc=/^#(?:[0-9a-f]{3}){1,2}$/i,pc=/^(?:rgba)?\((\d{1,3}),\s?(\d{1,3}),\s?(\d{1,3}),\s?(0|1|0\.\d*)\)$/i,qc=/^(?:rgb)?\((0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2}),\s?(0|[1-9]\d{0,2})\)$/i;function rc(a,b){this.code=a;this.a=V[a]||sc;this.message=b||"";a=this.a.replace(/((?:^|\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\s\xa0]+/g,"")});b=a.length-5;if(0>b||a.indexOf("Error",b)!=b)a+="Error";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||""}l(rc,Error);var sc="unknown error",V={15:"element not selectable",11:"element not visible"};V[31]=sc;V[30]=sc;V[24]="invalid cookie domain";V[29]="invalid element coordinates";V[12]="invalid element state"; +V[32]="invalid selector";V[51]="invalid selector";V[52]="invalid selector";V[17]="javascript error";V[405]="unsupported operation";V[34]="move target out of bounds";V[27]="no such alert";V[7]="no such element";V[8]="no such frame";V[23]="no such window";V[28]="script timeout";V[33]="session not created";V[10]="stale element reference";V[21]="timeout";V[25]="unable to set cookie";V[26]="unexpected alert open";V[13]=sc;V[9]="unknown command";var tc=va(),uc=ya()||u("iPod"),vc=u("iPad"),wc=u("Android")&&!(wa()||va()||u("Opera")||u("Silk")),xc=wa(),yc=u("Safari")&&!(wa()||u("Coast")||u("Opera")||u("Edge")||u("Edg/")||u("OPR")||va()||u("Silk")||u("Android"))&&!(ya()||u("iPad")||u("iPod"));function zc(a){return(a=a.exec(t))?a[1]:""}(function(){if(tc)return zc(/Firefox\/([0-9.]+)/);if(v||Ca||Ba)return Ga;if(xc)return ya()||u("iPad")||u("iPod")?zc(/CriOS\/([0-9.]+)/):zc(/Chrome\/([0-9.]+)/);if(yc&&!(ya()||u("iPad")||u("iPod")))return zc(/Version\/([0-9.]+)/);if(uc||vc){var a=/Version\/(\S+).*Mobile\/(\S+)/.exec(t);if(a)return a[1]+"."+a[2]}else if(wc)return(a=zc(/Android\s+([0-9.]+)/))?a:zc(/Version\/([0-9.]+)/);return""})();var Ac=v&&!(9<=Number(La));function W(a,b){b&&"string"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Bc=function(){var a={K:"http://www.w3.org/2000/svg"};return function(b){return a[b]||null}}(); +function Cc(a,b){var c=A(a);if(!c.documentElement)return null;(v||wc)&&kc(c?c.parentWindow||c.defaultView:window);try{var d=c.createNSResolver?c.createNSResolver(c.documentElement):Bc;if(v&&!Ka(7))return c.evaluate.call(c,b,a,d,9,null);if(!v||9<=Number(La)){for(var e={},f=c.getElementsByTagName("*"),g=0;g=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(qc))if(b= +Number(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=lc[b.toLowerCase()];if(!c&&(c="#"==b.charAt(0)?b:"#"+b,4==c.length&&(c=c.replace(nc,"#$1$1$2$2$3$3")),!oc.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?"rgba("+b.join(", ")+")":a}return a} +function Hc(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&"function"==ca(c.getPropertyValue)&&(d=c.getPropertyValue(b));return"inherit"!=d?void 0!==d?d:null:(a=Gc(a))?Hc(a,b):null} +function Ic(a,b,c){function d(g){var h=Jc(g);return 0=D.a+D.width;D=e.c>=D.b+D.height;if(L&&"hidden"==r.x||D&&"hidden"==r.y)return Z;if(L&&"visible"!=r.x||D&&"visible"!=r.y){if(w&&(r=d(a),e.f>=g.scrollWidth-r.x||e.a>=g.scrollHeight-r.y))return Z;e=Kc(a);return e==Z?Z:"scroll"}}}return"none"} +function Jc(a){var b=Lc(a);if(b)return b.rect;if(W(a,"HTML"))return a=A(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a="CSS1Compat"==a.compatMode?a.documentElement:a.body,a=new Va(a.clientWidth,a.clientHeight),new X(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new X(0,0,0,0)}b=new X(c.left,c.top,c.right-c.left,c.bottom-c.top);v&&a.ownerDocument.body&&(a=A(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop); +return b}function Lc(a){var b=W(a,"MAP");if(!b&&!W(a,"AREA"))return null;var c=b?a:W(a.parentNode,"MAP")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=Dc('/descendant::*[@usemap = "#'+c.name+'"]',A(c)))&&(e=Jc(d),b||"default"==a.shape.toLowerCase()||(a=Oc(a),b=Math.min(Math.max(a.a,0),e.width),c=Math.min(Math.max(a.b,0),e.height),e=new X(b+e.a,c+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{image:d,rect:e||new X(0,0,0,0)}} +function Oc(a){var b=a.shape.toLowerCase();a=a.coords.split(",");if("rect"==b&&4==a.length){b=a[0];var c=a[1];return new X(b,c,a[2]-b,a[3]-c)}if("circle"==b&&3==a.length)return b=a[2],new X(a[0]-b,a[1]-b,2*b,2*b);if("poly"==b&&2Marionette:SetContext + * command directs all subsequent browsing context scoped commands + * to that context. + * + * @class Marionette.Context + */ +export class Context { + /** + * Gets the correct context from a string. + * + * @param {string} s + * Context string serialisation. + * + * @returns {Context} + * Context. + * + * @throws {TypeError} + * If s is not a context. + */ + static fromString(s) { + switch (s) { + case "chrome": + return Context.Chrome; + + case "content": + return Context.Content; + + default: + throw new TypeError(`Unknown context: ${s}`); + } + } +} + +Context.Chrome = "chrome"; +Context.Content = "content"; + +/** + * Creates a browsing context wrapper. + * + * Browsing contexts handle interactions with the browser, according to + * the current environment. + */ +browser.Context = class { + /** + * @param {ChromeWindow} window + * ChromeWindow that contains the top-level browsing context. + * @param {GeckoDriver} driver + * Reference to driver instance. + */ + constructor(window, driver) { + this.window = window; + this.driver = driver; + + // In Firefox this is (not !) + // and MobileTabBrowser in GeckoView. + this.tabBrowser = lazy.TabManager.getTabBrowser(this.window); + + // Used to set curFrameId upon new session + this.newSession = true; + + // A reference to the tab corresponding to the current window handle, + // if any. Specifically, this.tab refers to the last tab that Marionette + // switched to in this browser window. Note that this may not equal the + // currently selected tab. For example, if Marionette switches to tab + // A, and then clicks on a button that opens a new tab B in the same + // browser window, this.tab will still point to tab A, despite tab B + // being the currently selected tab. + this.tab = null; + } + + /** + * Returns the content browser for the currently selected tab. + * If there is no tab selected, null will be returned. + */ + get contentBrowser() { + if (this.tab) { + return lazy.TabManager.getBrowserForTab(this.tab); + } else if ( + this.tabBrowser && + this.driver.isReftestBrowser(this.tabBrowser) + ) { + return this.tabBrowser; + } + + return null; + } + + get messageManager() { + if (this.contentBrowser) { + return this.contentBrowser.messageManager; + } + + return null; + } + + /** + * Checks if the browsing context has been discarded. + * + * The browsing context will have been discarded if the content + * browser, represented by the <xul:browser>, + * has been detached. + * + * @returns {boolean} + * True if browsing context has been discarded, false otherwise. + */ + get closed() { + return this.contentBrowser === null; + } + + /** + * Gets the position and dimensions of the top-level browsing context. + * + * @returns {Map.} + * Object with |x|, |y|, |width|, and |height| properties. + */ + get rect() { + return { + x: this.window.screenX, + y: this.window.screenY, + width: this.window.outerWidth, + height: this.window.outerHeight, + }; + } + + /** + * Retrieves the current tabmodal UI object. According to the browser + * associated with the currently selected tab. + */ + getTabModal() { + let br = this.contentBrowser; + if (!br.hasAttribute("tabmodalPromptShowing")) { + return null; + } + + // The modal is a direct sibling of the browser element. + // See tabbrowser.xml's getTabModalPromptBox. + let modalElements = br.parentNode.getElementsByTagName("tabmodalprompt"); + + return br.tabModalPromptBox.getPrompt(modalElements[0]); + } + + /** + * Close the current window. + * + * @returns {Promise} + * A promise which is resolved when the current window has been closed. + */ + async closeWindow() { + return lazy.windowManager.closeWindow(this.window); + } + + /** + * Focus the current window. + * + * @returns {Promise} + * A promise which is resolved when the current window has been focused. + */ + async focusWindow() { + await lazy.windowManager.focusWindow(this.window); + + // Also focus the currently selected tab if present. + this.contentBrowser?.focus(); + } + + /** + * Open a new browser window. + * + * @returns {Promise} + * A promise resolving to the newly created chrome window. + */ + openBrowserWindow(focus = false, isPrivate = false) { + return lazy.windowManager.openBrowserWindow({ + openerWindow: this.window, + focus, + isPrivate, + }); + } + + /** + * Close the current tab. + * + * @returns {Promise} + * A promise which is resolved when the current tab has been closed. + * + * @throws UnsupportedOperationError + * If tab handling for the current application isn't supported. + */ + async closeTab() { + // If the current window is not a browser then close it directly. Do the + // same if only one remaining tab is open, or no tab selected at all. + // + // Note: For GeckoView there will always be a single tab only. But for + // consistency with other platforms a specific condition has been added + // below as well even it's not really used. + if ( + !this.tabBrowser || + !this.tabBrowser.tabs || + this.tabBrowser.tabs.length === 1 || + !this.tab + ) { + return this.closeWindow(); + } + + let destroyed = new lazy.MessageManagerDestroyedPromise( + this.messageManager + ); + let tabClosed; + + if (lazy.AppInfo.isAndroid) { + await lazy.TabManager.removeTab(this.tab); + } else if (lazy.AppInfo.isFirefox) { + tabClosed = new lazy.EventPromise(this.tab, "TabClose"); + await this.tabBrowser.removeTab(this.tab); + } else { + throw new lazy.error.UnsupportedOperationError( + `closeTab() not supported for ${lazy.AppInfo.name}` + ); + } + + return Promise.all([destroyed, tabClosed]); + } + + /** + * Open a new tab in the currently selected chrome window. + */ + async openTab(focus = false) { + let tab = null; + + // Bug 1795841 - For Firefox the TabManager cannot be used yet. As such + // handle opening a tab differently for Android. + if (lazy.AppInfo.isAndroid) { + tab = await lazy.TabManager.addTab({ focus, window: this.window }); + } else if (lazy.AppInfo.isFirefox) { + const opened = new lazy.EventPromise(this.window, "TabOpen"); + this.window.BrowserOpenTab({ url: "about:blank" }); + await opened; + + tab = this.tabBrowser.selectedTab; + + // The new tab is always selected by default. If focus is not wanted, + // the previously tab needs to be selected again. + if (!focus) { + await lazy.TabManager.selectTab(this.tab); + } + } else { + throw new lazy.error.UnsupportedOperationError( + `openTab() not supported for ${lazy.AppInfo.name}` + ); + } + + return tab; + } + + /** + * Set the current tab. + * + * @param {number=} index + * Tab index to switch to. If the parameter is undefined, + * the currently selected tab will be used. + * @param {ChromeWindow=} window + * Switch to this window before selecting the tab. + * @param {boolean=} focus + * A boolean value which determins whether to focus + * the window. Defaults to true. + * + * @returns {Tab} + * The selected tab. + * + * @throws UnsupportedOperationError + * If tab handling for the current application isn't supported. + */ + async switchToTab(index, window = undefined, focus = true) { + if (window) { + this.window = window; + this.tabBrowser = lazy.TabManager.getTabBrowser(this.window); + } + + if (!this.tabBrowser || this.driver.isReftestBrowser(this.tabBrowser)) { + return null; + } + + if (typeof index == "undefined") { + this.tab = this.tabBrowser.selectedTab; + } else { + this.tab = this.tabBrowser.tabs[index]; + } + + if (focus) { + await lazy.TabManager.selectTab(this.tab); + } + + // By accessing the content browser's message manager a new browsing + // context is created for browserless tabs, which is needed to successfully + // run the WebDriver's is browsing context open step. This is temporary + // until we find a better solution on bug 1812258. + this.messageManager; + + return this.tab; + } + + /** + * Registers a new frame, and sets its current frame id to this frame + * if it is not already assigned, and if a) we already have a session + * or b) we're starting a new session and it is the right start frame. + * + * @param {XULBrowser} target + * The that was the target of the originating message. + */ + register(target) { + if (!this.tabBrowser) { + return; + } + + // If we're setting up a new session on Firefox, we only process the + // registration for this frame if it belongs to the current tab. + if (!this.tab) { + this.switchToTab(); + } + } +}; + +/** + * Marionette representation of the {@link ChromeWindow} window state. + * + * @enum {string} + */ +export const WindowState = { + Maximized: "maximized", + Minimized: "minimized", + Normal: "normal", + Fullscreen: "fullscreen", + + /** + * Converts {@link nsIDOMChromeWindow.windowState} to WindowState. + * + * @param {number} windowState + * Attribute from {@link nsIDOMChromeWindow.windowState}. + * + * @returns {WindowState} + * JSON representation. + * + * @throws {TypeError} + * If windowState was unknown. + */ + from(windowState) { + switch (windowState) { + case 1: + return WindowState.Maximized; + + case 2: + return WindowState.Minimized; + + case 3: + return WindowState.Normal; + + case 4: + return WindowState.Fullscreen; + + default: + throw new TypeError(`Unknown window state: ${windowState}`); + } + }, +}; diff --git a/remote/marionette/cert.sys.mjs b/remote/marionette/cert.sys.mjs new file mode 100644 index 0000000000..85f18c5975 --- /dev/null +++ b/remote/marionette/cert.sys.mjs @@ -0,0 +1,61 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + Preferences: "resource://gre/modules/Preferences.sys.mjs", +}); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "sss", + "@mozilla.org/ssservice;1", + "nsISiteSecurityService" +); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "certOverrideService", + "@mozilla.org/security/certoverride;1", + "nsICertOverrideService" +); + +const CERT_PINNING_ENFORCEMENT_PREF = "security.cert_pinning.enforcement_level"; +const HSTS_PRELOAD_LIST_PREF = "network.stricttransportsecurity.preloadlist"; + +/** @namespace */ +export const allowAllCerts = {}; + +/** + * Disable all security check and allow all certs. + */ +allowAllCerts.enable = function () { + // make it possible to register certificate overrides for domains + // that use HSTS or HPKP + lazy.Preferences.set(HSTS_PRELOAD_LIST_PREF, false); + lazy.Preferences.set(CERT_PINNING_ENFORCEMENT_PREF, 0); + + lazy.certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( + true + ); +}; + +/** + * Enable all security check. + */ +allowAllCerts.disable = function () { + lazy.certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData( + false + ); + + lazy.Preferences.reset(HSTS_PRELOAD_LIST_PREF); + lazy.Preferences.reset(CERT_PINNING_ENFORCEMENT_PREF); + + // clear collected HSTS and HPKP state + // through the site security service + lazy.sss.clearAll(); +}; diff --git a/remote/marionette/chrome/reftest.xhtml b/remote/marionette/chrome/reftest.xhtml new file mode 100644 index 0000000000..ec4145832d --- /dev/null +++ b/remote/marionette/chrome/reftest.xhtml @@ -0,0 +1,8 @@ + + + diff --git a/remote/marionette/chrome/test.xhtml b/remote/marionette/chrome/test.xhtml new file mode 100644 index 0000000000..c6220a6300 --- /dev/null +++ b/remote/marionette/chrome/test.xhtml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + `; + + const divEl = browser.document.querySelector("div"); + const svgEl = browser.document.querySelector("svg"); + const textareaEl = browser.document.querySelector("textarea"); + const videoEl = browser.document.querySelector("video"); + + const iframeEl = browser.document.querySelector("iframe"); + const childEl = iframeEl.contentDocument.createElement("div"); + iframeEl.contentDocument.body.appendChild(childEl); + + const shadowRoot = videoEl.openOrClosedShadowRoot; + + return { + browser, + nodeCache: new NodeCache(), + childEl, + divEl, + iframeEl, + shadowRoot, + svgEl, + textareaEl, + videoEl, + }; +} + +add_task(function test_findClosest() { + const { divEl, videoEl } = setupTest(); + + equal(element.findClosest(divEl, "foo"), null); + equal(element.findClosest(videoEl, "div"), divEl); +}); + +add_task(function test_isSelected() { + const { browser, divEl } = setupTest(); + + const checkbox = browser.document.createElement("input"); + checkbox.setAttribute("type", "checkbox"); + + ok(!element.isSelected(checkbox)); + checkbox.checked = true; + ok(element.isSelected(checkbox)); + + // selected is not a property of + checkbox.selected = true; + checkbox.checked = false; + ok(!element.isSelected(checkbox)); + + const option = browser.document.createElement("option"); + + ok(!element.isSelected(option)); + option.selected = true; + ok(element.isSelected(option)); + + // checked is not a property of