Implement webdriver element in view (#38329)

Implement step 6 of `ElementClick`:
https://w3c.github.io/webdriver/#dfn-element-click

Tests:
`tests/wpt/tests/webdriver/tests/classic/element_click/interactability.py`

cc: @xiaochengh

Signed-off-by: batu_hoang <hoang.binh.trong@huawei.com>
This commit is contained in:
batu_hoang 2025-07-31 12:27:02 +08:00 committed by GitHub
parent 8008d5aa85
commit 8e27fd48cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 52 additions and 29 deletions

View file

@ -28,6 +28,7 @@ use net_traits::CoreResourceMsg::{
use net_traits::IpcSend;
use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
use script_bindings::conversions::is_array_like;
use script_bindings::num::Finite;
use servo_url::ServoUrl;
use webdriver::common::{WebElement, WebFrame, WebWindow};
use webdriver::error::ErrorStatus;
@ -1829,10 +1830,16 @@ pub(crate) fn handle_element_click(
};
// Step 5
// TODO: scroll into view
// TODO: scroll into view is not implemented in Servo
// Step 6
// TODO: return error if still not in view
// 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) {
return Err(ErrorStatus::ElementNotInteractable);
}
// Step 7
// TODO: return error if obscured
@ -1889,6 +1896,40 @@ pub(crate) fn handle_element_click(
.unwrap();
}
/// <https://w3c.github.io/webdriver/#dfn-in-view>
fn is_element_in_view(element: &Element, document: &Document, can_gc: CanGc) -> bool {
element.enabled_state() &&
get_element_pointer_interactable_paint_tree(element, document, can_gc)
.contains(&DomRoot::from_ref(element))
}
/// <https://w3c.github.io/webdriver/#dfn-pointer-interactable-paint-tree>
fn get_element_pointer_interactable_paint_tree(
element: &Element,
document: &Document,
can_gc: CanGc,
) -> Vec<DomRoot<Element>> {
// Step 2. Let rectangles be the DOMRect sequence returned by calling getClientRects()
let rect = element.GetClientRects(can_gc);
if rect.first().is_some() {
// Step 4. Let center point be the in-view center point of
// the first indexed element in rectangles.
match get_element_in_view_center_point(element, can_gc) {
// Step 5. Return the elements from point given the coordinates center point
Some(center_point) => document.ElementsFromPoint(
Finite::wrap(center_point.x as f64),
Finite::wrap(center_point.y as f64),
can_gc,
),
None => Vec::new(),
}
} else {
// Step 3. If rectangles has the length of 0, return an empty sequence
Vec::new()
}
}
pub(crate) fn handle_is_enabled(
documents: &DocumentCollection,
pipeline: PipelineId,