diff --git a/Cargo.lock b/Cargo.lock index acc54ce224..a6b847123d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2669,6 +2669,7 @@ dependencies = [ "moli-web-mime", "parking_lot", "rcgen", + "serde", "strum", "tokio", "tokio-rustls", diff --git a/moli-core/src/runtime/tests.rs b/moli-core/src/runtime/tests.rs index 9c2317e940..1a2682ae62 100644 --- a/moli-core/src/runtime/tests.rs +++ b/moli-core/src/runtime/tests.rs @@ -90,6 +90,7 @@ async fn external_raw_bridge_drop_after_body_eof_cancels_pending_fetch_completio redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_rx, diff --git a/moli-core/tests/fetch_behaviors.rs b/moli-core/tests/fetch_behaviors.rs index f5f57243df..941ef634f2 100644 --- a/moli-core/tests/fetch_behaviors.rs +++ b/moli-core/tests/fetch_behaviors.rs @@ -10,7 +10,8 @@ use std::time::Instant; use support::FixtureServer; use tokio::time::Duration; - +#[path = "fetch_behaviors/cache_timing.rs"] +mod cache_timing; #[path = "fetch_behaviors/preload_as.rs"] mod preload_as; #[path = "fetch_behaviors/preload_crossorigin.rs"] diff --git a/moli-core/tests/fetch_behaviors/cache_timing.rs b/moli-core/tests/fetch_behaviors/cache_timing.rs new file mode 100644 index 0000000000..c8e05f14a4 --- /dev/null +++ b/moli-core/tests/fetch_behaviors/cache_timing.rs @@ -0,0 +1,164 @@ +use super::*; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; + +struct CacheDirectory(PathBuf); + +impl Drop for CacheDirectory { + fn drop(&mut self) { + let _ = std::fs::remove_dir_all(&self.0); + } +} + +#[tokio::test(flavor = "multi_thread")] +async fn resource_timing_distinguishes_network_local_and_revalidated_responses() -> Result<()> { + let listener = TcpListener::bind("127.0.0.1:0").await?; + let addr = listener.local_addr()?; + let cache_dir = CacheDirectory(std::env::temp_dir().join(format!( + "moli-resource-timing-{}-{}", + std::process::id(), + addr.port() + ))); + let requests = Arc::new(parking_lot::Mutex::new(HashMap::< + String, + Vec>, + >::new())); + let (shutdown, mut shutting_down) = tokio::sync::oneshot::channel::<()>(); + let server = tokio::spawn(async move { + let mut connections = tokio::task::JoinSet::new(); + loop { + tokio::select! { + _ = &mut shutting_down => break, + accepted = listener.accept() => { + let (mut socket, _) = accepted?; + let requests = Arc::clone(&requests); + connections.spawn(async move { + let mut bytes = Vec::new(); + loop { + let mut buffer = [0; 4096]; + let size = socket.read(&mut buffer).await?; + if size == 0 { return Ok::<_, anyhow::Error>(()); } + bytes.extend_from_slice(&buffer[..size]); + if bytes.windows(4).any(|part| part == b"\r\n\r\n") { break; } + } + let request = String::from_utf8(bytes)?; + let path = request.split_whitespace().nth(1).context("request path")?; + let url = url::Url::parse(&format!("http://fixture{path}"))?; + let params: HashMap<_, _> = url.query_pairs().collect(); + let mode = params.get("mode").map(|value| value.as_ref()).unwrap_or(""); + let context = params.get("context").map(|value| value.as_ref()).unwrap_or(""); + let key = format!("{mode}:{context}"); + let conditional = request.lines().find_map(|line| { + let (name, value) = line.split_once(':')?; + name.eq_ignore_ascii_case("if-none-match").then(|| value.trim().to_owned()) + }); + let mut headers = String::new(); + let (status, mime, body) = match url.path() { + "/probe-cache" => { + requests.lock().entry(key).or_default().push(conditional.clone()); + let validated = mode == "revalidate" && conditional.as_deref() == Some("\"v1\""); + let cache = if mode == "fresh" || validated { "max-age=600" } + else if mode == "revalidate" { "no-cache" } + else { "no-store" }; + headers = format!("ETag: \"v1\"\r\nCache-Control: {cache}\r\n"); + if mode == "padding" { + let padding: usize = params.get("padding").context("padding")?.parse()?; + headers.push_str(&format!("X-Padding: {}\r\n", "x".repeat(padding))); + } + if validated { ("304 Not Modified", "text/plain", String::new()) } + else { ("200 OK", "text/plain", "cached".to_owned()) } + } + "/probe-cache-stats" => { + headers.push_str("Cache-Control: no-store\r\n"); + let records = requests.lock().get(&key).cloned().unwrap_or_default(); + let records: Vec<_> = records.into_iter().map(|conditional| serde_json::json!({"conditional":conditional})).collect(); + ("200 OK", "application/json", serde_json::to_string(&records)?) + } + _ => ("200 OK", "text/html", "cache timing".to_owned()), + }; + socket.write_all(format!( + "HTTP/1.1 {status}\r\nContent-Type: {mime}\r\nContent-Length: {}\r\n{headers}Connection: close\r\n\r\n{body}", body.len() + ).as_bytes()).await?; + Ok(()) + }); + } + Some(result) = connections.join_next(), if !connections.is_empty() => { result??; } + } + } + connections.shutdown().await; + Ok::<_, anyhow::Error>(()) + }); + let mut config = AppConfig::default(); + config + .fetch_mut() + .set_http_cache_dir(Some(cache_dir.0.display().to_string())); + let browser = Browser::new(config)?; + let mut page = browser.fetch(&format!("http://{addr}/")).await?; + let fixture = include_str!("../fixtures/cache-resource-timing.js"); + for context in ["top", "child"] { + let script = if context == "top" { + fixture.to_owned() + } else { + format!( + r#"(async () => {{ + const frame = document.createElement('iframe'); + frame.srcdoc = 'child'; + await new Promise(resolve => {{ frame.onload=resolve; document.body.append(frame); }}); + frame.contentWindow.cacheTimingContext = 'child'; + const result = await frame.contentWindow.eval({}); + frame.remove(); + return result; + }})()"#, + serde_json::to_string(fixture)? + ) + }; + let result = tokio::time::timeout( + Duration::from_secs(15), + page.evaluate_runtime_expression_with_await_async( + &format!("({script}).then(JSON.stringify)"), + true, + ), + ) + .await??; + let observed: serde_json::Value = + serde_json::from_str(result["value"].as_str().context("timing observations")?)?; + let expected = [ + ("fresh", [306, 0, 0]), + ("revalidate", [306, 300, 0]), + ("no-store", [306, 306, 306]), + ]; + let mut expected_rows = Vec::new(); + for (mode, transfers) in expected { + let observations: Vec<_> = transfers + .into_iter() + .enumerate() + .map(|(index, transfer)| { + serde_json::json!({ + "body":"cached", "count":index + 1, "transfer":transfer, "encoded":6, "decoded":6, "status":200 + }) + }) + .collect(); + let stats = match mode { + "fresh" => serde_json::json!([{"conditional":null}]), + "revalidate" => serde_json::json!([{"conditional":null},{"conditional":"\"v1\""}]), + _ => { + serde_json::json!([{"conditional":null},{"conditional":null},{"conditional":null}]) + } + }; + expected_rows + .push(serde_json::json!({"mode":mode,"observations":observations,"stats":stats})); + } + assert_eq!( + observed, + serde_json::json!({"result":expected_rows,"headers":[ + {"padding":0,"transfer":306,"encoded":6}, + {"padding":4096,"transfer":306,"encoded":6} + ]}), + "{context}" + ); + } + let _ = shutdown.send(()); + server.await??; + Ok(()) +} diff --git a/moli-core/tests/fixtures/cache-resource-timing.js b/moli-core/tests/fixtures/cache-resource-timing.js new file mode 100644 index 0000000000..ea8de41573 --- /dev/null +++ b/moli-core/tests/fixtures/cache-resource-timing.js @@ -0,0 +1,45 @@ +(async () => { + const xhr = url => new Promise((resolve, reject) => { + const request = new XMLHttpRequest(); + request.open('GET', url); + request.onload = () => resolve(request.responseText); + request.onerror = () => reject(new Error('xhr ' + url)); + request.send(); + }); + const context = globalThis.cacheTimingContext || 'top'; + const resourceUrl = (path, parameters) => { + const url = new URL(path, document.baseURI); + url.search = new URLSearchParams({...parameters, context}); + return url.href; + }; + const result = []; + for (const mode of ['fresh', 'revalidate', 'no-store']) { + const url = resourceUrl('/probe-cache', {mode}); + const observations = []; + for (let i = 0; i < 3; i++) { + const body = await xhr(url); + await new Promise(resolve => setTimeout(resolve, 0)); + const entries = performance.getEntriesByName(url); + const entry = entries[entries.length - 1]; + observations.push({ + body, + count: entries.length, + transfer: entry.transferSize, + encoded: entry.encodedBodySize, + decoded: entry.decodedBodySize, + status: entry.responseStatus + }); + } + const stats = await (await fetch(resourceUrl('/probe-cache-stats', {mode}))).json(); + result.push({mode, observations, stats}); + } + const headers = []; + for (const padding of [0, 4096]) { + const url = resourceUrl('/probe-cache', {mode: 'padding', padding}); + await xhr(url); + await new Promise(resolve => setTimeout(resolve, 0)); + const entry = performance.getEntriesByName(url)[0]; + headers.push({padding, transfer: entry.transferSize, encoded: entry.encodedBodySize}); + } + return {result, headers}; +})() diff --git a/moli-fetch/Cargo.toml b/moli-fetch/Cargo.toml index 4aacbbb8f3..39fb64973f 100644 --- a/moli-fetch/Cargo.toml +++ b/moli-fetch/Cargo.toml @@ -27,6 +27,7 @@ moli-url-policy = { path = "../moli-url-policy" } moli-web-bot-auth = { path = "../moli-web-bot-auth" } moli-web-mime = { path = "../moli-web-mime" } parking_lot = "0.12" +serde = { version = "1.0.228", features = ["derive"] } strum = { version = "0.28.0", features = ["derive"] } tokio = { version = "1.51.0", features = ["rt", "sync"] } tracing = "0.1.41" diff --git a/moli-fetch/src/blocking/cache.rs b/moli-fetch/src/blocking/cache.rs index a16fb62ef1..1c2adb71f3 100644 --- a/moli-fetch/src/blocking/cache.rs +++ b/moli-fetch/src/blocking/cache.rs @@ -90,6 +90,7 @@ pub(crate) struct CachedStreamingResponseLookup { pub(crate) body: std::io::BufReader, pub(crate) expires_at_unix_ms: Option, pub(crate) force_validate: bool, + pub(crate) cache_state: crate::ResponseCacheState, } pub(crate) fn cached_streaming_response_is_stale(record: &CachedStreamingResponseLookup) -> bool { diff --git a/moli-fetch/src/blocking/cache/lookup.rs b/moli-fetch/src/blocking/cache/lookup.rs index 4163cb77d4..56f869dc54 100644 --- a/moli-fetch/src/blocking/cache/lookup.rs +++ b/moli-fetch/src/blocking/cache/lookup.rs @@ -165,6 +165,7 @@ fn cached_streaming_record_from_entry( body: entry.body, expires_at_unix_ms: entry.metadata.expires_at_unix_ms, force_validate, + cache_state: crate::ResponseCacheState::Local, } } diff --git a/moli-fetch/src/blocking/cache/revalidation.rs b/moli-fetch/src/blocking/cache/revalidation.rs index 3d18abb40e..ab4a236a64 100644 --- a/moli-fetch/src/blocking/cache/revalidation.rs +++ b/moli-fetch/src/blocking/cache/revalidation.rs @@ -25,6 +25,7 @@ pub(crate) fn merge_cached_not_modified_streaming_response_lookup( ) -> Result { let headers = merge_not_modified_headers(&cached.headers, not_modified_headers); cached.headers = headers.clone(); + cached.cache_state = crate::ResponseCacheState::Validated; cached.metadata.headers = headers.clone(); let final_url = Url::parse(&cached.final_url).with_context(|| { diff --git a/moli-fetch/src/blocking/collectors.rs b/moli-fetch/src/blocking/collectors.rs index 24b86ccc68..ef8f8641a1 100644 --- a/moli-fetch/src/blocking/collectors.rs +++ b/moli-fetch/src/blocking/collectors.rs @@ -537,6 +537,7 @@ impl StreamingResponseCollector { redirected: !self.redirect_chain.is_empty(), redirect_chain: self.redirect_chain.clone(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: self.negotiated_http_version, network_request_extra_info: self.network_request_extra_info.clone(), })); @@ -940,6 +941,7 @@ impl RawStreamingResponseCollector { redirected: !self.redirect_chain.is_empty(), redirect_chain: self.redirect_chain.clone(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: self.negotiated_http_version, network_request_extra_info: self.network_request_extra_info.clone(), })); diff --git a/moli-fetch/src/blocking/mod.rs b/moli-fetch/src/blocking/mod.rs index 8664dec4e9..8f0b66d5c1 100644 --- a/moli-fetch/src/blocking/mod.rs +++ b/moli-fetch/src/blocking/mod.rs @@ -73,6 +73,7 @@ pub struct StreamingHtmlResponseStart { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: crate::ResponseCacheState, pub negotiated_http_version: Option, pub network_request_extra_info: Option, } @@ -89,6 +90,7 @@ impl StreamingHtmlResponseStart { redirected: self.redirected, redirect_chain: self.redirect_chain, from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } diff --git a/moli-fetch/src/lib.rs b/moli-fetch/src/lib.rs index d486be36cb..a5e028316e 100644 --- a/moli-fetch/src/lib.rs +++ b/moli-fetch/src/lib.rs @@ -83,7 +83,7 @@ pub use request::{ pub use request_policy::{is_bad_port, should_request_be_blocked_due_to_bad_port}; pub use response::{ NegotiatedHttpVersion, NetworkRequestExtraInfo, NetworkResponseExtraInfo, RawResponse, - RedirectInfo, RedirectSource, Response, ResponseBody, ResponseHead, + RedirectInfo, RedirectSource, Response, ResponseBody, ResponseCacheState, ResponseHead, }; pub use runtime::PendingStreamingRawResponse; pub use runtime::{ diff --git a/moli-fetch/src/response.rs b/moli-fetch/src/response.rs index 17f5563c51..8e8802758f 100644 --- a/moli-fetch/src/response.rs +++ b/moli-fetch/src/response.rs @@ -4,6 +4,18 @@ use url::Url; use crate::{StreamingHtmlResponse, StreamingRawResponse}; +/// Fetch's response cache state, retained separately from the legacy +/// `from_cache` flag so Resource Timing can distinguish a 304 validation from +/// a response served without a network request. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ResponseCacheState { + #[default] + None, + Local, + Validated, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NegotiatedHttpVersion { Http09, @@ -51,6 +63,7 @@ pub struct ResponseHead { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: crate::ResponseCacheState, pub negotiated_http_version: Option, } @@ -327,6 +340,7 @@ pub struct Response { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: crate::ResponseCacheState, pub negotiated_http_version: Option, network_request_extra_info: Option, } @@ -347,6 +361,7 @@ impl Clone for Response { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, network_request_extra_info: self.network_request_extra_info.clone(), } @@ -401,6 +416,7 @@ impl Response { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } @@ -439,6 +455,7 @@ impl Response { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info: None, }) @@ -457,6 +474,7 @@ impl Response { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info: None, }) @@ -473,6 +491,7 @@ impl Response { redirected: self.redirected, redirect_chain: self.redirect_chain, from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, }; let (body, body_bytes) = self @@ -510,6 +529,7 @@ impl Response { redirected: self.redirected, redirect_chain: self.redirect_chain, from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, }; (head, self.body) @@ -536,6 +556,7 @@ pub struct RawResponse { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: crate::ResponseCacheState, pub negotiated_http_version: Option, network_request_extra_info: Option, } @@ -556,6 +577,7 @@ impl Clone for RawResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, network_request_extra_info: self.network_request_extra_info.clone(), } @@ -604,6 +626,7 @@ impl RawResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } @@ -628,6 +651,7 @@ impl RawResponse { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info: None, }) @@ -646,6 +670,7 @@ impl RawResponse { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info: None, }) @@ -662,6 +687,7 @@ impl RawResponse { redirected: self.redirected, redirect_chain: self.redirect_chain, from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, }; (head, self.body) diff --git a/moli-fetch/src/runtime.rs b/moli-fetch/src/runtime.rs index 1c29ad923e..6191d3c077 100644 --- a/moli-fetch/src/runtime.rs +++ b/moli-fetch/src/runtime.rs @@ -2344,6 +2344,7 @@ impl RuntimeOwner { redirected: !job.request.redirect_chain.is_empty(), redirect_chain: job.request.redirect_chain, from_cache: false, + cache_state: Default::default(), negotiated_http_version, network_request_extra_info: request_extra_info, })); @@ -2920,6 +2921,7 @@ fn complete_streaming_html_job(job: StreamingRuntimeJob, response: Response) { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info, })); @@ -2941,6 +2943,7 @@ fn complete_cached_streaming_html_job( let redirect_chain = job.request.redirect_chain.clone(); let CachedStreamingResponseLookup { metadata, + cache_state, final_url, status, headers, @@ -2973,6 +2976,7 @@ fn complete_cached_streaming_html_job( redirected, redirect_chain, from_cache: true, + cache_state, negotiated_http_version: None, network_request_extra_info: None, })); @@ -3063,6 +3067,7 @@ fn complete_cached_streaming_raw_job( let redirect_chain = job.request.redirect_chain.clone(); let CachedStreamingResponseLookup { metadata, + cache_state, final_url, status, headers, @@ -3095,6 +3100,7 @@ fn complete_cached_streaming_raw_job( redirected, redirect_chain, from_cache: true, + cache_state, negotiated_http_version: None, network_request_extra_info: None, })); @@ -3537,6 +3543,7 @@ fn proxy_connect_response_start( redirected: !redirect_chain.is_empty(), redirect_chain: redirect_chain.to_vec(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, network_request_extra_info: None, } @@ -3640,6 +3647,7 @@ fn collect_buffered_response( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version, }, body, @@ -4300,6 +4308,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "cached".to_owned(), diff --git a/moli-fetch/src/streaming_response.rs b/moli-fetch/src/streaming_response.rs index a09d73d2eb..0b13184a18 100644 --- a/moli-fetch/src/streaming_response.rs +++ b/moli-fetch/src/streaming_response.rs @@ -31,6 +31,7 @@ pub struct StreamingHtmlResponse { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: crate::ResponseCacheState, pub negotiated_http_version: Option, network_request_extra_info: Option, body_chunks: mpsc::UnboundedReceiver, @@ -58,6 +59,7 @@ impl StreamingHtmlResponse { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_chunks, @@ -82,6 +84,7 @@ impl StreamingHtmlResponse { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info: None, body_chunks, @@ -101,6 +104,7 @@ impl StreamingHtmlResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } @@ -174,6 +178,7 @@ pub struct StreamingRawResponse { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: crate::ResponseCacheState, pub negotiated_http_version: Option, network_request_extra_info: Option, body_chunks: mpsc::UnboundedReceiver>, @@ -205,6 +210,7 @@ impl StreamingRawResponse { redirected, redirect_chain, from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, network_request_extra_info: None, body_chunks, @@ -230,6 +236,7 @@ impl StreamingRawResponse { redirected: head.redirected, redirect_chain: head.redirect_chain, from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_extra_info: None, body_chunks, @@ -321,6 +328,7 @@ impl StreamingRawResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } @@ -387,6 +395,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, } } diff --git a/moli-fetch/src/tests/mod.rs b/moli-fetch/src/tests/mod.rs index 389e612fcb..c17e69a535 100644 --- a/moli-fetch/src/tests/mod.rs +++ b/moli-fetch/src/tests/mod.rs @@ -74,6 +74,7 @@ fn sample_response_head() -> ResponseHead { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, } } @@ -4405,11 +4406,19 @@ fn network_metadata_keeps_raw_304_separate_from_merged_cached_response() { fetch_raw_with_network_metadata_for_test(&client, Request::get(&server.url()).unwrap()) .unwrap(); assert_eq!(first.response().status, 200); + assert_eq!( + first.response().cache_state, + crate::ResponseCacheState::None + ); let second = fetch_raw_with_network_metadata_for_test(&client, Request::get(&server.url()).unwrap()) .unwrap(); assert_eq!(second.response().status, 200); + assert_eq!( + second.response().cache_state, + crate::ResponseCacheState::Validated + ); assert_eq!(second.response().body_bytes(), b"cached"); let exchange = second .observation_journal() @@ -4812,6 +4821,9 @@ fn fetch_client_cache_updates_freshness_after_not_modified() { assert_eq!(first.body_text(), "hit-1"); assert_eq!(second.body_text(), "hit-1"); assert_eq!(third.body_text(), "hit-1"); + assert_eq!(first.cache_state, crate::ResponseCacheState::None); + assert_eq!(second.cache_state, crate::ResponseCacheState::Validated); + assert_eq!(third.cache_state, crate::ResponseCacheState::Local); for response in [&first, &second, &third] { assert_eq!(response.status, 200); assert_eq!( diff --git a/moli-page-types/src/lib.rs b/moli-page-types/src/lib.rs index 3964c53043..3a5b37e94f 100644 --- a/moli-page-types/src/lib.rs +++ b/moli-page-types/src/lib.rs @@ -326,6 +326,7 @@ pub struct NavigationResponse { pub redirected: bool, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: moli_fetch::ResponseCacheState, pub negotiated_http_version: Option, network_request_headers: Option>, } @@ -346,6 +347,7 @@ impl Clone for NavigationResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.clone(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, network_request_headers: self.network_request_headers.clone(), } @@ -407,6 +409,7 @@ impl NavigationResponse { .map(Into::into) .collect(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } @@ -448,6 +451,7 @@ impl NavigationResponse { redirected: head.redirected, redirect_chain: head.redirect_chain.into_iter().map(Into::into).collect(), from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_headers: None, } @@ -474,6 +478,7 @@ impl NavigationResponse { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body, @@ -506,6 +511,7 @@ impl NavigationResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.into_iter().map(Into::into).collect(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, }; let (body, body_bytes) = self @@ -543,6 +549,7 @@ impl NavigationResponse { redirected: self.redirected, redirect_chain: self.redirect_chain.into_iter().map(Into::into).collect(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, }; (head, self.body) @@ -2431,6 +2438,7 @@ pub struct PendingSubresourceResponseInfo { /// a response-stage Fetch pause is held. pub response_body: SubresourceResponseBody, pub from_cache: bool, + pub cache_state: moli_fetch::ResponseCacheState, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -2450,6 +2458,7 @@ pub struct PendingSubresourceAuthInfo { pub response_headers: Vec<(String, Vec)>, pub response_body: SubresourceResponseBody, pub response_from_cache: bool, + pub response_cache_state: moli_fetch::ResponseCacheState, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -3310,6 +3319,8 @@ pub struct ChildFrameDocumentNetworkSnapshot { pub response_body: Option, #[serde(default)] pub from_cache: bool, + #[serde(default)] + pub cache_state: moli_fetch::ResponseCacheState, } /// A completed child main-resource request whose Network facts remain @@ -4033,6 +4044,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "hello".to_owned(), @@ -4060,6 +4072,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "é".to_owned(), @@ -4098,6 +4111,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, String::new(), diff --git a/moli-protocol/src/conn/fetch_support.rs b/moli-protocol/src/conn/fetch_support.rs index 7d915e2a9a..b2fbe5d708 100644 --- a/moli-protocol/src/conn/fetch_support.rs +++ b/moli-protocol/src/conn/fetch_support.rs @@ -273,6 +273,7 @@ impl PendingFetchAuthNavigation { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, b"auth required".to_vec(), diff --git a/moli-protocol/src/conn/page_state/fetch_state.rs b/moli-protocol/src/conn/page_state/fetch_state.rs index 59a7bb75bf..9d64029dde 100644 --- a/moli-protocol/src/conn/page_state/fetch_state.rs +++ b/moli-protocol/src/conn/page_state/fetch_state.rs @@ -408,6 +408,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body.to_vec(), diff --git a/moli-protocol/src/conn/runtime_load.rs b/moli-protocol/src/conn/runtime_load.rs index 76cb5992b7..6ff2bfce30 100644 --- a/moli-protocol/src/conn/runtime_load.rs +++ b/moli-protocol/src/conn/runtime_load.rs @@ -159,6 +159,7 @@ async fn prepare_browser_owned_error_page_navigation_with_engine_async( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; prepare_captured_document_response_with_engine_async( @@ -1215,6 +1216,7 @@ fn decoded_data_url_navigation_response( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, decoded.body, @@ -1311,6 +1313,7 @@ async fn load_inline_html_navigation_with_engine_async( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; prepare_navigation_from_captured_raw_response_with_engine_async( @@ -3003,6 +3006,7 @@ impl CdpConnection { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; self.build_navigation_from_captured_raw_response_with_load_inputs_async( diff --git a/moli-protocol/src/domains/fetch/navigation.rs b/moli-protocol/src/domains/fetch/navigation.rs index b2c1d8688b..4fbc78ced9 100644 --- a/moli-protocol/src/domains/fetch/navigation.rs +++ b/moli-protocol/src/domains/fetch/navigation.rs @@ -629,6 +629,7 @@ fn pause_data_url_response_stage_navigation_into_buffer( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body, diff --git a/moli-protocol/src/domains/network/activity.rs b/moli-protocol/src/domains/network/activity.rs index c90b76d301..1e4c1e61fe 100644 --- a/moli-protocol/src/domains/network/activity.rs +++ b/moli-protocol/src/domains/network/activity.rs @@ -592,6 +592,7 @@ mod tests { response_headers: vec![("content-type".to_owned(), b"text/plain".to_vec())], response_body: SubresourceResponseBody::from_bytes(b"prepared".to_vec()), from_cache: false, + cache_state: Default::default(), }); let action = crate::domains::activity::PreparedSubresourceContinueAction::capture_for_test( &mut conn, @@ -669,6 +670,7 @@ mod tests { response_headers: Vec::new(), response_body: SubresourceResponseBody::from_bytes(b"old".to_vec()), from_cache: false, + cache_state: Default::default(), }); let old_action = crate::domains::activity::PreparedSubresourceContinueAction::capture_for_test( diff --git a/moli-protocol/src/domains/network/tests/cache.rs b/moli-protocol/src/domains/network/tests/cache.rs index 58d2f28e80..c264dbbb3e 100644 --- a/moli-protocol/src/domains/network/tests/cache.rs +++ b/moli-protocol/src/domains/network/tests/cache.rs @@ -689,6 +689,7 @@ async fn clear_browser_cache_keeps_pending_response_navigation_transfer() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, b"body".to_vec(), diff --git a/moli-protocol/src/domains/page/producer_tests.rs b/moli-protocol/src/domains/page/producer_tests.rs index fb0bfa4318..0cc9115717 100644 --- a/moli-protocol/src/domains/page/producer_tests.rs +++ b/moli-protocol/src/domains/page/producer_tests.rs @@ -1643,6 +1643,7 @@ async fn child_frame_activity_emits_document_network_events_from_prepared_load() encoded_data_length: 3, response_body: Some(SubresourceResponseBody::from_bytes(vec![0x00, 0xff, b'a'])), from_cache: true, + cache_state: moli_fetch::ResponseCacheState::Local, }), }], "https://example.test".to_owned(), @@ -1774,6 +1775,7 @@ async fn stale_child_document_response_emits_network_without_navigation_or_lifec b"historical child body".to_vec(), )), from_cache: false, + cache_state: Default::default(), }, }], Vec::new(), @@ -1875,6 +1877,7 @@ async fn child_document_network_without_body_records_known_no_data() { encoded_data_length: 0, response_body: None, from_cache: false, + cache_state: Default::default(), }; let mut background_events = Vec::new(); diff --git a/moli-renderer-v8/src/context_bootstrap/performance_runtime.rs b/moli-renderer-v8/src/context_bootstrap/performance_runtime.rs index cc4411e680..ef0c4cedec 100644 --- a/moli-renderer-v8/src/context_bootstrap/performance_runtime.rs +++ b/moli-renderer-v8/src/context_bootstrap/performance_runtime.rs @@ -298,6 +298,7 @@ impl ResourcePerformanceEntry { response.status, &response.headers, body_size as f64, + response.cache_state, ) } @@ -315,6 +316,7 @@ impl ResourcePerformanceEntry { response.status, &response.headers, body_size as f64, + response.cache_state, ) } @@ -331,6 +333,7 @@ impl ResourcePerformanceEntry { response.status, &response.headers, response.body_bytes().len() as f64, + response.cache_state, ) } @@ -341,11 +344,15 @@ impl ResourcePerformanceEntry { response_status: u16, response_headers: &[(String, Vec)], body_size: f64, + cache_state: moli_fetch::ResponseCacheState, ) -> Self { - let header_size = response_headers - .iter() - .map(|(name, value)| name.len() + value.len() + 4) - .sum::() as f64; + // Resource Timing deliberately uses a fixed overhead rather than + // exposing response-header sizes, which could reveal cookies. + let transfer_size = match cache_state { + moli_fetch::ResponseCacheState::None => body_size + 300.0, + moli_fetch::ResponseCacheState::Local => 0.0, + moli_fetch::ResponseCacheState::Validated => 300.0, + }; let content_type = moli_web_mime::response_content_type(response_headers) .and_then(|value| moli_web_mime::mime_essence(&value)) .unwrap_or_default(); @@ -353,7 +360,7 @@ impl ResourcePerformanceEntry { name: name.into(), initiator_type: initiator_type.to_owned(), start_unix_millis, - transfer_size: (body_size + header_size).max(1.0), + transfer_size, encoded_body_size: body_size, decoded_body_size: body_size, // The fetch lifecycle does not yet retain render-blocking metadata. @@ -370,26 +377,15 @@ impl ResourcePerformanceEntry { start_unix_millis: Option, network: &crate::protocol_types::ChildFrameDocumentNetworkSnapshot, ) -> Self { - let body_size = network.encoded_data_length as f64; - let header_size = network - .response_headers - .iter() - .map(|(name, value)| name.len() + value.len() + 4) - .sum::() as f64; - let content_type = moli_web_mime::response_content_type(&network.response_headers) - .and_then(|value| moli_web_mime::mime_essence(&value)) - .unwrap_or_default(); - Self { - name: name.into(), - initiator_type: initiator_type.to_owned(), + Self::from_response_parts( + name, + initiator_type, start_unix_millis, - transfer_size: (body_size + header_size).max(1.0), - encoded_body_size: body_size, - decoded_body_size: body_size, - render_blocking_status: "non-blocking".to_owned(), - response_status: f64::from(network.status), - content_type, - } + network.status, + &network.response_headers, + network.encoded_data_length as f64, + network.cache_state, + ) } pub(crate) fn from_network_failure( diff --git a/moli-renderer-v8/src/document_script_scheduler.rs b/moli-renderer-v8/src/document_script_scheduler.rs index 1aa31efa8d..0550c57260 100644 --- a/moli-renderer-v8/src/document_script_scheduler.rs +++ b/moli-renderer-v8/src/document_script_scheduler.rs @@ -1108,6 +1108,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: true, + cache_state: moli_fetch::ResponseCacheState::Local, negotiated_http_version: None, }, "export default 1;".to_owned(), diff --git a/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs b/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs index 0387b72787..e709110ea2 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/child_documents/loads.rs @@ -888,6 +888,7 @@ fn child_document_load_outcome_from_response( encoded_data_length, response_body: Some(response_body), from_cache: head.from_cache, + cache_state: head.cache_state, }), }, ))) @@ -956,6 +957,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, moli_fetch::ResponseBody::materialized_bytes(body_bytes.clone()), diff --git a/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs b/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs index d1f4a53f64..ad665b2930 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/resource_loading.rs @@ -3085,6 +3085,7 @@ impl JsContextHost { redirected: false, redirect_chain: Vec::new(), from_cache: info.from_cache, + cache_state: info.cache_state, negotiated_http_version: None, }, ) @@ -3130,6 +3131,7 @@ impl JsContextHost { redirected: false, redirect_chain: Vec::new(), from_cache: info.response_from_cache, + cache_state: info.response_cache_state, negotiated_http_version: None, }, ) diff --git a/moli-renderer-v8/src/native_bridge/context_host/websockets/registration.rs b/moli-renderer-v8/src/native_bridge/context_host/websockets/registration.rs index d2ef7cf16b..92245f3f1a 100644 --- a/moli-renderer-v8/src/native_bridge/context_host/websockets/registration.rs +++ b/moli-renderer-v8/src/native_bridge/context_host/websockets/registration.rs @@ -306,6 +306,7 @@ impl JsContextHost { response_headers: response_headers.into_iter().collect(), response_body: SubresourceResponseBody::from_bytes(Vec::new()), from_cache: false, + cache_state: Default::default(), }), ); true diff --git a/moli-renderer-v8/src/network/backend/memory_cache.rs b/moli-renderer-v8/src/network/backend/memory_cache.rs index d6c13005db..d607e96fe8 100644 --- a/moli-renderer-v8/src/network/backend/memory_cache.rs +++ b/moli-renderer-v8/src/network/backend/memory_cache.rs @@ -836,6 +836,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body.to_owned(), @@ -854,6 +855,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body.to_vec(), @@ -1058,6 +1060,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "x".repeat(64), diff --git a/moli-renderer-v8/src/network/devtools_resource_load.rs b/moli-renderer-v8/src/network/devtools_resource_load.rs index 6542a641eb..27eeeecf86 100644 --- a/moli-renderer-v8/src/network/devtools_resource_load.rs +++ b/moli-renderer-v8/src/network/devtools_resource_load.rs @@ -68,6 +68,7 @@ impl RendererPreparedNetworkResourceLoad { cookie_set_reports: head.cookie_set_reports, redirect_chain: head.redirect_chain.into_iter().map(Into::into).collect(), from_cache: head.from_cache, + cache_state: head.cache_state, negotiated_http_version: head.negotiated_http_version, network_request_headers, }, @@ -90,6 +91,7 @@ pub struct RendererNetworkResourceLoadResponse { pub cookie_set_reports: Vec, pub redirect_chain: Vec, pub from_cache: bool, + pub cache_state: moli_fetch::ResponseCacheState, pub negotiated_http_version: Option, pub network_request_headers: Option>, } @@ -111,6 +113,7 @@ impl RendererNetworkResourceLoadResponse { .map(Into::into) .collect(), from_cache: self.from_cache, + cache_state: self.cache_state, negotiated_http_version: self.negotiated_http_version, } } diff --git a/moli-renderer-v8/src/network/request_client.rs b/moli-renderer-v8/src/network/request_client.rs index 3aa44d386d..9ca0732e73 100644 --- a/moli-renderer-v8/src/network/request_client.rs +++ b/moli-renderer-v8/src/network/request_client.rs @@ -1208,6 +1208,7 @@ fn streaming_raw_response_from_cached_subresource( ) -> Result { let mut head = response.head(); head.from_cache = true; + head.cache_state = moli_fetch::ResponseCacheState::Local; for redirect in &mut head.redirect_chain { redirect.from_cache = true; redirect.network_extra_info_available = false; @@ -1231,6 +1232,7 @@ fn streaming_raw_response_from_local_response(response: Response) -> Result Response { response.from_cache = true; + response.cache_state = moli_fetch::ResponseCacheState::Local; for redirect in &mut response.redirect_chain { redirect.from_cache = true; redirect.network_extra_info_available = false; diff --git a/moli-renderer-v8/src/network/request_client/tests.rs b/moli-renderer-v8/src/network/request_client/tests.rs index ad7465668b..3df1d2017b 100644 --- a/moli-renderer-v8/src/network/request_client/tests.rs +++ b/moli-renderer-v8/src/network/request_client/tests.rs @@ -288,6 +288,7 @@ async fn memory_cache_tee_drop_after_body_eof_cancels_pending_completion_and_rel redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_rx, @@ -1228,6 +1229,8 @@ async fn concurrent_script_text_waiter_preserves_owner_cache_state() -> Result<( !second.from_cache, "in-flight coalescing should preserve the owner's network provenance" ); + assert_eq!(first.cache_state, moli_fetch::ResponseCacheState::None); + assert_eq!(second.cache_state, moli_fetch::ResponseCacheState::None); let third = loader .fetch_cacheable_script_text_stream(request()?) @@ -1240,6 +1243,7 @@ async fn concurrent_script_text_waiter_preserves_owner_cache_state() -> Result<( third.from_cache, "completed script text cache hits should still report memory-cache provenance" ); + assert_eq!(third.cache_state, moli_fetch::ResponseCacheState::Local); server.await?; Ok(()) @@ -1605,6 +1609,7 @@ async fn cached_raw_subresource_marks_redirect_hops_from_cache() -> Result<()> { negotiated_http_version: None, }], from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, b"cached-final-body".to_vec(), diff --git a/moli-renderer-v8/src/network_host/async_fetch.rs b/moli-renderer-v8/src/network_host/async_fetch.rs index ad3f3f29e9..efeb63e464 100644 --- a/moli-renderer-v8/src/network_host/async_fetch.rs +++ b/moli-renderer-v8/src/network_host/async_fetch.rs @@ -1089,6 +1089,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, true, diff --git a/moli-renderer-v8/src/network_host/async_fetch/redirect_tests.rs b/moli-renderer-v8/src/network_host/async_fetch/redirect_tests.rs index e82015295c..c449789ff8 100644 --- a/moli-renderer-v8/src/network_host/async_fetch/redirect_tests.rs +++ b/moli-renderer-v8/src/network_host/async_fetch/redirect_tests.rs @@ -187,6 +187,7 @@ fn cors_redirect_referrer_never_recovers_stripped_information() -> Result<()> { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; assert_eq!( @@ -239,6 +240,7 @@ fn cors_redirect_modes_precede_location_parsing() -> Result<()> { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let result = redirects.advance(head, false, None); diff --git a/moli-renderer-v8/src/network_host/browser_response.rs b/moli-renderer-v8/src/network_host/browser_response.rs index a5d59041e3..69c3d2db90 100644 --- a/moli-renderer-v8/src/network_host/browser_response.rs +++ b/moli-renderer-v8/src/network_host/browser_response.rs @@ -129,6 +129,7 @@ pub(super) fn blob_response( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_bytes.to_vec(), @@ -152,6 +153,7 @@ pub(crate) fn data_url_response(url: &url::Url) -> Option { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_bytes, diff --git a/moli-renderer-v8/src/network_host/response/cors.rs b/moli-renderer-v8/src/network_host/response/cors.rs index d976038a24..bb8103ddfe 100644 --- a/moli-renderer-v8/src/network_host/response/cors.rs +++ b/moli-renderer-v8/src/network_host/response/cors.rs @@ -626,6 +626,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, } } @@ -657,6 +658,7 @@ mod tests { negotiated_http_version: None, }], from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let document_url = url("https://document.test/page.html"); diff --git a/moli-renderer-v8/src/network_host/xhr/delivery/pending.rs b/moli-renderer-v8/src/network_host/xhr/delivery/pending.rs index 9f7e701247..eec72d86a2 100644 --- a/moli-renderer-v8/src/network_host/xhr/delivery/pending.rs +++ b/moli-renderer-v8/src/network_host/xhr/delivery/pending.rs @@ -95,6 +95,7 @@ fn xhr_complete_callback( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, pending_body_value, diff --git a/moli-renderer-v8/src/runtime/browser_context_runtime.rs b/moli-renderer-v8/src/runtime/browser_context_runtime.rs index e352f8ece9..07205fb4e1 100644 --- a/moli-renderer-v8/src/runtime/browser_context_runtime.rs +++ b/moli-renderer-v8/src/runtime/browser_context_runtime.rs @@ -312,6 +312,7 @@ impl DetachedParserScriptFetchContinuation { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, ResponseBody::materialized_text(text, response_body), diff --git a/moli-renderer-v8/src/runtime/page_surface.rs b/moli-renderer-v8/src/runtime/page_surface.rs index 0ab6d3629e..f3cf4273da 100644 --- a/moli-renderer-v8/src/runtime/page_surface.rs +++ b/moli-renderer-v8/src/runtime/page_surface.rs @@ -732,6 +732,7 @@ fn dedicated_worker_navigation_response_eq( && left.redirected == right.redirected && left.redirect_chain == right.redirect_chain && left.from_cache == right.from_cache + && left.cache_state == right.cache_state && left.negotiated_http_version == right.negotiated_http_version && left.network_request_headers() == right.network_request_headers() } diff --git a/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs b/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs index e3d7f3102c..eb77ea87f2 100644 --- a/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs +++ b/moli-renderer-v8/src/runtime/page_vm/followed_navigation.rs @@ -491,6 +491,7 @@ fn about_blank_navigation_response(url: &Url) -> Option { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, Vec::new(), diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/async_subresource_completion.rs b/moli-renderer-v8/src/runtime/page_vm/tests/async_subresource_completion.rs index d6472b6d01..92f1b90511 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/async_subresource_completion.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/async_subresource_completion.rs @@ -245,6 +245,7 @@ fetch({:?}) redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, }, @@ -460,6 +461,7 @@ async fn streaming_finish_requires_matching_request_and_body_source_identity() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, }, diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs b/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs index a0e1bbbaad..b6a67a561a 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/child_document_completion.rs @@ -738,6 +738,7 @@ pub(super) fn stale_loaded_completion( encoded_data_length: 0, response_body: None, from_cache: false, + cache_state: Default::default(), }), }, ))), diff --git a/moli-renderer-v8/src/runtime/page_vm/tests/extracted/main_module_graphs.rs b/moli-renderer-v8/src/runtime/page_vm/tests/extracted/main_module_graphs.rs index 542bf93f03..83f5eeb3e4 100644 --- a/moli-renderer-v8/src/runtime/page_vm/tests/extracted/main_module_graphs.rs +++ b/moli-renderer-v8/src/runtime/page_vm/tests/extracted/main_module_graphs.rs @@ -633,6 +633,7 @@ fn module_graph_network_result_records_staged_response_started_with_cache_state( redirected: false, redirect_chain: Vec::new(), from_cache: true, + cache_state: moli_fetch::ResponseCacheState::Local, negotiated_http_version: None, }, "export default 1;".to_owned(), diff --git a/moli-renderer-v8/src/script_vm/subresource_fetch/pending_fetch.rs b/moli-renderer-v8/src/script_vm/subresource_fetch/pending_fetch.rs index 9abebe99f1..3021209847 100644 --- a/moli-renderer-v8/src/script_vm/subresource_fetch/pending_fetch.rs +++ b/moli-renderer-v8/src/script_vm/subresource_fetch/pending_fetch.rs @@ -859,6 +859,7 @@ impl ScriptVm { response_headers: response.headers.clone(), response_body: SubresourceResponseBody::from_navigation_response(&response), from_cache: response.from_cache, + cache_state: response.cache_state, }; self._context_host .borrow_mut() @@ -1115,6 +1116,7 @@ impl ScriptVm { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let pending = match continuation { @@ -1861,6 +1863,7 @@ impl ScriptVm { redirected: false, redirect_chain: Vec::new(), from_cache: pending.response.from_cache, + cache_state: pending.response.cache_state, negotiated_http_version: pending.response.negotiated_http_version, }); let result = if pending.pending.request_mode == moli_fetch::RequestMode::NoCors { @@ -1925,6 +1928,7 @@ impl ScriptVm { redirected: false, redirect_chain: Vec::new(), from_cache: pending.response.from_cache, + cache_state: pending.response.cache_state, negotiated_http_version: pending.response.negotiated_http_version, }); let result = if pending.pending.request_mode == moli_fetch::RequestMode::NoCors { @@ -1998,6 +2002,7 @@ impl ScriptVm { redirected: false, redirect_chain: Vec::new(), from_cache: pending.response.from_cache, + cache_state: pending.response.cache_state, negotiated_http_version: pending.response.negotiated_http_version, }), ), diff --git a/moli-renderer-v8/src/script_vm/subresource_fetch/streaming.rs b/moli-renderer-v8/src/script_vm/subresource_fetch/streaming.rs index 6c190debd3..d9d923abdc 100644 --- a/moli-renderer-v8/src/script_vm/subresource_fetch/streaming.rs +++ b/moli-renderer-v8/src/script_vm/subresource_fetch/streaming.rs @@ -138,6 +138,7 @@ impl ScriptVm { response_headers: response.headers.clone(), response_body: SubresourceResponseBody::from_navigation_response(&response), response_from_cache: response.from_cache, + response_cache_state: response.cache_state, }; trace_async_subresource_stage( "async_subresource_complete_running_auth_required", @@ -191,6 +192,7 @@ impl ScriptVm { response_headers: response.headers.clone(), response_body: SubresourceResponseBody::from_navigation_response(&response), from_cache: response.from_cache, + cache_state: response.cache_state, }; self._context_host .borrow_mut() diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs index 3f4282bb07..01167cab38 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/navigation/performance.rs @@ -588,6 +588,7 @@ fn parser_script_network_results_populate_buffered_resource_timing_snapshots() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "void 0;".to_owned(), diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/null_body.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/null_body.rs index fb2701074a..df4a9c9fae 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/null_body.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/null_body.rs @@ -179,6 +179,7 @@ fn xhr_null_body_statuses_discard_buffered_and_streamed_bytes() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; if streaming { @@ -344,6 +345,7 @@ fn xhr_null_body_late_completion_preserves_a_replacement_requests_progress() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/opaque_response.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/opaque_response.rs index 719eda0678..ad8e4e4438 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/opaque_response.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/opaque_response.rs @@ -64,6 +64,7 @@ fn window_service_worker_response_filter_survives_streaming_clone_and_cache() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let response_filter = Some(if cors { @@ -233,6 +234,7 @@ fn window_filtered_fetch_preserves_internal_head_through_clone_and_cache() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; if streaming { diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/redirect_filter.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/redirect_filter.rs index f0fbf6f5f3..dea6bb63fb 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/redirect_filter.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/redirect_filter.rs @@ -57,6 +57,7 @@ fn redirect_filter_completion_keeps_status_text_and_explicit_filters() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "body".to_owned(), diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/response_headers.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/response_headers.rs index f3eb14527c..a0467f6b4f 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/response_headers.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/response_headers.rs @@ -35,6 +35,7 @@ fn xhr_response_headers_are_filtered_combined_and_sorted_before_events() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; if streaming { diff --git a/moli-renderer-v8/src/script_vm/tests/dom_xhr/xhr.rs b/moli-renderer-v8/src/script_vm/tests/dom_xhr/xhr.rs index 0a19825c82..349ccb5150 100644 --- a/moli-renderer-v8/src/script_vm/tests/dom_xhr/xhr.rs +++ b/moli-renderer-v8/src/script_vm/tests/dom_xhr/xhr.rs @@ -1200,6 +1200,7 @@ fn xml_http_request_blob_response_uses_final_mime_type() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, moli_fetch::ResponseBody::materialized_bytes(b"body".to_vec()), @@ -1300,6 +1301,7 @@ fn xml_http_request_default_response_type_parses_response_xml_for_document_mime( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, moli_fetch::ResponseBody::materialized_bytes( @@ -1327,6 +1329,7 @@ fn xml_http_request_default_response_type_parses_response_xml_for_document_mime( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, moli_fetch::ResponseBody::materialized_bytes(b"plain text".to_vec()), @@ -1405,6 +1408,7 @@ fn xml_http_request_response_document_uses_response_url_and_requester_origin() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, moli_fetch::ResponseBody::materialized_bytes(br#"link"#.to_vec()), @@ -1502,6 +1506,7 @@ fn xml_http_request_document_response_requires_an_eligible_mime_and_well_formed_ redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; if streaming { @@ -1656,6 +1661,7 @@ fn xhr_streamed_response_documents_keep_distinct_source_modification_times() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, @@ -1773,6 +1779,7 @@ fn xhr_response_decoding_uses_headers_received_overrides_for_buffered_and_stream redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; if streaming { @@ -2958,6 +2965,7 @@ __streamingXhr.send(); redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; @@ -3144,6 +3152,7 @@ async fn streaming_subresource_finish_preserves_response_head_cache_state() { negotiated_http_version: None, }], from_cache: true, + cache_state: moli_fetch::ResponseCacheState::Local, negotiated_http_version: None, }, network_request_headers: None, @@ -3424,6 +3433,7 @@ async fn streaming_fetch_body_error_records_response_started_then_body_failed() redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, @@ -3525,6 +3535,7 @@ fn install_streaming_fetch_response_fixture( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_source_id, @@ -3587,6 +3598,7 @@ fn install_streaming_fetch_response_fixture( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, @@ -3699,6 +3711,7 @@ async fn streaming_fetch_body_cancel_aborts_streaming_subresource() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, @@ -4336,6 +4349,7 @@ async fn streaming_xhr_materialization_failure_errors_body_source_before_close() redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, diff --git a/moli-renderer-v8/src/script_vm/tests/extracted/child_documents.rs b/moli-renderer-v8/src/script_vm/tests/extracted/child_documents.rs index a98a43a3b2..d2f79a237f 100644 --- a/moli-renderer-v8/src/script_vm/tests/extracted/child_documents.rs +++ b/moli-renderer-v8/src/script_vm/tests/extracted/child_documents.rs @@ -655,6 +655,7 @@ async fn child_navigation_keeps_accepted_beacon_network_only_and_rejects_stale_s redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, }) diff --git a/moli-renderer-v8/src/script_vm/tests/extracted/page_runtime.rs b/moli-renderer-v8/src/script_vm/tests/extracted/page_runtime.rs index d2d901f1c6..1e9f9cdb34 100644 --- a/moli-renderer-v8/src/script_vm/tests/extracted/page_runtime.rs +++ b/moli-renderer-v8/src/script_vm/tests/extracted/page_runtime.rs @@ -325,6 +325,7 @@ async fn child_csp_report_keeps_exact_violation_document_without_v8_after_naviga redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, }) diff --git a/moli-renderer-v8/src/script_vm/tests/extracted/realms_and_teardown.rs b/moli-renderer-v8/src/script_vm/tests/extracted/realms_and_teardown.rs index aa616b15e9..f774e68dda 100644 --- a/moli-renderer-v8/src/script_vm/tests/extracted/realms_and_teardown.rs +++ b/moli-renderer-v8/src/script_vm/tests/extracted/realms_and_teardown.rs @@ -1967,6 +1967,7 @@ fn isolated_realm_destruction_aborts_fetch_and_detaches_keepalive() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, }) diff --git a/moli-renderer-v8/src/script_vm/tests/fetch_request_guard.rs b/moli-renderer-v8/src/script_vm/tests/fetch_request_guard.rs index 6a8cf000be..52faf95a3c 100644 --- a/moli-renderer-v8/src/script_vm/tests/fetch_request_guard.rs +++ b/moli-renderer-v8/src/script_vm/tests/fetch_request_guard.rs @@ -31,6 +31,7 @@ fn fetch_request_initializers_use_request_header_guards() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; vm.complete_async_subresource_fetch(crate::types::AsyncSubresourceFetchCompletion { diff --git a/moli-renderer-v8/src/script_vm/tests/mod.rs b/moli-renderer-v8/src/script_vm/tests/mod.rs index bd27ca2d86..22b367771b 100644 --- a/moli-renderer-v8/src/script_vm/tests/mod.rs +++ b/moli-renderer-v8/src/script_vm/tests/mod.rs @@ -374,6 +374,7 @@ fn register_pending_window_fetch_for_test( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, network_request_headers: None, diff --git a/moli-renderer-v8/src/script_vm/tests/post_parse.rs b/moli-renderer-v8/src/script_vm/tests/post_parse.rs index 12e5d1dc0a..579edb345a 100644 --- a/moli-renderer-v8/src/script_vm/tests/post_parse.rs +++ b/moli-renderer-v8/src/script_vm/tests/post_parse.rs @@ -357,6 +357,7 @@ fn external_script_redirect_final_url_obeys_script_src_csp() { redirected: true, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "globalThis.__redirectScriptRan = true;".to_owned(), diff --git a/moli-renderer-v8/src/script_vm/tests/webidl_fetch/url_and_initializer_semantics.rs b/moli-renderer-v8/src/script_vm/tests/webidl_fetch/url_and_initializer_semantics.rs index 99870d5976..26505c85b7 100644 --- a/moli-renderer-v8/src/script_vm/tests/webidl_fetch/url_and_initializer_semantics.rs +++ b/moli-renderer-v8/src/script_vm/tests/webidl_fetch/url_and_initializer_semantics.rs @@ -725,6 +725,7 @@ fn response_clone_tees_pending_network_body_after_parent_consumption() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_source_id, @@ -831,6 +832,7 @@ fn pending_fetch_body_pipe_through_text_decoder_stream_pulls_future_chunks() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, body_source_id, @@ -954,6 +956,7 @@ fn materialize_response_object_preserves_redirected_slot() { redirected: true, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, "redirected-body".to_owned(), @@ -1336,6 +1339,7 @@ redirect_mode: moli_fetch::RequestRedirectMode::Follow, redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let id = crate::network_host::new_network_body_source_id(); @@ -1439,6 +1443,7 @@ fn opaque_window_fetch_keeps_blocked_bytes_out_of_internal_clone_consumers() { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, }, @@ -1551,6 +1556,7 @@ redirect_mode: moli_fetch::RequestRedirectMode::Follow, method: "GET", mode: mol redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, moli_fetch::ResponseBody::materialized_bytes(Vec::new()), diff --git a/moli-renderer-v8/src/service_worker_runtime/script_loading.rs b/moli-renderer-v8/src/service_worker_runtime/script_loading.rs index 18ec746250..fd742035ea 100644 --- a/moli-renderer-v8/src/service_worker_runtime/script_loading.rs +++ b/moli-renderer-v8/src/service_worker_runtime/script_loading.rs @@ -554,6 +554,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; @@ -950,6 +951,7 @@ mod tests { redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let mut resource = diff --git a/moli-renderer-v8/src/service_worker_runtime/service/fetch_settlement.rs b/moli-renderer-v8/src/service_worker_runtime/service/fetch_settlement.rs index c0de2b8dda..c270b26189 100644 --- a/moli-renderer-v8/src/service_worker_runtime/service/fetch_settlement.rs +++ b/moli-renderer-v8/src/service_worker_runtime/service/fetch_settlement.rs @@ -115,6 +115,7 @@ fn service_worker_fetch_stream_response_head( redirected: response_head.redirected || !job.request.redirect_chain().is_empty(), redirect_chain: job.request.redirect_chain().to_vec(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, } } @@ -638,6 +639,7 @@ impl ServiceWorkerRuntimeService { redirected: response.redirected || !job.request.redirect_chain().is_empty(), redirect_chain: job.request.redirect_chain().to_vec(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }, String::from_utf8_lossy(&response.body).into_owned(), diff --git a/moli-renderer-v8/src/worker/global_scope/fetch.rs b/moli-renderer-v8/src/worker/global_scope/fetch.rs index d4952ed305..2607cba135 100644 --- a/moli-renderer-v8/src/worker/global_scope/fetch.rs +++ b/moli-renderer-v8/src/worker/global_scope/fetch.rs @@ -2677,6 +2677,7 @@ pub(in crate::worker) fn worker_response_from_body( redirected: false, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }) } @@ -3088,6 +3089,7 @@ pub(in crate::worker) fn drain_worker_fetch_completion_result( response_headers: response_head.headers.clone(), response_body, response_from_cache: response_head.from_cache, + response_cache_state: response_head.cache_state, }) } else { None @@ -3122,6 +3124,7 @@ pub(in crate::worker) fn drain_worker_fetch_completion_result( response_headers: response_head.headers.clone(), response_body: response_body.clone(), from_cache: response_head.from_cache, + cache_state: response_head.cache_state, }; pending.paused_response = Some(PausedWorkerSubresourceResponse { response_filter: completion.response_filter.clone(), diff --git a/moli-renderer-v8/src/worker/global_scope/xhr.rs b/moli-renderer-v8/src/worker/global_scope/xhr.rs index b83cdfd081..b3077b08a9 100644 --- a/moli-renderer-v8/src/worker/global_scope/xhr.rs +++ b/moli-renderer-v8/src/worker/global_scope/xhr.rs @@ -1069,6 +1069,7 @@ pub(in crate::worker) fn drain_worker_xhr_completion( response_headers: response_head.headers.clone(), response_body, response_from_cache: response_head.from_cache, + response_cache_state: response_head.cache_state, }) } else { None @@ -1104,6 +1105,7 @@ pub(in crate::worker) fn drain_worker_xhr_completion( response_headers: response_head.headers.clone(), response_body: response_body.clone(), from_cache: response_head.from_cache, + cache_state: response_head.cache_state, }; pending.paused_response = Some(PausedWorkerSubresourceResponse { response_filter: None, diff --git a/moli-renderer-v8/src/worker/thread/dispatch.rs b/moli-renderer-v8/src/worker/thread/dispatch.rs index 809be9db54..9f41772f86 100644 --- a/moli-renderer-v8/src/worker/thread/dispatch.rs +++ b/moli-renderer-v8/src/worker/thread/dispatch.rs @@ -3446,6 +3446,7 @@ fn start_service_worker_navigation_preload_response_in_context( redirected: started.response_head.redirected, redirect_chain: Vec::new(), from_cache: false, + cache_state: Default::default(), negotiated_http_version: None, }; let response_object = build_navigation_preload_response_object_from_stream_for_request_mode(