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