Browse Source

Ladybird+LibJS: Add optional logging of *all* JS exceptions

When running with --log-all-js-exceptions, we will print the message
and backtrace for every single JS exception that is thrown, not just
the ones nobody caught.

This can sometimes be very helpful in debugging sites that swallow
important exceptions.
Andreas Kling 1 year ago
parent
commit
5f9a905793

+ 3 - 0
Ladybird/AppKit/main.mm

@@ -47,6 +47,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     StringView webdriver_content_ipc_path;
     StringView webdriver_content_ipc_path;
     bool use_gpu_painting = false;
     bool use_gpu_painting = false;
     bool debug_web_content = false;
     bool debug_web_content = false;
+    bool log_all_js_exceptions = false;
 
 
     Core::ArgsParser args_parser;
     Core::ArgsParser args_parser;
     args_parser.set_general_help("The Ladybird web browser");
     args_parser.set_general_help("The Ladybird web browser");
@@ -55,6 +56,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting", 0);
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting", 0);
     args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content", 0);
     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(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.parse(arguments);
     args_parser.parse(arguments);
 
 
     WebView::ProcessManager::initialize();
     WebView::ProcessManager::initialize();
@@ -89,6 +91,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         .enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
         .enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
         .use_lagom_networking = Ladybird::UseLagomNetworking::Yes,
         .use_lagom_networking = Ladybird::UseLagomNetworking::Yes,
         .wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No,
         .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,
     };
     };
 
 
     auto* delegate = [[ApplicationDelegate alloc] init:move(initial_urls)
     auto* delegate = [[ApplicationDelegate alloc] init:move(initial_urls)

+ 2 - 0
Ladybird/HelperProcess.cpp

@@ -65,6 +65,8 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
                 arguments.append("--use-gpu-painting"sv);
                 arguments.append("--use-gpu-painting"sv);
             if (web_content_options.wait_for_debugger == Ladybird::WaitForDebugger::Yes)
             if (web_content_options.wait_for_debugger == Ladybird::WaitForDebugger::Yes)
                 arguments.append("--wait-for-debugger"sv);
                 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 (auto server = mach_server_name(); server.has_value()) {
             if (auto server = mach_server_name(); server.has_value()) {
                 arguments.append("--mach-server-name"sv);
                 arguments.append("--mach-server-name"sv);
                 arguments.append(server.value());
                 arguments.append(server.value());

+ 3 - 0
Ladybird/Qt/main.cpp

@@ -116,6 +116,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     bool enable_qt_networking = false;
     bool enable_qt_networking = false;
     bool use_gpu_painting = false;
     bool use_gpu_painting = false;
     bool debug_web_content = false;
     bool debug_web_content = false;
+    bool log_all_js_exceptions = false;
 
 
     Core::ArgsParser args_parser;
     Core::ArgsParser args_parser;
     args_parser.set_general_help("The Ladybird web browser :^)");
     args_parser.set_general_help("The Ladybird web browser :^)");
@@ -127,6 +128,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting", 0);
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "enable-gpu-painting", 0);
     args_parser.add_option(debug_web_content, "Wait for debugger to attach to WebContent", "debug-web-content", 0);
     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(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.parse(arguments);
     args_parser.parse(arguments);
 
 
     WebView::ProcessManager::initialize();
     WebView::ProcessManager::initialize();
@@ -170,6 +172,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
         .enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
         .enable_gpu_painting = use_gpu_painting ? Ladybird::EnableGPUPainting::Yes : Ladybird::EnableGPUPainting::No,
         .use_lagom_networking = enable_qt_networking ? Ladybird::UseLagomNetworking::No : Ladybird::UseLagomNetworking::Yes,
         .use_lagom_networking = enable_qt_networking ? Ladybird::UseLagomNetworking::No : Ladybird::UseLagomNetworking::Yes,
         .wait_for_debugger = debug_web_content ? Ladybird::WaitForDebugger::Yes : Ladybird::WaitForDebugger::No,
         .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,
     };
     };
 
 
     Ladybird::BrowserWindow window(initial_urls, cookie_jar, web_content_options, webdriver_content_ipc_path);
     Ladybird::BrowserWindow window(initial_urls, cookie_jar, web_content_options, webdriver_content_ipc_path);

+ 6 - 0
Ladybird/Types.h

@@ -35,6 +35,11 @@ enum class WaitForDebugger {
     Yes
     Yes
 };
 };
 
 
+enum class LogAllJSExceptions {
+    No,
+    Yes
+};
+
 struct WebContentOptions {
 struct WebContentOptions {
     String command_line;
     String command_line;
     String executable_path;
     String executable_path;
@@ -44,6 +49,7 @@ struct WebContentOptions {
     IsLayoutTestMode is_layout_test_mode { IsLayoutTestMode::No };
     IsLayoutTestMode is_layout_test_mode { IsLayoutTestMode::No };
     UseLagomNetworking use_lagom_networking { UseLagomNetworking::No };
     UseLagomNetworking use_lagom_networking { UseLagomNetworking::No };
     WaitForDebugger wait_for_debugger { WaitForDebugger::No };
     WaitForDebugger wait_for_debugger { WaitForDebugger::No };
+    LogAllJSExceptions log_all_js_exceptions { LogAllJSExceptions::No };
 };
 };
 
 
 }
 }

