Serenity Operating System
at master 228 lines 8.2 kB view raw
1/* 2 * Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2018-2020, Adam Hodgen <ant1441@gmail.com> 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include "DOMTreeModel.h" 9#include <AK/JsonObject.h> 10#include <AK/StringBuilder.h> 11#include <LibGUI/TreeView.h> 12#include <LibGfx/Palette.h> 13#include <ctype.h> 14 15namespace WebView { 16 17DOMTreeModel::DOMTreeModel(JsonObject dom_tree, GUI::TreeView* tree_view) 18 : m_tree_view(tree_view) 19 , m_dom_tree(move(dom_tree)) 20{ 21 // FIXME: Get these from the outside somehow instead of hard-coding paths here. 22#ifdef AK_OS_SERENITY 23 m_document_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"sv).release_value_but_fixme_should_propagate_errors()); 24 m_element_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"sv).release_value_but_fixme_should_propagate_errors()); 25 m_text_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png"sv).release_value_but_fixme_should_propagate_errors()); 26#endif 27 28 map_dom_nodes_to_parent(nullptr, &m_dom_tree); 29} 30 31DOMTreeModel::~DOMTreeModel() = default; 32 33GUI::ModelIndex DOMTreeModel::index(int row, int column, const GUI::ModelIndex& parent) const 34{ 35 if (!parent.is_valid()) { 36 return create_index(row, column, &m_dom_tree); 37 } 38 39 auto const& parent_node = *static_cast<JsonObject const*>(parent.internal_data()); 40 auto children = get_children(parent_node); 41 if (!children.has_value()) 42 return create_index(row, column, &m_dom_tree); 43 44 auto const& child_node = children->at(row).as_object(); 45 return create_index(row, column, &child_node); 46} 47 48GUI::ModelIndex DOMTreeModel::parent_index(const GUI::ModelIndex& index) const 49{ 50 // FIXME: Handle the template element (child elements are not stored in it, all of its children are in its document fragment "content") 51 // Probably in the JSON generation in Node.cpp? 52 if (!index.is_valid()) 53 return {}; 54 55 auto const& node = *static_cast<JsonObject const*>(index.internal_data()); 56 57 auto const* parent_node = get_parent(node); 58 if (!parent_node) 59 return {}; 60 61 // If the parent is the root document, we know it has index 0, 0 62 if (parent_node == &m_dom_tree) { 63 return create_index(0, 0, parent_node); 64 } 65 66 // Otherwise, we need to find the grandparent, to find the index of parent within that 67 auto const* grandparent_node = get_parent(*parent_node); 68 VERIFY(grandparent_node); 69 70 auto grandparent_children = get_children(*grandparent_node); 71 if (!grandparent_children.has_value()) 72 return {}; 73 74 for (size_t grandparent_child_index = 0; grandparent_child_index < grandparent_children->size(); ++grandparent_child_index) { 75 auto const& child = grandparent_children->at(grandparent_child_index).as_object(); 76 if (&child == parent_node) 77 return create_index(grandparent_child_index, 0, parent_node); 78 } 79 80 return {}; 81} 82 83int DOMTreeModel::row_count(const GUI::ModelIndex& index) const 84{ 85 if (!index.is_valid()) 86 return 1; 87 88 auto const& node = *static_cast<JsonObject const*>(index.internal_data()); 89 auto children = get_children(node); 90 return children.has_value() ? children->size() : 0; 91} 92 93int DOMTreeModel::column_count(const GUI::ModelIndex&) const 94{ 95 return 1; 96} 97 98static DeprecatedString with_whitespace_collapsed(StringView string) 99{ 100 StringBuilder builder; 101 for (size_t i = 0; i < string.length(); ++i) { 102 if (isspace(string[i])) { 103 builder.append(' '); 104 while (i < string.length()) { 105 if (isspace(string[i])) { 106 ++i; 107 continue; 108 } 109 builder.append(string[i]); 110 break; 111 } 112 continue; 113 } 114 builder.append(string[i]); 115 } 116 return builder.to_deprecated_string(); 117} 118 119GUI::Variant DOMTreeModel::data(const GUI::ModelIndex& index, GUI::ModelRole role) const 120{ 121 auto const& node = *static_cast<JsonObject const*>(index.internal_data()); 122 auto node_name = node.get_deprecated_string("name"sv).value_or({}); 123 auto type = node.get_deprecated_string("type"sv).value_or("unknown"); 124 125 // FIXME: This FIXME can go away when we fix the one below. 126#ifdef AK_OS_SERENITY 127 if (role == GUI::ModelRole::ForegroundColor) { 128 // FIXME: Allow models to return a foreground color *role*. 129 // Then we won't need to have a GUI::TreeView& member anymore. 130 if (type == "comment"sv) 131 return m_tree_view->palette().syntax_comment(); 132 if (type == "pseudo-element"sv) 133 return m_tree_view->palette().syntax_type(); 134 if (!node.get_bool("visible"sv).value_or(true)) 135 return m_tree_view->palette().syntax_comment(); 136 return {}; 137 } 138#endif 139 140 // FIXME: This FIXME can go away when the icons are provided from the outside (see constructor). 141#ifdef AK_OS_SERENITY 142 if (role == GUI::ModelRole::Icon) { 143 if (type == "document") 144 return m_document_icon; 145 if (type == "element") 146 return m_element_icon; 147 // FIXME: More node type icons? 148 return m_text_icon; 149 } 150#endif 151 152 if (role == GUI::ModelRole::Display) { 153 if (type == "text") 154 return with_whitespace_collapsed(node.get_deprecated_string("text"sv).value()); 155 if (type == "comment"sv) 156 return DeprecatedString::formatted("<!--{}-->", node.get_deprecated_string("data"sv).value()); 157 if (type != "element") 158 return node_name; 159 160 StringBuilder builder; 161 builder.append('<'); 162 builder.append(node_name.to_lowercase()); 163 if (node.has("attributes"sv)) { 164 auto attributes = node.get_object("attributes"sv).value(); 165 attributes.for_each_member([&builder](auto& name, JsonValue const& value) { 166 builder.append(' '); 167 builder.append(name); 168 builder.append('='); 169 builder.append('"'); 170 builder.append(value.to_deprecated_string()); 171 builder.append('"'); 172 }); 173 } 174 builder.append('>'); 175 return builder.to_deprecated_string(); 176 } 177 return {}; 178} 179 180void DOMTreeModel::map_dom_nodes_to_parent(JsonObject const* parent, JsonObject const* node) 181{ 182 m_dom_node_to_parent_map.set(node, parent); 183 m_node_id_to_dom_node_map.set(node->get_i32("id"sv).value_or(0), node); 184 185 auto children = get_children(*node); 186 if (!children.has_value()) 187 return; 188 189 children->for_each([&](auto const& child) { 190 auto const& child_node = child.as_object(); 191 map_dom_nodes_to_parent(node, &child_node); 192 }); 193} 194 195GUI::ModelIndex DOMTreeModel::index_for_node(i32 node_id, Optional<Web::CSS::Selector::PseudoElement> pseudo_element) const 196{ 197 auto node = m_node_id_to_dom_node_map.get(node_id).value_or(nullptr); 198 if (node) { 199 if (pseudo_element.has_value()) { 200 // Find pseudo-element child of the node. 201 auto node_children = get_children(*node); 202 for (size_t i = 0; i < node_children->size(); i++) { 203 auto& child = node_children->at(i).as_object(); 204 if (!child.has("pseudo-element"sv)) 205 continue; 206 207 auto child_pseudo_element = child.get_i32("pseudo-element"sv); 208 if (child_pseudo_element == to_underlying(pseudo_element.value())) 209 return create_index(i, 0, &child); 210 } 211 } else { 212 auto* parent = get_parent(*node); 213 if (!parent) 214 return {}; 215 auto parent_children = get_children(*parent); 216 for (size_t i = 0; i < parent_children->size(); i++) { 217 if (&parent_children->at(i).as_object() == node) { 218 return create_index(i, 0, node); 219 } 220 } 221 } 222 } 223 224 dbgln("Didn't find index for node {}, pseudo-element {}!", node_id, pseudo_element.has_value() ? Web::CSS::pseudo_element_name(pseudo_element.value()) : "NONE"sv); 225 return {}; 226} 227 228}