/* * Copyright (c) 2025, Tim Flynn * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include namespace WebContent { GC_DEFINE_ALLOCATOR(DevToolsConsoleClient); GC::Ref DevToolsConsoleClient::create(JS::Realm& realm, JS::Console& console, PageClient& client) { auto& window = as(realm.global_object()); auto console_global_environment_extensions = realm.create(realm, window); return realm.heap().allocate(realm, console, client, console_global_environment_extensions); } DevToolsConsoleClient::DevToolsConsoleClient(JS::Realm& realm, JS::Console& console, PageClient& client, ConsoleGlobalEnvironmentExtensions& console_global_environment_extensions) : WebContentConsoleClient(realm, console, client, console_global_environment_extensions) { } DevToolsConsoleClient::~DevToolsConsoleClient() = default; // https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#grips static JsonValue serialize_js_value(JS::Realm& realm, JS::Value value) { auto& vm = realm.vm(); auto serialize_type = [](StringView type) { JsonObject serialized; serialized.set("type"sv, type); return serialized; }; if (value.is_undefined()) return serialize_type("undefined"sv); if (value.is_null()) return serialize_type("null"sv); if (value.is_boolean()) return value.as_bool(); if (value.is_string()) return value.as_string().utf8_string(); if (value.is_number()) { if (value.is_nan()) return serialize_type("NaN"sv); if (value.is_positive_infinity()) return serialize_type("Infinity"sv); if (value.is_negative_infinity()) return serialize_type("-Infinity"sv); if (value.is_negative_zero()) return serialize_type("-0"sv); return value.as_double(); } if (value.is_bigint()) { auto serialized = serialize_type("BigInt"sv); serialized.set("text"sv, MUST(value.as_bigint().big_integer().to_base(10))); return serialized; } if (value.is_symbol()) return value.as_symbol().descriptive_string().to_utf8(); // FIXME: Handle serialization of object grips. For now, we stringify the object. if (value.is_object()) { Web::HTML::TemporaryExecutionContext execution_context { realm }; AllocatingMemoryStream stream; JS::PrintContext context { vm, stream, true }; MUST(JS::print(value, context)); return MUST(String::from_stream(stream, stream.used_buffer_size())); } return {}; } void DevToolsConsoleClient::handle_result(JS::Value result) { m_client->did_execute_js_console_input(serialize_js_value(m_realm, result)); } void DevToolsConsoleClient::report_exception(String const& name, String const& message, JS::ErrorData const& error_data, bool in_promise) { Vector trace; trace.ensure_capacity(error_data.traceback().size()); for (auto const& frame : error_data.traceback()) { auto const& source_range = frame.source_range(); WebView::StackFrame stack_frame; if (!frame.function_name.is_empty()) stack_frame.function = frame.function_name.to_utf8(); if (!source_range.filename().is_empty() || source_range.start.offset != 0 || source_range.end.offset != 0) { stack_frame.file = String::from_utf8_with_replacement_character(source_range.filename()); stack_frame.line = source_range.start.line; stack_frame.column = source_range.start.column; } if (stack_frame.function.has_value() || stack_frame.file.has_value()) trace.unchecked_append(move(stack_frame)); } send_console_output({ .timestamp = UnixDateTime::now(), .output = WebView::ConsoleError { .name = name, .message = message, .trace = move(trace), .inside_promise = in_promise, }, }); } void DevToolsConsoleClient::send_console_output(WebView::ConsoleOutput console_output) { m_client->did_output_js_console_message(move(console_output)); } // 2.3. Printer(logLevel, args[, options]), https://console.spec.whatwg.org/#printer JS::ThrowCompletionOr DevToolsConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments) { if (log_level == JS::Console::LogLevel::Trace) { auto const& trace = arguments.get(); m_console->output_debug_message(log_level, trace.label); Vector stack_frames; stack_frames.ensure_capacity(trace.stack.size()); for (auto const& frame : trace.stack) { stack_frames.unchecked_append(WebView::StackFrame { .function = frame.function_name, .file = frame.source_file, .line = frame.line, .column = frame.column, }); } send_console_output({ .timestamp = UnixDateTime::now(), .output = WebView::ConsoleTrace { .label = trace.label, .stack = move(stack_frames), }, }); return JS::js_undefined(); } // FIXME: Implement these. if (first_is_one_of(log_level, JS::Console::LogLevel::Table, JS::Console::LogLevel::Group, JS::Console::LogLevel::GroupCollapsed)) return JS::js_undefined(); auto const& argument_values = arguments.get>(); auto output = TRY(generically_format_values(argument_values)); m_console->output_debug_message(log_level, output); Vector serialized_arguments; serialized_arguments.ensure_capacity(argument_values.size()); for (auto value : argument_values) serialized_arguments.unchecked_append(serialize_js_value(m_console->realm(), value)); send_console_output({ .timestamp = UnixDateTime::now(), .output = WebView::ConsoleLog { .level = log_level, .arguments = move(serialized_arguments), }, }); return JS::js_undefined(); } }