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::{InRealm, 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 let in_realm_proof = cx.into();
474 let in_realm = InRealm::Already(&in_realm_proof);
475
476 unsafe {
477 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
478 }
479 report_pending_exception(cx.into(), in_realm, CanGc::from_cx(cx));
480 }
481 }
482
483 pub(crate) fn resolve_module_specifier(
485 global: &GlobalScope,
486 script: Option<&ModuleScript>,
487 specifier: DOMString,
488 ) -> Fallible<ServoUrl> {
489 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
491 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
493 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
497 None => (global, &global.api_base_url()),
502 };
503
504 let import_map = if global.is::<Window>() {
508 Some(global.import_map())
509 } else {
510 None
511 };
512 let specifier = &specifier.str();
513
514 let serialized_base_url = base_url.as_str();
516 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
518 let normalized_specifier = match &as_url {
521 Some(url) => url.as_str(),
522 None => specifier,
523 };
524
525 let mut result = None;
527 if let Some(map) = import_map {
528 for (prefix, imports) in &map.scopes {
530 let prefix = prefix.as_str();
533 if prefix == serialized_base_url ||
534 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
535 {
536 let scope_imports_match =
539 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
540
541 if scope_imports_match.is_some() {
543 result = scope_imports_match;
544 break;
545 }
546 }
547 }
548
549 if result.is_none() {
552 result =
553 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
554 }
555 }
556
557 if result.is_none() {
559 result = as_url.clone();
560 }
561
562 match result {
564 Some(result) => {
565 global.add_module_to_resolved_module_set(
568 serialized_base_url,
569 normalized_specifier,
570 as_url.clone(),
571 );
572 Ok(result)
574 },
575 None => Err(Error::Type(
578 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
579 .to_owned(),
580 )),
581 }
582 }
583
584 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
586 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
588 {
589 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
591 }
592 ServoUrl::parse(specifier).ok()
594 }
595}
596
597#[derive(JSTraceable, MallocSizeOf)]
598pub(crate) struct ModuleHandler {
599 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
600 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
601}
602
603impl ModuleHandler {
604 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
605 Box::new(Self {
606 task: DomRefCell::new(Some(task)),
607 })
608 }
609}
610
611impl Callback for ModuleHandler {
612 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
613 let task = self.task.borrow_mut().take().unwrap();
614 task.run_box(cx);
615 }
616}
617
618#[derive(JSTraceable, MallocSizeOf)]
619struct QueueTaskHandler {
620 #[conditional_malloc_size_of]
621 promise: Rc<Promise>,
622}
623
624impl Callback for QueueTaskHandler {
625 fn callback(&self, cx: &mut CurrentRealm, _: HandleValue) {
626 let global = GlobalScope::from_current_realm(cx);
627 let promise = TrustedPromise::new(self.promise.clone());
628
629 global.task_manager().networking_task_source().queue(
630 task!(continue_module_loading: move |cx| {
631 promise.root().resolve_native(&(), CanGc::from_cx(cx));
632 }),
633 );
634 }
635}
636
637#[derive(Clone)]
638pub(crate) struct ModuleFetchClient {
639 pub insecure_requests_policy: InsecureRequestsPolicy,
640 pub has_trustworthy_ancestor_origin: bool,
641 pub policy_container: PolicyContainer,
642 pub client: RequestClient,
643 pub pipeline_id: PipelineId,
644 pub origin: ImmutableOrigin,
645}
646
647impl ModuleFetchClient {
648 pub(crate) fn from_global_scope(global: &GlobalScope) -> Self {
649 Self {
650 insecure_requests_policy: global.insecure_requests_policy(),
651 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_or_current_origin(),
652 policy_container: global.policy_container(),
653 client: global.request_client(),
654 pipeline_id: global.pipeline_id(),
655 origin: global.origin().immutable().clone(),
656 }
657 }
658}
659
660struct ModuleContext {
662 owner: Trusted<GlobalScope>,
664 data: Vec<u8>,
666 metadata: Option<Metadata>,
668 module_request: ModuleRequest,
670 options: ScriptFetchOptions,
672 status: Result<(), NetworkError>,
674 introduction_type: Option<&'static CStr>,
676 policy_container: Option<PolicyContainer>,
678}
679
680impl FetchResponseListener for ModuleContext {
681 fn process_request_body(&mut self, _: RequestId) {}
683
684 fn process_response(
685 &mut self,
686 _: &mut js::context::JSContext,
687 _: RequestId,
688 metadata: Result<FetchMetadata, NetworkError>,
689 ) {
690 self.metadata = metadata.ok().map(|meta| match meta {
691 FetchMetadata::Unfiltered(m) => m,
692 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
693 });
694
695 let status = self
696 .metadata
697 .as_ref()
698 .map(|m| m.status.clone())
699 .unwrap_or_else(HttpStatus::new_error);
700
701 self.status = {
702 if status.is_error() {
703 Err(NetworkError::ResourceLoadError(
704 "No http status code received".to_owned(),
705 ))
706 } else if status.is_success() {
707 Ok(())
708 } else {
709 Err(NetworkError::ResourceLoadError(format!(
710 "HTTP error code {}",
711 status.code()
712 )))
713 }
714 };
715 }
716
717 fn process_response_chunk(
718 &mut self,
719 _: &mut js::context::JSContext,
720 _: RequestId,
721 mut chunk: Vec<u8>,
722 ) {
723 if self.status.is_ok() {
724 self.data.append(&mut chunk);
725 }
726 }
727
728 fn process_response_eof(
731 mut self,
732 cx: &mut js::context::JSContext,
733 _: RequestId,
734 response: Result<(), NetworkError>,
735 timing: ResourceFetchTiming,
736 ) {
737 let global = self.owner.root();
738 let (_url, module_type) = &self.module_request;
739
740 network_listener::submit_timing(cx, &self, &response, &timing);
741
742 let Some(ModuleStatus::Fetching(pending)) =
743 global.get_module_map_entry(&self.module_request)
744 else {
745 return error!("Processing response for a non pending module request");
746 };
747 let promise = pending
748 .borrow_mut()
749 .take()
750 .expect("Need promise to process response");
751
752 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
755 error!("Fetching module script failed {:?}", error);
756 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
757 return promise.resolve_native(&(), CanGc::from_cx(cx));
758 }
759
760 let metadata = self.metadata.take().unwrap();
761
762 if let Some(policy_container) = self.policy_container {
765 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
766 "We only need a policy container when initializing a worker's globalscope.",
767 );
768 workerscope.process_response_for_workerscope(&metadata, &policy_container);
769 }
770
771 let final_url = metadata.final_url;
772
773 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
775
776 let mut module_script = None;
778
779 let referrer_policy = metadata
781 .headers
782 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
783 .into();
784
785 if referrer_policy != ReferrerPolicy::EmptyString {
787 self.options.referrer_policy = referrer_policy;
788 }
789
790 if let Some(mime) = mime_type {
796 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
798
799 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
802 matches!(module_type, ModuleType::JavaScript)
803 {
804 if let Some(window) = global.downcast::<Window>() {
805 substitute_with_local_script(window, &mut source_text, final_url.clone());
806 }
807
808 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
809 cx,
810 Rc::new(DOMString::from(source_text.clone())),
811 &global,
812 &final_url,
813 self.options,
814 true,
815 1,
816 self.introduction_type,
817 ));
818 module_script = Some(module_tree);
819 }
820
821 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
824 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
825 cx,
826 &source_text,
827 &global,
828 &final_url,
829 self.introduction_type,
830 ));
831 module_script = Some(module_tree);
832 }
833 }
834 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
836 promise.resolve_native(&(), CanGc::from_cx(cx));
837 }
838
839 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
840 let global = self.owner.root();
841 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
842 scope.report_csp_violations(violations);
843 } else {
844 global.report_csp_violations(violations, None, None);
845 }
846 }
847}
848
849impl ResourceTimingListener for ModuleContext {
850 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
851 let initiator_type = InitiatorType::LocalName("module".to_string());
852 let (url, _) = &self.module_request;
853 (initiator_type, url.clone())
854 }
855
856 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
857 self.owner.root()
858 }
859}
860
861#[expect(unsafe_code)]
862#[expect(non_snake_case)]
863pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
866 unsafe {
867 if GetModuleResolveHook(rt).is_some() {
868 return;
869 }
870
871 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
872 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
873 SetScriptPrivateReferenceHooks(
874 rt,
875 Some(host_add_ref_top_level_script),
876 Some(host_release_top_level_script),
877 );
878 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
879 }
880}
881
882#[expect(unsafe_code)]
883unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
884 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
885 mem::forget(val.clone());
886 mem::forget(val);
887}
888
889#[expect(unsafe_code)]
890unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
891 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
892}
893
894#[expect(unsafe_code)]
895pub(crate) unsafe extern "C" fn host_import_module_dynamically(
898 cx: *mut RawJSContext,
899 reference_private: RawHandleValue,
900 specifier: RawHandle<*mut JSObject>,
901 promise: RawHandle<*mut JSObject>,
902) -> bool {
903 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
905 let cx = &mut cx;
906 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx.into());
907
908 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
909 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
910 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
911
912 let mut realm = CurrentRealm::assert(cx);
913 let payload = Payload::PromiseRecord(promise);
914 host_load_imported_module(
915 &mut realm,
916 None,
917 reference_private,
918 specifier,
919 module_type,
920 None,
921 payload,
922 );
923
924 true
925}
926
927#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
928pub(crate) struct ScriptFetchOptions {
930 pub(crate) integrity_metadata: String,
931 #[no_trace]
932 pub(crate) credentials_mode: CredentialsMode,
933 pub(crate) cryptographic_nonce: String,
934 #[no_trace]
935 pub(crate) parser_metadata: ParserMetadata,
936 #[no_trace]
937 pub(crate) referrer_policy: ReferrerPolicy,
938 pub(crate) render_blocking: bool,
942}
943
944impl ScriptFetchOptions {
945 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
947 Self {
948 cryptographic_nonce: String::new(),
949 integrity_metadata: String::new(),
950 parser_metadata: ParserMetadata::NotParserInserted,
951 credentials_mode: CredentialsMode::CredentialsSameOrigin,
952 referrer_policy: ReferrerPolicy::EmptyString,
953 render_blocking: false,
954 }
955 }
956
957 pub(crate) fn descendant_fetch_options(
959 &self,
960 url: &ServoUrl,
961 global: &GlobalScope,
962 ) -> ScriptFetchOptions {
963 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
965
966 Self {
969 integrity_metadata: integrity,
971 cryptographic_nonce: self.cryptographic_nonce.clone(),
972 credentials_mode: self.credentials_mode,
973 parser_metadata: self.parser_metadata,
974 referrer_policy: self.referrer_policy,
975 render_blocking: self.render_blocking,
976 }
977 }
978}
979
980#[expect(unsafe_code)]
981pub(crate) unsafe fn module_script_from_reference_private(
982 reference_private: &RawHandle<JSVal>,
983) -> Option<&ModuleScript> {
984 if reference_private.get().is_undefined() {
985 return None;
986 }
987 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
988}
989
990#[expect(unsafe_code)]
991#[expect(non_snake_case)]
992unsafe extern "C" fn HostResolveImportedModule(
995 cx: *mut RawJSContext,
996 reference_private: RawHandleValue,
997 specifier: RawHandle<*mut JSObject>,
998) -> *mut JSObject {
999 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1001 let mut realm = CurrentRealm::assert(&mut cx);
1002 let global_scope = GlobalScope::from_current_realm(&realm);
1003
1004 let cx = &mut realm;
1005
1006 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1008 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1009 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1010
1011 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
1012 let url = ModuleTree::resolve_module_specifier(
1013 &global_scope,
1014 module_data,
1015 DOMString::from(specifier),
1016 );
1017
1018 assert!(url.is_ok());
1020
1021 let parsed_url = url.unwrap();
1022
1023 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1025
1026 assert!(module.as_ref().is_some_and(
1028 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1029 ));
1030
1031 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1032 unreachable!()
1033 };
1034
1035 let fetched_module_object = module_tree.get_record();
1036
1037 assert!(fetched_module_object.is_some());
1039
1040 if let Some(record) = fetched_module_object {
1042 return record.handle().get();
1043 }
1044
1045 unreachable!()
1046}
1047
1048const SLOT_MODULEPRIVATE: usize = 0;
1050
1051#[expect(unsafe_code)]
1052#[expect(non_snake_case)]
1053unsafe extern "C" fn HostPopulateImportMeta(
1056 cx: *mut RawJSContext,
1057 reference_private: RawHandleValue,
1058 meta_object: RawHandle<*mut JSObject>,
1059) -> bool {
1060 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1062 let realm = CurrentRealm::assert(&mut cx);
1063 let global_scope = GlobalScope::from_current_realm(&realm);
1064
1065 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1067 Some(module_data) => module_data.base_url.clone(),
1068 None => global_scope.api_base_url(),
1069 };
1070
1071 unsafe {
1072 let url_string = JS_NewStringCopyN(
1073 &mut cx,
1074 base_url.as_str().as_ptr() as *const _,
1075 base_url.as_str().len(),
1076 );
1077 rooted!(&in(cx) let url_string = url_string);
1078
1079 if !JS_DefineProperty4(
1081 &mut cx,
1082 Handle::from_raw(meta_object),
1083 c"url".as_ptr(),
1084 url_string.handle(),
1085 JSPROP_ENUMERATE.into(),
1086 ) {
1087 return false;
1088 }
1089
1090 let resolve_function = DefineFunctionWithReserved(
1092 &mut cx,
1093 meta_object.get(),
1094 c"resolve".as_ptr(),
1095 Some(import_meta_resolve),
1096 1,
1097 JSPROP_ENUMERATE.into(),
1098 );
1099
1100 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1101 assert!(!obj.is_null());
1102 SetFunctionNativeReserved(
1103 obj.get(),
1104 SLOT_MODULEPRIVATE,
1105 &reference_private.get() as *const _,
1106 );
1107 }
1108
1109 true
1110}
1111
1112#[expect(unsafe_code)]
1113unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1114 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1116 let mut realm = CurrentRealm::assert(&mut cx);
1117 let global_scope = GlobalScope::from_current_realm(&realm);
1118
1119 let cx = &mut realm;
1120
1121 let args = unsafe { CallArgs::from_vp(vp, argc) };
1122
1123 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1124 let reference_private = module_private.handle().into();
1125 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1126
1127 let specifier = unsafe {
1131 let value = HandleValue::from_raw(args.get(0));
1132
1133 match NonNull::new(ToString(cx.raw_cx(), value)) {
1134 Some(jsstr) => jsstr_to_string(cx.raw_cx(), jsstr).into(),
1135 None => return false,
1136 }
1137 };
1138
1139 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1141
1142 match url {
1143 Ok(url) => {
1144 url.as_str().safe_to_jsval(
1146 cx.into(),
1147 unsafe { MutableHandleValue::from_raw(args.rval()) },
1148 CanGc::from_cx(cx),
1149 );
1150 true
1151 },
1152 Err(error) => {
1153 let resolution_error = gen_type_error(cx, &global_scope, error);
1154
1155 unsafe {
1156 JS_SetPendingException(
1157 cx,
1158 resolution_error.handle(),
1159 ExceptionStackBehavior::Capture,
1160 );
1161 }
1162 false
1163 },
1164 }
1165}
1166
1167#[expect(clippy::too_many_arguments)]
1168pub(crate) fn fetch_a_module_worker_script_graph(
1171 cx: &mut JSContext,
1172 global: &GlobalScope,
1173 url: ServoUrl,
1174 fetch_client: ModuleFetchClient,
1175 destination: Destination,
1176 referrer: Referrer,
1177 credentials_mode: CredentialsMode,
1178 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1179) {
1180 let global_scope = DomRoot::from_ref(global);
1181
1182 let options = ScriptFetchOptions {
1187 integrity_metadata: "".into(),
1188 credentials_mode,
1189 cryptographic_nonce: "".into(),
1190 parser_metadata: ParserMetadata::NotParserInserted,
1191 referrer_policy: ReferrerPolicy::EmptyString,
1192 render_blocking: false,
1193 };
1194
1195 fetch_a_single_module_script(
1198 cx,
1199 url,
1200 fetch_client.clone(),
1201 global,
1202 destination,
1203 options,
1204 referrer,
1205 None,
1206 true,
1207 Some(IntroductionType::WORKER),
1208 move |cx, module_tree| {
1209 let Some(module) = module_tree else {
1210 return on_complete(cx, None);
1212 };
1213
1214 fetch_the_descendants_and_link_module_script(
1217 cx,
1218 &global_scope,
1219 module,
1220 fetch_client,
1221 destination,
1222 on_complete,
1223 );
1224 },
1225 );
1226}
1227
1228pub(crate) fn fetch_an_external_module_script(
1230 cx: &mut JSContext,
1231 url: ServoUrl,
1232 global: &GlobalScope,
1233 options: ScriptFetchOptions,
1234 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1235) {
1236 let referrer = global.get_referrer();
1237 let fetch_client = ModuleFetchClient::from_global_scope(global);
1238 let global_scope = DomRoot::from_ref(global);
1239
1240 fetch_a_single_module_script(
1243 cx,
1244 url,
1245 fetch_client.clone(),
1246 global,
1247 Destination::Script,
1248 options,
1249 referrer,
1250 None,
1251 true,
1252 Some(IntroductionType::SRC_SCRIPT),
1253 move |cx, module_tree| {
1254 let Some(module) = module_tree else {
1255 return on_complete(cx, None);
1257 };
1258
1259 fetch_the_descendants_and_link_module_script(
1261 cx,
1262 &global_scope,
1263 module,
1264 fetch_client,
1265 Destination::Script,
1266 on_complete,
1267 );
1268 },
1269 );
1270}
1271
1272pub(crate) fn fetch_a_modulepreload_module(
1274 cx: &mut JSContext,
1275 url: ServoUrl,
1276 destination: Destination,
1277 global: &GlobalScope,
1278 options: ScriptFetchOptions,
1279 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1280) {
1281 let referrer = global.get_referrer();
1282 let fetch_client = ModuleFetchClient::from_global_scope(global);
1283 let global_scope = DomRoot::from_ref(global);
1284
1285 let module_type = if let Destination::Json = destination {
1288 Some(ModuleType::JSON)
1289 } else {
1290 None
1291 };
1292
1293 fetch_a_single_module_script(
1296 cx,
1297 url,
1298 fetch_client.clone(),
1299 global,
1300 destination,
1301 options,
1302 referrer,
1303 module_type,
1304 true,
1305 Some(IntroductionType::SRC_SCRIPT),
1306 move |cx, result| {
1307 on_complete(cx, result.is_none());
1309
1310 assert!(global_scope.is::<Window>());
1312
1313 if pref!(dom_allow_preloading_module_descendants) &&
1316 let Some(module) = result
1317 {
1318 fetch_the_descendants_and_link_module_script(
1319 cx,
1320 &global_scope,
1321 module,
1322 fetch_client,
1323 destination,
1324 |_, _| {},
1325 );
1326 }
1327 },
1328 );
1329}
1330
1331#[expect(clippy::too_many_arguments)]
1332pub(crate) fn fetch_inline_module_script(
1334 cx: &mut JSContext,
1335 global: &GlobalScope,
1336 module_script_text: Rc<DOMString>,
1337 url: ServoUrl,
1338 options: ScriptFetchOptions,
1339 line_number: u32,
1340 introduction_type: Option<&'static CStr>,
1341 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1342) {
1343 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1345 cx,
1346 module_script_text,
1347 global,
1348 &url,
1349 options,
1350 false,
1351 line_number,
1352 introduction_type,
1353 ));
1354 let fetch_client = ModuleFetchClient::from_global_scope(global);
1355
1356 fetch_the_descendants_and_link_module_script(
1358 cx,
1359 global,
1360 module_tree,
1361 fetch_client,
1362 Destination::Script,
1363 on_complete,
1364 );
1365}
1366
1367#[expect(unsafe_code)]
1368fn fetch_the_descendants_and_link_module_script(
1370 cx: &mut JSContext,
1371 global: &GlobalScope,
1372 module_script: Rc<ModuleTree>,
1373 fetch_client: ModuleFetchClient,
1374 destination: Destination,
1375 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1376) {
1377 if module_script.get_record().is_none() {
1380 let parse_error = module_script.get_parse_error().cloned();
1381
1382 module_script.set_rethrow_error(parse_error.unwrap());
1384
1385 on_complete(cx, Some(module_script));
1387
1388 return;
1390 }
1391
1392 let state = Rc::new(LoadState {
1395 error_to_rethrow: RefCell::new(None),
1396 destination,
1397 fetch_client,
1398 });
1399
1400 let mut realm = enter_auto_realm(cx, global);
1403 let cx = &mut realm.current_realm();
1404
1405 let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
1407
1408 let global_scope = DomRoot::from_ref(global);
1409 let fulfilled_module = module_script.clone();
1410 let fulfilled_on_complete = on_complete.clone();
1411
1412 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1414 task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
1415 let mut realm = AutoRealm::new(
1416 cx,
1417 NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
1418 );
1419 let cx = &mut *realm;
1420
1421 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1422
1423 let link = unsafe { ModuleLink(cx, handle) };
1425
1426 if !link {
1428 let exception = RethrowError::from_pending_exception(cx);
1429 fulfilled_module.set_rethrow_error(exception);
1430 }
1431
1432 fulfilled_on_complete(cx, Some(fulfilled_module));
1434 }),
1435 ));
1436
1437 let loading_promise_rejection =
1439 ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
1440 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1443 module_script.set_rethrow_error(error.clone());
1444 on_complete(cx, Some(module_script));
1445 } else {
1446 on_complete(cx, None);
1448 }
1449 })));
1450
1451 let handler = PromiseNativeHandler::new(
1452 global,
1453 Some(loading_promise_fulfillment),
1454 Some(loading_promise_rejection),
1455 CanGc::from_cx(cx),
1456 );
1457
1458 run_a_callback::<DomTypeHolder, _>(global, || {
1459 loading_promise.append_native_handler(cx, &handler);
1460 });
1461}
1462
1463#[expect(clippy::too_many_arguments)]
1465pub(crate) fn fetch_a_single_module_script(
1466 cx: &mut JSContext,
1467 url: ServoUrl,
1468 fetch_client: ModuleFetchClient,
1469 global: &GlobalScope,
1470 destination: Destination,
1471 options: ScriptFetchOptions,
1472 referrer: Referrer,
1473 module_type: Option<ModuleType>,
1474 is_top_level: bool,
1475 introduction_type: Option<&'static CStr>,
1476 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1477) {
1478 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1482
1483 let module_request = (url.clone(), module_type);
1489 let entry = global.get_module_map_entry(&module_request);
1490
1491 let pending = match entry {
1492 Some(ModuleStatus::Fetching(pending)) => pending,
1493 Some(ModuleStatus::Loaded(module_tree)) => {
1495 return on_complete(cx, module_tree);
1496 },
1497 None => DomRefCell::new(None),
1498 };
1499
1500 let global_scope = DomRoot::from_ref(global);
1501 let module_map_key = module_request.clone();
1502 let handler = ModuleHandler::new_boxed(Box::new(
1503 task!(fetch_completed: |cx, global_scope: DomRoot<GlobalScope>| {
1504 let key = module_map_key;
1505 let module = global_scope.get_module_map_entry(&key);
1506
1507 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1508 on_complete(cx, module_tree);
1509 }
1510 }),
1511 ));
1512
1513 let handler = PromiseNativeHandler::new(global, Some(handler), None, CanGc::from_cx(cx));
1514
1515 let mut realm = enter_auto_realm(cx, global);
1516 let cx = &mut realm.current_realm();
1517
1518 run_a_callback::<DomTypeHolder, _>(global, || {
1519 let has_pending_fetch = pending.borrow().is_some();
1520
1521 let promise = Promise::new_in_realm(cx);
1522
1523 if has_pending_fetch {
1526 promise.append_native_handler(cx, &handler);
1527
1528 let continue_loading_handler = PromiseNativeHandler::new(
1531 global,
1532 Some(Box::new(QueueTaskHandler { promise })),
1533 None,
1534 CanGc::from_cx(cx),
1535 );
1536
1537 let pending_promise = pending.borrow_mut().take();
1539 if let Some(promise) = pending_promise {
1540 promise.append_native_handler(cx, &continue_loading_handler);
1541 let _ = pending.borrow_mut().insert(promise);
1542 }
1543 return;
1544 }
1545
1546 promise.append_native_handler(cx, &handler);
1547
1548 let prev = pending.borrow_mut().replace(promise);
1549 assert!(prev.is_none());
1550
1551 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1553
1554 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1556 .then(|| fetch_client.policy_container.clone());
1557
1558 let webview_id = global.webview_id();
1559
1560 let mode = match destination {
1565 Destination::Worker | Destination::SharedWorker if is_top_level => {
1566 RequestMode::SameOrigin
1567 },
1568 _ => RequestMode::CorsMode,
1569 };
1570
1571 let destination = match module_type {
1574 ModuleType::JSON => Destination::Json,
1575 ModuleType::JavaScript | ModuleType::Unknown => destination,
1576 };
1577
1578 let request = RequestBuilder::new(
1582 webview_id,
1583 UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone()),
1584 referrer,
1585 )
1586 .destination(destination)
1587 .parser_metadata(options.parser_metadata)
1588 .integrity_metadata(options.integrity_metadata.clone())
1589 .credentials_mode(options.credentials_mode)
1590 .referrer_policy(options.referrer_policy)
1591 .mode(mode)
1592 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1593 .insecure_requests_policy(fetch_client.insecure_requests_policy)
1594 .has_trustworthy_ancestor_origin(fetch_client.has_trustworthy_ancestor_origin)
1595 .policy_container(fetch_client.policy_container)
1596 .client(fetch_client.client)
1597 .pipeline_id(Some(fetch_client.pipeline_id))
1598 .origin(fetch_client.origin);
1599
1600 let context = ModuleContext {
1601 owner: Trusted::new(global),
1602 data: vec![],
1603 metadata: None,
1604 module_request,
1605 options,
1606 status: Ok(()),
1607 introduction_type,
1608 policy_container,
1609 };
1610
1611 let task_source = global.task_manager().networking_task_source().to_sendable();
1612 global.fetch(request, context, task_source);
1613 })
1614}
1615
1616fn fill_module_compile_options(
1617 cx: &mut JSContext,
1618 url: &ServoUrl,
1619 introduction_type: Option<&'static CStr>,
1620 line_number: u32,
1621) -> CompileOptionsWrapper {
1622 let mut options = CompileOptionsWrapper::new(cx, cformat!("{url}"), line_number);
1623 if let Some(introduction_type) = introduction_type {
1624 options.set_introduction_type(introduction_type);
1625 }
1626
1627 options.set_muted_errors(false);
1629
1630 options.set_is_run_once(true);
1632 options.set_no_script_rval(true);
1633
1634 options
1635}
1636
1637pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1638pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1639
1640#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1642pub(crate) struct ResolvedModule {
1643 base_url: String,
1645 specifier: String,
1647 #[no_trace]
1649 specifier_url: Option<ServoUrl>,
1650}
1651
1652impl ResolvedModule {
1653 pub(crate) fn new(
1654 base_url: String,
1655 specifier: String,
1656 specifier_url: Option<ServoUrl>,
1657 ) -> Self {
1658 Self {
1659 base_url,
1660 specifier,
1661 specifier_url,
1662 }
1663 }
1664}
1665
1666#[derive(Default, JSTraceable, MallocSizeOf)]
1668pub(crate) struct ImportMap {
1669 #[no_trace]
1670 imports: ModuleSpecifierMap,
1671 #[no_trace]
1672 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1673 #[no_trace]
1674 integrity: ModuleIntegrityMap,
1675}
1676
1677impl ImportMap {
1678 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1680 self.integrity.get(url).cloned().unwrap_or_default()
1685 }
1686}
1687
1688pub(crate) fn register_import_map(
1690 cx: &mut JSContext,
1691 global: &GlobalScope,
1692 result: Fallible<ImportMap>,
1693) {
1694 match result {
1695 Ok(new_import_map) => {
1696 merge_existing_and_new_import_maps(global, new_import_map);
1698 },
1699 Err(exception) => {
1700 let mut realm = enter_auto_realm(cx, global);
1701 let cx = &mut realm.current_realm();
1702
1703 let in_realm_proof = cx.into();
1704 let in_realm = InRealm::Already(&in_realm_proof);
1705
1706 throw_dom_exception(cx.into(), global, exception, CanGc::from_cx(cx));
1709 report_pending_exception(cx.into(), in_realm, CanGc::from_cx(cx));
1710 },
1711 }
1712}
1713
1714fn merge_existing_and_new_import_maps(global: &GlobalScope, new_import_map: ImportMap) {
1716 let new_import_map_scopes = new_import_map.scopes;
1718
1719 let mut old_import_map = global.import_map_mut();
1721
1722 let mut new_import_map_imports = new_import_map.imports;
1724
1725 let resolved_module_set = global.resolved_module_set();
1726 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1728 for record in resolved_module_set.iter() {
1730 let prefix = scope_prefix.as_str();
1733 if prefix == record.base_url ||
1734 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1735 {
1736 scope_imports.retain(|key, val| {
1738 if *key == record.specifier ||
1743 (key.ends_with('\u{002f}') &&
1744 record.specifier.starts_with(key) &&
1745 (record.specifier_url.is_none() ||
1746 record
1747 .specifier_url
1748 .as_ref()
1749 .is_some_and(|u| u.is_special_scheme())))
1750 {
1751 Console::internal_warn(global, format!("Ignored rule: {key} -> {val:?}."));
1754 false
1756 } else {
1757 true
1758 }
1759 })
1760 }
1761 }
1762
1763 if old_import_map.scopes.contains_key(&scope_prefix) {
1765 let merged_module_specifier_map = merge_module_specifier_maps(
1768 global,
1769 scope_imports,
1770 &old_import_map.scopes[&scope_prefix],
1771 );
1772 old_import_map
1773 .scopes
1774 .insert(scope_prefix, merged_module_specifier_map);
1775 } else {
1776 old_import_map.scopes.insert(scope_prefix, scope_imports);
1778 }
1779 }
1780
1781 for (url, integrity) in &new_import_map.integrity {
1783 if old_import_map.integrity.contains_key(url) {
1785 Console::internal_warn(global, format!("Ignored rule: {url} -> {integrity}."));
1788 continue;
1790 }
1791
1792 old_import_map
1794 .integrity
1795 .insert(url.clone(), integrity.clone());
1796 }
1797
1798 for record in resolved_module_set.iter() {
1800 new_import_map_imports.retain(|specifier, val| {
1802 if record.specifier.starts_with(specifier) {
1807 Console::internal_warn(global, format!("Ignored rule: {specifier} -> {val:?}."));
1810 false
1812 } else {
1813 true
1814 }
1815 });
1816 }
1817
1818 let merged_module_specifier_map =
1821 merge_module_specifier_maps(global, new_import_map_imports, &old_import_map.imports);
1822 old_import_map.imports = merged_module_specifier_map;
1823
1824 old_import_map
1827 .scopes
1828 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1829}
1830
1831fn merge_module_specifier_maps(
1833 global: &GlobalScope,
1834 new_map: ModuleSpecifierMap,
1835 old_map: &ModuleSpecifierMap,
1836) -> ModuleSpecifierMap {
1837 let mut merged_map = old_map.clone();
1839
1840 for (specifier, url) in new_map {
1842 if old_map.contains_key(&specifier) {
1844 Console::internal_warn(global, format!("Ignored rule: {specifier} -> {url:?}."));
1847
1848 continue;
1850 }
1851
1852 merged_map.insert(specifier, url);
1854 }
1855
1856 merged_map
1857}
1858
1859pub(crate) fn parse_an_import_map_string(
1861 global: &GlobalScope,
1862 input: Rc<DOMString>,
1863 base_url: ServoUrl,
1864) -> Fallible<ImportMap> {
1865 let parsed: JsonValue = serde_json::from_str(&input.str())
1867 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1868 let JsonValue::Object(mut parsed) = parsed else {
1871 return Err(Error::Type(
1872 c"The top-level value needs to be a JSON object.".to_owned(),
1873 ));
1874 };
1875
1876 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1878 if let Some(imports) = parsed.get("imports") {
1880 let JsonValue::Object(imports) = imports else {
1883 return Err(Error::Type(
1884 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1885 ));
1886 };
1887 sorted_and_normalized_imports =
1890 sort_and_normalize_module_specifier_map(global, imports, &base_url);
1891 }
1892
1893 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1895 if let Some(scopes) = parsed.get("scopes") {
1897 let JsonValue::Object(scopes) = scopes else {
1900 return Err(Error::Type(
1901 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1902 ));
1903 };
1904 sorted_and_normalized_scopes = sort_and_normalize_scopes(global, scopes, &base_url)?;
1907 }
1908
1909 let mut normalized_integrity = ModuleIntegrityMap::new();
1911 if let Some(integrity) = parsed.get("integrity") {
1913 let JsonValue::Object(integrity) = integrity else {
1916 return Err(Error::Type(
1917 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1918 ));
1919 };
1920 normalized_integrity = normalize_module_integrity_map(global, integrity, &base_url);
1923 }
1924
1925 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1929 if !parsed.is_empty() {
1930 Console::internal_warn(
1931 global,
1932 "Invalid top-level key was present in the import map.
1933 Only \"imports\", \"scopes\", and \"integrity\" are allowed."
1934 .to_string(),
1935 );
1936 }
1937
1938 Ok(ImportMap {
1940 imports: sorted_and_normalized_imports,
1941 scopes: sorted_and_normalized_scopes,
1942 integrity: normalized_integrity,
1943 })
1944}
1945
1946fn sort_and_normalize_module_specifier_map(
1948 global: &GlobalScope,
1949 original_map: &JsonMap<String, JsonValue>,
1950 base_url: &ServoUrl,
1951) -> ModuleSpecifierMap {
1952 let mut normalized = ModuleSpecifierMap::new();
1954
1955 for (specifier_key, value) in original_map {
1957 let Some(normalized_specifier_key) =
1960 normalize_specifier_key(global, specifier_key, base_url)
1961 else {
1962 continue;
1964 };
1965
1966 let JsonValue::String(value) = value else {
1968 Console::internal_warn(global, "Addresses need to be strings.".to_string());
1971
1972 normalized.insert(normalized_specifier_key, None);
1974 continue;
1976 };
1977
1978 let Some(address_url) =
1980 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
1981 else {
1982 Console::internal_warn(
1986 global,
1987 format!("Value failed to resolve to module specifier: {value}"),
1988 );
1989
1990 normalized.insert(normalized_specifier_key, None);
1992 continue;
1994 };
1995
1996 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
1999 Console::internal_warn(
2003 global,
2004 format!(
2005 "Invalid address for specifier key '{specifier_key}': {address_url}.
2006 Since specifierKey ends with a slash, the address needs to as well."
2007 ),
2008 );
2009
2010 normalized.insert(normalized_specifier_key, None);
2012 continue;
2014 }
2015
2016 normalized.insert(normalized_specifier_key, Some(address_url));
2018 }
2019
2020 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2023 normalized
2024}
2025
2026fn sort_and_normalize_scopes(
2028 global: &GlobalScope,
2029 original_map: &JsonMap<String, JsonValue>,
2030 base_url: &ServoUrl,
2031) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2032 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2034
2035 for (scope_prefix, potential_specifier_map) in original_map {
2037 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2040 return Err(Error::Type(
2041 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
2042 .to_owned(),
2043 ));
2044 };
2045
2046 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2048 Console::internal_warn(
2052 global,
2053 format!("Scope prefix URL was not parseable: {scope_prefix}"),
2054 );
2055 continue;
2057 };
2058
2059 let normalized_scope_prefix = scope_prefix_url;
2061
2062 let normalized_specifier_map =
2065 sort_and_normalize_module_specifier_map(global, potential_specifier_map, base_url);
2066 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2067 }
2068
2069 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2072 Ok(normalized)
2073}
2074
2075fn normalize_module_integrity_map(
2077 global: &GlobalScope,
2078 original_map: &JsonMap<String, JsonValue>,
2079 base_url: &ServoUrl,
2080) -> ModuleIntegrityMap {
2081 let mut normalized = ModuleIntegrityMap::new();
2083
2084 for (key, value) in original_map {
2086 let Some(resolved_url) =
2089 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
2090 else {
2091 Console::internal_warn(
2095 global,
2096 format!("Key failed to resolve to module specifier: {key}"),
2097 );
2098 continue;
2100 };
2101
2102 let JsonValue::String(value) = value else {
2104 Console::internal_warn(
2107 global,
2108 "Integrity metadata values need to be strings.".to_string(),
2109 );
2110 continue;
2112 };
2113
2114 normalized.insert(resolved_url, value.clone());
2116 }
2117
2118 normalized
2120}
2121
2122fn normalize_specifier_key(
2124 global: &GlobalScope,
2125 specifier_key: &str,
2126 base_url: &ServoUrl,
2127) -> Option<String> {
2128 if specifier_key.is_empty() {
2130 Console::internal_warn(
2133 global,
2134 "Specifier keys may not be the empty string.".to_string(),
2135 );
2136 return None;
2138 }
2139 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2141
2142 if let Some(url) = url {
2144 return Some(url.into_string());
2145 }
2146
2147 Some(specifier_key.to_string())
2149}
2150
2151fn resolve_imports_match(
2156 normalized_specifier: &str,
2157 as_url: Option<&ServoUrl>,
2158 specifier_map: &ModuleSpecifierMap,
2159) -> Fallible<Option<ServoUrl>> {
2160 for (specifier_key, resolution_result) in specifier_map {
2162 if specifier_key == normalized_specifier {
2164 if let Some(resolution_result) = resolution_result {
2165 return Ok(Some(resolution_result.clone()));
2169 } else {
2170 return Err(Error::Type(
2172 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2173 ));
2174 }
2175 }
2176
2177 if specifier_key.ends_with('\u{002f}') &&
2182 normalized_specifier.starts_with(specifier_key) &&
2183 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2184 {
2185 let Some(resolution_result) = resolution_result else {
2188 return Err(Error::Type(
2189 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2190 ));
2191 };
2192
2193 let after_prefix = normalized_specifier
2195 .strip_prefix(specifier_key)
2196 .expect("specifier_key should be the prefix of normalized_specifier");
2197
2198 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2200
2201 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2203
2204 let Ok(url) = url else {
2207 return Err(Error::Type(
2208 c"Resolution of normalizedSpecifier was blocked since
2209 the afterPrefix portion could not be URL-parsed relative to
2210 the resolutionResult mapped to by the specifierKey prefix."
2211 .to_owned(),
2212 ));
2213 };
2214
2215 if !url.as_str().starts_with(resolution_result.as_str()) {
2218 return Err(Error::Type(
2219 c"Resolution of normalizedSpecifier was blocked due to
2220 it backtracking above its prefix specifierKey."
2221 .to_owned(),
2222 ));
2223 }
2224
2225 return Ok(Some(url));
2227 }
2228 }
2229
2230 Ok(None)
2232}