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.
This commit is contained in:
Andreas Kling 2024-04-16 14:39:57 +02:00
parent 5f9a905793
commit f4f4f7781d
8 changed files with 110 additions and 1 deletions

View file

@ -67,6 +67,8 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> 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());

View file

@ -117,6 +117,7 @@ ErrorOr<int> 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<int> 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<int> 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);

View file

@ -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 };
};
}

View file

@ -57,6 +57,10 @@ namespace JS {
extern bool g_log_all_js_exceptions;
}
namespace Web::WebIDL {
extern bool g_enable_idl_tracing;
}
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
AK::set_rich_debug_enabled(true);
@ -94,6 +98,7 @@ ErrorOr<int> 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<int> 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<int> 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());

View file

@ -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<JS::NonnullGCPtr<JS::Object>> @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<JS::NonnullGCPtr<JS::Object>> @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<JS::NonnullGCPtr<JS::Object>> @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> 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::TypeError>(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 <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/OverloadResolution.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>
)~~~");
@ -3976,8 +3993,9 @@ void generate_constructor_implementation(IDL::Interface const& interface, String
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/CallbackType.h>
#include <LibWeb/WebIDL/Types.h>
#include <LibWeb/WebIDL/OverloadResolution.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>
)~~~");
@ -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 <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/Buffers.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/OverloadResolution.h>
#include <LibWeb/WebIDL/Types.h>
@ -4417,6 +4437,7 @@ void generate_iterator_prototype_implementation(IDL::Interface const& interface,
#include <LibWeb/Bindings/@prototype_class@.h>
#include <LibWeb/Bindings/ExceptionOrUtils.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/WebIDL/Tracing.h>
#if __has_include(<LibWeb/@possible_include_path@.h>)
# include <LibWeb/@possible_include_path@.h>
@ -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 <LibWeb/HTML/WindowProxy.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
#include <LibWeb/WebIDL/OverloadResolution.h>
#include <LibWeb/WebIDL/Tracing.h>
#include <LibWeb/WebIDL/Types.h>
)~~~");

View file

@ -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

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2024, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Format.h>
#include <AK/StringBuilder.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/WebIDL/Tracing.h>
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());
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2024, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Forward.h>
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);
}
}