1use std::cell::{OnceCell, RefCell};
9use std::ffi::CStr;
10use std::fmt::Debug;
11use std::rc::Rc;
12use std::{mem, ptr};
13
14use encoding_rs::UTF_8;
15use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
16use html5ever::local_name;
17use hyper_serde::Serde;
18use indexmap::IndexMap;
19use indexmap::map::Entry;
20use js::conversions::jsstr_to_string;
21use js::gc::MutableHandleValue;
22use js::jsapi::{
23 CompileJsonModule1, CompileModule1, ExceptionStackBehavior, GetModuleRequestSpecifier,
24 GetModuleRequestType, GetModuleResolveHook, Handle as RawHandle, HandleValue as RawHandleValue,
25 Heap, JS_ClearPendingException, JS_DefineProperty4, JS_NewStringCopyN, JSAutoRealm, JSContext,
26 JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour, ModuleType,
27 SetModuleDynamicImportHook, SetModuleMetadataHook, SetModulePrivate, SetModuleResolveHook,
28 SetScriptPrivateReferenceHooks, ThrowOnModuleEvaluationFailure, Value,
29};
30use js::jsval::{JSVal, PrivateValue, UndefinedValue};
31use js::realm::CurrentRealm;
32use js::rust::wrappers::{
33 JS_GetPendingException, JS_SetPendingException, ModuleEvaluate, ModuleLink,
34};
35use js::rust::{
36 CompileOptionsWrapper, Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle,
37 transform_str_to_source_text,
38};
39use mime::Mime;
40use net_traits::http_status::HttpStatus;
41use net_traits::mime_classifier::MimeClassifier;
42use net_traits::request::{
43 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId, RequestMode,
44};
45use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
46use script_bindings::domstring::BytesView;
47use script_bindings::error::Fallible;
48use script_bindings::trace::CustomTraceable;
49use serde_json::{Map as JsonMap, Value as JsonValue};
50use servo_url::ServoUrl;
51
52use crate::document_loader::LoadType;
53use crate::dom::bindings::cell::DomRefCell;
54use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
55use crate::dom::bindings::error::{
56 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
57};
58use crate::dom::bindings::inheritance::Castable;
59use crate::dom::bindings::refcounted::Trusted;
60use crate::dom::bindings::reflector::{DomGlobal, DomObject};
61use crate::dom::bindings::root::DomRoot;
62use crate::dom::bindings::settings_stack::AutoIncumbentScript;
63use crate::dom::bindings::str::DOMString;
64use crate::dom::bindings::trace::RootedTraceableBox;
65use crate::dom::csp::{GlobalCspReporting, Violation};
66use crate::dom::document::Document;
67use crate::dom::element::Element;
68use crate::dom::globalscope::GlobalScope;
69use crate::dom::html::htmlscriptelement::{HTMLScriptElement, SCRIPT_JS_MIMES, Script};
70use crate::dom::htmlscriptelement::substitute_with_local_script;
71use crate::dom::node::NodeTraits;
72use crate::dom::performance::performanceresourcetiming::InitiatorType;
73use crate::dom::promise::Promise;
74use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
75use crate::dom::types::Console;
76use crate::dom::window::Window;
77use crate::dom::worker::TrustedWorkerAddress;
78use crate::fetch::RequestWithGlobalScope;
79use crate::module_loading::{
80 LoadState, Payload, host_load_imported_module, load_requested_modules,
81};
82use crate::network_listener::{
83 self, FetchResponseListener, NetworkListener, ResourceTimingListener,
84};
85use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
86use crate::script_runtime::{CanGc, IntroductionType, JSContext as SafeJSContext};
87use crate::task::NonSendTaskBox;
88
89pub(crate) fn gen_type_error(global: &GlobalScope, error: Error, can_gc: CanGc) -> RethrowError {
90 rooted!(in(*GlobalScope::get_cx()) let mut thrown = UndefinedValue());
91 error.to_jsval(GlobalScope::get_cx(), global, thrown.handle_mut(), can_gc);
92
93 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
94}
95
96#[derive(JSTraceable)]
97pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
98
99impl ModuleObject {
100 pub(crate) fn new(obj: RustHandleObject) -> ModuleObject {
101 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
102 }
103
104 pub(crate) fn handle(&'_ self) -> js::gc::HandleObject<'_> {
105 self.0.handle()
106 }
107}
108
109#[derive(JSTraceable)]
110pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
111
112impl RethrowError {
113 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
114 Self(RootedTraceableBox::from_box(val))
115 }
116
117 #[expect(unsafe_code)]
118 pub(crate) fn from_pending_exception(cx: SafeJSContext) -> Self {
119 rooted!(in(*cx) let mut exception = UndefinedValue());
120 assert!(unsafe { JS_GetPendingException(*cx, exception.handle_mut()) });
121 unsafe { JS_ClearPendingException(*cx) };
122
123 Self::new(Heap::boxed(exception.get()))
124 }
125
126 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
127 self.0.handle()
128 }
129}
130
131impl Debug for RethrowError {
132 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
133 "RethrowError(...)".fmt(fmt)
134 }
135}
136
137impl Clone for RethrowError {
138 fn clone(&self) -> Self {
139 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
140 }
141}
142
143pub(crate) struct ModuleScript {
144 pub(crate) base_url: ServoUrl,
145 pub(crate) options: ScriptFetchOptions,
146 owner: Option<ModuleOwner>,
147}
148
149impl ModuleScript {
150 pub(crate) fn new(
151 base_url: ServoUrl,
152 options: ScriptFetchOptions,
153 owner: Option<ModuleOwner>,
154 ) -> Self {
155 ModuleScript {
156 base_url,
157 options,
158 owner,
159 }
160 }
161}
162
163pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
164
165#[derive(Clone, JSTraceable)]
166pub(crate) enum ModuleStatus {
167 Fetching(DomRefCell<Option<Rc<Promise>>>),
168 Loaded(Option<Rc<ModuleTree>>),
169}
170
171#[derive(JSTraceable, MallocSizeOf)]
172pub(crate) struct ModuleTree {
173 #[no_trace]
174 url: ServoUrl,
175 #[ignore_malloc_size_of = "mozjs"]
176 record: OnceCell<ModuleObject>,
177 #[ignore_malloc_size_of = "mozjs"]
178 parse_error: OnceCell<RethrowError>,
179 #[ignore_malloc_size_of = "mozjs"]
180 rethrow_error: DomRefCell<Option<RethrowError>>,
181 #[no_trace]
182 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
183}
184
185impl ModuleTree {
186 pub(crate) fn get_url(&self) -> ServoUrl {
187 self.url.clone()
188 }
189
190 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
191 self.record.get()
192 }
193
194 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
195 self.parse_error.get()
196 }
197
198 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
199 &self.rethrow_error
200 }
201
202 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
203 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
204 }
205
206 pub(crate) fn find_descendant_inside_module_map(
207 &self,
208 global: &GlobalScope,
209 specifier: &String,
210 module_type: ModuleType,
211 ) -> Option<Rc<ModuleTree>> {
212 self.loaded_modules
213 .borrow()
214 .get(specifier)
215 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
216 .and_then(|status| match status {
217 ModuleStatus::Fetching(_) => None,
218 ModuleStatus::Loaded(module_tree) => module_tree,
219 })
220 }
221
222 pub(crate) fn insert_module_dependency(
223 &self,
224 module: &Rc<ModuleTree>,
225 module_request_specifier: String,
226 ) {
227 let url = module.url.clone();
229 match self
230 .loaded_modules
231 .borrow_mut()
232 .entry(module_request_specifier)
233 {
234 Entry::Occupied(entry) => {
237 assert_eq!(*entry.get(), url);
239 },
240 Entry::Vacant(entry) => {
242 entry.insert(url);
245 },
246 }
247 }
248}
249
250struct ModuleSource {
251 source: Rc<DOMString>,
252 unminified_dir: Option<String>,
253 external: bool,
254 url: ServoUrl,
255}
256
257impl crate::unminify::ScriptSource for ModuleSource {
258 fn unminified_dir(&self) -> Option<String> {
259 self.unminified_dir.clone()
260 }
261
262 fn extract_bytes(&self) -> BytesView<'_> {
263 self.source.as_bytes()
264 }
265
266 fn rewrite_source(&mut self, source: Rc<DOMString>) {
267 self.source = source;
268 }
269
270 fn url(&self) -> ServoUrl {
271 self.url.clone()
272 }
273
274 fn is_external(&self) -> bool {
275 self.external
276 }
277}
278
279impl ModuleTree {
280 #[expect(unsafe_code)]
281 #[expect(clippy::too_many_arguments)]
282 fn create_a_javascript_module_script(
286 source: Rc<DOMString>,
287 owner: ModuleOwner,
288 url: &ServoUrl,
289 options: ScriptFetchOptions,
290 external: bool,
291 line_number: u32,
292 introduction_type: Option<&'static CStr>,
293 _can_gc: CanGc,
294 ) -> Self {
295 let cx = GlobalScope::get_cx();
296 let global = owner.global();
297 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
298
299 let module = ModuleTree {
302 url: url.clone(),
303 record: OnceCell::new(),
304 parse_error: OnceCell::new(),
305 rethrow_error: DomRefCell::new(None),
306 loaded_modules: DomRefCell::new(IndexMap::new()),
307 };
308
309 let mut compile_options =
310 unsafe { CompileOptionsWrapper::new_raw(*cx, url.as_str(), line_number) };
311 if let Some(introduction_type) = introduction_type {
312 compile_options.set_introduction_type(introduction_type);
313 }
314 let mut module_source = ModuleSource {
315 source,
316 unminified_dir: global.unminified_js_dir(),
317 external,
318 url: url.clone(),
319 };
320 crate::unminify::unminify_js(&mut module_source);
321
322 unsafe {
323 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
325 module_script.set(CompileModule1(
326 *cx,
327 compile_options.ptr,
328 &mut transform_str_to_source_text(&module_source.source.str()),
329 ));
330
331 if module_script.is_null() {
333 warn!("fail to compile module script of {}", url);
334
335 let _ = module
337 .parse_error
338 .set(RethrowError::from_pending_exception(cx));
339
340 return module;
342 }
343
344 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
348
349 SetModulePrivate(
350 module_script.get(),
351 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
352 );
353
354 let _ = module.record.set(ModuleObject::new(module_script.handle()));
356 }
357
358 module
360 }
361
362 #[expect(unsafe_code)]
363 fn crate_a_json_module_script(
367 source: &str,
368 global: &GlobalScope,
369 url: &ServoUrl,
370 introduction_type: Option<&'static CStr>,
371 _can_gc: CanGc,
372 ) -> Self {
373 let cx = GlobalScope::get_cx();
374 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
375
376 let module = ModuleTree {
379 url: url.clone(),
380 record: OnceCell::new(),
381 parse_error: OnceCell::new(),
382 rethrow_error: DomRefCell::new(None),
383 loaded_modules: DomRefCell::new(IndexMap::new()),
384 };
385
386 let mut compile_options = unsafe { CompileOptionsWrapper::new_raw(*cx, url.as_str(), 1) };
391 if let Some(introduction_type) = introduction_type {
392 compile_options.set_introduction_type(introduction_type);
393 }
394
395 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
396
397 unsafe {
398 module_script.set(CompileJsonModule1(
400 *cx,
401 compile_options.ptr,
402 &mut transform_str_to_source_text(source),
403 ));
404 }
405
406 if module_script.is_null() {
408 warn!("fail to compile module script of {}", url);
409
410 let _ = module
411 .parse_error
412 .set(RethrowError::from_pending_exception(cx));
413 return module;
414 }
415
416 let _ = module.record.set(ModuleObject::new(module_script.handle()));
418
419 module
421 }
422
423 #[expect(unsafe_code)]
424 pub(crate) fn instantiate_module_tree(
427 global: &GlobalScope,
428 module_record: js::gc::HandleObject,
429 ) -> Result<(), RethrowError> {
430 let cx = GlobalScope::get_cx();
431 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
432
433 unsafe {
434 if !ModuleLink(*cx, module_record) {
435 warn!("fail to link & instantiate module");
436
437 rooted!(in(*cx) let mut exception = UndefinedValue());
438 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
439 JS_ClearPendingException(*cx);
440
441 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
442 exception.get(),
443 ))))
444 } else {
445 debug!("module instantiated successfully");
446
447 Ok(())
448 }
449 }
450 }
451
452 #[expect(unsafe_code)]
456 pub(crate) fn execute_module(
457 &self,
458 global: &GlobalScope,
459 module_record: js::gc::HandleObject,
460 mut eval_result: MutableHandleValue,
461 _can_gc: CanGc,
462 ) -> Result<(), RethrowError> {
463 let cx = GlobalScope::get_cx();
464 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
465
466 unsafe {
467 let ok = ModuleEvaluate(*cx, module_record, eval_result.reborrow());
468 assert!(ok, "module evaluation failed");
469
470 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
471 if eval_result.is_object() {
472 evaluation_promise.set(eval_result.to_object());
473 }
474
475 let throw_result = ThrowOnModuleEvaluationFailure(
476 *cx,
477 evaluation_promise.handle().into(),
478 ModuleErrorBehaviour::ThrowModuleErrorsSync,
479 );
480 if !throw_result {
481 warn!("fail to evaluate module");
482
483 rooted!(in(*cx) let mut exception = UndefinedValue());
484 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
485 JS_ClearPendingException(*cx);
486
487 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
488 exception.get(),
489 ))))
490 } else {
491 debug!("module evaluated successfully");
492 Ok(())
493 }
494 }
495 }
496
497 #[expect(unsafe_code)]
498 pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
499 let module_error = self.rethrow_error.borrow();
500
501 if let Some(exception) = &*module_error {
502 let ar = enter_realm(global);
503 unsafe {
504 JS_SetPendingException(
505 *GlobalScope::get_cx(),
506 exception.handle(),
507 ExceptionStackBehavior::Capture,
508 );
509 }
510 report_pending_exception(GlobalScope::get_cx(), true, InRealm::Entered(&ar), can_gc);
511 }
512 }
513
514 pub(crate) fn resolve_module_specifier(
516 global: &GlobalScope,
517 script: Option<&ModuleScript>,
518 specifier: DOMString,
519 can_gc: CanGc,
520 ) -> Fallible<ServoUrl> {
521 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
523 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
525 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
529 None => (global, &global.api_base_url()),
534 };
535
536 let import_map = if global.is::<Window>() {
540 Some(global.import_map())
541 } else {
542 None
543 };
544
545 let serialized_base_url = base_url.as_str();
547 let as_url = Self::resolve_url_like_module_specifier(&specifier, base_url);
549 let specifier = specifier.str();
552 let normalized_specifier = match &as_url {
553 Some(url) => url.as_str(),
554 None => &specifier,
555 };
556
557 let mut result = None;
559 if let Some(map) = import_map {
560 for (prefix, imports) in &map.scopes {
562 let prefix = prefix.as_str();
565 if prefix == serialized_base_url ||
566 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
567 {
568 result = resolve_imports_match(
573 normalized_specifier,
574 as_url.as_ref(),
575 imports,
576 can_gc,
577 )?;
578 break;
579 }
580 }
581
582 if result.is_none() {
585 result = resolve_imports_match(
586 normalized_specifier,
587 as_url.as_ref(),
588 &map.imports,
589 can_gc,
590 )?;
591 }
592 }
593
594 if result.is_none() {
596 result = as_url.clone();
597 }
598
599 match result {
601 Some(result) => {
602 global.add_module_to_resolved_module_set(
605 serialized_base_url,
606 normalized_specifier,
607 as_url.clone(),
608 );
609 Ok(result)
611 },
612 None => Err(Error::Type(
615 "Specifier was a bare specifier, but was not remapped to anything by importMap."
616 .to_owned(),
617 )),
618 }
619 }
620
621 fn resolve_url_like_module_specifier(
623 specifier: &DOMString,
624 base_url: &ServoUrl,
625 ) -> Option<ServoUrl> {
626 if specifier.starts_with('/') ||
628 specifier.starts_with_str("./") ||
629 specifier.starts_with_str("../")
630 {
631 return ServoUrl::parse_with_base(Some(base_url), &specifier.str()).ok();
633 }
634 ServoUrl::parse(&specifier.str()).ok()
636 }
637}
638
639#[derive(JSTraceable, MallocSizeOf)]
640pub(crate) struct ModuleHandler {
641 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
642 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
643}
644
645impl ModuleHandler {
646 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
647 Box::new(Self {
648 task: DomRefCell::new(Some(task)),
649 })
650 }
651}
652
653impl Callback for ModuleHandler {
654 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
655 let task = self.task.borrow_mut().take().unwrap();
656 task.run_box(cx);
657 }
658}
659
660#[derive(Clone, JSTraceable)]
663pub(crate) enum ModuleOwner {
664 #[expect(dead_code)]
665 Worker(TrustedWorkerAddress),
666 Window(Trusted<HTMLScriptElement>),
667 DynamicModule(Trusted<GlobalScope>),
668}
669
670impl ModuleOwner {
671 pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
672 match &self {
673 ModuleOwner::Worker(worker) => (*worker.root().clone()).global(),
674 ModuleOwner::Window(script) => (*script.root()).global(),
675 ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
676 }
677 }
678
679 fn notify_owner_to_finish(&self, module_tree: Option<Rc<ModuleTree>>, can_gc: CanGc) {
680 match &self {
681 ModuleOwner::Worker(_) => unimplemented!(),
682 ModuleOwner::DynamicModule(_) => unimplemented!(),
683 ModuleOwner::Window(script) => {
684 let document = script.root().owner_document();
685
686 let load = match module_tree {
687 Some(module_tree) => Ok(Script::Module(module_tree)),
688 None => Err(()),
689 };
690
691 let asynch = script
692 .root()
693 .upcast::<Element>()
694 .has_attribute(&local_name!("async"));
695
696 if !asynch && (*script.root()).get_parser_inserted() {
697 document.deferred_script_loaded(&script.root(), load, can_gc);
698 } else if !asynch && !(*script.root()).get_non_blocking() {
699 document.asap_in_order_script_loaded(&script.root(), load, can_gc);
700 } else {
701 document.asap_script_loaded(&script.root(), load, can_gc);
702 };
703 },
704 }
705 }
706}
707
708struct ModuleContext {
710 owner: ModuleOwner,
712 data: Vec<u8>,
714 metadata: Option<Metadata>,
716 module_request: ModuleRequest,
718 options: ScriptFetchOptions,
720 status: Result<(), NetworkError>,
722 introduction_type: Option<&'static CStr>,
724}
725
726impl FetchResponseListener for ModuleContext {
727 fn process_request_body(&mut self, _: RequestId) {}
729
730 fn process_request_eof(&mut self, _: RequestId) {}
732
733 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
734 self.metadata = metadata.ok().map(|meta| match meta {
735 FetchMetadata::Unfiltered(m) => m,
736 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
737 });
738
739 let status = self
740 .metadata
741 .as_ref()
742 .map(|m| m.status.clone())
743 .unwrap_or_else(HttpStatus::new_error);
744
745 self.status = {
746 if status.is_error() {
747 Err(NetworkError::ResourceLoadError(
748 "No http status code received".to_owned(),
749 ))
750 } else if status.is_success() {
751 Ok(())
752 } else {
753 Err(NetworkError::ResourceLoadError(format!(
754 "HTTP error code {}",
755 status.code()
756 )))
757 }
758 };
759 }
760
761 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
762 if self.status.is_ok() {
763 self.data.append(&mut chunk);
764 }
765 }
766
767 fn process_response_eof(
770 mut self,
771 _: RequestId,
772 response: Result<(), NetworkError>,
773 timing: ResourceFetchTiming,
774 ) {
775 let global = self.owner.global();
776 let (url, module_type) = &self.module_request;
777
778 if let Some(window) = global.downcast::<Window>() {
779 window
780 .Document()
781 .finish_load(LoadType::Script(url.clone()), CanGc::note());
782 }
783
784 network_listener::submit_timing(&self, &response, &timing, CanGc::note());
785
786 let Some(ModuleStatus::Fetching(pending)) =
787 global.get_module_map_entry(&self.module_request)
788 else {
789 return error!("Processing response for a non pending module request");
790 };
791 let promise = pending
792 .borrow_mut()
793 .take()
794 .expect("Need promise to process response");
795
796 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
799 error!("Fetching module script failed {:?}", error);
800 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
801 return promise.resolve_native(&(), CanGc::note());
802 }
803
804 let metadata = self.metadata.take().unwrap();
805 let final_url = metadata.final_url;
806
807 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
809
810 let mut module_script = None;
812
813 let referrer_policy = metadata
815 .headers
816 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
817 .into();
818
819 if referrer_policy != ReferrerPolicy::EmptyString {
821 self.options.referrer_policy = referrer_policy;
822 }
823
824 if let Some(mime) = mime_type {
830 let (mut source_text, _, _) = UTF_8.decode(&self.data);
832
833 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
836 matches!(module_type, ModuleType::JavaScript)
837 {
838 if let Some(window) = global.downcast::<Window>() {
839 substitute_with_local_script(window, &mut source_text, final_url.clone());
840 }
841
842 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
843 Rc::new(DOMString::from(source_text.clone())),
844 self.owner.clone(),
845 &final_url,
846 self.options,
847 true,
848 1,
849 self.introduction_type,
850 CanGc::note(),
851 ));
852 module_script = Some(module_tree);
853 }
854
855 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
858 let module_tree = Rc::new(ModuleTree::crate_a_json_module_script(
859 &source_text,
860 &global,
861 &final_url,
862 self.introduction_type,
863 CanGc::note(),
864 ));
865 module_script = Some(module_tree);
866 }
867 }
868 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
870 promise.resolve_native(&(), CanGc::note());
871 }
872
873 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
874 let global = &self.resource_timing_global();
875 global.report_csp_violations(violations, None, None);
876 }
877}
878
879impl ResourceTimingListener for ModuleContext {
880 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
881 let initiator_type = InitiatorType::LocalName("module".to_string());
882 let (url, _) = &self.module_request;
883 (initiator_type, url.clone())
884 }
885
886 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
887 self.owner.global()
888 }
889}
890
891#[expect(unsafe_code)]
892#[expect(non_snake_case)]
893pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
896 unsafe {
897 if GetModuleResolveHook(rt).is_some() {
898 return;
899 }
900
901 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
902 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
903 SetScriptPrivateReferenceHooks(
904 rt,
905 Some(host_add_ref_top_level_script),
906 Some(host_release_top_level_script),
907 );
908 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
909 }
910}
911
912#[expect(unsafe_code)]
913unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
914 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
915 mem::forget(val.clone());
916 mem::forget(val);
917}
918
919#[expect(unsafe_code)]
920unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
921 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
922}
923
924#[expect(unsafe_code)]
925pub(crate) unsafe extern "C" fn host_import_module_dynamically(
928 cx: *mut JSContext,
929 reference_private: RawHandleValue,
930 specifier: RawHandle<*mut JSObject>,
931 promise: RawHandle<*mut JSObject>,
932) -> bool {
933 let cx = unsafe { SafeJSContext::from_ptr(cx) };
935 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx);
936
937 let jsstr = ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(*cx, specifier) }).unwrap();
938 let module_type = unsafe { GetModuleRequestType(*cx, specifier) };
939 let specifier = unsafe { jsstr_to_string(*cx, jsstr) };
940
941 let payload = Payload::PromiseRecord(promise.clone());
942 host_load_imported_module(
943 cx,
944 None,
945 reference_private,
946 specifier,
947 module_type,
948 None,
949 payload,
950 );
951
952 true
953}
954
955#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
956pub(crate) struct ScriptFetchOptions {
958 #[no_trace]
959 pub(crate) referrer: Referrer,
960 pub(crate) integrity_metadata: String,
961 #[no_trace]
962 pub(crate) credentials_mode: CredentialsMode,
963 pub(crate) cryptographic_nonce: String,
964 #[no_trace]
965 pub(crate) parser_metadata: ParserMetadata,
966 #[no_trace]
967 pub(crate) referrer_policy: ReferrerPolicy,
968}
969
970impl ScriptFetchOptions {
971 pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
973 Self {
974 cryptographic_nonce: String::new(),
975 integrity_metadata: String::new(),
976 referrer: global.get_referrer(),
977 parser_metadata: ParserMetadata::NotParserInserted,
978 credentials_mode: CredentialsMode::CredentialsSameOrigin,
979 referrer_policy: ReferrerPolicy::EmptyString,
980 }
981 }
982
983 pub(crate) fn descendant_fetch_options(&self) -> ScriptFetchOptions {
985 Self {
986 referrer: self.referrer.clone(),
987 integrity_metadata: String::new(),
988 cryptographic_nonce: self.cryptographic_nonce.clone(),
989 credentials_mode: self.credentials_mode,
990 parser_metadata: self.parser_metadata,
991 referrer_policy: self.referrer_policy,
992 }
993 }
994}
995
996#[expect(unsafe_code)]
997pub(crate) unsafe fn module_script_from_reference_private(
998 reference_private: &RawHandle<JSVal>,
999) -> Option<&ModuleScript> {
1000 if reference_private.get().is_undefined() {
1001 return None;
1002 }
1003 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1004}
1005
1006#[expect(unsafe_code)]
1007#[expect(non_snake_case)]
1008unsafe extern "C" fn HostResolveImportedModule(
1011 cx: *mut JSContext,
1012 reference_private: RawHandleValue,
1013 specifier: RawHandle<*mut JSObject>,
1014) -> *mut JSObject {
1015 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1016 let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1017
1018 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1020 let jsstr =
1021 std::ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(cx, specifier) }).unwrap();
1022 let module_type = unsafe { GetModuleRequestType(cx, specifier) };
1023
1024 let specifier = DOMString::from_string(unsafe { jsstr_to_string(cx, jsstr) });
1025 let url =
1026 ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier, CanGc::note());
1027
1028 assert!(url.is_ok());
1030
1031 let parsed_url = url.unwrap();
1032
1033 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1035
1036 assert!(module.as_ref().is_some_and(
1038 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1039 ));
1040
1041 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1042 unreachable!()
1043 };
1044
1045 let fetched_module_object = module_tree.get_record();
1046
1047 assert!(fetched_module_object.is_some());
1049
1050 if let Some(record) = fetched_module_object {
1052 return record.handle().get();
1053 }
1054
1055 unreachable!()
1056}
1057
1058#[expect(unsafe_code)]
1059#[expect(non_snake_case)]
1060unsafe extern "C" fn HostPopulateImportMeta(
1063 cx: *mut JSContext,
1064 reference_private: RawHandleValue,
1065 meta_object: RawHandle<*mut JSObject>,
1066) -> bool {
1067 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1068 let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1069
1070 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1072 Some(module_data) => module_data.base_url.clone(),
1073 None => global_scope.api_base_url(),
1074 };
1075
1076 let url_string = unsafe {
1077 JS_NewStringCopyN(
1078 cx,
1079 base_url.as_str().as_ptr() as *const _,
1080 base_url.as_str().len(),
1081 )
1082 };
1083 rooted!(in(cx) let url_string = url_string);
1084
1085 unsafe {
1087 JS_DefineProperty4(
1088 cx,
1089 meta_object,
1090 c"url".as_ptr(),
1091 url_string.handle().into_handle(),
1092 JSPROP_ENUMERATE.into(),
1093 )
1094 }
1095}
1096
1097pub(crate) fn fetch_an_external_module_script(
1099 url: ServoUrl,
1100 owner: ModuleOwner,
1101 options: ScriptFetchOptions,
1102 can_gc: CanGc,
1103) {
1104 let referrer = owner.global().get_referrer();
1105
1106 fetch_a_single_module_script(
1109 url,
1110 owner.clone(),
1111 Destination::Script,
1112 options,
1113 referrer,
1114 None,
1115 true,
1116 Some(IntroductionType::SRC_SCRIPT),
1117 move |module_tree| {
1118 let Some(module) = module_tree else {
1119 return owner.notify_owner_to_finish(None, can_gc);
1121 };
1122
1123 fetch_the_descendants_and_link_module_script(
1125 module,
1126 Destination::Script,
1127 owner,
1128 can_gc,
1129 );
1130 },
1131 );
1132}
1133
1134pub(crate) fn fetch_inline_module_script(
1136 owner: ModuleOwner,
1137 module_script_text: Rc<DOMString>,
1138 url: ServoUrl,
1139 options: ScriptFetchOptions,
1140 line_number: u32,
1141 can_gc: CanGc,
1142) {
1143 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1145 module_script_text,
1146 owner.clone(),
1147 &url,
1148 options,
1149 false,
1150 line_number,
1151 Some(IntroductionType::INLINE_SCRIPT),
1152 can_gc,
1153 ));
1154
1155 fetch_the_descendants_and_link_module_script(module_tree, Destination::Script, owner, can_gc);
1157}
1158
1159fn fetch_the_descendants_and_link_module_script(
1161 module_script: Rc<ModuleTree>,
1162 destination: Destination,
1163 owner: ModuleOwner,
1164 can_gc: CanGc,
1165) {
1166 let global = owner.global();
1167
1168 if module_script.get_record().is_none() {
1171 let parse_error = module_script.get_parse_error().cloned();
1172
1173 module_script.set_rethrow_error(parse_error.unwrap());
1175
1176 owner.notify_owner_to_finish(Some(module_script), can_gc);
1178
1179 return;
1181 }
1182
1183 let state = Rc::new(LoadState {
1186 error_to_rethrow: RefCell::new(None),
1187 destination,
1188 fetch_client: owner.clone(),
1189 });
1190
1191 let loading_promise =
1195 load_requested_modules(&global, module_script.clone(), Some(Rc::clone(&state)));
1196
1197 let fulfillment_owner = owner.clone();
1198 let fulfilled_module = module_script.clone();
1199
1200 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1202 task!(fulfilled_steps: |fulfillment_owner: ModuleOwner| {
1203 let global = fulfillment_owner.global();
1204
1205 if let Some(record) = fulfilled_module.get_record() {
1206 let instantiated = ModuleTree::instantiate_module_tree(&global, record.handle());
1208
1209 if let Err(exception) = instantiated {
1211 fulfilled_module.set_rethrow_error(exception);
1212 }
1213 }
1214
1215 fulfillment_owner.notify_owner_to_finish(Some(fulfilled_module), CanGc::note());
1217 }),
1218 ));
1219
1220 let rejection_owner = owner.clone();
1221 let rejected_module = module_script.clone();
1222
1223 let loading_promise_rejection = ModuleHandler::new_boxed(Box::new(
1225 task!(rejected_steps: |rejection_owner: ModuleOwner, state: Rc<LoadState>| {
1226 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1229 rejected_module.set_rethrow_error(error.clone());
1230 rejection_owner.notify_owner_to_finish(Some(rejected_module), CanGc::note());
1231 } else {
1232 rejection_owner.notify_owner_to_finish(None, CanGc::note());
1234 }
1235 }),
1236 ));
1237
1238 let handler = PromiseNativeHandler::new(
1239 &global,
1240 Some(loading_promise_fulfillment),
1241 Some(loading_promise_rejection),
1242 can_gc,
1243 );
1244
1245 let realm = enter_realm(&*global);
1246 let comp = InRealm::Entered(&realm);
1247 let _ais = AutoIncumbentScript::new(&global);
1248
1249 loading_promise.append_native_handler(&handler, comp, can_gc);
1250}
1251
1252#[expect(clippy::too_many_arguments)]
1254pub(crate) fn fetch_a_single_module_script(
1255 url: ServoUrl,
1256 owner: ModuleOwner,
1257 destination: Destination,
1258 options: ScriptFetchOptions,
1259 referrer: Referrer,
1260 module_type: Option<ModuleType>,
1261 is_top_level: bool,
1262 introduction_type: Option<&'static CStr>,
1263 on_complete: impl FnOnce(Option<Rc<ModuleTree>>) + 'static,
1264) {
1265 let global = owner.global();
1266
1267 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1271
1272 let module_request = (url.clone(), module_type);
1278 let entry = global.get_module_map_entry(&module_request);
1279
1280 let pending = match entry {
1281 Some(ModuleStatus::Fetching(pending)) => pending,
1282 Some(ModuleStatus::Loaded(module_tree)) => {
1284 return on_complete(module_tree);
1285 },
1286 None => DomRefCell::new(None),
1287 };
1288
1289 let global_scope = DomRoot::from_ref(&*global);
1290 let module_map_key = module_request.clone();
1291 let handler = ModuleHandler::new_boxed(Box::new(
1292 task!(fetch_completed: |global_scope: DomRoot<GlobalScope>| {
1293 let key = module_map_key;
1294 let module = global_scope.get_module_map_entry(&key);
1295
1296 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1297 on_complete(module_tree);
1298 }
1299 }),
1300 ));
1301
1302 let handler = PromiseNativeHandler::new(&global, Some(handler), None, CanGc::note());
1303
1304 let realm = enter_realm(&*global);
1305 let comp = InRealm::Entered(&realm);
1306 let _ais = AutoIncumbentScript::new(&global);
1307
1308 let has_pending_fetch = pending.borrow().is_some();
1309 pending
1310 .borrow_mut()
1311 .get_or_insert_with(|| Promise::new_in_current_realm(comp, CanGc::note()))
1312 .append_native_handler(&handler, comp, CanGc::note());
1313
1314 if has_pending_fetch {
1317 return;
1318 }
1319
1320 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1322
1323 let document: Option<DomRoot<Document>> = match &owner {
1324 ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1325 ModuleOwner::Window(script) => Some(script.root().owner_document()),
1326 };
1327 let webview_id = document.as_ref().map(|document| document.webview_id());
1328
1329 let mode = match destination {
1334 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1335 _ => RequestMode::CorsMode,
1336 };
1337
1338 let destination = match module_type {
1340 ModuleType::JSON => Destination::Json,
1341 ModuleType::JavaScript | ModuleType::Unknown => destination,
1342 };
1343
1344 let request = RequestBuilder::new(webview_id, url.clone(), referrer)
1348 .destination(destination)
1349 .parser_metadata(options.parser_metadata)
1350 .integrity_metadata(options.integrity_metadata.clone())
1351 .credentials_mode(options.credentials_mode)
1352 .referrer_policy(options.referrer_policy)
1353 .mode(mode)
1354 .with_global_scope(&global)
1355 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1356
1357 let context = ModuleContext {
1358 owner,
1359 data: vec![],
1360 metadata: None,
1361 module_request,
1362 options,
1363 status: Ok(()),
1364 introduction_type,
1365 };
1366
1367 let network_listener = NetworkListener::new(
1368 context,
1369 global.task_manager().networking_task_source().to_sendable(),
1370 );
1371 match document {
1372 Some(document) => {
1373 document.loader_mut().fetch_async_with_callback(
1374 LoadType::Script(url),
1375 request,
1376 network_listener.into_callback(),
1377 );
1378 },
1379 None => global.fetch_with_network_listener(request, network_listener),
1380 };
1381}
1382
1383pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1384pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1385
1386#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1388pub(crate) struct ResolvedModule {
1389 base_url: String,
1391 specifier: String,
1393 #[no_trace]
1395 specifier_url: Option<ServoUrl>,
1396}
1397
1398impl ResolvedModule {
1399 pub(crate) fn new(
1400 base_url: String,
1401 specifier: String,
1402 specifier_url: Option<ServoUrl>,
1403 ) -> Self {
1404 Self {
1405 base_url,
1406 specifier,
1407 specifier_url,
1408 }
1409 }
1410}
1411
1412#[derive(Default, JSTraceable, MallocSizeOf)]
1414pub(crate) struct ImportMap {
1415 #[no_trace]
1416 imports: ModuleSpecifierMap,
1417 #[no_trace]
1418 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1419 #[no_trace]
1420 integrity: ModuleIntegrityMap,
1421}
1422
1423pub(crate) fn register_import_map(
1425 global: &GlobalScope,
1426 result: Fallible<ImportMap>,
1427 can_gc: CanGc,
1428) {
1429 match result {
1430 Ok(new_import_map) => {
1431 merge_existing_and_new_import_maps(global, new_import_map, can_gc);
1433 },
1434 Err(exception) => {
1435 throw_dom_exception(GlobalScope::get_cx(), global, exception.clone(), can_gc);
1438 },
1439 }
1440}
1441
1442fn merge_existing_and_new_import_maps(
1444 global: &GlobalScope,
1445 new_import_map: ImportMap,
1446 can_gc: CanGc,
1447) {
1448 let new_import_map_scopes = new_import_map.scopes;
1450
1451 let mut old_import_map = global.import_map_mut();
1453
1454 let mut new_import_map_imports = new_import_map.imports;
1456
1457 let resolved_module_set = global.resolved_module_set();
1458 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1460 for record in resolved_module_set.iter() {
1462 let prefix = scope_prefix.as_str();
1465 if prefix == record.base_url ||
1466 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1467 {
1468 scope_imports.retain(|key, val| {
1470 if *key == record.specifier ||
1475 (key.ends_with('\u{002f}') &&
1476 record.specifier.starts_with(key) &&
1477 (record.specifier_url.is_none() ||
1478 record
1479 .specifier_url
1480 .as_ref()
1481 .map(|u| u.is_special_scheme())
1482 .unwrap_or_default()))
1483 {
1484 Console::internal_warn(
1487 global,
1488 DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
1489 );
1490 false
1492 } else {
1493 true
1494 }
1495 })
1496 }
1497 }
1498
1499 if old_import_map.scopes.contains_key(&scope_prefix) {
1501 let merged_module_specifier_map = merge_module_specifier_maps(
1504 global,
1505 scope_imports,
1506 &old_import_map.scopes[&scope_prefix],
1507 can_gc,
1508 );
1509 old_import_map
1510 .scopes
1511 .insert(scope_prefix, merged_module_specifier_map);
1512 } else {
1513 old_import_map.scopes.insert(scope_prefix, scope_imports);
1515 }
1516 }
1517
1518 for (url, integrity) in &new_import_map.integrity {
1520 if old_import_map.integrity.contains_key(url) {
1522 Console::internal_warn(
1525 global,
1526 DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
1527 );
1528 continue;
1530 }
1531
1532 old_import_map
1534 .integrity
1535 .insert(url.clone(), integrity.clone());
1536 }
1537
1538 for record in resolved_module_set.iter() {
1540 new_import_map_imports.retain(|specifier, val| {
1542 if specifier.starts_with(&record.specifier) {
1544 Console::internal_warn(
1547 global,
1548 DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
1549 );
1550 false
1552 } else {
1553 true
1554 }
1555 });
1556 }
1557
1558 let merged_module_specifier_map = merge_module_specifier_maps(
1561 global,
1562 new_import_map_imports,
1563 &old_import_map.imports,
1564 can_gc,
1565 );
1566 old_import_map.imports = merged_module_specifier_map;
1567}
1568
1569fn merge_module_specifier_maps(
1571 global: &GlobalScope,
1572 new_map: ModuleSpecifierMap,
1573 old_map: &ModuleSpecifierMap,
1574 _can_gc: CanGc,
1575) -> ModuleSpecifierMap {
1576 let mut merged_map = old_map.clone();
1578
1579 for (specifier, url) in new_map {
1581 if old_map.contains_key(&specifier) {
1583 Console::internal_warn(
1586 global,
1587 DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
1588 );
1589
1590 continue;
1592 }
1593
1594 merged_map.insert(specifier, url);
1596 }
1597
1598 merged_map
1599}
1600
1601pub(crate) fn parse_an_import_map_string(
1603 module_owner: ModuleOwner,
1604 input: Rc<DOMString>,
1605 base_url: ServoUrl,
1606 can_gc: CanGc,
1607) -> Fallible<ImportMap> {
1608 let parsed: JsonValue = serde_json::from_str(&input.str())
1610 .map_err(|_| Error::Type("The value needs to be a JSON object.".to_owned()))?;
1611 let JsonValue::Object(mut parsed) = parsed else {
1614 return Err(Error::Type(
1615 "The top-level value needs to be a JSON object.".to_owned(),
1616 ));
1617 };
1618
1619 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1621 if let Some(imports) = parsed.get("imports") {
1623 let JsonValue::Object(imports) = imports else {
1626 return Err(Error::Type(
1627 "The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1628 ));
1629 };
1630 sorted_and_normalized_imports = sort_and_normalize_module_specifier_map(
1633 &module_owner.global(),
1634 imports,
1635 &base_url,
1636 can_gc,
1637 );
1638 }
1639
1640 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1642 if let Some(scopes) = parsed.get("scopes") {
1644 let JsonValue::Object(scopes) = scopes else {
1647 return Err(Error::Type(
1648 "The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1649 ));
1650 };
1651 sorted_and_normalized_scopes =
1654 sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url, can_gc)?;
1655 }
1656
1657 let mut normalized_integrity = ModuleIntegrityMap::new();
1659 if let Some(integrity) = parsed.get("integrity") {
1661 let JsonValue::Object(integrity) = integrity else {
1664 return Err(Error::Type(
1665 "The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1666 ));
1667 };
1668 normalized_integrity =
1671 normalize_module_integrity_map(&module_owner.global(), integrity, &base_url, can_gc);
1672 }
1673
1674 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1678 if !parsed.is_empty() {
1679 Console::internal_warn(
1680 &module_owner.global(),
1681 DOMString::from(
1682 "Invalid top-level key was present in the import map.
1683 Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
1684 ),
1685 );
1686 }
1687
1688 Ok(ImportMap {
1690 imports: sorted_and_normalized_imports,
1691 scopes: sorted_and_normalized_scopes,
1692 integrity: normalized_integrity,
1693 })
1694}
1695
1696fn sort_and_normalize_module_specifier_map(
1698 global: &GlobalScope,
1699 original_map: &JsonMap<String, JsonValue>,
1700 base_url: &ServoUrl,
1701 can_gc: CanGc,
1702) -> ModuleSpecifierMap {
1703 let mut normalized = ModuleSpecifierMap::new();
1705
1706 for (specifier_key, value) in original_map {
1708 let Some(normalized_specifier_key) =
1711 normalize_specifier_key(global, specifier_key, base_url, can_gc)
1712 else {
1713 continue;
1715 };
1716
1717 let JsonValue::String(value) = value else {
1719 Console::internal_warn(global, DOMString::from("Addresses need to be strings."));
1722
1723 normalized.insert(normalized_specifier_key, None);
1725 continue;
1727 };
1728
1729 let value = DOMString::from(value.as_str());
1731 let Some(address_url) = ModuleTree::resolve_url_like_module_specifier(&value, base_url)
1732 else {
1733 Console::internal_warn(
1737 global,
1738 DOMString::from(format!(
1739 "Value failed to resolve to module specifier: {value}"
1740 )),
1741 );
1742
1743 normalized.insert(normalized_specifier_key, None);
1745 continue;
1747 };
1748
1749 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
1752 Console::internal_warn(
1756 global,
1757 DOMString::from(format!(
1758 "Invalid address for specifier key '{specifier_key}': {address_url}.
1759 Since specifierKey ends with a slash, the address needs to as well."
1760 )),
1761 );
1762
1763 normalized.insert(normalized_specifier_key, None);
1765 continue;
1767 }
1768
1769 normalized.insert(normalized_specifier_key, Some(address_url));
1771 }
1772
1773 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1776 normalized
1777}
1778
1779fn sort_and_normalize_scopes(
1781 global: &GlobalScope,
1782 original_map: &JsonMap<String, JsonValue>,
1783 base_url: &ServoUrl,
1784 can_gc: CanGc,
1785) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
1786 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1788
1789 for (scope_prefix, potential_specifier_map) in original_map {
1791 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
1794 return Err(Error::Type(
1795 "The value of the scope with prefix scopePrefix needs to be a JSON object."
1796 .to_owned(),
1797 ));
1798 };
1799
1800 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
1802 Console::internal_warn(
1806 global,
1807 DOMString::from(format!(
1808 "Scope prefix URL was not parseable: {scope_prefix}"
1809 )),
1810 );
1811 continue;
1813 };
1814
1815 let normalized_scope_prefix = scope_prefix_url;
1817
1818 let normalized_specifier_map = sort_and_normalize_module_specifier_map(
1821 global,
1822 potential_specifier_map,
1823 base_url,
1824 can_gc,
1825 );
1826 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
1827 }
1828
1829 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1832 Ok(normalized)
1833}
1834
1835fn normalize_module_integrity_map(
1837 global: &GlobalScope,
1838 original_map: &JsonMap<String, JsonValue>,
1839 base_url: &ServoUrl,
1840 _can_gc: CanGc,
1841) -> ModuleIntegrityMap {
1842 let mut normalized = ModuleIntegrityMap::new();
1844
1845 for (key, value) in original_map {
1847 let Some(resolved_url) =
1850 ModuleTree::resolve_url_like_module_specifier(&DOMString::from(key.as_str()), base_url)
1851 else {
1852 Console::internal_warn(
1856 global,
1857 DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
1858 );
1859 continue;
1861 };
1862
1863 let JsonValue::String(value) = value else {
1865 Console::internal_warn(
1868 global,
1869 DOMString::from("Integrity metadata values need to be strings."),
1870 );
1871 continue;
1873 };
1874
1875 normalized.insert(resolved_url, value.clone());
1877 }
1878
1879 normalized
1881}
1882
1883fn normalize_specifier_key(
1885 global: &GlobalScope,
1886 specifier_key: &str,
1887 base_url: &ServoUrl,
1888 _can_gc: CanGc,
1889) -> Option<String> {
1890 if specifier_key.is_empty() {
1892 Console::internal_warn(
1895 global,
1896 DOMString::from("Specifier keys may not be the empty string."),
1897 );
1898 return None;
1900 }
1901 let url =
1903 ModuleTree::resolve_url_like_module_specifier(&DOMString::from(specifier_key), base_url);
1904
1905 if let Some(url) = url {
1907 return Some(url.into_string());
1908 }
1909
1910 Some(specifier_key.to_string())
1912}
1913
1914pub(crate) fn resolve_imports_match(
1919 normalized_specifier: &str,
1920 as_url: Option<&ServoUrl>,
1921 specifier_map: &ModuleSpecifierMap,
1922 _can_gc: CanGc,
1923) -> Fallible<Option<ServoUrl>> {
1924 for (specifier_key, resolution_result) in specifier_map {
1926 if specifier_key == normalized_specifier {
1928 if let Some(resolution_result) = resolution_result {
1929 return Ok(Some(resolution_result.clone()));
1933 } else {
1934 return Err(Error::Type(
1936 "Resolution of specifierKey was blocked by a null entry.".to_owned(),
1937 ));
1938 }
1939 }
1940
1941 if specifier_key.ends_with('\u{002f}') &&
1946 normalized_specifier.starts_with(specifier_key) &&
1947 (as_url.is_none() || as_url.map(|u| u.is_special_scheme()).unwrap_or_default())
1948 {
1949 let Some(resolution_result) = resolution_result else {
1952 return Err(Error::Type(
1953 "Resolution of specifierKey was blocked by a null entry.".to_owned(),
1954 ));
1955 };
1956
1957 let after_prefix = normalized_specifier
1959 .strip_prefix(specifier_key)
1960 .expect("specifier_key should be the prefix of normalized_specifier");
1961
1962 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
1964
1965 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
1967
1968 let Ok(url) = url else {
1971 return Err(Error::Type(
1972 "Resolution of normalizedSpecifier was blocked since
1973 the afterPrefix portion could not be URL-parsed relative to
1974 the resolutionResult mapped to by the specifierKey prefix."
1975 .to_owned(),
1976 ));
1977 };
1978
1979 if !url.as_str().starts_with(resolution_result.as_str()) {
1982 return Err(Error::Type(
1983 "Resolution of normalizedSpecifier was blocked due to
1984 it backtracking above its prefix specifierKey."
1985 .to_owned(),
1986 ));
1987 }
1988
1989 return Ok(Some(url));
1991 }
1992 }
1993
1994 Ok(None)
1996}