[WebDriver] Implement XPath Locator Strategy (#37783)

1. Implement XPath Locator Strategy
2. Use it for "Find Element(s)", "Find Element(s) from Element", "Find
Element(s) from Shadow Root"

Testing: `tests\wpt\tests\webdriver\tests\classic\find_element*\find.py`

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
This commit is contained in:
Euclid Ye 2025-07-01 01:20:52 +08:00 committed by GitHub
parent f682f9d6f5
commit d781d1b1cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 190 additions and 51 deletions

View file

@ -43,6 +43,9 @@ use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelec
use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XMLSerializerBinding::XMLSerializerMethods;
use crate::dom::bindings::codegen::Bindings::XPathResultBinding::{
XPathResultConstants, XPathResultMethods,
};
use crate::dom::bindings::conversions::{
ConversionBehavior, ConversionResult, FromJSValConvertible, StringificationBehavior,
get_property, get_property_jsval, jsid_to_string, jsstring_to_str, root_from_object,
@ -759,6 +762,87 @@ pub(crate) fn handle_find_elements_tag_name(
}
}
/// <https://w3c.github.io/webdriver/#xpath>
fn find_elements_xpath_strategy(
document: &Document,
start_node: &Node,
selector: String,
pipeline: PipelineId,
can_gc: CanGc,
) -> Result<Vec<String>, ErrorStatus> {
// Step 1. Let evaluateResult be the result of calling evaluate,
// with arguments selector, start node, null, ORDERED_NODE_SNAPSHOT_TYPE, and null.
// A snapshot is used to promote operation atomicity.
let evaluate_result = match document.Evaluate(
DOMString::from(selector),
start_node,
None,
XPathResultConstants::ORDERED_NODE_SNAPSHOT_TYPE,
None,
can_gc,
) {
Ok(res) => res,
Err(_) => return Err(ErrorStatus::InvalidSelector),
};
// Step 2. Let index be 0. (Handled altogether in Step 5.)
// Step 3: Let length be the result of getting the property "snapshotLength"
// from evaluateResult.
let length = match evaluate_result.GetSnapshotLength() {
Ok(len) => len,
Err(_) => return Err(ErrorStatus::InvalidSelector),
};
// Step 4: Prepare result vector
let mut result = Vec::new();
// Step 5: Repeat, while index is less than length:
for index in 0..length {
// Step 5.1. Let node be the result of calling snapshotItem with
// evaluateResult as this and index as the argument.
let node = match evaluate_result.SnapshotItem(index) {
Ok(node) => node.expect(
"Node should always exist as ORDERED_NODE_SNAPSHOT_TYPE \
gives static result and we verified the length!",
),
Err(_) => return Err(ErrorStatus::InvalidSelector),
};
// Step 5.2. If node is not an element return an error with error code invalid selector.
if !node.is::<Element>() {
return Err(ErrorStatus::InvalidSelector);
}
// Step 5.3. Append node to result.
result.push(node.unique_id(pipeline));
}
// Step 6. Return success with data result.
Ok(result)
}
pub(crate) fn handle_find_elements_xpath_selector(
documents: &DocumentCollection,
pipeline: PipelineId,
selector: String,
reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
can_gc: CanGc,
) {
match retrieve_document_and_check_root_existence(documents, pipeline) {
Ok(document) => reply
.send(find_elements_xpath_strategy(
&document,
document.upcast::<Node>(),
selector,
pipeline,
can_gc,
))
.unwrap(),
Err(error) => reply.send(Err(error)).unwrap(),
}
}
pub(crate) fn handle_find_element_elements_css_selector(
documents: &DocumentCollection,
pipeline: PipelineId,
@ -823,6 +907,31 @@ pub(crate) fn handle_find_element_elements_tag_name(
.unwrap();
}
pub(crate) fn handle_find_element_elements_xpath_selector(
documents: &DocumentCollection,
pipeline: PipelineId,
element_id: String,
selector: String,
reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
can_gc: CanGc,
) {
reply
.send(
get_known_element(documents, pipeline, element_id).and_then(|element| {
find_elements_xpath_strategy(
&documents
.find_document(pipeline)
.expect("Document existence guaranteed by `get_known_element`"),
element.upcast::<Node>(),
selector,
pipeline,
can_gc,
)
}),
)
.unwrap();
}
/// <https://w3c.github.io/webdriver/#find-elements-from-shadow-root>
pub(crate) fn handle_find_shadow_elements_css_selector(
documents: &DocumentCollection,
@ -902,6 +1011,31 @@ pub(crate) fn handle_find_shadow_elements_tag_name(
.unwrap();
}
pub(crate) fn handle_find_shadow_elements_xpath_selector(
documents: &DocumentCollection,
pipeline: PipelineId,
shadow_root_id: String,
selector: String,
reply: IpcSender<Result<Vec<String>, ErrorStatus>>,
can_gc: CanGc,
) {
reply
.send(
get_known_shadow_root(documents, pipeline, shadow_root_id).and_then(|shadow_root| {
find_elements_xpath_strategy(
&documents
.find_document(pipeline)
.expect("Document existence guaranteed by `get_known_shadow_root`"),
shadow_root.upcast::<Node>(),
selector,
pipeline,
can_gc,
)
}),
)
.unwrap();
}
/// <https://www.w3.org/TR/webdriver2/#dfn-get-element-shadow-root>
pub(crate) fn handle_get_element_shadow_root(
documents: &DocumentCollection,