diff --git a/Ladybird/BrowserWindow.cpp b/Ladybird/BrowserWindow.cpp index 1d34d7b196..277c90c831 100644 --- a/Ladybird/BrowserWindow.cpp +++ b/Ladybird/BrowserWindow.cpp @@ -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; diff --git a/Ladybird/BrowserWindow.h b/Ladybird/BrowserWindow.h index fe3274e8ae..1c769020e6 100644 --- a/Ladybird/BrowserWindow.h +++ b/Ladybird/BrowserWindow.h @@ -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; diff --git a/Ladybird/InspectorWidget.cpp b/Ladybird/InspectorWidget.cpp index a0894c4038..ba567346ef 100644 --- a/Ladybird/InspectorWidget.cpp +++ b/Ladybird/InspectorWidget.cpp @@ -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(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) diff --git a/Ladybird/InspectorWidget.h b/Ladybird/InspectorWidget.h index 1783dd546a..73cb8dcc04 100644 --- a/Ladybird/InspectorWidget.h +++ b/Ladybird/InspectorWidget.h @@ -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 m_pending_selection {}; }; } diff --git a/Ladybird/ModelTranslator.h b/Ladybird/ModelTranslator.h index a276636a1b..40f7c4cde0 100644 --- a/Ladybird/ModelTranslator.h +++ b/Ladybird/ModelTranslator.h @@ -23,6 +23,11 @@ public: endResetModel(); } + RefPtr 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; diff --git a/Ladybird/WebContentView.cpp b/Ladybird/WebContentView.cpp index f371dfa187..20f9781b8b 100644 --- a/Ladybird/WebContentView.cpp +++ b/Ladybird/WebContentView.cpp @@ -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() diff --git a/Ladybird/WebContentView.h b/Ladybird/WebContentView.h index 107db9195b..a51cfed348 100644 --- a/Ladybird/WebContentView.h +++ b/Ladybird/WebContentView.h @@ -93,7 +93,12 @@ public: void did_get_js_console_messages(i32 start_index, Vector message_types, Vector 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; };