Replace the local substring matcher with the adblock-rust engine exposed through the dedicated content blocker Rust FFI. Keep the previous engine when a replacement list cannot be parsed. Pass shared list buffers directly into Rust instead of building a duplicate C++ vector of lines first. Generate cosmetic CSS through the Rust matcher, including generic class and id selectors collected from shadow-including descendants. Keep a supplemental index for generic cosmetic selector-list rules. adblock-rust indexes these rules under the first class or id token only, so also key them by later simple class and id selectors in the list. Update ContentBlocker coverage for rule options, exceptions, third-party checks, blob and file URLs, invalid list replacement, filtering toggles, cosmetic CSS, and generic selector-list cosmetics.
336 lines
14 KiB
C++
336 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 <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);
|
|
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_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;
|
|
}
|
|
|
|
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<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_compositor_process = 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_compositor_process, "Use the Compositor helper process", "enable-compositor-process");
|
|
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::set_should_use_compositor_process(enable_compositor_process);
|
|
|
|
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 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 {};
|
|
}
|