Serenity Operating System
1/*
2 * Copyright (c) 2022, MacDue <macdue@dueutil.tech>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7#define AK_DONT_REPLACE_STD
8
9#include <LibWebView/AccessibilityTreeModel.h>
10#include <LibWebView/DOMTreeModel.h>
11#include <LibWebView/StylePropertiesModel.h>
12
13#include "InspectorWidget.h"
14#include <QCloseEvent>
15#include <QHeaderView>
16#include <QSplitter>
17#include <QStringList>
18#include <QTabWidget>
19#include <QTableView>
20#include <QTreeView>
21#include <QVBoxLayout>
22
23namespace Ladybird {
24
25InspectorWidget::InspectorWidget()
26{
27 setLayout(new QVBoxLayout);
28 auto splitter = new QSplitter(this);
29 layout()->addWidget(splitter);
30 splitter->setOrientation(Qt::Vertical);
31
32 auto add_tab = [&](auto* tab_widget, auto* widget, auto name) {
33 auto container = new QWidget;
34 container->setLayout(new QVBoxLayout);
35 container->layout()->addWidget(widget);
36 tab_widget->addTab(container, name);
37 };
38
39 auto top_tap_widget = new QTabWidget;
40 splitter->addWidget(top_tap_widget);
41
42 auto dom_tree_view = new QTreeView;
43 dom_tree_view->setHeaderHidden(true);
44 dom_tree_view->setModel(&m_dom_model);
45 QObject::connect(dom_tree_view->selectionModel(), &QItemSelectionModel::selectionChanged,
46 [this](QItemSelection const& selected, QItemSelection const&) {
47 auto indexes = selected.indexes();
48 if (indexes.size()) {
49 auto index = m_dom_model.to_gui(indexes.first());
50 set_selection(index);
51 }
52 });
53 add_tab(top_tap_widget, dom_tree_view, "DOM");
54
55 auto accessibility_tree_view = new QTreeView;
56 accessibility_tree_view->setHeaderHidden(true);
57 accessibility_tree_view->setModel(&m_accessibility_model);
58 add_tab(top_tap_widget, accessibility_tree_view, "Accessibility");
59
60 auto add_table_tab = [&](auto* tab_widget, auto& model, auto name) {
61 auto table_view = new QTableView;
62 table_view->setModel(&model);
63 table_view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
64 table_view->verticalHeader()->setVisible(false);
65 table_view->horizontalHeader()->setVisible(false);
66 add_tab(tab_widget, table_view, name);
67 };
68
69 auto node_tabs = new QTabWidget;
70 add_table_tab(node_tabs, m_computed_style_model, "Computed");
71 add_table_tab(node_tabs, m_resolved_style_model, "Resolved");
72 add_table_tab(node_tabs, m_custom_properties_model, "Variables");
73 splitter->addWidget(node_tabs);
74}
75
76void InspectorWidget::set_dom_json(StringView dom_json)
77{
78 m_dom_model.set_underlying_model(WebView::DOMTreeModel::create(dom_json));
79}
80
81void InspectorWidget::set_accessibility_json(StringView accessibility_json)
82{
83 m_accessibility_model.set_underlying_model(WebView::AccessibilityTreeModel::create(accessibility_json));
84}
85
86void InspectorWidget::clear_dom_json()
87{
88 m_dom_model.set_underlying_model(nullptr);
89 // The accessibility tree is pretty much another form of the DOM tree, so should be cleared at the time time.
90 m_accessibility_model.set_underlying_model(nullptr);
91 clear_style_json();
92}
93
94void InspectorWidget::load_style_json(StringView computed_style_json, StringView resolved_style_json, StringView custom_properties_json)
95{
96 m_computed_style_model.set_underlying_model(WebView::StylePropertiesModel::create(computed_style_json));
97 m_resolved_style_model.set_underlying_model(WebView::StylePropertiesModel::create(resolved_style_json));
98 m_custom_properties_model.set_underlying_model(WebView::StylePropertiesModel::create(custom_properties_json));
99}
100
101void InspectorWidget::clear_style_json()
102{
103 m_computed_style_model.set_underlying_model(nullptr);
104 m_resolved_style_model.set_underlying_model(nullptr);
105 m_custom_properties_model.set_underlying_model(nullptr);
106}
107
108void InspectorWidget::closeEvent(QCloseEvent* event)
109{
110 event->accept();
111 if (on_close)
112 on_close();
113}
114
115void InspectorWidget::set_selection(GUI::ModelIndex index)
116{
117 if (!index.is_valid())
118 return;
119
120 auto* json = static_cast<JsonObject const*>(index.internal_data());
121 VERIFY(json);
122
123 Selection selection {};
124 if (json->has_u32("pseudo-element"sv)) {
125 selection.dom_node_id = json->get_i32("parent-id"sv).value();
126 selection.pseudo_element = static_cast<Web::CSS::Selector::PseudoElement>(json->get_u32("pseudo-element"sv).value());
127 } else {
128 selection.dom_node_id = json->get_i32("id"sv).value();
129 }
130
131 if (selection == m_selection)
132 return;
133 m_selection = selection;
134
135 VERIFY(on_dom_node_inspected);
136 auto maybe_inspected_node_properties = on_dom_node_inspected(m_selection.dom_node_id, m_selection.pseudo_element);
137 if (!maybe_inspected_node_properties.is_error()) {
138 auto properties = maybe_inspected_node_properties.release_value();
139 load_style_json(properties.computed_style_json, properties.resolved_style_json, properties.custom_properties_json);
140 } else {
141 clear_style_json();
142 }
143}
144
145}