1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-08 13:37:10 +09:00
ladybird/Libraries/LibWebView/ProcessManager.cpp
Timothy Flynn 843209c6a9 LibWeb+LibWebView+WebContent: Add an about:processes page
The intent is that this will replace the separate Task Manager window.
This will allow us to more easily add features such as actual process
management, better rendering of the process table, etc. Included in this
page is the ability to sort table rows.

This also lays the ground work for more internal `about` pages, such as
about:config.
2025-03-19 10:03:17 -04:00

236 lines
6.9 KiB
C++

/*
* Copyright (c) 2024, Andrew Kaster <akaster@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/JsonArraySerializer.h>
#include <AK/JsonObjectSerializer.h>
#include <AK/NumberFormat.h>
#include <AK/String.h>
#include <LibCore/EventLoop.h>
#include <LibCore/System.h>
#include <LibWebView/ProcessManager.h>
namespace WebView {
ProcessType process_type_from_name(StringView name)
{
if (name == "Browser"sv)
return ProcessType::Browser;
if (name == "WebContent"sv)
return ProcessType::WebContent;
if (name == "WebWorker"sv)
return ProcessType::WebWorker;
if (name == "RequestServer"sv)
return ProcessType::RequestServer;
if (name == "ImageDecoder"sv)
return ProcessType::ImageDecoder;
dbgln("Unknown process type: '{}'", name);
VERIFY_NOT_REACHED();
}
StringView process_name_from_type(ProcessType type)
{
switch (type) {
case ProcessType::Browser:
return "Browser"sv;
case ProcessType::WebContent:
return "WebContent"sv;
case ProcessType::WebWorker:
return "WebWorker"sv;
case ProcessType::RequestServer:
return "RequestServer"sv;
case ProcessType::ImageDecoder:
return "ImageDecoder"sv;
}
VERIFY_NOT_REACHED();
}
ProcessManager::ProcessManager()
: on_process_exited([](Process&&) { })
{
m_signal_handle = Core::EventLoop::register_signal(SIGCHLD, [this](int) {
auto result = Core::System::waitpid(-1, WNOHANG);
while (!result.is_error() && result.value().pid > 0) {
auto& [pid, status] = result.value();
if (WIFEXITED(status) || WIFSIGNALED(status)) {
if (auto process = remove_process(pid); process.has_value())
on_process_exited(process.release_value());
}
result = Core::System::waitpid(-1, WNOHANG);
}
});
add_process(Process(WebView::ProcessType::Browser, nullptr, Core::Process::current()));
#ifdef AK_OS_MACH
auto self_send_port = mach_task_self();
auto res = mach_port_mod_refs(mach_task_self(), self_send_port, MACH_PORT_RIGHT_SEND, +1);
VERIFY(res == KERN_SUCCESS);
set_process_mach_port(getpid(), Core::MachPort::adopt_right(self_send_port, Core::MachPort::PortRight::Send));
#endif
}
ProcessManager::~ProcessManager()
{
Core::EventLoop::unregister_signal(m_signal_handle);
}
Optional<Process&> ProcessManager::find_process(pid_t pid)
{
return m_processes.get(pid);
}
void ProcessManager::add_process(WebView::Process&& process)
{
Threading::MutexLocker locker { m_lock };
auto pid = process.pid();
auto result = m_processes.set(pid, move(process));
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
m_statistics.processes.append(make<Core::Platform::ProcessInfo>(pid));
}
#if defined(AK_OS_MACH)
void ProcessManager::set_process_mach_port(pid_t pid, Core::MachPort&& port)
{
Threading::MutexLocker locker { m_lock };
for (auto const& info : m_statistics.processes) {
if (info->pid == pid) {
info->child_task_port = move(port);
return;
}
}
}
#endif
Optional<Process> ProcessManager::remove_process(pid_t pid)
{
Threading::MutexLocker locker { m_lock };
m_statistics.processes.remove_first_matching([&](auto const& info) {
return (info->pid == pid);
});
return m_processes.take(pid);
}
void ProcessManager::update_all_process_statistics()
{
Threading::MutexLocker locker { m_lock };
(void)update_process_statistics(m_statistics);
}
String ProcessManager::generate_html()
{
Threading::MutexLocker locker { m_lock };
StringBuilder builder;
builder.append(R"(
<html>
<head>
<title>Task Manager</title>
<style>
@media (prefers-color-scheme: dark) {
tr:nth-child(even) {
background: rgb(57, 57, 57);
}
}
@media (prefers-color-scheme: light) {
tr:nth-child(even) {
background: #f7f7f7;
}
}
html {
color-scheme: light dark;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
border-bottom: 1px solid #aaa;
}
td, th {
padding: 4px;
border: 1px solid #aaa;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Name</th>
<th>PID</th>
<th>Memory Usage</th>
<th>CPU %</th>
</tr>
</thead>
<tbody>
)"sv);
m_statistics.for_each_process([&](auto const& process) {
builder.append("<tr>"sv);
builder.append("<td>"sv);
auto& process_handle = this->find_process(process.pid).value();
builder.append(WebView::process_name_from_type(process_handle.type()));
if (process_handle.title().has_value())
builder.appendff(" - {}", escape_html_entities(*process_handle.title()));
builder.append("</td>"sv);
builder.append("<td>"sv);
builder.append(String::number(process.pid));
builder.append("</td>"sv);
builder.append("<td>"sv);
builder.append(human_readable_size(process.memory_usage_bytes));
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();
}
String ProcessManager::serialize_json()
{
Threading::MutexLocker locker { m_lock };
StringBuilder builder;
auto serializer = MUST(JsonArraySerializer<>::try_create(builder));
m_statistics.for_each_process([&](auto const& process) {
auto& process_handle = find_process(process.pid).value();
auto type = WebView::process_name_from_type(process_handle.type());
auto const& title = process_handle.title();
auto process_name = title.has_value()
? MUST(String::formatted("{} - {}", type, *title))
: String::from_utf8_without_validation(type.bytes());
auto object = MUST(serializer.add_object());
MUST(object.add("name"sv, move(process_name)));
MUST(object.add("pid"sv, process.pid));
MUST(object.add("cpu"sv, process.cpu_percent));
MUST(object.add("memory"sv, process.memory_usage_bytes));
MUST(object.finish());
});
MUST(serializer.finish());
return MUST(builder.to_string());
}
}