LibDevTools: Pass request initiator type to network panel

Propagate the request initiator type (e.g., "xmlhttprequest", "fetch",
"script", "stylesheet") from LibWeb through the IPC layer to DevTools.

This enables Firefox DevTools to correctly identify XHR/fetch requests
and display appropriate cause types in the Network panel's "Initiator"
column.
This commit is contained in:
Andreas Kling 2026-01-14 23:24:51 +01:00 committed by Andreas Kling
parent 31ffd2e8e5
commit 681d00c218
15 changed files with 50 additions and 20 deletions

View file

@ -343,7 +343,7 @@ void FrameActor::on_console_message(WebView::ConsoleOutput console_output)
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));
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;

View file

@ -25,13 +25,14 @@ NetworkEventActor::NetworkEventActor(DevToolsServer& devtools, String name, u64
NetworkEventActor::~NetworkEventActor() = default;
void NetworkEventActor::set_request_info(String url, String method, UnixDateTime start_time, Vector<HTTP::Header> request_headers, ByteBuffer request_body)
void NetworkEventActor::set_request_info(String url, String method, UnixDateTime start_time, Vector<HTTP::Header> request_headers, ByteBuffer request_body, Optional<String> initiator_type)
{
m_url = move(url);
m_method = move(method);
m_start_time = start_time;
m_request_headers = move(request_headers);
m_request_body = move(request_body);
m_initiator_type = move(initiator_type);
}
void NetworkEventActor::set_response_start(u32 status_code, Optional<String> reason_phrase)
@ -68,9 +69,26 @@ void NetworkEventActor::set_request_complete(u64 body_size, Requests::RequestTim
JsonObject NetworkEventActor::serialize_initial_event() const
{
// FIXME: Detect actual cause type (xhr, fetch, script, stylesheet, image, etc.)
// Determine if this is an XHR/fetch request (Firefox groups both under "XHR" filter)
bool is_xhr = m_initiator_type.has_value()
&& (m_initiator_type.value() == "xmlhttprequest"sv || m_initiator_type.value() == "fetch"sv);
// Map initiator type to Firefox DevTools cause type
StringView cause_type = "document"sv;
if (m_initiator_type.has_value()) {
auto const& type = m_initiator_type.value();
if (type == "xmlhttprequest"sv)
cause_type = "xhr"sv;
else if (type == "css"sv)
cause_type = "stylesheet"sv;
else if (type == "img"sv || type == "image"sv)
cause_type = "image"sv;
else
cause_type = type;
}
JsonObject cause;
cause.set("type"sv, "document"sv);
cause.set("type"sv, cause_type);
JsonObject event;
event.set("resourceType"sv, "network-event"sv);
@ -80,8 +98,7 @@ JsonObject NetworkEventActor::serialize_initial_event() const
event.set("timeStamp"sv, m_start_time.milliseconds_since_epoch());
event.set("url"sv, m_url);
event.set("method"sv, m_method);
// FIXME: Detect if request is XHR/fetch
event.set("isXHR"sv, false);
event.set("isXHR"sv, is_xhr);
event.set("cause"sv, move(cause));
event.set("private"sv, false);
// FIXME: Detect if response is from cache

View file

@ -25,7 +25,7 @@ public:
u64 request_id() const { return m_request_id; }
void set_request_info(String url, String method, UnixDateTime start_time, Vector<HTTP::Header> request_headers, ByteBuffer request_body);
void set_request_info(String url, String method, UnixDateTime start_time, Vector<HTTP::Header> request_headers, ByteBuffer request_body, Optional<String> initiator_type);
void set_response_start(u32 status_code, Optional<String> reason_phrase);
void set_response_headers(Vector<HTTP::Header> response_headers);
void append_response_body(ByteBuffer data);
@ -53,6 +53,7 @@ private:
UnixDateTime m_start_time;
Vector<HTTP::Header> m_request_headers;
ByteBuffer m_request_body;
Optional<String> m_initiator_type;
Optional<u32> m_status_code;
Optional<String> m_reason_phrase;

View file

@ -87,6 +87,7 @@ public:
UnixDateTime start_time;
Vector<HTTP::Header> request_headers;
ByteBuffer request_body;
Optional<String> initiator_type;
};
struct NetworkResponseData {

View file

@ -2085,6 +2085,7 @@ GC::Ref<PendingResponse> nonstandard_resource_loader_file_or_http_network_fetch(
load_request.set_page(page);
load_request.set_method(request->method());
load_request.set_store_set_cookie_headers(include_credentials == IncludeCredentials::Yes);
load_request.set_initiator_type(request->initiator_type());
if (auto const* body = request->body().get_pointer<GC::Ref<Infrastructure::Body>>()) {
(*body)->source().visit(

View file

@ -12,6 +12,7 @@
#include <LibHTTP/HeaderList.h>
#include <LibURL/URL.h>
#include <LibWeb/Export.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Page/Page.h>
@ -36,6 +37,9 @@ public:
bool store_set_cookie_headers() const { return m_store_set_cookie_headers; }
void set_store_set_cookie_headers(bool store_set_cookie_headers) { m_store_set_cookie_headers = store_set_cookie_headers; }
Optional<Fetch::Infrastructure::Request::InitiatorType> const& initiator_type() const { return m_initiator_type; }
void set_initiator_type(Optional<Fetch::Infrastructure::Request::InitiatorType> initiator_type) { m_initiator_type = move(initiator_type); }
void start_timer() { m_load_timer.start(); }
AK::Duration load_time() const { return m_load_timer.elapsed_time(); }
@ -52,6 +56,7 @@ private:
Core::ElapsedTimer m_load_timer;
GC::Root<Page> m_page;
bool m_store_set_cookie_headers { true };
Optional<Fetch::Infrastructure::Request::InitiatorType> m_initiator_type;
};
}

View file

@ -16,6 +16,7 @@
#include <LibURL/Parser.h>
#include <LibWeb/Cookie/Cookie.h>
#include <LibWeb/Cookie/ParsedCookie.h>
#include <LibWeb/Fetch/Infrastructure/HTTP/Requests.h>
#include <LibWeb/Fetch/Infrastructure/URL.h>
#include <LibWeb/Loader/ContentFilter.h>
#include <LibWeb/Loader/GeneratedPagesLoader.h>
@ -473,8 +474,12 @@ RefPtr<Requests::Request> ResourceLoader::start_network_request(LoadRequest cons
return {};
};
if (auto page = request.page())
page->client().page_did_start_network_request(protocol_request->id(), request.url().value(), request.method(), request.headers().headers(), request.body());
if (auto page = request.page()) {
Optional<String> initiator_type_string;
if (request.initiator_type().has_value())
initiator_type_string = Fetch::Infrastructure::initiator_type_to_string(request.initiator_type().value()).to_string();
page->client().page_did_start_network_request(protocol_request->id(), request.url().value(), request.method(), request.headers().headers(), request.body(), move(initiator_type_string));
}
++m_pending_loads;
if (on_load_counter_change)

View file

@ -419,7 +419,7 @@ public:
virtual void page_did_change_audio_play_state(HTML::AudioPlayState) { }
virtual void page_did_start_network_request([[maybe_unused]] u64 request_id, [[maybe_unused]] URL::URL const& url, [[maybe_unused]] ByteString const& method, [[maybe_unused]] Vector<HTTP::Header> const& request_headers, [[maybe_unused]] ReadonlyBytes request_body) { }
virtual void page_did_start_network_request([[maybe_unused]] u64 request_id, [[maybe_unused]] URL::URL const& url, [[maybe_unused]] ByteString const& method, [[maybe_unused]] Vector<HTTP::Header> const& request_headers, [[maybe_unused]] ReadonlyBytes request_body, [[maybe_unused]] Optional<String> initiator_type) { }
virtual void page_did_receive_network_response_headers([[maybe_unused]] u64 request_id, [[maybe_unused]] u32 status_code, [[maybe_unused]] Optional<String> reason_phrase, [[maybe_unused]] Vector<HTTP::Header> const& response_headers) { }
virtual void page_did_receive_network_response_body([[maybe_unused]] u64 request_id, [[maybe_unused]] ReadonlyBytes data) { }
virtual void page_did_finish_network_request([[maybe_unused]] u64 request_id, [[maybe_unused]] u64 body_size, [[maybe_unused]] Requests::RequestTimingInfo const& timing_info, [[maybe_unused]] Optional<Requests::NetworkError> const& network_error) { }

View file

@ -1301,8 +1301,8 @@ void Application::listen_for_network_events(DevTools::TabDescription const& desc
if (!view.has_value())
return;
view->on_network_request_started = [on_request_started = move(on_request_started)](u64 request_id, URL::URL const& url, ByteString const& method, Vector<HTTP::Header> const& headers, ByteBuffer request_body) {
on_request_started({ request_id, url.to_string(), MUST(String::from_byte_string(method)), UnixDateTime::now(), headers, move(request_body) });
view->on_network_request_started = [on_request_started = move(on_request_started)](u64 request_id, URL::URL const& url, ByteString const& method, Vector<HTTP::Header> const& headers, ByteBuffer request_body, Optional<String> initiator_type) {
on_request_started({ request_id, url.to_string(), MUST(String::from_byte_string(method)), UnixDateTime::now(), headers, move(request_body), move(initiator_type) });
};
view->on_network_response_headers_received = [on_response_headers = move(on_response_headers)](u64 request_id, u32 status_code, Optional<String> const& reason_phrase, Vector<HTTP::Header> const& headers) {

View file

@ -210,7 +210,7 @@ public:
Function<void(Web::CSS::StyleSheetIdentifier const&, URL::URL const&, String const&)> on_received_style_sheet_source;
Function<void(JsonValue)> on_received_js_console_result;
Function<void(ConsoleOutput)> on_console_message;
Function<void(u64 request_id, URL::URL const&, ByteString const&, Vector<HTTP::Header> const&, ByteBuffer)> on_network_request_started;
Function<void(u64 request_id, URL::URL const&, ByteString const&, Vector<HTTP::Header> const&, ByteBuffer, Optional<String>)> on_network_request_started;
Function<void(u64 request_id, u32 status_code, Optional<String> const&, Vector<HTTP::Header> const&)> on_network_response_headers_received;
Function<void(u64 request_id, ByteBuffer)> on_network_response_body_received;
Function<void(u64 request_id, u64 body_size, Requests::RequestTimingInfo const&, Optional<Requests::NetworkError> const&)> on_network_request_finished;

View file

@ -436,11 +436,11 @@ void WebContentClient::did_output_js_console_message(u64 page_id, ConsoleOutput
}
}
void WebContentClient::did_start_network_request(u64 page_id, u64 request_id, URL::URL url, ByteString method, Vector<HTTP::Header> request_headers, ByteBuffer request_body)
void WebContentClient::did_start_network_request(u64 page_id, u64 request_id, URL::URL url, ByteString method, Vector<HTTP::Header> request_headers, ByteBuffer request_body, Optional<String> initiator_type)
{
if (auto view = view_for_page_id(page_id); view.has_value()) {
if (view->on_network_request_started)
view->on_network_request_started(request_id, url, method, request_headers, move(request_body));
view->on_network_request_started(request_id, url, method, request_headers, move(request_body), move(initiator_type));
}
}

View file

@ -96,7 +96,7 @@ private:
virtual void did_get_internal_page_info(u64 page_id, PageInfoType, String) override;
virtual void did_execute_js_console_input(u64 page_id, JsonValue) override;
virtual void did_output_js_console_message(u64 page_id, ConsoleOutput) override;
virtual void did_start_network_request(u64 page_id, u64 request_id, URL::URL, ByteString method, Vector<HTTP::Header>, ByteBuffer request_body) override;
virtual void did_start_network_request(u64 page_id, u64 request_id, URL::URL, ByteString method, Vector<HTTP::Header>, ByteBuffer request_body, Optional<String> initiator_type) override;
virtual void did_receive_network_response_headers(u64 page_id, u64 request_id, u32 status_code, Optional<String> reason_phrase, Vector<HTTP::Header>) override;
virtual void did_receive_network_response_body(u64 page_id, u64 request_id, ByteBuffer data) override;
virtual void did_finish_network_request(u64 page_id, u64 request_id, u64 body_size, Requests::RequestTimingInfo, Optional<Requests::NetworkError>) override;

View file

@ -751,9 +751,9 @@ void PageClient::received_message_from_web_ui(String const& name, JS::Value data
m_web_ui->received_message_from_web_ui(name, data);
}
void PageClient::page_did_start_network_request(u64 request_id, URL::URL const& url, ByteString const& method, Vector<HTTP::Header> const& request_headers, ReadonlyBytes request_body)
void PageClient::page_did_start_network_request(u64 request_id, URL::URL const& url, ByteString const& method, Vector<HTTP::Header> const& request_headers, ReadonlyBytes request_body, Optional<String> initiator_type)
{
client().async_did_start_network_request(m_id, request_id, url, method, request_headers, request_body);
client().async_did_start_network_request(m_id, request_id, url, method, request_headers, request_body, move(initiator_type));
}
void PageClient::page_did_receive_network_response_headers(u64 request_id, u32 status_code, Optional<String> reason_phrase, Vector<HTTP::Header> const& response_headers)

View file

@ -179,7 +179,7 @@ private:
virtual void page_did_paint(Gfx::IntRect const& content_rect, i32 bitmap_id) override;
virtual void page_did_take_screenshot(Gfx::ShareableBitmap const& screenshot) override;
virtual void received_message_from_web_ui(String const& name, JS::Value data) override;
virtual void page_did_start_network_request(u64 request_id, URL::URL const&, ByteString const&, Vector<HTTP::Header> const&, ReadonlyBytes) override;
virtual void page_did_start_network_request(u64 request_id, URL::URL const&, ByteString const&, Vector<HTTP::Header> const&, ReadonlyBytes, Optional<String>) override;
virtual void page_did_receive_network_response_headers(u64 request_id, u32 status_code, Optional<String>, Vector<HTTP::Header> const&) override;
virtual void page_did_receive_network_response_body(u64 request_id, ReadonlyBytes) override;
virtual void page_did_finish_network_request(u64 request_id, u64 body_size, Requests::RequestTimingInfo const&, Optional<Requests::NetworkError> const&) override;

View file

@ -115,7 +115,7 @@ endpoint WebContentClient
did_execute_js_console_input(u64 page_id, JsonValue result) =|
did_output_js_console_message(u64 page_id, WebView::ConsoleOutput console_output) =|
did_start_network_request(u64 page_id, u64 request_id, URL::URL url, ByteString method, Vector<HTTP::Header> request_headers, ByteBuffer request_body) =|
did_start_network_request(u64 page_id, u64 request_id, URL::URL url, ByteString method, Vector<HTTP::Header> request_headers, ByteBuffer request_body, Optional<String> initiator_type) =|
did_receive_network_response_headers(u64 page_id, u64 request_id, u32 status_code, Optional<String> reason_phrase, Vector<HTTP::Header> response_headers) =|
did_receive_network_response_body(u64 page_id, u64 request_id, ByteBuffer data) =|
did_finish_network_request(u64 page_id, u64 request_id, u64 body_size, Requests::RequestTimingInfo timing_info, Optional<Requests::NetworkError> network_error) =|