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