diff --git a/Base/res/ladybird/about-pages/processes.html b/Base/res/ladybird/about-pages/processes.html
new file mode 100644
index 00000000000..6d630831e1f
--- /dev/null
+++ b/Base/res/ladybird/about-pages/processes.html
@@ -0,0 +1,175 @@
+
+
+
+ Task Manager
+
+
+
+
+
+
+ Name |
+ PID |
+ CPU |
+ Memory |
+
+
+
+
+
+
+
+
diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt
index 6d39459ed23..db826f8432e 100644
--- a/Libraries/LibWeb/CMakeLists.txt
+++ b/Libraries/LibWeb/CMakeLists.txt
@@ -563,6 +563,7 @@ set(SOURCES
Internals/InternalAnimationTimeline.cpp
Internals/Internals.cpp
Internals/InternalsBase.cpp
+ Internals/Processes.cpp
IntersectionObserver/IntersectionObserver.cpp
IntersectionObserver/IntersectionObserverEntry.cpp
Layout/AudioBox.cpp
diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h
index a0b40c9e817..d85324a960c 100644
--- a/Libraries/LibWeb/Forward.h
+++ b/Libraries/LibWeb/Forward.h
@@ -624,6 +624,7 @@ class RequestList;
namespace Web::Internals {
class Internals;
+class Processes;
}
namespace Web::IntersectionObserver {
diff --git a/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp b/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp
index be50aff2f83..d17003e13b4 100644
--- a/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp
+++ b/Libraries/LibWeb/HTML/Scripting/WindowEnvironmentSettingsObject.cpp
@@ -79,7 +79,7 @@ void WindowEnvironmentSettingsObject::setup(Page& page, URL::URL const& creation
// Non-Standard: We cannot fully initialize window object until *after* the we set up
// the realm's [[HostDefined]] internal slot as the internal slot contains the web platform intrinsics
- MUST(window.initialize_web_interfaces({}));
+ MUST(window.initialize_web_interfaces({}, creation_url));
}
// https://html.spec.whatwg.org/multipage/window-object.html#script-settings-for-window-objects:responsible-document
diff --git a/Libraries/LibWeb/HTML/Window.cpp b/Libraries/LibWeb/HTML/Window.cpp
index 3fae3a9bee3..ba3b173f280 100644
--- a/Libraries/LibWeb/HTML/Window.cpp
+++ b/Libraries/LibWeb/HTML/Window.cpp
@@ -60,6 +60,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -721,7 +722,7 @@ void Window::set_internals_object_exposed(bool exposed)
s_internals_object_exposed = exposed;
}
-WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge)
+WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge, URL::URL const& url)
{
auto& realm = this->realm();
add_window_exposed_interfaces(*this);
@@ -734,6 +735,14 @@ WebIDL::ExceptionOr Window::initialize_web_interfaces(Badge(realm), JS::default_attributes);
+ if (url.scheme() == "about"sv && url.paths().size() == 1) {
+ auto const& path = url.paths().first();
+
+ if (path == "processes"sv) {
+ define_direct_property("processes", realm.create(realm), JS::default_attributes);
+ }
+ }
+
return {};
}
diff --git a/Libraries/LibWeb/HTML/Window.h b/Libraries/LibWeb/HTML/Window.h
index 4a8645973c0..899d07011ee 100644
--- a/Libraries/LibWeb/HTML/Window.h
+++ b/Libraries/LibWeb/HTML/Window.h
@@ -148,7 +148,7 @@ public:
// https://html.spec.whatwg.org/multipage/interaction.html#history-action-activation
bool has_history_action_activation() const;
- WebIDL::ExceptionOr initialize_web_interfaces(Badge);
+ WebIDL::ExceptionOr initialize_web_interfaces(Badge, URL::URL const&);
Vector> pdf_viewer_plugin_objects();
Vector> pdf_viewer_mime_type_objects();
diff --git a/Libraries/LibWeb/Internals/Processes.cpp b/Libraries/LibWeb/Internals/Processes.cpp
new file mode 100644
index 00000000000..09cd2d71c65
--- /dev/null
+++ b/Libraries/LibWeb/Internals/Processes.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2025, Tim Flynn
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Web::Internals {
+
+GC_DEFINE_ALLOCATOR(Processes);
+
+Processes::Processes(JS::Realm& realm)
+ : InternalsBase(realm)
+{
+}
+
+Processes::~Processes() = default;
+
+void Processes::initialize(JS::Realm& realm)
+{
+ Base::initialize(realm);
+ WEB_SET_PROTOTYPE_FOR_INTERFACE(Processes);
+}
+
+void Processes::update_process_statistics()
+{
+ page().client().update_process_statistics();
+}
+
+}
diff --git a/Libraries/LibWeb/Internals/Processes.h b/Libraries/LibWeb/Internals/Processes.h
new file mode 100644
index 00000000000..2fcbea7b0bb
--- /dev/null
+++ b/Libraries/LibWeb/Internals/Processes.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2025, Tim Flynn
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include
+
+namespace Web::Internals {
+
+class Processes final : public InternalsBase {
+ WEB_PLATFORM_OBJECT(Processes, InternalsBase);
+ GC_DECLARE_ALLOCATOR(Processes);
+
+public:
+ virtual ~Processes() override;
+
+ void update_process_statistics();
+
+private:
+ explicit Processes(JS::Realm&);
+
+ virtual void initialize(JS::Realm&) override;
+};
+
+}
diff --git a/Libraries/LibWeb/Internals/Processes.idl b/Libraries/LibWeb/Internals/Processes.idl
new file mode 100644
index 00000000000..9be603113f2
--- /dev/null
+++ b/Libraries/LibWeb/Internals/Processes.idl
@@ -0,0 +1,4 @@
+[Exposed=Nobody]
+interface Processes {
+ undefined updateProcessStatistics();
+};
diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h
index ef834ada7c7..5abb1043cf5 100644
--- a/Libraries/LibWeb/Page/Page.h
+++ b/Libraries/LibWeb/Page/Page.h
@@ -400,6 +400,8 @@ public:
virtual void page_did_mutate_dom([[maybe_unused]] FlyString const& type, [[maybe_unused]] DOM::Node const& target, [[maybe_unused]] DOM::NodeList& added_nodes, [[maybe_unused]] DOM::NodeList& removed_nodes, [[maybe_unused]] GC::Ptr previous_sibling, [[maybe_unused]] GC::Ptr next_sibling, [[maybe_unused]] Optional const& attribute_name) { }
+ virtual void update_process_statistics() { }
+
virtual bool is_ready_to_paint() const = 0;
virtual DisplayListPlayerType display_list_player_type() const = 0;
diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake
index 1ca79bc8591..b51b98cd335 100644
--- a/Libraries/LibWeb/idl_files.cmake
+++ b/Libraries/LibWeb/idl_files.cmake
@@ -266,6 +266,7 @@ libweb_js_bindings(IndexedDB/IDBTransaction)
libweb_js_bindings(IndexedDB/IDBVersionChangeEvent)
libweb_js_bindings(Internals/InternalAnimationTimeline)
libweb_js_bindings(Internals/Internals)
+libweb_js_bindings(Internals/Processes)
libweb_js_bindings(IntersectionObserver/IntersectionObserver)
libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry)
libweb_js_bindings(MathML/MathMLElement)
diff --git a/Libraries/LibWebView/Application.cpp b/Libraries/LibWebView/Application.cpp
index 94ffe6521ff..faed92da45b 100644
--- a/Libraries/LibWebView/Application.cpp
+++ b/Libraries/LibWebView/Application.cpp
@@ -331,6 +331,19 @@ String Application::generate_process_statistics_html()
return m_process_manager.generate_html();
}
+void Application::send_updated_process_statistics_to_view(ViewImplementation& view)
+{
+ m_process_manager.update_all_process_statistics();
+ auto statistics = m_process_manager.serialize_json();
+
+ StringBuilder builder;
+ builder.append("processes.loadProcessStatistics(\""sv);
+ builder.append_escaped_for_json(statistics);
+ builder.append("\");"sv);
+
+ view.run_javascript(MUST(builder.to_string()));
+}
+
void Application::process_did_exit(Process&& process)
{
if (m_in_shutdown)
diff --git a/Libraries/LibWebView/Application.h b/Libraries/LibWebView/Application.h
index ce5becec20d..4d46b1d6a80 100644
--- a/Libraries/LibWebView/Application.h
+++ b/Libraries/LibWebView/Application.h
@@ -60,6 +60,8 @@ public:
void update_process_statistics();
String generate_process_statistics_html();
+ void send_updated_process_statistics_to_view(ViewImplementation&);
+
ErrorOr path_for_downloaded_file(StringView file) const;
enum class DevtoolsState {
diff --git a/Libraries/LibWebView/ProcessManager.cpp b/Libraries/LibWebView/ProcessManager.cpp
index 281be7a2647..e3a0352baf3 100644
--- a/Libraries/LibWebView/ProcessManager.cpp
+++ b/Libraries/LibWebView/ProcessManager.cpp
@@ -4,6 +4,8 @@
* SPDX-License-Identifier: BSD-2-Clause
*/
+#include
+#include
#include
#include
#include
@@ -202,4 +204,33 @@ String ProcessManager::generate_html()
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());
+}
+
}
diff --git a/Libraries/LibWebView/ProcessManager.h b/Libraries/LibWebView/ProcessManager.h
index 21f5bf435fe..478b9f56341 100644
--- a/Libraries/LibWebView/ProcessManager.h
+++ b/Libraries/LibWebView/ProcessManager.h
@@ -37,6 +37,7 @@ public:
void update_all_process_statistics();
String generate_html();
+ String serialize_json();
Function on_process_exited;
diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp
index 6d742706f48..462f382e9eb 100644
--- a/Libraries/LibWebView/WebContentClient.cpp
+++ b/Libraries/LibWebView/WebContentClient.cpp
@@ -690,6 +690,12 @@ Messages::WebContentClient::RequestWorkerAgentResponse WebContentClient::request
return IPC::File {};
}
+void WebContentClient::update_process_statistics(u64 page_id)
+{
+ if (auto view = view_for_page_id(page_id); view.has_value())
+ WebView::Application::the().send_updated_process_statistics_to_view(*view);
+}
+
Optional WebContentClient::view_for_page_id(u64 page_id, SourceLocation location)
{
// Don't bother logging anything for the spare WebContent process. It will only receive a load notification for about:blank.
diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h
index b2dde34bf44..d01b5148c27 100644
--- a/Libraries/LibWebView/WebContentClient.h
+++ b/Libraries/LibWebView/WebContentClient.h
@@ -129,6 +129,7 @@ private:
virtual void did_update_navigation_buttons_state(u64 page_id, bool back_enabled, bool forward_enabled) override;
virtual void did_allocate_backing_stores(u64 page_id, i32 front_bitmap_id, Gfx::ShareableBitmap, i32 back_bitmap_id, Gfx::ShareableBitmap) override;
virtual Messages::WebContentClient::RequestWorkerAgentResponse request_worker_agent(u64 page_id) override;
+ virtual void update_process_statistics(u64 page_id) override;
Optional view_for_page_id(u64, SourceLocation = SourceLocation::current());
diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp
index ed3f59b7bfd..eed200cdd51 100644
--- a/Services/WebContent/PageClient.cpp
+++ b/Services/WebContent/PageClient.cpp
@@ -699,6 +699,11 @@ void PageClient::page_did_mutate_dom(FlyString const& type, Web::DOM::Node const
client().async_did_mutate_dom(m_id, { type.to_string(), target.unique_id(), move(serialized_target), mutation.release_value() });
}
+void PageClient::update_process_statistics()
+{
+ client().async_update_process_statistics(m_id);
+}
+
ErrorOr PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_path)
{
VERIFY(!m_webdriver);
diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h
index 666feaa300c..b04811f4d27 100644
--- a/Services/WebContent/PageClient.h
+++ b/Services/WebContent/PageClient.h
@@ -174,6 +174,7 @@ private:
virtual void page_did_allocate_backing_stores(i32 front_bitmap_id, Gfx::ShareableBitmap front_bitmap, i32 back_bitmap_id, Gfx::ShareableBitmap back_bitmap) override;
virtual IPC::File request_worker_agent() override;
virtual void page_did_mutate_dom(FlyString const& type, Web::DOM::Node const& target, Web::DOM::NodeList& added_nodes, Web::DOM::NodeList& removed_nodes, GC::Ptr previous_sibling, GC::Ptr next_sibling, Optional const& attribute_name) override;
+ virtual void update_process_statistics() override;
Web::Layout::Viewport* layout_root();
void setup_palette();
diff --git a/Services/WebContent/WebContentClient.ipc b/Services/WebContent/WebContentClient.ipc
index 732b3839d4f..b7b6d076b33 100644
--- a/Services/WebContent/WebContentClient.ipc
+++ b/Services/WebContent/WebContentClient.ipc
@@ -108,4 +108,6 @@ endpoint WebContentClient
did_find_in_page(u64 page_id, size_t current_match_index, Optional total_match_count) =|
request_worker_agent(u64 page_id) => (IPC::File socket) // FIXME: Add required attributes to select a SharedWorker Agent
+
+ update_process_statistics(u64 page_id) =|
}
diff --git a/UI/cmake/ResourceFiles.cmake b/UI/cmake/ResourceFiles.cmake
index 1447fe40223..a2f3ee8d694 100644
--- a/UI/cmake/ResourceFiles.cmake
+++ b/UI/cmake/ResourceFiles.cmake
@@ -64,6 +64,7 @@ list(TRANSFORM BROWSER_ICONS PREPEND "${LADYBIRD_SOURCE_DIR}/Base/res/icons/brow
set(ABOUT_PAGES
about.html
newtab.html
+ processes.html
)
set(WEB_TEMPLATES
directory.html