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::realm::CurrentRealm;
14use js::rust::HandleValue;
15use js::rust::wrappers::JS_SetPendingException;
16use net_traits::request::{
17    CorsSettings, CredentialsMode, Destination, Referrer, Request as NetTraitsRequest,
18    RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
19};
20use net_traits::{
21    CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseMsg,
22    FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming, cancel_async_fetch,
23};
24use rustc_hash::FxHashMap;
25use script_bindings::cformat;
26use serde::{Deserialize, Serialize};
27use servo_url::ServoUrl;
28use timers::TimerEventRequest;
29use uuid::Uuid;
30
31use crate::body::BodyMixin;
32use crate::dom::abortsignal::AbortAlgorithm;
33use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
34use crate::dom::bindings::codegen::Bindings::RequestBinding::{
35    RequestInfo, RequestInit, RequestMethods,
36};
37use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
38use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
39use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
40use crate::dom::bindings::error::{Error, Fallible};
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::{enter_auto_realm, 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: &mut js::context::JSContext,
183) {
184    // Step 1. Reject promise with error.
185    promise.reject(cx.into(), abort_reason, CanGc::from_cx(cx));
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);
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, CanGc::from_cx(cx));
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    cx: &mut CurrentRealm,
212) -> Rc<Promise> {
213    // Step 1. Let p be a new promise.
214    let promise = Promise::new_in_realm(cx);
215
216    // Step 7. Let responseObject be null.
217    // NOTE: We do initialize the object earlier earlier so we can use it to track errors
218    let response = Response::new(global, CanGc::from_cx(cx));
219    response
220        .Headers(CanGc::from_cx(cx))
221        .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, CanGc::from_cx(cx), input, init) {
226        Err(e) => {
227            response.error_stream(e.clone(), CanGc::from_cx(cx));
228            promise.reject_error(e, CanGc::from_cx(cx));
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.into(), 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        );
251        // Step 4.2. Return p.
252        return promise;
253    }
254
255    let keep_alive = request.keep_alive;
256    // Step 5. Let globalObject be request’s client’s global object.
257    // NOTE:   We already get the global object as an argument
258    let mut request_init = request_init_from_request(request, global);
259
260    // Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s
261    //         service-workers mode to "none".
262    if global.is::<ServiceWorkerGlobalScope>() {
263        request_init.service_workers_mode = ServiceWorkersMode::None;
264    }
265
266    // Step 8. Let relevantRealm be this’s relevant realm.
267    //
268    // Is `comp` as argument
269
270    // Step 9. Let locallyAborted be false.
271    // Step 10. Let controller be null.
272    let fetch_context = FetchContext {
273        fetch_promise: Some(TrustedPromise::new(promise.clone())),
274        response_object: Trusted::new(&*response),
275        request: Trusted::new(&*request_object),
276        global: Trusted::new(global),
277        locally_aborted: false,
278        canceller: FetchCanceller::new(request_id, keep_alive, global.core_resource_thread()),
279        url: request_init.url.clone(),
280    };
281    let network_listener = NetworkListener::new(
282        fetch_context,
283        global.task_manager().networking_task_source().to_sendable(),
284    );
285    let fetch_context = network_listener.context.clone();
286
287    // Step 11. Add the following abort steps to requestObject’s signal:
288    signal.add(&AbortAlgorithm::Fetch(fetch_context));
289
290    // Step 12. Set controller to the result of calling fetch given request and
291    // processResponse given response being these steps:
292    global.fetch_with_network_listener(request_init, network_listener);
293
294    // Step 13. Return p.
295    promise
296}
297
298/// <https://fetch.spec.whatwg.org/#queue-a-deferred-fetch>
299fn queue_deferred_fetch(
300    request: NetTraitsRequest,
301    activate_after: Finite<f64>,
302    global: &GlobalScope,
303) -> DeferredFetchRecordId {
304    let trusted_global = Trusted::new(global);
305    let mut request = request;
306    // Step 1. Populate request from client given request.
307    request.client = Some(global.request_client());
308    request.populate_request_from_client();
309    // Step 2. Set request’s service-workers mode to "none".
310    request.service_workers_mode = ServiceWorkersMode::None;
311    // Step 3. Set request’s keepalive to true.
312    request.keep_alive = true;
313    // Step 4. Let deferredRecord be a new deferred fetch record whose request is request, and whose notify invoked is onActivatedWithoutTermination.
314    let deferred_record = Rc::new(DeferredFetchRecord {
315        request,
316        invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
317        activated: Cell::new(false),
318    });
319    // Step 5. Append deferredRecord to request’s client’s fetch group’s deferred fetch records.
320    let deferred_fetch_record_id = global.append_deferred_fetch(deferred_record);
321    // Step 6. If activateAfter is non-null, then run the following steps in parallel:
322    global.schedule_timer(TimerEventRequest {
323        callback: Box::new(move || {
324            // Step 6.2. Process deferredRecord.
325            let global = trusted_global.root();
326            global.deferred_fetch_record_for_id(&deferred_fetch_record_id).process(&global);
327
328            // Last step of https://fetch.spec.whatwg.org/#process-a-deferred-fetch
329            //
330            // Step 4. Queue a global task on the deferred fetch task source with
331            // deferredRecord’s request’s client’s global object to run deferredRecord’s notify invoked.
332            let trusted_global = trusted_global.clone();
333            global.task_manager().deferred_fetch_task_source().queue(
334                task!(notify_deferred_record: move || {
335                    trusted_global.root().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
336                }),
337            );
338        }),
339        // Step 6.1. The user agent should wait until any of the following conditions is met:
340        duration: Duration::from_millis(*activate_after as u64),
341    });
342    // Step 7. Return deferredRecord.
343    deferred_fetch_record_id
344}
345
346/// <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
347#[expect(non_snake_case, unsafe_code)]
348pub(crate) fn FetchLater(
349    window: &Window,
350    input: RequestInfo,
351    init: RootedTraceableBox<DeferredRequestInit>,
352    can_gc: CanGc,
353) -> Fallible<DomRoot<FetchLaterResult>> {
354    let global_scope = window.upcast();
355    let document = window.Document();
356    // Step 1. Let requestObject be the result of invoking the initial value
357    // of Request as constructor with input and init as arguments.
358    let request_object = Request::constructor(global_scope, None, can_gc, input, &init.parent)?;
359    // Step 2. If requestObject’s signal is aborted, then throw signal’s abort reason.
360    let signal = request_object.Signal();
361    if signal.aborted() {
362        let cx = GlobalScope::get_cx();
363        rooted!(in(*cx) let mut abort_reason = UndefinedValue());
364        signal.Reason(cx, abort_reason.handle_mut());
365        unsafe {
366            assert!(!JS_IsExceptionPending(*cx));
367            JS_SetPendingException(*cx, abort_reason.handle(), ExceptionStackBehavior::Capture);
368        }
369        return Err(Error::JSFailed);
370    }
371    // Step 3. Let request be requestObject’s request.
372    let request = request_object.get_request();
373    // Step 4. Let activateAfter be null.
374    let mut activate_after = Finite::wrap(0_f64);
375    // Step 5. If init is given and init["activateAfter"] exists, then set
376    // activateAfter to init["activateAfter"].
377    if let Some(init_activate_after) = init.activateAfter.as_ref() {
378        activate_after = *init_activate_after;
379    }
380    // Step 6. If activateAfter is less than 0, then throw a RangeError.
381    if *activate_after < 0.0 {
382        return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
383    }
384    // Step 7. If this’s relevant global object’s associated document is not fully active, then throw a TypeError.
385    if !document.is_fully_active() {
386        return Err(Error::Type(c"Document is not fully active".to_owned()));
387    }
388    let url = request.url();
389    // Step 8. If request’s URL’s scheme is not an HTTP(S) scheme, then throw a TypeError.
390    if !matches!(url.scheme(), "http" | "https") {
391        return Err(Error::Type(c"URL is not http(s)".to_owned()));
392    }
393    // Step 9. If request’s URL is not a potentially trustworthy URL, then throw a SecurityError.
394    if !url.is_potentially_trustworthy() {
395        return Err(Error::Type(c"URL is not trustworthy".to_owned()));
396    }
397    // Step 10. If request’s body is not null, and request’s body length is null or zero, then throw a TypeError.
398    if let Some(body) = request.body.as_ref() {
399        if body.len().is_none_or(|len| len == 0) {
400            return Err(Error::Type(c"Body is empty".to_owned()));
401        }
402    }
403    // Step 11. If the available deferred-fetch quota given request’s client and request’s URL’s
404    // origin is less than request’s total request length, then throw a "QuotaExceededError" DOMException.
405    let quota = document.available_deferred_fetch_quota(request.url().origin());
406    let requested = request.total_request_length() as isize;
407    if quota < requested {
408        return Err(Error::QuotaExceeded {
409            quota: Some(Finite::wrap(quota as f64)),
410            requested: Some(Finite::wrap(requested as f64)),
411        });
412    }
413    // Step 12. Let activated be false.
414    // Step 13. Let deferredRecord be the result of calling queue a deferred fetch given request,
415    // activateAfter, and the following step: set activated to true.
416    let deferred_record_id = queue_deferred_fetch(request, activate_after, global_scope);
417    // Step 14. Add the following abort steps to requestObject’s signal: Set deferredRecord’s invoke state to "aborted".
418    signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
419    // Step 15. Return a new FetchLaterResult whose activated getter steps are to return activated.
420    Ok(FetchLaterResult::new(window, deferred_record_id, can_gc))
421}
422
423/// <https://fetch.spec.whatwg.org/#deferred-fetch-record-invoke-state>
424#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
425pub(crate) enum DeferredFetchRecordInvokeState {
426    Pending,
427    Sent,
428    Aborted,
429}
430
431/// <https://fetch.spec.whatwg.org/#deferred-fetch-record>
432#[derive(MallocSizeOf)]
433pub(crate) struct DeferredFetchRecord {
434    /// <https://fetch.spec.whatwg.org/#deferred-fetch-record-request>
435    pub(crate) request: NetTraitsRequest,
436    /// <https://fetch.spec.whatwg.org/#deferred-fetch-record-invoke-state>
437    pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
438    activated: Cell<bool>,
439}
440
441impl DeferredFetchRecord {
442    /// Part of step 13 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
443    fn activate(&self) {
444        // and the following step: set activated to true.
445        self.activated.set(true);
446    }
447    /// Part of step 14 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
448    pub(crate) fn abort(&self) {
449        // Set deferredRecord’s invoke state to "aborted".
450        self.invoke_state
451            .set(DeferredFetchRecordInvokeState::Aborted);
452    }
453    /// Part of step 15 of <https://fetch.spec.whatwg.org/#dom-window-fetchlater>
454    pub(crate) fn activated_getter_steps(&self) -> bool {
455        // whose activated getter steps are to return activated.
456        self.activated.get()
457    }
458    /// <https://fetch.spec.whatwg.org/#process-a-deferred-fetch>
459    pub(crate) fn process(&self, global: &GlobalScope) {
460        // Step 1. If deferredRecord’s invoke state is not "pending", then return.
461        if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
462            return;
463        }
464        // Step 2. Set deferredRecord’s invoke state to "sent".
465        self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
466        // Step 3. Fetch deferredRecord’s request.
467        let fetch_later_listener = FetchLaterListener {
468            url: self.request.url(),
469            global: Trusted::new(global),
470        };
471        let request_init = request_init_from_request(self.request.clone(), global);
472        global.fetch(
473            request_init,
474            fetch_later_listener,
475            global.task_manager().networking_task_source().to_sendable(),
476        );
477        // Step 4 is handled by caller
478    }
479}
480
481#[derive(JSTraceable, MallocSizeOf)]
482pub(crate) struct FetchContext {
483    #[ignore_malloc_size_of = "unclear ownership semantics"]
484    fetch_promise: Option<TrustedPromise>,
485    response_object: Trusted<Response>,
486    request: Trusted<Request>,
487    global: Trusted<GlobalScope>,
488    locally_aborted: bool,
489    canceller: FetchCanceller,
490    #[no_trace]
491    url: ServoUrl,
492}
493
494impl FetchContext {
495    /// Step 11 of <https://fetch.spec.whatwg.org/#dom-global-fetch>
496    pub(crate) fn abort_fetch(
497        &mut self,
498        abort_reason: HandleValue,
499        cx: &mut js::context::JSContext,
500    ) {
501        // Step 11.1. Set locallyAborted to true.
502        self.locally_aborted = true;
503        // Step 11.2. Assert: controller is non-null.
504        //
505        // N/a, that's self
506
507        // Step 11.3. Abort controller with requestObject’s signal’s abort reason.
508        self.canceller.abort();
509
510        // Step 11.4. Abort the fetch() call with p, request, responseObject,
511        // and requestObject’s signal’s abort reason.
512        let promise = self
513            .fetch_promise
514            .take()
515            .expect("fetch promise is missing")
516            .root();
517        abort_fetch_call(
518            promise,
519            &self.request.root(),
520            Some(&self.response_object.root()),
521            abort_reason,
522            &self.global.root(),
523            cx,
524        );
525    }
526}
527
528/// Step 12 of <https://fetch.spec.whatwg.org/#dom-global-fetch>
529impl FetchResponseListener for FetchContext {
530    fn process_request_body(&mut self, _: RequestId) {
531        // TODO
532    }
533
534    fn process_request_eof(&mut self, _: RequestId) {
535        // TODO
536    }
537
538    fn process_response(
539        &mut self,
540        cx: &mut js::context::JSContext,
541        _: RequestId,
542        fetch_metadata: Result<FetchMetadata, NetworkError>,
543    ) {
544        // Step 12.1. If locallyAborted is true, then abort these steps.
545        if self.locally_aborted {
546            return;
547        }
548        let promise = self
549            .fetch_promise
550            .take()
551            .expect("fetch promise is missing")
552            .root();
553
554        let mut realm = enter_auto_realm(cx, &*promise);
555        let cx = &mut realm.current_realm();
556        match fetch_metadata {
557            // Step 12.3. If response is a network error, then reject
558            // p with a TypeError and abort these steps.
559            Err(error) => {
560                promise.reject_error(
561                    Error::Type(cformat!("Network error: {:?}", error)),
562                    CanGc::from_cx(cx),
563                );
564                self.fetch_promise = Some(TrustedPromise::new(promise));
565                let response = self.response_object.root();
566                response.set_type(DOMResponseType::Error, CanGc::from_cx(cx));
567                response.error_stream(
568                    Error::Type(c"Network error occurred".to_owned()),
569                    CanGc::from_cx(cx),
570                );
571                return;
572            },
573            // Step 12.4. Set responseObject to the result of creating a Response object,
574            // given response, "immutable", and relevantRealm.
575            Ok(metadata) => match metadata {
576                FetchMetadata::Unfiltered(m) => {
577                    fill_headers_with_metadata(self.response_object.root(), m, CanGc::from_cx(cx));
578                    self.response_object
579                        .root()
580                        .set_type(DOMResponseType::Default, CanGc::from_cx(cx));
581                },
582                FetchMetadata::Filtered { filtered, .. } => match filtered {
583                    FilteredMetadata::Basic(m) => {
584                        fill_headers_with_metadata(
585                            self.response_object.root(),
586                            m,
587                            CanGc::from_cx(cx),
588                        );
589                        self.response_object
590                            .root()
591                            .set_type(DOMResponseType::Basic, CanGc::from_cx(cx));
592                    },
593                    FilteredMetadata::Cors(m) => {
594                        fill_headers_with_metadata(
595                            self.response_object.root(),
596                            m,
597                            CanGc::from_cx(cx),
598                        );
599                        self.response_object
600                            .root()
601                            .set_type(DOMResponseType::Cors, CanGc::from_cx(cx));
602                    },
603                    FilteredMetadata::Opaque => {
604                        self.response_object
605                            .root()
606                            .set_type(DOMResponseType::Opaque, CanGc::from_cx(cx));
607                    },
608                    FilteredMetadata::OpaqueRedirect(url) => {
609                        let r = self.response_object.root();
610                        r.set_type(DOMResponseType::Opaqueredirect, CanGc::from_cx(cx));
611                        r.set_final_url(url);
612                    },
613                },
614            },
615        }
616
617        // Step 12.5. Resolve p with responseObject.
618        promise.resolve_native(&self.response_object.root(), CanGc::from_cx(cx));
619        self.fetch_promise = Some(TrustedPromise::new(promise));
620    }
621
622    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
623        let response = self.response_object.root();
624        response.stream_chunk(chunk, CanGc::note());
625    }
626
627    fn process_response_eof(
628        self,
629        cx: &mut js::context::JSContext,
630        _: RequestId,
631        response: Result<(), NetworkError>,
632        timing: ResourceFetchTiming,
633    ) {
634        let response_object = self.response_object.root();
635        let _ac = enter_realm(&*response_object);
636        if let Err(ref error) = response {
637            if *error == NetworkError::DecompressionError {
638                response_object.error_stream(
639                    Error::Type(c"Network error occurred".to_owned()),
640                    CanGc::from_cx(cx),
641                );
642            }
643        }
644        response_object.finish(CanGc::from_cx(cx));
645        // TODO
646        // ... trailerObject is not supported in Servo yet.
647
648        // navigation submission is handled in servoparser/mod.rs
649        network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
650    }
651
652    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
653        let global = &self.resource_timing_global();
654        global.report_csp_violations(violations, None, None);
655    }
656}
657
658impl ResourceTimingListener for FetchContext {
659    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
660        (InitiatorType::Fetch, self.url.clone())
661    }
662
663    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
664        self.response_object.root().global()
665    }
666}
667
668struct FetchLaterListener {
669    /// URL of this request.
670    url: ServoUrl,
671    /// The global object fetching the report uri violation
672    global: Trusted<GlobalScope>,
673}
674
675impl FetchResponseListener for FetchLaterListener {
676    fn process_request_body(&mut self, _: RequestId) {}
677
678    fn process_request_eof(&mut self, _: RequestId) {}
679
680    fn process_response(
681        &mut self,
682        _: &mut js::context::JSContext,
683        _: RequestId,
684        fetch_metadata: Result<FetchMetadata, NetworkError>,
685    ) {
686        _ = fetch_metadata;
687    }
688
689    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
690        _ = chunk;
691    }
692
693    fn process_response_eof(
694        self,
695        cx: &mut js::context::JSContext,
696        _: RequestId,
697        response: Result<(), NetworkError>,
698        timing: ResourceFetchTiming,
699    ) {
700        network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
701    }
702
703    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
704        let global = self.resource_timing_global();
705        global.report_csp_violations(violations, None, None);
706    }
707}
708
709impl ResourceTimingListener for FetchLaterListener {
710    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
711        (InitiatorType::Fetch, self.url.clone())
712    }
713
714    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
715        self.global.root()
716    }
717}
718
719fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata, can_gc: CanGc) {
720    r.set_headers(m.headers, can_gc);
721    r.set_status(&m.status);
722    r.set_final_url(m.final_url);
723    r.set_redirected(m.redirected);
724}
725
726pub(crate) trait CspViolationsProcessor {
727    fn process_csp_violations(&self, violations: Vec<Violation>);
728}
729
730/// Convenience function for synchronously loading a whole resource.
731pub(crate) fn load_whole_resource(
732    request: RequestBuilder,
733    core_resource_thread: &CoreResourceThread,
734    global: &GlobalScope,
735    csp_violations_processor: &dyn CspViolationsProcessor,
736    cx: &mut js::context::JSContext,
737) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
738    let request = request.https_state(global.get_https_state());
739    let (action_sender, action_receiver) = ipc::channel().unwrap();
740    let url = request.url.clone();
741    core_resource_thread
742        .send(CoreResourceMsg::Fetch(
743            request,
744            FetchChannels::ResponseMsg(action_sender),
745        ))
746        .unwrap();
747
748    let mut buf = vec![];
749    let mut metadata = None;
750    let mut muted_errors = false;
751    loop {
752        match action_receiver.recv().unwrap() {
753            FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => {
754            },
755            FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
756                muted_errors = m.is_cors_cross_origin();
757                metadata = Some(match m {
758                    FetchMetadata::Unfiltered(m) => m,
759                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
760                })
761            },
762            FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
763            FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
764                let metadata = metadata.unwrap();
765                if let Some(timing) = &metadata.timing {
766                    submit_timing_data(
767                        global,
768                        url,
769                        InitiatorType::Other,
770                        timing,
771                        CanGc::from_cx(cx),
772                    );
773                }
774                return Ok((metadata, buf, muted_errors));
775            },
776            FetchResponseMsg::ProcessResponse(_, Err(e)) |
777            FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
778            FetchResponseMsg::ProcessCspViolations(_, violations) => {
779                csp_violations_processor.process_csp_violations(violations);
780            },
781        }
782    }
783}
784
785pub(crate) trait RequestWithGlobalScope {
786    fn with_global_scope(self, global: &GlobalScope) -> Self;
787}
788
789impl RequestWithGlobalScope for RequestBuilder {
790    fn with_global_scope(self, global: &GlobalScope) -> Self {
791        self.insecure_requests_policy(global.insecure_requests_policy())
792            .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
793            .policy_container(global.policy_container())
794            .client(global.request_client())
795            .pipeline_id(Some(global.pipeline_id()))
796            .origin(global.origin().immutable().clone())
797            .https_state(global.get_https_state())
798    }
799}
800
801/// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request>
802#[allow(clippy::too_many_arguments)]
803pub(crate) fn create_a_potential_cors_request(
804    webview_id: Option<WebViewId>,
805    url: ServoUrl,
806    destination: Destination,
807    cors_setting: Option<CorsSettings>,
808    same_origin_fallback: Option<bool>,
809    referrer: Referrer,
810) -> RequestBuilder {
811    RequestBuilder::new(webview_id, url, referrer)
812        // Step 1. Let mode be "no-cors" if corsAttributeState is No CORS, and "cors" otherwise.
813        .mode(match cors_setting {
814            Some(_) => RequestMode::CorsMode,
815            // Step 2. If same-origin fallback flag is set and mode is "no-cors", set mode to "same-origin".
816            None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
817            None => RequestMode::NoCors,
818        })
819        .credentials_mode(match cors_setting {
820            // Step 4. If corsAttributeState is Anonymous, set credentialsMode to "same-origin".
821            Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
822            // Step 3. Let credentialsMode be "include".
823            _ => CredentialsMode::Include,
824        })
825        // Step 5. Return a new request whose URL is url, destination is destination,
826        // mode is mode, credentials mode is credentialsMode, and whose use-URL-credentials flag is set.
827        .destination(destination)
828        .use_url_credentials(true)
829}