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