/* * Copyright (c) 2023, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include namespace WebView { static ErrorOr parse_json_tree(StringView json) { auto parsed_tree = TRY(JsonValue::from_string(json)); if (!parsed_tree.is_object()) return Error::from_string_literal("Expected tree to be a JSON object"); return parsed_tree; } InspectorClient::InspectorClient(ViewImplementation& content_web_view, ViewImplementation& inspector_web_view) : m_content_web_view(content_web_view) , m_inspector_web_view(inspector_web_view) { m_content_web_view.on_received_dom_tree = [this](auto const& dom_tree) { if (auto result = parse_json_tree(dom_tree); result.is_error()) dbgln("Failed to load DOM tree: {}", result.error()); else m_dom_tree = result.release_value(); maybe_load_inspector(); }; m_content_web_view.on_received_accessibility_tree = [this](auto const& dom_tree) { if (auto result = parse_json_tree(dom_tree); result.is_error()) dbgln("Failed to load accessibility tree: {}", result.error()); else m_accessibility_tree = result.release_value(); maybe_load_inspector(); }; m_inspector_web_view.enable_inspector_prototype(); m_inspector_web_view.use_native_user_style_sheet(); m_inspector_web_view.on_inspector_loaded = [this]() { m_inspector_loaded = true; if (m_pending_selection.has_value()) select_node(m_pending_selection.release_value()); else select_default_node(); }; m_inspector_web_view.on_inspector_selected_dom_node = [this](auto node_id, auto const& pseudo_element) { auto inspected_node_properties = m_content_web_view.inspect_dom_node(node_id, pseudo_element); if (on_dom_node_properties_received) on_dom_node_properties_received(move(inspected_node_properties)); }; inspect(); } InspectorClient::~InspectorClient() { m_content_web_view.on_received_dom_tree = nullptr; m_content_web_view.on_received_accessibility_tree = nullptr; } void InspectorClient::inspect() { m_content_web_view.inspect_dom_tree(); m_content_web_view.inspect_accessibility_tree(); } void InspectorClient::reset() { m_dom_tree.clear(); m_accessibility_tree.clear(); m_body_node_id.clear(); m_pending_selection.clear(); m_inspector_loaded = false; } void InspectorClient::select_hovered_node() { auto hovered_node_id = m_content_web_view.get_hovered_node_id(); select_node(hovered_node_id); } void InspectorClient::select_default_node() { if (m_body_node_id.has_value()) select_node(*m_body_node_id); } void InspectorClient::clear_selection() { m_content_web_view.clear_inspected_dom_node(); static constexpr auto script = "inspector.clearInspectedDOMNode();"sv; m_inspector_web_view.run_javascript(script); } void InspectorClient::select_node(i32 node_id) { if (!m_inspector_loaded) { m_pending_selection = node_id; return; } auto script = MUST(String::formatted("inspector.inspectDOMNodeID({});", node_id)); m_inspector_web_view.run_javascript(script); } void InspectorClient::maybe_load_inspector() { if (!m_dom_tree.has_value() || !m_accessibility_tree.has_value()) return; StringBuilder builder; builder.append(R"~~~(
)~~~"sv); generate_dom_tree(builder); builder.append(R"~~~(
)~~~"sv); generate_accessibility_tree(builder); builder.append(R"~~~(
)~~~"sv); m_inspector_web_view.load_html(builder.string_view()); } template static void generate_tree(StringBuilder& builder, JsonObject const& node, Generator&& generator) { if (auto children = node.get_array("children"sv); children.has_value() && !children->is_empty()) { auto name = node.get_deprecated_string("name"sv).value_or({}); builder.append("
"sv); builder.append(""sv); generator(node); builder.append(""sv); children->for_each([&](auto const& child) { builder.append("
"sv); generate_tree(builder, child.as_object(), generator); builder.append("
"sv); }); builder.append("
"sv); } else { generator(node); } } void InspectorClient::generate_dom_tree(StringBuilder& builder) { generate_tree(builder, m_dom_tree->as_object(), [&](JsonObject const& node) { auto type = node.get_deprecated_string("type"sv).value_or("unknown"sv); auto name = node.get_deprecated_string("name"sv).value_or({}); StringBuilder data_attributes; auto append_data_attribute = [&](auto name, auto value) { if (!data_attributes.is_empty()) data_attributes.append(' '); data_attributes.appendff("data-{}=\"{}\"", name, value); }; if (auto pseudo_element = node.get_integer("pseudo-element"sv); pseudo_element.has_value()) { append_data_attribute("id"sv, node.get_integer("parent-id"sv).value()); append_data_attribute("pseudo-element"sv, *pseudo_element); } else { append_data_attribute("id"sv, node.get_integer("id"sv).value()); } if (type == "text"sv) { auto deprecated_text = node.get_deprecated_string("text"sv).release_value(); deprecated_text = escape_html_entities(deprecated_text); if (auto text = MUST(Web::Infra::strip_and_collapse_whitespace(deprecated_text)); text.is_empty()) { builder.appendff("", data_attributes.string_view()); builder.append(name); builder.append(""sv); } else { builder.appendff("", data_attributes.string_view()); builder.append(text); builder.append(""sv); } return; } if (type == "comment"sv) { auto comment = node.get_deprecated_string("data"sv).release_value(); comment = escape_html_entities(comment); builder.appendff("", data_attributes.string_view()); builder.appendff("<!--{}-->", comment); builder.append(""sv); return; } if (type == "shadow-root"sv) { auto mode = node.get_deprecated_string("mode"sv).release_value(); builder.appendff("", data_attributes.string_view()); builder.appendff("{} ({})", name, mode); builder.append(""sv); return; } if (type != "element"sv) { builder.appendff("", data_attributes.string_view()); builder.appendff(name); builder.append(""sv); return; } if (name.equals_ignoring_ascii_case("BODY"sv)) m_body_node_id = node.get_integer("id"sv).value(); builder.appendff("", data_attributes.string_view()); builder.append("<"sv); builder.appendff("{}", name.to_lowercase()); if (auto attributes = node.get_object("attributes"sv); attributes.has_value()) { attributes->for_each_member([&builder](auto const& name, auto const& value) { builder.append(" "sv); builder.appendff("{}", name); builder.append('='); builder.appendff("\"{}\"", value); }); } builder.append(">"sv); builder.append(""sv); }); } void InspectorClient::generate_accessibility_tree(StringBuilder& builder) { generate_tree(builder, m_accessibility_tree->as_object(), [&](JsonObject const& node) { auto type = node.get_deprecated_string("type"sv).value_or("unknown"sv); auto role = node.get_deprecated_string("role"sv).value_or({}); if (type == "text"sv) { auto text = node.get_deprecated_string("text"sv).release_value(); text = escape_html_entities(text); builder.appendff(""); builder.append(MUST(Web::Infra::strip_and_collapse_whitespace(text))); builder.append(""sv); return; } if (type != "element"sv) { builder.appendff(""); builder.appendff(role.to_lowercase()); builder.append(""sv); return; } auto name = node.get_deprecated_string("name"sv).value_or({}); auto description = node.get_deprecated_string("description"sv).value_or({}); builder.appendff(""); builder.append(role.to_lowercase()); builder.appendff(" name: \"{}\", description: \"{}\"", name, description); builder.append(""sv); }); } }