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;
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
174}
175
176fn abort_fetch_call(
178 promise: Rc<Promise>,
179 request: &Request,
180 response_object: Option<&Response>,
181 abort_reason: HandleValue,
182 global: &GlobalScope,
183 cx: &mut JSContext,
184) {
185 promise.reject(cx, abort_reason);
187 if let Some(body) = request.body() &&
189 body.is_readable()
190 {
191 body.cancel(cx, global, abort_reason);
192 }
193 let Some(response) = response_object else {
196 return;
197 };
198 if let Some(body) = response.body() &&
200 body.is_readable()
201 {
202 body.error(cx, abort_reason);
203 }
204}
205
206#[expect(non_snake_case)]
208pub(crate) fn Fetch(
209 global: &GlobalScope,
210 input: RequestInfo,
211 init: RootedTraceableBox<RequestInit>,
212 cx: &mut CurrentRealm,
213) -> Rc<Promise> {
214 let promise = Promise::new_in_realm(cx);
216
217 let response = Response::new(cx, global);
220 response.Headers(cx).set_guard(Guard::Immutable);
221
222 let request_object = match Request::Constructor(cx, global, None, input, init) {
225 Err(e) => {
226 response.error_stream(cx, e.clone());
227 promise.reject_error(cx, e);
228 return promise;
229 },
230 Ok(r) => r,
231 };
232 let request = request_object.get_request();
234 let request_id = request.id;
235
236 let signal = request_object.Signal();
238 if signal.aborted() {
239 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
241 signal.Reason(abort_reason.handle_mut());
242 abort_fetch_call(
243 promise.clone(),
244 &request_object,
245 None,
246 abort_reason.handle(),
247 global,
248 cx,
249 );
250 return promise;
252 }
253
254 let keep_alive = request.keep_alive;
255 let mut request_init = request_init_from_request(request, global);
258
259 if global.is::<ServiceWorkerGlobalScope>() {
262 request_init.service_workers_mode = ServiceWorkersMode::None;
263 }
264
265 let fetch_context = FetchContext {
272 fetch_promise: Some(TrustedPromise::new(promise.clone())),
273 response_object: Trusted::new(&*response),
274 request: Trusted::new(&*request_object),
275 global: Trusted::new(global),
276 locally_aborted: false,
277 canceller: FetchCanceller::new(request_id, keep_alive, global.core_resource_thread()),
278 url: request_init.url.url(),
279 };
280 let network_listener = NetworkListener::new(
281 fetch_context,
282 global.task_manager().networking_task_source().to_sendable(),
283 );
284 let fetch_context = network_listener.context.clone();
285
286 signal.add(&AbortAlgorithm::Fetch(fetch_context));
288
289 global.fetch_with_network_listener(request_init, network_listener);
292
293 promise
295}
296
297fn queue_deferred_fetch(
299 request: NetTraitsRequest,
300 activate_after: Finite<f64>,
301 global: &GlobalScope,
302) -> DeferredFetchRecordId {
303 let trusted_global = Trusted::new(global);
304 let mut request = request;
305 request.client = Some(global.request_client(None));
307 request.populate_request_from_client();
308 request.service_workers_mode = ServiceWorkersMode::None;
310 request.keep_alive = true;
312 let deferred_record = Rc::new(DeferredFetchRecord {
314 request,
315 invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
316 activated: Cell::new(false),
317 });
318 let deferred_fetch_record_id = global.append_deferred_fetch(deferred_record);
320 global.schedule_timer(TimerEventRequest {
322 callback: Box::new(move || {
323 let global = trusted_global.root();
325 global.deferred_fetch_record_for_id(&deferred_fetch_record_id).process(&global);
326
327 let trusted_global = trusted_global.clone();
332 global.task_manager().deferred_fetch_task_source().queue(
333 task!(notify_deferred_record: move || {
334 trusted_global.root().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
335 }),
336 );
337 }),
338 duration: Duration::from_millis(*activate_after as u64),
340 });
341 deferred_fetch_record_id
343}
344
345#[expect(non_snake_case, unsafe_code)]
347pub(crate) fn FetchLater(
348 cx: &mut JSContext,
349 window: &Window,
350 input: RequestInfo,
351 init: RootedTraceableBox<DeferredRequestInit>,
352) -> Fallible<DomRoot<FetchLaterResult>> {
353 let global_scope = window.upcast();
354 let document = window.Document();
355 let request_object = Request::constructor(cx, global_scope, None, input, &init.parent)?;
358 let signal = request_object.Signal();
360 if signal.aborted() {
361 rooted!(&in(cx) let mut abort_reason = UndefinedValue());
362 signal.Reason(abort_reason.handle_mut());
363 unsafe {
364 assert!(!JS_IsExceptionPending(cx));
365 JS_SetPendingException(cx, abort_reason.handle(), ExceptionStackBehavior::Capture)
366 };
367 return Err(Error::JSFailed);
368 }
369 let request = request_object.get_request();
371 let mut activate_after = Finite::wrap(0_f64);
373 if let Some(init_activate_after) = init.activateAfter.as_ref() {
376 activate_after = *init_activate_after;
377 }
378 if *activate_after < 0.0 {
380 return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
381 }
382 if !document.is_fully_active() {
384 return Err(Error::Type(c"Document is not fully active".to_owned()));
385 }
386 let url = request.url();
387 if !matches!(url.scheme(), "http" | "https") {
389 return Err(Error::Type(c"URL is not http(s)".to_owned()));
390 }
391 if !url.is_potentially_trustworthy() {
393 return Err(Error::Type(c"URL is not trustworthy".to_owned()));
394 }
395 if request
397 .body
398 .as_ref()
399 .is_some_and(|body| body.len().is_none())
400 {
401 return Err(Error::Type(c"Body is empty".to_owned()));
402 }
403 let quota = document.available_deferred_fetch_quota(request.url().origin());
406 let requested = request.total_request_length() as isize;
407 if quota < requested {
408 return Err(Error::QuotaExceeded {
409 quota: Some(Finite::wrap(quota as f64)),
410 requested: Some(Finite::wrap(requested as f64)),
411 });
412 }
413 let deferred_record_id = queue_deferred_fetch(request, activate_after, global_scope);
417 signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
419 Ok(FetchLaterResult::new(cx, window, deferred_record_id))
421}
422
423#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
425pub(crate) enum DeferredFetchRecordInvokeState {
426 Pending,
427 Sent,
428 Aborted,
429}
430
431#[derive(MallocSizeOf)]
433pub(crate) struct DeferredFetchRecord {
434 pub(crate) request: NetTraitsRequest,
436 pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
438 activated: Cell<bool>,
439}
440
441impl DeferredFetchRecord {
442 fn activate(&self) {
444 self.activated.set(true);
446 }
447 pub(crate) fn abort(&self) {
449 self.invoke_state
451 .set(DeferredFetchRecordInvokeState::Aborted);
452 }
453 pub(crate) fn activated_getter_steps(&self) -> bool {
455 self.activated.get()
457 }
458 pub(crate) fn process(&self, global: &GlobalScope) {
460 if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
462 return;
463 }
464 self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
466 let fetch_later_listener = FetchLaterListener {
468 url: self.request.url(),
469 global: Trusted::new(global),
470 };
471 let request_init = request_init_from_request(self.request.clone(), global);
472 global.fetch(
473 request_init,
474 fetch_later_listener,
475 global.task_manager().networking_task_source().to_sendable(),
476 );
477 }
479}
480
481#[derive(JSTraceable, MallocSizeOf)]
482pub(crate) struct FetchContext {
483 #[ignore_malloc_size_of = "unclear ownership semantics"]
484 fetch_promise: Option<TrustedPromise>,
485 response_object: Trusted<Response>,
486 request: Trusted<Request>,
487 global: Trusted<GlobalScope>,
488 locally_aborted: bool,
489 canceller: FetchCanceller,
490 #[no_trace]
491 url: ServoUrl,
492}
493
494impl FetchContext {
495 pub(crate) fn abort_fetch(&mut self, abort_reason: HandleValue, cx: &mut JSContext) {
497 self.locally_aborted = true;
499 self.canceller.abort();
505
506 let promise = self
509 .fetch_promise
510 .take()
511 .expect("fetch promise is missing")
512 .root();
513 abort_fetch_call(
514 promise,
515 &self.request.root(),
516 Some(&self.response_object.root()),
517 abort_reason,
518 &self.global.root(),
519 cx,
520 );
521 }
522}
523
524impl FetchResponseListener for FetchContext {
526 fn process_request_body(&mut self, _: RequestId) {
527 }
529
530 fn process_response(
531 &mut self,
532 cx: &mut JSContext,
533 _: RequestId,
534 fetch_metadata: Result<FetchMetadata, NetworkError>,
535 ) {
536 if self.locally_aborted {
538 return;
539 }
540 let promise = self
541 .fetch_promise
542 .take()
543 .expect("fetch promise is missing")
544 .root();
545
546 let mut realm = enter_auto_realm(cx, &*promise);
547 let cx = &mut realm.current_realm();
548 match fetch_metadata {
549 Err(error) => {
552 promise.reject_error(cx, Error::Type(cformat!("Network error: {:?}", error)));
553 self.fetch_promise = Some(TrustedPromise::new(promise));
554 let response = self.response_object.root();
555 response.set_type(cx, DOMResponseType::Error);
556 response.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
557 return;
558 },
559 Ok(metadata) => match metadata {
562 FetchMetadata::Unfiltered(m) => {
563 let r = self.response_object.root();
564 fill_headers_with_metadata(cx, &r, m);
565 r.set_type(cx, DOMResponseType::Default);
566 },
567 FetchMetadata::Filtered { filtered, .. } => match filtered {
568 FilteredMetadata::Basic(m) => {
569 let r = self.response_object.root();
570 fill_headers_with_metadata(cx, &r, m);
571 r.set_type(cx, DOMResponseType::Basic);
572 },
573 FilteredMetadata::Cors(m) => {
574 let r = self.response_object.root();
575 fill_headers_with_metadata(cx, &r, m);
576 r.set_type(cx, DOMResponseType::Cors);
577 },
578 FilteredMetadata::Opaque => {
579 self.response_object
580 .root()
581 .set_type(cx, DOMResponseType::Opaque);
582 },
583 FilteredMetadata::OpaqueRedirect(url) => {
584 let r = self.response_object.root();
585 r.set_type(cx, DOMResponseType::Opaqueredirect);
586 r.set_final_url(url);
587 },
588 },
589 },
590 }
591
592 promise.resolve_native(cx, &self.response_object.root());
594 self.fetch_promise = Some(TrustedPromise::new(promise));
595 }
596
597 fn process_response_chunk(&mut self, cx: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
598 let response = self.response_object.root();
599 response.stream_chunk(cx, chunk);
600 }
601
602 fn process_response_eof(
603 self,
604 cx: &mut JSContext,
605 _: RequestId,
606 response: Result<(), NetworkError>,
607 timing: ResourceFetchTiming,
608 ) {
609 let response_object = self.response_object.root();
610 let mut realm = enter_auto_realm(cx, &*response_object);
611 let cx = &mut realm.current_realm();
612 if let Err(ref error) = response &&
613 *error == NetworkError::DecompressionError
614 {
615 response_object.error_stream(cx, Error::Type(c"Network error occurred".to_owned()));
616 }
617 response_object.finish(cx);
618 network_listener::submit_timing(cx, &self, &response, &timing);
623 }
624
625 fn process_csp_violations(
626 &mut self,
627 cx: &mut JSContext,
628 _request_id: RequestId,
629 violations: Vec<Violation>,
630 ) {
631 let global = &self.resource_timing_global();
632 global.report_csp_violations(cx, violations, None, None);
633 }
634}
635
636impl ResourceTimingListener for FetchContext {
637 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
638 (InitiatorType::Fetch, self.url.clone())
639 }
640
641 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
642 self.response_object.root().global()
643 }
644}
645
646struct FetchLaterListener {
647 url: ServoUrl,
649 global: Trusted<GlobalScope>,
651}
652
653impl FetchResponseListener for FetchLaterListener {
654 fn process_request_body(&mut self, _: RequestId) {}
655
656 fn process_response(
657 &mut self,
658 _: &mut JSContext,
659 _: RequestId,
660 fetch_metadata: Result<FetchMetadata, NetworkError>,
661 ) {
662 _ = fetch_metadata;
663 }
664
665 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Vec<u8>) {
666 _ = chunk;
667 }
668
669 fn process_response_eof(
670 self,
671 cx: &mut JSContext,
672 _: RequestId,
673 response: Result<(), NetworkError>,
674 timing: ResourceFetchTiming,
675 ) {
676 network_listener::submit_timing(cx, &self, &response, &timing);
677 }
678
679 fn process_csp_violations(
680 &mut self,
681 cx: &mut JSContext,
682 _request_id: RequestId,
683 violations: Vec<Violation>,
684 ) {
685 let global = self.resource_timing_global();
686 global.report_csp_violations(cx, violations, None, None);
687 }
688}
689
690impl ResourceTimingListener for FetchLaterListener {
691 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
692 (InitiatorType::Fetch, self.url.clone())
693 }
694
695 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
696 self.global.root()
697 }
698}
699
700fn fill_headers_with_metadata(cx: &mut JSContext, r: &Response, m: Metadata) {
701 r.set_headers(cx, m.headers);
702 r.set_status(&m.status);
703 r.set_final_url(m.final_url);
704 r.set_redirected(m.redirected);
705}
706
707pub(crate) trait CspViolationsProcessor {
708 fn process_csp_violations(&self, cx: &mut JSContext, violations: Vec<Violation>);
709}
710
711pub(crate) fn load_whole_resource(
713 request: RequestBuilder,
714 core_resource_thread: &CoreResourceThread,
715 global: &GlobalScope,
716 csp_violations_processor: &dyn CspViolationsProcessor,
717 cx: &mut JSContext,
718) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
719 let (action_sender, action_receiver) = ipc::channel().unwrap();
720 let url = request.url.url();
721 core_resource_thread
722 .send(CoreResourceMsg::Fetch(
723 request,
724 FetchChannels::ResponseMsg(action_sender),
725 ))
726 .unwrap();
727
728 let mut buf = vec![];
729 let mut metadata = None;
730 let mut muted_errors = false;
731 loop {
732 match action_receiver.recv().unwrap() {
733 FetchResponseMsg::ProcessRequestBody(..) => {},
734 FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
735 muted_errors = m.is_cors_cross_origin();
736 metadata = Some(match m {
737 FetchMetadata::Unfiltered(m) => m,
738 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
739 })
740 },
741 FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
742 FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
743 let metadata = metadata.unwrap();
744 if let Some(timing) = &metadata.timing {
745 submit_timing_data(cx, global, url, InitiatorType::Other, timing);
746 }
747 return Ok((metadata, buf, muted_errors));
748 },
749 FetchResponseMsg::ProcessResponse(_, Err(e)) |
750 FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
751 FetchResponseMsg::ProcessCspViolations(_, violations) => {
752 csp_violations_processor.process_csp_violations(cx, violations);
753 },
754 FetchResponseMsg::ProcessContentLength(_request_id, size) => {
755 buf.reserve(size - buf.len())
756 },
757 }
758 }
759}
760
761pub(crate) trait RequestWithGlobalScope {
762 fn with_global_scope(self, global: &GlobalScope) -> Self;
763}
764
765impl RequestWithGlobalScope for RequestBuilder {
766 fn with_global_scope(self, global: &GlobalScope) -> Self {
767 self.client(global.request_client(None))
768 .pipeline_id(Some(global.pipeline_id()))
769 }
770}
771
772#[allow(clippy::too_many_arguments)]
774pub(crate) fn create_a_potential_cors_request(
775 webview_id: Option<WebViewId>,
776 url: ServoUrl,
777 destination: Destination,
778 cors_setting: Option<CorsSettings>,
779 same_origin_fallback: Option<bool>,
780 referrer: Referrer,
781) -> RequestBuilder {
782 RequestBuilder::new(
783 webview_id,
784 UrlWithBlobClaim::from_url_without_having_claimed_blob(url),
785 referrer,
786 )
787 .mode(match cors_setting {
789 Some(_) => RequestMode::CorsMode,
790 None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
792 None => RequestMode::NoCors,
793 })
794 .credentials_mode(match cors_setting {
795 Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
797 _ => CredentialsMode::Include,
799 })
800 .destination(destination)
803 .use_url_credentials(true)
804}