1use std::cell::{OnceCell, RefCell};
9use std::ffi::CStr;
10use std::fmt::Debug;
11use std::ptr::NonNull;
12use std::rc::Rc;
13use std::{mem, ptr};
14
15use encoding_rs::UTF_8;
16use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
17use hyper_serde::Serde;
18use indexmap::IndexMap;
19use indexmap::map::Entry;
20use js::context::JSContext;
21use js::conversions::jsstr_to_string;
22use js::gc::{HandleObject, MutableHandleValue};
23use js::jsapi::{
24 CallArgs, ExceptionStackBehavior, GetFunctionNativeReserved, GetModuleResolveHook,
25 Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
26 JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
27 ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook, SetModuleMetadataHook,
28 SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks, Value,
29};
30use js::jsval::{JSVal, PrivateValue, UndefinedValue};
31use js::realm::{AutoRealm, CurrentRealm};
32use js::rust::wrappers2::{
33 CompileJsonModule1, CompileModule1, DefineFunctionWithReserved, GetModuleRequestSpecifier,
34 GetModuleRequestType, JS_ClearPendingException, JS_DefineProperty4, JS_GetPendingException,
35 JS_NewStringCopyN, JS_SetPendingException, ModuleEvaluate, ModuleLink,
36 ThrowOnModuleEvaluationFailure,
37};
38use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
39use mime::Mime;
40use net_traits::http_status::HttpStatus;
41use net_traits::mime_classifier::MimeClassifier;
42use net_traits::policy_container::PolicyContainer;
43use net_traits::request::{
44 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
45 RequestClient, RequestId, RequestMode,
46};
47use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
48use script_bindings::cell::DomRefCell;
49use script_bindings::domstring::BytesView;
50use script_bindings::error::Fallible;
51use script_bindings::reflector::DomObject;
52use script_bindings::settings_stack::run_a_callback;
53use script_bindings::trace::CustomTraceable;
54use serde_json::{Map as JsonMap, Value as JsonValue};
55use servo_base::id::PipelineId;
56use servo_config::pref;
57use servo_url::{ImmutableOrigin, ServoUrl};
58
59use crate::DomTypeHolder;
60use crate::dom::bindings::conversions::SafeToJSValConvertible;
61use crate::dom::bindings::error::{
62 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
63};
64use crate::dom::bindings::inheritance::Castable;
65use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
66use crate::dom::bindings::root::DomRoot;
67use crate::dom::bindings::str::DOMString;
68use crate::dom::bindings::trace::RootedTraceableBox;
69use crate::dom::csp::{GlobalCspReporting, Violation};
70use crate::dom::globalscope::GlobalScope;
71use crate::dom::globalscope::script_execution::{ErrorReporting, fill_compile_options};
72use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
73use crate::dom::performance::performanceresourcetiming::InitiatorType;
74use crate::dom::promise::Promise;
75use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
76use crate::dom::types::{
77 Console, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope,
78};
79use crate::dom::window::Window;
80use crate::module_loading::{
81 LoadState, Payload, host_load_imported_module, load_requested_modules,
82};
83use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
84use crate::realms::enter_auto_realm;
85use crate::script_runtime::IntroductionType;
86use crate::task::NonSendTaskBox;
87use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
88
89pub(crate) fn gen_type_error(
90 cx: &mut JSContext,
91 global: &GlobalScope,
92 error: Error,
93) -> RethrowError {
94 rooted!(&in(cx) let mut thrown = UndefinedValue());
95 error.to_jsval(cx, global, thrown.handle_mut());
96
97 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
98}
99
100#[derive(JSTraceable)]
101pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
102
103impl ModuleObject {
104 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
105 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
106 }
107
108 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
109 self.0.handle()
110 }
111}
112
113#[derive(JSTraceable)]
114pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
115
116impl RethrowError {
117 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
118 Self(RootedTraceableBox::from_box(val))
119 }
120
121 #[expect(unsafe_code)]
122 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
123 rooted!(&in(cx) let mut exception = UndefinedValue());
124 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
125 unsafe { JS_ClearPendingException(cx) };
126
127 Self::new(Heap::boxed(exception.get()))
128 }
129
130 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
131 self.0.handle()
132 }
133}
134
135impl Debug for RethrowError {
136 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
137 "RethrowError(...)".fmt(fmt)
138 }
139}
140
141impl Clone for RethrowError {
142 fn clone(&self) -> Self {
143 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
144 }
145}
146
147pub(crate) struct ModuleScript {
148 pub(crate) base_url: ServoUrl,
149 pub(crate) options: ScriptFetchOptions,
150 pub(crate) owner: Option<Trusted<GlobalScope>>,
151}
152
153impl ModuleScript {
154 pub(crate) fn new(
155 base_url: ServoUrl,
156 options: ScriptFetchOptions,
157 owner: Option<Trusted<GlobalScope>>,
158 ) -> Self {
159 ModuleScript {
160 base_url,
161 options,
162 owner,
163 }
164 }
165}
166
167pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
168
169#[derive(Clone, JSTraceable)]
170pub(crate) enum ModuleStatus {
171 Fetching(DomRefCell<Option<Rc<Promise>>>),
172 Loaded(Option<Rc<ModuleTree>>),
173}
174
175#[derive(JSTraceable, MallocSizeOf)]
176pub(crate) struct ModuleTree {
177 #[no_trace]
178 url: ServoUrl,
179 #[ignore_malloc_size_of = "mozjs"]
180 record: OnceCell<ModuleObject>,
181 #[ignore_malloc_size_of = "mozjs"]
182 parse_error: OnceCell<RethrowError>,
183 #[ignore_malloc_size_of = "mozjs"]
184 rethrow_error: DomRefCell<Option<RethrowError>>,
185 #[no_trace]
186 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
187}
188
189impl ModuleTree {
190 pub(crate) fn get_url(&self) -> ServoUrl {
191 self.url.clone()
192 }
193
194 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
195 self.record.get()
196 }
197
198 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
199 self.parse_error.get()
200 }
201
202 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
203 &self.rethrow_error
204 }
205
206 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
207 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
208 }
209
210 pub(crate) fn find_descendant_inside_module_map(
211 &self,
212 global: &GlobalScope,
213 specifier: &String,
214 module_type: ModuleType,
215 ) -> Option<Rc<ModuleTree>> {
216 self.loaded_modules
217 .borrow()
218 .get(specifier)
219 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
220 .and_then(|status| match status {
221 ModuleStatus::Fetching(_) => None,
222 ModuleStatus::Loaded(module_tree) => module_tree,
223 })
224 }
225
226 pub(crate) fn insert_module_dependency(
227 &self,
228 module: &Rc<ModuleTree>,
229 module_request_specifier: String,
230 ) {
231 let url = module.url.clone();
233 match self
234 .loaded_modules
235 .borrow_mut()
236 .entry(module_request_specifier)
237 {
238 Entry::Occupied(entry) => {
241 assert_eq!(*entry.get(), url);
243 },
244 Entry::Vacant(entry) => {
246 entry.insert(url);
249 },
250 }
251 }
252}
253
254pub(crate) struct ModuleSource {
255 pub source: Rc<DOMString>,
256 pub unminified_dir: Option<String>,
257 pub external: bool,
258 pub url: ServoUrl,
259}
260
261impl crate::unminify::ScriptSource for ModuleSource {
262 fn unminified_dir(&self) -> Option<String> {
263 self.unminified_dir.clone()
264 }
265
266 fn extract_bytes(&self) -> BytesView<'_> {
267 self.source.as_bytes()
268 }
269
270 fn rewrite_source(&mut self, source: Rc<DOMString>) {
271 self.source = source;
272 }
273
274 fn url(&self) -> ServoUrl {
275 self.url.clone()
276 }
277
278 fn is_external(&self) -> bool {
279 self.external
280 }
281}
282
283impl ModuleTree {
284 #[expect(unsafe_code)]
285 #[expect(clippy::too_many_arguments)]
286 fn create_a_javascript_module_script(
288 cx: &mut JSContext,
289 source: Rc<DOMString>,
290 global: &GlobalScope,
291 url: &ServoUrl,
292 options: ScriptFetchOptions,
293 external: bool,
294 line_number: u32,
295 introduction_type: Option<&'static CStr>,
296 ) -> Self {
297 let mut realm = AutoRealm::new(
298 cx,
299 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
300 );
301 let cx = &mut *realm;
302
303 let owner = Trusted::new(global);
304
305 let module = ModuleTree {
308 url: url.clone(),
309 record: OnceCell::new(),
310 parse_error: OnceCell::new(),
311 rethrow_error: DomRefCell::new(None),
312 loaded_modules: DomRefCell::new(IndexMap::new()),
313 };
314
315 let compile_options = fill_compile_options(
316 cx,
317 url.as_str(),
318 introduction_type,
319 ErrorReporting::Unmuted,
320 true, line_number,
322 );
323
324 let mut module_source = ModuleSource {
325 source,
326 unminified_dir: global.unminified_js_dir(),
327 external,
328 url: url.clone(),
329 };
330 crate::unminify::unminify_js(&mut module_source);
331
332 unsafe {
333 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
335 module_script.set(CompileModule1(
336 cx,
337 compile_options.ptr,
338 &mut transform_str_to_source_text(&module_source.source.str()),
339 ));
340
341 if module_script.is_null() {
343 warn!("fail to compile module script of {}", url);
344
345 let _ = module
347 .parse_error
348 .set(RethrowError::from_pending_exception(cx));
349
350 return module;
352 }
353
354 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
358
359 SetModulePrivate(
360 module_script.get(),
361 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
362 );
363
364 let _ = module.record.set(ModuleObject::new(module_script.handle()));
366 }
367
368 module
370 }
371
372 #[expect(unsafe_code)]
373 fn create_a_json_module_script(
375 cx: &mut JSContext,
376 source: &str,
377 global: &GlobalScope,
378 url: &ServoUrl,
379 introduction_type: Option<&'static CStr>,
380 ) -> Self {
381 let mut realm = AutoRealm::new(
382 cx,
383 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
384 );
385 let cx = &mut *realm;
386
387 let module = ModuleTree {
390 url: url.clone(),
391 record: OnceCell::new(),
392 parse_error: OnceCell::new(),
393 rethrow_error: DomRefCell::new(None),
394 loaded_modules: DomRefCell::new(IndexMap::new()),
395 };
396
397 let compile_options = fill_compile_options(
402 cx,
403 url.as_str(),
404 introduction_type,
405 ErrorReporting::Unmuted,
406 true, 1, );
409
410 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
411
412 unsafe {
413 module_script.set(CompileJsonModule1(
415 cx,
416 compile_options.ptr,
417 &mut transform_str_to_source_text(source),
418 ));
419 }
420
421 if module_script.is_null() {
423 warn!("fail to compile module script of {}", url);
424
425 let _ = module
426 .parse_error
427 .set(RethrowError::from_pending_exception(cx));
428 return module;
429 }
430
431 let _ = module.record.set(ModuleObject::new(module_script.handle()));
433
434 module
436 }
437
438 #[expect(unsafe_code)]
441 pub(crate) fn execute_module(
442 &self,
443 cx: &mut JSContext,
444 global: &GlobalScope,
445 module_record: HandleObject,
446 mut eval_result: MutableHandleValue,
447 ) -> Result<(), RethrowError> {
448 let mut realm = AutoRealm::new(
449 cx,
450 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
451 );
452 let cx = &mut *realm;
453
454 unsafe {
455 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
456 assert!(ok, "module evaluation failed");
457
458 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
459 if eval_result.is_object() {
460 evaluation_promise.set(eval_result.to_object());
461 }
462
463 let throw_result = ThrowOnModuleEvaluationFailure(
464 cx,
465 evaluation_promise.handle(),
466 ModuleErrorBehaviour::ThrowModuleErrorsSync,
467 );
468 if !throw_result {
469 warn!("fail to evaluate module");
470
471 Err(RethrowError::from_pending_exception(cx))
472 } else {
473 debug!("module evaluated successfully");
474 Ok(())
475 }
476 }
477 }
478
479 #[expect(unsafe_code)]
480 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
481 let module_error = self.rethrow_error.borrow();
482
483 if let Some(exception) = &*module_error {
484 let mut realm = enter_auto_realm(cx, global);
485 let cx = &mut realm.current_realm();
486
487 unsafe {
488 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
489 }
490 report_pending_exception(cx);
491 }
492 }
493
494 pub(crate) fn resolve_module_specifier(
496 global: &GlobalScope,
497 script: Option<&ModuleScript>,
498 specifier: DOMString,
499 ) -> Fallible<ServoUrl> {
500 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
502 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
504 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
508 None => (global, &global.api_base_url()),
513 };
514
515 let import_map = if global.is::<Window>() {
519 Some(global.import_map())
520 } else {
521 None
522 };
523 let specifier = &specifier.str();
524
525 let serialized_base_url = base_url.as_str();
527 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
529 let normalized_specifier = match &as_url {
532 Some(url) => url.as_str(),
533 None => specifier,
534 };
535
536 let mut result = None;
538 if let Some(map) = import_map {
539 for (prefix, imports) in &map.scopes {
541 let prefix = prefix.as_str();
544 if prefix == serialized_base_url ||
545 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
546 {
547 let scope_imports_match =
550 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
551
552 if scope_imports_match.is_some() {
554 result = scope_imports_match;
555 break;
556 }
557 }
558 }
559
560 if result.is_none() {
563 result =
564 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
565 }
566 }
567
568 if result.is_none() {
570 result = as_url.clone();
571 }
572
573 match result {
575 Some(result) => {
576 global.add_module_to_resolved_module_set(
579 serialized_base_url,
580 normalized_specifier,
581 as_url.clone(),
582 );
583 Ok(result)
585 },
586 None => Err(Error::Type(
589 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
590 .to_owned(),
591 )),
592 }
593 }
594
595 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
597 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
599 {
600 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
602 }
603 ServoUrl::parse(specifier).ok()
605 }
606}
607
608#[derive(JSTraceable, MallocSizeOf)]
609pub(crate) struct ModuleHandler {
610 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
611 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
612}
613
614impl ModuleHandler {
615 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
616 Box::new(Self {
617 task: DomRefCell::new(Some(task)),
618 })
619 }
620}
621
622impl Callback for ModuleHandler {
623 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
624 let task = self.task.borrow_mut().take().unwrap();
625 task.run_box(cx);
626 }
627}
628
629#[derive(JSTraceable, MallocSizeOf)]
630struct QueueTaskHandler {
631 #[conditional_malloc_size_of]
632 promise: Rc<Promise>,
633}
634
635impl Callback for QueueTaskHandler {
636 fn callback(&self, cx: &mut CurrentRealm, _: HandleValue) {
637 let global = GlobalScope::from_current_realm(cx);
638 let promise = TrustedPromise::new(self.promise.clone());
639
640 global.task_manager().networking_task_source().queue(
641 task!(continue_module_loading: move |cx| {
642 promise.root().resolve_native(cx, &());
643 }),
644 );
645 }
646}
647
648#[derive(Clone)]
649pub(crate) struct ModuleFetchClient {
650 pub insecure_requests_policy: InsecureRequestsPolicy,
651 pub has_trustworthy_ancestor_origin: bool,
652 pub policy_container: PolicyContainer,
653 pub client: RequestClient,
654 pub pipeline_id: PipelineId,
655 pub origin: ImmutableOrigin,
656}
657
658impl ModuleFetchClient {
659 pub(crate) fn from_global_scope(global: &GlobalScope) -> Self {
660 Self {
661 insecure_requests_policy: global.insecure_requests_policy(),
662 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_or_current_origin(),
663 policy_container: global.policy_container(),
664 client: global.request_client(),
665 pipeline_id: global.pipeline_id(),
666 origin: global.origin().immutable().clone(),
667 }
668 }
669}
670
671struct ModuleContext {
673 owner: Trusted<GlobalScope>,
675 data: Vec<u8>,
677 metadata: Option<Metadata>,
679 module_request: ModuleRequest,
681 options: ScriptFetchOptions,
683 status: Result<(), NetworkError>,
685 introduction_type: Option<&'static CStr>,
687 policy_container: Option<PolicyContainer>,
689}
690
691impl FetchResponseListener for ModuleContext {
692 fn process_request_body(&mut self, _: RequestId) {}
694
695 fn process_response(
696 &mut self,
697 _: &mut js::context::JSContext,
698 _: RequestId,
699 metadata: Result<FetchMetadata, NetworkError>,
700 ) {
701 self.metadata = metadata.ok().map(|meta| match meta {
702 FetchMetadata::Unfiltered(m) => m,
703 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
704 });
705
706 let status = self
707 .metadata
708 .as_ref()
709 .map(|m| m.status.clone())
710 .unwrap_or_else(HttpStatus::new_error);
711
712 self.status = {
713 if status.is_error() {
714 Err(NetworkError::ResourceLoadError(
715 "No http status code received".to_owned(),
716 ))
717 } else if status.is_success() {
718 Ok(())
719 } else {
720 Err(NetworkError::ResourceLoadError(format!(
721 "HTTP error code {}",
722 status.code()
723 )))
724 }
725 };
726 }
727
728 fn process_response_chunk(
729 &mut self,
730 _: &mut js::context::JSContext,
731 _: RequestId,
732 mut chunk: Vec<u8>,
733 ) {
734 if self.status.is_ok() {
735 self.data.append(&mut chunk);
736 }
737 }
738
739 fn process_response_eof(
742 mut self,
743 cx: &mut js::context::JSContext,
744 _: RequestId,
745 response: Result<(), NetworkError>,
746 timing: ResourceFetchTiming,
747 ) {
748 let global = self.owner.root();
749 let (_url, module_type) = &self.module_request;
750
751 network_listener::submit_timing(cx, &self, &response, &timing);
752
753 let Some(ModuleStatus::Fetching(pending)) =
754 global.get_module_map_entry(&self.module_request)
755 else {
756 return error!("Processing response for a non pending module request");
757 };
758 let promise = pending
759 .borrow_mut()
760 .take()
761 .expect("Need promise to process response");
762
763 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
766 error!("Fetching module script failed {:?}", error);
767 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
768 return promise.resolve_native(cx, &());
769 }
770
771 let metadata = self.metadata.take().unwrap();
772
773 if let Some(policy_container) = self.policy_container {
776 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
777 "We only need a policy container when initializing a worker's globalscope.",
778 );
779 workerscope.process_response_for_workerscope(&metadata, &policy_container);
780 }
781
782 let final_url = metadata.final_url;
783
784 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
786
787 let mut module_script = None;
789
790 let referrer_policy = metadata
792 .headers
793 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
794 .into();
795
796 if referrer_policy != ReferrerPolicy::EmptyString {
798 self.options.referrer_policy = referrer_policy;
799 }
800
801 if let Some(mime) = mime_type {
807 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
809
810 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
813 matches!(module_type, ModuleType::JavaScript)
814 {
815 if let Some(window) = global.downcast::<Window>() &&
816 let Some(script_souce) = window.local_script_source()
817 {
818 substitute_with_local_script(script_souce, &mut source_text, final_url.clone());
819 }
820
821 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
822 cx,
823 Rc::new(DOMString::from(source_text.clone())),
824 &global,
825 &final_url,
826 self.options,
827 true,
828 1,
829 self.introduction_type,
830 ));
831 module_script = Some(module_tree);
832 }
833
834 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
837 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
838 cx,
839 &source_text,
840 &global,
841 &final_url,
842 self.introduction_type,
843 ));
844 module_script = Some(module_tree);
845 }
846 }
847 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
849 promise.resolve_native(cx, &());
850 }
851
852 fn process_csp_violations(
853 &mut self,
854 cx: &mut js::context::JSContext,
855 _request_id: RequestId,
856 violations: Vec<Violation>,
857 ) {
858 let global = self.owner.root();
859 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
860 scope.report_csp_violations(violations);
861 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
862 scope.report_csp_violations(violations);
863 } else {
864 global.report_csp_violations(cx, violations, None, None);
865 }
866 }
867}
868
869impl ResourceTimingListener for ModuleContext {
870 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
871 let initiator_type = InitiatorType::LocalName("module".to_string());
872 let (url, _) = &self.module_request;
873 (initiator_type, url.clone())
874 }
875
876 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
877 self.owner.root()
878 }
879}
880
881#[expect(unsafe_code)]
882#[expect(non_snake_case)]
883pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
886 unsafe {
887 if GetModuleResolveHook(rt).is_some() {
888 return;
889 }
890
891 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
892 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
893 SetScriptPrivateReferenceHooks(
894 rt,
895 Some(host_add_ref_top_level_script),
896 Some(host_release_top_level_script),
897 );
898 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
899 }
900}
901
902#[expect(unsafe_code)]
903unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
904 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
905 mem::forget(val.clone());
906 mem::forget(val);
907}
908
909#[expect(unsafe_code)]
910unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
911 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
912}
913
914#[expect(unsafe_code)]
915pub(crate) unsafe extern "C" fn host_import_module_dynamically(
918 cx: *mut RawJSContext,
919 reference_private: RawHandleValue,
920 specifier: RawHandle<*mut JSObject>,
921 promise: RawHandle<*mut JSObject>,
922) -> bool {
923 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
925 let cx = &mut cx;
926 let promise = Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise) });
927
928 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
929 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
930 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
931
932 let mut realm = CurrentRealm::assert(cx);
933 let payload = Payload::PromiseRecord(promise);
934 host_load_imported_module(
935 &mut realm,
936 None,
937 reference_private,
938 specifier,
939 module_type,
940 None,
941 payload,
942 );
943
944 true
945}
946
947#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
948pub(crate) struct ScriptFetchOptions {
950 pub(crate) integrity_metadata: String,
951 #[no_trace]
952 pub(crate) credentials_mode: CredentialsMode,
953 pub(crate) cryptographic_nonce: String,
954 #[no_trace]
955 pub(crate) parser_metadata: ParserMetadata,
956 #[no_trace]
957 pub(crate) referrer_policy: ReferrerPolicy,
958 pub(crate) render_blocking: bool,
962}
963
964impl ScriptFetchOptions {
965 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
967 Self {
968 cryptographic_nonce: String::new(),
969 integrity_metadata: String::new(),
970 parser_metadata: ParserMetadata::NotParserInserted,
971 credentials_mode: CredentialsMode::CredentialsSameOrigin,
972 referrer_policy: ReferrerPolicy::EmptyString,
973 render_blocking: false,
974 }
975 }
976
977 pub(crate) fn descendant_fetch_options(
979 &self,
980 url: &ServoUrl,
981 global: &GlobalScope,
982 ) -> ScriptFetchOptions {
983 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
985
986 Self {
989 integrity_metadata: integrity,
991 cryptographic_nonce: self.cryptographic_nonce.clone(),
992 credentials_mode: self.credentials_mode,
993 parser_metadata: self.parser_metadata,
994 referrer_policy: self.referrer_policy,
995 render_blocking: self.render_blocking,
996 }
997 }
998}
999
1000#[expect(unsafe_code)]
1001pub(crate) unsafe fn module_script_from_reference_private(
1002 reference_private: &RawHandle<JSVal>,
1003) -> Option<&ModuleScript> {
1004 if reference_private.get().is_undefined() {
1005 return None;
1006 }
1007 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1008}
1009
1010#[expect(unsafe_code)]
1011#[expect(non_snake_case)]
1012unsafe extern "C" fn HostResolveImportedModule(
1015 cx: *mut RawJSContext,
1016 reference_private: RawHandleValue,
1017 specifier: RawHandle<*mut JSObject>,
1018) -> *mut JSObject {
1019 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1021 let mut realm = CurrentRealm::assert(&mut cx);
1022 let global_scope = GlobalScope::from_current_realm(&realm);
1023
1024 let cx = &mut realm;
1025
1026 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1028 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1029 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1030
1031 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
1032 let url = ModuleTree::resolve_module_specifier(
1033 &global_scope,
1034 module_data,
1035 DOMString::from(specifier),
1036 );
1037
1038 assert!(url.is_ok());
1040
1041 let parsed_url = url.unwrap();
1042
1043 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1045
1046 assert!(module.as_ref().is_some_and(
1048 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1049 ));
1050
1051 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1052 unreachable!()
1053 };
1054
1055 let fetched_module_object = module_tree.get_record();
1056
1057 assert!(fetched_module_object.is_some());
1059
1060 if let Some(record) = fetched_module_object {
1062 return record.handle().get();
1063 }
1064
1065 unreachable!()
1066}
1067
1068const SLOT_MODULEPRIVATE: usize = 0;
1070
1071#[expect(unsafe_code)]
1072#[expect(non_snake_case)]
1073unsafe extern "C" fn HostPopulateImportMeta(
1076 cx: *mut RawJSContext,
1077 reference_private: RawHandleValue,
1078 meta_object: RawHandle<*mut JSObject>,
1079) -> bool {
1080 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1082 let realm = CurrentRealm::assert(&mut cx);
1083 let global_scope = GlobalScope::from_current_realm(&realm);
1084
1085 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1087 Some(module_data) => module_data.base_url.clone(),
1088 None => global_scope.api_base_url(),
1089 };
1090
1091 unsafe {
1092 let url_string = JS_NewStringCopyN(
1093 &mut cx,
1094 base_url.as_str().as_ptr() as *const _,
1095 base_url.as_str().len(),
1096 );
1097 rooted!(&in(cx) let url_string = url_string);
1098
1099 if !JS_DefineProperty4(
1101 &mut cx,
1102 Handle::from_raw(meta_object),
1103 c"url".as_ptr(),
1104 url_string.handle(),
1105 JSPROP_ENUMERATE.into(),
1106 ) {
1107 return false;
1108 }
1109
1110 let resolve_function = DefineFunctionWithReserved(
1112 &mut cx,
1113 meta_object.get(),
1114 c"resolve".as_ptr(),
1115 Some(import_meta_resolve),
1116 1,
1117 JSPROP_ENUMERATE.into(),
1118 );
1119
1120 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1121 assert!(!obj.is_null());
1122 SetFunctionNativeReserved(
1123 obj.get(),
1124 SLOT_MODULEPRIVATE,
1125 &reference_private.get() as *const _,
1126 );
1127 }
1128
1129 true
1130}
1131
1132#[expect(unsafe_code)]
1133unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1134 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1136 let mut realm = CurrentRealm::assert(&mut cx);
1137 let global_scope = GlobalScope::from_current_realm(&realm);
1138
1139 let cx = &mut realm;
1140
1141 let args = unsafe { CallArgs::from_vp(vp, argc) };
1142
1143 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1144 let reference_private = module_private.handle().into();
1145 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1146
1147 let specifier = unsafe {
1151 let value = HandleValue::from_raw(args.get(0));
1152
1153 match NonNull::new(ToString(cx, value)) {
1154 Some(jsstr) => jsstr_to_string(cx, jsstr).into(),
1155 None => return false,
1156 }
1157 };
1158
1159 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1161
1162 match url {
1163 Ok(url) => {
1164 url.as_str()
1166 .safe_to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
1167 true
1168 },
1169 Err(error) => {
1170 let resolution_error = gen_type_error(cx, &global_scope, error);
1171
1172 unsafe {
1173 JS_SetPendingException(
1174 cx,
1175 resolution_error.handle(),
1176 ExceptionStackBehavior::Capture,
1177 );
1178 }
1179 false
1180 },
1181 }
1182}
1183
1184#[expect(clippy::too_many_arguments)]
1185pub(crate) fn fetch_a_module_worker_script_graph(
1188 cx: &mut JSContext,
1189 global: &GlobalScope,
1190 url: ServoUrl,
1191 fetch_client: ModuleFetchClient,
1192 destination: Destination,
1193 referrer: Referrer,
1194 credentials_mode: CredentialsMode,
1195 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1196) {
1197 let global_scope = DomRoot::from_ref(global);
1198
1199 let options = ScriptFetchOptions {
1204 integrity_metadata: "".into(),
1205 credentials_mode,
1206 cryptographic_nonce: "".into(),
1207 parser_metadata: ParserMetadata::NotParserInserted,
1208 referrer_policy: ReferrerPolicy::EmptyString,
1209 render_blocking: false,
1210 };
1211
1212 fetch_a_single_module_script(
1215 cx,
1216 url,
1217 fetch_client.clone(),
1218 global,
1219 destination,
1220 options,
1221 referrer,
1222 None,
1223 true,
1224 Some(IntroductionType::WORKER),
1225 move |cx, module_tree| {
1226 let Some(module) = module_tree else {
1227 return on_complete(cx, None);
1229 };
1230
1231 fetch_the_descendants_and_link_module_script(
1234 cx,
1235 &global_scope,
1236 module,
1237 fetch_client,
1238 destination,
1239 on_complete,
1240 );
1241 },
1242 );
1243}
1244
1245pub(crate) fn fetch_an_external_module_script(
1247 cx: &mut JSContext,
1248 url: ServoUrl,
1249 global: &GlobalScope,
1250 options: ScriptFetchOptions,
1251 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1252) {
1253 let referrer = global.get_referrer();
1254 let fetch_client = ModuleFetchClient::from_global_scope(global);
1255 let global_scope = DomRoot::from_ref(global);
1256
1257 fetch_a_single_module_script(
1260 cx,
1261 url,
1262 fetch_client.clone(),
1263 global,
1264 Destination::Script,
1265 options,
1266 referrer,
1267 None,
1268 true,
1269 Some(IntroductionType::SRC_SCRIPT),
1270 move |cx, module_tree| {
1271 let Some(module) = module_tree else {
1272 return on_complete(cx, None);
1274 };
1275
1276 fetch_the_descendants_and_link_module_script(
1278 cx,
1279 &global_scope,
1280 module,
1281 fetch_client,
1282 Destination::Script,
1283 on_complete,
1284 );
1285 },
1286 );
1287}
1288
1289pub(crate) fn fetch_a_modulepreload_module(
1291 cx: &mut JSContext,
1292 url: ServoUrl,
1293 destination: Destination,
1294 global: &GlobalScope,
1295 options: ScriptFetchOptions,
1296 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1297) {
1298 let referrer = global.get_referrer();
1299 let fetch_client = ModuleFetchClient::from_global_scope(global);
1300 let global_scope = DomRoot::from_ref(global);
1301
1302 let module_type = if let Destination::Json = destination {
1305 Some(ModuleType::JSON)
1306 } else {
1307 None
1308 };
1309
1310 fetch_a_single_module_script(
1313 cx,
1314 url,
1315 fetch_client.clone(),
1316 global,
1317 destination,
1318 options,
1319 referrer,
1320 module_type,
1321 true,
1322 Some(IntroductionType::SRC_SCRIPT),
1323 move |cx, result| {
1324 on_complete(cx, result.is_none());
1326
1327 assert!(global_scope.is::<Window>());
1329
1330 if pref!(dom_allow_preloading_module_descendants) &&
1333 let Some(module) = result
1334 {
1335 fetch_the_descendants_and_link_module_script(
1336 cx,
1337 &global_scope,
1338 module,
1339 fetch_client,
1340 destination,
1341 |_, _| {},
1342 );
1343 }
1344 },
1345 );
1346}
1347
1348#[expect(clippy::too_many_arguments)]
1349pub(crate) fn fetch_inline_module_script(
1351 cx: &mut JSContext,
1352 global: &GlobalScope,
1353 module_script_text: Rc<DOMString>,
1354 url: ServoUrl,
1355 options: ScriptFetchOptions,
1356 line_number: u32,
1357 introduction_type: Option<&'static CStr>,
1358 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1359) {
1360 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1362 cx,
1363 module_script_text,
1364 global,
1365 &url,
1366 options,
1367 false,
1368 line_number,
1369 introduction_type,
1370 ));
1371 let fetch_client = ModuleFetchClient::from_global_scope(global);
1372
1373 fetch_the_descendants_and_link_module_script(
1375 cx,
1376 global,
1377 module_tree,
1378 fetch_client,
1379 Destination::Script,
1380 on_complete,
1381 );
1382}
1383
1384#[expect(unsafe_code)]
1385fn fetch_the_descendants_and_link_module_script(
1387 cx: &mut JSContext,
1388 global: &GlobalScope,
1389 module_script: Rc<ModuleTree>,
1390 fetch_client: ModuleFetchClient,
1391 destination: Destination,
1392 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1393) {
1394 if module_script.get_record().is_none() {
1397 let parse_error = module_script.get_parse_error().cloned();
1398
1399 module_script.set_rethrow_error(parse_error.unwrap());
1401
1402 on_complete(cx, Some(module_script));
1404
1405 return;
1407 }
1408
1409 let state = Rc::new(LoadState {
1412 error_to_rethrow: RefCell::new(None),
1413 destination,
1414 fetch_client,
1415 });
1416
1417 let mut realm = enter_auto_realm(cx, global);
1420 let cx = &mut realm.current_realm();
1421
1422 let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
1424
1425 let global_scope = DomRoot::from_ref(global);
1426 let fulfilled_module = module_script.clone();
1427 let fulfilled_on_complete = on_complete.clone();
1428
1429 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1431 task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
1432 let mut realm = AutoRealm::new(
1433 cx,
1434 NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
1435 );
1436 let cx = &mut *realm;
1437
1438 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1439
1440 let link = unsafe { ModuleLink(cx, handle) };
1442
1443 if !link {
1445 let exception = RethrowError::from_pending_exception(cx);
1446 fulfilled_module.set_rethrow_error(exception);
1447 }
1448
1449 fulfilled_on_complete(cx, Some(fulfilled_module));
1451 }),
1452 ));
1453
1454 let loading_promise_rejection =
1456 ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
1457 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1460 module_script.set_rethrow_error(error.clone());
1461 on_complete(cx, Some(module_script));
1462 } else {
1463 on_complete(cx, None);
1465 }
1466 })));
1467
1468 let handler = PromiseNativeHandler::new(
1469 cx,
1470 global,
1471 Some(loading_promise_fulfillment),
1472 Some(loading_promise_rejection),
1473 );
1474
1475 run_a_callback::<DomTypeHolder, _>(global, || {
1476 loading_promise.append_native_handler(cx, &handler);
1477 });
1478}
1479
1480#[expect(clippy::too_many_arguments)]
1482pub(crate) fn fetch_a_single_module_script(
1483 cx: &mut JSContext,
1484 url: ServoUrl,
1485 fetch_client: ModuleFetchClient,
1486 global: &GlobalScope,
1487 destination: Destination,
1488 options: ScriptFetchOptions,
1489 referrer: Referrer,
1490 module_type: Option<ModuleType>,
1491 is_top_level: bool,
1492 introduction_type: Option<&'static CStr>,
1493 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1494) {
1495 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1499
1500 let module_request = (url.clone(), module_type);
1506 let entry = global.get_module_map_entry(&module_request);
1507
1508 let pending = match entry {
1509 Some(ModuleStatus::Fetching(pending)) => pending,
1510 Some(ModuleStatus::Loaded(module_tree)) => {
1512 return on_complete(cx, module_tree);
1513 },
1514 None => DomRefCell::new(None),
1515 };
1516
1517 let global_scope = DomRoot::from_ref(global);
1518 let module_map_key = module_request.clone();
1519 let handler = ModuleHandler::new_boxed(Box::new(
1520 task!(fetch_completed: |cx, global_scope: DomRoot<GlobalScope>| {
1521 let key = module_map_key;
1522 let module = global_scope.get_module_map_entry(&key);
1523
1524 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1525 on_complete(cx, module_tree);
1526 }
1527 }),
1528 ));
1529
1530 let handler = PromiseNativeHandler::new(cx, global, Some(handler), None);
1531
1532 let mut realm = enter_auto_realm(cx, global);
1533 let cx = &mut realm.current_realm();
1534
1535 run_a_callback::<DomTypeHolder, _>(global, || {
1536 let has_pending_fetch = pending.borrow().is_some();
1537
1538 let promise = Promise::new_in_realm(cx);
1539
1540 if has_pending_fetch {
1543 promise.append_native_handler(cx, &handler);
1544
1545 let continue_loading_handler = PromiseNativeHandler::new(
1548 cx,
1549 global,
1550 Some(Box::new(QueueTaskHandler { promise })),
1551 None,
1552 );
1553
1554 let pending_promise = pending.borrow_mut().take();
1556 if let Some(promise) = pending_promise {
1557 promise.append_native_handler(cx, &continue_loading_handler);
1558 let _ = pending.borrow_mut().insert(promise);
1559 }
1560 return;
1561 }
1562
1563 promise.append_native_handler(cx, &handler);
1564
1565 let prev = pending.borrow_mut().replace(promise);
1566 assert!(prev.is_none());
1567
1568 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1570
1571 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1573 .then(|| fetch_client.policy_container.clone());
1574
1575 let webview_id = global.webview_id();
1576
1577 let mode = match destination {
1582 Destination::Worker | Destination::SharedWorker if is_top_level => {
1583 RequestMode::SameOrigin
1584 },
1585 _ => RequestMode::CorsMode,
1586 };
1587
1588 let destination = match module_type {
1591 ModuleType::JSON => Destination::Json,
1592 ModuleType::JavaScript | ModuleType::Unknown => destination,
1593 };
1594
1595 let request = RequestBuilder::new(
1599 webview_id,
1600 ensure_blob_referenced_by_url_is_kept_alive(global, url.clone()),
1601 referrer,
1602 )
1603 .destination(destination)
1604 .parser_metadata(options.parser_metadata)
1605 .integrity_metadata(options.integrity_metadata.clone())
1606 .credentials_mode(options.credentials_mode)
1607 .referrer_policy(options.referrer_policy)
1608 .mode(mode)
1609 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1610 .insecure_requests_policy(fetch_client.insecure_requests_policy)
1611 .has_trustworthy_ancestor_origin(fetch_client.has_trustworthy_ancestor_origin)
1612 .policy_container(fetch_client.policy_container)
1613 .client(fetch_client.client)
1614 .pipeline_id(Some(fetch_client.pipeline_id))
1615 .origin(fetch_client.origin);
1616
1617 let context = ModuleContext {
1618 owner: Trusted::new(global),
1619 data: vec![],
1620 metadata: None,
1621 module_request,
1622 options,
1623 status: Ok(()),
1624 introduction_type,
1625 policy_container,
1626 };
1627
1628 let task_source = global.task_manager().networking_task_source().to_sendable();
1629 global.fetch(request, context, task_source);
1630 })
1631}
1632
1633pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1634pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1635
1636#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1638pub(crate) struct ResolvedModule {
1639 base_url: String,
1641 specifier: String,
1643 #[no_trace]
1645 specifier_url: Option<ServoUrl>,
1646}
1647
1648impl ResolvedModule {
1649 pub(crate) fn new(
1650 base_url: String,
1651 specifier: String,
1652 specifier_url: Option<ServoUrl>,
1653 ) -> Self {
1654 Self {
1655 base_url,
1656 specifier,
1657 specifier_url,
1658 }
1659 }
1660}
1661
1662#[derive(Default, JSTraceable, MallocSizeOf)]
1664pub(crate) struct ImportMap {
1665 #[no_trace]
1666 imports: ModuleSpecifierMap,
1667 #[no_trace]
1668 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1669 #[no_trace]
1670 integrity: ModuleIntegrityMap,
1671}
1672
1673impl ImportMap {
1674 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1676 self.integrity.get(url).cloned().unwrap_or_default()
1681 }
1682}
1683
1684pub(crate) fn register_import_map(
1686 cx: &mut JSContext,
1687 global: &GlobalScope,
1688 result: Fallible<ImportMap>,
1689) {
1690 match result {
1691 Ok(new_import_map) => {
1692 merge_existing_and_new_import_maps(cx, global, new_import_map);
1694 },
1695 Err(exception) => {
1696 let mut realm = enter_auto_realm(cx, global);
1697 let cx = &mut realm.current_realm();
1698
1699 throw_dom_exception(cx, global, exception);
1702 report_pending_exception(cx);
1703 },
1704 }
1705}
1706
1707fn merge_existing_and_new_import_maps(
1709 cx: &mut JSContext,
1710 global: &GlobalScope,
1711 new_import_map: ImportMap,
1712) {
1713 let new_import_map_scopes = new_import_map.scopes;
1715
1716 let mut old_import_map = global.import_map_mut();
1718
1719 let mut new_import_map_imports = new_import_map.imports;
1721
1722 let resolved_module_set = global.resolved_module_set();
1723 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1725 for record in resolved_module_set.iter() {
1727 let prefix = scope_prefix.as_str();
1730 if prefix == record.base_url ||
1731 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1732 {
1733 scope_imports.retain(|key, val| {
1735 if *key == record.specifier ||
1740 (key.ends_with('\u{002f}') &&
1741 record.specifier.starts_with(key) &&
1742 (record.specifier_url.is_none() ||
1743 record
1744 .specifier_url
1745 .as_ref()
1746 .is_some_and(|u| u.is_special_scheme())))
1747 {
1748 Console::internal_warn(
1751 cx,
1752 global,
1753 format!("Ignored rule: {key} -> {val:?}."),
1754 );
1755 false
1757 } else {
1758 true
1759 }
1760 })
1761 }
1762 }
1763
1764 if old_import_map.scopes.contains_key(&scope_prefix) {
1766 let merged_module_specifier_map = merge_module_specifier_maps(
1769 cx,
1770 global,
1771 scope_imports,
1772 &old_import_map.scopes[&scope_prefix],
1773 );
1774 old_import_map
1775 .scopes
1776 .insert(scope_prefix, merged_module_specifier_map);
1777 } else {
1778 old_import_map.scopes.insert(scope_prefix, scope_imports);
1780 }
1781 }
1782
1783 for (url, integrity) in &new_import_map.integrity {
1785 if old_import_map.integrity.contains_key(url) {
1787 Console::internal_warn(cx, global, format!("Ignored rule: {url} -> {integrity}."));
1790 continue;
1792 }
1793
1794 old_import_map
1796 .integrity
1797 .insert(url.clone(), integrity.clone());
1798 }
1799
1800 for record in resolved_module_set.iter() {
1802 new_import_map_imports.retain(|specifier, val| {
1804 if record.specifier.starts_with(specifier) {
1809 Console::internal_warn(
1812 cx,
1813 global,
1814 format!("Ignored rule: {specifier} -> {val:?}."),
1815 );
1816 false
1818 } else {
1819 true
1820 }
1821 });
1822 }
1823
1824 let merged_module_specifier_map =
1827 merge_module_specifier_maps(cx, global, new_import_map_imports, &old_import_map.imports);
1828 old_import_map.imports = merged_module_specifier_map;
1829
1830 old_import_map
1833 .scopes
1834 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1835}
1836
1837fn merge_module_specifier_maps(
1839 cx: &mut JSContext,
1840 global: &GlobalScope,
1841 new_map: ModuleSpecifierMap,
1842 old_map: &ModuleSpecifierMap,
1843) -> ModuleSpecifierMap {
1844 let mut merged_map = old_map.clone();
1846
1847 for (specifier, url) in new_map {
1849 if old_map.contains_key(&specifier) {
1851 Console::internal_warn(cx, global, format!("Ignored rule: {specifier} -> {url:?}."));
1854
1855 continue;
1857 }
1858
1859 merged_map.insert(specifier, url);
1861 }
1862
1863 merged_map
1864}
1865
1866pub(crate) fn parse_an_import_map_string(
1868 cx: &mut JSContext,
1869 global: &GlobalScope,
1870 input: Rc<DOMString>,
1871 base_url: ServoUrl,
1872) -> Fallible<ImportMap> {
1873 let parsed: JsonValue = serde_json::from_str(&input.str())
1875 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1876 let JsonValue::Object(mut parsed) = parsed else {
1879 return Err(Error::Type(
1880 c"The top-level value needs to be a JSON object.".to_owned(),
1881 ));
1882 };
1883
1884 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1886 if let Some(imports) = parsed.get("imports") {
1888 let JsonValue::Object(imports) = imports else {
1891 return Err(Error::Type(
1892 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1893 ));
1894 };
1895 sorted_and_normalized_imports =
1898 sort_and_normalize_module_specifier_map(cx, global, imports, &base_url);
1899 }
1900
1901 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1903 if let Some(scopes) = parsed.get("scopes") {
1905 let JsonValue::Object(scopes) = scopes else {
1908 return Err(Error::Type(
1909 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1910 ));
1911 };
1912 sorted_and_normalized_scopes = sort_and_normalize_scopes(cx, global, scopes, &base_url)?;
1915 }
1916
1917 let mut normalized_integrity = ModuleIntegrityMap::new();
1919 if let Some(integrity) = parsed.get("integrity") {
1921 let JsonValue::Object(integrity) = integrity else {
1924 return Err(Error::Type(
1925 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1926 ));
1927 };
1928 normalized_integrity = normalize_module_integrity_map(cx, global, integrity, &base_url);
1931 }
1932
1933 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1937 if !parsed.is_empty() {
1938 Console::internal_warn(
1939 cx,
1940 global,
1941 "Invalid top-level key was present in the import map.
1942 Only \"imports\", \"scopes\", and \"integrity\" are allowed."
1943 .to_string(),
1944 );
1945 }
1946
1947 Ok(ImportMap {
1949 imports: sorted_and_normalized_imports,
1950 scopes: sorted_and_normalized_scopes,
1951 integrity: normalized_integrity,
1952 })
1953}
1954
1955fn sort_and_normalize_module_specifier_map(
1957 cx: &mut JSContext,
1958 global: &GlobalScope,
1959 original_map: &JsonMap<String, JsonValue>,
1960 base_url: &ServoUrl,
1961) -> ModuleSpecifierMap {
1962 let mut normalized = ModuleSpecifierMap::new();
1964
1965 for (specifier_key, value) in original_map {
1967 let Some(normalized_specifier_key) =
1970 normalize_specifier_key(cx, global, specifier_key, base_url)
1971 else {
1972 continue;
1974 };
1975
1976 let JsonValue::String(value) = value else {
1978 Console::internal_warn(cx, global, "Addresses need to be strings.".to_string());
1981
1982 normalized.insert(normalized_specifier_key, None);
1984 continue;
1986 };
1987
1988 let Some(address_url) =
1990 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
1991 else {
1992 Console::internal_warn(
1996 cx,
1997 global,
1998 format!("Value failed to resolve to module specifier: {value}"),
1999 );
2000
2001 normalized.insert(normalized_specifier_key, None);
2003 continue;
2005 };
2006
2007 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2010 Console::internal_warn(
2014 cx,
2015 global,
2016 format!(
2017 "Invalid address for specifier key '{specifier_key}': {address_url}.
2018 Since specifierKey ends with a slash, the address needs to as well."
2019 ),
2020 );
2021
2022 normalized.insert(normalized_specifier_key, None);
2024 continue;
2026 }
2027
2028 normalized.insert(normalized_specifier_key, Some(address_url));
2030 }
2031
2032 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2035 normalized
2036}
2037
2038fn sort_and_normalize_scopes(
2040 cx: &mut JSContext,
2041 global: &GlobalScope,
2042 original_map: &JsonMap<String, JsonValue>,
2043 base_url: &ServoUrl,
2044) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2045 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2047
2048 for (scope_prefix, potential_specifier_map) in original_map {
2050 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2053 return Err(Error::Type(
2054 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
2055 .to_owned(),
2056 ));
2057 };
2058
2059 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2061 Console::internal_warn(
2065 cx,
2066 global,
2067 format!("Scope prefix URL was not parseable: {scope_prefix}"),
2068 );
2069 continue;
2071 };
2072
2073 let normalized_scope_prefix = scope_prefix_url;
2075
2076 let normalized_specifier_map =
2079 sort_and_normalize_module_specifier_map(cx, global, potential_specifier_map, base_url);
2080 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2081 }
2082
2083 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2086 Ok(normalized)
2087}
2088
2089fn normalize_module_integrity_map(
2091 cx: &mut JSContext,
2092 global: &GlobalScope,
2093 original_map: &JsonMap<String, JsonValue>,
2094 base_url: &ServoUrl,
2095) -> ModuleIntegrityMap {
2096 let mut normalized = ModuleIntegrityMap::new();
2098
2099 for (key, value) in original_map {
2101 let Some(resolved_url) =
2104 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
2105 else {
2106 Console::internal_warn(
2110 cx,
2111 global,
2112 format!("Key failed to resolve to module specifier: {key}"),
2113 );
2114 continue;
2116 };
2117
2118 let JsonValue::String(value) = value else {
2120 Console::internal_warn(
2123 cx,
2124 global,
2125 "Integrity metadata values need to be strings.".to_string(),
2126 );
2127 continue;
2129 };
2130
2131 normalized.insert(resolved_url, value.clone());
2133 }
2134
2135 normalized
2137}
2138
2139fn normalize_specifier_key(
2141 cx: &mut JSContext,
2142 global: &GlobalScope,
2143 specifier_key: &str,
2144 base_url: &ServoUrl,
2145) -> Option<String> {
2146 if specifier_key.is_empty() {
2148 Console::internal_warn(
2151 cx,
2152 global,
2153 "Specifier keys may not be the empty string.".to_string(),
2154 );
2155 return None;
2157 }
2158 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2160
2161 if let Some(url) = url {
2163 return Some(url.into_string());
2164 }
2165
2166 Some(specifier_key.to_string())
2168}
2169
2170fn resolve_imports_match(
2175 normalized_specifier: &str,
2176 as_url: Option<&ServoUrl>,
2177 specifier_map: &ModuleSpecifierMap,
2178) -> Fallible<Option<ServoUrl>> {
2179 for (specifier_key, resolution_result) in specifier_map {
2181 if specifier_key == normalized_specifier {
2183 if let Some(resolution_result) = resolution_result {
2184 return Ok(Some(resolution_result.clone()));
2188 } else {
2189 return Err(Error::Type(
2191 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2192 ));
2193 }
2194 }
2195
2196 if specifier_key.ends_with('\u{002f}') &&
2201 normalized_specifier.starts_with(specifier_key) &&
2202 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2203 {
2204 let Some(resolution_result) = resolution_result else {
2207 return Err(Error::Type(
2208 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2209 ));
2210 };
2211
2212 let after_prefix = normalized_specifier
2214 .strip_prefix(specifier_key)
2215 .expect("specifier_key should be the prefix of normalized_specifier");
2216
2217 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2219
2220 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2222
2223 let Ok(url) = url else {
2226 return Err(Error::Type(
2227 c"Resolution of normalizedSpecifier was blocked since
2228 the afterPrefix portion could not be URL-parsed relative to
2229 the resolutionResult mapped to by the specifierKey prefix."
2230 .to_owned(),
2231 ));
2232 };
2233
2234 if !url.as_str().starts_with(resolution_result.as_str()) {
2237 return Err(Error::Type(
2238 c"Resolution of normalizedSpecifier was blocked due to
2239 it backtracking above its prefix specifierKey."
2240 .to_owned(),
2241 ));
2242 }
2243
2244 return Ok(Some(url));
2246 }
2247 }
2248
2249 Ok(None)
2251}