ladybird/Services/WebContent/DevToolsConsoleClient.cpp
Andreas Kling 7025dd1fa7 Libraries: Parse JS strings from UTF-16
Thread UTF-16 string input through JSON, script parsing, Date parsing,
Intl option parsing, Temporal parsing, and the helper library boundaries
that feed those parsers. Preserve ASCII fast paths where the source data
is known to be ASCII.
2026-06-22 19:51:25 +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().utf16_string_view().to_utf8_but_should_be_ported_to_utf16();
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.to_utf8(),
.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();
}
}