ladybird/Services/WebContent/main.cpp
Aliaksandr Kalenik 40d446d558 LibWebView+WebContent: Stop creating GPU Skia backend in WebContent
Canvas and display list rasterization now run in the Compositor
process, so WebContent no longer needs its own Skia GPU backend. Drop
the WebContent --force-cpu-painting option and stop forwarding it when
launching the renderer. The flag remains available for Compositor.
2026-06-18 10:25:44 +02:00

331 lines
14 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 <LibCore/TimeZone.h>
#include <LibCrypto/OpenSSLForward.h>
#include <LibGfx/Font/FontDatabase.h>
#include <LibGfx/Font/PathFontProvider.h>
#include <LibIPC/ConnectionFromClient.h>
#include <LibIPC/TransportHandle.h>
#include <LibMain/Main.h>
#include <LibRequests/RequestClient.h>
#include <LibUnicode/TimeZone.h>
#include <LibWeb/Bindings/MainThreadVM.h>
#include <LibWeb/DOM/Document.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/ContentBlocker.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
#include <LibWeb/Loader/ResourceLoader.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 <Services/RendererSandbox.h>
#include <WebContent/ConnectionFromClient.h>
#include <WebContent/PageClient.h>
#include <WebContent/WebContentCompositorHost.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);
Core::Process::terminate_immediately(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_blockers(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;
}
auto& event_loop = Core::EventLoop::initialize_for_current_thread();
WebView::platform_init();
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPlugin);
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_fontconfig = false;
bool collect_garbage_on_every_allocation = false;
bool is_headless = false;
bool disable_scrollbar_painting = false;
bool disable_async_scrolling = false;
bool disable_sandbox = false;
bool report_session_history_updates_in_test_mode = false;
StringView echo_server_port_string_view {};
StringView default_time_zone {};
StringView style_invalidation_counter_dump_interval {};
bool file_origins_are_tuple_origins = false;
Core::ArgsParser args_parser;
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_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(disable_async_scrolling, "Disable async scrolling", "disable-async-scrolling");
args_parser.add_option(disable_sandbox, "Disable process sandboxing", "disable-sandbox");
args_parser.add_option(report_session_history_updates_in_test_mode, "Report session history updates in test mode", "report-session-history-updates-in-test-mode");
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(style_invalidation_counter_dump_interval, "Dump style invalidation counters after every N style invalidations", "dump-style-invalidation-counters", 0, "N");
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 = Core::TimeZone::set_current_time_zone(default_time_zone); result.is_error())
dbgln("Failed to set default time zone: {}", result.error());
}
if (!style_invalidation_counter_dump_interval.is_empty()) {
auto interval = style_invalidation_counter_dump_interval.to_number<u64>();
if (!interval.has_value() || *interval == 0)
VERIFY_NOT_REACHED();
Web::DOM::Document::set_style_invalidation_counter_dump_interval(*interval);
}
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);
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);
WebContent::PageClient::set_async_scrolling_enabled(!disable_async_scrolling);
WebContent::PageClient::set_should_report_session_history_updates_in_test_mode(report_session_history_updates_in_test_mode);
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_blocker_error = load_content_blockers(config_path);
if (maybe_content_blocker_error.is_error())
dbgln("Failed to load content blockers: {}", maybe_content_blocker_error.error());
if (!disable_sandbox)
TRY(RendererSandbox::apply_sandbox(config_path));
#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));
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_blockers(StringView config_path)
{
auto buffer = TRY(ByteBuffer::create_uninitialized(4096));
auto file = TRY(Core::File::open(ByteString::formatted("{}/BrowserContentBlockers.txt", config_path), Core::File::OpenMode::Read));
auto content_blocker_list = TRY(Core::InputBufferedFile::create(move(file)));
Vector<String> patterns;
while (TRY(content_blocker_list->can_read_line())) {
auto line = TRY(content_blocker_list->read_line(buffer));
if (line.is_empty())
continue;
auto pattern = TRY(String::from_utf8(line));
patterns.append(move(pattern));
}
auto& content_blocker = Web::ContentBlocker::the();
TRY(content_blocker.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 {};
}