From 2000fd38c148b2c44187143cc9324fbdc7459c65 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Wed, 27 May 2026 17:12:14 +0100 Subject: [PATCH] DevTools+WebContent: Support the flexbox highlighter --- .../LibDevTools/Actors/HighlighterActor.cpp | 15 +++- .../LibDevTools/Actors/HighlighterActor.h | 1 + Libraries/LibDevTools/DevToolsDelegate.h | 2 + Libraries/LibWebView/Application.cpp | 12 +++ Libraries/LibWebView/Application.h | 2 + Libraries/LibWebView/ViewImplementation.cpp | 10 +++ Libraries/LibWebView/ViewImplementation.h | 2 + Services/WebContent/ConnectionFromClient.cpp | 48 ++++++++++++ Services/WebContent/ConnectionFromClient.h | 2 + Services/WebContent/WebContentServer.ipc | 2 + Tests/LibDevTools/TestDevToolsProtocol.cpp | 75 +++++++++++++++++++ 11 files changed, 169 insertions(+), 2 deletions(-) diff --git a/Libraries/LibDevTools/Actors/HighlighterActor.cpp b/Libraries/LibDevTools/Actors/HighlighterActor.cpp index d9f550c028..d6487e729d 100644 --- a/Libraries/LibDevTools/Actors/HighlighterActor.cpp +++ b/Libraries/LibDevTools/Actors/HighlighterActor.cpp @@ -31,7 +31,10 @@ HighlighterActor::~HighlighterActor() = default; void HighlighterActor::clear_current_highlight() { if (auto tab = InspectorActor::tab_for(m_inspector)) { - if (m_type_name == "CssGridHighlighter"sv) { + if (m_type_name == "FlexboxHighlighter"sv) { + if (m_highlighted_flexbox_node_id.has_value()) + devtools().delegate().clear_flexbox_highlight(tab->description(), *m_highlighted_flexbox_node_id); + } else if (m_type_name == "CssGridHighlighter"sv) { if (m_highlighted_grid_node_id.has_value()) devtools().delegate().clear_grid_highlight(tab->description(), *m_highlighted_grid_node_id); } else if (m_is_highlighting_dom_node) { @@ -39,6 +42,7 @@ void HighlighterActor::clear_current_highlight() } } + m_highlighted_flexbox_node_id = {}; m_highlighted_grid_node_id = {}; m_is_highlighting_dom_node = false; } @@ -55,7 +59,14 @@ void HighlighterActor::handle_message(Message const& message) response.set("value"sv, false); if (auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), *node); dom_node.has_value()) { - if (m_type_name == "CssGridHighlighter"sv) { + if (m_type_name == "FlexboxHighlighter"sv) { + if (m_highlighted_flexbox_node_id.has_value() && *m_highlighted_flexbox_node_id != dom_node->identifier.id) + devtools().delegate().clear_flexbox_highlight(dom_node->tab->description(), *m_highlighted_flexbox_node_id); + + auto options = message.data.get("options"sv).value_or(JsonObject {}); + devtools().delegate().highlight_flexbox(dom_node->tab->description(), dom_node->identifier.id, move(options)); + m_highlighted_flexbox_node_id = dom_node->identifier.id; + } else if (m_type_name == "CssGridHighlighter"sv) { if (m_highlighted_grid_node_id.has_value() && *m_highlighted_grid_node_id != dom_node->identifier.id) devtools().delegate().clear_grid_highlight(dom_node->tab->description(), *m_highlighted_grid_node_id); diff --git a/Libraries/LibDevTools/Actors/HighlighterActor.h b/Libraries/LibDevTools/Actors/HighlighterActor.h index 8332f873dd..2f03e11feb 100644 --- a/Libraries/LibDevTools/Actors/HighlighterActor.h +++ b/Libraries/LibDevTools/Actors/HighlighterActor.h @@ -32,6 +32,7 @@ private: WeakPtr m_inspector; String m_type_name; + Optional m_highlighted_flexbox_node_id; Optional m_highlighted_grid_node_id; bool m_is_highlighting_dom_node { false }; }; diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index 74df4b403d..efe4e54ca1 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -56,6 +56,8 @@ public: virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional) const { } virtual void clear_highlighted_dom_node(TabDescription const&) const { } + virtual void highlight_flexbox(TabDescription const&, Web::UniqueNodeID, JsonValue) const { } + virtual void clear_flexbox_highlight(TabDescription const&, Web::UniqueNodeID) const { } virtual void highlight_grid(TabDescription const&, Web::UniqueNodeID, JsonValue) const { } virtual void clear_grid_highlight(TabDescription const&, Web::UniqueNodeID) const { } diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index 21fdc5165c..b966479923 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -1834,6 +1834,18 @@ void Application::clear_highlighted_dom_node(DevTools::TabDescription const& des view->clear_highlighted_dom_node(); } +void Application::highlight_flexbox(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, JsonValue options) const +{ + if (auto view = ViewImplementation::find_view_by_id(description.id); view.has_value()) + view->highlight_flexbox(node_id, move(options)); +} + +void Application::clear_flexbox_highlight(DevTools::TabDescription const& description, Web::UniqueNodeID node_id) const +{ + if (auto view = ViewImplementation::find_view_by_id(description.id); view.has_value()) + view->clear_flexbox_highlight(node_id); +} + void Application::highlight_grid(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, JsonValue options) const { if (auto view = ViewImplementation::find_view_by_id(description.id); view.has_value()) diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 92e54cf657..8756b761b4 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -263,6 +263,8 @@ private: virtual void inspect_current_flexbox(DevTools::TabDescription const&, Web::UniqueNodeID, bool, OnCurrentFlexboxReceived) const override; virtual void highlight_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional) const override; virtual void clear_highlighted_dom_node(DevTools::TabDescription const&) const override; + virtual void highlight_flexbox(DevTools::TabDescription const&, Web::UniqueNodeID, JsonValue) const override; + virtual void clear_flexbox_highlight(DevTools::TabDescription const&, Web::UniqueNodeID) const override; virtual void highlight_grid(DevTools::TabDescription const&, Web::UniqueNodeID, JsonValue) const override; virtual void clear_grid_highlight(DevTools::TabDescription const&, Web::UniqueNodeID) const override; virtual void listen_for_dom_mutations(DevTools::TabDescription const&, OnDOMMutationReceived) const override; diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index d663fcc11e..e1bbad2144 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -500,6 +500,16 @@ void ViewImplementation::clear_highlighted_dom_node() highlight_dom_node(0, {}); } +void ViewImplementation::highlight_flexbox(Web::UniqueNodeID node_id, JsonValue options) +{ + client().async_highlight_flexbox(page_id(), node_id, move(options)); +} + +void ViewImplementation::clear_flexbox_highlight(Web::UniqueNodeID node_id) +{ + client().async_clear_flexbox_highlight(page_id(), node_id); +} + void ViewImplementation::highlight_grid(Web::UniqueNodeID node_id, JsonValue options) { client().async_highlight_grid(page_id(), node_id, move(options)); diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index 72c4274e38..78efed8acc 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -129,6 +129,8 @@ public: void highlight_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); void clear_highlighted_dom_node(); + void highlight_flexbox(Web::UniqueNodeID node_id, JsonValue options); + void clear_flexbox_highlight(Web::UniqueNodeID node_id); void highlight_grid(Web::UniqueNodeID node_id, JsonValue options); void clear_grid_highlight(Web::UniqueNodeID node_id); diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index aa9dafba2e..5c3e0508d1 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include #include #include @@ -988,6 +989,21 @@ void ConnectionFromClient::highlight_dom_node(u64 page_id, Web::UniqueNodeID nod node->document().set_highlighted_node(node, pseudo_element); } +static Web::Painting::FlexboxInspectorOverlayOptions flexbox_inspector_overlay_options_from_json(JsonValue const& options) +{ + Web::Painting::FlexboxInspectorOverlayOptions result; + + if (options.is_object()) { + auto const& object = options.as_object(); + if (auto color = object.get_string("color"sv); color.has_value()) { + if (auto parsed_color = Gfx::Color::from_string(*color); parsed_color.has_value()) + result.color = *parsed_color; + } + } + + return result; +} + static Web::Painting::GridInspectorOverlayOptions grid_inspector_overlay_options_from_json(JsonValue const& options) { Web::Painting::GridInspectorOverlayOptions result; @@ -1011,6 +1027,38 @@ static Web::Painting::GridInspectorOverlayOptions grid_inspector_overlay_options return result; } +void ConnectionFromClient::highlight_flexbox(u64 page_id, Web::UniqueNodeID node_id, JsonValue options) +{ + auto page = this->page(page_id); + if (!page.has_value()) + return; + + auto* node = Web::DOM::Node::from_unique_id(node_id); + if (!node || !node->layout_node()) + return; + + node->document().set_flexbox_highlighted_node(node, flexbox_inspector_overlay_options_from_json(options)); +} + +void ConnectionFromClient::clear_flexbox_highlight(u64 page_id, Web::UniqueNodeID node_id) +{ + auto page = this->page(page_id); + if (!page.has_value()) + return; + + if (node_id != 0) { + auto* node = Web::DOM::Node::from_unique_id(node_id); + if (node) + node->document().clear_flexbox_highlighted_node(node); + return; + } + + for (auto& navigable : Web::HTML::all_navigables()) { + if (navigable->active_document()) + navigable->active_document()->clear_flexbox_highlighted_node(nullptr); + } +} + void ConnectionFromClient::highlight_grid(u64 page_id, Web::UniqueNodeID node_id, JsonValue options) { auto page = this->page(page_id); diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 4cf6ab583a..3ba0358c1b 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -96,6 +96,8 @@ private: virtual void inspect_current_flexbox(u64 page_id, Web::UniqueNodeID node_id, bool only_look_at_parents) override; virtual void clear_inspected_dom_node(u64 page_id) override; virtual void highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) override; + virtual void highlight_flexbox(u64 page_id, Web::UniqueNodeID node_id, JsonValue options) override; + virtual void clear_flexbox_highlight(u64 page_id, Web::UniqueNodeID node_id) override; virtual void highlight_grid(u64 page_id, Web::UniqueNodeID node_id, JsonValue options) override; virtual void clear_grid_highlight(u64 page_id, Web::UniqueNodeID node_id) override; virtual void inspect_accessibility_tree(u64 page_id) override; diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 07a0c3d000..6ffc8213f5 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -63,6 +63,8 @@ endpoint WebContentServer inspect_current_flexbox(u64 page_id, Web::UniqueNodeID node_id, bool only_look_at_parents) =| clear_inspected_dom_node(u64 page_id) =| highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional pseudo_element) =| + highlight_flexbox(u64 page_id, Web::UniqueNodeID node_id, JsonValue options) =| + clear_flexbox_highlight(u64 page_id, Web::UniqueNodeID node_id) =| highlight_grid(u64 page_id, Web::UniqueNodeID node_id, JsonValue options) =| clear_grid_highlight(u64 page_id, Web::UniqueNodeID node_id) =| inspect_accessibility_tree(u64 page_id) =| diff --git a/Tests/LibDevTools/TestDevToolsProtocol.cpp b/Tests/LibDevTools/TestDevToolsProtocol.cpp index baa15de098..49dbda8ad3 100644 --- a/Tests/LibDevTools/TestDevToolsProtocol.cpp +++ b/Tests/LibDevTools/TestDevToolsProtocol.cpp @@ -428,6 +428,19 @@ public: last_highlighted_pseudo_element = pseudo_element; } + virtual void highlight_flexbox(DevTools::TabDescription const&, Web::UniqueNodeID node_id, JsonValue options) const override + { + ++highlight_flexbox_call_count; + last_highlighted_flexbox_node = node_id; + last_flexbox_highlight_options = move(options); + } + + virtual void clear_flexbox_highlight(DevTools::TabDescription const&, Web::UniqueNodeID node_id) const override + { + ++clear_flexbox_highlight_call_count; + last_cleared_flexbox_node = node_id; + } + virtual void highlight_grid(DevTools::TabDescription const&, Web::UniqueNodeID node_id, JsonValue options) const override { ++highlight_grid_call_count; @@ -690,6 +703,8 @@ public: mutable size_t inspect_current_flexbox_call_count { 0 }; mutable size_t highlight_dom_node_call_count { 0 }; mutable size_t clear_highlighted_dom_node_call_count { 0 }; + mutable size_t highlight_flexbox_call_count { 0 }; + mutable size_t clear_flexbox_highlight_call_count { 0 }; mutable size_t highlight_grid_call_count { 0 }; mutable size_t clear_grid_highlight_call_count { 0 }; mutable size_t listen_for_dom_mutations_call_count { 0 }; @@ -726,6 +741,9 @@ public: mutable Optional last_current_grid_node; mutable Optional last_current_flexbox_node; mutable bool last_current_flexbox_only_look_at_parents { false }; + mutable Optional last_highlighted_flexbox_node; + mutable Optional last_cleared_flexbox_node; + mutable JsonValue last_flexbox_highlight_options; mutable Optional last_highlighted_grid_node; mutable Optional last_cleared_grid_node; mutable JsonValue last_grid_highlight_options; @@ -1192,6 +1210,63 @@ TEST_CASE(inspector_walker_highlighter_layout_and_editing) EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 3u); EXPECT_EQ(session->delegate.last_cleared_grid_node.value(), 4u); + JsonObject flexbox_highlighter_request; + flexbox_highlighter_request.set("to"sv, inspector_actor); + flexbox_highlighter_request.set("type"sv, "getHighlighterByType"sv); + flexbox_highlighter_request.set("typeName"sv, "FlexboxHighlighter"sv); + auto flexbox_highlighter_actor = client.request(move(flexbox_highlighter_request)).get_object("highlighter"sv)->get_string("actor"sv).release_value(); + + JsonObject second_flexbox_highlighter_request; + second_flexbox_highlighter_request.set("to"sv, inspector_actor); + second_flexbox_highlighter_request.set("type"sv, "getHighlighterByType"sv); + second_flexbox_highlighter_request.set("typeName"sv, "FlexboxHighlighter"sv); + auto second_flexbox_highlighter_actor = client.request(move(second_flexbox_highlighter_request)) + .get_object("highlighter"sv) + ->get_string("actor"sv) + .release_value(); + EXPECT_NE(second_flexbox_highlighter_actor, flexbox_highlighter_actor); + + JsonObject flexbox_options; + flexbox_options.set("color"sv, "#9400ff"sv); + + JsonObject show_flexbox_highlighter; + show_flexbox_highlighter.set("to"sv, flexbox_highlighter_actor); + show_flexbox_highlighter.set("type"sv, "show"sv); + show_flexbox_highlighter.set("node"sv, flex_actor); + show_flexbox_highlighter.set("options"sv, move(flexbox_options)); + EXPECT(client.request(move(show_flexbox_highlighter)).get_bool("value"sv).value()); + EXPECT_EQ(session->delegate.highlight_flexbox_call_count, 1u); + EXPECT_EQ(session->delegate.last_highlighted_flexbox_node.value(), 10u); + EXPECT_EQ(session->delegate.last_flexbox_highlight_options.as_object().get_string("color"sv).value(), "#9400ff"sv); + EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 0u); + + JsonObject show_second_flexbox_highlighter; + show_second_flexbox_highlighter.set("to"sv, second_flexbox_highlighter_actor); + show_second_flexbox_highlighter.set("type"sv, "show"sv); + show_second_flexbox_highlighter.set("node"sv, first_flex_item_actor); + EXPECT(client.request(move(show_second_flexbox_highlighter)).get_bool("value"sv).value()); + EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 0u); + EXPECT_EQ(session->delegate.highlight_flexbox_call_count, 2u); + EXPECT_EQ(session->delegate.last_highlighted_flexbox_node.value(), 11u); + + EXPECT_EQ(client.request(flexbox_highlighter_actor, "hide"sv).get_string("from"sv).value(), flexbox_highlighter_actor); + EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 1u); + EXPECT_EQ(session->delegate.last_cleared_flexbox_node.value(), 10u); + + JsonObject retarget_second_flexbox_highlighter; + retarget_second_flexbox_highlighter.set("to"sv, second_flexbox_highlighter_actor); + retarget_second_flexbox_highlighter.set("type"sv, "show"sv); + retarget_second_flexbox_highlighter.set("node"sv, flex_actor); + EXPECT(client.request(move(retarget_second_flexbox_highlighter)).get_bool("value"sv).value()); + EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 2u); + EXPECT_EQ(session->delegate.last_cleared_flexbox_node.value(), 11u); + EXPECT_EQ(session->delegate.highlight_flexbox_call_count, 3u); + EXPECT_EQ(session->delegate.last_highlighted_flexbox_node.value(), 10u); + + EXPECT_EQ(client.request(second_flexbox_highlighter_actor, "release"sv).get_string("from"sv).value(), second_flexbox_highlighter_actor); + EXPECT_EQ(session->delegate.clear_flexbox_highlight_call_count, 3u); + EXPECT_EQ(session->delegate.last_cleared_flexbox_node.value(), 10u); + auto layout_actor = client.request(walker_actor, "getLayoutInspector"sv).get_object("actor"sv)->get_string("actor"sv).release_value(); JsonObject get_grids;