ladybird/Libraries/LibWebView/HelperProcess.cpp
Aliaksandr Kalenik 4ea4d63008 Everywhere: Replace Unix socket IPC transport with Mach ports on macOS
On macOS, use Mach port messaging instead of Unix domain sockets for
all IPC transport. This makes the transport capable of carrying Mach
port rights as message attachments, which is a prerequisite for sending
IOSurface handles over the main IPC channel (currently sent via a
separate out-of-band path). It also avoids the need for the FD
acknowledgement protocol that TransportSocket requires, since Mach port
right transfers are atomic in the kernel.

Three connection establishment patterns:

- Spawned helper processes (WebContent, RequestServer, etc.) use the
  existing MachPortServer: the child sends its task port with a reply
  port, and the parent responds with a pre-created port pair.

- Socket-bootstrapped connections (WebDriver, BrowserProcess) exchange
  Mach port names over the socket, then drop the socket.

- Pre-created pairs for IPC tests and in-message transport transfer.

Attachment on macOS now wraps a MachPort instead of a file descriptor,
converting between the two via fileport_makeport()/fileport_makefd().

The LibIPC socket transport tests are disabled on macOS since they are
socket-specific.
2026-03-23 18:50:48 +01:00

280 lines
11 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/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_process == 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 {
"--command-line"sv,
web_content_options.command_line.to_byte_string(),
"--executable-path"sv,
web_content_options.executable_path.to_byte_string(),
};
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);
// Propogate this process-wide setting to the child process also.
if (URL::file_scheme_urls_have_tuple_origins())
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 (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<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);
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());
}
// Propogate this process-wide setting to the child process also.
if (URL::file_scheme_urls_have_tuple_origins())
arguments.append("--tuple-file-origins"sv);
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();
}
}