/* * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace DevTools { NonnullRefPtr PageStyleActor::create(DevToolsServer& devtools, String name, WeakPtr inspector) { return adopt_ref(*new PageStyleActor(devtools, move(name), move(inspector))); } PageStyleActor::PageStyleActor(DevToolsServer& devtools, String name, WeakPtr inspector) : Actor(devtools, move(name)) , m_inspector(move(inspector)) { } PageStyleActor::~PageStyleActor() = default; void PageStyleActor::handle_message(StringView type, JsonObject const& message) { JsonObject response; response.set("from"sv, name()); if (type == "getApplied"sv) { // FIXME: This provides information to the "styles" pane in the inspector tab, which allows toggling and editing // styles live. We do not yet support figuring out the list of styles that apply to a specific node. response.set("entries"sv, JsonArray {}); send_message(move(response)); return; } if (type == "getComputed"sv) { auto node = message.get_string("node"sv); if (!node.has_value()) { send_missing_parameter_error("node"sv); return; } inspect_dom_node(*node, [](auto& self, auto const& properties, auto block_token) { self.received_computed_style(properties.computed_style, move(block_token)); }); return; } if (type == "getLayout"sv) { auto node = message.get_string("node"sv); if (!node.has_value()) { send_missing_parameter_error("node"sv); return; } inspect_dom_node(*node, [](auto& self, auto const& properties, auto block_token) { self.received_layout(properties.computed_style, properties.node_box_sizing, move(block_token)); }); return; } if (type == "isPositionEditable") { response.set("value"sv, false); send_message(move(response)); return; } send_unrecognized_packet_type_error(type); } JsonValue PageStyleActor::serialize_style() const { JsonObject traits; traits.set("fontStyleLevel4"sv, true); traits.set("fontWeightLevel4"sv, true); traits.set("fontStretchLevel4"sv, true); traits.set("fontVariations"sv, true); JsonObject style; style.set("actor"sv, name()); style.set("traits"sv, move(traits)); return style; } template void PageStyleActor::inspect_dom_node(StringView node_actor, Callback&& callback) { auto tab = InspectorActor::tab_for(m_inspector); auto walker = InspectorActor::walker_for(m_inspector); if (!tab || !walker) return; auto const& dom_node = walker->dom_node(node_actor); if (!dom_node.has_value()) return; auto block_token = block_responses(); devtools().delegate().inspect_dom_node( tab->description(), dom_node->id, dom_node->pseudo_element, [weak_self = make_weak_ptr(), block_token = move(block_token), callback = forward(callback)](ErrorOr properties) mutable { if (properties.is_error()) { dbgln_if(DEVTOOLS_DEBUG, "Unable to inspect DOM node: {}", properties.error()); return; } if (auto self = weak_self.strong_ref()) callback(*self, properties.value(), move(block_token)); }); } void PageStyleActor::received_layout(JsonObject const& computed_style, JsonObject const& node_box_sizing, BlockToken block_token) { JsonObject message; message.set("from"sv, name()); message.set("autoMargins"sv, JsonObject {}); auto pixel_value = [&](auto const& object, auto key) { return object.get_double_with_precision_loss(key).value_or(0); }; auto set_pixel_value_from = [&](auto const& object, auto object_key, auto message_key) { message.set(message_key, MUST(String::formatted("{}px", pixel_value(object, object_key)))); }; auto set_computed_value_from = [&](auto const& object, auto key) { message.set(key, object.get_string(key).value_or(String {})); }; message.set("width"sv, pixel_value(node_box_sizing, "content_width"sv)); message.set("height"sv, pixel_value(node_box_sizing, "content_height"sv)); // FIXME: This response should also contain "top", "right", "bottom", and "left", but our box model metrics in // WebContent do not provide this information. set_pixel_value_from(node_box_sizing, "border_top"sv, "border-top-width"sv); set_pixel_value_from(node_box_sizing, "border_right"sv, "border-right-width"sv); set_pixel_value_from(node_box_sizing, "border_bottom"sv, "border-bottom-width"sv); set_pixel_value_from(node_box_sizing, "border_left"sv, "border-left-width"sv); set_pixel_value_from(node_box_sizing, "margin_top"sv, "margin-top"sv); set_pixel_value_from(node_box_sizing, "margin_right"sv, "margin-right"sv); set_pixel_value_from(node_box_sizing, "margin_bottom"sv, "margin-bottom"sv); set_pixel_value_from(node_box_sizing, "margin_left"sv, "margin-left"sv); set_pixel_value_from(node_box_sizing, "padding_top"sv, "padding-top"sv); set_pixel_value_from(node_box_sizing, "padding_right"sv, "padding-right"sv); set_pixel_value_from(node_box_sizing, "padding_bottom"sv, "padding-bottom"sv); set_pixel_value_from(node_box_sizing, "padding_left"sv, "padding-left"sv); set_computed_value_from(computed_style, "box-sizing"sv); set_computed_value_from(computed_style, "display"sv); set_computed_value_from(computed_style, "float"sv); set_computed_value_from(computed_style, "line-height"sv); set_computed_value_from(computed_style, "position"sv); set_computed_value_from(computed_style, "z-index"sv); send_message(move(message), move(block_token)); } void PageStyleActor::received_computed_style(JsonObject const& computed_style, BlockToken block_token) { JsonObject computed; computed_style.for_each_member([&](String const& name, JsonValue const& value) { JsonObject property; property.set("matched"sv, true); property.set("value"sv, value); computed.set(name, move(property)); }); JsonObject message; message.set("from"sv, name()); message.set("computed"sv, move(computed)); send_message(move(message), move(block_token)); } }