ladybird/Services/WebContent/DevToolsConsoleClient.cpp
Andreas Kling 21cbfb3cb1 LibJS: Drop source ranges from bytecode source maps
Store source map locations as bytecode offset, line, and column.
Runtime consumers only emit the start line and column, so source end
positions and source text offsets do not need to be carried through
Executable source maps, bytecode cache serialization, or the Rust FFI.

Keep SourceCode's internal position cache able to track source text
offsets so callers can still translate source offsets to line and
column pairs when needed. Hash dump-bytecode IDs from the name, first
source position, and bytecode size instead of source slices that need
end offsets.

Bump the bytecode cache format version for the slimmer serialized
source map entry shape.
2026-05-14 09:41:03 +02:00

198 lines
6.6 KiB
C++

/*
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/GenericShorthands.h>
#include <AK/JsonObject.h>
#include <AK/JsonValue.h>
#include <AK/MemoryStream.h>
#include <LibJS/Print.h>
#include <LibJS/Runtime/BigInt.h>
#include <LibJS/Runtime/ErrorData.h>
#include <LibJS/Runtime/Realm.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/Window.h>
#include <WebContent/ConsoleGlobalEnvironmentExtensions.h>
#include <WebContent/DevToolsConsoleClient.h>
#include <WebContent/PageClient.h>
namespace WebContent {
GC_DEFINE_ALLOCATOR(DevToolsConsoleClient);
GC::Ref<DevToolsConsoleClient> DevToolsConsoleClient::create(JS::Realm& realm, JS::Console& console, PageClient& client)
{
auto& window = as<Web::HTML::Window>(realm.global_object());
auto console_global_environment_extensions = realm.create<ConsoleGlobalEnvironmentExtensions>(realm, window);
return realm.heap().allocate<DevToolsConsoleClient>(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<WebView::StackFrame> 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.line != 0 || source_range.start.column != 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<JS::Value> DevToolsConsoleClient::printer(JS::Console::LogLevel log_level, PrinterArguments arguments)
{
if (log_level == JS::Console::LogLevel::Trace) {
auto const& trace = arguments.get<JS::Console::Trace>();
m_console->output_debug_message(log_level, trace.label);
Vector<WebView::StackFrame> 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<GC::RootVector<JS::Value>>();
auto output = TRY(generically_format_values(argument_values));
m_console->output_debug_message(log_level, output);
Vector<JsonValue> 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();
}
}