From 843209c6a961bad53aa58bc20a3135954914b3f7 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Sun, 16 Mar 2025 10:49:28 -0400 Subject: [PATCH] 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. --- Base/res/ladybird/about-pages/processes.html | 175 ++++++++++++++++++ Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/Forward.h | 1 + .../WindowEnvironmentSettingsObject.cpp | 2 +- Libraries/LibWeb/HTML/Window.cpp | 11 +- Libraries/LibWeb/HTML/Window.h | 2 +- Libraries/LibWeb/Internals/Processes.cpp | 35 ++++ Libraries/LibWeb/Internals/Processes.h | 28 +++ Libraries/LibWeb/Internals/Processes.idl | 4 + Libraries/LibWeb/Page/Page.h | 2 + Libraries/LibWeb/idl_files.cmake | 1 + Libraries/LibWebView/Application.cpp | 13 ++ Libraries/LibWebView/Application.h | 2 + Libraries/LibWebView/ProcessManager.cpp | 31 ++++ Libraries/LibWebView/ProcessManager.h | 1 + Libraries/LibWebView/WebContentClient.cpp | 6 + Libraries/LibWebView/WebContentClient.h | 1 + Services/WebContent/PageClient.cpp | 5 + Services/WebContent/PageClient.h | 1 + Services/WebContent/WebContentClient.ipc | 2 + UI/cmake/ResourceFiles.cmake | 1 + 21 files changed, 322 insertions(+), 3 deletions(-) create mode 100644 Base/res/ladybird/about-pages/processes.html create mode 100644 Libraries/LibWeb/Internals/Processes.cpp create mode 100644 Libraries/LibWeb/Internals/Processes.h create mode 100644 Libraries/LibWeb/Internals/Processes.idl 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 + + + + + + + + + + + + + +
NamePIDCPUMemory
+ + + + 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