LibWebView: Create a spare WebContent process

Since cross-site navigation is a pretty frequent task, creating a spare
process is commonplace in other browsers to reduce the overhead of
directing the target site to a new process.

We store this process on the WebView application. If it is unavailable,
we queue a task to create it later.
This commit is contained in:
Timothy Flynn 2025-03-09 18:01:25 -04:00 committed by Jelle Raaijmakers
commit aca4385daf
Notes: github-actions[bot] 2025-03-11 11:11:42 +00:00
8 changed files with 98 additions and 9 deletions

View file

@ -191,6 +191,52 @@ void Application::initialize(Main::Arguments const& arguments, URL::URL new_tab_
} }
} }
static ErrorOr<NonnullRefPtr<WebContentClient>> create_web_content_client(Optional<ViewImplementation&> view)
{
auto request_server_socket = TRY(connect_new_request_server_client());
auto image_decoder_socket = TRY(connect_new_image_decoder_client());
if (view.has_value())
return WebView::launch_web_content_process(*view, move(image_decoder_socket), move(request_server_socket));
return WebView::launch_spare_web_content_process(move(image_decoder_socket), move(request_server_socket));
}
ErrorOr<NonnullRefPtr<WebContentClient>> Application::launch_web_content_process(ViewImplementation& view)
{
if (m_spare_web_content_process) {
auto web_content_client = m_spare_web_content_process.release_nonnull();
launch_spare_web_content_process();
web_content_client->assign_view({}, view);
return web_content_client;
}
launch_spare_web_content_process();
return create_web_content_client(view);
}
void Application::launch_spare_web_content_process()
{
if (m_has_queued_task_to_launch_spare_web_content_process)
return;
m_has_queued_task_to_launch_spare_web_content_process = true;
Core::deferred_invoke([this]() {
m_has_queued_task_to_launch_spare_web_content_process = false;
auto web_content_client = create_web_content_client({});
if (web_content_client.is_error()) {
dbgln("Unable to create spare web content client: {}", web_content_client.error());
return;
}
m_spare_web_content_process = web_content_client.release_value();
if (auto process = find_process(m_spare_web_content_process->pid()); process.has_value())
process->set_title("(spare)"_string);
});
}
ErrorOr<void> Application::launch_services() ErrorOr<void> Application::launch_services()
{ {
TRY(launch_request_server()); TRY(launch_request_server());

View file

@ -45,6 +45,7 @@ public:
Core::EventLoop& event_loop() { return m_event_loop; } Core::EventLoop& event_loop() { return m_event_loop; }
ErrorOr<NonnullRefPtr<WebContentClient>> launch_web_content_process(ViewImplementation&);
ErrorOr<void> launch_services(); ErrorOr<void> launch_services();
void add_child_process(Process&&); void add_child_process(Process&&);
@ -85,6 +86,7 @@ protected:
private: private:
void initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url); void initialize(Main::Arguments const& arguments, URL::URL new_tab_page_url);
void launch_spare_web_content_process();
ErrorOr<void> launch_request_server(); ErrorOr<void> launch_request_server();
ErrorOr<void> launch_image_decoder_server(); ErrorOr<void> launch_image_decoder_server();
ErrorOr<void> launch_devtools_server(); ErrorOr<void> launch_devtools_server();
@ -118,6 +120,9 @@ private:
RefPtr<Requests::RequestClient> m_request_server_client; RefPtr<Requests::RequestClient> m_request_server_client;
RefPtr<ImageDecoderClient::Client> m_image_decoder_client; RefPtr<ImageDecoderClient::Client> m_image_decoder_client;
RefPtr<WebContentClient> m_spare_web_content_process;
bool m_has_queued_task_to_launch_spare_web_content_process { false };
RefPtr<Database> m_database; RefPtr<Database> m_database;
OwnPtr<CookieJar> m_cookie_jar; OwnPtr<CookieJar> m_cookie_jar;

View file

@ -10,6 +10,7 @@
namespace WebView { namespace WebView {
class Application;
class CookieJar; class CookieJar;
class Database; class Database;
class InspectorClient; class InspectorClient;

View file

@ -79,10 +79,11 @@ static ErrorOr<NonnullRefPtr<ClientType>> launch_server_process(
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process( template<typename... ClientArguments>
WebView::ViewImplementation& view, static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process_impl(
IPC::File image_decoder_socket, IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket) Optional<IPC::File> request_server_socket,
ClientArguments&&... client_arguments)
{ {
auto const& chrome_options = WebView::Application::chrome_options(); auto const& chrome_options = WebView::Application::chrome_options();
auto const& web_content_options = WebView::Application::web_content_options(); auto const& web_content_options = WebView::Application::web_content_options();
@ -138,7 +139,22 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
arguments.append("--image-decoder-socket"sv); arguments.append("--image-decoder-socket"sv);
arguments.append(ByteString::number(image_decoder_socket.fd())); arguments.append(ByteString::number(image_decoder_socket.fd()));
return launch_server_process<WebView::WebContentClient>("WebContent"sv, move(arguments), view); 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,
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket)
{
return launch_web_content_process_impl(move(image_decoder_socket), move(request_server_socket), view);
}
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_spare_web_content_process(
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket)
{
return launch_web_content_process_impl(move(image_decoder_socket), move(request_server_socket));
} }
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process() ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process()

View file

@ -21,6 +21,10 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
IPC::File image_decoder_socket, IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket = {}); Optional<IPC::File> request_server_socket = {});
ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_spare_web_content_process(
IPC::File image_decoder_socket,
Optional<IPC::File> request_server_socket = {});
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(); ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process();
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(); ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process();
ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process(); ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process();

View file

@ -568,11 +568,8 @@ void ViewImplementation::initialize_client(CreateNewClient create_new_client)
if (create_new_client == CreateNewClient::Yes) { if (create_new_client == CreateNewClient::Yes) {
m_client_state = {}; m_client_state = {};
// FIXME: Fail to open the tab, rather than crashing the whole application if these fail. // FIXME: Fail to open the tab, rather than crashing the whole application if this fails.
auto request_server_socket = connect_new_request_server_client().release_value_but_fixme_should_propagate_errors(); m_client_state.client = Application::the().launch_web_content_process(*this).release_value_but_fixme_should_propagate_errors();
auto image_decoder_socket = connect_new_image_decoder_client().release_value_but_fixme_should_propagate_errors();
m_client_state.client = launch_web_content_process(*this, AK::move(image_decoder_socket), AK::move(request_server_socket)).release_value_but_fixme_should_propagate_errors();
} else { } else {
m_client_state.client->register_view(m_client_state.page_index, *this); m_client_state.client->register_view(m_client_state.page_index, *this);
} }

View file

@ -31,6 +31,12 @@ WebContentClient::WebContentClient(IPC::Transport transport, ViewImplementation&
m_views.set(0, &view); m_views.set(0, &view);
} }
WebContentClient::WebContentClient(IPC::Transport transport)
: IPC::ConnectionToServer<WebContentClientEndpoint, WebContentServerEndpoint>(*this, move(transport))
{
s_clients.set(this);
}
WebContentClient::~WebContentClient() WebContentClient::~WebContentClient()
{ {
s_clients.remove(this); s_clients.remove(this);
@ -41,6 +47,12 @@ void WebContentClient::die()
// Intentionally empty. Restart is handled at another level. // Intentionally empty. Restart is handled at another level.
} }
void WebContentClient::assign_view(Badge<Application>, ViewImplementation& view)
{
VERIFY(m_views.is_empty());
m_views.set(0, &view);
}
void WebContentClient::register_view(u64 page_id, ViewImplementation& view) void WebContentClient::register_view(u64 page_id, ViewImplementation& view)
{ {
VERIFY(page_id > 0); VERIFY(page_id > 0);
@ -768,6 +780,10 @@ void WebContentClient::did_get_style_sheet_source(u64 page_id, Web::CSS::StyleSh
Optional<ViewImplementation&> WebContentClient::view_for_page_id(u64 page_id, SourceLocation location) Optional<ViewImplementation&> 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.
if (m_views.is_empty())
return {};
if (auto view = m_views.get(page_id); view.has_value()) if (auto view = m_views.get(page_id); view.has_value())
return *view.value(); return *view.value();

View file

@ -16,6 +16,7 @@
#include <LibWeb/HTML/SelectItem.h> #include <LibWeb/HTML/SelectItem.h>
#include <LibWeb/HTML/WebViewHints.h> #include <LibWeb/HTML/WebViewHints.h>
#include <LibWeb/Page/EventResult.h> #include <LibWeb/Page/EventResult.h>
#include <LibWebView/Forward.h>
#include <WebContent/WebContentClientEndpoint.h> #include <WebContent/WebContentClientEndpoint.h>
#include <WebContent/WebContentServerEndpoint.h> #include <WebContent/WebContentServerEndpoint.h>
@ -38,14 +39,17 @@ public:
static size_t client_count() { return s_clients.size(); } static size_t client_count() { return s_clients.size(); }
explicit WebContentClient(IPC::Transport);
WebContentClient(IPC::Transport, ViewImplementation&); WebContentClient(IPC::Transport, ViewImplementation&);
~WebContentClient(); ~WebContentClient();
void assign_view(Badge<Application>, ViewImplementation&);
void register_view(u64 page_id, ViewImplementation&); void register_view(u64 page_id, ViewImplementation&);
void unregister_view(u64 page_id); void unregister_view(u64 page_id);
Function<void()> on_web_content_process_crash; Function<void()> on_web_content_process_crash;
pid_t pid() const { return m_process_handle.pid; }
void set_pid(pid_t pid) { m_process_handle.pid = pid; } void set_pid(pid_t pid) { m_process_handle.pid = pid; }
private: private: