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