1use std::borrow::Cow;
9use std::cell::{OnceCell, RefCell};
10use std::collections::hash_map::Entry;
11use std::ffi::CStr;
12use std::fmt::Debug;
13use std::ptr::NonNull;
14use std::rc::Rc;
15use std::{mem, ptr};
16
17use bytes::{Bytes, BytesMut};
18use encoding_rs::UTF_8;
19use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
20use hyper_serde::Serde;
21use js::context::JSContext;
22use js::conversions::{ToJSValConvertible, jsstr_to_string};
23use js::gc::{HandleObject, MutableHandleValue};
24use js::jsapi::{
25 CallArgs, ColumnNumberOneOrigin, ExceptionStackBehavior, GetFunctionNativeReserved,
26 GetModuleLoadHook, Handle as RawHandle, HandleValue as RawHandleValue, Heap,
27 JS_GetFunctionObject, JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime,
28 JSScript, ModuleErrorBehaviour, ModuleType, SetFunctionNativeReserved, SetModuleLoadHook,
29 SetModuleMetadataHook, SetModulePrivate, SetScriptPrivateReferenceHooks, Value,
30};
31use js::jsval::{JSVal, ObjectValue, PrivateValue, UndefinedValue};
32use js::realm::{AutoRealm, CurrentRealm};
33use js::rust::wrappers2::{
34 CompileJsonModule1, CompileModule1, DefineFunctionWithReserved, GetModuleRequestSpecifier,
35 JS_ClearPendingException, JS_DefineProperty4, JS_GetModulePrivate, JS_GetPendingException,
36 JS_NewStringCopyN, JS_SetPendingException, ModuleEvaluate, ThrowOnModuleEvaluationFailure,
37};
38use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
39use mime::Mime;
40use net_traits::blob_url_store::UrlWithBlobClaim;
41use net_traits::http_status::HttpStatus;
42use net_traits::mime_classifier::MimeClassifier;
43use net_traits::policy_container::PolicyContainer;
44use net_traits::request::{
45 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestClient,
46 RequestId, RequestMode,
47};
48use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
49use script_bindings::cell::DomRefCell;
50use script_bindings::error::Fallible;
51use script_bindings::reflector::DomObject;
52use script_bindings::trace::CustomTraceable;
53use servo_config::pref;
54use servo_url::ServoUrl;
55
56use crate::dom::bindings::error::{Error, ErrorToJsval, report_pending_exception};
57use crate::dom::bindings::inheritance::Castable;
58use crate::dom::bindings::refcounted::Trusted;
59use crate::dom::bindings::root::DomRoot;
60use crate::dom::bindings::trace::RootedTraceableBox;
61use crate::dom::csp::{GlobalCspReporting, Violation};
62use crate::dom::globalscope::GlobalScope;
63use crate::dom::globalscope::script_execution::fill_compile_options;
64use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
65use crate::dom::performance::performanceresourcetiming::InitiatorType;
66use crate::dom::promisenativehandler::Callback;
67use crate::dom::script_execution::ScriptOptions;
68use crate::dom::types::{
69 DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope, WorkletGlobalScope,
70};
71use crate::dom::window::Window;
72use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
73use crate::modules::import_map::{ModuleSpecifierMap, resolve_url_like_module_specifier};
74use crate::modules::module_loading::{
75 LoadState, host_load_imported_module, load_requested_modules,
76};
77use crate::realms::enter_auto_realm;
78use crate::runtime::script_runtime::IntroductionType;
79use crate::tasks::task::NonSendTaskBox;
80use crate::unminify::{ScriptSource, unminify_js};
81
82pub(crate) fn gen_type_error(
83 cx: &mut JSContext,
84 global: &GlobalScope,
85 error: Error,
86) -> RethrowError {
87 rooted!(&in(cx) let mut thrown = UndefinedValue());
88 error.to_jsval(cx, global, thrown.handle_mut());
89
90 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
91}
92
93#[derive(JSTraceable)]
94pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
95
96impl ModuleObject {
97 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
98 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
99 }
100
101 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
102 self.0.handle()
103 }
104}
105
106#[derive(JSTraceable)]
107pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
108
109impl RethrowError {
110 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
111 Self(RootedTraceableBox::from_box(val))
112 }
113
114 #[expect(unsafe_code)]
115 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
116 rooted!(&in(cx) let mut exception = UndefinedValue());
117 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
118 unsafe { JS_ClearPendingException(cx) };
119
120 Self::new(Heap::boxed(exception.get()))
121 }
122
123 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
124 self.0.handle()
125 }
126}
127
128impl Debug for RethrowError {
129 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
130 "RethrowError(...)".fmt(fmt)
131 }
132}
133
134impl Clone for RethrowError {
135 fn clone(&self) -> Self {
136 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
137 }
138}
139
140pub(crate) struct ModuleScript {
141 pub(crate) base_url: ServoUrl,
142 pub(crate) options: ScriptFetchOptions,
143 pub(crate) owner: Option<Trusted<GlobalScope>>,
144}
145
146impl ModuleScript {
147 pub(crate) fn new(
148 base_url: ServoUrl,
149 options: ScriptFetchOptions,
150 owner: Option<Trusted<GlobalScope>>,
151 ) -> Self {
152 ModuleScript {
153 base_url,
154 options,
155 owner,
156 }
157 }
158}
159
160pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
161
162#[derive(JSTraceable)]
163pub(crate) enum ModuleStatus {
164 #[expect(clippy::type_complexity)]
165 Fetching(#[no_trace] Vec<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>),
166 Loaded(Rc<ModuleTree>),
167}
168
169#[derive(JSTraceable, MallocSizeOf)]
170pub(crate) struct ModuleTree {
171 #[ignore_malloc_size_of = "mozjs"]
172 record: OnceCell<ModuleObject>,
173 #[ignore_malloc_size_of = "mozjs"]
174 parse_error: OnceCell<RethrowError>,
175 #[ignore_malloc_size_of = "mozjs"]
176 rethrow_error: DomRefCell<Option<RethrowError>>,
177}
178
179impl ModuleTree {
180 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
181 self.record.get()
182 }
183
184 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
185 self.parse_error.get()
186 }
187
188 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
189 &self.rethrow_error
190 }
191
192 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
193 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
194 }
195}
196
197impl ModuleTree {
198 #[expect(unsafe_code)]
199 #[expect(clippy::too_many_arguments)]
200 fn create_a_javascript_module_script(
202 cx: &mut JSContext,
203 source: Cow<'_, str>,
204 global: &GlobalScope,
205 url: &ServoUrl,
206 options: ScriptFetchOptions,
207 external: bool,
208 line_number: u32,
209 introduction_type: Option<&'static CStr>,
210 ) -> Self {
211 let mut realm = AutoRealm::new(
212 cx,
213 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
214 );
215 let cx = &mut *realm;
216
217 let owner = Trusted::new(global);
218
219 let module = ModuleTree {
222 record: OnceCell::new(),
223 parse_error: OnceCell::new(),
224 rethrow_error: DomRefCell::new(None),
225 };
226
227 let compile_options = fill_compile_options(
228 cx,
229 url.as_str(),
230 ScriptOptions::empty(),
231 introduction_type,
232 line_number,
233 );
234
235 let mut source = if let Some(unminified_js_dir) = global.unminified_js_dir() {
236 let mut module_source = ScriptSource {
237 source,
238 external,
239 url,
240 };
241 unminify_js(&mut module_source, unminified_js_dir);
242 transform_str_to_source_text(&module_source.source)
243 } else {
244 transform_str_to_source_text(&source)
245 };
246
247 unsafe {
248 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
250 module_script.set(CompileModule1(cx, compile_options.ptr, &mut source));
251
252 if module_script.is_null() {
254 warn!("fail to compile module script of {}", url);
255
256 let _ = module
258 .parse_error
259 .set(RethrowError::from_pending_exception(cx));
260
261 return module;
263 }
264
265 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
269
270 SetModulePrivate(
271 module_script.get(),
272 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
273 );
274
275 let _ = module.record.set(ModuleObject::new(module_script.handle()));
277 }
278
279 module
281 }
282
283 #[expect(unsafe_code)]
284 fn create_a_json_module_script(
286 cx: &mut JSContext,
287 source: &str,
288 global: &GlobalScope,
289 url: &ServoUrl,
290 introduction_type: Option<&'static CStr>,
291 ) -> Self {
292 let mut realm = AutoRealm::new(
293 cx,
294 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
295 );
296 let cx = &mut *realm;
297
298 let module = ModuleTree {
301 record: OnceCell::new(),
302 parse_error: OnceCell::new(),
303 rethrow_error: DomRefCell::new(None),
304 };
305
306 let compile_options = fill_compile_options(
311 cx,
312 url.as_str(),
313 ScriptOptions::empty(),
314 introduction_type,
315 1, );
317
318 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
319
320 unsafe {
321 module_script.set(CompileJsonModule1(
323 cx,
324 compile_options.ptr,
325 &mut transform_str_to_source_text(source),
326 ));
327 }
328
329 if module_script.is_null() {
331 warn!("fail to compile module script of {}", url);
332
333 let _ = module
334 .parse_error
335 .set(RethrowError::from_pending_exception(cx));
336 return module;
337 }
338
339 let _ = module.record.set(ModuleObject::new(module_script.handle()));
341
342 module
344 }
345
346 #[expect(unsafe_code)]
349 pub(crate) fn execute_module(
350 &self,
351 cx: &mut JSContext,
352 global: &GlobalScope,
353 module_record: HandleObject,
354 mut eval_result: MutableHandleValue,
355 ) -> Result<(), RethrowError> {
356 let mut realm = AutoRealm::new(
357 cx,
358 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
359 );
360 let cx = &mut *realm;
361
362 unsafe {
363 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
364 assert!(ok, "module evaluation failed");
365
366 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
367 if eval_result.is_object() {
368 evaluation_promise.set(eval_result.to_object());
369 }
370
371 let throw_result = ThrowOnModuleEvaluationFailure(
372 cx,
373 evaluation_promise.handle(),
374 ModuleErrorBehaviour::ThrowModuleErrorsSync,
375 );
376 if !throw_result {
377 warn!("fail to evaluate module");
378
379 Err(RethrowError::from_pending_exception(cx))
380 } else {
381 debug!("module evaluated successfully");
382 Ok(())
383 }
384 }
385 }
386
387 #[expect(unsafe_code)]
388 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
389 let module_error = self.rethrow_error.borrow();
390
391 if let Some(exception) = &*module_error {
392 let mut realm = enter_auto_realm(cx, global);
393 let cx = &mut realm.current_realm();
394
395 unsafe {
396 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
397 }
398 report_pending_exception(cx);
399 }
400 }
401
402 pub(crate) fn resolve_module_specifier(
404 global: &GlobalScope,
405 script: Option<&ModuleScript>,
406 specifier: String,
407 ) -> Fallible<ServoUrl> {
408 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
410 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
412 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
416 None => (global, &global.api_base_url()),
421 };
422
423 let import_map = if global.is::<Window>() {
427 Some(global.import_map())
428 } else {
429 None
430 };
431
432 let serialized_base_url = base_url.as_str();
434 let as_url = resolve_url_like_module_specifier(&specifier, base_url);
436 let normalized_specifier = match &as_url {
439 Some(url) => url.as_str(),
440 None => &specifier,
441 };
442
443 let mut result = None;
445 if let Some(map) = import_map {
446 for (prefix, imports) in &map.scopes {
448 let prefix = prefix.as_str();
451 if prefix == serialized_base_url ||
452 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
453 {
454 let scope_imports_match =
457 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
458
459 if scope_imports_match.is_some() {
461 result = scope_imports_match;
462 break;
463 }
464 }
465 }
466
467 if result.is_none() {
470 result =
471 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
472 }
473 }
474
475 if result.is_none() {
477 result = as_url.clone();
478 }
479
480 match result {
482 Some(result) => {
483 global.add_module_to_resolved_module_set(
486 serialized_base_url,
487 normalized_specifier,
488 as_url.clone(),
489 );
490 Ok(result)
492 },
493 None => Err(Error::Type(
496 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
497 .to_owned(),
498 )),
499 }
500 }
501}
502
503#[derive(JSTraceable, MallocSizeOf)]
504pub(crate) struct ModuleHandler {
505 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
506 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
507}
508
509impl ModuleHandler {
510 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
511 Box::new(Self {
512 task: DomRefCell::new(Some(task)),
513 })
514 }
515}
516
517impl Callback for ModuleHandler {
518 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
519 let task = self.task.borrow_mut().take().unwrap();
520 task.run_box(cx);
521 }
522}
523
524struct ModuleContext {
526 owner: Trusted<GlobalScope>,
528 data: BytesMut,
530 metadata: Option<Metadata>,
532 module_request: ModuleRequest,
534 options: ScriptFetchOptions,
536 status: Result<(), NetworkError>,
538 introduction_type: Option<&'static CStr>,
540 policy_container: Option<PolicyContainer>,
542}
543
544impl FetchResponseListener for ModuleContext {
545 fn process_request_body(&mut self, _: RequestId) {}
547
548 fn process_response(
549 &mut self,
550 _: &mut js::context::JSContext,
551 _: RequestId,
552 metadata: Result<FetchMetadata, NetworkError>,
553 ) {
554 self.metadata = metadata.ok().map(|meta| match meta {
555 FetchMetadata::Unfiltered(m) => m,
556 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
557 });
558
559 let status = self
560 .metadata
561 .as_ref()
562 .map(|m| m.status.clone())
563 .unwrap_or_else(HttpStatus::new_error);
564
565 self.status = {
566 if status.is_error() {
567 Err(NetworkError::ResourceLoadError(
568 "No http status code received".to_owned(),
569 ))
570 } else if status.is_success() {
571 Ok(())
572 } else {
573 Err(NetworkError::ResourceLoadError(format!(
574 "HTTP error code {}",
575 status.code()
576 )))
577 }
578 };
579 }
580
581 fn process_response_chunk(
582 &mut self,
583 _: &mut js::context::JSContext,
584 _: RequestId,
585 chunk: Bytes,
586 ) {
587 if self.status.is_ok() {
588 self.data.extend_from_slice(&chunk);
589 }
590 }
591
592 fn process_response_eof(
595 mut self,
596 cx: &mut js::context::JSContext,
597 _: RequestId,
598 response: Result<(), NetworkError>,
599 timing: ResourceFetchTiming,
600 ) {
601 let global = self.owner.root();
602 let (_url, module_type) = &self.module_request;
603
604 if !global.is::<WorkletGlobalScope>() {
605 network_listener::submit_timing(cx, &self, &response, &timing);
606 }
607
608 let module_map = global.module_map();
609
610 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
613 error!("Fetching module script failed {:?}", error);
614 let Some(ModuleStatus::Fetching(callbacks)) =
617 module_map.safe_borrow_mut(cx).remove(&self.module_request)
618 else {
619 return error!("Processing response for a non pending module request");
620 };
621
622 for callback in callbacks {
624 (callback)(cx, None);
625 }
626
627 return;
629 }
630
631 let metadata = self.metadata.take().unwrap();
632
633 if let Some(policy_container) = self.policy_container {
636 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
637 "We only need a policy container when initializing a worker's globalscope.",
638 );
639 workerscope.process_response_for_workerscope(&metadata, &policy_container);
640 }
641
642 let final_url = metadata.final_url;
643
644 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
646
647 let mut module_script = None;
649
650 let referrer_policy = metadata
652 .headers
653 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
654 .into();
655
656 if referrer_policy != ReferrerPolicy::EmptyString {
658 self.options.referrer_policy = referrer_policy;
659 }
660
661 if let Some(mime) = mime_type {
667 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
669
670 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
673 matches!(module_type, ModuleType::JavaScript)
674 {
675 if let Some(window) = global.downcast::<Window>() &&
676 let Some(script_souce) = window.local_script_source()
677 {
678 substitute_with_local_script(script_souce, &mut source_text, &final_url);
679 }
680
681 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
682 cx,
683 source_text,
684 &global,
685 &final_url,
686 self.options,
687 true,
688 1,
689 self.introduction_type,
690 ));
691 module_script = Some(module_tree);
692 } else if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
693 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
696 cx,
697 &source_text,
698 &global,
699 &final_url,
700 self.introduction_type,
701 ));
702 module_script = Some(module_tree);
703 }
704 }
705
706 let callbacks = match module_map
707 .safe_borrow_mut(cx)
708 .entry(self.module_request.clone())
709 {
710 Entry::Occupied(mut entry) => {
711 let old_value = match module_script.as_ref() {
714 None => entry.remove(),
715 Some(module_script) => {
716 entry.insert(ModuleStatus::Loaded(module_script.clone()))
717 },
718 };
719
720 match old_value {
721 ModuleStatus::Loaded(_) => {
722 return error!("Processing response for a non pending module request");
723 },
724 ModuleStatus::Fetching(callbacks) => callbacks,
725 }
726 },
727 Entry::Vacant(_) => {
728 return error!("Processing response for a non pending module request");
729 },
730 };
731
732 for callback in callbacks {
734 (callback)(cx, module_script.clone());
735 }
736 }
737
738 fn process_csp_violations(
739 &mut self,
740 cx: &mut js::context::JSContext,
741 _request_id: RequestId,
742 violations: Vec<Violation>,
743 ) {
744 let global = self.owner.root();
745 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
746 scope.report_csp_violations(violations);
747 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
748 scope.report_csp_violations(violations);
749 } else {
750 global.report_csp_violations(cx, violations, None, None);
751 }
752 }
753
754 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
755 self.data.reserve(size - self.data.len());
756 }
757}
758
759impl ResourceTimingListener for ModuleContext {
760 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
761 let initiator_type = InitiatorType::LocalName("module".to_string());
762 let (url, _) = &self.module_request;
763 (initiator_type, url.clone())
764 }
765
766 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
767 self.owner.root()
768 }
769}
770
771#[expect(unsafe_code)]
772#[expect(non_snake_case)]
773pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
776 unsafe {
777 if GetModuleLoadHook(rt).is_some() {
778 return;
779 }
780
781 SetModuleLoadHook(rt, Some(HostLoadImportedModule));
782 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
783 SetScriptPrivateReferenceHooks(
784 rt,
785 Some(host_add_ref_top_level_script),
786 Some(host_release_top_level_script),
787 );
788 }
789}
790
791#[expect(unsafe_code)]
792unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
793 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
794 mem::forget(val.clone());
795 mem::forget(val);
796}
797
798#[expect(unsafe_code)]
799unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
800 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
801}
802
803#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
804pub(crate) struct ScriptFetchOptions {
806 pub(crate) integrity_metadata: String,
807 #[no_trace]
808 pub(crate) credentials_mode: CredentialsMode,
809 pub(crate) cryptographic_nonce: String,
810 #[no_trace]
811 pub(crate) parser_metadata: ParserMetadata,
812 #[no_trace]
813 pub(crate) referrer_policy: ReferrerPolicy,
814 pub(crate) render_blocking: bool,
818}
819
820impl ScriptFetchOptions {
821 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
823 Self {
824 cryptographic_nonce: String::new(),
825 integrity_metadata: String::new(),
826 parser_metadata: ParserMetadata::NotParserInserted,
827 credentials_mode: CredentialsMode::CredentialsSameOrigin,
828 referrer_policy: ReferrerPolicy::EmptyString,
829 render_blocking: false,
830 }
831 }
832
833 pub(crate) fn descendant_fetch_options(
835 &self,
836 url: &ServoUrl,
837 global: &GlobalScope,
838 ) -> ScriptFetchOptions {
839 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
841
842 Self {
845 integrity_metadata: integrity,
847 cryptographic_nonce: self.cryptographic_nonce.clone(),
848 credentials_mode: self.credentials_mode,
849 parser_metadata: self.parser_metadata,
850 referrer_policy: self.referrer_policy,
851 render_blocking: self.render_blocking,
852 }
853 }
854}
855
856#[expect(unsafe_code)]
857pub(crate) unsafe fn module_script_from_reference_private<'a>(
858 reference_private: Handle<'a, JSVal>,
859) -> Option<&'a ModuleScript> {
860 if reference_private.get().is_undefined() {
861 return None;
862 }
863 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
864}
865
866#[expect(unsafe_code)]
867#[expect(non_snake_case)]
868unsafe extern "C" fn HostLoadImportedModule(
871 cx: *mut RawJSContext,
872 referrer: RawHandle<*mut JSScript>,
873 module_request: RawHandle<*mut JSObject>,
874 host_defined: RawHandleValue,
875 payload: RawHandleValue,
876 _line_number: u32,
877 _column_number: ColumnNumberOneOrigin,
878) -> bool {
879 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
881 let mut realm = CurrentRealm::assert(&mut cx);
882 let cx = &mut realm;
883
884 let referrer = unsafe { Handle::from_raw(referrer) };
885 let module_request = unsafe { Handle::from_raw(module_request) };
886 let host_defined = unsafe { Handle::from_raw(host_defined) };
887 let payload = unsafe { Handle::from_raw(payload) };
888
889 let jsstr = unsafe { GetModuleRequestSpecifier(cx, module_request) };
890 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
891
892 host_load_imported_module(
893 cx,
894 referrer,
895 module_request,
896 specifier,
897 host_defined,
898 payload,
899 );
900 true
901}
902
903const MODULE_RECORD_SLOT: usize = 0;
905
906#[expect(unsafe_code)]
907#[expect(non_snake_case)]
908unsafe extern "C" fn HostPopulateImportMeta(
911 cx: *mut RawJSContext,
912 module_record: RawHandle<*mut JSObject>,
913 meta_object: RawHandle<*mut JSObject>,
914) -> bool {
915 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
917 let mut realm = CurrentRealm::assert(&mut cx);
918 let global_scope = GlobalScope::from_current_realm(&mut realm);
919
920 rooted!(&in(cx) let mut module_private: JSVal);
922 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
923 let base_url = match unsafe { module_script_from_reference_private(module_private.handle()) } {
924 Some(module_data) => module_data.base_url.clone(),
925 None => global_scope.api_base_url(),
926 };
927
928 unsafe {
929 let url_string = JS_NewStringCopyN(
930 &mut cx,
931 base_url.as_str().as_ptr() as *const _,
932 base_url.as_str().len(),
933 );
934 rooted!(&in(cx) let url_string = url_string);
935
936 if !JS_DefineProperty4(
938 &mut cx,
939 Handle::from_raw(meta_object),
940 c"url".as_ptr(),
941 url_string.handle(),
942 JSPROP_ENUMERATE.into(),
943 ) {
944 return false;
945 }
946
947 let resolve_function = DefineFunctionWithReserved(
949 &mut cx,
950 meta_object.get(),
951 c"resolve".as_ptr(),
952 Some(import_meta_resolve),
953 1,
954 JSPROP_ENUMERATE.into(),
955 );
956
957 if resolve_function.is_null() {
958 return false;
959 }
960
961 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
962 assert!(!obj.is_null());
963 SetFunctionNativeReserved(
964 obj.get(),
965 MODULE_RECORD_SLOT,
966 &ObjectValue(module_record.get()),
967 );
968 }
969
970 true
971}
972
973#[expect(unsafe_code)]
974unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
975 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
977 let mut realm = CurrentRealm::assert(&mut cx);
978 let global_scope = GlobalScope::from_current_realm(&mut realm);
979
980 let cx = &mut realm;
981
982 let args = unsafe { CallArgs::from_vp(vp, argc) };
983
984 rooted!(&in(cx) let module_value = unsafe { *GetFunctionNativeReserved(args.callee(), MODULE_RECORD_SLOT) });
985 assert!(!module_value.is_undefined());
986 rooted!(&in(cx) let module_record = module_value.to_object());
987 rooted!(&in(cx) let mut module_private: JSVal);
988 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
989 assert!(!module_private.is_undefined());
990 let module_data = unsafe { module_script_from_reference_private(module_private.handle()) };
991
992 let specifier = unsafe {
996 let value = HandleValue::from_raw(args.get(0));
997
998 match NonNull::new(ToString(cx, value)) {
999 Some(jsstr) => jsstr_to_string(cx, jsstr),
1000 None => return false,
1001 }
1002 };
1003
1004 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1006
1007 match url {
1008 Ok(url) => {
1009 url.as_str()
1011 .safe_to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
1012 true
1013 },
1014 Err(error) => {
1015 let resolution_error = gen_type_error(cx, &global_scope, error);
1016
1017 unsafe {
1018 JS_SetPendingException(
1019 cx,
1020 resolution_error.handle(),
1021 ExceptionStackBehavior::Capture,
1022 );
1023 }
1024 false
1025 },
1026 }
1027}
1028
1029#[expect(clippy::too_many_arguments)]
1030pub(crate) fn fetch_a_module_script_graph(
1033 cx: &mut JSContext,
1034 global: &GlobalScope,
1035 url: UrlWithBlobClaim,
1036 fetch_client: RequestClient,
1037 destination: Destination,
1038 referrer: Referrer,
1039 credentials_mode: CredentialsMode,
1040 introduction_type: Option<&'static CStr>,
1041 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1042) {
1043 let global_scope = DomRoot::from_ref(global);
1044
1045 let options = ScriptFetchOptions {
1050 integrity_metadata: "".into(),
1051 credentials_mode,
1052 cryptographic_nonce: "".into(),
1053 parser_metadata: ParserMetadata::NotParserInserted,
1054 referrer_policy: ReferrerPolicy::EmptyString,
1055 render_blocking: false,
1056 };
1057
1058 fetch_a_single_module_script(
1061 cx,
1062 url,
1063 fetch_client.clone(),
1064 global,
1065 destination,
1066 options,
1067 referrer,
1068 None,
1069 true,
1070 introduction_type,
1071 move |cx, module_tree| {
1072 let Some(module) = module_tree else {
1073 return on_complete(cx, None);
1075 };
1076
1077 fetch_the_descendants_and_link_module_script(
1080 cx,
1081 &global_scope,
1082 module,
1083 fetch_client,
1084 destination,
1085 on_complete,
1086 );
1087 },
1088 );
1089}
1090
1091pub(crate) fn fetch_an_external_module_script(
1093 cx: &mut JSContext,
1094 url: UrlWithBlobClaim,
1095 global: &GlobalScope,
1096 options: ScriptFetchOptions,
1097 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1098) {
1099 let referrer = global.get_referrer();
1100 let fetch_client = global.request_client(Some(cx.no_gc()));
1101 let global_scope = DomRoot::from_ref(global);
1102
1103 fetch_a_single_module_script(
1106 cx,
1107 url,
1108 fetch_client.clone(),
1109 global,
1110 Destination::Script,
1111 options,
1112 referrer,
1113 None,
1114 true,
1115 Some(IntroductionType::SRC_SCRIPT),
1116 move |cx, module_tree| {
1117 let Some(module) = module_tree else {
1118 return on_complete(cx, None);
1120 };
1121
1122 fetch_the_descendants_and_link_module_script(
1124 cx,
1125 &global_scope,
1126 module,
1127 fetch_client,
1128 Destination::Script,
1129 on_complete,
1130 );
1131 },
1132 );
1133}
1134
1135pub(crate) fn fetch_a_modulepreload_module(
1137 cx: &mut JSContext,
1138 url: UrlWithBlobClaim,
1139 destination: Destination,
1140 global: &GlobalScope,
1141 options: ScriptFetchOptions,
1142 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1143) {
1144 let referrer = global.get_referrer();
1145 let fetch_client = global.request_client(Some(cx.no_gc()));
1146 let global_scope = DomRoot::from_ref(global);
1147
1148 let module_type = if let Destination::Json = destination {
1151 Some(ModuleType::JSON)
1152 } else {
1153 None
1154 };
1155
1156 fetch_a_single_module_script(
1159 cx,
1160 url,
1161 fetch_client.clone(),
1162 global,
1163 destination,
1164 options,
1165 referrer,
1166 module_type,
1167 true,
1168 Some(IntroductionType::SRC_SCRIPT),
1169 move |cx, result| {
1170 on_complete(cx, result.is_none());
1172
1173 assert!(global_scope.is::<Window>());
1175
1176 if pref!(dom_allow_preloading_module_descendants) &&
1179 let Some(module) = result
1180 {
1181 fetch_the_descendants_and_link_module_script(
1182 cx,
1183 &global_scope,
1184 module,
1185 fetch_client,
1186 destination,
1187 |_, _| {},
1188 );
1189 }
1190 },
1191 );
1192}
1193
1194#[expect(clippy::too_many_arguments)]
1195pub(crate) fn fetch_inline_module_script(
1197 cx: &mut JSContext,
1198 global: &GlobalScope,
1199 module_script_text: Cow<'_, str>,
1200 url: ServoUrl,
1201 options: ScriptFetchOptions,
1202 line_number: u32,
1203 introduction_type: Option<&'static CStr>,
1204 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1205) {
1206 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1208 cx,
1209 module_script_text,
1210 global,
1211 &url,
1212 options,
1213 false,
1214 line_number,
1215 introduction_type,
1216 ));
1217 let fetch_client = global.request_client(Some(cx.no_gc()));
1218
1219 fetch_the_descendants_and_link_module_script(
1221 cx,
1222 global,
1223 module_tree,
1224 fetch_client,
1225 Destination::Script,
1226 on_complete,
1227 );
1228}
1229
1230fn fetch_the_descendants_and_link_module_script(
1232 cx: &mut JSContext,
1233 global: &GlobalScope,
1234 module_script: Rc<ModuleTree>,
1235 fetch_client: RequestClient,
1236 destination: Destination,
1237 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1238) {
1239 let Some(record) = module_script.get_record() else {
1242 let parse_error = module_script.get_parse_error().cloned();
1243
1244 module_script.set_rethrow_error(parse_error.unwrap());
1246
1247 on_complete(cx, Some(module_script));
1249
1250 return;
1252 };
1253
1254 let state = Box::new(LoadState {
1257 error_to_rethrow: RefCell::new(None),
1258 destination,
1259 fetch_client,
1260 module_script: DomRefCell::new(Some(module_script.clone())),
1261 on_complete: DomRefCell::new(Some(Box::new(on_complete))),
1262 });
1263
1264 let mut realm = enter_auto_realm(cx, global);
1267 let cx = &mut realm.current_realm();
1268
1269 load_requested_modules(cx, record.handle(), state);
1271}
1272
1273#[expect(clippy::too_many_arguments)]
1275pub(crate) fn fetch_a_single_module_script(
1276 cx: &mut JSContext,
1277 url: UrlWithBlobClaim,
1278 fetch_client: RequestClient,
1279 global: &GlobalScope,
1280 destination: Destination,
1281 options: ScriptFetchOptions,
1282 referrer: Referrer,
1283 module_type: Option<ModuleType>,
1284 is_top_level: bool,
1285 introduction_type: Option<&'static CStr>,
1286 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1287) {
1288 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1292
1293 let module_request = (url.url(), module_type);
1298
1299 let module_map = global.module_map();
1301 let mut module_map_borrow = module_map.safe_borrow_mut(cx);
1302
1303 let entry = module_map_borrow.entry(module_request.clone());
1304
1305 match entry {
1306 Entry::Occupied(mut entry) => match entry.get_mut() {
1307 ModuleStatus::Loaded(module_tree) => {
1310 let module = module_tree.clone();
1311 drop(module_map_borrow);
1312 return on_complete(cx, Some(module));
1313 },
1314 ModuleStatus::Fetching(callbacks) => return callbacks.push(Box::new(on_complete)),
1317 },
1318 Entry::Vacant(entry) => {
1320 entry.insert(ModuleStatus::Fetching(vec![Box::new(on_complete)]));
1321 },
1322 }
1323
1324 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1326 .then(|| fetch_client.policy_container.clone());
1327
1328 let mode = match destination {
1333 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1334 _ => RequestMode::CorsMode,
1335 };
1336
1337 let destination = match module_type {
1340 ModuleType::JSON => Destination::Json,
1341 ModuleType::CSS => todo!("https://github.com/servo/servo/issues/47179"),
1342 ModuleType::Text => {
1343 todo!("https://github.com/servo/servo/issues/47149")
1344 },
1345 ModuleType::Bytes => unreachable!("Not in ESR153"),
1346 ModuleType::JavaScript | ModuleType::Unknown => destination,
1347 };
1348
1349 let request = RequestBuilder::new(global.webview_id(), url, referrer)
1353 .destination(destination)
1354 .parser_metadata(options.parser_metadata)
1355 .integrity_metadata(options.integrity_metadata.clone())
1356 .credentials_mode(options.credentials_mode)
1357 .referrer_policy(options.referrer_policy)
1358 .mode(mode)
1359 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1360 .client(fetch_client)
1361 .pipeline_id(Some(global.pipeline_id()));
1362
1363 let context = ModuleContext {
1364 owner: Trusted::new(global),
1365 data: BytesMut::new(),
1366 metadata: None,
1367 module_request,
1368 options,
1369 status: Ok(()),
1370 introduction_type,
1371 policy_container,
1372 };
1373
1374 let task_source = global.task_manager().networking_task_source().to_sendable();
1375 global.fetch(request, context, task_source);
1376}
1377
1378#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1380pub(crate) struct ResolvedModule {
1381 pub(crate) base_url: String,
1383 pub(crate) specifier: String,
1385 #[no_trace]
1387 pub(crate) specifier_url: Option<ServoUrl>,
1388}
1389
1390impl ResolvedModule {
1391 pub(crate) fn new(
1392 base_url: String,
1393 specifier: String,
1394 specifier_url: Option<ServoUrl>,
1395 ) -> Self {
1396 Self {
1397 base_url,
1398 specifier,
1399 specifier_url,
1400 }
1401 }
1402}
1403
1404fn resolve_imports_match(
1409 normalized_specifier: &str,
1410 as_url: Option<&ServoUrl>,
1411 specifier_map: &ModuleSpecifierMap,
1412) -> Fallible<Option<ServoUrl>> {
1413 for (specifier_key, resolution_result) in specifier_map {
1415 if specifier_key == normalized_specifier {
1417 if let Some(resolution_result) = resolution_result {
1418 return Ok(Some(resolution_result.clone()));
1422 } else {
1423 return Err(Error::Type(
1425 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
1426 ));
1427 }
1428 }
1429
1430 if specifier_key.ends_with('\u{002f}') &&
1435 normalized_specifier.starts_with(specifier_key) &&
1436 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
1437 {
1438 let Some(resolution_result) = resolution_result else {
1441 return Err(Error::Type(
1442 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
1443 ));
1444 };
1445
1446 let after_prefix = normalized_specifier
1448 .strip_prefix(specifier_key)
1449 .expect("specifier_key should be the prefix of normalized_specifier");
1450
1451 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
1453
1454 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
1456
1457 let Ok(url) = url else {
1460 return Err(Error::Type(
1461 c"Resolution of normalizedSpecifier was blocked since
1462 the afterPrefix portion could not be URL-parsed relative to
1463 the resolutionResult mapped to by the specifierKey prefix."
1464 .to_owned(),
1465 ));
1466 };
1467
1468 if !url.as_str().starts_with(resolution_result.as_str()) {
1471 return Err(Error::Type(
1472 c"Resolution of normalizedSpecifier was blocked due to
1473 it backtracking above its prefix specifierKey."
1474 .to_owned(),
1475 ));
1476 }
1477
1478 return Ok(Some(url));
1480 }
1481 }
1482
1483 Ok(None)
1485}