From 72905c84d5411fa281ff88a96c81a09a6177825b Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Fri, 21 Feb 2025 09:34:02 -0500 Subject: [PATCH] LibWeb+LibWebView+WebContent: Support both inspecting/highlighting nodes Our own Inspector differs from most other DevTools implementations with regard to highlighting DOM nodes as you hover elements in the inspected DOM tree. In other implementations, as you change the hovered node, the browser will render a box model overlay onto the page for that node. We currently don't do this; we wait until you click the node, at which point we both paint the overlay and inspect the node's properties. This patch does not change that behavior, but separates the IPCs and internal tracking of inspected nodes to support the standard DevTools behavior. So the DOM document now stores an inspected node and a highlighted node. The former is used for features such as "$0" in the JavaScript console, and the latter is used for the box model overlay. Our Inspector continues to set these to the same node. --- Libraries/LibWeb/DOM/Document.cpp | 32 ++++++++++++------- Libraries/LibWeb/DOM/Document.h | 20 +++++++----- Libraries/LibWeb/Painting/PaintableBox.cpp | 4 +-- Libraries/LibWebView/InspectorClient.cpp | 2 ++ Libraries/LibWebView/ViewImplementation.cpp | 24 ++++++++++---- Libraries/LibWebView/ViewImplementation.h | 8 +++-- Services/WebContent/ConnectionFromClient.cpp | 23 +++++++++++-- Services/WebContent/ConnectionFromClient.h | 1 + .../ConsoleGlobalEnvironmentExtensions.cpp | 2 +- Services/WebContent/WebContentServer.ipc | 1 + 10 files changed, 83 insertions(+), 34 deletions(-) diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index 80f3c9284cf..e8b53d91b1e 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -519,6 +519,7 @@ void Document::visit_edges(Cell::Visitor& visitor) visitor.visit(m_style_sheets); visitor.visit(m_hovered_node); visitor.visit(m_inspected_node); + visitor.visit(m_highlighted_node); visitor.visit(m_active_favicon); visitor.visit(m_focused_element); visitor.visit(m_active_element); @@ -1638,29 +1639,36 @@ Layout::Viewport* Document::layout_node() return static_cast(Node::layout_node()); } -void Document::set_inspected_node(Node* node, Optional pseudo_element) +void Document::set_inspected_node(GC::Ptr node) { - if (m_inspected_node.ptr() == node && m_inspected_pseudo_element == pseudo_element) + m_inspected_node = node; +} + +void Document::set_highlighted_node(GC::Ptr node, Optional pseudo_element) +{ + if (m_highlighted_node == node && m_highlighted_pseudo_element == pseudo_element) return; - if (auto layout_node = inspected_layout_node(); layout_node && layout_node->first_paintable()) + if (auto layout_node = highlighted_layout_node(); layout_node && layout_node->first_paintable()) layout_node->first_paintable()->set_needs_display(); - m_inspected_node = node; - m_inspected_pseudo_element = pseudo_element; + m_highlighted_node = node; + m_highlighted_pseudo_element = pseudo_element; - if (auto layout_node = inspected_layout_node(); layout_node && layout_node->first_paintable()) + if (auto layout_node = highlighted_layout_node(); layout_node && layout_node->first_paintable()) layout_node->first_paintable()->set_needs_display(); } -Layout::Node* Document::inspected_layout_node() +GC::Ptr Document::highlighted_layout_node() { - if (!m_inspected_node) + if (!m_highlighted_node) return nullptr; - if (!m_inspected_pseudo_element.has_value() || !m_inspected_node->is_element()) - return m_inspected_node->layout_node(); - auto& element = static_cast(*m_inspected_node); - return element.get_pseudo_element_node(m_inspected_pseudo_element.value()); + + if (!m_highlighted_pseudo_element.has_value() || !m_highlighted_node->is_element()) + return m_highlighted_node->layout_node(); + + auto const& element = static_cast(*m_highlighted_node); + return element.get_pseudo_element_node(m_highlighted_pseudo_element.value()); } static Node* find_common_ancestor(Node* a, Node* b) diff --git a/Libraries/LibWeb/DOM/Document.h b/Libraries/LibWeb/DOM/Document.h index f17818f198a..528873a459d 100644 --- a/Libraries/LibWeb/DOM/Document.h +++ b/Libraries/LibWeb/DOM/Document.h @@ -186,11 +186,13 @@ public: Node* hovered_node() { return m_hovered_node.ptr(); } Node const* hovered_node() const { return m_hovered_node.ptr(); } - void set_inspected_node(Node*, Optional); - Node* inspected_node() { return m_inspected_node.ptr(); } - Node const* inspected_node() const { return m_inspected_node.ptr(); } - Layout::Node* inspected_layout_node(); - Layout::Node const* inspected_layout_node() const { return const_cast(this)->inspected_layout_node(); } + void set_inspected_node(GC::Ptr); + GC::Ptr inspected_node() const { return m_inspected_node; } + + void set_highlighted_node(GC::Ptr, Optional); + GC::Ptr highlighted_node() const { return m_highlighted_node; } + GC::Ptr highlighted_layout_node(); + GC::Ptr highlighted_layout_node() const { return const_cast(this)->highlighted_layout_node(); } Element* document_element(); Element const* document_element() const; @@ -866,9 +868,6 @@ private: GC::Ref m_page; OwnPtr m_style_computer; GC::Ptr m_style_sheets; - GC::Ptr m_hovered_node; - GC::Ptr m_inspected_node; - Optional m_inspected_pseudo_element; GC::Ptr m_active_favicon; WeakPtr m_browsing_context; URL::URL m_url; @@ -877,6 +876,11 @@ private: GC::Ptr m_layout_root; + GC::Ptr m_hovered_node; + GC::Ptr m_inspected_node; + GC::Ptr m_highlighted_node; + Optional m_highlighted_pseudo_element; + Optional m_normal_link_color; Optional m_active_link_color; Optional m_visited_link_color; diff --git a/Libraries/LibWeb/Painting/PaintableBox.cpp b/Libraries/LibWeb/Painting/PaintableBox.cpp index e70c872799d..b88b3697307 100644 --- a/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -475,7 +475,7 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const } } - if (phase == PaintPhase::Overlay && layout_node().document().inspected_layout_node() == &layout_node_with_style_and_box_metrics()) { + if (phase == PaintPhase::Overlay && layout_node().document().highlighted_layout_node() == &layout_node_with_style_and_box_metrics()) { auto content_rect = absolute_united_content_rect(); auto margin_rect = united_rect_for_continuation_chain(*this, [](PaintableBox const& box) { auto margin_box = box.box_model().margin_box(); @@ -759,7 +759,7 @@ void paint_text_fragment(PaintContext& context, TextPaintable const& paintable, auto fragment_absolute_rect = fragment.absolute_rect(); auto fragment_absolute_device_rect = context.enclosing_device_rect(fragment_absolute_rect); - if (paintable.document().inspected_layout_node() == &paintable.layout_node()) + if (paintable.document().highlighted_layout_node() == &paintable.layout_node()) context.display_list_recorder().draw_rect(fragment_absolute_device_rect.to_type(), Color::Magenta); auto text = paintable.text_for_rendering(); diff --git a/Libraries/LibWebView/InspectorClient.cpp b/Libraries/LibWebView/InspectorClient.cpp index ef46885e869..75f8baf94e9 100644 --- a/Libraries/LibWebView/InspectorClient.cpp +++ b/Libraries/LibWebView/InspectorClient.cpp @@ -161,6 +161,7 @@ InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImple }; m_inspector_web_view.on_inspector_selected_dom_node = [this](auto node_id, auto const& pseudo_element) { + m_content_web_view.highlight_dom_node(node_id, pseudo_element); m_content_web_view.inspect_dom_node(node_id, pseudo_element); }; @@ -306,6 +307,7 @@ void InspectorClient::select_default_node() void InspectorClient::clear_selection() { + m_content_web_view.clear_highlighted_dom_node(); m_content_web_view.clear_inspected_dom_node(); static constexpr auto script = "inspector.clearInspectedDOMNode();"sv; diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index c77811fcced..122f6dcea71 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -303,24 +303,34 @@ void ViewImplementation::inspect_dom_tree() client().async_inspect_dom_tree(page_id()); } -void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element) -{ - client().async_inspect_dom_node(page_id(), node_id, move(pseudo_element)); -} - void ViewImplementation::inspect_accessibility_tree() { client().async_inspect_accessibility_tree(page_id()); } +void ViewImplementation::get_hovered_node_id() +{ + client().async_get_hovered_node_id(page_id()); +} + +void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element) +{ + client().async_inspect_dom_node(page_id(), node_id, move(pseudo_element)); +} + void ViewImplementation::clear_inspected_dom_node() { inspect_dom_node(0, {}); } -void ViewImplementation::get_hovered_node_id() +void ViewImplementation::highlight_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element) { - client().async_get_hovered_node_id(page_id()); + client().async_highlight_dom_node(page_id(), node_id, move(pseudo_element)); +} + +void ViewImplementation::clear_highlighted_dom_node() +{ + highlight_dom_node(0, {}); } void ViewImplementation::set_dom_node_text(Web::UniqueNodeID node_id, String text) diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index 5ec51cbf85e..f87f8b9476c 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -103,11 +103,15 @@ public: void get_source(); void inspect_dom_tree(); - void inspect_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); void inspect_accessibility_tree(); - void clear_inspected_dom_node(); void get_hovered_node_id(); + void inspect_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); + void clear_inspected_dom_node(); + + void highlight_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); + void clear_highlighted_dom_node(); + void set_dom_node_text(Web::UniqueNodeID node_id, String text); void set_dom_node_tag(Web::UniqueNodeID node_id, String name); void add_dom_node_attributes(Web::UniqueNodeID node_id, Vector attributes); diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index 622e36aae62..d06a97fdb15 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -442,7 +442,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const for (auto& navigable : Web::HTML::all_navigables()) { if (navigable->active_document() != nullptr) { - navigable->active_document()->set_inspected_node(nullptr, {}); + navigable->active_document()->set_inspected_node(nullptr); } } @@ -453,7 +453,7 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const return; } - node->document().set_inspected_node(node, pseudo_element); + node->document().set_inspected_node(node); if (node->is_element()) { auto& element = as(*node); @@ -599,6 +599,25 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID const async_did_inspect_dom_node(page_id, false, {}, {}, {}, {}, {}, {}); } +void ConnectionFromClient::highlight_dom_node(u64 page_id, Web::UniqueNodeID const& node_id, Optional const& pseudo_element) +{ + auto page = this->page(page_id); + if (!page.has_value()) + return; + + for (auto& navigable : Web::HTML::all_navigables()) { + if (navigable->active_document() != nullptr) { + navigable->active_document()->set_highlighted_node(nullptr, {}); + } + } + + auto* node = Web::DOM::Node::from_unique_id(node_id); + if (!node || !node->layout_node()) + return; + + node->document().set_highlighted_node(node, pseudo_element); +} + void ConnectionFromClient::inspect_accessibility_tree(u64 page_id) { if (auto page = this->page(page_id); page.has_value()) { diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index ca68ca511e9..61640740f1f 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -76,6 +76,7 @@ private: virtual void get_source(u64 page_id) override; virtual void inspect_dom_tree(u64 page_id) override; virtual void inspect_dom_node(u64 page_id, Web::UniqueNodeID const& node_id, Optional const& pseudo_element) override; + virtual void highlight_dom_node(u64 page_id, Web::UniqueNodeID const& node_id, Optional const& pseudo_element) override; virtual void inspect_accessibility_tree(u64 page_id) override; virtual void get_hovered_node_id(u64 page_id) override; diff --git a/Services/WebContent/ConsoleGlobalEnvironmentExtensions.cpp b/Services/WebContent/ConsoleGlobalEnvironmentExtensions.cpp index 8e58ed04653..950b46984b1 100644 --- a/Services/WebContent/ConsoleGlobalEnvironmentExtensions.cpp +++ b/Services/WebContent/ConsoleGlobalEnvironmentExtensions.cpp @@ -53,7 +53,7 @@ JS_DEFINE_NATIVE_FUNCTION(ConsoleGlobalEnvironmentExtensions::$0_getter) { auto* console_global_object = TRY(get_console(vm)); auto& window = *console_global_object->m_window_object; - auto* inspected_node = window.associated_document().inspected_node(); + auto inspected_node = window.associated_document().inspected_node(); if (!inspected_node) return JS::js_undefined(); diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 30c275779ec..86ad895db53 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -45,6 +45,7 @@ endpoint WebContentServer get_source(u64 page_id) =| inspect_dom_tree(u64 page_id) =| inspect_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) =| + highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) =| inspect_accessibility_tree(u64 page_id) =| get_hovered_node_id(u64 page_id) =| js_console_input(u64 page_id, ByteString js_source) =|