ladybird/Services/WebContent/main.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

328 lines
13 KiB
C++

/*
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/LexicalPath.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/EventLoop.h>
#include <LibCore/LocalServer.h>
#include <LibCore/Process.h>
#include <LibCore/Resource.h>
#include <LibCore/System.h>
#include <LibCrypto/OpenSSLForward.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibGfx/SkiaBackendContext.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibIPC/TransportHandle.h>
#include <LibJS/Bytecode/Interpreter.h>
#include <LibMain/Main.h>
#include <LibRequests/RequestClient.h>
#include <LibUnicode/TimeZone.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/Fetch/Fetching/Fetching.h>
#include <LibWeb/HTML/UniversalGlobalScope.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Internals/Internals.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Loader/ResourceLoader.h>
#include <LibWeb/Painting/BackingStoreManager.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Platform/FontPlugin.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWebView/Plugins/ImageCodecPlugin.h>
#include <LibWebView/SiteIsolation.h>
#include <LibWebView/Utilities.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageClient.h>
#include <WebContent/WebDriverConnection.h>
#include <openssl/thread.h>
#if defined(AK_OS_MACOS)
# include <LibIPC/Transport.h>
# include <LibIPC/TransportBootstrapMach.h>
#else
# include <LibIPC/SingleServer.h>
#endif
#if defined(AK_OS_WINDOWS)
# include <objbase.h>
#endif
#include <SDL3/SDL_init.h>
#if !defined(AK_OS_WINDOWS)
# include <signal.h>
static void crash_signal_handler(int signo)
{
char const* name;
switch (signo) {
case SIGSEGV:
name = "SIGSEGV";
break;
case SIGBUS:
name = "SIGBUS";
break;
case SIGFPE:
name = "SIGFPE";
break;
case SIGABRT:
name = "SIGABRT";
break;
case SIGILL:
name = "SIGILL";
break;
default:
name = "unknown";
break;
}
warnln("\n\033[31;1mCRASH\033[0m: Received signal {} ({})", name, signo);
dump_backtrace(2, 100);
exit(128 + signo);
}
static void install_crash_signal_handlers()
{
struct sigaction sa;
sa.sa_handler = crash_signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESETHAND;
sigaction(SIGSEGV, &sa, nullptr);
sigaction(SIGBUS, &sa, nullptr);
sigaction(SIGFPE, &sa, nullptr);
sigaction(SIGABRT, &sa, nullptr);
sigaction(SIGILL, &sa, nullptr);
}
#endif
static ErrorOr<void> load_content_filters(StringView config_path);
static ErrorOr<void> connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle);
static ErrorOr<void> connect_to_image_decoder(IPC::TransportHandle const& handle);
ErrorOr<int> ladybird_main(Main::Arguments arguments)
{
AK::set_rich_debug_enabled(true);
#if !defined(AK_OS_WINDOWS)
install_crash_signal_handlers();
#endif
#if defined(AK_OS_WINDOWS)
// NOTE: We need this here otherwise SDL inits COM in the APARTMENTTHREADED model which we don't want as we need to
// make calls across threads which would otherwise have a high overhead. It is safe for all the objects we use.
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
VERIFY(SUCCEEDED(hr));
ScopeGuard uninitialize_com = []() { CoUninitialize(); };
#endif
// SDL is used for the Gamepad API.
if (!SDL_Init(SDL_INIT_GAMEPAD)) {
dbgln("Failed to initialize SDL3: {}", SDL_GetError());
return -1;
}
Core::EventLoop event_loop;
WebView::platform_init();
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPlugin);
StringView command_line {};
StringView executable_path {};
auto config_path = ByteString::formatted("{}/ladybird/default-config", WebView::s_ladybird_resource_root);
StringView mach_server_name {};
Vector<ByteString> certificates;
bool enable_test_mode = false;
bool expose_experimental_interfaces = false;
bool expose_internals_object = false;
bool wait_for_debugger = false;
bool log_all_js_exceptions = false;
bool disable_site_isolation = false;
bool enable_idl_tracing = false;
bool enable_http_memory_cache = false;
bool force_cpu_painting = false;
bool force_fontconfig = false;
bool collect_garbage_on_every_allocation = false;
bool is_headless = false;
bool disable_scrollbar_painting = false;
StringView echo_server_port_string_view {};
StringView default_time_zone {};
bool file_origins_are_tuple_origins = false;
Core::ArgsParser args_parser;
args_parser.add_option(command_line, "Browser process command line", "command-line", 0, "command_line");
args_parser.add_option(executable_path, "Browser process executable path", "executable-path", 0, "executable_path");
args_parser.add_option(config_path, "Ladybird configuration path", "config-path", 0, "config_path");
args_parser.add_option(enable_test_mode, "Enable test mode", "test-mode");
args_parser.add_option(expose_experimental_interfaces, "Expose experimental IDL interfaces", "expose-experimental-interfaces");
args_parser.add_option(expose_internals_object, "Expose internals object", "expose-internals-object");
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
args_parser.add_option(mach_server_name, "Mach server name", "mach-server-name", 0, "mach_server_name");
args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions");
args_parser.add_option(disable_site_isolation, "Disable site isolation", "disable-site-isolation");
args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing");
args_parser.add_option(enable_http_memory_cache, "Enable HTTP cache", "enable-http-memory-cache");
args_parser.add_option(force_cpu_painting, "Force CPU painting", "force-cpu-painting");
args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig");
args_parser.add_option(collect_garbage_on_every_allocation, "Collect garbage after every JS heap allocation", "collect-garbage-on-every-allocation");
args_parser.add_option(disable_scrollbar_painting, "Don't paint horizontal or vertical viewport scrollbars", "disable-scrollbar-painting");
args_parser.add_option(echo_server_port_string_view, "Echo server port used in test internals", "echo-server-port", 0, "echo_server_port");
args_parser.add_option(is_headless, "Report that the browser is running in headless mode", "headless");
args_parser.add_option(default_time_zone, "Default time zone", "default-time-zone", 0, "time-zone-id");
args_parser.add_option(file_origins_are_tuple_origins, "Treat file:// URLs as having tuple origins", "tuple-file-origins");
args_parser.parse(arguments);
if (wait_for_debugger) {
Core::Process::wait_for_debugger_and_break();
}
if (!default_time_zone.is_empty()) {
if (auto result = Unicode::set_current_time_zone(default_time_zone); result.is_error())
dbgln("Failed to set default time zone: {}", result.error());
}
if (file_origins_are_tuple_origins)
URL::set_file_scheme_urls_have_tuple_origins();
auto& font_provider = static_cast<Gfx::PathFontProvider&>(Gfx::FontDatabase::the().install_system_font_provider(make<Gfx::PathFontProvider>()));
if (force_fontconfig) {
font_provider.set_name_but_fixme_should_create_custom_system_font_provider("FontConfig"_string);
}
font_provider.load_all_fonts_from_uri("resource://fonts"sv);
Web::set_browser_process_command_line(command_line);
Web::set_browser_process_executable_path(executable_path);
// Always use the CPU backend for tests, as the GPU backend is not deterministic
if (force_cpu_painting) {
WebContent::PageClient::set_use_skia_painter(WebContent::PageClient::UseSkiaPainter::CPUBackend);
} else {
Gfx::SkiaBackendContext::initialize_gpu_backend();
WebContent::PageClient::set_use_skia_painter(WebContent::PageClient::UseSkiaPainter::GPUBackendIfAvailable);
}
WebContent::PageClient::set_is_headless(is_headless);
if (disable_site_isolation)
WebView::disable_site_isolation();
if (enable_http_memory_cache)
Web::Fetch::Fetching::set_http_memory_cache_enabled(true);
Web::Painting::set_paint_viewport_scrollbars(!disable_scrollbar_painting);
if (!echo_server_port_string_view.is_empty()) {
if (auto maybe_echo_server_port = echo_server_port_string_view.to_number<u16>(); maybe_echo_server_port.has_value())
Web::Internals::Internals::set_echo_server_port(maybe_echo_server_port.value());
else
VERIFY_NOT_REACHED();
}
OPENSSL_TRY(OSSL_set_max_threads(nullptr, Core::System::hardware_concurrency()));
Web::HTML::Window::set_enable_test_mode(enable_test_mode);
Web::HTML::Window::set_internals_object_exposed(expose_internals_object);
Web::HTML::UniversalGlobalScopeMixin::set_experimental_interfaces_exposed(expose_experimental_interfaces);
Web::Platform::FontPlugin::install(*new Web::Platform::FontPlugin(enable_test_mode, &font_provider));
Web::Bindings::initialize_main_thread_vm(Web::Bindings::AgentType::SimilarOriginWindow);
if (collect_garbage_on_every_allocation)
Web::Bindings::main_thread_vm().heap().set_should_collect_on_every_allocation(true);
if (log_all_js_exceptions) {
JS::set_log_all_js_exceptions(true);
}
if (enable_idl_tracing) {
Web::WebIDL::set_enable_idl_tracing(true);
}
auto maybe_content_filter_error = load_content_filters(config_path);
if (maybe_content_filter_error.is_error())
dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());
#if defined(AK_OS_MACOS)
auto browser_port = TRY(Core::MachPort::look_up_from_bootstrap_server(ByteString { mach_server_name }));
auto transport_ports = TRY(IPC::bootstrap_transport_from_server_port(browser_port));
Web::Painting::BackingStoreManager::set_browser_mach_port(move(browser_port));
auto webcontent_client = WebContent::ConnectionFromClient::construct(
make<IPC::Transport>(move(transport_ports.receive_right), move(transport_ports.send_right)));
#else
auto webcontent_client = TRY(IPC::take_over_accepted_client_from_system_server<WebContent::ConnectionFromClient>(mach_server_name));
#endif
auto& heap = Web::Bindings::main_thread_vm().heap();
webcontent_client->on_request_server_connection = [&heap](auto const& handle) {
if (auto result = connect_to_resource_loader(heap, handle); result.is_error())
dbgln("Failed to connect to resource loader: {}", result.error());
};
webcontent_client->on_image_decoder_connection = [](auto const& handle) {
if (auto result = connect_to_image_decoder(handle); result.is_error())
dbgln("Failed to connect to image decoder: {}", result.error());
};
return event_loop.exec();
}
static ErrorOr<void> load_content_filters(StringView config_path)
{
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
auto file = TRY(Core::File::open(ByteString::formatted("{}/BrowserContentFilters.txt", config_path), Core::File::OpenMode::Read));
auto ad_filter_list = TRY(Core::InputBufferedFile::create(move(file)));
Vector<String> patterns;
while (TRY(ad_filter_list->can_read_line())) {
auto line = TRY(ad_filter_list->read_line(buffer));
if (line.is_empty())
continue;
auto pattern = TRY(String::from_utf8(line));
TRY(patterns.try_append(move(pattern)));
}
auto& content_filter = Web::ContentFilter::the();
TRY(content_filter.set_patterns(patterns));
return {};
}
ErrorOr<void> connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle)
{
auto transport = TRY(handle.create_transport());
auto request_client = TRY(try_make_ref_counted<Requests::RequestClient>(move(transport)));
#ifdef AK_OS_WINDOWS
auto response = request_client->send_sync<Messages::RequestServer::InitTransport>(Core::System::getpid());
request_client->transport().set_peer_pid(response->peer_pid());
#endif
if (Web::ResourceLoader::is_initialized())
Web::ResourceLoader::the().set_client(move(request_client));
else
Web::ResourceLoader::initialize(heap, move(request_client));
return {};
}
ErrorOr<void> connect_to_image_decoder(IPC::TransportHandle const& handle)
{
auto transport = TRY(handle.create_transport());
auto new_client = TRY(try_make_ref_counted<ImageDecoderClient::Client>(move(transport)));
#ifdef AK_OS_WINDOWS
auto response = new_client->send_sync<Messages::ImageDecoderServer::InitTransport>(Core::System::getpid());
new_client->transport().set_peer_pid(response->peer_pid());
#endif
if (Web::Platform::ImageCodecPlugin::is_initialized())
static_cast<WebView::ImageCodecPlugin&>(Web::Platform::ImageCodecPlugin::the()).set_client(move(new_client));
else
Web::Platform::ImageCodecPlugin::install(*new WebView::ImageCodecPlugin(move(new_client)));
return {};
}