+ 11 - 1
Ladybird/WebContent/main.cpp

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org>
+ * Copyright (c) 2020-2024, Andreas Kling <kling@serenityos.org>
  *
  *
  * SPDX-License-Identifier: BSD-2-Clause
  * SPDX-License-Identifier: BSD-2-Clause
  */
  */
@@ -53,6 +53,10 @@ static ErrorOr<void> load_content_filters();
 static ErrorOr<void> load_autoplay_allowlist();
 static ErrorOr<void> load_autoplay_allowlist();
 static ErrorOr<void> initialize_lagom_networking(Vector<ByteString> const& certificates);
 static ErrorOr<void> initialize_lagom_networking(Vector<ByteString> const& certificates);
 
 
+namespace JS {
+extern bool g_log_all_js_exceptions;
+}
+
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 ErrorOr<int> serenity_main(Main::Arguments arguments)
 {
 {
     AK::set_rich_debug_enabled(true);
     AK::set_rich_debug_enabled(true);
@@ -89,6 +93,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     bool use_lagom_networking = false;
     bool use_lagom_networking = false;
     bool use_gpu_painting = false;
     bool use_gpu_painting = false;
     bool wait_for_debugger = false;
     bool wait_for_debugger = false;
+    bool log_all_js_exceptions = false;
 
 
     Core::ArgsParser args_parser;
     Core::ArgsParser args_parser;
     args_parser.add_option(command_line, "Chrome process command line", "command-line", 0, "command_line");
     args_parser.add_option(command_line, "Chrome process command line", "command-line", 0, "command_line");
@@ -100,6 +105,7 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "use-gpu-painting", 0);
     args_parser.add_option(use_gpu_painting, "Enable GPU painting", "use-gpu-painting", 0);
     args_parser.add_option(wait_for_debugger, "Wait for debugger", "wait-for-debugger", 0);
     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(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.parse(arguments);
     args_parser.parse(arguments);
 
 
@@ -134,6 +140,10 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
 
 
     TRY(Web::Bindings::initialize_main_thread_vm());
     TRY(Web::Bindings::initialize_main_thread_vm());
 
 
+    if (log_all_js_exceptions) {
+        JS::g_log_all_js_exceptions = true;
+    }
+
     auto maybe_content_filter_error = load_content_filters();
     auto maybe_content_filter_error = load_content_filters();
     if (maybe_content_filter_error.is_error())
     if (maybe_content_filter_error.is_error())
         dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());
         dbgln("Failed to load content filters: {}", maybe_content_filter_error.error());

+ 18 - 0
Userland/Libraries/LibJS/Runtime/Completion.cpp

@@ -16,6 +16,8 @@
 
 
 namespace JS {
 namespace JS {
 
 
+bool g_log_all_js_exceptions = false;
+
 Completion::Completion(ThrowCompletionOr<Value> const& throw_completion_or_value)
 Completion::Completion(ThrowCompletionOr<Value> const& throw_completion_or_value)
 {
 {
     if (throw_completion_or_value.is_throw_completion()) {
     if (throw_completion_or_value.is_throw_completion()) {
@@ -122,9 +124,25 @@ ThrowCompletionOr<Value> await(VM& vm, Value value)
     return throw_completion(result);
     return throw_completion(result);
 }
 }
 
 
+static void log_exception(Value value)
+{
+    if (!value.is_object()) {
+        dbgln("\033[31;1mTHROW!\033[0m {}", value);
+        return;
+    }
+
+    auto& object = value.as_object();
+    auto& vm = object.vm();
+    dbgln("\033[31;1mTHROW!\033[0m {}", object.get(vm.names.message).value());
+    vm.dump_backtrace();
+}
+
 // 6.2.4.2 ThrowCompletion ( value ), https://tc39.es/ecma262/#sec-throwcompletion
 // 6.2.4.2 ThrowCompletion ( value ), https://tc39.es/ecma262/#sec-throwcompletion
 Completion throw_completion(Value value)
 Completion throw_completion(Value value)
 {
 {
+    if (g_log_all_js_exceptions)
+        log_exception(value);
+
     // 1. Return Completion Record { [[Type]]: throw, [[Value]]: value, [[Target]]: empty }.
     // 1. Return Completion Record { [[Type]]: throw, [[Value]]: value, [[Target]]: empty }.
     return { Completion::Type::Throw, value, {} };
     return { Completion::Type::Throw, value, {} };
 }
 }