From 31c0d00ab1d650525f68f0762b21aae974836d30 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Tue, 26 Mar 2024 11:38:12 -0600 Subject: [PATCH] Ladybird: Add a simple TaskManager window for tracking child processes This implementation uses a really basic WebView to update stats once a second. In the future it might make more sense to both move the details into LibWebView, and to create a native widget for each platform to remove the overhead of having an extra WebView. --- Ladybird/CMakeLists.txt | 1 + Ladybird/Qt/BrowserWindow.cpp | 26 +++++++ Ladybird/Qt/BrowserWindow.h | 7 ++ Ladybird/Qt/TaskManagerWindow.cpp | 49 ++++++++++++++ Ladybird/Qt/TaskManagerWindow.h | 31 +++++++++ Ladybird/cmake/ResourceFiles.cmake | 2 + .../Libraries/LibWebView/ProcessManager.cpp | 67 +++++++++++++++++++ .../Libraries/LibWebView/ProcessManager.h | 2 + 8 files changed, 185 insertions(+) create mode 100644 Ladybird/Qt/TaskManagerWindow.cpp create mode 100644 Ladybird/Qt/TaskManagerWindow.h diff --git a/Ladybird/CMakeLists.txt b/Ladybird/CMakeLists.txt index 0f6f978a33..83ea86f20e 100644 --- a/Ladybird/CMakeLists.txt +++ b/Ladybird/CMakeLists.txt @@ -124,6 +124,7 @@ if (ENABLE_QT) Qt/Settings.cpp Qt/SettingsDialog.cpp Qt/Tab.cpp + Qt/TaskManagerWindow.cpp Qt/TVGIconEngine.cpp Qt/StringUtils.cpp Qt/WebContentView.cpp diff --git a/Ladybird/Qt/BrowserWindow.cpp b/Ladybird/Qt/BrowserWindow.cpp index e2a9850fbd..c7f50edd11 100644 --- a/Ladybird/Qt/BrowserWindow.cpp +++ b/Ladybird/Qt/BrowserWindow.cpp @@ -12,6 +12,7 @@ #include "Settings.h" #include "SettingsDialog.h" #include "StringUtils.h" +#include "TaskManagerWindow.h" #include "WebContentView.h" #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -213,6 +215,14 @@ BrowserWindow::BrowserWindow(Vector const& initial_urls, WebView::Cook } }); + auto* task_manager_action = new QAction("Open Task &Manager", this); + task_manager_action->setIcon(load_icon_from_uri("resource://icons/16x16/app-system-monitor.png"sv)); + task_manager_action->setShortcuts({ QKeySequence("Ctrl+Shift+M") }); + inspect_menu->addAction(task_manager_action); + QObject::connect(task_manager_action, &QAction::triggered, this, [this] { + show_task_manager_window(); + }); + auto* debug_menu = menuBar()->addMenu("&Debug"); auto* dump_session_history_tree_action = new QAction("Dump Session History Tree", this); @@ -890,4 +900,20 @@ void BrowserWindow::closeEvent(QCloseEvent* event) QMainWindow::closeEvent(event); } +void BrowserWindow::show_task_manager_window() +{ + if (!m_task_manager_window) { + m_task_manager_window = new TaskManagerWindow(this); + } + m_task_manager_window->show(); + m_task_manager_window->activateWindow(); + m_task_manager_window->raise(); +} + +void BrowserWindow::close_task_manager_window() +{ + if (m_task_manager_window) + m_task_manager_window->close(); +} + } diff --git a/Ladybird/Qt/BrowserWindow.h b/Ladybird/Qt/BrowserWindow.h index 619588384f..a5a651abe8 100644 --- a/Ladybird/Qt/BrowserWindow.h +++ b/Ladybird/Qt/BrowserWindow.h @@ -24,6 +24,7 @@ namespace Ladybird { class SettingsDialog; class WebContentView; +class TaskManagerWindow; class BrowserWindow : public QMainWindow { Q_OBJECT @@ -132,6 +133,9 @@ private: QString tool_tip_for_page_mute_state(Tab&) const; QTabBar::ButtonPosition audio_button_position_for_tab(int tab_index) const; + void show_task_manager_window(); + void close_task_manager_window(); + QScreen* m_current_screen; double m_device_pixel_ratio { 0 }; @@ -150,6 +154,9 @@ private: SettingsDialog* m_settings_dialog { nullptr }; + // FIXME: This should be owned at a higher level in case we have multiple browser windows + TaskManagerWindow* m_task_manager_window { nullptr }; + WebView::CookieJar& m_cookie_jar; WebContentOptions m_web_content_options; diff --git a/Ladybird/Qt/TaskManagerWindow.cpp b/Ladybird/Qt/TaskManagerWindow.cpp new file mode 100644 index 0000000000..cf4a1cb4c6 --- /dev/null +++ b/Ladybird/Qt/TaskManagerWindow.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "TaskManagerWindow.h" +#include +#include + +namespace Ladybird { + +TaskManagerWindow::TaskManagerWindow(QWidget* parent) + : QWidget(parent, Qt::WindowFlags(Qt::WindowType::Window)) + , m_web_view(new WebContentView(this, {}, {})) +{ + setLayout(new QVBoxLayout); + layout()->addWidget(m_web_view); + + setWindowTitle("Task Manager"); + resize(400, 300); + + m_update_timer.setInterval(1000); + + QObject::connect(&m_update_timer, &QTimer::timeout, [this] { + this->update_statistics(); + }); + + update_statistics(); +} + +void TaskManagerWindow::showEvent(QShowEvent*) +{ + m_update_timer.start(); +} + +void TaskManagerWindow::hideEvent(QHideEvent*) +{ + m_update_timer.stop(); +} + +void TaskManagerWindow::update_statistics() +{ + + WebView::ProcessManager::the().update_all_processes(); + m_web_view->load_html(WebView::ProcessManager::the().generate_html()); +} + +} diff --git a/Ladybird/Qt/TaskManagerWindow.h b/Ladybird/Qt/TaskManagerWindow.h new file mode 100644 index 0000000000..efe7863e36 --- /dev/null +++ b/Ladybird/Qt/TaskManagerWindow.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, Andrew Kaster + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include "WebContentView.h" +#include +#include + +namespace Ladybird { + +class TaskManagerWindow : public QWidget { + Q_OBJECT + +public: + explicit TaskManagerWindow(QWidget* parent); + +private: + virtual void showEvent(QShowEvent*) override; + virtual void hideEvent(QHideEvent*) override; + + void update_statistics(); + + WebContentView* m_web_view { nullptr }; + QTimer m_update_timer; +}; + +} diff --git a/Ladybird/cmake/ResourceFiles.cmake b/Ladybird/cmake/ResourceFiles.cmake index 52d6fbc24c..ee7741f5f8 100644 --- a/Ladybird/cmake/ResourceFiles.cmake +++ b/Ladybird/cmake/ResourceFiles.cmake @@ -18,6 +18,7 @@ list(TRANSFORM FONTS PREPEND "${SERENITY_SOURCE_DIR}/Base/res/fonts/") set(16x16_ICONS app-browser.png + app-system-monitor.png audio-volume-high.png audio-volume-muted.png close-tab.png @@ -48,6 +49,7 @@ set(16x16_ICONS ) set(32x32_ICONS app-browser.png + app-system-monitor.png filetype-folder.png filetype-unknown.png msgbox-warning.png diff --git a/Userland/Libraries/LibWebView/ProcessManager.cpp b/Userland/Libraries/LibWebView/ProcessManager.cpp index 12e0a8b7d6..9cf12d005a 100644 --- a/Userland/Libraries/LibWebView/ProcessManager.cpp +++ b/Userland/Libraries/LibWebView/ProcessManager.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -114,4 +115,70 @@ void ProcessManager::update_all_processes() // FIXME: Actually gather stats in a platform-specific way } +String ProcessManager::generate_html() +{ + StringBuilder builder; + auto processes = m_processes; + + builder.append(R"( + + + + + + + + + + + + + + + + )"sv); + + for (auto& process : processes) { + builder.append(""sv); + builder.append(""sv); + builder.append(""sv); + builder.append(""sv); + builder.append(""sv); + builder.append(""sv); + } + + builder.append(R"( + +
TypePIDMemory UsageCPU %
"sv); + builder.append(WebView::process_name_from_type(process.type)); + builder.append(""sv); + builder.append(MUST(String::number(process.pid))); + builder.append(""sv); + builder.append(MUST(String::formatted("{} KB", process.memory_usage_kib))); + builder.append(""sv); + builder.append(MUST(String::formatted("{:.1f}", process.cpu_percent))); + builder.append("
+ + + )"sv); + + return builder.to_string_without_validation(); +} + } diff --git a/Userland/Libraries/LibWebView/ProcessManager.h b/Userland/Libraries/LibWebView/ProcessManager.h index 266a41175c..4149b31678 100644 --- a/Userland/Libraries/LibWebView/ProcessManager.h +++ b/Userland/Libraries/LibWebView/ProcessManager.h @@ -43,6 +43,8 @@ public: void update_all_processes(); Vector processes() const { return m_processes; } + String generate_html(); + private: ProcessManager(); ~ProcessManager();