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