diff --git a/Applications/Browser/main.cpp b/Applications/Browser/main.cpp index 69e86b16a9d..b17894207c6 100644 --- a/Applications/Browser/main.cpp +++ b/Applications/Browser/main.cpp @@ -9,9 +9,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -129,6 +131,27 @@ int main(int argc, char** argv) })); menubar->add_menu(move(app_menu)); + RefPtr dom_inspector_window; + RefPtr dom_tree_view; + + auto inspect_menu = make("Inspect"); + inspect_menu->add_action(GAction::create("Inspect DOM tree", [&](auto&) { + if (!dom_inspector_window) { + dom_inspector_window = GWindow::construct(); + dom_inspector_window->set_rect(100, 100, 300, 500); + dom_inspector_window->set_title("DOM inspector"); + dom_tree_view = GTreeView::construct(nullptr); + dom_inspector_window->set_main_widget(dom_tree_view); + } + if (html_widget->document()) + dom_tree_view->set_model(DOMTreeModel::create(*html_widget->document())); + else + dom_tree_view->set_model(nullptr); + dom_inspector_window->show(); + dom_inspector_window->move_to_front(); + })); + menubar->add_menu(move(inspect_menu)); + auto debug_menu = make("Debug"); debug_menu->add_action(GAction::create("Dump DOM tree", [&](auto&) { dump_tree(*html_widget->document()); diff --git a/Libraries/LibHTML/DOMTreeModel.cpp b/Libraries/LibHTML/DOMTreeModel.cpp new file mode 100644 index 00000000000..ff687e878bc --- /dev/null +++ b/Libraries/LibHTML/DOMTreeModel.cpp @@ -0,0 +1,110 @@ +#include "DOMTreeModel.h" +#include +#include +#include +#include +#include + +DOMTreeModel::DOMTreeModel(Document& document) + : m_document(document) +{ + m_element_icon.set_bitmap_for_size(16, GraphicsBitmap::load_from_file("/res/icons/16x16/inspector-object.png")); + m_text_icon.set_bitmap_for_size(16, GraphicsBitmap::load_from_file("/res/icons/16x16/filetype-unknown.png")); +} + +DOMTreeModel::~DOMTreeModel() +{ +} + +GModelIndex DOMTreeModel::index(int row, int column, const GModelIndex& parent) const +{ + if (!parent.is_valid()) { + return create_index(row, column, &m_document); + } + auto& parent_node = *static_cast(parent.internal_data()); + return create_index(row, column, parent_node.child_at_index(row)); +} + +GModelIndex DOMTreeModel::parent_index(const GModelIndex& index) const +{ + if (!index.is_valid()) + return {}; + auto& node = *static_cast(index.internal_data()); + if (!node.parent()) + return {}; + + // No grandparent? Parent is the document! + if (!node.parent()->parent()) { + return create_index(0, 0, &m_document); + } + + // Walk the grandparent's children to find the index of node's parent in its parent. + // (This is needed to produce the row number of the GModelIndex corresponding to node's parent.) + int grandparent_child_index = 0; + for (auto* grandparent_child = node.parent()->parent()->first_child(); grandparent_child; grandparent_child = grandparent_child->next_sibling()) { + if (grandparent_child == node.parent()) + return create_index(grandparent_child_index, 0, node.parent()); + ++grandparent_child_index; + } + + ASSERT_NOT_REACHED(); + return {}; +} + +int DOMTreeModel::row_count(const GModelIndex& index) const +{ + if (!index.is_valid()) + return 1; + auto& node = *static_cast(index.internal_data()); + return node.child_count(); +} + +int DOMTreeModel::column_count(const GModelIndex&) const +{ + return 1; +} + +static String with_whitespace_collapsed(const StringView& string) +{ + StringBuilder builder; + for (int i = 0; i < string.length(); ++i) { + if (isspace(string[i])) { + builder.append(' '); + while (i < string.length()) { + if (isspace(string[i])) { + ++i; + continue; + } + builder.append(string[i]); + break; + } + continue; + } + builder.append(string[i]); + } + return builder.to_string(); +} + +GVariant DOMTreeModel::data(const GModelIndex& index, Role role) const +{ + auto* node = static_cast(index.internal_data()); + if (role == Role::Icon) { + if (node->is_element()) + return m_element_icon; + // FIXME: More node type icons? + return m_text_icon; + } + if (role == Role::Display) { + if (node->is_text()) { + + return String::format("%s", with_whitespace_collapsed(to(*node).data()).characters()); + } + return String::format("<%s>", node->tag_name().characters()); + } + return {}; +} + +void DOMTreeModel::update() +{ + did_update(); +} diff --git a/Libraries/LibHTML/DOMTreeModel.h b/Libraries/LibHTML/DOMTreeModel.h new file mode 100644 index 00000000000..8c37a83dc1f --- /dev/null +++ b/Libraries/LibHTML/DOMTreeModel.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +class Document; + +class DOMTreeModel final : public GModel { +public: + static NonnullRefPtr create(Document& document) + { + return adopt(*new DOMTreeModel(document)); + } + + virtual ~DOMTreeModel() override; + + virtual int row_count(const GModelIndex& = GModelIndex()) const override; + virtual int column_count(const GModelIndex& = GModelIndex()) const override; + virtual GVariant data(const GModelIndex&, Role = Role::Display) const override; + virtual GModelIndex index(int row, int column, const GModelIndex& parent = GModelIndex()) const override; + virtual GModelIndex parent_index(const GModelIndex&) const override; + virtual void update() override; + +private: + explicit DOMTreeModel(Document&); + + Document& m_document; + + GIcon m_element_icon; + GIcon m_text_icon; +}; diff --git a/Libraries/LibHTML/Makefile.shared b/Libraries/LibHTML/Makefile.shared index b97bc804290..9c06ef23f8d 100644 --- a/Libraries/LibHTML/Makefile.shared +++ b/Libraries/LibHTML/Makefile.shared @@ -51,6 +51,7 @@ LIBHTML_OBJS = \ Layout/LineBox.o \ Layout/LineBoxFragment.o \ Layout/LayoutTreeBuilder.o \ + DOMTreeModel.o \ FontCache.o \ ResourceLoader.o \ HtmlView.o \ diff --git a/Libraries/LibHTML/TreeNode.h b/Libraries/LibHTML/TreeNode.h index 4459e4cfc00..d87b8e0a6f1 100644 --- a/Libraries/LibHTML/TreeNode.h +++ b/Libraries/LibHTML/TreeNode.h @@ -45,6 +45,30 @@ public: const T* first_child() const { return m_first_child; } const T* last_child() const { return m_last_child; } + int child_count() const + { + int count = 0; + for (auto* child = first_child(); child; child = child->next_sibling()) + ++count; + return count; + } + + T* child_at_index(int index) + { + int count = 0; + for (auto* child = first_child(); child; child = child->next_sibling()) { + if (count == index) + return child; + ++count; + } + return nullptr; + } + + const T* child_at_index(int index) const + { + return const_cast(this)->child_at_index(index); + } + bool is_ancestor_of(const TreeNode&) const; void prepend_child(NonnullRefPtr node, bool call_inserted_into = true);