LibWebView+WebContent: Make page IDs browser-assigned

Page IDs were allocated independently by each WebContent PageHost,
starting at zero for every process. That made them unsuitable as a
global identity for page-presenting compositor contexts, because the
same numeric page ID could exist in multiple WebContent processes.

Move page ID allocation to WebView::Application and initialize each
WebContent process with its browser-assigned first page ID over IPC.
New view requests now get a browser-assigned page ID before
WebContent creates the PageClient, and PageHost no longer owns a
local page counter.

This gives page-presenting compositor contexts a stable global page
identity, which will allow their context IDs to be derived from page
IDs and simplify compositor context allocation in a follow-up.
This commit is contained in:
Aliaksandr Kalenik 2026-05-30 16:58:13 +02:00 committed by Alexander Kalenik
parent de9fbf1679
commit 626dc6ff3f
16 changed files with 75 additions and 68 deletions

View file

@ -504,14 +504,15 @@ void Application::open_url_in_new_window(URL::URL const& url)
dbgln("open_url_in_new_window() is unsupported on this platform (url: {})", url);
}
static ErrorOr<NonnullRefPtr<WebContentClient>> create_web_content_client(Optional<ViewImplementation&> view)
ErrorOr<NonnullRefPtr<WebContentClient>> Application::create_web_content_client(Optional<ViewImplementation&> view, u64 initial_page_id)
{
auto request_server_handle = TRY(connect_new_request_server_client());
auto image_decoder_handle = TRY(connect_new_image_decoder_client());
NonnullRefPtr<WebContentClient> client = view.has_value()
? TRY(WebView::launch_web_content_process(*view))
: TRY(WebView::launch_spare_web_content_process());
auto client = TRY(WebView::launch_web_content_process(initial_page_id));
client->async_initialize(initial_page_id);
if (view.has_value())
client->assign_view({}, *view);
client->async_connect_to_request_server(move(request_server_handle));
client->async_connect_to_image_decoder(move(image_decoder_handle));
@ -520,6 +521,12 @@ static ErrorOr<NonnullRefPtr<WebContentClient>> create_web_content_client(Option
return client;
}
u64 Application::allocate_page_id()
{
VERIFY(m_next_page_id < Web::Compositor::page_presenting_context_id_tag);
return m_next_page_id++;
}
static bool can_send_compositor_process_ipc(RefPtr<CompositorClient> const& compositor_client)
{
if (!compositor_client)
@ -652,7 +659,7 @@ ErrorOr<NonnullRefPtr<WebContentClient>> Application::launch_web_content_process
}
launch_spare_web_content_process();
return create_web_content_client(view);
return create_web_content_client(view, allocate_page_id());
}
void Application::launch_spare_web_content_process()
@ -676,7 +683,7 @@ void Application::launch_spare_web_content_process()
Core::deferred_invoke([this]() {
m_has_queued_task_to_launch_spare_web_content_process = false;
auto web_content_client = create_web_content_client({});
auto web_content_client = create_web_content_client({}, allocate_page_id());
if (web_content_client.is_error()) {
dbgln("Unable to create spare web content client: {}", web_content_client.error());
return;

View file

@ -92,6 +92,7 @@ public:
#endif
ErrorOr<NonnullRefPtr<WebContentClient>> launch_web_content_process(ViewImplementation&);
u64 allocate_page_id();
ErrorOr<void> connect_web_content_to_compositor(WebContentClient&);
void register_compositor_context(WebContentClient&, Web::Compositor::CompositorContextId, Optional<u64> page_id, Web::Compositor::PagePresentationRegistration);
ErrorOr<void> try_register_compositor_context(WebContentClient&, Web::Compositor::CompositorContextId, Optional<u64> page_id, Web::Compositor::PagePresentationRegistration);
@ -231,6 +232,7 @@ protected:
Main::Arguments& arguments() { return m_arguments; }
private:
ErrorOr<NonnullRefPtr<WebContentClient>> create_web_content_client(Optional<ViewImplementation&>, u64 initial_page_id);
ErrorOr<void> launch_services();
void launch_spare_web_content_process();
ErrorOr<void> launch_compositor_process();
@ -327,6 +329,7 @@ private:
RefPtr<WebContentClient> m_spare_web_content_process;
bool m_has_queued_task_to_launch_spare_web_content_process { false };
u64 m_next_page_id { 1 };
RefPtr<Database::Database> m_database;
RefPtr<Database::Database> m_history_database;

View file

@ -82,8 +82,7 @@ static ErrorOr<NonnullRefPtr<ClientType>> launch_server_process(
VERIFY_NOT_REACHED();
}
template<typename... ClientArguments>
static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process_impl(ClientArguments&&... client_arguments)
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(u64 initial_page_id)
{
auto const& browser_options = WebView::Application::browser_options();
auto const& web_content_options = WebView::Application::web_content_options();
@ -142,17 +141,7 @@ static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_proc
arguments.append("--mach-server-name"sv);
arguments.append(server.value());
}
return launch_server_process<WebView::WebContentClient>("WebContent"sv, move(arguments), forward<ClientArguments>(client_arguments)...);
}
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(WebView::ViewImplementation& view)
{
return launch_web_content_process_impl(view);
}
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_spare_web_content_process()
{
return launch_web_content_process_impl();
return launch_server_process<WebView::WebContentClient>("WebContent"sv, move(arguments), initial_page_id);
}
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process()

View file

@ -13,15 +13,12 @@
#include <LibRequests/RequestClient.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWebView/Forward.h>
#include <LibWebView/ViewImplementation.h>
#include <LibWebView/WebContentClient.h>
#include <LibWebView/WebWorkerClient.h>
namespace WebView {
WEBVIEW_API ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(WebView::ViewImplementation&);
WEBVIEW_API ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_spare_web_content_process();
WEBVIEW_API ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(u64 initial_page_id);
WEBVIEW_API ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process();
WEBVIEW_API ErrorOr<NonnullRefPtr<WebView::CompositorClient>> launch_compositor_process();

View file

@ -46,16 +46,11 @@ static Optional<String> history_title(Utf16String const& title, URL::URL const&
return title_utf8;
}
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport, ViewImplementation& view)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
{
s_clients.set(this);
m_views.set(0, view);
}
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport)
WebContentClient::WebContentClient(NonnullOwnPtr<IPC::Transport> transport, u64 initial_page_id)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
, m_initial_page_id(initial_page_id)
{
VERIFY(m_initial_page_id > 0);
s_clients.set(this);
}
@ -143,7 +138,8 @@ void WebContentClient::remember_compositor_context(Web::Compositor::CompositorCo
void WebContentClient::assign_view(Badge<Application>, ViewImplementation& view)
{
VERIFY(m_views.is_empty());
m_views.set(0, view);
view.m_client_state.page_index = m_initial_page_id;
m_views.set(m_initial_page_id, view);
}
void WebContentClient::set_compositor_connection_id(Badge<Application>, i32 compositor_connection_id)
@ -157,6 +153,7 @@ void WebContentClient::register_view(u64 page_id, ViewImplementation& view)
if (m_detached_page_close_timer)
m_detached_page_close_timer->stop();
Application::process_manager().cancel_forced_exit(pid());
view.m_client_state.page_index = page_id;
m_views.set(page_id, view);
m_history_recorded_urls_for_current_load.remove(page_id);
}
@ -405,7 +402,7 @@ 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())
if (auto web_ui = WebUI::create(*this, page_id, 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();
@ -1016,14 +1013,16 @@ void WebContentClient::did_post_broadcast_channel_message(u64, Web::HTML::Broadc
WorkerProcessManager::the().broadcast_channel_message_from_web_content(message);
}
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index)
Messages::WebContentClient::DidRequestNewWebViewResponse WebContentClient::did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints)
{
auto new_page_id = Application::the().allocate_page_id();
String handle;
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_new_web_view)
return view->on_new_web_view(activate_tab, hints, page_index);
handle = view->on_new_web_view(activate_tab, hints, new_page_id);
}
return String {};
return { new_page_id, move(handle) };
}
void WebContentClient::did_request_activate_tab(u64 page_id)

