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.
This commit is contained in:
Andrew Kaster 2024-03-26 11:38:12 -06:00 committed by Andrew Kaster
parent 096feaaeb8
commit 31c0d00ab1
8 changed files with 185 additions and 0 deletions

View file

@ -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

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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());
}
}

View file

@ -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;
};
}

View file

@ -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

View file

@ -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();
}
}

View file

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