1use std::cell::Cell;
6use std::rc::Rc;
7use std::time::Duration;
8
9use js::context::JSContext;
10use js::jsapi::ExceptionStackBehavior;
11use js::jsval::UndefinedValue;
12use js::realm::CurrentRealm;
13use js::rust::HandleValue;
14use js::rust::wrappers2::{JS_IsExceptionPending, JS_SetPendingException};
15use net_traits::blob_url_store::UrlWithBlobClaim;
16use net_traits::request::{
17 CorsSettings, CredentialsMode, Destination, Referrer, Request as NetTraitsRequest,
18 RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
19};
20use net_traits::{
21 CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseMsg,
22 FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming, cancel_async_fetch,
23};
24use rustc_hash::FxHashMap;
25use script_bindings::cformat;
26use serde::{Deserialize, Serialize};
27use servo_base::generic_channel::GenericCallback;
28use servo_base::id::WebViewId;
29use servo_url::ServoUrl;
30use timers::TimerEventRequest;
31use uuid::Uuid;
32
33use crate::body::BodyMixin;
34use crate::dom::abortsignal::AbortAlgorithm;
35use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
36use crate::dom::bindings::codegen::Bindings::RequestBinding::{
37 RequestInfo, RequestInit, RequestMethods,
38};
39use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
40use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
41use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
42use crate::dom::bindings::error::{Error, Fallible};
43use crate::dom::bindings::inheritance::Castable;
44use crate::dom::bindings::num::Finite;
45use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
46use crate::dom::bindings::reflector::DomGlobal;
47use crate::dom::bindings::root::DomRoot;
48use crate::dom::bindings::trace::RootedTraceableBox;
49use crate::dom::csp::{GlobalCspReporting, Violation};
50use crate::dom::fetchlaterresult::FetchLaterResult;
51use crate::dom::globalscope::GlobalScope;
52use crate::dom::headers::Guard;
53use crate::dom::performance::performanceresourcetiming::InitiatorType;
54use crate::dom::promise::Promise;
55use crate::dom::request::Request;
56use crate::dom::response::Response;
57use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
58use crate::dom::window::Window;
59use crate::network_listener::{
60 self, FetchResponseListener, NetworkListener, ResourceTimingListener, submit_timing_data,
61};
62use crate::realms::enter_auto_realm;
63
64#[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(None))
171 .response_tainting(request.response_tainting);
172 builder.id = request.id;
173 builder.reload_navigation = request.reload_navigation;
174 builder.history_navigation = request.history_navigation;
175 builder
176}
177
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 JSContext,
186) {
187 promise.reject(cx, abort_reason);
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.Headers(cx).set_guard(Guard::Immutable);
223
224 let request_object = match Request::Constructor(cx, global, None, input, init) {
227 Err(e) => {
228 response.error_stream(cx, e.clone());
229 promise.reject_error(cx, e);
230 return promise;
231 },
232 Ok(r) => r,
233 };
234 let request = request_object.get_request();
236 let request_id = request.id;
237
238 let signal = request_object.Signal();
240 if signal.aborted() {
241 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
243 signal.Reason(abort_reason.handle_mut());
244 abort_fetch_call(
245 promise.clone(),
246 &request_object,
247 None,
248 abort_reason.handle(),
249 global,
250 cx,
251 );
252 return promise;
254 }
255
256 let keep_alive = request.keep_alive;
257 let mut request_init = request_init_from_request(request, global);
260
261 if global.is::<ServiceWorkerGlobalScope>() {
264 request_init.service_workers_mode = ServiceWorkersMode::None;
265 }
266
267 let fetch_context = FetchContext {
274 fetch_promise: Some(TrustedPromise::new(promise.clone())),
275 response_object: Trusted::new(&*response),
276 request: Trusted::new(&*request_object),
277 global: Trusted::new(global),
278 locally_aborted: false,
279 canceller: FetchCanceller::new(request_id, keep_alive, global.core_resource_thread()),
280 url: request_init.url.url(),
281 };
282 let network_listener = NetworkListener::new(
283 fetch_context,
284 global.task_manager().networking_task_source().to_sendable(),
285 );
286 let fetch_context = network_listener.context.clone();
287
288 signal.add(&AbortAlgorithm::Fetch(fetch_context));
290
291 global.fetch_with_network_listener(request_init, network_listener);
294
295 promise
297}
298
299fn queue_deferred_fetch(
301 request: NetTraitsRequest,
302 activate_after: Finite<f64>,
303 global: &GlobalScope,
304) -> DeferredFetchRecordId {
305 let trusted_global = Trusted::new(global);
306 let mut request = request;
307 request.client = Some(global.request_client(None));
309 request.populate_request_from_client();
310 request.service_workers_mode = ServiceWorkersMode::None;
312 request.keep_alive = true;
314 let deferred_record = Rc::new(DeferredFetchRecord {
316 request,
317 invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
318 activated: Cell::new(false),
319 });
320 let deferred_fetch_record_id = global.append_deferred_fetch(deferred_record);
322 global.schedule_timer(TimerEventRequest {
324 callback: Box::new(move || {
325 let global = trusted_global.root();
327 global.deferred_fetch_record_for_id(&deferred_fetch_record_id).process(&global);
328
329 let trusted_global = trusted_global.clone();
334 global.task_manager().deferred_fetch_task_source().queue(
335 task!(notify_deferred_record: move || {
336 trusted_global.root().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
337 }),
338 );
339 }),
340 duration: Duration::from_millis(*activate_after as u64),
342 });
343 deferred_fetch_record_id
345}
346
347#[expect(non_snake_case, unsafe_code)]
349pub(crate) fn FetchLater(
350 cx: &mut JSContext,
351 window: &Window,
352 input: RequestInfo,
353 init: RootedTraceableBox<DeferredRequestInit>,
354) -> Fallible<DomRoot<FetchLaterResult>> {
355 let global_scope = window.upcast();
356 let document = window.Document();
357 let request_object = Request::constructor(cx, global_scope, None, input, &init.parent)?;
360 let signal = request_object.Signal();
362 if signal.aborted() {
363 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
364 signal.Reason(abort_reason.handle_mut());
365 unsafe {
366 assert!(!JS_IsExceptionPending(cx));
367 JS_SetPendingException(cx, abort_reason.handle(), ExceptionStackBehavior::Capture)
368 };
369 return Err(Error::JSFailed);
370 }
371 let request = request_object.get_request();
373 let mut activate_after = Finite::wrap(0_f64);
375 if let Some(init_activate_after) = init.activateAfter.as_ref() {
378 activate_after = *init_activate_after;
379 }
380 if *activate_after < 0.0 {
382 return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
383 }
384 if !document.is_fully_active() {
386 return Err(Error::Type(c"Document is not fully active".to_owned()));
387 }
388 let url = request.url();
389 if !matches!(url.scheme(), "http" | "https") {
391 return Err(Error::Type(c"URL is not http(s)".to_owned()));
392 }
393 if !url.is_potentially_trustworthy() {
395 return Err(Error::Type(c"URL is not trustworthy".to_owned()));
396 }
397 if request
399 .body
400 .as_ref()
401 .is_some_and(|body| body.len().is_none())
402 {
403 return Err(Error::Type(c"Body is empty".to_owned()));
404 }
405 let quota = document.available_deferred_fetch_quota(request.url().origin());
408 let requested = request.total_request_length() as isize;
409 if quota < requested {
410 return Err(Error::QuotaExceeded {
411 quota: Some(Finite::wrap(quota as f64)),
412 requested: Some(Finite::wrap(requested as f64)),
413 });
414 }
415 let deferred_record_id = queue_deferred_fetch(request, activate_after, global_scope);
419 signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
421 Ok(FetchLaterResult::new(cx, window, deferred_record_id))
423}
424
425#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
427pub(crate) enum DeferredFetchRecordInvokeState {
428 Pending,
429 Sent,
430 Aborted,
431}
432
433#[derive(MallocSizeOf)]
435pub(crate) struct DeferredFetchRecord {
436 pub(crate) request: NetTraitsRequest,
438 pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
440 activated: Cell<bool>,
441}
442
443impl DeferredFetchRecord {
444 fn activate(&self) {
446 self.activated.set(true);
448 }
449 pub(crate) fn abort(&self) {
451 self.invoke_state
453 .set(DeferredFetchRecordInvokeState::Aborted);
454 }
455 pub(crate) fn activated_getter_steps(&self) -> bool {
457 self.activated.get()
459 }
460 pub(crate) fn process(&self, global: &GlobalScope) {
462 if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
464 return;
465 }
466 self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
468 let fetch_later_listener = FetchLaterListener {
470 url: self.request.url(),
471 global: Trusted::new(global),
472 };
473 let request_init = request_init_from_request(self.request.clone(), global);
474 global.fetch(
475 request_init,
476 fetch_later_listener,
477 global.task_manager().networking_task_source().to_sendable(),
478 );
479 }
481}
482
483#[derive(JSTraceable, MallocSizeOf)]
484pub(crate) struct FetchContext {
485 #[ignore_malloc_size_of = "unclear ownership semantics"]
486 fetch_promise: Option<TrustedPromise>,
487 response_object: Trusted<Response>,
488 request: Trusted<Request>,
489 global: Trusted<GlobalScope>,
490 locally_aborted: bool,
491 canceller: FetchCanceller,
492 #[no_trace]
493 url: ServoUrl,
494}
495
496impl FetchContext {
497 pub(crate) fn abort_fetch(&mut self, abort_reason: HandleValue, cx: &mut JSContext) {
499 self.locally_aborted = true;
501 self.canceller.abort();
507
508 let promise = self
511 .fetch_promise
512 .take()
513 .expect("fetch promise is missing")
514 .root();
515 abort_fetch_call(
516 promise,
517 &self.request.root(),
518 Some(&self.response_object.root()),
519 abort_reason,
520 &self.global.root(),
521 cx,
522 );
523 }
524}
525
526impl FetchResponseListener for FetchContext {
528 fn process_request_body(&mut self, _: RequestId) {
529 }
531
532 fn process_response(
533 &mut self,
534 cx: &mut JSContext,
535 _: RequestId,
536 fetch_metadata: Result<FetchMetadata, NetworkError>,
537 ) {
538 if self.locally_aborted {
540 return;
541 }
542 let promise = self
543 .fetch_promise
544 .take()
545 .expect("fetch promise is missing")
546 .root();
547
548 let mut realm = enter_auto_realm(cx, &*promise);
549 let cx = &mut realm.current_realm();
550 match fetch_metadata {
551 Err(error) => {
554 promise.reject_error(cx, Error::Type(cformat!("Network error: {:?}", error)));
555 self.fetch_promise = Some(TrustedPromise::new(promise));
556 let response = self.response_object.root();
557 response.set_type(cx, DOMResponseType::Error);
558 response.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
559 return;
560 },
561 Ok(metadata) => match metadata {
564 FetchMetadata::Unfiltered(m) => {
565 let r = self.response_object.root();
566 fill_headers_with_metadata(cx, &r, m);
567 r.set_type(cx, DOMResponseType::Default);
568 },
569 FetchMetadata::Filtered { filtered, .. } => match filtered {
570 FilteredMetadata::Basic(m) => {
571 let r = self.response_object.root();
572 fill_headers_with_metadata(cx, &r, m);
573 r.set_type(cx, DOMResponseType::Basic);
574 },
575 FilteredMetadata::Cors(m) => {
576 let r = self.response_object.root();
577 fill_headers_with_metadata(cx, &r, m);
578 r.set_type(cx, DOMResponseType::Cors);
579 },
580 FilteredMetadata::Opaque => {
581 self.response_object
582 .root()
583 .set_type(cx, DOMResponseType::Opaque);
584 },
585 FilteredMetadata::OpaqueRedirect(url) => {
586 let r = self.response_object.root();
587 r.set_type(cx, DOMResponseType::Opaqueredirect);
588 r.set_final_url(url);
589 },
590 },
591 },
592 }
593
594 promise.resolve_native(cx, &self.response_object.root());
596 self.fetch_promise = Some(TrustedPromise::new(promise));
597 }
598
599 fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
600 let response = self.response_object.root();
601 response.stream_chunk(cx, chunk);
602 }
603
604 fn process_response_eof(
605 self,
606 cx: &mut JSContext,
607 _: RequestId,
608 response: Result<(), NetworkError>,
609 timing: ResourceFetchTiming,
610 ) {
611 let response_object = self.response_object.root();
612 let mut realm = enter_auto_realm(cx, &*response_object);
613 let cx = &mut realm.current_realm();
614 if let Err(ref error) = response &&
615 *error == NetworkError::DecompressionError
616 {
617 response_object.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
618 }
619 response_object.finish(cx);
620 network_listener::submit_timing(cx, &self, &response, &timing);
625 }
626
627 fn process_csp_violations(
628 &mut self,
629 cx: &mut JSContext,
630 _request_id: RequestId,
631 violations: Vec<Violation>,
632 ) {
633 let global = &self.resource_timing_global();
634 global.report_csp_violations(cx, violations, None, None);
635 }
636}
637
638impl ResourceTimingListener for FetchContext {
639 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
640 (InitiatorType::Fetch, self.url.clone())
641 }
642
643 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
644 self.response_object.root().global()
645 }
646}
647
648struct FetchLaterListener {
649 url: ServoUrl,
651 global: Trusted<GlobalScope>,
653}
654
655impl FetchResponseListener for FetchLaterListener {
656 fn process_request_body(&mut self, _: RequestId) {}
657
658 fn process_response(
659 &mut self,
660 _: &mut JSContext,
661 _: RequestId,
662 fetch_metadata: Result<FetchMetadata, NetworkError>,
663 ) {
664 _ = fetch_metadata;
665 }
666
667 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
668 _ = chunk;
669 }
670
671 fn process_response_eof(
672 self,
673 cx: &mut JSContext,
674 _: RequestId,
675 response: Result<(), NetworkError>,
676 timing: ResourceFetchTiming,
677 ) {
678 network_listener::submit_timing(cx, &self, &response, &timing);
679 }
680
681 fn process_csp_violations(
682 &mut self,
683 cx: &mut JSContext,
684 _request_id: RequestId,
685 violations: Vec<Violation>,
686 ) {
687 let global = self.resource_timing_global();
688 global.report_csp_violations(cx, violations, None, None);
689 }
690}
691
692impl ResourceTimingListener for FetchLaterListener {
693 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
694 (InitiatorType::Fetch, self.url.clone())
695 }
696
697 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
698 self.global.root()
699 }
700}
701
702fn fill_headers_with_metadata(cx: &mut JSContext, r: &Response, m: Metadata) {
703 r.set_headers(cx, m.headers);
704 r.set_status(&m.status);
705 r.set_final_url(m.final_url);
706 r.set_redirected(m.redirected);
707}
708
709pub(crate) trait CspViolationsProcessor {
710 fn process_csp_violations(&self, cx: &mut JSContext, violations: Vec<Violation>);
711}
712
713pub(crate) fn load_whole_resource(
715 request: RequestBuilder,
716 core_resource_thread: &CoreResourceThread,
717 global: &GlobalScope,
718 csp_violations_processor: &dyn CspViolationsProcessor,
719 cx: &mut JSContext,
720) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
721 let (action_sender, action_receiver) = GenericCallback::new_blocking().unwrap();
722 let url = request.url.url();
723 core_resource_thread
724 .send(CoreResourceMsg::Fetch(
725 request,
726 FetchChannels::ResponseMsg(action_sender),
727 ))
728 .unwrap();
729
730 let mut buf = vec![];
731 let mut metadata = None;
732 let mut muted_errors = false;
733 loop {
734 match action_receiver.recv().unwrap() {
735 FetchResponseMsg::ProcessRequestBody(..) => {},
736 FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
737 muted_errors = m.is_cors_cross_origin();
738 metadata = Some(match m {
739 FetchMetadata::Unfiltered(m) => m,
740 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
741 })
742 },
743 FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
744 FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
745 let metadata = metadata.unwrap();
746 if let Some(timing) = &metadata.timing {
747 submit_timing_data(cx, global, url, InitiatorType::Other, timing);
748 }
749 return Ok((metadata, buf, muted_errors));
750 },
751 FetchResponseMsg::ProcessResponse(_, Err(e)) |
752 FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
753 FetchResponseMsg::ProcessCspViolations(_, violations) => {
754 csp_violations_processor.process_csp_violations(cx, violations);
755 },
756 FetchResponseMsg::ProcessContentLength(_request_id, size) => {
757 buf.reserve(size - buf.len())
758 },
759 }
760 }
761}
762
763pub(crate) trait RequestWithGlobalScope {
764 fn with_global_scope(self, global: &GlobalScope) -> Self;
765}
766
767impl RequestWithGlobalScope for RequestBuilder {
768 fn with_global_scope(self, global: &GlobalScope) -> Self {
769 self.client(global.request_client(None))
770 .pipeline_id(Some(global.pipeline_id()))
771 }
772}
773
774#[allow(clippy::too_many_arguments)]
778pub(crate) fn create_a_potential_cors_request(
779 webview_id: Option<WebViewId>,
780 url: ServoUrl,
781 destination: Destination,
782 cors_setting: Option<CorsSettings>,
783 same_origin_fallback: Option<bool>,
784 referrer: Referrer,
785) -> RequestBuilder {
786 create_a_potential_cors_request_with_claim(
787 webview_id,
788 UrlWithBlobClaim::from_url_without_having_claimed_blob(url),
789 destination,
790 cors_setting,
791 same_origin_fallback,
792 referrer,
793 )
794}
795
796#[allow(clippy::too_many_arguments)]
798pub(crate) fn create_a_potential_cors_request_with_claim(
799 webview_id: Option<WebViewId>,
800 url: UrlWithBlobClaim,
801 destination: Destination,
802 cors_setting: Option<CorsSettings>,
803 same_origin_fallback: Option<bool>,
804 referrer: Referrer,
805) -> RequestBuilder {
806 RequestBuilder::new(webview_id, url, referrer)
807 .mode(match cors_setting {
809 Some(_) => RequestMode::CorsMode,
810 None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
812 None => RequestMode::NoCors,
813 })
814 .credentials_mode(match cors_setting {
815 Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
817 _ => CredentialsMode::Include,
819 })
820 .destination(destination)
823 .use_url_credentials(true)
824}