1use std::borrow::Cow;
9use std::cell::OnceCell;
10use std::collections::hash_map::Entry;
11use std::ffi::CStr;
12use std::fmt::Debug;
13use std::ptr::NonNull;
14use std::rc::Rc;
15use std::sync::Arc;
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, GetFunctionNativeReserved, GetModuleLoadHook,
26 Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
27 JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, JSScript, ModuleType,
28 SetFunctionNativeReserved, SetModuleLoadHook, SetModuleMetadataHook, SetModulePrivate,
29 SetScriptPrivateReferenceHooks, Value,
30};
31use js::jsval::{JSVal, ObjectValue, PrivateValue, UndefinedValue};
32use js::realm::CurrentRealm;
33use js::rust::wrappers2::{
34 CompileJsonModule1, CompileModule1, CreateDefaultExportSyntheticModule,
35 DefineFunctionWithReserved, GetModuleRequestSpecifier, JS_ClearPendingException,
36 JS_DefineProperty4, JS_GetModulePrivate, JS_GetPendingException, JS_NewStringCopyN,
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::reflector::DomObject;
51use script_bindings::trace::CustomTraceable;
52use servo_config::pref;
53use servo_url::ServoUrl;
54
55use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::{
56 CSSStyleSheetInit, CSSStyleSheetMethods,
57};
58use crate::dom::bindings::codegen::UnionTypes::MediaListOrString;
59use crate::dom::bindings::error::throw_dom_exception;
60use crate::dom::bindings::inheritance::Castable;
61use crate::dom::bindings::refcounted::Trusted;
62use crate::dom::bindings::root::DomRoot;
63use crate::dom::bindings::str::{DOMString, USVString};
64use crate::dom::bindings::trace::RootedTraceableBox;
65use crate::dom::csp::{GlobalCspReporting, Violation};
66use crate::dom::css::cssstylesheet::CSSStyleSheet;
67use crate::dom::globalscope::GlobalScope;
68use crate::dom::html::htmlscriptelement::substitute_with_local_script;
69use crate::dom::performance::performanceresourcetiming::InitiatorType;
70use crate::dom::promisenativehandler::Callback;
71use crate::dom::types::{
72 DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope, WorkletGlobalScope,
73};
74use crate::dom::window::Window;
75use crate::fetch::network_listener::{self, FetchResponseListener, ResourceTimingListener};
76use crate::realms::enter_auto_realm;
77use crate::runtime::script_runtime::IntroductionType;
78use crate::scripting::fetching_options::{BaseScript, ScriptFetchOptions};
79use crate::scripting::import_map::ImportMap;
80use crate::scripting::module_loading::{
81 LoadState, host_load_imported_module, load_requested_modules,
82};
83use crate::scripting::script_execution::{ScriptOptions, fill_compile_options};
84use crate::tasks::task::NonSendTaskBox;
85use crate::unminify::{ScriptSource, unminify_js};
86
87#[derive(JSTraceable)]
88pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
89
90impl ModuleObject {
91 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
92 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
93 }
94
95 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
96 self.0.handle()
97 }
98}
99
100#[derive(JSTraceable)]
101pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
102
103impl RethrowError {
104 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
105 Self(RootedTraceableBox::from_box(val))
106 }
107
108 #[expect(unsafe_code)]
109 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
110 rooted!(&in(cx) let mut exception = UndefinedValue());
111 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
112 unsafe { JS_ClearPendingException(cx) };
113
114 Self::new(Heap::boxed(exception.get()))
115 }
116
117 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
118 self.0.handle()
119 }
120}
121
122impl Debug for RethrowError {
123 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
124 "RethrowError(...)".fmt(fmt)
125 }
126}
127
128impl Clone for RethrowError {
129 fn clone(&self) -> Self {
130 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
131 }
132}
133
134pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
135
136#[derive(JSTraceable)]
137pub(crate) enum ModuleStatus {
138 #[expect(clippy::type_complexity)]
139 Fetching(#[no_trace] Vec<Box<dyn FnOnce(&mut JSContext, Option<Rc<ModuleTree>>)>>),
140 Loaded(Rc<ModuleTree>),
141}
142
143#[derive(Default, JSTraceable, MallocSizeOf)]
144pub(crate) struct ModuleTree {
145 #[ignore_malloc_size_of = "mozjs"]
146 record: OnceCell<ModuleObject>,
147 #[ignore_malloc_size_of = "mozjs"]
148 parse_error: OnceCell<RethrowError>,
149 #[ignore_malloc_size_of = "mozjs"]
150 rethrow_error: DomRefCell<Option<RethrowError>>,
151}
152
153impl ModuleTree {
154 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
155 self.record.get()
156 }
157
158 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
159 self.parse_error.get()
160 }
161
162 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
163 &self.rethrow_error
164 }
165
166 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
167 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
168 }
169}
170
171impl ModuleTree {
172 #[expect(unsafe_code)]
173 #[expect(clippy::too_many_arguments)]
174 fn create_a_javascript_module_script(
176 cx: &mut CurrentRealm,
177 source: Cow<'_, str>,
178 global: &GlobalScope,
179 url: &ServoUrl,
180 options: ScriptFetchOptions,
181 external: bool,
182 line_number: u32,
183 introduction_type: Option<&'static CStr>,
184 ) -> Self {
185 let owner = Trusted::new(global);
186
187 let module = ModuleTree::default();
190
191 let compile_options = fill_compile_options(
192 cx,
193 url.as_str(),
194 ScriptOptions::empty(),
195 introduction_type,
196 line_number,
197 );
198
199 let mut module_source = ScriptSource {
200 source,
201 external,
202 url,
203 };
204 if let Some(unminified_js_dir) = global.unminified_js_dir() {
205 unminify_js(&mut module_source, unminified_js_dir);
206 }
207 let mut source = transform_str_to_source_text(&module_source.source);
208
209 unsafe {
210 rooted!(&in(cx) let module_script = CompileModule1(cx, compile_options.ptr, &mut source));
212
213 if module_script.is_null() {
215 warn!("fail to compile module script of {}", url);
216
217 let _ = module
219 .parse_error
220 .set(RethrowError::from_pending_exception(cx));
221
222 return module;
224 }
225
226 let module_script_data = Rc::new(BaseScript::new(url.clone(), options, Some(owner)));
230
231 SetModulePrivate(
232 module_script.get(),
233 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
234 );
235
236 let _ = module.record.set(ModuleObject::new(module_script.handle()));
238 }
239
240 module
242 }
243
244 #[expect(unsafe_code)]
245 fn create_a_json_module_script(
247 cx: &mut CurrentRealm,
248 source: &str,
249 url: &ServoUrl,
250 introduction_type: Option<&'static CStr>,
251 ) -> Self {
252 let module = ModuleTree::default();
255
256 let compile_options = fill_compile_options(
261 cx,
262 url.as_str(),
263 ScriptOptions::empty(),
264 introduction_type,
265 1, );
267
268 let mut source = transform_str_to_source_text(source);
269
270 rooted!(&in(cx) let module_script = unsafe { CompileJsonModule1(cx, compile_options.ptr, &mut source) });
272
273 if module_script.is_null() {
275 warn!("fail to compile module script of {}", url);
276
277 let _ = module
278 .parse_error
279 .set(RethrowError::from_pending_exception(cx));
280 return module;
281 }
282
283 let _ = module.record.set(ModuleObject::new(module_script.handle()));
285
286 module
288 }
289
290 #[expect(unsafe_code)]
291 fn create_a_css_module_script(
293 cx: &mut CurrentRealm,
294 source: &str,
295 global: &GlobalScope,
296 url: ServoUrl,
297 ) -> Self {
298 let script = ModuleTree::default();
301
302 let dictionary = CSSStyleSheetInit {
309 baseURL: Some(url.into_string().into()),
310 disabled: false,
311 media: MediaListOrString::String(DOMString::new()),
312 };
313 let sheet = CSSStyleSheet::Constructor(cx, global.as_window(), None, &dictionary);
314
315 if let Err(error) = sheet.ReplaceSync(cx, USVString::from(source.to_owned())) {
318 throw_dom_exception(cx, global, error);
321
322 let _ = script
323 .parse_error
324 .set(RethrowError::from_pending_exception(cx));
325 return script;
326 }
327
328 rooted!(&in(cx) let sheet = ObjectValue(sheet.reflector().get_jsobject().get()));
330 rooted!(&in(cx) let module_script = unsafe { CreateDefaultExportSyntheticModule(cx, sheet.handle()) });
331 let _ = script.record.set(ModuleObject::new(module_script.handle()));
332
333 script
335 }
336
337 #[expect(unsafe_code)]
338 fn create_a_text_module_script(cx: &mut CurrentRealm, source: &str) -> Self {
340 let script = ModuleTree::default();
343
344 rooted!(&in(cx) let mut text = UndefinedValue());
350 source.to_jsval(cx, text.handle_mut());
351 rooted!(&in(cx) let result = unsafe { CreateDefaultExportSyntheticModule(cx, text.handle()) });
352
353 let _ = script.record.set(ModuleObject::new(result.handle()));
355
356 script
358 }
359}
360
361#[derive(JSTraceable, MallocSizeOf)]
362pub(crate) struct ModuleHandler {
363 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
364 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
365}
366
367impl ModuleHandler {
368 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
369 Box::new(Self {
370 task: DomRefCell::new(Some(task)),
371 })
372 }
373}
374
375impl Callback for ModuleHandler {
376 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
377 let task = self.task.borrow_mut().take().unwrap();
378 task.run_box(cx);
379 }
380}
381
382struct ModuleContext {
384 owner: Trusted<GlobalScope>,
386 data: BytesMut,
388 metadata: Option<Metadata>,
390 module_request: ModuleRequest,
392 options: ScriptFetchOptions,
394 status: Result<(), NetworkError>,
396 introduction_type: Option<&'static CStr>,
398 policy_container: Option<Arc<PolicyContainer>>,
400}
401
402impl FetchResponseListener for ModuleContext {
403 fn process_request_body(&mut self, _: RequestId) {}
405
406 fn process_response(
407 &mut self,
408 _: &mut JSContext,
409 _: RequestId,
410 metadata: Result<FetchMetadata, NetworkError>,
411 ) {
412 self.metadata = metadata.ok().map(Into::into);
413
414 let status = self
415 .metadata
416 .as_ref()
417 .map(|m| m.status.clone())
418 .unwrap_or_else(HttpStatus::new_error);
419
420 self.status = {
421 if status.is_error() {
422 Err(NetworkError::ResourceLoadError(
423 "No http status code received".to_owned(),
424 ))
425 } else if status.is_success() {
426 Ok(())
427 } else {
428 Err(NetworkError::ResourceLoadError(format!(
429 "HTTP error code {}",
430 status.code()
431 )))
432 }
433 };
434 }
435
436 fn process_response_chunk(&mut self, _: &mut JSContext, _: RequestId, chunk: Bytes) {
437 if self.status.is_ok() {
438 self.data.extend_from_slice(&chunk);
439 }
440 }
441
442 fn process_response_eof(
445 mut self,
446 cx: &mut JSContext,
447 _: RequestId,
448 response: Result<(), NetworkError>,
449 timing: ResourceFetchTiming,
450 ) {
451 let global = self.owner.root();
452 let (_url, module_type) = &self.module_request;
453
454 if !global.is::<WorkletGlobalScope>() {
455 network_listener::submit_timing(cx, &self, &response, &timing);
456 }
457
458 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
461 error!("Fetching module script failed {:?}", error);
462 let Some(ModuleStatus::Fetching(callbacks)) = global.with_module_map(|module_map| {
465 module_map.safe_borrow_mut(cx).remove(&self.module_request)
466 }) else {
467 return error!("Processing response for a non pending module request");
468 };
469
470 for callback in callbacks {
472 (callback)(cx, None);
473 }
474
475 return;
477 }
478
479 let metadata = self.metadata.take().unwrap();
480
481 if let Some(policy_container) = self.policy_container {
484 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
485 "We only need a policy container when initializing a worker's globalscope.",
486 );
487 workerscope.process_response_for_workerscope(&metadata, &policy_container);
488 }
489
490 let final_url = metadata.final_url;
491
492 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
494
495 let mut module_script = None;
497
498 let referrer_policy = metadata
500 .headers
501 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
502 .into();
503
504 if referrer_policy != ReferrerPolicy::EmptyString {
506 self.options.referrer_policy = referrer_policy;
507 }
508
509 let mut realm = enter_auto_realm(cx, &*global);
510 let cx = &mut realm.current_realm();
511
512 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
517
518 match (module_type, mime_type) {
519 (ModuleType::Text, _) => {
522 let module_tree =
523 Rc::new(ModuleTree::create_a_text_module_script(cx, &source_text));
524 module_script = Some(module_tree);
525 },
526 (ModuleType::JavaScript, Some(mime)) if MimeClassifier::is_javascript(&mime) => {
530 if let Some(window) = global.downcast::<Window>() &&
531 let Some(script_souce) = window.local_script_source()
532 {
533 substitute_with_local_script(script_souce, &mut source_text, &final_url);
534 }
535
536 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
537 cx,
538 source_text,
539 &global,
540 &final_url,
541 self.options,
542 true,
543 1,
544 self.introduction_type,
545 ));
546 module_script = Some(module_tree);
547 },
548 (ModuleType::CSS, Some(mime)) if MimeClassifier::is_css(&mime) => {
552 let module_tree = Rc::new(ModuleTree::create_a_css_module_script(
553 cx,
554 &source_text,
555 &global,
556 final_url.clone(),
557 ));
558 module_script = Some(module_tree);
559 },
560 (ModuleType::JSON, Some(mime)) if MimeClassifier::is_json(&mime) => {
563 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
564 cx,
565 &source_text,
566 &final_url,
567 self.introduction_type,
568 ));
569 module_script = Some(module_tree);
570 },
571 _ => {},
572 }
573
574 let callbacks = global.with_module_map(|module_map| {
575 match module_map
576 .safe_borrow_mut(cx)
577 .entry(self.module_request.clone())
578 {
579 Entry::Occupied(mut entry) => {
580 let old_value = match module_script.as_ref() {
583 None => entry.remove(),
584 Some(module_script) => {
585 entry.insert(ModuleStatus::Loaded(module_script.clone()))
586 },
587 };
588
589 match old_value {
590 ModuleStatus::Loaded(_) => None,
591 ModuleStatus::Fetching(callbacks) => Some(callbacks),
592 }
593 },
594 Entry::Vacant(_) => None,
595 }
596 });
597
598 if let Some(callbacks) = callbacks {
599 for callback in callbacks {
601 (callback)(cx, module_script.clone());
602 }
603 } else {
604 error!("Processing response for a non pending module request");
605 }
606 }
607
608 fn process_csp_violations(
609 &mut self,
610 cx: &mut JSContext,
611 _request_id: RequestId,
612 violations: Vec<Violation>,
613 ) {
614 let global = self.owner.root();
615 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
616 scope.report_csp_violations(violations);
617 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
618 scope.report_csp_violations(violations);
619 } else {
620 global.report_csp_violations(cx, violations, None, None);
621 }
622 }
623
624 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
625 self.data.reserve(size.saturating_sub(self.data.len()));
626 }
627}
628
629impl ResourceTimingListener for ModuleContext {
630 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
631 let initiator_type = InitiatorType::LocalName("module".to_string());
632 let (url, _) = &self.module_request;
633 (initiator_type, url.clone())
634 }
635
636 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
637 self.owner.root()
638 }
639}
640
641#[expect(unsafe_code)]
642#[expect(non_snake_case)]
643pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
646 unsafe {
647 if GetModuleLoadHook(rt).is_some() {
648 return;
649 }
650
651 SetModuleLoadHook(rt, Some(HostLoadImportedModule));
652 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
653 SetScriptPrivateReferenceHooks(
654 rt,
655 Some(host_add_ref_top_level_script),
656 Some(host_release_top_level_script),
657 );
658 }
659}
660
661#[expect(unsafe_code)]
662unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
663 let val = unsafe { Rc::from_raw((*value).to_private() as *const BaseScript) };
664 std::mem::forget(val.clone());
665 std::mem::forget(val);
666}
667
668#[expect(unsafe_code)]
669unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
670 let _val = unsafe { Rc::from_raw((*value).to_private() as *const BaseScript) };
671}
672
673#[expect(unsafe_code)]
674pub(crate) unsafe fn module_script_from_reference_private<'a>(
675 reference_private: Handle<'a, JSVal>,
676) -> Option<&'a BaseScript> {
677 if reference_private.get().is_undefined() {
678 return None;
679 }
680 unsafe { (reference_private.get().to_private() as *const BaseScript).as_ref() }
681}
682
683#[expect(unsafe_code)]
684#[expect(non_snake_case)]
685unsafe extern "C" fn HostLoadImportedModule(
688 cx: *mut RawJSContext,
689 referrer: RawHandle<*mut JSScript>,
690 module_request: RawHandle<*mut JSObject>,
691 host_defined: RawHandleValue,
692 payload: RawHandleValue,
693 _line_number: u32,
694 _column_number: ColumnNumberOneOrigin,
695) -> bool {
696 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
698 let mut realm = CurrentRealm::assert(&mut cx);
699 let cx = &mut realm;
700
701 let referrer = unsafe { Handle::from_raw(referrer) };
702 let module_request = unsafe { Handle::from_raw(module_request) };
703 let host_defined = unsafe { Handle::from_raw(host_defined) };
704 let payload = unsafe { Handle::from_raw(payload) };
705
706 let jsstr = unsafe { GetModuleRequestSpecifier(cx, module_request) };
707 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
708
709 host_load_imported_module(
710 cx,
711 referrer,
712 module_request,
713 specifier,
714 host_defined,
715 payload,
716 );
717 true
718}
719
720const MODULE_RECORD_SLOT: usize = 0;
722
723#[expect(unsafe_code)]
724#[expect(non_snake_case)]
725unsafe extern "C" fn HostPopulateImportMeta(
728 cx: *mut RawJSContext,
729 module_record: RawHandle<*mut JSObject>,
730 meta_object: RawHandle<*mut JSObject>,
731) -> bool {
732 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
734 let mut realm = CurrentRealm::assert(&mut cx);
735 let global_scope = GlobalScope::from_current_realm(&mut realm);
736
737 rooted!(&in(cx) let mut module_private: JSVal);
739 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
740 let base_url = match unsafe { module_script_from_reference_private(module_private.handle()) } {
741 Some(module_data) => module_data.base_url.clone(),
742 None => global_scope.api_base_url(),
743 };
744
745 unsafe {
746 let url_string = JS_NewStringCopyN(
747 &mut cx,
748 base_url.as_str().as_ptr() as *const _,
749 base_url.as_str().len(),
750 );
751 rooted!(&in(cx) let url_string = url_string);
752
753 if !JS_DefineProperty4(
755 &mut cx,
756 Handle::from_raw(meta_object),
757 c"url".as_ptr(),
758 url_string.handle(),
759 JSPROP_ENUMERATE.into(),
760 ) {
761 return false;
762 }
763
764 let resolve_function = DefineFunctionWithReserved(
766 &mut cx,
767 meta_object.get(),
768 c"resolve".as_ptr(),
769 Some(import_meta_resolve),
770 1,
771 JSPROP_ENUMERATE.into(),
772 );
773
774 if resolve_function.is_null() {
775 return false;
776 }
777
778 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
779 assert!(!obj.is_null());
780 SetFunctionNativeReserved(
781 obj.get(),
782 MODULE_RECORD_SLOT,
783 &ObjectValue(module_record.get()),
784 );
785 }
786
787 true
788}
789
790#[expect(unsafe_code)]
791unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
792 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
794 let mut realm = CurrentRealm::assert(&mut cx);
795 let global_scope = GlobalScope::from_current_realm(&mut realm);
796
797 let cx = &mut realm;
798
799 let args = unsafe { CallArgs::from_vp(vp, argc) };
800
801 rooted!(&in(cx) let module_value = unsafe { *GetFunctionNativeReserved(args.callee(), MODULE_RECORD_SLOT) });
802 assert!(!module_value.is_undefined());
803 rooted!(&in(cx) let module_record = module_value.to_object());
804 rooted!(&in(cx) let mut module_private: JSVal);
805 unsafe { JS_GetModulePrivate(module_record.get(), module_private.handle_mut()) };
806 assert!(!module_private.is_undefined());
807 let module_data = unsafe { module_script_from_reference_private(module_private.handle()) };
808
809 let specifier = unsafe {
813 let value = HandleValue::from_raw(args.get(0));
814
815 match NonNull::new(ToString(cx, value)) {
816 Some(jsstr) => jsstr_to_string(cx, jsstr),
817 None => return false,
818 }
819 };
820
821 let url = ImportMap::resolve_module_specifier(&global_scope, module_data, 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) {
1016 let mut realm = enter_auto_realm(cx, global);
1017 let cx = &mut realm.current_realm();
1018
1019 let module_tree = Rc::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 module_tree,
1037 fetch_client,
1038 Destination::Script,
1039 on_complete,
1040 );
1041}
1042
1043fn fetch_the_descendants_and_link_module_script(
1045 cx: &mut JSContext,
1046 global: &GlobalScope,
1047 module_script: Rc<ModuleTree>,
1048 fetch_client: RequestClient,
1049 destination: Destination,
1050 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1051) {
1052 let Some(record) = module_script.get_record() else {
1055 let parse_error = module_script.get_parse_error().cloned();
1056
1057 module_script.set_rethrow_error(parse_error.unwrap());
1059
1060 on_complete(cx, Some(module_script));
1062
1063 return;
1065 };
1066
1067 let state = Box::new(LoadState {
1070 destination,
1071 fetch_client,
1072 module_script: DomRefCell::new(Some(module_script.clone())),
1073 on_complete: DomRefCell::new(Some(Box::new(on_complete))),
1074 });
1075
1076 let mut realm = enter_auto_realm(cx, global);
1079 let cx = &mut realm.current_realm();
1080
1081 load_requested_modules(cx, record.handle(), state);
1083}
1084
1085#[expect(clippy::too_many_arguments)]
1087pub(crate) fn fetch_a_single_module_script(
1088 cx: &mut JSContext,
1089 url: UrlWithBlobClaim,
1090 fetch_client: RequestClient,
1091 global: &GlobalScope,
1092 destination: Destination,
1093 options: ScriptFetchOptions,
1094 referrer: Referrer,
1095 module_type: Option<ModuleType>,
1096 is_top_level: bool,
1097 introduction_type: Option<&'static CStr>,
1098 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1099) {
1100 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1104
1105 let module_request = (url.url(), module_type);
1110
1111 let occupied = global.with_module_map(|module_map| {
1113 let mut module_map_borrow = module_map.safe_borrow_mut(cx);
1114
1115 let entry = module_map_borrow.entry(module_request.clone());
1116
1117 match entry {
1118 Entry::Occupied(mut entry) => {
1119 match entry.get_mut() {
1120 ModuleStatus::Loaded(module_tree) => {
1123 let module = module_tree.clone();
1124 drop(module_map_borrow);
1125 on_complete(cx, Some(module));
1126 },
1127 ModuleStatus::Fetching(callbacks) => {
1130 callbacks.push(Box::new(on_complete));
1131 },
1132 }
1133 true
1134 },
1135 Entry::Vacant(entry) => {
1137 entry.insert(ModuleStatus::Fetching(vec![Box::new(on_complete)]));
1138 false
1139 },
1140 }
1141 });
1142
1143 if occupied {
1144 return;
1145 }
1146
1147 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1149 .then(|| fetch_client.policy_container.clone());
1150
1151 let mode = match destination {
1156 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1157 _ => RequestMode::CorsMode,
1158 };
1159
1160 let destination = match module_type {
1163 ModuleType::JSON => Destination::Json,
1164 ModuleType::CSS => Destination::Style,
1165 ModuleType::Text => Destination::Text,
1166 ModuleType::Bytes => unreachable!("Not in ESR153"),
1167 ModuleType::JavaScript | ModuleType::Unknown => destination,
1168 };
1169
1170 let request = RequestBuilder::new(global.webview_id(), url, referrer)
1174 .destination(destination)
1175 .parser_metadata(options.parser_metadata)
1176 .integrity_metadata(options.integrity_metadata.clone())
1177 .credentials_mode(options.credentials_mode)
1178 .referrer_policy(options.referrer_policy)
1179 .mode(mode)
1180 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1181 .client(fetch_client)
1182 .pipeline_id(Some(global.pipeline_id()));
1183
1184 let context = ModuleContext {
1185 owner: Trusted::new(global),
1186 data: BytesMut::new(),
1187 metadata: None,
1188 module_request,
1189 options,
1190 status: Ok(()),
1191 introduction_type,
1192 policy_container,
1193 };
1194
1195 let task_source = global.task_manager().networking_task_source().to_sendable();
1196 global.fetch(request, context, task_source);
1197}
1198
1199#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1201pub(crate) struct ResolvedModule {
1202 pub(crate) base_url: String,
1204 pub(crate) specifier: String,
1206 #[no_trace]
1208 pub(crate) specifier_url: Option<ServoUrl>,
1209}
1210
1211impl ResolvedModule {
1212 pub(crate) fn new(
1213 base_url: String,
1214 specifier: String,
1215 specifier_url: Option<ServoUrl>,
1216 ) -> Self {
1217 Self {
1218 base_url,
1219 specifier,
1220 specifier_url,
1221 }
1222 }
1223}