/* * Copyright (c) 2020-2024, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(AK_OS_MACOS) # include # include #else # include #endif #if defined(AK_OS_WINDOWS) # include #endif #include #if !defined(AK_OS_WINDOWS) # include 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 load_content_filters(StringView config_path); static ErrorOr connect_to_resource_loader(GC::Heap& heap, IPC::TransportHandle const& handle); static ErrorOr connect_to_image_decoder(IPC::TransportHandle const& handle); ErrorOr 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); auto config_path = ByteString::formatted("{}/ladybird/default-config", WebView::s_ladybird_resource_root); StringView mach_server_name {}; Vector 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(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::FontDatabase::the().install_system_font_provider(make())); 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); if (!echo_server_port_string_view.is_empty()) { if (auto maybe_echo_server_port = echo_server_port_string_view.to_number(); 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)); auto webcontent_client = WebContent::ConnectionFromClient::construct( make(move(transport_ports.receive_right), move(transport_ports.send_right))); #else auto webcontent_client = TRY(IPC::take_over_accepted_client_from_system_server(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 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 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 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(move(transport))); #ifdef AK_OS_WINDOWS auto response = request_client->send_sync(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 connect_to_image_decoder(IPC::TransportHandle const& handle) { auto transport = TRY(handle.create_transport()); auto new_client = TRY(try_make_ref_counted(move(transport))); #ifdef AK_OS_WINDOWS auto response = new_client->send_sync(Core::System::getpid()); new_client->transport().set_peer_pid(response->peer_pid()); #endif if (Web::Platform::ImageCodecPlugin::is_initialized()) static_cast(Web::Platform::ImageCodecPlugin::the()).set_client(move(new_client)); else Web::Platform::ImageCodecPlugin::install(*new WebView::ImageCodecPlugin(move(new_client))); return {}; }