From daca9f599522994caf7866eb72722de9366bd4b5 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Wed, 19 Mar 2025 15:50:56 -0400 Subject: [PATCH] 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. --- Libraries/LibDevTools/Actors/FrameActor.cpp | 1 + .../LibDevTools/Actors/PageStyleActor.cpp | 85 +++--- Libraries/LibDevTools/Actors/PageStyleActor.h | 15 +- Libraries/LibDevTools/DevToolsDelegate.h | 7 +- Libraries/LibWebView/Application.cpp | 32 +- Libraries/LibWebView/Application.h | 4 +- Libraries/LibWebView/CMakeLists.txt | 1 + Libraries/LibWebView/DOMNodeProperties.cpp | 26 ++ Libraries/LibWebView/DOMNodeProperties.h | 35 +++ Libraries/LibWebView/Forward.h | 1 + Libraries/LibWebView/ViewImplementation.cpp | 6 +- Libraries/LibWebView/ViewImplementation.h | 13 +- Libraries/LibWebView/WebContentClient.cpp | 46 +-- Libraries/LibWebView/WebContentClient.h | 2 +- Services/WebContent/ConnectionFromClient.cpp | 278 ++++++++---------- Services/WebContent/ConnectionFromClient.h | 4 +- Services/WebContent/WebContentClient.ipc | 3 +- Services/WebContent/WebContentServer.ipc | 4 +- 18 files changed, 287 insertions(+), 276 deletions(-) create mode 100644 Libraries/LibWebView/DOMNodeProperties.cpp create mode 100644 Libraries/LibWebView/DOMNodeProperties.h diff --git a/Libraries/LibDevTools/Actors/FrameActor.cpp b/Libraries/LibDevTools/Actors/FrameActor.cpp index e413386d048..2fe33be7690 100644 --- a/Libraries/LibDevTools/Actors/FrameActor.cpp +++ b/Libraries/LibDevTools/Actors/FrameActor.cpp @@ -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()); diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.cpp b/Libraries/LibDevTools/Actors/PageStyleActor.cpp index 6600cc120b9..7db023d5cfc 100644 --- a/Libraries/LibDevTools/Actors/PageStyleActor.cpp +++ b/Libraries/LibDevTools/Actors/PageStyleActor.cpp @@ -4,6 +4,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include @@ -111,9 +113,20 @@ PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtrdescription(), + [weak_self = make_weak_ptr()](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(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(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(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 -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(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)](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)); } } diff --git a/Libraries/LibDevTools/Actors/PageStyleActor.h b/Libraries/LibDevTools/Actors/PageStyleActor.h index 54a9e70edc3..469e44558b2 100644 --- a/Libraries/LibDevTools/Actors/PageStyleActor.h +++ b/Libraries/LibDevTools/Actors/PageStyleActor.h @@ -6,19 +6,12 @@ #pragma once -#include -#include #include #include +#include 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 - 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 m_inspector; + + Vector m_pending_inspect_requests; }; } diff --git a/Libraries/LibDevTools/DevToolsDelegate.h b/Libraries/LibDevTools/DevToolsDelegate.h index a050087bc63..380019008b4 100644 --- a/Libraries/LibDevTools/DevToolsDelegate.h +++ b/Libraries/LibDevTools/DevToolsDelegate.h @@ -17,6 +17,7 @@ #include #include #include +#include #include namespace DevTools { @@ -31,8 +32,10 @@ public: using OnTabInspectionComplete = Function)>; virtual void inspect_tab(TabDescription const&, OnTabInspectionComplete) const { } - using OnDOMNodeInspectionComplete = Function)>; - virtual void inspect_dom_node(TabDescription const&, Web::UniqueNodeID, Optional, OnDOMNodeInspectionComplete) const { } + using OnDOMNodePropertiesReceived = Function; + 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) const { } virtual void clear_inspected_dom_node(TabDescription const&) const { } virtual void highlight_dom_node(TabDescription const&, Web::UniqueNodeID, Optional) const { } diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp index cd866dfc055..cd6871d1170 100644 --- a/Libraries/LibWebView/Application.cpp +++ b/Libraries/LibWebView/Application.cpp @@ -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 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 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 diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h index 055c78669c5..b51f35c2cf6 100644 --- a/Libraries/LibWebView/Application.h +++ b/Libraries/LibWebView/Application.h @@ -97,7 +97,9 @@ private: virtual Vector tab_list() const override; virtual Vector 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, 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) const override; virtual void clear_inspected_dom_node(DevTools::TabDescription const&) 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; diff --git a/Libraries/LibWebView/CMakeLists.txt b/Libraries/LibWebView/CMakeLists.txt index a9476ec7276..f628ab8fbf9 100644 --- a/Libraries/LibWebView/CMakeLists.txt +++ b/Libraries/LibWebView/CMakeLists.txt @@ -7,6 +7,7 @@ set(SOURCES ConsoleOutput.cpp CookieJar.cpp Database.cpp + DOMNodeProperties.cpp HelperProcess.cpp Mutation.cpp Plugins/FontPlugin.cpp diff --git a/Libraries/LibWebView/DOMNodeProperties.cpp b/Libraries/LibWebView/DOMNodeProperties.cpp new file mode 100644 index 00000000000..1389e37d5a2 --- /dev/null +++ b/Libraries/LibWebView/DOMNodeProperties.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +template<> +ErrorOr IPC::encode(Encoder& encoder, WebView::DOMNodeProperties const& attribute) +{ + TRY(encoder.encode(attribute.type)); + TRY(encoder.encode(attribute.properties)); + return {}; +} + +template<> +ErrorOr IPC::decode(Decoder& decoder) +{ + auto type = TRY(decoder.decode()); + auto properties = TRY(decoder.decode()); + + return WebView::DOMNodeProperties { type, move(properties) }; +} diff --git a/Libraries/LibWebView/DOMNodeProperties.h b/Libraries/LibWebView/DOMNodeProperties.h new file mode 100644 index 00000000000..ae71db6db1b --- /dev/null +++ b/Libraries/LibWebView/DOMNodeProperties.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace WebView { + +struct DOMNodeProperties { + enum class Type { + ComputedStyle, + Layout, + UsedFonts, + }; + + Type type { Type::ComputedStyle }; + JsonValue properties; +}; + +} + +namespace IPC { + +template<> +ErrorOr encode(Encoder&, WebView::DOMNodeProperties const&); + +template<> +ErrorOr decode(Decoder&); + +} diff --git a/Libraries/LibWebView/Forward.h b/Libraries/LibWebView/Forward.h index f5d98001318..2056ee0d205 100644 --- a/Libraries/LibWebView/Forward.h +++ b/Libraries/LibWebView/Forward.h @@ -21,6 +21,7 @@ class WebContentClient; struct Attribute; struct ConsoleOutput; struct CookieStorageKey; +struct DOMNodeProperties; struct Mutation; struct ProcessHandle; struct SearchEngine; diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index 41113bef978..e90c4fd1f22 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -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 pseudo_element) +void ViewImplementation::inspect_dom_node(Web::UniqueNodeID node_id, DOMNodeProperties::Type property_type, Optional 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 pseudo_element) diff --git a/Libraries/LibWebView/ViewImplementation.h b/Libraries/LibWebView/ViewImplementation.h index 0fcfbebd140..3c58e1979f0 100644 --- a/Libraries/LibWebView/ViewImplementation.h +++ b/Libraries/LibWebView/ViewImplementation.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -26,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -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); static Optional 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 pseudo_element); + void inspect_dom_node(Web::UniqueNodeID node_id, DOMNodeProperties::Type, Optional pseudo_element); void clear_inspected_dom_node(); void highlight_dom_node(Web::UniqueNodeID node_id, Optional pseudo_element); diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index 462f382e9eb..d822bc4e88d 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -277,8 +277,7 @@ void WebContentClient::did_get_source(u64 page_id, URL::URL url, URL::URL base_u } } -template -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) { - 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) { - 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); + 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(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) diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index d01b5148c27..205f433c2ea 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -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 node_id) override; diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index ab1ca3ec574..8c9638e2fcf 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -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 pseudo_element) +void ConnectionFromClient::inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional 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(*node); + node->document().set_inspected_node(node); + + GC::Ptr 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(*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(*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 pseudo_element) { - StringBuilder builder; - auto serializer = MUST(JsonObjectSerializer<>::try_create(builder)); - HashTable 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 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(*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 pseudo_element) diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 33688ecae18..3e6d8c55890 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -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 pseudo_element) override; + virtual void inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type, Web::UniqueNodeID node_id, Optional 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 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/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc index b7b6d076b33..adfc6bbf2fb 100644 --- a/Services/WebContent/WebContentClient.ipc +++ b/Services/WebContent/WebContentClient.ipc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -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 node_id) =| diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 19f9d3fdf07..bc40bc2f4d5 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -12,6 +12,7 @@ #include #include #include +#include #include 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 pseudo_element) =| + inspect_dom_node(u64 page_id, WebView::DOMNodeProperties::Type property_type, Web::UniqueNodeID node_id, Optional pseudo_element) =| + clear_inspected_dom_node(u64 page_id) =| 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) =|