Resolve applied-rule stylesheet identities through StyleSheetsActor and include Firefox parentStyleSheet, line, and column fields on rule forms. Firefox can then show matched rules as stylesheet rules and open the corresponding CSS source location from the Rules panel.
638 lines
24 KiB
C++
638 lines
24 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 <LibCore/EventLoop.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/Actors/WatcherActor.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<WatcherActor> watcher, 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(watcher), move(css_properties), move(console), move(inspector), move(style_sheets), move(thread), move(accessibility)));
|
|
}
|
|
|
|
FrameActor::FrameActor(DevToolsServer& devtools, String name, WeakPtr<TabActor> tab, WeakPtr<WatcherActor> watcher, 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_watcher(move(watcher))
|
|
, 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()) {
|
|
devtools.delegate().listen_for_console_messages(
|
|
tab->description(),
|
|
weak_callback(*this, [](auto& self, WebView::ConsoleOutput console_output) {
|
|
self.on_console_message(move(console_output));
|
|
}));
|
|
|
|
// FIXME: We should adopt WebContent to inform us when style sheets are available or removed.
|
|
Core::deferred_invoke([weak_self = make_weak_ptr<FrameActor>(), tab] {
|
|
if (auto self = weak_self.strong_ref()) {
|
|
self->devtools().delegate().retrieve_style_sheets(tab->description(),
|
|
self->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_callback(*this, [](auto& self, DevToolsDelegate::NetworkRequestData data) {
|
|
self.on_network_request_started(move(data));
|
|
}),
|
|
weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkResponseData data) {
|
|
self.on_network_response_headers_received(move(data));
|
|
}),
|
|
weak_callback(*this, [](auto& self, u64 request_id, ByteBuffer data) {
|
|
self.on_network_response_body_received(request_id, move(data));
|
|
}),
|
|
weak_callback(*this, [](auto& self, DevToolsDelegate::NetworkRequestCompleteData data) {
|
|
self.on_network_request_finished(move(data));
|
|
}));
|
|
|
|
devtools.delegate().listen_for_navigation_events(
|
|
tab->description(),
|
|
weak_callback(*this, [](auto& self, String url) {
|
|
self.on_navigation_started(move(url));
|
|
}),
|
|
weak_callback(*this, [](auto& self, String url, String title) {
|
|
self.on_navigation_finished(move(url), move(title));
|
|
}));
|
|
|
|
m_is_listening = true;
|
|
}
|
|
}
|
|
|
|
FrameActor::~FrameActor()
|
|
{
|
|
stop_listening();
|
|
}
|
|
|
|
void FrameActor::stop_listening()
|
|
{
|
|
if (!m_is_listening)
|
|
return;
|
|
|
|
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());
|
|
}
|
|
|
|
m_is_listening = false;
|
|
}
|
|
|
|
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 == "reload"sv) {
|
|
bool bypass_cache = false;
|
|
if (auto options = message.data.get_object("options"sv); options.has_value())
|
|
bypass_cache = options->get_bool("force"sv).value_or(false);
|
|
|
|
if (auto tab = m_tab.strong_ref())
|
|
devtools().delegate().reload_tab(tab->description(), bypass_cache);
|
|
|
|
send_response(message, move(response));
|
|
return;
|
|
}
|
|
|
|
if (message.type == "goBack"sv) {
|
|
if (auto tab = m_tab.strong_ref())
|
|
devtools().delegate().traverse_the_history_by_delta(tab->description(), -1);
|
|
|
|
send_response(message, move(response));
|
|
return;
|
|
}
|
|
|
|
if (message.type == "goForward"sv) {
|
|
if (auto tab = m_tab.strong_ref())
|
|
devtools().delegate().traverse_the_history_by_delta(tab->description(), 1);
|
|
|
|
send_response(message, move(response));
|
|
return;
|
|
}
|
|
|
|
if (message.type == "listFrames"sv) {
|
|
send_response(message, move(response));
|
|
send_pending_navigation_document_events_after_target_switch();
|
|
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));
|
|
}
|
|
|
|
void FrameActor::set_pending_navigation_document_events_after_target_switch(String const& url, String const& title)
|
|
{
|
|
m_pending_navigation_document_events_after_target_switch = PendingNavigationDocumentEvents {
|
|
.url = url,
|
|
.title = title,
|
|
};
|
|
}
|
|
|
|
void FrameActor::send_pending_navigation_document_events_after_target_switch()
|
|
{
|
|
if (!m_pending_navigation_document_events_after_target_switch.has_value())
|
|
return;
|
|
|
|
auto pending_events = m_pending_navigation_document_events_after_target_switch.release_value();
|
|
send_document_event("dom-loading"sv, pending_events.url);
|
|
send_document_event("dom-interactive"sv, pending_events.url, pending_events.title);
|
|
send_document_event("dom-complete"sv, pending_events.url);
|
|
}
|
|
|
|
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("followWindowGlobalLifeCycle"sv, true);
|
|
target.set("innerWindowId"sv, tab_actor->inner_window_id());
|
|
target.set("isPopup"sv, false);
|
|
target.set("isPrivate"sv, false);
|
|
target.set("outerWindowID"sv, tab_actor->description().id);
|
|
target.set("processID"sv, 1);
|
|
target.set("topInnerWindowId"sv, tab_actor->inner_window_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 = StyleSheetsActor::resource_id_for_index(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::send_document_event(StringView name, String const& url, Optional<StringView> title)
|
|
{
|
|
JsonObject document_event;
|
|
document_event.set("resourceType"sv, "document-event"sv);
|
|
document_event.set("name"sv, name);
|
|
document_event.set("time"sv, UnixDateTime::now().milliseconds_since_epoch());
|
|
document_event.set("isFrameSwitching"sv, false);
|
|
|
|
if (name == "will-navigate"sv)
|
|
document_event.set("newURI"sv, url);
|
|
if (name == "dom-loading"sv || name == "dom-interactive"sv)
|
|
document_event.set("url"sv, url);
|
|
if (title.has_value())
|
|
document_event.set("title"sv, title.value());
|
|
|
|
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));
|
|
}
|
|
|
|
void FrameActor::on_navigation_started(String url)
|
|
{
|
|
if (auto inspector = m_inspector.strong_ref())
|
|
inspector->on_navigation_started();
|
|
|
|
// Clear our internal tracking of network events
|
|
m_network_events.clear();
|
|
|
|
send_document_event("will-navigate"sv, url);
|
|
}
|
|
|
|
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->navigate_to(url, title);
|
|
|
|
if (auto inspector = m_inspector.strong_ref())
|
|
inspector->on_navigation_finished();
|
|
|
|
auto finished_url = url;
|
|
auto finished_title = title;
|
|
|
|
Core::deferred_invoke([watcher = m_watcher, target = make_weak_ptr<FrameActor>(), url = move(finished_url), title = move(finished_title)] {
|
|
auto strong_watcher = watcher.strong_ref();
|
|
auto strong_target = target.strong_ref();
|
|
if (strong_watcher && strong_target)
|
|
strong_watcher->switch_frame_target(*strong_target, url, title);
|
|
});
|
|
}
|
|
|
|
}
|