From f4f4f7781d2a1e5f31632751bf6eb6b1ba833945 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 16 Apr 2024 14:39:57 +0200 Subject: [PATCH] Ladybird+LibWeb: Add optional IDL call tracing When launched with the new --enable-idl-tracing option, we now log every call to web platform APIs declared via IDL, along with the arguments passed. This can be very helpful when trying to figure out what a site is doing, especially if it's not doing what you'd expect. --- Ladybird/HelperProcess.cpp | 2 + Ladybird/Qt/main.cpp | 3 ++ Ladybird/Types.h | 6 +++ Ladybird/WebContent/main.cpp | 10 +++++ .../BindingsGenerator/IDLGenerators.cpp | 25 ++++++++++- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + Userland/Libraries/LibWeb/WebIDL/Tracing.cpp | 42 +++++++++++++++++++ Userland/Libraries/LibWeb/WebIDL/Tracing.h | 22 ++++++++++ 8 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibWeb/WebIDL/Tracing.cpp create mode 100644 Userland/Libraries/LibWeb/WebIDL/Tracing.h diff --git a/Ladybird/HelperProcess.cpp b/Ladybird/HelperProcess.cpp index 2e235a28a70..d05d7a7349a 100644 --- a/Ladybird/HelperProcess.cpp +++ b/Ladybird/HelperProcess.cpp @@ -67,6 +67,8 @@ ErrorOr> launch_web_content_process( arguments.append("--wait-for-debugger"sv); if (web_content_options.log_all_js_exceptions == Ladybird::LogAllJSExceptions::Yes) arguments.append("--log-all-js-exceptions"sv); + if (web_content_options.enable_idl_tracing == Ladybird::EnableIDLTracing::Yes) + arguments.append("--enable-idl-tracing"sv); if (auto server = mach_server_name(); server.has_value()) { arguments.append("--mach-server-name"sv); arguments.append(server.value()); diff --git a/Ladybird/Qt/main.cpp b/Ladybird/Qt/main.cpp index 1fe89ece4e8..7db4dab016b 100644 --- a/Ladybird/Qt/main.cpp +++ b/Ladybird/Qt/main.cpp @@ -117,6 +117,7 @@ ErrorOr serenity_main(Main::Arguments arguments) bool use_gpu_painting = false; bool debug_web_content = false; bool log_all_js_exceptions = false; + bool enable_idl_tracing = false; Core::ArgsParser args_parser; args_parser.set_general_help("The Ladybird web browser :^)"); @@ -129,6 +130,7 @@ ErrorOr serenity_main(Main::Arguments arguments) args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content", 0); args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate"); args_parser.add_option(log_all_js_exceptions, "Log all JavaScript exceptions", "log-all-js-exceptions", 0); + args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing", 0); args_parser.parse(arguments); WebView::ProcessManager::initialize(); @@ -173,6 +175,7 @@ ErrorOr serenity_main(Main::Arguments arguments) .use_lagom_networking = enable_qt_networking ? Ladybird::UseLagomNetworking::No : Ladybird::UseLagomNetworking::Yes, .wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No, .log_all_js_exceptions = log_all_js_exceptions ? Ladybird::LogAllJSExceptions::Yes : Ladybird::LogAllJSExceptions::No, + .enable_idl_tracing = enable_idl_tracing ? Ladybird::EnableIDLTracing::Yes : Ladybird::EnableIDLTracing::No, }; Ladybird::BrowserWindow window(initial_urls, cookie_jar, web_content_options, webdriver_content_ipc_path); diff --git a/Ladybird/Types.h b/Ladybird/Types.h index 8b78856c3fd..b9911e79743 100644 --- a/Ladybird/Types.h +++ b/Ladybird/Types.h @@ -40,6 +40,11 @@ enum class LogAllJSExceptions { Yes }; +enum class EnableIDLTracing { + No, + Yes +}; + struct WebContentOptions { String command_line; String executable_path; @@ -50,6 +55,7 @@ struct WebContentOptions { UseLagomNetworking use_lagom_networking { UseLagomNetworking::No }; WaitForDebugger wait_for_debugger { WaitForDebugger::No }; LogAllJSExceptions log_all_js_exceptions { LogAllJSExceptions::No }; + EnableIDLTracing enable_idl_tracing { EnableIDLTracing::No }; }; } diff --git a/Ladybird/WebContent/main.cpp b/Ladybird/WebContent/main.cpp index 8431c22bfb9..2f1a5263d9d 100644 --- a/Ladybird/WebContent/main.cpp +++ b/Ladybird/WebContent/main.cpp @@ -57,6 +57,10 @@ namespace JS { extern bool g_log_all_js_exceptions; } +namespace Web::WebIDL { +extern bool g_enable_idl_tracing; +} + ErrorOr serenity_main(Main::Arguments arguments) { AK::set_rich_debug_enabled(true); @@ -94,6 +98,7 @@ ErrorOr serenity_main(Main::Arguments arguments) bool use_gpu_painting = false; bool wait_for_debugger = false; bool log_all_js_exceptions = false; + bool enable_idl_tracing = false; Core::ArgsParser args_parser; args_parser.add_option(command_line, "Chrome process command line", "command-line", 0, "command_line"); @@ -106,6 +111,7 @@ ErrorOr serenity_main(Main::Arguments arguments) args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger", 0); 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", 0); + args_parser.add_option(enable_idl_tracing, "Enable IDL tracing", "enable-idl-tracing", 0); args_parser.parse(arguments); @@ -144,6 +150,10 @@ ErrorOr serenity_main(Main::Arguments arguments) JS::g_log_all_js_exceptions = true; } + if (enable_idl_tracing) { + Web::WebIDL::g_enable_idl_tracing = true; + } + auto maybe_content_filter_error = load_content_filters(); if (maybe_content_filter_error.is_error()) dbgln("Failed to load content filters: {}", maybe_content_filter_error.error()); diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index 42107fcf340..5e0adac8831 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -1909,6 +1909,7 @@ static void generate_function(SourceGenerator& generator, IDL::Function const& f function_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@@overload_suffix@) { + WebIDL::log_trace(vm, "@class_name@::@function.name:snakecase@@overload_suffix@"); [[maybe_unused]] auto& realm = *vm.current_realm(); )~~~"); @@ -2185,11 +2186,13 @@ static void generate_overload_arbiter(SourceGenerator& generator, auto const& ov JS::ThrowCompletionOr> @constructor_class@::construct(JS::FunctionObject& new_target) { auto& vm = this->vm(); + WebIDL::log_trace(vm, "@constructor_class@::construct"); )~~~"); } else { function_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@function.name:snakecase@) { + WebIDL::log_trace(vm, "@class_name@::@function.name:snakecase@"); )~~~"); } @@ -2463,6 +2466,7 @@ static void generate_constructor(SourceGenerator& generator, IDL::Constructor co constructor_generator.append(R"~~~( JS::ThrowCompletionOr> @constructor_class@::construct@overload_suffix@([[maybe_unused]] FunctionObject& new_target) { + WebIDL::log_trace(vm(), "@constructor_class@::construct@overload_suffix@"); )~~~"); generator.append(R"~~~( @@ -2522,6 +2526,7 @@ static void generate_constructors(SourceGenerator& generator, IDL::Interface con generator.append(R"~~~( JS::ThrowCompletionOr> @constructor_class@::construct([[maybe_unused]] FunctionObject& new_target) { + WebIDL::log_trace(vm(), "@constructor_class@::construct"); )~~~"); generator.set("constructor.length", "0"); generator.append(R"~~~( @@ -2769,6 +2774,7 @@ static void generate_default_to_json_function(SourceGenerator& generator, ByteSt function_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_json) { + WebIDL::log_trace(vm, "@class_name@::to_json"); auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); @@ -3220,6 +3226,7 @@ static JS::ThrowCompletionOr<@fully_qualified_name@*> impl_from(JS::VM& vm) attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) { + WebIDL::log_trace(vm, "@class_name@::@attribute.getter_callback@"); [[maybe_unused]] auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); )~~~"); @@ -3424,6 +3431,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.getter_callback@) attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) { + WebIDL::log_trace(vm, "@class_name@::@attribute.setter_callback@"); [[maybe_unused]] auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); @@ -3508,6 +3516,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) { + WebIDL::log_trace(vm, "@class_name@::@attribute.setter_callback@"); auto this_value = vm.this_value(); JS::GCPtr window; if (this_value.is_object()) { @@ -3532,6 +3541,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) { + WebIDL::log_trace(vm, "@class_name@::@attribute.setter_callback@"); auto this_value = vm.this_value(); if (!this_value.is_object() || !is<@fully_qualified_name@>(this_value.as_object())) return vm.throw_completion(JS::ErrorType::NotAnObjectOfType, "@namespaced_name@"); @@ -3546,6 +3556,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) { + WebIDL::log_trace(vm, "@class_name@::@attribute.setter_callback@"); auto* impl = TRY(impl_from(vm)); auto value = vm.argument(0); @@ -3588,6 +3599,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::@attribute.setter_callback@) stringifier_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_string) { + WebIDL::log_trace(vm, "@class_name@::to_string"); [[maybe_unused]] auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); @@ -3613,6 +3625,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::to_string) iterator_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries) { + WebIDL::log_trace(vm, "@class_name@::entries"); auto* impl = TRY(impl_from(vm)); return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(*impl, Object::PropertyKind::KeyAndValue); })); @@ -3620,6 +3633,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::entries) JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each) { + WebIDL::log_trace(vm, "@class_name@::for_each"); [[maybe_unused]] auto& realm = *vm.current_realm(); auto* impl = TRY(impl_from(vm)); @@ -3642,6 +3656,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::for_each) JS_DEFINE_NATIVE_FUNCTION(@class_name@::keys) { + WebIDL::log_trace(vm, "@class_name@::keys"); auto* impl = TRY(impl_from(vm)); return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(*impl, Object::PropertyKind::Key); })); @@ -3649,6 +3664,7 @@ JS_DEFINE_NATIVE_FUNCTION(@class_name@::keys) JS_DEFINE_NATIVE_FUNCTION(@class_name@::values) { + WebIDL::log_trace(vm, "@class_name@::values"); auto* impl = TRY(impl_from(vm)); return TRY(throw_dom_exception_if_needed(vm, [&] { return @iterator_name@::create(*impl, Object::PropertyKind::Value); })); @@ -3735,6 +3751,7 @@ void generate_namespace_implementation(IDL::Interface const& interface, StringBu #include #include #include +#include #include )~~~"); @@ -3976,8 +3993,9 @@ void generate_constructor_implementation(IDL::Interface const& interface, String #include #include #include -#include #include +#include +#include )~~~"); @@ -4123,6 +4141,7 @@ void @constructor_class@::initialize(JS::Realm& realm) attribute_generator.append(R"~~~( JS_DEFINE_NATIVE_FUNCTION(@constructor_class@::@attribute.getter_callback@) { + WebIDL::log_trace(vm, "@constructor_class@::@attribute.getter_callback@"); auto retval = TRY(throw_dom_exception_if_needed(vm, [&] { return @fully_qualified_name@::@attribute.cpp_name@(vm); })); )~~~"); @@ -4225,6 +4244,7 @@ void generate_prototype_implementation(IDL::Interface const& interface, StringBu #include #include #include +#include #include #include @@ -4417,6 +4437,7 @@ void generate_iterator_prototype_implementation(IDL::Interface const& interface, #include #include #include +#include #if __has_include() # include @@ -4483,6 +4504,7 @@ static JS::ThrowCompletionOr<@fully_qualified_name@*> impl_from(JS::VM& vm) JS_DEFINE_NATIVE_FUNCTION(@prototype_class@::next) { + WebIDL::log_trace(vm, "@prototype_class@::next"); auto* impl = TRY(impl_from(vm)); return TRY(throw_dom_exception_if_needed(vm, [&] { return impl->next(); })); } @@ -4552,6 +4574,7 @@ void generate_global_mixin_implementation(IDL::Interface const& interface, Strin #include #include #include +#include #include )~~~"); diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 813b944308f..46a8cf83fc2 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -675,6 +675,7 @@ set(SOURCES WebIDL/ObservableArray.cpp WebIDL/OverloadResolution.cpp WebIDL/Promise.cpp + WebIDL/Tracing.cpp WebSockets/WebSocket.cpp XHR/EventNames.cpp XHR/FormData.cpp diff --git a/Userland/Libraries/LibWeb/WebIDL/Tracing.cpp b/Userland/Libraries/LibWeb/WebIDL/Tracing.cpp new file mode 100644 index 00000000000..0d462996f9b --- /dev/null +++ b/Userland/Libraries/LibWeb/WebIDL/Tracing.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::WebIDL { + +bool g_enable_idl_tracing = false; + +void log_trace_impl(JS::VM& vm, char const* function) +{ + if (!g_enable_idl_tracing) + return; + + StringBuilder builder; + for (size_t i = 0; i < vm.argument_count(); ++i) { + if (i != 0) + builder.append(", "sv); + auto argument = vm.argument(i); + if (argument.is_string()) + builder.append_code_point('"'); + auto string = argument.to_string_without_side_effects(); + for (auto code_point : string.code_points()) { + if (code_point < 0x20) { + builder.appendff("\\u{:04x}", code_point); + continue; + } + builder.append_code_point(code_point); + } + if (argument.is_string()) + builder.append_code_point('"'); + } + dbgln("{}({})", function, builder.string_view()); +} + +} diff --git a/Userland/Libraries/LibWeb/WebIDL/Tracing.h b/Userland/Libraries/LibWeb/WebIDL/Tracing.h new file mode 100644 index 00000000000..6a4ba27d29b --- /dev/null +++ b/Userland/Libraries/LibWeb/WebIDL/Tracing.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, Andreas Kling + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Web::WebIDL { + +extern bool g_enable_idl_tracing; + +inline void log_trace(JS::VM& vm, char const* function) +{ + void log_trace_impl(JS::VM&, char const*); + if (g_enable_idl_tracing) + log_trace_impl(vm, function); +} + +}