Bug 1822466 - [marionette] Check Navigable's seen nodes map for known nodes. r=webdriver-reviewers,jdescottes
Differential Revision: https://phabricator.services.mozilla.com/D177492
This commit is contained in:
@@ -82,10 +82,11 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
let waitForNextTick = false;
|
||||
|
||||
const { name, data: serializedData } = msg;
|
||||
|
||||
const data = lazy.json.deserialize(
|
||||
serializedData,
|
||||
this.#processActor.getNodeCache(),
|
||||
this.contentWindow
|
||||
this.contentWindow.browsingContext
|
||||
);
|
||||
|
||||
switch (name) {
|
||||
@@ -183,9 +184,14 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
||||
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
|
||||
}
|
||||
|
||||
return {
|
||||
data: lazy.json.clone(result, this.#processActor.getNodeCache()),
|
||||
};
|
||||
const { seenNodeIds, serializedValue } = lazy.json.clone(
|
||||
result,
|
||||
this.#processActor.getNodeCache()
|
||||
);
|
||||
|
||||
// Because in WebDriver classic nodes can only be returned from the same
|
||||
// browsing context, we only need the seen unique ids as flat array.
|
||||
return { seenNodeIds: [...seenNodeIds.values()].flat(), serializedValue };
|
||||
} catch (e) {
|
||||
// Always wrap errors as WebDriverError
|
||||
return { error: lazy.error.wrap(e).toJSON() };
|
||||
|
||||
@@ -9,6 +9,8 @@ const lazy = {};
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
capture: "chrome://remote/content/shared/Capture.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
getSeenNodesForBrowsingContext:
|
||||
"chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
});
|
||||
|
||||
@@ -18,7 +20,6 @@ XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
||||
|
||||
// Because Marionette supports a single session only we store its id
|
||||
// globally so that the parent actor can access it.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let webDriverSessionId = null;
|
||||
|
||||
export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||
@@ -32,20 +33,68 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||
});
|
||||
}
|
||||
|
||||
async sendQuery(name, data) {
|
||||
async sendQuery(name, serializedValue) {
|
||||
const seenNodes = lazy.getSeenNodesForBrowsingContext(
|
||||
webDriverSessionId,
|
||||
this.manager.browsingContext
|
||||
);
|
||||
|
||||
// return early if a dialog is opened
|
||||
const result = await Promise.race([
|
||||
super.sendQuery(name, data),
|
||||
const {
|
||||
error,
|
||||
seenNodeIds,
|
||||
serializedValue: serializedResult,
|
||||
} = await Promise.race([
|
||||
super.sendQuery(name, serializedValue),
|
||||
this.dialogOpenedPromise(),
|
||||
]).finally(() => {
|
||||
this._resolveDialogOpened = null;
|
||||
});
|
||||
|
||||
if ("error" in result) {
|
||||
throw lazy.error.WebDriverError.fromJSON(result.error);
|
||||
} else {
|
||||
return result.data;
|
||||
if (error) {
|
||||
const err = lazy.error.WebDriverError.fromJSON(error);
|
||||
this.#handleError(err, seenNodes);
|
||||
}
|
||||
|
||||
// Update seen nodes for serialized element and shadow root nodes.
|
||||
seenNodeIds?.forEach(nodeId => seenNodes.add(nodeId));
|
||||
|
||||
return serializedResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle WebDriver error and replace error type if necessary.
|
||||
*
|
||||
* @param {WebDriverError} error
|
||||
* The WebDriver error to handle.
|
||||
* @param {Set<string>} seenNodes
|
||||
* List of node ids already seen in this navigable.
|
||||
*
|
||||
* @throws {WebDriverError}
|
||||
* The original or replaced WebDriver error.
|
||||
*/
|
||||
#handleError(error, seenNodes) {
|
||||
// If an element hasn't been found during deserialization check if it
|
||||
// may be a stale reference.
|
||||
if (
|
||||
error instanceof lazy.error.NoSuchElementError &&
|
||||
error.data.elementId !== undefined &&
|
||||
seenNodes.has(error.data.elementId)
|
||||
) {
|
||||
throw new lazy.error.StaleElementReferenceError(error);
|
||||
}
|
||||
|
||||
// If a shadow root hasn't been found during deserialization check if it
|
||||
// may be a detached reference.
|
||||
if (
|
||||
error instanceof lazy.error.NoSuchShadowRootError &&
|
||||
error.data.shadowId !== undefined &&
|
||||
seenNodes.has(error.data.shadowId)
|
||||
) {
|
||||
throw new lazy.error.DetachedShadowRootError(error);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
notifyDialogOpened() {
|
||||
|
||||
@@ -488,9 +488,10 @@ element.findClosest = function (startNode, selector) {
|
||||
* 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)) {
|
||||
if (!isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
|
||||
throw new lazy.error.NoSuchElementError(
|
||||
`The element with the reference ${nodeId} is not known in the current browsing context`
|
||||
`The element with the reference ${nodeId} is not known in the current browsing context`,
|
||||
{ elementId: nodeId }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -536,9 +537,10 @@ element.getKnownElement = function (browsingContext, nodeId, nodeCache) {
|
||||
* 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)) {
|
||||
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`
|
||||
`The shadow root with the reference ${nodeId} is not known in the current browsing context`,
|
||||
{ shadowId: nodeId }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -564,6 +566,39 @@ element.getKnownShadowRoot = function (browsingContext, nodeId, nodeCache) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if <var>obj<var> is an HTML or JS collection.
|
||||
*
|
||||
@@ -609,39 +644,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.
|
||||
*
|
||||
|
||||
@@ -95,9 +95,11 @@ function cloneObject(value, seen, cloneAlgorithm) {
|
||||
* @param {NodeCache} nodeCache
|
||||
* Node cache that holds already seen WebElement and ShadowRoot references.
|
||||
*
|
||||
* @returns {object}
|
||||
* Same object as provided by `value` with the WebDriver specific
|
||||
* elements replaced by WebReference's.
|
||||
* @returns {Object<Map<BrowsingContext, Array<string>, object>>}
|
||||
* 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.
|
||||
*
|
||||
* @throws {JavaScriptError}
|
||||
* If an object contains cyclic references.
|
||||
@@ -106,6 +108,8 @@ function cloneObject(value, seen, cloneAlgorithm) {
|
||||
* attached to the DOM.
|
||||
*/
|
||||
json.clone = function (value, nodeCache) {
|
||||
const seenNodeIds = new Map();
|
||||
|
||||
function cloneJSON(value, seen) {
|
||||
if (seen === undefined) {
|
||||
seen = new Set();
|
||||
@@ -143,7 +147,8 @@ json.clone = function (value, nodeCache) {
|
||||
);
|
||||
}
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value);
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value, seenNodeIds);
|
||||
|
||||
return lazy.WebReference.from(value, nodeRef).toJSON();
|
||||
}
|
||||
|
||||
@@ -157,7 +162,8 @@ json.clone = function (value, nodeCache) {
|
||||
);
|
||||
}
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value);
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value, seenNodeIds);
|
||||
|
||||
return lazy.WebReference.from(value, nodeRef).toJSON();
|
||||
}
|
||||
|
||||
@@ -177,7 +183,7 @@ json.clone = function (value, nodeCache) {
|
||||
return cloneObject(value, seen, cloneJSON);
|
||||
}
|
||||
|
||||
return cloneJSON(value, new Set());
|
||||
return { seenNodeIds, serializedValue: cloneJSON(value, new Set()) };
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -187,8 +193,8 @@ json.clone = function (value, nodeCache) {
|
||||
* Arbitrary object.
|
||||
* @param {NodeCache} nodeCache
|
||||
* Node cache that holds already seen WebElement and ShadowRoot references.
|
||||
* @param {WindowProxy} win
|
||||
* Current window.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The browsing context to check.
|
||||
*
|
||||
* @returns {object}
|
||||
* Same object as provided by `value` with the WebDriver specific
|
||||
@@ -199,7 +205,7 @@ json.clone = function (value, nodeCache) {
|
||||
* @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 (value, nodeCache, browsingContext) {
|
||||
function deserializeJSON(value, seen) {
|
||||
if (seen === undefined) {
|
||||
seen = new Set();
|
||||
@@ -223,7 +229,7 @@ json.deserialize = function (value, nodeCache, win) {
|
||||
|
||||
if (webRef instanceof lazy.ShadowRoot) {
|
||||
return lazy.element.getKnownShadowRoot(
|
||||
win.browsingContext,
|
||||
browsingContext,
|
||||
webRef.uuid,
|
||||
nodeCache
|
||||
);
|
||||
@@ -231,7 +237,7 @@ json.deserialize = function (value, nodeCache, win) {
|
||||
|
||||
if (webRef instanceof lazy.WebElement) {
|
||||
return lazy.element.getKnownElement(
|
||||
win.browsingContext,
|
||||
browsingContext,
|
||||
webRef.uuid,
|
||||
nodeCache
|
||||
);
|
||||
|
||||
@@ -449,106 +449,123 @@ 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() {
|
||||
add_task(async function test_getKnownElement() {
|
||||
const { browser, nodeCache, shadowRoot, videoEl } = setupTest();
|
||||
const seenNodes = new Set();
|
||||
|
||||
// Unknown element reference
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(browser.browsingContext, "foo", nodeCache);
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
"foo",
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /NoSuchElementError/);
|
||||
|
||||
// With a ShadowRoot reference
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
const seenNodeIds = new Map();
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||||
shadowRoot,
|
||||
seenNodeIds
|
||||
);
|
||||
seenNodes.add(shadowRootRef);
|
||||
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(browser.browsingContext, shadowRootRef, nodeCache);
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
shadowRootRef,
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /NoSuchElementError/);
|
||||
|
||||
// Deleted element (eg. garbage collected)
|
||||
let detachedEl = browser.document.createElement("div");
|
||||
const detachedElRef = nodeCache.getOrCreateNodeReference(detachedEl);
|
||||
const detachedElRef = nodeCache.getOrCreateNodeReference(
|
||||
detachedEl,
|
||||
seenNodeIds
|
||||
);
|
||||
seenNodes.add(detachedElRef);
|
||||
|
||||
// ... not connected to the DOM
|
||||
// Element not connected to the DOM
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(browser.browsingContext, detachedElRef, nodeCache);
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
detachedElRef,
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /StaleElementReferenceError/);
|
||||
|
||||
// ... element garbage collected
|
||||
// Element garbage collected
|
||||
detachedEl = null;
|
||||
MemoryReporter.minimizeMemoryUsage(() => {
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
detachedElRef,
|
||||
nodeCache
|
||||
);
|
||||
}, /StaleElementReferenceError/);
|
||||
});
|
||||
|
||||
await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve));
|
||||
Assert.throws(() => {
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
detachedElRef,
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /StaleElementReferenceError/);
|
||||
|
||||
// Known element reference
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl);
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||||
seenNodes.add(videoElRef);
|
||||
|
||||
equal(
|
||||
element.getKnownElement(browser.browsingContext, videoElRef, nodeCache),
|
||||
element.getKnownElement(
|
||||
browser.browsingContext,
|
||||
videoElRef,
|
||||
nodeCache,
|
||||
seenNodes
|
||||
),
|
||||
videoEl
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_getKnownShadowRoot() {
|
||||
add_task(async function test_getKnownShadowRoot() {
|
||||
const { browser, nodeCache, shadowRoot, videoEl } = setupTest();
|
||||
const seenNodeIds = new Map();
|
||||
const seenNodes = new Set();
|
||||
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl);
|
||||
const videoElRef = nodeCache.getOrCreateNodeReference(videoEl, seenNodeIds);
|
||||
seenNodes.add(videoElRef);
|
||||
|
||||
// Unknown ShadowRoot reference
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(browser.browsingContext, "foo", nodeCache);
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
"foo",
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /NoSuchShadowRootError/);
|
||||
|
||||
// With a HTMLElement reference
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(browser.browsingContext, videoElRef, nodeCache);
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
videoElRef,
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /NoSuchShadowRootError/);
|
||||
|
||||
// Known ShadowRoot reference
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||||
shadowRoot,
|
||||
seenNodeIds
|
||||
);
|
||||
seenNodes.add(shadowRootRef);
|
||||
|
||||
equal(
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
shadowRootRef,
|
||||
nodeCache
|
||||
nodeCache,
|
||||
seenNodes
|
||||
),
|
||||
shadowRoot
|
||||
);
|
||||
@@ -558,30 +575,35 @@ add_task(function test_getKnownShadowRoot() {
|
||||
let detachedShadowRoot = el.attachShadow({ mode: "open" });
|
||||
detachedShadowRoot.innerHTML = "<input></input>";
|
||||
|
||||
const detachedShadowRootRef =
|
||||
nodeCache.getOrCreateNodeReference(detachedShadowRoot);
|
||||
const detachedShadowRootRef = nodeCache.getOrCreateNodeReference(
|
||||
detachedShadowRoot,
|
||||
seenNodeIds
|
||||
);
|
||||
seenNodes.add(detachedShadowRootRef);
|
||||
|
||||
// ... not connected to the DOM
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
detachedShadowRootRef,
|
||||
nodeCache
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /DetachedShadowRootError/);
|
||||
|
||||
// ... host and shadow root garbage collected
|
||||
el = null;
|
||||
detachedShadowRoot = null;
|
||||
MemoryReporter.minimizeMemoryUsage(() => {
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
detachedShadowRootRef,
|
||||
nodeCache
|
||||
);
|
||||
}, /DetachedShadowRootError/);
|
||||
});
|
||||
|
||||
await new Promise(resolve => MemoryReporter.minimizeMemoryUsage(resolve));
|
||||
Assert.throws(() => {
|
||||
element.getKnownShadowRoot(
|
||||
browser.browsingContext,
|
||||
detachedShadowRootRef,
|
||||
nodeCache,
|
||||
seenNodes
|
||||
);
|
||||
}, /DetachedShadowRootError/);
|
||||
});
|
||||
|
||||
add_task(function test_isDetached() {
|
||||
|
||||
@@ -27,68 +27,95 @@ function setupTest() {
|
||||
browser.document.body.appendChild(iframeEl);
|
||||
const childEl = iframeEl.contentDocument.createElement("div");
|
||||
|
||||
return { browser, nodeCache, childEl, iframeEl, htmlEl, shadowRoot, svgEl };
|
||||
return {
|
||||
browser,
|
||||
browsingContext: browser.browsingContext,
|
||||
nodeCache,
|
||||
childEl,
|
||||
iframeEl,
|
||||
htmlEl,
|
||||
seenNodeIds: new Map(),
|
||||
shadowRoot,
|
||||
svgEl,
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
equal(json.clone(undefined, nodeCache), null);
|
||||
equal(json.clone(null, nodeCache), null);
|
||||
assert_cloned_value(undefined, null, nodeCache);
|
||||
assert_cloned_value(null, null, nodeCache);
|
||||
|
||||
// primitives
|
||||
equal(json.clone(true, nodeCache), true);
|
||||
equal(json.clone(42, nodeCache), 42);
|
||||
equal(json.clone("foo", nodeCache), "foo");
|
||||
assert_cloned_value(true, true, nodeCache);
|
||||
assert_cloned_value(42, 42, nodeCache);
|
||||
assert_cloned_value("foo", "foo", nodeCache);
|
||||
|
||||
// toJSON
|
||||
equal(
|
||||
json.clone({
|
||||
assert_cloned_value(
|
||||
{
|
||||
toJSON() {
|
||||
return "foo";
|
||||
},
|
||||
}),
|
||||
"foo"
|
||||
},
|
||||
"foo",
|
||||
nodeCache
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_clone_ShadowRoot() {
|
||||
const { nodeCache, shadowRoot } = setupTest();
|
||||
const { nodeCache, seenNodeIds, shadowRoot } = setupTest();
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
deepEqual(
|
||||
json.clone(shadowRoot, nodeCache),
|
||||
WebReference.from(shadowRoot, shadowRootRef).toJSON()
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||||
shadowRoot,
|
||||
seenNodeIds
|
||||
);
|
||||
assert_cloned_value(
|
||||
shadowRoot,
|
||||
WebReference.from(shadowRoot, shadowRootRef).toJSON(),
|
||||
nodeCache,
|
||||
seenNodeIds
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_clone_WebElement() {
|
||||
const { htmlEl, nodeCache, svgEl } = setupTest();
|
||||
const { htmlEl, nodeCache, seenNodeIds, svgEl } = setupTest();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
deepEqual(
|
||||
json.clone(htmlEl, nodeCache),
|
||||
WebReference.from(htmlEl, htmlElRef).toJSON()
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl, seenNodeIds);
|
||||
assert_cloned_value(
|
||||
htmlEl,
|
||||
WebReference.from(htmlEl, htmlElRef).toJSON(),
|
||||
nodeCache,
|
||||
seenNodeIds
|
||||
);
|
||||
|
||||
// Check an element with a different namespace
|
||||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl);
|
||||
deepEqual(
|
||||
json.clone(svgEl, nodeCache),
|
||||
WebReference.from(svgEl, svgElRef).toJSON()
|
||||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl, seenNodeIds);
|
||||
assert_cloned_value(
|
||||
svgEl,
|
||||
WebReference.from(svgEl, svgElRef).toJSON(),
|
||||
nodeCache,
|
||||
seenNodeIds
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_clone_Sequences() {
|
||||
const { htmlEl, nodeCache } = setupTest();
|
||||
const { htmlEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl, seenNodeIds);
|
||||
|
||||
const input = [
|
||||
null,
|
||||
true,
|
||||
[],
|
||||
[42],
|
||||
htmlEl,
|
||||
{
|
||||
toJSON() {
|
||||
@@ -98,20 +125,25 @@ add_task(function test_clone_Sequences() {
|
||||
{ bar: "baz" },
|
||||
];
|
||||
|
||||
const actual = json.clone(input, nodeCache);
|
||||
|
||||
equal(actual[0], null);
|
||||
equal(actual[1], true);
|
||||
deepEqual(actual[2], []);
|
||||
deepEqual(actual[3], { [WebElement.Identifier]: htmlElRef });
|
||||
equal(actual[4], "foo");
|
||||
deepEqual(actual[5], { bar: "baz" });
|
||||
assert_cloned_value(
|
||||
input,
|
||||
[
|
||||
null,
|
||||
true,
|
||||
[42],
|
||||
{ [WebElement.Identifier]: htmlElRef },
|
||||
"foo",
|
||||
{ bar: "baz" },
|
||||
],
|
||||
nodeCache,
|
||||
seenNodeIds
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_clone_objects() {
|
||||
const { htmlEl, nodeCache } = setupTest();
|
||||
const { htmlEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl, seenNodeIds);
|
||||
|
||||
const input = {
|
||||
null: null,
|
||||
@@ -126,14 +158,19 @@ add_task(function test_clone_objects() {
|
||||
object: { bar: "baz" },
|
||||
};
|
||||
|
||||
const actual = json.clone(input, nodeCache);
|
||||
|
||||
equal(actual.null, null);
|
||||
equal(actual.boolean, true);
|
||||
deepEqual(actual.array, [42]);
|
||||
deepEqual(actual.element, { [WebElement.Identifier]: htmlElRef });
|
||||
equal(actual.toJSON, "foo");
|
||||
deepEqual(actual.object, { bar: "baz" });
|
||||
assert_cloned_value(
|
||||
input,
|
||||
{
|
||||
null: null,
|
||||
boolean: true,
|
||||
array: [42],
|
||||
element: { [WebElement.Identifier]: htmlElRef },
|
||||
toJSON: "foo",
|
||||
object: { bar: "baz" },
|
||||
},
|
||||
nodeCache,
|
||||
seenNodeIds
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_clone_сyclicReference() {
|
||||
@@ -169,68 +206,84 @@ add_task(function test_clone_сyclicReference() {
|
||||
});
|
||||
|
||||
add_task(function test_deserialize_generalTypes() {
|
||||
const { browser, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
const { browsingContext, nodeCache } = setupTest();
|
||||
|
||||
// null
|
||||
equal(json.deserialize(undefined, nodeCache, win), undefined);
|
||||
equal(json.deserialize(null, nodeCache, win), null);
|
||||
equal(json.deserialize(undefined, nodeCache, browsingContext), undefined);
|
||||
equal(json.deserialize(null, nodeCache, browsingContext), 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(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 { browser, nodeCache, shadowRoot } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
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, win);
|
||||
json.deserialize(
|
||||
unknownShadowRootId,
|
||||
nodeCache,
|
||||
browsingContext,
|
||||
seenNodes
|
||||
);
|
||||
}, /NoSuchShadowRootError/);
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
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
|
||||
const root = json.deserialize(shadowRootEl, nodeCache, win);
|
||||
seenNodes.add(shadowRootRef);
|
||||
const root = json.deserialize(
|
||||
shadowRootEl,
|
||||
nodeCache,
|
||||
browsingContext,
|
||||
seenNodes
|
||||
);
|
||||
deepEqual(root, shadowRoot);
|
||||
deepEqual(root, nodeCache.getNode(browser.browsingContext, shadowRootRef));
|
||||
deepEqual(root, nodeCache.getNode(browsingContext, shadowRootRef));
|
||||
});
|
||||
|
||||
add_task(function test_deserialize_WebElement() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
const { browser, browsingContext, htmlEl, nodeCache, seenNodeIds } =
|
||||
setupTest();
|
||||
const seenNodes = new Set();
|
||||
|
||||
// Fails to resolve for unknown elements
|
||||
const unknownWebElId = { [WebElement.Identifier]: "foo" };
|
||||
Assert.throws(() => {
|
||||
json.deserialize(unknownWebElId, nodeCache, win);
|
||||
json.deserialize(unknownWebElId, nodeCache, browsingContext, seenNodes);
|
||||
}, /NoSuchElementError/);
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl, seenNodeIds);
|
||||
const htmlWebEl = { [WebElement.Identifier]: htmlElRef };
|
||||
|
||||
// Fails to resolve for missing window reference
|
||||
Assert.throws(() => json.deserialize(htmlWebEl, nodeCache), /TypeError/);
|
||||
|
||||
// Previously seen element is associated with original web element reference
|
||||
const el = json.deserialize(htmlWebEl, nodeCache, win);
|
||||
seenNodes.add(htmlElRef);
|
||||
const el = json.deserialize(htmlWebEl, nodeCache, browsingContext, seenNodes);
|
||||
deepEqual(el, htmlEl);
|
||||
deepEqual(el, nodeCache.getNode(browser.browsingContext, htmlElRef));
|
||||
});
|
||||
|
||||
add_task(function test_deserialize_Sequences() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
const { browsingContext, htmlEl, nodeCache, seenNodeIds } = setupTest();
|
||||
const seenNodes = new Set();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl, seenNodeIds);
|
||||
seenNodes.add(htmlElRef);
|
||||
|
||||
const input = [
|
||||
null,
|
||||
@@ -240,7 +293,7 @@ add_task(function test_deserialize_Sequences() {
|
||||
{ bar: "baz" },
|
||||
];
|
||||
|
||||
const actual = json.deserialize(input, nodeCache, win);
|
||||
const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes);
|
||||
|
||||
equal(actual[0], null);
|
||||
equal(actual[1], true);
|
||||
@@ -250,10 +303,11 @@ add_task(function test_deserialize_Sequences() {
|
||||
});
|
||||
|
||||
add_task(function test_deserialize_objects() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
const { browsingContext, htmlEl, nodeCache, seenNodeIds } = setupTest();
|
||||
const seenNodes = new Set();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl, seenNodeIds);
|
||||
seenNodes.add(htmlElRef);
|
||||
|
||||
const input = {
|
||||
null: null,
|
||||
@@ -263,7 +317,7 @@ add_task(function test_deserialize_objects() {
|
||||
object: { bar: "baz" },
|
||||
};
|
||||
|
||||
const actual = json.deserialize(input, nodeCache, win);
|
||||
const actual = json.deserialize(input, nodeCache, browsingContext, seenNodes);
|
||||
|
||||
equal(actual.null, null);
|
||||
equal(actual.boolean, true);
|
||||
|
||||
@@ -253,6 +253,25 @@ export var TabManager = {
|
||||
return browsingContext.id.toString();
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the navigable for the given browsing context.
|
||||
*
|
||||
* Because Gecko doesn't support the Navigable concept in content
|
||||
* scope the content browser could be used to uniquely identify
|
||||
* top-level browsing contexts.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
*
|
||||
* @returns {BrowsingContext|XULBrowser} The navigable
|
||||
*/
|
||||
getNavigableForBrowsingContext(browsingContext) {
|
||||
if (browsingContext.isContent && browsingContext.parent === null) {
|
||||
return browsingContext.embedderElement;
|
||||
}
|
||||
|
||||
return browsingContext;
|
||||
},
|
||||
|
||||
getTabCount() {
|
||||
let count = 0;
|
||||
for (const win of this.windows) {
|
||||
|
||||
@@ -128,6 +128,34 @@ add_task(async function test_addTab_window() {
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_getNavigableForBrowsingContext() {
|
||||
const browser = gBrowser.selectedBrowser;
|
||||
|
||||
info(`Navigate to ${TEST_URL}`);
|
||||
const loaded = BrowserTestUtils.browserLoaded(browser);
|
||||
BrowserTestUtils.loadURIString(browser, TEST_URL);
|
||||
await loaded;
|
||||
|
||||
const contexts = browser.browsingContext.getAllBrowsingContextsInSubtree();
|
||||
is(contexts.length, 2, "Top context has 1 child");
|
||||
|
||||
// For a top-level browsing context the content browser is returned.
|
||||
const topContext = contexts[0];
|
||||
is(
|
||||
TabManager.getNavigableForBrowsingContext(topContext),
|
||||
browser,
|
||||
"Top-Level browsing context has the content browser as navigable"
|
||||
);
|
||||
|
||||
// For child browsing contexts the browsing context itself is returned.
|
||||
const childContext = contexts[1];
|
||||
is(
|
||||
TabManager.getNavigableForBrowsingContext(childContext),
|
||||
childContext,
|
||||
"Child browsing context has itself as navigable"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_getTabForBrowsingContext() {
|
||||
const tab = await TabManager.addTab();
|
||||
try {
|
||||
|
||||
@@ -50,11 +50,14 @@ export class NodeCache {
|
||||
*
|
||||
* @param {Node} node
|
||||
* The node to be added.
|
||||
* @param {Map<BrowsingContext, Array<string>>} seenNodeIds
|
||||
* Map of browsing contexts to their seen node ids during the current
|
||||
* serialization.
|
||||
*
|
||||
* @returns {string}
|
||||
* The unique node reference for the DOM node.
|
||||
*/
|
||||
getOrCreateNodeReference(node) {
|
||||
getOrCreateNodeReference(node, seenNodeIds) {
|
||||
if (!Node.isInstance(node)) {
|
||||
throw new TypeError(`Failed to create node reference for ${node}`);
|
||||
}
|
||||
@@ -82,6 +85,13 @@ export class NodeCache {
|
||||
|
||||
this.#nodeIdMap.set(node, nodeId);
|
||||
this.#seenNodesMap.set(nodeId, details);
|
||||
|
||||
// Also add the information for the node id and its correlated browsing
|
||||
// context to allow the parent process to update the seen nodes.
|
||||
if (!seenNodeIds.has(browsingContext)) {
|
||||
seenNodeIds.set(browsingContext, []);
|
||||
}
|
||||
seenNodeIds.get(browsingContext).push(nodeId);
|
||||
}
|
||||
|
||||
return nodeId;
|
||||
|
||||
@@ -19,6 +19,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
"chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
|
||||
RootMessageHandlerRegistry:
|
||||
"chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
unregisterProcessDataActor:
|
||||
"chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
|
||||
WebDriverBiDiConnection:
|
||||
@@ -201,6 +202,10 @@ export class WebDriverSession {
|
||||
this._connections.add(connection);
|
||||
}
|
||||
|
||||
// Maps a Navigable (browsing context or content browser for top-level
|
||||
// browsing contexts) to a Set of nodeId's.
|
||||
this.navigableSeenNodes = new WeakMap();
|
||||
|
||||
lazy.registerProcessDataActor();
|
||||
|
||||
webDriverSessions.set(this.id, this);
|
||||
@@ -209,6 +214,10 @@ export class WebDriverSession {
|
||||
destroy() {
|
||||
webDriverSessions.delete(this.id);
|
||||
|
||||
lazy.unregisterProcessDataActor();
|
||||
|
||||
this.navigableSeenNodes = null;
|
||||
|
||||
lazy.allowAllCerts.disable();
|
||||
|
||||
// Close all open connections which unregister themselves.
|
||||
@@ -227,8 +236,6 @@ export class WebDriverSession {
|
||||
);
|
||||
this._messageHandler.destroy();
|
||||
}
|
||||
|
||||
lazy.unregisterProcessDataActor();
|
||||
}
|
||||
|
||||
async execute(module, command, params) {
|
||||
@@ -352,6 +359,30 @@ export class WebDriverSession {
|
||||
|
||||
/**
|
||||
* Get a WebDriver session corresponding to the session id.
|
||||
* Get the list of seen nodes for the given browsing context.
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* The id of the WebDriver session to use.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* Browsing context the node is part of.
|
||||
*
|
||||
* @returns {Set}
|
||||
* The list of seen nodes.
|
||||
*/
|
||||
export function getSeenNodesForBrowsingContext(sessionId, browsingContext) {
|
||||
const navigable =
|
||||
lazy.TabManager.getNavigableForBrowsingContext(browsingContext);
|
||||
const session = getWebDriverSessionById(sessionId);
|
||||
|
||||
if (!session.navigableSeenNodes.has(navigable)) {
|
||||
// The navigable hasn't been seen yet.
|
||||
session.navigableSeenNodes.set(navigable, new Set());
|
||||
}
|
||||
|
||||
return session.navigableSeenNodes.get(navigable);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* The ID of the WebDriver session to retrieve.
|
||||
|
||||
@@ -33,6 +33,7 @@ function setupTest() {
|
||||
divEl,
|
||||
iframeEl,
|
||||
shadowRoot,
|
||||
seenNodeIds: new Map(),
|
||||
svgEl,
|
||||
textareaEl,
|
||||
videoEl,
|
||||
@@ -40,32 +41,37 @@ function setupTest() {
|
||||
}
|
||||
|
||||
add_task(function getOrCreateNodeReference_invalid() {
|
||||
const { nodeCache } = setupTest();
|
||||
const { nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const invalidValues = [null, undefined, "foo", 42, true, [], {}];
|
||||
|
||||
for (const value of invalidValues) {
|
||||
info(`Testing value: ${value}`);
|
||||
Assert.throws(() => nodeCache.getOrCreateNodeReference(value), /TypeError/);
|
||||
Assert.throws(
|
||||
() => nodeCache.getOrCreateNodeReference(value, seenNodeIds),
|
||||
/TypeError/
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function getOrCreateNodeReference_supportedNodeTypes() {
|
||||
const { browser, divEl, nodeCache } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const xmlDocument = new DOMParser().parseFromString(
|
||||
"<xml></xml>",
|
||||
"application/xml"
|
||||
);
|
||||
// Bug 1820734: No ownerGlobal is available in XPCShell tests
|
||||
// const xmlDocument = new DOMParser().parseFromString(
|
||||
// "<xml></xml>",
|
||||
// "application/xml"
|
||||
// );
|
||||
|
||||
const values = [
|
||||
{ node: divEl, type: Node.ELEMENT_NODE },
|
||||
{ node: divEl.attributes[0], type: Node.ATTRIBUTE_NODE },
|
||||
{ node: browser.document.createTextNode("foo"), type: Node.TEXT_NODE },
|
||||
{
|
||||
node: xmlDocument.createCDATASection("foo"),
|
||||
type: Node.CDATA_SECTION_NODE,
|
||||
},
|
||||
// Bug 1820734: No ownerGlobal is available in XPCShell tests
|
||||
// {
|
||||
// node: xmlDocument.createCDATASection("foo"),
|
||||
// type: Node.CDATA_SECTION_NODE,
|
||||
// },
|
||||
{
|
||||
node: browser.document.createProcessingInstruction(
|
||||
"xml-stylesheet",
|
||||
@@ -91,39 +97,51 @@ add_task(function getOrCreateNodeReference_supportedNodeTypes() {
|
||||
|
||||
values.forEach((value, index) => {
|
||||
info(`Testing value: ${value.type}`);
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value.node);
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value.node, seenNodeIds);
|
||||
equal(nodeCache.size, index + 1);
|
||||
equal(typeof nodeRef, "string");
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(nodeRef));
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function getOrCreateNodeReference_referenceAlreadyCreated() {
|
||||
const { divEl, nodeCache } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
const divElRefOther = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const divElRefOther = nodeCache.getOrCreateNodeReference(divEl);
|
||||
equal(nodeCache.size, 1);
|
||||
equal(divElRefOther, divElRef);
|
||||
equal(nodeCache.size, 1);
|
||||
equal(seenNodeIds.size, 1);
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(divElRef));
|
||||
});
|
||||
|
||||
add_task(function getOrCreateNodeReference_differentReference() {
|
||||
const { divEl, nodeCache, shadowRoot } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds, shadowRoot } = setupTest();
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
equal(nodeCache.size, 1);
|
||||
equal(seenNodeIds.size, 1);
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(divElRef));
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(
|
||||
shadowRoot,
|
||||
seenNodeIds
|
||||
);
|
||||
equal(nodeCache.size, 2);
|
||||
equal(seenNodeIds.size, 1);
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(divElRef));
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(shadowRootRef));
|
||||
|
||||
notEqual(divElRef, shadowRootRef);
|
||||
});
|
||||
|
||||
add_task(function getOrCreateNodeReference_differentReferencePerNodeCache() {
|
||||
const { browser, divEl, nodeCache } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds } = setupTest();
|
||||
const nodeCache2 = new NodeCache();
|
||||
|
||||
const divElRef1 = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const divElRef2 = nodeCache2.getOrCreateNodeReference(divEl);
|
||||
const divElRef1 = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
const divElRef2 = nodeCache2.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
|
||||
notEqual(divElRef1, divElRef2);
|
||||
equal(
|
||||
@@ -131,15 +149,20 @@ add_task(function getOrCreateNodeReference_differentReferencePerNodeCache() {
|
||||
nodeCache2.getNode(browser.browsingContext, divElRef2)
|
||||
);
|
||||
|
||||
equal(seenNodeIds.size, 1);
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(divElRef1));
|
||||
ok(seenNodeIds.get(browser.browsingContext).includes(divElRef2));
|
||||
|
||||
equal(nodeCache.getNode(browser.browsingContext, divElRef2), null);
|
||||
});
|
||||
|
||||
add_task(function clear() {
|
||||
const { browser, divEl, nodeCache, svgEl } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds, svgEl } = setupTest();
|
||||
|
||||
nodeCache.getOrCreateNodeReference(divEl);
|
||||
nodeCache.getOrCreateNodeReference(svgEl);
|
||||
nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
nodeCache.getOrCreateNodeReference(svgEl, seenNodeIds);
|
||||
equal(nodeCache.size, 2);
|
||||
equal(seenNodeIds.size, 1);
|
||||
|
||||
// Clear requires explicit arguments.
|
||||
Assert.throws(() => nodeCache.clear(), /Error/);
|
||||
@@ -147,35 +170,37 @@ add_task(function clear() {
|
||||
// Clear references for a different browsing context
|
||||
const browser2 = Services.appShell.createWindowlessBrowser(false);
|
||||
const imgEl = browser2.document.createElement("img");
|
||||
const imgElRef = nodeCache.getOrCreateNodeReference(imgEl);
|
||||
const imgElRef = nodeCache.getOrCreateNodeReference(imgEl, seenNodeIds);
|
||||
equal(nodeCache.size, 3);
|
||||
equal(seenNodeIds.size, 2);
|
||||
|
||||
nodeCache.clear({ browsingContext: browser.browsingContext });
|
||||
equal(nodeCache.size, 1);
|
||||
equal(nodeCache.getNode(browser2.browsingContext, imgElRef), imgEl);
|
||||
|
||||
// Clear all references
|
||||
nodeCache.getOrCreateNodeReference(divEl);
|
||||
nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
equal(nodeCache.size, 2);
|
||||
equal(seenNodeIds.size, 2);
|
||||
|
||||
nodeCache.clear({ all: true });
|
||||
equal(nodeCache.size, 0);
|
||||
});
|
||||
|
||||
add_task(function getNode_multiple_nodes() {
|
||||
const { browser, divEl, nodeCache, svgEl } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds, svgEl } = setupTest();
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl);
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl, seenNodeIds);
|
||||
|
||||
equal(nodeCache.getNode(browser.browsingContext, svgElRef), svgEl);
|
||||
equal(nodeCache.getNode(browser.browsingContext, divElRef), divEl);
|
||||
});
|
||||
|
||||
add_task(function getNode_differentBrowsingContextInSameGroup() {
|
||||
const { iframeEl, divEl, nodeCache } = setupTest();
|
||||
const { iframeEl, divEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
equal(nodeCache.size, 1);
|
||||
|
||||
equal(
|
||||
@@ -185,9 +210,9 @@ add_task(function getNode_differentBrowsingContextInSameGroup() {
|
||||
});
|
||||
|
||||
add_task(function getNode_differentBrowsingContextInOtherGroup() {
|
||||
const { divEl, nodeCache } = setupTest();
|
||||
const { divEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
equal(nodeCache.size, 1);
|
||||
|
||||
const browser2 = Services.appShell.createWindowlessBrowser(false);
|
||||
@@ -195,10 +220,10 @@ add_task(function getNode_differentBrowsingContextInOtherGroup() {
|
||||
});
|
||||
|
||||
add_task(async function getNode_nodeDeleted() {
|
||||
const { browser, nodeCache } = setupTest();
|
||||
const { browser, nodeCache, seenNodeIds } = setupTest();
|
||||
let el = browser.document.createElement("div");
|
||||
|
||||
const elRef = nodeCache.getOrCreateNodeReference(el);
|
||||
const elRef = nodeCache.getOrCreateNodeReference(el, seenNodeIds);
|
||||
|
||||
// Delete element and force a garbage collection
|
||||
el = null;
|
||||
@@ -209,9 +234,9 @@ add_task(async function getNode_nodeDeleted() {
|
||||
});
|
||||
|
||||
add_task(function getNodeDetails_forTopBrowsingContext() {
|
||||
const { browser, divEl, nodeCache } = setupTest();
|
||||
const { browser, divEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl);
|
||||
const divElRef = nodeCache.getOrCreateNodeReference(divEl, seenNodeIds);
|
||||
|
||||
const nodeDetails = nodeCache.getReferenceDetails(divElRef);
|
||||
equal(nodeDetails.browserId, browser.browsingContext.browserId);
|
||||
@@ -223,9 +248,9 @@ add_task(function getNodeDetails_forTopBrowsingContext() {
|
||||
});
|
||||
|
||||
add_task(async function getNodeDetails_forChildBrowsingContext() {
|
||||
const { browser, iframeEl, childEl, nodeCache } = setupTest();
|
||||
const { browser, iframeEl, childEl, nodeCache, seenNodeIds } = setupTest();
|
||||
|
||||
const childElRef = nodeCache.getOrCreateNodeReference(childEl);
|
||||
const childElRef = nodeCache.getOrCreateNodeReference(childEl, seenNodeIds);
|
||||
|
||||
const nodeDetails = nodeCache.getReferenceDetails(childElRef);
|
||||
equal(nodeDetails.browserId, browser.browsingContext.browserId);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
const { Capabilities, Timeouts } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs"
|
||||
);
|
||||
const { WebDriverSession, getWebDriverSessionById } =
|
||||
const { getWebDriverSessionById, WebDriverSession } =
|
||||
ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/Session.sys.mjs"
|
||||
);
|
||||
|
||||
@@ -297,7 +297,7 @@ class TestNavigate(BaseNavigationTestCase):
|
||||
self.marionette.navigate("about:robots")
|
||||
self.assertFalse(self.is_remote_tab)
|
||||
|
||||
with self.assertRaises(errors.NoSuchElementException):
|
||||
with self.assertRaises(errors.StaleElementException):
|
||||
elem.click()
|
||||
|
||||
def test_about_blank_for_new_docshell(self):
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
[back.py]
|
||||
expected:
|
||||
if not debug and (os == "linux") and fission: [OK, TIMEOUT]
|
||||
if not debug and (os == "win") and (processor == "x86_64"): [OK, TIMEOUT]
|
||||
if not debug and (os == "android"): [OK, TIMEOUT]
|
||||
[test_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if not fission and (os == "linux"): PASS
|
||||
FAIL
|
||||
|
||||
[test_history_pushstate]
|
||||
expected:
|
||||
if (os == "win") and not debug and (processor == "x86_64"): [PASS, FAIL]
|
||||
|
||||
@@ -3,8 +3,3 @@
|
||||
if (os == "linux") and fission and not debug: [OK, TIMEOUT]
|
||||
[test_link_unload_event]
|
||||
bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1786639
|
||||
|
||||
[test_link_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if (processor == "x86") and debug: [PASS, FAIL]
|
||||
FAIL
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[forward.py]
|
||||
[test_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if (os == "linux") and not fission: PASS
|
||||
if (os == "win") and not fission: FAIL
|
||||
if (os == "mac") and not fission: FAIL
|
||||
FAIL
|
||||
@@ -1,7 +0,0 @@
|
||||
[navigate.py]
|
||||
[test_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if (os == "linux") and not fission: PASS
|
||||
if (os == "win") and not fission: FAIL
|
||||
if (os == "mac") and not fission: FAIL
|
||||
FAIL
|
||||
Reference in New Issue
Block a user