From 30fac0abb9a75f21d2bef860fe30811f911a416a Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sat, 4 Nov 2023 11:53:49 -0400 Subject: [PATCH] Browser: Convert the inspector widget to the WebView model class This creates a LibGUI-specific wrapper around the WebView model classes and uses it for the inspector widget tree / table view models. --- .../Applications/Browser/InspectorWidget.cpp | 19 +- Userland/Applications/Browser/ModelAdapter.h | 222 ++++++++++++++++++ 2 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 Userland/Applications/Browser/ModelAdapter.h diff --git a/Userland/Applications/Browser/InspectorWidget.cpp b/Userland/Applications/Browser/InspectorWidget.cpp index 852b8c7e10..a8f5461ceb 100644 --- a/Userland/Applications/Browser/InspectorWidget.cpp +++ b/Userland/Applications/Browser/InspectorWidget.cpp @@ -8,6 +8,7 @@ #include "InspectorWidget.h" #include "ElementSizePreviewWidget.h" +#include "ModelAdapter.h" #include #include #include @@ -15,11 +16,7 @@ #include #include #include -#include -#include -#include #include -#include namespace Browser { @@ -31,7 +28,7 @@ void InspectorWidget::set_selection(Selection selection) return; } - auto* model = verify_cast(m_dom_tree_view->model()); + auto* model = verify_cast(m_dom_tree_view->model()); auto index = model->index_for_node(selection.dom_node_id, selection.pseudo_element); if (!index.is_valid()) { dbgln("InspectorWidget told to inspect non-existent node: {}", selection.to_string()); @@ -137,7 +134,7 @@ void InspectorWidget::select_default_node() void InspectorWidget::set_dom_json(StringView json) { - m_dom_tree_view->set_model(WebView::DOMTreeModel::create(json, *m_dom_tree_view)); + m_dom_tree_view->set_model(DOMTreeModel::create(*m_dom_tree_view, json).release_value_but_fixme_should_propagate_errors()); m_dom_loaded = true; if (m_pending_selection.has_value()) @@ -167,13 +164,13 @@ void InspectorWidget::set_dom_node_properties_json(Selection selection, StringVi void InspectorWidget::load_style_json(StringView computed_values_json, StringView resolved_values_json, StringView custom_properties_json) { - m_computed_style_table_view->set_model(WebView::StylePropertiesModel::create(computed_values_json)); + m_computed_style_table_view->set_model(PropertyTableModel::create(PropertyTableModel::Type::StyleProperties, computed_values_json).release_value_but_fixme_should_propagate_errors()); m_computed_style_table_view->set_searchable(true); - m_resolved_style_table_view->set_model(WebView::StylePropertiesModel::create(resolved_values_json)); + m_resolved_style_table_view->set_model(PropertyTableModel::create(PropertyTableModel::Type::StyleProperties, resolved_values_json).release_value_but_fixme_should_propagate_errors()); m_resolved_style_table_view->set_searchable(true); - m_custom_properties_table_view->set_model(WebView::StylePropertiesModel::create(custom_properties_json)); + m_custom_properties_table_view->set_model(PropertyTableModel::create(PropertyTableModel::Type::StyleProperties, custom_properties_json).release_value_but_fixme_should_propagate_errors()); m_custom_properties_table_view->set_searchable(true); } @@ -206,7 +203,7 @@ void InspectorWidget::update_node_box_model(StringView node_box_sizing_json) void InspectorWidget::update_aria_properties_state_model(StringView aria_properties_state_json) { - m_aria_properties_state_view->set_model(WebView::AriaPropertiesStateModel::create(aria_properties_state_json).release_value_but_fixme_should_propagate_errors()); + m_aria_properties_state_view->set_model(PropertyTableModel::create(PropertyTableModel::Type::ARIAProperties, aria_properties_state_json).release_value_but_fixme_should_propagate_errors()); m_aria_properties_state_view->set_searchable(true); } @@ -232,7 +229,7 @@ void InspectorWidget::clear_style_json() void InspectorWidget::set_accessibility_json(StringView json) { - m_accessibility_tree_view->set_model(WebView::AccessibilityTreeModel::create(json, *m_accessibility_tree_view)); + m_accessibility_tree_view->set_model(TreeModel::create(TreeModel::Type::AccessibilityTree, json).release_value_but_fixme_should_propagate_errors()); } } diff --git a/Userland/Applications/Browser/ModelAdapter.h b/Userland/Applications/Browser/ModelAdapter.h new file mode 100644 index 0000000000..93ba57d3ac --- /dev/null +++ b/Userland/Applications/Browser/ModelAdapter.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Browser { + +template +class ModelAdapter : public GUI::Model { +public: + using Type = typename ModelType::Type; + + static ErrorOr> create(Type type, StringView model) + { + return adopt_ref(*new ModelAdapter(type, TRY(parse_json_model(model)))); + } + + virtual ~ModelAdapter() = default; + + virtual int row_count(GUI::ModelIndex const& parent) const override + { + return m_model.row_count(to_web_view_model_index(parent)); + } + + virtual int column_count(GUI::ModelIndex const& parent) const override + { + return m_model.column_count(to_web_view_model_index(parent)); + } + + virtual GUI::ModelIndex index(int row, int column, GUI::ModelIndex const& parent) const override + { + auto index = m_model.index(row, column, to_web_view_model_index(parent)); + return to_gui_model_index(index); + } + + virtual GUI::ModelIndex parent_index(GUI::ModelIndex const& index) const override + { + if constexpr (requires { m_model.parent(declval()); }) { + auto parent = m_model.parent(to_web_view_model_index(index)); + return to_gui_model_index(parent); + } else { + return {}; + } + } + + virtual GUI::Variant data(GUI::ModelIndex const& index, GUI::ModelRole role) const override + { + if (role == GUI::ModelRole::Display) { + auto text = m_model.text_for_display(to_web_view_model_index(index)); + return text.to_deprecated_string(); + } + + return {}; + } + + GUI::ModelIndex index_for_node(i32 node_id, Optional const& pseudo_element) const + { + if constexpr (requires { m_model.index_for_node(node_id, pseudo_element); }) { + auto parent = m_model.index_for_node(node_id, pseudo_element); + return to_gui_model_index(parent); + } else { + return {}; + } + } + +protected: + ModelAdapter(Type type, JsonValue model) + : m_model(type, move(model)) + { + } + + static ErrorOr parse_json_model(StringView model) + { + auto json_model = TRY(JsonValue::from_string(model)); + if (!json_model.is_object()) + return Error::from_string_literal("Expected model to be a JSON object"); + + return json_model; + } + + ALWAYS_INLINE GUI::ModelIndex to_gui_model_index(WebView::ModelIndex const& index) const + { + if (!index.is_valid()) + return {}; + return create_index(index.row, index.column, index.internal_data); + } + + ALWAYS_INLINE WebView::ModelIndex to_web_view_model_index(GUI::ModelIndex const& index) const + { + if (!index.is_valid()) + return {}; + return { index.row(), index.column(), index.internal_data() }; + } + + ModelType m_model; +}; + +using TreeModel = ModelAdapter; + +class DOMTreeModel : public TreeModel { +public: + static ErrorOr> create(GUI::TreeView const& tree_view, StringView model) + { + auto json_model = TRY(parse_json_model(model)); + + // FIXME: Get these from the outside somehow instead of hard-coding paths here. + auto document_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"sv)); + auto element_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"sv)); + auto text_icon = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png"sv)); + + return adopt_ref(*new DOMTreeModel(tree_view, move(json_model), move(document_icon), move(element_icon), move(text_icon))); + } + + virtual GUI::Variant data(const GUI::ModelIndex& index, GUI::ModelRole role) const override + { + auto const& node = *static_cast(index.internal_data()); + auto node_name = node.get_deprecated_string("name"sv).value_or({}); + auto type = node.get_deprecated_string("type"sv).value_or("unknown"); + + // FIXME: This FIXME can go away when the icons are provided from the outside (see constructor). + if (role == GUI::ModelRole::ForegroundColor) { + // FIXME: Allow models to return a foreground color *role*. Then we won't need to have a GUI::TreeView& member anymore. + if (type == "comment"sv || type == "shadow-root"sv) + return m_tree_view.palette().syntax_comment(); + if (type == "pseudo-element"sv) + return m_tree_view.palette().syntax_type(); + if (!node.get_bool("visible"sv).value_or(true)) + return m_tree_view.palette().syntax_comment(); + return {}; + } + + // FIXME: This FIXME can go away when the icons are provided from the outside (see constructor). + if (role == GUI::ModelRole::Icon) { + if (type == "document") + return m_document_icon; + if (type == "element") + return m_element_icon; + // FIXME: More node type icons? + return m_text_icon; + } + + return TreeModel::data(index, role); + } + +private: + DOMTreeModel( + GUI::TreeView const& tree_view, + JsonValue tree, + NonnullRefPtr document_icon, + NonnullRefPtr element_icon, + NonnullRefPtr text_icon) + : TreeModel(TreeModel::Type::DOMTree, move(tree)) + , m_tree_view(tree_view) + { + m_document_icon.set_bitmap_for_size(16, move(document_icon)); + m_element_icon.set_bitmap_for_size(16, move(element_icon)); + m_text_icon.set_bitmap_for_size(16, move(text_icon)); + } + + GUI::TreeView const& m_tree_view; + GUI::Icon m_document_icon; + GUI::Icon m_element_icon; + GUI::Icon m_text_icon; +}; + +class PropertyTableModel : public ModelAdapter { +public: + static ErrorOr> create(Type type, StringView model) + { + return adopt_ref(*new PropertyTableModel(type, TRY(parse_json_model(model)))); + } + + virtual ErrorOr column_name(int column_index) const override + { + return m_model.column_name(column_index); + } + + virtual bool is_searchable() const override { return true; } + + virtual Vector matches(StringView searching, unsigned flags, GUI::ModelIndex const&) override + { + Vector found_indices; + + m_model.for_each_property_name([&](auto index, auto const& property_name) { + if (!string_matches(property_name, searching, flags)) + return IterationDecision::Continue; + + found_indices.append(to_gui_model_index(index)); + + if ((flags & FirstMatchOnly) != 0) + return IterationDecision::Break; + return IterationDecision::Continue; + }); + + return found_indices; + } + +private: + PropertyTableModel(Type type, JsonValue model) + : ModelAdapter(type, move(model)) + { + } +}; + +}