mirror of
https://github.com/lexmount/moli.git
synced 2026-09-26 00:01:32 +00:00
perf(response): defer redundant body text copies
This commit is contained in:
Generated
-1
@@ -2695,7 +2695,6 @@ dependencies = [
|
||||
"moli-dom",
|
||||
"moli-fetch",
|
||||
"moli-web-mime",
|
||||
"parking_lot",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -14,7 +14,6 @@ moli-dom = { path = "../moli-dom" }
|
||||
moli-fetch = { path = "../moli-fetch" }
|
||||
moli-web-mime = { path = "../moli-web-mime" }
|
||||
http-auth = { version = "0.1.10", default-features = false }
|
||||
parking_lot = "0.12"
|
||||
regex = "1.12.3"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
|
||||
+95
-189
@@ -26,7 +26,6 @@ use std::{
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt, PermissionsExt};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
@@ -1477,15 +1476,17 @@ pub struct SubresourceResponseBody {
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SubresourceResponseBodyInner {
|
||||
Memory {
|
||||
text: String,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
File {
|
||||
path: PathBuf,
|
||||
len: usize,
|
||||
text_cache: Mutex<Option<String>>,
|
||||
},
|
||||
Memory(Vec<u8>),
|
||||
File { path: PathBuf, len: usize },
|
||||
}
|
||||
|
||||
impl SubresourceResponseBodyInner {
|
||||
fn in_memory_bytes(&self) -> Option<&[u8]> {
|
||||
match self {
|
||||
Self::Memory(bytes) => Some(bytes),
|
||||
Self::File { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SubresourceResponseBodyInner {
|
||||
@@ -1501,19 +1502,13 @@ impl PartialEq for SubresourceResponseBody {
|
||||
if Arc::ptr_eq(&self.inner, &other.inner) {
|
||||
return true;
|
||||
}
|
||||
match (self.inner.as_ref(), other.inner.as_ref()) {
|
||||
(
|
||||
SubresourceResponseBodyInner::Memory {
|
||||
text: left_text,
|
||||
bytes: left_bytes,
|
||||
},
|
||||
SubresourceResponseBodyInner::Memory {
|
||||
text: right_text,
|
||||
bytes: right_bytes,
|
||||
},
|
||||
) => left_text == right_text && left_bytes == right_bytes,
|
||||
_ => false,
|
||||
}
|
||||
let Some(left_bytes) = self.inner.in_memory_bytes() else {
|
||||
return false;
|
||||
};
|
||||
let Some(right_bytes) = other.inner.in_memory_bytes() else {
|
||||
return false;
|
||||
};
|
||||
left_bytes == right_bytes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1593,7 +1588,6 @@ impl SubresourceResponseBodyWriter {
|
||||
inner: Arc::new(SubresourceResponseBodyInner::File {
|
||||
path,
|
||||
len: self.len,
|
||||
text_cache: Mutex::new(None),
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -1603,10 +1597,7 @@ impl SubresourceResponseBodyWriter {
|
||||
if let Some(path) = self.path.take() {
|
||||
let _ = fs::remove_file(path);
|
||||
}
|
||||
SubresourceResponseBody::from_text_and_bytes(
|
||||
String::from_utf8_lossy(&self.memory).into_owned(),
|
||||
std::mem::take(&mut self.memory),
|
||||
)
|
||||
SubresourceResponseBody::from_bytes(std::mem::take(&mut self.memory))
|
||||
}
|
||||
|
||||
fn ensure_file(&mut self) -> io::Result<()> {
|
||||
@@ -1659,115 +1650,22 @@ impl Drop for SubresourceResponseBodyWriter {
|
||||
}
|
||||
|
||||
impl SubresourceResponseBody {
|
||||
pub fn from_text(text: String) -> Self {
|
||||
let bytes = text.as_bytes().to_vec();
|
||||
Self::from_text_and_bytes(text, bytes)
|
||||
}
|
||||
|
||||
pub fn from_text_and_bytes(text: String, bytes: Vec<u8>) -> Self {
|
||||
pub fn from_bytes(bytes: Vec<u8>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(SubresourceResponseBodyInner::Memory { text, bytes }),
|
||||
inner: Arc::new(SubresourceResponseBodyInner::Memory(bytes)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_materialized_body(body: ResponseBody) -> Self {
|
||||
let (text, bytes) = body
|
||||
.try_into_lossy_materialized_text()
|
||||
.expect("SubresourceResponseBody should be built from a materialized body");
|
||||
Self::from_text_and_bytes(text, bytes)
|
||||
}
|
||||
|
||||
/// Builds the neutral subresource body carrier from a materialized fetch
|
||||
/// response without exposing a loose `(String, Vec<u8>)` pair to callers.
|
||||
/// Copies the exact bytes from a materialized fetch response into the
|
||||
/// renderer-neutral subresource body carrier.
|
||||
pub fn from_fetch_response(response: &Response) -> Self {
|
||||
Self::from_materialized_body(response.materialized_body())
|
||||
Self::from_bytes(response.body_bytes().to_vec())
|
||||
}
|
||||
|
||||
/// Builds the neutral subresource body carrier from a materialized
|
||||
/// navigation response at the explicit compatibility boundary.
|
||||
/// Copies the exact bytes from a materialized navigation response into the
|
||||
/// renderer-neutral subresource body carrier.
|
||||
pub fn from_navigation_response(response: &NavigationResponse) -> Self {
|
||||
Self::from_materialized_body(response.materialized_body())
|
||||
}
|
||||
|
||||
/// Builds a materialized navigation response at a compatibility boundary
|
||||
/// that still needs both the lossy text view and exact bytes.
|
||||
pub fn to_navigation_response(&self, head: ResponseHead) -> NavigationResponse {
|
||||
self.diagnostic_navigation_response(head)
|
||||
}
|
||||
|
||||
/// Best-effort navigation response for diagnostics and legacy tests.
|
||||
/// Production protocol paths should use `try_to_navigation_response`.
|
||||
pub fn diagnostic_navigation_response(&self, head: ResponseHead) -> NavigationResponse {
|
||||
NavigationResponse::from_head_and_materialized_body(
|
||||
head,
|
||||
self.diagnostic_materialized_body(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Fallible variant of `to_navigation_response` for callers that can
|
||||
/// surface file-backed body source errors instead of treating them as an
|
||||
/// empty body.
|
||||
pub fn try_to_navigation_response(&self, head: ResponseHead) -> io::Result<NavigationResponse> {
|
||||
self.try_materialized_body()
|
||||
.map(|body| NavigationResponse::from_head_and_materialized_body(head, body))
|
||||
}
|
||||
|
||||
/// Builds a materialized response body at an explicit compatibility
|
||||
/// boundary. File-backed bodies are read once so callers that need both the
|
||||
/// text view and exact bytes do not duplicate spool I/O.
|
||||
pub fn materialized_body(&self) -> ResponseBody {
|
||||
self.diagnostic_materialized_body()
|
||||
}
|
||||
|
||||
/// Best-effort materialized body for diagnostics and legacy tests.
|
||||
/// Production protocol paths should use `try_materialized_body`.
|
||||
pub fn diagnostic_materialized_body(&self) -> ResponseBody {
|
||||
self.try_materialized_body()
|
||||
.unwrap_or_else(|_| ResponseBody::materialized_text(String::new(), Vec::new()))
|
||||
}
|
||||
|
||||
/// Fallible materialization for production paths that need to distinguish
|
||||
/// source read failure from a legitimate empty body.
|
||||
pub fn try_materialized_body(&self) -> io::Result<ResponseBody> {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { text, bytes } => {
|
||||
Ok(ResponseBody::materialized_text(text.clone(), bytes.clone()))
|
||||
}
|
||||
SubresourceResponseBodyInner::File { text_cache, .. } => {
|
||||
let bytes = self.materialize_bytes()?;
|
||||
let mut cache = text_cache.lock();
|
||||
let text = cache
|
||||
.get_or_insert_with(|| String::from_utf8_lossy(&bytes).into_owned())
|
||||
.clone();
|
||||
Ok(ResponseBody::materialized_text(text, bytes))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(&self) -> Cow<'_, str> {
|
||||
self.diagnostic_text()
|
||||
}
|
||||
|
||||
/// Best-effort text view for diagnostics and legacy tests. Production
|
||||
/// protocol paths should use `try_text` so source read failures remain
|
||||
/// visible instead of becoming an empty string.
|
||||
pub fn diagnostic_text(&self) -> Cow<'_, str> {
|
||||
self.try_text()
|
||||
.unwrap_or_else(|_| Cow::Owned(String::new()))
|
||||
}
|
||||
|
||||
pub fn try_text(&self) -> io::Result<Cow<'_, str>> {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { text, .. } => Ok(Cow::Borrowed(text)),
|
||||
SubresourceResponseBodyInner::File { text_cache, .. } => {
|
||||
let mut cache = text_cache.lock();
|
||||
if cache.is_none() {
|
||||
let bytes = self.materialize_bytes()?;
|
||||
*cache = Some(String::from_utf8_lossy(&bytes).into_owned());
|
||||
}
|
||||
Ok(Cow::Owned(cache.as_deref().unwrap_or_default().to_owned()))
|
||||
}
|
||||
}
|
||||
Self::from_bytes(response.body_bytes().to_vec())
|
||||
}
|
||||
|
||||
pub fn bytes(&self) -> Cow<'_, [u8]> {
|
||||
@@ -1782,7 +1680,7 @@ impl SubresourceResponseBody {
|
||||
|
||||
pub fn try_bytes(&self) -> io::Result<Cow<'_, [u8]>> {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { bytes, .. } => Ok(Cow::Borrowed(bytes)),
|
||||
SubresourceResponseBodyInner::Memory(bytes) => Ok(Cow::Borrowed(bytes)),
|
||||
SubresourceResponseBodyInner::File { .. } => self.materialize_bytes().map(Cow::Owned),
|
||||
}
|
||||
}
|
||||
@@ -1829,7 +1727,7 @@ impl SubresourceResponseBody {
|
||||
|
||||
pub fn materialize_bytes_from(&self, offset: usize) -> io::Result<Vec<u8>> {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { bytes, .. } => {
|
||||
SubresourceResponseBodyInner::Memory(bytes) => {
|
||||
Ok(bytes.get(offset..).map(<[u8]>::to_vec).unwrap_or_default())
|
||||
}
|
||||
SubresourceResponseBodyInner::File { path, len, .. } => {
|
||||
@@ -1850,7 +1748,7 @@ impl SubresourceResponseBody {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { bytes, .. } => {
|
||||
SubresourceResponseBodyInner::Memory(bytes) => {
|
||||
let Some(remaining) = bytes.get(offset..) else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
@@ -1873,7 +1771,7 @@ impl SubresourceResponseBody {
|
||||
|
||||
pub fn write_bytes_to<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { bytes, .. } => writer.write_all(bytes),
|
||||
SubresourceResponseBodyInner::Memory(bytes) => writer.write_all(bytes),
|
||||
SubresourceResponseBodyInner::File { path, .. } => {
|
||||
let mut file = File::open(path)?;
|
||||
let mut buffer = [0; 64 * 1024];
|
||||
@@ -1891,7 +1789,7 @@ impl SubresourceResponseBody {
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { bytes, .. } => bytes.len(),
|
||||
SubresourceResponseBodyInner::Memory(bytes) => bytes.len(),
|
||||
SubresourceResponseBodyInner::File { len, .. } => *len,
|
||||
}
|
||||
}
|
||||
@@ -1998,11 +1896,12 @@ impl SubresourceResponseWaitCriteria {
|
||||
return Ok(!self.requires_response_body());
|
||||
};
|
||||
|
||||
let response_body_text = if self.requires_response_body() {
|
||||
Some(response_body.try_text()?)
|
||||
let response_body_bytes = if self.requires_response_body() {
|
||||
Some(response_body.try_bytes()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let response_body_text = response_body_bytes.as_deref().map(String::from_utf8_lossy);
|
||||
|
||||
if let Some(needle) = self.body_contains.as_deref()
|
||||
&& !response_body_text
|
||||
@@ -2244,7 +2143,7 @@ impl SubresourceNetworkRecord {
|
||||
final_url,
|
||||
status,
|
||||
response_headers,
|
||||
SubresourceResponseBody::from_text(response_body),
|
||||
SubresourceResponseBody::from_bytes(response_body.into_bytes()),
|
||||
cookie_set_reports,
|
||||
)
|
||||
}
|
||||
@@ -3852,7 +3751,7 @@ mod tests {
|
||||
);
|
||||
let body = SubresourceBodyFinished::ready(
|
||||
handle,
|
||||
SubresourceResponseBody::from_text_and_bytes(String::new(), vec![1, 2, 3]),
|
||||
SubresourceResponseBody::from_bytes(vec![1, 2, 3]),
|
||||
);
|
||||
let mut report = ScriptExecutionReport::default();
|
||||
report.extend_network_output(ScriptNetworkOutput::from_items([
|
||||
@@ -4059,6 +3958,55 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subresource_response_body_stores_only_exact_response_bytes() {
|
||||
let response = Response::from_head_and_body(
|
||||
ResponseHead {
|
||||
final_url: test_url("/utf8.txt"),
|
||||
status: 200,
|
||||
headers: Vec::new(),
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
redirected: false,
|
||||
redirect_chain: Vec::new(),
|
||||
from_cache: false,
|
||||
negotiated_http_version: None,
|
||||
},
|
||||
"hello".to_owned(),
|
||||
b"hello".to_vec(),
|
||||
);
|
||||
let body = SubresourceResponseBody::from_fetch_response(&response);
|
||||
let SubresourceResponseBodyInner::Memory(bytes) = body.inner.as_ref() else {
|
||||
panic!("fetch response should use in-memory byte storage");
|
||||
};
|
||||
|
||||
assert_eq!(bytes, b"hello");
|
||||
assert_eq!(body.try_bytes().unwrap().as_ref(), b"hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subresource_response_body_preserves_non_utf8_response_bytes() {
|
||||
let response = Response::from_head_and_body(
|
||||
ResponseHead {
|
||||
final_url: test_url("/legacy.txt"),
|
||||
status: 200,
|
||||
headers: Vec::new(),
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
redirected: false,
|
||||
redirect_chain: Vec::new(),
|
||||
from_cache: false,
|
||||
negotiated_http_version: None,
|
||||
},
|
||||
"é".to_owned(),
|
||||
vec![0xe9],
|
||||
);
|
||||
let body = SubresourceResponseBody::from_fetch_response(&response);
|
||||
|
||||
assert_eq!(body.try_bytes().unwrap().as_ref(), &[0xe9]);
|
||||
assert_eq!(String::from_utf8_lossy(&body.try_bytes().unwrap()), "�");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subresource_response_body_writer_keeps_small_body_in_memory() {
|
||||
let mut writer = SubresourceResponseBodyWriter::new(16);
|
||||
@@ -4067,7 +4015,7 @@ mod tests {
|
||||
let body = writer.finish();
|
||||
|
||||
assert_eq!(body.len(), 11);
|
||||
assert_eq!(body.diagnostic_text(), "hello world");
|
||||
assert_eq!(body.try_bytes().unwrap().as_ref(), b"hello world");
|
||||
assert_eq!(body.clone_body_bytes(), b"hello world");
|
||||
}
|
||||
|
||||
@@ -4079,7 +4027,7 @@ mod tests {
|
||||
let body = writer.finish();
|
||||
|
||||
assert_eq!(body.len(), 11);
|
||||
assert_eq!(body.diagnostic_text(), "hello world");
|
||||
assert_eq!(body.try_bytes().unwrap().as_ref(), b"hello world");
|
||||
|
||||
let mut copied = Vec::new();
|
||||
body.write_bytes_to(&mut copied)
|
||||
@@ -4089,10 +4037,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn subresource_response_body_reads_memory_and_spooled_chunks_by_offset() {
|
||||
let memory = SubresourceResponseBody::from_text_and_bytes(
|
||||
"hello world".to_owned(),
|
||||
b"hello world".to_vec(),
|
||||
);
|
||||
let memory = SubresourceResponseBody::from_bytes(b"hello world".to_vec());
|
||||
assert_eq!(memory.read_chunk(6, 3).unwrap(), b"wor");
|
||||
assert_eq!(memory.materialize_bytes_from(6).unwrap(), b"world");
|
||||
assert_eq!(memory.diagnostic_clone_body_bytes_from(6), b"world");
|
||||
@@ -4117,35 +4062,13 @@ mod tests {
|
||||
inner: Arc::new(SubresourceResponseBodyInner::File {
|
||||
path: missing_path,
|
||||
len: 5,
|
||||
text_cache: Mutex::new(None),
|
||||
}),
|
||||
};
|
||||
|
||||
assert!(body.materialize_bytes().is_err());
|
||||
assert!(body.materialize_bytes_from(2).is_err());
|
||||
assert!(body.try_materialized_body().is_err());
|
||||
assert!(body.try_text().is_err());
|
||||
assert!(body.try_bytes().is_err());
|
||||
assert!(
|
||||
body.try_to_navigation_response(ResponseHead {
|
||||
final_url: Url::parse("https://example.test/missing").unwrap(),
|
||||
status: 200,
|
||||
headers: Vec::new(),
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
redirected: false,
|
||||
redirect_chain: Vec::new(),
|
||||
from_cache: false,
|
||||
negotiated_http_version: None,
|
||||
})
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(body.diagnostic_clone_body_bytes(), Vec::<u8>::new());
|
||||
assert_eq!(
|
||||
body.diagnostic_materialized_body().as_materialized_bytes(),
|
||||
Some(&[][..])
|
||||
);
|
||||
assert_eq!(body.diagnostic_text(), "");
|
||||
assert_eq!(body.diagnostic_bytes().as_ref(), &[] as &[u8]);
|
||||
|
||||
let record = SubresourceNetworkRecord::success_with_body(
|
||||
@@ -4231,10 +4154,7 @@ mod tests {
|
||||
Url::parse("https://example.test/api").unwrap(),
|
||||
200,
|
||||
vec![("content-type".to_owned(), "text/plain".to_owned())],
|
||||
SubresourceResponseBody::from_text_and_bytes(
|
||||
"order #42 ready".to_owned(),
|
||||
b"order #42 ready".to_vec(),
|
||||
),
|
||||
SubresourceResponseBody::from_bytes(b"order #42 ready".to_vec()),
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
@@ -4334,14 +4254,12 @@ mod tests {
|
||||
inner: Arc::new(SubresourceResponseBodyInner::File {
|
||||
path: missing_path_a,
|
||||
len: 5,
|
||||
text_cache: Mutex::new(None),
|
||||
}),
|
||||
};
|
||||
let body_b = SubresourceResponseBody {
|
||||
inner: Arc::new(SubresourceResponseBodyInner::File {
|
||||
path: missing_path_b,
|
||||
len: 5,
|
||||
text_cache: Mutex::new(None),
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -4427,28 +4345,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subresource_response_body_to_navigation_response_materializes_spooled_body_once() {
|
||||
fn subresource_response_body_preserves_invalid_bytes_when_spooled() {
|
||||
let mut writer = SubresourceResponseBodyWriter::new(2);
|
||||
writer.append(b"hi ");
|
||||
writer.append(&[0xff, b'!']);
|
||||
let body = writer.finish();
|
||||
let navigation = body.diagnostic_navigation_response(ResponseHead {
|
||||
final_url: Url::parse("https://example.test/data").unwrap(),
|
||||
status: 200,
|
||||
headers: vec![(
|
||||
"content-type".to_owned(),
|
||||
"application/octet-stream".to_owned(),
|
||||
)],
|
||||
request_cookie_report: None,
|
||||
cookie_set_reports: Vec::new(),
|
||||
redirected: false,
|
||||
redirect_chain: Vec::new(),
|
||||
from_cache: false,
|
||||
negotiated_http_version: None,
|
||||
});
|
||||
|
||||
assert_eq!(navigation.body_bytes(), &[b'h', b'i', b' ', 0xff, b'!']);
|
||||
assert_eq!(navigation.body_text(), "hi \u{fffd}!");
|
||||
assert_eq!(
|
||||
body.materialize_bytes().unwrap(),
|
||||
&[b'h', b'i', b' ', 0xff, b'!']
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -124,23 +124,13 @@ impl SubresourceResponseBody {
|
||||
#[doc(hidden)]
|
||||
pub fn renderer_transport_retained_memory_bytes(&self) -> usize {
|
||||
match self.inner.as_ref() {
|
||||
SubresourceResponseBodyInner::Memory { text, bytes } => text
|
||||
SubresourceResponseBodyInner::Memory(bytes) => bytes
|
||||
.capacity()
|
||||
.saturating_add(bytes.capacity())
|
||||
.saturating_add(std::mem::size_of::<SubresourceResponseBodyInner>()),
|
||||
SubresourceResponseBodyInner::File {
|
||||
path, text_cache, ..
|
||||
} => path
|
||||
SubresourceResponseBodyInner::File { path, .. } => path
|
||||
.as_os_str()
|
||||
.len()
|
||||
.saturating_mul(2)
|
||||
.saturating_add(
|
||||
text_cache
|
||||
.lock()
|
||||
.as_ref()
|
||||
.map(String::capacity)
|
||||
.unwrap_or(0),
|
||||
)
|
||||
.saturating_add(std::mem::size_of::<SubresourceResponseBodyInner>()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,10 +558,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn captured_body_from_subresource_response_body_reads_shared_source() -> Result<()> {
|
||||
let subresource_body = moli_core::page::SubresourceResponseBody::from_text_and_bytes(
|
||||
"hello world".to_owned(),
|
||||
b"hello world".to_vec(),
|
||||
);
|
||||
let subresource_body =
|
||||
moli_core::page::SubresourceResponseBody::from_bytes(b"hello world".to_vec());
|
||||
let body = CapturedBody::from_subresource_response_body(&subresource_body);
|
||||
|
||||
assert!(matches!(
|
||||
|
||||
@@ -622,7 +622,7 @@ mod tests {
|
||||
network_request_headers: None,
|
||||
response_status: 200,
|
||||
response_headers: vec![("content-type".to_owned(), "text/plain".to_owned())],
|
||||
response_body: SubresourceResponseBody::from_text("prepared".to_owned()),
|
||||
response_body: SubresourceResponseBody::from_bytes(b"prepared".to_vec()),
|
||||
from_cache: false,
|
||||
});
|
||||
let action = crate::domains::activity::PreparedSubresourceContinueAction::capture_for_test(
|
||||
@@ -708,7 +708,7 @@ mod tests {
|
||||
network_request_headers: None,
|
||||
response_status: 200,
|
||||
response_headers: Vec::new(),
|
||||
response_body: SubresourceResponseBody::from_text("old".to_owned()),
|
||||
response_body: SubresourceResponseBody::from_bytes(b"old".to_vec()),
|
||||
from_cache: false,
|
||||
});
|
||||
let old_action =
|
||||
|
||||
@@ -1170,7 +1170,7 @@ mod tests {
|
||||
request_url.clone(),
|
||||
200,
|
||||
Vec::new(),
|
||||
SubresourceResponseBody::from_text("complete".to_owned()),
|
||||
SubresourceResponseBody::from_bytes(b"complete".to_vec()),
|
||||
Vec::new(),
|
||||
);
|
||||
assert_xhr_terminal_retains_internal_resource_type(vec![
|
||||
@@ -1200,7 +1200,7 @@ mod tests {
|
||||
);
|
||||
let body = SubresourceBodyFinished::ready(
|
||||
handle,
|
||||
SubresourceResponseBody::from_text("staged".to_owned()),
|
||||
SubresourceResponseBody::from_bytes(b"staged".to_vec()),
|
||||
);
|
||||
assert_xhr_terminal_retains_internal_resource_type(vec![
|
||||
ScriptNetworkOutputItem::SubresourceRequestStarted(Box::new(request)),
|
||||
|
||||
@@ -3363,7 +3363,7 @@ mod tests {
|
||||
.with_from_cache(true);
|
||||
let body = SubresourceBodyFinished::ready(
|
||||
handle,
|
||||
SubresourceResponseBody::from_text_and_bytes(String::new(), vec![1, 2, 3]),
|
||||
SubresourceResponseBody::from_bytes(vec![1, 2, 3]),
|
||||
);
|
||||
let items = vec![
|
||||
ScriptNetworkOutputItem::SubresourceRequestStarted(Box::new(request)),
|
||||
@@ -3759,7 +3759,7 @@ mod tests {
|
||||
delivery_order_index,
|
||||
index,
|
||||
loader_id: "LOADER-1".to_owned(),
|
||||
response_body: Some(SubresourceResponseBody::from_text(String::new())),
|
||||
response_body: Some(SubresourceResponseBody::from_bytes(Vec::new())),
|
||||
request_handle: None,
|
||||
websocket_socket_id: (resource_type == SubresourceResourceType::WebSocket).then_some(7),
|
||||
frame_id: None,
|
||||
@@ -4286,8 +4286,9 @@ mod tests {
|
||||
success_output
|
||||
.response_body()
|
||||
.expect("success response should retain a body source")
|
||||
.diagnostic_text(),
|
||||
"api-body"
|
||||
.diagnostic_bytes()
|
||||
.as_ref(),
|
||||
b"api-body"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4779,8 +4780,8 @@ mod tests {
|
||||
.expect("first subresource output should exist");
|
||||
subresource_output.url =
|
||||
Url::parse("https://example.com/mutated.js").expect("test URL should parse");
|
||||
subresource_output.response_body = Some(SubresourceResponseBody::from_text(
|
||||
"mutated-body".to_owned(),
|
||||
subresource_output.response_body = Some(SubresourceResponseBody::from_bytes(
|
||||
b"mutated-body".to_vec(),
|
||||
));
|
||||
let TargetWebSocketDeliveryPlanRecord::Handshake(handshake) = output_queue
|
||||
.delivery_outputs
|
||||
@@ -4828,8 +4829,9 @@ mod tests {
|
||||
.metadata()
|
||||
.response_body()
|
||||
.expect("prepared success output should own its response body")
|
||||
.diagnostic_text(),
|
||||
"prepared-body",
|
||||
.diagnostic_bytes()
|
||||
.as_ref(),
|
||||
b"prepared-body",
|
||||
"prepared subresource token must own prepare-time body source instead of rereading queue slots"
|
||||
);
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ async fn get_response_body_returns_partial_body_after_staged_loading_failed() {
|
||||
let body = SubresourceBodyFinished::failed_with_partial_body(
|
||||
handle,
|
||||
"net::ERR_ABORTED".to_owned(),
|
||||
SubresourceResponseBody::from_text("partial body".to_owned()),
|
||||
SubresourceResponseBody::from_bytes(b"partial body".to_vec()),
|
||||
);
|
||||
let items = vec![
|
||||
ScriptNetworkOutputItem::SubresourceRequestStarted(Box::new(request)),
|
||||
|
||||
@@ -4114,10 +4114,9 @@ mod producer_tests {
|
||||
status: 200,
|
||||
response_headers: vec![("Content-Type".to_owned(), "text/html".to_owned())],
|
||||
encoded_data_length: 3,
|
||||
response_body: Some(SubresourceResponseBody::from_text_and_bytes(
|
||||
"\0\u{fffd}a".to_owned(),
|
||||
vec![0x00, 0xff, b'a'],
|
||||
)),
|
||||
response_body: Some(SubresourceResponseBody::from_bytes(vec![
|
||||
0x00, 0xff, b'a',
|
||||
])),
|
||||
from_cache: true,
|
||||
}),
|
||||
}],
|
||||
@@ -4251,8 +4250,8 @@ mod producer_tests {
|
||||
status: 200,
|
||||
response_headers: vec![("Content-Type".to_owned(), "text/html".to_owned())],
|
||||
encoded_data_length: 21,
|
||||
response_body: Some(SubresourceResponseBody::from_text(
|
||||
"historical child body".to_owned(),
|
||||
response_body: Some(SubresourceResponseBody::from_bytes(
|
||||
b"historical child body".to_vec(),
|
||||
)),
|
||||
from_cache: false,
|
||||
},
|
||||
|
||||
@@ -303,10 +303,7 @@ impl RendererPreparedAppManifestLoad {
|
||||
false,
|
||||
)
|
||||
};
|
||||
let response_body = SubresourceResponseBody::from_text_and_bytes(
|
||||
String::from_utf8_lossy(&response.body).into_owned(),
|
||||
response.body,
|
||||
);
|
||||
let response_body = SubresourceResponseBody::from_bytes(response.body);
|
||||
let record = SubresourceNetworkRecord::success_with_body(
|
||||
frame_id,
|
||||
document_url.clone(),
|
||||
|
||||
@@ -743,7 +743,10 @@ fn child_document_load_outcome_from_response(
|
||||
if child_document_response_should_ignore_navigation(head.status, &head.headers) {
|
||||
return Ok(ChildDocumentLoadOutcome::IgnoredNavigation);
|
||||
}
|
||||
let response_body = SubresourceResponseBody::from_materialized_body(body);
|
||||
let response_body = SubresourceResponseBody::from_bytes(
|
||||
body.try_into_materialized_bytes()
|
||||
.map_err(|_| "child document response body should remain materialized".to_owned())?,
|
||||
);
|
||||
let encoded_data_length = response_body.len();
|
||||
let content_type = child_document_content_type_from_headers(&head.headers)
|
||||
.or_else(|| child_document_content_type_for_url(&head.final_url));
|
||||
|
||||
@@ -28,6 +28,17 @@ enum ImageSubresourceFetchRegistration {
|
||||
Dispatched(moli_fetch::FetchCancelHandle),
|
||||
}
|
||||
|
||||
fn navigation_response_from_subresource_body(
|
||||
body: &SubresourceResponseBody,
|
||||
head: moli_fetch::ResponseHead,
|
||||
) -> crate::types::NavigationResponse {
|
||||
let bytes = body.materialize_bytes().unwrap_or_default();
|
||||
crate::types::NavigationResponse::from_head_and_materialized_body(
|
||||
head,
|
||||
moli_fetch::ResponseBody::materialized_bytes(bytes),
|
||||
)
|
||||
}
|
||||
|
||||
impl JsContextHost {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn has_pending_load_event_delaying_subresource_requests(&self) -> bool {
|
||||
@@ -2795,9 +2806,9 @@ impl JsContextHost {
|
||||
request_method: in_flight.request_method,
|
||||
request_headers: in_flight.request_headers,
|
||||
request_body: in_flight.request_body,
|
||||
response: info
|
||||
.response_body
|
||||
.to_navigation_response(moli_fetch::ResponseHead {
|
||||
response: navigation_response_from_subresource_body(
|
||||
&info.response_body,
|
||||
moli_fetch::ResponseHead {
|
||||
final_url: info.url.clone(),
|
||||
status: info.response_status,
|
||||
headers: info.response_headers.clone(),
|
||||
@@ -2807,8 +2818,9 @@ impl JsContextHost {
|
||||
redirect_chain: Vec::new(),
|
||||
from_cache: info.from_cache,
|
||||
negotiated_http_version: None,
|
||||
})
|
||||
.with_network_request_headers(info.network_request_headers.clone()),
|
||||
},
|
||||
)
|
||||
.with_network_request_headers(info.network_request_headers.clone()),
|
||||
});
|
||||
self.record_pending_subresource_continue_event(
|
||||
PendingSubresourceContinueEvent::ResponsePaused(info),
|
||||
@@ -2838,9 +2850,9 @@ impl JsContextHost {
|
||||
request_body: in_flight.request_body,
|
||||
intercept_response: info.intercept_response,
|
||||
initial_network_request_headers: info.network_request_headers.clone(),
|
||||
response: info
|
||||
.response_body
|
||||
.to_navigation_response(moli_fetch::ResponseHead {
|
||||
response: navigation_response_from_subresource_body(
|
||||
&info.response_body,
|
||||
moli_fetch::ResponseHead {
|
||||
final_url: info.response_final_url.clone(),
|
||||
status: info.response_status,
|
||||
headers: info.response_headers.clone(),
|
||||
@@ -2850,8 +2862,9 @@ impl JsContextHost {
|
||||
redirect_chain: Vec::new(),
|
||||
from_cache: info.response_from_cache,
|
||||
negotiated_http_version: None,
|
||||
})
|
||||
.with_network_request_headers(info.network_request_headers.clone()),
|
||||
},
|
||||
)
|
||||
.with_network_request_headers(info.network_request_headers.clone()),
|
||||
});
|
||||
self.record_pending_subresource_continue_event(
|
||||
PendingSubresourceContinueEvent::AuthRequired(info),
|
||||
|
||||
@@ -255,7 +255,7 @@ impl JsContextHost {
|
||||
network_request_headers: None,
|
||||
response_status,
|
||||
response_headers,
|
||||
response_body: SubresourceResponseBody::from_text(String::new()),
|
||||
response_body: SubresourceResponseBody::from_bytes(Vec::new()),
|
||||
from_cache: false,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -2057,10 +2057,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn network_body_source_subresource_reads_through_fallible_source() {
|
||||
let body = SubresourceResponseBody::from_text_and_bytes(
|
||||
"hello world".to_owned(),
|
||||
b"hello world".to_vec(),
|
||||
);
|
||||
let body = SubresourceResponseBody::from_bytes(b"hello world".to_vec());
|
||||
let id = register_network_body_subresource_body(body);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -50,7 +50,7 @@ impl CorsPreflightNetworkObserver {
|
||||
response.final_url.clone(),
|
||||
response.status,
|
||||
response.headers.clone(),
|
||||
SubresourceResponseBody::from_text(String::new()),
|
||||
SubresourceResponseBody::from_bytes(Vec::new()),
|
||||
response.cookie_set_reports.clone(),
|
||||
)
|
||||
.with_from_cache(response.from_cache)
|
||||
|
||||
@@ -102,9 +102,7 @@ impl RendererSyntheticResponseBody {
|
||||
/// Converts a synthetic fulfill body into the renderer-neutral subresource
|
||||
/// record body without reopening a loose text/byte pair at each call site.
|
||||
pub fn into_subresource_response_body(self) -> crate::protocol_types::SubresourceResponseBody {
|
||||
crate::protocol_types::SubresourceResponseBody::from_materialized_body(
|
||||
self.into_response_body(),
|
||||
)
|
||||
crate::protocol_types::SubresourceResponseBody::from_bytes(self.into_body_bytes())
|
||||
}
|
||||
|
||||
/// Builds a materialized fetch response for worker/Web compatibility paths
|
||||
|
||||
@@ -5539,14 +5539,14 @@ impl ScriptVm {
|
||||
) {
|
||||
let materialize_started =
|
||||
moli_trace::cdp_runtime_trace_enabled().then(Instant::now);
|
||||
match response_body.try_materialized_body() {
|
||||
Ok(body) => {
|
||||
match response_body.materialize_bytes() {
|
||||
Ok(bytes) => {
|
||||
trace_async_subresource_stage(
|
||||
"async_subresource_streaming_xhr_body_materialized",
|
||||
trace_fields,
|
||||
materialize_started,
|
||||
);
|
||||
Some(body)
|
||||
Some(moli_fetch::ResponseBody::materialized_bytes(bytes))
|
||||
}
|
||||
Err(error) => {
|
||||
let error_text = format!(
|
||||
|
||||
@@ -63,7 +63,7 @@ fn service_worker_csp_report_seen(
|
||||
&& matches!(
|
||||
body.result(),
|
||||
crate::types::SubresourceBodyFinishedResult::Ready(response_body)
|
||||
if response_body.diagnostic_text() == expected_body
|
||||
if response_body.diagnostic_bytes().as_ref() == expected_body.as_bytes()
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -82,7 +82,7 @@ fn service_worker_csp_report_seen(
|
||||
response_body,
|
||||
..
|
||||
} if final_url.as_str() == report_url
|
||||
&& response_body.diagnostic_text() == expected_body
|
||||
&& response_body.diagnostic_bytes().as_ref() == expected_body.as_bytes()
|
||||
)
|
||||
)
|
||||
});
|
||||
@@ -12993,7 +12993,7 @@ async fn navigator_service_worker_intercepts_element_resource_destinations_once(
|
||||
status: 200,
|
||||
response_body,
|
||||
..
|
||||
} if response_body.diagnostic_text() == expected_body
|
||||
} if response_body.diagnostic_bytes().as_ref() == expected_body.as_bytes()
|
||||
)
|
||||
)
|
||||
}) {
|
||||
@@ -13025,7 +13025,7 @@ async fn navigator_service_worker_intercepts_element_resource_destinations_once(
|
||||
&& matches!(
|
||||
body.result(),
|
||||
crate::types::SubresourceBodyFinishedResult::Ready(response_body)
|
||||
if response_body.diagnostic_text() == expected_body
|
||||
if response_body.diagnostic_bytes().as_ref() == expected_body.as_bytes()
|
||||
)
|
||||
)
|
||||
})
|
||||
@@ -13236,8 +13236,8 @@ async fn navigator_service_worker_intercepts_stylesheet_font_face_destination()
|
||||
response_body,
|
||||
..
|
||||
} if final_url.as_str() == expected_font_url
|
||||
&& response_body.diagnostic_text()
|
||||
== "stylesheet-font:font:/app/fonts/demo.woff2"
|
||||
&& response_body.diagnostic_bytes().as_ref()
|
||||
== b"stylesheet-font:font:/app/fonts/demo.woff2"
|
||||
)
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2316,7 +2316,7 @@ async fn streaming_fetch_body_error_records_response_started_then_body_failed()
|
||||
partial_body,
|
||||
} => {
|
||||
assert_eq!(error_text, crate::network_host::ABORTED_ERROR_TEXT);
|
||||
assert_eq!(partial_body.diagnostic_text(), "partial");
|
||||
assert_eq!(partial_body.diagnostic_bytes().as_ref(), b"partial");
|
||||
}
|
||||
other => panic!("expected failed body with partial payload, got {other:?}"),
|
||||
}
|
||||
@@ -2679,7 +2679,7 @@ async fn streaming_fetch_body_cancel_records_response_started_then_body_failed()
|
||||
partial_body,
|
||||
} => {
|
||||
assert_eq!(error_text, crate::network_host::ABORTED_ERROR_TEXT);
|
||||
assert_eq!(partial_body.diagnostic_text(), "partial");
|
||||
assert_eq!(partial_body.diagnostic_bytes().as_ref(), b"partial");
|
||||
}
|
||||
other => panic!("expected failed body with partial payload, got {other:?}"),
|
||||
}
|
||||
|
||||
@@ -2648,7 +2648,10 @@ fn cancel_pending_window_fetch_auth_preserves_401_for_response_stage() {
|
||||
};
|
||||
assert_eq!(info.internal_id, internal_id);
|
||||
assert_eq!(info.response_status, 401);
|
||||
assert_eq!(info.response_body.text(), "auth required");
|
||||
assert_eq!(
|
||||
info.response_body.try_bytes().unwrap().as_ref(),
|
||||
b"auth required"
|
||||
);
|
||||
|
||||
let pending = vm
|
||||
._context_host
|
||||
|
||||
@@ -2914,7 +2914,7 @@ pub(in crate::worker) fn drain_worker_fetch_completion_result(
|
||||
WorkerFetchResponseParts::Subresource { mut head, body } => {
|
||||
head.headers = filtered_headers;
|
||||
let body = if opaque_response_blocked {
|
||||
SubresourceResponseBody::from_text(String::new())
|
||||
SubresourceResponseBody::from_bytes(Vec::new())
|
||||
} else {
|
||||
body
|
||||
};
|
||||
|
||||
@@ -1266,8 +1266,8 @@ impl WorkerXhrResponse {
|
||||
match self {
|
||||
Self::Materialized(response) => Ok(response.into_body()),
|
||||
Self::Streamed { head, body } => body
|
||||
.try_materialized_body()
|
||||
.map(|body| (*head, body))
|
||||
.materialize_bytes()
|
||||
.map(|bytes| (*head, ResponseBody::materialized_bytes(bytes)))
|
||||
.map_err(|error| format!("failed to materialize worker XHR body: {error}")),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3081,7 +3081,10 @@ async fn worker_xhr_response_stage_interception_pauses_before_done() {
|
||||
assert_eq!(info.internal_id, 37);
|
||||
assert_eq!(info.resource_type, SubresourceResourceType::Xhr);
|
||||
assert_eq!(info.response_status, 200);
|
||||
assert_eq!(info.response_body.text().as_ref(), "origin-worker-xhr");
|
||||
assert_eq!(
|
||||
info.response_body.try_bytes().unwrap().as_ref(),
|
||||
b"origin-worker-xhr"
|
||||
);
|
||||
|
||||
handle.continue_pending_xhr_response(
|
||||
request,
|
||||
@@ -4207,7 +4210,10 @@ async fn worker_fetch_response_stage_interception_pauses_before_resolving_respon
|
||||
};
|
||||
assert_eq!(info.internal_id, 23);
|
||||
assert_eq!(info.response_status, 200);
|
||||
assert_eq!(info.response_body.text().as_ref(), "origin-worker-body");
|
||||
assert_eq!(
|
||||
info.response_body.try_bytes().unwrap().as_ref(),
|
||||
b"origin-worker-body"
|
||||
);
|
||||
|
||||
handle.continue_pending_fetch_response(
|
||||
request,
|
||||
|
||||
@@ -375,7 +375,8 @@ fn render_subresource_network_record_with_body_option(
|
||||
payload.insert("body_length".to_owned(), json!(response_body.len()));
|
||||
// Trace diagnostics are textual hints. Keep exact protocol bytes in
|
||||
// the subresource carrier and derive the lossy view only here.
|
||||
let response_body_text = response_body.diagnostic_text();
|
||||
let response_body_bytes = response_body.diagnostic_bytes();
|
||||
let response_body_text = String::from_utf8_lossy(response_body_bytes.as_ref());
|
||||
if include_body_text {
|
||||
payload.insert("body_text".to_owned(), json!(response_body_text.as_ref()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user