LibDevTools+LibWebView+WebContent: Selectively fetch DOM node properties

When we inspect a DOM node, we currently serialize many properties for
that node, including its layout, computed style, used fonts, etc. Now
that we aren't piggy-backing on the Inspector interface, we can instead
only serialize the specific information required by DevTools.
This commit is contained in:
Timothy Flynn 2025-03-19 15:50:56 -04:00 committed by Jelle Raaijmakers
parent 19529590b9
commit daca9f5995
Notes: github-actions[bot] 2025-03-20 08:02:26 +00:00
18 changed files with 287 additions and 276 deletions

View file

@ -66,6 +66,7 @@ void FrameActor::handle_message(Message const& message)
if (message.type == "detach"sv) {
if (auto tab = m_tab.strong_ref()) {
devtools().delegate().stop_listening_for_dom_properties(tab->description());
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
devtools().delegate().stop_listening_for_console_messages(tab->description());
devtools().delegate().stop_listening_for_style_sheet_sources(tab->description());

View file

@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <LibDevTools/Actors/InspectorActor.h>
#include <LibDevTools/Actors/PageStyleActor.h>
@ -111,9 +113,20 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtr<In
: Actor(devtools, move(name))
, m_inspector(move(inspector))
{
if (auto tab = InspectorActor::tab_for(m_inspector)) {
devtools.delegate().listen_for_dom_properties(tab->description(),
[weak_self = make_weak_ptr<PageStyleActor>()](WebView::DOMNodeProperties const& properties) {
if (auto self = weak_self.strong_ref())
self->received_dom_node_properties(properties);
});
}
}
PageStyleActor::~PageStyleActor() = default;
PageStyleActor::~PageStyleActor()
{
if (auto tab = InspectorActor::tab_for(m_inspector))
devtools().delegate().stop_listening_for_dom_properties(tab->description());
}
void PageStyleActor::handle_message(Message const& message)
{
@ -134,38 +147,17 @@ void PageStyleActor::handle_message(Message const& message)
}
if (message.type == "getComputed"sv) {
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
received_computed_style(response, properties.computed_style);
});
inspect_dom_node(message, WebView::DOMNodeProperties::Type::ComputedStyle);
return;
}
if (message.type == "getLayout"sv) {
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
received_layout(response, properties.node_box_sizing);
});
inspect_dom_node(message, WebView::DOMNodeProperties::Type::Layout);
return;
}
if (message.type == "getUsedFontFaces"sv) {
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
inspect_dom_node(message, *node, [](auto& response, auto const& properties) {
received_fonts(response, properties.fonts);
});
inspect_dom_node(message, WebView::DOMNodeProperties::Type::UsedFonts);
return;
}
@ -192,19 +184,46 @@ JsonValue PageStyleActor::serialize_style() const
return style;
}
template<typename Callback>
void PageStyleActor::inspect_dom_node(Message const& message, StringView node_actor, Callback&& callback)
void PageStyleActor::inspect_dom_node(Message const& message, WebView::DOMNodeProperties::Type property_type)
{
auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), node_actor);
auto node = get_required_parameter<String>(message, "node"sv);
if (!node.has_value())
return;
auto dom_node = WalkerActor::dom_node_for(InspectorActor::walker_for(m_inspector), *node);
if (!dom_node.has_value()) {
send_unknown_actor_error(message, node_actor);
send_unknown_actor_error(message, *node);
return;
}
devtools().delegate().inspect_dom_node(dom_node->tab->description(), dom_node->identifier.id, dom_node->identifier.pseudo_element,
async_handler(message, [callback = forward<Callback>(callback)](auto&, auto properties, auto& response) {
callback(response, properties);
}));
devtools().delegate().inspect_dom_node(dom_node->tab->description(), property_type, dom_node->identifier.id, dom_node->identifier.pseudo_element);
m_pending_inspect_requests.append({ .id = message.id });
}
void PageStyleActor::received_dom_node_properties(WebView::DOMNodeProperties const& properties)
{
if (m_pending_inspect_requests.is_empty())
return;
JsonObject response;
switch (properties.type) {
case WebView::DOMNodeProperties::Type::ComputedStyle:
if (properties.properties.is_object())
received_computed_style(response, properties.properties.as_object());
break;
case WebView::DOMNodeProperties::Type::Layout:
if (properties.properties.is_object())
received_layout(response, properties.properties.as_object());
break;
case WebView::DOMNodeProperties::Type::UsedFonts:
if (properties.properties.is_array())
received_fonts(response, properties.properties.as_array());
break;
}
auto message = m_pending_inspect_requests.take_first();
send_response(message, move(response));
}
}

