ladybird/Services/WebContent/main.cpp
Andreas Kling 24f37c6732 LibWebView: Keep browser history in the UI process
Use the LibWebView history mirror to preserve traversable session
history across WebContent process swaps. WebContent reports snapshots to
the UI process, and new renderers can be seeded from the mirror.

Browser back and forward now resolve through the UI-owned used history
steps. WebContent still runs the spec traversal path when the current
renderer has enough matching state to do so.

Handle canceled and no-op UI navigations without leaving speculative
history entries or pending WebDriver waits behind. Preserve traversal
precheck state across synchronous IPC shutdown, and avoid overwriting a
restored target entry's persisted scroll state before the document has
adopted that entry.
2026-06-14 17:38:44 +02:00

342 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 <LibGfx/SkiaBackendContext.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_cpu_painting = 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 enable_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_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(disable_async_scrolling, "Disable async scrolling", "disable-async-scrolling");
args_parser.add_option(enable_sandbox, "Enable process sandboxing", "enable-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);
// 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);
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 (enable_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 {};
}