View file

@ -54,8 +54,7 @@ public:
static size_t client_count() { return s_clients.size(); }
static Optional<WebContentClient&> client_for_compositor_context_id(Web::Compositor::CompositorContextId);
explicit WebContentClient(NonnullOwnPtr<IPC::Transport>);
WebContentClient(NonnullOwnPtr<IPC::Transport>, ViewImplementation&);
WebContentClient(NonnullOwnPtr<IPC::Transport>, u64 initial_page_id);
~WebContentClient();
void assign_view(Badge<Application>, ViewImplementation&);
@ -161,7 +160,7 @@ private:
virtual Messages::WebContentClient::DidRequestStorageKeysResponse did_request_storage_keys(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) override;
virtual void did_clear_storage(Web::StorageAPI::StorageEndpointType storage_endpoint, String storage_key) override;
virtual void did_post_broadcast_channel_message(u64 page_id, Web::HTML::BroadcastChannelMessage message) override;
virtual Messages::WebContentClient::DidRequestNewWebViewResponse did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab, Web::HTML::WebViewHints, Optional<u64> page_index) override;
virtual Messages::WebContentClient::DidRequestNewWebViewResponse did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab, Web::HTML::WebViewHints) override;
virtual void did_request_activate_tab(u64 page_id) override;
virtual void did_close_browsing_context(u64 page_id) override;
virtual void did_change_needs_beforeunload_check(u64 page_id, bool needs_beforeunload_check) override;
@ -211,6 +210,7 @@ private:
HashMap<Web::Compositor::CompositorContextId, u64> m_page_ids_for_compositor_context_ids;
HashMap<u64, String> m_history_recorded_urls_for_current_load;
Optional<i32> m_compositor_connection_id;
u64 m_initial_page_id { 0 };
ProcessHandle m_process_handle;
RefPtr<Core::Timer> m_detached_page_close_timer;

