1use std::cell::Cell;
6use std::rc::Rc;
7use std::time::Duration;
8
9use ipc_channel::ipc;
10use js::context::JSContext;
11use js::jsapi::ExceptionStackBehavior;
12use js::jsval::UndefinedValue;
13use js::realm::CurrentRealm;
14use js::rust::HandleValue;
15use js::rust::wrappers2::{JS_IsExceptionPending, JS_SetPendingException};
16use net_traits::blob_url_store::UrlWithBlobClaim;
17use net_traits::request::{
18 CorsSettings, CredentialsMode, Destination, Referrer, Request as NetTraitsRequest,
19 RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
20};
21use net_traits::{
22 CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseMsg,
23 FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming, cancel_async_fetch,
24};
25use rustc_hash::FxHashMap;
26use script_bindings::cformat;
27use serde::{Deserialize, Serialize};
28use servo_base::id::WebViewId;
29use servo_url::ServoUrl;
30use timers::TimerEventRequest;
31use uuid::Uuid;
32
33use crate::body::BodyMixin;
34use crate::dom::abortsignal::AbortAlgorithm;
35use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
36use crate::dom::bindings::codegen::Bindings::RequestBinding::{
37 RequestInfo, RequestInit, RequestMethods,
38};
39use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
40use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
41use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
42use crate::dom::bindings::error::{Error, Fallible};
43use crate::dom::bindings::inheritance::Castable;
44use crate::dom::bindings::num::Finite;
45use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
46use crate::dom::bindings::reflector::DomGlobal;
47use crate::dom::bindings::root::DomRoot;
48use crate::dom::bindings::trace::RootedTraceableBox;
49use crate::dom::csp::{GlobalCspReporting, Violation};
50use crate::dom::fetchlaterresult::FetchLaterResult;
51use crate::dom::globalscope::GlobalScope;
52use crate::dom::headers::Guard;
53use crate::dom::performance::performanceresourcetiming::InitiatorType;
54use crate::dom::promise::Promise;
55use crate::dom::request::Request;
56use crate::dom::response::Response;
57use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
58use crate::dom::window::Window;
59use crate::network_listener::{
60 self, FetchResponseListener, NetworkListener, ResourceTimingListener, submit_timing_data,
61};
62use crate::realms::enter_auto_realm;
63use crate::script_runtime::CanGc;
64
65#[derive(Default, JSTraceable, MallocSizeOf)]
70pub(crate) struct FetchCanceller {
71 #[no_trace]
72 request_id: Option<RequestId>,
73 #[no_trace]
74 core_resource_thread: Option<CoreResourceThread>,
75 keep_alive: bool,
76}
77
78impl FetchCanceller {
79 pub(crate) fn new(
82 request_id: RequestId,
83 keep_alive: bool,
84 core_resource_thread: CoreResourceThread,
85 ) -> Self {
86 Self {
87 request_id: Some(request_id),
88 core_resource_thread: Some(core_resource_thread),
89 keep_alive,
90 }
91 }
92
93 pub(crate) fn keep_alive(&self) -> bool {
94 self.keep_alive
95 }
96
97 fn cancel(&mut self) {
98 if let Some(request_id) = self.request_id.take() {
99 if let Some(ref core_resource_thread) = self.core_resource_thread {
103 cancel_async_fetch(vec![request_id], core_resource_thread);
106 }
107 }
108 }
109
110 pub(crate) fn ignore(&mut self) {
113 let _ = self.request_id.take();
114 }
115
116 pub(crate) fn abort(&mut self) {
118 self.cancel();
119 }
120
121 pub(crate) fn terminate(&mut self) {
123 self.cancel();
124 }
125}
126
127#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
128pub(crate) struct DeferredFetchRecordId(Uuid);
130
131impl Default for DeferredFetchRecordId {
132 fn default() -> Self {
133 Self(Uuid::new_v4())
134 }
135}
136
137pub(crate) type QueuedDeferredFetchRecord = Rc<DeferredFetchRecord>;
138
139#[derive(Default, MallocSizeOf)]
141pub(crate) struct FetchGroup {
142 #[conditional_malloc_size_of]
144 pub(crate) deferred_fetch_records: FxHashMap<DeferredFetchRecordId, QueuedDeferredFetchRecord>,
145}
146
147fn request_init_from_request(request: NetTraitsRequest, global: &GlobalScope) -> RequestBuilder {
148 let mut builder = RequestBuilder::new(
149 request.target_webview_id,
150 request.url_with_blob_claim(),
151 request.referrer,
152 )
153 .method(request.method)
154 .headers(request.headers)
155 .unsafe_request(request.unsafe_request)
156 .body(request.body)
157 .destination(request.destination)
158 .synchronous(request.synchronous)
159 .mode(request.mode)
160 .cache_mode(request.cache_mode)
161 .use_cors_preflight(request.use_cors_preflight)
162 .credentials_mode(request.credentials_mode)
163 .use_url_credentials(request.use_url_credentials)
164 .referrer_policy(request.referrer_policy)
165 .pipeline_id(request.pipeline_id)
166 .redirect_mode(request.redirect_mode)
167 .integrity_metadata(request.integrity_metadata)
168 .cryptographic_nonce_metadata(request.cryptographic_nonce_metadata)
169 .parser_metadata(request.parser_metadata)
170 .initiator(request.initiator)
171 .client(global.request_client())
172 .insecure_requests_policy(request.insecure_requests_policy)
173 .has_trustworthy_ancestor_origin(request.has_trustworthy_ancestor_origin)
174 .response_tainting(request.response_tainting);
175 builder.id = request.id;
176 builder
177}
178
179fn abort_fetch_call(
181 promise: Rc<Promise>,
182 request: &Request,
183 response_object: Option<&Response>,
184 abort_reason: HandleValue,
185 global: &GlobalScope,
186 cx: &mut JSContext,
187) {
188 promise.reject(cx, abort_reason);
190 if let Some(body) = request.body() &&
192 body.is_readable()
193 {
194 body.cancel(cx, global, abort_reason);
195 }
196 let Some(response) = response_object else {
199 return;
200 };
201 if let Some(body) = response.body() &&
203 body.is_readable()
204 {
205 body.error(cx, abort_reason);
206 }
207}
208
209#[expect(non_snake_case)]
211pub(crate) fn Fetch(
212 global: &GlobalScope,
213 input: RequestInfo,
214 init: RootedTraceableBox<RequestInit>,
215 cx: &mut CurrentRealm,
216) -> Rc<Promise> {
217 let promise = Promise::new_in_realm(cx);
219
220 let response = Response::new(cx, global);
223 response.Headers(cx).set_guard(Guard::Immutable);
224
225 let request_object = match Request::Constructor(cx, global, None, input, init) {
228 Err(e) => {
229 response.error_stream(cx, e.clone());
230 promise.reject_error(cx, e);
231 return promise;
232 },
233 Ok(r) => r,
234 };
235 let request = request_object.get_request();
237 let request_id = request.id;
238
239 let signal = request_object.Signal();
241 if signal.aborted() {
242 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
244 signal.Reason(cx.into(), abort_reason.handle_mut());
245 abort_fetch_call(
246 promise.clone(),
247 &request_object,
248 None,
249 abort_reason.handle(),
250 global,
251 cx,
252 );
253 return promise;
255 }
256
257 let keep_alive = request.keep_alive;
258 let mut request_init = request_init_from_request(request, global);
261
262 if global.is::<ServiceWorkerGlobalScope>() {
265 request_init.service_workers_mode = ServiceWorkersMode::None;
266 }
267
268 let fetch_context = FetchContext {
275 fetch_promise: Some(TrustedPromise::new(promise.clone())),
276 response_object: Trusted::new(&*response),
277 request: Trusted::new(&*request_object),
278 global: Trusted::new(global),
279 locally_aborted: false,
280 canceller: FetchCanceller::new(request_id, keep_alive, global.core_resource_thread()),
281 url: request_init.url.url(),
282 };
283 let network_listener = NetworkListener::new(
284 fetch_context,
285 global.task_manager().networking_task_source().to_sendable(),
286 );
287 let fetch_context = network_listener.context.clone();
288
289 signal.add(&AbortAlgorithm::Fetch(fetch_context));
291
292 global.fetch_with_network_listener(request_init, network_listener);
295
296 promise
298}
299
300fn queue_deferred_fetch(
302 request: NetTraitsRequest,
303 activate_after: Finite<f64>,
304 global: &GlobalScope,
305) -> DeferredFetchRecordId {
306 let trusted_global = Trusted::new(global);
307 let mut request = request;
308 request.client = Some(global.request_client());
310 request.populate_request_from_client();
311 request.service_workers_mode = ServiceWorkersMode::None;
313 request.keep_alive = true;
315 let deferred_record = Rc::new(DeferredFetchRecord {
317 request,
318 invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
319 activated: Cell::new(false),
320 });
321 let deferred_fetch_record_id = global.append_deferred_fetch(deferred_record);
323 global.schedule_timer(TimerEventRequest {
325 callback: Box::new(move || {
326 let global = trusted_global.root();
328 global.deferred_fetch_record_for_id(&deferred_fetch_record_id).process(&global);
329
330 let trusted_global = trusted_global.clone();
335 global.task_manager().deferred_fetch_task_source().queue(
336 task!(notify_deferred_record: move || {
337 trusted_global.root().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
338 }),
339 );
340 }),
341 duration: Duration::from_millis(*activate_after as u64),
343 });
344 deferred_fetch_record_id
346}
347
348#[expect(non_snake_case, unsafe_code)]
350pub(crate) fn FetchLater(
351 cx: &mut JSContext,
352 window: &Window,
353 input: RequestInfo,
354 init: RootedTraceableBox<DeferredRequestInit>,
355) -> Fallible<DomRoot<FetchLaterResult>> {
356 let global_scope = window.upcast();
357 let document = window.Document();
358 let request_object = Request::constructor(cx, global_scope, None, input, &init.parent)?;
361 let signal = request_object.Signal();
363 if signal.aborted() {
364 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
365 signal.Reason(cx.into(), abort_reason.handle_mut());
366 unsafe {
367 assert!(!JS_IsExceptionPending(cx));
368 JS_SetPendingException(cx, abort_reason.handle(), ExceptionStackBehavior::Capture)
369 };
370 return Err(Error::JSFailed);
371 }
372 let request = request_object.get_request();
374 let mut activate_after = Finite::wrap(0_f64);
376 if let Some(init_activate_after) = init.activateAfter.as_ref() {
379 activate_after = *init_activate_after;
380 }
381 if *activate_after < 0.0 {
383 return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
384 }
385 if !document.is_fully_active() {
387 return Err(Error::Type(c"Document is not fully active".to_owned()));
388 }
389 let url = request.url();
390 if !matches!(url.scheme(), "http" | "https") {
392 return Err(Error::Type(c"URL is not http(s)".to_owned()));
393 }
394 if !url.is_potentially_trustworthy() {
396 return Err(Error::Type(c"URL is not trustworthy".to_owned()));
397 }
398 if request
400 .body
401 .as_ref()
402 .is_some_and(|body| body.len().is_none())
403 {
404 return Err(Error::Type(c"Body is empty".to_owned()));
405 }
406 let quota = document.available_deferred_fetch_quota(request.url().origin());
409 let requested = request.total_request_length() as isize;
410 if quota < requested {
411 return Err(Error::QuotaExceeded {
412 quota: Some(Finite::wrap(quota as f64)),
413 requested: Some(Finite::wrap(requested as f64)),
414 });
415 }
416 let deferred_record_id = queue_deferred_fetch(request, activate_after, global_scope);
420 signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
422 Ok(FetchLaterResult::new(
424 window,
425 deferred_record_id,
426 CanGc::from_cx(cx),
427 ))
428}
429
430#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
432pub(crate) enum DeferredFetchRecordInvokeState {
433 Pending,
434 Sent,
435 Aborted,
436}
437
438#[derive(MallocSizeOf)]
440pub(crate) struct DeferredFetchRecord {
441 pub(crate) request: NetTraitsRequest,
443 pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
445 activated: Cell<bool>,
446}
447
448impl DeferredFetchRecord {
449 fn activate(&self) {
451 self.activated.set(true);
453 }
454 pub(crate) fn abort(&self) {
456 self.invoke_state
458 .set(DeferredFetchRecordInvokeState::Aborted);
459 }
460 pub(crate) fn activated_getter_steps(&self) -> bool {
462 self.activated.get()
464 }
465 pub(crate) fn process(&self, global: &GlobalScope) {
467 if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
469 return;
470 }
471 self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
473 let fetch_later_listener = FetchLaterListener {
475 url: self.request.url(),
476 global: Trusted::new(global),
477 };
478 let request_init = request_init_from_request(self.request.clone(), global);
479 global.fetch(
480 request_init,
481 fetch_later_listener,
482 global.task_manager().networking_task_source().to_sendable(),
483 );
484 }
486}
487
488#[derive(JSTraceable, MallocSizeOf)]
489pub(crate) struct FetchContext {
490 #[ignore_malloc_size_of = "unclear ownership semantics"]
491 fetch_promise: Option<TrustedPromise>,
492 response_object: Trusted<Response>,
493 request: Trusted<Request>,
494 global: Trusted<GlobalScope>,
495 locally_aborted: bool,
496 canceller: FetchCanceller,
497 #[no_trace]
498 url: ServoUrl,
499}
500
501impl FetchContext {
502 pub(crate) fn abort_fetch(&mut self, abort_reason: HandleValue, cx: &mut JSContext) {
504 self.locally_aborted = true;
506 self.canceller.abort();
512
513 let promise = self
516 .fetch_promise
517 .take()
518 .expect("fetch promise is missing")
519 .root();
520 abort_fetch_call(
521 promise,
522 &self.request.root(),
523 Some(&self.response_object.root()),
524 abort_reason,
525 &self.global.root(),
526 cx,
527 );
528 }
529}
530
531impl FetchResponseListener for FetchContext {
533 fn process_request_body(&mut self, _: RequestId) {
534 }
536
537 fn process_response(
538 &mut self,
539 cx: &mut JSContext,
540 _: RequestId,
541 fetch_metadata: Result<FetchMetadata, NetworkError>,
542 ) {
543 if self.locally_aborted {
545 return;
546 }
547 let promise = self
548 .fetch_promise
549 .take()
550 .expect("fetch promise is missing")
551 .root();
552
553 let mut realm = enter_auto_realm(cx, &*promise);
554 let cx = &mut realm.current_realm();
555 match fetch_metadata {
556 Err(error) => {
559 promise.reject_error(cx, Error::Type(cformat!("Network error: {:?}", error)));
560 self.fetch_promise = Some(TrustedPromise::new(promise));
561 let response = self.response_object.root();
562 response.set_type(cx, DOMResponseType::Error);
563 response.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
564 return;
565 },
566 Ok(metadata) => match metadata {
569 FetchMetadata::Unfiltered(m) => {
570 fill_headers_with_metadata(cx, self.response_object.root(), m);
571 self.response_object
572 .root()
573 .set_type(cx, DOMResponseType::Default);
574 },
575 FetchMetadata::Filtered { filtered, .. } => match filtered {
576 FilteredMetadata::Basic(m) => {
577 fill_headers_with_metadata(cx, self.response_object.root(), m);
578 self.response_object
579 .root()
580 .set_type(cx, DOMResponseType::Basic);
581 },
582 FilteredMetadata::Cors(m) => {
583 fill_headers_with_metadata(cx, self.response_object.root(), m);
584 self.response_object
585 .root()
586 .set_type(cx, DOMResponseType::Cors);
587 },
588 FilteredMetadata::Opaque => {
589 self.response_object
590 .root()
591 .set_type(cx, DOMResponseType::Opaque);
592 },
593 FilteredMetadata::OpaqueRedirect(url) => {
594 let r = self.response_object.root();
595 r.set_type(cx, DOMResponseType::Opaqueredirect);
596 r.set_final_url(url);
597 },
598 },
599 },
600 }
601
602 promise.resolve_native(cx, &self.response_object.root());
604 self.fetch_promise = Some(TrustedPromise::new(promise));
605 }
606
607 fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
608 let response = self.response_object.root();
609 response.stream_chunk(cx, chunk);
610 }
611
612 fn process_response_eof(
613 self,
614 cx: &mut JSContext,
615 _: RequestId,
616 response: Result<(), NetworkError>,
617 timing: ResourceFetchTiming,
618 ) {
619 let response_object = self.response_object.root();
620 let mut realm = enter_auto_realm(cx, &*response_object);
621 let cx = &mut realm.current_realm();
622 if let Err(ref error) = response &&
623 *error == NetworkError::DecompressionError
624 {
625 response_object.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
626 }
627 response_object.finish(cx);
628 network_listener::submit_timing(cx, &self, &response, &timing);
633 }
634
635 fn process_csp_violations(
636 &mut self,
637 cx: &mut JSContext,
638 _request_id: RequestId,
639 violations: Vec<Violation>,
640 ) {
641 let global = &self.resource_timing_global();
642 global.report_csp_violations(cx, violations, None, None);
643 }
644}
645
646impl ResourceTimingListener for FetchContext {
647 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
648 (InitiatorType::Fetch, self.url.clone())
649 }
650
651 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
652 self.response_object.root().global()
653 }
654}
655
656struct FetchLaterListener {
657 url: ServoUrl,
659 global: Trusted<GlobalScope>,
661}
662
663impl FetchResponseListener for FetchLaterListener {
664 fn process_request_body(&mut self, _: RequestId) {}
665
666 fn process_response(
667 &mut self,
668 _: &mut JSContext,
669 _: RequestId,
670 fetch_metadata: Result<FetchMetadata, NetworkError>,
671 ) {
672 _ = fetch_metadata;
673 }
674
675 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
676 _ = chunk;
677 }
678
679 fn process_response_eof(
680 self,
681 cx: &mut JSContext,
682 _: RequestId,
683 response: Result<(), NetworkError>,
684 timing: ResourceFetchTiming,
685 ) {
686 network_listener::submit_timing(cx, &self, &response, &timing);
687 }
688
689 fn process_csp_violations(
690 &mut self,
691 cx: &mut JSContext,
692 _request_id: RequestId,
693 violations: Vec<Violation>,
694 ) {
695 let global = self.resource_timing_global();
696 global.report_csp_violations(cx, violations, None, None);
697 }
698}
699
700impl ResourceTimingListener for FetchLaterListener {
701 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
702 (InitiatorType::Fetch, self.url.clone())
703 }
704
705 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
706 self.global.root()
707 }
708}
709
710fn fill_headers_with_metadata(cx: &mut JSContext, r: DomRoot<Response>, m: Metadata) {
711 r.set_headers(cx, m.headers);
712 r.set_status(&m.status);
713 r.set_final_url(m.final_url);
714 r.set_redirected(m.redirected);
715}
716
717pub(crate) trait CspViolationsProcessor {
718 fn process_csp_violations(&self, cx: &mut JSContext, violations: Vec<Violation>);
719}
720
721pub(crate) fn load_whole_resource(
723 request: RequestBuilder,
724 core_resource_thread: &CoreResourceThread,
725 global: &GlobalScope,
726 csp_violations_processor: &dyn CspViolationsProcessor,
727 cx: &mut JSContext,
728) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
729 let (action_sender, action_receiver) = ipc::channel().unwrap();
730 let url = request.url.url();
731 core_resource_thread
732 .send(CoreResourceMsg::Fetch(
733 request,
734 FetchChannels::ResponseMsg(action_sender),
735 ))
736 .unwrap();
737
738 let mut buf = vec![];
739 let mut metadata = None;
740 let mut muted_errors = false;
741 loop {
742 match action_receiver.recv().unwrap() {
743 FetchResponseMsg::ProcessRequestBody(..) => {},
744 FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
745 muted_errors = m.is_cors_cross_origin();
746 metadata = Some(match m {
747 FetchMetadata::Unfiltered(m) => m,
748 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
749 })
750 },
751 FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
752 FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
753 let metadata = metadata.unwrap();
754 if let Some(timing) = &metadata.timing {
755 submit_timing_data(cx, global, url, InitiatorType::Other, timing);
756 }
757 return Ok((metadata, buf, muted_errors));
758 },
759 FetchResponseMsg::ProcessResponse(_, Err(e)) |
760 FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
761 FetchResponseMsg::ProcessCspViolations(_, violations) => {
762 csp_violations_processor.process_csp_violations(cx, violations);
763 },
764 }
765 }
766}
767
768pub(crate) trait RequestWithGlobalScope {
769 fn with_global_scope(self, global: &GlobalScope) -> Self;
770}
771
772impl RequestWithGlobalScope for RequestBuilder {
773 fn with_global_scope(self, global: &GlobalScope) -> Self {
774 self.insecure_requests_policy(global.insecure_requests_policy())
775 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
776 .policy_container(global.policy_container())
777 .client(global.request_client())
778 .pipeline_id(Some(global.pipeline_id()))
779 .origin(global.origin().immutable().clone())
780 }
781}
782
783#[allow(clippy::too_many_arguments)]
785pub(crate) fn create_a_potential_cors_request(
786 webview_id: Option<WebViewId>,
787 url: ServoUrl,
788 destination: Destination,
789 cors_setting: Option<CorsSettings>,
790 same_origin_fallback: Option<bool>,
791 referrer: Referrer,
792) -> RequestBuilder {
793 RequestBuilder::new(
794 webview_id,
795 UrlWithBlobClaim::from_url_without_having_claimed_blob(url),
796 referrer,
797 )
798 .mode(match cors_setting {
800 Some(_) => RequestMode::CorsMode,
801 None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
803 None => RequestMode::NoCors,
804 })
805 .credentials_mode(match cors_setting {
806 Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
808 _ => CredentialsMode::Include,
810 })
811 .destination(destination)
814 .use_url_credentials(true)
815}