mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibWeb+LibWebView+Services: Add a flag to enable experimental interfaces
This adds the --expose-experimental-interfaces command line flag to enable experimental IDL interfaces. Any IDL interface with Experimental in its exposed attributes will be disabled by default. The problem is that by stubbing out or partially implementing interfaces in LibWeb, we actually make some sites behave worse. For example, the OffscreenCanvas interface being exposed makes sites believe we fully support it, even though we don't. If the interface was not exposed, these sites may fall back to ordinary canvas objects. Similarly, to use YouTube, we currently have to patch out MSE interfaces. This flag will allow developers to iteratively work on features, without breaking such sites. We enable experimental interfaces during tests.
This commit is contained in:
committed by
Shannon Booth
parent
b357d3c3c8
commit
8ad1c72ed3
Notes:
github-actions[bot]
2026-02-17 21:19:21 +00:00
Author: https://github.com/trflynn89 Commit: https://github.com/LadybirdBrowser/ladybird/commit/8ad1c72ed39 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8007 Reviewed-by: https://github.com/shannonbooth ✅
@@ -242,4 +242,16 @@ void UniversalGlobalScopeMixin::notify_about_rejected_promises(Badge<EventLoop>)
|
||||
}));
|
||||
}
|
||||
|
||||
static bool s_experimental_interfaces_exposed = false;
|
||||
|
||||
void UniversalGlobalScopeMixin::set_experimental_interfaces_exposed(bool exposed)
|
||||
{
|
||||
s_experimental_interfaces_exposed = exposed;
|
||||
}
|
||||
|
||||
bool UniversalGlobalScopeMixin::expose_experimental_interfaces()
|
||||
{
|
||||
return s_experimental_interfaces_exposed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ public:
|
||||
ImportMap const& import_map() const { return m_import_map; }
|
||||
void set_import_map(ImportMap const& import_map) { m_import_map = import_map; }
|
||||
|
||||
static WEB_API void set_experimental_interfaces_exposed(bool);
|
||||
static WEB_API bool expose_experimental_interfaces();
|
||||
|
||||
protected:
|
||||
void visit_edges(GC::Cell::Visitor&);
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
||||
bool disable_content_filter = false;
|
||||
Optional<StringView> resource_substitution_map_path;
|
||||
bool enable_autoplay = false;
|
||||
bool expose_experimental_interfaces = false;
|
||||
bool expose_internals_object = false;
|
||||
bool force_cpu_painting = false;
|
||||
bool force_fontconfig = false;
|
||||
@@ -179,6 +180,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
||||
args_parser.add_option(disable_http_disk_cache, "Disable HTTP disk cache", "disable-http-disk-cache");
|
||||
args_parser.add_option(disable_content_filter, "Disable content filter", "disable-content-filter");
|
||||
args_parser.add_option(enable_autoplay, "Enable multimedia autoplay", "enable-autoplay");
|
||||
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(force_cpu_painting, "Force CPU painting", "force-cpu-painting");
|
||||
args_parser.add_option(force_fontconfig, "Force using fontconfig for font loading", "force-fontconfig");
|
||||
@@ -289,6 +291,7 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
||||
.disable_site_isolation = disable_site_isolation ? DisableSiteIsolation::Yes : DisableSiteIsolation::No,
|
||||
.enable_idl_tracing = enable_idl_tracing ? EnableIDLTracing::Yes : EnableIDLTracing::No,
|
||||
.enable_http_memory_cache = disable_http_memory_cache ? EnableMemoryHTTPCache::No : EnableMemoryHTTPCache::Yes,
|
||||
.expose_experimental_interfaces = expose_experimental_interfaces ? ExposeExperimentalInterfaces::Yes : ExposeExperimentalInterfaces::No,
|
||||
.expose_internals_object = expose_internals_object ? ExposeInternalsObject::Yes : ExposeInternalsObject::No,
|
||||
.force_cpu_painting = force_cpu_painting ? ForceCPUPainting::Yes : ForceCPUPainting::No,
|
||||
.force_fontconfig = force_fontconfig ? ForceFontconfig::Yes : ForceFontconfig::No,
|
||||
@@ -300,8 +303,9 @@ ErrorOr<void> Application::initialize(Main::Arguments const& arguments)
|
||||
|
||||
create_platform_options(m_browser_options, m_request_server_options, m_web_content_options);
|
||||
|
||||
// Test mode implies internals object is exposed and the Skia CPU backend is used
|
||||
// Test mode implies experimental interfaces and internals object are exposed and the Skia CPU backend is used.
|
||||
if (m_web_content_options.is_test_mode == IsTestMode::Yes) {
|
||||
m_web_content_options.expose_experimental_interfaces = ExposeExperimentalInterfaces::Yes;
|
||||
m_web_content_options.expose_internals_object = ExposeInternalsObject::Yes;
|
||||
m_web_content_options.force_cpu_painting = ForceCPUPainting::Yes;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,8 @@ static ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_proc
|
||||
arguments.append("--enable-idl-tracing"sv);
|
||||
if (web_content_options.enable_http_memory_cache == WebView::EnableMemoryHTTPCache::Yes)
|
||||
arguments.append("--enable-http-memory-cache"sv);
|
||||
if (web_content_options.expose_experimental_interfaces == WebView::ExposeExperimentalInterfaces::Yes)
|
||||
arguments.append("--expose-experimental-interfaces"sv);
|
||||
if (web_content_options.expose_internals_object == WebView::ExposeInternalsObject::Yes)
|
||||
arguments.append("--expose-internals-object"sv);
|
||||
if (web_content_options.force_cpu_painting == WebView::ForceCPUPainting::Yes)
|
||||
@@ -182,6 +184,8 @@ ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(Web
|
||||
|
||||
Vector<ByteString> arguments;
|
||||
|
||||
if (web_content_options.expose_experimental_interfaces == WebView::ExposeExperimentalInterfaces::Yes)
|
||||
arguments.append("--expose-experimental-interfaces"sv);
|
||||
if (web_content_options.enable_http_memory_cache == WebView::EnableMemoryHTTPCache::Yes)
|
||||
arguments.append("--enable-http-memory-cache"sv);
|
||||
|
||||
|
||||
@@ -131,6 +131,11 @@ enum class DisableSiteIsolation {
|
||||
Yes,
|
||||
};
|
||||
|
||||
enum class ExposeExperimentalInterfaces {
|
||||
No,
|
||||
Yes,
|
||||
};
|
||||
|
||||
enum class ExposeInternalsObject {
|
||||
No,
|
||||
Yes,
|
||||
@@ -166,6 +171,7 @@ struct WebContentOptions {
|
||||
DisableSiteIsolation disable_site_isolation { DisableSiteIsolation::No };
|
||||
EnableIDLTracing enable_idl_tracing { EnableIDLTracing::No };
|
||||
EnableMemoryHTTPCache enable_http_memory_cache { EnableMemoryHTTPCache::No };
|
||||
ExposeExperimentalInterfaces expose_experimental_interfaces { ExposeExperimentalInterfaces::No };
|
||||
ExposeInternalsObject expose_internals_object { ExposeInternalsObject::No };
|
||||
ForceCPUPainting force_cpu_painting { ForceCPUPainting::No };
|
||||
ForceFontconfig force_fontconfig { ForceFontconfig::No };
|
||||
|
||||
@@ -184,10 +184,9 @@ void Intrinsics::create_web_namespace<@namespace_class@>(JS::Realm& realm)
|
||||
};
|
||||
|
||||
generator.append(R"~~~(
|
||||
static bool is_secure_context_interface(InterfaceName name)
|
||||
static constexpr bool is_secure_context_interface(InterfaceName name)
|
||||
{
|
||||
switch (name) {
|
||||
)~~~");
|
||||
switch (name) {)~~~");
|
||||
for (auto const& interface : interface_sets.intrinsics) {
|
||||
if (!interface.extended_attributes.contains("SecureContext"))
|
||||
continue;
|
||||
@@ -202,15 +201,34 @@ static bool is_secure_context_interface(InterfaceName name)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
generator.append(R"~~~(
|
||||
static constexpr bool is_experimental_interface(InterfaceName name)
|
||||
{
|
||||
switch (name) {)~~~");
|
||||
for (auto const& interface : interface_sets.intrinsics) {
|
||||
if (!interface.extended_attributes.contains("Experimental"))
|
||||
continue;
|
||||
|
||||
generator.set("experimental_interface_name", interface.name);
|
||||
generator.append(R"~~~(
|
||||
case InterfaceName::@experimental_interface_name@:)~~~");
|
||||
}
|
||||
generator.append(R"~~~(
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)~~~");
|
||||
|
||||
auto generate_global_exposed = [&generator](StringView global_name, Vector<IDL::Interface&> const& interface_set) {
|
||||
generator.set("global_name", global_name);
|
||||
generator.append(R"~~~(
|
||||
static bool is_@global_name@_exposed(InterfaceName name)
|
||||
static constexpr bool is_@global_name@_exposed(InterfaceName name)
|
||||
{
|
||||
switch (name) {
|
||||
)~~~");
|
||||
switch (name) {)~~~");
|
||||
for (auto const& interface : interface_set) {
|
||||
auto gen = generator.fork();
|
||||
gen.set("interface_name", interface.name);
|
||||
@@ -262,6 +280,10 @@ bool is_exposed(InterfaceName name, JS::Realm& realm)
|
||||
if (is_secure_context_interface(name) && HTML::is_non_secure_context(principal_host_defined_environment_settings_object(realm)))
|
||||
return false;
|
||||
|
||||
// AD-HOC: Do not expose experimental interfaces unless instructed to do so.
|
||||
if (!HTML::UniversalGlobalScopeMixin::expose_experimental_interfaces() && is_experimental_interface(name))
|
||||
return false;
|
||||
|
||||
// FIXME: 3. If realm’s settings object’s cross-origin isolated capability is false, and construct is
|
||||
// conditionally exposed on [CrossOriginIsolated], then return false.
|
||||
|
||||
@@ -378,6 +400,7 @@ static ErrorOr<void> generate_exposed_interface_implementation(StringView class_
|
||||
#include <LibWeb/Bindings/Intrinsics.h>
|
||||
#include <LibWeb/Bindings/@global_object_name@ExposedInterfaces.h>
|
||||
#include <LibWeb/HTML/Scripting/Environments.h>
|
||||
#include <LibWeb/HTML/UniversalGlobalScope.h>
|
||||
)~~~");
|
||||
for (auto& interface : exposed_interfaces) {
|
||||
auto gen = generator.fork();
|
||||
@@ -408,7 +431,9 @@ namespace Web::Bindings {
|
||||
void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
|
||||
{
|
||||
static constexpr u8 attr = JS::Attribute::Writable | JS::Attribute::Configurable;
|
||||
|
||||
[[maybe_unused]] bool is_secure_context = HTML::is_secure_context(HTML::relevant_principal_settings_object(global));
|
||||
[[maybe_unused]] bool expose_experimental_interfaces = HTML::UniversalGlobalScopeMixin::expose_experimental_interfaces();
|
||||
)~~~");
|
||||
|
||||
auto add_interface = [class_name](SourceGenerator& gen, IDL::Interface const& interface) {
|
||||
@@ -420,11 +445,16 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
|
||||
gen.set("interface_name", interface.namespaced_name);
|
||||
gen.set("prototype_class", interface.prototype_class);
|
||||
|
||||
if (interface.extended_attributes.contains("SecureContext")) {
|
||||
if (interface.extended_attributes.contains("SecureContext"sv)) {
|
||||
gen.append(R"~~~(
|
||||
if (is_secure_context) {)~~~");
|
||||
}
|
||||
|
||||
if (interface.extended_attributes.contains("Experimental"sv)) {
|
||||
gen.append(R"~~~(
|
||||
if (expose_experimental_interfaces) {)~~~");
|
||||
}
|
||||
|
||||
gen.append(R"~~~(
|
||||
global.define_intrinsic_accessor("@interface_name@"_utf16_fly_string, attr, [](auto& realm) -> JS::Value { return &ensure_web_constructor<@prototype_class@>(realm, "@interface_name@"_fly_string); });)~~~");
|
||||
|
||||
@@ -450,7 +480,12 @@ void add_@global_object_snake_name@_exposed_interfaces(JS::Object& global)
|
||||
global.define_intrinsic_accessor("@legacy_interface_name@"_utf16_fly_string, attr, [](auto& realm) -> JS::Value { return &ensure_web_constructor<@prototype_class@>(realm, "@legacy_interface_name@"_fly_string); });)~~~");
|
||||
}
|
||||
|
||||
if (interface.extended_attributes.contains("SecureContext")) {
|
||||
if (interface.extended_attributes.contains("Experimental"sv)) {
|
||||
gen.append(R"~~~(
|
||||
})~~~");
|
||||
}
|
||||
|
||||
if (interface.extended_attributes.contains("SecureContext"sv)) {
|
||||
gen.append(R"~~~(
|
||||
})~~~");
|
||||
}
|
||||
|
||||
@@ -86,6 +86,7 @@ WPT_ARGS=(
|
||||
"--install-webdriver"
|
||||
"--webdriver-arg=--force-cpu-painting"
|
||||
"--webdriver-arg=--default-time-zone=UTC"
|
||||
"--webdriver-arg=--expose-experimental-interfaces"
|
||||
"--no-pause-after-test"
|
||||
"--install-fonts"
|
||||
"${EXTRA_WPT_ARGS[@]}"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#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>
|
||||
@@ -90,6 +91,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
int request_server_socket { -1 };
|
||||
int image_decoder_socket { -1 };
|
||||
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;
|
||||
@@ -111,6 +113,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
args_parser.add_option(request_server_socket, "File descriptor of the socket for the RequestServer connection", "request-server-socket", 'r', "request_server_socket");
|
||||
args_parser.add_option(image_decoder_socket, "File descriptor of the socket for the ImageDecoder connection", "image-decoder-socket", 'i', "image_decoder_socket");
|
||||
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");
|
||||
@@ -185,6 +188,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
|
||||
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 WebView::FontPlugin(enable_test_mode, &font_provider));
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ static ErrorOr<Core::Process> launch_process(StringView application, ReadonlySpa
|
||||
return result;
|
||||
}
|
||||
|
||||
static Vector<ByteString> create_arguments(ByteString const& socket_path, bool headless, bool force_cpu_painting, Optional<StringView> debug_process, Optional<StringView> default_time_zone)
|
||||
static Vector<ByteString> create_arguments(ByteString const& socket_path, bool headless, bool expose_experimental_interfaces, bool force_cpu_painting, Optional<StringView> debug_process, Optional<StringView> default_time_zone)
|
||||
{
|
||||
Vector<ByteString> arguments {
|
||||
"--webdriver-content-path"sv,
|
||||
@@ -53,6 +53,8 @@ static Vector<ByteString> create_arguments(ByteString const& socket_path, bool h
|
||||
arguments.append("--force-new-process"sv);
|
||||
arguments.append("--enable-autoplay"sv);
|
||||
arguments.append("--disable-scrollbar-painting"sv);
|
||||
if (expose_experimental_interfaces)
|
||||
arguments.append("--expose-experimental-interfaces"sv);
|
||||
if (force_cpu_painting)
|
||||
arguments.append("--force-cpu-painting"sv);
|
||||
|
||||
@@ -75,6 +77,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
|
||||
auto listen_address = "0.0.0.0"sv;
|
||||
int port = 8000;
|
||||
bool expose_experimental_interfaces = false;
|
||||
bool force_cpu_painting = false;
|
||||
bool headless = false;
|
||||
Optional<StringView> debug_process;
|
||||
@@ -84,6 +87,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
args_parser.add_option(listen_address, "IP address to listen on", "listen-address", 'l', "listen_address");
|
||||
args_parser.add_option(port, "Port to listen on", "port", 'p', "port");
|
||||
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
|
||||
args_parser.add_option(expose_experimental_interfaces, "Expose experimental IDL interfaces", "expose-experimental-interfaces");
|
||||
args_parser.add_option(force_cpu_painting, "Launch browser with GPU painting disabled", "force-cpu-painting");
|
||||
args_parser.add_option(debug_process, "Wait for a debugger to attach to the given process name (WebContent, RequestServer, etc.)", "debug-process", 0, "process-name");
|
||||
args_parser.add_option(headless, "Launch browser without a graphical interface", "headless");
|
||||
@@ -128,7 +132,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
}
|
||||
|
||||
auto launch_browser_callback = [&](ByteString const& socket_path, bool headless) {
|
||||
auto arguments = create_arguments(socket_path, headless, force_cpu_painting, debug_process, default_time_zone);
|
||||
auto arguments = create_arguments(socket_path, headless, expose_experimental_interfaces, force_cpu_painting, debug_process, default_time_zone);
|
||||
return launch_process("Ladybird"sv, arguments.span());
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <LibMain/Main.h>
|
||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
||||
#include <LibWeb/HTML/UniversalGlobalScope.h>
|
||||
#include <LibWeb/Loader/GeneratedPagesLoader.h>
|
||||
#include <LibWeb/Loader/ResourceLoader.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
@@ -50,6 +51,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
StringView serenity_resource_root;
|
||||
StringView worker_type_string;
|
||||
Vector<ByteString> certificates;
|
||||
bool expose_experimental_interfaces = false;
|
||||
bool enable_http_memory_cache = false;
|
||||
bool wait_for_debugger = false;
|
||||
|
||||
@@ -58,6 +60,7 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
args_parser.add_option(image_decoder_socket, "File descriptor of the socket for the ImageDecoder connection", "image-decoder-socket", 'i', "image_decoder_socket");
|
||||
args_parser.add_option(serenity_resource_root, "Absolute path to directory for serenity resources", "serenity-resource-root", 'r', "serenity-resource-root");
|
||||
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
|
||||
args_parser.add_option(expose_experimental_interfaces, "Expose experimental IDL interfaces", "expose-experimental-interfaces");
|
||||
args_parser.add_option(enable_http_memory_cache, "Enable HTTP cache", "enable-http-memory-cache");
|
||||
args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger");
|
||||
args_parser.add_option(worker_type_string, "Type of WebWorker to start (dedicated, shared, or service)", "type", 't', "type");
|
||||
@@ -80,6 +83,8 @@ ErrorOr<int> ladybird_main(Main::Arguments arguments)
|
||||
|
||||
TRY(initialize_image_decoder(image_decoder_socket));
|
||||
|
||||
Web::HTML::UniversalGlobalScopeMixin::set_experimental_interfaces_exposed(expose_experimental_interfaces);
|
||||
|
||||
Web::Platform::EventLoopPlugin::install(*new Web::Platform::EventLoopPluginSerenity);
|
||||
|
||||
Web::Platform::FontPlugin::install(*new WebView::FontPlugin(false));
|
||||
|
||||
Reference in New Issue
Block a user