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