LibJS: Add source locations to console.trace()
LibJS+DevTools: Implement console.trace() with source locations - Add Console::TraceFrame struct with source location data - Implement Console::trace() to gather stack information - Add WebView::StackFrame and ConsoleTrace for IPC - Implement DevToolsConsoleClient::printer() for traces - Update FrameActor to format traces for DevTools - Update WorkerDebugConsoleClient trace handling - Update ReplConsoleClient to format trace output
This commit is contained in:
parent
0b346d1952
commit
2df5a7bb31
10 changed files with 145 additions and 24 deletions
|
|
@ -286,6 +286,39 @@ void FrameActor::on_console_message(WebView::ConsoleOutput console_output)
|
|||
|
||||
console_messages.must_append(move(message));
|
||||
},
|
||||
[&](WebView::ConsoleTrace const& trace) {
|
||||
message.set("level"sv, "trace"sv);
|
||||
message.set("timeStamp"sv, console_output.timestamp.milliseconds_since_epoch());
|
||||
|
||||
JsonArray arguments;
|
||||
if (!trace.label.is_empty())
|
||||
arguments.must_append(trace.label);
|
||||
message.set("arguments"sv, move(arguments));
|
||||
|
||||
JsonArray stack_array;
|
||||
for (auto const& frame : trace.stack) {
|
||||
JsonObject frame_object;
|
||||
frame_object.set("functionName"sv, frame.function.value_or("<anonymous>"_string));
|
||||
frame_object.set("filename"sv, frame.file.value_or("unknown"_string));
|
||||
frame_object.set("lineNumber"sv, static_cast<i64>(frame.line.value_or(0)));
|
||||
frame_object.set("columnNumber"sv, static_cast<i64>(frame.column.value_or(0)));
|
||||
stack_array.must_append(move(frame_object));
|
||||
}
|
||||
message.set("stacktrace"sv, move(stack_array));
|
||||
|
||||
if (trace.stack.is_empty()) {
|
||||
message.set("filename"sv, "unknown"sv);
|
||||
message.set("lineNumber"sv, 0);
|
||||
message.set("columnNumber"sv, 0);
|
||||
} else {
|
||||
auto const& first_frame = trace.stack.first();
|
||||
message.set("filename"sv, first_frame.file.value_or("unknown"_string));
|
||||
message.set("lineNumber"sv, static_cast<i64>(first_frame.line.value_or(0)));
|
||||
message.set("columnNumber"sv, static_cast<i64>(first_frame.column.value_or(0)));
|
||||
}
|
||||
|
||||
console_messages.must_append(move(message));
|
||||
},
|
||||
[&](WebView::ConsoleError const& error) {
|
||||
StringBuilder stack;
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,11 @@
|
|||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/Array.h>
|
||||
#include <LibJS/Runtime/Completion.h>
|
||||
#include <LibJS/Runtime/ExecutionContext.h>
|
||||
#include <LibJS/Runtime/StringConstructor.h>
|
||||
#include <LibJS/Runtime/VM.h>
|
||||
#include <LibJS/Runtime/ValueInlines.h>
|
||||
#include <LibJS/SourceRange.h>
|
||||
|
||||
namespace JS {
|
||||
|
||||
|
|
@ -347,13 +350,28 @@ ThrowCompletionOr<Value> Console::trace()
|
|||
|
||||
// 1. Let trace be some implementation-defined, potentially-interactive representation of the callstack from where this function was called.
|
||||
Console::Trace trace;
|
||||
auto& execution_context_stack = vm.execution_context_stack();
|
||||
// NOTE: -2 to skip the console.trace() execution context
|
||||
for (ssize_t i = execution_context_stack.size() - 2; i >= 0; --i) {
|
||||
auto function_name = execution_context_stack[i]->function ? execution_context_stack[i]->function->name_for_call_stack() : ""_utf16;
|
||||
trace.stack.append(function_name.is_empty()
|
||||
? "<anonymous>"_string
|
||||
: function_name.to_utf8());
|
||||
auto stack_trace = vm.stack_trace();
|
||||
|
||||
// NOTE: Skip the first frame (console.trace() itself)
|
||||
for (size_t i = 1; i < stack_trace.size(); ++i) {
|
||||
auto const& element = stack_trace[i];
|
||||
auto* context = element.execution_context;
|
||||
|
||||
Console::TraceFrame frame;
|
||||
|
||||
auto function_name = (context && context->function) ? context->function->name_for_call_stack() : ""_utf16;
|
||||
frame.function_name = function_name.is_empty() ? "<anonymous>"_string : function_name.to_utf8();
|
||||
|
||||
if (element.source_range) {
|
||||
auto const& source_range = element.source_range->realize_source_range();
|
||||
if (!source_range.filename().is_empty()) {
|
||||
frame.source_file = MUST(String::from_byte_string(source_range.filename()));
|
||||
frame.line = source_range.start.line;
|
||||
frame.column = source_range.start.column;
|
||||
}
|
||||
}
|
||||
|
||||
trace.stack.append(move(frame));
|
||||
}
|
||||
|
||||
// 2. Optionally, let formattedData be the result of Formatter(data), and incorporate formattedData as a label for trace.
|
||||
|
|
|
|||
|
|
@ -55,9 +55,16 @@ public:
|
|||
String label;
|
||||
};
|
||||
|
||||
struct TraceFrame {
|
||||
String function_name;
|
||||
Optional<String> source_file;
|
||||
Optional<size_t> line;
|
||||
Optional<size_t> column;
|
||||
};
|
||||
|
||||
struct Trace {
|
||||
String label;
|
||||
Vector<String> stack;
|
||||
Vector<TraceFrame> stack;
|
||||
};
|
||||
|
||||
void set_client(ConsoleClient& client) { m_client = &client; }
|
||||
|
|
|
|||
|
|
@ -24,15 +24,7 @@ SourceRange const& TracebackFrame::source_range() const
|
|||
{
|
||||
if (!cached_source_range)
|
||||
return dummy_source_range;
|
||||
if (auto* unrealized = cached_source_range->source_range.get_pointer<UnrealizedSourceRange>()) {
|
||||
auto source_range = [&] {
|
||||
if (!unrealized->source_code)
|
||||
return dummy_source_range;
|
||||
return unrealized->realize();
|
||||
}();
|
||||
cached_source_range->source_range = move(source_range);
|
||||
}
|
||||
return cached_source_range->source_range.get<SourceRange>();
|
||||
return cached_source_range->realize_source_range();
|
||||
}
|
||||
|
||||
GC::Ref<Error> Error::create(Realm& realm)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,20 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
SourceRange const& realize_source_range()
|
||||
{
|
||||
static SourceRange dummy_source_range { SourceCode::create({}, {}), {}, {} };
|
||||
|
||||
if (auto* unrealized = source_range.get_pointer<UnrealizedSourceRange>()) {
|
||||
if (unrealized->source_code) {
|
||||
source_range = unrealized->realize();
|
||||
} else {
|
||||
source_range = dummy_source_range;
|
||||
}
|
||||
}
|
||||
return source_range.get<SourceRange>();
|
||||
}
|
||||
|
||||
size_t program_counter { 0 };
|
||||
Variant<UnrealizedSourceRange, SourceRange> source_range;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ JS::ThrowCompletionOr<JS::Value> WorkerDebugConsoleClient::printer(JS::Console::
|
|||
if (!trace.label.is_empty())
|
||||
builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label);
|
||||
|
||||
for (auto& function_name : trace.stack)
|
||||
builder.appendff("{}-> {}\n", indent, function_name);
|
||||
for (auto& frame : trace.stack)
|
||||
builder.appendff("{}-> {}\n", indent, frame.function_name);
|
||||
|
||||
dbgln("{}", builder.string_view());
|
||||
return JS::js_undefined();
|
||||
|
|
|
|||
|
|
@ -70,6 +70,24 @@ ErrorOr<WebView::ConsoleError> IPC::decode(Decoder& decoder)
|
|||
return WebView::ConsoleError { move(name), move(message), move(trace), inside_promise };
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, WebView::ConsoleTrace const& trace)
|
||||
{
|
||||
TRY(encoder.encode(trace.label));
|
||||
TRY(encoder.encode(trace.stack));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<WebView::ConsoleTrace> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto label = TRY(decoder.decode<String>());
|
||||
auto stack = TRY(decoder.decode<Vector<WebView::StackFrame>>());
|
||||
|
||||
return WebView::ConsoleTrace { move(label), move(stack) };
|
||||
}
|
||||
|
||||
template<>
|
||||
ErrorOr<void> IPC::encode(Encoder& encoder, WebView::ConsoleOutput const& output)
|
||||
{
|
||||
|
|
@ -83,7 +101,7 @@ template<>
|
|||
ErrorOr<WebView::ConsoleOutput> IPC::decode(Decoder& decoder)
|
||||
{
|
||||
auto timestamp = TRY(decoder.decode<UnixDateTime>());
|
||||
auto output = TRY(decoder.decode<Variant<WebView::ConsoleLog, WebView::ConsoleError>>());
|
||||
auto output = TRY(decoder.decode<Variant<WebView::ConsoleLog, WebView::ConsoleError, WebView::ConsoleTrace>>());
|
||||
|
||||
return WebView::ConsoleOutput { timestamp, move(output) };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,14 @@ struct WEBVIEW_API ConsoleError {
|
|||
bool inside_promise { false };
|
||||
};
|
||||
|
||||
struct WEBVIEW_API ConsoleTrace {
|
||||
String label;
|
||||
Vector<StackFrame> stack;
|
||||
};
|
||||
|
||||
struct WEBVIEW_API ConsoleOutput {
|
||||
UnixDateTime timestamp;
|
||||
Variant<ConsoleLog, ConsoleError> output;
|
||||
Variant<ConsoleLog, ConsoleError, ConsoleTrace> output;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -63,6 +68,12 @@ ErrorOr<void> encode(Encoder&, WebView::ConsoleError const&);
|
|||
template<>
|
||||
ErrorOr<WebView::ConsoleError> decode(Decoder&);
|
||||
|
||||
template<>
|
||||
ErrorOr<void> encode(Encoder&, WebView::ConsoleTrace const&);
|
||||
|
||||
template<>
|
||||
ErrorOr<WebView::ConsoleTrace> decode(Decoder&);
|
||||
|
||||
template<>
|
||||
WEBVIEW_API ErrorOr<void> encode(Encoder&, WebView::ConsoleOutput const&);
|
||||
|
||||
|
|
|
|||
|
|
@ -145,8 +145,36 @@ void DevToolsConsoleClient::send_console_output(WebView::ConsoleOutput console_o
|
|||
// 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::Trace, JS::Console::LogLevel::Group, JS::Console::LogLevel::GroupCollapsed))
|
||||
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>>();
|
||||
|
|
|
|||
|
|
@ -446,8 +446,8 @@ public:
|
|||
if (!trace.label.is_empty())
|
||||
builder.appendff("{}\033[36;1m{}\033[0m\n", indent, trace.label);
|
||||
|
||||
for (auto& function_name : trace.stack)
|
||||
builder.appendff("{}-> {}\n", indent, function_name);
|
||||
for (auto& frame : trace.stack)
|
||||
builder.appendff("{}-> {}\n", indent, frame.function_name);
|
||||
|
||||
outln("{}", builder.string_view());
|
||||
return JS::js_undefined();
|
||||
|
|
|
|||
Loading…
Reference in a new issue