mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 16:00:54 +00:00
fix(resource-timing): record XMLHttpRequest entries
Record XMLHttpRequest response timing through the existing resource entry path for streaming and buffered responses. Cover the request URL, xmlhttprequest initiator type, native PerformanceResourceTiming identity, and exactly one network request.
This commit is contained in:
@@ -228,12 +228,62 @@ impl ResourcePerformanceEntry {
|
||||
response: &crate::protocol_types::NavigationResponse,
|
||||
) -> Self {
|
||||
let body_size = response.body_bytes().len() as f64;
|
||||
let header_size = response
|
||||
.headers
|
||||
Self::from_response_parts(
|
||||
name,
|
||||
initiator_type,
|
||||
start_unix_millis,
|
||||
response.status,
|
||||
&response.headers,
|
||||
body_size,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_streaming_network_response(
|
||||
name: impl Into<String>,
|
||||
initiator_type: &'static str,
|
||||
start_unix_millis: Option<f64>,
|
||||
response: &moli_fetch::ResponseHead,
|
||||
body_size: usize,
|
||||
) -> Self {
|
||||
Self::from_response_parts(
|
||||
name,
|
||||
initiator_type,
|
||||
start_unix_millis,
|
||||
response.status,
|
||||
&response.headers,
|
||||
body_size as f64,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_fetch_response(
|
||||
name: impl Into<String>,
|
||||
initiator_type: &'static str,
|
||||
start_unix_millis: Option<f64>,
|
||||
response: &moli_fetch::Response,
|
||||
) -> Self {
|
||||
Self::from_response_parts(
|
||||
name,
|
||||
initiator_type,
|
||||
start_unix_millis,
|
||||
response.status,
|
||||
&response.headers,
|
||||
response.body_bytes().len() as f64,
|
||||
)
|
||||
}
|
||||
|
||||
fn from_response_parts(
|
||||
name: impl Into<String>,
|
||||
initiator_type: &'static str,
|
||||
start_unix_millis: Option<f64>,
|
||||
response_status: u16,
|
||||
response_headers: &[(String, String)],
|
||||
body_size: f64,
|
||||
) -> Self {
|
||||
let header_size = response_headers
|
||||
.iter()
|
||||
.map(|(name, value)| name.len() + value.len() + 4)
|
||||
.sum::<usize>() as f64;
|
||||
let content_type = moli_web_mime::response_content_type(&response.headers)
|
||||
let content_type = moli_web_mime::response_content_type(response_headers)
|
||||
.and_then(|value| moli_web_mime::mime_essence(&value))
|
||||
.unwrap_or_default();
|
||||
Self {
|
||||
@@ -246,7 +296,7 @@ impl ResourcePerformanceEntry {
|
||||
// The fetch lifecycle does not yet retain render-blocking metadata.
|
||||
// Avoid claiming that a resource blocked rendering until it does.
|
||||
render_blocking_status: "non-blocking".to_owned(),
|
||||
response_status: f64::from(response.status),
|
||||
response_status: f64::from(response_status),
|
||||
content_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,6 +428,15 @@ fn send_synchronous_network_xhr(
|
||||
|
||||
match result {
|
||||
Ok(response) => {
|
||||
crate::context_bootstrap::record_resource_performance_entry(
|
||||
scope,
|
||||
crate::context_bootstrap::ResourcePerformanceEntry::from_fetch_response(
|
||||
prepared.resolved_url.as_str(),
|
||||
"xmlhttprequest",
|
||||
None,
|
||||
&response,
|
||||
),
|
||||
);
|
||||
let observable_headers = crate::network_host::filter_cors_exposed_response_headers(
|
||||
&prepared.request_origin,
|
||||
&response.head(),
|
||||
|
||||
@@ -3740,6 +3740,15 @@ impl ScriptVm {
|
||||
resolver.resolve(scope, response_obj.into());
|
||||
}
|
||||
PendingSubresourceContinuation::Xhr(xhr) => {
|
||||
crate::context_bootstrap::record_resource_performance_entry(
|
||||
scope,
|
||||
crate::context_bootstrap::ResourcePerformanceEntry::from_network_response(
|
||||
pending.info.url.as_str(),
|
||||
"xmlhttprequest",
|
||||
None,
|
||||
&observable_response,
|
||||
),
|
||||
);
|
||||
let xhr = v8::Local::new(scope, &xhr);
|
||||
let (head, body) = observable_response.into_body();
|
||||
crate::network_host::apply_xhr_response_body_source_with_status_text(
|
||||
@@ -5542,6 +5551,7 @@ impl ScriptVm {
|
||||
let finish_body_started =
|
||||
moli_trace::cdp_runtime_trace_enabled().then(Instant::now);
|
||||
let response_body = streaming.body_writer.finish();
|
||||
let response_body_size = response_body.len();
|
||||
trace_async_subresource_stage(
|
||||
"async_subresource_streaming_body_finished",
|
||||
trace_fields,
|
||||
@@ -5697,6 +5707,16 @@ impl ScriptVm {
|
||||
{
|
||||
let xhr_started =
|
||||
moli_trace::cdp_runtime_trace_enabled().then(Instant::now);
|
||||
crate::context_bootstrap::record_resource_performance_entry(
|
||||
scope,
|
||||
crate::context_bootstrap::ResourcePerformanceEntry::from_streaming_network_response(
|
||||
streaming.pending.info.url.as_str(),
|
||||
"xmlhttprequest",
|
||||
None,
|
||||
&streaming.head,
|
||||
response_body_size,
|
||||
),
|
||||
);
|
||||
let xhr = v8::Local::new(scope, &xhr);
|
||||
let mut observable_head = streaming.head;
|
||||
if !streaming
|
||||
|
||||
@@ -83,6 +83,71 @@ async fn same_origin_window_fetch_and_xhr_post_send_origin_on_wire() {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn asynchronous_window_xhr_records_resource_timing_with_xmlhttprequest_initiator() {
|
||||
let server = StaticHttpServer::spawn(1).await;
|
||||
let base_url = server.base_url();
|
||||
let loader = static_http_loader(std::iter::empty::<String>());
|
||||
let mut vm = new_page_task_executor_test_vm_with_loader(
|
||||
base_url
|
||||
.join("page.html")
|
||||
.expect("page fixture URL")
|
||||
.as_str(),
|
||||
&loader,
|
||||
);
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
globalThis.__xhrResourceTimingProbe = "pending";
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "xhr-resource");
|
||||
xhr.onload = () => { globalThis.__xhrResourceTimingProbe = "done"; };
|
||||
xhr.onerror = () => { globalThis.__xhrResourceTimingProbe = "error"; };
|
||||
xhr.send();
|
||||
return "started";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("XHR resource timing probe should evaluate");
|
||||
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(globalThis.__xhrResourceTimingProbe)",
|
||||
"done",
|
||||
"XHR resource timing probe",
|
||||
)
|
||||
.await;
|
||||
|
||||
let entry = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const url = new URL("xhr-resource", location.href).href;
|
||||
const entries = performance.getEntriesByName(url, "resource");
|
||||
return JSON.stringify(entries.map(entry => ({
|
||||
name: entry.name,
|
||||
initiatorType: entry.initiatorType,
|
||||
isResourceTiming: entry instanceof PerformanceResourceTiming
|
||||
})));
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("XHR resource timing entry should be readable");
|
||||
assert_eq!(
|
||||
entry,
|
||||
format!(
|
||||
r#"[{{"name":"{}","initiatorType":"xmlhttprequest","isResourceTiming":true}}]"#,
|
||||
base_url.join("xhr-resource").expect("XHR resource URL")
|
||||
)
|
||||
);
|
||||
|
||||
let requests = server.finish().await;
|
||||
assert_eq!(requests.len(), 1);
|
||||
assert_eq!(requests[0].target, "/xhr-resource");
|
||||
}
|
||||
|
||||
fn pending_fetch_continuation<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
resolver: v8::Local<'s, v8::PromiseResolver>,
|
||||
|
||||
Reference in New Issue
Block a user