LibWeb+WebContent: Add node picker hit testing

Picking needs hit testing in the page process, where layout and event
targeting state live. Expose a small page-level query and WebContent IPC
entry point that returns the node id at a viewport position.

This lets DevTools ask WebContent what the picker is pointing at without
duplicating hit-test logic outside LibWeb.
This commit is contained in:
Sam Atkins 2026-05-28 12:10:27 +01:00
parent 97070f836c
commit 30632e2dcb
11 changed files with 44 additions and 0 deletions

View file

@ -1490,6 +1490,15 @@ Optional<EventHandler::Target> EventHandler::target_for_mouse_position(CSSPixelP
return {};
}
GC::Ptr<DOM::Node> EventHandler::target_node_for_mouse_position(CSSPixelPoint position)
{
auto target = target_for_mouse_position(position);
if (!target.has_value() || !target->paintable)
return {};
return target->paintable->dom_node();
}
GC::Ptr<DOM::Node> EventHandler::focus_candidate_for_position(CSSPixelPoint visual_viewport_position) const
{
auto exact_hit = paint_root()->hit_test(visual_viewport_position, Painting::HitTestType::Exact);

View file

@ -49,6 +49,7 @@ public:
EventResult handle_mousewheel(CSSPixelPoint, CSSPixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y, bool async_scroll_performed_default_action = false, Optional<AsyncScrollOperation>* async_scroll_operation = nullptr);
EventResult handle_mouseleave();
void update_hover_after_scroll();
GC::Ptr<DOM::Node> target_node_for_mouse_position(CSSPixelPoint);
EventResult handle_keydown(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);

View file

@ -327,6 +327,15 @@ EventResult Page::handle_mouseleave()
return top_level_traversable()->event_handler().handle_mouseleave();
}
UniqueNodeID Page::node_id_at_position(DevicePixelPoint position)
{
auto node = top_level_traversable()->event_handler().target_node_for_mouse_position(device_to_css_point(position));
if (!node)
return 0;
return node->unique_id();
}
EventResult Page::handle_mousewheel(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y, bool async_scroll_performed_default_action, Optional<AsyncScrollOperation>* async_scroll_operation)
{
return top_level_traversable()->event_handler().handle_mousewheel(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers, wheel_delta_x, wheel_delta_y, async_scroll_performed_default_action, async_scroll_operation);

View file

@ -118,6 +118,7 @@ public:
EventResult handle_mousedown(DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, int click_count);
EventResult handle_mousemove(DevicePixelPoint, DevicePixelPoint screen_position, unsigned buttons, unsigned modifiers);
EventResult handle_mouseleave();
UniqueNodeID node_id_at_position(DevicePixelPoint);
EventResult handle_mousewheel(DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, double wheel_delta_x, double wheel_delta_y, bool async_scroll_performed_default_action = false, Optional<AsyncScrollOperation>* async_scroll_operation = nullptr);
EventResult handle_drag_and_drop_event(DragEvent::Type, DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers, Vector<HTML::SelectedFile> files);

View file

@ -246,6 +246,7 @@ public:
Function<void(Optional<JsonObject>)> on_received_current_flexbox;
Function<void(JsonObject)> on_received_accessibility_tree;
Function<void(Web::UniqueNodeID)> on_received_hovered_node_id;
Function<void(u64 request_id, Web::UniqueNodeID)> on_received_node_id_at_position;
Function<void(Mutation)> on_dom_mutation_received;
Function<void(Optional<Web::UniqueNodeID> const& node_id)> on_finished_editing_dom_node;
Function<void(String)> on_received_dom_node_html;

View file

@ -749,6 +749,14 @@ void WebContentClient::did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID no
}
}
void WebContentClient::did_get_node_id_at_position(u64 page_id, u64 request_id, Web::UniqueNodeID node_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_node_id_at_position)
view->on_received_node_id_at_position(request_id, node_id);
}
}
void WebContentClient::did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {

View file

@ -124,6 +124,7 @@ private:
virtual void did_inspect_current_flexbox(u64 page_id, String) override;
virtual void did_inspect_accessibility_tree(u64 page_id, String) override;
virtual void did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id) override;
virtual void did_get_node_id_at_position(u64 page_id, u64 request_id, Web::UniqueNodeID node_id) override;
virtual void did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id) override;
virtual void did_mutate_dom(u64 page_id, Mutation) override;
virtual void did_get_dom_node_html(u64 page_id, String html) override;

View file

@ -1122,6 +1122,17 @@ void ConnectionFromClient::get_hovered_node_id(u64 page_id)
async_did_get_hovered_node_id(page_id, node_id);
}
void ConnectionFromClient::get_node_id_at_position(u64 page_id, u64 request_id, Web::DevicePixelPoint position)
{
auto page = this->page(page_id);
if (!page.has_value()) {
async_did_get_node_id_at_position(page_id, request_id, 0);
return;
}
async_did_get_node_id_at_position(page_id, request_id, page->page().node_id_at_position(position));
}
void ConnectionFromClient::list_style_sheets(u64 page_id)
{
auto page = this->page(page_id);

View file

@ -102,6 +102,7 @@ private:
virtual void clear_grid_highlight(u64 page_id, Web::UniqueNodeID node_id) override;
virtual void inspect_accessibility_tree(u64 page_id) override;
virtual void get_hovered_node_id(u64 page_id) override;
virtual void get_node_id_at_position(u64 page_id, u64 request_id, Web::DevicePixelPoint position) override;
virtual void list_style_sheets(u64 page_id) override;
virtual void request_style_sheet_source(u64 page_id, Web::CSS::StyleSheetIdentifier identifier) override;

View file

@ -72,6 +72,7 @@ endpoint WebContentClient
did_inspect_current_flexbox(u64 page_id, String flexbox_layout) =|
did_inspect_accessibility_tree(u64 page_id, String accessibility_tree) =|
did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id) =|
did_get_node_id_at_position(u64 page_id, u64 request_id, Web::UniqueNodeID node_id) =|
did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id) =|
did_mutate_dom(u64 page_id, WebView::Mutation mutation) =|
did_get_dom_node_html(u64 page_id, String html) =|

View file

@ -69,6 +69,7 @@ endpoint WebContentServer
clear_grid_highlight(u64 page_id, Web::UniqueNodeID node_id) =|
inspect_accessibility_tree(u64 page_id) =|
get_hovered_node_id(u64 page_id) =|
get_node_id_at_position(u64 page_id, u64 request_id, Web::DevicePixelPoint position) =|
js_console_input(u64 page_id, String js_source) =|
run_javascript(u64 page_id, String js_source) =|