1use std::cell::Cell;
6use std::rc::Rc;
7use std::time::Duration;
8
9use ipc_channel::ipc;
10use js::jsapi::{ExceptionStackBehavior, JS_IsExceptionPending};
11use js::jsval::UndefinedValue;
12use js::realm::CurrentRealm;
13use js::rust::HandleValue;
14use js::rust::wrappers::JS_SetPendingException;
15use net_traits::blob_url_store::UrlWithBlobClaim;
16use net_traits::request::{
17 CorsSettings, CredentialsMode, Destination, Referrer, Request as NetTraitsRequest,
18 RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
19};
20use net_traits::{
21 CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseMsg,
22 FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming, cancel_async_fetch,
23};
24use rustc_hash::FxHashMap;
25use script_bindings::cformat;
26use serde::{Deserialize, Serialize};
27use servo_base::id::WebViewId;
28use servo_url::ServoUrl;
29use timers::TimerEventRequest;
30use uuid::Uuid;
31
32use crate::body::BodyMixin;
33use crate::dom::abortsignal::AbortAlgorithm;
34use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
35use crate::dom::bindings::codegen::Bindings::RequestBinding::{
36 RequestInfo, RequestInit, RequestMethods,
37};
38use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
39use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
40use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
41use crate::dom::bindings::error::{Error, Fallible};
42use crate::dom::bindings::inheritance::Castable;
43use crate::dom::bindings::num::Finite;
44use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
45use crate::dom::bindings::reflector::DomGlobal;
46use crate::dom::bindings::root::DomRoot;
47use crate::dom::bindings::trace::RootedTraceableBox;
48use crate::dom::csp::{GlobalCspReporting, Violation};
49use crate::dom::fetchlaterresult::FetchLaterResult;
50use crate::dom::globalscope::GlobalScope;
51use crate::dom::headers::Guard;
52use crate::dom::performance::performanceresourcetiming::InitiatorType;
53use crate::dom::promise::Promise;
54use crate::dom::request::Request;
55use crate::dom::response::Response;
56use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
57use crate::dom::window::Window;
58use crate::network_listener::{
59 self, FetchResponseListener, NetworkListener, ResourceTimingListener, submit_timing_data,
60};
61use crate::realms::{enter_auto_realm, enter_realm};
62use crate::script_runtime::CanGc;
63
64#[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 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 if let Some(ref core_resource_thread) = self.core_resource_thread {
102 cancel_async_fetch(vec![request_id], core_resource_thread);
105 }
106 }
107 }
108
109 pub(crate) fn ignore(&mut self) {
112 let _ = self.request_id.take();
113 }
114
115 pub(crate) fn abort(&mut self) {
117 self.cancel();
118 }
119
120 pub(crate) fn terminate(&mut self) {
122 self.cancel();
123 }
124}
125
126#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
127pub(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#[derive(Default, MallocSizeOf)]
140pub(crate) struct FetchGroup {
141 #[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())
171 .insecure_requests_policy(request.insecure_requests_policy)
172 .has_trustworthy_ancestor_origin(request.has_trustworthy_ancestor_origin)
173 .response_tainting(request.response_tainting);
174 builder.id = request.id;
175 builder
176}
177
178fn abort_fetch_call(
180 promise: Rc<Promise>,
181 request: &Request,
182 response_object: Option<&Response>,
183 abort_reason: HandleValue,
184 global: &GlobalScope,
185 cx: &mut js::context::JSContext,
186) {
187 promise.reject(cx.into(), abort_reason, CanGc::from_cx(cx));
189 if let Some(body) = request.body() &&
191 body.is_readable()
192 {
193 body.cancel(cx, global, abort_reason);
194 }
195 let Some(response) = response_object else {
198 return;
199 };
200 if let Some(body) = response.body() &&
202 body.is_readable()
203 {
204 body.error(cx, abort_reason);
205 }
206}
207
208#[expect(non_snake_case)]
210pub(crate) fn Fetch(
211 global: &GlobalScope,
212 input: RequestInfo,
213 init: RootedTraceableBox<RequestInit>,
214 cx: &mut CurrentRealm,
215) -> Rc<Promise> {
216 let promise = Promise::new_in_realm(cx);
218
219 let response = Response::new(cx, global);
222 response
223 .Headers(CanGc::from_cx(cx))
224 .set_guard(Guard::Immutable);
225
226 let request_object = match Request::Constructor(cx, global, None, input, init) {
229 Err(e) => {
230 response.error_stream(cx, e.clone());
231 promise.reject_error(e, CanGc::from_cx(cx));
232 return promise;
233 },
234 Ok(r) => r,
235 };
236 let request = request_object.get_request();
238 let request_id = request.id;
239
240 let signal = request_object.Signal();
242 if signal.aborted() {
243 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
245 signal.Reason(cx.into(), abort_reason.handle_mut());
246 abort_fetch_call(
247 promise.clone(),
248 &request_object,
249 None,
250 abort_reason.handle(),
251 global,
252 cx,
253 );
254 return promise;
256 }
257
258 let keep_alive = request.keep_alive;
259 let mut request_init = request_init_from_request(request, global);
262
263 if global.is::<ServiceWorkerGlobalScope>() {
266 request_init.service_workers_mode = ServiceWorkersMode::None;
267 }
268
269 let fetch_context = FetchContext {
276 fetch_promise: Some(TrustedPromise::new(promise.clone())),
277 response_object: Trusted::new(&*response),
278 request: Trusted::new(&*request_object),
279 global: Trusted::new(global),
280 locally_aborted: false,
281 canceller: FetchCanceller::new(request_id, keep_alive, global.core_resource_thread()),
282 url: request_init.url.url(),
283 };
284 let network_listener = NetworkListener::new(
285 fetch_context,
286 global.task_manager().networking_task_source().to_sendable(),
287 );
288 let fetch_context = network_listener.context.clone();
289
290 signal.add(&AbortAlgorithm::Fetch(fetch_context));
292
293 global.fetch_with_network_listener(request_init, network_listener);
296
297 promise
299}
300
301fn queue_deferred_fetch(
303 request: NetTraitsRequest,
304 activate_after: Finite<f64>,
305 global: &GlobalScope,
306) -> DeferredFetchRecordId {
307 let trusted_global = Trusted::new(global);
308 let mut request = request;
309 request.client = Some(global.request_client());
311 request.populate_request_from_client();
312 request.service_workers_mode = ServiceWorkersMode::None;
314 request.keep_alive = true;
316 let deferred_record = Rc::new(DeferredFetchRecord {
318 request,
319 invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
320 activated: Cell::new(false),
321 });
322 let deferred_fetch_record_id = global.append_deferred_fetch(deferred_record);
324 global.schedule_timer(TimerEventRequest {
326 callback: Box::new(move || {
327 let global = trusted_global.root();
329 global.deferred_fetch_record_for_id(&deferred_fetch_record_id).process(&global);
330
331 let trusted_global = trusted_global.clone();
336 global.task_manager().deferred_fetch_task_source().queue(
337 task!(notify_deferred_record: move || {
338 trusted_global.root().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
339 }),
340 );
341 }),
342 duration: Duration::from_millis(*activate_after as u64),
344 });
345 deferred_fetch_record_id
347}
348
349#[expect(non_snake_case, unsafe_code)]
351pub(crate) fn FetchLater(
352 cx: &mut js::context::JSContext,
353 window: &Window,
354 input: RequestInfo,
355 init: RootedTraceableBox<DeferredRequestInit>,
356) -> Fallible<DomRoot<FetchLaterResult>> {
357 let global_scope = window.upcast();
358 let document = window.Document();
359 let request_object = Request::constructor(cx, global_scope, None, input, &init.parent)?;
362 let signal = request_object.Signal();
364 if signal.aborted() {
365 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
366 signal.Reason(cx.into(), abort_reason.handle_mut());
367 unsafe {
368 assert!(!JS_IsExceptionPending(cx.raw_cx()));
369 JS_SetPendingException(
370 cx.raw_cx(),
371 abort_reason.handle(),
372 ExceptionStackBehavior::Capture,
373 );
374 }
375 return Err(Error::JSFailed);
376 }
377 let request = request_object.get_request();
379 let mut activate_after = Finite::wrap(0_f64);
381 if let Some(init_activate_after) = init.activateAfter.as_ref() {
384 activate_after = *init_activate_after;
385 }
386 if *activate_after < 0.0 {
388 return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
389 }
390 if !document.is_fully_active() {
392 return Err(Error::Type(c"Document is not fully active".to_owned()));
393 }
394 let url = request.url();
395 if !matches!(url.scheme(), "http" | "https") {
397 return Err(Error::Type(c"URL is not http(s)".to_owned()));
398 }
399 if !url.is_potentially_trustworthy() {
401 return Err(Error::Type(c"URL is not trustworthy".to_owned()));
402 }
403 if request
405 .body
406 .as_ref()
407 .is_some_and(|body| body.len().is_none())
408 {
409 return Err(Error::Type(c"Body is empty".to_owned()));
410 }
411 let quota = document.available_deferred_fetch_quota(request.url().origin());
414 let requested = request.total_request_length() as isize;
415 if quota < requested {
416 return Err(Error::QuotaExceeded {
417 quota: Some(Finite::wrap(quota as f64)),
418 requested: Some(Finite::wrap(requested as f64)),
419 });
420 }
421 let deferred_record_id = queue_deferred_fetch(request, activate_after, global_scope);
425 signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
427 Ok(FetchLaterResult::new(
429 window,
430 deferred_record_id,
431 CanGc::from_cx(cx),
432 ))
433}
434
435#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
437pub(crate) enum DeferredFetchRecordInvokeState {
438 Pending,
439 Sent,
440 Aborted,
441}
442
443#[derive(MallocSizeOf)]
445pub(crate) struct DeferredFetchRecord {
446 pub(crate) request: NetTraitsRequest,
448 pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
450 activated: Cell<bool>,
451}
452
453impl DeferredFetchRecord {
454 fn activate(&self) {
456 self.activated.set(true);
458 }
459 pub(crate) fn abort(&self) {
461 self.invoke_state
463 .set(DeferredFetchRecordInvokeState::Aborted);
464 }
465 pub(crate) fn activated_getter_steps(&self) -> bool {
467 self.activated.get()
469 }
470 pub(crate) fn process(&self, global: &GlobalScope) {
472 if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
474 return;
475 }
476 self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
478 let fetch_later_listener = FetchLaterListener {
480 url: self.request.url(),
481 global: Trusted::new(global),
482 };
483 let request_init = request_init_from_request(self.request.clone(), global);
484 global.fetch(
485 request_init,
486 fetch_later_listener,
487 global.task_manager().networking_task_source().to_sendable(),
488 );
489 }
491}
492
493#[derive(JSTraceable, MallocSizeOf)]
494pub(crate) struct FetchContext {
495 #[ignore_malloc_size_of = "unclear ownership semantics"]
496 fetch_promise: Option<TrustedPromise>,
497 response_object: Trusted<Response>,
498 request: Trusted<Request>,
499 global: Trusted<GlobalScope>,
500 locally_aborted: bool,
501 canceller: FetchCanceller,
502 #[no_trace]
503 url: ServoUrl,
504}
505
506impl FetchContext {
507 pub(crate) fn abort_fetch(
509 &mut self,
510 abort_reason: HandleValue,
511 cx: &mut js::context::JSContext,
512 ) {
513 self.locally_aborted = true;
515 self.canceller.abort();
521
522 let promise = self
525 .fetch_promise
526 .take()
527 .expect("fetch promise is missing")
528 .root();
529 abort_fetch_call(
530 promise,
531 &self.request.root(),
532 Some(&self.response_object.root()),
533 abort_reason,
534 &self.global.root(),
535 cx,
536 );
537 }
538}
539
540impl FetchResponseListener for FetchContext {
542 fn process_request_body(&mut self, _: RequestId) {
543 }
545
546 fn process_response(
547 &mut self,
548 cx: &mut js::context::JSContext,
549 _: RequestId,
550 fetch_metadata: Result<FetchMetadata, NetworkError>,
551 ) {
552 if self.locally_aborted {
554 return;
555 }
556 let promise = self
557 .fetch_promise
558 .take()
559 .expect("fetch promise is missing")
560 .root();
561
562 let mut realm = enter_auto_realm(cx, &*promise);
563 let cx = &mut realm.current_realm();
564 match fetch_metadata {
565 Err(error) => {
568 promise.reject_error(
569 Error::Type(cformat!("Network error: {:?}", error)),
570 CanGc::from_cx(cx),
571 );
572 self.fetch_promise = Some(TrustedPromise::new(promise));
573 let response = self.response_object.root();
574 response.set_type(DOMResponseType::Error, CanGc::from_cx(cx));
575 response.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
576 return;
577 },
578 Ok(metadata) => match metadata {
581 FetchMetadata::Unfiltered(m) => {
582 fill_headers_with_metadata(self.response_object.root(), m, CanGc::from_cx(cx));
583 self.response_object
584 .root()
585 .set_type(DOMResponseType::Default, CanGc::from_cx(cx));
586 },
587 FetchMetadata::Filtered { filtered, .. } => match filtered {
588 FilteredMetadata::Basic(m) => {
589 fill_headers_with_metadata(
590 self.response_object.root(),
591 m,
592 CanGc::from_cx(cx),
593 );
594 self.response_object
595 .root()
596 .set_type(DOMResponseType::Basic, CanGc::from_cx(cx));
597 },
598 FilteredMetadata::Cors(m) => {
599 fill_headers_with_metadata(
600 self.response_object.root(),
601 m,
602 CanGc::from_cx(cx),
603 );
604 self.response_object
605 .root()
606 .set_type(DOMResponseType::Cors, CanGc::from_cx(cx));
607 },
608 FilteredMetadata::Opaque => {
609 self.response_object
610 .root()
611 .set_type(DOMResponseType::Opaque, CanGc::from_cx(cx));
612 },
613 FilteredMetadata::OpaqueRedirect(url) => {
614 let r = self.response_object.root();
615 r.set_type(DOMResponseType::Opaqueredirect, CanGc::from_cx(cx));
616 r.set_final_url(url);
617 },
618 },
619 },
620 }
621
622 promise.resolve_native(&self.response_object.root(), CanGc::from_cx(cx));
624 self.fetch_promise = Some(TrustedPromise::new(promise));
625 }
626
627 fn process_response_chunk(
628 &mut self,
629 cx: &mut js::context::JSContext,
630 _: RequestId,
631 chunk: Vec<u8>,
632 ) {
633 let response = self.response_object.root();
634 response.stream_chunk(cx, chunk);
635 }
636
637 fn process_response_eof(
638 self,
639 cx: &mut js::context::JSContext,
640 _: RequestId,
641 response: Result<(), NetworkError>,
642 timing: ResourceFetchTiming,
643 ) {
644 let response_object = self.response_object.root();
645 let _ac = enter_realm(&*response_object);
646 if let Err(ref error) = response &&
647 *error == NetworkError::DecompressionError
648 {
649 response_object.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
650 }
651 response_object.finish(cx);
652 network_listener::submit_timing(cx, &self, &response, &timing);
657 }
658
659 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
660 let global = &self.resource_timing_global();
661 global.report_csp_violations(violations, None, None);
662 }
663}
664
665impl ResourceTimingListener for FetchContext {
666 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
667 (InitiatorType::Fetch, self.url.clone())
668 }
669
670 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
671 self.response_object.root().global()
672 }
673}
674
675struct FetchLaterListener {
676 url: ServoUrl,
678 global: Trusted<GlobalScope>,
680}
681
682impl FetchResponseListener for FetchLaterListener {
683 fn process_request_body(&mut self, _: RequestId) {}
684
685 fn process_response(
686 &mut self,
687 _: &mut js::context::JSContext,
688 _: RequestId,
689 fetch_metadata: Result<FetchMetadata, NetworkError>,
690 ) {
691 _ = fetch_metadata;
692 }
693
694 fn process_response_chunk(
695 &mut self,
696 _: &mut js::context::JSContext,
697 _: RequestId,
698 chunk: Vec<u8>,
699 ) {
700 _ = chunk;
701 }
702
703 fn process_response_eof(
704 self,
705 cx: &mut js::context::JSContext,
706 _: RequestId,
707 response: Result<(), NetworkError>,
708 timing: ResourceFetchTiming,
709 ) {
710 network_listener::submit_timing(cx, &self, &response, &timing);
711 }
712
713 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
714 let global = self.resource_timing_global();
715 global.report_csp_violations(violations, None, None);
716 }
717}
718
719impl ResourceTimingListener for FetchLaterListener {
720 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
721 (InitiatorType::Fetch, self.url.clone())
722 }
723
724 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
725 self.global.root()
726 }
727}
728
729fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata, can_gc: CanGc) {
730 r.set_headers(m.headers, can_gc);
731 r.set_status(&m.status);
732 r.set_final_url(m.final_url);
733 r.set_redirected(m.redirected);
734}
735
736pub(crate) trait CspViolationsProcessor {
737 fn process_csp_violations(&self, violations: Vec<Violation>);
738}
739
740pub(crate) fn load_whole_resource(
742 request: RequestBuilder,
743 core_resource_thread: &CoreResourceThread,
744 global: &GlobalScope,
745 csp_violations_processor: &dyn CspViolationsProcessor,
746 cx: &mut js::context::JSContext,
747) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
748 let (action_sender, action_receiver) = ipc::channel().unwrap();
749 let url = request.url.url();
750 core_resource_thread
751 .send(CoreResourceMsg::Fetch(
752 request,
753 FetchChannels::ResponseMsg(action_sender),
754 ))
755 .unwrap();
756
757 let mut buf = vec![];
758 let mut metadata = None;
759 let mut muted_errors = false;
760 loop {
761 match action_receiver.recv().unwrap() {
762 FetchResponseMsg::ProcessRequestBody(..) => {},
763 FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
764 muted_errors = m.is_cors_cross_origin();
765 metadata = Some(match m {
766 FetchMetadata::Unfiltered(m) => m,
767 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
768 })
769 },
770 FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
771 FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
772 let metadata = metadata.unwrap();
773 if let Some(timing) = &metadata.timing {
774 submit_timing_data(cx, global, url, InitiatorType::Other, timing);
775 }
776 return Ok((metadata, buf, muted_errors));
777 },
778 FetchResponseMsg::ProcessResponse(_, Err(e)) |
779 FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
780 FetchResponseMsg::ProcessCspViolations(_, violations) => {
781 csp_violations_processor.process_csp_violations(violations);
782 },
783 }
784 }
785}
786
787pub(crate) trait RequestWithGlobalScope {
788 fn with_global_scope(self, global: &GlobalScope) -> Self;
789}
790
791impl RequestWithGlobalScope for RequestBuilder {
792 fn with_global_scope(self, global: &GlobalScope) -> Self {
793 self.insecure_requests_policy(global.insecure_requests_policy())
794 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
795 .policy_container(global.policy_container())
796 .client(global.request_client())
797 .pipeline_id(Some(global.pipeline_id()))
798 .origin(global.origin().immutable().clone())
799 }
800}
801
802#[allow(clippy::too_many_arguments)]
804pub(crate) fn create_a_potential_cors_request(
805 webview_id: Option<WebViewId>,
806 url: ServoUrl,
807 destination: Destination,
808 cors_setting: Option<CorsSettings>,
809 same_origin_fallback: Option<bool>,
810 referrer: Referrer,
811) -> RequestBuilder {
812 RequestBuilder::new(
813 webview_id,
814 UrlWithBlobClaim::from_url_without_having_claimed_blob(url),
815 referrer,
816 )
817 .mode(match cors_setting {
819 Some(_) => RequestMode::CorsMode,
820 None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
822 None => RequestMode::NoCors,
823 })
824 .credentials_mode(match cors_setting {
825 Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
827 _ => CredentialsMode::Include,
829 })
830 .destination(destination)
833 .use_url_credentials(true)
834}