Ladybird: Allow right clicking and inspecting elements

This adds "Inspect Element" (currently the only entry) to the context
menu for the page, which will do what you expect (most of the time),
and bring up the Inspector with hovered element selected.
This commit is contained in:
MacDue 2023-05-08 21:15:35 +01:00 committed by Andreas Kling
parent 15211cd753
commit 09773048b6
7 changed files with 101 additions and 9 deletions

View file

@ -330,11 +330,28 @@ BrowserWindow::BrowserWindow(Browser::CookieJar& cookie_jar, StringView webdrive
QObject::connect(m_tabs_container, &QTabWidget::tabCloseRequested, this, &BrowserWindow::close_tab);
QObject::connect(close_current_tab_action, &QAction::triggered, this, &BrowserWindow::close_current_tab);
setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(this, &QWidget::customContextMenuRequested, this, &BrowserWindow::show_context_menu);
new_tab(s_settings->new_tab_page(), Web::HTML::ActivateTab::Yes);
setCentralWidget(m_tabs_container);
}
void BrowserWindow::show_context_menu(QPoint const& point)
{
QMenu contextMenu("Context menu", this);
QAction inspect_action("&Inspect Element", this);
connect(&inspect_action, &QAction::triggered, this, [this] {
if (!m_current_tab)
return;
m_current_tab->view().show_inspector(WebContentView::InspectorTarget::HoveredElement);
});
contextMenu.addAction(&inspect_action);
contextMenu.exec(mapToGlobal(point));
}
void BrowserWindow::set_current_tab(Tab* tab)
{
m_current_tab = tab;

View file

@ -49,6 +49,7 @@ public slots:
void reset_zoom();
void select_all();
void copy_selected_text();
void show_context_menu(QPoint const&);
protected:
bool eventFilter(QObject* obj, QEvent* event) override;

View file

@ -37,10 +37,10 @@ InspectorWidget::InspectorWidget()
auto top_tap_widget = new QTabWidget;
splitter->addWidget(top_tap_widget);
auto dom_tree_view = new QTreeView;
dom_tree_view->setHeaderHidden(true);
dom_tree_view->setModel(&m_dom_model);
QObject::connect(dom_tree_view->selectionModel(), &QItemSelectionModel::selectionChanged,
m_dom_tree_view = new QTreeView;
m_dom_tree_view->setHeaderHidden(true);
m_dom_tree_view->setModel(&m_dom_model);
QObject::connect(m_dom_tree_view->selectionModel(), &QItemSelectionModel::selectionChanged,
[this](QItemSelection const& selected, QItemSelection const&) {
auto indexes = selected.indexes();
if (indexes.size()) {
@ -48,7 +48,7 @@ InspectorWidget::InspectorWidget()
set_selection(index);
}
});
add_tab(top_tap_widget, dom_tree_view, "DOM");
add_tab(top_tap_widget, m_dom_tree_view, "DOM");
auto accessibility_tree_view = new QTreeView;
accessibility_tree_view->setHeaderHidden(true);
@ -74,6 +74,11 @@ InspectorWidget::InspectorWidget()
void InspectorWidget::set_dom_json(StringView dom_json)
{
m_dom_model.set_underlying_model(WebView::DOMTreeModel::create(dom_json));
m_dom_loaded = true;
if (m_pending_selection.has_value())
set_selection(m_pending_selection.release_value());
else
select_default_node();
}
void InspectorWidget::set_accessibility_json(StringView accessibility_json)
@ -87,6 +92,8 @@ void InspectorWidget::clear_dom_json()
// The accessibility tree is pretty much another form of the DOM tree, so should be cleared at the time time.
m_accessibility_model.set_underlying_model(nullptr);
clear_style_json();
clear_selection();
m_dom_loaded = false;
}
void InspectorWidget::load_style_json(StringView computed_style_json, StringView resolved_style_json, StringView custom_properties_json)
@ -101,6 +108,7 @@ void InspectorWidget::clear_style_json()
m_computed_style_model.set_underlying_model(nullptr);
m_resolved_style_model.set_underlying_model(nullptr);
m_custom_properties_model.set_underlying_model(nullptr);
clear_selection();
}
void InspectorWidget::closeEvent(QCloseEvent* event)
@ -108,6 +116,40 @@ void InspectorWidget::closeEvent(QCloseEvent* event)
event->accept();
if (on_close)
on_close();
clear_selection();
}
void InspectorWidget::clear_selection()
{
m_selection = {};
m_dom_tree_view->clearSelection();
}
void InspectorWidget::set_selection(Selection selection)
{
if (!m_dom_loaded) {
m_pending_selection = selection;
return;
}
auto* model = verify_cast<WebView::DOMTreeModel>(m_dom_model.underlying_model().ptr());
auto index = model->index_for_node(selection.dom_node_id, selection.pseudo_element);
auto qt_index = m_dom_model.to_qt(index);
if (!qt_index.isValid()) {
dbgln("Failed to set DOM inspector selection! Could not find valid model index for node: {}", selection.dom_node_id);
return;
}
m_dom_tree_view->scrollTo(qt_index);
m_dom_tree_view->setCurrentIndex(qt_index);
}
void InspectorWidget::select_default_node()
{
clear_style_json();
m_dom_tree_view->collapseAll();
m_dom_tree_view->setCurrentIndex({});
}
void InspectorWidget::set_selection(GUI::ModelIndex index)

View file

@ -30,6 +30,13 @@ public:
bool operator==(Selection const& other) const = default;
};
bool dom_loaded() const { return m_dom_loaded; }
void set_selection(Selection);
void clear_selection();
void select_default_node();
void clear_dom_json();
void set_dom_json(StringView dom_json);
@ -52,6 +59,11 @@ private:
ModelTranslator m_computed_style_model {};
ModelTranslator m_resolved_style_model {};
ModelTranslator m_custom_properties_model {};
QTreeView* m_dom_tree_view { nullptr };
bool m_dom_loaded { false };
Optional<Selection> m_pending_selection {};
};
}

View file

@ -23,6 +23,11 @@ public:
endResetModel();
}
RefPtr<GUI::Model> underlying_model()
{
return m_model;
}
virtual int columnCount(QModelIndex const& parent) const override;
virtual int rowCount(QModelIndex const& parent) const override;
virtual QVariant data(QModelIndex const&, int role) const override;

View file

@ -540,12 +540,22 @@ bool WebContentView::is_inspector_open() const
return m_inspector_widget && m_inspector_widget->isVisible();
}
void WebContentView::show_inspector()
void WebContentView::show_inspector(InspectorTarget inspector_target)
{
bool inspector_previously_loaded = m_inspector_widget;
ensure_inspector_widget();
if (!inspector_previously_loaded || !m_inspector_widget->dom_loaded()) {
inspect_dom_tree();
inspect_accessibility_tree();
}
m_inspector_widget->show();
inspect_dom_tree();
inspect_accessibility_tree();
if (inspector_target == InspectorTarget::HoveredElement) {
auto hovered_node = get_hovered_node_id();
m_inspector_widget->set_selection({ hovered_node });
} else {
m_inspector_widget->select_default_node();
}
}
void WebContentView::update_zoom()

View file

@ -93,7 +93,12 @@ public:
void did_get_js_console_messages(i32 start_index, Vector<DeprecatedString> message_types, Vector<DeprecatedString> messages);
void show_js_console();
void show_inspector();
enum class InspectorTarget {
Document,
HoveredElement
};
void show_inspector(InspectorTarget = InspectorTarget::Document);
Ladybird::ConsoleWidget* console() { return m_console_widget; };