Browse Source

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.
Andrew Kaster 1 năm trước cách đây
mục cha
commit
31c0d00ab1

+ 1 - 0
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

+ 26 - 0
Ladybird/Qt/BrowserWindow.cpp

@@ -12,6 +12,7 @@
 #include "Settings.h"
 #include "SettingsDialog.h"
 #include "StringUtils.h"
+#include "TaskManagerWindow.h"
 #include "WebContentView.h"
 #include <AK/TypeCasts.h>
 #include <Ladybird/Utilities.h>
@@ -21,6 +22,7 @@
 #include <LibWebView/UserAgent.h>
 #include <QAction>
 #include <QActionGroup>
+#include <QApplication>
 #include <QClipboard>
 #include <QGuiApplication>
 #include <QInputDialog>
@@ -213,6 +215,14 @@ BrowserWindow::BrowserWindow(Vector<URL::URL> 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();
+}
+
 }

+ 7 - 0
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;

+ 49 - 0
Ladybird/Qt/TaskManagerWindow.cpp

@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include "TaskManagerWindow.h"
+#include <LibWebView/ProcessManager.h>
+#include <QVBoxLayout>
+
+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());
+}
+
+}

+ 31 - 0
Ladybird/Qt/TaskManagerWindow.h

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include "WebContentView.h"
+#include <QTimer>
+#include <QWidget>
+
+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;
+};
+
+}

+ 2 - 0
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

+ 67 - 0
Userland/Libraries/LibWebView/ProcessManager.cpp

@@ -4,6 +4,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
+#include <AK/String.h>
 #include <LibCore/EventLoop.h>
 #include <LibCore/System.h>
 #include <LibWebView/ProcessManager.h>
@@ -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"(
+        <html>
+        <head>
+        <style>
+                table {
+                    width: 100%;
+                    border-collapse: collapse;
+                }
+                th {
+                    text-align: left;
+                    border-bottom: 1px solid #aaa;
+                }
+                td, th {
+                    padding: 4px;
+                    border: 1px solid #aaa;
+                }
+                tr:nth-child(odd) {
+                    background: #f7f7f7;
+                }
+        </style>
+        </head>
+        <body>
+        <table>
+                <thead>
+                <tr>
+                        <th>Type</th>
+                        <th>PID</th>
+                        <th>Memory Usage</th>
+                        <th>CPU %</th>
+                </tr>
+                </thead>
+                <tbody>
+    )"sv);
+
+    for (auto& process : processes) {
+        builder.append("<tr>"sv);
+        builder.append("<td>"sv);
+        builder.append(WebView::process_name_from_type(process.type));
+        builder.append("</td>"sv);
+        builder.append("<td>"sv);
+        builder.append(MUST(String::number(process.pid)));
+        builder.append("</td>"sv);
+        builder.append("<td>"sv);
+        builder.append(MUST(String::formatted("{} KB", process.memory_usage_kib)));
+        builder.append("</td>"sv);
+        builder.append("<td>"sv);
+        builder.append(MUST(String::formatted("{:.1f}", process.cpu_percent)));
+        builder.append("</td>"sv);
+        builder.append("</tr>"sv);
+    }
+
+    builder.append(R"(
+                </tbody>
+                </table>
+                </body>
+                </html>
+    )"sv);
+
+    return builder.to_string_without_validation();
+}
+
 }

+ 2 - 0
Userland/Libraries/LibWebView/ProcessManager.h

@@ -43,6 +43,8 @@ public:
     void update_all_processes();
     Vector<ProcessInfo> processes() const { return m_processes; }
 
+    String generate_html();
+
 private:
     ProcessManager();
     ~ProcessManager();