summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-06-12 22:30:11 +0200
committerAndreas Kling <kling@serenityos.org>2020-06-12 22:30:11 +0200
commit6f19067c9a6999c236eda8cb27199e7d256b990b (patch)
tree239503ef5098bedc3b8590d5e7b57e89ee89cf4b
parent68ae1de46320e82fb6ab8cfd81e8e09c0dff6249 (diff)
downloadserenity-6f19067c9a6999c236eda8cb27199e7d256b990b.zip
LibWeb+Browser: Add a barebones LayoutTreeModel to the inspector window
This allows you to inspect the layout tree, along side the DOM tree. It will need more functionality to be truly useful, but it's a start.
-rw-r--r--Applications/Browser/InspectorWidget.cpp46
-rw-r--r--Applications/Browser/InspectorWidget.h3
-rw-r--r--Libraries/LibWeb/CMakeLists.txt1
-rw-r--r--Libraries/LibWeb/LayoutTreeModel.cpp162
-rw-r--r--Libraries/LibWeb/LayoutTreeModel.h61
5 files changed, 258 insertions, 15 deletions
diff --git a/Applications/Browser/InspectorWidget.cpp b/Applications/Browser/InspectorWidget.cpp
index ef9f019777..6b7796cff5 100644
--- a/Applications/Browser/InspectorWidget.cpp
+++ b/Applications/Browser/InspectorWidget.cpp
@@ -33,34 +33,49 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Element.h>
#include <LibWeb/DOMTreeModel.h>
+#include <LibWeb/LayoutTreeModel.h>
#include <LibWeb/StylePropertiesModel.h>
namespace Browser {
+void InspectorWidget::set_inspected_node(Web::Node* node)
+{
+ node->document().set_inspected_node(node);
+ if (node && node->is_element()) {
+ auto& element = Web::to<Web::Element>(*node);
+ if (element.resolved_style()) {
+ m_style_table_view->set_model(Web::StylePropertiesModel::create(*element.resolved_style()));
+ m_computed_style_table_view->set_model(Web::StylePropertiesModel::create(*element.computed_style()));
+ }
+ } else {
+ m_style_table_view->set_model(nullptr);
+ m_computed_style_table_view->set_model(nullptr);
+ }
+}
+
InspectorWidget::InspectorWidget()
{
set_layout<GUI::VerticalBoxLayout>();
auto& splitter = add<GUI::VerticalSplitter>();
- m_dom_tree_view = splitter.add<GUI::TreeView>();
+
+ auto& top_tab_widget = splitter.add<GUI::TabWidget>();
+
+ m_dom_tree_view = top_tab_widget.add_tab<GUI::TreeView>("DOM");
m_dom_tree_view->on_selection = [this](auto& index) {
auto* node = static_cast<Web::Node*>(index.internal_data());
- node->document().set_inspected_node(node);
- if (node->is_element()) {
- auto& element = Web::to<Web::Element>(*node);
- if (element.resolved_style()) {
- m_style_table_view->set_model(Web::StylePropertiesModel::create(*element.resolved_style()));
- m_computed_style_table_view->set_model(Web::StylePropertiesModel::create(*element.computed_style()));
- }
- } else {
- m_style_table_view->set_model(nullptr);
- m_computed_style_table_view->set_model(nullptr);
- }
+ set_inspected_node(node);
+ };
+
+ m_layout_tree_view = top_tab_widget.add_tab<GUI::TreeView>("Layout");
+ m_layout_tree_view->on_selection = [this](auto& index) {
+ auto* node = static_cast<Web::LayoutNode*>(index.internal_data());
+ set_inspected_node(node->node());
};
- auto& tab_widget = splitter.add<GUI::TabWidget>();
+ auto& bottom_tab_widget = splitter.add<GUI::TabWidget>();
- m_style_table_view = tab_widget.add_tab<GUI::TableView>("Styles");
- m_computed_style_table_view = tab_widget.add_tab<GUI::TableView>("Computed");
+ m_style_table_view = bottom_tab_widget.add_tab<GUI::TableView>("Styles");
+ m_computed_style_table_view = bottom_tab_widget.add_tab<GUI::TableView>("Computed");
}
InspectorWidget::~InspectorWidget()
@@ -73,6 +88,7 @@ void InspectorWidget::set_document(Web::Document* document)
return;
m_document = document;
m_dom_tree_view->set_model(Web::DOMTreeModel::create(*document));
+ m_layout_tree_view->set_model(Web::LayoutTreeModel::create(*document));
}
}
diff --git a/Applications/Browser/InspectorWidget.h b/Applications/Browser/InspectorWidget.h
index 49e8b30a0f..5af8a3973c 100644
--- a/Applications/Browser/InspectorWidget.h
+++ b/Applications/Browser/InspectorWidget.h
@@ -41,7 +41,10 @@ public:
private:
InspectorWidget();
+ void set_inspected_node(Web::Node*);
+
RefPtr<GUI::TreeView> m_dom_tree_view;
+ RefPtr<GUI::TreeView> m_layout_tree_view;
RefPtr<GUI::TableView> m_style_table_view;
RefPtr<GUI::TableView> m_computed_style_table_view;
RefPtr<Web::Document> m_document;
diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt
index 6f9a59fe6b..71ebc082d6 100644
--- a/Libraries/LibWeb/CMakeLists.txt
+++ b/Libraries/LibWeb/CMakeLists.txt
@@ -93,6 +93,7 @@ set(SOURCES
Layout/LayoutWidget.cpp
Layout/LineBox.cpp
Layout/LineBoxFragment.cpp
+ LayoutTreeModel.cpp
Loader/FrameLoader.cpp
Loader/ImageResource.cpp
Loader/Resource.cpp
diff --git a/Libraries/LibWeb/LayoutTreeModel.cpp b/Libraries/LibWeb/LayoutTreeModel.cpp
new file mode 100644
index 0000000000..1dc18ba0c5
--- /dev/null
+++ b/Libraries/LibWeb/LayoutTreeModel.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "LayoutTreeModel.h"
+#include <AK/StringBuilder.h>
+#include <LibWeb/DOM/Document.h>
+#include <LibWeb/DOM/Element.h>
+#include <LibWeb/DOM/Text.h>
+#include <LibWeb/Layout/LayoutText.h>
+#include <ctype.h>
+#include <stdio.h>
+
+namespace Web {
+
+LayoutTreeModel::LayoutTreeModel(Document& document)
+ : m_document(document)
+{
+ m_document_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-html.png"));
+ m_element_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/inspector-object.png"));
+ m_text_icon.set_bitmap_for_size(16, Gfx::Bitmap::load_from_file("/res/icons/16x16/filetype-unknown.png"));
+}
+
+LayoutTreeModel::~LayoutTreeModel()
+{
+}
+
+GUI::ModelIndex LayoutTreeModel::index(int row, int column, const GUI::ModelIndex& parent) const
+{
+ if (!parent.is_valid())
+ return create_index(row, column, m_document->layout_node());
+ auto& parent_node = *static_cast<LayoutNode*>(parent.internal_data());
+ return create_index(row, column, parent_node.child_at_index(row));
+}
+
+GUI::ModelIndex LayoutTreeModel::parent_index(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return {};
+ auto& node = *static_cast<LayoutNode*>(index.internal_data());
+ if (!node.parent())
+ return {};
+
+ // No grandparent? Parent is the document!
+ if (!node.parent()->parent()) {
+ return create_index(0, 0, m_document->layout_node());
+ }
+
+ // 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 GUI::ModelIndex 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 LayoutTreeModel::row_count(const GUI::ModelIndex& index) const
+{
+ if (!index.is_valid())
+ return 1;
+ auto& node = *static_cast<LayoutNode*>(index.internal_data());
+ return node.child_count();
+}
+
+int LayoutTreeModel::column_count(const GUI::ModelIndex&) const
+{
+ return 1;
+}
+
+static String with_whitespace_collapsed(const StringView& string)
+{
+ StringBuilder builder;
+ for (size_t 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();
+}
+
+GUI::Variant LayoutTreeModel::data(const GUI::ModelIndex& index, Role role) const
+{
+ auto& node = *static_cast<LayoutNode*>(index.internal_data());
+ if (role == Role::Icon) {
+ if (node.is_root())
+ return m_document_icon;
+ if (node.is_text())
+ return m_text_icon;
+ return m_element_icon;
+ }
+ if (role == Role::Display) {
+ if (node.is_text())
+ return String::format("LayoutText: %s", with_whitespace_collapsed(to<LayoutText>(node).text_for_rendering()).characters());
+ StringBuilder builder;
+ builder.append(node.class_name());
+ builder.append(' ');
+ if (node.is_anonymous()) {
+ builder.append("[anonymous]");
+ } else if (!node.node()->is_element()) {
+ builder.append(node.node()->tag_name());
+ } else {
+ auto& element = to<Element>(*node.node());
+ builder.append('<');
+ builder.append(element.tag_name());
+ element.for_each_attribute([&](auto& name, auto& value) {
+ builder.append(' ');
+ builder.append(name);
+ builder.append('=');
+ builder.append('"');
+ builder.append(value);
+ builder.append('"');
+ });
+ builder.append('>');
+ }
+ return builder.to_string();
+ }
+ return {};
+}
+
+void LayoutTreeModel::update()
+{
+ did_update();
+}
+
+}
diff --git a/Libraries/LibWeb/LayoutTreeModel.h b/Libraries/LibWeb/LayoutTreeModel.h
new file mode 100644
index 0000000000..2fb8bbca59
--- /dev/null
+++ b/Libraries/LibWeb/LayoutTreeModel.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <LibGUI/Model.h>
+
+namespace Web {
+
+class Document;
+
+class LayoutTreeModel final : public GUI::Model {
+public:
+ static NonnullRefPtr<LayoutTreeModel> create(Document& document)
+ {
+ return adopt(*new LayoutTreeModel(document));
+ }
+
+ virtual ~LayoutTreeModel() override;
+
+ virtual int row_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual int column_count(const GUI::ModelIndex& = GUI::ModelIndex()) const override;
+ virtual GUI::Variant data(const GUI::ModelIndex&, Role = Role::Display) const override;
+ virtual GUI::ModelIndex index(int row, int column, const GUI::ModelIndex& parent = GUI::ModelIndex()) const override;
+ virtual GUI::ModelIndex parent_index(const GUI::ModelIndex&) const override;
+ virtual void update() override;
+
+private:
+ explicit LayoutTreeModel(Document&);
+
+ NonnullRefPtr<Document> m_document;
+
+ GUI::Icon m_document_icon;
+ GUI::Icon m_element_icon;
+ GUI::Icon m_text_icon;
+};
+
+}