script::webdriver_handlers: Replace find_node_by_unique_id with get_known_element (#37502)

Implement previously missing step 3 of [get a known
element](https://w3c.github.io/webdriver/#dfn-get-a-known-element): "If
node is not null and node does not implement Element, return error with
error code no such element"

Testing: All webdriver conformance test

---------

Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
This commit is contained in:
Euclid Ye 2025-06-17 23:23:59 +08:00 committed by GitHub
parent 6159a11905
commit f22c273ea9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -72,17 +72,24 @@ use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
use crate::script_thread::ScriptThread; use crate::script_thread::ScriptThread;
/// <https://w3c.github.io/webdriver/#dfn-get-a-known-element> /// <https://w3c.github.io/webdriver/#dfn-get-a-known-element>
fn find_node_by_unique_id( fn get_known_element(
documents: &DocumentCollection, documents: &DocumentCollection,
pipeline: PipelineId, pipeline: PipelineId,
node_id: String, node_id: String,
) -> Result<DomRoot<Node>, ErrorStatus> { ) -> Result<DomRoot<Element>, ErrorStatus> {
let doc = documents let doc = documents
.find_document(pipeline) .find_document(pipeline)
.expect("webdriver_handlers::Document should exists"); .expect("webdriver_handlers::Document should exists");
find_node_by_unique_id_in_document(&doc, node_id) // Step 3. If node is not null and node does not implement Element
// return error with error code no such element.
find_node_by_unique_id_in_document(&doc, node_id).and_then(|node| {
node.downcast::<Element>()
.map(DomRoot::from_ref)
.ok_or(ErrorStatus::NoSuchElement)
})
} }
// This is also used by `dom/window.rs`
pub(crate) fn find_node_by_unique_id_in_document( pub(crate) fn find_node_by_unique_id_in_document(
document: &Document, document: &Document,
node_id: String, node_id: String,
@ -526,8 +533,9 @@ pub(crate) fn handle_get_browsing_context_id(
Err(ErrorStatus::UnsupportedOperation) Err(ErrorStatus::UnsupportedOperation)
}, },
WebDriverFrameId::Element(element_id) => { WebDriverFrameId::Element(element_id) => {
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
node.downcast::<HTMLIFrameElement>() element
.downcast::<HTMLIFrameElement>()
.and_then(|element| element.browsing_context_id()) .and_then(|element| element.browsing_context_id())
.ok_or(ErrorStatus::NoSuchFrame) .ok_or(ErrorStatus::NoSuchFrame)
}) })
@ -588,9 +596,8 @@ pub(crate) fn handle_get_element_in_view_center_point(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).map(|node| { get_known_element(documents, pipeline, element_id).map(|element| {
get_element_in_view_center_point(node.downcast::<Element>().unwrap(), can_gc) get_element_in_view_center_point(&element, can_gc).map(|point| (point.x, point.y))
.map(|point| (point.x, point.y))
}), }),
) )
.unwrap(); .unwrap();
@ -627,7 +634,7 @@ pub(crate) fn handle_find_element_css(
document document
.QuerySelector(DOMString::from(selector)) .QuerySelector(DOMString::from(selector))
.map_err(|_| ErrorStatus::InvalidSelector) .map_err(|_| ErrorStatus::InvalidSelector)
.map(|node| node.map(|x| x.upcast::<Node>().unique_id(pipeline))), .map(|element| element.map(|x| x.upcast::<Node>().unique_id(pipeline))),
) )
.unwrap(), .unwrap(),
Err(error) => reply.send(Err(error)).unwrap(), Err(error) => reply.send(Err(error)).unwrap(),
@ -668,7 +675,7 @@ pub(crate) fn handle_find_element_tag_name(
.GetElementsByTagName(DOMString::from(selector), can_gc) .GetElementsByTagName(DOMString::from(selector), can_gc)
.elements_iter() .elements_iter()
.next() .next()
.map(|node| node.upcast::<Node>().unique_id(pipeline)))) .map(|element| element.upcast::<Node>().unique_id(pipeline))))
.unwrap(), .unwrap(),
Err(error) => reply.send(Err(error)).unwrap(), Err(error) => reply.send(Err(error)).unwrap(),
} }
@ -747,10 +754,12 @@ pub(crate) fn handle_find_element_element_css(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
node.query_selector(DOMString::from(selector)) element
.upcast::<Node>()
.query_selector(DOMString::from(selector))
.map_err(|_| ErrorStatus::InvalidSelector) .map_err(|_| ErrorStatus::InvalidSelector)
.map(|node| node.map(|x| x.upcast::<Node>().unique_id(pipeline))) .map(|element| element.map(|x| x.upcast::<Node>().unique_id(pipeline)))
}), }),
) )
.unwrap(); .unwrap();
@ -767,8 +776,9 @@ pub(crate) fn handle_find_element_element_link_text(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id) get_known_element(documents, pipeline, element_id).and_then(|element| {
.and_then(|node| first_matching_link(&node, selector.clone(), partial, can_gc)), first_matching_link(element.upcast::<Node>(), selector.clone(), partial, can_gc)
}),
) )
.unwrap(); .unwrap();
} }
@ -783,15 +793,12 @@ pub(crate) fn handle_find_element_element_tag_name(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| match node get_known_element(documents, pipeline, element_id).map(|element| {
.downcast::<Element>( element
) {
Some(element) => Ok(element
.GetElementsByTagName(DOMString::from(selector), can_gc) .GetElementsByTagName(DOMString::from(selector), can_gc)
.elements_iter() .elements_iter()
.next() .next()
.map(|x| x.upcast::<Node>().unique_id(pipeline))), .map(|x| x.upcast::<Node>().unique_id(pipeline))
None => Err(ErrorStatus::UnknownError),
}), }),
) )
.unwrap(); .unwrap();
@ -806,8 +813,10 @@ pub(crate) fn handle_find_element_elements_css(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
node.query_selector_all(DOMString::from(selector)) element
.upcast::<Node>()
.query_selector_all(DOMString::from(selector))
.map_err(|_| ErrorStatus::InvalidSelector) .map_err(|_| ErrorStatus::InvalidSelector)
.map(|nodes| { .map(|nodes| {
nodes nodes
@ -831,8 +840,9 @@ pub(crate) fn handle_find_element_elements_link_text(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id) get_known_element(documents, pipeline, element_id).and_then(|element| {
.and_then(|node| all_matching_links(&node, selector.clone(), partial, can_gc)), all_matching_links(element.upcast::<Node>(), selector.clone(), partial, can_gc)
}),
) )
.unwrap(); .unwrap();
} }
@ -847,15 +857,12 @@ pub(crate) fn handle_find_element_elements_tag_name(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| match node get_known_element(documents, pipeline, element_id).map(|element| {
.downcast::<Element>( element
) {
Some(element) => Ok(element
.GetElementsByTagName(DOMString::from(selector), can_gc) .GetElementsByTagName(DOMString::from(selector), can_gc)
.elements_iter() .elements_iter()
.map(|x| x.upcast::<Node>().unique_id(pipeline)) .map(|x| x.upcast::<Node>().unique_id(pipeline))
.collect::<Vec<String>>()), .collect::<Vec<String>>()
None => Err(ErrorStatus::UnknownError),
}), }),
) )
.unwrap(); .unwrap();
@ -870,13 +877,10 @@ pub(crate) fn handle_get_element_shadow_root(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| match node get_known_element(documents, pipeline, element_id).map(|element| {
.downcast::<Element>( element
) {
Some(element) => Ok(element
.GetShadowRoot() .GetShadowRoot()
.map(|x| x.upcast::<Node>().unique_id(pipeline))), .map(|x| x.upcast::<Node>().unique_id(pipeline))
None => Err(ErrorStatus::NoSuchElement),
}), }),
) )
.unwrap(); .unwrap();
@ -893,16 +897,16 @@ pub(crate) fn handle_will_send_keys(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
// Step 6: Let file be true if element is input element // Step 6: Let file be true if element is input element
// in the file upload state, or false otherwise // in the file upload state, or false otherwise
let file_input = node let file_input = element
.downcast::<HTMLInputElement>() .downcast::<HTMLInputElement>()
.filter(|&input_element| input_element.input_type() == InputType::File); .filter(|&input_element| input_element.input_type() == InputType::File);
// Step 7: If file is false or the session's strict file interactability // Step 7: If file is false or the session's strict file interactability
if file_input.is_none() || strict_file_interactability { if file_input.is_none() || strict_file_interactability {
match node.downcast::<HTMLElement>() { match element.downcast::<HTMLElement>() {
Some(element) => { Some(element) => {
// Need a way to find if this actually succeeded // Need a way to find if this actually succeeded
element.Focus(can_gc); element.Focus(can_gc);
@ -970,12 +974,8 @@ pub(crate) fn handle_get_computed_role(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, node_id).and_then(|node| { get_known_element(documents, pipeline, node_id)
match node.downcast::<Element>() { .map(|element| element.GetRole().map(String::from)),
Some(element) => Ok(element.GetRole().map(String::from)),
None => Err(ErrorStatus::UnknownError),
}
}),
) )
.unwrap(); .unwrap();
} }
@ -1181,9 +1181,9 @@ pub(crate) fn handle_get_rect(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
// https://w3c.github.io/webdriver/webdriver-spec.html#dfn-calculate-the-absolute-position // https://w3c.github.io/webdriver/webdriver-spec.html#dfn-calculate-the-absolute-position
match node.downcast::<HTMLElement>() { match element.downcast::<HTMLElement>() {
Some(html_element) => { Some(html_element) => {
// Step 1 // Step 1
let mut x = 0; let mut x = 0;
@ -1227,17 +1227,12 @@ pub(crate) fn handle_get_bounding_client_rect(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| match node get_known_element(documents, pipeline, element_id).map(|element| {
.downcast::<Element>( let rect = element.GetBoundingClientRect(can_gc);
) { Rect::new(
Some(element) => { Point2D::new(rect.X() as f32, rect.Y() as f32),
let rect = element.GetBoundingClientRect(can_gc); Size2D::new(rect.Width() as f32, rect.Height() as f32),
Ok(Rect::new( )
Point2D::new(rect.X() as f32, rect.Y() as f32),
Size2D::new(rect.Width() as f32, rect.Height() as f32),
))
},
None => Err(ErrorStatus::UnknownError),
}), }),
) )
.unwrap(); .unwrap();
@ -1253,10 +1248,16 @@ pub(crate) fn handle_get_text(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, node_id).map(|node| { get_known_element(documents, pipeline, node_id).map(|element| {
node.downcast::<HTMLElement>() element
.map(|element| element.InnerText(can_gc).to_string()) .downcast::<HTMLElement>()
.unwrap_or_else(|| node.GetTextContent().map_or("".to_owned(), String::from)) .map(|htmlelement| htmlelement.InnerText(can_gc).to_string())
.unwrap_or_else(|| {
element
.upcast::<Node>()
.GetTextContent()
.map_or("".to_owned(), String::from)
})
}), }),
) )
.unwrap(); .unwrap();
@ -1270,8 +1271,8 @@ pub(crate) fn handle_get_name(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, node_id) get_known_element(documents, pipeline, node_id)
.map(|node| String::from(node.downcast::<Element>().unwrap().TagName())), .map(|element| String::from(element.TagName())),
) )
.unwrap(); .unwrap();
} }
@ -1285,9 +1286,8 @@ pub(crate) fn handle_get_attribute(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, node_id).map(|node| { get_known_element(documents, pipeline, node_id).map(|element| {
node.downcast::<Element>() element
.unwrap()
.GetAttribute(DOMString::from(name)) .GetAttribute(DOMString::from(name))
.map(String::from) .map(String::from)
}), }),
@ -1306,7 +1306,7 @@ pub(crate) fn handle_get_property(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, node_id).map(|node| { get_known_element(documents, pipeline, node_id).map(|element| {
let document = documents.find_document(pipeline).unwrap(); let document = documents.find_document(pipeline).unwrap();
let realm = enter_realm(&*document); let realm = enter_realm(&*document);
let cx = document.window().get_cx(); let cx = document.window().get_cx();
@ -1315,7 +1315,7 @@ pub(crate) fn handle_get_property(
match unsafe { match unsafe {
get_property_jsval( get_property_jsval(
*cx, *cx,
node.reflector().get_jsobject(), element.reflector().get_jsobject(),
&name, &name,
property.handle_mut(), property.handle_mut(),
) )
@ -1323,7 +1323,7 @@ pub(crate) fn handle_get_property(
Ok(_) => { Ok(_) => {
match jsval_to_webdriver( match jsval_to_webdriver(
cx, cx,
&node.global(), &element.global(),
property.handle(), property.handle(),
InRealm::entered(&realm), InRealm::entered(&realm),
can_gc, can_gc,
@ -1333,7 +1333,7 @@ pub(crate) fn handle_get_property(
} }
}, },
Err(error) => { Err(error) => {
throw_dom_exception(cx, &node.global(), error, can_gc); throw_dom_exception(cx, &element.global(), error, can_gc);
WebDriverJSValue::Undefined WebDriverJSValue::Undefined
}, },
} }
@ -1352,12 +1352,11 @@ pub(crate) fn handle_get_css(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, node_id).map(|node| { get_known_element(documents, pipeline, node_id).map(|element| {
let window = node.owner_window(); let window = element.owner_window();
let element = node.downcast::<Element>().unwrap();
String::from( String::from(
window window
.GetComputedStyle(element, None) .GetComputedStyle(&element, None)
.GetPropertyValue(DOMString::from(name), can_gc), .GetPropertyValue(DOMString::from(name), can_gc),
) )
}), }),
@ -1422,15 +1421,15 @@ pub(crate) fn handle_element_click(
reply reply
.send( .send(
// Step 3 // Step 3
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
// Step 4 // Step 4
if let Some(input_element) = node.downcast::<HTMLInputElement>() { if let Some(input_element) = element.downcast::<HTMLInputElement>() {
if input_element.input_type() == InputType::File { if input_element.input_type() == InputType::File {
return Err(ErrorStatus::InvalidArgument); return Err(ErrorStatus::InvalidArgument);
} }
} }
let Some(container) = get_container(&node) else { let Some(container) = get_container(element.upcast::<Node>()) else {
return Err(ErrorStatus::UnknownError); return Err(ErrorStatus::UnknownError);
}; };
@ -1444,7 +1443,7 @@ pub(crate) fn handle_element_click(
// TODO: return error if obscured // TODO: return error if obscured
// Step 8 // Step 8
match node.downcast::<HTMLOptionElement>() { match element.downcast::<HTMLOptionElement>() {
Some(option_element) => { Some(option_element) => {
// Steps 8.2 - 8.4 // Steps 8.2 - 8.4
let event_target = container.upcast::<EventTarget>(); let event_target = container.upcast::<EventTarget>();
@ -1488,7 +1487,7 @@ pub(crate) fn handle_element_click(
Ok(None) Ok(None)
}, },
None => Ok(Some(node.unique_id(pipeline))), None => Ok(Some(element.upcast::<Node>().unique_id(pipeline))),
} }
}), }),
) )
@ -1503,12 +1502,8 @@ pub(crate) fn handle_is_enabled(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| match node get_known_element(documents, pipeline, element_id)
.downcast::<Element>( .map(|element| element.enabled_state()),
) {
Some(element) => Ok(element.enabled_state()),
None => Err(ErrorStatus::UnknownError),
}),
) )
.unwrap(); .unwrap();
} }
@ -1521,12 +1516,12 @@ pub(crate) fn handle_is_selected(
) { ) {
reply reply
.send( .send(
find_node_by_unique_id(documents, pipeline, element_id).and_then(|node| { get_known_element(documents, pipeline, element_id).and_then(|element| {
if let Some(input_element) = node.downcast::<HTMLInputElement>() { if let Some(input_element) = element.downcast::<HTMLInputElement>() {
Ok(input_element.Checked()) Ok(input_element.Checked())
} else if let Some(option_element) = node.downcast::<HTMLOptionElement>() { } else if let Some(option_element) = element.downcast::<HTMLOptionElement>() {
Ok(option_element.Selected()) Ok(option_element.Selected())
} else if node.is::<HTMLElement>() { } else if element.is::<HTMLElement>() {
Ok(false) // regular elements are not selectable Ok(false) // regular elements are not selectable
} else { } else {
Err(ErrorStatus::UnknownError) Err(ErrorStatus::UnknownError)