View file

@ -16,29 +16,31 @@
namespace WebView {
template<typename WebUIType>
static ErrorOr<NonnullRefPtr<WebUIType>> create_web_ui(WebContentClient& client, String host)
static ErrorOr<NonnullRefPtr<WebUIType>> create_web_ui(WebContentClient& client, u64 page_id, String host)
{
VERIFY(page_id > 0);
auto paired = TRY(IPC::Transport::create_paired());
auto handle = move(paired.remote_handle);
auto web_ui = WebUIType::create(client, move(paired.local), move(host));
client.async_connect_to_web_ui(0, move(handle));
client.async_connect_to_web_ui(page_id, move(handle));
return web_ui;
}
ErrorOr<RefPtr<WebUI>> WebUI::create(WebContentClient& client, String host)
ErrorOr<RefPtr<WebUI>> WebUI::create(WebContentClient& client, u64 page_id, String host)
{
RefPtr<WebUI> web_ui;
if (host == "bookmarks"sv)
web_ui = TRY(create_web_ui<BookmarksUI>(client, move(host)));
web_ui = TRY(create_web_ui<BookmarksUI>(client, page_id, move(host)));
else if (host == "processes"sv)
web_ui = TRY(create_web_ui<ProcessesUI>(client, move(host)));
web_ui = TRY(create_web_ui<ProcessesUI>(client, page_id, move(host)));
else if (host == "settings"sv)
web_ui = TRY(create_web_ui<SettingsUI>(client, move(host)));
web_ui = TRY(create_web_ui<SettingsUI>(client, page_id, move(host)));
else if (host == "version"sv)
web_ui = TRY(create_web_ui<VersionUI>(client, move(host)));
web_ui = TRY(create_web_ui<VersionUI>(client, page_id, move(host)));
if (web_ui)
web_ui->register_interfaces();

View file

@ -12,6 +12,7 @@
#include <AK/NonnullRefPtr.h>
#include <AK/RefPtr.h>
#include <AK/String.h>
#include <AK/Types.h>
#include <LibIPC/ConnectionToServer.h>
#include <LibIPC/Transport.h>
#include <LibWebView/Forward.h>
@ -24,7 +25,7 @@ class WEBVIEW_API WebUI
: public IPC::ConnectionToServer<WebUIClientEndpoint, WebUIServerEndpoint>
, public WebUIClientEndpoint {
public:
static ErrorOr<RefPtr<WebUI>> create(WebContentClient&, String host);
static ErrorOr<RefPtr<WebUI>> create(WebContentClient&, u64 page_id, String host);
virtual ~WebUI();
String const& host() const { return m_host; }

View file

@ -112,6 +112,11 @@ Messages::WebContentServer::InitTransportResponse ConnectionFromClient::init_tra
VERIFY_NOT_REACHED();
}
void ConnectionFromClient::initialize(u64 initial_page_id)
{
m_page_host->initialize(initial_page_id);
}
Optional<PageClient&> ConnectionFromClient::page(u64 index, SourceLocation location)
{
if (auto page = m_page_host->page(index); page.has_value())

View file

@ -66,6 +66,7 @@ private:
Optional<PageClient const&> page(u64 index, SourceLocation = SourceLocation::current()) const;
virtual Messages::WebContentServer::InitTransportResponse init_transport(int peer_pid) override;
virtual void initialize(u64 initial_page_id) override;
virtual void close_server() override;
virtual Messages::WebContentServer::GetWindowHandleResponse get_window_handle(u64 page_id) override;
virtual void set_window_handle(u64 page_id, String handle) override;

View file

@ -702,22 +702,18 @@ void PageClient::page_did_update_resource_count(i32 count_waiting)
PageClient::NewWebViewResult PageClient::page_did_request_new_web_view(Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Web::HTML::TokenizedFeature::NoOpener no_opener)
{
auto& new_client = m_owner.create_page();
Optional<u64> page_id;
if (no_opener == Web::HTML::TokenizedFeature::NoOpener::Yes) {
// FIXME: Create an abstraction to let this WebContent process know about a new process we create?
// FIXME: For now, just create a new page in the same process anyway
}
page_id = new_client.m_id;
auto response = client().send_sync_but_allow_failure<Messages::WebContentClient::DidRequestNewWebView>(m_id, activate_tab, hints, page_id);
auto response = client().send_sync_but_allow_failure<Messages::WebContentClient::DidRequestNewWebView>(m_id, activate_tab, hints);
if (!response) {
dbgln("WebContent client disconnected during DidRequestNewWebView. Exiting peacefully.");
exit(0);
}
auto& new_client = m_owner.create_page(response->new_page_id());
return { &new_client.page(), response->take_handle() };
}

View file

@ -20,25 +20,31 @@ namespace WebContent {
PageHost::PageHost(ConnectionFromClient& client)
: m_client(client)
{
auto& first_page = create_page();
}
void PageHost::initialize(u64 initial_page_id)
{
VERIFY(m_pages.is_empty());
auto& first_page = create_page(initial_page_id);
Web::HTML::TraversableNavigable::create_a_fresh_top_level_traversable(first_page.page(), URL::about_blank());
}
PageClient& PageHost::create_page()
PageClient& PageHost::create_page(u64 page_id)
{
m_pages.set(m_next_id, PageClient::create(Web::Bindings::main_thread_vm(), *this, m_next_id));
++m_next_id;
return *m_pages.get(m_next_id - 1).value();
VERIFY(page_id > 0);
VERIFY(!m_pages.contains(page_id));
m_pages.set(page_id, PageClient::create(Web::Bindings::main_thread_vm(), *this, page_id));
return *m_pages.get(page_id).value();
}
void PageHost::remove_page(Badge<PageClient>, u64 index)
void PageHost::remove_page(Badge<PageClient>, u64 page_id)
{
m_pages.remove(index);
m_pages.remove(page_id);
}
Optional<PageClient&> PageHost::page(u64 index)
Optional<PageClient&> PageHost::page(u64 page_id)
{
return m_pages.get(index).map([](auto& value) -> PageClient& {
return m_pages.get(page_id).map([](auto& value) -> PageClient& {
return *value;
});
}

View file

@ -35,9 +35,10 @@ public:
static NonnullOwnPtr<PageHost> create(ConnectionFromClient& client) { return adopt_own(*new PageHost(client)); }
virtual ~PageHost();
Optional<PageClient&> page(u64 index);
PageClient& create_page();
void remove_page(Badge<PageClient>, u64 index);
void initialize(u64 initial_page_id);
Optional<PageClient&> page(u64 page_id);
PageClient& create_page(u64 page_id);
void remove_page(Badge<PageClient>, u64 page_id);
ConnectionFromClient& client() const { return m_client; }
void ensure_compositor_host();
@ -51,7 +52,6 @@ private:
ConnectionFromClient& m_client;
OwnPtr<Web::Compositor::CompositorHost> m_compositor_host;
HashMap<u64, GC::Root<PageClient>> m_pages;
u64 m_next_id { 0 };
};
}

View file

@ -107,7 +107,7 @@ endpoint WebContentClient
did_post_broadcast_channel_message(u64 page_id, Web::HTML::BroadcastChannelMessage message) =|
did_update_resource_count(u64 page_id, i32 count_waiting) =|
did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints, Optional<u64> page_index) => (String handle)
did_request_new_web_view(u64 page_id, Web::HTML::ActivateTab activate_tab, Web::HTML::WebViewHints hints) => (u64 new_page_id, String handle)
did_request_activate_tab(u64 page_id) =|
did_close_browsing_context(u64 page_id) =|
did_change_needs_beforeunload_check(u64 page_id, bool needs_beforeunload_check) =|

View file

@ -26,6 +26,7 @@
endpoint WebContentServer
{
init_transport(int peer_pid) => (int peer_pid)
initialize(u64 initial_page_id) =|
close_server() =|
get_window_handle(u64 page_id) => (String handle)

View file

@ -41,7 +41,7 @@ NonnullRefPtr<Core::Promise<RefPtr<Gfx::Bitmap const>>> TestWebView::take_screen
VERIFY(!m_pending_screenshot);
m_pending_screenshot = Core::Promise<RefPtr<Gfx::Bitmap const>>::construct();
client().async_take_document_screenshot(0);
client().async_take_document_screenshot(page_id());
return *m_pending_screenshot;
}