webdriver: Improve "element click" by using container + Upgrade outdated test (#38436)

- According to
[spec](https://w3c.github.io/webdriver/#ref-for-dfn-in-view-3), we
should use container instead of element itself to determine "in-view".
- Updated `test_element_intercepted_no_pointer_events` in
`element_click/interactability.py` to expect "element not interactable".
This was outdated with spec as original test was written 7 years ago
https://github.com/web-platform-tests/wpt/pull/11453.


Testing: new passing cases for `<option>`, `<select>`.

---------

Signed-off-by: Euclid Ye <euclid.ye@huawei.com>
This commit is contained in:
Euclid Ye 2025-08-04 16:12:50 +08:00 committed by GitHub
parent 79a45c7da3
commit c59ee57b5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 32 additions and 46 deletions

View file

@ -1786,7 +1786,7 @@ pub(crate) fn handle_element_clear(
.unwrap();
}
fn get_option_parent(node: &Node) -> Option<DomRoot<Node>> {
fn get_option_parent(node: &Node) -> Option<DomRoot<Element>> {
// Get parent for `<option>` or `<optiongrp>` based on container spec:
// > 1. Let datalist parent be the first datalist element reached by traversing the tree
// > in reverse order from element, or undefined if the root of the tree is reached.
@ -1801,18 +1801,19 @@ fn get_option_parent(node: &Node) -> Option<DomRoot<Node>> {
node.preceding_nodes(&root_node)
.find(|preceding| preceding.is::<HTMLSelectElement>())
})
.map(|result_node| DomRoot::downcast::<Element>(result_node).unwrap())
}
// https://w3c.github.io/webdriver/#dfn-container
fn get_container(node: &Node) -> Option<DomRoot<Node>> {
if node.is::<HTMLOptionElement>() {
return get_option_parent(node);
/// <https://w3c.github.io/webdriver/#dfn-container>
fn get_container(element: &Element) -> Option<DomRoot<Element>> {
if element.is::<HTMLOptionElement>() {
return get_option_parent(element.upcast::<Node>());
}
if node.is::<HTMLOptGroupElement>() {
let option_parent = get_option_parent(node);
return option_parent.or_else(|| Some(DomRoot::from_ref(node)));
if element.is::<HTMLOptGroupElement>() {
return get_option_parent(element.upcast::<Node>())
.or_else(|| Some(DomRoot::from_ref(element)));
}
Some(DomRoot::from_ref(node))
Some(DomRoot::from_ref(element))
}
// https://w3c.github.io/webdriver/#element-click
@ -1834,7 +1835,7 @@ pub(crate) fn handle_element_click(
}
}
let Some(container) = get_container(element.upcast::<Node>()) else {
let Some(container) = get_container(&element) else {
return Err(ErrorStatus::UnknownError);
};
@ -1843,17 +1844,22 @@ pub(crate) fn handle_element_click(
// Step 6. If element's container is still not in view
// return error with error code element not interactable.
let document = documents
.find_document(pipeline)
.expect("Document existence guaranteed by `get_known_element`");
if !is_element_in_view(&element, &document, can_gc) {
let paint_tree = get_element_pointer_interactable_paint_tree(
&container,
&documents
.find_document(pipeline)
.expect("Document existence guaranteed by `get_known_element`"),
can_gc,
);
if !is_element_in_view(&container, &paint_tree, can_gc) {
return Err(ErrorStatus::ElementNotInteractable);
}
// Step 7
// TODO: return error if obscured
// Step 8
// Step 8 for <option> element.
match element.downcast::<HTMLOptionElement>() {
Some(option_element) => {
// Steps 8.2 - 8.4
@ -1906,20 +1912,21 @@ pub(crate) fn handle_element_click(
}
/// <https://w3c.github.io/webdriver/#dfn-in-view>
fn is_element_in_view(element: &Element, document: &Document, can_gc: CanGc) -> bool {
fn is_element_in_view(element: &Element, paint_tree: &[DomRoot<Element>], can_gc: CanGc) -> bool {
// An element is in view if it is a member of its own pointer-interactable paint tree,
// given the pretense that its pointer events are not disabled.
if !paint_tree.contains(&DomRoot::from_ref(element)) {
return false;
}
use style::computed_values::pointer_events::T as PointerEvents;
// https://w3c.github.io/webdriver/#dfn-pointer-events-are-not-disabled
// An element is said to have pointer events disabled
// if the resolved value of its "pointer-events" style property is "none".
let pointer_events_enabled = element
let pointer_events_not_disabled = element
.style(can_gc)
.is_none_or(|style| style.get_inherited_ui().pointer_events != PointerEvents::None);
// An element is in view if it is a member of its own pointer-interactable paint tree,
// given the pretense that its pointer events are not disabled.
pointer_events_enabled &&
get_element_pointer_interactable_paint_tree(element, document, can_gc)
.contains(&DomRoot::from_ref(element))
pointer_events_not_disabled
}
/// <https://w3c.github.io/webdriver/#dfn-pointer-interactable-paint-tree>

View file

@ -943609,7 +943609,7 @@
]
],
"interactability.py": [
"65f8a9015eeaa6a450e603d1ca6e1914516eb506",
"aefaa1fd8e8aaddc6eaf336b5f1d8d820665b907",
[
null,
{}

View file

@ -1,6 +1,3 @@
[interactability.py]
[test_element_intercepted]
expected: FAIL
[test_element_intercepted_no_pointer_events]
expected: FAIL

View file

@ -13,21 +13,3 @@
[test_out_of_view_dropdown]
expected: FAIL
[test_click_multiple_option]
expected: FAIL
[test_click_preselected_multiple_option]
expected: FAIL
[test_click_multiple_does_not_deselect_others]
expected: FAIL
[test_click_selected_multiple_option]
expected: FAIL
[test_out_of_view_multiple]
expected: FAIL
[test_option_disabled]
expected: FAIL

View file

@ -115,11 +115,11 @@ def test_element_intercepted(session, inline):
assert_error(response, "element click intercepted")
def test_element_intercepted_no_pointer_events(session, inline):
def test_element_not_interactable_pointer_events_none(session, inline):
session.url = inline("""<input type=button value=Roger style="pointer-events: none">""")
element = session.find.css("input", all=False)
response = element_click(session, element)
assert_error(response, "element click intercepted")
assert_error(response, "element not interactable")
def test_element_not_visible_overflow_hidden(session, inline):