Skip to main content

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