View file

@ -6,19 +6,12 @@
#pragma once
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/NonnullRefPtr.h>
#include <LibDevTools/Actor.h>
#include <LibWebView/DOMNodeProperties.h>
namespace DevTools {
struct DOMNodeProperties {
JsonObject computed_style;
JsonObject node_box_sizing;
JsonArray fonts;
};
class PageStyleActor final : public Actor {
public:
static constexpr auto base_name = "page-style"sv;
@ -33,10 +26,12 @@ private:
virtual void handle_message(Message const&) override;
template<typename Callback>
void inspect_dom_node(Message const&, StringView node_actor, Callback&&);
void inspect_dom_node(Message const&, WebView::DOMNodeProperties::Type);
void received_dom_node_properties(WebView::DOMNodeProperties const&);
WeakPtr<InspectorActor> m_inspector;
Vector<Message, 1> m_pending_inspect_requests;
};
}

View file

@ -17,6 +17,7 @@
#include <LibWeb/CSS/Selector.h>
#include <LibWeb/CSS/StyleSheetIdentifier.h>
#include <LibWeb/Forward.h>
#include <LibWebView/DOMNodeProperties.h>
#include <LibWebView/Forward.h>
namespace DevTools {
@ -31,8 +32,10 @@ public:
using OnTabInspectionComplete = Function<void(ErrorOr<JsonValue>)>;
virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { }
using OnDOMNodeInspectionComplete = Function<void(ErrorOr<DOMNodeProperties>)>;
virtual void inspect_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>, OnDOMNodeInspectionComplete) const { }
using OnDOMNodePropertiesReceived = Function<void(WebView::DOMNodeProperties)>;
virtual void listen_for_dom_properties(TabDescription const&, OnDOMNodePropertiesReceived) const { }
virtual void stop_listening_for_dom_properties(TabDescription const&) const { }
virtual void inspect_dom_node(TabDescription const&, WebView::DOMNodeProperties::Type, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }
virtual void clear_inspected_dom_node(TabDescription const&) const { }
virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const { }

View file

@ -451,25 +451,31 @@ void Application::inspect_tab(DevTools::TabDescription const& description, OnTab
view->inspect_dom_tree();
}
void Application::inspect_dom_node(DevTools::TabDescription const& description, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element, OnDOMNodeInspectionComplete on_complete) const
void Application::listen_for_dom_properties(DevTools::TabDescription const& description, OnDOMNodePropertiesReceived on_dom_node_properties_received) const
{
auto view = ViewImplementation::find_view_by_id(description.id);
if (!view.has_value()) {
on_complete(Error::from_string_literal("Unable to locate tab"));
if (!view.has_value())
return;
}
view->on_received_dom_node_properties = [&view = *view, on_complete = move(on_complete)](ViewImplementation::DOMNodeProperties properties) {
view.on_received_dom_node_properties = nullptr;
view->on_received_dom_node_properties = move(on_dom_node_properties_received);
}
on_complete(DevTools::DOMNodeProperties {
.computed_style = move(properties.computed_style),
.node_box_sizing = move(properties.node_box_sizing),
.fonts = move(properties.fonts),
});
};
void Application::stop_listening_for_dom_properties(DevTools::TabDescription const& description) const
{
auto view = ViewImplementation::find_view_by_id(description.id);
if (!view.has_value())
return;
view->inspect_dom_node(node_id, pseudo_element);
view->on_received_dom_node_properties = nullptr;
}
void Application::inspect_dom_node(DevTools::TabDescription const& description, DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) const
{
auto view = ViewImplementation::find_view_by_id(description.id);
if (!view.has_value())
return;
view->inspect_dom_node(node_id, property_type, pseudo_element);
}
void Application::clear_inspected_dom_node(DevTools::TabDescription const& description) const

