forked from mirrors/gecko-dev
Merge mozilla-central to autoland. a=merge CLOSED TREE
This commit is contained in:
commit
4ab7fd28a6
45 changed files with 1412 additions and 2119 deletions
|
|
@ -57,7 +57,6 @@ remote.jar:
|
|||
content/shared/webdriver/Actions.sys.mjs (shared/webdriver/Actions.sys.mjs)
|
||||
content/shared/webdriver/Assert.sys.mjs (shared/webdriver/Assert.sys.mjs)
|
||||
content/shared/webdriver/Capabilities.sys.mjs (shared/webdriver/Capabilities.sys.mjs)
|
||||
content/shared/webdriver/Element.sys.mjs (shared/webdriver/Element.sys.mjs)
|
||||
content/shared/webdriver/Errors.sys.mjs (shared/webdriver/Errors.sys.mjs)
|
||||
content/shared/webdriver/KeyData.sys.mjs (shared/webdriver/KeyData.sys.mjs)
|
||||
content/shared/webdriver/NodeCache.sys.mjs (shared/webdriver/NodeCache.sys.mjs)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs",
|
||||
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||
atom: "chrome://remote/content/marionette/atom.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
element: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
evaluate: "chrome://remote/content/marionette/evaluate.sys.mjs",
|
||||
interaction: "chrome://remote/content/marionette/interaction.sys.mjs",
|
||||
|
|
@ -21,7 +21,6 @@ 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/web-reference.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
||||
|
|
@ -59,10 +58,6 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
|||
return this._legacyactions;
|
||||
}
|
||||
|
||||
get #nodeCache() {
|
||||
return this.#processActor.getNodeCache();
|
||||
}
|
||||
|
||||
actorCreated() {
|
||||
lazy.logger.trace(
|
||||
`[${this.browsingContext.id}] MarionetteCommands actor created ` +
|
||||
|
|
@ -87,12 +82,11 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
|||
let waitForNextTick = false;
|
||||
|
||||
const { name, data: serializedData } = msg;
|
||||
const data = await lazy.json.deserialize({
|
||||
value: serializedData,
|
||||
browsingContext: this.contentWindow.browsingContext,
|
||||
getKnownElement: this.#getKnownElement.bind(this),
|
||||
getKnownShadowRoot: this.#getKnownShadowRoot.bind(this),
|
||||
});
|
||||
const data = lazy.json.deserialize(
|
||||
serializedData,
|
||||
this.#processActor.getNodeCache(),
|
||||
this.contentWindow
|
||||
);
|
||||
|
||||
switch (name) {
|
||||
case "MarionetteCommandsParent:clearElement":
|
||||
|
|
@ -190,11 +184,7 @@ export class MarionetteCommandsChild extends JSWindowActorChild {
|
|||
}
|
||||
|
||||
return {
|
||||
data: await lazy.json.clone({
|
||||
value: result,
|
||||
getOrCreateNodeReference: this.#getOrCreateNodeReference.bind(this),
|
||||
browsingContext: this.contentWindow.browsingContext,
|
||||
}),
|
||||
data: lazy.json.clone(result, this.#processActor.getNodeCache()),
|
||||
};
|
||||
} catch (e) {
|
||||
// Always wrap errors as WebDriverError
|
||||
|
|
@ -616,148 +606,4 @@ 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.
|
||||
*/
|
||||
async #getKnownElement(browsingContext, nodeId) {
|
||||
const isKnown = await this.#isNodeReferenceKnown(browsingContext, nodeId);
|
||||
if (!isKnown) {
|
||||
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.
|
||||
*/
|
||||
async #getKnownShadowRoot(browsingContext, nodeId) {
|
||||
const isKnown = await this.#isNodeReferenceKnown(browsingContext, nodeId);
|
||||
if (!isKnown) {
|
||||
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 {BrowsingContext} browsingContext
|
||||
* The browsing context the element is part of.
|
||||
* @param {Node} node
|
||||
* The node to create or get a WebReference for.
|
||||
*
|
||||
* @returns {WebReference} The web reference for the node.
|
||||
*/
|
||||
async #getOrCreateNodeReference(browsingContext, node) {
|
||||
const nodeRef = this.#nodeCache.getOrCreateNodeReference(node);
|
||||
const webRef = lazy.WebReference.from(node, nodeRef);
|
||||
|
||||
await this.sendQuery("MarionetteCommandsChild:addNodeToSeenNodes", {
|
||||
browsingContext,
|
||||
nodeId: webRef.uuid,
|
||||
});
|
||||
|
||||
return webRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Promise<boolean>}
|
||||
* A promise that resolved to True if the element is known in the given
|
||||
* browsing context.
|
||||
*/
|
||||
#isNodeReferenceKnown(browsingContext, nodeId) {
|
||||
return this.sendQuery("MarionetteCommandsChild:isNodeReferenceKnown", {
|
||||
browsingContext,
|
||||
nodeId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,17 +10,12 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
capture: "chrome://remote/content/shared/Capture.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
session: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
||||
lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
|
||||
);
|
||||
|
||||
// Because Marionette supports a single session only we store its id
|
||||
// globally so that the parent actor can access it.
|
||||
let webDriverSessionId = null;
|
||||
|
||||
export class MarionetteCommandsParent extends JSWindowActorParent {
|
||||
actorCreated() {
|
||||
this._resolveDialogOpened = null;
|
||||
|
|
@ -32,28 +27,6 @@ export class MarionetteCommandsParent extends JSWindowActorParent {
|
|||
});
|
||||
}
|
||||
|
||||
async receiveMessage(msg) {
|
||||
const { name, data } = msg;
|
||||
|
||||
switch (name) {
|
||||
case "MarionetteCommandsChild:addNodeToSeenNodes":
|
||||
return lazy.session.addNodeToSeenNodes(
|
||||
webDriverSessionId,
|
||||
data.browsingContext,
|
||||
data.nodeId
|
||||
);
|
||||
|
||||
case "MarionetteCommandsChild:isNodeReferenceKnown":
|
||||
return lazy.session.isNodeReferenceKnown(
|
||||
webDriverSessionId,
|
||||
data.browsingContext,
|
||||
data.nodeId
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async sendQuery(name, data) {
|
||||
// return early if a dialog is opened
|
||||
const result = await Promise.race([
|
||||
|
|
@ -377,11 +350,8 @@ export function getMarionetteCommandsActorProxy(browsingContextFn) {
|
|||
|
||||
/**
|
||||
* Register the MarionetteCommands actor that holds all the commands.
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* The id of the current WebDriver session.
|
||||
*/
|
||||
export function registerCommandsActor(sessionId) {
|
||||
export function registerCommandsActor() {
|
||||
try {
|
||||
ChromeUtils.registerWindowActor("MarionetteCommands", {
|
||||
kind: "JSWindowActor",
|
||||
|
|
@ -404,12 +374,8 @@ export function registerCommandsActor(sessionId) {
|
|||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
webDriverSessionId = sessionId;
|
||||
}
|
||||
|
||||
export function unregisterCommandsActor() {
|
||||
webDriverSessionId = null;
|
||||
|
||||
ChromeUtils.unregisterWindowActor("MarionetteCommands");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@
|
|||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
import {
|
||||
element,
|
||||
ShadowRoot,
|
||||
WebElement,
|
||||
} from "chrome://remote/content/marionette/element.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
|
|
@ -18,7 +24,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
DebounceCallback: "chrome://remote/content/marionette/sync.sys.mjs",
|
||||
disableEventsActor:
|
||||
"chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
enableEventsActor:
|
||||
"chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
|
|
@ -39,7 +44,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
registerCommandsActor:
|
||||
"chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
|
||||
RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
|
||||
ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
|
||||
Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
|
||||
|
|
@ -51,7 +55,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
"chrome://remote/content/shared/Navigate.sys.mjs",
|
||||
waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
|
||||
WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
||||
WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
||||
windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
|
||||
WindowState: "chrome://remote/content/marionette/browser.sys.mjs",
|
||||
});
|
||||
|
|
@ -62,21 +65,16 @@ XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
|||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
|
||||
XPCOMUtils.defineLazyGetter(
|
||||
lazy,
|
||||
"supportedStrategies",
|
||||
() =>
|
||||
new Set([
|
||||
lazy.element.Strategy.ClassName,
|
||||
lazy.element.Strategy.Selector,
|
||||
lazy.element.Strategy.ID,
|
||||
lazy.element.Strategy.Name,
|
||||
lazy.element.Strategy.LinkText,
|
||||
lazy.element.Strategy.PartialLinkText,
|
||||
lazy.element.Strategy.TagName,
|
||||
lazy.element.Strategy.XPath,
|
||||
])
|
||||
);
|
||||
const SUPPORTED_STRATEGIES = new Set([
|
||||
element.Strategy.ClassName,
|
||||
element.Strategy.Selector,
|
||||
element.Strategy.ID,
|
||||
element.Strategy.Name,
|
||||
element.Strategy.LinkText,
|
||||
element.Strategy.PartialLinkText,
|
||||
element.Strategy.TagName,
|
||||
element.Strategy.XPath,
|
||||
]);
|
||||
|
||||
// Timeout used to abort fullscreen, maximize, and minimize
|
||||
// commands if no window manager is present.
|
||||
|
|
@ -475,7 +473,7 @@ GeckoDriver.prototype.newSession = async function(cmd) {
|
|||
this.dialog = lazy.modal.findModalDialogs(this.curBrowser);
|
||||
}
|
||||
|
||||
lazy.registerCommandsActor(this.currentSession.id);
|
||||
lazy.registerCommandsActor();
|
||||
lazy.enableEventsActor();
|
||||
|
||||
Services.obs.addObserver(this, TOPIC_BROWSER_READY);
|
||||
|
|
@ -1361,7 +1359,7 @@ GeckoDriver.prototype.switchToFrame = async function(cmd) {
|
|||
// Bug 1495063: Elements should be passed as WebReference reference
|
||||
let byFrame;
|
||||
if (typeof el == "string") {
|
||||
byFrame = lazy.WebElement.fromUUID(el).toJSON();
|
||||
byFrame = WebElement.fromUUID(el).toJSON();
|
||||
} else if (el) {
|
||||
byFrame = el;
|
||||
}
|
||||
|
|
@ -1404,7 +1402,7 @@ GeckoDriver.prototype.singleTap = async function(cmd) {
|
|||
lazy.assert.open(this.getBrowsingContext());
|
||||
|
||||
let { id, x, y } = cmd.parameters;
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
await this.getActor().singleTap(
|
||||
webEl,
|
||||
|
|
@ -1487,7 +1485,7 @@ GeckoDriver.prototype.releaseActions = async function() {
|
|||
GeckoDriver.prototype.findElement = async function(cmd) {
|
||||
const { element: el, using, value } = cmd.parameters;
|
||||
|
||||
if (!lazy.supportedStrategies.has(using)) {
|
||||
if (!SUPPORTED_STRATEGIES.has(using)) {
|
||||
throw new lazy.error.InvalidSelectorError(
|
||||
`Strategy not supported: ${using}`
|
||||
);
|
||||
|
|
@ -1499,7 +1497,7 @@ GeckoDriver.prototype.findElement = async function(cmd) {
|
|||
|
||||
let startNode;
|
||||
if (typeof el != "undefined") {
|
||||
startNode = lazy.WebElement.fromUUID(el).toJSON();
|
||||
startNode = WebElement.fromUUID(el).toJSON();
|
||||
}
|
||||
|
||||
let opts = {
|
||||
|
|
@ -1541,7 +1539,7 @@ GeckoDriver.prototype.findElement = async function(cmd) {
|
|||
GeckoDriver.prototype.findElementFromShadowRoot = async function(cmd) {
|
||||
const { shadowRoot, using, value } = cmd.parameters;
|
||||
|
||||
if (!lazy.supportedStrategies.has(using)) {
|
||||
if (!SUPPORTED_STRATEGIES.has(using)) {
|
||||
throw new lazy.error.InvalidSelectorError(
|
||||
`Strategy not supported: ${using}`
|
||||
);
|
||||
|
|
@ -1553,7 +1551,7 @@ GeckoDriver.prototype.findElementFromShadowRoot = async function(cmd) {
|
|||
|
||||
const opts = {
|
||||
all: false,
|
||||
startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
|
||||
startNode: ShadowRoot.fromUUID(shadowRoot).toJSON(),
|
||||
timeout: this.currentSession.timeouts.implicit,
|
||||
};
|
||||
|
||||
|
|
@ -1586,7 +1584,7 @@ GeckoDriver.prototype.findElementFromShadowRoot = async function(cmd) {
|
|||
GeckoDriver.prototype.findElements = async function(cmd) {
|
||||
const { element: el, using, value } = cmd.parameters;
|
||||
|
||||
if (!lazy.supportedStrategies.has(using)) {
|
||||
if (!SUPPORTED_STRATEGIES.has(using)) {
|
||||
throw new lazy.error.InvalidSelectorError(
|
||||
`Strategy not supported: ${using}`
|
||||
);
|
||||
|
|
@ -1598,7 +1596,7 @@ GeckoDriver.prototype.findElements = async function(cmd) {
|
|||
|
||||
let startNode;
|
||||
if (typeof el != "undefined") {
|
||||
startNode = lazy.WebElement.fromUUID(el).toJSON();
|
||||
startNode = WebElement.fromUUID(el).toJSON();
|
||||
}
|
||||
|
||||
let opts = {
|
||||
|
|
@ -1637,7 +1635,7 @@ GeckoDriver.prototype.findElements = async function(cmd) {
|
|||
GeckoDriver.prototype.findElementsFromShadowRoot = async function(cmd) {
|
||||
const { shadowRoot, using, value } = cmd.parameters;
|
||||
|
||||
if (!lazy.supportedStrategies.has(using)) {
|
||||
if (!SUPPORTED_STRATEGIES.has(using)) {
|
||||
throw new lazy.error.InvalidSelectorError(
|
||||
`Strategy not supported: ${using}`
|
||||
);
|
||||
|
|
@ -1649,7 +1647,7 @@ GeckoDriver.prototype.findElementsFromShadowRoot = async function(cmd) {
|
|||
|
||||
const opts = {
|
||||
all: true,
|
||||
startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
|
||||
startNode: ShadowRoot.fromUUID(shadowRoot).toJSON(),
|
||||
timeout: this.currentSession.timeouts.implicit,
|
||||
};
|
||||
|
||||
|
|
@ -1690,7 +1688,7 @@ GeckoDriver.prototype.getShadowRoot = async function(cmd) {
|
|||
cmd.parameters.id,
|
||||
lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
|
||||
);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getShadowRoot(webEl);
|
||||
};
|
||||
|
|
@ -1743,7 +1741,7 @@ GeckoDriver.prototype.clickElement = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
const actor = this.getActor();
|
||||
|
||||
|
|
@ -1795,7 +1793,7 @@ GeckoDriver.prototype.getElementAttribute = async function(cmd) {
|
|||
|
||||
const id = lazy.assert.string(cmd.parameters.id);
|
||||
const name = lazy.assert.string(cmd.parameters.name);
|
||||
const webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
const webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getElementAttribute(webEl, name);
|
||||
};
|
||||
|
|
@ -1829,7 +1827,7 @@ GeckoDriver.prototype.getElementProperty = async function(cmd) {
|
|||
|
||||
const id = lazy.assert.string(cmd.parameters.id);
|
||||
const name = lazy.assert.string(cmd.parameters.name);
|
||||
const webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
const webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getElementProperty(webEl, name);
|
||||
};
|
||||
|
|
@ -1861,7 +1859,7 @@ GeckoDriver.prototype.getElementText = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getElementText(webEl);
|
||||
};
|
||||
|
|
@ -1892,7 +1890,7 @@ GeckoDriver.prototype.getElementTagName = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getElementTagName(webEl);
|
||||
};
|
||||
|
|
@ -1921,7 +1919,7 @@ GeckoDriver.prototype.isElementDisplayed = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().isElementDisplayed(
|
||||
webEl,
|
||||
|
|
@ -1958,7 +1956,7 @@ GeckoDriver.prototype.getElementValueOfCssProperty = async function(cmd) {
|
|||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let prop = lazy.assert.string(cmd.parameters.propertyName);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getElementValueOfCssProperty(webEl, prop);
|
||||
};
|
||||
|
|
@ -1989,7 +1987,7 @@ GeckoDriver.prototype.isElementEnabled = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().isElementEnabled(
|
||||
webEl,
|
||||
|
|
@ -2021,7 +2019,7 @@ GeckoDriver.prototype.isElementSelected = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().isElementSelected(
|
||||
webEl,
|
||||
|
|
@ -2046,7 +2044,7 @@ GeckoDriver.prototype.getElementRect = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getElementRect(webEl);
|
||||
};
|
||||
|
|
@ -2077,7 +2075,7 @@ GeckoDriver.prototype.sendKeysToElement = async function(cmd) {
|
|||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let text = lazy.assert.string(cmd.parameters.text);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().sendKeysToElement(
|
||||
webEl,
|
||||
|
|
@ -2109,7 +2107,7 @@ GeckoDriver.prototype.clearElement = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
await this.getActor().clearElement(webEl);
|
||||
};
|
||||
|
|
@ -2465,7 +2463,7 @@ GeckoDriver.prototype.takeScreenshot = async function(cmd) {
|
|||
full = typeof full == "undefined" ? true : full;
|
||||
scroll = typeof scroll == "undefined" ? true : scroll;
|
||||
|
||||
let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;
|
||||
let webEl = id ? WebElement.fromUUID(id).toJSON() : null;
|
||||
|
||||
// Only consider full screenshot if no element has been specified
|
||||
full = webEl ? false : full;
|
||||
|
|
@ -3262,7 +3260,7 @@ GeckoDriver.prototype.getComputedLabel = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
|
||||
return this.getActor().getComputedLabel(webEl);
|
||||
};
|
||||
|
|
@ -3283,7 +3281,7 @@ GeckoDriver.prototype.getComputedRole = async function(cmd) {
|
|||
await this._handleUserPrompts();
|
||||
|
||||
let id = lazy.assert.string(cmd.parameters.id);
|
||||
let webEl = lazy.WebElement.fromUUID(id).toJSON();
|
||||
let webEl = WebElement.fromUUID(id).toJSON();
|
||||
return this.getActor().getComputedRole(webEl);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@
|
|||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||
atom: "chrome://remote/content/marionette/atom.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
PollPromise: "chrome://remote/content/marionette/sync.sys.mjs",
|
||||
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
||||
});
|
||||
|
||||
const ORDERED_NODE_ITERATOR_TYPE = 5;
|
||||
|
|
@ -465,6 +467,102 @@ 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.
|
||||
*
|
||||
|
|
@ -510,6 +608,39 @@ 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.
|
||||
*
|
||||
|
|
@ -1213,3 +1344,287 @@ element.isBooleanAttribute = function(el, attr) {
|
|||
}
|
||||
return boolEls[el.localName].includes(attr);
|
||||
};
|
||||
|
||||
/**
|
||||
* A web reference is an abstraction used to identify an element when
|
||||
* it is transported via the protocol, between remote- and local ends.
|
||||
*
|
||||
* In Marionette this abstraction can represent DOM elements,
|
||||
* WindowProxies, and XUL elements.
|
||||
*/
|
||||
export class WebReference {
|
||||
/**
|
||||
* @param {string} uuid
|
||||
* Identifier that must be unique across all browsing contexts
|
||||
* for the contract to be upheld.
|
||||
*/
|
||||
constructor(uuid) {
|
||||
this.uuid = lazy.assert.string(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an equality check between this web element and
|
||||
* <var>other</var>.
|
||||
*
|
||||
* @param {WebReference} other
|
||||
* Web element to compare with this.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if this and <var>other</var> are the same. False
|
||||
* otherwise.
|
||||
*/
|
||||
is(other) {
|
||||
return other instanceof WebReference && this.uuid === other.uuid;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `[object ${this.constructor.name} uuid=${this.uuid}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WebReference} reference for a DOM or XUL element,
|
||||
* <code>WindowProxy</code>, or <code>ShadowRoot</code>.
|
||||
*
|
||||
* @param {(Element|ShadowRoot|WindowProxy|MockXULElement)} node
|
||||
* Node to construct a web element reference for.
|
||||
* @param {string=} uuid
|
||||
* Optional unique identifier of the WebReference if already known.
|
||||
* If not defined a new unique identifier will be created.
|
||||
*
|
||||
* @returns {WebReference}
|
||||
* Web reference for <var>node</var>.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>node</var> is neither a <code>WindowProxy</code>,
|
||||
* DOM or XUL element, or <code>ShadowRoot</code>.
|
||||
*/
|
||||
static from(node, uuid) {
|
||||
if (uuid === undefined) {
|
||||
uuid = element.generateUUID();
|
||||
}
|
||||
|
||||
if (element.isShadowRoot(node) && !element.isInPrivilegedDocument(node)) {
|
||||
// When we support Chrome Shadowroots we will need to
|
||||
// do a check here of shadowroot.host being in a privileged document
|
||||
// See Bug 1743541
|
||||
return new ShadowRoot(uuid);
|
||||
} else if (element.isElement(node)) {
|
||||
return new WebElement(uuid);
|
||||
} else if (element.isDOMWindow(node)) {
|
||||
if (node.parent === node) {
|
||||
return new WebWindow(uuid);
|
||||
}
|
||||
return new WebFrame(uuid);
|
||||
}
|
||||
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
"Expected DOM window/element " + lazy.pprint`or XUL element, got: ${node}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshals a JSON Object to one of {@link ShadowRoot}, {@link WebElement},
|
||||
* {@link WebFrame}, or {@link WebWindow}.
|
||||
*
|
||||
* @param {Object<string, string>} json
|
||||
* Web reference, which is supposed to be a JSON Object
|
||||
* where the key is one of the {@link WebReference} concrete
|
||||
* classes' UUID identifiers.
|
||||
*
|
||||
* @returns {WebReference}
|
||||
* Web reference for the JSON object.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>json</var> is not a web reference.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
lazy.assert.object(json);
|
||||
if (json instanceof WebReference) {
|
||||
return json;
|
||||
}
|
||||
let keys = Object.keys(json);
|
||||
|
||||
for (let key of keys) {
|
||||
switch (key) {
|
||||
case ShadowRoot.Identifier:
|
||||
return ShadowRoot.fromJSON(json);
|
||||
|
||||
case WebElement.Identifier:
|
||||
return WebElement.fromJSON(json);
|
||||
|
||||
case WebFrame.Identifier:
|
||||
return WebFrame.fromJSON(json);
|
||||
|
||||
case WebWindow.Identifier:
|
||||
return WebWindow.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if <var>obj<var> is a {@link WebReference} reference.
|
||||
*
|
||||
* @param {Object<string, string>} obj
|
||||
* Object that represents a {@link WebReference}.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if <var>obj</var> is a {@link WebReference}, false otherwise.
|
||||
*/
|
||||
static isReference(obj) {
|
||||
if (Object.prototype.toString.call(obj) != "[object Object]") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
ShadowRoot.Identifier in obj ||
|
||||
WebElement.Identifier in obj ||
|
||||
WebFrame.Identifier in obj ||
|
||||
WebWindow.Identifier in obj
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM elements are represented as web elements when they are
|
||||
* transported over the wire protocol.
|
||||
*/
|
||||
export class WebElement extends WebReference {
|
||||
toJSON() {
|
||||
return { [WebElement.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
if (!(Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web element reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
|
||||
let uuid = json[Identifier];
|
||||
return new WebElement(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link WebElement} from a string <var>uuid</var>.
|
||||
*
|
||||
* This whole function is a workaround for the fact that clients
|
||||
* to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
|
||||
* Objects instead of web element representations.
|
||||
*
|
||||
* @param {string} uuid
|
||||
* UUID to be associated with the web reference.
|
||||
*
|
||||
* @returns {WebElement}
|
||||
* The web element reference.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>uuid</var> is not a string.
|
||||
*/
|
||||
static fromUUID(uuid) {
|
||||
return new WebElement(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
WebElement.Identifier = "element-6066-11e4-a52e-4f735466cecf";
|
||||
|
||||
/**
|
||||
* Shadow Root elements are represented as shadow root references when they are
|
||||
* transported over the wire protocol
|
||||
*/
|
||||
export class ShadowRoot extends WebReference {
|
||||
toJSON() {
|
||||
return { [ShadowRoot.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
const { Identifier } = ShadowRoot;
|
||||
|
||||
if (!(Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected shadow root reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
|
||||
let uuid = json[Identifier];
|
||||
return new ShadowRoot(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link ShadowRoot} from a string <var>uuid</var>.
|
||||
*
|
||||
* This whole function is a workaround for the fact that clients
|
||||
* to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
|
||||
* Objects instead of shadow root representations.
|
||||
*
|
||||
* @param {string} uuid
|
||||
* UUID to be associated with the web reference.
|
||||
*
|
||||
* @returns {ShadowRoot}
|
||||
* The shadow root reference.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>uuid</var> is not a string.
|
||||
*/
|
||||
static fromUUID(uuid) {
|
||||
lazy.assert.string(uuid);
|
||||
|
||||
return new ShadowRoot(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
ShadowRoot.Identifier = "shadow-6066-11e4-a52e-4f735466cecf";
|
||||
|
||||
/**
|
||||
* Top-level browsing contexts, such as <code>WindowProxy</code>
|
||||
* whose <code>opener</code> is null, are represented as web windows
|
||||
* over the wire protocol.
|
||||
*/
|
||||
export class WebWindow extends WebReference {
|
||||
toJSON() {
|
||||
return { [WebWindow.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
if (!(WebWindow.Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web window reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
let uuid = json[WebWindow.Identifier];
|
||||
return new WebWindow(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
WebWindow.Identifier = "window-fcc6-11e5-b4f8-330a88ab9d7f";
|
||||
|
||||
/**
|
||||
* Nested browsing contexts, such as the <code>WindowProxy</code>
|
||||
* associated with <tt><frame></tt> and <tt><iframe></tt>,
|
||||
* are represented as web frames over the wire protocol.
|
||||
*/
|
||||
export class WebFrame extends WebReference {
|
||||
toJSON() {
|
||||
return { [WebFrame.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
if (!(WebFrame.Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web frame reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
let uuid = json[WebFrame.Identifier];
|
||||
return new WebFrame(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
WebFrame.Identifier = "frame-075b-4da1-b6ba-e579c2d3230a";
|
||||
|
|
@ -11,7 +11,7 @@ const lazy = {};
|
|||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs",
|
||||
atom: "chrome://remote/content/marionette/atom.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
element: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
event: "chrome://remote/content/marionette/event.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ remote.jar:
|
|||
content/marionette/cert.sys.mjs (cert.sys.mjs)
|
||||
content/marionette/cookie.sys.mjs (cookie.sys.mjs)
|
||||
content/marionette/driver.sys.mjs (driver.sys.mjs)
|
||||
content/marionette/element.sys.mjs (element.sys.mjs)
|
||||
content/marionette/evaluate.sys.mjs (evaluate.sys.mjs)
|
||||
content/marionette/event.sys.mjs (event.sys.mjs)
|
||||
content/marionette/interaction.sys.mjs (interaction.sys.mjs)
|
||||
|
|
@ -36,7 +37,6 @@ remote.jar:
|
|||
content/marionette/stream-utils.sys.mjs (stream-utils.sys.mjs)
|
||||
content/marionette/sync.sys.mjs (sync.sys.mjs)
|
||||
content/marionette/transport.sys.mjs (transport.sys.mjs)
|
||||
content/marionette/web-reference.sys.mjs (web-reference.sys.mjs)
|
||||
#ifdef ENABLE_TESTS
|
||||
content/marionette/test_dialog.dtd (chrome/test_dialog.dtd)
|
||||
content/marionette/test_dialog.properties (chrome/test_dialog.properties)
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
element: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
||||
ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
||||
WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
||||
WebReference: "chrome://remote/content/marionette/web-reference.sys.mjs",
|
||||
ShadowRoot: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
WebElement: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
WebReference: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
||||
|
|
@ -34,10 +34,10 @@ export const json = {};
|
|||
* The clone algorithm to invoke for individual list entries or object
|
||||
* properties.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* A promise that resolves to the cloned object.
|
||||
* @returns {object}
|
||||
* The cloned object.
|
||||
*/
|
||||
async function cloneObject(value, seen, cloneAlgorithm) {
|
||||
function cloneObject(value, seen, cloneAlgorithm) {
|
||||
// Only proceed with cloning an object if it hasn't been seen yet.
|
||||
if (seen.has(value)) {
|
||||
throw new lazy.error.JavaScriptError("Cyclic object value");
|
||||
|
|
@ -47,15 +47,13 @@ async function cloneObject(value, seen, cloneAlgorithm) {
|
|||
let result;
|
||||
|
||||
if (lazy.element.isCollection(value)) {
|
||||
result = await Promise.all(
|
||||
[...value].map(entry => cloneAlgorithm(entry, seen))
|
||||
);
|
||||
result = [...value].map(entry => cloneAlgorithm(entry, seen));
|
||||
} else {
|
||||
// arbitrary objects
|
||||
result = {};
|
||||
for (let prop in value) {
|
||||
try {
|
||||
result[prop] = await cloneAlgorithm(value[prop], seen);
|
||||
result[prop] = cloneAlgorithm(value[prop], seen);
|
||||
} catch (e) {
|
||||
if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
|
||||
lazy.logger.debug(`Skipping ${prop}: ${e.message}`);
|
||||
|
|
@ -92,18 +90,14 @@ async function cloneObject(value, seen, cloneAlgorithm) {
|
|||
*
|
||||
* - If a cyclic references is detected a JavaScriptError is thrown.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {BrowsingContext} options.browsingContext
|
||||
* Current browsing context.
|
||||
* @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.
|
||||
* @param {object} options.value
|
||||
* @param {object} value
|
||||
* Object to be cloned.
|
||||
* @param {NodeCache} nodeCache
|
||||
* Node cache that holds already seen WebElement and ShadowRoot references.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the same object as provided by `value` with
|
||||
* the WebDriver specific elements replaced by WebReference's.
|
||||
* @returns {object}
|
||||
* Same object as provided by `value` with the WebDriver specific
|
||||
* elements replaced by WebReference's.
|
||||
*
|
||||
* @throws {JavaScriptError}
|
||||
* If an object contains cyclic references.
|
||||
|
|
@ -111,18 +105,8 @@ async function cloneObject(value, seen, cloneAlgorithm) {
|
|||
* If the element has gone stale, indicating it is no longer
|
||||
* attached to the DOM.
|
||||
*/
|
||||
json.clone = async function(options) {
|
||||
const { browsingContext, getOrCreateNodeReference, value } = options;
|
||||
|
||||
if (typeof browsingContext === "undefined") {
|
||||
throw new TypeError("Browsing context not specified");
|
||||
}
|
||||
|
||||
if (typeof getOrCreateNodeReference !== "function") {
|
||||
throw new TypeError("Invalid callback for 'getOrCreateNodeReference'");
|
||||
}
|
||||
|
||||
async function cloneJSON(value, seen) {
|
||||
json.clone = function(value, nodeCache) {
|
||||
function cloneJSON(value, seen) {
|
||||
if (seen === undefined) {
|
||||
seen = new Set();
|
||||
}
|
||||
|
|
@ -159,8 +143,8 @@ json.clone = async function(options) {
|
|||
);
|
||||
}
|
||||
|
||||
const ref = await getOrCreateNodeReference(browsingContext, value);
|
||||
return ref.toJSON();
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value);
|
||||
return lazy.WebReference.from(value, nodeRef).toJSON();
|
||||
}
|
||||
|
||||
if (isNode && lazy.element.isShadowRoot(value)) {
|
||||
|
|
@ -173,8 +157,8 @@ json.clone = async function(options) {
|
|||
);
|
||||
}
|
||||
|
||||
const ref = await getOrCreateNodeReference(browsingContext, value);
|
||||
return ref.toJSON();
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(value);
|
||||
return lazy.WebReference.from(value, nodeRef).toJSON();
|
||||
}
|
||||
|
||||
if (typeof value.toJSON == "function") {
|
||||
|
|
@ -199,50 +183,24 @@ json.clone = async function(options) {
|
|||
/**
|
||||
* Deserialize an arbitrary object.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {BrowsingContext} options.browsingContext
|
||||
* Current browsing context.
|
||||
* @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.
|
||||
* @param {object} options.value
|
||||
* @param {object} value
|
||||
* Arbitrary object.
|
||||
* @param {NodeCache} nodeCache
|
||||
* Node cache that holds already seen WebElement and ShadowRoot references.
|
||||
* @param {WindowProxy} win
|
||||
* Current window.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the same object as provided by `value` with the
|
||||
* WebDriver specific references replaced with real JavaScript objects.
|
||||
* @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 = async function(options) {
|
||||
const {
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
getKnownShadowRoot,
|
||||
value,
|
||||
} = options;
|
||||
|
||||
if (typeof browsingContext === "undefined") {
|
||||
throw new TypeError("Browsing context not specified");
|
||||
}
|
||||
|
||||
if (typeof getKnownElement !== "function") {
|
||||
throw new TypeError("Invalid callback for 'getKnownElement'");
|
||||
}
|
||||
|
||||
if (typeof getKnownShadowRoot !== "function") {
|
||||
throw new TypeError("Invalid callback for 'getKnownShadowRoot'");
|
||||
}
|
||||
|
||||
async function deserializeJSON(value, seen) {
|
||||
json.deserialize = function(value, nodeCache, win) {
|
||||
function deserializeJSON(value, seen) {
|
||||
if (seen === undefined) {
|
||||
seen = new Set();
|
||||
}
|
||||
|
|
@ -264,11 +222,19 @@ json.deserialize = async function(options) {
|
|||
const webRef = lazy.WebReference.fromJSON(value);
|
||||
|
||||
if (webRef instanceof lazy.ShadowRoot) {
|
||||
return getKnownShadowRoot(browsingContext, webRef.uuid);
|
||||
return lazy.element.getKnownShadowRoot(
|
||||
win.browsingContext,
|
||||
webRef.uuid,
|
||||
nodeCache
|
||||
);
|
||||
}
|
||||
|
||||
if (webRef instanceof lazy.WebElement) {
|
||||
return getKnownElement(browsingContext, webRef.uuid);
|
||||
return lazy.element.getKnownElement(
|
||||
win.browsingContext,
|
||||
webRef.uuid,
|
||||
nodeCache
|
||||
);
|
||||
}
|
||||
|
||||
// WebFrame and WebWindow not supported yet
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
Preferences: "resource://gre/modules/Preferences.sys.mjs",
|
||||
|
||||
accessibility: "chrome://remote/content/marionette/accessibility.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
element: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
json: "chrome://remote/content/marionette/json.sys.mjs",
|
||||
event: "chrome://remote/content/marionette/event.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
WebElement: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
WebElement: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "logger", () =>
|
||||
|
|
|
|||
|
|
@ -2,11 +2,23 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const { element } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/Element.sys.mjs"
|
||||
const {
|
||||
element,
|
||||
ShadowRoot,
|
||||
WebElement,
|
||||
WebFrame,
|
||||
WebReference,
|
||||
WebWindow,
|
||||
} = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/marionette/element.sys.mjs"
|
||||
);
|
||||
const { NodeCache } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/NodeCache.sys.mjs"
|
||||
);
|
||||
|
||||
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
const MemoryReporter = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
|
||||
Ci.nsIMemoryReporterManager
|
||||
);
|
||||
|
||||
class MockElement {
|
||||
constructor(tagName, attrs = {}) {
|
||||
|
|
@ -91,6 +103,7 @@ function setupTest() {
|
|||
|
||||
return {
|
||||
browser,
|
||||
nodeCache: new NodeCache(),
|
||||
childEl,
|
||||
divEl,
|
||||
iframeEl,
|
||||
|
|
@ -442,6 +455,142 @@ 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();
|
||||
|
||||
|
|
@ -475,3 +624,177 @@ add_task(function test_isStale() {
|
|||
childEl.remove();
|
||||
ok(element.isStale(childEl));
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_ctor() {
|
||||
const el = new WebReference("foo");
|
||||
equal(el.uuid, "foo");
|
||||
|
||||
for (let t of [42, true, [], {}, null, undefined]) {
|
||||
Assert.throws(() => new WebReference(t), /to be a string/);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function test_WebElemenet_is() {
|
||||
const a = new WebReference("a");
|
||||
const b = new WebReference("b");
|
||||
|
||||
ok(a.is(a));
|
||||
ok(b.is(b));
|
||||
ok(!a.is(b));
|
||||
ok(!b.is(a));
|
||||
|
||||
ok(!a.is({}));
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_from() {
|
||||
const { divEl, iframeEl } = setupTest();
|
||||
|
||||
ok(WebReference.from(divEl) instanceof WebElement);
|
||||
ok(WebReference.from(xulEl) instanceof WebElement);
|
||||
ok(WebReference.from(divEl.ownerGlobal) instanceof WebWindow);
|
||||
ok(WebReference.from(iframeEl.contentWindow) instanceof WebFrame);
|
||||
ok(WebReference.from(domElInPrivilegedDocument) instanceof WebElement);
|
||||
ok(WebReference.from(xulElInPrivilegedDocument) instanceof WebElement);
|
||||
|
||||
Assert.throws(() => WebReference.from({}), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_WebElement() {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
const ref = { [Identifier]: "foo" };
|
||||
const webEl = WebReference.fromJSON(ref);
|
||||
ok(webEl instanceof WebElement);
|
||||
equal(webEl.uuid, "foo");
|
||||
|
||||
let identifierPrecedence = {
|
||||
[Identifier]: "identifier-uuid",
|
||||
};
|
||||
const precedenceEl = WebReference.fromJSON(identifierPrecedence);
|
||||
ok(precedenceEl instanceof WebElement);
|
||||
equal(precedenceEl.uuid, "identifier-uuid");
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_WebWindow() {
|
||||
const ref = { [WebWindow.Identifier]: "foo" };
|
||||
const win = WebReference.fromJSON(ref);
|
||||
|
||||
ok(win instanceof WebWindow);
|
||||
equal(win.uuid, "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_WebFrame() {
|
||||
const ref = { [WebFrame.Identifier]: "foo" };
|
||||
const frame = WebReference.fromJSON(ref);
|
||||
ok(frame instanceof WebFrame);
|
||||
equal(frame.uuid, "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_malformed() {
|
||||
Assert.throws(() => WebReference.fromJSON({}), /InvalidArgumentError/);
|
||||
Assert.throws(() => WebReference.fromJSON(null), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_isReference() {
|
||||
for (let t of [42, true, "foo", [], {}]) {
|
||||
ok(!WebReference.isReference(t));
|
||||
}
|
||||
|
||||
ok(WebReference.isReference({ [WebElement.Identifier]: "foo" }));
|
||||
ok(WebReference.isReference({ [WebWindow.Identifier]: "foo" }));
|
||||
ok(WebReference.isReference({ [WebFrame.Identifier]: "foo" }));
|
||||
});
|
||||
|
||||
add_task(function test_generateUUID() {
|
||||
equal(typeof element.generateUUID(), "string");
|
||||
});
|
||||
|
||||
add_task(function test_WebElement_toJSON() {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
const el = new WebElement("foo");
|
||||
const json = el.toJSON();
|
||||
|
||||
ok(Identifier in json);
|
||||
equal(json[Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebElement_fromJSON() {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
const el = WebElement.fromJSON({ [Identifier]: "foo" });
|
||||
ok(el instanceof WebElement);
|
||||
equal(el.uuid, "foo");
|
||||
|
||||
Assert.throws(() => WebElement.fromJSON({}), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebElement_fromUUID() {
|
||||
const domWebEl = WebElement.fromUUID("bar");
|
||||
|
||||
ok(domWebEl instanceof WebElement);
|
||||
equal(domWebEl.uuid, "bar");
|
||||
|
||||
Assert.throws(() => WebElement.fromUUID(), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_ShadowRoot_toJSON() {
|
||||
const { Identifier } = ShadowRoot;
|
||||
|
||||
const shadowRoot = new ShadowRoot("foo");
|
||||
const json = shadowRoot.toJSON();
|
||||
|
||||
ok(Identifier in json);
|
||||
equal(json[Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_ShadowRoot_fromJSON() {
|
||||
const { Identifier } = ShadowRoot;
|
||||
|
||||
const shadowRoot = ShadowRoot.fromJSON({ [Identifier]: "foo" });
|
||||
ok(shadowRoot instanceof ShadowRoot);
|
||||
equal(shadowRoot.uuid, "foo");
|
||||
|
||||
Assert.throws(() => ShadowRoot.fromJSON({}), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_ShadowRoot_fromUUID() {
|
||||
const shadowRoot = ShadowRoot.fromUUID("baz");
|
||||
|
||||
ok(shadowRoot instanceof ShadowRoot);
|
||||
equal(shadowRoot.uuid, "baz");
|
||||
|
||||
Assert.throws(() => ShadowRoot.fromUUID(), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebWindow_toJSON() {
|
||||
const win = new WebWindow("foo");
|
||||
const json = win.toJSON();
|
||||
|
||||
ok(WebWindow.Identifier in json);
|
||||
equal(json[WebWindow.Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebWindow_fromJSON() {
|
||||
const ref = { [WebWindow.Identifier]: "foo" };
|
||||
const win = WebWindow.fromJSON(ref);
|
||||
|
||||
ok(win instanceof WebWindow);
|
||||
equal(win.uuid, "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebFrame_toJSON() {
|
||||
const frame = new WebFrame("foo");
|
||||
const json = frame.toJSON();
|
||||
|
||||
ok(WebFrame.Identifier in json);
|
||||
equal(json[WebFrame.Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebFrame_fromJSON() {
|
||||
const ref = { [WebFrame.Identifier]: "foo" };
|
||||
const win = WebFrame.fromJSON(ref);
|
||||
|
||||
ok(win instanceof WebFrame);
|
||||
equal(win.uuid, "foo");
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@ 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"
|
||||
"chrome://remote/content/marionette/element.sys.mjs"
|
||||
);
|
||||
|
||||
function setupTest() {
|
||||
|
|
@ -27,164 +27,61 @@ function setupTest() {
|
|||
browser.document.body.appendChild(iframeEl);
|
||||
const childEl = iframeEl.contentDocument.createElement("div");
|
||||
|
||||
return {
|
||||
browser,
|
||||
browsingContext: browser.browsingContext,
|
||||
nodeCache,
|
||||
childEl,
|
||||
iframeEl,
|
||||
htmlEl,
|
||||
shadowRoot,
|
||||
svgEl,
|
||||
};
|
||||
return { browser, nodeCache, childEl, iframeEl, htmlEl, shadowRoot, svgEl };
|
||||
}
|
||||
|
||||
function clone(options = {}) {
|
||||
const {
|
||||
browsingContext,
|
||||
getOrCreateNodeReference = async () =>
|
||||
ok(false, "'getOrCreateNodeReference' called"),
|
||||
value,
|
||||
} = options;
|
||||
|
||||
return json.clone({ browsingContext, getOrCreateNodeReference, value });
|
||||
}
|
||||
|
||||
function deserialize(options = {}) {
|
||||
const {
|
||||
browsingContext,
|
||||
getKnownElement = async () => ok(false, "'getKnownElement' called"),
|
||||
getKnownShadowRoot = async () => ok(false, "'getKnownShadowRoot' called"),
|
||||
value,
|
||||
} = options;
|
||||
|
||||
return json.deserialize({
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
getKnownShadowRoot,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
add_task(async function test_clone_generalTypes() {
|
||||
const { browsingContext } = setupTest();
|
||||
add_task(function test_clone_generalTypes() {
|
||||
const { nodeCache } = setupTest();
|
||||
|
||||
// null
|
||||
equal(await clone({ browsingContext, value: undefined }), null);
|
||||
equal(await clone({ browsingContext, value: null }), null);
|
||||
equal(json.clone(undefined, nodeCache), null);
|
||||
equal(json.clone(null, nodeCache), null);
|
||||
|
||||
// primitives
|
||||
equal(await clone({ browsingContext, value: true }), true);
|
||||
equal(await clone({ browsingContext, value: 42 }), 42);
|
||||
equal(await clone({ browsingContext, value: "foo" }), "foo");
|
||||
equal(json.clone(true, nodeCache), true);
|
||||
equal(json.clone(42, nodeCache), 42);
|
||||
equal(json.clone("foo", nodeCache), "foo");
|
||||
|
||||
// toJSON
|
||||
equal(
|
||||
await clone({
|
||||
browsingContext,
|
||||
value: {
|
||||
toJSON() {
|
||||
return "foo";
|
||||
},
|
||||
json.clone({
|
||||
toJSON() {
|
||||
return "foo";
|
||||
},
|
||||
}),
|
||||
"foo"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_clone_ShadowRoot() {
|
||||
const { browsingContext, nodeCache, shadowRoot } = setupTest();
|
||||
|
||||
async function getOrCreateNodeReference(bc, node) {
|
||||
equal(bc, browsingContext);
|
||||
equal(node, shadowRoot);
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
// Fails with missing browsing context
|
||||
await Assert.rejects(
|
||||
json.clone({ getOrCreateNodeReference, value: shadowRoot }),
|
||||
/TypeError/,
|
||||
"Missing getOrCreateNodeReference callback expected to throw"
|
||||
);
|
||||
|
||||
// Fails with missing getOrCreateNodeReference callback
|
||||
await Assert.rejects(
|
||||
json.clone({ browsingContext, value: shadowRoot }),
|
||||
/TypeError/,
|
||||
"Missing getOrCreateNodeReference callback expected to throw"
|
||||
);
|
||||
add_task(function test_clone_ShadowRoot() {
|
||||
const { nodeCache, shadowRoot } = setupTest();
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
deepEqual(
|
||||
await clone({
|
||||
browsingContext,
|
||||
getOrCreateNodeReference,
|
||||
value: shadowRoot,
|
||||
}),
|
||||
json.clone(shadowRoot, nodeCache),
|
||||
WebReference.from(shadowRoot, shadowRootRef).toJSON()
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_clone_WebElement() {
|
||||
const { browsingContext, htmlEl, nodeCache, svgEl } = setupTest();
|
||||
|
||||
async function getOrCreateNodeReference(bc, node) {
|
||||
equal(bc, browsingContext);
|
||||
ok([htmlEl, svgEl].includes(node));
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
|
||||
// Fails with missing browsing context
|
||||
await Assert.rejects(
|
||||
json.clone({ getOrCreateNodeReference, value: htmlEl }),
|
||||
/TypeError/,
|
||||
"Missing getOrCreateNodeReference callback expected to throw"
|
||||
);
|
||||
|
||||
// Fails with missing getOrCreateNodeReference callback
|
||||
await Assert.rejects(
|
||||
json.clone({ browsingContext, value: htmlEl }),
|
||||
/TypeError/,
|
||||
"Missing getOrCreateNodeReference callback expected to throw"
|
||||
);
|
||||
add_task(function test_clone_WebElement() {
|
||||
const { htmlEl, nodeCache, svgEl } = setupTest();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
deepEqual(
|
||||
await clone({
|
||||
browsingContext,
|
||||
getOrCreateNodeReference,
|
||||
value: htmlEl,
|
||||
}),
|
||||
json.clone(htmlEl, nodeCache),
|
||||
WebReference.from(htmlEl, htmlElRef).toJSON()
|
||||
);
|
||||
|
||||
// Check an element with a different namespace
|
||||
const svgElRef = nodeCache.getOrCreateNodeReference(svgEl);
|
||||
deepEqual(
|
||||
await clone({
|
||||
browsingContext,
|
||||
getOrCreateNodeReference,
|
||||
value: svgEl,
|
||||
}),
|
||||
json.clone(svgEl, nodeCache),
|
||||
WebReference.from(svgEl, svgElRef).toJSON()
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_clone_Sequences() {
|
||||
const { browsingContext, htmlEl, nodeCache } = setupTest();
|
||||
|
||||
async function getOrCreateNodeReference(bc, node) {
|
||||
equal(bc, browsingContext);
|
||||
equal(node, htmlEl);
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
add_task(function test_clone_Sequences() {
|
||||
const { htmlEl, nodeCache } = setupTest();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
|
|
@ -201,11 +98,7 @@ add_task(async function test_clone_Sequences() {
|
|||
{ bar: "baz" },
|
||||
];
|
||||
|
||||
const actual = await clone({
|
||||
browsingContext,
|
||||
getOrCreateNodeReference,
|
||||
value: input,
|
||||
});
|
||||
const actual = json.clone(input, nodeCache);
|
||||
|
||||
equal(actual[0], null);
|
||||
equal(actual[1], true);
|
||||
|
|
@ -215,16 +108,8 @@ add_task(async function test_clone_Sequences() {
|
|||
deepEqual(actual[5], { bar: "baz" });
|
||||
});
|
||||
|
||||
add_task(async function test_clone_objects() {
|
||||
const { browsingContext, htmlEl, nodeCache } = setupTest();
|
||||
|
||||
async function getOrCreateNodeReference(bc, node) {
|
||||
equal(bc, browsingContext);
|
||||
equal(node, htmlEl);
|
||||
|
||||
const nodeRef = nodeCache.getOrCreateNodeReference(node);
|
||||
return WebReference.from(node, nodeRef);
|
||||
}
|
||||
add_task(function test_clone_objects() {
|
||||
const { htmlEl, nodeCache } = setupTest();
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
|
|
@ -241,11 +126,7 @@ add_task(async function test_clone_objects() {
|
|||
object: { bar: "baz" },
|
||||
};
|
||||
|
||||
const actual = await clone({
|
||||
browsingContext,
|
||||
getOrCreateNodeReference,
|
||||
value: input,
|
||||
});
|
||||
const actual = json.clone(input, nodeCache);
|
||||
|
||||
equal(actual.null, null);
|
||||
equal(actual.boolean, true);
|
||||
|
|
@ -255,164 +136,102 @@ add_task(async function test_clone_objects() {
|
|||
deepEqual(actual.object, { bar: "baz" });
|
||||
});
|
||||
|
||||
add_task(async function test_clone_сyclicReference() {
|
||||
const { browsingContext } = setupTest();
|
||||
|
||||
const array = [];
|
||||
array.push(array);
|
||||
|
||||
const obj = {};
|
||||
obj.reference = obj;
|
||||
add_task(function test_clone_сyclicReference() {
|
||||
const { nodeCache } = setupTest();
|
||||
|
||||
// object
|
||||
await Assert.rejects(
|
||||
clone({ browsingContext, value: obj }),
|
||||
/JavaScriptError/,
|
||||
"Cyclic reference expected to throw"
|
||||
);
|
||||
Assert.throws(() => {
|
||||
const obj = {};
|
||||
obj.reference = obj;
|
||||
json.clone(obj, nodeCache);
|
||||
}, /JavaScriptError/);
|
||||
|
||||
// array
|
||||
await Assert.rejects(
|
||||
clone({ browsingContext, value: array }),
|
||||
/JavaScriptError/,
|
||||
"Cyclic reference expected to throw"
|
||||
);
|
||||
Assert.throws(() => {
|
||||
const array = [];
|
||||
array.push(array);
|
||||
json.clone(array, nodeCache);
|
||||
}, /JavaScriptError/);
|
||||
|
||||
// array in object
|
||||
await Assert.rejects(
|
||||
clone({ browsingContext, value: { array } }),
|
||||
/JavaScriptError/,
|
||||
"Cyclic reference expected to throw"
|
||||
);
|
||||
Assert.throws(() => {
|
||||
const array = [];
|
||||
array.push(array);
|
||||
json.clone({ array }, nodeCache);
|
||||
}, /JavaScriptError/);
|
||||
|
||||
// object in array
|
||||
await Assert.rejects(
|
||||
clone({ browsingContext, value: [obj] }),
|
||||
/JavaScriptError/,
|
||||
"Cyclic reference expected to throw"
|
||||
);
|
||||
Assert.throws(() => {
|
||||
const obj = {};
|
||||
obj.reference = obj;
|
||||
json.clone([obj], nodeCache);
|
||||
}, /JavaScriptError/);
|
||||
});
|
||||
|
||||
add_task(async function test_deserialize_generalTypes() {
|
||||
const { browsingContext } = setupTest();
|
||||
add_task(function test_deserialize_generalTypes() {
|
||||
const { browser, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
// null
|
||||
equal(await deserialize({ browsingContext, value: undefined }), undefined);
|
||||
equal(await deserialize({ browsingContext, value: null }), null);
|
||||
equal(json.deserialize(undefined, nodeCache, win), undefined);
|
||||
equal(json.deserialize(null, nodeCache, win), null);
|
||||
|
||||
// primitives
|
||||
equal(await deserialize({ browsingContext, value: true }), true);
|
||||
equal(await deserialize({ browsingContext, value: 42 }), 42);
|
||||
equal(await deserialize({ browsingContext, value: "foo" }), "foo");
|
||||
equal(json.deserialize(true, nodeCache, win), true);
|
||||
equal(json.deserialize(42, nodeCache, win), 42);
|
||||
equal(json.deserialize("foo", nodeCache, win), "foo");
|
||||
});
|
||||
|
||||
add_task(async function test_deserialize_ShadowRoot() {
|
||||
const { browsingContext, nodeCache, shadowRoot } = setupTest();
|
||||
add_task(function test_deserialize_ShadowRoot() {
|
||||
const { browser, nodeCache, shadowRoot } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
const getKnownElement = async () => ok(false, "'getKnownElement' called");
|
||||
const getKnownShadowRoot = async (bc, nodeId) =>
|
||||
nodeCache.getNode(bc, nodeId);
|
||||
|
||||
// Unknown shadow root
|
||||
// Fails to resolve for unknown elements
|
||||
const unknownShadowRootId = { [ShadowRoot.Identifier]: "foo" };
|
||||
equal(
|
||||
await deserialize({
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
getKnownShadowRoot,
|
||||
value: unknownShadowRootId,
|
||||
}),
|
||||
null
|
||||
);
|
||||
Assert.throws(() => {
|
||||
json.deserialize(unknownShadowRootId, nodeCache, win);
|
||||
}, /NoSuchShadowRootError/);
|
||||
|
||||
const shadowRootRef = nodeCache.getOrCreateNodeReference(shadowRoot);
|
||||
const shadowRootEl = { [ShadowRoot.Identifier]: shadowRootRef };
|
||||
|
||||
// Fails with missing browsing context
|
||||
await Assert.rejects(
|
||||
json.deserialize({
|
||||
getKnownElement,
|
||||
getKnownShadowRoot,
|
||||
value: shadowRootEl,
|
||||
}),
|
||||
/TypeError/,
|
||||
"Missing browsing context expected to throw"
|
||||
);
|
||||
|
||||
// Fails with missing getKnownShadowRoot callback
|
||||
await Assert.rejects(
|
||||
json.deserialize({ browsingContext, getKnownElement, value: shadowRootEl }),
|
||||
/TypeError/,
|
||||
"Missing getKnownShadowRoot callback expected to throw"
|
||||
);
|
||||
// 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 = await deserialize({
|
||||
browsingContext,
|
||||
getKnownShadowRoot,
|
||||
value: shadowRootEl,
|
||||
});
|
||||
deepEqual(root, nodeCache.getNode(browsingContext, shadowRootRef));
|
||||
const root = json.deserialize(shadowRootEl, nodeCache, win);
|
||||
deepEqual(root, shadowRoot);
|
||||
deepEqual(root, nodeCache.getNode(browser.browsingContext, shadowRootRef));
|
||||
});
|
||||
|
||||
add_task(async function test_deserialize_WebElement() {
|
||||
const { browsingContext, htmlEl, nodeCache } = setupTest();
|
||||
add_task(function test_deserialize_WebElement() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
const getKnownElement = async (bc, nodeId) => nodeCache.getNode(bc, nodeId);
|
||||
const getKnownShadowRoot = async () =>
|
||||
ok(false, "'getKnownShadowRoot' called");
|
||||
|
||||
// Unknown element
|
||||
// Fails to resolve for unknown elements
|
||||
const unknownWebElId = { [WebElement.Identifier]: "foo" };
|
||||
equal(
|
||||
await json.deserialize({
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
getKnownShadowRoot,
|
||||
value: unknownWebElId,
|
||||
}),
|
||||
null
|
||||
);
|
||||
Assert.throws(() => {
|
||||
json.deserialize(unknownWebElId, nodeCache, win);
|
||||
}, /NoSuchElementError/);
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
const htmlWebEl = { [WebElement.Identifier]: htmlElRef };
|
||||
|
||||
// Fails with missing browsing context
|
||||
await Assert.rejects(
|
||||
json.deserialize({ getKnownElement, getKnownShadowRoot, value: htmlWebEl }),
|
||||
/TypeError/,
|
||||
"Missing browsing context expected to throw"
|
||||
);
|
||||
|
||||
// Fails with missing getKnownElement callback
|
||||
await Assert.rejects(
|
||||
json.deserialize({ browsingContext, getKnownShadowRoot, value: htmlWebEl }),
|
||||
/TypeError/,
|
||||
"Missing getKnownElement callback expected to throw"
|
||||
);
|
||||
// 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 = await deserialize({
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
value: htmlWebEl,
|
||||
});
|
||||
const el = json.deserialize(htmlWebEl, nodeCache, win);
|
||||
deepEqual(el, htmlEl);
|
||||
deepEqual(el, nodeCache.getNode(browsingContext, htmlElRef));
|
||||
deepEqual(el, nodeCache.getNode(browser.browsingContext, htmlElRef));
|
||||
});
|
||||
|
||||
add_task(async function test_deserialize_Sequences() {
|
||||
const { browsingContext, htmlEl, nodeCache } = setupTest();
|
||||
add_task(function test_deserialize_Sequences() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
async function getKnownElement(bc, nodeId) {
|
||||
equal(bc, browsingContext);
|
||||
equal(nodeId, htmlElRef);
|
||||
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
const input = [
|
||||
null,
|
||||
true,
|
||||
|
|
@ -421,11 +240,7 @@ add_task(async function test_deserialize_Sequences() {
|
|||
{ bar: "baz" },
|
||||
];
|
||||
|
||||
const actual = await deserialize({
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
value: input,
|
||||
});
|
||||
const actual = json.deserialize(input, nodeCache, win);
|
||||
|
||||
equal(actual[0], null);
|
||||
equal(actual[1], true);
|
||||
|
|
@ -434,18 +249,12 @@ add_task(async function test_deserialize_Sequences() {
|
|||
deepEqual(actual[4], { bar: "baz" });
|
||||
});
|
||||
|
||||
add_task(async function test_deserialize_objects() {
|
||||
const { browsingContext, htmlEl, nodeCache } = setupTest();
|
||||
add_task(function test_deserialize_objects() {
|
||||
const { browser, htmlEl, nodeCache } = setupTest();
|
||||
const win = browser.document.ownerGlobal;
|
||||
|
||||
const htmlElRef = nodeCache.getOrCreateNodeReference(htmlEl);
|
||||
|
||||
async function getKnownElement(bc, nodeId) {
|
||||
equal(bc, browsingContext);
|
||||
equal(nodeId, htmlElRef);
|
||||
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
const input = {
|
||||
null: null,
|
||||
boolean: true,
|
||||
|
|
@ -454,11 +263,7 @@ add_task(async function test_deserialize_objects() {
|
|||
object: { bar: "baz" },
|
||||
};
|
||||
|
||||
const actual = await deserialize({
|
||||
browsingContext,
|
||||
getKnownElement,
|
||||
value: input,
|
||||
});
|
||||
const actual = json.deserialize(input, nodeCache, win);
|
||||
|
||||
equal(actual.null, null);
|
||||
equal(actual.boolean, true);
|
||||
|
|
|
|||
|
|
@ -1,283 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const {
|
||||
ShadowRoot,
|
||||
WebElement,
|
||||
WebFrame,
|
||||
WebReference,
|
||||
WebWindow,
|
||||
} = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/marionette/web-reference.sys.mjs"
|
||||
);
|
||||
const { element } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/Element.sys.mjs"
|
||||
);
|
||||
|
||||
class MockElement {
|
||||
constructor(tagName, attrs = {}) {
|
||||
this.tagName = tagName;
|
||||
this.localName = tagName;
|
||||
|
||||
this.isConnected = false;
|
||||
this.ownerGlobal = {
|
||||
document: {
|
||||
isActive() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
for (let attr in attrs) {
|
||||
this[attr] = attrs[attr];
|
||||
}
|
||||
}
|
||||
|
||||
get nodeType() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
get ELEMENT_NODE() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// this is a severely limited CSS selector
|
||||
// that only supports lists of tag names
|
||||
matches(selector) {
|
||||
let tags = selector.split(",");
|
||||
return tags.includes(this.localName);
|
||||
}
|
||||
}
|
||||
|
||||
class MockXULElement extends MockElement {
|
||||
constructor(tagName, attrs = {}) {
|
||||
super(tagName, attrs);
|
||||
this.namespaceURI = XUL_NS;
|
||||
|
||||
if (typeof this.ownerDocument == "undefined") {
|
||||
this.ownerDocument = {};
|
||||
}
|
||||
if (typeof this.ownerDocument.documentElement == "undefined") {
|
||||
this.ownerDocument.documentElement = { namespaceURI: XUL_NS };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const xulEl = new MockXULElement("text");
|
||||
|
||||
const domElInPrivilegedDocument = new MockElement("input", {
|
||||
nodePrincipal: { isSystemPrincipal: true },
|
||||
});
|
||||
const xulElInPrivilegedDocument = new MockXULElement("text", {
|
||||
nodePrincipal: { isSystemPrincipal: true },
|
||||
});
|
||||
|
||||
function setupTest() {
|
||||
const browser = Services.appShell.createWindowlessBrowser(false);
|
||||
|
||||
browser.document.body.innerHTML = `
|
||||
<div id="foo" style="margin: 50px">
|
||||
<iframe></iframe>
|
||||
<video></video>
|
||||
<svg xmlns="http://www.w3.org/2000/svg"></svg>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const divEl = browser.document.querySelector("div");
|
||||
const svgEl = browser.document.querySelector("svg");
|
||||
const textareaEl = browser.document.querySelector("textarea");
|
||||
const videoEl = browser.document.querySelector("video");
|
||||
|
||||
const iframeEl = browser.document.querySelector("iframe");
|
||||
const childEl = iframeEl.contentDocument.createElement("div");
|
||||
iframeEl.contentDocument.body.appendChild(childEl);
|
||||
|
||||
const shadowRoot = videoEl.openOrClosedShadowRoot;
|
||||
|
||||
return {
|
||||
browser,
|
||||
childEl,
|
||||
divEl,
|
||||
iframeEl,
|
||||
shadowRoot,
|
||||
svgEl,
|
||||
textareaEl,
|
||||
videoEl,
|
||||
};
|
||||
}
|
||||
|
||||
add_task(function test_WebReference_ctor() {
|
||||
const el = new WebReference("foo");
|
||||
equal(el.uuid, "foo");
|
||||
|
||||
for (let t of [42, true, [], {}, null, undefined]) {
|
||||
Assert.throws(() => new WebReference(t), /to be a string/);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function test_WebElemenet_is() {
|
||||
const a = new WebReference("a");
|
||||
const b = new WebReference("b");
|
||||
|
||||
ok(a.is(a));
|
||||
ok(b.is(b));
|
||||
ok(!a.is(b));
|
||||
ok(!b.is(a));
|
||||
|
||||
ok(!a.is({}));
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_from() {
|
||||
const { divEl, iframeEl } = setupTest();
|
||||
|
||||
ok(WebReference.from(divEl) instanceof WebElement);
|
||||
ok(WebReference.from(xulEl) instanceof WebElement);
|
||||
ok(WebReference.from(divEl.ownerGlobal) instanceof WebWindow);
|
||||
ok(WebReference.from(iframeEl.contentWindow) instanceof WebFrame);
|
||||
ok(WebReference.from(domElInPrivilegedDocument) instanceof WebElement);
|
||||
ok(WebReference.from(xulElInPrivilegedDocument) instanceof WebElement);
|
||||
|
||||
Assert.throws(() => WebReference.from({}), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_WebElement() {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
const ref = { [Identifier]: "foo" };
|
||||
const webEl = WebReference.fromJSON(ref);
|
||||
ok(webEl instanceof WebElement);
|
||||
equal(webEl.uuid, "foo");
|
||||
|
||||
let identifierPrecedence = {
|
||||
[Identifier]: "identifier-uuid",
|
||||
};
|
||||
const precedenceEl = WebReference.fromJSON(identifierPrecedence);
|
||||
ok(precedenceEl instanceof WebElement);
|
||||
equal(precedenceEl.uuid, "identifier-uuid");
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_WebWindow() {
|
||||
const ref = { [WebWindow.Identifier]: "foo" };
|
||||
const win = WebReference.fromJSON(ref);
|
||||
|
||||
ok(win instanceof WebWindow);
|
||||
equal(win.uuid, "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_WebFrame() {
|
||||
const ref = { [WebFrame.Identifier]: "foo" };
|
||||
const frame = WebReference.fromJSON(ref);
|
||||
ok(frame instanceof WebFrame);
|
||||
equal(frame.uuid, "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_fromJSON_malformed() {
|
||||
Assert.throws(() => WebReference.fromJSON({}), /InvalidArgumentError/);
|
||||
Assert.throws(() => WebReference.fromJSON(null), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebReference_isReference() {
|
||||
for (let t of [42, true, "foo", [], {}]) {
|
||||
ok(!WebReference.isReference(t));
|
||||
}
|
||||
|
||||
ok(WebReference.isReference({ [WebElement.Identifier]: "foo" }));
|
||||
ok(WebReference.isReference({ [WebWindow.Identifier]: "foo" }));
|
||||
ok(WebReference.isReference({ [WebFrame.Identifier]: "foo" }));
|
||||
});
|
||||
|
||||
add_task(function test_generateUUID() {
|
||||
equal(typeof element.generateUUID(), "string");
|
||||
});
|
||||
|
||||
add_task(function test_WebElement_toJSON() {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
const el = new WebElement("foo");
|
||||
const json = el.toJSON();
|
||||
|
||||
ok(Identifier in json);
|
||||
equal(json[Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebElement_fromJSON() {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
const el = WebElement.fromJSON({ [Identifier]: "foo" });
|
||||
ok(el instanceof WebElement);
|
||||
equal(el.uuid, "foo");
|
||||
|
||||
Assert.throws(() => WebElement.fromJSON({}), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebElement_fromUUID() {
|
||||
const domWebEl = WebElement.fromUUID("bar");
|
||||
|
||||
ok(domWebEl instanceof WebElement);
|
||||
equal(domWebEl.uuid, "bar");
|
||||
|
||||
Assert.throws(() => WebElement.fromUUID(), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_ShadowRoot_toJSON() {
|
||||
const { Identifier } = ShadowRoot;
|
||||
|
||||
const shadowRoot = new ShadowRoot("foo");
|
||||
const json = shadowRoot.toJSON();
|
||||
|
||||
ok(Identifier in json);
|
||||
equal(json[Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_ShadowRoot_fromJSON() {
|
||||
const { Identifier } = ShadowRoot;
|
||||
|
||||
const shadowRoot = ShadowRoot.fromJSON({ [Identifier]: "foo" });
|
||||
ok(shadowRoot instanceof ShadowRoot);
|
||||
equal(shadowRoot.uuid, "foo");
|
||||
|
||||
Assert.throws(() => ShadowRoot.fromJSON({}), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_ShadowRoot_fromUUID() {
|
||||
const shadowRoot = ShadowRoot.fromUUID("baz");
|
||||
|
||||
ok(shadowRoot instanceof ShadowRoot);
|
||||
equal(shadowRoot.uuid, "baz");
|
||||
|
||||
Assert.throws(() => ShadowRoot.fromUUID(), /InvalidArgumentError/);
|
||||
});
|
||||
|
||||
add_task(function test_WebWindow_toJSON() {
|
||||
const win = new WebWindow("foo");
|
||||
const json = win.toJSON();
|
||||
|
||||
ok(WebWindow.Identifier in json);
|
||||
equal(json[WebWindow.Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebWindow_fromJSON() {
|
||||
const ref = { [WebWindow.Identifier]: "foo" };
|
||||
const win = WebWindow.fromJSON(ref);
|
||||
|
||||
ok(win instanceof WebWindow);
|
||||
equal(win.uuid, "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebFrame_toJSON() {
|
||||
const frame = new WebFrame("foo");
|
||||
const json = frame.toJSON();
|
||||
|
||||
ok(WebFrame.Identifier in json);
|
||||
equal(json[WebFrame.Identifier], "foo");
|
||||
});
|
||||
|
||||
add_task(function test_WebFrame_fromJSON() {
|
||||
const ref = { [WebFrame.Identifier]: "foo" };
|
||||
const win = WebFrame.fromJSON(ref);
|
||||
|
||||
ok(win instanceof WebFrame);
|
||||
equal(win.uuid, "foo");
|
||||
});
|
||||
|
|
@ -9,10 +9,10 @@ skip-if = appname == "thunderbird"
|
|||
[test_actors.js]
|
||||
[test_browser.js]
|
||||
[test_cookie.js]
|
||||
[test_element.js]
|
||||
[test_json.js]
|
||||
[test_message.js]
|
||||
[test_modal.js]
|
||||
[test_navigate.js]
|
||||
[test_prefs.js]
|
||||
[test_sync.js]
|
||||
[test_web_reference.js]
|
||||
|
|
|
|||
|
|
@ -1,302 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* A web reference is an abstraction used to identify an element when
|
||||
* it is transported via the protocol, between remote- and local ends.
|
||||
*
|
||||
* In Marionette this abstraction can represent DOM elements,
|
||||
* WindowProxies, and XUL elements.
|
||||
*/
|
||||
export class WebReference {
|
||||
/**
|
||||
* @param {string} uuid
|
||||
* Identifier that must be unique across all browsing contexts
|
||||
* for the contract to be upheld.
|
||||
*/
|
||||
constructor(uuid) {
|
||||
this.uuid = lazy.assert.string(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an equality check between this web element and
|
||||
* <var>other</var>.
|
||||
*
|
||||
* @param {WebReference} other
|
||||
* Web element to compare with this.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if this and <var>other</var> are the same. False
|
||||
* otherwise.
|
||||
*/
|
||||
is(other) {
|
||||
return other instanceof WebReference && this.uuid === other.uuid;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `[object ${this.constructor.name} uuid=${this.uuid}]`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link WebReference} reference for a DOM or XUL element,
|
||||
* <code>WindowProxy</code>, or <code>ShadowRoot</code>.
|
||||
*
|
||||
* @param {(Element|ShadowRoot|WindowProxy|MockXULElement)} node
|
||||
* Node to construct a web element reference for.
|
||||
* @param {string=} uuid
|
||||
* Optional unique identifier of the WebReference if already known.
|
||||
* If not defined a new unique identifier will be created.
|
||||
*
|
||||
* @returns {WebReference}
|
||||
* Web reference for <var>node</var>.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>node</var> is neither a <code>WindowProxy</code>,
|
||||
* DOM or XUL element, or <code>ShadowRoot</code>.
|
||||
*/
|
||||
static from(node, uuid) {
|
||||
if (uuid === undefined) {
|
||||
uuid = Services.uuid
|
||||
.generateUUID()
|
||||
.toString()
|
||||
.slice(1, -1);
|
||||
}
|
||||
|
||||
if (
|
||||
lazy.element.isShadowRoot(node) &&
|
||||
!lazy.element.isInPrivilegedDocument(node)
|
||||
) {
|
||||
// When we support Chrome Shadowroots we will need to
|
||||
// do a check here of shadowroot.host being in a privileged document
|
||||
// See Bug 1743541
|
||||
return new ShadowRoot(uuid);
|
||||
} else if (lazy.element.isElement(node)) {
|
||||
return new WebElement(uuid);
|
||||
} else if (lazy.element.isDOMWindow(node)) {
|
||||
if (node.parent === node) {
|
||||
return new WebWindow(uuid);
|
||||
}
|
||||
return new WebFrame(uuid);
|
||||
}
|
||||
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
"Expected DOM window/element " + lazy.pprint`or XUL element, got: ${node}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unmarshals a JSON Object to one of {@link ShadowRoot}, {@link WebElement},
|
||||
* {@link WebFrame}, or {@link WebWindow}.
|
||||
*
|
||||
* @param {Object<string, string>} json
|
||||
* Web reference, which is supposed to be a JSON Object
|
||||
* where the key is one of the {@link WebReference} concrete
|
||||
* classes' UUID identifiers.
|
||||
*
|
||||
* @returns {WebReference}
|
||||
* Web reference for the JSON object.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>json</var> is not a web reference.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
lazy.assert.object(json);
|
||||
if (json instanceof WebReference) {
|
||||
return json;
|
||||
}
|
||||
let keys = Object.keys(json);
|
||||
|
||||
for (let key of keys) {
|
||||
switch (key) {
|
||||
case ShadowRoot.Identifier:
|
||||
return ShadowRoot.fromJSON(json);
|
||||
|
||||
case WebElement.Identifier:
|
||||
return WebElement.fromJSON(json);
|
||||
|
||||
case WebFrame.Identifier:
|
||||
return WebFrame.fromJSON(json);
|
||||
|
||||
case WebWindow.Identifier:
|
||||
return WebWindow.fromJSON(json);
|
||||
}
|
||||
}
|
||||
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if <var>obj<var> is a {@link WebReference} reference.
|
||||
*
|
||||
* @param {Object<string, string>} obj
|
||||
* Object that represents a {@link WebReference}.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if <var>obj</var> is a {@link WebReference}, false otherwise.
|
||||
*/
|
||||
static isReference(obj) {
|
||||
if (Object.prototype.toString.call(obj) != "[object Object]") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
ShadowRoot.Identifier in obj ||
|
||||
WebElement.Identifier in obj ||
|
||||
WebFrame.Identifier in obj ||
|
||||
WebWindow.Identifier in obj
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM elements are represented as web elements when they are
|
||||
* transported over the wire protocol.
|
||||
*/
|
||||
export class WebElement extends WebReference {
|
||||
toJSON() {
|
||||
return { [WebElement.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
const { Identifier } = WebElement;
|
||||
|
||||
if (!(Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web element reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
|
||||
let uuid = json[Identifier];
|
||||
return new WebElement(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link WebElement} from a string <var>uuid</var>.
|
||||
*
|
||||
* This whole function is a workaround for the fact that clients
|
||||
* to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
|
||||
* Objects instead of web element representations.
|
||||
*
|
||||
* @param {string} uuid
|
||||
* UUID to be associated with the web reference.
|
||||
*
|
||||
* @returns {WebElement}
|
||||
* The web element reference.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>uuid</var> is not a string.
|
||||
*/
|
||||
static fromUUID(uuid) {
|
||||
return new WebElement(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
WebElement.Identifier = "element-6066-11e4-a52e-4f735466cecf";
|
||||
|
||||
/**
|
||||
* Shadow Root elements are represented as shadow root references when they are
|
||||
* transported over the wire protocol
|
||||
*/
|
||||
export class ShadowRoot extends WebReference {
|
||||
toJSON() {
|
||||
return { [ShadowRoot.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
const { Identifier } = ShadowRoot;
|
||||
|
||||
if (!(Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected shadow root reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
|
||||
let uuid = json[Identifier];
|
||||
return new ShadowRoot(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link ShadowRoot} from a string <var>uuid</var>.
|
||||
*
|
||||
* This whole function is a workaround for the fact that clients
|
||||
* to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
|
||||
* Objects instead of shadow root representations.
|
||||
*
|
||||
* @param {string} uuid
|
||||
* UUID to be associated with the web reference.
|
||||
*
|
||||
* @returns {ShadowRoot}
|
||||
* The shadow root reference.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>uuid</var> is not a string.
|
||||
*/
|
||||
static fromUUID(uuid) {
|
||||
lazy.assert.string(uuid);
|
||||
|
||||
return new ShadowRoot(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
ShadowRoot.Identifier = "shadow-6066-11e4-a52e-4f735466cecf";
|
||||
|
||||
/**
|
||||
* Top-level browsing contexts, such as <code>WindowProxy</code>
|
||||
* whose <code>opener</code> is null, are represented as web windows
|
||||
* over the wire protocol.
|
||||
*/
|
||||
export class WebWindow extends WebReference {
|
||||
toJSON() {
|
||||
return { [WebWindow.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
if (!(WebWindow.Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web window reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
let uuid = json[WebWindow.Identifier];
|
||||
return new WebWindow(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
WebWindow.Identifier = "window-fcc6-11e5-b4f8-330a88ab9d7f";
|
||||
|
||||
/**
|
||||
* Nested browsing contexts, such as the <code>WindowProxy</code>
|
||||
* associated with <tt><frame></tt> and <tt><iframe></tt>,
|
||||
* are represented as web frames over the wire protocol.
|
||||
*/
|
||||
export class WebFrame extends WebReference {
|
||||
toJSON() {
|
||||
return { [WebFrame.Identifier]: this.uuid };
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
if (!(WebFrame.Identifier in json)) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
lazy.pprint`Expected web frame reference, got: ${json}`
|
||||
);
|
||||
}
|
||||
let uuid = json[WebFrame.Identifier];
|
||||
return new WebFrame(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
WebFrame.Identifier = "frame-075b-4da1-b6ba-e579c2d3230a";
|
||||
|
|
@ -253,25 +253,6 @@ 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,34 +128,6 @@ 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 {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const lazy = {};
|
|||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
element: "chrome://remote/content/marionette/element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
event: "chrome://remote/content/marionette/event.sys.mjs",
|
||||
keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ 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:
|
||||
|
|
@ -29,9 +28,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
|
||||
XPCOMUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
|
||||
|
||||
/** @namespace */
|
||||
export const session = {};
|
||||
|
||||
/**
|
||||
* Representation of WebDriver session.
|
||||
*/
|
||||
|
|
@ -204,22 +200,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);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
webDriverSessions.delete(this.id);
|
||||
|
||||
lazy.unregisterProcessDataActor();
|
||||
|
||||
this.navigableSeenNodes = null;
|
||||
|
||||
lazy.allowAllCerts.disable();
|
||||
|
||||
// Close all open connections which unregister themselves.
|
||||
|
|
@ -238,6 +222,8 @@ export class WebDriverSession {
|
|||
);
|
||||
this._messageHandler.destroy();
|
||||
}
|
||||
|
||||
lazy.unregisterProcessDataActor();
|
||||
}
|
||||
|
||||
async execute(module, command, params) {
|
||||
|
|
@ -360,66 +346,3 @@ export class WebDriverSession {
|
|||
return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* The ID of the WebDriver session to retrieve.
|
||||
*
|
||||
* @returns {WebDriverSession}
|
||||
* The WebDriver session.
|
||||
*/
|
||||
export function getWebDriverSessionById(sessionId) {
|
||||
return webDriverSessions.get(sessionId);
|
||||
}
|
||||
|
||||
// Global singleton that holds active WebDriver sessions
|
||||
const webDriverSessions = new Map();
|
||||
|
||||
/**
|
||||
* Adds the given node id to the list of seen nodes.
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* The id of the WebDriver session to use.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* Browsing context the node is part of.
|
||||
* @param {string} nodeId
|
||||
* Unique id of the node.
|
||||
*/
|
||||
session.addNodeToSeenNodes = function(sessionId, browsingContext, nodeId) {
|
||||
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());
|
||||
}
|
||||
|
||||
// Add the nodeId to the list of already seen nodes.
|
||||
session.navigableSeenNodes.get(navigable).add(nodeId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the node id is known for the navigable and session.
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* The id of the WebDriver session to use.
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* Browsing context the node is part of.
|
||||
* @param {string} nodeId
|
||||
* Unique id of the node.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if the node is known for the given navigable and session.
|
||||
*/
|
||||
session.isNodeReferenceKnown = function(sessionId, browsingContext, nodeId) {
|
||||
const navigable = lazy.TabManager.getNavigableForBrowsingContext(
|
||||
browsingContext
|
||||
);
|
||||
const session = getWebDriverSessionById(sessionId);
|
||||
|
||||
// Check if the nodeId has been seen before.
|
||||
return !!session.navigableSeenNodes.get(navigable)?.has(nodeId);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,10 +7,7 @@
|
|||
const { Capabilities, Timeouts } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/Capabilities.sys.mjs"
|
||||
);
|
||||
const {
|
||||
WebDriverSession,
|
||||
getWebDriverSessionById,
|
||||
} = ChromeUtils.importESModule(
|
||||
const { WebDriverSession } = ChromeUtils.importESModule(
|
||||
"chrome://remote/content/shared/webdriver/Session.sys.mjs"
|
||||
);
|
||||
|
||||
|
|
@ -21,12 +18,6 @@ add_task(function test_WebDriverSession_ctor() {
|
|||
ok(session.capabilities instanceof Capabilities);
|
||||
});
|
||||
|
||||
add_task(function test_WebDriverSession_destroy() {
|
||||
const session = new WebDriverSession();
|
||||
|
||||
session.destroy();
|
||||
});
|
||||
|
||||
add_task(function test_WebDriverSession_getters() {
|
||||
const session = new WebDriverSession();
|
||||
|
||||
|
|
@ -56,19 +47,3 @@ add_task(function test_WebDriverSession_setters() {
|
|||
session.timeouts = timeouts;
|
||||
equal(session.timeouts, session.capabilities.get("timeouts"));
|
||||
});
|
||||
|
||||
add_task(function test_getWebDriverSessionById() {
|
||||
const session1 = new WebDriverSession();
|
||||
const session2 = new WebDriverSession();
|
||||
|
||||
equal(getWebDriverSessionById(session1.id), session1);
|
||||
equal(getWebDriverSessionById(session2.id), session2);
|
||||
|
||||
session1.destroy();
|
||||
equal(getWebDriverSessionById(session1.id), undefined);
|
||||
equal(getWebDriverSessionById(session2.id), session2);
|
||||
|
||||
session2.destroy();
|
||||
equal(getWebDriverSessionById(session1.id), undefined);
|
||||
equal(getWebDriverSessionById(session2.id), undefined);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ head = head.js
|
|||
[test_Actions.js]
|
||||
[test_Assert.js]
|
||||
[test_Capabilities.js]
|
||||
[test_Element.js]
|
||||
[test_Errors.js]
|
||||
[test_NodeCache.js]
|
||||
[test_Session.js]
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ const lazy = {};
|
|||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||
element: "chrome://remote/content/shared/webdriver/Element.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
Log: "chrome://remote/content/shared/Log.sys.mjs",
|
||||
});
|
||||
|
|
@ -48,20 +47,6 @@ export const OwnershipModel = {
|
|||
Root: "root",
|
||||
};
|
||||
|
||||
/**
|
||||
* Extra options for serializing and deserializing remote values.
|
||||
*
|
||||
* @typedef {object} RemoteValueOptions
|
||||
*
|
||||
* @param {Function=} emitScriptMessage
|
||||
* Callback to emit a "script.message" event.
|
||||
* @param {Function=} getNode
|
||||
* Callback to retrieve a DOM node via its nodeId and browsing context.
|
||||
* @param {Function=} getOrCreateNodeReference
|
||||
* Async callback to get or create a new node reference. Its return value
|
||||
* is the unique id of the DOM node.
|
||||
*/
|
||||
|
||||
/**
|
||||
* An object which holds the information of how
|
||||
* ECMAScript objects should be serialized.
|
||||
|
|
@ -159,16 +144,16 @@ function checkDateTimeString(dateString) {
|
|||
* The Realm in which the value is deserialized.
|
||||
* @param {Array} serializedValueList
|
||||
* List of serialized values.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value deserialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<Array>}
|
||||
* Promise that resolves to the list of deserialized values.
|
||||
* @returns {Array} List of deserialized values.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>serializedValueList</var> is not an array.
|
||||
*/
|
||||
async function deserializeValueList(realm, serializedValueList, options = {}) {
|
||||
function deserializeValueList(realm, serializedValueList, options = {}) {
|
||||
lazy.assert.array(
|
||||
serializedValueList,
|
||||
`Expected "serializedValueList" to be an array, got ${serializedValueList}`
|
||||
|
|
@ -177,7 +162,7 @@ async function deserializeValueList(realm, serializedValueList, options = {}) {
|
|||
const deserializedValues = [];
|
||||
|
||||
for (const item of serializedValueList) {
|
||||
deserializedValues.push((await deserialize(realm, item, options)).data);
|
||||
deserializedValues.push(deserialize(realm, item, options));
|
||||
}
|
||||
|
||||
return deserializedValues;
|
||||
|
|
@ -192,21 +177,17 @@ async function deserializeValueList(realm, serializedValueList, options = {}) {
|
|||
* The Realm in which the value is deserialized.
|
||||
* @param {Array} serializedKeyValueList
|
||||
* List of serialized key-value.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value deserialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<Array>}
|
||||
* Promise that resolves to the list of deserialized key-value pairs.
|
||||
* @returns {Array} List of deserialized key-value.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* If <var>serializedKeyValueList</var> is not an array or
|
||||
* not an array of key-value arrays.
|
||||
*/
|
||||
async function deserializeKeyValueList(
|
||||
realm,
|
||||
serializedKeyValueList,
|
||||
options = {}
|
||||
) {
|
||||
function deserializeKeyValueList(realm, serializedKeyValueList, options = {}) {
|
||||
lazy.assert.array(
|
||||
serializedKeyValueList,
|
||||
`Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
|
||||
|
|
@ -221,16 +202,11 @@ async function deserializeKeyValueList(
|
|||
);
|
||||
}
|
||||
const [serializedKey, serializedValue] = serializedKeyValue;
|
||||
|
||||
let deserializedKey;
|
||||
if (typeof serializedKey == "string") {
|
||||
deserializedKey = serializedKey;
|
||||
} else {
|
||||
deserializedKey = (await deserialize(realm, serializedKey, options)).data;
|
||||
}
|
||||
const deserializedValue = (
|
||||
await deserialize(realm, serializedValue, options)
|
||||
).data;
|
||||
const deserializedKey =
|
||||
typeof serializedKey == "string"
|
||||
? serializedKey
|
||||
: deserialize(realm, serializedKey, options);
|
||||
const deserializedValue = deserialize(realm, serializedValue, options);
|
||||
|
||||
deserializedKeyValueList.push([deserializedKey, deserializedValue]);
|
||||
}
|
||||
|
|
@ -249,21 +225,21 @@ async function deserializeKeyValueList(
|
|||
* Shared unique reference of the Node.
|
||||
* @param {Realm} realm
|
||||
* The Realm in which the value is deserialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value deserialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Node}
|
||||
* The deserialized DOM node.
|
||||
* @returns {Node} The deserialized DOM node.
|
||||
*/
|
||||
function deserializeSharedReference(sharedRef, realm, options = {}) {
|
||||
const { getNode } = options;
|
||||
const { nodeCache } = options;
|
||||
|
||||
const browsingContext = realm.browsingContext;
|
||||
if (!browsingContext) {
|
||||
throw new lazy.error.NoSuchNodeError("Realm isn't a Window global");
|
||||
}
|
||||
|
||||
const node = getNode(browsingContext, sharedRef);
|
||||
const node = nodeCache.getNode(browsingContext, sharedRef);
|
||||
|
||||
if (node === null) {
|
||||
throw new lazy.error.NoSuchNodeError(
|
||||
|
|
@ -271,6 +247,25 @@ function deserializeSharedReference(sharedRef, realm, options = {}) {
|
|||
);
|
||||
}
|
||||
|
||||
// Bug 1819902: Instead of a browsing context check compare the origin
|
||||
const isSameBrowsingContext = sharedRef => {
|
||||
const nodeDetails = nodeCache.getReferenceDetails(sharedRef);
|
||||
|
||||
if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) {
|
||||
// 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;
|
||||
};
|
||||
|
||||
if (!isSameBrowsingContext(sharedRef)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
@ -283,16 +278,16 @@ function deserializeSharedReference(sharedRef, realm, options = {}) {
|
|||
* The Realm in which the value is deserialized.
|
||||
* @param {object} serializedValue
|
||||
* Value of any type to be deserialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value deserialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
* @param {Function} options.emitScriptMessage
|
||||
* The function to emit "script.message" event.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to an object that contains the deserialized
|
||||
* representation of the value as `data` property.
|
||||
* @returns {object} Deserialized representation of the value.
|
||||
*/
|
||||
export async function deserialize(realm, serializedValue, options = {}) {
|
||||
export function deserialize(realm, serializedValue, options = {}) {
|
||||
const { handle, sharedId, type, value } = serializedValue;
|
||||
let data = undefined;
|
||||
|
||||
// With a shared id present deserialize as node reference.
|
||||
if (sharedId !== undefined) {
|
||||
|
|
@ -301,157 +296,134 @@ export async function deserialize(realm, serializedValue, options = {}) {
|
|||
`Expected "sharedId" to be a string, got ${sharedId}`
|
||||
);
|
||||
|
||||
data = deserializeSharedReference(sharedId, realm, options);
|
||||
return deserializeSharedReference(sharedId, realm, options);
|
||||
}
|
||||
|
||||
// With a handle present deserialize as remote reference.
|
||||
else if (handle !== undefined) {
|
||||
if (handle !== undefined) {
|
||||
lazy.assert.string(
|
||||
handle,
|
||||
`Expected "handle" to be a string, got ${handle}`
|
||||
);
|
||||
|
||||
data = realm.getObjectForHandle(handle);
|
||||
if (!data) {
|
||||
const object = realm.getObjectForHandle(handle);
|
||||
if (!object) {
|
||||
throw new lazy.error.NoSuchHandleError(
|
||||
`Unable to find an object reference for "handle" ${handle}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
lazy.assert.string(type, `Expected "type" to be a string, got ${type}`);
|
||||
|
||||
// Primitive protocol values
|
||||
switch (type) {
|
||||
case "undefined":
|
||||
data = undefined;
|
||||
break;
|
||||
case "null":
|
||||
data = null;
|
||||
break;
|
||||
case "string":
|
||||
data = lazy.assert.string(
|
||||
value,
|
||||
`Expected "value" to be a string, got ${value}`
|
||||
);
|
||||
break;
|
||||
case "number":
|
||||
// If value is already a number return its value.
|
||||
if (typeof value === "number") {
|
||||
data = value;
|
||||
} else {
|
||||
// Otherwise it has to be one of the special strings
|
||||
lazy.assert.in(
|
||||
value,
|
||||
["NaN", "-0", "Infinity", "-Infinity"],
|
||||
`Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
|
||||
);
|
||||
data = Number(value);
|
||||
}
|
||||
break;
|
||||
case "boolean":
|
||||
lazy.assert.boolean(
|
||||
value,
|
||||
`Expected "value" to be a boolean, got ${value}`
|
||||
);
|
||||
data = value;
|
||||
break;
|
||||
case "bigint":
|
||||
lazy.assert.string(
|
||||
value,
|
||||
`Expected "value" to be a string, got ${value}`
|
||||
);
|
||||
try {
|
||||
data = BigInt(value);
|
||||
} catch (e) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
`Failed to deserialize value as BigInt: ${value}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
// Script channel
|
||||
case "channel": {
|
||||
const channel = message =>
|
||||
options.emitScriptMessage(realm, value, message);
|
||||
data = realm.cloneIntoRealm(channel);
|
||||
break;
|
||||
}
|
||||
// Non-primitive protocol values
|
||||
case "array":
|
||||
const deserializedArray = await deserializeValueList(
|
||||
realm,
|
||||
value,
|
||||
options
|
||||
);
|
||||
// TODO: clone deserializedList instead?
|
||||
data = realm.cloneIntoRealm([]);
|
||||
deserializedArray.forEach(v => data.push(v));
|
||||
break;
|
||||
case "date":
|
||||
// We want to support only Date Time String format,
|
||||
// check if the value follows it.
|
||||
checkDateTimeString(value);
|
||||
|
||||
data = realm.cloneIntoRealm(new Date(value));
|
||||
break;
|
||||
case "map":
|
||||
data = realm.cloneIntoRealm(new Map());
|
||||
const deserializedMap = await deserializeKeyValueList(
|
||||
realm,
|
||||
value,
|
||||
options
|
||||
);
|
||||
deserializedMap.forEach(([k, v]) => data.set(k, v));
|
||||
break;
|
||||
case "object":
|
||||
data = realm.cloneIntoRealm({});
|
||||
const deserializedObject = await deserializeKeyValueList(
|
||||
realm,
|
||||
value,
|
||||
options
|
||||
);
|
||||
deserializedObject.forEach(([k, v]) => (data[k] = v));
|
||||
break;
|
||||
case "regexp":
|
||||
lazy.assert.object(
|
||||
value,
|
||||
`Expected "value" for RegExp to be an object, got ${value}`
|
||||
);
|
||||
const { pattern, flags } = value;
|
||||
lazy.assert.string(
|
||||
pattern,
|
||||
`Expected "pattern" for RegExp to be a string, got ${pattern}`
|
||||
);
|
||||
if (flags !== undefined) {
|
||||
lazy.assert.string(
|
||||
flags,
|
||||
`Expected "flags" for RegExp to be a string, got ${flags}`
|
||||
);
|
||||
}
|
||||
try {
|
||||
data = realm.cloneIntoRealm(new RegExp(pattern, flags));
|
||||
} catch (e) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
`Failed to deserialize value as RegExp: ${value}`
|
||||
);
|
||||
}
|
||||
break;
|
||||
case "set":
|
||||
data = realm.cloneIntoRealm(new Set());
|
||||
const deserializedSet = await deserializeValueList(
|
||||
realm,
|
||||
value,
|
||||
options
|
||||
);
|
||||
deserializedSet.forEach(v => data.add(v));
|
||||
break;
|
||||
|
||||
default:
|
||||
lazy.logger.warn(`Unsupported type for local value ${type}`);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
// Wrap into an object so that Promises can be returned as well.
|
||||
return { data };
|
||||
lazy.assert.string(type, `Expected "type" to be a string, got ${type}`);
|
||||
|
||||
// Primitive protocol values
|
||||
switch (type) {
|
||||
case "undefined":
|
||||
return undefined;
|
||||
case "null":
|
||||
return null;
|
||||
case "string":
|
||||
lazy.assert.string(
|
||||
value,
|
||||
`Expected "value" to be a string, got ${value}`
|
||||
);
|
||||
return value;
|
||||
case "number":
|
||||
// If value is already a number return its value.
|
||||
if (typeof value === "number") {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Otherwise it has to be one of the special strings
|
||||
lazy.assert.in(
|
||||
value,
|
||||
["NaN", "-0", "Infinity", "-Infinity"],
|
||||
`Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
|
||||
);
|
||||
return Number(value);
|
||||
case "boolean":
|
||||
lazy.assert.boolean(
|
||||
value,
|
||||
`Expected "value" to be a boolean, got ${value}`
|
||||
);
|
||||
return value;
|
||||
case "bigint":
|
||||
lazy.assert.string(
|
||||
value,
|
||||
`Expected "value" to be a string, got ${value}`
|
||||
);
|
||||
try {
|
||||
return BigInt(value);
|
||||
} catch (e) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
`Failed to deserialize value as BigInt: ${value}`
|
||||
);
|
||||
}
|
||||
|
||||
// Script channel
|
||||
case "channel": {
|
||||
const channel = message =>
|
||||
options.emitScriptMessage(realm, value, message);
|
||||
return realm.cloneIntoRealm(channel);
|
||||
}
|
||||
|
||||
// Non-primitive protocol values
|
||||
case "array":
|
||||
const array = realm.cloneIntoRealm([]);
|
||||
deserializeValueList(realm, value, options).forEach(v => array.push(v));
|
||||
return array;
|
||||
case "date":
|
||||
// We want to support only Date Time String format,
|
||||
// check if the value follows it.
|
||||
checkDateTimeString(value);
|
||||
|
||||
return realm.cloneIntoRealm(new Date(value));
|
||||
case "map":
|
||||
const map = realm.cloneIntoRealm(new Map());
|
||||
deserializeKeyValueList(realm, value, options).forEach(([k, v]) =>
|
||||
map.set(k, v)
|
||||
);
|
||||
|
||||
return map;
|
||||
case "object":
|
||||
const object = realm.cloneIntoRealm({});
|
||||
deserializeKeyValueList(realm, value, options).forEach(
|
||||
([k, v]) => (object[k] = v)
|
||||
);
|
||||
return object;
|
||||
case "regexp":
|
||||
lazy.assert.object(
|
||||
value,
|
||||
`Expected "value" for RegExp to be an object, got ${value}`
|
||||
);
|
||||
const { pattern, flags } = value;
|
||||
lazy.assert.string(
|
||||
pattern,
|
||||
`Expected "pattern" for RegExp to be a string, got ${pattern}`
|
||||
);
|
||||
if (flags !== undefined) {
|
||||
lazy.assert.string(
|
||||
flags,
|
||||
`Expected "flags" for RegExp to be a string, got ${flags}`
|
||||
);
|
||||
}
|
||||
try {
|
||||
return realm.cloneIntoRealm(new RegExp(pattern, flags));
|
||||
} catch (e) {
|
||||
throw new lazy.error.InvalidArgumentError(
|
||||
`Failed to deserialize value as RegExp: ${value}`
|
||||
);
|
||||
}
|
||||
case "set":
|
||||
const set = realm.cloneIntoRealm(new Set());
|
||||
deserializeValueList(realm, value, options).forEach(v => set.add(v));
|
||||
return set;
|
||||
}
|
||||
|
||||
lazy.logger.warn(`Unsupported type for local value ${type}`);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -488,14 +460,15 @@ function getHandleForObject(realm, ownershipType, object) {
|
|||
* Node to create the unique reference for.
|
||||
* @param {Realm} realm
|
||||
* The Realm in which the value is serialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value serialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
* Promise that resolves to a shared unique reference for the Node.
|
||||
* @returns {string}
|
||||
* Shared unique reference for the Node.
|
||||
*/
|
||||
async function getSharedIdForNode(node, realm, options = {}) {
|
||||
const { getOrCreateNodeReference } = options;
|
||||
function getSharedIdForNode(node, realm, options = {}) {
|
||||
const { nodeCache } = options;
|
||||
|
||||
if (!Node.isInstance(node)) {
|
||||
return null;
|
||||
|
|
@ -506,7 +479,26 @@ async function getSharedIdForNode(node, realm, options = {}) {
|
|||
return null;
|
||||
}
|
||||
|
||||
return getOrCreateNodeReference(browsingContext, Cu.unwaiveXrays(node));
|
||||
const unwrapped = Cu.unwaiveXrays(node);
|
||||
return nodeCache.getOrCreateNodeReference(unwrapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if <var>node</var> is shadow root.
|
||||
*
|
||||
* @param {Node} node
|
||||
* Node to check.
|
||||
*
|
||||
* @returns {boolean}
|
||||
* True if <var>node</var> is shadow root, false otherwise.
|
||||
*/
|
||||
function isShadowRoot(node) {
|
||||
const DOCUMENT_FRAGMENT_NODE = 11;
|
||||
return (
|
||||
node &&
|
||||
node.nodeType === DOCUMENT_FRAGMENT_NODE &&
|
||||
node.containingShadowRoot == node
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -530,13 +522,13 @@ async function getSharedIdForNode(node, realm, options = {}) {
|
|||
* Map of internal ids.
|
||||
* @param {Realm} realm
|
||||
* The Realm from which comes the value being serialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value serialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the serialized values.
|
||||
* @returns {object} Object for serialized values.
|
||||
*/
|
||||
async function serializeArrayLike(
|
||||
function serializeArrayLike(
|
||||
production,
|
||||
handleId,
|
||||
knownObject,
|
||||
|
|
@ -551,7 +543,7 @@ async function serializeArrayLike(
|
|||
setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
|
||||
|
||||
if (!knownObject && serializationOptions.maxObjectDepth !== 0) {
|
||||
serialized.value = await serializeList(
|
||||
serialized.value = serializeList(
|
||||
value,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -579,13 +571,13 @@ async function serializeArrayLike(
|
|||
* Map of internal ids.
|
||||
* @param {Realm} realm
|
||||
* The Realm from which comes the value being serialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value serialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<Array>}
|
||||
* Promise that resolves to a list of serialized values.
|
||||
* @returns {Array} List of serialized values.
|
||||
*/
|
||||
async function serializeList(
|
||||
function serializeList(
|
||||
iterable,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -604,7 +596,7 @@ async function serializeList(
|
|||
|
||||
for (const item of iterable) {
|
||||
serialized.push(
|
||||
await serialize(
|
||||
serialize(
|
||||
item,
|
||||
childSerializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -633,13 +625,13 @@ async function serializeList(
|
|||
* Map of internal ids.
|
||||
* @param {Realm} realm
|
||||
* The Realm from which comes the value being serialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value serialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<Array>}
|
||||
* Promise that resolves to a list of serialized values.
|
||||
* @returns {Array} List of serialized values.
|
||||
*/
|
||||
async function serializeMapping(
|
||||
function serializeMapping(
|
||||
iterable,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -660,7 +652,7 @@ async function serializeMapping(
|
|||
const serializedKey =
|
||||
typeof key == "string"
|
||||
? key
|
||||
: await serialize(
|
||||
: serialize(
|
||||
key,
|
||||
childSerializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -668,7 +660,7 @@ async function serializeMapping(
|
|||
realm,
|
||||
options
|
||||
);
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
item,
|
||||
childSerializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -696,13 +688,13 @@ async function serializeMapping(
|
|||
* Map of internal ids.
|
||||
* @param {Realm} realm
|
||||
* The Realm from which comes the value being serialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value serialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the serialized value.
|
||||
* @returns {object} Serialized value.
|
||||
*/
|
||||
async function serializeNode(
|
||||
function serializeNode(
|
||||
node,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -730,7 +722,7 @@ async function serializeNode(
|
|||
serialized.childNodeCount = node.childNodes.length;
|
||||
if (
|
||||
maxDomDepth !== 0 &&
|
||||
(!lazy.element.isShadowRoot(node) ||
|
||||
(!isShadowRoot(node) ||
|
||||
(includeShadowTree === IncludeShadowTreeMode.Open &&
|
||||
node.mode === "open") ||
|
||||
includeShadowTree === IncludeShadowTreeMode.All)
|
||||
|
|
@ -744,7 +736,7 @@ async function serializeNode(
|
|||
}
|
||||
for (const child of node.childNodes) {
|
||||
children.push(
|
||||
await serialize(
|
||||
serialize(
|
||||
child,
|
||||
childSerializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -767,7 +759,7 @@ async function serializeNode(
|
|||
const shadowRoot = Cu.unwaiveXrays(node).openOrClosedShadowRoot;
|
||||
serialized.shadowRoot = null;
|
||||
if (shadowRoot !== null) {
|
||||
serialized.shadowRoot = await serialize(
|
||||
serialized.shadowRoot = serialize(
|
||||
shadowRoot,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -778,7 +770,7 @@ async function serializeNode(
|
|||
}
|
||||
}
|
||||
|
||||
if (lazy.element.isShadowRoot(node)) {
|
||||
if (isShadowRoot(node)) {
|
||||
serialized.mode = node.mode;
|
||||
}
|
||||
|
||||
|
|
@ -800,13 +792,13 @@ async function serializeNode(
|
|||
* Map of internal ids.
|
||||
* @param {Realm} realm
|
||||
* The Realm from which comes the value being serialized.
|
||||
* @param {RemoteValueOptions} options
|
||||
* Extra Remote Value serialization options.
|
||||
* @param {object} options
|
||||
* @param {NodeCache} options.nodeCache
|
||||
* The cache containing DOM node references.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the serialized representation of the value.
|
||||
* @returns {object} Serialized representation of the value.
|
||||
*/
|
||||
export async function serialize(
|
||||
export function serialize(
|
||||
value,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -878,7 +870,7 @@ export async function serialize(
|
|||
setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
|
||||
|
||||
if (!knownObject && maxObjectDepth !== 0) {
|
||||
serialized.value = await serializeMapping(
|
||||
serialized.value = serializeMapping(
|
||||
value.entries(),
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -892,7 +884,7 @@ export async function serialize(
|
|||
setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
|
||||
|
||||
if (!knownObject && maxObjectDepth !== 0) {
|
||||
serialized.value = await serializeList(
|
||||
serialized.value = serializeList(
|
||||
value.values(),
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -920,7 +912,7 @@ export async function serialize(
|
|||
const serialized = buildSerialized("node", handleId);
|
||||
|
||||
// Get or create the shared id for WebDriver classic compat from the node.
|
||||
const sharedId = await getSharedIdForNode(value, realm, options);
|
||||
const sharedId = getSharedIdForNode(value, realm, options);
|
||||
if (sharedId !== null) {
|
||||
serialized.sharedId = sharedId;
|
||||
}
|
||||
|
|
@ -928,7 +920,7 @@ export async function serialize(
|
|||
setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
|
||||
|
||||
if (!knownObject) {
|
||||
serialized.value = await serializeNode(
|
||||
serialized.value = serializeNode(
|
||||
value,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
@ -949,7 +941,7 @@ export async function serialize(
|
|||
setInternalIdsIfNeeded(serializationInternalMap, serialized, value);
|
||||
|
||||
if (!knownObject && maxObjectDepth !== 0) {
|
||||
serialized.value = await serializeMapping(
|
||||
serialized.value = serializeMapping(
|
||||
Object.entries(value),
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ remote.jar:
|
|||
|
||||
# WebDriver BiDi modules
|
||||
content/webdriver-bidi/modules/ModuleRegistry.sys.mjs (modules/ModuleRegistry.sys.mjs)
|
||||
content/webdriver-bidi/modules/RootBiDiModule.sys.mjs (modules/RootBiDiModule.sys.mjs)
|
||||
content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs (modules/WindowGlobalBiDiModule.sys.mjs)
|
||||
|
||||
# WebDriver BiDi root modules
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
session: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* Base class for all Root BiDi MessageHandler modules.
|
||||
*/
|
||||
export class RootBiDiModule extends Module {
|
||||
/**
|
||||
* Adds the given node id to the list of seen nodes.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {BrowsingContext} options.browsingContext
|
||||
* Browsing context the node is part of.
|
||||
* @param {string} options.nodeId
|
||||
* Unique id of the node.
|
||||
*/
|
||||
_addNodeToSeenNodes(options = {}) {
|
||||
const { browsingContext, nodeId } = options;
|
||||
|
||||
lazy.session.addNodeToSeenNodes(
|
||||
this.messageHandler.sessionId,
|
||||
browsingContext,
|
||||
nodeId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,147 +4,15 @@
|
|||
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
deserialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
serialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
});
|
||||
|
||||
/**
|
||||
* Base class for all WindowGlobal BiDi MessageHandler modules.
|
||||
*/
|
||||
export class WindowGlobalBiDiModule extends Module {
|
||||
get #nodeCache() {
|
||||
return this.#processActor.getNodeCache();
|
||||
get nodeCache() {
|
||||
return this.processActor.getNodeCache();
|
||||
}
|
||||
|
||||
get #processActor() {
|
||||
get processActor() {
|
||||
return ChromeUtils.domProcessChild.getActor("WebDriverProcessData");
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to deserialize a local / remote value.
|
||||
*
|
||||
* @param {Realm} realm
|
||||
* The Realm in which the value is deserialized.
|
||||
* @param {object} serializedValue
|
||||
* Value of any type to be deserialized.
|
||||
* @param {RemoteValueOptions=} options
|
||||
* Extra Remote Value deserialization options.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the deserialized representation of the value.
|
||||
*/
|
||||
deserialize(realm, serializedValue, options = {}) {
|
||||
options.getNode = this.#getNode.bind(this);
|
||||
|
||||
return lazy.deserialize(realm, serializedValue, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to serialize a value as a remote value.
|
||||
*
|
||||
* @param {object} value
|
||||
* Value of any type to be serialized.
|
||||
* @param {SerializationOptions} serializationOptions
|
||||
* Options which define how ECMAScript objects should be serialized.
|
||||
* @param {OwnershipModel} ownershipType
|
||||
* The ownership model to use for this serialization.
|
||||
* @param {Realm} realm
|
||||
* The Realm from which comes the value being serialized.
|
||||
* @param {RemoteValueOptions=} options
|
||||
* Extra Remote Value serialization options.
|
||||
*
|
||||
* @returns {Promise<object>}
|
||||
* Promise that resolves to the serialized representation of the value.
|
||||
*/
|
||||
serialize(value, serializationOptions, ownershipType, realm, options = {}) {
|
||||
options.getOrCreateNodeReference = this.#getOrCreateNodeReference.bind(
|
||||
this
|
||||
);
|
||||
|
||||
return lazy.serialize(
|
||||
value,
|
||||
serializationOptions,
|
||||
ownershipType,
|
||||
new Map(),
|
||||
realm,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
/**
|
||||
* Resolve node from specified web reference identifier.
|
||||
*
|
||||
* @param {BrowsingContext} browsingContext
|
||||
* The browsing context to retrieve the node from.
|
||||
* @param {string} nodeId
|
||||
* The WebReference uuid for a DOM node.
|
||||
*
|
||||
* @returns {Node|null}
|
||||
* The DOM node that the identifier was generated for, or null if the
|
||||
* node has not been found.
|
||||
*/
|
||||
#getNode(browsingContext, nodeId) {
|
||||
const node = this.#nodeCache.getNode(browsingContext, nodeId);
|
||||
|
||||
if (node === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Bug 1819902: Instead of a browsing context check compare the origin
|
||||
const isSameBrowsingContext = nodeId => {
|
||||
const nodeDetails = this.#nodeCache.getReferenceDetails(nodeId);
|
||||
|
||||
if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) {
|
||||
// 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;
|
||||
};
|
||||
|
||||
if (!isSameBrowsingContext(nodeId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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 {BrowsingContext} browsingContext
|
||||
* The browsing context the node is part of.
|
||||
* @param {Node} node
|
||||
* The node to create or get a WebReference for.
|
||||
*
|
||||
* @returns {string}
|
||||
* The unique shared id for the node.
|
||||
*/
|
||||
async #getOrCreateNodeReference(browsingContext, node) {
|
||||
const nodeId = this.#nodeCache.getOrCreateNodeReference(node);
|
||||
|
||||
// Update the seen nodes map for WebDriver classic compatibility
|
||||
await this.messageHandler.sendRootCommand({
|
||||
moduleName: "script",
|
||||
commandName: "_addNodeToSeenNodes",
|
||||
params: {
|
||||
browsingContext,
|
||||
nodeId,
|
||||
},
|
||||
});
|
||||
|
||||
return nodeId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
||||
|
||||
import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ const WaitCondition = {
|
|||
Complete: "complete",
|
||||
};
|
||||
|
||||
class BrowsingContextModule extends RootBiDiModule {
|
||||
class BrowsingContextModule extends Module {
|
||||
#contextListener;
|
||||
#subscribedEvents;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
"chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
|
||||
});
|
||||
|
||||
class InputModule extends RootBiDiModule {
|
||||
class InputModule extends Module {
|
||||
destroy() {}
|
||||
|
||||
async performActions(options = {}) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
class LogModule extends RootBiDiModule {
|
||||
class LogModule extends Module {
|
||||
destroy() {}
|
||||
|
||||
static get supportedEvents() {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ const InitiatorType = {
|
|||
*/
|
||||
/* eslint-enable jsdoc/valid-types */
|
||||
|
||||
class NetworkModule extends RootBiDiModule {
|
||||
class NetworkModule extends Module {
|
||||
#networkListener;
|
||||
#subscribedEvents;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ const ScriptEvaluateResultType = {
|
|||
Success: "success",
|
||||
};
|
||||
|
||||
class ScriptModule extends RootBiDiModule {
|
||||
class ScriptModule extends Module {
|
||||
#preloadScriptMap;
|
||||
|
||||
constructor(messageHandler) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";
|
||||
import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";
|
||||
|
||||
const lazy = {};
|
||||
|
||||
|
|
@ -16,7 +16,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
});
|
||||
|
||||
class SessionModule extends RootBiDiModule {
|
||||
class SessionModule extends Module {
|
||||
#browsingContextIdEventMap;
|
||||
#globalEventSet;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const lazy = {};
|
|||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
|
||||
deserialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
});
|
||||
|
||||
|
|
@ -31,7 +32,6 @@ class InputModule extends WindowGlobalBiDiModule {
|
|||
}
|
||||
|
||||
await this.#deserializeActionOrigins(actions);
|
||||
|
||||
const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions);
|
||||
await actionChain.dispatch(this.#actionState, this.messageHandler.window);
|
||||
}
|
||||
|
|
@ -85,8 +85,9 @@ class InputModule extends WindowGlobalBiDiModule {
|
|||
}
|
||||
|
||||
const realm = this.messageHandler.getRealm();
|
||||
|
||||
return (await this.deserialize(realm, sharedReference)).data;
|
||||
return lazy.deserialize(realm, sharedReference, {
|
||||
nodeCache: this.nodeCache,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||
"chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs",
|
||||
isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
|
||||
OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
serialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
setDefaultSerializationOptions:
|
||||
"chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
});
|
||||
|
|
@ -103,7 +104,7 @@ class LogModule extends WindowGlobalBiDiModule {
|
|||
}
|
||||
}
|
||||
|
||||
#onConsoleAPIMessage = async (eventName, data = {}) => {
|
||||
#onConsoleAPIMessage = (eventName, data = {}) => {
|
||||
const {
|
||||
// `arguments` cannot be used as variable name in functions
|
||||
arguments: messageArguments,
|
||||
|
|
@ -137,17 +138,22 @@ class LogModule extends WindowGlobalBiDiModule {
|
|||
|
||||
// Serialize each arg as remote value.
|
||||
const defaultRealm = this.messageHandler.getRealm();
|
||||
const nodeCache = this.nodeCache;
|
||||
const serializedArgs = [];
|
||||
for (const arg of args) {
|
||||
// Note that we can pass a default realm for now since realms are only
|
||||
// involved when creating object references, which will not happen with
|
||||
// OwnershipModel.None. This will be revisited in Bug 1742589.
|
||||
serializedArgs.push(
|
||||
await this.serialize(
|
||||
lazy.serialize(
|
||||
Cu.waiveXrays(arg),
|
||||
lazy.setDefaultSerializationOptions(),
|
||||
lazy.OwnershipModel.None,
|
||||
defaultRealm
|
||||
new Map(),
|
||||
defaultRealm,
|
||||
{
|
||||
nodeCache,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/m
|
|||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
deserialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
getFramesFromStack: "chrome://remote/content/shared/Stack.sys.mjs",
|
||||
isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
|
||||
OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
serialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
setDefaultSerializationOptions:
|
||||
"chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
stringify: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
|
|
@ -49,7 +51,7 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
this.#stopObserving();
|
||||
}
|
||||
|
||||
async observe(subject, topic) {
|
||||
observe(subject, topic) {
|
||||
if (topic !== "document-element-inserted") {
|
||||
return;
|
||||
}
|
||||
|
|
@ -58,11 +60,11 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
|
||||
// Ignore events without a window and those from other tabs.
|
||||
if (window === this.messageHandler.window) {
|
||||
await this.#evaluatePreloadScripts();
|
||||
this.#evaluatePreloadScripts();
|
||||
}
|
||||
}
|
||||
|
||||
async #buildExceptionDetails(exception, stack, realm, resultOwnership) {
|
||||
#buildExceptionDetails(exception, stack, realm, resultOwnership, options) {
|
||||
exception = this.#toRawObject(exception);
|
||||
|
||||
// A stacktrace is mandatory to build exception details and a missing stack
|
||||
|
|
@ -92,11 +94,13 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
|
||||
return {
|
||||
columnNumber: stack.column - 1,
|
||||
exception: await this.serialize(
|
||||
exception: lazy.serialize(
|
||||
exception,
|
||||
lazy.setDefaultSerializationOptions(),
|
||||
resultOwnership,
|
||||
realm
|
||||
new Map(),
|
||||
realm,
|
||||
options
|
||||
),
|
||||
lineNumber: stack.line - 1,
|
||||
stackTrace: { callFrames },
|
||||
|
|
@ -109,7 +113,8 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
realm,
|
||||
awaitPromise,
|
||||
resultOwnership,
|
||||
serializationOptions
|
||||
serializationOptions,
|
||||
options
|
||||
) {
|
||||
let evaluationStatus, exception, result, stack;
|
||||
|
||||
|
|
@ -155,22 +160,25 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
case EvaluationStatus.Normal:
|
||||
return {
|
||||
evaluationStatus,
|
||||
result: await this.serialize(
|
||||
result: lazy.serialize(
|
||||
this.#toRawObject(result),
|
||||
serializationOptions,
|
||||
resultOwnership,
|
||||
realm
|
||||
new Map(),
|
||||
realm,
|
||||
options
|
||||
),
|
||||
realmId: realm.id,
|
||||
};
|
||||
case EvaluationStatus.Throw:
|
||||
return {
|
||||
evaluationStatus,
|
||||
exceptionDetails: await this.#buildExceptionDetails(
|
||||
exceptionDetails: this.#buildExceptionDetails(
|
||||
exception,
|
||||
stack,
|
||||
realm,
|
||||
resultOwnership
|
||||
resultOwnership,
|
||||
options
|
||||
),
|
||||
realmId: realm.id,
|
||||
};
|
||||
|
|
@ -188,17 +196,18 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
* @param {ChannelProperties} channelProperties
|
||||
* @param {RemoteValue} message
|
||||
*/
|
||||
async #emitScriptMessage(realm, channelProperties, message) {
|
||||
#emitScriptMessage = (realm, channelProperties, message) => {
|
||||
const {
|
||||
channel,
|
||||
ownership: ownershipType = lazy.OwnershipModel.None,
|
||||
serializationOptions,
|
||||
} = channelProperties;
|
||||
|
||||
const data = await this.serialize(
|
||||
const data = lazy.serialize(
|
||||
this.#toRawObject(message),
|
||||
lazy.setDefaultSerializationOptions(serializationOptions),
|
||||
ownershipType,
|
||||
new Map(),
|
||||
realm
|
||||
);
|
||||
|
||||
|
|
@ -207,9 +216,9 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
data,
|
||||
source: this.#getSource(realm),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async #evaluatePreloadScripts() {
|
||||
#evaluatePreloadScripts() {
|
||||
let resolveBlockerPromise;
|
||||
const blockerPromise = new Promise(resolve => {
|
||||
resolveBlockerPromise = resolve;
|
||||
|
|
@ -223,17 +232,12 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
functionDeclaration,
|
||||
sandbox,
|
||||
} = script;
|
||||
|
||||
const realm = this.messageHandler.getRealm({ sandboxName: sandbox });
|
||||
|
||||
const deserializedArguments = [];
|
||||
for (const commandArgument of commandArguments) {
|
||||
const argValue = await this.deserialize(realm, commandArgument, {
|
||||
emitScriptMessage: this.#emitScriptMessage.bind(this),
|
||||
});
|
||||
deserializedArguments.push(argValue.data);
|
||||
}
|
||||
|
||||
const deserializedArguments = commandArguments.map(arg =>
|
||||
lazy.deserialize(realm, arg, {
|
||||
emitScriptMessage: this.#emitScriptMessage,
|
||||
})
|
||||
);
|
||||
const rv = realm.executeInGlobalWithBindings(
|
||||
functionDeclaration,
|
||||
deserializedArguments
|
||||
|
|
@ -332,26 +336,25 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
} = options;
|
||||
|
||||
const realm = this.messageHandler.getRealm({ realmId, sandboxName });
|
||||
const nodeCache = this.nodeCache;
|
||||
|
||||
const deserializedArguments = [];
|
||||
if (commandArguments !== null) {
|
||||
const args = await Promise.all(
|
||||
commandArguments.map(arg =>
|
||||
this.deserialize(realm, arg, {
|
||||
emitScriptMessage: this.#emitScriptMessage.bind(this),
|
||||
const deserializedArguments =
|
||||
commandArguments !== null
|
||||
? commandArguments.map(arg =>
|
||||
lazy.deserialize(realm, arg, {
|
||||
emitScriptMessage: this.#emitScriptMessage,
|
||||
nodeCache,
|
||||
})
|
||||
)
|
||||
: [];
|
||||
|
||||
const deserializedThis =
|
||||
thisParameter !== null
|
||||
? lazy.deserialize(realm, thisParameter, {
|
||||
emitScriptMessage: this.#emitScriptMessage,
|
||||
nodeCache,
|
||||
})
|
||||
)
|
||||
);
|
||||
args.forEach(arg => deserializedArguments.push(arg.data));
|
||||
}
|
||||
|
||||
let deserializedThis = null;
|
||||
if (thisParameter !== null) {
|
||||
const thisArg = await this.deserialize(realm, thisParameter, {
|
||||
emitScriptMessage: this.#emitScriptMessage.bind(this),
|
||||
});
|
||||
deserializedThis = thisArg.data;
|
||||
}
|
||||
: null;
|
||||
|
||||
const rv = realm.executeInGlobalWithBindings(
|
||||
functionDeclaration,
|
||||
|
|
@ -364,7 +367,10 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
realm,
|
||||
awaitPromise,
|
||||
resultOwnership,
|
||||
serializationOptions
|
||||
serializationOptions,
|
||||
{
|
||||
nodeCache,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -430,7 +436,10 @@ class ScriptModule extends WindowGlobalBiDiModule {
|
|||
realm,
|
||||
awaitPromise,
|
||||
resultOwnership,
|
||||
serializationOptions
|
||||
serializationOptions,
|
||||
{
|
||||
nodeCache: this.nodeCache,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -444,14 +444,14 @@ const REMOTE_COMPLEX_VALUES = [
|
|||
},
|
||||
];
|
||||
|
||||
add_task(async function test_deserializePrimitiveTypes() {
|
||||
add_task(function test_deserializePrimitiveTypes() {
|
||||
const realm = new Realm();
|
||||
|
||||
for (const type of PRIMITIVE_TYPES) {
|
||||
const { value: expectedValue, serialized } = type;
|
||||
|
||||
info(`Checking '${serialized.type}'`);
|
||||
const value = (await deserialize(realm, serialized)).data;
|
||||
const value = deserialize(realm, serialized);
|
||||
|
||||
if (serialized.value == "NaN") {
|
||||
ok(Number.isNaN(value), `Got expected value for ${serialized}`);
|
||||
|
|
@ -465,7 +465,7 @@ add_task(async function test_deserializePrimitiveTypes() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeDateLocalValue() {
|
||||
add_task(function test_deserializeDateLocalValue() {
|
||||
const realm = new Realm();
|
||||
|
||||
const validaDateStrings = [
|
||||
|
|
@ -485,9 +485,7 @@ add_task(async function test_deserializeDateLocalValue() {
|
|||
];
|
||||
for (const dateString of validaDateStrings) {
|
||||
info(`Checking '${dateString}'`);
|
||||
const value = (
|
||||
await deserialize(realm, { type: "date", value: dateString })
|
||||
).data;
|
||||
const value = deserialize(realm, { type: "date", value: dateString });
|
||||
|
||||
Assert.equal(
|
||||
value.getTime(),
|
||||
|
|
@ -497,7 +495,7 @@ add_task(async function test_deserializeDateLocalValue() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeLocalValues() {
|
||||
add_task(function test_deserializeLocalValues() {
|
||||
const realm = new Realm();
|
||||
|
||||
for (const type of REMOTE_SIMPLE_VALUES.concat(REMOTE_COMPLEX_VALUES)) {
|
||||
|
|
@ -509,7 +507,7 @@ add_task(async function test_deserializeLocalValues() {
|
|||
}
|
||||
|
||||
info(`Checking '${serialized.type}'`);
|
||||
const value = (await deserialize(realm, serialized)).data;
|
||||
const value = deserialize(realm, serialized);
|
||||
assertLocalValue(serialized.type, value, expectedValue);
|
||||
}
|
||||
});
|
||||
|
|
@ -539,9 +537,7 @@ add_task(async function test_deserializeChannel() {
|
|||
};
|
||||
|
||||
info(`Checking 'channel'`);
|
||||
const deserializedValue = (
|
||||
await deserialize(realm, channel, deserializationOptions)
|
||||
).data;
|
||||
const deserializedValue = deserialize(realm, channel, deserializationOptions);
|
||||
Assert.equal(
|
||||
Object.prototype.toString.call(deserializedValue),
|
||||
"[object Function]",
|
||||
|
|
@ -550,7 +546,7 @@ add_task(async function test_deserializeChannel() {
|
|||
Assert.equal(deserializedValue("foo"), "foo", "Got expected result");
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeLocalValuesByHandle() {
|
||||
add_task(function test_deserializeLocalValuesByHandle() {
|
||||
// Create two realms, realm1 will be used to serialize values, while realm2
|
||||
// will be used as a reference empty realm without any object reference.
|
||||
const realm1 = new Realm();
|
||||
|
|
@ -563,7 +559,7 @@ add_task(async function test_deserializeLocalValuesByHandle() {
|
|||
|
||||
info(`Checking '${serialized.type}'`);
|
||||
// Serialize the value once to get a handle.
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
expectedValue,
|
||||
{ maxObjectDepth: 0 },
|
||||
"root",
|
||||
|
|
@ -576,62 +572,43 @@ add_task(async function test_deserializeLocalValuesByHandle() {
|
|||
const remoteReference = { handle: serializedValue.handle };
|
||||
|
||||
// Check that the remote reference can be deserialized in realm1.
|
||||
const deserializedValue = (await deserialize(realm1, remoteReference)).data;
|
||||
const deserializedValue = deserialize(realm1, remoteReference);
|
||||
assertLocalValue(serialized.type, deserializedValue, expectedValue);
|
||||
|
||||
try {
|
||||
await deserialize(realm2, remoteReference);
|
||||
ok(false, "Expected no such handle error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"NoSuchHandleError",
|
||||
"Got expected error when using the wrong realm for deserialize"
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm2, remoteReference),
|
||||
/NoSuchHandleError:/,
|
||||
`Got expected error when using the wrong realm for deserialize`
|
||||
);
|
||||
|
||||
realm1.removeObjectHandle(serializedValue.handle);
|
||||
try {
|
||||
await deserialize(realm1, remoteReference);
|
||||
ok(false, "Expected no such handle error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"NoSuchHandleError",
|
||||
"Got expected error when after deleting the object handle"
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm1, remoteReference),
|
||||
/NoSuchHandleError:/,
|
||||
`Got expected error when after deleting the object handle`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeHandleInvalidTypes() {
|
||||
add_task(function test_deserializeHandleInvalidTypes() {
|
||||
const realm = new Realm();
|
||||
|
||||
for (const invalidType of [false, 42, {}, []]) {
|
||||
info(`Checking type: '${invalidType}'`);
|
||||
|
||||
try {
|
||||
await deserialize(realm, { type: "object", handle: invalidType });
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for type ${invalidType}`
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, { type: "object", handle: invalidType }),
|
||||
/InvalidArgumentError:/,
|
||||
`Got expected error for type ${invalidType}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeSharedIdInvalidTypes() {
|
||||
add_task(function test_deserializeSharedIdInvalidTypes() {
|
||||
const nodeCache = new NodeCache();
|
||||
|
||||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
|
||||
function getNode(bc, nodeId) {
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
for (const invalidType of [false, 42, {}, []]) {
|
||||
info(`Checking type: '${invalidType}'`);
|
||||
|
||||
|
|
@ -639,20 +616,15 @@ add_task(async function test_deserializeSharedIdInvalidTypes() {
|
|||
sharedId: invalidType,
|
||||
};
|
||||
|
||||
try {
|
||||
(await deserialize(realm, serializedValue, { getNode })).data;
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for type ${invalidType}`
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, serializedValue, { nodeCache }),
|
||||
/InvalidArgumentError:/,
|
||||
`Got expected error for type ${invalidType}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeSharedIdInvalidValue() {
|
||||
add_task(function test_deserializeSharedIdInvalidValue() {
|
||||
const nodeCache = new NodeCache();
|
||||
|
||||
const serializedValue = {
|
||||
|
|
@ -661,23 +633,14 @@ add_task(async function test_deserializeSharedIdInvalidValue() {
|
|||
|
||||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
|
||||
function getNode(bc, nodeId) {
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
try {
|
||||
(await deserialize(realm, serializedValue, { getNode })).data;
|
||||
ok(false, "Expected no such node error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"NoSuchNodeError",
|
||||
"Got expected error for unknown 'sharedId'"
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, serializedValue, { nodeCache }),
|
||||
/NoSuchNodeError:/,
|
||||
"Got expected error for unknown 'sharedId'"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeSharedId() {
|
||||
add_task(function test_deserializeSharedId() {
|
||||
const nodeCache = new NodeCache();
|
||||
const domElRef = nodeCache.getOrCreateNodeReference(domEl);
|
||||
|
||||
|
|
@ -687,16 +650,12 @@ add_task(async function test_deserializeSharedId() {
|
|||
|
||||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
|
||||
function getNode(bc, nodeId) {
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
const node = (await deserialize(realm, serializedValue, { getNode })).data;
|
||||
const node = deserialize(realm, serializedValue, { nodeCache });
|
||||
|
||||
Assert.equal(node, domEl);
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeSharedIdPrecedenceOverHandle() {
|
||||
add_task(function test_deserializeSharedIdPrecedenceOverHandle() {
|
||||
const nodeCache = new NodeCache();
|
||||
const domElRef = nodeCache.getOrCreateNodeReference(domEl);
|
||||
|
||||
|
|
@ -707,16 +666,12 @@ add_task(async function test_deserializeSharedIdPrecedenceOverHandle() {
|
|||
|
||||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
|
||||
function getNode(bc, nodeId) {
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
const node = (await deserialize(realm, serializedValue, { getNode })).data;
|
||||
const node = deserialize(realm, serializedValue, { nodeCache });
|
||||
|
||||
Assert.equal(node, domEl);
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeSharedIdNoWindowRealm() {
|
||||
add_task(function test_deserializeSharedIdNoWindowRealm() {
|
||||
const nodeCache = new NodeCache();
|
||||
const domElRef = nodeCache.getOrCreateNodeReference(domEl);
|
||||
|
||||
|
|
@ -726,23 +681,30 @@ add_task(async function test_deserializeSharedIdNoWindowRealm() {
|
|||
|
||||
const realm = new Realm();
|
||||
|
||||
function getNode(bc, nodeId) {
|
||||
return nodeCache.getNode(bc, nodeId);
|
||||
}
|
||||
|
||||
try {
|
||||
(await deserialize(realm, serializedValue, { getNode })).data;
|
||||
ok(false, "Expected no such node error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"NoSuchNodeError",
|
||||
"Got expected error for a non-window realm"
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, serializedValue, { nodeCache }),
|
||||
/NoSuchNodeError/,
|
||||
`Got expected error for a non-window realm`
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_deserializePrimitiveTypesInvalidValues() {
|
||||
// Bug 1819902: Instead of a browsing context check compare the origin
|
||||
add_task(function test_deserializeSharedIdOtherBrowsingContext() {
|
||||
const nodeCache = new NodeCache();
|
||||
const domElRef = nodeCache.getOrCreateNodeReference(domEl);
|
||||
|
||||
const serializedValue = {
|
||||
sharedId: domElRef,
|
||||
};
|
||||
|
||||
const realm = new WindowRealm(iframeEl.contentWindow);
|
||||
|
||||
const node = deserialize(realm, serializedValue, { nodeCache });
|
||||
|
||||
Assert.equal(node, null);
|
||||
});
|
||||
|
||||
add_task(function test_deserializePrimitiveTypesInvalidValues() {
|
||||
const realm = new Realm();
|
||||
|
||||
const invalidValues = [
|
||||
|
|
@ -761,21 +723,16 @@ add_task(async function test_deserializePrimitiveTypesInvalidValues() {
|
|||
for (const value of values) {
|
||||
info(`Checking '${type}' with value ${value}`);
|
||||
|
||||
try {
|
||||
(await deserialize(realm, { type, value })).data;
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for type ${type} and value ${value}`
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, { type, value }),
|
||||
/InvalidArgument/,
|
||||
`Got expected error for type ${type} and value ${value}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeDateLocalValueInvalidValues() {
|
||||
add_task(function test_deserializeDateLocalValueInvalidValues() {
|
||||
const realm = new Realm();
|
||||
|
||||
const invalidaDateStrings = [
|
||||
|
|
@ -817,20 +774,15 @@ add_task(async function test_deserializeDateLocalValueInvalidValues() {
|
|||
for (const dateString of invalidaDateStrings) {
|
||||
info(`Checking '${dateString}'`);
|
||||
|
||||
try {
|
||||
(await deserialize(realm, { type: "date", value: dateString })).data;
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for date string: ${dateString}`
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, { type: "date", value: dateString }),
|
||||
/InvalidArgumentError:/,
|
||||
`Got expected error for date string: ${dateString}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeLocalValuesInvalidType() {
|
||||
add_task(function test_deserializeLocalValuesInvalidType() {
|
||||
const realm = new Realm();
|
||||
|
||||
const invalidTypes = [undefined, null, false, 42, {}];
|
||||
|
|
@ -838,36 +790,25 @@ add_task(async function test_deserializeLocalValuesInvalidType() {
|
|||
for (const invalidType of invalidTypes) {
|
||||
info(`Checking type: '${invalidType}'`);
|
||||
|
||||
try {
|
||||
(await deserialize(realm, { type: invalidType })).data;
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for type ${invalidType}`
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, { type: invalidType }),
|
||||
/InvalidArgumentError:/,
|
||||
`Got expected error for type ${invalidType}`
|
||||
);
|
||||
|
||||
try {
|
||||
(
|
||||
await deserialize(realm, {
|
||||
Assert.throws(
|
||||
() =>
|
||||
deserialize(realm, {
|
||||
type: "array",
|
||||
value: [{ type: invalidType }],
|
||||
})
|
||||
).data;
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for nested type ${invalidType}`
|
||||
);
|
||||
}
|
||||
}),
|
||||
/InvalidArgumentError:/,
|
||||
`Got expected error for nested type ${invalidType}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_deserializeLocalValuesInvalidValues() {
|
||||
add_task(function test_deserializeLocalValuesInvalidValues() {
|
||||
const realm = new Realm();
|
||||
|
||||
const invalidValues = [
|
||||
|
|
@ -970,21 +911,16 @@ add_task(async function test_deserializeLocalValuesInvalidValues() {
|
|||
for (const value of values) {
|
||||
info(`Checking '${type}' with value ${value}`);
|
||||
|
||||
try {
|
||||
(await deserialize(realm, { type, value })).data;
|
||||
ok(false, "Expected invalid argument error to be raised");
|
||||
} catch (e) {
|
||||
Assert.equal(
|
||||
e.name,
|
||||
"InvalidArgumentError",
|
||||
`Got expected error for type ${type} and value ${value}`
|
||||
);
|
||||
}
|
||||
Assert.throws(
|
||||
() => deserialize(realm, { type, value }),
|
||||
/InvalidArgumentError:/,
|
||||
`Got expected error for type ${type} and value ${value}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_serializePrimitiveTypes() {
|
||||
add_task(function test_serializePrimitiveTypes() {
|
||||
const realm = new Realm();
|
||||
|
||||
for (const type of PRIMITIVE_TYPES) {
|
||||
|
|
@ -992,7 +928,7 @@ add_task(async function test_serializePrimitiveTypes() {
|
|||
const defaultSerializationOptions = setDefaultSerializationOptions();
|
||||
|
||||
const serializationInternalMap = new Map();
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
value,
|
||||
defaultSerializationOptions,
|
||||
"none",
|
||||
|
|
@ -1005,7 +941,7 @@ add_task(async function test_serializePrimitiveTypes() {
|
|||
// For primitive values, the serialization with ownershipType=root should
|
||||
// be exactly identical to the one with ownershipType=none.
|
||||
const serializationInternalMapWithRoot = new Map();
|
||||
const serializedWithRoot = await serialize(
|
||||
const serializedWithRoot = serialize(
|
||||
value,
|
||||
defaultSerializationOptions,
|
||||
"root",
|
||||
|
|
@ -1017,7 +953,7 @@ add_task(async function test_serializePrimitiveTypes() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_serializeRemoteSimpleValues() {
|
||||
add_task(function test_serializeRemoteSimpleValues() {
|
||||
const realm = new Realm();
|
||||
|
||||
for (const type of REMOTE_SIMPLE_VALUES) {
|
||||
|
|
@ -1026,7 +962,7 @@ add_task(async function test_serializeRemoteSimpleValues() {
|
|||
|
||||
info(`Checking '${serialized.type}' with none ownershipType`);
|
||||
const serializationInternalMapWithNone = new Map();
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
value,
|
||||
defaultSerializationOptions,
|
||||
"none",
|
||||
|
|
@ -1039,7 +975,7 @@ add_task(async function test_serializeRemoteSimpleValues() {
|
|||
|
||||
info(`Checking '${serialized.type}' with root ownershipType`);
|
||||
const serializationInternalMapWithRoot = new Map();
|
||||
const serializedWithRoot = await serialize(
|
||||
const serializedWithRoot = serialize(
|
||||
value,
|
||||
defaultSerializationOptions,
|
||||
"root",
|
||||
|
|
@ -1061,7 +997,7 @@ add_task(async function test_serializeRemoteSimpleValues() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_serializeRemoteComplexValues() {
|
||||
add_task(function test_serializeRemoteComplexValues() {
|
||||
const realm = new Realm();
|
||||
|
||||
for (const type of REMOTE_COMPLEX_VALUES) {
|
||||
|
|
@ -1072,7 +1008,7 @@ add_task(async function test_serializeRemoteComplexValues() {
|
|||
|
||||
info(`Checking '${serialized.type}' with none ownershipType`);
|
||||
const serializationInternalMapWithNone = new Map();
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
value,
|
||||
serializationOptionsWithDefaults,
|
||||
"none",
|
||||
|
|
@ -1085,7 +1021,7 @@ add_task(async function test_serializeRemoteComplexValues() {
|
|||
|
||||
info(`Checking '${serialized.type}' with root ownershipType`);
|
||||
const serializationInternalMapWithRoot = new Map();
|
||||
const serializedWithRoot = await serialize(
|
||||
const serializedWithRoot = serialize(
|
||||
value,
|
||||
serializationOptionsWithDefaults,
|
||||
"root",
|
||||
|
|
@ -1107,7 +1043,7 @@ add_task(async function test_serializeRemoteComplexValues() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_serializeNodeChildren() {
|
||||
add_task(function test_serializeNodeChildren() {
|
||||
const nodeCache = new NodeCache();
|
||||
// Add the used elements to the cache so that we know the unique reference.
|
||||
const bodyElRef = nodeCache.getOrCreateNodeReference(bodyEl);
|
||||
|
|
@ -1116,11 +1052,6 @@ add_task(async function test_serializeNodeChildren() {
|
|||
|
||||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
|
||||
function getOrCreateNodeReference(bc, node) {
|
||||
Assert.equal(bc, realm.browsingContext, "Got expected browsing context");
|
||||
return nodeCache.getOrCreateNodeReference(node);
|
||||
}
|
||||
|
||||
const dataSet = [
|
||||
{
|
||||
node: bodyEl,
|
||||
|
|
@ -1275,20 +1206,20 @@ add_task(async function test_serializeNodeChildren() {
|
|||
|
||||
const serializationInternalMap = new Map();
|
||||
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
node,
|
||||
serializationOptions,
|
||||
"none",
|
||||
serializationInternalMap,
|
||||
realm,
|
||||
{ getOrCreateNodeReference }
|
||||
{ nodeCache }
|
||||
);
|
||||
|
||||
Assert.deepEqual(serializedValue, serialized, "Got expected structure");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(async function test_serializeShadowRoot() {
|
||||
add_task(function test_serializeShadowRoot() {
|
||||
const nodeCache = new NodeCache();
|
||||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
|
||||
|
|
@ -1309,11 +1240,6 @@ add_task(async function test_serializeShadowRoot() {
|
|||
insideShadowRootElement
|
||||
);
|
||||
|
||||
function getOrCreateNodeReference(bc, node) {
|
||||
Assert.equal(bc, realm.browsingContext, "Got expected browsing context");
|
||||
return nodeCache.getOrCreateNodeReference(node);
|
||||
}
|
||||
|
||||
const dataSet = [
|
||||
{
|
||||
node: customElement,
|
||||
|
|
@ -1440,13 +1366,13 @@ add_task(async function test_serializeShadowRoot() {
|
|||
|
||||
const serializationInternalMap = new Map();
|
||||
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
node,
|
||||
serializationOptions,
|
||||
"none",
|
||||
serializationInternalMap,
|
||||
realm,
|
||||
{ getOrCreateNodeReference }
|
||||
{ nodeCache }
|
||||
);
|
||||
|
||||
Assert.deepEqual(serializedValue, serialized, "Got expected structure");
|
||||
|
|
@ -1454,7 +1380,7 @@ add_task(async function test_serializeShadowRoot() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(async function test_serializeWithSerializationInternalMap() {
|
||||
add_task(function test_serializeWithSerializationInternalMap() {
|
||||
const dataSet = [
|
||||
{
|
||||
data: [1],
|
||||
|
|
@ -1497,7 +1423,7 @@ add_task(async function test_serializeWithSerializationInternalMap() {
|
|||
{ bar: data },
|
||||
];
|
||||
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
value,
|
||||
{ maxObjectDepth: 2 },
|
||||
"none",
|
||||
|
|
@ -1544,36 +1470,34 @@ add_task(async function test_serializeWithSerializationInternalMap() {
|
|||
}
|
||||
});
|
||||
|
||||
add_task(
|
||||
async function test_serializeMultipleValuesWithSerializationInternalMap() {
|
||||
const realm = new Realm();
|
||||
const serializationInternalMap = new Map();
|
||||
const obj1 = { foo: "bar" };
|
||||
const obj2 = [1, 2];
|
||||
const value = [obj1, obj2, obj1, obj2];
|
||||
add_task(function test_serializeMultipleValuesWithSerializationInternalMap() {
|
||||
const realm = new Realm();
|
||||
const serializationInternalMap = new Map();
|
||||
const obj1 = { foo: "bar" };
|
||||
const obj2 = [1, 2];
|
||||
const value = [obj1, obj2, obj1, obj2];
|
||||
|
||||
await serialize(
|
||||
value,
|
||||
{ maxObjectDepth: 2 },
|
||||
"none",
|
||||
serializationInternalMap,
|
||||
realm
|
||||
);
|
||||
serialize(
|
||||
value,
|
||||
{ maxObjectDepth: 2 },
|
||||
"none",
|
||||
serializationInternalMap,
|
||||
realm
|
||||
);
|
||||
|
||||
assertInternalIds(serializationInternalMap, 2);
|
||||
assertInternalIds(serializationInternalMap, 2);
|
||||
|
||||
const internalId1 = serializationInternalMap.get(obj1).internalId;
|
||||
const internalId2 = serializationInternalMap.get(obj2).internalId;
|
||||
const internalId1 = serializationInternalMap.get(obj1).internalId;
|
||||
const internalId2 = serializationInternalMap.get(obj2).internalId;
|
||||
|
||||
Assert.notEqual(
|
||||
internalId1,
|
||||
internalId2,
|
||||
"Internal ids for different object are also different"
|
||||
);
|
||||
}
|
||||
);
|
||||
Assert.notEqual(
|
||||
internalId1,
|
||||
internalId2,
|
||||
"Internal ids for different object are also different"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_serializeNodeSharedId() {
|
||||
add_task(function test_serializeNodeSharedId() {
|
||||
const nodeCache = new NodeCache();
|
||||
// Already add the domEl to the cache so that we know the unique reference.
|
||||
const domElRef = nodeCache.getOrCreateNodeReference(domEl);
|
||||
|
|
@ -1581,18 +1505,13 @@ add_task(async function test_serializeNodeSharedId() {
|
|||
const realm = new WindowRealm(browser.document.defaultView);
|
||||
const serializationInternalMap = new Map();
|
||||
|
||||
function getOrCreateNodeReference(bc, node) {
|
||||
Assert.equal(bc, realm.browsingContext, "Got expected browsing context");
|
||||
return nodeCache.getOrCreateNodeReference(node);
|
||||
}
|
||||
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
domEl,
|
||||
{ maxDomDepth: 0 },
|
||||
"root",
|
||||
serializationInternalMap,
|
||||
realm,
|
||||
{ getOrCreateNodeReference }
|
||||
{ nodeCache }
|
||||
);
|
||||
|
||||
Assert.equal(nodeCache.size, 1, "No additional reference added");
|
||||
|
|
@ -1600,32 +1519,27 @@ add_task(async function test_serializeNodeSharedId() {
|
|||
Assert.notEqual(serializedValue.handle, domElRef);
|
||||
});
|
||||
|
||||
add_task(async function test_serializeNodeSharedId_noWindowRealm() {
|
||||
add_task(function test_serializeNodeSharedId_noWindowRealm() {
|
||||
const nodeCache = new NodeCache();
|
||||
nodeCache.getOrCreateNodeReference(domEl);
|
||||
|
||||
const realm = new Realm();
|
||||
const serializationInternalMap = new Map();
|
||||
|
||||
function getOrCreateNodeReference(bc, node) {
|
||||
Assert.equal(bc, realm.browsingContext, "Got expected browsing context");
|
||||
return nodeCache.getOrCreateNodeReference(node);
|
||||
}
|
||||
|
||||
const serializedValue = await serialize(
|
||||
const serializedValue = serialize(
|
||||
domEl,
|
||||
{ maxDomDepth: 0 },
|
||||
"none",
|
||||
serializationInternalMap,
|
||||
realm,
|
||||
{ getOrCreateNodeReference }
|
||||
{ nodeCache }
|
||||
);
|
||||
|
||||
Assert.equal(nodeCache.size, 1, "No additional reference added");
|
||||
Assert.equal(serializedValue.sharedId, undefined);
|
||||
});
|
||||
|
||||
add_task(async function test_stringify() {
|
||||
add_task(function test_stringify() {
|
||||
const STRINGIFY_TEST_CASES = [
|
||||
[undefined, "undefined"],
|
||||
[null, "null"],
|
||||
|
|
@ -1705,8 +1619,6 @@ function assertInternalIds(serializationInternalMap, amount) {
|
|||
);
|
||||
}
|
||||
|
||||
function deserializeValue(realm, value) {}
|
||||
|
||||
function deserializeInWindowRealm(serialized) {
|
||||
return SpecialPowers.spawn(
|
||||
gBrowser.selectedBrowser,
|
||||
|
|
@ -1720,7 +1632,7 @@ function deserializeInWindowRealm(serialized) {
|
|||
);
|
||||
const realm = new WindowRealm(content);
|
||||
info(`Checking '${_serialized.type}'`);
|
||||
return (await deserialize(realm, _serialized)).data;
|
||||
return deserialize(realm, _serialized);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -297,7 +297,7 @@ class TestNavigate(BaseNavigationTestCase):
|
|||
self.marionette.navigate("about:robots")
|
||||
self.assertFalse(self.is_remote_tab)
|
||||
|
||||
with self.assertRaises(errors.StaleElementException):
|
||||
with self.assertRaises(errors.NoSuchElementException):
|
||||
elem.click()
|
||||
|
||||
def test_about_blank_for_new_docshell(self):
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
[back.py]
|
||||
expected:
|
||||
if (os == "win") and not debug and (processor == "x86_64"): [OK, TIMEOUT]
|
||||
if (os == "linux") and fission and not debug: [OK, TIMEOUT]
|
||||
if (os == "android") and not debug: [OK, TIMEOUT]
|
||||
[test_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if not fission and (os == "linux"): FAIL
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[forward.py]
|
||||
[test_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if (os == "linux") and not fission: FAIL
|
||||
if (os == "win") and not fission: FAIL
|
||||
if (os == "mac") and not fission: FAIL
|
||||
PASS
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
[navigate.py]
|
||||
[test_cross_origin[capabilities0\]]
|
||||
expected:
|
||||
if (os == "linux") and not fission: FAIL
|
||||
if (os == "win") and not fission: FAIL
|
||||
if (os == "mac") and not fission: FAIL
|
||||
PASS
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import pytest
|
||||
from webdriver.bidi import error
|
||||
from webdriver.bidi.modules.script import ContextTarget
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_reference_node_argument_different_browsing_context(
|
||||
bidi_session, get_test_page, top_context
|
||||
):
|
||||
await bidi_session.browsing_context.navigate(
|
||||
context=top_context["context"], url=get_test_page(), wait="complete"
|
||||
)
|
||||
|
||||
remote_reference = await bidi_session.script.evaluate(
|
||||
expression="""document.querySelector("#button")""",
|
||||
await_promise=False,
|
||||
target=ContextTarget(top_context["context"]),
|
||||
)
|
||||
|
||||
# Retrieve the first child browsing context
|
||||
all_contexts = await bidi_session.browsing_context.get_tree(
|
||||
root=top_context["context"]
|
||||
)
|
||||
assert len(all_contexts) == 1
|
||||
child_context = all_contexts[0]["children"][0]["context"]
|
||||
|
||||
with pytest.raises(error.NoSuchNodeException):
|
||||
await bidi_session.script.call_function(
|
||||
function_declaration="(node) => node.nodeType",
|
||||
arguments=[remote_reference],
|
||||
await_promise=False,
|
||||
target=ContextTarget(child_context),
|
||||
)
|
||||
|
|
@ -164,6 +164,6 @@ def test_cross_origin(session, url):
|
|||
|
||||
assert session.url == first_page
|
||||
|
||||
with pytest.raises(error.StaleElementReferenceException):
|
||||
with pytest.raises(error.NoSuchElementException):
|
||||
elem.click()
|
||||
elem = session.find.css("#delete", all=False)
|
||||
|
|
|
|||
|
|
@ -136,124 +136,94 @@ async def test_local_value(bidi_session, top_context, argument, expected_type):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"setup_expression, function_declaration, await_promise, expected",
|
||||
"setup_expression, function_declaration, expected",
|
||||
[
|
||||
(
|
||||
"Symbol('foo')",
|
||||
"(symbol) => symbol.toString()",
|
||||
False,
|
||||
{"type": "string", "value": "Symbol(foo)"},
|
||||
),
|
||||
(
|
||||
"[1,2]",
|
||||
"(array) => array[0]",
|
||||
False,
|
||||
{"type": "number", "value": 1}),
|
||||
("[1,2]", "(array) => array[0]", {"type": "number", "value": 1}),
|
||||
(
|
||||
"new RegExp('foo')",
|
||||
"(regexp) => regexp.source",
|
||||
False,
|
||||
{"type": "string", "value": "foo"},
|
||||
),
|
||||
(
|
||||
"new Date(1654004849000)",
|
||||
"(date) => date.toISOString()",
|
||||
False,
|
||||
{"type": "string", "value": "2022-05-31T13:47:29.000Z"},
|
||||
),
|
||||
(
|
||||
"new Map([['foo', 'bar']])",
|
||||
"(map) => map.get('foo')",
|
||||
False,
|
||||
{"type": "string", "value": "bar"},
|
||||
),
|
||||
(
|
||||
"new Set(['foo'])",
|
||||
"(set) => set.has('foo')",
|
||||
False,
|
||||
{"type": "boolean", "value": True},
|
||||
),
|
||||
(
|
||||
"{const weakMap = new WeakMap(); weakMap.set(weakMap, 'foo')}",
|
||||
"(weakMap)=> weakMap.get(weakMap)",
|
||||
False,
|
||||
{"type": "string", "value": "foo"},
|
||||
),
|
||||
(
|
||||
"{const weakSet = new WeakSet(); weakSet.add(weakSet)}",
|
||||
"(weakSet)=> weakSet.has(weakSet)",
|
||||
False,
|
||||
{"type": "boolean", "value": True},
|
||||
),
|
||||
(
|
||||
"new Error('error message')",
|
||||
"(error) => error.message",
|
||||
False,
|
||||
{"type": "string", "value": "error message"},
|
||||
),
|
||||
(
|
||||
"new SyntaxError('syntax error message')",
|
||||
"(error) => error.message",
|
||||
False,
|
||||
{"type": "string", "value": "syntax error message"},
|
||||
),
|
||||
(
|
||||
"new Promise((resolve) => resolve(3))",
|
||||
"(promise) => promise",
|
||||
True,
|
||||
{"type": "number", "value": 3},
|
||||
),
|
||||
(
|
||||
"new Promise(() => {})",
|
||||
"(promise) => promise",
|
||||
False,
|
||||
{"type": "promise"},
|
||||
),
|
||||
(
|
||||
"new Int8Array(2)",
|
||||
"(int8Array) => int8Array.length",
|
||||
False,
|
||||
{"type": "number", "value": 2},
|
||||
),
|
||||
(
|
||||
"new ArrayBuffer(8)",
|
||||
"(arrayBuffer) => arrayBuffer.byteLength",
|
||||
False,
|
||||
{"type": "number", "value": 8},
|
||||
),
|
||||
(
|
||||
"() => true",
|
||||
"(func) => func()",
|
||||
False,
|
||||
{"type": "boolean", "value": True}),
|
||||
("() => true", "(func) => func()", {"type": "boolean", "value": True}),
|
||||
(
|
||||
"(function() {return false;})",
|
||||
"(func) => func()",
|
||||
False,
|
||||
{"type": "boolean", "value": False},
|
||||
),
|
||||
(
|
||||
"window.foo = 3; window",
|
||||
"(window) => window.foo",
|
||||
False,
|
||||
{"type": "number", "value": 3},
|
||||
),
|
||||
(
|
||||
"window.url = new URL('https://example.com'); window.url",
|
||||
"(url) => url.hostname",
|
||||
False,
|
||||
{"type": "string", "value": "example.com"},
|
||||
),
|
||||
(
|
||||
"({SOME_PROPERTY:'SOME_VALUE'})",
|
||||
"(obj) => obj.SOME_PROPERTY",
|
||||
False,
|
||||
{"type": "string", "value": "SOME_VALUE"},
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_remote_reference_argument(
|
||||
bidi_session, top_context, setup_expression, function_declaration, await_promise, expected
|
||||
bidi_session, top_context, setup_expression, function_declaration, expected
|
||||
):
|
||||
remote_value_result = await bidi_session.script.evaluate(
|
||||
expression=setup_expression,
|
||||
|
|
@ -268,7 +238,7 @@ async def test_remote_reference_argument(
|
|||
result = await bidi_session.script.call_function(
|
||||
function_declaration=function_declaration,
|
||||
arguments=[{"handle": remote_value_handle}],
|
||||
await_promise=await_promise,
|
||||
await_promise=True if remote_value_result["type"] == "promise" else False,
|
||||
target=ContextTarget(top_context["context"]),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
from webdriver import error
|
||||
|
||||
from webdriver.error import NoSuchElementException
|
||||
|
||||
from tests.support.asserts import assert_success
|
||||
from tests.support.helpers import wait_for_new_handle
|
||||
|
|
@ -120,7 +121,7 @@ def test_link_from_toplevel_context_with_target(session, inline, target):
|
|||
wait = Poll(
|
||||
session,
|
||||
timeout=5,
|
||||
ignored_exceptions=error.NoSuchElementException,
|
||||
ignored_exceptions=NoSuchElementException,
|
||||
message="Expected element has not been found")
|
||||
wait.until(lambda s: s.find.css("#foo"))
|
||||
|
||||
|
|
@ -159,7 +160,7 @@ def test_link_from_nested_context_with_target(session, inline, iframe, target):
|
|||
wait = Poll(
|
||||
session,
|
||||
timeout=5,
|
||||
ignored_exceptions=error.NoSuchElementException,
|
||||
ignored_exceptions=NoSuchElementException,
|
||||
message="Expected element has not been found")
|
||||
wait.until(lambda s: s.find.css("#foo"))
|
||||
|
||||
|
|
@ -179,8 +180,6 @@ def test_link_cross_origin(session, inline, url):
|
|||
assert_success(response)
|
||||
|
||||
assert session.url == target_page
|
||||
with pytest.raises(error.StaleElementReferenceException):
|
||||
link.click()
|
||||
|
||||
session.find.css("#delete", all=False)
|
||||
|
||||
|
|
|
|||
|
|
@ -190,6 +190,6 @@ def test_cross_origin(session, url):
|
|||
|
||||
assert session.url == second_page
|
||||
|
||||
with pytest.raises(error.StaleElementReferenceException):
|
||||
with pytest.raises(error.NoSuchElementException):
|
||||
elem.click()
|
||||
elem = session.find.css("#delete", all=False)
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ def test_cross_origin(session, inline, url):
|
|||
assert_success(response)
|
||||
|
||||
assert session.url == second_page
|
||||
with pytest.raises(error.StaleElementReferenceException):
|
||||
with pytest.raises(error.NoSuchElementException):
|
||||
elem.click()
|
||||
|
||||
session.find.css("#delete", all=False)
|
||||
|
|
|
|||
Loading…
Reference in a new issue