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