View file

@ -97,7 +97,9 @@ private:
virtual Vector<DevTools::TabDescription> tab_list() const override;
virtual Vector<DevTools::CSSProperty> css_property_list() const override;
virtual void inspect_tab(DevTools::TabDescription const&, OnTabInspectionComplete) const override;
virtual void inspect_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>, OnDOMNodeInspectionComplete) const override;
virtual void listen_for_dom_properties(DevTools::TabDescription const&, OnDOMNodePropertiesReceived) const override;
virtual void stop_listening_for_dom_properties(DevTools::TabDescription const&) const override;
virtual void inspect_dom_node(DevTools::TabDescription const&, DOMNodeProperties::Type, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const override;
virtual void clear_inspected_dom_node(DevTools::TabDescription const&) const override;
virtual void highlight_dom_node(DevTools::TabDescription const&, Web::UniqueNodeID, Optional<Web::CSS::Selector::PseudoElement::Type>) const override;
virtual void clear_highlighted_dom_node(DevTools::TabDescription const&) const override;

View file

@ -7,6 +7,7 @@ set(SOURCES
ConsoleOutput.cpp
CookieJar.cpp
Database.cpp
DOMNodeProperties.cpp
HelperProcess.cpp
Mutation.cpp
Plugins/FontPlugin.cpp

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibWebView/DOMNodeProperties.h>
template<>
ErrorOr<void> IPC::encode(Encoder& encoder, WebView::DOMNodeProperties const& attribute)
{
TRY(encoder.encode(attribute.type));
TRY(encoder.encode(attribute.properties));
return {};
}
template<>
ErrorOr<WebView::DOMNodeProperties> IPC::decode(Decoder& decoder)
{
auto type = TRY(decoder.decode<WebView::DOMNodeProperties::Type>());
auto properties = TRY(decoder.decode<JsonValue>());
return WebView::DOMNodeProperties { type, move(properties) };
}

View file

@ -0,0 +1,35 @@
/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/JsonValue.h>
#include <LibIPC/Forward.h>
namespace WebView {
struct DOMNodeProperties {
enum class Type {
ComputedStyle,
Layout,
UsedFonts,
};
Type type { Type::ComputedStyle };
JsonValue properties;
};
}
namespace IPC {
template<>
ErrorOr<void> encode(Encoder&, WebView::DOMNodeProperties const&);
template<>
ErrorOr<WebView::DOMNodeProperties> decode(Decoder&);
}

View file

@ -21,6 +21,7 @@ class WebContentClient;
struct Attribute;
struct ConsoleOutput;
struct CookieStorageKey;
struct DOMNodeProperties;
struct Mutation;
struct ProcessHandle;
struct SearchEngine;

View file

@ -329,14 +329,14 @@ void ViewImplementation::get_hovered_node_id()
client().async_get_hovered_node_id(page_id());
}
void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)
void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, DOMNodeProperties::Type property_type, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)
{
client().async_inspect_dom_node(page_id(), node_id, pseudo_element);
client().async_inspect_dom_node(page_id(), property_type, node_id, pseudo_element);
}
void ViewImplementation::clear_inspected_dom_node()
{
inspect_dom_node(0, {});
client().async_clear_inspected_dom_node(page_id());
}
void ViewImplementation::highlight_dom_node(Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)

