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.
198 lines
6.6 KiB
C++
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();
|
|
}
|
|
|
|
}
|