perf(fetch): avoid copying response bytes for module consumers

Use byte-only decomposition for Response and NavigationResponse instead of
discarding the text returned by into_parts(). Module, dedicated worker, and
service worker consumers now transfer the existing exact byte allocation.

Verify storage transfer for UTF-8 and non-UTF-8 payloads.
This commit is contained in:
ldm0
2026-09-13 17:03:02 +08:00
parent 55cb96873c
commit e32030e484
6 changed files with 72 additions and 3 deletions
+10
View File
@@ -419,6 +419,16 @@ impl Response {
(head, body)
}
/// Consumes a materialized response and transfers its exact byte payload
/// without retaining the text view or copying UTF-8 text storage.
pub fn into_byte_parts(self) -> (ResponseHead, Vec<u8>) {
let (head, body) = self.into_body();
let bytes = body
.try_into_materialized_bytes()
.expect("Response body should remain materialized");
(head, bytes)
}
pub fn into_body(self) -> (ResponseHead, ResponseBody) {
let head = ResponseHead {
final_url: self.final_url,
+29
View File
@@ -288,6 +288,35 @@ fn response_body_keeps_invalid_utf8_bytes_beside_lossy_text() {
));
}
#[test]
fn response_byte_parts_transfer_utf8_storage_without_copying() {
let head = sample_response_head();
let bytes = b"module source".to_vec();
let storage = bytes.as_ptr();
let response = Response::from_head_and_lossy_body_bytes(head.clone(), bytes);
let (split_head, bytes) = response.into_byte_parts();
assert_eq!(split_head.final_url, head.final_url);
assert_eq!(split_head.status, head.status);
assert_eq!(split_head.headers, head.headers);
assert_eq!(bytes, b"module source");
assert_eq!(bytes.as_ptr(), storage, "UTF-8 storage must be transferred");
}
#[test]
fn response_byte_parts_preserve_exact_non_utf8_storage() {
let bytes = vec![b'a', 0xff, b'b'];
let storage = bytes.as_ptr();
let response = Response::from_head_and_lossy_body_bytes(sample_response_head(), bytes);
assert_eq!(response.body_text(), "a\u{fffd}b");
let (_, bytes) = response.into_byte_parts();
assert_eq!(bytes, [b'a', 0xff, b'b']);
assert_eq!(bytes.as_ptr(), storage, "exact bytes must be transferred");
}
#[tokio::test]
async fn response_body_materializes_streaming_text_source() {
let (body_tx, body_rx) = mpsc::unbounded_channel();
+30
View File
@@ -492,6 +492,16 @@ impl NavigationResponse {
(head, body)
}
/// Consumes a materialized response and transfers its exact byte payload
/// without retaining the text view or copying UTF-8 text storage.
pub fn into_byte_parts(self) -> (ResponseHead, Vec<u8>) {
let (head, body) = self.into_body();
let bytes = body
.try_into_materialized_bytes()
.expect("NavigationResponse body should remain materialized");
(head, bytes)
}
pub fn into_body(self) -> (ResponseHead, ResponseBody) {
let head = ResponseHead {
final_url: self.final_url,
@@ -3419,6 +3429,26 @@ mod tests {
Url::parse(&format!("https://example.test{path}")).expect("test URL should parse")
}
#[test]
fn navigation_response_byte_parts_transfer_exact_storage() {
let template =
NavigationResponse::from_text_body(test_url("/module"), 200, Vec::new(), String::new());
for bytes in [b"module source".to_vec(), vec![b'a', 0xff, b'b']] {
let expected = bytes.clone();
let storage = bytes.as_ptr();
let response = NavigationResponse::from_head_and_materialized_body(
template.head(),
ResponseBody::lossy_text_from_bytes(bytes),
);
let (head, bytes) = response.into_byte_parts();
assert_eq!(head.final_url, template.final_url);
assert_eq!(bytes, expected);
assert_eq!(bytes.as_ptr(), storage, "exact bytes must be transferred");
}
}
#[test]
fn script_execution_report_exposes_globals_snapshot_freshness() {
let mut report = ScriptExecutionReport::default();
+1 -1
View File
@@ -468,7 +468,7 @@ impl NativeModuleGraphFetchRequest {
crate::referrer_policy::response_referrer_policy_from_headers(
&response.headers,
);
let (head, _, body_bytes) = response.into_parts();
let (head, body_bytes) = response.into_byte_parts();
crate::network_host::validate_cors_response_chain(
&initiator_url,
&head,
@@ -359,7 +359,7 @@ fn load_imported_script_resource_for_update_check(
moli_fetch::ensure_http_status_success(response.final_url.as_str(), response.status, false)
.map_err(|error| error.to_string())?;
ensure_imported_script_resource_mime(kind, &response)?;
let (head, _body, body_bytes) = response.into_parts();
let (head, body_bytes) = response.into_byte_parts();
let mut resource = ServiceWorkerScriptResource::from_response_parts(
request_url_without_fragment,
&head,
+1 -1
View File
@@ -757,7 +757,7 @@ fn start_worker_module_graph_fetch(
.elapsed()
.as_millis()
.min(u64::MAX as u128) as u64;
let (head, _body, body_bytes) = response.into_parts();
let (head, body_bytes) = response.into_byte_parts();
let response_referrer_policy =
crate::referrer_policy::response_referrer_policy_from_headers(&head.headers);
let resource = WorkerScriptResource::from_response_parts(