Skip to main content

net/fetch/
fetch_params.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::sync::{Arc, Mutex};
6
7use content_security_policy as csp;
8use net_traits::ResourceFetchTimingContainer;
9use net_traits::fetch::fetch_controller::{FetchController, FetchControllerState};
10use net_traits::request::{PreloadEntry, PreloadId, PreloadKey, Request, RequestClient};
11use net_traits::response::Response;
12use rustc_hash::FxHashMap;
13use tokio::sync::oneshot::Receiver as TokioReceiver;
14
15/// <https://fetch.spec.whatwg.org/#fetch-params-preloaded-response-candidate>
16pub enum PreloadResponseCandidate {
17    None,
18    Pending(TokioReceiver<Response>, PreloadId),
19    Response(Box<Response>, PreloadId),
20}
21
22impl PreloadResponseCandidate {
23    /// Part of Step 12 of <https://fetch.spec.whatwg.org/#concept-main-fetch>
24    pub(crate) async fn response(&mut self) -> Option<(Response, PreloadId)> {
25        // Step 3. Return fetchParams’s preloaded response candidate.
26        match self {
27            PreloadResponseCandidate::None => None,
28            // Step 2. Assert: fetchParams’s preloaded response candidate is a response.
29            PreloadResponseCandidate::Response(response, preload_id) => {
30                Some((*response.clone(), preload_id.clone()))
31            },
32            // Step 1. Wait until fetchParams’s preloaded response candidate is not "pending".
33            PreloadResponseCandidate::Pending(receiver, preload_id) => receiver
34                .await
35                .ok()
36                .map(|response| (response, preload_id.clone())),
37        }
38    }
39}
40
41/// <https://fetch.spec.whatwg.org/#fetch-params>
42pub struct FetchParams {
43    /// <https://fetch.spec.whatwg.org/#fetch-params-request>
44    pub request: Request,
45    /// <https://fetch.spec.whatwg.org/#fetch-params-preloaded-response-candidate>
46    pub preload_response_candidate: PreloadResponseCandidate,
47    /// <https://fetch.spec.whatwg.org/#fetch-params-controller>
48    pub controller: FetchController,
49    /// <https://fetch.spec.whatwg.org/#fetch-params-timing-info>
50    pub timing_info: ResourceFetchTimingContainer,
51}
52
53impl FetchParams {
54    pub fn new(request: Request, timing_info: ResourceFetchTimingContainer) -> FetchParams {
55        FetchParams {
56            request,
57            timing_info,
58            preload_response_candidate: PreloadResponseCandidate::None,
59            controller: Default::default(),
60        }
61    }
62
63    /// <https://fetch.spec.whatwg.org/#fetch-params-aborted>
64    pub fn is_aborted(&self) -> bool {
65        // > A fetch params fetchParams is aborted
66        // > if its controller’s state is "aborted".
67        self.controller.state == FetchControllerState::Aborted
68    }
69
70    /// <https://fetch.spec.whatwg.org/#fetch-params-canceled>
71    pub fn is_canceled(&self) -> bool {
72        // > A fetch params fetchParams is canceled
73        // > if its controller’s state is "aborted" or "terminated".
74        matches!(
75            self.controller.state,
76            FetchControllerState::Aborted | FetchControllerState::Terminated
77        )
78    }
79}
80
81pub type SharedPreloadedResources = Arc<Mutex<FxHashMap<PreloadId, PreloadEntry>>>;
82
83pub trait ConsumePreloadedResources {
84    fn consume_preloaded_resource(
85        &self,
86        request: &Request,
87        preloaded_resources: SharedPreloadedResources,
88    ) -> Option<PreloadResponseCandidate>;
89}
90
91impl ConsumePreloadedResources for RequestClient {
92    /// <https://html.spec.whatwg.org/multipage/#consume-a-preloaded-resource>
93    fn consume_preloaded_resource(
94        &self,
95        request: &Request,
96        preloaded_resources: SharedPreloadedResources,
97    ) -> Option<PreloadResponseCandidate> {
98        // Step 1. Let key be a preload key whose URL is url,
99        // destination is destination, mode is mode, and credentials mode is credentialsMode.
100        let key = PreloadKey {
101            url: request.url(),
102            destination: request.destination,
103            mode: request.mode.clone(),
104            credentials_mode: request.credentials_mode,
105        };
106        // Step 2. Let preloads be window's associated Document's map of preloaded resources.
107        // Step 3. If key does not exist in preloads, then return false.
108        let preload_id = self.preloaded_resources.get(&key)?;
109
110        // Step 4. Let entry be preloads[key].
111        let mut preloaded_resources_lock = preloaded_resources.lock();
112        let preloads = preloaded_resources_lock.as_mut().unwrap();
113        let preload_entry = preloads.get_mut(preload_id)?;
114
115        // Step 5. Let consumerIntegrityMetadata be the result of parsing integrityMetadata.
116        let consumer_integrity_metadata =
117            csp::parse_subresource_integrity_metadata(&request.integrity_metadata);
118        // Step 6. Let preloadIntegrityMetadata be the result of parsing entry's integrity metadata.
119        let preload_integrity_metadata =
120            csp::parse_subresource_integrity_metadata(&preload_entry.integrity_metadata);
121        // Step 7. If none of the following conditions apply:
122        if !(
123            // consumerIntegrityMetadata is no metadata;
124            consumer_integrity_metadata == csp::SubresourceIntegrityMetadata::NoMetadata
125            // consumerIntegrityMetadata is equal to preloadIntegrityMetadata; or
126            || consumer_integrity_metadata == preload_integrity_metadata
127        ) {
128            // then return false.
129            return None;
130        }
131
132        // Step 8. Remove preloads[key].
133        // Note: `self.preloads` is an immutable copy of the list of preloads
134        //       that is not shared with any other running fetch.
135        //       Instead, we defer removing the associated PreloadId->PreloadEntry
136        //       map entry until the entire response has been preloaded, since
137        //       it stores the Sender which will be used to transmit the complete
138        //       response.
139
140        // Step 10. Otherwise, call onResponseAvailable with entry's response.
141        if let Some(response) = preload_entry.response.as_ref() {
142            Some(PreloadResponseCandidate::Response(
143                Box::new(response.clone()),
144                preload_id.clone(),
145            ))
146        } else {
147            // Step 9. If entry's response is null, then set entry's on response available to onResponseAvailable.
148            let (sender, receiver) = tokio::sync::oneshot::channel();
149            preload_entry.on_response_available = Some(sender);
150            // Step 11. Return true.
151            Some(PreloadResponseCandidate::Pending(
152                receiver,
153                preload_id.clone(),
154            ))
155        }
156    }
157}