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