View file

@ -9,7 +9,6 @@
#include <AK/Forward.h>
#include <AK/Function.h>
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/LexicalPath.h>
#include <AK/Queue.h>
@ -26,6 +25,7 @@
#include <LibWeb/HTML/SelectItem.h>
#include <LibWeb/Page/EventResult.h>
#include <LibWeb/Page/InputEvent.h>
#include <LibWebView/DOMNodeProperties.h>
#include <LibWebView/Forward.h>
#include <LibWebView/PageInfo.h>
#include <LibWebView/WebContentClient.h>
@ -36,15 +36,6 @@ class ViewImplementation {
public:
virtual ~ViewImplementation();
struct DOMNodeProperties {
JsonObject computed_style;
JsonObject resolved_style;
JsonObject custom_properties;
JsonObject node_box_sizing;
JsonObject aria_properties_state;
JsonArray fonts;
};
static void for_each_view(Function<IterationDecision(ViewImplementation&)>);
static Optional<ViewImplementation&> find_view_by_id(u64);
@ -108,7 +99,7 @@ public:
void inspect_accessibility_tree();
void get_hovered_node_id();
void inspect_dom_node(Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element);
void inspect_dom_node(Web::UniqueNodeID node_id, DOMNodeProperties::Type, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element);
void clear_inspected_dom_node();
void highlight_dom_node(Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element);

View file

@ -277,8 +277,7 @@ void WebContentClient::did_get_source(u64 page_id, URL::URL url, URL::URL base_u
}
}
template<typename JsonType = JsonObject>
static JsonType parse_json(StringView json, StringView name)
static JsonObject parse_json(StringView json, StringView name)
{
auto parsed_tree = JsonValue::from_string(json);
if (parsed_tree.is_error()) {
@ -286,23 +285,12 @@ static JsonType parse_json(StringView json, StringView name)
return {};
}
if constexpr (IsSame<JsonType, JsonObject>) {
if (!parsed_tree.value().is_object()) {
dbgln("Expected {} to be an object: {}", name, parsed_tree.value());
return {};
}
return move(parsed_tree.release_value().as_object());
} else if constexpr (IsSame<JsonType, JsonArray>) {
if (!parsed_tree.value().is_array()) {
dbgln("Expected {} to be an array: {}", name, parsed_tree.value());
return {};
}
return move(parsed_tree.release_value().as_array());
} else {
static_assert(DependentFalse<JsonType>);
if (!parsed_tree.value().is_object()) {
dbgln("Expected {} to be an object: {}", name, parsed_tree.value());
return {};
}
return move(parsed_tree.release_value().as_object());
}
void WebContentClient::did_inspect_dom_tree(u64 page_id, String dom_tree)
@ -313,26 +301,12 @@ void WebContentClient::did_inspect_dom_tree(u64 page_id, String dom_tree)
}
}
void WebContentClient::did_inspect_dom_node(u64 page_id, bool has_style, String computed_style, String resolved_style, String custom_properties, String node_box_sizing, String aria_properties_state, String fonts)
void WebContentClient::did_inspect_dom_node(u64 page_id, DOMNodeProperties properties)
{
auto view = view_for_page_id(page_id);
if (!view.has_value() || !view->on_received_dom_node_properties)
return;
ViewImplementation::DOMNodeProperties properties;
if (has_style) {
properties = ViewImplementation::DOMNodeProperties {
.computed_style = parse_json(computed_style, "computed style"sv),
.resolved_style = parse_json(resolved_style, "resolved style"sv),
.custom_properties = parse_json(custom_properties, "custom properties"sv),
.node_box_sizing = parse_json(node_box_sizing, "node box sizing"sv),
.aria_properties_state = parse_json(aria_properties_state, "aria properties state"sv),
.fonts = parse_json<JsonArray>(fonts, "fonts"sv),
};
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_received_dom_node_properties)
view->on_received_dom_node_properties(move(properties));
}
view->on_received_dom_node_properties(move(properties));
}
void WebContentClient::did_inspect_accessibility_tree(u64 page_id, String accessibility_tree)

