mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-17 06:09:19 +00:00
Bug 1855023 - [bidi] Implement basic "browsingContext.locateNodes" command for CSS selector. r=webdriver-reviewers,jdescottes,whimboo
Differential Revision: https://phabricator.services.mozilla.com/D196740
This commit is contained in:
parent
5441935834
commit
3664943889
@ -23,12 +23,15 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
"chrome://remote/content/shared/NavigationManager.sys.mjs",
|
||||
NavigationListener:
|
||||
"chrome://remote/content/shared/listeners/NavigationListener.sys.mjs",
|
||||
OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||
pprint: "chrome://remote/content/shared/Format.sys.mjs",
|
||||
print: "chrome://remote/content/shared/PDF.sys.mjs",
|
||||
ProgressListener: "chrome://remote/content/shared/Navigate.sys.mjs",
|
||||
PromptListener:
|
||||
"chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
|
||||
setDefaultAndAssertSerializationOptions:
|
||||
"chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
|
||||
TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
|
||||
waitForInitialNavigationCompleted:
|
||||
"chrome://remote/content/shared/Navigate.sys.mjs",
|
||||
@ -75,6 +78,22 @@ const CreateType = {
|
||||
window: "window",
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {string} LocatorType
|
||||
*/
|
||||
|
||||
/**
|
||||
* Enum of types supported by the browsingContext.locateNodes command.
|
||||
*
|
||||
* @readonly
|
||||
* @enum {LocatorType}
|
||||
*/
|
||||
export const LocatorType = {
|
||||
css: "css",
|
||||
innerText: "innerText",
|
||||
xpath: "xpath",
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {string} OriginType
|
||||
*/
|
||||
@ -701,6 +720,175 @@ class BrowsingContextModule extends Module {
|
||||
throw new lazy.error.NoSuchAlertError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used as an argument for browsingContext.locateNodes command, as one of the available variants
|
||||
* {CssLocator}, {InnerTextLocator} or {XPathLocator}, to represent a way of how lookup of nodes
|
||||
* is going to be performed.
|
||||
*
|
||||
* @typedef Locator
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used as an argument for browsingContext.locateNodes command
|
||||
* to represent a lookup by css selector.
|
||||
*
|
||||
* @typedef CssLocator
|
||||
*
|
||||
* @property {LocatorType} [type=LocatorType.css]
|
||||
* @property {string} value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used as an argument for browsingContext.locateNodes command
|
||||
* to represent a lookup by inner text.
|
||||
*
|
||||
* @typedef InnerTextLocator
|
||||
*
|
||||
* @property {LocatorType} [type=LocatorType.innerText]
|
||||
* @property {string} value
|
||||
* @property {boolean=} ignoreCase
|
||||
* @property {("full"|"partial")=} matchType
|
||||
* @property {number=} maxDepth
|
||||
*/
|
||||
|
||||
/**
|
||||
* Used as an argument for browsingContext.locateNodes command
|
||||
* to represent a lookup by xpath.
|
||||
*
|
||||
* @typedef XPathLocator
|
||||
*
|
||||
* @property {LocatorType} [type=LocatorType.xpath]
|
||||
* @property {string} value
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a list of all nodes matching
|
||||
* the specified locator.
|
||||
*
|
||||
* @param {object} options
|
||||
* @param {string} options.context
|
||||
* Id of the browsing context.
|
||||
* @param {Locator} options.locator
|
||||
* The type of lookup which is going to be used.
|
||||
* @param {number=} options.maxNodeCount
|
||||
* The maximum amount of nodes which is going to be returned.
|
||||
* Defaults to return all the found nodes.
|
||||
* @param {OwnershipModel=} options.ownership
|
||||
* The ownership model to use for the serialization
|
||||
* of the DOM nodes. Defaults to `OwnershipModel.None`.
|
||||
* @property {string=} sandbox
|
||||
* The name of the sandbox. If the value is null or empty
|
||||
* string, the default realm will be used.
|
||||
* @property {SerializationOptions=} serializationOptions
|
||||
* An object which holds the information of how the DOM nodes
|
||||
* should be serialized.
|
||||
* @property {Array<SharedReference>=} startNodes
|
||||
* A list of references to nodes, which are used as
|
||||
* starting points for lookup.
|
||||
*
|
||||
* @throws {InvalidArgumentError}
|
||||
* Raised if an argument is of an invalid type or value.
|
||||
* @throws {InvalidSelectorError}
|
||||
* Raised if a locator value is invalid.
|
||||
* @throws {NoSuchFrameError}
|
||||
* If the browsing context cannot be found.
|
||||
* @throws {UnsupportedOperationError}
|
||||
* Raised when unsupported lookup types are used.
|
||||
*/
|
||||
async locateNodes(options = {}) {
|
||||
const {
|
||||
context: contextId,
|
||||
locator,
|
||||
maxNodeCount = null,
|
||||
ownership = lazy.OwnershipModel.None,
|
||||
sandbox = null,
|
||||
serializationOptions,
|
||||
startNodes = null,
|
||||
} = options;
|
||||
|
||||
lazy.assert.string(
|
||||
contextId,
|
||||
`Expected "context" to be a string, got ${contextId}`
|
||||
);
|
||||
|
||||
const context = this.#getBrowsingContext(contextId);
|
||||
|
||||
lazy.assert.object(
|
||||
locator,
|
||||
`Expected "locator" to be an object, got ${locator}`
|
||||
);
|
||||
|
||||
const locatorTypes = Object.values(LocatorType);
|
||||
|
||||
lazy.assert.that(
|
||||
locatorType => locatorTypes.includes(locatorType),
|
||||
`Expected "locator.type" to be one of ${locatorTypes}, got ${locator.type}`
|
||||
)(locator.type);
|
||||
|
||||
if (locator.type !== LocatorType.css) {
|
||||
throw new lazy.error.UnsupportedOperationError(
|
||||
`"locator.type" argument with value: ${locator.type} is not supported yet.`
|
||||
);
|
||||
}
|
||||
|
||||
if (maxNodeCount != null) {
|
||||
const maxNodeCountErrorMsg = `Expected "maxNodeCount" to be an integer and greater than 0, got ${maxNodeCount}`;
|
||||
lazy.assert.that(maxNodeCount => {
|
||||
lazy.assert.integer(maxNodeCount, maxNodeCountErrorMsg);
|
||||
return maxNodeCount > 0;
|
||||
}, maxNodeCountErrorMsg)(maxNodeCount);
|
||||
}
|
||||
|
||||
const ownershipTypes = Object.values(lazy.OwnershipModel);
|
||||
lazy.assert.that(
|
||||
ownership => ownershipTypes.includes(ownership),
|
||||
`Expected "ownership" to be one of ${ownershipTypes}, got ${ownership}`
|
||||
)(ownership);
|
||||
|
||||
if (sandbox != null) {
|
||||
lazy.assert.string(
|
||||
sandbox,
|
||||
`Expected "sandbox" to be a string, got ${sandbox}`
|
||||
);
|
||||
}
|
||||
|
||||
const serializationOptionsWithDefaults =
|
||||
lazy.setDefaultAndAssertSerializationOptions(serializationOptions);
|
||||
|
||||
if (startNodes != null) {
|
||||
lazy.assert.that(startNodes => {
|
||||
lazy.assert.array(
|
||||
startNodes,
|
||||
`Expected "startNodes" to be an array, got ${startNodes}`
|
||||
);
|
||||
return !!startNodes.length;
|
||||
}, `Expected "startNodes" to have at least one element, got ${startNodes}`)(
|
||||
startNodes
|
||||
);
|
||||
}
|
||||
|
||||
const result = await this.messageHandler.forwardCommand({
|
||||
moduleName: "browsingContext",
|
||||
commandName: "_locateNodes",
|
||||
destination: {
|
||||
type: lazy.WindowGlobalMessageHandler.type,
|
||||
id: context.id,
|
||||
},
|
||||
params: {
|
||||
locator,
|
||||
maxNodeCount,
|
||||
resultOwnership: ownership,
|
||||
sandbox,
|
||||
serializationOptions: serializationOptionsWithDefaults,
|
||||
startNodes,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
nodes: result.serializedNodes,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An object that holds the WebDriver Bidi navigation information.
|
||||
*
|
||||
@ -1117,7 +1305,10 @@ class BrowsingContextModule extends Module {
|
||||
|
||||
const context = this.#getBrowsingContext(contextId);
|
||||
|
||||
lazy.assert.integer(delta);
|
||||
lazy.assert.integer(
|
||||
delta,
|
||||
`Expected "delta" to be an integer, got ${delta}`
|
||||
);
|
||||
|
||||
const sessionHistory = context.sessionHistory;
|
||||
const allSteps = sessionHistory.count;
|
||||
|
@ -8,13 +8,21 @@ const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
|
||||
assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
|
||||
ClipRectangleType:
|
||||
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
|
||||
error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
|
||||
LoadListener: "chrome://remote/content/shared/listeners/LoadListener.sys.mjs",
|
||||
LocatorType:
|
||||
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
|
||||
OriginType:
|
||||
"chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
|
||||
});
|
||||
|
||||
const DOCUMENT_FRAGMENT_NODE = 11;
|
||||
const DOCUMENT_NODE = 9;
|
||||
const ELEMENT_NODE = 1;
|
||||
|
||||
class BrowsingContextModule extends WindowGlobalBiDiModule {
|
||||
#loadListener;
|
||||
#subscribedEvents;
|
||||
@ -143,6 +151,40 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Locate nodes using css selector.
|
||||
*
|
||||
* @see https://w3c.github.io/webdriver-bidi/#locate-nodes-using-css
|
||||
*/
|
||||
#locateNodesUsingCss(contextNodes, selector, maxReturnedNodeCount) {
|
||||
const returnedNodes = [];
|
||||
|
||||
for (const contextNode of contextNodes) {
|
||||
let elements;
|
||||
try {
|
||||
elements = contextNode.querySelectorAll(selector);
|
||||
} catch (e) {
|
||||
throw new lazy.error.InvalidSelectorError(
|
||||
`${e.message}: "${selector}"`
|
||||
);
|
||||
}
|
||||
|
||||
if (maxReturnedNodeCount === null) {
|
||||
returnedNodes.push(...elements);
|
||||
} else {
|
||||
for (const element of elements) {
|
||||
returnedNodes.push(element);
|
||||
|
||||
if (returnedNodes.length === maxReturnedNodeCount) {
|
||||
return returnedNodes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return returnedNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize rectangle. This ensures that the resulting rect has
|
||||
* positive width and height dimensions.
|
||||
@ -312,6 +354,69 @@ class BrowsingContextModule extends WindowGlobalBiDiModule {
|
||||
|
||||
return this.#rectangleIntersection(originRect, clipRect);
|
||||
}
|
||||
|
||||
_locateNodes(params = {}) {
|
||||
const {
|
||||
locator,
|
||||
maxNodeCount,
|
||||
resultOwnership,
|
||||
sandbox,
|
||||
serializationOptions,
|
||||
startNodes,
|
||||
} = params;
|
||||
|
||||
const realm = this.messageHandler.getRealm({ sandboxName: sandbox });
|
||||
|
||||
const contextNodes = [];
|
||||
if (startNodes === null) {
|
||||
contextNodes.push(this.messageHandler.window.document.documentElement);
|
||||
} else {
|
||||
for (const serializedStartNode of startNodes) {
|
||||
const startNode = this.deserialize(realm, serializedStartNode);
|
||||
lazy.assert.that(
|
||||
startNode =>
|
||||
Node.isInstance(startNode) &&
|
||||
[DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE].includes(
|
||||
startNode.nodeType
|
||||
),
|
||||
`Expected an item of "startNodes" to be an Element, got ${startNode}`
|
||||
)(startNode);
|
||||
|
||||
contextNodes.push(startNode);
|
||||
}
|
||||
}
|
||||
|
||||
let returnedNodes;
|
||||
switch (locator.type) {
|
||||
case lazy.LocatorType.css: {
|
||||
returnedNodes = this.#locateNodesUsingCss(
|
||||
contextNodes,
|
||||
locator.value,
|
||||
maxNodeCount
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const serializedNodes = [];
|
||||
const seenNodeIds = new Map();
|
||||
for (const returnedNode of returnedNodes) {
|
||||
serializedNodes.push(
|
||||
this.serialize(
|
||||
returnedNode,
|
||||
serializationOptions,
|
||||
resultOwnership,
|
||||
realm,
|
||||
{ seenNodeIds }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
serializedNodes,
|
||||
_extraData: { seenNodeIds },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const browsingContext = BrowsingContextModule;
|
||||
|
Loading…
x
Reference in New Issue
Block a user