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 encoding_rs::UTF_8;
18use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
19use hyper_serde::Serde;
20use indexmap::IndexMap;
21use indexmap::map::Entry as IndexMapEntry;
22use js::context::JSContext;
23use js::conversions::{ToJSValConvertible, jsstr_to_string};
24use js::gc::{HandleObject, MutableHandleValue};
25use js::jsapi::{
26 CallArgs, ExceptionStackBehavior, GetFunctionNativeReserved, GetModuleResolveHook,
27 Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
28 JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
29 ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook, SetModuleMetadataHook,
30 SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks, Value,
31};
32use js::jsval::{JSVal, PrivateValue, UndefinedValue};
33use js::realm::{AutoRealm, CurrentRealm};
34use js::rust::wrappers2::{
35 CompileJsonModule1, CompileModule1, DefineFunctionWithReserved, GetModuleRequestSpecifier,
36 GetModuleRequestType, JS_ClearPendingException, JS_DefineProperty4, JS_GetPendingException,
37 JS_NewStringCopyN, JS_SetPendingException, ModuleEvaluate, ModuleLink,
38 ThrowOnModuleEvaluationFailure,
39};
40use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
41use mime::Mime;
42use net_traits::blob_url_store::UrlWithBlobClaim;
43use net_traits::http_status::HttpStatus;
44use net_traits::mime_classifier::MimeClassifier;
45use net_traits::policy_container::PolicyContainer;
46use net_traits::request::{
47 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestClient,
48 RequestId, RequestMode,
49};
50use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
51use script_bindings::cell::DomRefCell;
52use script_bindings::error::Fallible;
53use script_bindings::reflector::DomObject;
54use script_bindings::settings_stack::run_a_callback;
55use script_bindings::trace::CustomTraceable;
56use servo_config::pref;
57use servo_url::ServoUrl;
58
59use crate::DomTypeHolder;
60use crate::dom::bindings::error::{Error, ErrorToJsval, report_pending_exception};
61use crate::dom::bindings::inheritance::Castable;
62use crate::dom::bindings::refcounted::Trusted;
63use crate::dom::bindings::root::DomRoot;
64use crate::dom::bindings::str::DOMString;
65use crate::dom::bindings::trace::RootedTraceableBox;
66use crate::dom::csp::{GlobalCspReporting, Violation};
67use crate::dom::globalscope::GlobalScope;
68use crate::dom::globalscope::script_execution::{ErrorReporting, fill_compile_options};
69use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
70use crate::dom::performance::performanceresourcetiming::InitiatorType;
71use crate::dom::promise::Promise;
72use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
73use crate::dom::types::{
74 DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope, WorkletGlobalScope,
75};
76use crate::dom::window::Window;
77use crate::modules::import_map::{ModuleSpecifierMap, resolve_url_like_module_specifier};
78use crate::modules::module_loading::{
79 LoadState, Payload, host_load_imported_module, load_requested_modules,
80};
81use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
82use crate::realms::enter_auto_realm;
83use crate::script_runtime::IntroductionType;
84use crate::tasks::task::NonSendTaskBox;
85
86pub(crate) fn gen_type_error(
87 cx: &mut JSContext,
88 global: &GlobalScope,
89 error: Error,
90) -> RethrowError {
91 rooted!(&in(cx) let mut thrown = UndefinedValue());
92 error.to_jsval(cx, global, thrown.handle_mut());
93
94 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
95}
96
97#[derive(JSTraceable)]
98pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
99
100impl ModuleObject {
101 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
102 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
103 }
104
105 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
106 self.0.handle()
107 }
108}
109
110#[derive(JSTraceable)]
111pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
112
113impl RethrowError {
114 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
115 Self(RootedTraceableBox::from_box(val))
116 }
117
118 #[expect(unsafe_code)]
119 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
120 rooted!(&in(cx) let mut exception = UndefinedValue());
121 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
122 unsafe { JS_ClearPendingException(cx) };
123
124 Self::new(Heap::boxed(exception.get()))
125 }
126
127 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
128 self.0.handle()
129 }
130}
131
132impl Debug for RethrowError {
133 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
134 "RethrowError(...)".fmt(fmt)
135 }
136}
137
138impl Clone for RethrowError {
139 fn clone(&self) -> Self {
140 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
141 }
142}
143
144pub(crate) struct ModuleScript {
145 pub(crate) base_url: ServoUrl,
146 pub(crate) options: ScriptFetchOptions,
147 pub(crate) owner: Option<Trusted<GlobalScope>>,
148}
149
150impl ModuleScript {
151 pub(crate) fn new(
152 base_url: ServoUrl,
153 options: ScriptFetchOptions,
154 owner: Option<Trusted<GlobalScope>>,
155 ) -> Self {
156 ModuleScript {
157 base_url,
158 options,
159 owner,
160 }
161 }
162}
163
164pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
165
166#[derive(JSTraceable)]
167pub(crate) enum ModuleStatus {
168 #[expect(clippy::type_complexity)]
169 Fetching(#[no_trace] Vec<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>),
170 Loaded(Rc<ModuleTree>),
171}
172
173#[derive(JSTraceable, MallocSizeOf)]
174pub(crate) struct ModuleTree {
175 #[no_trace]
176 url: ServoUrl,
177 #[ignore_malloc_size_of = "mozjs"]
178 record: OnceCell<ModuleObject>,
179 #[ignore_malloc_size_of = "mozjs"]
180 parse_error: OnceCell<RethrowError>,
181 #[ignore_malloc_size_of = "mozjs"]
182 rethrow_error: DomRefCell<Option<RethrowError>>,
183 #[no_trace]
184 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
185}
186
187impl ModuleTree {
188 pub(crate) fn get_url(&self) -> ServoUrl {
189 self.url.clone()
190 }
191
192 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
193 self.record.get()
194 }
195
196 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
197 self.parse_error.get()
198 }
199
200 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
201 &self.rethrow_error
202 }
203
204 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
205 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
206 }
207
208 pub(crate) fn find_descendant_inside_module_map(
209 &self,
210 global: &GlobalScope,
211 specifier: &String,
212 module_type: ModuleType,
213 ) -> Option<Rc<ModuleTree>> {
214 self.loaded_modules
215 .borrow()
216 .get(specifier)
217 .and_then(|url| global.module_tree_for_request_if_loaded(&(url.clone(), module_type)))
218 }
219
220 pub(crate) fn insert_module_dependency(
221 &self,
222 module: &Rc<ModuleTree>,
223 module_request_specifier: String,
224 ) {
225 let url = module.url.clone();
227 match self
228 .loaded_modules
229 .borrow_mut()
230 .entry(module_request_specifier)
231 {
232 IndexMapEntry::Occupied(entry) => {
235 assert_eq!(*entry.get(), url);
237 },
238 IndexMapEntry::Vacant(entry) => {
240 entry.insert(url);
243 },
244 }
245 }
246}
247
248pub(crate) struct ModuleSource<'a> {
249 pub source: Cow<'a, str>,
250 pub unminified_dir: Option<String>,
251 pub external: bool,
252 pub url: ServoUrl,
253}
254
255impl<'a> crate::unminify::ScriptSource for ModuleSource<'a> {
256 fn unminified_dir(&self) -> Option<String> {
257 self.unminified_dir.clone()
258 }
259
260 fn extract_bytes(&self) -> &[u8] {
261 self.source.as_bytes()
262 }
263
264 fn rewrite_source(&mut self, source: String) {
265 self.source = source.into();
266 }
267
268 fn url(&self) -> ServoUrl {
269 self.url.clone()
270 }
271
272 fn is_external(&self) -> bool {
273 self.external
274 }
275}
276
277impl ModuleTree {
278 #[expect(unsafe_code)]
279 #[expect(clippy::too_many_arguments)]
280 fn create_a_javascript_module_script(
282 cx: &mut JSContext,
283 source: Cow<'_, str>,
284 global: &GlobalScope,
285 url: &ServoUrl,
286 options: ScriptFetchOptions,
287 external: bool,
288 line_number: u32,
289 introduction_type: Option<&'static CStr>,
290 ) -> Self {
291 let mut realm = AutoRealm::new(
292 cx,
293 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
294 );
295 let cx = &mut *realm;
296
297 let owner = Trusted::new(global);
298
299 let module = ModuleTree {
302 url: url.clone(),
303 record: OnceCell::new(),
304 parse_error: OnceCell::new(),
305 rethrow_error: DomRefCell::new(None),
306 loaded_modules: DomRefCell::new(IndexMap::new()),
307 };
308
309 let compile_options = fill_compile_options(
310 cx,
311 url.as_str(),
312 introduction_type,
313 ErrorReporting::Unmuted,
314 true, line_number,
316 );
317
318 let mut source = if global.unminified_js_dir().is_some() {
319 let mut module_source = ModuleSource {
320 source,
321 unminified_dir: global.unminified_js_dir(),
322 external,
323 url: url.clone(),
324 };
325 crate::unminify::unminify_js(&mut module_source);
326 transform_str_to_source_text(&module_source.source)
327 } else {
328 transform_str_to_source_text(&source)
329 };
330
331 unsafe {
332 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
334 module_script.set(CompileModule1(cx, compile_options.ptr, &mut source));
335
336 if module_script.is_null() {
338 warn!("fail to compile module script of {}", url);
339
340 let _ = module
342 .parse_error
343 .set(RethrowError::from_pending_exception(cx));
344
345 return module;
347 }
348
349 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
353
354 SetModulePrivate(
355 module_script.get(),
356 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
357 );
358
359 let _ = module.record.set(ModuleObject::new(module_script.handle()));
361 }
362
363 module
365 }
366
367 #[expect(unsafe_code)]
368 fn create_a_json_module_script(
370 cx: &mut JSContext,
371 source: &str,
372 global: &GlobalScope,
373 url: &ServoUrl,
374 introduction_type: Option<&'static CStr>,
375 ) -> Self {
376 let mut realm = AutoRealm::new(
377 cx,
378 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
379 );
380 let cx = &mut *realm;
381
382 let module = ModuleTree {
385 url: url.clone(),
386 record: OnceCell::new(),
387 parse_error: OnceCell::new(),
388 rethrow_error: DomRefCell::new(None),
389 loaded_modules: DomRefCell::new(IndexMap::new()),
390 };
391
392 let compile_options = fill_compile_options(
397 cx,
398 url.as_str(),
399 introduction_type,
400 ErrorReporting::Unmuted,
401 true, 1, );
404
405 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
406
407 unsafe {
408 module_script.set(CompileJsonModule1(
410 cx,
411 compile_options.ptr,
412 &mut transform_str_to_source_text(source),
413 ));
414 }
415
416 if module_script.is_null() {
418 warn!("fail to compile module script of {}", url);
419
420 let _ = module
421 .parse_error
422 .set(RethrowError::from_pending_exception(cx));
423 return module;
424 }
425
426 let _ = module.record.set(ModuleObject::new(module_script.handle()));
428
429 module
431 }
432
433 #[expect(unsafe_code)]
436 pub(crate) fn execute_module(
437 &self,
438 cx: &mut JSContext,
439 global: &GlobalScope,
440 module_record: HandleObject,
441 mut eval_result: MutableHandleValue,
442 ) -> Result<(), RethrowError> {
443 let mut realm = AutoRealm::new(
444 cx,
445 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
446 );
447 let cx = &mut *realm;
448
449 unsafe {
450 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
451 assert!(ok, "module evaluation failed");
452
453 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
454 if eval_result.is_object() {
455 evaluation_promise.set(eval_result.to_object());
456 }
457
458 let throw_result = ThrowOnModuleEvaluationFailure(
459 cx,
460 evaluation_promise.handle(),
461 ModuleErrorBehaviour::ThrowModuleErrorsSync,
462 );
463 if !throw_result {
464 warn!("fail to evaluate module");
465
466 Err(RethrowError::from_pending_exception(cx))
467 } else {
468 debug!("module evaluated successfully");
469 Ok(())
470 }
471 }
472 }
473
474 #[expect(unsafe_code)]
475 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
476 let module_error = self.rethrow_error.borrow();
477
478 if let Some(exception) = &*module_error {
479 let mut realm = enter_auto_realm(cx, global);
480 let cx = &mut realm.current_realm();
481
482 unsafe {
483 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
484 }
485 report_pending_exception(cx);
486 }
487 }
488
489 pub(crate) fn resolve_module_specifier(
491 global: &GlobalScope,
492 script: Option<&ModuleScript>,
493 specifier: DOMString,
494 ) -> Fallible<ServoUrl> {
495 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
497 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
499 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
503 None => (global, &global.api_base_url()),
508 };
509
510 let import_map = if global.is::<Window>() {
514 Some(global.import_map())
515 } else {
516 None
517 };
518 let specifier = &specifier.str();
519
520 let serialized_base_url = base_url.as_str();
522 let as_url = resolve_url_like_module_specifier(specifier, base_url);
524 let normalized_specifier = match &as_url {
527 Some(url) => url.as_str(),
528 None => specifier,
529 };
530
531 let mut result = None;
533 if let Some(map) = import_map {
534 for (prefix, imports) in &map.scopes {
536 let prefix = prefix.as_str();
539 if prefix == serialized_base_url ||
540 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
541 {
542 let scope_imports_match =
545 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
546
547 if scope_imports_match.is_some() {
549 result = scope_imports_match;
550 break;
551 }
552 }
553 }
554
555 if result.is_none() {
558 result =
559 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
560 }
561 }
562
563 if result.is_none() {
565 result = as_url.clone();
566 }
567
568 match result {
570 Some(result) => {
571 global.add_module_to_resolved_module_set(
574 serialized_base_url,
575 normalized_specifier,
576 as_url.clone(),
577 );
578 Ok(result)
580 },
581 None => Err(Error::Type(
584 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
585 .to_owned(),
586 )),
587 }
588 }
589}
590
591#[derive(JSTraceable, MallocSizeOf)]
592pub(crate) struct ModuleHandler {
593 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
594 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
595}
596
597impl ModuleHandler {
598 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
599 Box::new(Self {
600 task: DomRefCell::new(Some(task)),
601 })
602 }
603}
604
605impl Callback for ModuleHandler {
606 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
607 let task = self.task.borrow_mut().take().unwrap();
608 task.run_box(cx);
609 }
610}
611
612struct ModuleContext {
614 owner: Trusted<GlobalScope>,
616 data: Vec<u8>,
618 metadata: Option<Metadata>,
620 module_request: ModuleRequest,
622 options: ScriptFetchOptions,
624 status: Result<(), NetworkError>,
626 introduction_type: Option<&'static CStr>,
628 policy_container: Option<PolicyContainer>,
630}
631
632impl FetchResponseListener for ModuleContext {
633 fn process_request_body(&mut self, _: RequestId) {}
635
636 fn process_response(
637 &mut self,
638 _: &mut js::context::JSContext,
639 _: RequestId,
640 metadata: Result<FetchMetadata, NetworkError>,
641 ) {
642 self.metadata = metadata.ok().map(|meta| match meta {
643 FetchMetadata::Unfiltered(m) => m,
644 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
645 });
646
647 let status = self
648 .metadata
649 .as_ref()
650 .map(|m| m.status.clone())
651 .unwrap_or_else(HttpStatus::new_error);
652
653 self.status = {
654 if status.is_error() {
655 Err(NetworkError::ResourceLoadError(
656 "No http status code received".to_owned(),
657 ))
658 } else if status.is_success() {
659 Ok(())
660 } else {
661 Err(NetworkError::ResourceLoadError(format!(
662 "HTTP error code {}",
663 status.code()
664 )))
665 }
666 };
667 }
668
669 fn process_response_chunk(
670 &mut self,
671 _: &mut js::context::JSContext,
672 _: RequestId,
673 mut chunk: Vec<u8>,
674 ) {
675 if self.status.is_ok() {
676 self.data.append(&mut chunk);
677 }
678 }
679
680 fn process_response_eof(
683 mut self,
684 cx: &mut js::context::JSContext,
685 _: RequestId,
686 response: Result<(), NetworkError>,
687 timing: ResourceFetchTiming,
688 ) {
689 let global = self.owner.root();
690 let (_url, module_type) = &self.module_request;
691
692 if !global.is::<WorkletGlobalScope>() {
693 network_listener::submit_timing(cx, &self, &response, &timing);
694 }
695
696 let module_map = global.module_map();
697
698 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
701 error!("Fetching module script failed {:?}", error);
702 let Some(ModuleStatus::Fetching(callbacks)) =
705 module_map.safe_borrow_mut(cx).remove(&self.module_request)
706 else {
707 return error!("Processing response for a non pending module request");
708 };
709
710 for callback in callbacks {
712 (callback)(cx, None);
713 }
714
715 return;
717 }
718
719 let metadata = self.metadata.take().unwrap();
720
721 if let Some(policy_container) = self.policy_container {
724 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
725 "We only need a policy container when initializing a worker's globalscope.",
726 );
727 workerscope.process_response_for_workerscope(&metadata, &policy_container);
728 }
729
730 let final_url = metadata.final_url;
731
732 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
734
735 let mut module_script = None;
737
738 let referrer_policy = metadata
740 .headers
741 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
742 .into();
743
744 if referrer_policy != ReferrerPolicy::EmptyString {
746 self.options.referrer_policy = referrer_policy;
747 }
748
749 if let Some(mime) = mime_type {
755 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
757
758 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
761 matches!(module_type, ModuleType::JavaScript)
762 {
763 if let Some(window) = global.downcast::<Window>() &&
764 let Some(script_souce) = window.local_script_source()
765 {
766 substitute_with_local_script(script_souce, &mut source_text, final_url.clone());
767 }
768
769 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
770 cx,
771 source_text,
772 &global,
773 &final_url,
774 self.options,
775 true,
776 1,
777 self.introduction_type,
778 ));
779 module_script = Some(module_tree);
780 } else if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
781 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
784 cx,
785 &source_text,
786 &global,
787 &final_url,
788 self.introduction_type,
789 ));
790 module_script = Some(module_tree);
791 }
792 }
793
794 let callbacks = match module_map
795 .safe_borrow_mut(cx)
796 .entry(self.module_request.clone())
797 {
798 Entry::Occupied(mut entry) => {
799 let old_value = match module_script.as_ref() {
802 None => entry.remove(),
803 Some(module_script) => {
804 entry.insert(ModuleStatus::Loaded(module_script.clone()))
805 },
806 };
807
808 match old_value {
809 ModuleStatus::Loaded(_) => {
810 return error!("Processing response for a non pending module request");
811 },
812 ModuleStatus::Fetching(callbacks) => callbacks,
813 }
814 },
815 Entry::Vacant(_) => {
816 return error!("Processing response for a non pending module request");
817 },
818 };
819
820 for callback in callbacks {
822 (callback)(cx, module_script.clone());
823 }
824 }
825
826 fn process_csp_violations(
827 &mut self,
828 cx: &mut js::context::JSContext,
829 _request_id: RequestId,
830 violations: Vec<Violation>,
831 ) {
832 let global = self.owner.root();
833 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
834 scope.report_csp_violations(violations);
835 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
836 scope.report_csp_violations(violations);
837 } else {
838 global.report_csp_violations(cx, violations, None, None);
839 }
840 }
841
842 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
843 self.data.reserve(size - self.data.len());
844 }
845}
846
847impl ResourceTimingListener for ModuleContext {
848 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
849 let initiator_type = InitiatorType::LocalName("module".to_string());
850 let (url, _) = &self.module_request;
851 (initiator_type, url.clone())
852 }
853
854 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
855 self.owner.root()
856 }
857}
858
859#[expect(unsafe_code)]
860#[expect(non_snake_case)]
861pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
864 unsafe {
865 if GetModuleResolveHook(rt).is_some() {
866 return;
867 }
868
869 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
870 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
871 SetScriptPrivateReferenceHooks(
872 rt,
873 Some(host_add_ref_top_level_script),
874 Some(host_release_top_level_script),
875 );
876 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
877 }
878}
879
880#[expect(unsafe_code)]
881unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
882 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
883 mem::forget(val.clone());
884 mem::forget(val);
885}
886
887#[expect(unsafe_code)]
888unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
889 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
890}
891
892#[expect(unsafe_code)]
893pub(crate) unsafe extern "C" fn host_import_module_dynamically(
896 cx: *mut RawJSContext,
897 reference_private: RawHandleValue,
898 specifier: RawHandle<*mut JSObject>,
899 promise: RawHandle<*mut JSObject>,
900) -> bool {
901 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
903 let cx = &mut cx;
904 let promise = Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise) });
905
906 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
907 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
908 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
909
910 let mut realm = CurrentRealm::assert(cx);
911 let payload = Payload::PromiseRecord(promise);
912 host_load_imported_module(
913 &mut realm,
914 None,
915 reference_private,
916 specifier,
917 module_type,
918 None,
919 payload,
920 );
921
922 true
923}
924
925#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
926pub(crate) struct ScriptFetchOptions {
928 pub(crate) integrity_metadata: String,
929 #[no_trace]
930 pub(crate) credentials_mode: CredentialsMode,
931 pub(crate) cryptographic_nonce: String,
932 #[no_trace]
933 pub(crate) parser_metadata: ParserMetadata,
934 #[no_trace]
935 pub(crate) referrer_policy: ReferrerPolicy,
936 pub(crate) render_blocking: bool,
940}
941
942impl ScriptFetchOptions {
943 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
945 Self {
946 cryptographic_nonce: String::new(),
947 integrity_metadata: String::new(),
948 parser_metadata: ParserMetadata::NotParserInserted,
949 credentials_mode: CredentialsMode::CredentialsSameOrigin,
950 referrer_policy: ReferrerPolicy::EmptyString,
951 render_blocking: false,
952 }
953 }
954
955 pub(crate) fn descendant_fetch_options(
957 &self,
958 url: &ServoUrl,
959 global: &GlobalScope,
960 ) -> ScriptFetchOptions {
961 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
963
964 Self {
967 integrity_metadata: integrity,
969 cryptographic_nonce: self.cryptographic_nonce.clone(),
970 credentials_mode: self.credentials_mode,
971 parser_metadata: self.parser_metadata,
972 referrer_policy: self.referrer_policy,
973 render_blocking: self.render_blocking,
974 }
975 }
976}
977
978#[expect(unsafe_code)]
979pub(crate) unsafe fn module_script_from_reference_private(
980 reference_private: &RawHandle<JSVal>,
981) -> Option<&ModuleScript> {
982 if reference_private.get().is_undefined() {
983 return None;
984 }
985 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
986}
987
988#[expect(unsafe_code)]
989#[expect(non_snake_case)]
990unsafe extern "C" fn HostResolveImportedModule(
993 cx: *mut RawJSContext,
994 reference_private: RawHandleValue,
995 specifier: RawHandle<*mut JSObject>,
996) -> *mut JSObject {
997 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
999 let mut realm = CurrentRealm::assert(&mut cx);
1000 let global_scope = GlobalScope::from_current_realm(&mut realm);
1001
1002 let cx = &mut realm;
1003
1004 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1006 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1007 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1008
1009 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
1010 let url = ModuleTree::resolve_module_specifier(
1011 &global_scope,
1012 module_data,
1013 DOMString::from(specifier),
1014 );
1015
1016 assert!(url.is_ok());
1018
1019 let parsed_url = url.unwrap();
1020
1021 let module_tree = global_scope.module_tree_for_request_if_loaded(&(parsed_url, module_type));
1023
1024 let module = module_tree.expect("Attempted to link a module not found inside module map");
1025
1026 let fetched_module_object = module.get_record();
1027
1028 assert!(fetched_module_object.is_some());
1030
1031 if let Some(record) = fetched_module_object {
1033 return record.handle().get();
1034 }
1035
1036 unreachable!()
1037}
1038
1039const SLOT_MODULEPRIVATE: usize = 0;
1041
1042#[expect(unsafe_code)]
1043#[expect(non_snake_case)]
1044unsafe extern "C" fn HostPopulateImportMeta(
1047 cx: *mut RawJSContext,
1048 reference_private: RawHandleValue,
1049 meta_object: RawHandle<*mut JSObject>,
1050) -> bool {
1051 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1053 let mut realm = CurrentRealm::assert(&mut cx);
1054 let global_scope = GlobalScope::from_current_realm(&mut realm);
1055
1056 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1058 Some(module_data) => module_data.base_url.clone(),
1059 None => global_scope.api_base_url(),
1060 };
1061
1062 unsafe {
1063 let url_string = JS_NewStringCopyN(
1064 &mut cx,
1065 base_url.as_str().as_ptr() as *const _,
1066 base_url.as_str().len(),
1067 );
1068 rooted!(&in(cx) let url_string = url_string);
1069
1070 if !JS_DefineProperty4(
1072 &mut cx,
1073 Handle::from_raw(meta_object),
1074 c"url".as_ptr(),
1075 url_string.handle(),
1076 JSPROP_ENUMERATE.into(),
1077 ) {
1078 return false;
1079 }
1080
1081 let resolve_function = DefineFunctionWithReserved(
1083 &mut cx,
1084 meta_object.get(),
1085 c"resolve".as_ptr(),
1086 Some(import_meta_resolve),
1087 1,
1088 JSPROP_ENUMERATE.into(),
1089 );
1090
1091 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1092 assert!(!obj.is_null());
1093 SetFunctionNativeReserved(
1094 obj.get(),
1095 SLOT_MODULEPRIVATE,
1096 &reference_private.get() as *const _,
1097 );
1098 }
1099
1100 true
1101}
1102
1103#[expect(unsafe_code)]
1104unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1105 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1107 let mut realm = CurrentRealm::assert(&mut cx);
1108 let global_scope = GlobalScope::from_current_realm(&mut realm);
1109
1110 let cx = &mut realm;
1111
1112 let args = unsafe { CallArgs::from_vp(vp, argc) };
1113
1114 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1115 let reference_private = module_private.handle().into();
1116 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1117
1118 let specifier = unsafe {
1122 let value = HandleValue::from_raw(args.get(0));
1123
1124 match NonNull::new(ToString(cx, value)) {
1125 Some(jsstr) => jsstr_to_string(cx, jsstr).into(),
1126 None => return false,
1127 }
1128 };
1129
1130 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1132
1133 match url {
1134 Ok(url) => {
1135 url.as_str()
1137 .safe_to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
1138 true
1139 },
1140 Err(error) => {
1141 let resolution_error = gen_type_error(cx, &global_scope, error);
1142
1143 unsafe {
1144 JS_SetPendingException(
1145 cx,
1146 resolution_error.handle(),
1147 ExceptionStackBehavior::Capture,
1148 );
1149 }
1150 false
1151 },
1152 }
1153}
1154
1155#[expect(clippy::too_many_arguments)]
1156pub(crate) fn fetch_a_module_script_graph(
1159 cx: &mut JSContext,
1160 global: &GlobalScope,
1161 url: UrlWithBlobClaim,
1162 fetch_client: RequestClient,
1163 destination: Destination,
1164 referrer: Referrer,
1165 credentials_mode: CredentialsMode,
1166 introduction_type: Option<&'static CStr>,
1167 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1168) {
1169 let global_scope = DomRoot::from_ref(global);
1170
1171 let options = ScriptFetchOptions {
1176 integrity_metadata: "".into(),
1177 credentials_mode,
1178 cryptographic_nonce: "".into(),
1179 parser_metadata: ParserMetadata::NotParserInserted,
1180 referrer_policy: ReferrerPolicy::EmptyString,
1181 render_blocking: false,
1182 };
1183
1184 fetch_a_single_module_script(
1187 cx,
1188 url,
1189 fetch_client.clone(),
1190 global,
1191 destination,
1192 options,
1193 referrer,
1194 None,
1195 true,
1196 introduction_type,
1197 move |cx, module_tree| {
1198 let Some(module) = module_tree else {
1199 return on_complete(cx, None);
1201 };
1202
1203 fetch_the_descendants_and_link_module_script(
1206 cx,
1207 &global_scope,
1208 module,
1209 fetch_client,
1210 destination,
1211 on_complete,
1212 );
1213 },
1214 );
1215}
1216
1217pub(crate) fn fetch_an_external_module_script(
1219 cx: &mut JSContext,
1220 url: UrlWithBlobClaim,
1221 global: &GlobalScope,
1222 options: ScriptFetchOptions,
1223 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1224) {
1225 let referrer = global.get_referrer();
1226 let fetch_client = global.request_client(Some(cx.no_gc()));
1227 let global_scope = DomRoot::from_ref(global);
1228
1229 fetch_a_single_module_script(
1232 cx,
1233 url,
1234 fetch_client.clone(),
1235 global,
1236 Destination::Script,
1237 options,
1238 referrer,
1239 None,
1240 true,
1241 Some(IntroductionType::SRC_SCRIPT),
1242 move |cx, module_tree| {
1243 let Some(module) = module_tree else {
1244 return on_complete(cx, None);
1246 };
1247
1248 fetch_the_descendants_and_link_module_script(
1250 cx,
1251 &global_scope,
1252 module,
1253 fetch_client,
1254 Destination::Script,
1255 on_complete,
1256 );
1257 },
1258 );
1259}
1260
1261pub(crate) fn fetch_a_modulepreload_module(
1263 cx: &mut JSContext,
1264 url: UrlWithBlobClaim,
1265 destination: Destination,
1266 global: &GlobalScope,
1267 options: ScriptFetchOptions,
1268 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1269) {
1270 let referrer = global.get_referrer();
1271 let fetch_client = global.request_client(Some(cx.no_gc()));
1272 let global_scope = DomRoot::from_ref(global);
1273
1274 let module_type = if let Destination::Json = destination {
1277 Some(ModuleType::JSON)
1278 } else {
1279 None
1280 };
1281
1282 fetch_a_single_module_script(
1285 cx,
1286 url,
1287 fetch_client.clone(),
1288 global,
1289 destination,
1290 options,
1291 referrer,
1292 module_type,
1293 true,
1294 Some(IntroductionType::SRC_SCRIPT),
1295 move |cx, result| {
1296 on_complete(cx, result.is_none());
1298
1299 assert!(global_scope.is::<Window>());
1301
1302 if pref!(dom_allow_preloading_module_descendants) &&
1305 let Some(module) = result
1306 {
1307 fetch_the_descendants_and_link_module_script(
1308 cx,
1309 &global_scope,
1310 module,
1311 fetch_client,
1312 destination,
1313 |_, _| {},
1314 );
1315 }
1316 },
1317 );
1318}
1319
1320#[expect(clippy::too_many_arguments)]
1321pub(crate) fn fetch_inline_module_script(
1323 cx: &mut JSContext,
1324 global: &GlobalScope,
1325 module_script_text: Cow<'_, str>,
1326 url: ServoUrl,
1327 options: ScriptFetchOptions,
1328 line_number: u32,
1329 introduction_type: Option<&'static CStr>,
1330 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1331) {
1332 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1334 cx,
1335 module_script_text,
1336 global,
1337 &url,
1338 options,
1339 false,
1340 line_number,
1341 introduction_type,
1342 ));
1343 let fetch_client = global.request_client(Some(cx.no_gc()));
1344
1345 fetch_the_descendants_and_link_module_script(
1347 cx,
1348 global,
1349 module_tree,
1350 fetch_client,
1351 Destination::Script,
1352 on_complete,
1353 );
1354}
1355
1356#[expect(unsafe_code)]
1357fn fetch_the_descendants_and_link_module_script(
1359 cx: &mut JSContext,
1360 global: &GlobalScope,
1361 module_script: Rc<ModuleTree>,
1362 fetch_client: RequestClient,
1363 destination: Destination,
1364 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1365) {
1366 if module_script.get_record().is_none() {
1369 let parse_error = module_script.get_parse_error().cloned();
1370
1371 module_script.set_rethrow_error(parse_error.unwrap());
1373
1374 on_complete(cx, Some(module_script));
1376
1377 return;
1379 }
1380
1381 let state = Rc::new(LoadState {
1384 error_to_rethrow: RefCell::new(None),
1385 destination,
1386 fetch_client,
1387 });
1388
1389 let mut realm = enter_auto_realm(cx, global);
1392 let cx = &mut realm.current_realm();
1393
1394 let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
1396
1397 let global_scope = DomRoot::from_ref(global);
1398 let fulfilled_module = module_script.clone();
1399 let fulfilled_on_complete = on_complete.clone();
1400
1401 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1403 task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
1404 let mut realm = AutoRealm::new(
1405 cx,
1406 NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
1407 );
1408 let cx = &mut *realm;
1409
1410 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1411
1412 let link = unsafe { ModuleLink(cx, handle) };
1414
1415 if !link {
1417 let exception = RethrowError::from_pending_exception(cx);
1418 fulfilled_module.set_rethrow_error(exception);
1419 }
1420
1421 fulfilled_on_complete(cx, Some(fulfilled_module));
1423 }),
1424 ));
1425
1426 let loading_promise_rejection =
1428 ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
1429 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1432 module_script.set_rethrow_error(error.clone());
1433 on_complete(cx, Some(module_script));
1434 } else {
1435 on_complete(cx, None);
1437 }
1438 })));
1439
1440 let handler = PromiseNativeHandler::new(
1441 cx,
1442 global,
1443 Some(loading_promise_fulfillment),
1444 Some(loading_promise_rejection),
1445 );
1446
1447 run_a_callback::<DomTypeHolder, _>(global, || {
1448 loading_promise.append_native_handler(cx, &handler);
1449 });
1450}
1451
1452#[expect(clippy::too_many_arguments)]
1454pub(crate) fn fetch_a_single_module_script(
1455 cx: &mut JSContext,
1456 url: UrlWithBlobClaim,
1457 fetch_client: RequestClient,
1458 global: &GlobalScope,
1459 destination: Destination,
1460 options: ScriptFetchOptions,
1461 referrer: Referrer,
1462 module_type: Option<ModuleType>,
1463 is_top_level: bool,
1464 introduction_type: Option<&'static CStr>,
1465 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1466) {
1467 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1471
1472 let module_request = (url.url(), module_type);
1477
1478 let module_map = global.module_map();
1480 let mut module_map_borrow = module_map.safe_borrow_mut(cx);
1481
1482 let entry = module_map_borrow.entry(module_request.clone());
1483
1484 match entry {
1485 Entry::Occupied(mut entry) => match entry.get_mut() {
1486 ModuleStatus::Loaded(module_tree) => {
1489 let module = module_tree.clone();
1490 drop(module_map_borrow);
1491 return on_complete(cx, Some(module));
1492 },
1493 ModuleStatus::Fetching(callbacks) => return callbacks.push(Box::new(on_complete)),
1496 },
1497 Entry::Vacant(entry) => {
1499 entry.insert(ModuleStatus::Fetching(vec![Box::new(on_complete)]));
1500 },
1501 }
1502
1503 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1505 .then(|| fetch_client.policy_container.clone());
1506
1507 let mode = match destination {
1512 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1513 _ => RequestMode::CorsMode,
1514 };
1515
1516 let destination = match module_type {
1519 ModuleType::JSON => Destination::Json,
1520 ModuleType::JavaScript | ModuleType::Unknown => destination,
1521 };
1522
1523 let request = RequestBuilder::new(global.webview_id(), url, referrer)
1527 .destination(destination)
1528 .parser_metadata(options.parser_metadata)
1529 .integrity_metadata(options.integrity_metadata.clone())
1530 .credentials_mode(options.credentials_mode)
1531 .referrer_policy(options.referrer_policy)
1532 .mode(mode)
1533 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1534 .client(fetch_client)
1535 .pipeline_id(Some(global.pipeline_id()));
1536
1537 let context = ModuleContext {
1538 owner: Trusted::new(global),
1539 data: vec![],
1540 metadata: None,
1541 module_request,
1542 options,
1543 status: Ok(()),
1544 introduction_type,
1545 policy_container,
1546 };
1547
1548 let task_source = global.task_manager().networking_task_source().to_sendable();
1549 global.fetch(request, context, task_source);
1550}
1551
1552#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1554pub(crate) struct ResolvedModule {
1555 pub(crate) base_url: String,
1557 pub(crate) specifier: String,
1559 #[no_trace]
1561 pub(crate) specifier_url: Option<ServoUrl>,
1562}
1563
1564impl ResolvedModule {
1565 pub(crate) fn new(
1566 base_url: String,
1567 specifier: String,
1568 specifier_url: Option<ServoUrl>,
1569 ) -> Self {
1570 Self {
1571 base_url,
1572 specifier,
1573 specifier_url,
1574 }
1575 }
1576}
1577
1578fn resolve_imports_match(
1583 normalized_specifier: &str,
1584 as_url: Option<&ServoUrl>,
1585 specifier_map: &ModuleSpecifierMap,
1586) -> Fallible<Option<ServoUrl>> {
1587 for (specifier_key, resolution_result) in specifier_map {
1589 if specifier_key == normalized_specifier {
1591 if let Some(resolution_result) = resolution_result {
1592 return Ok(Some(resolution_result.clone()));
1596 } else {
1597 return Err(Error::Type(
1599 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
1600 ));
1601 }
1602 }
1603
1604 if specifier_key.ends_with('\u{002f}') &&
1609 normalized_specifier.starts_with(specifier_key) &&
1610 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
1611 {
1612 let Some(resolution_result) = resolution_result else {
1615 return Err(Error::Type(
1616 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
1617 ));
1618 };
1619
1620 let after_prefix = normalized_specifier
1622 .strip_prefix(specifier_key)
1623 .expect("specifier_key should be the prefix of normalized_specifier");
1624
1625 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
1627
1628 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
1630
1631 let Ok(url) = url else {
1634 return Err(Error::Type(
1635 c"Resolution of normalizedSpecifier was blocked since
1636 the afterPrefix portion could not be URL-parsed relative to
1637 the resolutionResult mapped to by the specifierKey prefix."
1638 .to_owned(),
1639 ));
1640 };
1641
1642 if !url.as_str().starts_with(resolution_result.as_str()) {
1645 return Err(Error::Type(
1646 c"Resolution of normalizedSpecifier was blocked due to
1647 it backtracking above its prefix specifierKey."
1648 .to_owned(),
1649 ));
1650 }
1651
1652 return Ok(Some(url));
1654 }
1655 }
1656
1657 Ok(None)
1659}