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}