472 lines
12 KiB
JavaScript
472 lines
12 KiB
JavaScript
const { json, getKnownElement, getKnownShadowRoot } =
|
||
ChromeUtils.importESModule("chrome://remote/content/marionette/json.sys.mjs");
|
||
const { NodeCache } = ChromeUtils.importESModule(
|
||
"chrome://remote/content/shared/webdriver/NodeCache.sys.mjs"
|
||
);
|
||
const { ShadowRoot, WebElement, WebReference } = ChromeUtils.importESModule(
|
||
"chrome://remote/content/marionette/web-reference.sys.mjs"
|
||
);
|
||
|
||
const MemoryReporter = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
|
||
Ci.nsIMemoryReporterManager
|
||
);
|
||
|
||
function setupTest() {
|
||
const browser = Services.appShell.createWindowlessBrowser(false);
|
||
const nodeCache = new NodeCache();
|
||
|
||
const videoEl = browser.document.createElement("video");
|
||
browser.document.body.appendChild(videoEl);
|
||
|
||
const svgEl = browser.document.createElementNS(
|
||
"http://www.w3.org/2000/svg",
|
||
"rect"
|
||
);
|
||
browser.document.body.appendChild(svgEl);
|
||
|
||
const shadowRoot = videoEl.openOrClosedShadowRoot;
|
||
|
||
const iframeEl = browser.document.createElement("iframe");
|
||
browser.document.body.appendChild(iframeEl);
|
||
const childEl = iframeEl.contentDocument.createElement("div");
|
||
|
||
return {
|
||
browser,
|
||
browsingContext: browser.browsingContext,
|
||
nodeCache,
|
||
childEl,
|
||
iframeEl,
|
||
seenNodeIds: new Map(),
|
||
shadowRoot,
|
||
svgEl,
|
||
videoEl,
|
||
};
|
||
}
|
||
|
||
function assert_cloned_value(value, clonedValue, nodeCache, seenNodes = []) {
|
||
const { seenNodeIds, serializedValue } = json.clone(value, nodeCache);
|
||
|
||
deepEqual(serializedValue, clonedValue);
|
||
deepEqual([...seenNodeIds.values()], seenNodes);
|
||
}
|
||
|
||
add_task(function test_clone_generalTypes() {
|
||
const { nodeCache } = setupTest();
|
||
|
||
// null
|
||
assert_cloned_value(undefined, null, nodeCache);
|
||
assert_cloned_value(null, null, nodeCache);
|
||
|
||
// primitives
|
||
assert_cloned_value(true, true, nodeCache);
|
||
assert_cloned_value(42, 42, nodeCache);
|
||
assert_cloned_value("foo", "foo", nodeCache);
|
||
|
||
// toJSON
|
||
assert_cloned_value(
|
||
{
|
||
toJSON() {
|
||
return "foo";
|
||
},
|
||
},
|
||
"foo",
|
||
nodeCache
|
||
);
|
||
});
|
||
|
||
add_task(function test_clone_ShadowRoot() {
|
||
const { nodeCache, seenNodeIds, shadowRoot } = setupTest();
|
||
|
||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||
shadowRoot,
|
||
seenNodeIds
|
||
);
|
||
assert_cloned_value(
|
||
shadowRoot,
|
||
WebReference.from(shadowRoot, shadowRootRef).toJSON(),
|
||
nodeCache,
|
||
seenNodeIds
|
||
);
|
||
});
|
||
|
||
add_task(function test_clone_WebElement() {
|
||
const { videoEl, nodeCache, seenNodeIds, svgEl } = setupTest();
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
assert_cloned_value(
|
||
videoEl,
|
||
WebReference.from(videoEl, videoElRef).toJSON(),
|
||
nodeCache,
|
||
seenNodeIds
|
||
);
|
||
|
||
// Check an element with a different namespace
|
||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl, seenNodeIds);
|
||
assert_cloned_value(
|
||
svgEl,
|
||
WebReference.from(svgEl, svgElRef).toJSON(),
|
||
nodeCache,
|
||
seenNodeIds
|
||
);
|
||
});
|
||
|
||
add_task(function test_clone_Sequences() {
|
||
const { videoEl, nodeCache, seenNodeIds } = setupTest();
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
|
||
const input = [
|
||
null,
|
||
true,
|
||
[42],
|
||
videoEl,
|
||
{
|
||
toJSON() {
|
||
return "foo";
|
||
},
|
||
},
|
||
{ bar: "baz" },
|
||
];
|
||
|
||
assert_cloned_value(
|
||
input,
|
||
[
|
||
null,
|
||
true,
|
||
[42],
|
||
{ [WebElement.Identifier]: videoElRef },
|
||
"foo",
|
||
{ bar: "baz" },
|
||
],
|
||
nodeCache,
|
||
seenNodeIds
|
||
);
|
||
});
|
||
|
||
add_task(function test_clone_objects() {
|
||
const { videoEl, nodeCache, seenNodeIds } = setupTest();
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
|
||
const input = {
|
||
null: null,
|
||
boolean: true,
|
||
array: [42],
|
||
element: videoEl,
|
||
toJSON: {
|
||
toJSON() {
|
||
return "foo";
|
||
},
|
||
},
|
||
object: { bar: "baz" },
|
||
};
|
||
|
||
assert_cloned_value(
|
||
input,
|
||
{
|
||
null: null,
|
||
boolean: true,
|
||
array: [42],
|
||
element: { [WebElement.Identifier]: videoElRef },
|
||
toJSON: "foo",
|
||
object: { bar: "baz" },
|
||
},
|
||
nodeCache,
|
||
seenNodeIds
|
||
);
|
||
});
|
||
|
||
add_task(function test_clone_сyclicReference() {
|
||
const { nodeCache } = setupTest();
|
||
|
||
// object
|
||
Assert.throws(() => {
|
||
const obj = {};
|
||
obj.reference = obj;
|
||
json.clone(obj, nodeCache);
|
||
}, /JavaScriptError/);
|
||
|
||
// array
|
||
Assert.throws(() => {
|
||
const array = [];
|
||
array.push(array);
|
||
json.clone(array, nodeCache);
|
||
}, /JavaScriptError/);
|
||
|
||
// array in object
|
||
Assert.throws(() => {
|
||
const array = [];
|
||
array.push(array);
|
||
json.clone({ array }, nodeCache);
|
||
}, /JavaScriptError/);
|
||
|
||
// object in array
|
||
Assert.throws(() => {
|
||
const obj = {};
|
||
obj.reference = obj;
|
||
json.clone([obj], nodeCache);
|
||
}, /JavaScriptError/);
|
||
});
|
||
|
||
add_task(function test_deserialize_generalTypes() {
|
||
const { browsingContext, nodeCache } = setupTest();
|
||
|
||
// null
|
||
equal(json.deserialize(undefined, nodeCache, browsingContext), undefined);
|
||
equal(json.deserialize(null, nodeCache, browsingContext), null);
|
||
|
||
// primitives
|
||
equal(json.deserialize(true, nodeCache, browsingContext), true);
|
||
equal(json.deserialize(42, nodeCache, browsingContext), 42);
|
||
equal(json.deserialize("foo", nodeCache, browsingContext), "foo");
|
||
});
|
||
|
||
add_task(function test_deserialize_ShadowRoot() {
|
||
const { browsingContext, nodeCache, seenNodeIds, shadowRoot } = setupTest();
|
||
const seenNodes = new Set();
|
||
|
||
// Fails to resolve for unknown elements
|
||
const unknownShadowRootId = { [ShadowRoot.Identifier]: "foo" };
|
||
Assert.throws(() => {
|
||
json.deserialize(
|
||
unknownShadowRootId,
|
||
nodeCache,
|
||
browsingContext,
|
||
seenNodes
|
||
);
|
||
}, /NoSuchShadowRootError/);
|
||
|
||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||
shadowRoot,
|
||
seenNodeIds
|
||
);
|
||
const shadowRootEl = { [ShadowRoot.Identifier]: shadowRootRef };
|
||
|
||
// Fails to resolve for missing window reference
|
||
Assert.throws(() => json.deserialize(shadowRootEl, nodeCache), /TypeError/);
|
||
|
||
// Previously seen element is associated with original web element reference
|
||
seenNodes.add(shadowRootRef);
|
||
const root = json.deserialize(
|
||
shadowRootEl,
|
||
nodeCache,
|
||
browsingContext,
|
||
seenNodes
|
||
);
|
||
deepEqual(root, shadowRoot);
|
||
deepEqual(root, nodeCache.getNode(browsingContext, shadowRootRef));
|
||
});
|
||
|
||
add_task(function test_deserialize_WebElement() {
|
||
const { browser, browsingContext, videoEl, nodeCache, seenNodeIds } =
|
||
setupTest();
|
||
const seenNodes = new Set();
|
||
|
||
// Fails to resolve for unknown elements
|
||
const unknownWebElId = { [WebElement.Identifier]: "foo" };
|
||
Assert.throws(() => {
|
||
json.deserialize(unknownWebElId, nodeCache, browsingContext, seenNodes);
|
||
}, /NoSuchElementError/);
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
const htmlWebEl = { [WebElement.Identifier]: videoElRef };
|
||
|
||
// Fails to resolve for missing window reference
|
||
Assert.throws(() => json.deserialize(htmlWebEl, nodeCache), /TypeError/);
|
||
|
||
// Previously seen element is associated with original web element reference
|
||
seenNodes.add(videoElRef);
|
||
const el = json.deserialize(htmlWebEl, nodeCache, browsingContext, seenNodes);
|
||
deepEqual(el, videoEl);
|
||
deepEqual(el, nodeCache.getNode(browser.browsingContext, videoElRef));
|
||
});
|
||
|
||
add_task(function test_deserialize_Sequences() {
|
||
const { browsingContext, videoEl, nodeCache, seenNodeIds } = setupTest();
|
||
const seenNodes = new Set();
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
seenNodes.add(videoElRef);
|
||
|
||
const input = [
|
||
null,
|
||
true,
|
||
[42],
|
||
{ [WebElement.Identifier]: videoElRef },
|
||
{ bar: "baz" },
|
||
];
|
||
|
||
const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes);
|
||
|
||
equal(actual[0], null);
|
||
equal(actual[1], true);
|
||
deepEqual(actual[2], [42]);
|
||
deepEqual(actual[3], videoEl);
|
||
deepEqual(actual[4], { bar: "baz" });
|
||
});
|
||
|
||
add_task(function test_deserialize_objects() {
|
||
const { browsingContext, videoEl, nodeCache, seenNodeIds } = setupTest();
|
||
const seenNodes = new Set();
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
seenNodes.add(videoElRef);
|
||
|
||
const input = {
|
||
null: null,
|
||
boolean: true,
|
||
array: [42],
|
||
element: { [WebElement.Identifier]: videoElRef },
|
||
object: { bar: "baz" },
|
||
};
|
||
|
||
const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes);
|
||
|
||
equal(actual.null, null);
|
||
equal(actual.boolean, true);
|
||
deepEqual(actual.array, [42]);
|
||
deepEqual(actual.element, videoEl);
|
||
deepEqual(actual.object, { bar: "baz" });
|
||
|
||
nodeCache.clear({ all: true });
|
||
});
|
||
|
||
add_task(async function test_getKnownElement() {
|
||
const { browser, nodeCache, seenNodeIds, shadowRoot, videoEl } = setupTest();
|
||
const seenNodes = new Set();
|
||
|
||
// Unknown element reference
|
||
Assert.throws(() => {
|
||
getKnownElement(browser.browsingContext, "foo", nodeCache, seenNodes);
|
||
}, /NoSuchElementError/);
|
||
|
||
// With a ShadowRoot reference
|
||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||
shadowRoot,
|
||
seenNodeIds
|
||
);
|
||
seenNodes.add(shadowRootRef);
|
||
|
||
Assert.throws(() => {
|
||
getKnownElement(
|
||
browser.browsingContext,
|
||
shadowRootRef,
|
||
nodeCache,
|
||
seenNodes
|
||
);
|
||
}, /NoSuchElementError/);
|
||
|
||
let detachedEl = browser.document.createElement("div");
|
||
const detachedElRef = nodeCache.getOrCreateNodeReference(
|
||
detachedEl,
|
||
seenNodeIds
|
||
);
|
||
seenNodes.add(detachedElRef);
|
||
|
||
// Element not connected to the DOM
|
||
Assert.throws(() => {
|
||
getKnownElement(
|
||
browser.browsingContext,
|
||
detachedElRef,
|
||
nodeCache,
|
||
seenNodes
|
||
);
|
||
}, /StaleElementReferenceError/);
|
||
|
||
// Element garbage collected
|
||
detachedEl = null;
|
||
|
||
await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve));
|
||
Assert.throws(() => {
|
||
getKnownElement(
|
||
browser.browsingContext,
|
||
detachedElRef,
|
||
nodeCache,
|
||
seenNodes
|
||
);
|
||
}, /StaleElementReferenceError/);
|
||
|
||
// Known element reference
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
seenNodes.add(videoElRef);
|
||
|
||
equal(
|
||
getKnownElement(browser.browsingContext, videoElRef, nodeCache, seenNodes),
|
||
videoEl
|
||
);
|
||
});
|
||
|
||
add_task(async function test_getKnownShadowRoot() {
|
||
const { browser, nodeCache, seenNodeIds, shadowRoot, videoEl } = setupTest();
|
||
const seenNodes = new Set();
|
||
|
||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||
seenNodes.add(videoElRef);
|
||
|
||
// Unknown ShadowRoot reference
|
||
Assert.throws(() => {
|
||
getKnownShadowRoot(browser.browsingContext, "foo", nodeCache, seenNodes);
|
||
}, /NoSuchShadowRootError/);
|
||
|
||
// With a videoElement reference
|
||
Assert.throws(() => {
|
||
getKnownShadowRoot(
|
||
browser.browsingContext,
|
||
videoElRef,
|
||
nodeCache,
|
||
seenNodes
|
||
);
|
||
}, /NoSuchShadowRootError/);
|
||
|
||
// Known ShadowRoot reference
|
||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||
shadowRoot,
|
||
seenNodeIds
|
||
);
|
||
seenNodes.add(shadowRootRef);
|
||
|
||
equal(
|
||
getKnownShadowRoot(
|
||
browser.browsingContext,
|
||
shadowRootRef,
|
||
nodeCache,
|
||
seenNodes
|
||
),
|
||
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,
|
||
seenNodeIds
|
||
);
|
||
seenNodes.add(detachedShadowRootRef);
|
||
|
||
// ... not connected to the DOM
|
||
Assert.throws(() => {
|
||
getKnownShadowRoot(
|
||
browser.browsingContext,
|
||
detachedShadowRootRef,
|
||
nodeCache,
|
||
seenNodes
|
||
);
|
||
}, /DetachedShadowRootError/);
|
||
|
||
// ... host and shadow root garbage collected
|
||
el = null;
|
||
detachedShadowRoot = null;
|
||
|
||
await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve));
|
||
Assert.throws(() => {
|
||
getKnownShadowRoot(
|
||
browser.browsingContext,
|
||
detachedShadowRootRef,
|
||
nodeCache,
|
||
seenNodes
|
||
);
|
||
}, /DetachedShadowRootError/);
|
||
});
|