2023-01-06 16:31:23 +00: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/. */
|
|
|
|
|
2023-10-24 05:00:05 +00:00
|
|
|
import { WebFrame, WebWindow } from "./web-reference.sys.mjs";
|
|
|
|
|
2023-01-06 16:31:23 +00:00
|
|
|
const lazy = {};
|
|
|
|
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
2023-06-12 08:53:05 +00:00
|
|
|
dom: "chrome://remote/content/shared/DOM.sys.mjs",
|
2023-01-06 16:31:23 +00:00
|
|
|
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
|
|
|
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
|
|
|
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
2023-06-12 08:53:05 +00:00
|
|
|
ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
2023-10-24 05:00:05 +00:00
|
|
|
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
2023-06-12 08:53:05 +00:00
|
|
|
WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
2023-10-24 05:00:05 +00:00
|
|
|
WebFrame: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
2023-06-12 08:53:05 +00:00
|
|
|
WebReference: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
2023-10-24 05:00:05 +00:00
|
|
|
WebWindow: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
2023-01-06 16:31:23 +00:00
|
|
|
});
|
|
|
|
|
Bug 1845311 - Use ChromeUtils.defineLazyGetter in more places r=arai,webdriver-reviewers,geckoview-reviewers,extension-reviewers,application-update-reviewers,credential-management-reviewers,devtools-reviewers,fxview-reviewers,anti-tracking-reviewers,sessionstore-reviewers,pbz,joschmidt,robwu,issammani,bytesized,owlish,dao
Differential Revision: https://phabricator.services.mozilla.com/D184481
2023-07-26 16:28:11 +00:00
|
|
|
ChromeUtils.defineLazyGetter(lazy, "logger", () =>
|
2023-01-06 16:31:23 +00:00
|
|
|
lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
|
|
|
|
);
|
|
|
|
|
|
|
|
/** @namespace */
|
|
|
|
export const json = {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clone an object including collections.
|
|
|
|
*
|
2023-04-05 16:25:47 +00:00
|
|
|
* @param {object} value
|
2023-01-06 16:31:23 +00:00
|
|
|
* Object to be cloned.
|
2023-01-06 16:31:24 +00:00
|
|
|
* @param {Set} seen
|
|
|
|
* List of objects already processed.
|
2023-01-06 16:31:23 +00:00
|
|
|
* @param {Function} cloneAlgorithm
|
|
|
|
* The clone algorithm to invoke for individual list entries or object
|
|
|
|
* properties.
|
|
|
|
*
|
2023-05-16 12:33:54 +00:00
|
|
|
* @returns {object}
|
|
|
|
* The cloned object.
|
2023-01-06 16:31:23 +00:00
|
|
|
*/
|
2023-05-16 12:33:54 +00:00
|
|
|
function cloneObject(value, seen, cloneAlgorithm) {
|
2023-01-06 16:31:24 +00:00
|
|
|
// Only proceed with cloning an object if it hasn't been seen yet.
|
|
|
|
if (seen.has(value)) {
|
|
|
|
throw new lazy.error.JavaScriptError("Cyclic object value");
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
2023-01-06 16:31:24 +00:00
|
|
|
seen.add(value);
|
|
|
|
|
|
|
|
let result;
|
2023-01-06 16:31:23 +00:00
|
|
|
|
2023-06-12 08:53:05 +00:00
|
|
|
if (lazy.dom.isCollection(value)) {
|
2023-05-16 12:33:54 +00:00
|
|
|
result = [...value].map(entry => cloneAlgorithm(entry, seen));
|
2023-01-06 16:31:24 +00:00
|
|
|
} else {
|
|
|
|
// arbitrary objects
|
|
|
|
result = {};
|
|
|
|
for (let prop in value) {
|
|
|
|
try {
|
2023-05-16 12:33:54 +00:00
|
|
|
result[prop] = cloneAlgorithm(value[prop], seen);
|
2023-01-06 16:31:24 +00:00
|
|
|
} catch (e) {
|
|
|
|
if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
|
|
|
|
lazy.logger.debug(`Skipping ${prop}: ${e.message}`);
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:31:24 +00:00
|
|
|
seen.delete(value);
|
|
|
|
|
2023-01-06 16:31:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clone arbitrary objects to JSON-safe primitives that can be
|
|
|
|
* transported across processes and over the Marionette protocol.
|
|
|
|
*
|
|
|
|
* The marshaling rules are as follows:
|
|
|
|
*
|
|
|
|
* - Primitives are returned as is.
|
|
|
|
*
|
|
|
|
* - Collections, such as `Array`, `NodeList`, `HTMLCollection`
|
|
|
|
* et al. are transformed to arrays and then recursed.
|
|
|
|
*
|
|
|
|
* - Elements and ShadowRoots that are not known WebReference's are added to
|
|
|
|
* the `NodeCache`. For both the associated unique web reference identifier
|
|
|
|
* is returned.
|
|
|
|
*
|
|
|
|
* - Objects with custom JSON representations, i.e. if they have
|
|
|
|
* a callable `toJSON` function, are returned verbatim. This means
|
|
|
|
* their internal integrity _are not_ checked. Be careful.
|
|
|
|
*
|
2023-01-06 16:31:24 +00:00
|
|
|
* - If a cyclic references is detected a JavaScriptError is thrown.
|
2023-01-06 16:31:23 +00:00
|
|
|
*
|
2023-05-16 12:33:54 +00:00
|
|
|
* @param {object} value
|
2023-05-12 19:15:56 +00:00
|
|
|
* Object to be cloned.
|
2023-05-16 12:33:54 +00:00
|
|
|
* @param {NodeCache} nodeCache
|
|
|
|
* Node cache that holds already seen WebElement and ShadowRoot references.
|
2023-01-06 16:31:23 +00:00
|
|
|
*
|
2024-08-23 16:43:56 +00:00
|
|
|
* @returns {{
|
|
|
|
* seenNodeIds: Map<BrowsingContext, string[]>,
|
|
|
|
* serializedValue: any,
|
|
|
|
* hasSerializedWindows: boolean
|
|
|
|
* }}
|
2023-06-12 08:53:04 +00:00
|
|
|
* Object that contains a list of browsing contexts each with a list of
|
|
|
|
* shared ids for collected elements and shadow root nodes, and second the
|
|
|
|
* same object as provided by `value` with the WebDriver classic supported
|
|
|
|
* DOM nodes replaced by WebReference's.
|
2023-01-06 16:31:23 +00:00
|
|
|
*
|
|
|
|
* @throws {JavaScriptError}
|
|
|
|
* If an object contains cyclic references.
|
|
|
|
* @throws {StaleElementReferenceError}
|
|
|
|
* If the element has gone stale, indicating it is no longer
|
|
|
|
* attached to the DOM.
|
|
|
|
*/
|
2023-05-20 12:26:49 +00:00
|
|
|
json.clone = function (value, nodeCache) {
|
2023-06-12 08:53:04 +00:00
|
|
|
const seenNodeIds = new Map();
|
2023-10-24 05:00:05 +00:00
|
|
|
let hasSerializedWindows = false;
|
2023-06-12 08:53:04 +00:00
|
|
|
|
2023-05-16 12:33:54 +00:00
|
|
|
function cloneJSON(value, seen) {
|
2023-01-06 16:31:24 +00:00
|
|
|
if (seen === undefined) {
|
|
|
|
seen = new Set();
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:31:23 +00:00
|
|
|
if ([undefined, null].includes(value)) {
|
|
|
|
return null;
|
2023-01-26 21:38:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const type = typeof value;
|
|
|
|
|
|
|
|
if (["boolean", "number", "string"].includes(type)) {
|
2023-01-06 16:31:23 +00:00
|
|
|
// Primitive values
|
|
|
|
return value;
|
2023-01-26 21:38:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Evaluation of code might take place in mutable sandboxes, which are
|
2023-10-24 05:00:05 +00:00
|
|
|
// created to waive XRays by default. As such DOM nodes and windows
|
|
|
|
// have to be unwaived before accessing properties like "ownerGlobal"
|
|
|
|
// is possible.
|
2023-01-27 10:31:48 +00:00
|
|
|
//
|
|
|
|
// Until bug 1743788 is fixed there might be the possibility that more
|
|
|
|
// objects might need to be unwaived as well.
|
2023-01-26 21:38:26 +00:00
|
|
|
const isNode = Node.isInstance(value);
|
2023-10-24 05:00:05 +00:00
|
|
|
const isWindow = Window.isInstance(value);
|
|
|
|
if (isNode || isWindow) {
|
2023-01-26 21:38:26 +00:00
|
|
|
value = Cu.unwaiveXrays(value);
|
|
|
|
}
|
|
|
|
|
2023-06-12 08:53:05 +00:00
|
|
|
if (isNode && lazy.dom.isElement(value)) {
|
2023-01-27 10:31:48 +00:00
|
|
|
// Convert DOM elements to WebReference instances.
|
2023-01-06 16:31:23 +00:00
|
|
|
|
2023-06-12 08:53:05 +00:00
|
|
|
if (lazy.dom.isStale(value)) {
|
2023-01-27 10:31:48 +00:00
|
|
|
// Don't create a reference for stale elements.
|
2023-01-06 16:31:23 +00:00
|
|
|
throw new lazy.error.StaleElementReferenceError(
|
2023-01-26 21:38:26 +00:00
|
|
|
lazy.pprint`The element ${value} is no longer attached to the DOM`
|
2023-01-06 16:31:23 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-06-12 08:53:04 +00:00
|
|
|
const nodeRef = nodeCache.getOrCreateNodeReference(value, seenNodeIds);
|
|
|
|
|
2023-05-16 12:33:54 +00:00
|
|
|
return lazy.WebReference.from(value, nodeRef).toJSON();
|
2023-01-26 21:38:26 +00:00
|
|
|
}
|
|
|
|
|
2023-06-12 08:53:05 +00:00
|
|
|
if (isNode && lazy.dom.isShadowRoot(value)) {
|
2023-01-27 10:31:48 +00:00
|
|
|
// Convert ShadowRoot instances to WebReference references.
|
|
|
|
|
2023-06-12 08:53:05 +00:00
|
|
|
if (lazy.dom.isDetached(value)) {
|
2023-01-27 10:31:48 +00:00
|
|
|
// Don't create a reference for detached shadow roots.
|
|
|
|
throw new lazy.error.DetachedShadowRootError(
|
|
|
|
lazy.pprint`The ShadowRoot ${value} is no longer attached to the DOM`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-06-12 08:53:04 +00:00
|
|
|
const nodeRef = nodeCache.getOrCreateNodeReference(value, seenNodeIds);
|
|
|
|
|
2023-05-16 12:33:54 +00:00
|
|
|
return lazy.WebReference.from(value, nodeRef).toJSON();
|
2023-01-27 10:31:48 +00:00
|
|
|
}
|
|
|
|
|
2023-10-24 05:00:05 +00:00
|
|
|
if (isWindow) {
|
|
|
|
// Convert window instances to WebReference references.
|
|
|
|
let reference;
|
|
|
|
|
|
|
|
if (value.browsingContext.parent == null) {
|
|
|
|
reference = new WebWindow(value.browsingContext.browserId.toString());
|
|
|
|
hasSerializedWindows = true;
|
|
|
|
} else {
|
|
|
|
reference = new WebFrame(value.browsingContext.id.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
return reference.toJSON();
|
|
|
|
}
|
|
|
|
|
2023-01-26 21:38:26 +00:00
|
|
|
if (typeof value.toJSON == "function") {
|
2023-01-06 16:31:23 +00:00
|
|
|
// custom JSON representation
|
|
|
|
let unsafeJSON;
|
|
|
|
try {
|
|
|
|
unsafeJSON = value.toJSON();
|
|
|
|
} catch (e) {
|
|
|
|
throw new lazy.error.JavaScriptError(`toJSON() failed with: ${e}`);
|
|
|
|
}
|
2023-01-26 21:38:26 +00:00
|
|
|
|
2023-01-06 16:31:24 +00:00
|
|
|
return cloneJSON(unsafeJSON, seen);
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Collections and arbitrary objects
|
2023-01-06 16:31:24 +00:00
|
|
|
return cloneObject(value, seen, cloneJSON);
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
|
|
|
|
2023-10-24 05:00:05 +00:00
|
|
|
return {
|
|
|
|
seenNodeIds,
|
|
|
|
serializedValue: cloneJSON(value, new Set()),
|
|
|
|
hasSerializedWindows,
|
|
|
|
};
|
2023-01-06 16:31:23 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Deserialize an arbitrary object.
|
|
|
|
*
|
2023-05-16 12:33:54 +00:00
|
|
|
* @param {object} value
|
2023-05-12 19:15:56 +00:00
|
|
|
* Arbitrary object.
|
2023-05-16 12:33:54 +00:00
|
|
|
* @param {NodeCache} nodeCache
|
|
|
|
* Node cache that holds already seen WebElement and ShadowRoot references.
|
2023-06-12 08:53:04 +00:00
|
|
|
* @param {BrowsingContext} browsingContext
|
|
|
|
* The browsing context to check.
|
2023-01-06 16:31:23 +00:00
|
|
|
*
|
2023-05-16 12:33:54 +00:00
|
|
|
* @returns {object}
|
|
|
|
* Same object as provided by `value` with the WebDriver specific
|
|
|
|
* references replaced with real JavaScript objects.
|
2023-01-06 16:31:23 +00:00
|
|
|
*
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If the WebElement reference has not been seen before.
|
|
|
|
* @throws {StaleElementReferenceError}
|
|
|
|
* If the element is stale, indicating it is no longer attached to the DOM.
|
|
|
|
*/
|
2023-06-12 08:53:04 +00:00
|
|
|
json.deserialize = function (value, nodeCache, browsingContext) {
|
2023-05-16 12:33:54 +00:00
|
|
|
function deserializeJSON(value, seen) {
|
2023-01-06 16:31:24 +00:00
|
|
|
if (seen === undefined) {
|
|
|
|
seen = new Set();
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:31:23 +00:00
|
|
|
if (value === undefined || value === null) {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (typeof value) {
|
|
|
|
case "boolean":
|
|
|
|
case "number":
|
|
|
|
case "string":
|
|
|
|
default:
|
|
|
|
return value;
|
|
|
|
|
|
|
|
case "object":
|
|
|
|
if (lazy.WebReference.isReference(value)) {
|
|
|
|
// Create a WebReference based on the WebElement identifier.
|
|
|
|
const webRef = lazy.WebReference.fromJSON(value);
|
|
|
|
|
2023-01-27 10:31:48 +00:00
|
|
|
if (webRef instanceof lazy.ShadowRoot) {
|
2023-06-12 08:53:05 +00:00
|
|
|
return getKnownShadowRoot(browsingContext, webRef.uuid, nodeCache);
|
2023-01-27 10:31:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (webRef instanceof lazy.WebElement) {
|
2023-06-12 08:53:05 +00:00
|
|
|
return getKnownElement(browsingContext, webRef.uuid, nodeCache);
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
|
|
|
|
2023-10-24 05:00:05 +00:00
|
|
|
if (webRef instanceof lazy.WebFrame) {
|
|
|
|
const browsingContext = BrowsingContext.get(webRef.uuid);
|
|
|
|
|
|
|
|
if (browsingContext === null || browsingContext.parent === null) {
|
|
|
|
throw new lazy.error.NoSuchWindowError(
|
|
|
|
`Unable to locate frame with id: ${webRef.uuid}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return browsingContext.window;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (webRef instanceof lazy.WebWindow) {
|
|
|
|
const browsingContext = BrowsingContext.getCurrentTopByBrowserId(
|
|
|
|
webRef.uuid
|
|
|
|
);
|
|
|
|
|
|
|
|
if (browsingContext === null) {
|
|
|
|
throw new lazy.error.NoSuchWindowError(
|
|
|
|
`Unable to locate window with id: ${webRef.uuid}`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return browsingContext.window;
|
|
|
|
}
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
|
|
|
|
2023-01-06 16:31:24 +00:00
|
|
|
return cloneObject(value, seen, deserializeJSON);
|
2023-01-06 16:31:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-06 16:31:24 +00:00
|
|
|
return deserializeJSON(value, new Set());
|
2023-01-06 16:31:23 +00:00
|
|
|
};
|
2023-06-12 08:53:05 +00:00
|
|
|
|
2023-10-24 05:00:05 +00:00
|
|
|
/**
|
|
|
|
* Convert unique navigable ids to internal browser ids.
|
|
|
|
*
|
|
|
|
* @param {object} serializedData
|
|
|
|
* The data to process.
|
|
|
|
*
|
|
|
|
* @returns {object}
|
|
|
|
* The processed data.
|
|
|
|
*/
|
|
|
|
json.mapFromNavigableIds = function (serializedData) {
|
|
|
|
function _processData(data) {
|
|
|
|
if (lazy.WebReference.isReference(data)) {
|
|
|
|
const webRef = lazy.WebReference.fromJSON(data);
|
|
|
|
|
|
|
|
if (webRef instanceof lazy.WebWindow) {
|
|
|
|
const browser = lazy.TabManager.getBrowserById(webRef.uuid);
|
|
|
|
if (browser) {
|
|
|
|
webRef.uuid = browser?.browserId.toString();
|
|
|
|
data = webRef.toJSON();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (typeof data === "object") {
|
|
|
|
for (const entry in data) {
|
|
|
|
data[entry] = _processData(data[entry]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _processData(serializedData);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert browser ids to unique navigable ids.
|
|
|
|
*
|
|
|
|
* @param {object} serializedData
|
|
|
|
* The data to process.
|
|
|
|
*
|
|
|
|
* @returns {object}
|
|
|
|
* The processed data.
|
|
|
|
*/
|
|
|
|
json.mapToNavigableIds = function (serializedData) {
|
|
|
|
function _processData(data) {
|
|
|
|
if (lazy.WebReference.isReference(data)) {
|
|
|
|
const webRef = lazy.WebReference.fromJSON(data);
|
|
|
|
if (webRef instanceof lazy.WebWindow) {
|
|
|
|
const browsingContext = BrowsingContext.getCurrentTopByBrowserId(
|
|
|
|
webRef.uuid
|
|
|
|
);
|
|
|
|
|
|
|
|
webRef.uuid = lazy.TabManager.getIdForBrowsingContext(browsingContext);
|
|
|
|
data = webRef.toJSON();
|
|
|
|
}
|
|
|
|
} else if (typeof data == "object") {
|
|
|
|
for (const entry in data) {
|
|
|
|
data[entry] = _processData(data[entry]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _processData(serializedData);
|
|
|
|
};
|
|
|
|
|
2023-06-12 08:53:05 +00:00
|
|
|
/**
|
|
|
|
* Resolve element from specified web reference identifier.
|
|
|
|
*
|
|
|
|
* @param {BrowsingContext} browsingContext
|
|
|
|
* The browsing context to retrieve the element from.
|
|
|
|
* @param {string} nodeId
|
|
|
|
* The WebReference uuid for a DOM element.
|
|
|
|
* @param {NodeCache} nodeCache
|
|
|
|
* Node cache that holds already seen WebElement and ShadowRoot references.
|
|
|
|
*
|
|
|
|
* @returns {Element}
|
|
|
|
* The DOM element that the identifier was generated for.
|
|
|
|
*
|
|
|
|
* @throws {NoSuchElementError}
|
|
|
|
* If the element doesn't exist in the current browsing context.
|
|
|
|
* @throws {StaleElementReferenceError}
|
|
|
|
* If the element has gone stale, indicating its node document is no
|
|
|
|
* longer the active document or it is no longer attached to the DOM.
|
|
|
|
*/
|
|
|
|
export function getKnownElement(browsingContext, nodeId, nodeCache) {
|
|
|
|
if (!isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
|
|
|
|
throw new lazy.error.NoSuchElementError(
|
|
|
|
`The element with the reference ${nodeId} is not known in the current browsing context`,
|
|
|
|
{ elementId: nodeId }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const node = nodeCache.getNode(browsingContext, nodeId);
|
|
|
|
|
|
|
|
// Ensure the node is of the correct Node type.
|
|
|
|
if (node !== null && !lazy.dom.isElement(node)) {
|
|
|
|
throw new lazy.error.NoSuchElementError(
|
|
|
|
`The element with the reference ${nodeId} is not of type HTMLElement`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If null, which may be the case if the element has been unwrapped from a
|
|
|
|
// weak reference, it is always considered stale.
|
|
|
|
if (node === null || lazy.dom.isStale(node)) {
|
|
|
|
throw new lazy.error.StaleElementReferenceError(
|
|
|
|
`The element with the reference ${nodeId} ` +
|
|
|
|
"is stale; either its node document is not the active document, " +
|
|
|
|
"or it is no longer connected to the DOM"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resolve ShadowRoot from specified web reference identifier.
|
|
|
|
*
|
|
|
|
* @param {BrowsingContext} browsingContext
|
|
|
|
* The browsing context to retrieve the shadow root from.
|
|
|
|
* @param {string} nodeId
|
|
|
|
* The WebReference uuid for a ShadowRoot.
|
|
|
|
* @param {NodeCache} nodeCache
|
|
|
|
* Node cache that holds already seen WebElement and ShadowRoot references.
|
|
|
|
*
|
|
|
|
* @returns {ShadowRoot}
|
|
|
|
* The ShadowRoot that the identifier was generated for.
|
|
|
|
*
|
|
|
|
* @throws {NoSuchShadowRootError}
|
|
|
|
* If the ShadowRoot doesn't exist in the current browsing context.
|
|
|
|
* @throws {DetachedShadowRootError}
|
|
|
|
* If the ShadowRoot is detached, indicating its node document is no
|
|
|
|
* longer the active document or it is no longer attached to the DOM.
|
|
|
|
*/
|
|
|
|
export function getKnownShadowRoot(browsingContext, nodeId, nodeCache) {
|
|
|
|
if (!isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
|
|
|
|
throw new lazy.error.NoSuchShadowRootError(
|
|
|
|
`The shadow root with the reference ${nodeId} is not known in the current browsing context`,
|
|
|
|
{ shadowId: nodeId }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
const node = nodeCache.getNode(browsingContext, nodeId);
|
|
|
|
|
|
|
|
// Ensure the node is of the correct Node type.
|
|
|
|
if (node !== null && !lazy.dom.isShadowRoot(node)) {
|
|
|
|
throw new lazy.error.NoSuchShadowRootError(
|
|
|
|
`The shadow root with the reference ${nodeId} is not of type ShadowRoot`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// If null, which may be the case if the element has been unwrapped from a
|
|
|
|
// weak reference, it is always considered stale.
|
|
|
|
if (node === null || lazy.dom.isDetached(node)) {
|
|
|
|
throw new lazy.error.DetachedShadowRootError(
|
|
|
|
`The shadow root with the reference ${nodeId} ` +
|
|
|
|
"is detached; either its node document is not the active document, " +
|
|
|
|
"or it is no longer connected to the DOM"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines if the node reference is known for the given browsing context.
|
|
|
|
*
|
|
|
|
* For WebDriver classic only nodes from the same browsing context are
|
|
|
|
* allowed to be accessed.
|
|
|
|
*
|
|
|
|
* @param {BrowsingContext} browsingContext
|
|
|
|
* The browsing context the element has to be part of.
|
|
|
|
* @param {ElementIdentifier} nodeId
|
|
|
|
* The WebElement reference identifier for a DOM element.
|
|
|
|
* @param {NodeCache} nodeCache
|
|
|
|
* Node cache that holds already seen node references.
|
|
|
|
*
|
|
|
|
* @returns {boolean}
|
|
|
|
* True if the element is known in the given browsing context.
|
|
|
|
*/
|
|
|
|
function isNodeReferenceKnown(browsingContext, nodeId, nodeCache) {
|
|
|
|
const nodeDetails = nodeCache.getReferenceDetails(nodeId);
|
|
|
|
if (nodeDetails === null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nodeDetails.isTopBrowsingContext) {
|
|
|
|
// As long as Navigables are not available any cross-group navigation will
|
|
|
|
// cause a swap of the current top-level browsing context. The only unique
|
|
|
|
// identifier in such a case is the browser id the top-level browsing
|
|
|
|
// context actually lives in.
|
|
|
|
return nodeDetails.browserId === browsingContext.browserId;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nodeDetails.browsingContextId === browsingContext.id;
|
|
|
|
}
|