2012-03-22 19:19:57 +04:00
|
|
|
/* 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/. */
|
|
|
|
|
2016-02-03 21:54:23 +03:00
|
|
|
"use strict";
|
2017-06-28 21:04:14 +03:00
|
|
|
/* global XPCNativeWrapper */
|
2015-04-15 14:18:00 +03:00
|
|
|
|
2019-01-17 21:18:31 +03:00
|
|
|
const { assert } = ChromeUtils.import("chrome://marionette/content/assert.js");
|
|
|
|
const { atom } = ChromeUtils.import("chrome://marionette/content/atom.js");
|
2017-06-28 21:01:49 +03:00
|
|
|
const {
|
2017-10-24 19:42:32 +03:00
|
|
|
InvalidArgumentError,
|
2017-06-28 21:01:49 +03:00
|
|
|
InvalidSelectorError,
|
|
|
|
NoSuchElementError,
|
|
|
|
StaleElementReferenceError,
|
2019-01-17 21:18:31 +03:00
|
|
|
} = ChromeUtils.import("chrome://marionette/content/error.js");
|
|
|
|
const { pprint } = ChromeUtils.import("chrome://marionette/content/format.js");
|
|
|
|
const { PollPromise } = ChromeUtils.import(
|
|
|
|
"chrome://marionette/content/sync.js"
|
|
|
|
);
|
2015-04-15 14:18:00 +03:00
|
|
|
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
this.EXPORTED_SYMBOLS = [
|
|
|
|
"ChromeWebElement",
|
|
|
|
"ContentWebElement",
|
|
|
|
"ContentWebFrame",
|
|
|
|
"ContentWebWindow",
|
|
|
|
"element",
|
|
|
|
"WebElement",
|
|
|
|
];
|
2017-07-26 15:11:53 +03:00
|
|
|
|
2018-02-05 06:39:18 +03:00
|
|
|
const ORDERED_NODE_ITERATOR_TYPE = 5;
|
|
|
|
const FIRST_ORDERED_NODE_TYPE = 9;
|
|
|
|
|
2017-12-14 21:14:56 +03:00
|
|
|
const ELEMENT_NODE = 1;
|
|
|
|
const DOCUMENT_NODE = 9;
|
2017-10-24 18:51:38 +03:00
|
|
|
|
2020-04-24 12:23:52 +03:00
|
|
|
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
2017-07-26 15:11:53 +03:00
|
|
|
|
2017-10-10 16:51:27 +03:00
|
|
|
/** XUL elements that support checked property. */
|
|
|
|
const XUL_CHECKED_ELS = new Set(["button", "checkbox", "toolbarbutton"]);
|
|
|
|
|
|
|
|
/** XUL elements that support selected property. */
|
|
|
|
const XUL_SELECTED_ELS = new Set([
|
|
|
|
"menu",
|
|
|
|
"menuitem",
|
|
|
|
"menuseparator",
|
|
|
|
"radio",
|
|
|
|
"richlistitem",
|
|
|
|
"tab",
|
|
|
|
]);
|
|
|
|
|
2017-07-26 15:11:53 +03:00
|
|
|
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(
|
|
|
|
Ci.nsIUUIDGenerator
|
|
|
|
);
|
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
/**
|
2016-05-20 18:52:01 +03:00
|
|
|
* This module provides shared functionality for dealing with DOM-
|
|
|
|
* and web elements in Marionette.
|
2015-09-17 14:31:39 +03:00
|
|
|
*
|
|
|
|
* A web element is an abstraction used to identify an element when it
|
|
|
|
* is transported across the protocol, between remote- and local ends.
|
|
|
|
*
|
|
|
|
* Each element has an associated web element reference (a UUID) that
|
|
|
|
* uniquely identifies the the element across all browsing contexts. The
|
|
|
|
* web element reference for every element representing the same element
|
|
|
|
* is the same.
|
|
|
|
*
|
2017-07-26 15:11:53 +03:00
|
|
|
* The {@link element.Store} provides a mapping between web element
|
2016-05-20 18:52:01 +03:00
|
|
|
* references and DOM elements for each browsing context. It also provides
|
2015-09-17 14:31:39 +03:00
|
|
|
* functionality for looking up and retrieving elements.
|
2017-07-26 15:11:53 +03:00
|
|
|
*
|
|
|
|
* @namespace
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2016-03-04 15:01:09 +03:00
|
|
|
this.element = {};
|
|
|
|
|
|
|
|
element.Strategy = {
|
|
|
|
ClassName: "class name",
|
|
|
|
Selector: "css selector",
|
|
|
|
ID: "id",
|
|
|
|
Name: "name",
|
|
|
|
LinkText: "link text",
|
|
|
|
PartialLinkText: "partial link text",
|
|
|
|
TagName: "tag name",
|
|
|
|
XPath: "xpath",
|
|
|
|
};
|
|
|
|
|
2016-05-20 17:07:21 +03:00
|
|
|
/**
|
|
|
|
* Stores known/seen elements and their associated web element
|
|
|
|
* references.
|
|
|
|
*
|
2017-10-24 19:38:55 +03:00
|
|
|
* Elements are added by calling {@link #add()} or {@link addAll()},
|
|
|
|
* and may be queried by their web element reference using {@link get()}.
|
2017-07-26 15:11:53 +03:00
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @memberof element
|
2016-05-20 17:07:21 +03:00
|
|
|
*/
|
|
|
|
element.Store = class {
|
2016-05-20 15:32:52 +03:00
|
|
|
constructor() {
|
2016-05-20 17:07:21 +03:00
|
|
|
this.els = {};
|
2016-05-20 15:32:52 +03:00
|
|
|
}
|
2012-03-22 19:19:57 +04:00
|
|
|
|
2016-05-20 18:54:42 +03:00
|
|
|
clear() {
|
2016-05-20 17:07:21 +03:00
|
|
|
this.els = {};
|
2016-05-20 15:32:52 +03:00
|
|
|
}
|
2012-03-22 19:19:57 +04:00
|
|
|
|
|
|
|
/**
|
2016-05-20 15:28:27 +03:00
|
|
|
* Make a collection of elements seen.
|
|
|
|
*
|
|
|
|
* The oder of the returned web element references is guaranteed to
|
|
|
|
* match that of the collection passed in.
|
|
|
|
*
|
|
|
|
* @param {NodeList} els
|
|
|
|
* Sequence of elements to add to set of seen elements.
|
|
|
|
*
|
|
|
|
* @return {Array.<WebElement>}
|
|
|
|
* List of the web element references associated with each element
|
2017-10-24 19:38:55 +03:00
|
|
|
* from <var>els</var>.
|
2016-05-20 15:28:27 +03:00
|
|
|
*/
|
2016-05-20 15:32:52 +03:00
|
|
|
addAll(els) {
|
2016-05-20 15:28:27 +03:00
|
|
|
let add = this.add.bind(this);
|
|
|
|
return [...els].map(add);
|
2016-05-20 15:32:52 +03:00
|
|
|
}
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
/**
|
2016-05-20 15:32:52 +03:00
|
|
|
* Make an element seen.
|
|
|
|
*
|
2017-10-05 20:01:35 +03:00
|
|
|
* @param {(Element|WindowProxy|XULElement)} el
|
2016-05-20 15:32:52 +03:00
|
|
|
* Element to add to set of seen elements.
|
|
|
|
*
|
2017-10-05 20:01:35 +03:00
|
|
|
* @return {WebElement}
|
2016-05-20 15:32:52 +03:00
|
|
|
* Web element reference associated with element.
|
2017-10-05 20:01:35 +03:00
|
|
|
*
|
|
|
|
* @throws {TypeError}
|
|
|
|
* If <var>el</var> is not an {@link Element} or a {@link XULElement}.
|
2016-05-20 15:32:52 +03:00
|
|
|
*/
|
|
|
|
add(el) {
|
2017-10-05 20:01:35 +03:00
|
|
|
const isDOMElement = element.isDOMElement(el);
|
|
|
|
const isDOMWindow = element.isDOMWindow(el);
|
|
|
|
const isXULElement = element.isXULElement(el);
|
|
|
|
const context = isXULElement ? "chrome" : "content";
|
|
|
|
|
2017-10-31 22:57:40 +03:00
|
|
|
if (!(isDOMElement || isDOMWindow || isXULElement)) {
|
|
|
|
throw new TypeError(
|
|
|
|
"Expected an element or WindowProxy, " + pprint`got: ${el}`
|
|
|
|
);
|
2017-10-05 20:01:35 +03:00
|
|
|
}
|
|
|
|
|
2016-05-20 17:07:21 +03:00
|
|
|
for (let i in this.els) {
|
2016-05-20 15:28:27 +03:00
|
|
|
let foundEl;
|
2013-04-30 22:22:03 +04:00
|
|
|
try {
|
2016-05-20 17:07:21 +03:00
|
|
|
foundEl = this.els[i].get();
|
2015-04-15 14:18:00 +03:00
|
|
|
} catch (e) {}
|
2016-05-20 15:28:27 +03:00
|
|
|
|
2013-04-17 20:06:39 +04:00
|
|
|
if (foundEl) {
|
2016-05-20 17:07:21 +03:00
|
|
|
if (new XPCNativeWrapper(foundEl) == new XPCNativeWrapper(el)) {
|
2017-10-05 20:01:35 +03:00
|
|
|
return WebElement.fromUUID(i, context);
|
2013-04-17 20:06:39 +04:00
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
|
|
|
|
// cleanup reference to gc'd element
|
2015-04-15 14:18:00 +03:00
|
|
|
} else {
|
2016-05-20 17:07:21 +03:00
|
|
|
delete this.els[i];
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
|
|
|
}
|
2016-05-20 15:28:27 +03:00
|
|
|
|
2017-10-05 20:01:35 +03:00
|
|
|
let webEl = WebElement.from(el);
|
|
|
|
this.els[webEl.uuid] = Cu.getWeakReference(el);
|
|
|
|
return webEl;
|
2016-05-20 15:32:52 +03:00
|
|
|
}
|
2014-02-06 02:04:42 +04:00
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
/**
|
2016-05-20 17:07:21 +03:00
|
|
|
* Determine if the provided web element reference has been seen
|
|
|
|
* before/is in the element store.
|
2012-03-22 19:19:57 +04:00
|
|
|
*
|
2017-10-02 21:09:26 +03:00
|
|
|
* Unlike when getting the element, a staleness check is not
|
|
|
|
* performed.
|
|
|
|
*
|
2017-10-05 20:01:35 +03:00
|
|
|
* @param {WebElement} webEl
|
2016-05-20 17:07:21 +03:00
|
|
|
* Element's associated web element reference.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if element is in the store, false otherwise.
|
2017-10-05 20:01:35 +03:00
|
|
|
*
|
|
|
|
* @throws {TypeError}
|
|
|
|
* If <var>webEl</var> is not a {@link WebElement}.
|
2016-05-20 17:07:21 +03:00
|
|
|
*/
|
2017-10-05 20:01:35 +03:00
|
|
|
has(webEl) {
|
|
|
|
if (!(webEl instanceof WebElement)) {
|
|
|
|
throw new TypeError(pprint`Expected web element, got: ${webEl}`);
|
|
|
|
}
|
|
|
|
return Object.keys(this.els).includes(webEl.uuid);
|
2016-05-20 17:07:21 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2017-10-05 20:01:35 +03:00
|
|
|
* Retrieve a DOM {@link Element} or a {@link XULElement} by its
|
|
|
|
* unique {@link WebElement} reference.
|
2016-05-20 17:07:21 +03:00
|
|
|
*
|
2017-10-05 20:01:35 +03:00
|
|
|
* @param {WebElement} webEl
|
|
|
|
* Web element reference to find the associated {@link Element}
|
|
|
|
* of.
|
2018-09-18 15:51:21 +03:00
|
|
|
* @param {WindowProxy} win
|
2017-10-02 21:09:26 +03:00
|
|
|
* Current browsing context, which may differ from the associate
|
|
|
|
* browsing context of <var>el</var>.
|
2016-05-20 17:07:21 +03:00
|
|
|
*
|
2017-10-05 20:01:35 +03:00
|
|
|
* @returns {(Element|XULElement)}
|
2016-05-20 17:07:21 +03:00
|
|
|
* Element associated with reference.
|
2012-03-22 19:19:57 +04:00
|
|
|
*
|
2017-10-05 20:01:35 +03:00
|
|
|
* @throws {TypeError}
|
|
|
|
* If <var>webEl</var> is not a {@link WebElement}.
|
2017-08-05 02:50:46 +03:00
|
|
|
* @throws {NoSuchElementError}
|
2017-09-12 15:18:52 +03:00
|
|
|
* If the web element reference <var>uuid</var> has not been
|
|
|
|
* seen before.
|
2016-05-20 17:07:21 +03:00
|
|
|
* @throws {StaleElementReferenceError}
|
2017-09-12 15:18:52 +03:00
|
|
|
* If the element has gone stale, indicating it is no longer
|
|
|
|
* attached to the DOM, or its node document is no longer the
|
|
|
|
* active document.
|
2012-03-22 19:19:57 +04:00
|
|
|
*/
|
2018-09-18 15:51:21 +03:00
|
|
|
get(webEl, win) {
|
2017-10-05 20:01:35 +03:00
|
|
|
if (!(webEl instanceof WebElement)) {
|
|
|
|
throw new TypeError(pprint`Expected web element, got: ${webEl}`);
|
|
|
|
}
|
|
|
|
if (!this.has(webEl)) {
|
2017-09-12 15:18:52 +03:00
|
|
|
throw new NoSuchElementError(
|
2017-10-05 20:01:35 +03:00
|
|
|
"Web element reference not seen before: " + webEl.uuid
|
|
|
|
);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
|
2017-09-12 15:18:52 +03:00
|
|
|
let el;
|
2017-10-05 20:01:35 +03:00
|
|
|
let ref = this.els[webEl.uuid];
|
2013-04-30 22:22:03 +04:00
|
|
|
try {
|
2017-09-12 15:18:52 +03:00
|
|
|
el = ref.get();
|
2016-05-20 17:07:21 +03:00
|
|
|
} catch (e) {
|
2017-10-05 20:01:35 +03:00
|
|
|
delete this.els[webEl.uuid];
|
2013-04-30 22:22:03 +04:00
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
|
2018-09-18 15:51:21 +03:00
|
|
|
if (element.isStale(el, win)) {
|
2015-04-15 14:18:00 +03:00
|
|
|
throw new StaleElementReferenceError(
|
2017-10-05 20:01:35 +03:00
|
|
|
pprint`The element reference of ${el || webEl.uuid} is stale; ` +
|
2017-10-02 21:09:26 +03:00
|
|
|
"either the element is no longer attached to the DOM, " +
|
|
|
|
"it is not in the current frame context, " +
|
2017-08-21 21:09:15 +03:00
|
|
|
"or the document has been refreshed"
|
|
|
|
);
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
2016-05-20 17:07:21 +03:00
|
|
|
|
2012-03-22 19:19:57 +04:00
|
|
|
return el;
|
2016-05-20 15:32:52 +03:00
|
|
|
}
|
2016-05-20 15:28:27 +03:00
|
|
|
};
|
2014-02-06 02:04:42 +04:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
/**
|
|
|
|
* Find a single element or a collection of elements starting at the
|
|
|
|
* document root or a given node.
|
|
|
|
*
|
|
|
|
* If |timeout| is above 0, an implicit search technique is used.
|
2017-10-24 19:38:55 +03:00
|
|
|
* This will wait for the duration of <var>timeout</var> for the
|
|
|
|
* element to appear in the DOM.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-10-24 19:38:55 +03:00
|
|
|
* See the {@link element.Strategy} enum for a full list of supported
|
|
|
|
* search strategies that can be passed to <var>strategy</var>.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* Available flags for <var>opts</var>:
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* <dl>
|
|
|
|
* <dt><code>all</code>
|
|
|
|
* <dd>
|
|
|
|
* If true, a multi-element search selector is used and a sequence
|
|
|
|
* of elements will be returned. Otherwise a single element.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* <dt><code>timeout</code>
|
|
|
|
* <dd>
|
|
|
|
* Duration to wait before timing out the search. If <code>all</code>
|
|
|
|
* is false, a {@link NoSuchElementError} is thrown if unable to
|
|
|
|
* find the element within the timeout duration.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* <dt><code>startNode</code>
|
|
|
|
* <dd>Element to use as the root of the search.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* @param {Object.<string, WindowProxy>} container
|
2016-05-20 15:28:27 +03:00
|
|
|
* Window object and an optional shadow root that contains the
|
|
|
|
* root shadow DOM element.
|
|
|
|
* @param {string} strategy
|
|
|
|
* Search strategy whereby to locate the element(s).
|
|
|
|
* @param {string} selector
|
|
|
|
* Selector search pattern. The selector must be compatible with
|
2017-08-21 20:56:19 +03:00
|
|
|
* the chosen search <var>strategy</var>.
|
2016-05-20 15:28:27 +03:00
|
|
|
* @param {Object.<string, ?>} opts
|
|
|
|
* Options.
|
|
|
|
*
|
2017-08-21 20:56:19 +03:00
|
|
|
* @return {Promise.<(Element|Array.<Element>)>}
|
2016-05-20 15:28:27 +03:00
|
|
|
* Single element or a sequence of elements.
|
|
|
|
*
|
|
|
|
* @throws InvalidSelectorError
|
2017-08-21 20:56:19 +03:00
|
|
|
* If <var>strategy</var> is unknown.
|
2016-05-20 15:28:27 +03:00
|
|
|
* @throws InvalidSelectorError
|
2017-08-21 20:56:19 +03:00
|
|
|
* If <var>selector</var> is malformed.
|
2016-05-20 15:28:27 +03:00
|
|
|
* @throws NoSuchElementError
|
|
|
|
* If a single element is requested, this error will throw if the
|
|
|
|
* element is not found.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.find = function(container, strategy, selector, opts = {}) {
|
2017-10-02 19:13:57 +03:00
|
|
|
let all = !!opts.all;
|
|
|
|
let timeout = opts.timeout || 0;
|
|
|
|
let startNode = opts.startNode;
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
let searchFn;
|
|
|
|
if (opts.all) {
|
|
|
|
searchFn = findElements.bind(this);
|
|
|
|
} else {
|
|
|
|
searchFn = findElement.bind(this);
|
|
|
|
}
|
2016-02-23 18:18:55 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
return new Promise((resolve, reject) => {
|
2017-10-02 18:50:11 +03:00
|
|
|
let findElements = new PollPromise(
|
|
|
|
(resolve, reject) => {
|
2017-10-02 19:13:57 +03:00
|
|
|
let res = find_(container, strategy, selector, searchFn, {
|
|
|
|
all,
|
|
|
|
startNode,
|
|
|
|
});
|
2016-11-22 01:41:20 +03:00
|
|
|
if (res.length > 0) {
|
|
|
|
resolve(Array.from(res));
|
|
|
|
} else {
|
|
|
|
reject([]);
|
|
|
|
}
|
2017-10-02 19:13:57 +03:00
|
|
|
},
|
|
|
|
{ timeout }
|
|
|
|
);
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
findElements.then(foundEls => {
|
|
|
|
// the following code ought to be moved into findElement
|
|
|
|
// and findElements when bug 1254486 is addressed
|
|
|
|
if (!opts.all && (!foundEls || foundEls.length == 0)) {
|
2019-10-29 15:55:35 +03:00
|
|
|
let msg = `Unable to locate element: ${selector}`;
|
2016-05-20 15:28:27 +03:00
|
|
|
reject(new NoSuchElementError(msg));
|
|
|
|
}
|
2016-02-23 18:18:55 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
if (opts.all) {
|
|
|
|
resolve(foundEls);
|
|
|
|
}
|
|
|
|
resolve(foundEls[0]);
|
|
|
|
}, reject);
|
|
|
|
});
|
|
|
|
};
|
2016-01-22 00:07:36 +03:00
|
|
|
|
2017-10-02 19:13:57 +03:00
|
|
|
function find_(
|
|
|
|
container,
|
|
|
|
strategy,
|
|
|
|
selector,
|
|
|
|
searchFn,
|
|
|
|
{ startNode = null, all = false } = {}
|
|
|
|
) {
|
2016-05-20 15:28:27 +03:00
|
|
|
let rootNode = container.shadowRoot || container.frame.document;
|
2016-12-02 19:36:52 +03:00
|
|
|
|
2017-10-02 19:13:57 +03:00
|
|
|
if (!startNode) {
|
2019-10-29 15:55:35 +03:00
|
|
|
startNode = rootNode;
|
2016-12-02 19:36:52 +03:00
|
|
|
}
|
2016-02-23 18:18:55 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
let res;
|
|
|
|
try {
|
|
|
|
res = searchFn(strategy, selector, rootNode, startNode);
|
|
|
|
} catch (e) {
|
|
|
|
throw new InvalidSelectorError(
|
|
|
|
`Given ${strategy} expression "${selector}" is invalid: ${e}`
|
|
|
|
);
|
|
|
|
}
|
2016-02-23 18:18:55 +03:00
|
|
|
|
2016-07-04 19:34:06 +03:00
|
|
|
if (res) {
|
2017-10-02 19:13:57 +03:00
|
|
|
if (all) {
|
2016-07-04 19:34:06 +03:00
|
|
|
return res;
|
|
|
|
}
|
2016-05-20 15:28:27 +03:00
|
|
|
return [res];
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
2012-04-03 22:08:49 +04:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
/**
|
2016-07-02 23:51:21 +03:00
|
|
|
* Find a single element by XPath expression.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {HTMLDocument} document
|
|
|
|
* Document root.
|
|
|
|
* @param {Element} startNode
|
2016-07-02 23:51:21 +03:00
|
|
|
* Where in the DOM hiearchy to begin searching.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {string} expression
|
2016-07-02 23:51:21 +03:00
|
|
|
* XPath search expression.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @return {Node}
|
|
|
|
* First element matching <var>expression</var>.
|
2016-05-20 15:28:27 +03:00
|
|
|
*/
|
2017-10-24 18:51:38 +03:00
|
|
|
element.findByXPath = function(document, startNode, expression) {
|
|
|
|
let iter = document.evaluate(
|
|
|
|
expression,
|
|
|
|
startNode,
|
|
|
|
null,
|
|
|
|
FIRST_ORDERED_NODE_TYPE,
|
|
|
|
null
|
|
|
|
);
|
2016-07-02 23:51:21 +03:00
|
|
|
return iter.singleNodeValue;
|
|
|
|
};
|
2012-04-03 22:08:49 +04:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
/**
|
2016-07-02 23:51:21 +03:00
|
|
|
* Find elements by XPath expression.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {HTMLDocument} document
|
2016-07-02 23:51:21 +03:00
|
|
|
* Document root.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {Element} startNode
|
2016-07-02 23:51:21 +03:00
|
|
|
* Where in the DOM hierarchy to begin searching.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {string} expression
|
2016-07-02 23:51:21 +03:00
|
|
|
* XPath search expression.
|
2016-05-20 15:28:27 +03:00
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @return {Iterable.<Node>}
|
|
|
|
* Iterator over elements matching <var>expression</var>.
|
2016-05-20 15:28:27 +03:00
|
|
|
*/
|
2017-10-24 18:51:38 +03:00
|
|
|
element.findByXPathAll = function*(document, startNode, expression) {
|
|
|
|
let iter = document.evaluate(
|
|
|
|
expression,
|
|
|
|
startNode,
|
|
|
|
null,
|
|
|
|
ORDERED_NODE_ITERATOR_TYPE,
|
|
|
|
null
|
|
|
|
);
|
2016-07-02 23:51:21 +03:00
|
|
|
let el = iter.iterateNext();
|
|
|
|
while (el) {
|
2017-10-24 18:51:38 +03:00
|
|
|
yield el;
|
2016-07-02 23:51:21 +03:00
|
|
|
el = iter.iterateNext();
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2016-07-02 23:51:21 +03:00
|
|
|
};
|
2014-02-06 02:04:42 +04:00
|
|
|
|
2016-07-02 23:38:48 +03:00
|
|
|
/**
|
2017-10-24 18:51:38 +03:00
|
|
|
* Find all hyperlinks descendant of <var>startNode</var> which
|
|
|
|
* link text is <var>linkText</var>.
|
2016-07-02 23:38:48 +03:00
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {Element} startNode
|
2017-10-05 14:56:28 +03:00
|
|
|
* Where in the DOM hierarchy to begin searching.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {string} linkText
|
2016-07-02 23:38:48 +03:00
|
|
|
* Link text to search for.
|
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @return {Iterable.<HTMLAnchorElement>}
|
2017-10-05 14:56:28 +03:00
|
|
|
* Sequence of link elements which text is <var>s</var>.
|
2016-07-02 23:38:48 +03:00
|
|
|
*/
|
2017-10-24 18:51:38 +03:00
|
|
|
element.findByLinkText = function(startNode, linkText) {
|
2017-12-05 15:25:09 +03:00
|
|
|
return filterLinks(
|
|
|
|
startNode,
|
|
|
|
link => atom.getElementText(link).trim() === linkText
|
|
|
|
);
|
2017-10-24 18:51:38 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Find all hyperlinks descendant of <var>startNode</var> which
|
|
|
|
* link text contains <var>linkText</var>.
|
|
|
|
*
|
|
|
|
* @param {Element} startNode
|
|
|
|
* Where in the DOM hierachy to begin searching.
|
|
|
|
* @param {string} linkText
|
|
|
|
* Link text to search for.
|
|
|
|
*
|
|
|
|
* @return {Iterable.<HTMLAnchorElement>}
|
|
|
|
* Iterator of link elements which text containins
|
|
|
|
* <var>linkText</var>.
|
|
|
|
*/
|
|
|
|
element.findByPartialLinkText = function(startNode, linkText) {
|
2017-12-05 15:25:09 +03:00
|
|
|
return filterLinks(startNode, link =>
|
|
|
|
atom.getElementText(link).includes(linkText)
|
|
|
|
);
|
2016-07-02 23:38:48 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2017-10-24 18:51:38 +03:00
|
|
|
* Filters all hyperlinks that are descendant of <var>startNode</var>
|
|
|
|
* by <var>predicate</var>.
|
2016-07-02 23:38:48 +03:00
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {Element} startNode
|
2016-07-02 23:38:48 +03:00
|
|
|
* Where in the DOM hierarchy to begin searching.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {function(HTMLAnchorElement): boolean} predicate
|
2016-07-02 23:38:48 +03:00
|
|
|
* Function that determines if given link should be included in
|
|
|
|
* return value or filtered away.
|
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @return {Iterable.<HTMLAnchorElement>}
|
|
|
|
* Iterator of link elements matching <var>predicate</var>.
|
2016-07-02 23:38:48 +03:00
|
|
|
*/
|
2017-10-24 18:51:38 +03:00
|
|
|
function* filterLinks(startNode, predicate) {
|
|
|
|
for (let link of startNode.getElementsByTagName("a")) {
|
2016-07-02 23:38:48 +03:00
|
|
|
if (predicate(link)) {
|
2017-10-24 18:51:38 +03:00
|
|
|
yield link;
|
2016-07-02 23:38:48 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
/**
|
|
|
|
* Finds a single element.
|
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {element.Strategy} strategy
|
2016-05-20 15:28:27 +03:00
|
|
|
* Selector strategy to use.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {string} selector
|
2016-05-20 15:28:27 +03:00
|
|
|
* Selector expression.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {HTMLDocument} document
|
2016-05-20 15:28:27 +03:00
|
|
|
* Document root.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {Element=} startNode
|
2016-05-20 15:28:27 +03:00
|
|
|
* Optional node from which to start searching.
|
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @return {Element}
|
2016-05-20 15:28:27 +03:00
|
|
|
* Found elements.
|
|
|
|
*
|
|
|
|
* @throws {InvalidSelectorError}
|
2017-10-24 18:51:38 +03:00
|
|
|
* If strategy <var>using</var> is not recognised.
|
2016-05-20 15:28:27 +03:00
|
|
|
* @throws {Error}
|
2017-10-24 18:51:38 +03:00
|
|
|
* If selector expression <var>selector</var> is malformed.
|
2016-05-20 15:28:27 +03:00
|
|
|
*/
|
2017-10-24 18:51:38 +03:00
|
|
|
function findElement(strategy, selector, document, startNode = undefined) {
|
|
|
|
switch (strategy) {
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.ID: {
|
2017-06-28 21:16:34 +03:00
|
|
|
if (startNode.getElementById) {
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementById(selector);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2017-10-24 18:51:38 +03:00
|
|
|
let expr = `.//*[@id="${selector}"]`;
|
|
|
|
return element.findByXPath(document, startNode, expr);
|
2019-07-05 12:01:24 +03:00
|
|
|
}
|
2015-09-17 14:31:39 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.Name: {
|
2017-06-28 21:16:34 +03:00
|
|
|
if (startNode.getElementsByName) {
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementsByName(selector)[0];
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2017-10-24 18:51:38 +03:00
|
|
|
let expr = `.//*[@name="${selector}"]`;
|
|
|
|
return element.findByXPath(document, startNode, expr);
|
2019-07-05 12:01:24 +03:00
|
|
|
}
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.ClassName:
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementsByClassName(selector)[0];
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.TagName:
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementsByTagName(selector)[0];
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.XPath:
|
2017-10-24 18:51:38 +03:00
|
|
|
return element.findByXPath(document, startNode, selector);
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.LinkText:
|
2016-07-02 23:38:48 +03:00
|
|
|
for (let link of startNode.getElementsByTagName("a")) {
|
2017-12-05 15:25:09 +03:00
|
|
|
if (atom.getElementText(link).trim() === selector) {
|
2016-07-02 23:38:48 +03:00
|
|
|
return link;
|
|
|
|
}
|
|
|
|
}
|
2017-06-28 21:16:34 +03:00
|
|
|
return undefined;
|
2016-07-02 23:38:48 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.PartialLinkText:
|
2016-07-02 23:38:48 +03:00
|
|
|
for (let link of startNode.getElementsByTagName("a")) {
|
2017-12-05 15:25:09 +03:00
|
|
|
if (atom.getElementText(link).includes(selector)) {
|
2016-07-02 23:38:48 +03:00
|
|
|
return link;
|
2012-03-22 19:19:57 +04:00
|
|
|
}
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2017-06-28 21:16:34 +03:00
|
|
|
return undefined;
|
2016-03-04 15:01:09 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.Selector:
|
|
|
|
try {
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.querySelector(selector);
|
2016-05-20 15:28:27 +03:00
|
|
|
} catch (e) {
|
2017-10-24 18:51:38 +03:00
|
|
|
throw new InvalidSelectorError(`${e.message}: "${selector}"`);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
|
|
|
}
|
2017-06-28 21:16:34 +03:00
|
|
|
|
2017-10-24 18:51:38 +03:00
|
|
|
throw new InvalidSelectorError(`No such strategy: ${strategy}`);
|
2017-02-07 22:18:40 +03:00
|
|
|
}
|
2012-03-22 19:19:57 +04:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
/**
|
|
|
|
* Find multiple elements.
|
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {element.Strategy} strategy
|
2016-05-20 15:28:27 +03:00
|
|
|
* Selector strategy to use.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {string} selector
|
2016-05-20 15:28:27 +03:00
|
|
|
* Selector expression.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {HTMLDocument} document
|
2016-05-20 15:28:27 +03:00
|
|
|
* Document root.
|
2017-10-24 18:51:38 +03:00
|
|
|
* @param {Element=} startNode
|
2016-05-20 15:28:27 +03:00
|
|
|
* Optional node from which to start searching.
|
|
|
|
*
|
2017-10-24 18:51:38 +03:00
|
|
|
* @return {Array.<Element>}
|
2016-05-20 15:28:27 +03:00
|
|
|
* Found elements.
|
|
|
|
*
|
|
|
|
* @throws {InvalidSelectorError}
|
2017-10-24 18:51:38 +03:00
|
|
|
* If strategy <var>strategy</var> is not recognised.
|
2016-05-20 15:28:27 +03:00
|
|
|
* @throws {Error}
|
2017-10-24 18:51:38 +03:00
|
|
|
* If selector expression <var>selector</var> is malformed.
|
2016-05-20 15:28:27 +03:00
|
|
|
*/
|
2017-10-24 18:51:38 +03:00
|
|
|
function findElements(strategy, selector, document, startNode = undefined) {
|
|
|
|
switch (strategy) {
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.ID:
|
2017-10-24 18:51:38 +03:00
|
|
|
selector = `.//*[@id="${selector}"]`;
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
// fall through
|
|
|
|
case element.Strategy.XPath:
|
2017-10-24 18:51:38 +03:00
|
|
|
return [...element.findByXPathAll(document, startNode, selector)];
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.Name:
|
|
|
|
if (startNode.getElementsByName) {
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementsByName(selector);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
2017-10-24 18:51:38 +03:00
|
|
|
return [
|
|
|
|
...element.findByXPathAll(
|
|
|
|
document,
|
|
|
|
startNode,
|
|
|
|
`.//*[@name="${selector}"]`
|
2019-07-05 12:01:24 +03:00
|
|
|
),
|
2017-10-24 18:51:38 +03:00
|
|
|
];
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.ClassName:
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementsByClassName(selector);
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.TagName:
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.getElementsByTagName(selector);
|
2016-05-20 15:28:27 +03:00
|
|
|
|
|
|
|
case element.Strategy.LinkText:
|
2017-10-24 18:51:38 +03:00
|
|
|
return [...element.findByLinkText(startNode, selector)];
|
2016-07-02 23:38:48 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.PartialLinkText:
|
2017-10-24 18:51:38 +03:00
|
|
|
return [...element.findByPartialLinkText(startNode, selector)];
|
2016-03-04 15:01:09 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
case element.Strategy.Selector:
|
2017-10-24 18:51:38 +03:00
|
|
|
return startNode.querySelectorAll(selector);
|
2016-03-04 15:01:09 +03:00
|
|
|
|
2016-05-20 15:28:27 +03:00
|
|
|
default:
|
2017-10-24 18:51:38 +03:00
|
|
|
throw new InvalidSelectorError(`No such strategy: ${strategy}`);
|
2016-05-20 15:28:27 +03:00
|
|
|
}
|
|
|
|
}
|
2016-01-14 17:25:46 +03:00
|
|
|
|
2017-12-31 17:39:14 +03:00
|
|
|
/**
|
|
|
|
* Finds the closest parent node of <var>startNode</var> by CSS a
|
|
|
|
* <var>selector</var> expression.
|
|
|
|
*
|
|
|
|
* @param {Node} startNode
|
|
|
|
* Cyce through <var>startNode</var>'s parent nodes in tree-order
|
|
|
|
* and return the first match to <var>selector</var>.
|
|
|
|
* @param {string} selector
|
|
|
|
* CSS selector expression.
|
|
|
|
*
|
|
|
|
* @return {Node=}
|
|
|
|
* First match to <var>selector</var>, or null if no match was found.
|
|
|
|
*/
|
|
|
|
element.findClosest = function(startNode, selector) {
|
|
|
|
let node = startNode;
|
|
|
|
while (node.parentNode && node.parentNode.nodeType == ELEMENT_NODE) {
|
|
|
|
node = node.parentNode;
|
|
|
|
if (node.matches(selector)) {
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
2017-09-16 16:35:41 +03:00
|
|
|
/**
|
|
|
|
* Determines if <var>obj<var> is an HTML or JS collection.
|
|
|
|
*
|
|
|
|
* @param {*} seq
|
|
|
|
* Type to determine.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if <var>seq</va> is collection.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.isCollection = function(seq) {
|
2016-07-04 19:34:06 +03:00
|
|
|
switch (Object.prototype.toString.call(seq)) {
|
|
|
|
case "[object Arguments]":
|
|
|
|
case "[object Array]":
|
|
|
|
case "[object FileList]":
|
|
|
|
case "[object HTMLAllCollection]":
|
|
|
|
case "[object HTMLCollection]":
|
|
|
|
case "[object HTMLFormControlsCollection]":
|
|
|
|
case "[object HTMLOptionsCollection]":
|
|
|
|
case "[object NodeList]":
|
|
|
|
return true;
|
2016-02-23 18:18:55 +03:00
|
|
|
|
2016-07-04 19:34:06 +03:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
2016-02-23 18:18:55 +03:00
|
|
|
};
|
|
|
|
|
2017-08-21 21:09:15 +03:00
|
|
|
/**
|
|
|
|
* Determines if <var>el</var> is stale.
|
|
|
|
*
|
2017-08-30 16:22:39 +03:00
|
|
|
* A stale element is an element no longer attached to the DOM or which
|
2017-10-02 21:09:26 +03:00
|
|
|
* node document is not the active document of the current browsing
|
|
|
|
* context.
|
|
|
|
*
|
|
|
|
* The currently selected browsing context, specified through
|
|
|
|
* <var>window<var>, is a WebDriver concept defining the target
|
|
|
|
* against which commands will run. As the current browsing context
|
|
|
|
* may differ from <var>el</var>'s associated context, an element is
|
|
|
|
* considered stale even if it is connected to a living (not discarded)
|
|
|
|
* browsing context such as an <tt><iframe></tt>.
|
|
|
|
*
|
|
|
|
* @param {Element=} el
|
|
|
|
* DOM element to check for staleness. If null, which may be
|
|
|
|
* the case if the element has been unwrapped from a weak
|
|
|
|
* reference, it is always considered stale.
|
2018-09-18 15:51:21 +03:00
|
|
|
* @param {WindowProxy=} win
|
2017-10-02 21:09:26 +03:00
|
|
|
* Current browsing context, which may differ from the associate
|
|
|
|
* browsing context of <var>el</var>. When retrieving XUL
|
|
|
|
* elements, this is optional.
|
2017-08-21 21:09:15 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if <var>el</var> is stale, false otherwise.
|
|
|
|
*/
|
2018-09-18 15:51:21 +03:00
|
|
|
element.isStale = function(el, win = undefined) {
|
|
|
|
if (typeof win == "undefined") {
|
|
|
|
win = el.ownerGlobal;
|
2017-09-12 15:18:52 +03:00
|
|
|
}
|
|
|
|
|
2018-09-18 15:51:21 +03:00
|
|
|
if (el === null || !el.ownerGlobal || el.ownerDocument !== win.document) {
|
2017-08-31 15:25:52 +03:00
|
|
|
return true;
|
|
|
|
}
|
2017-09-12 15:18:52 +03:00
|
|
|
|
2017-08-31 15:25:52 +03:00
|
|
|
return !el.isConnected;
|
2016-05-20 17:11:37 +03:00
|
|
|
};
|
|
|
|
|
2017-10-10 16:51:27 +03:00
|
|
|
/**
|
|
|
|
* Determine if <var>el</var> is selected or not.
|
|
|
|
*
|
|
|
|
* This operation only makes sense on
|
|
|
|
* <tt><input type=checkbox></tt>,
|
|
|
|
* <tt><input type=radio></tt>,
|
|
|
|
* and <tt>>option></tt> elements.
|
|
|
|
*
|
|
|
|
* @param {(DOMElement|XULElement)} el
|
|
|
|
* Element to test if selected.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if element is selected, false otherwise.
|
|
|
|
*/
|
|
|
|
element.isSelected = function(el) {
|
|
|
|
if (!el) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (element.isXULElement(el)) {
|
|
|
|
if (XUL_CHECKED_ELS.has(el.tagName)) {
|
|
|
|
return el.checked;
|
|
|
|
} else if (XUL_SELECTED_ELS.has(el.tagName)) {
|
|
|
|
return el.selected;
|
|
|
|
}
|
2017-10-13 21:20:26 +03:00
|
|
|
} else if (element.isDOMElement(el)) {
|
2017-10-10 16:51:27 +03:00
|
|
|
if (el.localName == "input" && ["checkbox", "radio"].includes(el.type)) {
|
|
|
|
return el.checked;
|
|
|
|
} else if (el.localName == "option") {
|
|
|
|
return el.selected;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
|
2017-12-31 17:44:02 +03:00
|
|
|
/**
|
|
|
|
* An element is considered read only if it is an
|
|
|
|
* <code><input></code> or <code><textarea></code>
|
|
|
|
* element whose <code>readOnly</code> content IDL attribute is set.
|
|
|
|
*
|
|
|
|
* @param {Element} el
|
|
|
|
* Element to test is read only.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if element is read only.
|
|
|
|
*/
|
|
|
|
element.isReadOnly = function(el) {
|
|
|
|
return (
|
|
|
|
element.isDOMElement(el) &&
|
|
|
|
["input", "textarea"].includes(el.localName) &&
|
|
|
|
el.readOnly
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
Bug 1354211 - Add element.isDisabled. r=automatedtester
Adds a new function called element.isDisabled to test for disabledness.
An element is only considered disabled if it is indeed an element
that can be disabled, for example that it is a <button>, <input>,
<select>, <textarea>, <optgroup>, or <option> element. All other
elements are not disabled.
In nested trees the disabledness state of a containing element
affects child elements, without this being refelected in the child
element's content IDL attribute. In the following example, the
<option> element is disabled:
<select disabled>
<option>foo
</select>
Similarly:
<select>
<optgroup disabled>
<option>bar
</optgroup>
</select>
This means we have to traverse the tree to find the closest ancestral
parent element, whether it is <optgroup> or <select>, and inspect
its state before we can be sure the element in question is not disabled.
MozReview-Commit-ID: 578zLNj7nXK
--HG--
extra : rebase_source : fb8f4dc519a57a8fb4849a098f1e06f79aa02c9f
2017-12-31 17:48:46 +03:00
|
|
|
/**
|
|
|
|
* An element is considered disabled if it is a an element
|
|
|
|
* that can be disabled, or it belongs to a container group which
|
|
|
|
* <code>disabled</code> content IDL attribute affects it.
|
|
|
|
*
|
|
|
|
* @param {Element} el
|
|
|
|
* Element to test for disabledness.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if element, or its container group, is disabled.
|
|
|
|
*/
|
|
|
|
element.isDisabled = function(el) {
|
|
|
|
if (!element.isDOMElement(el)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (el.localName) {
|
|
|
|
case "option":
|
|
|
|
case "optgroup":
|
|
|
|
if (el.disabled) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
let parent = element.findClosest(el, "optgroup,select");
|
|
|
|
return element.isDisabled(parent);
|
|
|
|
|
|
|
|
case "button":
|
|
|
|
case "input":
|
|
|
|
case "select":
|
|
|
|
case "textarea":
|
|
|
|
return el.disabled;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-01-15 20:14:37 +03:00
|
|
|
/**
|
|
|
|
* Denotes elements that can be used for typing and clearing.
|
|
|
|
*
|
|
|
|
* Elements that are considered WebDriver-editable are non-readonly
|
|
|
|
* and non-disabled <code><input></code> elements in the Text,
|
|
|
|
* Search, URL, Telephone, Email, Password, Date, Month, Date and
|
|
|
|
* Time Local, Number, Range, Color, and File Upload states, and
|
|
|
|
* <code><textarea></code> elements.
|
|
|
|
*
|
|
|
|
* @param {Element} el
|
|
|
|
* Element to test.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if editable, false otherwise.
|
|
|
|
*/
|
|
|
|
element.isMutableFormControl = function(el) {
|
|
|
|
if (!element.isDOMElement(el)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (element.isReadOnly(el) || element.isDisabled(el)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (el.localName == "textarea") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (el.localName != "input") {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (el.type) {
|
|
|
|
case "color":
|
|
|
|
case "date":
|
|
|
|
case "datetime-local":
|
|
|
|
case "email":
|
|
|
|
case "file":
|
|
|
|
case "month":
|
|
|
|
case "number":
|
|
|
|
case "password":
|
|
|
|
case "range":
|
|
|
|
case "search":
|
|
|
|
case "tel":
|
|
|
|
case "text":
|
|
|
|
case "time":
|
|
|
|
case "url":
|
|
|
|
case "week":
|
|
|
|
return true;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-12-31 17:52:46 +03:00
|
|
|
/**
|
|
|
|
* An editing host is a node that is either an HTML element with a
|
|
|
|
* <code>contenteditable</code> attribute, or the HTML element child
|
|
|
|
* of a document whose <code>designMode</code> is enabled.
|
|
|
|
*
|
|
|
|
* @param {Element} el
|
|
|
|
* Element to determine if is an editing host.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if editing host, false otherwise.
|
|
|
|
*/
|
|
|
|
element.isEditingHost = function(el) {
|
|
|
|
return (
|
|
|
|
element.isDOMElement(el) &&
|
|
|
|
(el.isContentEditable || el.ownerDocument.designMode == "on")
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if an element is editable according to WebDriver.
|
|
|
|
*
|
|
|
|
* An element is considered editable if it is not read-only or
|
|
|
|
* disabled, and one of the following conditions are met:
|
|
|
|
*
|
|
|
|
* <ul>
|
|
|
|
* <li>It is a <code><textarea></code> element.
|
|
|
|
*
|
|
|
|
* <li>It is an <code><input></code> element that is not of
|
|
|
|
* the <code>checkbox</code>, <code>radio</code>, <code>hidden</code>,
|
|
|
|
* <code>submit</code>, <code>button</code>, or <code>image</code> types.
|
|
|
|
*
|
|
|
|
* <li>It is content-editable.
|
|
|
|
*
|
|
|
|
* <li>It belongs to a document in design mode.
|
|
|
|
* </ul>
|
|
|
|
*
|
|
|
|
* @param {Element}
|
|
|
|
* Element to test if editable.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if editable, false otherwise.
|
|
|
|
*/
|
|
|
|
element.isEditable = function(el) {
|
|
|
|
if (!element.isDOMElement(el)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (element.isReadOnly(el) || element.isDisabled(el)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-01-15 20:14:37 +03:00
|
|
|
return element.isMutableFormControl(el) || element.isEditingHost(el);
|
2017-12-31 17:52:46 +03:00
|
|
|
};
|
|
|
|
|
2016-02-03 21:54:23 +03:00
|
|
|
/**
|
|
|
|
* This function generates a pair of coordinates relative to the viewport
|
|
|
|
* given a target element and coordinates relative to that element's
|
|
|
|
* top-left corner.
|
|
|
|
*
|
|
|
|
* @param {Node} node
|
|
|
|
* Target node.
|
2016-03-04 18:34:28 +03:00
|
|
|
* @param {number=} xOffset
|
|
|
|
* Horizontal offset relative to target's top-left corner.
|
|
|
|
* Defaults to the centre of the target's bounding box.
|
|
|
|
* @param {number=} yOffset
|
|
|
|
* Vertical offset relative to target's top-left corner. Defaults to
|
|
|
|
* the centre of the target's bounding box.
|
|
|
|
*
|
|
|
|
* @return {Object.<string, number>}
|
|
|
|
* X- and Y coordinates.
|
|
|
|
*
|
|
|
|
* @throws TypeError
|
2017-10-24 19:38:55 +03:00
|
|
|
* If <var>xOffset</var> or <var>yOffset</var> are not numbers.
|
2016-02-03 21:54:23 +03:00
|
|
|
*/
|
2016-03-04 18:34:28 +03:00
|
|
|
element.coordinates = function(node, xOffset = undefined, yOffset = undefined) {
|
2016-02-03 21:54:23 +03:00
|
|
|
let box = node.getBoundingClientRect();
|
2016-03-04 18:34:28 +03:00
|
|
|
|
|
|
|
if (typeof xOffset == "undefined" || xOffset === null) {
|
|
|
|
xOffset = box.width / 2.0;
|
2016-02-03 21:54:23 +03:00
|
|
|
}
|
2016-03-04 18:34:28 +03:00
|
|
|
if (typeof yOffset == "undefined" || yOffset === null) {
|
|
|
|
yOffset = box.height / 2.0;
|
2016-02-03 21:54:23 +03:00
|
|
|
}
|
2016-03-04 18:34:28 +03:00
|
|
|
|
|
|
|
if (typeof yOffset != "number" || typeof xOffset != "number") {
|
|
|
|
throw new TypeError("Offset must be a number");
|
|
|
|
}
|
|
|
|
|
2016-02-03 21:54:23 +03:00
|
|
|
return {
|
2016-03-04 18:34:28 +03:00
|
|
|
x: box.left + xOffset,
|
|
|
|
y: box.top + yOffset,
|
2016-02-03 21:54:23 +03:00
|
|
|
};
|
2016-02-23 18:03:05 +03:00
|
|
|
};
|
2016-02-03 21:54:23 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* This function returns true if the node is in the viewport.
|
|
|
|
*
|
2016-03-08 16:53:00 +03:00
|
|
|
* @param {Element} el
|
2016-02-03 21:54:23 +03:00
|
|
|
* Target element.
|
|
|
|
* @param {number=} x
|
|
|
|
* Horizontal offset relative to target. Defaults to the centre of
|
|
|
|
* the target's bounding box.
|
|
|
|
* @param {number=} y
|
|
|
|
* Vertical offset relative to target. Defaults to the centre of
|
|
|
|
* the target's bounding box.
|
2016-03-08 16:53:00 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
2017-10-24 19:38:55 +03:00
|
|
|
* True if if <var>el</var> is in viewport, false otherwise.
|
2016-02-03 21:54:23 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.inViewport = function(el, x = undefined, y = undefined) {
|
2016-02-03 21:54:23 +03:00
|
|
|
let win = el.ownerGlobal;
|
2016-02-23 18:01:12 +03:00
|
|
|
let c = element.coordinates(el, x, y);
|
2016-02-03 21:54:23 +03:00
|
|
|
let vp = {
|
|
|
|
top: win.pageYOffset,
|
|
|
|
left: win.pageXOffset,
|
|
|
|
bottom: win.pageYOffset + win.innerHeight,
|
2017-06-30 02:40:24 +03:00
|
|
|
right: win.pageXOffset + win.innerWidth,
|
2016-02-03 21:54:23 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
vp.left <= c.x + win.pageXOffset &&
|
|
|
|
c.x + win.pageXOffset <= vp.right &&
|
|
|
|
vp.top <= c.y + win.pageYOffset &&
|
|
|
|
c.y + win.pageYOffset <= vp.bottom
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2017-02-03 22:52:34 +03:00
|
|
|
/**
|
|
|
|
* Gets the element's container element.
|
|
|
|
*
|
|
|
|
* An element container is defined by the WebDriver
|
2017-10-16 17:28:47 +03:00
|
|
|
* specification to be an <tt><option></tt> element in a
|
|
|
|
* <a href="https://html.spec.whatwg.org/#concept-element-contexts">valid
|
|
|
|
* element context</a>, meaning that it has an ancestral element
|
|
|
|
* that is either <tt><datalist></tt> or <tt><select></tt>.
|
2017-02-03 22:52:34 +03:00
|
|
|
*
|
|
|
|
* If the element does not have a valid context, its container element
|
|
|
|
* is itself.
|
|
|
|
*
|
|
|
|
* @param {Element} el
|
|
|
|
* Element to get the container of.
|
|
|
|
*
|
|
|
|
* @return {Element}
|
2017-12-31 17:32:38 +03:00
|
|
|
* Container element of <var>el</var>.
|
2017-02-03 22:52:34 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.getContainer = function(el) {
|
2017-12-31 17:32:38 +03:00
|
|
|
// Does <option> or <optgroup> have a valid context,
|
2017-02-03 22:52:34 +03:00
|
|
|
// meaning is it a child of <datalist> or <select>?
|
2017-12-31 17:32:38 +03:00
|
|
|
if (["option", "optgroup"].includes(el.localName)) {
|
2017-12-31 17:41:56 +03:00
|
|
|
return element.findClosest(el, "datalist,select") || el;
|
2017-02-03 22:52:34 +03:00
|
|
|
}
|
|
|
|
|
2018-12-04 18:30:43 +03:00
|
|
|
return el;
|
2017-02-03 22:52:34 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* An element is in view if it is a member of its own pointer-interactable
|
|
|
|
* paint tree.
|
|
|
|
*
|
|
|
|
* This means an element is considered to be in view, but not necessarily
|
|
|
|
* pointer-interactable, if it is found somewhere in the
|
2017-10-16 17:30:23 +03:00
|
|
|
* <code>elementsFromPoint</code> list at <var>el</var>'s in-view
|
|
|
|
* centre coordinates.
|
2017-02-03 22:52:34 +03:00
|
|
|
*
|
2017-10-16 17:30:23 +03:00
|
|
|
* Before running the check, we change <var>el</var>'s pointerEvents
|
|
|
|
* style property to "auto", since elements without pointer events
|
|
|
|
* enabled do not turn up in the paint tree we get from
|
|
|
|
* document.elementsFromPoint. This is a specialisation that is only
|
|
|
|
* relevant when checking if the element is in view.
|
2017-04-17 21:34:44 +03:00
|
|
|
*
|
2017-02-03 22:52:34 +03:00
|
|
|
* @param {Element} el
|
|
|
|
* Element to check if is in view.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
2017-10-16 17:30:23 +03:00
|
|
|
* True if <var>el</var> is inside the viewport, or false otherwise.
|
2017-02-03 22:52:34 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.isInView = function(el) {
|
2017-04-17 21:34:44 +03:00
|
|
|
let originalPointerEvents = el.style.pointerEvents;
|
2017-11-01 18:33:08 +03:00
|
|
|
|
2017-04-17 21:34:44 +03:00
|
|
|
try {
|
|
|
|
el.style.pointerEvents = "auto";
|
|
|
|
const tree = element.getPointerInteractablePaintTree(el);
|
2017-11-01 18:33:08 +03:00
|
|
|
|
|
|
|
// Bug 1413493 - <tr> is not part of the returned paint tree yet. As
|
|
|
|
// workaround check the visibility based on the first contained cell.
|
|
|
|
if (el.localName === "tr" && el.cells && el.cells.length > 0) {
|
|
|
|
return tree.includes(el.cells[0]);
|
|
|
|
}
|
|
|
|
|
2017-04-17 21:34:44 +03:00
|
|
|
return tree.includes(el);
|
|
|
|
} finally {
|
|
|
|
el.style.pointerEvents = originalPointerEvents;
|
|
|
|
}
|
2017-02-03 22:52:34 +03:00
|
|
|
};
|
|
|
|
|
2016-02-03 21:54:23 +03:00
|
|
|
/**
|
|
|
|
* This function throws the visibility of the element error if the element is
|
|
|
|
* not displayed or the given coordinates are not within the viewport.
|
|
|
|
*
|
2017-02-03 22:52:34 +03:00
|
|
|
* @param {Element} el
|
2016-02-03 21:54:23 +03:00
|
|
|
* Element to check if visible.
|
|
|
|
* @param {number=} x
|
|
|
|
* Horizontal offset relative to target. Defaults to the centre of
|
|
|
|
* the target's bounding box.
|
|
|
|
* @param {number=} y
|
|
|
|
* Vertical offset relative to target. Defaults to the centre of
|
|
|
|
* the target's bounding box.
|
2016-03-08 16:52:38 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if visible, false otherwise.
|
2016-02-03 21:54:23 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.isVisible = function(el, x = undefined, y = undefined) {
|
2016-03-03 17:20:39 +03:00
|
|
|
let win = el.ownerGlobal;
|
|
|
|
|
2017-11-09 22:39:51 +03:00
|
|
|
if (!atom.isElementDisplayed(el, win)) {
|
2016-02-03 21:54:23 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (el.tagName.toLowerCase() == "body") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-02-23 18:01:12 +03:00
|
|
|
if (!element.inViewport(el, x, y)) {
|
2016-11-15 00:06:50 +03:00
|
|
|
element.scrollIntoView(el);
|
|
|
|
if (!element.inViewport(el)) {
|
2016-02-03 21:54:23 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2016-03-04 19:44:38 +03:00
|
|
|
/**
|
|
|
|
* A pointer-interactable element is defined to be the first
|
|
|
|
* non-transparent element, defined by the paint order found at the centre
|
|
|
|
* point of its rectangle that is inside the viewport, excluding the size
|
|
|
|
* of any rendered scrollbars.
|
|
|
|
*
|
2017-04-17 19:24:19 +03:00
|
|
|
* An element is obscured if the pointer-interactable paint tree at its
|
|
|
|
* centre point is empty, or the first element in this tree is not an
|
|
|
|
* inclusive descendant of itself.
|
|
|
|
*
|
2016-03-04 19:44:38 +03:00
|
|
|
* @param {DOMElement} el
|
|
|
|
* Element determine if is pointer-interactable.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
2017-04-17 19:24:19 +03:00
|
|
|
* True if element is obscured, false otherwise.
|
2016-03-04 19:44:38 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.isObscured = function(el) {
|
2017-02-03 22:52:34 +03:00
|
|
|
let tree = element.getPointerInteractablePaintTree(el);
|
2017-04-17 19:24:19 +03:00
|
|
|
return !el.contains(tree[0]);
|
2016-03-04 19:44:38 +03:00
|
|
|
};
|
|
|
|
|
2017-08-04 17:51:19 +03:00
|
|
|
// TODO(ato): Only used by deprecated action API
|
|
|
|
// https://bugzil.la/1354578
|
2016-11-15 00:10:08 +03:00
|
|
|
/**
|
2018-10-22 17:21:27 +03:00
|
|
|
* Calculates the in-view centre point of an element's client rect.
|
|
|
|
*
|
|
|
|
* The portion of an element that is said to be _in view_, is the
|
|
|
|
* intersection of two squares: the first square being the initial
|
|
|
|
* viewport, and the second a DOM element. From this square we
|
|
|
|
* calculate the in-view _centre point_ and convert it into CSS pixels.
|
|
|
|
*
|
|
|
|
* Although Gecko's system internals allow click points to be
|
|
|
|
* given in floating point precision, the DOM operates in CSS pixels.
|
|
|
|
* When the in-view centre point is later used to retrieve a coordinate's
|
|
|
|
* paint tree, we need to ensure to operate in the same language.
|
|
|
|
*
|
|
|
|
* As a word of warning, there appears to be inconsistencies between
|
|
|
|
* how `DOMElement.elementsFromPoint` and `DOMWindowUtils.sendMouseEvent`
|
|
|
|
* internally rounds (ceils/floors) coordinates.
|
2016-11-15 00:10:08 +03:00
|
|
|
*
|
|
|
|
* @param {DOMRect} rect
|
2017-08-21 20:52:18 +03:00
|
|
|
* Element off a DOMRect sequence produced by calling
|
2018-10-22 17:21:27 +03:00
|
|
|
* `getClientRects` on an {@link Element}.
|
2018-09-18 15:51:21 +03:00
|
|
|
* @param {WindowProxy} win
|
2017-08-21 20:52:18 +03:00
|
|
|
* Current window global.
|
2016-11-15 00:10:08 +03:00
|
|
|
*
|
|
|
|
* @return {Map.<string, number>}
|
2017-08-21 20:52:18 +03:00
|
|
|
* X and Y coordinates that denotes the in-view centre point of
|
2018-10-22 17:21:27 +03:00
|
|
|
* `rect`.
|
2016-11-15 00:10:08 +03:00
|
|
|
*/
|
2018-09-18 15:51:21 +03:00
|
|
|
element.getInViewCentrePoint = function(rect, win) {
|
2018-10-22 17:21:27 +03:00
|
|
|
const { floor, max, min } = Math;
|
2016-11-15 00:10:08 +03:00
|
|
|
|
2018-10-22 17:21:27 +03:00
|
|
|
// calculate the intersection of the rect that is inside the viewport
|
|
|
|
let visible = {
|
2016-11-15 00:10:08 +03:00
|
|
|
left: max(0, min(rect.x, rect.x + rect.width)),
|
2018-09-18 15:51:21 +03:00
|
|
|
right: min(win.innerWidth, max(rect.x, rect.x + rect.width)),
|
2016-11-15 00:10:08 +03:00
|
|
|
top: max(0, min(rect.y, rect.y + rect.height)),
|
2018-09-18 15:51:21 +03:00
|
|
|
bottom: min(win.innerHeight, max(rect.y, rect.y + rect.height)),
|
2016-11-15 00:10:08 +03:00
|
|
|
};
|
|
|
|
|
2018-10-22 17:21:27 +03:00
|
|
|
// arrive at the centre point of the visible rectangle
|
|
|
|
let x = (visible.left + visible.right) / 2.0;
|
|
|
|
let y = (visible.top + visible.bottom) / 2.0;
|
|
|
|
|
|
|
|
// convert to CSS pixels, as centre point can be float
|
|
|
|
x = floor(x);
|
|
|
|
y = floor(y);
|
|
|
|
|
|
|
|
return { x, y };
|
2016-11-15 00:10:08 +03:00
|
|
|
};
|
|
|
|
|
2016-03-04 19:44:38 +03:00
|
|
|
/**
|
|
|
|
* Produces a pointer-interactable elements tree from a given element.
|
|
|
|
*
|
|
|
|
* The tree is defined by the paint order found at the centre point of
|
|
|
|
* the element's rectangle that is inside the viewport, excluding the size
|
|
|
|
* of any rendered scrollbars.
|
|
|
|
*
|
|
|
|
* @param {DOMElement} el
|
|
|
|
* Element to determine if is pointer-interactable.
|
|
|
|
*
|
|
|
|
* @return {Array.<DOMElement>}
|
2017-02-03 22:52:34 +03:00
|
|
|
* Sequence of elements in paint order.
|
2016-03-04 19:44:38 +03:00
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.getPointerInteractablePaintTree = function(el) {
|
2017-02-03 22:52:34 +03:00
|
|
|
const doc = el.ownerDocument;
|
|
|
|
const win = doc.defaultView;
|
2017-04-17 19:51:13 +03:00
|
|
|
const rootNode = el.getRootNode();
|
|
|
|
|
2016-11-15 00:10:08 +03:00
|
|
|
// pointer-interactable elements tree, step 1
|
2017-08-30 16:22:39 +03:00
|
|
|
if (!el.isConnected) {
|
2016-11-15 00:10:08 +03:00
|
|
|
return [];
|
|
|
|
}
|
2016-03-04 19:44:38 +03:00
|
|
|
|
|
|
|
// steps 2-3
|
2016-11-15 00:10:08 +03:00
|
|
|
let rects = el.getClientRects();
|
|
|
|
if (rects.length == 0) {
|
|
|
|
return [];
|
|
|
|
}
|
2016-03-04 19:44:38 +03:00
|
|
|
|
2016-11-15 00:10:08 +03:00
|
|
|
// step 4
|
|
|
|
let centre = element.getInViewCentrePoint(rects[0], win);
|
2016-03-04 19:44:38 +03:00
|
|
|
|
2016-11-15 00:10:08 +03:00
|
|
|
// step 5
|
2018-04-28 02:24:58 +03:00
|
|
|
return rootNode.elementsFromPoint(centre.x, centre.y);
|
2016-03-04 19:44:38 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
// TODO(ato): Not implemented.
|
|
|
|
// In fact, it's not defined in the spec.
|
2017-10-03 16:35:47 +03:00
|
|
|
element.isKeyboardInteractable = () => true;
|
2016-03-04 19:44:38 +03:00
|
|
|
|
2016-11-15 00:06:50 +03:00
|
|
|
/**
|
|
|
|
* Attempts to scroll into view |el|.
|
|
|
|
*
|
|
|
|
* @param {DOMElement} el
|
|
|
|
* Element to scroll into view.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.scrollIntoView = function(el) {
|
2016-11-15 00:06:50 +03:00
|
|
|
if (el.scrollIntoView) {
|
|
|
|
el.scrollIntoView({ block: "end", inline: "nearest", behavior: "instant" });
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-10-17 16:53:42 +03:00
|
|
|
/**
|
|
|
|
* Ascertains whether <var>node</var> is a DOM-, SVG-, or XUL element.
|
|
|
|
*
|
|
|
|
* @param {*} node
|
2017-10-31 22:57:40 +03:00
|
|
|
* Element thought to be an <code>Element</code> or
|
|
|
|
* <code>XULElement</code>.
|
2017-10-17 16:53:42 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if <var>node</var> is an element, false otherwise.
|
|
|
|
*/
|
|
|
|
element.isElement = function(node) {
|
2017-10-31 22:57:40 +03:00
|
|
|
return element.isDOMElement(node) || element.isXULElement(node);
|
2017-10-17 16:53:42 +03:00
|
|
|
};
|
|
|
|
|
2017-09-16 16:37:21 +03:00
|
|
|
/**
|
2017-10-13 21:20:26 +03:00
|
|
|
* Ascertains whether <var>node</var> is a DOM element.
|
|
|
|
*
|
|
|
|
* @param {*} node
|
|
|
|
* Element thought to be an <code>Element</code>.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if <var>node</var> is a DOM element, false otherwise.
|
|
|
|
*/
|
|
|
|
element.isDOMElement = function(node) {
|
|
|
|
return (
|
|
|
|
typeof node == "object" &&
|
|
|
|
node !== null &&
|
2017-10-31 22:57:40 +03:00
|
|
|
"nodeType" in node &&
|
2017-12-14 21:14:56 +03:00
|
|
|
[ELEMENT_NODE, DOCUMENT_NODE].includes(node.nodeType) &&
|
2017-10-31 22:55:49 +03:00
|
|
|
!element.isXULElement(node)
|
|
|
|
);
|
2017-10-13 21:20:26 +03:00
|
|
|
};
|
|
|
|
|
2017-10-16 16:25:53 +03:00
|
|
|
/**
|
2020-04-24 12:23:52 +03:00
|
|
|
* Ascertains whether <var>el</var> is a XUL element.
|
2017-09-16 16:37:21 +03:00
|
|
|
*
|
|
|
|
* @param {*} node
|
2020-04-24 12:23:52 +03:00
|
|
|
* Element to check
|
2017-09-16 16:37:21 +03:00
|
|
|
*
|
|
|
|
* @return {boolean}
|
2020-04-24 12:23:52 +03:00
|
|
|
* True if <var>node</var> is a XULElement,
|
2017-09-16 16:37:21 +03:00
|
|
|
* false otherwise.
|
|
|
|
*/
|
|
|
|
element.isXULElement = function(node) {
|
|
|
|
return (
|
|
|
|
typeof node == "object" &&
|
|
|
|
node !== null &&
|
2017-10-31 22:59:41 +03:00
|
|
|
"nodeType" in node &&
|
2017-09-16 16:37:21 +03:00
|
|
|
node.nodeType === node.ELEMENT_NODE &&
|
2020-04-24 12:23:52 +03:00
|
|
|
node.namespaceURI === XUL_NS
|
2017-09-16 16:37:21 +03:00
|
|
|
);
|
2016-02-03 21:54:23 +03:00
|
|
|
};
|
2016-05-16 23:24:57 +03:00
|
|
|
|
2017-10-13 21:22:01 +03:00
|
|
|
/**
|
|
|
|
* Ascertains whether <var>node</var> is a <code>WindowProxy</code>.
|
|
|
|
*
|
|
|
|
* @param {*} node
|
|
|
|
* Node thought to be a <code>WindowProxy</code>.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if <var>node</var> is a DOM window.
|
|
|
|
*/
|
|
|
|
element.isDOMWindow = function(node) {
|
|
|
|
// TODO(ato): This should use Object.prototype.toString.call(node)
|
|
|
|
// but it's not clear how to write a good xpcshell test for that,
|
|
|
|
// seeing as we stub out a WindowProxy.
|
|
|
|
return (
|
|
|
|
typeof node == "object" &&
|
|
|
|
node !== null &&
|
|
|
|
typeof node.toString == "function" &&
|
|
|
|
node.toString() == "[object Window]" &&
|
|
|
|
node.self === node
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-05-16 23:24:57 +03:00
|
|
|
const boolEls = {
|
|
|
|
audio: ["autoplay", "controls", "loop", "muted"],
|
|
|
|
button: ["autofocus", "disabled", "formnovalidate"],
|
|
|
|
details: ["open"],
|
|
|
|
dialog: ["open"],
|
|
|
|
fieldset: ["disabled"],
|
|
|
|
form: ["novalidate"],
|
|
|
|
iframe: ["allowfullscreen"],
|
|
|
|
img: ["ismap"],
|
2017-06-30 02:40:24 +03:00
|
|
|
input: [
|
|
|
|
"autofocus",
|
|
|
|
"checked",
|
|
|
|
"disabled",
|
|
|
|
"formnovalidate",
|
|
|
|
"multiple",
|
|
|
|
"readonly",
|
|
|
|
"required",
|
|
|
|
],
|
2016-05-16 23:24:57 +03:00
|
|
|
keygen: ["autofocus", "disabled"],
|
|
|
|
menuitem: ["checked", "default", "disabled"],
|
|
|
|
ol: ["reversed"],
|
|
|
|
optgroup: ["disabled"],
|
|
|
|
option: ["disabled", "selected"],
|
|
|
|
script: ["async", "defer"],
|
|
|
|
select: ["autofocus", "disabled", "multiple", "required"],
|
|
|
|
textarea: ["autofocus", "disabled", "readonly", "required"],
|
|
|
|
track: ["default"],
|
|
|
|
video: ["autoplay", "controls", "loop", "muted"],
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tests if the attribute is a boolean attribute on element.
|
|
|
|
*
|
|
|
|
* @param {DOMElement} el
|
2017-10-16 17:30:57 +03:00
|
|
|
* Element to test if <var>attr</var> is a boolean attribute on.
|
2016-05-16 23:24:57 +03:00
|
|
|
* @param {string} attr
|
|
|
|
* Attribute to test is a boolean attribute.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if the attribute is boolean, false otherwise.
|
|
|
|
*/
|
2017-06-30 02:40:24 +03:00
|
|
|
element.isBooleanAttribute = function(el, attr) {
|
2017-10-13 21:20:26 +03:00
|
|
|
if (!element.isDOMElement(el)) {
|
2016-05-16 23:24:57 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// global boolean attributes that apply to all HTML elements,
|
|
|
|
// except for custom elements
|
2017-06-30 02:40:24 +03:00
|
|
|
const customElement = !el.localName.includes("-");
|
|
|
|
if ((attr == "hidden" || attr == "itemscope") && customElement) {
|
2016-05-16 23:24:57 +03:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!boolEls.hasOwnProperty(el.localName)) {
|
|
|
|
return false;
|
|
|
|
}
|
2017-07-29 12:03:00 +03:00
|
|
|
return boolEls[el.localName].includes(attr);
|
2016-05-16 23:24:57 +03:00
|
|
|
};
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
|
|
|
|
/**
|
|
|
|
* A web element is an abstraction used to identify an element when
|
|
|
|
* it is transported via the protocol, between remote- and local ends.
|
|
|
|
*
|
|
|
|
* In Marionette this abstraction can represent DOM elements,
|
|
|
|
* WindowProxies, and XUL elements.
|
|
|
|
*/
|
|
|
|
class WebElement {
|
|
|
|
/**
|
|
|
|
* @param {string} uuid
|
|
|
|
* Identifier that must be unique across all browsing contexts
|
|
|
|
* for the contract to be upheld.
|
|
|
|
*/
|
|
|
|
constructor(uuid) {
|
|
|
|
this.uuid = assert.string(uuid);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Performs an equality check between this web element and
|
|
|
|
* <var>other</var>.
|
|
|
|
*
|
|
|
|
* @param {WebElement} other
|
|
|
|
* Web element to compare with this.
|
|
|
|
*
|
|
|
|
* @return {boolean}
|
|
|
|
* True if this and <var>other</var> are the same. False
|
|
|
|
* otherwise.
|
|
|
|
*/
|
|
|
|
is(other) {
|
|
|
|
return other instanceof WebElement && this.uuid === other.uuid;
|
|
|
|
}
|
|
|
|
|
|
|
|
toString() {
|
|
|
|
return `[object ${this.constructor.name} uuid=${this.uuid}]`;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a new {@link WebElement} reference for a DOM element,
|
|
|
|
* <code>WindowProxy</code>, or XUL element.
|
|
|
|
*
|
|
|
|
* @param {(Element|WindowProxy|XULElement)} node
|
|
|
|
* Node to construct a web element reference for.
|
|
|
|
*
|
|
|
|
* @return {(ContentWebElement|ChromeWebElement)}
|
|
|
|
* Web element reference for <var>el</var>.
|
|
|
|
*
|
2017-10-24 19:42:32 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
* If <var>node</var> is neither a <code>WindowProxy</code>,
|
|
|
|
* DOM element, or a XUL element.
|
|
|
|
*/
|
|
|
|
static from(node) {
|
|
|
|
const uuid = WebElement.generateUUID();
|
|
|
|
|
2017-10-31 22:57:40 +03:00
|
|
|
if (element.isDOMElement(node)) {
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
return new ContentWebElement(uuid);
|
|
|
|
} else if (element.isDOMWindow(node)) {
|
|
|
|
if (node.parent === node) {
|
|
|
|
return new ContentWebWindow(uuid);
|
|
|
|
}
|
|
|
|
return new ContentWebFrame(uuid);
|
|
|
|
} else if (element.isXULElement(node)) {
|
|
|
|
return new ChromeWebElement(uuid);
|
|
|
|
}
|
|
|
|
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError(
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
"Expected DOM window/element " + pprint`or XUL element, got: ${node}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Unmarshals a JSON Object to one of {@link ContentWebElement},
|
|
|
|
* {@link ContentWebWindow}, {@link ContentWebFrame}, or
|
|
|
|
* {@link ChromeWebElement}.
|
|
|
|
*
|
|
|
|
* @param {Object.<string, string>} json
|
|
|
|
* Web element reference, which is supposed to be a JSON Object
|
|
|
|
* where the key is one of the {@link WebElement} concrete
|
|
|
|
* classes' UUID identifiers.
|
|
|
|
*
|
|
|
|
* @return {WebElement}
|
|
|
|
* Representation of the web element.
|
|
|
|
*
|
2017-10-24 19:42:32 +03:00
|
|
|
* @throws {InvalidArgumentError}
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
* If <var>json</var> is not a web element reference.
|
|
|
|
*/
|
|
|
|
static fromJSON(json) {
|
2017-10-24 19:42:32 +03:00
|
|
|
assert.object(json);
|
|
|
|
let keys = Object.keys(json);
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
|
|
|
|
for (let key of keys) {
|
|
|
|
switch (key) {
|
|
|
|
case ContentWebElement.Identifier:
|
|
|
|
return ContentWebElement.fromJSON(json);
|
|
|
|
|
|
|
|
case ContentWebWindow.Identifier:
|
|
|
|
return ContentWebWindow.fromJSON(json);
|
|
|
|
|
|
|
|
case ContentWebFrame.Identifier:
|
|
|
|
return ContentWebFrame.fromJSON(json);
|
|
|
|
|
|
|
|
case ChromeWebElement.Identifier:
|
|
|
|
return ChromeWebElement.fromJSON(json);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError(
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
pprint`Expected web element reference, got: ${json}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructs a {@link ContentWebElement} or {@link ChromeWebElement}
|
|
|
|
* from a a string <var>uuid</var>.
|
|
|
|
*
|
|
|
|
* This whole function is a workaround for the fact that clients
|
|
|
|
* to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
|
|
|
|
* Objects instead of web element representations. For that reason
|
|
|
|
* we need the <var>context</var> argument to determine what kind of
|
|
|
|
* {@link WebElement} to return.
|
|
|
|
*
|
|
|
|
* @param {string} uuid
|
|
|
|
* UUID to be associated with the web element.
|
|
|
|
* @param {Context} context
|
|
|
|
* Context, which is used to determine if the returned type
|
|
|
|
* should be a content web element or a chrome web element.
|
|
|
|
*
|
|
|
|
* @return {WebElement}
|
|
|
|
* One of {@link ContentWebElement} or {@link ChromeWebElement},
|
|
|
|
* based on <var>context</var>.
|
|
|
|
*
|
|
|
|
* @throws {InvalidArgumentError}
|
2017-10-24 19:42:32 +03:00
|
|
|
* If <var>uuid</var> is not a string or <var>context</var>
|
|
|
|
* is an invalid context.
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
*/
|
|
|
|
static fromUUID(uuid, context) {
|
|
|
|
assert.string(uuid);
|
|
|
|
|
|
|
|
switch (context) {
|
|
|
|
case "chrome":
|
|
|
|
return new ChromeWebElement(uuid);
|
|
|
|
|
|
|
|
case "content":
|
|
|
|
return new ContentWebElement(uuid);
|
|
|
|
|
|
|
|
default:
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError("Unknown context: " + context);
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if <var>ref<var> is a {@link WebElement} reference,
|
2018-07-27 18:04:05 +03:00
|
|
|
* i.e. if it has {@link ContentWebElement.Identifier}, or
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
* {@link ChromeWebElement.Identifier} as properties.
|
|
|
|
*
|
|
|
|
* @param {Object.<string, string>} obj
|
|
|
|
* Object that represents a reference to a {@link WebElement}.
|
|
|
|
* @return {boolean}
|
|
|
|
* True if <var>obj</var> is a {@link WebElement}, false otherwise.
|
|
|
|
*/
|
|
|
|
static isReference(obj) {
|
|
|
|
if (Object.prototype.toString.call(obj) != "[object Object]") {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
ContentWebElement.Identifier in obj ||
|
|
|
|
ContentWebWindow.Identifier in obj ||
|
|
|
|
ContentWebFrame.Identifier in obj ||
|
|
|
|
ChromeWebElement.Identifier in obj
|
|
|
|
) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates a unique identifier.
|
|
|
|
*
|
|
|
|
* @return {string}
|
|
|
|
* UUID.
|
|
|
|
*/
|
|
|
|
static generateUUID() {
|
|
|
|
let uuid = uuidGen.generateUUID().toString();
|
|
|
|
return uuid.substring(1, uuid.length - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.WebElement = WebElement;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* DOM elements are represented as web elements when they are
|
|
|
|
* transported over the wire protocol.
|
|
|
|
*/
|
|
|
|
class ContentWebElement extends WebElement {
|
|
|
|
toJSON() {
|
2018-07-27 18:04:05 +03:00
|
|
|
return { [ContentWebElement.Identifier]: this.uuid };
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static fromJSON(json) {
|
2018-07-27 18:04:05 +03:00
|
|
|
const { Identifier } = ContentWebElement;
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
|
2018-07-27 18:04:05 +03:00
|
|
|
if (!(Identifier in json)) {
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError(
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
pprint`Expected web element reference, got: ${json}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-07-27 18:04:05 +03:00
|
|
|
let uuid = json[Identifier];
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
return new ContentWebElement(uuid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ContentWebElement.Identifier = "element-6066-11e4-a52e-4f735466cecf";
|
|
|
|
this.ContentWebElement = ContentWebElement;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Top-level browsing contexts, such as <code>WindowProxy</code>
|
|
|
|
* whose <code>opener</code> is null, are represented as web windows
|
|
|
|
* over the wire protocol.
|
|
|
|
*/
|
|
|
|
class ContentWebWindow extends WebElement {
|
|
|
|
toJSON() {
|
2018-07-27 18:04:05 +03:00
|
|
|
return { [ContentWebWindow.Identifier]: this.uuid };
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static fromJSON(json) {
|
|
|
|
if (!(ContentWebWindow.Identifier in json)) {
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError(
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
pprint`Expected web window reference, got: ${json}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
let uuid = json[ContentWebWindow.Identifier];
|
|
|
|
return new ContentWebWindow(uuid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ContentWebWindow.Identifier = "window-fcc6-11e5-b4f8-330a88ab9d7f";
|
|
|
|
this.ContentWebWindow = ContentWebWindow;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Nested browsing contexts, such as the <code>WindowProxy</code>
|
|
|
|
* associated with <tt><frame></tt> and <tt><iframe></tt>,
|
|
|
|
* are represented as web frames over the wire protocol.
|
|
|
|
*/
|
|
|
|
class ContentWebFrame extends WebElement {
|
|
|
|
toJSON() {
|
2018-07-27 18:04:05 +03:00
|
|
|
return { [ContentWebFrame.Identifier]: this.uuid };
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static fromJSON(json) {
|
|
|
|
if (!(ContentWebFrame.Identifier in json)) {
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError(
|
|
|
|
pprint`Expected web frame reference, got: ${json}`
|
|
|
|
);
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
}
|
|
|
|
let uuid = json[ContentWebFrame.Identifier];
|
|
|
|
return new ContentWebFrame(uuid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ContentWebFrame.Identifier = "frame-075b-4da1-b6ba-e579c2d3230a";
|
|
|
|
this.ContentWebFrame = ContentWebFrame;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* XUL elements in chrome space are represented as chrome web elements
|
|
|
|
* over the wire protocol.
|
|
|
|
*/
|
|
|
|
class ChromeWebElement extends WebElement {
|
|
|
|
toJSON() {
|
2018-07-27 18:04:05 +03:00
|
|
|
return { [ChromeWebElement.Identifier]: this.uuid };
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static fromJSON(json) {
|
|
|
|
if (!(ChromeWebElement.Identifier in json)) {
|
2017-10-24 19:42:32 +03:00
|
|
|
throw new InvalidArgumentError(
|
|
|
|
"Expected chrome element reference " +
|
2020-04-24 12:23:52 +03:00
|
|
|
pprint`for XUL element, got: ${json}`
|
Bug 1400256 - Add web element abstractions. r=whimboo
This patch introduces a series of web element abstraction types for
representing web element references.
Adds a series of new types for representing web element references
in Marionette: ChromeWebElement, ContentWebElement, ContentWebFrame,
and ContentWebWindow. The last three are direct representations of
web element, web frame, and web window definitions described in the
Webdriver specification. The first is a custom Marionette type as
we also support retrieving XUL elements from chrome space and must
be considered proprietary.
Each of the classes extend the WebElement abstract type, which is
the primary entry point when unmarshaling JSON input from the client.
Based on the characteristics of the JSON Object, one of the different
concrete types will be constructed.
The purpose of this change is to make marshaling of elements and
WindowProxies easier, both when we receive web element reference
objects from clients and when transporting them over IPC internally.
The WebElement.fromUUID function should be considered a temporary
workaround until we have fixed the current Marionette clients to send
web element reference JSON Objects as input, instead of plain {id:
<uuid>, …} fields.
MozReview-Commit-ID: FGcRq5H1Tzp
--HG--
extra : rebase_source : fe82087e8935adb519e2934fc37f1d46c21d9187
2017-10-13 21:23:45 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
let uuid = json[ChromeWebElement.Identifier];
|
|
|
|
return new ChromeWebElement(uuid);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ChromeWebElement.Identifier = "chromeelement-9fc5-4b51-a3c8-01716eedeb04";
|
|
|
|
this.ChromeWebElement = ChromeWebElement;
|