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::{
39 CompileOptionsWrapper, Handle, HandleValue, ToString, transform_str_to_source_text,
40};
41use mime::Mime;
42use net_traits::blob_url_store::UrlWithBlobClaim;
43use net_traits::http_status::HttpStatus;
44use net_traits::mime_classifier::MimeClassifier;
45use net_traits::policy_container::PolicyContainer;
46use net_traits::request::{
47 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
48 RequestClient, RequestId, RequestMode,
49};
50use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
51use script_bindings::cell::DomRefCell;
52use script_bindings::cformat;
53use script_bindings::domstring::BytesView;
54use script_bindings::error::Fallible;
55use script_bindings::reflector::DomObject;
56use script_bindings::settings_stack::run_a_callback;
57use script_bindings::trace::CustomTraceable;
58use serde_json::{Map as JsonMap, Value as JsonValue};
59use servo_base::id::PipelineId;
60use servo_config::pref;
61use servo_url::{ImmutableOrigin, ServoUrl};
62
63use crate::DomTypeHolder;
64use crate::dom::bindings::conversions::SafeToJSValConvertible;
65use crate::dom::bindings::error::{
66 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
67};
68use crate::dom::bindings::inheritance::Castable;
69use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
70use crate::dom::bindings::root::DomRoot;
71use crate::dom::bindings::str::DOMString;
72use crate::dom::bindings::trace::RootedTraceableBox;
73use crate::dom::csp::{GlobalCspReporting, Violation};
74use crate::dom::globalscope::GlobalScope;
75use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
76use crate::dom::performance::performanceresourcetiming::InitiatorType;
77use crate::dom::promise::Promise;
78use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
79use crate::dom::types::{Console, DedicatedWorkerGlobalScope, WorkerGlobalScope};
80use crate::dom::window::Window;
81use crate::module_loading::{
82 LoadState, Payload, host_load_imported_module, load_requested_modules,
83};
84use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
85use crate::realms::enter_auto_realm;
86use crate::script_runtime::{CanGc, IntroductionType};
87use crate::task::NonSendTaskBox;
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.into(), global, thrown.handle_mut(), CanGc::from_cx(cx));
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_module_compile_options(cx, url, introduction_type, line_number);
316
317 let mut module_source = ModuleSource {
318 source,
319 unminified_dir: global.unminified_js_dir(),
320 external,
321 url: url.clone(),
322 };
323 crate::unminify::unminify_js(&mut module_source);
324
325 unsafe {
326 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
328 module_script.set(CompileModule1(
329 cx,
330 compile_options.ptr,
331 &mut transform_str_to_source_text(&module_source.source.str()),
332 ));
333
334 if module_script.is_null() {
336 warn!("fail to compile module script of {}", url);
337
338 let _ = module
340 .parse_error
341 .set(RethrowError::from_pending_exception(cx));
342
343 return module;
345 }
346
347 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
351
352 SetModulePrivate(
353 module_script.get(),
354 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
355 );
356
357 let _ = module.record.set(ModuleObject::new(module_script.handle()));
359 }
360
361 module
363 }
364
365 #[expect(unsafe_code)]
366 fn create_a_json_module_script(
368 cx: &mut JSContext,
369 source: &str,
370 global: &GlobalScope,
371 url: &ServoUrl,
372 introduction_type: Option<&'static CStr>,
373 ) -> Self {
374 let mut realm = AutoRealm::new(
375 cx,
376 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
377 );
378 let cx = &mut *realm;
379
380 let module = ModuleTree {
383 url: url.clone(),
384 record: OnceCell::new(),
385 parse_error: OnceCell::new(),
386 rethrow_error: DomRefCell::new(None),
387 loaded_modules: DomRefCell::new(IndexMap::new()),
388 };
389
390 let compile_options = fill_module_compile_options(cx, url, introduction_type, 1);
395
396 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
397
398 unsafe {
399 module_script.set(CompileJsonModule1(
401 cx,
402 compile_options.ptr,
403 &mut transform_str_to_source_text(source),
404 ));
405 }
406
407 if module_script.is_null() {
409 warn!("fail to compile module script of {}", url);
410
411 let _ = module
412 .parse_error
413 .set(RethrowError::from_pending_exception(cx));
414 return module;
415 }
416
417 let _ = module.record.set(ModuleObject::new(module_script.handle()));
419
420 module
422 }
423
424 #[expect(unsafe_code)]
427 pub(crate) fn execute_module(
428 &self,
429 cx: &mut JSContext,
430 global: &GlobalScope,
431 module_record: HandleObject,
432 mut eval_result: MutableHandleValue,
433 ) -> Result<(), RethrowError> {
434 let mut realm = AutoRealm::new(
435 cx,
436 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
437 );
438 let cx = &mut *realm;
439
440 unsafe {
441 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
442 assert!(ok, "module evaluation failed");
443
444 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
445 if eval_result.is_object() {
446 evaluation_promise.set(eval_result.to_object());
447 }
448
449 let throw_result = ThrowOnModuleEvaluationFailure(
450 cx,
451 evaluation_promise.handle(),
452 ModuleErrorBehaviour::ThrowModuleErrorsSync,
453 );
454 if !throw_result {
455 warn!("fail to evaluate module");
456
457 Err(RethrowError::from_pending_exception(cx))
458 } else {
459 debug!("module evaluated successfully");
460 Ok(())
461 }
462 }
463 }
464
465 #[expect(unsafe_code)]
466 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
467 let module_error = self.rethrow_error.borrow();
468
469 if let Some(exception) = &*module_error {
470 let mut realm = enter_auto_realm(cx, global);
471 let cx = &mut realm.current_realm();
472
473 unsafe {
474 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
475 }
476 report_pending_exception(cx);
477 }
478 }
479
480 pub(crate) fn resolve_module_specifier(
482 global: &GlobalScope,
483 script: Option<&ModuleScript>,
484 specifier: DOMString,
485 ) -> Fallible<ServoUrl> {
486 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
488 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
490 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
494 None => (global, &global.api_base_url()),
499 };
500
501 let import_map = if global.is::<Window>() {
505 Some(global.import_map())
506 } else {
507 None
508 };
509 let specifier = &specifier.str();
510
511 let serialized_base_url = base_url.as_str();
513 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
515 let normalized_specifier = match &as_url {
518 Some(url) => url.as_str(),
519 None => specifier,
520 };
521
522 let mut result = None;
524 if let Some(map) = import_map {
525 for (prefix, imports) in &map.scopes {
527 let prefix = prefix.as_str();
530 if prefix == serialized_base_url ||
531 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
532 {
533 let scope_imports_match =
536 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
537
538 if scope_imports_match.is_some() {
540 result = scope_imports_match;
541 break;
542 }
543 }
544 }
545
546 if result.is_none() {
549 result =
550 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
551 }
552 }
553
554 if result.is_none() {
556 result = as_url.clone();
557 }
558
559 match result {
561 Some(result) => {
562 global.add_module_to_resolved_module_set(
565 serialized_base_url,
566 normalized_specifier,
567 as_url.clone(),
568 );
569 Ok(result)
571 },
572 None => Err(Error::Type(
575 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
576 .to_owned(),
577 )),
578 }
579 }
580
581 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
583 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
585 {
586 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
588 }
589 ServoUrl::parse(specifier).ok()
591 }
592}
593
594#[derive(JSTraceable, MallocSizeOf)]
595pub(crate) struct ModuleHandler {
596 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
597 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
598}
599
600impl ModuleHandler {
601 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
602 Box::new(Self {
603 task: DomRefCell::new(Some(task)),
604 })
605 }
606}
607
608impl Callback for ModuleHandler {
609 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
610 let task = self.task.borrow_mut().take().unwrap();
611 task.run_box(cx);
612 }
613}
614
615#[derive(JSTraceable, MallocSizeOf)]
616struct QueueTaskHandler {
617 #[conditional_malloc_size_of]
618 promise: Rc<Promise>,
619}
620
621impl Callback for QueueTaskHandler {
622 fn callback(&self, cx: &mut CurrentRealm, _: HandleValue) {
623 let global = GlobalScope::from_current_realm(cx);
624 let promise = TrustedPromise::new(self.promise.clone());
625
626 global.task_manager().networking_task_source().queue(
627 task!(continue_module_loading: move |cx| {
628 promise.root().resolve_native_with_cx(cx, &());
629 }),
630 );
631 }
632}
633
634#[derive(Clone)]
635pub(crate) struct ModuleFetchClient {
636 pub insecure_requests_policy: InsecureRequestsPolicy,
637 pub has_trustworthy_ancestor_origin: bool,
638 pub policy_container: PolicyContainer,
639 pub client: RequestClient,
640 pub pipeline_id: PipelineId,
641 pub origin: ImmutableOrigin,
642}
643
644impl ModuleFetchClient {
645 pub(crate) fn from_global_scope(global: &GlobalScope) -> Self {
646 Self {
647 insecure_requests_policy: global.insecure_requests_policy(),
648 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_or_current_origin(),
649 policy_container: global.policy_container(),
650 client: global.request_client(),
651 pipeline_id: global.pipeline_id(),
652 origin: global.origin().immutable().clone(),
653 }
654 }
655}
656
657struct ModuleContext {
659 owner: Trusted<GlobalScope>,
661 data: Vec<u8>,
663 metadata: Option<Metadata>,
665 module_request: ModuleRequest,
667 options: ScriptFetchOptions,
669 status: Result<(), NetworkError>,
671 introduction_type: Option<&'static CStr>,
673 policy_container: Option<PolicyContainer>,
675}
676
677impl FetchResponseListener for ModuleContext {
678 fn process_request_body(&mut self, _: RequestId) {}
680
681 fn process_response(
682 &mut self,
683 _: &mut js::context::JSContext,
684 _: RequestId,
685 metadata: Result<FetchMetadata, NetworkError>,
686 ) {
687 self.metadata = metadata.ok().map(|meta| match meta {
688 FetchMetadata::Unfiltered(m) => m,
689 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
690 });
691
692 let status = self
693 .metadata
694 .as_ref()
695 .map(|m| m.status.clone())
696 .unwrap_or_else(HttpStatus::new_error);
697
698 self.status = {
699 if status.is_error() {
700 Err(NetworkError::ResourceLoadError(
701 "No http status code received".to_owned(),
702 ))
703 } else if status.is_success() {
704 Ok(())
705 } else {
706 Err(NetworkError::ResourceLoadError(format!(
707 "HTTP error code {}",
708 status.code()
709 )))
710 }
711 };
712 }
713
714 fn process_response_chunk(
715 &mut self,
716 _: &mut js::context::JSContext,
717 _: RequestId,
718 mut chunk: Vec<u8>,
719 ) {
720 if self.status.is_ok() {
721 self.data.append(&mut chunk);
722 }
723 }
724
725 fn process_response_eof(
728 mut self,
729 cx: &mut js::context::JSContext,
730 _: RequestId,
731 response: Result<(), NetworkError>,
732 timing: ResourceFetchTiming,
733 ) {
734 let global = self.owner.root();
735 let (_url, module_type) = &self.module_request;
736
737 network_listener::submit_timing(cx, &self, &response, &timing);
738
739 let Some(ModuleStatus::Fetching(pending)) =
740 global.get_module_map_entry(&self.module_request)
741 else {
742 return error!("Processing response for a non pending module request");
743 };
744 let promise = pending
745 .borrow_mut()
746 .take()
747 .expect("Need promise to process response");
748
749 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
752 error!("Fetching module script failed {:?}", error);
753 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
754 return promise.resolve_native_with_cx(cx, &());
755 }
756
757 let metadata = self.metadata.take().unwrap();
758
759 if let Some(policy_container) = self.policy_container {
762 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
763 "We only need a policy container when initializing a worker's globalscope.",
764 );
765 workerscope.process_response_for_workerscope(&metadata, &policy_container);
766 }
767
768 let final_url = metadata.final_url;
769
770 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
772
773 let mut module_script = None;
775
776 let referrer_policy = metadata
778 .headers
779 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
780 .into();
781
782 if referrer_policy != ReferrerPolicy::EmptyString {
784 self.options.referrer_policy = referrer_policy;
785 }
786
787 if let Some(mime) = mime_type {
793 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
795
796 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
799 matches!(module_type, ModuleType::JavaScript)
800 {
801 if let Some(window) = global.downcast::<Window>() {
802 substitute_with_local_script(window, &mut source_text, final_url.clone());
803 }
804
805 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
806 cx,
807 Rc::new(DOMString::from(source_text.clone())),
808 &global,
809 &final_url,
810 self.options,
811 true,
812 1,
813 self.introduction_type,
814 ));
815 module_script = Some(module_tree);
816 }
817
818 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
821 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
822 cx,
823 &source_text,
824 &global,
825 &final_url,
826 self.introduction_type,
827 ));
828 module_script = Some(module_tree);
829 }
830 }
831 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
833 promise.resolve_native_with_cx(cx, &());
834 }
835
836 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
837 let global = self.owner.root();
838 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
839 scope.report_csp_violations(violations);
840 } else {
841 global.report_csp_violations(violations, None, None);
842 }
843 }
844}
845
846impl ResourceTimingListener for ModuleContext {
847 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
848 let initiator_type = InitiatorType::LocalName("module".to_string());
849 let (url, _) = &self.module_request;
850 (initiator_type, url.clone())
851 }
852
853 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
854 self.owner.root()
855 }
856}
857
858#[expect(unsafe_code)]
859#[expect(non_snake_case)]
860pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
863 unsafe {
864 if GetModuleResolveHook(rt).is_some() {
865 return;
866 }
867
868 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
869 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
870 SetScriptPrivateReferenceHooks(
871 rt,
872 Some(host_add_ref_top_level_script),
873 Some(host_release_top_level_script),
874 );
875 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
876 }
877}
878
879#[expect(unsafe_code)]
880unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
881 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
882 mem::forget(val.clone());
883 mem::forget(val);
884}
885
886#[expect(unsafe_code)]
887unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
888 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
889}
890
891#[expect(unsafe_code)]
892pub(crate) unsafe extern "C" fn host_import_module_dynamically(
895 cx: *mut RawJSContext,
896 reference_private: RawHandleValue,
897 specifier: RawHandle<*mut JSObject>,
898 promise: RawHandle<*mut JSObject>,
899) -> bool {
900 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
902 let cx = &mut cx;
903 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx.into());
904
905 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
906 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
907 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
908
909 let mut realm = CurrentRealm::assert(cx);
910 let payload = Payload::PromiseRecord(promise);
911 host_load_imported_module(
912 &mut realm,
913 None,
914 reference_private,
915 specifier,
916 module_type,
917 None,
918 payload,
919 );
920
921 true
922}
923
924#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
925pub(crate) struct ScriptFetchOptions {
927 pub(crate) integrity_metadata: String,
928 #[no_trace]
929 pub(crate) credentials_mode: CredentialsMode,
930 pub(crate) cryptographic_nonce: String,
931 #[no_trace]
932 pub(crate) parser_metadata: ParserMetadata,
933 #[no_trace]
934 pub(crate) referrer_policy: ReferrerPolicy,
935 pub(crate) render_blocking: bool,
939}
940
941impl ScriptFetchOptions {
942 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
944 Self {
945 cryptographic_nonce: String::new(),
946 integrity_metadata: String::new(),
947 parser_metadata: ParserMetadata::NotParserInserted,
948 credentials_mode: CredentialsMode::CredentialsSameOrigin,
949 referrer_policy: ReferrerPolicy::EmptyString,
950 render_blocking: false,
951 }
952 }
953
954 pub(crate) fn descendant_fetch_options(
956 &self,
957 url: &ServoUrl,
958 global: &GlobalScope,
959 ) -> ScriptFetchOptions {
960 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
962
963 Self {
966 integrity_metadata: integrity,
968 cryptographic_nonce: self.cryptographic_nonce.clone(),
969 credentials_mode: self.credentials_mode,
970 parser_metadata: self.parser_metadata,
971 referrer_policy: self.referrer_policy,
972 render_blocking: self.render_blocking,
973 }
974 }
975}
976
977#[expect(unsafe_code)]
978pub(crate) unsafe fn module_script_from_reference_private(
979 reference_private: &RawHandle<JSVal>,
980) -> Option<&ModuleScript> {
981 if reference_private.get().is_undefined() {
982 return None;
983 }
984 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
985}
986
987#[expect(unsafe_code)]
988#[expect(non_snake_case)]
989unsafe extern "C" fn HostResolveImportedModule(
992 cx: *mut RawJSContext,
993 reference_private: RawHandleValue,
994 specifier: RawHandle<*mut JSObject>,
995) -> *mut JSObject {
996 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
998 let mut realm = CurrentRealm::assert(&mut cx);
999 let global_scope = GlobalScope::from_current_realm(&realm);
1000
1001 let cx = &mut realm;
1002
1003 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1005 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1006 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1007
1008 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
1009 let url = ModuleTree::resolve_module_specifier(
1010 &global_scope,
1011 module_data,
1012 DOMString::from(specifier),
1013 );
1014
1015 assert!(url.is_ok());
1017
1018 let parsed_url = url.unwrap();
1019
1020 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1022
1023 assert!(module.as_ref().is_some_and(
1025 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1026 ));
1027
1028 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1029 unreachable!()
1030 };
1031
1032 let fetched_module_object = module_tree.get_record();
1033
1034 assert!(fetched_module_object.is_some());
1036
1037 if let Some(record) = fetched_module_object {
1039 return record.handle().get();
1040 }
1041
1042 unreachable!()
1043}
1044
1045const SLOT_MODULEPRIVATE: usize = 0;
1047
1048#[expect(unsafe_code)]
1049#[expect(non_snake_case)]
1050unsafe extern "C" fn HostPopulateImportMeta(
1053 cx: *mut RawJSContext,
1054 reference_private: RawHandleValue,
1055 meta_object: RawHandle<*mut JSObject>,
1056) -> bool {
1057 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1059 let realm = CurrentRealm::assert(&mut cx);
1060 let global_scope = GlobalScope::from_current_realm(&realm);
1061
1062 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1064 Some(module_data) => module_data.base_url.clone(),
1065 None => global_scope.api_base_url(),
1066 };
1067
1068 unsafe {
1069 let url_string = JS_NewStringCopyN(
1070 &mut cx,
1071 base_url.as_str().as_ptr() as *const _,
1072 base_url.as_str().len(),
1073 );
1074 rooted!(&in(cx) let url_string = url_string);
1075
1076 if !JS_DefineProperty4(
1078 &mut cx,
1079 Handle::from_raw(meta_object),
1080 c"url".as_ptr(),
1081 url_string.handle(),
1082 JSPROP_ENUMERATE.into(),
1083 ) {
1084 return false;
1085 }
1086
1087 let resolve_function = DefineFunctionWithReserved(
1089 &mut cx,
1090 meta_object.get(),
1091 c"resolve".as_ptr(),
1092 Some(import_meta_resolve),
1093 1,
1094 JSPROP_ENUMERATE.into(),
1095 );
1096
1097 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1098 assert!(!obj.is_null());
1099 SetFunctionNativeReserved(
1100 obj.get(),
1101 SLOT_MODULEPRIVATE,
1102 &reference_private.get() as *const _,
1103 );
1104 }
1105
1106 true
1107}
1108
1109#[expect(unsafe_code)]
1110unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1111 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1113 let mut realm = CurrentRealm::assert(&mut cx);
1114 let global_scope = GlobalScope::from_current_realm(&realm);
1115
1116 let cx = &mut realm;
1117
1118 let args = unsafe { CallArgs::from_vp(vp, argc) };
1119
1120 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1121 let reference_private = module_private.handle().into();
1122 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1123
1124 let specifier = unsafe {
1128 let value = HandleValue::from_raw(args.get(0));
1129
1130 match NonNull::new(ToString(cx, value)) {
1131 Some(jsstr) => jsstr_to_string(cx, jsstr).into(),
1132 None => return false,
1133 }
1134 };
1135
1136 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1138
1139 match url {
1140 Ok(url) => {
1141 url.as_str().safe_to_jsval(
1143 cx.into(),
1144 unsafe { MutableHandleValue::from_raw(args.rval()) },
1145 CanGc::from_cx(cx),
1146 );
1147 true
1148 },
1149 Err(error) => {
1150 let resolution_error = gen_type_error(cx, &global_scope, error);
1151
1152 unsafe {
1153 JS_SetPendingException(
1154 cx,
1155 resolution_error.handle(),
1156 ExceptionStackBehavior::Capture,
1157 );
1158 }
1159 false
1160 },
1161 }
1162}
1163
1164#[expect(clippy::too_many_arguments)]
1165pub(crate) fn fetch_a_module_worker_script_graph(
1168 cx: &mut JSContext,
1169 global: &GlobalScope,
1170 url: ServoUrl,
1171 fetch_client: ModuleFetchClient,
1172 destination: Destination,
1173 referrer: Referrer,
1174 credentials_mode: CredentialsMode,
1175 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1176) {
1177 let global_scope = DomRoot::from_ref(global);
1178
1179 let options = ScriptFetchOptions {
1184 integrity_metadata: "".into(),
1185 credentials_mode,
1186 cryptographic_nonce: "".into(),
1187 parser_metadata: ParserMetadata::NotParserInserted,
1188 referrer_policy: ReferrerPolicy::EmptyString,
1189 render_blocking: false,
1190 };
1191
1192 fetch_a_single_module_script(
1195 cx,
1196 url,
1197 fetch_client.clone(),
1198 global,
1199 destination,
1200 options,
1201 referrer,
1202 None,
1203 true,
1204 Some(IntroductionType::WORKER),
1205 move |cx, module_tree| {
1206 let Some(module) = module_tree else {
1207 return on_complete(cx, None);
1209 };
1210
1211 fetch_the_descendants_and_link_module_script(
1214 cx,
1215 &global_scope,
1216 module,
1217 fetch_client,
1218 destination,
1219 on_complete,
1220 );
1221 },
1222 );
1223}
1224
1225pub(crate) fn fetch_an_external_module_script(
1227 cx: &mut JSContext,
1228 url: ServoUrl,
1229 global: &GlobalScope,
1230 options: ScriptFetchOptions,
1231 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1232) {
1233 let referrer = global.get_referrer();
1234 let fetch_client = ModuleFetchClient::from_global_scope(global);
1235 let global_scope = DomRoot::from_ref(global);
1236
1237 fetch_a_single_module_script(
1240 cx,
1241 url,
1242 fetch_client.clone(),
1243 global,
1244 Destination::Script,
1245 options,
1246 referrer,
1247 None,
1248 true,
1249 Some(IntroductionType::SRC_SCRIPT),
1250 move |cx, module_tree| {
1251 let Some(module) = module_tree else {
1252 return on_complete(cx, None);
1254 };
1255
1256 fetch_the_descendants_and_link_module_script(
1258 cx,
1259 &global_scope,
1260 module,
1261 fetch_client,
1262 Destination::Script,
1263 on_complete,
1264 );
1265 },
1266 );
1267}
1268
1269pub(crate) fn fetch_a_modulepreload_module(
1271 cx: &mut JSContext,
1272 url: ServoUrl,
1273 destination: Destination,
1274 global: &GlobalScope,
1275 options: ScriptFetchOptions,
1276 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1277) {
1278 let referrer = global.get_referrer();
1279 let fetch_client = ModuleFetchClient::from_global_scope(global);
1280 let global_scope = DomRoot::from_ref(global);
1281
1282 let module_type = if let Destination::Json = destination {
1285 Some(ModuleType::JSON)
1286 } else {
1287 None
1288 };
1289
1290 fetch_a_single_module_script(
1293 cx,
1294 url,
1295 fetch_client.clone(),
1296 global,
1297 destination,
1298 options,
1299 referrer,
1300 module_type,
1301 true,
1302 Some(IntroductionType::SRC_SCRIPT),
1303 move |cx, result| {
1304 on_complete(cx, result.is_none());
1306
1307 assert!(global_scope.is::<Window>());
1309
1310 if pref!(dom_allow_preloading_module_descendants) &&
1313 let Some(module) = result
1314 {
1315 fetch_the_descendants_and_link_module_script(
1316 cx,
1317 &global_scope,
1318 module,
1319 fetch_client,
1320 destination,
1321 |_, _| {},
1322 );
1323 }
1324 },
1325 );
1326}
1327
1328#[expect(clippy::too_many_arguments)]
1329pub(crate) fn fetch_inline_module_script(
1331 cx: &mut JSContext,
1332 global: &GlobalScope,
1333 module_script_text: Rc<DOMString>,
1334 url: ServoUrl,
1335 options: ScriptFetchOptions,
1336 line_number: u32,
1337 introduction_type: Option<&'static CStr>,
1338 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1339) {
1340 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1342 cx,
1343 module_script_text,
1344 global,
1345 &url,
1346 options,
1347 false,
1348 line_number,
1349 introduction_type,
1350 ));
1351 let fetch_client = ModuleFetchClient::from_global_scope(global);
1352
1353 fetch_the_descendants_and_link_module_script(
1355 cx,
1356 global,
1357 module_tree,
1358 fetch_client,
1359 Destination::Script,
1360 on_complete,
1361 );
1362}
1363
1364#[expect(unsafe_code)]
1365fn fetch_the_descendants_and_link_module_script(
1367 cx: &mut JSContext,
1368 global: &GlobalScope,
1369 module_script: Rc<ModuleTree>,
1370 fetch_client: ModuleFetchClient,
1371 destination: Destination,
1372 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1373) {
1374 if module_script.get_record().is_none() {
1377 let parse_error = module_script.get_parse_error().cloned();
1378
1379 module_script.set_rethrow_error(parse_error.unwrap());
1381
1382 on_complete(cx, Some(module_script));
1384
1385 return;
1387 }
1388
1389 let state = Rc::new(LoadState {
1392 error_to_rethrow: RefCell::new(None),
1393 destination,
1394 fetch_client,
1395 });
1396
1397 let mut realm = enter_auto_realm(cx, global);
1400 let cx = &mut realm.current_realm();
1401
1402 let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
1404
1405 let global_scope = DomRoot::from_ref(global);
1406 let fulfilled_module = module_script.clone();
1407 let fulfilled_on_complete = on_complete.clone();
1408
1409 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1411 task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
1412 let mut realm = AutoRealm::new(
1413 cx,
1414 NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
1415 );
1416 let cx = &mut *realm;
1417
1418 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1419
1420 let link = unsafe { ModuleLink(cx, handle) };
1422
1423 if !link {
1425 let exception = RethrowError::from_pending_exception(cx);
1426 fulfilled_module.set_rethrow_error(exception);
1427 }
1428
1429 fulfilled_on_complete(cx, Some(fulfilled_module));
1431 }),
1432 ));
1433
1434 let loading_promise_rejection =
1436 ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
1437 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1440 module_script.set_rethrow_error(error.clone());
1441 on_complete(cx, Some(module_script));
1442 } else {
1443 on_complete(cx, None);
1445 }
1446 })));
1447
1448 let handler = PromiseNativeHandler::new(
1449 cx,
1450 global,
1451 Some(loading_promise_fulfillment),
1452 Some(loading_promise_rejection),
1453 );
1454
1455 run_a_callback::<DomTypeHolder, _>(global, || {
1456 loading_promise.append_native_handler(cx, &handler);
1457 });
1458}
1459
1460#[expect(clippy::too_many_arguments)]
1462pub(crate) fn fetch_a_single_module_script(
1463 cx: &mut JSContext,
1464 url: ServoUrl,
1465 fetch_client: ModuleFetchClient,
1466 global: &GlobalScope,
1467 destination: Destination,
1468 options: ScriptFetchOptions,
1469 referrer: Referrer,
1470 module_type: Option<ModuleType>,
1471 is_top_level: bool,
1472 introduction_type: Option<&'static CStr>,
1473 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1474) {
1475 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1479
1480 let module_request = (url.clone(), module_type);
1486 let entry = global.get_module_map_entry(&module_request);
1487
1488 let pending = match entry {
1489 Some(ModuleStatus::Fetching(pending)) => pending,
1490 Some(ModuleStatus::Loaded(module_tree)) => {
1492 return on_complete(cx, module_tree);
1493 },
1494 None => DomRefCell::new(None),
1495 };
1496
1497 let global_scope = DomRoot::from_ref(global);
1498 let module_map_key = module_request.clone();
1499 let handler = ModuleHandler::new_boxed(Box::new(
1500 task!(fetch_completed: |cx, global_scope: DomRoot<GlobalScope>| {
1501 let key = module_map_key;
1502 let module = global_scope.get_module_map_entry(&key);
1503
1504 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1505 on_complete(cx, module_tree);
1506 }
1507 }),
1508 ));
1509
1510 let handler = PromiseNativeHandler::new(cx, global, Some(handler), None);
1511
1512 let mut realm = enter_auto_realm(cx, global);
1513 let cx = &mut realm.current_realm();
1514
1515 run_a_callback::<DomTypeHolder, _>(global, || {
1516 let has_pending_fetch = pending.borrow().is_some();
1517
1518 let promise = Promise::new_in_realm(cx);
1519
1520 if has_pending_fetch {
1523 promise.append_native_handler(cx, &handler);
1524
1525 let continue_loading_handler = PromiseNativeHandler::new(
1528 cx,
1529 global,
1530 Some(Box::new(QueueTaskHandler { promise })),
1531 None,
1532 );
1533
1534 let pending_promise = pending.borrow_mut().take();
1536 if let Some(promise) = pending_promise {
1537 promise.append_native_handler(cx, &continue_loading_handler);
1538 let _ = pending.borrow_mut().insert(promise);
1539 }
1540 return;
1541 }
1542
1543 promise.append_native_handler(cx, &handler);
1544
1545 let prev = pending.borrow_mut().replace(promise);
1546 assert!(prev.is_none());
1547
1548 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1550
1551 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1553 .then(|| fetch_client.policy_container.clone());
1554
1555 let webview_id = global.webview_id();
1556
1557 let mode = match destination {
1562 Destination::Worker | Destination::SharedWorker if is_top_level => {
1563 RequestMode::SameOrigin
1564 },
1565 _ => RequestMode::CorsMode,
1566 };
1567
1568 let destination = match module_type {
1571 ModuleType::JSON => Destination::Json,
1572 ModuleType::JavaScript | ModuleType::Unknown => destination,
1573 };
1574
1575 let request = RequestBuilder::new(
1579 webview_id,
1580 UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone()),
1581 referrer,
1582 )
1583 .destination(destination)
1584 .parser_metadata(options.parser_metadata)
1585 .integrity_metadata(options.integrity_metadata.clone())
1586 .credentials_mode(options.credentials_mode)
1587 .referrer_policy(options.referrer_policy)
1588 .mode(mode)
1589 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1590 .insecure_requests_policy(fetch_client.insecure_requests_policy)
1591 .has_trustworthy_ancestor_origin(fetch_client.has_trustworthy_ancestor_origin)
1592 .policy_container(fetch_client.policy_container)
1593 .client(fetch_client.client)
1594 .pipeline_id(Some(fetch_client.pipeline_id))
1595 .origin(fetch_client.origin);
1596
1597 let context = ModuleContext {
1598 owner: Trusted::new(global),
1599 data: vec![],
1600 metadata: None,
1601 module_request,
1602 options,
1603 status: Ok(()),
1604 introduction_type,
1605 policy_container,
1606 };
1607
1608 let task_source = global.task_manager().networking_task_source().to_sendable();
1609 global.fetch(request, context, task_source);
1610 })
1611}
1612
1613fn fill_module_compile_options(
1614 cx: &mut JSContext,
1615 url: &ServoUrl,
1616 introduction_type: Option<&'static CStr>,
1617 line_number: u32,
1618) -> CompileOptionsWrapper {
1619 let mut options = CompileOptionsWrapper::new(cx, cformat!("{url}"), line_number);
1620 if let Some(introduction_type) = introduction_type {
1621 options.set_introduction_type(introduction_type);
1622 }
1623
1624 options.set_muted_errors(false);
1626
1627 options.set_is_run_once(true);
1629 options.set_no_script_rval(true);
1630
1631 options
1632}
1633
1634pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1635pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1636
1637#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1639pub(crate) struct ResolvedModule {
1640 base_url: String,
1642 specifier: String,
1644 #[no_trace]
1646 specifier_url: Option<ServoUrl>,
1647}
1648
1649impl ResolvedModule {
1650 pub(crate) fn new(
1651 base_url: String,
1652 specifier: String,
1653 specifier_url: Option<ServoUrl>,
1654 ) -> Self {
1655 Self {
1656 base_url,
1657 specifier,
1658 specifier_url,
1659 }
1660 }
1661}
1662
1663#[derive(Default, JSTraceable, MallocSizeOf)]
1665pub(crate) struct ImportMap {
1666 #[no_trace]
1667 imports: ModuleSpecifierMap,
1668 #[no_trace]
1669 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1670 #[no_trace]
1671 integrity: ModuleIntegrityMap,
1672}
1673
1674impl ImportMap {
1675 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1677 self.integrity.get(url).cloned().unwrap_or_default()
1682 }
1683}
1684
1685pub(crate) fn register_import_map(
1687 cx: &mut JSContext,
1688 global: &GlobalScope,
1689 result: Fallible<ImportMap>,
1690) {
1691 match result {
1692 Ok(new_import_map) => {
1693 merge_existing_and_new_import_maps(cx, global, new_import_map);
1695 },
1696 Err(exception) => {
1697 let mut realm = enter_auto_realm(cx, global);
1698 let cx = &mut realm.current_realm();
1699
1700 throw_dom_exception(cx.into(), global, exception, CanGc::from_cx(cx));
1703 report_pending_exception(cx);
1704 },
1705 }
1706}
1707
1708fn merge_existing_and_new_import_maps(
1710 cx: &mut JSContext,
1711 global: &GlobalScope,
1712 new_import_map: ImportMap,
1713) {
1714 let new_import_map_scopes = new_import_map.scopes;
1716
1717 let mut old_import_map = global.import_map_mut();
1719
1720 let mut new_import_map_imports = new_import_map.imports;
1722
1723 let resolved_module_set = global.resolved_module_set();
1724 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1726 for record in resolved_module_set.iter() {
1728 let prefix = scope_prefix.as_str();
1731 if prefix == record.base_url ||
1732 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1733 {
1734 scope_imports.retain(|key, val| {
1736 if *key == record.specifier ||
1741 (key.ends_with('\u{002f}') &&
1742 record.specifier.starts_with(key) &&
1743 (record.specifier_url.is_none() ||
1744 record
1745 .specifier_url
1746 .as_ref()
1747 .is_some_and(|u| u.is_special_scheme())))
1748 {
1749 Console::internal_warn(
1752 cx,
1753 global,
1754 format!("Ignored rule: {key} -> {val:?}."),
1755 );
1756 false
1758 } else {
1759 true
1760 }
1761 })
1762 }
1763 }
1764
1765 if old_import_map.scopes.contains_key(&scope_prefix) {
1767 let merged_module_specifier_map = merge_module_specifier_maps(
1770 cx,
1771 global,
1772 scope_imports,
1773 &old_import_map.scopes[&scope_prefix],
1774 );
1775 old_import_map
1776 .scopes
1777 .insert(scope_prefix, merged_module_specifier_map);
1778 } else {
1779 old_import_map.scopes.insert(scope_prefix, scope_imports);
1781 }
1782 }
1783
1784 for (url, integrity) in &new_import_map.integrity {
1786 if old_import_map.integrity.contains_key(url) {
1788 Console::internal_warn(cx, global, format!("Ignored rule: {url} -> {integrity}."));
1791 continue;
1793 }
1794
1795 old_import_map
1797 .integrity
1798 .insert(url.clone(), integrity.clone());
1799 }
1800
1801 for record in resolved_module_set.iter() {
1803 new_import_map_imports.retain(|specifier, val| {
1805 if record.specifier.starts_with(specifier) {
1810 Console::internal_warn(
1813 cx,
1814 global,
1815 format!("Ignored rule: {specifier} -> {val:?}."),
1816 );
1817 false
1819 } else {
1820 true
1821 }
1822 });
1823 }
1824
1825 let merged_module_specifier_map =
1828 merge_module_specifier_maps(cx, global, new_import_map_imports, &old_import_map.imports);
1829 old_import_map.imports = merged_module_specifier_map;
1830
1831 old_import_map
1834 .scopes
1835 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1836}
1837
1838fn merge_module_specifier_maps(
1840 cx: &mut JSContext,
1841 global: &GlobalScope,
1842 new_map: ModuleSpecifierMap,
1843 old_map: &ModuleSpecifierMap,
1844) -> ModuleSpecifierMap {
1845 let mut merged_map = old_map.clone();
1847
1848 for (specifier, url) in new_map {
1850 if old_map.contains_key(&specifier) {
1852 Console::internal_warn(cx, global, format!("Ignored rule: {specifier} -> {url:?}."));
1855
1856 continue;
1858 }
1859
1860 merged_map.insert(specifier, url);
1862 }
1863
1864 merged_map
1865}
1866
1867pub(crate) fn parse_an_import_map_string(
1869 cx: &mut JSContext,
1870 global: &GlobalScope,
1871 input: Rc<DOMString>,
1872 base_url: ServoUrl,
1873) -> Fallible<ImportMap> {
1874 let parsed: JsonValue = serde_json::from_str(&input.str())
1876 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1877 let JsonValue::Object(mut parsed) = parsed else {
1880 return Err(Error::Type(
1881 c"The top-level value needs to be a JSON object.".to_owned(),
1882 ));
1883 };
1884
1885 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1887 if let Some(imports) = parsed.get("imports") {
1889 let JsonValue::Object(imports) = imports else {
1892 return Err(Error::Type(
1893 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1894 ));
1895 };
1896 sorted_and_normalized_imports =
1899 sort_and_normalize_module_specifier_map(cx, global, imports, &base_url);
1900 }
1901
1902 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1904 if let Some(scopes) = parsed.get("scopes") {
1906 let JsonValue::Object(scopes) = scopes else {
1909 return Err(Error::Type(
1910 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1911 ));
1912 };
1913 sorted_and_normalized_scopes = sort_and_normalize_scopes(cx, global, scopes, &base_url)?;
1916 }
1917
1918 let mut normalized_integrity = ModuleIntegrityMap::new();
1920 if let Some(integrity) = parsed.get("integrity") {
1922 let JsonValue::Object(integrity) = integrity else {
1925 return Err(Error::Type(
1926 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1927 ));
1928 };
1929 normalized_integrity = normalize_module_integrity_map(cx, global, integrity, &base_url);
1932 }
1933
1934 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1938 if !parsed.is_empty() {
1939 Console::internal_warn(
1940 cx,
1941 global,
1942 "Invalid top-level key was present in the import map.
1943 Only \"imports\", \"scopes\", and \"integrity\" are allowed."
1944 .to_string(),
1945 );
1946 }
1947
1948 Ok(ImportMap {
1950 imports: sorted_and_normalized_imports,
1951 scopes: sorted_and_normalized_scopes,
1952 integrity: normalized_integrity,
1953 })
1954}
1955
1956fn sort_and_normalize_module_specifier_map(
1958 cx: &mut JSContext,
1959 global: &GlobalScope,
1960 original_map: &JsonMap<String, JsonValue>,
1961 base_url: &ServoUrl,
1962) -> ModuleSpecifierMap {
1963 let mut normalized = ModuleSpecifierMap::new();
1965
1966 for (specifier_key, value) in original_map {
1968 let Some(normalized_specifier_key) =
1971 normalize_specifier_key(cx, global, specifier_key, base_url)
1972 else {
1973 continue;
1975 };
1976
1977 let JsonValue::String(value) = value else {
1979 Console::internal_warn(cx, global, "Addresses need to be strings.".to_string());
1982
1983 normalized.insert(normalized_specifier_key, None);
1985 continue;
1987 };
1988
1989 let Some(address_url) =
1991 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
1992 else {
1993 Console::internal_warn(
1997 cx,
1998 global,
1999 format!("Value failed to resolve to module specifier: {value}"),
2000 );
2001
2002 normalized.insert(normalized_specifier_key, None);
2004 continue;
2006 };
2007
2008 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2011 Console::internal_warn(
2015 cx,
2016 global,
2017 format!(
2018 "Invalid address for specifier key '{specifier_key}': {address_url}.
2019 Since specifierKey ends with a slash, the address needs to as well."
2020 ),
2021 );
2022
2023 normalized.insert(normalized_specifier_key, None);
2025 continue;
2027 }
2028
2029 normalized.insert(normalized_specifier_key, Some(address_url));
2031 }
2032
2033 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2036 normalized
2037}
2038
2039fn sort_and_normalize_scopes(
2041 cx: &mut JSContext,
2042 global: &GlobalScope,
2043 original_map: &JsonMap<String, JsonValue>,
2044 base_url: &ServoUrl,
2045) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2046 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2048
2049 for (scope_prefix, potential_specifier_map) in original_map {
2051 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2054 return Err(Error::Type(
2055 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
2056 .to_owned(),
2057 ));
2058 };
2059
2060 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2062 Console::internal_warn(
2066 cx,
2067 global,
2068 format!("Scope prefix URL was not parseable: {scope_prefix}"),
2069 );
2070 continue;
2072 };
2073
2074 let normalized_scope_prefix = scope_prefix_url;
2076
2077 let normalized_specifier_map =
2080 sort_and_normalize_module_specifier_map(cx, global, potential_specifier_map, base_url);
2081 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2082 }
2083
2084 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2087 Ok(normalized)
2088}
2089
2090fn normalize_module_integrity_map(
2092 cx: &mut JSContext,
2093 global: &GlobalScope,
2094 original_map: &JsonMap<String, JsonValue>,
2095 base_url: &ServoUrl,
2096) -> ModuleIntegrityMap {
2097 let mut normalized = ModuleIntegrityMap::new();
2099
2100 for (key, value) in original_map {
2102 let Some(resolved_url) =
2105 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
2106 else {
2107 Console::internal_warn(
2111 cx,
2112 global,
2113 format!("Key failed to resolve to module specifier: {key}"),
2114 );
2115 continue;
2117 };
2118
2119 let JsonValue::String(value) = value else {
2121 Console::internal_warn(
2124 cx,
2125 global,
2126 "Integrity metadata values need to be strings.".to_string(),
2127 );
2128 continue;
2130 };
2131
2132 normalized.insert(resolved_url, value.clone());
2134 }
2135
2136 normalized
2138}
2139
2140fn normalize_specifier_key(
2142 cx: &mut JSContext,
2143 global: &GlobalScope,
2144 specifier_key: &str,
2145 base_url: &ServoUrl,
2146) -> Option<String> {
2147 if specifier_key.is_empty() {
2149 Console::internal_warn(
2152 cx,
2153 global,
2154 "Specifier keys may not be the empty string.".to_string(),
2155 );
2156 return None;
2158 }
2159 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2161
2162 if let Some(url) = url {
2164 return Some(url.into_string());
2165 }
2166
2167 Some(specifier_key.to_string())
2169}
2170
2171fn resolve_imports_match(
2176 normalized_specifier: &str,
2177 as_url: Option<&ServoUrl>,
2178 specifier_map: &ModuleSpecifierMap,
2179) -> Fallible<Option<ServoUrl>> {
2180 for (specifier_key, resolution_result) in specifier_map {
2182 if specifier_key == normalized_specifier {
2184 if let Some(resolution_result) = resolution_result {
2185 return Ok(Some(resolution_result.clone()));
2189 } else {
2190 return Err(Error::Type(
2192 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2193 ));
2194 }
2195 }
2196
2197 if specifier_key.ends_with('\u{002f}') &&
2202 normalized_specifier.starts_with(specifier_key) &&
2203 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2204 {
2205 let Some(resolution_result) = resolution_result else {
2208 return Err(Error::Type(
2209 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2210 ));
2211 };
2212
2213 let after_prefix = normalized_specifier
2215 .strip_prefix(specifier_key)
2216 .expect("specifier_key should be the prefix of normalized_specifier");
2217
2218 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2220
2221 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2223
2224 let Ok(url) = url else {
2227 return Err(Error::Type(
2228 c"Resolution of normalizedSpecifier was blocked since
2229 the afterPrefix portion could not be URL-parsed relative to
2230 the resolutionResult mapped to by the specifierKey prefix."
2231 .to_owned(),
2232 ));
2233 };
2234
2235 if !url.as_str().starts_with(resolution_result.as_str()) {
2238 return Err(Error::Type(
2239 c"Resolution of normalizedSpecifier was blocked due to
2240 it backtracking above its prefix specifierKey."
2241 .to_owned(),
2242 ));
2243 }
2244
2245 return Ok(Some(url));
2247 }
2248 }
2249
2250 Ok(None)
2252}