View file

@ -77,7 +77,7 @@ private:
virtual void did_request_media_context_menu(u64 page_id, Gfx::IntPoint, ByteString, unsigned, Web::Page::MediaContextMenu) override;
virtual void did_get_source(u64 page_id, URL::URL, URL::URL, String) override;
virtual void did_inspect_dom_tree(u64 page_id, String) override;
virtual void did_inspect_dom_node(u64 page_id, bool has_style, String computed_style, String resolved_style, String custom_properties, String node_box_sizing, String aria_properties_state, String fonts) override;
virtual void did_inspect_dom_node(u64 page_id, DOMNodeProperties) 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_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id) override;

View file

@ -438,7 +438,121 @@ void ConnectionFromClient::inspect_dom_tree(u64 page_id)
}
}
void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)
void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)
{
auto page = this->page(page_id);
if (!page.has_value())
return;
clear_inspected_dom_node(page_id);
auto* node = Web::DOM::Node::from_unique_id(node_id);
// Nodes without layout (aka non-visible nodes) don't have style computed.
if (!node || !node->layout_node() || !node->is_element()) {
async_did_inspect_dom_node(page_id, { property_type, {} });
return;
}
auto& element = as<Web::DOM::Element>(*node);
node->document().set_inspected_node(node);
GC::Ptr<Web::CSS::ComputedProperties> properties;
if (pseudo_element.has_value()) {
if (auto pseudo_element_node = element.get_pseudo_element_node(*pseudo_element))
properties = element.pseudo_element_computed_properties(*pseudo_element);
} else {
properties = element.computed_properties();
}
if (!properties) {
async_did_inspect_dom_node(page_id, { property_type, {} });
return;
}
auto serialize_computed_style = [&]() {
JsonObject serialized;
properties->for_each_property([&](auto property_id, auto& value) {
serialized.set(
Web::CSS::string_from_property_id(property_id),
value.to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
});
return serialized;
};
auto serialize_layout = [&](Web::Layout::Node const* layout_node) {
if (!layout_node || !layout_node->is_box() || !layout_node->first_paintable() || !layout_node->first_paintable()->is_paintable_box()) {
return JsonObject {};
}
auto const& paintable_box = as<Web::Painting::PaintableBox>(*layout_node->first_paintable());
auto const& box_model = paintable_box.box_model();
JsonObject serialized;
serialized.set("width"sv, paintable_box.content_width().to_double());
serialized.set("height"sv, paintable_box.content_height().to_double());
serialized.set("padding-top"sv, box_model.padding.top.to_double());
serialized.set("padding-right"sv, box_model.padding.right.to_double());
serialized.set("padding-bottom"sv, box_model.padding.bottom.to_double());
serialized.set("padding-left"sv, box_model.padding.left.to_double());
serialized.set("margin-top"sv, box_model.margin.top.to_double());
serialized.set("margin-right"sv, box_model.margin.right.to_double());
serialized.set("margin-bottom"sv, box_model.margin.bottom.to_double());
serialized.set("margin-left"sv, box_model.margin.left.to_double());
serialized.set("border-top-width"sv, box_model.border.top.to_double());
serialized.set("border-right-width"sv, box_model.border.right.to_double());
serialized.set("border-bottom-width"sv, box_model.border.bottom.to_double());
serialized.set("border-left-width"sv, box_model.border.left.to_double());
serialized.set("box-sizing"sv, properties->property(Web::CSS::PropertyID::BoxSizing).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
serialized.set("display"sv, properties->property(Web::CSS::PropertyID::Display).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
serialized.set("float"sv, properties->property(Web::CSS::PropertyID::Float).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
serialized.set("line-height"sv, properties->property(Web::CSS::PropertyID::LineHeight).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
serialized.set("position"sv, properties->property(Web::CSS::PropertyID::Position).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
serialized.set("z-index"sv, properties->property(Web::CSS::PropertyID::ZIndex).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal));
return serialized;
};
auto serialize_used_fonts = [&]() {
JsonArray serialized;
properties->computed_font_list().for_each_font_entry([&](Gfx::FontCascadeList::Entry const& entry) {
auto const& font = *entry.font;
JsonObject font_object;
font_object.set("name"sv, font.family().to_string());
font_object.set("size"sv, font.point_size());
font_object.set("weight"sv, font.weight());
serialized.must_append(move(font_object));
});
return serialized;
};
JsonValue serialized;
switch (property_type) {
case WebView::DOMNodeProperties::Type::ComputedStyle:
serialized = serialize_computed_style();
break;
case WebView::DOMNodeProperties::Type::Layout:
serialized = serialize_layout(element.layout_node());
break;
case WebView::DOMNodeProperties::Type::UsedFonts:
serialized = serialize_used_fonts();
break;
}
async_did_inspect_dom_node(page_id, { property_type, move(serialized) });
}
void ConnectionFromClient::clear_inspected_dom_node(u64 page_id)
{
auto page = this->page(page_id);
if (!page.has_value())
@ -449,168 +563,6 @@ void ConnectionFromClient::inspect_dom_node(u64 page_id, Web::UniqueNodeID node_
navigable->active_document()->set_inspected_node(nullptr);
}
}
auto* node = Web::DOM::Node::from_unique_id(node_id);
// Note: Nodes without layout (aka non-visible nodes, don't have style computed)
if (!node || !node->layout_node()) {
async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {});
return;
}
node->document().set_inspected_node(node);
if (node->is_element()) {
auto& element = as<Web::DOM::Element>(*node);
if (!element.computed_properties()) {
async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {});
return;
}
auto serialize_json = [](Web::CSS::ComputedProperties const& properties) {
StringBuilder builder;
auto serializer = MUST(JsonObjectSerializer<>::try_create(builder));
properties.for_each_property([&](auto property_id, auto& value) {
MUST(serializer.add(Web::CSS::string_from_property_id(property_id), value.to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal).to_byte_string()));
});
MUST(serializer.finish());
return MUST(builder.to_string());
};
auto serialize_custom_properties_json = [](Web::DOM::Element const& element, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) {
StringBuilder builder;
auto serializer = MUST(JsonObjectSerializer<>::try_create(builder));
HashTable<FlyString> seen_properties;
auto const* element_to_check = &element;
auto pseudo_element_to_check = pseudo_element;
while (element_to_check) {
for (auto const& property : element_to_check->custom_properties(pseudo_element_to_check)) {
if (!seen_properties.contains(property.key)) {
seen_properties.set(property.key);
MUST(serializer.add(property.key, property.value.value->to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
}
}
if (pseudo_element_to_check.has_value()) {
pseudo_element_to_check.clear();
} else {
element_to_check = element_to_check->parent_element();
}
}
MUST(serializer.finish());
return MUST(builder.to_string());
};
auto serialize_node_box_sizing_json = [](Web::Layout::Node const* layout_node, GC::Ptr<Web::CSS::ComputedProperties> properties) {
if (!layout_node || !layout_node->is_box() || !layout_node->first_paintable() || !layout_node->first_paintable()->is_paintable_box()) {
return "{}"_string;
}
auto const& paintable_box = as<Web::Painting::PaintableBox>(*layout_node->first_paintable());
auto const& box_model = paintable_box.box_model();
StringBuilder builder;
auto serializer = MUST(JsonObjectSerializer<>::try_create(builder));
MUST(serializer.add("width"sv, paintable_box.content_width().to_double()));
MUST(serializer.add("height"sv, paintable_box.content_height().to_double()));
MUST(serializer.add("padding-top"sv, box_model.padding.top.to_double()));
MUST(serializer.add("padding-right"sv, box_model.padding.right.to_double()));
MUST(serializer.add("padding-bottom"sv, box_model.padding.bottom.to_double()));
MUST(serializer.add("padding-left"sv, box_model.padding.left.to_double()));
MUST(serializer.add("margin-top"sv, box_model.margin.top.to_double()));
MUST(serializer.add("margin-right"sv, box_model.margin.right.to_double()));
MUST(serializer.add("margin-bottom"sv, box_model.margin.bottom.to_double()));
MUST(serializer.add("margin-left"sv, box_model.margin.left.to_double()));
MUST(serializer.add("border-top-width"sv, box_model.border.top.to_double()));
MUST(serializer.add("border-right-width"sv, box_model.border.right.to_double()));
MUST(serializer.add("border-bottom-width"sv, box_model.border.bottom.to_double()));
MUST(serializer.add("border-left-width"sv, box_model.border.left.to_double()));
if (properties) {
MUST(serializer.add("box-sizing"sv, properties->property(Web::CSS::PropertyID::BoxSizing).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
MUST(serializer.add("display"sv, properties->property(Web::CSS::PropertyID::Display).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
MUST(serializer.add("float"sv, properties->property(Web::CSS::PropertyID::Float).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
MUST(serializer.add("line-height"sv, properties->property(Web::CSS::PropertyID::LineHeight).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
MUST(serializer.add("position"sv, properties->property(Web::CSS::PropertyID::Position).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
MUST(serializer.add("z-index"sv, properties->property(Web::CSS::PropertyID::ZIndex).to_string(Web::CSS::CSSStyleValue::SerializationMode::Normal)));
}
MUST(serializer.finish());
return MUST(builder.to_string());
};
auto serialize_aria_properties_state_json = [](Web::DOM::Element const& element) {
auto role_name = element.role_or_default();
if (!role_name.has_value()) {
return "{}"_string;
}
auto aria_data = MUST(Web::ARIA::AriaData::build_data(element));
auto role = MUST(Web::ARIA::RoleType::build_role_object(role_name.value(), element.is_focusable(), *aria_data));
StringBuilder builder;
auto serializer = MUST(JsonObjectSerializer<>::try_create(builder));
MUST(role->serialize_as_json(serializer));
MUST(serializer.finish());
return MUST(builder.to_string());
};
auto serialize_fonts_json = [](Web::CSS::ComputedProperties const& properties) {
StringBuilder builder;
auto serializer = MUST(JsonArraySerializer<>::try_create(builder));
auto const& font_list = properties.computed_font_list();
font_list.for_each_font_entry([&serializer](Gfx::FontCascadeList::Entry const& entry) {
auto const& font = entry.font;
auto font_json_object = MUST(serializer.add_object());
MUST(font_json_object.add("name"sv, font->family()));
MUST(font_json_object.add("size"sv, font->point_size()));
MUST(font_json_object.add("weight"sv, font->weight()));
MUST(font_json_object.finish());
});
MUST(serializer.finish());
return MUST(builder.to_string());
};
if (pseudo_element.has_value()) {
auto pseudo_element_node = element.get_pseudo_element_node(pseudo_element.value());
if (!pseudo_element_node) {
async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {});
return;
}
auto pseudo_element_style = element.pseudo_element_computed_properties(pseudo_element.value());
String computed_values;
String fonts_json;
String resolved_values;
if (pseudo_element_style) {
computed_values = serialize_json(*pseudo_element_style);
fonts_json = serialize_fonts_json(*pseudo_element_style);
resolved_values = serialize_json(element.resolved_css_values(pseudo_element.value()));
} else {
dbgln("Inspected pseudo-element has no computed style.");
}
auto custom_properties_json = serialize_custom_properties_json(element, pseudo_element);
auto node_box_sizing_json = serialize_node_box_sizing_json(pseudo_element_node.ptr(), pseudo_element_style);
async_did_inspect_dom_node(page_id, true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json, "{}"_string, fonts_json);
return;
}
auto computed_values = serialize_json(*element.computed_properties());
auto resolved_values = serialize_json(element.resolved_css_values());
auto custom_properties_json = serialize_custom_properties_json(element, {});
auto node_box_sizing_json = serialize_node_box_sizing_json(element.layout_node(), element.computed_properties());
auto aria_properties_state_json = serialize_aria_properties_state_json(element);
auto fonts_json = serialize_fonts_json(*element.computed_properties());
async_did_inspect_dom_node(page_id, true, computed_values, resolved_values, custom_properties_json, node_box_sizing_json, aria_properties_state_json, move(fonts_json));
return;
}
async_did_inspect_dom_node(page_id, false, String {}, String {}, String {}, String {}, String {}, String {});
}
void ConnectionFromClient::highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element)

View file

@ -22,6 +22,7 @@
#include <LibWeb/Page/EventResult.h>
#include <LibWeb/Page/InputEvent.h>
#include <LibWeb/Platform/Timer.h>
#include <LibWebView/DOMNodeProperties.h>
#include <LibWebView/Forward.h>
#include <LibWebView/PageInfo.h>
#include <WebContent/Forward.h>
@ -75,7 +76,8 @@ private:
virtual void debug_request(u64 page_id, ByteString, ByteString) override;
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 node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) override;
virtual void inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) override;
virtual void clear_inspected_dom_node(u64 page_id) override;
virtual void highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) override;
virtual void inspect_accessibility_tree(u64 page_id) override;
virtual void get_hovered_node_id(u64 page_id) override;

View file

@ -17,6 +17,7 @@
#include <LibWeb/Page/Page.h>
#include <LibWebView/Attribute.h>
#include <LibWebView/ConsoleOutput.h>
#include <LibWebView/DOMNodeProperties.h>
#include <LibWebView/Mutation.h>
#include <LibWebView/PageInfo.h>
#include <LibWebView/ProcessHandle.h>
@ -52,7 +53,7 @@ endpoint WebContentClient
did_get_source(u64 page_id, URL::URL url, URL::URL base_url, String source) =|
did_inspect_dom_tree(u64 page_id, String dom_tree) =|
did_inspect_dom_node(u64 page_id, bool has_style, String computed_style, String resolved_style, String custom_properties, String node_box_sizing, String aria_properties_state, String fonts) =|
did_inspect_dom_node(u64 page_id, WebView::DOMNodeProperties properties) =|
did_inspect_accessibility_tree(u64 page_id, String accessibility_tree) =|
did_get_hovered_node_id(u64 page_id, Web::UniqueNodeID node_id) =|
did_finish_editing_dom_node(u64 page_id, Optional<Web::UniqueNodeID> node_id) =|

View file

@ -12,6 +12,7 @@
#include <LibWeb/Page/InputEvent.h>
#include <LibWeb/WebDriver/ExecuteScript.h>
#include <LibWebView/Attribute.h>
#include <LibWebView/DOMNodeProperties.h>
#include <LibWebView/PageInfo.h>
endpoint WebContentServer
@ -44,7 +45,8 @@ endpoint WebContentServer
debug_request(u64 page_id, ByteString request, ByteString argument) =|
get_source(u64 page_id) =|
inspect_dom_tree(u64 page_id) =|
inspect_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) =|
inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) =|
clear_inspected_dom_node(u64 page_id) =|
highlight_dom_node(u64 page_id, Web::UniqueNodeID node_id, Optional<Web::CSS::Selector::PseudoElement::Type> pseudo_element) =|
inspect_accessibility_tree(u64 page_id) =|
get_hovered_node_id(u64 page_id) =|