From 6e8d77ff7f9f29a87d0e53ae26e0c5b325fde06a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 20 Feb 2025 08:58:35 -0500 Subject: [PATCH] LibDevTools: Support highlighting DOM nodes hovered in the inspector --- Libraries/LibDevTools/Actors/FrameActor.cpp | 8 +++++ .../LibDevTools/Actors/HighlighterActor.cpp | 35 ++++++++++++++++--- .../LibDevTools/Actors/HighlighterActor.h | 6 ++-- .../LibDevTools/Actors/InspectorActor.cpp | 23 +++++++++++- Libraries/LibDevTools/Actors/InspectorActor.h | 4 +++ Libraries/LibDevTools/Actors/TabActor.cpp | 11 +++++- Libraries/LibDevTools/Actors/TabActor.h | 2 ++ Libraries/LibDevTools/Actors/WalkerActor.cpp | 22 ++++++++++++ Libraries/LibDevTools/Actors/WalkerActor.h | 9 +++++ Libraries/LibDevTools/DevToolsDelegate.h | 5 +++ 10 files changed, 116 insertions(+), 9 deletions(-) diff --git a/Libraries/LibDevTools/Actors/FrameActor.cpp b/Libraries/LibDevTools/Actors/FrameActor.cpp index b47ee9437bb..92e79237cab 100644 --- a/Libraries/LibDevTools/Actors/FrameActor.cpp +++ b/Libraries/LibDevTools/Actors/FrameActor.cpp @@ -35,6 +35,14 @@ void FrameActor::handle_message(StringView type, JsonObject const&) JsonObject response; response.set("from"sv, name()); + if (type == "detach"sv) { + if (auto tab = m_tab.strong_ref()) + tab->reset_selected_node(); + + send_message(move(response)); + return; + } + if (type == "listFrames"sv) { send_message(move(response)); return; diff --git a/Libraries/LibDevTools/Actors/HighlighterActor.cpp b/Libraries/LibDevTools/Actors/HighlighterActor.cpp index d4c74d9626f..f136be28840 100644 --- a/Libraries/LibDevTools/Actors/HighlighterActor.cpp +++ b/Libraries/LibDevTools/Actors/HighlighterActor.cpp @@ -6,33 +6,58 @@ #include #include +#include +#include +#include +#include +#include namespace DevTools { -NonnullRefPtr HighlighterActor::create(DevToolsServer& devtools, String name) +NonnullRefPtr HighlighterActor::create(DevToolsServer& devtools, String name, WeakPtr inspector) { - return adopt_ref(*new HighlighterActor(devtools, move(name))); + return adopt_ref(*new HighlighterActor(devtools, move(name), move(inspector))); } -HighlighterActor::HighlighterActor(DevToolsServer& devtools, String name) +HighlighterActor::HighlighterActor(DevToolsServer& devtools, String name, WeakPtr inspector) : Actor(devtools, move(name)) + , m_inspector(move(inspector)) { } HighlighterActor::~HighlighterActor() = default; -void HighlighterActor::handle_message(StringView type, JsonObject const&) +void HighlighterActor::handle_message(StringView type, JsonObject const& message) { JsonObject response; response.set("from"sv, name()); if (type == "show"sv) { - response.set("value"sv, true); + auto node = message.get_string("node"sv); + if (!node.has_value()) { + send_missing_parameter_error("node"sv); + return; + } + + auto tab = InspectorActor::tab_for(m_inspector); + auto walker = InspectorActor::walker_for(m_inspector); + response.set("value"sv, false); + + if (tab && walker) { + if (auto const& dom_node = walker->dom_node(*node); dom_node.has_value()) { + devtools().delegate().highlight_dom_node(tab->description(), dom_node->id, dom_node->pseudo_element); + response.set("value"sv, true); + } + } + send_message(move(response)); return; } if (type == "hide"sv) { + if (auto tab = InspectorActor::tab_for(m_inspector)) + devtools().delegate().clear_highlighted_dom_node(tab->description()); + send_message(move(response)); return; } diff --git a/Libraries/LibDevTools/Actors/HighlighterActor.h b/Libraries/LibDevTools/Actors/HighlighterActor.h index 3a0028c5b89..a95fca28071 100644 --- a/Libraries/LibDevTools/Actors/HighlighterActor.h +++ b/Libraries/LibDevTools/Actors/HighlighterActor.h @@ -15,14 +15,16 @@ class HighlighterActor final : public Actor { public: static constexpr auto base_name = "highlighter"sv; - static NonnullRefPtr create(DevToolsServer&, String name); + static NonnullRefPtr create(DevToolsServer&, String name, WeakPtr); virtual ~HighlighterActor() override; virtual void handle_message(StringView type, JsonObject const&) override; JsonValue serialize_highlighter() const; private: - HighlighterActor(DevToolsServer&, String name); + HighlighterActor(DevToolsServer&, String name, WeakPtr); + + WeakPtr m_inspector; }; } diff --git a/Libraries/LibDevTools/Actors/InspectorActor.cpp b/Libraries/LibDevTools/Actors/InspectorActor.cpp index 96f8ca150f5..8ad57617ba9 100644 --- a/Libraries/LibDevTools/Actors/InspectorActor.cpp +++ b/Libraries/LibDevTools/Actors/InspectorActor.cpp @@ -51,7 +51,7 @@ void InspectorActor::handle_message(StringView type, JsonObject const& message) } auto highlighter = m_highlighters.ensure(*type_name, [&]() -> NonnullRefPtr { - return devtools().register_actor(); + return devtools().register_actor(*this); }); response.set("highlighter"sv, highlighter->serialize_highlighter()); @@ -82,12 +82,19 @@ void InspectorActor::handle_message(StringView type, JsonObject const& message) return; } + if (type == "supportsHighlighters"sv) { + response.set("value"sv, true); + send_message(move(response)); + return; + } + send_unrecognized_packet_type_error(type); } void InspectorActor::received_dom_tree(JsonObject dom_tree, BlockToken block_token) { auto& walker_actor = devtools().register_actor(m_tab, move(dom_tree)); + m_walker = walker_actor; JsonObject walker; walker.set("actor"sv, walker_actor.name()); @@ -99,4 +106,18 @@ void InspectorActor::received_dom_tree(JsonObject dom_tree, BlockToken block_tok send_message(move(message), move(block_token)); } +RefPtr InspectorActor::tab_for(WeakPtr const& weak_inspector) +{ + if (auto inspector = weak_inspector.strong_ref()) + return inspector->m_tab.strong_ref(); + return {}; +} + +RefPtr InspectorActor::walker_for(WeakPtr const& weak_inspector) +{ + if (auto inspector = weak_inspector.strong_ref()) + return inspector->m_walker.strong_ref(); + return {}; +} + } diff --git a/Libraries/LibDevTools/Actors/InspectorActor.h b/Libraries/LibDevTools/Actors/InspectorActor.h index dc06901908d..8f76acc0a4a 100644 --- a/Libraries/LibDevTools/Actors/InspectorActor.h +++ b/Libraries/LibDevTools/Actors/InspectorActor.h @@ -21,12 +21,16 @@ public: virtual void handle_message(StringView type, JsonObject const&) override; + static RefPtr tab_for(WeakPtr const&); + static RefPtr walker_for(WeakPtr const&); + private: InspectorActor(DevToolsServer&, String name, WeakPtr); void received_dom_tree(JsonObject, BlockToken); WeakPtr m_tab; + WeakPtr m_walker; WeakPtr m_page_style; HashMap> m_highlighters; }; diff --git a/Libraries/LibDevTools/Actors/TabActor.cpp b/Libraries/LibDevTools/Actors/TabActor.cpp index 64fbb5e0b19..ff690c1681d 100644 --- a/Libraries/LibDevTools/Actors/TabActor.cpp +++ b/Libraries/LibDevTools/Actors/TabActor.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace DevTools { @@ -22,7 +23,10 @@ TabActor::TabActor(DevToolsServer& devtools, String name, TabDescription descrip { } -TabActor::~TabActor() = default; +TabActor::~TabActor() +{ + reset_selected_node(); +} void TabActor::handle_message(StringView type, JsonObject const&) { @@ -69,4 +73,9 @@ JsonObject TabActor::serialize_description() const return description; } +void TabActor::reset_selected_node() +{ + devtools().delegate().clear_highlighted_dom_node(description()); +} + } diff --git a/Libraries/LibDevTools/Actors/TabActor.h b/Libraries/LibDevTools/Actors/TabActor.h index fe28799be54..880accab913 100644 --- a/Libraries/LibDevTools/Actors/TabActor.h +++ b/Libraries/LibDevTools/Actors/TabActor.h @@ -30,6 +30,8 @@ public: TabDescription const& description() const { return m_description; } JsonObject serialize_description() const; + void reset_selected_node(); + private: TabActor(DevToolsServer&, String name, TabDescription); diff --git a/Libraries/LibDevTools/Actors/WalkerActor.cpp b/Libraries/LibDevTools/Actors/WalkerActor.cpp index 9b85320ac13..3a89f0f2030 100644 --- a/Libraries/LibDevTools/Actors/WalkerActor.cpp +++ b/Libraries/LibDevTools/Actors/WalkerActor.cpp @@ -228,6 +228,28 @@ JsonValue WalkerActor::serialize_node(JsonObject const& node) const return serialized; } +Optional WalkerActor::dom_node(StringView actor) +{ + auto maybe_dom_node = m_actor_to_dom_node_map.get(actor); + if (!maybe_dom_node.has_value() || !maybe_dom_node.value()) + return {}; + + auto const& dom_node = *maybe_dom_node.value(); + + auto pseudo_element = dom_node.get_integer>("pseudo-element"sv).map([](auto value) { + VERIFY(value < to_underlying(Web::CSS::Selector::PseudoElement::Type::KnownPseudoElementCount)); + return static_cast(value); + }); + + Web::UniqueNodeID node_id { 0 }; + if (pseudo_element.has_value()) + node_id = dom_node.get_integer("parent-id"sv).value(); + else + node_id = dom_node.get_integer("id"sv).value(); + + return DOMNode { .node = dom_node, .id = node_id, .pseudo_element = pseudo_element }; +} + Optional WalkerActor::find_node_by_selector(JsonObject const& node, StringView selector) { auto matches = [&](auto const& candidate) { diff --git a/Libraries/LibDevTools/Actors/WalkerActor.h b/Libraries/LibDevTools/Actors/WalkerActor.h index 70b3e3fed31..969a26f4d93 100644 --- a/Libraries/LibDevTools/Actors/WalkerActor.h +++ b/Libraries/LibDevTools/Actors/WalkerActor.h @@ -11,6 +11,8 @@ #include #include #include +#include +#include namespace DevTools { @@ -26,6 +28,13 @@ public: static bool is_suitable_for_dom_inspection(JsonValue const&); JsonValue serialize_root() const; + struct DOMNode { + JsonObject const& node; + Web::UniqueNodeID id { 0 }; + Optional pseudo_element; + }; + Optional dom_node(StringView actor); + private: WalkerActor(DevToolsServer&, String name, WeakPtr, JsonObject dom_tree); diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index 41c5c38c228..67a066b46cb 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include namespace DevTools { @@ -25,6 +27,9 @@ public: using OnTabInspectionComplete = Function)>; virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { } + + virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional) const { } + virtual void clear_highlighted_dom_node(TabDescription const&) const { } }; }