mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 1822466 - [marionette] Move WebElement and ShadowRoot serialization/deserialization helpers to Marionette command client actor. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D177491
This commit is contained in:
parent
7d0e610e9a
commit
cb7c4b36b3
@ -21,6 +21,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
sandbox: "chrome://remote/content/marionette/evaluate.sys.mjs",
|
||||
Sandboxes: "chrome://remote/content/marionette/evaluate.sys.mjs",
|
||||
WebReference: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
||||
@ -58,6 +59,10 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
return this._legacyactions;
|
||||
}
|
||||
|
||||
get #nodeCache() {
|
||||
return this.#processActor.getNodeCache();
|
||||
}
|
||||
|
||||
actorCreated() {
|
||||
lazy.logger.trace(
|
||||
`[${this.browsingContext.id}] MarionetteCommands actor created ` +
|
||||
@ -82,11 +87,12 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
let waitForNextTick = false;
|
||||
|
||||
const { name, data: serializedData } = msg;
|
||||
const data = lazy.json.deserialize(
|
||||
serializedData,
|
||||
this.#processActor.getNodeCache(),
|
||||
this.contentWindow
|
||||
);
|
||||
const data = lazy.json.deserialize({
|
||||
value: serializedData,
|
||||
win: this.contentWindow,
|
||||
getKnownElement: this.#getKnownElement.bind(this),
|
||||
getKnownShadowRoot: this.#getKnownShadowRoot.bind(this),
|
||||
});
|
||||
|
||||
switch (name) {
|
||||
case "MarionetteCommandsParent:clearElement":
|
||||
@ -184,7 +190,10 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
}
|
||||
|
||||
return {
|
||||
data: lazy.json.clone(result, this.#processActor.getNodeCache()),
|
||||
data: lazy.json.clone({
|
||||
value: result,
|
||||
getOrCreateNodeReference: this.#getOrCreateNodeReference.bind(this),
|
||||
}),
|
||||
};
|
||||
} catch (e) {
|
||||
// Always wrap errors as WebDriverError
|
||||
@ -605,4 +614,146 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
|
||||
return { browsingContextId: browsingContext.id };
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
#getKnownElement(browsingContext, nodeId) {
|
||||
if (!this.#isNodeReferenceKnown(browsingContext, nodeId)) {
|
||||
throw new lazy.error.NoSuchElementError(
|
||||
`The element with the reference ${nodeId} is not known in the current browsing context`
|
||||
);
|
||||
}
|
||||
|
||||
const node = this.#nodeCache.getNode(browsingContext, nodeId);
|
||||
|
||||
// Ensure the node is of the correct Node type.
|
||||
if (node !== null && !lazy.element.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.element.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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
#getKnownShadowRoot(browsingContext, nodeId) {
|
||||
if (!this.#isNodeReferenceKnown(browsingContext, nodeId)) {
|
||||
throw new lazy.error.NoSuchShadowRootError(
|
||||
`The shadow root with the reference ${nodeId} is not known in the current browsing context`
|
||||
);
|
||||
}
|
||||
|
||||
const node = this.#nodeCache.getNode(browsingContext, nodeId);
|
||||
|
||||
// Ensure the node is of the correct Node type.
|
||||
if (node !== null && !lazy.element.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.element.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WebReference for the given node.
|
||||
*
|
||||
* Hereby it tries to find a known node reference for that node in the
|
||||
* node cache, and returns it. Otherwise it creates a new reference and
|
||||
* adds it to the cache.
|
||||
*
|
||||
* @param {Node} node
|
||||
* The node to create or get a WebReference for.
|
||||
*
|
||||
* @returns {WebReference} The web reference for the node.
|
||||
*/
|
||||
#getOrCreateNodeReference(node) {
|
||||
const nodeRef = this.#nodeCache.getOrCreateNodeReference(node);
|
||||
return lazy.WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if the element is known in the given browsing context.
|
||||
*/
|
||||
#isNodeReferenceKnown(browsingContext, nodeId) {
|
||||
const nodeDetails = this.#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;
|
||||
}
|
||||
}
|
||||
|
@ -467,102 +467,6 @@ element.findClosest = function(startNode, selector) {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
element.getKnownElement = function(browsingContext, nodeId, nodeCache) {
|
||||
if (!element.isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
|
||||
throw new lazy.error.NoSuchElementError(
|
||||
`The element with the reference ${nodeId} is not known in the current browsing context`
|
||||
);
|
||||
}
|
||||
|
||||
const node = nodeCache.getNode(browsingContext, nodeId);
|
||||
|
||||
// Ensure the node is of the correct Node type.
|
||||
if (node !== null && !element.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 || element.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.
|
||||
*/
|
||||
element.getKnownShadowRoot = function(browsingContext, nodeId, nodeCache) {
|
||||
if (!element.isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
|
||||
throw new lazy.error.NoSuchShadowRootError(
|
||||
`The shadow root with the reference ${nodeId} is not known in the current browsing context`
|
||||
);
|
||||
}
|
||||
|
||||
const node = nodeCache.getNode(browsingContext, nodeId);
|
||||
|
||||
// Ensure the node is of the correct Node type.
|
||||
if (node !== null && !element.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 || element.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 <var>obj<var> is an HTML or JS collection.
|
||||
*
|
||||
@ -608,39 +512,6 @@ element.isDetached = function(shadowRoot) {
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
element.isNodeReferenceKnown = function(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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if <var>el</var> is stale.
|
||||
*
|
||||
|
@ -90,10 +90,12 @@ function cloneObject(value, seen, cloneAlgorithm) {
|
||||
*
|
||||
* - If a cyclic references is detected a JavaScriptError is thrown.
|
||||
*
|
||||
* @param {object} value
|
||||
* @param {object} options
|
||||
* @param {object} options.value
|
||||
* Object to be cloned.
|
||||
* @param {NodeCache} nodeCache
|
||||
* Node cache that holds already seen WebElement and ShadowRoot references.
|
||||
* @param {Function} options.getOrCreateNodeReference
|
||||
* Callback that tries to use a known node reference from the node cache,
|
||||
* or creates a new one if not present yet.
|
||||
*
|
||||
* @returns {object}
|
||||
* Same object as provided by `value` with the WebDriver specific
|
||||
@ -105,7 +107,9 @@ function cloneObject(value, seen, cloneAlgorithm) {
|
||||
* If the element has gone stale, indicating it is no longer
|
||||
* attached to the DOM.
|
||||
*/
|
||||
json.clone = function(value, nodeCache) {
|
||||
json.clone = function(options) {
|
||||
const { getOrCreateNodeReference, value } = options;
|
||||
|
||||
function cloneJSON(value, seen) {
|
||||
if (seen === undefined) {
|
||||
seen = new Set();
|
||||
@ -143,8 +147,7 @@ json.clone = function(value, nodeCache) {
|
||||
);
|
||||
}
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value);
|
||||
return lazy.WebReference.from(value, nodeRef).toJSON();
|
||||
return getOrCreateNodeReference(value).toJSON();
|
||||
}
|
||||
|
||||
if (isNode && lazy.element.isShadowRoot(value)) {
|
||||
@ -157,8 +160,7 @@ json.clone = function(value, nodeCache) {
|
||||
);
|
||||
}
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value);
|
||||
return lazy.WebReference.from(value, nodeRef).toJSON();
|
||||
return getOrCreateNodeReference(value).toJSON();
|
||||
}
|
||||
|
||||
if (typeof value.toJSON == "function") {
|
||||
@ -183,23 +185,32 @@ json.clone = function(value, nodeCache) {
|
||||
/**
|
||||
* Deserialize an arbitrary object.
|
||||
*
|
||||
* @param {object} value
|
||||
* @param {object} options
|
||||
* @param {object} options.value
|
||||
* Arbitrary object.
|
||||
* @param {NodeCache} nodeCache
|
||||
* Node cache that holds already seen WebElement and ShadowRoot references.
|
||||
* @param {WindowProxy} win
|
||||
* @param {WindowProxy} options.win
|
||||
* Current window.
|
||||
* @param {Function} options.getKnownElement
|
||||
* Callback that will try to resolve a WebElement reference to an Element node.
|
||||
* @param {Function} options.getKnownShadowRoot
|
||||
* Callback that will try to resolve a ShadowRoot reference to a ShadowRoot node.
|
||||
*
|
||||
* @returns {object}
|
||||
* Same object as provided by `value` with the WebDriver specific
|
||||
* references replaced with real JavaScript objects.
|
||||
*
|
||||
* @throws {DetachedShadowRootError}
|
||||
* If the ShadowRoot is detached, indicating it is no longer attached to the DOM.
|
||||
* @throws {NoSuchElementError}
|
||||
* If the WebElement reference has not been seen before.
|
||||
* @throws {NoSuchShadowRootError}
|
||||
* If the ShadowRoot reference has not been seen before.
|
||||
* @throws {StaleElementReferenceError}
|
||||
* If the element is stale, indicating it is no longer attached to the DOM.
|
||||
*/
|
||||
json.deserialize = function(value, nodeCache, win) {
|
||||
json.deserialize = function(options) {
|
||||
const { value, win, getKnownElement, getKnownShadowRoot } = options;
|
||||
|
||||
function deserializeJSON(value, seen) {
|
||||
if (seen === undefined) {
|
||||
seen = new Set();
|
||||
@ -222,19 +233,11 @@ json.deserialize = function(value, nodeCache, win) {
|
||||
const webRef = lazy.WebReference.fromJSON(value);
|
||||
|
||||
if (webRef instanceof lazy.ShadowRoot) {
|
||||
return lazy.element.getKnownShadowRoot(
|
||||
win.browsingContext,
|
||||
webRef.uuid,
|
||||
nodeCache
|
||||
);
|
||||
return getKnownShadowRoot(win.browsingContext, webRef.uuid);
|
||||
}
|
||||
|
||||
if (webRef instanceof lazy.WebElement) {
|
||||
return lazy.element.getKnownElement(
|
||||
win.browsingContext,
|
||||
webRef.uuid,
|
||||
nodeCache
|
||||
);
|
||||
return getKnownElement(win.browsingContext, webRef.uuid);
|
||||
}
|
||||
|
||||
// WebFrame and WebWindow not supported yet
|
||||
|
@ -12,13 +12,6 @@ const {
|
||||
} = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/marionette/element.sys.mjs"
|
||||
);
|
||||
const { NodeCache } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/NodeCache.sys.mjs"
|
||||
);
|
||||
|
||||
const MemoryReporter = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
|
||||
Ci.nsIMemoryReporterManager
|
||||
);
|
||||
|
||||
class MockElement {
|
||||
constructor(tagName, attrs = {}) {
|
||||
@ -103,7 +96,6 @@ function setupTest() {
|
||||
|
||||
return {
|
||||
browser,
|
||||
nodeCache: new NodeCache(),
|
||||
childEl,
|
||||
divEl,
|
||||
iframeEl,
|
||||
@ -455,142 +447,6 @@ add_task(function test_coordinates() {
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_isNodeReferenceKnown() {
|
||||
const { browser, nodeCache, childEl, iframeEl, videoEl } = setupTest();
|
||||
|
||||
// Unknown node reference
|
||||
ok(!element.isNodeReferenceKnown(browser.browsingContext, "foo", nodeCache));
|
||||
|
||||
// Known node reference
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl);
|
||||
ok(
|
||||
element.isNodeReferenceKnown(browser.browsingContext, videoElRef, nodeCache)
|
||||
);
|
||||
|
||||
// Different top-level browsing context
|
||||
const browser2 = Services.appShell.createWindowlessBrowser(false);
|
||||
ok(
|
||||
!element.isNodeReferenceKnown(
|
||||
browser2.browsingContext,
|
||||
videoElRef,
|
||||
nodeCache
|
||||
)
|
||||
);
|
||||
|
||||
// Different child browsing context
|
||||
const childElRef = nodeCache.getOrCreateNodeReference(childEl);
|
||||
const childBrowsingContext = iframeEl.contentWindow.browsingContext;
|
||||
ok(element.isNodeReferenceKnown(childBrowsingContext, childElRef, nodeCache));
|
||||
|
||||
const iframeEl2 = browser2.document.createElement("iframe");
|
||||
browser2.document.body.appendChild(iframeEl2);
|
||||
const childBrowsingContext2 = iframeEl2.contentWindow.browsingContext;
|
||||
ok(
|
||||
!element.isNodeReferenceKnown(childBrowsingContext2, childElRef, nodeCache)
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_getKnownElement() {
|
||||
const { browser, nodeCache, shadowRoot, videoEl } = setupTest();
|
||||
|
||||
// Unknown element reference
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(browser.browsingContext, "foo", nodeCache);
|
||||
}, /NoSuchElementError/);
|
||||
|
||||
// With a ShadowRoot reference
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(browser.browsingContext, shadowRootRef, nodeCache);
|
||||
}, /NoSuchElementError/);
|
||||
|
||||
// Deleted element (eg. garbage collected)
|
||||
let detachedEl = browser.document.createElement("div");
|
||||
const detachedElRef = nodeCache.getOrCreateNodeReference(detachedEl);
|
||||
|
||||
// ... not connected to the DOM
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(browser.browsingContext, detachedElRef, nodeCache);
|
||||
}, /StaleElementReferenceError/);
|
||||
|
||||
// ... element garbage collected
|
||||
detachedEl = null;
|
||||
MemoryReporter.minimizeMemoryUsage(() => {
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
detachedElRef,
|
||||
nodeCache
|
||||
);
|
||||
}, /StaleElementReferenceError/);
|
||||
});
|
||||
|
||||
// Known element reference
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl);
|
||||
equal(
|
||||
element.getKnownElement(browser.browsingContext, videoElRef, nodeCache),
|
||||
videoEl
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_getKnownShadowRoot() {
|
||||
const { browser, nodeCache, shadowRoot, videoEl } = setupTest();
|
||||
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl);
|
||||
|
||||
// Unknown ShadowRoot reference
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(browser.browsingContext, "foo", nodeCache);
|
||||
}, /NoSuchShadowRootError/);
|
||||
|
||||
// With a HTMLElement reference
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(browser.browsingContext, videoElRef, nodeCache);
|
||||
}, /NoSuchShadowRootError/);
|
||||
|
||||
// Known ShadowRoot reference
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
equal(
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
shadowRootRef,
|
||||
nodeCache
|
||||
),
|
||||
shadowRoot
|
||||
);
|
||||
|
||||
// Detached ShadowRoot host
|
||||
let el = browser.document.createElement("div");
|
||||
let detachedShadowRoot = el.attachShadow({ mode: "open" });
|
||||
detachedShadowRoot.innerHTML = "<input></input>";
|
||||
|
||||
const detachedShadowRootRef = nodeCache.getOrCreateNodeReference(
|
||||
detachedShadowRoot
|
||||
);
|
||||
|
||||
// ... not connected to the DOM
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
detachedShadowRootRef,
|
||||
nodeCache
|
||||
);
|
||||
}, /DetachedShadowRootError/);
|
||||
|
||||
// ... host and shadow root garbage collected
|
||||
el = null;
|
||||
detachedShadowRoot = null;
|
||||
MemoryReporter.minimizeMemoryUsage(() => {
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
detachedShadowRootRef,
|
||||
nodeCache
|
||||
);
|
||||
}, /DetachedShadowRootError/);
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_isDetached() {
|
||||
const { childEl, iframeEl } = setupTest();
|
||||
|
||||
|
@ -31,22 +31,22 @@ function setupTest() {
|
||||
}
|
||||
|
||||
add_task(function test_clone_generalTypes() {
|
||||
const { nodeCache } = setupTest();
|
||||
|
||||
// null
|
||||
equal(json.clone(undefined, nodeCache), null);
|
||||
equal(json.clone(null, nodeCache), null);
|
||||
equal(json.clone({ value: undefined }), null);
|
||||
equal(json.clone({ value: null }), null);
|
||||
|
||||
// primitives
|
||||
equal(json.clone(true, nodeCache), true);
|
||||
equal(json.clone(42, nodeCache), 42);
|
||||
equal(json.clone("foo", nodeCache), "foo");
|
||||
equal(json.clone({ value: true }), true);
|
||||
equal(json.clone({ value: 42 }), 42);
|
||||
equal(json.clone({ value: "foo" }), "foo");
|
||||
|
||||
// toJSON
|
||||
equal(
|
||||
json.clone({
|
||||
toJSON() {
|
||||
return "foo";
|
||||
value: {
|
||||
toJSON() {
|
||||
return "foo";
|
||||
},
|
||||
},
|
||||
}),
|
||||
"foo"
|
||||
@ -56,9 +56,14 @@ add_task(function test_clone_generalTypes() {
|
||||
add_task(function test_clone_ShadowRoot() {
|
||||
const { nodeCache, shadowRoot } = setupTest();
|
||||
|
||||
function getOrCreateNodeReference(node) {
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
deepEqual(
|
||||
json.clone(shadowRoot, nodeCache),
|
||||
json.clone({ value: shadowRoot, getOrCreateNodeReference }),
|
||||
WebReference.from(shadowRoot, shadowRootRef).toJSON()
|
||||
);
|
||||
});
|
||||
@ -66,16 +71,21 @@ add_task(function test_clone_ShadowRoot() {
|
||||
add_task(function test_clone_WebElement() {
|
||||
const { htmlEl, nodeCache, svgEl } = setupTest();
|
||||
|
||||
function getOrCreateNodeReference(node) {
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
deepEqual(
|
||||
json.clone(htmlEl, nodeCache),
|
||||
json.clone({ value: htmlEl, getOrCreateNodeReference }),
|
||||
WebReference.from(htmlEl, htmlElRef).toJSON()
|
||||
);
|
||||
|
||||
// Check an element with a different namespace
|
||||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl);
|
||||
deepEqual(
|
||||
json.clone(svgEl, nodeCache),
|
||||
json.clone({ value: svgEl, getOrCreateNodeReference }),
|
||||
WebReference.from(svgEl, svgElRef).toJSON()
|
||||
);
|
||||
});
|
||||
@ -83,6 +93,11 @@ add_task(function test_clone_WebElement() {
|
||||
add_task(function test_clone_Sequences() {
|
||||
const { htmlEl, nodeCache } = setupTest();
|
||||
|
||||
function getOrCreateNodeReference(node) {
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
const input = [
|
||||
@ -98,7 +113,7 @@ add_task(function test_clone_Sequences() {
|
||||
{ bar: "baz" },
|
||||
];
|
||||
|
||||
const actual = json.clone(input, nodeCache);
|
||||
const actual = json.clone({ value: input, getOrCreateNodeReference });
|
||||
|
||||
equal(actual[0], null);
|
||||
equal(actual[1], true);
|
||||
@ -111,6 +126,11 @@ add_task(function test_clone_Sequences() {
|
||||
add_task(function test_clone_objects() {
|
||||
const { htmlEl, nodeCache } = setupTest();
|
||||
|
||||
function getOrCreateNodeReference(node) {
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
const input = {
|
||||
@ -126,7 +146,7 @@ add_task(function test_clone_objects() {
|
||||
object: { bar: "baz" },
|
||||
};
|
||||
|
||||
const actual = json.clone(input, nodeCache);
|
||||
const actual = json.clone({ value: input, getOrCreateNodeReference });
|
||||
|
||||
equal(actual.null, null);
|
||||
equal(actual.boolean, true);
|
||||
@ -137,70 +157,85 @@ add_task(function test_clone_objects() {
|
||||
});
|
||||
|
||||
add_task(function test_clone_сyclicReference() {
|
||||
const { nodeCache } = setupTest();
|
||||
|
||||
// object
|
||||
Assert.throws(() => {
|
||||
const obj = {};
|
||||
obj.reference = obj;
|
||||
json.clone(obj, nodeCache);
|
||||
json.clone({ value: obj });
|
||||
}, /JavaScriptError/);
|
||||
|
||||
// array
|
||||
Assert.throws(() => {
|
||||
const array = [];
|
||||
array.push(array);
|
||||
json.clone(array, nodeCache);
|
||||
json.clone({ value: array });
|
||||
}, /JavaScriptError/);
|
||||
|
||||
// array in object
|
||||
Assert.throws(() => {
|
||||
const array = [];
|
||||
array.push(array);
|
||||
json.clone({ array }, nodeCache);
|
||||
json.clone({ value: { array } });
|
||||
}, /JavaScriptError/);
|
||||
|
||||
// object in array
|
||||
Assert.throws(() => {
|
||||
const obj = {};
|
||||
obj.reference = obj;
|
||||
json.clone([obj], nodeCache);
|
||||
json.clone({ value: [obj] });
|
||||
}, /JavaScriptError/);
|
||||
});
|
||||
|
||||
add_task(function test_deserialize_generalTypes() {
|
||||
const { browser, nodeCache } = setupTest();
|
||||
const { browser } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
// null
|
||||
equal(json.deserialize(undefined, nodeCache, win), undefined);
|
||||
equal(json.deserialize(null, nodeCache, win), null);
|
||||
equal(json.deserialize({ value: undefined, win }), undefined);
|
||||
equal(json.deserialize({ value: null, win }), null);
|
||||
|
||||
// primitives
|
||||
equal(json.deserialize(true, nodeCache, win), true);
|
||||
equal(json.deserialize(42, nodeCache, win), 42);
|
||||
equal(json.deserialize("foo", nodeCache, win), "foo");
|
||||
equal(json.deserialize({ value: true, win }), true);
|
||||
equal(json.deserialize({ value: 42, win }), 42);
|
||||
equal(json.deserialize({ value: "foo", win }), "foo");
|
||||
});
|
||||
|
||||
add_task(function test_deserialize_ShadowRoot() {
|
||||
const { browser, nodeCache, shadowRoot } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
// Fails to resolve for unknown elements
|
||||
function getKnownShadowRoot(browsingContext, nodeId) {
|
||||
return nodeCache.getNode(browsingContext, nodeId);
|
||||
}
|
||||
|
||||
// Unknown shadow root
|
||||
const unknownShadowRootId = { [ShadowRoot.Identifier]: "foo" };
|
||||
Assert.throws(() => {
|
||||
json.deserialize(unknownShadowRootId, nodeCache, win);
|
||||
}, /NoSuchShadowRootError/);
|
||||
equal(
|
||||
json.deserialize({ value: unknownShadowRootId, getKnownShadowRoot, win }),
|
||||
null
|
||||
);
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
const shadowRootEl = { [ShadowRoot.Identifier]: shadowRootRef };
|
||||
|
||||
// Fails to resolve for missing getKnownShadowRoot callback
|
||||
Assert.throws(
|
||||
() => equal(json.deserialize({ value: shadowRootEl, win })),
|
||||
/TypeError/
|
||||
);
|
||||
|
||||
// Fails to resolve for missing window reference
|
||||
Assert.throws(() => json.deserialize(shadowRootEl, nodeCache), /TypeError/);
|
||||
Assert.throws(
|
||||
() => equal(json.deserialize({ value: shadowRootEl, getKnownShadowRoot })),
|
||||
/TypeError/
|
||||
);
|
||||
|
||||
// Previously seen element is associated with original web element reference
|
||||
const root = json.deserialize(shadowRootEl, nodeCache, win);
|
||||
deepEqual(root, shadowRoot);
|
||||
const root = json.deserialize({
|
||||
value: shadowRootEl,
|
||||
getKnownShadowRoot,
|
||||
win,
|
||||
});
|
||||
deepEqual(root, nodeCache.getNode(browser.browsingContext, shadowRootRef));
|
||||
});
|
||||
|
||||
@ -208,20 +243,34 @@ add_task(function test_deserialize_WebElement() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
// Fails to resolve for unknown elements
|
||||
function getKnownElement(browsingContext, nodeId) {
|
||||
return nodeCache.getNode(browsingContext, nodeId);
|
||||
}
|
||||
|
||||
// Unknown element
|
||||
const unknownWebElId = { [WebElement.Identifier]: "foo" };
|
||||
Assert.throws(() => {
|
||||
json.deserialize(unknownWebElId, nodeCache, win);
|
||||
}, /NoSuchElementError/);
|
||||
equal(
|
||||
json.deserialize({ value: unknownWebElId, getKnownElement, win }),
|
||||
null
|
||||
);
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlWebEl = { [WebElement.Identifier]: htmlElRef };
|
||||
|
||||
// Fails to resolve for missing getKnownElement callback
|
||||
Assert.throws(
|
||||
() => equal(json.deserialize({ value: htmlWebEl, win })),
|
||||
/TypeError/
|
||||
);
|
||||
|
||||
// Fails to resolve for missing window reference
|
||||
Assert.throws(() => json.deserialize(htmlWebEl, nodeCache), /TypeError/);
|
||||
Assert.throws(
|
||||
() => equal(json.deserialize({ value: htmlWebEl, getKnownElement })),
|
||||
/TypeError/
|
||||
);
|
||||
|
||||
// Previously seen element is associated with original web element reference
|
||||
const el = json.deserialize(htmlWebEl, nodeCache, win);
|
||||
const el = json.deserialize({ value: htmlWebEl, getKnownElement, win });
|
||||
deepEqual(el, htmlEl);
|
||||
deepEqual(el, nodeCache.getNode(browser.browsingContext, htmlElRef));
|
||||
});
|
||||
@ -230,6 +279,10 @@ add_task(function test_deserialize_Sequences() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
function getKnownElement(browsingContext, nodeId) {
|
||||
return nodeCache.getNode(browsingContext, nodeId);
|
||||
}
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
const input = [
|
||||
@ -240,7 +293,7 @@ add_task(function test_deserialize_Sequences() {
|
||||
{ bar: "baz" },
|
||||
];
|
||||
|
||||
const actual = json.deserialize(input, nodeCache, win);
|
||||
const actual = json.deserialize({ value: input, getKnownElement, win });
|
||||
|
||||
equal(actual[0], null);
|
||||
equal(actual[1], true);
|
||||
@ -253,6 +306,10 @@ add_task(function test_deserialize_objects() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
function getKnownElement(browsingContext, nodeId) {
|
||||
return nodeCache.getNode(browsingContext, nodeId);
|
||||
}
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
const input = {
|
||||
@ -263,7 +320,7 @@ add_task(function test_deserialize_objects() {
|
||||
object: { bar: "baz" },
|
||||
};
|
||||
|
||||
const actual = json.deserialize(input, nodeCache, win);
|
||||
const actual = json.deserialize({ value: input, getKnownElement, win });
|
||||
|
||||
equal(actual.null, null);
|
||||
equal(actual.boolean, true);
|
||||
|
Loading…
Reference in New Issue
Block a user