From 41aeb9e63a56f34722fda17cee731980bbf4e9de Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 24 Mar 2025 09:27:36 -0400 Subject: [PATCH] LibWeb+LibWebView+WebContent: Introduce a WebUI framework When we build internal pages (e.g. about:settings), there is currently quite a lot of boilerplate needed to communicate between the browser and the page. This includes creating IDL for the page and the IPC for every message sent between the processes. These internal pages are also special in that they have privileged access to and control over the browser process. The framework introduced here serves to ease the setup of new internal pages and to reduce the access that WebContent processes have to the browser process. WebUI pages can send requests to the browser process via a `ladybird.sendMessage` API. Responses from the browser are passed through a WebUIMessage event. So, for example, an internal page may: ladybird.sendMessage("getDataFor", { id: 123 }); document.addEventListener("WebUIMessage", event => { if (event.name === "gotData") { console.assert(event.data.id === 123); } }); To handle these messages, we set up a new IPC connection between the browser and WebContent processes. This connection is torn down when the user navigates away from the internal page. --- Libraries/LibWeb/CMakeLists.txt | 1 + Libraries/LibWeb/Forward.h | 1 + Libraries/LibWeb/Internals/WebUI.cpp | 34 +++++++ Libraries/LibWeb/Internals/WebUI.h | 29 ++++++ Libraries/LibWeb/Internals/WebUI.idl | 4 + Libraries/LibWeb/Page/Page.h | 2 + Libraries/LibWeb/idl_files.cmake | 1 + Libraries/LibWebView/CMakeLists.txt | 3 + Libraries/LibWebView/Forward.h | 1 + Libraries/LibWebView/WebContentClient.cpp | 19 +++- Libraries/LibWebView/WebContentClient.h | 4 + Libraries/LibWebView/WebUI.cpp | 75 ++++++++++++++++ Libraries/LibWebView/WebUI.h | 63 +++++++++++++ Meta/Lagom/CMakeLists.txt | 2 + Services/WebContent/CMakeLists.txt | 1 + Services/WebContent/ConnectionFromClient.cpp | 9 ++ Services/WebContent/ConnectionFromClient.h | 1 + Services/WebContent/Forward.h | 1 + Services/WebContent/PageClient.cpp | 24 +++++ Services/WebContent/PageClient.h | 3 + Services/WebContent/WebContentServer.ipc | 1 + Services/WebContent/WebUIClient.ipc | 3 + Services/WebContent/WebUIConnection.cpp | 95 ++++++++++++++++++++ Services/WebContent/WebUIConnection.h | 39 ++++++++ Services/WebContent/WebUIServer.ipc | 3 + 25 files changed, 416 insertions(+), 3 deletions(-) create mode 100644 Libraries/LibWeb/Internals/WebUI.cpp create mode 100644 Libraries/LibWeb/Internals/WebUI.h create mode 100644 Libraries/LibWeb/Internals/WebUI.idl create mode 100644 Libraries/LibWebView/WebUI.cpp create mode 100644 Libraries/LibWebView/WebUI.h create mode 100644 Services/WebContent/WebUIClient.ipc create mode 100644 Services/WebContent/WebUIConnection.cpp create mode 100644 Services/WebContent/WebUIConnection.h create mode 100644 Services/WebContent/WebUIServer.ipc diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index da4c2e7a9bf..49698ff26d4 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -568,6 +568,7 @@ set(SOURCES Internals/InternalsBase.cpp Internals/Processes.cpp Internals/Settings.cpp + Internals/WebUI.cpp IntersectionObserver/IntersectionObserver.cpp IntersectionObserver/IntersectionObserverEntry.cpp Layout/AudioBox.cpp diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index ae7b6c43c33..6f890896e09 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -628,6 +628,7 @@ class RequestList; namespace Web::Internals { class Internals; class Processes; +class WebUI; } namespace Web::IntersectionObserver { diff --git a/Libraries/LibWeb/Internals/WebUI.cpp b/Libraries/LibWeb/Internals/WebUI.cpp new file mode 100644 index 00000000000..6eafca240a7 --- /dev/null +++ b/Libraries/LibWeb/Internals/WebUI.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::Internals { + +GC_DEFINE_ALLOCATOR(WebUI); + +WebUI::WebUI(JS::Realm& realm) + : InternalsBase(realm) +{ +} + +WebUI::~WebUI() = default; + +void WebUI::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + WEB_SET_PROTOTYPE_FOR_INTERFACE(WebUI); +} + +void WebUI::send_message(String const& name, JS::Value data) +{ + page().client().received_message_from_web_ui(name, data); +} + +} diff --git a/Libraries/LibWeb/Internals/WebUI.h b/Libraries/LibWeb/Internals/WebUI.h new file mode 100644 index 00000000000..d1b067bc6f8 --- /dev/null +++ b/Libraries/LibWeb/Internals/WebUI.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Web::Internals { + +class WebUI final : public InternalsBase { + WEB_PLATFORM_OBJECT(WebUI, InternalsBase); + GC_DECLARE_ALLOCATOR(WebUI); + +public: + virtual ~WebUI() override; + + void send_message(String const& name, JS::Value data); + +private: + explicit WebUI(JS::Realm&); + + virtual void initialize(JS::Realm&) override; +}; + +} diff --git a/Libraries/LibWeb/Internals/WebUI.idl b/Libraries/LibWeb/Internals/WebUI.idl new file mode 100644 index 00000000000..91c2383bd89 --- /dev/null +++ b/Libraries/LibWeb/Internals/WebUI.idl @@ -0,0 +1,4 @@ +[Exposed=Nobody] +interface WebUI { + undefined sendMessage(DOMString name, optional any data = null); +}; diff --git a/Libraries/LibWeb/Page/Page.h b/Libraries/LibWeb/Page/Page.h index e845ff43cc7..d08ba7e6ddd 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 received_message_from_web_ui([[maybe_unused]] String const& name, [[maybe_unused]] JS::Value data) { } + virtual void update_process_statistics() { } virtual void request_current_settings() { } diff --git a/Libraries/LibWeb/idl_files.cmake b/Libraries/LibWeb/idl_files.cmake index 6962eb5a474..33ca0e21bcd 100644 --- a/Libraries/LibWeb/idl_files.cmake +++ b/Libraries/LibWeb/idl_files.cmake @@ -268,6 +268,7 @@ libweb_js_bindings(Internals/InternalAnimationTimeline) libweb_js_bindings(Internals/Internals) libweb_js_bindings(Internals/Processes) libweb_js_bindings(Internals/Settings) +libweb_js_bindings(Internals/WebUI) libweb_js_bindings(IntersectionObserver/IntersectionObserver) libweb_js_bindings(IntersectionObserver/IntersectionObserverEntry) libweb_js_bindings(MathML/MathMLElement) diff --git a/Libraries/LibWebView/CMakeLists.txt b/Libraries/LibWebView/CMakeLists.txt index 60b7fd49658..c69ad93d447 100644 --- a/Libraries/LibWebView/CMakeLists.txt +++ b/Libraries/LibWebView/CMakeLists.txt @@ -24,6 +24,7 @@ set(SOURCES Utilities.cpp ViewImplementation.cpp WebContentClient.cpp + WebUI.cpp ) if (APPLE) @@ -69,6 +70,8 @@ set(GENERATED_SOURCES ../../Services/WebContent/WebContentServerEndpoint.h ../../Services/WebContent/WebDriverClientEndpoint.h ../../Services/WebContent/WebDriverServerEndpoint.h + ../../Services/WebContent/WebUIClientEndpoint.h + ../../Services/WebContent/WebUIServerEndpoint.h NativeStyleSheetSource.cpp UIProcessClientEndpoint.h UIProcessServerEndpoint.h diff --git a/Libraries/LibWebView/Forward.h b/Libraries/LibWebView/Forward.h index 860a7a65744..4b172e1a659 100644 --- a/Libraries/LibWebView/Forward.h +++ b/Libraries/LibWebView/Forward.h @@ -18,6 +18,7 @@ class ProcessManager; class Settings; class ViewImplementation; class WebContentClient; +class WebUI; struct Attribute; struct ConsoleOutput; diff --git a/Libraries/LibWebView/WebContentClient.cpp b/Libraries/LibWebView/WebContentClient.cpp index dfb93a78821..3a9792fe7c4 100644 --- a/Libraries/LibWebView/WebContentClient.cpp +++ b/Libraries/LibWebView/WebContentClient.cpp @@ -4,12 +4,13 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include "WebContentClient.h" -#include "Application.h" -#include "ViewImplementation.h" #include +#include #include #include +#include +#include +#include namespace WebView { @@ -68,6 +69,11 @@ void WebContentClient::unregister_view(u64 page_id) } } +void WebContentClient::web_ui_disconnected(Badge) +{ + m_web_ui.clear(); +} + void WebContentClient::did_paint(u64 page_id, Gfx::IntRect rect, i32 bitmap_id) { if (auto view = view_for_page_id(page_id); view.has_value()) @@ -95,6 +101,13 @@ void WebContentClient::did_start_loading(u64 page_id, URL::URL url, bool is_redi void WebContentClient::did_finish_loading(u64 page_id, URL::URL url) { + if (url.scheme() == "about"sv && url.paths().size() == 1) { + if (auto web_ui = WebUI::create(*this, url.paths().first()); web_ui.is_error()) + warnln("Could not create WebUI for {}: {}", url, web_ui.error()); + else + m_web_ui = web_ui.release_value(); + } + if (auto view = view_for_page_id(page_id); view.has_value()) { view->set_url({}, url); diff --git a/Libraries/LibWebView/WebContentClient.h b/Libraries/LibWebView/WebContentClient.h index d1b85cac40d..4025508b903 100644 --- a/Libraries/LibWebView/WebContentClient.h +++ b/Libraries/LibWebView/WebContentClient.h @@ -47,6 +47,8 @@ public: void register_view(u64 page_id, ViewImplementation&); void unregister_view(u64 page_id); + void web_ui_disconnected(Badge); + Function on_web_content_process_crash; pid_t pid() const { return m_process_handle.pid; } @@ -142,6 +144,8 @@ private: ProcessHandle m_process_handle; + RefPtr m_web_ui; + static HashTable s_clients; }; diff --git a/Libraries/LibWebView/WebUI.cpp b/Libraries/LibWebView/WebUI.cpp new file mode 100644 index 00000000000..9806b35fcd2 --- /dev/null +++ b/Libraries/LibWebView/WebUI.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace WebView { + +template +static ErrorOr> create_web_ui(WebContentClient& client, String host) +{ + Array socket_fds { 0, 0 }; + TRY(Core::System::socketpair(AF_LOCAL, SOCK_STREAM, 0, socket_fds.data())); + + auto client_socket = Core::LocalSocket::adopt_fd(socket_fds[0]); + if (client_socket.is_error()) { + close(socket_fds[0]); + close(socket_fds[1]); + + return client_socket.release_error(); + } + + auto web_ui = WebUIType::create(client, IPC::Transport { client_socket.release_value() }, move(host)); + client.async_connect_to_web_ui(0, IPC::File::adopt_fd(socket_fds[1])); + + return web_ui; +} + +ErrorOr> WebUI::create(WebContentClient&, String) +{ + RefPtr web_ui; + + if (web_ui) + web_ui->register_interfaces(); + + return web_ui; +} + +WebUI::WebUI(WebContentClient& client, IPC::Transport transport, String host) + : IPC::ConnectionToServer(*this, move(transport)) + , m_client(client) + , m_host(move(host)) +{ +} + +WebUI::~WebUI() = default; + +void WebUI::die() +{ + m_client.web_ui_disconnected({}); +} + +void WebUI::register_interface(StringView name, Interface interface) +{ + auto result = m_interfaces.set(name, move(interface)); + VERIFY(result == HashSetResult::InsertedNewEntry); +} + +void WebUI::received_message(String name, JsonValue data) +{ + auto interface = m_interfaces.get(name); + if (!interface.has_value()) { + warnln("Received message from WebUI for unrecognized interface: {}", name); + return; + } + + interface.value()(move(data)); +} + +} diff --git a/Libraries/LibWebView/WebUI.h b/Libraries/LibWebView/WebUI.h new file mode 100644 index 00000000000..4c312b2befe --- /dev/null +++ b/Libraries/LibWebView/WebUI.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WebView { + +class WebUI + : public IPC::ConnectionToServer + , public WebUIClientEndpoint { +public: + static ErrorOr> create(WebContentClient&, String host); + virtual ~WebUI(); + + String const& host() const { return m_host; } + +protected: + WebUI(WebContentClient&, IPC::Transport, String host); + + using Interface = Function; + + virtual void register_interfaces() { } + void register_interface(StringView name, Interface); + +private: + virtual void die() override; + virtual void received_message(String name, JsonValue data) override; + + WebContentClient& m_client; + String m_host; + + HashMap m_interfaces; +}; + +#define WEB_UI(WebUIType) \ +public: \ + static NonnullRefPtr create(WebContentClient& client, IPC::Transport transport, String host) \ + { \ + return adopt_ref(*new WebUIType(client, move(transport), move(host))); \ + } \ + \ +private: \ + WebUIType(WebContentClient& client, IPC::Transport transport, String host) \ + : WebView::WebUI(client, move(transport), move(host)) \ + { \ + } + +} diff --git a/Meta/Lagom/CMakeLists.txt b/Meta/Lagom/CMakeLists.txt index 8e7dca3dc4d..101daeccd2d 100644 --- a/Meta/Lagom/CMakeLists.txt +++ b/Meta/Lagom/CMakeLists.txt @@ -433,6 +433,8 @@ compile_ipc(${SERENITY_PROJECT_ROOT}/Services/WebContent/WebContentServer.ipc Se compile_ipc(${SERENITY_PROJECT_ROOT}/Services/WebContent/WebContentClient.ipc Services/WebContent/WebContentClientEndpoint.h) compile_ipc(${SERENITY_PROJECT_ROOT}/Services/WebContent/WebDriverClient.ipc Services/WebContent/WebDriverClientEndpoint.h) compile_ipc(${SERENITY_PROJECT_ROOT}/Services/WebContent/WebDriverServer.ipc Services/WebContent/WebDriverServerEndpoint.h) +compile_ipc(${SERENITY_PROJECT_ROOT}/Services/WebContent/WebUIClient.ipc Services/WebContent/WebUIClientEndpoint.h) +compile_ipc(${SERENITY_PROJECT_ROOT}/Services/WebContent/WebUIServer.ipc Services/WebContent/WebUIServerEndpoint.h) foreach(lib IN LISTS lagom_standard_libraries) add_serenity_subdirectory("Libraries/Lib${lib}") diff --git a/Services/WebContent/CMakeLists.txt b/Services/WebContent/CMakeLists.txt index 652ddc009bc..ed3215395f0 100644 --- a/Services/WebContent/CMakeLists.txt +++ b/Services/WebContent/CMakeLists.txt @@ -9,6 +9,7 @@ set(SOURCES PageHost.cpp WebContentConsoleClient.cpp WebDriverConnection.cpp + WebUIConnection.cpp ) if (ANDROID) diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index 4feb100e23a..1c1fffd176a 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -124,6 +124,15 @@ void ConnectionFromClient::connect_to_webdriver(u64 page_id, ByteString webdrive } } +void ConnectionFromClient::connect_to_web_ui(u64 page_id, IPC::File web_ui_socket) +{ + if (auto page = this->page(page_id); page.has_value()) { + // FIXME: Propagate this error back to the browser. + if (auto result = page->connect_to_web_ui(move(web_ui_socket)); result.is_error()) + dbgln("Unable to connect to the WebUI host: {}", result.error()); + } +} + void ConnectionFromClient::connect_to_image_decoder(IPC::File image_decoder_socket) { if (on_image_decoder_connection) diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index edc083edb89..d288a3040b0 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -61,6 +61,7 @@ private: virtual Messages::WebContentServer::GetWindowHandleResponse get_window_handle(u64 page_id) override; virtual void set_window_handle(u64 page_id, String handle) override; virtual void connect_to_webdriver(u64 page_id, ByteString webdriver_ipc_path) override; + virtual void connect_to_web_ui(u64 page_id, IPC::File web_ui_socket) override; virtual void connect_to_image_decoder(IPC::File image_decoder_socket) override; virtual void update_system_theme(u64 page_id, Core::AnonymousBuffer) override; virtual void update_screen_rects(u64 page_id, Vector, u32) override; diff --git a/Services/WebContent/Forward.h b/Services/WebContent/Forward.h index 215a41e60ef..5724e323b55 100644 --- a/Services/WebContent/Forward.h +++ b/Services/WebContent/Forward.h @@ -15,5 +15,6 @@ class PageHost; class PageClient; class WebContentConsoleClient; class WebDriverConnection; +class WebUIConnection; } diff --git a/Services/WebContent/PageClient.cpp b/Services/WebContent/PageClient.cpp index ad9f06783b9..433328b8be9 100644 --- a/Services/WebContent/PageClient.cpp +++ b/Services/WebContent/PageClient.cpp @@ -32,6 +32,7 @@ #include #include #include +#include namespace WebContent { @@ -95,6 +96,8 @@ void PageClient::visit_edges(JS::Cell::Visitor& visitor) if (m_webdriver) m_webdriver->visit_edges(visitor); + if (m_web_ui) + m_web_ui->visit_edges(visitor); } ConnectionFromClient& PageClient::client() const @@ -372,6 +375,8 @@ void PageClient::page_did_change_active_document_in_top_level_browsing_context(W { auto& realm = document.realm(); + m_web_ui.clear(); + if (auto console_client = document.console_client()) { auto& web_content_console_client = as(*console_client); m_top_level_document_console_client = web_content_console_client; @@ -737,6 +742,24 @@ ErrorOr PageClient::connect_to_webdriver(ByteString const& webdriver_ipc_p return {}; } +ErrorOr PageClient::connect_to_web_ui(IPC::File web_ui_socket) +{ + auto* active_document = page().top_level_browsing_context().active_document(); + if (!active_document || !active_document->window()) + return {}; + + VERIFY(!m_web_ui); + m_web_ui = TRY(WebUIConnection::connect(move(web_ui_socket), *active_document)); + + return {}; +} + +void PageClient::received_message_from_web_ui(String const& name, JS::Value data) +{ + if (m_web_ui) + m_web_ui->received_message_from_web_ui(name, data); +} + void PageClient::initialize_js_console(Web::DOM::Document& document) { if (document.is_temporary_document_for_fragment_parsing()) @@ -910,4 +933,5 @@ void PageClient::queue_screenshot_task(Optional node_id) m_screenshot_tasks.enqueue({ node_id }); page().top_level_traversable()->set_needs_repaint(); } + } diff --git a/Services/WebContent/PageClient.h b/Services/WebContent/PageClient.h index bf07c14f2be..b2cb0db6ce6 100644 --- a/Services/WebContent/PageClient.h +++ b/Services/WebContent/PageClient.h @@ -44,6 +44,7 @@ public: virtual Web::Page const& page() const override { return *m_page; } ErrorOr connect_to_webdriver(ByteString const& webdriver_ipc_path); + ErrorOr connect_to_web_ui(IPC::File); virtual void paint_next_frame() override; virtual void process_screenshot_requests() override; @@ -173,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 received_message_from_web_ui(String const& name, JS::Value data) override; virtual void update_process_statistics() override; virtual void request_current_settings() override; virtual void restore_default_settings() override; @@ -211,6 +213,7 @@ private: Web::CSS::PreferredMotion m_preferred_motion { Web::CSS::PreferredMotion::NoPreference }; RefPtr m_webdriver; + RefPtr m_web_ui; BackingStoreManager m_backing_store_manager; diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index ee34cacd306..82637b6d83c 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -24,6 +24,7 @@ endpoint WebContentServer set_window_handle(u64 page_id, String handle) =| connect_to_webdriver(u64 page_id, ByteString webdriver_ipc_path) =| + connect_to_web_ui(u64 page_id, IPC::File socket_fd) =| connect_to_image_decoder(IPC::File socket_fd) =| update_system_theme(u64 page_id, Core::AnonymousBuffer theme_buffer) =| diff --git a/Services/WebContent/WebUIClient.ipc b/Services/WebContent/WebUIClient.ipc new file mode 100644 index 00000000000..e6ebad581a2 --- /dev/null +++ b/Services/WebContent/WebUIClient.ipc @@ -0,0 +1,3 @@ +endpoint WebUIClient { + received_message(String name, JsonValue data) =| +} diff --git a/Services/WebContent/WebUIConnection.cpp b/Services/WebContent/WebUIConnection.cpp new file mode 100644 index 00000000000..2e7513f9691 --- /dev/null +++ b/Services/WebContent/WebUIConnection.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WebContent { + +static auto LADYBIRD_PROPERTY = JS::PropertyKey { "ladybird"_fly_string }; +static auto WEB_UI_LOADED_EVENT = "WebUILoaded"_fly_string; +static auto WEB_UI_MESSAGE_EVENT = "WebUIMessage"_fly_string; + +ErrorOr> WebUIConnection::connect(IPC::File web_ui_socket, Web::DOM::Document& document) +{ + auto socket = TRY(Core::LocalSocket::adopt_fd(web_ui_socket.take_fd())); + TRY(socket->set_blocking(true)); + + return adopt_ref(*new WebUIConnection(IPC::Transport { move(socket) }, document)); +} + +WebUIConnection::WebUIConnection(IPC::Transport transport, Web::DOM::Document& document) + : IPC::ConnectionFromClient(*this, move(transport), 1) + , m_document(document) +{ + auto& realm = m_document->realm(); + m_document->window()->define_direct_property(LADYBIRD_PROPERTY, realm.create(realm), JS::default_attributes); + + Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, m_document, GC::create_function(realm.heap(), [&document = *m_document]() { + document.dispatch_event(Web::DOM::Event::create(document.realm(), WEB_UI_LOADED_EVENT)); + })); +} + +WebUIConnection::~WebUIConnection() +{ + if (!m_document->window()) + return; + + (void)m_document->window()->internal_delete(LADYBIRD_PROPERTY); +} + +void WebUIConnection::visit_edges(JS::Cell::Visitor& visitor) +{ + visitor.visit(m_document); +} + +void WebUIConnection::send_message(String name, JsonValue data) +{ + if (!m_document->browsing_context()) + return; + + JsonObject detail; + detail.set("name"sv, move(name)); + detail.set("data"sv, move(data)); + + auto& realm = m_document->realm(); + Web::HTML::TemporaryExecutionContext context { realm }; + + auto serialized_detail = Web::WebDriver::json_deserialize(*m_document->browsing_context(), detail); + if (serialized_detail.is_error()) { + warnln("Unable to serialize JSON data from browser: {}", serialized_detail.error()); + return; + } + + Web::DOM::CustomEventInit event_init {}; + event_init.detail = serialized_detail.value(); + + m_document->dispatch_event(Web::DOM::CustomEvent::create(realm, WEB_UI_MESSAGE_EVENT, event_init)); +} + +void WebUIConnection::received_message_from_web_ui(String const& name, JS::Value data) +{ + if (!m_document->browsing_context()) + return; + + auto deserialized_data = Web::WebDriver::json_clone(*m_document->browsing_context(), data); + if (deserialized_data.is_error()) { + warnln("Unable to deserialize JS data from WebUI: {}", deserialized_data.error()); + return; + } + + async_received_message(name, deserialized_data.value()); +} + +} diff --git a/Services/WebContent/WebUIConnection.h b/Services/WebContent/WebUIConnection.h new file mode 100644 index 00000000000..fd6915a5d35 --- /dev/null +++ b/Services/WebContent/WebUIConnection.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace WebContent { + +class WebUIConnection final : public IPC::ConnectionFromClient { +public: + static ErrorOr> connect(IPC::File, Web::DOM::Document&); + virtual ~WebUIConnection() override; + + void visit_edges(JS::Cell::Visitor&); + + void received_message_from_web_ui(String const& name, JS::Value data); + +private: + WebUIConnection(IPC::Transport, Web::DOM::Document&); + + virtual void die() override { } + virtual void send_message(String name, JsonValue data) override; + + GC::Ref m_document; +}; + +} diff --git a/Services/WebContent/WebUIServer.ipc b/Services/WebContent/WebUIServer.ipc new file mode 100644 index 00000000000..2598c94e5f7 --- /dev/null +++ b/Services/WebContent/WebUIServer.ipc @@ -0,0 +1,3 @@ +endpoint WebUIServer { + send_message(String name, JsonValue data) =| +}