gecko-dev/remote/marionette/web-reference.sys.mjs

307 lines
8.5 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
dom: "chrome://remote/content/shared/DOM.sys.mjs",
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
pprint: "chrome://remote/content/shared/Format.sys.mjs",
});
/**
* A web reference 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.
*/
export class WebReference {
/**
* @param {string} uuid
* Identifier that must be unique across all browsing contexts
* for the contract to be upheld.
*/
constructor(uuid) {
this.uuid = lazy.assert.string(
uuid,
lazy.pprint`Expected "uuid" to be a string, got ${uuid}`
);
}
/**
* Performs an equality check between this web element and
* <var>other</var>.
*
* @param {WebReference} other
* Web element to compare with this.
*
* @returns {boolean}
* True if this and <var>other</var> are the same. False
* otherwise.
*/
is(other) {
return other instanceof WebReference && this.uuid === other.uuid;
}
toString() {
return `[object ${this.constructor.name} uuid=${this.uuid}]`;
}
/**
* Returns a new {@link WebReference} reference for a DOM or XUL element,
* <code>WindowProxy</code>, or <code>ShadowRoot</code>.
*
* @param {(Element|ShadowRoot|WindowProxy|MockXULElement)} node
* Node to construct a web element reference for.
* @param {string=} uuid
* Optional unique identifier of the WebReference if already known.
* If not defined a new unique identifier will be created.
*
* @returns {WebReference}
* Web reference for <var>node</var>.
*
* @throws {InvalidArgumentError}
* If <var>node</var> is neither a <code>WindowProxy</code>,
* DOM or XUL element, or <code>ShadowRoot</code>.
*/
static from(node, uuid) {
if (uuid === undefined) {
uuid = lazy.generateUUID();
}
if (lazy.dom.isShadowRoot(node) && !lazy.dom.isInPrivilegedDocument(node)) {
// When we support Chrome Shadowroots we will need to
// do a check here of shadowroot.host being in a privileged document
// See Bug 1743541
return new ShadowRoot(uuid);
} else if (lazy.dom.isElement(node)) {
return new WebElement(uuid);
} else if (lazy.dom.isDOMWindow(node)) {
if (node.parent === node) {
return new WebWindow(uuid);
}
return new WebFrame(uuid);
}
throw new lazy.error.InvalidArgumentError(
"Expected DOM window/element " + lazy.pprint`or XUL element, got: ${node}`
);
}
/**
* Unmarshals a JSON Object to one of {@link ShadowRoot}, {@link WebElement},
* {@link WebFrame}, or {@link WebWindow}.
*
* @param {Record<string, string>} json
* Web reference, which is supposed to be a JSON Object
* where the key is one of the {@link WebReference} concrete
* classes' UUID identifiers.
*
* @returns {WebReference}
* Web reference for the JSON object.
*
* @throws {InvalidArgumentError}
* If <var>json</var> is not a web reference.
*/
static fromJSON(json) {
lazy.assert.object(
json,
lazy.pprint`Expected web reference to be an object, got ${json}`
);
if (json instanceof WebReference) {
return json;
}
let keys = Object.keys(json);
for (let key of keys) {
switch (key) {
case ShadowRoot.Identifier:
return ShadowRoot.fromJSON(json);
case WebElement.Identifier:
return WebElement.fromJSON(json);
case WebFrame.Identifier:
return WebFrame.fromJSON(json);
case WebWindow.Identifier:
return WebWindow.fromJSON(json);
}
}
throw new lazy.error.InvalidArgumentError(
lazy.pprint`Expected web reference, got: ${json}`
);
}
/**
* Checks if <var>obj<var> is a {@link WebReference} reference.
*
* @param {Record<string, string>} obj
* Object that represents a {@link WebReference}.
*
* @returns {boolean}
* True if <var>obj</var> is a {@link WebReference}, false otherwise.
*/
static isReference(obj) {
if (Object.prototype.toString.call(obj) != "[object Object]") {
return false;
}
if (
ShadowRoot.Identifier in obj ||
WebElement.Identifier in obj ||
WebFrame.Identifier in obj ||
WebWindow.Identifier in obj
) {
return true;
}
return false;
}
}
/**
* Shadow Root elements are represented as shadow root references when they are
* transported over the wire protocol
*/
export class ShadowRoot extends WebReference {
toJSON() {
return { [ShadowRoot.Identifier]: this.uuid };
}
static fromJSON(json) {
const { Identifier } = ShadowRoot;
if (!(Identifier in json)) {
throw new lazy.error.InvalidArgumentError(
lazy.pprint`Expected shadow root reference, got: ${json}`
);
}
let uuid = json[Identifier];
return new ShadowRoot(uuid);
}
/**
* Constructs a {@link ShadowRoot} from 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 shadow root representations.
*
* @param {string} uuid
* UUID to be associated with the web reference.
*
* @returns {ShadowRoot}
* The shadow root reference.
*
* @throws {InvalidArgumentError}
* If <var>uuid</var> is not a string.
*/
static fromUUID(uuid) {
lazy.assert.string(
uuid,
lazy.pprint`Expected "uuid" to be a string, got: ${uuid}`
);
return new ShadowRoot(uuid);
}
}
ShadowRoot.Identifier = "shadow-6066-11e4-a52e-4f735466cecf";
/**
* DOM elements are represented as web elements when they are
* transported over the wire protocol.
*/
export class WebElement extends WebReference {
toJSON() {
return { [WebElement.Identifier]: this.uuid };
}
static fromJSON(json) {
const { Identifier } = WebElement;
if (!(Identifier in json)) {
throw new lazy.error.InvalidArgumentError(
lazy.pprint`Expected web element reference, got: ${json}`
);
}
let uuid = json[Identifier];
return new WebElement(uuid);
}
/**
* Constructs a {@link WebElement} from 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.
*
* @param {string} uuid
* UUID to be associated with the web reference.
*
* @returns {WebElement}
* The web element reference.
*
* @throws {InvalidArgumentError}
* If <var>uuid</var> is not a string.
*/
static fromUUID(uuid) {
return new WebElement(uuid);
}
}
WebElement.Identifier = "element-6066-11e4-a52e-4f735466cecf";
/**
* Nested browsing contexts, such as the <code>WindowProxy</code>
* associated with <tt>&lt;frame&gt;</tt> and <tt>&lt;iframe&gt;</tt>,
* are represented as web frames over the wire protocol.
*/
export class WebFrame extends WebReference {
toJSON() {
return { [WebFrame.Identifier]: this.uuid };
}
static fromJSON(json) {
if (!(WebFrame.Identifier in json)) {
throw new lazy.error.InvalidArgumentError(
lazy.pprint`Expected web frame reference, got: ${json}`
);
}
let uuid = json[WebFrame.Identifier];
return new WebFrame(uuid);
}
}
WebFrame.Identifier = "frame-075b-4da1-b6ba-e579c2d3230a";
/**
* Top-level browsing contexts, such as <code>WindowProxy</code>
* whose <code>opener</code> is null, are represented as web windows
* over the wire protocol.
*/
export class WebWindow extends WebReference {
toJSON() {
return { [WebWindow.Identifier]: this.uuid };
}
static fromJSON(json) {
if (!(WebWindow.Identifier in json)) {
throw new lazy.error.InvalidArgumentError(
lazy.pprint`Expected web window reference, got: ${json}`
);
}
let uuid = json[WebWindow.Identifier];
return new WebWindow(uuid);
}
}
WebWindow.Identifier = "window-fcc6-11e5-b4f8-330a88ab9d7f";