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:
Henrik Skupin 2023-05-12 19:15:55 +00:00
parent 7d0e610e9a
commit cb7c4b36b3
5 changed files with 281 additions and 343 deletions

View File

@ -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;
}
}

View File

@ -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.
*

View File

@ -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

View File

@ -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();

View File

@ -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);