From 8e27fd48cceb948492c02d847ec1c8831d9bf268 Mon Sep 17 00:00:00 2001 From: batu_hoang <55729155+longvatrong111@users.noreply.github.com> Date: Thu, 31 Jul 2025 12:27:02 +0800 Subject: [PATCH] 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 --- components/script/webdriver_handlers.rs | 47 +++++++++++++++++-- components/webdriver_server/lib.rs | 2 +- .../element_click/interactability.py.ini | 32 +++---------- 3 files changed, 52 insertions(+), 29 deletions(-) diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index 7b1b3cf8570..edfda2cb513 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -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(); } +/// +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)) +} + +/// +fn get_element_pointer_interactable_paint_tree( + element: &Element, + document: &Document, + can_gc: CanGc, +) -> Vec> { + // 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, diff --git a/components/webdriver_server/lib.rs b/components/webdriver_server/lib.rs index 8c262c4fa55..fad7deecffd 100644 --- a/components/webdriver_server/lib.rs +++ b/components/webdriver_server/lib.rs @@ -2141,7 +2141,7 @@ impl Handler { let (sender, receiver) = ipc::channel().unwrap(); - // Steps 1 - 7 + Step 8 for