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