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.
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().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();
|
|
}
|
|
|
|
}
|