mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-19 02:10:26 +00:00
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
573 lines
22 KiB
C++
573 lines
22 KiB
C++
/*
|
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/Enumerate.h>
|
|
#include <AK/JsonArray.h>
|
|
#include <AK/JsonObject.h>
|
|
#include <LibDevTools/Actors/AccessibilityActor.h>
|
|
#include <LibDevTools/Actors/CSSPropertiesActor.h>
|
|
#include <LibDevTools/Actors/ConsoleActor.h>
|
|
#include <LibDevTools/Actors/FrameActor.h>
|
|
#include <LibDevTools/Actors/InspectorActor.h>
|
|
#include <LibDevTools/Actors/NetworkEventActor.h>
|
|
#include <LibDevTools/Actors/StyleSheetsActor.h>
|
|
#include <LibDevTools/Actors/TabActor.h>
|
|
#include <LibDevTools/Actors/ThreadActor.h>
|
|
#include <LibDevTools/DevToolsDelegate.h>
|
|
#include <LibDevTools/DevToolsServer.h>
|
|
#include <LibWebView/ConsoleOutput.h>
|
|
|
|
namespace DevTools {
|
|
|
|
NonnullRefPtr<FrameActor> FrameActor::create(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread, WeakPtr<AccessibilityActor> accessibility)
|
|
{
|
|
return adopt_ref(*new FrameActor(devtools, move(name), move(tab), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread), move(accessibility)));
|
|
}
|
|
|
|
FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<CSSPropertiesActor> css_properties, WeakPtr<ConsoleActor> console, WeakPtr<InspectorActor> inspector, WeakPtr<StyleSheetsActor> style_sheets, WeakPtr<ThreadActor> thread, WeakPtr<AccessibilityActor> accessibility)
|
|
: Actor(devtools, move(name))
|
|
, m_tab(move(tab))
|
|
, m_css_properties(move(css_properties))
|
|
, m_console(move(console))
|
|
, m_inspector(move(inspector))
|
|
, m_style_sheets(move(style_sheets))
|
|
, m_thread(move(thread))
|
|
, m_accessibility(move(accessibility))
|
|
{
|
|
if (auto tab = m_tab.strong_ref()) {
|
|
// NB: We must notify WebContent that DevTools is connected before setting up listeners,
|
|
// so that WebContent knows to start sending network response bodies over IPC.
|
|
// IPC messages are processed in order, so this is guaranteed to arrive first.
|
|
devtools.delegate().did_connect_devtools_client(tab->description());
|
|
|
|
devtools.delegate().listen_for_console_messages(
|
|
tab->description(),
|
|
[weak_self = make_weak_ptr<FrameActor>()](WebView::ConsoleOutput console_output) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_console_message(move(console_output));
|
|
});
|
|
|
|
// FIXME: We should adopt WebContent to inform us when style sheets are available or removed.
|
|
devtools.delegate().retrieve_style_sheets(tab->description(),
|
|
async_handler<FrameActor>({}, [](auto& self, auto style_sheets, auto& response) {
|
|
self.style_sheets_available(response, move(style_sheets));
|
|
}));
|
|
|
|
devtools.delegate().listen_for_network_events(
|
|
tab->description(),
|
|
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkRequestData data) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_network_request_started(move(data));
|
|
},
|
|
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkResponseData data) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_network_response_headers_received(move(data));
|
|
},
|
|
[weak_self = make_weak_ptr<FrameActor>()](u64 request_id, ByteBuffer data) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_network_response_body_received(request_id, move(data));
|
|
},
|
|
[weak_self = make_weak_ptr<FrameActor>()](DevToolsDelegate::NetworkRequestCompleteData data) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_network_request_finished(move(data));
|
|
});
|
|
|
|
devtools.delegate().listen_for_navigation_events(
|
|
tab->description(),
|
|
[weak_self = make_weak_ptr<FrameActor>()](String url) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_navigation_started(move(url));
|
|
},
|
|
[weak_self = make_weak_ptr<FrameActor>()](String url, String title) {
|
|
if (auto self = weak_self.strong_ref())
|
|
self->on_navigation_finished(move(url), move(title));
|
|
});
|
|
}
|
|
}
|
|
|
|
FrameActor::~FrameActor()
|
|
{
|
|
if (auto tab = m_tab.strong_ref()) {
|
|
devtools().delegate().stop_listening_for_console_messages(tab->description());
|
|
devtools().delegate().stop_listening_for_network_events(tab->description());
|
|
devtools().delegate().stop_listening_for_navigation_events(tab->description());
|
|
devtools().delegate().did_disconnect_devtools_client(tab->description());
|
|
}
|
|
}
|
|
|
|
void FrameActor::handle_message(Message const& message)
|
|
{
|
|
JsonObject response;
|
|
|
|
if (message.type == "detach"sv) {
|
|
if (auto tab = m_tab.strong_ref()) {
|
|
devtools().delegate().stop_listening_for_dom_properties(tab->description());
|
|
devtools().delegate().stop_listening_for_dom_mutations(tab->description());
|
|
devtools().delegate().stop_listening_for_console_messages(tab->description());
|
|
devtools().delegate().stop_listening_for_style_sheet_sources(tab->description());
|
|
tab->reset_selected_node();
|
|
}
|
|
|
|
send_response(message, move(response));
|
|
return;
|
|
}
|
|
|
|
if (message.type == "listFrames"sv) {
|
|
send_response(message, move(response));
|
|
return;
|
|
}
|
|
|
|
send_unrecognized_packet_type_error(message);
|
|
}
|
|
|
|
void FrameActor::send_frame_update_message()
|
|
{
|
|
JsonArray frames;
|
|
|
|
if (auto tab_actor = m_tab.strong_ref()) {
|
|
JsonObject frame;
|
|
frame.set("id"sv, tab_actor->description().id);
|
|
frame.set("title"sv, tab_actor->description().title);
|
|
frame.set("url"sv, tab_actor->description().url);
|
|
frames.must_append(move(frame));
|
|
}
|
|
|
|
JsonObject message;
|
|
message.set("type"sv, "frameUpdate"sv);
|
|
message.set("frames"sv, move(frames));
|
|
send_message(move(message));
|
|
}
|
|
|
|
JsonObject FrameActor::serialize_target() const
|
|
{
|
|
JsonObject traits;
|
|
traits.set("frames"sv, true);
|
|
traits.set("isBrowsingContext"sv, true);
|
|
traits.set("logInPage"sv, false);
|
|
traits.set("navigation"sv, true);
|
|
traits.set("supportsTopLevelTargetFlag"sv, true);
|
|
traits.set("watchpoints"sv, true);
|
|
|
|
JsonObject target;
|
|
target.set("actor"sv, name());
|
|
target.set("targetType"sv, "frame"sv);
|
|
|
|
if (auto tab_actor = m_tab.strong_ref()) {
|
|
target.set("title"sv, tab_actor->description().title);
|
|
target.set("url"sv, tab_actor->description().url);
|
|
target.set("browsingContextID"sv, tab_actor->description().id);
|
|
target.set("outerWindowID"sv, tab_actor->description().id);
|
|
target.set("isTopLevelTarget"sv, true);
|
|
}
|
|
|
|
target.set("traits"sv, move(traits));
|
|
|
|
if (auto accessibility = m_accessibility.strong_ref())
|
|
target.set("accessibilityActor"sv, accessibility->name());
|
|
if (auto console = m_console.strong_ref())
|
|
target.set("consoleActor"sv, console->name());
|
|
if (auto css_properties = m_css_properties.strong_ref())
|
|
target.set("cssPropertiesActor"sv, css_properties->name());
|
|
if (auto inspector = m_inspector.strong_ref())
|
|
target.set("inspectorActor"sv, inspector->name());
|
|
if (auto style_sheets = m_style_sheets.strong_ref())
|
|
target.set("styleSheetsActor"sv, style_sheets->name());
|
|
if (auto thread = m_thread.strong_ref())
|
|
target.set("threadActor"sv, thread->name());
|
|
|
|
return target;
|
|
}
|
|
|
|
void FrameActor::style_sheets_available(JsonObject& response, Vector<Web::CSS::StyleSheetIdentifier> style_sheets)
|
|
{
|
|
JsonArray sheets;
|
|
|
|
String tab_url;
|
|
if (auto tab_actor = m_tab.strong_ref())
|
|
tab_url = tab_actor->description().url;
|
|
|
|
auto style_sheets_actor = m_style_sheets.strong_ref();
|
|
if (!style_sheets_actor)
|
|
return;
|
|
|
|
for (auto const& [i, style_sheet] : enumerate(style_sheets)) {
|
|
auto resource_id = MUST(String::formatted("{}-stylesheet:{}", style_sheets_actor->name(), i));
|
|
|
|
JsonValue href;
|
|
JsonValue source_map_base_url;
|
|
JsonValue title;
|
|
|
|
if (style_sheet.url.has_value()) {
|
|
if (style_sheet.type == Web::CSS::StyleSheetIdentifier::Type::UserAgent) {
|
|
// LibWeb sets the URL to a style sheet name for UA style sheets. DevTools would reject these invalid URLs.
|
|
href = MUST(String::formatted("resource://{}", style_sheet.url.value()));
|
|
title = *style_sheet.url;
|
|
source_map_base_url = tab_url;
|
|
} else if (style_sheet.type == Web::CSS::StyleSheetIdentifier::Type::StyleElement) {
|
|
source_map_base_url = *style_sheet.url;
|
|
} else {
|
|
href = *style_sheet.url;
|
|
source_map_base_url = *style_sheet.url;
|
|
}
|
|
} else {
|
|
source_map_base_url = tab_url;
|
|
}
|
|
|
|
JsonObject sheet;
|
|
sheet.set("atRules"sv, JsonArray {});
|
|
sheet.set("constructed"sv, false);
|
|
sheet.set("disabled"sv, false);
|
|
sheet.set("fileName"sv, JsonValue {});
|
|
sheet.set("href"sv, move(href));
|
|
sheet.set("isNew"sv, false);
|
|
sheet.set("nodeHref"sv, tab_url);
|
|
sheet.set("resourceId"sv, move(resource_id));
|
|
sheet.set("ruleCount"sv, style_sheet.rule_count);
|
|
sheet.set("sourceMapBaseURL"sv, move(source_map_base_url));
|
|
sheet.set("sourceMapURL"sv, ""sv);
|
|
sheet.set("styleSheetIndex"sv, i);
|
|
sheet.set("system"sv, style_sheet.type == Web::CSS::StyleSheetIdentifier::Type::UserAgent);
|
|
sheet.set("title"sv, move(title));
|
|
|
|
sheets.must_append(move(sheet));
|
|
}
|
|
|
|
JsonArray stylesheets;
|
|
stylesheets.must_append("stylesheet"sv);
|
|
stylesheets.must_append(move(sheets));
|
|
|
|
JsonArray array;
|
|
array.must_append(move(stylesheets));
|
|
|
|
response.set("type"sv, "resources-available-array"sv);
|
|
response.set("array"sv, move(array));
|
|
|
|
style_sheets_actor->set_style_sheets(move(style_sheets));
|
|
}
|
|
|
|
void FrameActor::on_console_message(WebView::ConsoleOutput console_output)
|
|
{
|
|
JsonArray console_messages;
|
|
JsonArray error_messages;
|
|
|
|
JsonObject message;
|
|
|
|
console_output.output.visit(
|
|
[&](WebView::ConsoleLog& log) {
|
|
switch (log.level) {
|
|
case JS::Console::LogLevel::Debug:
|
|
message.set("level"sv, "debug"sv);
|
|
break;
|
|
case JS::Console::LogLevel::Error:
|
|
message.set("level"sv, "error"sv);
|
|
break;
|
|
case JS::Console::LogLevel::Info:
|
|
message.set("level"sv, "info"sv);
|
|
break;
|
|
case JS::Console::LogLevel::Log:
|
|
message.set("level"sv, "log"sv);
|
|
break;
|
|
case JS::Console::LogLevel::Warn:
|
|
message.set("level"sv, "warn"sv);
|
|
break;
|
|
default:
|
|
// FIXME: Implement remaining console levels.
|
|
return;
|
|
}
|
|
|
|
message.set("filename"sv, "<eval>"sv);
|
|
message.set("lineNumber"sv, 1);
|
|
message.set("columnNumber"sv, 1);
|
|
message.set("timeStamp"sv, console_output.timestamp.milliseconds_since_epoch());
|
|
message.set("arguments"sv, JsonArray { move(log.arguments) });
|
|
|
|
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;
|
|
|
|
for (auto const& frame : error.trace) {
|
|
if (frame.function.has_value())
|
|
stack.append(*frame.function);
|
|
stack.append('@');
|
|
stack.append(frame.file.map([](auto const& file) -> StringView { return file; }).value_or("unknown"sv));
|
|
stack.appendff(":{}:{}\n", frame.line.value_or(0), frame.column.value_or(0));
|
|
}
|
|
|
|
JsonObject preview;
|
|
preview.set("kind"sv, "Error"sv);
|
|
preview.set("message"sv, error.message);
|
|
preview.set("name"sv, error.name);
|
|
if (!stack.is_empty())
|
|
preview.set("stack"sv, MUST(stack.to_string()));
|
|
|
|
JsonObject exception;
|
|
exception.set("class"sv, error.name);
|
|
exception.set("isError"sv, true);
|
|
exception.set("preview"sv, move(preview));
|
|
|
|
JsonObject page_error;
|
|
page_error.set("error"sv, true);
|
|
page_error.set("exception"sv, move(exception));
|
|
page_error.set("hasException"sv, !error.trace.is_empty());
|
|
page_error.set("isPromiseRejection"sv, error.inside_promise);
|
|
page_error.set("timeStamp"sv, console_output.timestamp.milliseconds_since_epoch());
|
|
|
|
message.set("pageError"sv, move(page_error));
|
|
error_messages.must_append(move(message));
|
|
});
|
|
|
|
JsonArray array;
|
|
|
|
if (!console_messages.is_empty()) {
|
|
JsonArray console_message;
|
|
console_message.must_append("console-message"sv);
|
|
console_message.must_append(move(console_messages));
|
|
|
|
array.must_append(move(console_message));
|
|
}
|
|
if (!error_messages.is_empty()) {
|
|
JsonArray error_message;
|
|
error_message.must_append("error-message"sv);
|
|
error_message.must_append(move(error_messages));
|
|
|
|
array.must_append(move(error_message));
|
|
}
|
|
|
|
if (array.is_empty())
|
|
return;
|
|
|
|
JsonObject resources_message;
|
|
resources_message.set("type"sv, "resources-available-array"sv);
|
|
resources_message.set("array"sv, move(array));
|
|
send_message(move(resources_message));
|
|
}
|
|
|
|
void FrameActor::on_network_request_started(DevToolsDelegate::NetworkRequestData data)
|
|
{
|
|
auto& actor = devtools().register_actor<NetworkEventActor>(data.request_id);
|
|
actor.set_request_info(move(data.url), move(data.method), data.start_time, move(data.request_headers), move(data.request_body), move(data.initiator_type));
|
|
m_network_events.set(data.request_id, actor);
|
|
|
|
JsonArray events;
|
|
events.must_append(actor.serialize_initial_event());
|
|
|
|
JsonArray network_event;
|
|
network_event.must_append("network-event"sv);
|
|
network_event.must_append(move(events));
|
|
|
|
JsonArray array;
|
|
array.must_append(move(network_event));
|
|
|
|
JsonObject message;
|
|
message.set("type"sv, "resources-available-array"sv);
|
|
message.set("array"sv, move(array));
|
|
send_message(move(message));
|
|
}
|
|
|
|
void FrameActor::on_network_response_headers_received(DevToolsDelegate::NetworkResponseData data)
|
|
{
|
|
auto it = m_network_events.find(data.request_id);
|
|
if (it == m_network_events.end())
|
|
return;
|
|
|
|
auto& actor = *it->value;
|
|
actor.set_response_start(data.status_code, data.reason_phrase);
|
|
|
|
// Extract Content-Type before moving headers
|
|
String mime_type;
|
|
i64 headers_size = 0;
|
|
for (auto const& header : data.response_headers) {
|
|
headers_size += static_cast<i64>(header.name.bytes().size() + header.value.bytes().size() + 4);
|
|
if (header.name.equals_ignoring_ascii_case("content-type"sv))
|
|
mime_type = MUST(String::from_byte_string(header.value));
|
|
}
|
|
|
|
actor.set_response_headers(move(data.response_headers));
|
|
|
|
// Build resource updates object
|
|
JsonObject resource_updates;
|
|
resource_updates.set("status"sv, String::number(data.status_code));
|
|
resource_updates.set("statusText"sv, data.reason_phrase.value_or(String {}));
|
|
resource_updates.set("headersSize"sv, headers_size);
|
|
resource_updates.set("mimeType"sv, mime_type);
|
|
// FIXME: Get actual HTTP version from response
|
|
resource_updates.set("httpVersion"sv, "HTTP/1.1"sv);
|
|
// FIXME: Get actual remote address and port from connection
|
|
resource_updates.set("remoteAddress"sv, String {});
|
|
resource_updates.set("remotePort"sv, 0);
|
|
// FIXME: Calculate actual waiting time (time between request sent and first byte received)
|
|
resource_updates.set("waitingTime"sv, 0);
|
|
// Mark headers as available
|
|
resource_updates.set("responseHeadersAvailable"sv, true);
|
|
|
|
JsonObject update_entry;
|
|
update_entry.set("resourceId"sv, static_cast<i64>(data.request_id));
|
|
update_entry.set("resourceType"sv, "network-event"sv);
|
|
update_entry.set("resourceUpdates"sv, move(resource_updates));
|
|
update_entry.set("browsingContextID"sv, 1);
|
|
update_entry.set("innerWindowId"sv, 1);
|
|
|
|
JsonArray updates;
|
|
updates.must_append(move(update_entry));
|
|
|
|
JsonArray network_event_updates;
|
|
network_event_updates.must_append("network-event"sv);
|
|
network_event_updates.must_append(move(updates));
|
|
|
|
JsonArray array;
|
|
array.must_append(move(network_event_updates));
|
|
|
|
JsonObject message;
|
|
message.set("type"sv, "resources-updated-array"sv);
|
|
message.set("array"sv, move(array));
|
|
send_message(move(message));
|
|
}
|
|
|
|
void FrameActor::on_network_response_body_received(u64 request_id, ByteBuffer data)
|
|
{
|
|
auto it = m_network_events.find(request_id);
|
|
if (it == m_network_events.end())
|
|
return;
|
|
|
|
it->value->append_response_body(move(data));
|
|
}
|
|
|
|
void FrameActor::on_network_request_finished(DevToolsDelegate::NetworkRequestCompleteData data)
|
|
{
|
|
auto it = m_network_events.find(data.request_id);
|
|
if (it == m_network_events.end())
|
|
return;
|
|
|
|
auto& actor = *it->value;
|
|
actor.set_request_complete(data.body_size, data.timing_info, data.network_error);
|
|
|
|
// Calculate total time in milliseconds
|
|
auto total_time = (data.timing_info.response_end_microseconds - data.timing_info.request_start_microseconds) / 1000;
|
|
|
|
// Build resource updates object with content and timing info
|
|
JsonObject resource_updates;
|
|
resource_updates.set("contentSize"sv, static_cast<i64>(data.body_size));
|
|
resource_updates.set("transferredSize"sv, static_cast<i64>(data.body_size));
|
|
resource_updates.set("totalTime"sv, total_time);
|
|
// Mark as complete
|
|
resource_updates.set("responseContentAvailable"sv, true);
|
|
resource_updates.set("eventTimingsAvailable"sv, true);
|
|
|
|
JsonObject update_entry;
|
|
update_entry.set("resourceId"sv, static_cast<i64>(data.request_id));
|
|
update_entry.set("resourceType"sv, "network-event"sv);
|
|
update_entry.set("resourceUpdates"sv, move(resource_updates));
|
|
update_entry.set("browsingContextID"sv, 1);
|
|
update_entry.set("innerWindowId"sv, 1);
|
|
|
|
JsonArray updates;
|
|
updates.must_append(move(update_entry));
|
|
|
|
JsonArray network_event_updates;
|
|
network_event_updates.must_append("network-event"sv);
|
|
network_event_updates.must_append(move(updates));
|
|
|
|
JsonArray array;
|
|
array.must_append(move(network_event_updates));
|
|
|
|
JsonObject message;
|
|
message.set("type"sv, "resources-updated-array"sv);
|
|
message.set("array"sv, move(array));
|
|
send_message(move(message));
|
|
}
|
|
|
|
void FrameActor::on_navigation_started(String url)
|
|
{
|
|
// Clear our internal tracking of network events
|
|
m_network_events.clear();
|
|
|
|
// Send will-navigate document event to trigger network panel clear
|
|
JsonObject document_event;
|
|
document_event.set("resourceType"sv, "document-event"sv);
|
|
document_event.set("name"sv, "will-navigate"sv);
|
|
document_event.set("time"sv, UnixDateTime::now().milliseconds_since_epoch());
|
|
document_event.set("newURI"sv, url);
|
|
document_event.set("isFrameSwitching"sv, false);
|
|
|
|
JsonArray events;
|
|
events.must_append(move(document_event));
|
|
|
|
JsonArray document_event_array;
|
|
document_event_array.must_append("document-event"sv);
|
|
document_event_array.must_append(move(events));
|
|
|
|
JsonArray array;
|
|
array.must_append(move(document_event_array));
|
|
|
|
JsonObject resources_message;
|
|
resources_message.set("type"sv, "resources-available-array"sv);
|
|
resources_message.set("array"sv, move(array));
|
|
send_message(move(resources_message));
|
|
|
|
// Also send tabNavigated for backwards compatibility
|
|
JsonObject message;
|
|
message.set("type"sv, "tabNavigated"sv);
|
|
message.set("url"sv, move(url));
|
|
message.set("state"sv, "start"sv);
|
|
message.set("isFrameSwitching"sv, false);
|
|
send_message(move(message));
|
|
}
|
|
|
|
void FrameActor::on_navigation_finished(String url, String title)
|
|
{
|
|
// Update the tab description with the new URL and title
|
|
if (auto tab = m_tab.strong_ref()) {
|
|
tab->set_url(url);
|
|
tab->set_title(title);
|
|
}
|
|
|
|
JsonObject message;
|
|
message.set("type"sv, "tabNavigated"sv);
|
|
message.set("url"sv, move(url));
|
|
message.set("title"sv, move(title));
|
|
message.set("state"sv, "stop"sv);
|
|
message.set("isFrameSwitching"sv, false);
|
|
send_message(move(message));
|
|
|
|
// Also send a frameUpdate message to update the frame info
|
|
send_frame_update_message();
|
|
}
|
|
|
|
}
|