DevTools: Support the CSS grid highlighter

Create typed highlighter actors for Firefox's highlighter requests.
The actor keeps the requested type so CssGridHighlighter can use grid
highlighting while the other highlighter types keep the existing box
model-compatible behavior.
This commit is contained in:
Sam Atkins 2026-05-22 16:00:33 +01:00
parent 5c551ac6aa
commit ad541dca7f
4 changed files with 98 additions and 9 deletions

View file

@ -14,14 +14,15 @@
namespace DevTools {
NonnullRefPtr<HighlighterActor> HighlighterActor::create(DevToolsServer& devtools, String name, WeakPtr<InspectorActor> inspector)
NonnullRefPtr<HighlighterActor> HighlighterActor::create(DevToolsServer& devtools, String name, WeakPtr<InspectorActor> inspector, String type_name)
{
return adopt_ref(*new HighlighterActor(devtools, move(name), move(inspector)));
return adopt_ref(*new HighlighterActor(devtools, move(name), move(inspector), move(type_name)));
}
HighlighterActor::HighlighterActor(DevToolsServer& devtools, String name, WeakPtr<InspectorActor> inspector)
HighlighterActor::HighlighterActor(DevToolsServer& devtools, String name, WeakPtr<InspectorActor> inspector, String type_name)
: Actor(devtools, move(name))
, m_inspector(move(inspector))
, m_type_name(move(type_name))
{
}
@ -39,7 +40,16 @@ 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()) {
devtools().delegate().highlight_dom_node(dom_node->tab->description(), dom_node->identifier.id, dom_node->identifier.pseudo_element);
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);
auto options = message.data.get("options"sv).value_or(JsonObject {});
devtools().delegate().highlight_grid(dom_node->tab->description(), dom_node->identifier.id, move(options));
m_highlighted_grid_node_id = dom_node->identifier.id;
} else {
devtools().delegate().highlight_dom_node(dom_node->tab->description(), dom_node->identifier.id, dom_node->identifier.pseudo_element);
}
response.set("value"sv, true);
}
@ -48,8 +58,26 @@ void HighlighterActor::handle_message(Message const& message)
}
if (message.type == "hide"sv) {
if (auto tab = InspectorActor::tab_for(m_inspector))
devtools().delegate().clear_highlighted_dom_node(tab->description());
if (auto tab = InspectorActor::tab_for(m_inspector)) {
if (m_type_name == "CssGridHighlighter"sv) {
devtools().delegate().clear_grid_highlight(tab->description(), m_highlighted_grid_node_id.value_or(0));
m_highlighted_grid_node_id = {};
} else {
devtools().delegate().clear_highlighted_dom_node(tab->description());
}
}
send_response(message, move(response));
return;
}
if (message.type == "release"sv || message.type == "finalize"sv) {
if (auto tab = InspectorActor::tab_for(m_inspector)) {
if (m_type_name == "CssGridHighlighter"sv)
devtools().delegate().clear_grid_highlight(tab->description(), m_highlighted_grid_node_id.value_or(0));
else
devtools().delegate().clear_highlighted_dom_node(tab->description());
}
send_response(message, move(response));
return;

View file

@ -7,8 +7,10 @@
#pragma once
#include <AK/NonnullRefPtr.h>
#include <AK/Optional.h>
#include <LibDevTools/Actor.h>
#include <LibDevTools/Forward.h>
#include <LibWeb/Forward.h>
namespace DevTools {
@ -16,17 +18,19 @@ class DEVTOOLS_API HighlighterActor final : public Actor {
public:
static constexpr auto base_name = "highlighter"sv;
static NonnullRefPtr<HighlighterActor> create(DevToolsServer&, String name, WeakPtr<InspectorActor>);
static NonnullRefPtr<HighlighterActor> create(DevToolsServer&, String name, WeakPtr<InspectorActor>, String type_name);
virtual ~HighlighterActor() override;
JsonValue serialize_highlighter() const;
private:
HighlighterActor(DevToolsServer&, String name, WeakPtr<InspectorActor>);
HighlighterActor(DevToolsServer&, String name, WeakPtr<InspectorActor>, String type_name);
virtual void handle_message(Message const&) override;
WeakPtr<InspectorActor> m_inspector;
String m_type_name;
Optional<Web::UniqueNodeID> m_highlighted_grid_node_id;
};
}

View file

@ -48,7 +48,7 @@ void InspectorActor::handle_message(Message const& message)
return;
auto highlighter = m_highlighters.ensure(*type_name, [&]() -> NonnullRefPtr<HighlighterActor> {
return devtools().register_actor<HighlighterActor>(*this);
return devtools().register_actor<HighlighterActor>(*this, *type_name);
});
response.set("highlighter"sv, highlighter->serialize_highlighter());

View file

@ -335,6 +335,19 @@ public:
last_highlighted_pseudo_element = pseudo_element;
}
virtual void highlight_grid(DevTools::TabDescription const&, Web::UniqueNodeID node_id, JsonValue options) const override
{
++highlight_grid_call_count;
last_highlighted_grid_node = node_id;
last_grid_highlight_options = move(options);
}
virtual void clear_grid_highlight(DevTools::TabDescription const&, Web::UniqueNodeID node_id) const override
{
++clear_grid_highlight_call_count;
last_cleared_grid_node = node_id;
}
virtual void listen_for_dom_mutations(DevTools::TabDescription const&, OnDOMMutationReceived callback) const override
{
++listen_for_dom_mutations_call_count;
@ -583,6 +596,8 @@ public:
mutable size_t inspect_current_grid_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_grid_call_count { 0 };
mutable size_t clear_grid_highlight_call_count { 0 };
mutable size_t listen_for_dom_mutations_call_count { 0 };
mutable size_t stop_listening_for_dom_mutations_call_count { 0 };
mutable size_t get_dom_node_inner_html_call_count { 0 };
@ -615,6 +630,9 @@ public:
mutable Optional<Web::CSS::PseudoElement> last_inspected_pseudo_element;
mutable Optional<Web::UniqueNodeID> last_grid_root_node;
mutable Optional<Web::UniqueNodeID> last_current_grid_node;
mutable Optional<Web::UniqueNodeID> last_highlighted_grid_node;
mutable Optional<Web::UniqueNodeID> last_cleared_grid_node;
mutable JsonValue last_grid_highlight_options;
mutable Optional<Web::UniqueNodeID> last_edited_node;
mutable Optional<Web::UniqueNodeID> last_parent_node;
mutable Optional<Web::UniqueNodeID> last_sibling_node;
@ -1016,6 +1034,45 @@ TEST_CASE(inspector_walker_highlighter_layout_and_editing)
EXPECT(!client.request(move(show_unknown)).get_bool("value"sv).value());
EXPECT_EQ(client.request(highlighter_actor, "hide"sv).get_string("from"sv).value(), highlighter_actor);
JsonObject grid_highlighter_request;
grid_highlighter_request.set("to"sv, inspector_actor);
grid_highlighter_request.set("type"sv, "getHighlighterByType"sv);
grid_highlighter_request.set("typeName"sv, "CssGridHighlighter"sv);
auto grid_highlighter_actor = client.request(move(grid_highlighter_request)).get_object("highlighter"sv)->get_string("actor"sv).release_value();
JsonObject second_grid_highlighter_request;
second_grid_highlighter_request.set("to"sv, inspector_actor);
second_grid_highlighter_request.set("type"sv, "getHighlighterByType"sv);
second_grid_highlighter_request.set("typeName"sv, "CssGridHighlighter"sv);
EXPECT_EQ(client.request(move(second_grid_highlighter_request)).get_object("highlighter"sv)->get_string("actor"sv).value(), grid_highlighter_actor);
JsonObject grid_options;
grid_options.set("showGridArea"sv, true);
JsonObject show_grid_highlighter;
show_grid_highlighter.set("to"sv, grid_highlighter_actor);
show_grid_highlighter.set("type"sv, "show"sv);
show_grid_highlighter.set("node"sv, div_actor);
show_grid_highlighter.set("options"sv, move(grid_options));
EXPECT(client.request(move(show_grid_highlighter)).get_bool("value"sv).value());
EXPECT_EQ(session->delegate.highlight_grid_call_count, 1u);
EXPECT_EQ(session->delegate.last_highlighted_grid_node.value(), 4u);
EXPECT(session->delegate.last_grid_highlight_options.as_object().get_bool("showGridArea"sv).value());
JsonObject show_second_grid_highlighter;
show_second_grid_highlighter.set("to"sv, grid_highlighter_actor);
show_second_grid_highlighter.set("type"sv, "show"sv);
show_second_grid_highlighter.set("node"sv, span_actor);
EXPECT(client.request(move(show_second_grid_highlighter)).get_bool("value"sv).value());
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 1u);
EXPECT_EQ(session->delegate.last_cleared_grid_node.value(), 4u);
EXPECT_EQ(session->delegate.highlight_grid_call_count, 2u);
EXPECT_EQ(session->delegate.last_highlighted_grid_node.value(), 8u);
EXPECT_EQ(client.request(grid_highlighter_actor, "hide"sv).get_string("from"sv).value(), grid_highlighter_actor);
EXPECT_EQ(session->delegate.clear_grid_highlight_call_count, 2u);
EXPECT_EQ(session->delegate.last_cleared_grid_node.value(), 8u);
auto layout_actor = client.request(walker_actor, "getLayoutInspector"sv).get_object("actor"sv)->get_string("actor"sv).release_value();
JsonObject get_grids;