Skip to main content

script/fetch/
fetch.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
5#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::Cell;
8use std::rc::Rc;
9use std::time::Duration;
10
11use bytes::Bytes;
12use js::context::JSContext;
13use js::jsapi::ExceptionStackBehavior;
14use js::jsval::UndefinedValue;
15use js::realm::CurrentRealm;
16use js::rust::HandleValue;
17use js::rust::wrappers2::{JS_IsExceptionPending, JS_SetPendingException};
18use net_traits::blob_url_store::UrlWithBlobClaim;
19use net_traits::request::{
20    CorsSettings, CredentialsMode, Destination, Referrer, Request as NetTraitsRequest,
21    RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
22};
23use net_traits::{
24    CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseMsg,
25    FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming, cancel_async_fetch, fetch_async,
26};
27use rustc_hash::FxHashMap;
28use script_bindings::cformat;
29use serde::{Deserialize, Serialize};
30use servo_base::generic_channel::GenericCallback;
31use servo_base::id::WebViewId;
32use servo_url::ServoUrl;
33use timers::TimerEventRequest;
34use uuid::Uuid;
35
36use crate::dom::abortsignal::AbortAlgorithm;
37use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
38use crate::dom::bindings::codegen::Bindings::RequestBinding::{
39    RequestInfo, RequestInit, RequestMethods,
40};
41use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
42use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
43use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
44use crate::dom::bindings::error::{Error, Fallible};
45use crate::dom::bindings::inheritance::Castable;
46use crate::dom::bindings::num::Finite;
47use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
48use crate::dom::bindings::reflector::DomGlobal;
49use crate::dom::bindings::root::DomRoot;
50use crate::dom::bindings::trace::RootedTraceableBox;
51use crate::dom::csp::{GlobalCspReporting, Violation};
52use crate::dom::fetchlaterresult::FetchLaterResult;
53use crate::dom::globalscope::GlobalScope;
54use crate::dom::headers::Guard;
55use crate::dom::performance::performanceresourcetiming::InitiatorType;
56use crate::dom::promise::{Promise, RootedPromise};
57use crate::dom::request::Request;
58use crate::dom::response::Response;
59use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
60use crate::dom::window::Window;
61use crate::fetch::body::BodyMixin;
62use crate::fetch::network_listener::{
63    self, FetchResponseListener, NetworkListener, ResourceTimingListener, submit_timing_data,
64};
65use crate::realms::enter_auto_realm;
66
67/// Fetch canceller object. By default initialized to having a
68/// request associated with it, which can be aborted or terminated.
69/// Calling `ignore` will sever the relationship with the request,
70/// meaning it cannot be cancelled through this canceller from that point on.
71#[derive(Default, JSTraceable, MallocSizeOf)]
72pub(crate) struct FetchCanceller {
73    #[no_trace]
74    request_id: Option<RequestId>,
75    #[no_trace]
76    core_resource_thread: Option<CoreResourceThread>,
77    keep_alive: bool,
78}
79
80impl FetchCanceller {
81    /// Create a FetchCanceller associated with a request,
82    /// and a particular(public vs private) resource thread.
83    pub(crate) fn new(
84        request_id: RequestId,
85        keep_alive: bool,
86        core_resource_thread: CoreResourceThread,
87    ) -> Self {
88        Self {
89            request_id: Some(request_id),
90            core_resource_thread: Some(core_resource_thread),
91            keep_alive,
92        }
93    }
94
95    pub(crate) fn keep_alive(&self) -> bool {
96        self.keep_alive
97    }
98
99    fn cancel(&mut self) {
100        if let Some(request_id) = self.request_id.take() {
101            // stop trying to make fetch happen
102            // it's not going to happen
103
104            if let Some(ref core_resource_thread) = self.core_resource_thread {
105                // No error handling here. Cancellation is a courtesy call,
106                // we don't actually care if the other side heard.
107                cancel_async_fetch(vec![request_id], core_resource_thread);
108            }
109        }
110    }
111
112    /// Use this if you don't want it to send a cancellation request
113    /// on drop (e.g. if the fetch completes)
114    pub(crate) fn ignore(&mut self) {
115        let _ = self.request_id.take();
116    }
117
118    /// <https://fetch.spec.whatwg.org/#fetch-controller-abort>
119    pub(crate) fn abort(&mut self) {
120        self.cancel();
121    }
122
123    /// <https://fetch.spec.whatwg.org/#fetch-controller-terminate>
124    pub(crate) fn terminate(&mut self) {
125        self.cancel();
126    }
127}
128
129/// An id to differentiate one deferred fetch record from another.
130#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
131pub(crate) struct DeferredFetchRecordId(Uuid);
132
133impl Default for DeferredFetchRecordId {
134    fn default() -> Self {
135        Self(Uuid::new_v4())
136    }
137}
138
139pub(crate) type QueuedDeferredFetchRecord = Rc<DeferredFetchRecord>;
140
141/// <https://fetch.spec.whatwg.org/#fetch-record>
142#[derive(MallocSizeOf)]
143pub(crate) struct FetchRecord {
144    /// <https://fetch.spec.whatwg.org/#concept-fetch-record-fetch>
145    ///
146    /// Note: The fetch controller is currently represented in Servo by the [`FetchCanceller`].
147    controller: Option<FetchCanceller>,
148    /// Whether or not the [`Request`] has finished.
149    ///
150    /// TODO: In the specification this is in the [`Request`], so it should be moved there and the
151    /// [`Request`] stored here, which requires making everything traceable.
152    done: bool,
153}
154
155/// <https://fetch.spec.whatwg.org/#concept-fetch-group>
156#[derive(MallocSizeOf)]
157pub(crate) struct FetchGroup {
158    /// The [`CoreResourceThread`] for this [`FetchGroup`].
159    core_resource_thread: CoreResourceThread,
160    /// <https://fetch.spec.whatwg.org/#fetch-group-deferred-fetch-records>
161    #[conditional_malloc_size_of]
162    pub(crate) deferred_fetch_records: FxHashMap<DeferredFetchRecordId, QueuedDeferredFetchRecord>,
163    /// <https://fetch.spec.whatwg.org/#concept-fetch-record>
164    pub(crate) fetch_records: FxHashMap<RequestId, FetchRecord>,
165}
166
167impl FetchGroup {
168    pub(crate) fn new(core_resource_thread: CoreResourceThread) -> Self {
169        Self {
170            core_resource_thread,
171            deferred_fetch_records: Default::default(),
172            fetch_records: Default::default(),
173        }
174    }
175
176    pub(crate) fn fetch<Listener: FetchResponseListener>(
177        &mut self,
178        request: RequestBuilder,
179        listener: NetworkListener<Listener>,
180    ) {
181        self.fetch_records.insert(
182            request.id,
183            FetchRecord {
184                controller: Some(FetchCanceller::new(
185                    request.id,
186                    request.keep_alive,
187                    self.core_resource_thread.clone(),
188                )),
189                done: false,
190            },
191        );
192        fetch_async(
193            &self.core_resource_thread,
194            request,
195            None,
196            listener.into_callback(),
197        );
198    }
199
200    pub(crate) fn deferred_fetches(&self) -> Vec<QueuedDeferredFetchRecord> {
201        self.deferred_fetch_records.values().cloned().collect()
202    }
203
204    fn append_deferred_fetch(
205        &mut self,
206        deferred_record: DeferredFetchRecord,
207    ) -> DeferredFetchRecordId {
208        let deferred_fetch_record_id = DeferredFetchRecordId::default();
209        self.deferred_fetch_records
210            .insert(deferred_fetch_record_id, Rc::new(deferred_record));
211        deferred_fetch_record_id
212    }
213
214    pub(crate) fn deferred_fetch_record_for_id(
215        &self,
216        deferred_fetch_record_id: &DeferredFetchRecordId,
217    ) -> QueuedDeferredFetchRecord {
218        self.deferred_fetch_records
219            .get(deferred_fetch_record_id)
220            .expect("Should always use a generated fetch_record_id instead of passing your own")
221            .clone()
222    }
223
224    pub(crate) fn fetch_controller(
225        &mut self,
226        request_id: &RequestId,
227    ) -> Option<&mut FetchCanceller> {
228        self.fetch_records.get_mut(request_id)?.controller.as_mut()
229    }
230
231    /// <https://fetch.spec.whatwg.org/#concept-fetch-group-terminate>
232    ///
233    /// Returns `true` if any fetches were cancelled and `false` otherwise.
234    pub(crate) fn terminate(&mut self, global: &GlobalScope) -> bool {
235        // Step 1. For each fetch record record of fetchGroup’s fetch records,
236        // if record’s controller is non-null and record’s request’s done flag
237        // is unset and keepalive is false, terminate record’s controller.
238        let mut cancelled_any = false;
239        self.fetch_records.retain(|_, fetch_record| {
240            let Some(controller) = fetch_record.controller.as_mut() else {
241                return false;
242            };
243            if fetch_record.done {
244                return false;
245            }
246            if !controller.keep_alive() {
247                controller.terminate();
248                cancelled_any = true;
249                return false;
250            }
251            true
252        });
253
254        // Step 2. Process deferred fetches for fetchGroup.
255        self.process_deferred_fetches(global);
256
257        cancelled_any
258    }
259
260    /// <https://fetch.spec.whatwg.org/#process-deferred-fetches>
261    pub(crate) fn process_deferred_fetches(&mut self, global: &GlobalScope) {
262        // Step 1. For each deferred fetch record deferredRecord of fetchGroup’s
263        // deferred fetch records, process a deferred fetch deferredRecord.
264        for deferred_fetch in self.deferred_fetches() {
265            self.process_a_deferred_fetch(global, &deferred_fetch);
266        }
267    }
268
269    /// <https://fetch.spec.whatwg.org/#process-a-deferred-fetch>
270    pub(crate) fn process_a_deferred_fetch(
271        &mut self,
272        global: &GlobalScope,
273        deferred_fetch: &DeferredFetchRecord,
274    ) {
275        // Step 1. If deferredRecord’s invoke state is not "pending", then return.
276        if deferred_fetch.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
277            return;
278        }
279        // Step 2. Set deferredRecord’s invoke state to "sent".
280        deferred_fetch
281            .invoke_state
282            .set(DeferredFetchRecordInvokeState::Sent);
283        // Step 3. Fetch deferredRecord’s request.
284        let fetch_later_listener = FetchLaterListener {
285            url: deferred_fetch.request.url(),
286            global: Trusted::new(global),
287        };
288        let task_source = global.task_manager().networking_task_source().to_sendable();
289        self.fetch(
290            request_init_from_request(deferred_fetch.request.clone(), global),
291            NetworkListener::new(fetch_later_listener, task_source, global),
292        );
293        // Step 4 is handled by caller
294    }
295
296    pub(crate) fn mark_fetch_request_as_done(&mut self, request_id: &RequestId) {
297        if let Some(fetch_record) = self.fetch_records.get_mut(request_id) {
298            fetch_record.done = true;
299        }
300    }
301}
302
303fn request_init_from_request(request: NetTraitsRequest, global: &GlobalScope) -> RequestBuilder {
304    let mut builder = RequestBuilder::new(
305        request.target_webview_id,
306        request.url_with_blob_claim(),
307        request.referrer,
308    )
309    .method(request.method)
310    .headers(request.headers)
311    .unsafe_request(request.unsafe_request)
312    .body(request.body)
313    .destination(request.destination)
314    .synchronous(request.synchronous)
315    .mode(request.mode)
316    .cache_mode(request.cache_mode)
317    .use_cors_preflight(request.use_cors_preflight)
318    .credentials_mode(request.credentials_mode)
319    .use_url_credentials(request.use_url_credentials)
320    .referrer_policy(request.referrer_policy)
321    .pipeline_id(request.pipeline_id)
322    .redirect_mode(request.redirect_mode)
323    .integrity_metadata(request.integrity_metadata)
324    .cryptographic_nonce_metadata(request.cryptographic_nonce_metadata)
325    .parser_metadata(request.parser_metadata)
326    .initiator(request.initiator)
327    .client(global.request_client(None))
328    .response_tainting(request.response_tainting);
329    builder.id = request.id;
330    builder.reload_navigation = request.reload_navigation;
331    builder.history_navigation = request.history_navigation;
332    builder
333}
334
335/// <https://fetch.spec.whatwg.org/#abort-fetch>
336fn abort_fetch_call(
337    promise: &RootedPromise,
338    request: &Request,
339    response_object: Option<&Response>,
340    abort_reason: HandleValue,
341    global: &GlobalScope,
342    cx: &mut JSContext,
343) {
344    // Step 1. Reject promise with error.
345    promise.reject(cx, abort_reason);
346    // Step 2. If request’s body is non-null and is readable, then cancel request’s body with error.
347    if let Some(body) = request.body() &&
348        body.is_readable()
349    {
350        body.cancel(cx, global, abort_reason);
351    }
352    // Step 3. If responseObject is null, then return.
353    // Step 4. Let response be responseObject’s response.
354    let Some(response) = response_object else {
355        return;
356    };
357    // Step 5. If response’s body is non-null and is readable, then error response’s body with error.
358    if let Some(body) = response.body() &&
359        body.is_readable()
360    {
361        body.error(cx, abort_reason);
362    }
363}
364
365/// <https://fetch.spec.whatwg.org/#dom-global-fetch>
366#[expect(non_snake_case)]
367pub(crate) fn Fetch(
368    global: &GlobalScope,
369    input: RequestInfo,
370    init: RootedTraceableBox<RequestInit>,
371    cx: &mut CurrentRealm,
372) -> RootedPromise {
373    // Step 1. Let p be a new promise.
374    let promise = Promise::new_in_realm_rooted(cx);
375
376    // Step 7. Let responseObject be null.
377    // NOTE: We do initialize the object earlier so we can use it to track errors.
378    let response = Response::new(cx, global);
379    response.Headers(cx).set_guard(Guard::Immutable);
380
381    // Step 2. Let requestObject be the result of invoking the initial value of Request as constructor
382    //         with input and init as arguments. If this throws an exception, reject p with it and return p.
383    let request_object = match Request::Constructor(cx, global, None, input, init) {
384        Err(e) => {
385            response.error_stream(cx, e.clone());
386            promise.reject_error(cx, e);
387            return promise;
388        },
389        Ok(r) => r,
390    };
391    // Step 3. Let request be requestObject’s request.
392    let request = request_object.request().clone();
393
394    // Step 4. If requestObject’s signal is aborted, then:
395    let signal = request_object.Signal();
396    if signal.aborted() {
397        // Step 4.1. Abort the fetch() call with p, request, null, and requestObject’s signal’s abort reason.
398        rooted!(&in(cx) let mut abort_reason = UndefinedValue());
399        signal.Reason(abort_reason.handle_mut());
400        abort_fetch_call(
401            &promise,
402            &request_object,
403            None,
404            abort_reason.handle(),
405            global,
406            cx,
407        );
408        // Step 4.2. Return p.
409        return promise;
410    }
411
412    // Step 5. Let globalObject be request’s client’s global object.
413    // NOTE:   We already get the global object as an argument
414    let mut request_builder = request_init_from_request(request, global);
415
416    // Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s
417    //         service-workers mode to "none".
418    if global.is::<ServiceWorkerGlobalScope>() {
419        request_builder.service_workers_mode = ServiceWorkersMode::None;
420    }
421
422    // Step 8. Let relevantRealm be this’s relevant realm.
423    //
424    // Is `comp` as argument
425
426    // Step 9. Let locallyAborted be false.
427    // Step 10. Let controller be null.
428    let fetch_context = FetchContext {
429        fetch_promise: Some(TrustedPromise::from(&promise)),
430        response_object: Trusted::new(&*response),
431        request: Trusted::new(&*request_object),
432        global: Trusted::new(global),
433        locally_aborted: false,
434        url: request_builder.url.url(),
435    };
436    let network_listener = NetworkListener::new(
437        fetch_context,
438        global.task_manager().networking_task_source().to_sendable(),
439        global,
440    );
441    let fetch_context = network_listener.context.clone();
442
443    // Step 11. Add the following abort steps to requestObject’s signal:
444    signal.add(&AbortAlgorithm::Fetch(fetch_context));
445
446    // Step 12. Set controller to the result of calling fetch given request and
447    // processResponse given response being these steps:
448    global
449        .fetch_group_mut()
450        .fetch(request_builder, network_listener);
451
452    // Step 13. Return p.
453    promise
454}
455
456/// <https://fetch.spec.whatwg.org/#queue-a-deferred-fetch>
457fn queue_deferred_fetch(
458    request: NetTraitsRequest,
459    activate_after: Finite<f64>,
460    global: &GlobalScope,
461) -> DeferredFetchRecordId {
462    let trusted_global = Trusted::new(global);
463    let mut request = request;
464    // Step 1. Populate request from client given request.
465    request.client = Some(global.request_client(None));
466    request.populate_request_from_client();
467    // Step 2. Set request’s service-workers mode to "none".
468    request.service_workers_mode = ServiceWorkersMode::None;
469    // Step 3. Set request’s keepalive to true.
470    request.keep_alive = true;
471    // Step 4. Let deferredRecord be a new deferred fetch record whose request is request, and whose notify invoked is onActivatedWithoutTermination.
472    let deferred_record = DeferredFetchRecord {
473        request,
474        invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
475        activated: Cell::new(false),
476    };
477
478    // Step 5. Append deferredRecord to request’s client’s fetch group’s deferred fetch records.
479    let deferred_fetch_record_id = global
480        .fetch_group_mut()
481        .append_deferred_fetch(deferred_record);
482
483    // Step 6. If activateAfter is non-null, then run the following steps in parallel:
484    global.schedule_timer(TimerEventRequest {
485        callback: Box::new(move || {
486            // Step 6.2. Process deferredRecord.
487            let global = trusted_global.root();
488            let mut fetch_group = global.fetch_group_mut();
489            let deferred_fetch_record =
490                fetch_group.deferred_fetch_record_for_id(&deferred_fetch_record_id);
491            fetch_group.process_a_deferred_fetch(&global, &deferred_fetch_record);
492
493            // Last step of https://fetch.spec.whatwg.org/#process-a-deferred-fetch
494            //
495            // Step 4. Queue a global task on the deferred fetch task source with
496            // deferredRecord’s request’s client’s global object to run deferredRecord’s notify invoked.
497            let trusted_global = trusted_global.clone();
498            global.task_manager().deferred_fetch_task_source().queue(
499                task!(notify_deferred_record: move || {
500                    trusted_global.root().fetch_group().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
501                }),
502            );
503        }),
504        // Step 6.1. The user agent should wait until any of the following conditions is met:
505        duration: Duration::from_millis(*activate_after as u64),
506    });
507    // Step 7. Return deferredRecord.
508    deferred_fetch_record_id
509}
510
511/// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
512#[expect(non_snake_case, unsafe_code)]
513pub(crate) fn FetchLater(
514    cx: &mut JSContext,
515    window: &Window,
516    input: RequestInfo,
517    init: RootedTraceableBox<DeferredRequestInit>,
518) -> Fallible<DomRoot<FetchLaterResult>> {
519    let global_scope = window.upcast();
520    let document = window.Document();
521    // Step 1. Let requestObject be the result of invoking the initial value
522    // of Request as constructor with input and init as arguments.
523    let request_object = Request::constructor(cx, global_scope, None, input, &init.parent)?;
524    // Step 2. If requestObject’s signal is aborted, then throw signal’s abort reason.
525    let signal = request_object.Signal();
526    if signal.aborted() {
527        rooted!(&in(cx) let mut abort_reason = UndefinedValue());
528        signal.Reason(abort_reason.handle_mut());
529        unsafe {
530            assert!(!JS_IsExceptionPending(cx));
531            JS_SetPendingException(cx, abort_reason.handle(), ExceptionStackBehavior::Capture)
532        };
533        return Err(Error::JSFailed);
534    }
535    // Step 3. Let request be requestObject’s request.
536    let request = request_object.request();
537    // Step 4. Let activateAfter be null.
538    let mut activate_after = Finite::wrap(0_f64);
539    // Step 5. If init is given and init["activateAfter"] exists, then set
540    // activateAfter to init["activateAfter"].
541    if let Some(init_activate_after) = init.activateAfter.as_ref() {
542        activate_after = *init_activate_after;
543    }
544    // Step 6. If activateAfter is less than 0, then throw a RangeError.
545    if *activate_after < 0.0 {
546        return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
547    }
548    // Step 7. If this’s relevant global object’s associated document is not fully active, then throw a TypeError.
549    if !document.is_fully_active() {
550        return Err(Error::Type(c"Document is not fully active".to_owned()));
551    }
552    let url = request.url();
553    // Step 8. If request’s URL’s scheme is not an HTTP(S) scheme, then throw a TypeError.
554    if !matches!(url.scheme(), "http" | "https") {
555        return Err(Error::Type(c"URL is not http(s)".to_owned()));
556    }
557    // Step 9. If request’s URL is not a potentially trustworthy URL, then throw a SecurityError.
558    if !url.is_potentially_trustworthy() {
559        return Err(Error::Type(c"URL is not trustworthy".to_owned()));
560    }
561    // Step 10. If request’s body is not null, and request’s body length is null, then throw a TypeError.
562    if request
563        .body
564        .as_ref()
565        .is_some_and(|body| body.len().is_none())
566    {
567        return Err(Error::Type(c"Body is empty".to_owned()));
568    }
569    // Step 11. If the available deferred-fetch quota given request’s client and request’s URL’s
570    // origin is less than request’s total request length, then throw a "QuotaExceededError" DOMException.
571    let quota = document.available_deferred_fetch_quota(request.url().origin());
572    let requested = request.total_request_length() as isize;
573    if quota < requested {
574        return Err(Error::QuotaExceeded {
575            quota: Some(Finite::wrap(quota as f64)),
576            requested: Some(Finite::wrap(requested as f64)),
577        });
578    }
579    // Step 12. Let activated be false.
580    // Step 13. Let deferredRecord be the result of calling queue a deferred fetch given request,
581    // activateAfter, and the following step: set activated to true.
582    let deferred_record_id = queue_deferred_fetch(request.clone(), activate_after, global_scope);
583    // Step 14. Add the following abort steps to requestObject’s signal: Set deferredRecord’s invoke state to "aborted".
584    signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
585    // Step 15. Return a new FetchLaterResult whose activated getter steps are to return activated.
586    Ok(FetchLaterResult::new(cx, window, deferred_record_id))
587}
588
589/// <https://fetch.spec.whatwg.org/#deferred-fetch-record-invoke-state>
590#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
591pub(crate) enum DeferredFetchRecordInvokeState {
592    Pending,
593    Sent,
594    Aborted,
595}
596
597/// <https://fetch.spec.whatwg.org/#deferred-fetch-record>
598#[derive(MallocSizeOf)]
599pub(crate) struct DeferredFetchRecord {
600    /// <https://fetch.spec.whatwg.org/#deferred-fetch-record-request>
601    pub(crate) request: NetTraitsRequest,
602    /// <https://fetch.spec.whatwg.org/#deferred-fetch-record-invoke-state>
603    pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
604    activated: Cell<bool>,
605}
606
607impl DeferredFetchRecord {
608    /// Part of step 13 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
609    fn activate(&self) {
610        // and the following step: set activated to true.
611        self.activated.set(true);
612    }
613    /// Part of step 14 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
614    pub(crate) fn abort(&self) {
615        // Set deferredRecord’s invoke state to "aborted".
616        self.invoke_state
617            .set(DeferredFetchRecordInvokeState::Aborted);
618    }
619    /// Part of step 15 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
620    pub(crate) fn activated_getter_steps(&self) -> bool {
621        // whose activated getter steps are to return activated.
622        self.activated.get()
623    }
624}
625
626#[derive(JSTraceable, MallocSizeOf)]
627pub(crate) struct FetchContext {
628    #[ignore_malloc_size_of = "unclear ownership semantics"]
629    fetch_promise: Option<TrustedPromise>,
630    response_object: Trusted<Response>,
631    request: Trusted<Request>,
632    global: Trusted<GlobalScope>,
633    locally_aborted: bool,
634    #[no_trace]
635    url: ServoUrl,
636}
637
638impl FetchContext {
639    /// Step 11 of <https://fetch.spec.whatwg.org/#dom-global-fetch>
640    pub(crate) fn abort_fetch(&mut self, abort_reason: HandleValue, cx: &mut JSContext) {
641        // Step 11.1. Set locallyAborted to true.
642        self.locally_aborted = true;
643
644        // Step 11.2. Assert: controller is non-null.
645        //
646        // Note: We currently prune fetch records that have finished (behaviorally
647        // equivalent to the specification and better for memory usage), so it might be
648        // the case that the `FetchRecord` is gone and the controller inaccessible.
649
650        // Step 11.3. Abort controller with requestObject’s signal’s abort reason.
651        let global = self.global.root();
652        let request = self.request.root();
653        if let Some(controller) = global
654            .fetch_group_mut()
655            .fetch_controller(&request.request().id)
656        {
657            controller.abort();
658        }
659
660        // Step 11.4. Abort the fetch() call with p, request, responseObject,
661        // and requestObject’s signal’s abort reason.
662        let promise = self
663            .fetch_promise
664            .take()
665            .expect("fetch promise is missing")
666            .root(cx);
667        abort_fetch_call(
668            &promise,
669            &request,
670            Some(&self.response_object.root()),
671            abort_reason,
672            &global,
673            cx,
674        );
675    }
676}
677
678/// Step 12 of <https://fetch.spec.whatwg.org/#dom-global-fetch>
679impl FetchResponseListener for FetchContext {
680    fn process_request_body(&mut self, _: RequestId) {
681        // TODO
682    }
683
684    fn process_response(
685        &mut self,
686        cx: &mut JSContext,
687        _: RequestId,
688        fetch_metadata: Result<FetchMetadata, NetworkError>,
689    ) {
690        // Step 12.1. If locallyAborted is true, then abort these steps.
691        if self.locally_aborted {
692            return;
693        }
694        let promise = self
695            .fetch_promise
696            .take()
697            .expect("fetch promise is missing")
698            .root(cx);
699
700        let mut realm = enter_auto_realm(cx, &*promise);
701        let cx = &mut realm.current_realm();
702        match fetch_metadata {
703            // Step 12.3. If response is a network error, then reject
704            // p with a TypeError and abort these steps.
705            Err(error) => {
706                promise.reject_error(cx, Error::Type(cformat!("Network error: {:?}", error)));
707                self.fetch_promise = Some(TrustedPromise::from(&promise));
708                let response = self.response_object.root();
709                response.set_type(cx, DOMResponseType::Error);
710                response.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
711                return;
712            },
713            // Step 12.4. Set responseObject to the result of creating a Response object,
714            // given response, "immutable", and relevantRealm.
715            Ok(metadata) => match metadata {
716                FetchMetadata::Unfiltered(m) => {
717                    let r = self.response_object.root();
718                    fill_headers_with_metadata(cx, &r, m);
719                    r.set_type(cx, DOMResponseType::Default);
720                },
721                FetchMetadata::Filtered { filtered, .. } => match filtered {
722                    FilteredMetadata::Basic(m) => {
723                        let r = self.response_object.root();
724                        fill_headers_with_metadata(cx, &r, m);
725                        r.set_type(cx, DOMResponseType::Basic);
726                    },
727                    FilteredMetadata::Cors(m) => {
728                        let r = self.response_object.root();
729                        fill_headers_with_metadata(cx, &r, m);
730                        r.set_type(cx, DOMResponseType::Cors);
731                    },
732                    FilteredMetadata::Opaque => {
733                        self.response_object
734                            .root()
735                            .set_type(cx, DOMResponseType::Opaque);
736                    },
737                    FilteredMetadata::OpaqueRedirect(url) => {
738                        let r = self.response_object.root();
739                        r.set_type(cx, DOMResponseType::Opaqueredirect);
740                        r.set_final_url(url);
741                    },
742                },
743            },
744        }
745
746        // Step 12.5. Resolve p with responseObject.
747        promise.resolve_native(cx, &self.response_object.root());
748        self.fetch_promise = Some(TrustedPromise::from(&promise));
749    }
750
751    fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Bytes) {
752        let response = self.response_object.root();
753        response.stream_chunk(cx, chunk);
754    }
755
756    fn process_response_eof(
757        self,
758        cx: &mut JSContext,
759        _: RequestId,
760        response: Result<(), NetworkError>,
761        timing: ResourceFetchTiming,
762    ) {
763        let response_object = self.response_object.root();
764        let mut realm = enter_auto_realm(cx, &*response_object);
765        let cx = &mut realm.current_realm();
766        if let Err(ref error) = response &&
767            *error == NetworkError::DecompressionError
768        {
769            response_object.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
770        }
771        response_object.finish(cx);
772        // TODO
773        // ... trailerObject is not supported in Servo yet.
774
775        // navigation submission is handled in servoparser/mod.rs
776        network_listener::submit_timing(cx, &self, &response, &timing);
777    }
778
779    fn process_csp_violations(
780        &mut self,
781        cx: &mut JSContext,
782        _request_id: RequestId,
783        violations: Vec<Violation>,
784    ) {
785        let global = &self.resource_timing_global();
786        global.report_csp_violations(cx, violations, None, None);
787    }
788}
789
790impl ResourceTimingListener for FetchContext {
791    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
792        (InitiatorType::Fetch, self.url.clone())
793    }
794
795    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
796        self.response_object.root().global()
797    }
798}
799
800struct FetchLaterListener {
801    /// URL of this request.
802    url: ServoUrl,
803    /// The global object fetching the report uri violation
804    global: Trusted<GlobalScope>,
805}
806
807impl FetchResponseListener for FetchLaterListener {
808    fn process_request_body(&mut self, _: RequestId) {}
809
810    fn process_response(
811        &mut self,
812        _: &mut JSContext,
813        _: RequestId,
814        fetch_metadata: Result<FetchMetadata, NetworkError>,
815    ) {
816        _ = fetch_metadata;
817    }
818
819    fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Bytes) {
820        _ = chunk;
821    }
822
823    fn process_response_eof(
824        self,
825        cx: &mut JSContext,
826        _: RequestId,
827        response: Result<(), NetworkError>,
828        timing: ResourceFetchTiming,
829    ) {
830        network_listener::submit_timing(cx, &self, &response, &timing);
831    }
832
833    fn process_csp_violations(
834        &mut self,
835        cx: &mut JSContext,
836        _request_id: RequestId,
837        violations: Vec<Violation>,
838    ) {
839        let global = self.resource_timing_global();
840        global.report_csp_violations(cx, violations, None, None);
841    }
842}
843
844impl ResourceTimingListener for FetchLaterListener {
845    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
846        (InitiatorType::Fetch, self.url.clone())
847    }
848
849    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
850        self.global.root()
851    }
852}
853
854fn fill_headers_with_metadata(cx: &mut JSContext, r: &Response, m: Metadata) {
855    r.set_headers(cx, m.headers);
856    r.set_status(&m.status);
857    r.set_final_url(m.final_url);
858    r.set_redirected(m.redirected);
859}
860
861pub(crate) trait CspViolationsProcessor {
862    fn process_csp_violations(&self, cx: &mut JSContext, violations: Vec<Violation>);
863}
864
865/// Convenience function for synchronously loading a whole resource.
866pub(crate) fn load_whole_resource(
867    request: RequestBuilder,
868    core_resource_thread: &CoreResourceThread,
869    global: &GlobalScope,
870    csp_violations_processor: &dyn CspViolationsProcessor,
871    cx: &mut JSContext,
872) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
873    let (action_sender, action_receiver) = GenericCallback::new_blocking().unwrap();
874    let url = request.url.url();
875    core_resource_thread
876        .send(CoreResourceMsg::Fetch(
877            request,
878            FetchChannels::ResponseMsg(action_sender),
879        ))
880        .unwrap();
881
882    let mut buf = vec![];
883    let mut metadata = None;
884    let mut muted_errors = false;
885    loop {
886        match action_receiver.recv().unwrap() {
887            FetchResponseMsg::ProcessRequestBody(..) => {},
888            FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
889                muted_errors = m.is_cors_cross_origin();
890                metadata = Some(match m {
891                    FetchMetadata::Unfiltered(m) => m,
892                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
893                })
894            },
895            FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
896            FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
897                let metadata = metadata.unwrap();
898                if let Some(timing) = &metadata.timing {
899                    submit_timing_data(cx, global, url, InitiatorType::Other, timing);
900                }
901                return Ok((metadata, buf, muted_errors));
902            },
903            FetchResponseMsg::ProcessResponse(_, Err(e)) |
904            FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
905            FetchResponseMsg::ProcessCspViolations(_, violations) => {
906                csp_violations_processor.process_csp_violations(cx, violations);
907            },
908            FetchResponseMsg::ProcessContentLength(_request_id, size) => {
909                buf.reserve(size.saturating_sub(buf.len()))
910            },
911        }
912    }
913}
914
915pub(crate) trait RequestWithGlobalScope {
916    fn with_global_scope(self, global: &GlobalScope) -> Self;
917}
918
919impl RequestWithGlobalScope for RequestBuilder {
920    fn with_global_scope(self, global: &GlobalScope) -> Self {
921        self.client(global.request_client(None))
922            .pipeline_id(Some(global.pipeline_id()))
923    }
924}
925
926/// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request>
927/// This function is temporary, since it does not ensure that blob URLs are claimed
928/// appropriately. All callers must migrate to create_a_potential_cors_request_with_claim.
929#[allow(clippy::too_many_arguments)]
930pub(crate) fn create_a_potential_cors_request(
931    webview_id: Option<WebViewId>,
932    url: ServoUrl,
933    destination: Destination,
934    cors_setting: Option<CorsSettings>,
935    same_origin_fallback: Option<bool>,
936    referrer: Referrer,
937) -> RequestBuilder {
938    create_a_potential_cors_request_with_claim(
939        webview_id,
940        UrlWithBlobClaim::from_url_without_having_claimed_blob(url),
941        destination,
942        cors_setting,
943        same_origin_fallback,
944        referrer,
945    )
946}
947
948/// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request>
949#[allow(clippy::too_many_arguments)]
950pub(crate) fn create_a_potential_cors_request_with_claim(
951    webview_id: Option<WebViewId>,
952    url: UrlWithBlobClaim,
953    destination: Destination,
954    cors_setting: Option<CorsSettings>,
955    same_origin_fallback: Option<bool>,
956    referrer: Referrer,
957) -> RequestBuilder {
958    RequestBuilder::new(webview_id, url, referrer)
959        // Step 1. Let mode be "no-cors" if corsAttributeState is No CORS, and "cors" otherwise.
960        .mode(match cors_setting {
961            Some(_) => RequestMode::CorsMode,
962            // Step 2. If same-origin fallback flag is set and mode is "no-cors", set mode to "same-origin".
963            None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
964            None => RequestMode::NoCors,
965        })
966        .credentials_mode(match cors_setting {
967            // Step 4. If corsAttributeState is Anonymous, set credentialsMode to "same-origin".
968            Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
969            // Step 3. Let credentialsMode be "include".
970            _ => CredentialsMode::Include,
971        })
972        // Step 5. Return a new request whose URL is url, destination is destination,
973        // mode is mode, credentials mode is credentialsMode, and whose use-URL-credentials flag is set.
974        .destination(destination)
975        .use_url_credentials(true)
976}