1use std::borrow::Cow;
9use std::collections::hash_map::Entry;
10use std::ffi::CStr;
11use std::fmt::Debug;
12use std::ptr::NonNull;
13use std::rc::Rc;
14use std::sync::Arc;
15
16use bytes::{Bytes, BytesMut};
17use encoding_rs::UTF_8;
18use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
19use hyper_serde::Serde;
20use js::context::JSContext;
21use js::conversions::{ToJSValConvertible, jsstr_to_string};
22use js::gc::{HandleObject, MutableHandleValue};
23use js::jsapi::{
24 CallArgs, ColumnNumberOneOrigin, GetFunctionNativeReserved, GetModuleLoadHook,
25 Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
26 JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, JSScript, ModuleType,
27 SetFunctionNativeReserved, SetModuleLoadHook, SetModuleMetadataHook, SetModulePrivate,
28 SetScriptPrivateReferenceHooks, Value,
29};
30use js::jsval::{JSVal, ObjectValue, PrivateValue, UndefinedValue};
31use js::realm::CurrentRealm;
32use js::rust::wrappers2::{
33 CompileJsonModule1, CompileModule1, CreateDefaultExportSyntheticModule,
34 DefineFunctionWithReserved, GetModuleRequestSpecifier, JS_ClearPendingException,
35 JS_DefineProperty4, JS_GetModulePrivate, JS_GetPendingException, JS_NewStringCopyN,
36};
37use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
38use mime::Mime;
39use net_traits::blob_url_store::UrlWithBlobClaim;
40use net_traits::mime_classifier::MimeClassifier;
41use net_traits::policy_container::PolicyContainer;
42use net_traits::request::{
43 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestClient,
44 RequestId, RequestMode,
45};
46use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
47use script_bindings::cell::DomRefCell;
48use script_bindings::reflector::DomObject;
49use servo_config::pref;
50use servo_url::ServoUrl;
51
52use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
53 CSSStyleSheetInit, CSSStyleSheetMethods,
54};
55use crate::dom::bindings::codegen::UnionTypes::MediaListOrString;
56use crate::dom::bindings::error::{ErrorToJsval, throw_dom_exception};
57use crate::dom::bindings::inheritance::Castable;
58use crate::dom::bindings::root::DomRoot;
59use crate::dom::bindings::str::{DOMString, USVString};
60use crate::dom::bindings::trace::RootedTraceableBox;
61use crate::dom::csp::{GlobalCspReporting, Violation};
62use crate::dom::css::cssstylesheet::CSSStyleSheet;
63use crate::dom::globalscope::GlobalScope;
64use crate::dom::html::htmlscriptelement::substitute_with_local_script;
65use crate::dom::performance::performanceresourcetiming::InitiatorType;
66use crate::dom::promisenativehandler::Callback;
67use crate::dom::types::{
68 DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope, WorkletGlobalScope,
69};
70use crate::dom::window::Window;
71use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
72use crate::realms::enter_auto_realm;
73use crate::runtime::script_runtime::IntroductionType;
74use crate::scripting::fetching_options::{BaseScript, ScriptFetchOptions};
75use crate::scripting::import_map::{ImportMap, ReferringScript};
76use crate::scripting::module_loading::{
77 LoadState, host_load_imported_module, load_requested_modules,
78};
79use crate::scripting::script_execution::{ScriptOptions, fill_compile_options};
80use crate::tasks::task::NonSendTaskBox;
81use crate::unminify::{ScriptSource, unminify_js};
82
83#[derive(JSTraceable)]
84pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
85
86impl ModuleObject {
87 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
88 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
89 }
90
91 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
92 self.0.handle()
93 }
94}
95
96#[derive(JSTraceable)]
97pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
98
99impl RethrowError {
100 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
101 Self(RootedTraceableBox::from_box(val))
102 }
103
104 #[expect(unsafe_code)]
105 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
106 rooted!(&in(cx) let mut exception = UndefinedValue());
107 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
108 unsafe { JS_ClearPendingException(cx) };
109
110 Self::new(Heap::boxed(exception.get()))
111 }
112
113 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
114 self.0.handle()
115 }
116}
117
118impl Debug for RethrowError {
119 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
120 "RethrowError(...)".fmt(fmt)
121 }
122}
123
124impl Clone for RethrowError {
125 fn clone(&self) -> Self {
126 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
127 }
128}
129
130pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
131
132#[derive(JSTraceable)]
133pub(crate) enum ModuleStatus {
134 #[expect(clippy::type_complexity)]
135 Fetching(#[no_trace] Vec<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>),
136 Loaded(Rc<ModuleTree>),
137}
138
139#[derive(Default, JSTraceable, MallocSizeOf)]
140#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
141#[cfg_attr(crown, crown::unrooted_must_root_lint::allow_unrooted_in_rc)]
142pub(crate) struct ModuleTree {
143 #[ignore_malloc_size_of = "mozjs"]
144 record: DomRefCell<Option<Heap<*mut JSObject>>>,
145 #[ignore_malloc_size_of = "mozjs"]
146 parse_error: DomRefCell<Option<Heap<JSVal>>>,
147 #[ignore_malloc_size_of = "mozjs"]
148 rethrow_error: DomRefCell<Option<Heap<JSVal>>>,
149}
150
151impl ModuleTree {
152 fn set_record(&self, record: HandleObject) {
153 *self.record.borrow_mut() = Some(Heap::default());
154 self.record.borrow().as_ref().unwrap().set(record.get());
155 }
156
157 pub(crate) fn record(&self) -> Option<RootedTraceableBox<Heap<*mut JSObject>>> {
158 (*self.record.borrow())
159 .as_ref()
160 .map(|record| RootedTraceableBox::from_box(Heap::boxed(record.get())))
161 }
162
163 fn set_parse_error(&self, error: HandleValue) {
164 *self.parse_error.borrow_mut() = Some(Heap::default());
165 self.parse_error.borrow().as_ref().unwrap().set(error.get());
166 }
167
168 pub(crate) fn parse_error(&self) -> Option<RethrowError> {
169 (*self.parse_error.borrow())
170 .as_ref()
171 .map(|error| RethrowError::new(Heap::boxed(error.get())))
172 }
173
174 pub(crate) fn set_rethrow_error(&self, error: HandleValue) {
175 *self.rethrow_error.borrow_mut() = Some(Heap::default());
176 self.rethrow_error
177 .borrow()
178 .as_ref()
179 .unwrap()
180 .set(error.get());
181 }
182
183 pub(crate) fn rethrow_error(&self) -> Option<RethrowError> {
184 (*self.rethrow_error.borrow())
185 .as_ref()
186 .map(|error| RethrowError::new(Heap::boxed(error.get())))
187 }
188
189 pub(crate) fn has_rethrow_error(&self) -> bool {
190 self.rethrow_error.borrow().is_some()
191 }
192
193 #[expect(unsafe_code)]
194 #[expect(clippy::too_many_arguments)]
195 fn create_a_javascript_module_script(
197 cx: &mut CurrentRealm,
198 source: Cow<'_, str>,
199 global: &GlobalScope,
200 url: &ServoUrl,
201 options: ScriptFetchOptions,
202 external: bool,
203 line_number: u32,
204 introduction_type: Option<&'static CStr>,
205 ) -> Rc<ModuleTree> {
206 let module = Rc::new(ModuleTree::default());
209
210 let compile_options = fill_compile_options(
211 cx,
212 url.as_str(),
213 ScriptOptions::empty(),
214 introduction_type,
215 line_number,
216 );
217
218 let mut module_source = ScriptSource {
219 source,
220 external,
221 url,
222 };
223 if let Some(unminified_js_dir) = global.unminified_js_dir() {
224 unminify_js(&mut module_source, unminified_js_dir);
225 }
226 let mut source = transform_str_to_source_text(&module_source.source);
227
228 unsafe {
229 rooted!(&in(cx) let module_script = CompileModule1(cx, compile_options.ptr, &mut source));
231
232 if module_script.is_null() {
234 warn!("fail to compile module script of {}", url);
235
236 let exception = RethrowError::from_pending_exception(cx);
238 module.set_parse_error(exception.handle());
239
240 return module;
242 }
243
244 let module_script_data = Rc::new(BaseScript::new(url.clone(), options));
250
251 SetModulePrivate(
252 module_script.get(),
253 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
254 );
255
256 module.set_record(module_script.handle());
258 }
259
260 module
262 }
263
264 #[expect(unsafe_code)]
265 fn create_a_json_module_script(
267 cx: &mut CurrentRealm,
268 source: &str,
269 url: &ServoUrl,
270 introduction_type: Option<&'static CStr>,
271 ) -> Rc<ModuleTree> {
272 let module = Rc::new(ModuleTree::default());
275
276 let compile_options = fill_compile_options(
281 cx,
282 url.as_str(),
283 ScriptOptions::empty(),
284 introduction_type,
285 1, );
287
288 let mut source = transform_str_to_source_text(source);
289
290 rooted!(&in(cx) let module_script = unsafe { CompileJsonModule1(cx, compile_options.ptr, &mut source) });
292
293 if module_script.is_null() {
295 warn!("fail to compile module script of {}", url);
296
297 let exception = RethrowError::from_pending_exception(cx);
298 module.set_parse_error(exception.handle());
299 return module;
300 }
301
302 module.set_record(module_script.handle());
304
305 module
307 }
308
309 #[expect(unsafe_code)]
310 fn create_a_css_module_script(
312 cx: &mut CurrentRealm,
313 source: &str,
314 global: &GlobalScope,
315 url: ServoUrl,
316 ) -> Rc<ModuleTree> {
317 let script = Rc::new(ModuleTree::default());
320
321 let dictionary = CSSStyleSheetInit {
328 baseURL: Some(url.into_string().into()),
329 disabled: false,
330 media: MediaListOrString::String(DOMString::new()),
331 };
332 let sheet = CSSStyleSheet::Constructor(cx, global.as_window(), None, &dictionary);
333
334 if let Err(error) = sheet.ReplaceSync(cx, USVString::from(source.to_owned())) {
337 rooted!(&in(cx) let mut exception = UndefinedValue());
340 error.to_jsval(cx, global, exception.handle_mut());
341
342 script.set_parse_error(exception.handle());
343 return script;
344 }
345
346 rooted!(&in(cx) let sheet = ObjectValue(sheet.reflector().get_jsobject().get()));
348 rooted!(&in(cx) let module_script = unsafe { CreateDefaultExportSyntheticModule(cx, sheet.handle()) });
349 script.set_record(module_script.handle());
350
351 script
353 }
354
355 #[expect(unsafe_code)]
356 fn create_a_text_module_script(cx: &mut CurrentRealm, source: &str) -> Rc<ModuleTree> {
358 let script = Rc::new(ModuleTree::default());
361
362 rooted!(&in(cx) let mut text = UndefinedValue());
368 source.to_jsval(cx, text.handle_mut());
369 rooted!(&in(cx) let result = unsafe { CreateDefaultExportSyntheticModule(cx, text.handle()) });
370
371 script.set_record(result.handle());
373
374 script
376 }
377}
378
379#[derive(JSTraceable, MallocSizeOf)]
380pub(crate) struct ModuleHandler {
381 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
382 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
383}
384
385impl ModuleHandler {
386 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
387 Box::new(Self {
388 task: DomRefCell::new(Some(task)),
389 })
390 }
391}
392
393impl Callback for ModuleHandler {
394 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
395 let task = self.task.borrow_mut().take().unwrap();
396 task.run_box(cx);
397 }
398}
399
400struct ModuleContext {
402 data: BytesMut,
404 metadata: Option<Metadata>,
406 module_request: ModuleRequest,
408 options: ScriptFetchOptions,
410 status: Result<(), NetworkError>,
412 introduction_type: Option<&'static CStr>,
414 policy_container: Option<Arc<PolicyContainer>>,
416}
417
418impl FetchResponseListener for ModuleContext {
419 fn process_request_body(&mut self, _: &GlobalScope, _: RequestId) {}
421
422 fn process_response(
423 &mut self,
424 _: &mut JSContext,
425 _: &GlobalScope,
426 _: RequestId,
427 metadata: Result<FetchMetadata, NetworkError>,
428 ) {
429 self.metadata = metadata.ok().map(Into::into);
430
431 self.status = network_listener::check_response_status(self.metadata.as_ref());
432 }
433
434 fn process_response_chunk(
435 &mut self,
436 _: &mut JSContext,
437 _: &GlobalScope,
438 _: RequestId,
439 chunk: Bytes,
440 ) {
441 if self.status.is_ok() {
442 self.data.extend_from_slice(&chunk);
443 }
444 }
445
446 fn process_response_eof(
449 mut self,
450 cx: &mut JSContext,
451 global: &GlobalScope,
452 _: RequestId,
453 response: Result<(), NetworkError>,
454 timing: ResourceFetchTiming,
455 ) {
456 let (_url, module_type) = &self.module_request;
457
458 if !global.is::<WorkletGlobalScope>() {
459 network_listener::submit_timing(cx, global, &self, &response, &timing);
460 }
461
462 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
465 error!("Fetching module script failed {:?}", error);
466 let Some(ModuleStatus::Fetching(callbacks)) = global.with_module_map(|module_map| {
469 module_map.safe_borrow_mut(cx).remove(&self.module_request)
470 }) else {
471 return error!("Processing response for a non pending module request");
472 };
473
474 for callback in callbacks {
476 (callback)(cx, None);
477 }
478
479 return;
481 }
482
483 let metadata = self.metadata.take().unwrap();
484
485 if let Some(policy_container) = self.policy_container {
488 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
489 "We only need a policy container when initializing a worker's globalscope.",
490 );
491 workerscope.process_response_for_workerscope(&metadata, &policy_container);
492 }
493
494 let final_url = metadata.final_url;
495
496 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
498
499 let mut module_script = None;
501
502 let referrer_policy = metadata
504 .headers
505 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
506 .into();
507
508 if referrer_policy != ReferrerPolicy::EmptyString {
510 self.options.referrer_policy = referrer_policy;
511 }
512
513 let mut realm = enter_auto_realm(cx, global);
514 let cx = &mut realm.current_realm();
515
516 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
521
522 match (module_type, mime_type) {
523 (ModuleType::Text, _) => {
526 module_script = Some(ModuleTree::create_a_text_module_script(cx, &source_text));
527 },
528 (ModuleType::JavaScript, Some(mime)) if MimeClassifier::is_javascript(&mime) => {
532 if let Some(window) = global.downcast::<Window>() &&
533 let Some(script_souce) = window.local_script_source()
534 {
535 substitute_with_local_script(script_souce, &mut source_text, &final_url);
536 }
537
538 module_script = Some(ModuleTree::create_a_javascript_module_script(
539 cx,
540 source_text,
541 global,
542 &final_url,
543 self.options,
544 true,
545 1,
546 self.introduction_type,
547 ));
548 },
549 (ModuleType::CSS, Some(mime)) if MimeClassifier::is_css(&mime) => {
553 module_script = Some(ModuleTree::create_a_css_module_script(
554 cx,
555 &source_text,
556 global,
557 final_url.clone(),
558 ));
559 },
560 (ModuleType::JSON, Some(mime)) if MimeClassifier::is_json(&mime) => {
563 module_script = Some(ModuleTree::create_a_json_module_script(
564 cx,
565 &source_text,
566 &final_url,
567 self.introduction_type,
568 ));
569 },
570 _ => {},
571 }
572
573 let callbacks = global.with_module_map(|module_map| {
574 match module_map
575 .safe_borrow_mut(cx)
576 .entry(self.module_request.clone())
577 {
578 Entry::Occupied(mut entry) => {
579 let old_value = match module_script.as_ref() {
582 None => entry.remove(),
583 Some(module_script) => {
584 entry.insert(ModuleStatus::Loaded(module_script.clone()))
585 },
586 };
587
588 match old_value {
589 ModuleStatus::Loaded(_) => None,
590 ModuleStatus::Fetching(callbacks) => Some(callbacks),
591 }
592 },
593 Entry::Vacant(_) => None,
594 }
595 });
596
597 if let Some(callbacks) = callbacks {
598 for callback in callbacks {
600 (callback)(cx, module_script.clone());
601 }
602 } else {
603 error!("Processing response for a non pending module request");
604 }
605 }
606
607 fn process_csp_violations(
608 &mut self,
609 cx: &mut JSContext,
610 global: &GlobalScope,
611 _request_id: RequestId,
612 violations: Vec<Violation>,
613 ) {
614 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
615 scope.report_csp_violations(violations);
616 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
617 scope.report_csp_violations(violations);
618 } else {
619 global.report_csp_violations(cx, violations, None, None);
620 }
621 }
622
623 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
624 self.data.reserve(size.saturating_sub(self.data.len()));
625 }
626}
627
628impl ResourceTimingListener for ModuleContext {
629 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
630 let initiator_type = InitiatorType::LocalName("module".into());
631 let (url, _) = &self.module_request;
632 (initiator_type, url.clone())
633 }
634}
635
636#[expect(unsafe_code)]
637#[expect(non_snake_case)]
638pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
641 unsafe {
642 if GetModuleLoadHook(rt).is_some() {
643 return;
644 }
645
646 SetModuleLoadHook(rt, Some(HostLoadImportedModule));
647 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
648 SetScriptPrivateReferenceHooks(
649 rt,
650 Some(host_add_ref_top_level_script),
651 Some(host_release_top_level_script),
652 );
653 }
654}
655
656#[expect(unsafe_code)]
657unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
658 let val = unsafe { Rc::from_raw((*value).to_private() as *const BaseScript) };
659 std::mem::forget(val.clone());
660 std::mem::forget(val);
661}
662
663#[expect(unsafe_code)]
664unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
665 let _val = unsafe { Rc::from_raw((*value).to_private() as *const BaseScript) };
666}
667
668#[expect(unsafe_code)]
669pub(crate) unsafe fn module_script_from_reference_private<'a>(
670 reference_private: Handle<'a, JSVal>,
671) -> Option<&'a BaseScript> {
672 if reference_private.get().is_undefined() {
673 return None;
674 }
675 unsafe { (reference_private.get().to_private() as *const BaseScript).as_ref() }
676}
677
678#[expect(unsafe_code)]
679#[expect(non_snake_case)]
680unsafe extern "C" fn HostLoadImportedModule(
683 cx: *mut RawJSContext,
684 referrer: RawHandle<*mut JSScript>,
685 module_request: RawHandle<*mut JSObject>,
686 host_defined: RawHandleValue,
687 payload: RawHandleValue,
688 _line_number: u32,
689 _column_number: ColumnNumberOneOrigin,
690) -> bool {
691 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
693 let mut realm = CurrentRealm::assert(&mut cx);
694 let cx = &mut realm;
695
696 let referrer = unsafe { Handle::from_raw(referrer) };
697 let module_request = unsafe { Handle::from_raw(module_request) };
698 let host_defined = unsafe { Handle::from_raw(host_defined) };
699 let payload = unsafe { Handle::from_raw(payload) };
700
701 let jsstr = unsafe { GetModuleRequestSpecifier(cx, module_request) };
702 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
703
704 host_load_imported_module(
705 cx,
706 referrer,
707 module_request,
708 specifier,
709 host_defined,
710 payload,
711 );
712 true
713}
714
715const MODULE_RECORD_SLOT: usize = 0;
717
718#[expect(unsafe_code)]
719#[expect(non_snake_case)]
720unsafe extern "C" fn HostPopulateImportMeta(
723 cx: *mut RawJSContext,
724 module_record: RawHandle<*mut JSObject>,
725 meta_object: RawHandle<*mut JSObject>,
726) -> bool {
727 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
729 let mut realm = CurrentRealm::assert(&mut cx);
730 let global_scope = GlobalScope::from_current_realm(&mut realm);
731
732 rooted!(&in(cx) let mut module_private: JSVal);
734 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
735 let base_url = match unsafe { module_script_from_reference_private(module_private.handle()) } {
736 Some(module_data) => module_data.base_url.clone(),
737 None => global_scope.api_base_url(),
738 };
739
740 unsafe {
741 let url_string = JS_NewStringCopyN(
742 &mut cx,
743 base_url.as_str().as_ptr() as *const _,
744 base_url.as_str().len(),
745 );
746 rooted!(&in(cx) let url_string = url_string);
747
748 if !JS_DefineProperty4(
750 &mut cx,
751 Handle::from_raw(meta_object),
752 c"url".as_ptr(),
753 url_string.handle(),
754 JSPROP_ENUMERATE.into(),
755 ) {
756 return false;
757 }
758
759 let resolve_function = DefineFunctionWithReserved(
761 &mut cx,
762 meta_object.get(),
763 c"resolve".as_ptr(),
764 Some(import_meta_resolve),
765 1,
766 JSPROP_ENUMERATE.into(),
767 );
768
769 if resolve_function.is_null() {
770 return false;
771 }
772
773 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
774 assert!(!obj.is_null());
775 SetFunctionNativeReserved(
776 obj.get(),
777 MODULE_RECORD_SLOT,
778 &ObjectValue(module_record.get()),
779 );
780 }
781
782 true
783}
784
785#[expect(unsafe_code)]
786unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
787 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
789 let mut realm = CurrentRealm::assert(&mut cx);
790 let global_scope = GlobalScope::from_current_realm(&mut realm);
791
792 let cx = &mut realm;
793
794 let args = unsafe { CallArgs::from_vp(vp, argc) };
795
796 rooted!(&in(cx) let module_value = unsafe { *GetFunctionNativeReserved(args.callee(), MODULE_RECORD_SLOT) });
797 assert!(!module_value.is_undefined());
798 rooted!(&in(cx) let module_record = module_value.to_object());
799 rooted!(&in(cx) let mut module_private: JSVal);
800 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
801 assert!(!module_private.is_undefined());
802 let module_data = unsafe { module_script_from_reference_private(module_private.handle()) };
803
804 let specifier = unsafe {
808 let value = HandleValue::from_raw(args.get(0));
809
810 match NonNull::new(ToString(cx, value)) {
811 Some(jsstr) => jsstr_to_string(cx, jsstr),
812 None => return false,
813 }
814 };
815
816 let referring_script = module_data.map(|script| ReferringScript {
818 script,
819 settings_object: unsafe { GlobalScope::from_object(module_record.get()) },
820 });
821 let url =
822 ImportMap::resolve_module_specifier(&global_scope, referring_script.as_ref(), specifier);
823
824 match url {
825 Ok(url) => {
826 url.as_str()
828 .to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
829 true
830 },
831 Err(error) => {
832 throw_dom_exception(cx, &global_scope, error);
833 false
834 },
835 }
836}
837
838#[expect(clippy::too_many_arguments)]
839pub(crate) fn fetch_a_module_script_graph(
842 cx: &mut JSContext,
843 global: &GlobalScope,
844 url: UrlWithBlobClaim,
845 fetch_client: RequestClient,
846 destination: Destination,
847 referrer: Referrer,
848 credentials_mode: CredentialsMode,
849 introduction_type: Option<&'static CStr>,
850 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
851) {
852 let global_scope = DomRoot::from_ref(global);
853
854 let options = ScriptFetchOptions {
859 integrity_metadata: String::new(),
860 credentials_mode,
861 cryptographic_nonce: String::new(),
862 parser_metadata: ParserMetadata::NotParserInserted,
863 referrer_policy: ReferrerPolicy::EmptyString,
864 render_blocking: false,
865 };
866
867 fetch_a_single_module_script(
870 cx,
871 url,
872 fetch_client.clone(),
873 global,
874 destination,
875 options,
876 referrer,
877 None,
878 true,
879 introduction_type,
880 move |cx, module_tree| {
881 let Some(module) = module_tree else {
882 return on_complete(cx, None);
884 };
885
886 fetch_the_descendants_and_link_module_script(
889 cx,
890 &global_scope,
891 module,
892 fetch_client,
893 destination,
894 on_complete,
895 );
896 },
897 );
898}
899
900pub(crate) fn fetch_an_external_module_script(
902 cx: &mut JSContext,
903 url: UrlWithBlobClaim,
904 global: &GlobalScope,
905 options: ScriptFetchOptions,
906 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
907) {
908 let referrer = global.get_referrer();
909 let fetch_client = global.request_client(Some(cx.no_gc()));
910 let global_scope = DomRoot::from_ref(global);
911
912 fetch_a_single_module_script(
915 cx,
916 url,
917 fetch_client.clone(),
918 global,
919 Destination::Script,
920 options,
921 referrer,
922 None,
923 true,
924 Some(IntroductionType::SRC_SCRIPT),
925 move |cx, module_tree| {
926 let Some(module) = module_tree else {
927 return on_complete(cx, None);
929 };
930
931 fetch_the_descendants_and_link_module_script(
933 cx,
934 &global_scope,
935 module,
936 fetch_client,
937 Destination::Script,
938 on_complete,
939 );
940 },
941 );
942}
943
944pub(crate) fn fetch_a_modulepreload_module(
946 cx: &mut JSContext,
947 url: UrlWithBlobClaim,
948 destination: Destination,
949 global: &GlobalScope,
950 options: ScriptFetchOptions,
951 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
952) {
953 let referrer = global.get_referrer();
954 let fetch_client = global.request_client(Some(cx.no_gc()));
955 let global_scope = DomRoot::from_ref(global);
956
957 let module_type = match destination {
960 Destination::Json => Some(ModuleType::JSON),
961 Destination::Style => Some(ModuleType::CSS),
962 Destination::Text => Some(ModuleType::Text),
963 _ => None,
964 };
965
966 fetch_a_single_module_script(
969 cx,
970 url,
971 fetch_client.clone(),
972 global,
973 destination,
974 options,
975 referrer,
976 module_type,
977 true,
978 Some(IntroductionType::SRC_SCRIPT),
979 move |cx, result| {
980 on_complete(cx, result.is_none());
982
983 assert!(global_scope.is::<Window>());
985
986 if pref!(dom_allow_preloading_module_descendants) &&
989 let Some(module) = result
990 {
991 fetch_the_descendants_and_link_module_script(
992 cx,
993 &global_scope,
994 module,
995 fetch_client,
996 destination,
997 |_, _| {},
998 );
999 }
1000 },
1001 );
1002}
1003
1004#[expect(clippy::too_many_arguments)]
1005pub(crate) fn fetch_inline_module_script(
1007 cx: &mut JSContext,
1008 global: &GlobalScope,
1009 module_script_text: Cow<'_, str>,
1010 url: ServoUrl,
1011 options: ScriptFetchOptions,
1012 line_number: u32,
1013 introduction_type: Option<&'static CStr>,
1014 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1015) -> RootedTraceableBox<Rc<ModuleTree>> {
1016 let mut realm = enter_auto_realm(cx, global);
1017 let cx = &mut realm.current_realm();
1018
1019 let script = RootedTraceableBox::new(ModuleTree::create_a_javascript_module_script(
1021 cx,
1022 module_script_text,
1023 global,
1024 &url,
1025 options,
1026 false,
1027 line_number,
1028 introduction_type,
1029 ));
1030 let fetch_client = global.request_client(Some(cx.no_gc()));
1031
1032 fetch_the_descendants_and_link_module_script(
1034 cx,
1035 global,
1036 script.clone(),
1037 fetch_client,
1038 Destination::Script,
1039 on_complete,
1040 );
1041
1042 script
1043}
1044
1045fn fetch_the_descendants_and_link_module_script(
1047 cx: &mut JSContext,
1048 global: &GlobalScope,
1049 module_script: Rc<ModuleTree>,
1050 fetch_client: RequestClient,
1051 destination: Destination,
1052 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1053) {
1054 let Some(record) = module_script.record() else {
1057 let parse_error = module_script.parse_error().unwrap();
1058
1059 module_script.set_rethrow_error(parse_error.handle());
1061
1062 on_complete(cx, Some(module_script));
1064
1065 return;
1067 };
1068
1069 let state = Box::new(LoadState {
1072 destination,
1073 fetch_client,
1074 module_script: DomRefCell::new(Some(module_script)),
1075 on_complete: DomRefCell::new(Some(Box::new(on_complete))),
1076 });
1077
1078 let mut realm = enter_auto_realm(cx, global);
1081 let cx = &mut realm.current_realm();
1082
1083 load_requested_modules(cx, record.handle(), state);
1085}
1086
1087#[expect(clippy::too_many_arguments)]
1089pub(crate) fn fetch_a_single_module_script(
1090 cx: &mut JSContext,
1091 url: UrlWithBlobClaim,
1092 fetch_client: RequestClient,
1093 global: &GlobalScope,
1094 destination: Destination,
1095 options: ScriptFetchOptions,
1096 referrer: Referrer,
1097 module_type: Option<ModuleType>,
1098 is_top_level: bool,
1099 introduction_type: Option<&'static CStr>,
1100 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1101) {
1102 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1106
1107 let module_request = (url.url(), module_type);
1112
1113 let occupied = global.with_module_map(|module_map| {
1115 let mut module_map_borrow = module_map.safe_borrow_mut(cx);
1116
1117 let entry = module_map_borrow.entry(module_request.clone());
1118
1119 match entry {
1120 Entry::Occupied(mut entry) => {
1121 match entry.get_mut() {
1122 ModuleStatus::Loaded(module_tree) => {
1125 let module = module_tree.clone();
1126 drop(module_map_borrow);
1127 on_complete(cx, Some(module));
1128 },
1129 ModuleStatus::Fetching(callbacks) => {
1132 callbacks.push(Box::new(on_complete));
1133 },
1134 }
1135 true
1136 },
1137 Entry::Vacant(entry) => {
1139 entry.insert(ModuleStatus::Fetching(vec![Box::new(on_complete)]));
1140 false
1141 },
1142 }
1143 });
1144
1145 if occupied {
1146 return;
1147 }
1148
1149 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1151 .then(|| fetch_client.policy_container.clone());
1152
1153 let mode = match destination {
1158 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1159 _ => RequestMode::CorsMode,
1160 };
1161
1162 let destination = match module_type {
1165 ModuleType::JSON => Destination::Json,
1166 ModuleType::CSS => Destination::Style,
1167 ModuleType::Text => Destination::Text,
1168 ModuleType::Bytes => unreachable!("Not in ESR153"),
1169 ModuleType::JavaScript | ModuleType::Unknown => destination,
1170 };
1171
1172 let request = RequestBuilder::new(global.webview_id(), url, referrer)
1176 .destination(destination)
1177 .parser_metadata(options.parser_metadata)
1178 .integrity_metadata(options.integrity_metadata.clone())
1179 .credentials_mode(options.credentials_mode)
1180 .referrer_policy(options.referrer_policy)
1181 .mode(mode)
1182 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1183 .client(fetch_client)
1184 .pipeline_id(Some(global.pipeline_id()));
1185
1186 let context = ModuleContext {
1187 data: BytesMut::new(),
1188 metadata: None,
1189 module_request,
1190 options,
1191 status: Ok(()),
1192 introduction_type,
1193 policy_container,
1194 };
1195
1196 let task_source = global.task_manager().networking_task_source().to_sendable();
1197 global.fetch(request, context, task_source);
1198}
1199
1200#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1202pub(crate) struct ResolvedModule {
1203 pub(crate) base_url: String,
1205 pub(crate) specifier: String,
1207 #[no_trace]
1209 pub(crate) specifier_url: Option<ServoUrl>,
1210}
1211
1212impl ResolvedModule {
1213 pub(crate) fn new(
1214 base_url: String,
1215 specifier: String,
1216 specifier_url: Option<ServoUrl>,
1217 ) -> Self {
1218 Self {
1219 base_url,
1220 specifier,
1221 specifier_url,
1222 }
1223 }
1224}