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