The browser previously treated the out-of-process Compositor as fatal. Restart the shared Compositor from the browser process, reconnect process-backed WebContent clients, recreate compositor contexts, restore viewport state, and ask WebContent to repaint and republish canvas and media resources. WebContent now marks its compositor connection lost, returns conservative values for synchronous compositor queries while reconnecting, and drops outgoing updates until the replacement transport arrives. Synchronous input queries through the compositor control connection now use fallible IPC. If the Compositor exits after the open check or before the sync reply arrives, scroll and mouse handling report that the Compositor did not handle the event and let the normal WebContent fallback run. Mouse events queued while the Compositor is unavailable now fall back to direct WebContent dispatch. This keeps input completion in step with the pending-event queue. Recovery is capped at three automatic restarts. If the restart limit is exceeded, if restart, reconnect, or context recreation fails, or if the replacement Compositor exits during active recovery, the browser crashes instead of switching process-backed views to a fallback path.
299 lines
13 KiB
C++
299 lines
13 KiB
C++
/*
|
|
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Enumerate.h>
|
|
#include <LibCore/Process.h>
|
|
#include <LibCore/System.h>
|
|
#include <LibWebView/Application.h>
|
|
#include <LibWebView/CompositorClient.h>
|
|
#include <LibWebView/HelperProcess.h>
|
|
#include <LibWebView/Utilities.h>
|
|
|
|
namespace WebView {
|
|
|
|
template<typename ClientType, typename... ClientArguments>
|
|
static ErrorOr<NonnullRefPtr<ClientType>> launch_server_process(
|
|
StringView server_name,
|
|
Vector<ByteString> arguments,
|
|
ClientArguments&&... client_arguments)
|
|
{
|
|
auto process_type = WebView::process_type_from_name(server_name);
|
|
auto const& browser_options = WebView::Application::browser_options();
|
|
|
|
auto candidate_server_paths = TRY(get_paths_for_helper_process(server_name));
|
|
|
|
if (browser_options.profile_helper_process == process_type) {
|
|
arguments.prepend({
|
|
"--tool=callgrind"sv,
|
|
"--instr-atstart=no"sv,
|
|
""sv, // Placeholder for the process path.
|
|
});
|
|
}
|
|
|
|
if (browser_options.debug_helper_processes.contains_slow(process_type))
|
|
arguments.append("--wait-for-debugger"sv);
|
|
|
|
for (auto [i, path] : enumerate(candidate_server_paths)) {
|
|
Core::ProcessSpawnOptions options { .name = server_name, .arguments = arguments };
|
|
|
|
if (browser_options.profile_helper_process == process_type) {
|
|
options.executable = "valgrind"sv;
|
|
options.search_for_executable_in_path = true;
|
|
arguments[2] = path;
|
|
} else {
|
|
options.executable = path;
|
|
}
|
|
|
|
bool capture_output = WebView::Application::the().should_capture_web_content_output();
|
|
auto result = WebView::Process::spawn<ClientType>(process_type, move(options), capture_output, forward<ClientArguments>(client_arguments)...);
|
|
|
|
if (!result.is_error()) {
|
|
auto&& [process, client] = result.release_value();
|
|
|
|
if constexpr (requires { client->set_pid(pid_t {}); })
|
|
client->set_pid(process.pid());
|
|
|
|
if constexpr (requires { client->transport().set_peer_pid(0); } && !IsSame<ClientType, Web::HTML::WebWorkerClient>) {
|
|
auto response = client->template send_sync<typename ClientType::InitTransport>(Core::System::getpid());
|
|
client->transport().set_peer_pid(response->peer_pid());
|
|
}
|
|
|
|
WebView::Application::the().add_child_process(move(process));
|
|
|
|
if (browser_options.profile_helper_process == process_type) {
|
|
dbgln();
|
|
dbgln("\033[1;34mLaunched {} process under callgrind!\033[0m", server_name);
|
|
dbgln("\033[1;36mRun `\033[4mcallgrind_control -i on\033[24m` to start instrumentation and `\033[4mcallgrind_control -i off\033[24m` stop it again.\033[0m");
|
|
dbgln();
|
|
}
|
|
|
|
return move(client);
|
|
}
|
|
|
|
if (i == candidate_server_paths.size() - 1) {
|
|
warnln("Could not launch any of {}: {}", candidate_server_paths, result.error());
|
|
return result.release_error();
|
|
}
|
|
}
|
|
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
template<typename... ClientArguments>
|
|
static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process_impl(ClientArguments&&... client_arguments)
|
|
{
|
|
auto const& browser_options = WebView::Application::browser_options();
|
|
auto const& web_content_options = WebView::Application::web_content_options();
|
|
|
|
Vector<ByteString> arguments;
|
|
|
|
if (browser_options.headless_mode.has_value())
|
|
arguments.append("--headless"sv);
|
|
|
|
if (web_content_options.config_path.has_value()) {
|
|
arguments.append("--config-path"sv);
|
|
arguments.append(web_content_options.config_path.value());
|
|
}
|
|
if (web_content_options.is_test_mode == WebView::IsTestMode::Yes)
|
|
arguments.append("--test-mode"sv);
|
|
if (web_content_options.log_all_js_exceptions == WebView::LogAllJSExceptions::Yes)
|
|
arguments.append("--log-all-js-exceptions"sv);
|
|
if (web_content_options.disable_site_isolation == WebView::DisableSiteIsolation::Yes)
|
|
arguments.append("--disable-site-isolation"sv);
|
|
if (web_content_options.enable_idl_tracing == WebView::EnableIDLTracing::Yes)
|
|
arguments.append("--enable-idl-tracing"sv);
|
|
if (web_content_options.enable_http_memory_cache == WebView::EnableMemoryHTTPCache::Yes)
|
|
arguments.append("--enable-http-memory-cache"sv);
|
|
if (web_content_options.expose_experimental_interfaces == WebView::ExposeExperimentalInterfaces::Yes)
|
|
arguments.append("--expose-experimental-interfaces"sv);
|
|
if (web_content_options.expose_internals_object == WebView::ExposeInternalsObject::Yes)
|
|
arguments.append("--expose-internals-object"sv);
|
|
if (web_content_options.force_cpu_painting == WebView::ForceCPUPainting::Yes)
|
|
arguments.append("--force-cpu-painting"sv);
|
|
if (web_content_options.force_fontconfig == WebView::ForceFontconfig::Yes)
|
|
arguments.append("--force-fontconfig"sv);
|
|
if (web_content_options.collect_garbage_on_every_allocation == WebView::CollectGarbageOnEveryAllocation::Yes)
|
|
arguments.append("--collect-garbage-on-every-allocation"sv);
|
|
if (web_content_options.paint_viewport_scrollbars == PaintViewportScrollbars::No)
|
|
arguments.append("--disable-scrollbar-painting"sv);
|
|
if (web_content_options.enable_async_scrolling == EnableAsyncScrolling::No)
|
|
arguments.append("--disable-async-scrolling"sv);
|
|
if (WebView::Application::the().should_use_compositor_process())
|
|
arguments.append("--enable-compositor-process"sv);
|
|
if (web_content_options.file_scheme_urls_have_tuple_origins == FileSchemeUrlsHaveTupleOrigins::Yes)
|
|
arguments.append("--tuple-file-origins"sv);
|
|
|
|
if (auto const maybe_echo_server_port = web_content_options.echo_server_port; maybe_echo_server_port.has_value()) {
|
|
arguments.append("--echo-server-port"sv);
|
|
arguments.append(ByteString::number(maybe_echo_server_port.value()));
|
|
}
|
|
|
|
if (web_content_options.default_time_zone.has_value()) {
|
|
arguments.append("--default-time-zone");
|
|
arguments.append(web_content_options.default_time_zone.value());
|
|
}
|
|
if (web_content_options.style_invalidation_counter_dump_interval.has_value()) {
|
|
arguments.append("--dump-style-invalidation-counters"sv);
|
|
arguments.append(ByteString::number(*web_content_options.style_invalidation_counter_dump_interval));
|
|
}
|
|
|
|
if (auto server = mach_server_name(); server.has_value()) {
|
|
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();
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process()
|
|
{
|
|
Vector<ByteString> arguments;
|
|
if (auto server = mach_server_name(); server.has_value()) {
|
|
arguments.append("--mach-server-name"sv);
|
|
arguments.append(server.value());
|
|
}
|
|
|
|
return launch_server_process<ImageDecoderClient::Client>("ImageDecoder"sv, arguments);
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<WebView::CompositorClient>> launch_compositor_process()
|
|
{
|
|
auto const& web_content_options = WebView::Application::web_content_options();
|
|
|
|
Vector<ByteString> arguments;
|
|
if (web_content_options.force_cpu_painting == WebView::ForceCPUPainting::Yes)
|
|
arguments.append("--force-cpu-painting"sv);
|
|
if (web_content_options.force_fontconfig == WebView::ForceFontconfig::Yes)
|
|
arguments.append("--force-fontconfig"sv);
|
|
if (web_content_options.enable_async_scrolling == EnableAsyncScrolling::No)
|
|
arguments.append("--disable-async-scrolling"sv);
|
|
if (auto server = mach_server_name(); server.has_value()) {
|
|
arguments.append("--mach-server-name"sv);
|
|
arguments.append(server.value());
|
|
}
|
|
|
|
return launch_server_process<WebView::CompositorClient>("Compositor"sv, move(arguments));
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(Web::Bindings::AgentType type)
|
|
{
|
|
auto const& web_content_options = WebView::Application::web_content_options();
|
|
|
|
Vector<ByteString> arguments;
|
|
|
|
if (web_content_options.expose_experimental_interfaces == WebView::ExposeExperimentalInterfaces::Yes)
|
|
arguments.append("--expose-experimental-interfaces"sv);
|
|
if (web_content_options.enable_http_memory_cache == WebView::EnableMemoryHTTPCache::Yes)
|
|
arguments.append("--enable-http-memory-cache"sv);
|
|
if (web_content_options.file_scheme_urls_have_tuple_origins == FileSchemeUrlsHaveTupleOrigins::Yes)
|
|
arguments.append("--tuple-file-origins"sv);
|
|
|
|
arguments.append("--type"sv);
|
|
switch (type) {
|
|
case Web::Bindings::AgentType::DedicatedWorker:
|
|
arguments.append("dedicated"sv);
|
|
break;
|
|
case Web::Bindings::AgentType::SharedWorker:
|
|
arguments.append("shared"sv);
|
|
break;
|
|
case Web::Bindings::AgentType::ServiceWorker:
|
|
arguments.append("service"sv);
|
|
break;
|
|
default:
|
|
VERIFY_NOT_REACHED();
|
|
}
|
|
|
|
if (auto server = mach_server_name(); server.has_value()) {
|
|
arguments.append("--mach-server-name"sv);
|
|
arguments.append(server.value());
|
|
}
|
|
|
|
return launch_server_process<Web::HTML::WebWorkerClient>("WebWorker"sv, move(arguments));
|
|
}
|
|
|
|
ErrorOr<NonnullRefPtr<Requests::RequestClient>> launch_request_server_process()
|
|
{
|
|
auto const& request_server_options = Application::request_server_options();
|
|
|
|
Vector<ByteString> arguments;
|
|
|
|
for (auto const& certificate : request_server_options.certificates)
|
|
arguments.append(ByteString::formatted("--certificate={}", certificate));
|
|
|
|
arguments.append("--http-disk-cache-mode"sv);
|
|
|
|
switch (request_server_options.http_disk_cache_mode) {
|
|
case HTTPDiskCacheMode::Disabled:
|
|
arguments.append("disabled"sv);
|
|
break;
|
|
case HTTPDiskCacheMode::Enabled:
|
|
arguments.append("enabled"sv);
|
|
break;
|
|
case HTTPDiskCacheMode::Partitioned:
|
|
arguments.append("partitioned"sv);
|
|
break;
|
|
case HTTPDiskCacheMode::Testing:
|
|
arguments.append("testing"sv);
|
|
break;
|
|
}
|
|
|
|
if (auto server = mach_server_name(); server.has_value()) {
|
|
arguments.append("--mach-server-name"sv);
|
|
arguments.append(server.value());
|
|
}
|
|
|
|
if (request_server_options.resource_substitution_map_path.has_value())
|
|
arguments.append(ByteString::formatted("--resource-map={}", *request_server_options.resource_substitution_map_path));
|
|
|
|
auto client = TRY(launch_server_process<Requests::RequestClient>("RequestServer"sv, move(arguments)));
|
|
|
|
auto const& browsing_data_settings = Application::settings().browsing_data_settings();
|
|
client->async_set_disk_cache_settings(browsing_data_settings.disk_cache_settings);
|
|
|
|
Application::settings().dns_settings().visit(
|
|
[](SystemDNS) {},
|
|
[&](DNSOverTLS const& dns_over_tls) {
|
|
dbgln("Setting DNS server to {}:{} with TLS ({} local dnssec)", dns_over_tls.server_address, dns_over_tls.port, dns_over_tls.validate_dnssec_locally ? "with" : "without");
|
|
client->async_set_dns_server(dns_over_tls.server_address, dns_over_tls.port, true, dns_over_tls.validate_dnssec_locally);
|
|
},
|
|
[&](DNSOverUDP const& dns_over_udp) {
|
|
dbgln("Setting DNS server to {}:{} ({} local dnssec)", dns_over_udp.server_address, dns_over_udp.port, dns_over_udp.validate_dnssec_locally ? "with" : "without");
|
|
client->async_set_dns_server(dns_over_udp.server_address, dns_over_udp.port, false, dns_over_udp.validate_dnssec_locally);
|
|
});
|
|
|
|
return client;
|
|
}
|
|
|
|
ErrorOr<IPC::TransportHandle> connect_new_request_server_client()
|
|
{
|
|
auto response = Application::request_server_client().send_sync_but_allow_failure<Messages::RequestServer::ConnectNewClient>();
|
|
if (!response)
|
|
return Error::from_string_literal("Failed to connect to RequestServer");
|
|
return response->take_handle();
|
|
}
|
|
|
|
ErrorOr<IPC::TransportHandle> connect_new_image_decoder_client()
|
|
{
|
|
auto response = Application::image_decoder_client().send_sync_but_allow_failure<Messages::ImageDecoderServer::ConnectNewClients>(1);
|
|
if (!response)
|
|
return Error::from_string_literal("Failed to connect to ImageDecoder");
|
|
|
|
auto handles = response->take_handles();
|
|
if (handles.size() != 1)
|
|
return Error::from_string_literal("Failed to connect to ImageDecoder");
|
|
return handles.take_last();
|
|
}
|
|
|
|
}
|