Files
servo/components/net/fetch/fetch_params.rs
Tim van der Lippe de27dc69e5 Implement pending preload responses with futures (#40059)
This implements waiting for pending preloads, where the preload request
is still fetching the result when the second "real" request is started.
It is
implemented by storing responses in the `SharedPreloadedResources`
which is communicated via `PreloadId` send to the `CoreResourceManager`.

Part of #35035

---------

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
Signed-off-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
2026-01-04 12:22:54 +00:00

133 lines
5.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::sync::{Arc, Mutex};
use content_security_policy as csp;
use net_traits::request::{PreloadEntry, PreloadId, PreloadKey, Request, RequestClient};
use net_traits::response::Response;
use rustc_hash::FxHashMap;
use tokio::sync::oneshot::Receiver as TokioReceiver;
/// <https://fetch.spec.whatwg.org/#fetch-params-preloaded-response-candidate>
pub enum PreloadResponseCandidate {
None,
Pending(TokioReceiver<Response>, PreloadId),
Response(Box<Response>, PreloadId),
}
impl PreloadResponseCandidate {
/// Part of Step 12 of <https://fetch.spec.whatwg.org/#concept-main-fetch>
pub(crate) async fn response(&mut self) -> Option<(Response, PreloadId)> {
// Step 3. Return fetchParamss preloaded response candidate.
match self {
PreloadResponseCandidate::None => None,
// Step 2. Assert: fetchParamss preloaded response candidate is a response.
PreloadResponseCandidate::Response(response, preload_id) => {
Some((*response.clone(), preload_id.clone()))
},
// Step 1. Wait until fetchParamss preloaded response candidate is not "pending".
PreloadResponseCandidate::Pending(receiver, preload_id) => receiver
.await
.ok()
.map(|response| (response, preload_id.clone())),
}
}
}
/// <https://fetch.spec.whatwg.org/#fetch-params>
pub struct FetchParams {
/// <https://fetch.spec.whatwg.org/#fetch-params-request>
pub request: Request,
/// <https://fetch.spec.whatwg.org/#fetch-params-preloaded-response-candidate>
pub preload_response_candidate: PreloadResponseCandidate,
}
impl FetchParams {
pub fn new(request: Request) -> FetchParams {
FetchParams {
request,
preload_response_candidate: PreloadResponseCandidate::None,
}
}
}
pub type SharedPreloadedResources = Arc<Mutex<FxHashMap<PreloadId, PreloadEntry>>>;
pub trait ConsumePreloadedResources {
fn consume_preloaded_resource(
&self,
request: &Request,
preloaded_resources: SharedPreloadedResources,
) -> Option<PreloadResponseCandidate>;
}
impl ConsumePreloadedResources for RequestClient {
/// <https://html.spec.whatwg.org/multipage/#consume-a-preloaded-resource>
fn consume_preloaded_resource(
&self,
request: &Request,
preloaded_resources: SharedPreloadedResources,
) -> Option<PreloadResponseCandidate> {
// Step 1. Let key be a preload key whose URL is url,
// destination is destination, mode is mode, and credentials mode is credentialsMode.
let key = PreloadKey {
url: request.url().clone(),
destination: request.destination,
mode: request.mode.clone(),
credentials_mode: request.credentials_mode,
};
// Step 2. Let preloads be window's associated Document's map of preloaded resources.
// Step 3. If key does not exist in preloads, then return false.
let preload_id = self.preloaded_resources.get(&key)?;
// Step 4. Let entry be preloads[key].
let mut preloaded_resources_lock = preloaded_resources.lock();
let preloads = preloaded_resources_lock.as_mut().unwrap();
let preload_entry = preloads.get_mut(preload_id)?;
// Step 5. Let consumerIntegrityMetadata be the result of parsing integrityMetadata.
let consumer_integrity_metadata =
csp::parse_subresource_integrity_metadata(&request.integrity_metadata);
// Step 6. Let preloadIntegrityMetadata be the result of parsing entry's integrity metadata.
let preload_integrity_metadata =
csp::parse_subresource_integrity_metadata(&preload_entry.integrity_metadata);
// Step 7. If none of the following conditions apply:
if !(
// consumerIntegrityMetadata is no metadata;
consumer_integrity_metadata == csp::SubresourceIntegrityMetadata::NoMetadata
// consumerIntegrityMetadata is equal to preloadIntegrityMetadata; or
|| consumer_integrity_metadata == preload_integrity_metadata
) {
// then return false.
return None;
}
// Step 8. Remove preloads[key].
// Note: `self.preloads` is an immutable copy of the list of preloads
// that is not shared with any other running fetch.
// Instead, we defer removing the associated PreloadId->PreloadEntry
// map entry until the entire response has been preloaded, since
// it stores the Sender which will be used to transmit the complete
// response.
// Step 10. Otherwise, call onResponseAvailable with entry's response.
if let Some(response) = preload_entry.response.as_ref() {
Some(PreloadResponseCandidate::Response(
Box::new(response.clone()),
preload_id.clone(),
))
} else {
// Step 9. If entry's response is null, then set entry's on response available to onResponseAvailable.
let (sender, receiver) = tokio::sync::oneshot::channel();
preload_entry.on_response_available = Some(sender);
// Step 11. Return true.
Some(PreloadResponseCandidate::Pending(
receiver,
preload_id.clone(),
))
}
}
}