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 html5ever::local_name;
18use hyper_serde::Serde;
19use indexmap::IndexMap;
20use indexmap::map::Entry;
21use js::context::JSContext;
22use js::conversions::jsstr_to_string;
23use js::gc::MutableHandleValue;
24use js::jsapi::{
25 CallArgs, CompileJsonModule1, CompileModule1, ExceptionStackBehavior,
26 GetFunctionNativeReserved, GetModuleResolveHook, Handle as RawHandle,
27 HandleValue as RawHandleValue, Heap, JS_ClearPendingException, JS_GetFunctionObject,
28 JSAutoRealm, JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime,
29 ModuleErrorBehaviour, ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook,
30 SetModuleMetadataHook, SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks,
31 ThrowOnModuleEvaluationFailure, Value,
32};
33use js::jsval::{JSVal, PrivateValue, UndefinedValue};
34use js::realm::{AutoRealm, CurrentRealm};
35use js::rust::wrappers::{JS_GetPendingException, JS_SetPendingException, ModuleEvaluate};
36use js::rust::wrappers2::{
37 DefineFunctionWithReserved, GetModuleRequestSpecifier, GetModuleRequestType,
38 JS_DefineProperty4, JS_NewStringCopyN, ModuleLink,
39};
40use js::rust::{
41 CompileOptionsWrapper, Handle, HandleObject as RustHandleObject, HandleValue, ToString,
42 transform_str_to_source_text,
43};
44use mime::Mime;
45use net_traits::http_status::HttpStatus;
46use net_traits::mime_classifier::MimeClassifier;
47use net_traits::request::{
48 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId, RequestMode,
49};
50use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
51use script_bindings::cformat;
52use script_bindings::domstring::BytesView;
53use script_bindings::error::Fallible;
54use script_bindings::trace::CustomTraceable;
55use serde_json::{Map as JsonMap, Value as JsonValue};
56use servo_url::ServoUrl;
57
58use crate::document_loader::LoadType;
59use crate::dom::bindings::cell::DomRefCell;
60use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
61use crate::dom::bindings::conversions::SafeToJSValConvertible;
62use crate::dom::bindings::error::{
63 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
64};
65use crate::dom::bindings::inheritance::Castable;
66use crate::dom::bindings::refcounted::Trusted;
67use crate::dom::bindings::reflector::{DomGlobal, DomObject};
68use crate::dom::bindings::root::DomRoot;
69use crate::dom::bindings::settings_stack::AutoIncumbentScript;
70use crate::dom::bindings::str::DOMString;
71use crate::dom::bindings::trace::RootedTraceableBox;
72use crate::dom::csp::{GlobalCspReporting, Violation};
73use crate::dom::document::Document;
74use crate::dom::element::Element;
75use crate::dom::globalscope::GlobalScope;
76use crate::dom::html::htmlscriptelement::{HTMLScriptElement, SCRIPT_JS_MIMES, Script};
77use crate::dom::htmlscriptelement::substitute_with_local_script;
78use crate::dom::node::NodeTraits;
79use crate::dom::performance::performanceresourcetiming::InitiatorType;
80use crate::dom::promise::Promise;
81use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
82use crate::dom::types::Console;
83use crate::dom::window::Window;
84use crate::dom::worker::TrustedWorkerAddress;
85use crate::fetch::RequestWithGlobalScope;
86use crate::module_loading::{
87 LoadState, Payload, host_load_imported_module, load_requested_modules,
88};
89use crate::network_listener::{
90 self, FetchResponseListener, NetworkListener, ResourceTimingListener,
91};
92use crate::realms::{InRealm, enter_realm};
93use crate::script_runtime::{CanGc, IntroductionType, JSContext as SafeJSContext};
94use crate::task::NonSendTaskBox;
95
96pub(crate) fn gen_type_error(global: &GlobalScope, error: Error, can_gc: CanGc) -> RethrowError {
97 rooted!(in(*GlobalScope::get_cx()) let mut thrown = UndefinedValue());
98 error.to_jsval(GlobalScope::get_cx(), global, thrown.handle_mut(), can_gc);
99
100 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
101}
102
103#[derive(JSTraceable)]
104pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
105
106impl ModuleObject {
107 pub(crate) fn new(obj: RustHandleObject) -> ModuleObject {
108 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
109 }
110
111 pub(crate) fn handle(&'_ self) -> js::gc::HandleObject<'_> {
112 self.0.handle()
113 }
114}
115
116#[derive(JSTraceable)]
117pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
118
119impl RethrowError {
120 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
121 Self(RootedTraceableBox::from_box(val))
122 }
123
124 #[expect(unsafe_code)]
125 pub(crate) fn from_pending_exception(cx: SafeJSContext) -> Self {
126 rooted!(in(*cx) let mut exception = UndefinedValue());
127 assert!(unsafe { JS_GetPendingException(*cx, exception.handle_mut()) });
128 unsafe { JS_ClearPendingException(*cx) };
129
130 Self::new(Heap::boxed(exception.get()))
131 }
132
133 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
134 self.0.handle()
135 }
136}
137
138impl Debug for RethrowError {
139 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
140 "RethrowError(...)".fmt(fmt)
141 }
142}
143
144impl Clone for RethrowError {
145 fn clone(&self) -> Self {
146 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
147 }
148}
149
150pub(crate) struct ModuleScript {
151 pub(crate) base_url: ServoUrl,
152 pub(crate) options: ScriptFetchOptions,
153 owner: Option<ModuleOwner>,
154}
155
156impl ModuleScript {
157 pub(crate) fn new(
158 base_url: ServoUrl,
159 options: ScriptFetchOptions,
160 owner: Option<ModuleOwner>,
161 ) -> Self {
162 ModuleScript {
163 base_url,
164 options,
165 owner,
166 }
167 }
168}
169
170pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
171
172#[derive(Clone, JSTraceable)]
173pub(crate) enum ModuleStatus {
174 Fetching(DomRefCell<Option<Rc<Promise>>>),
175 Loaded(Option<Rc<ModuleTree>>),
176}
177
178#[derive(JSTraceable, MallocSizeOf)]
179pub(crate) struct ModuleTree {
180 #[no_trace]
181 url: ServoUrl,
182 #[ignore_malloc_size_of = "mozjs"]
183 record: OnceCell<ModuleObject>,
184 #[ignore_malloc_size_of = "mozjs"]
185 parse_error: OnceCell<RethrowError>,
186 #[ignore_malloc_size_of = "mozjs"]
187 rethrow_error: DomRefCell<Option<RethrowError>>,
188 #[no_trace]
189 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
190}
191
192impl ModuleTree {
193 pub(crate) fn get_url(&self) -> ServoUrl {
194 self.url.clone()
195 }
196
197 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
198 self.record.get()
199 }
200
201 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
202 self.parse_error.get()
203 }
204
205 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
206 &self.rethrow_error
207 }
208
209 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
210 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
211 }
212
213 pub(crate) fn find_descendant_inside_module_map(
214 &self,
215 global: &GlobalScope,
216 specifier: &String,
217 module_type: ModuleType,
218 ) -> Option<Rc<ModuleTree>> {
219 self.loaded_modules
220 .borrow()
221 .get(specifier)
222 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
223 .and_then(|status| match status {
224 ModuleStatus::Fetching(_) => None,
225 ModuleStatus::Loaded(module_tree) => module_tree,
226 })
227 }
228
229 pub(crate) fn insert_module_dependency(
230 &self,
231 module: &Rc<ModuleTree>,
232 module_request_specifier: String,
233 ) {
234 let url = module.url.clone();
236 match self
237 .loaded_modules
238 .borrow_mut()
239 .entry(module_request_specifier)
240 {
241 Entry::Occupied(entry) => {
244 assert_eq!(*entry.get(), url);
246 },
247 Entry::Vacant(entry) => {
249 entry.insert(url);
252 },
253 }
254 }
255}
256
257pub(crate) struct ModuleSource {
258 pub source: Rc<DOMString>,
259 pub unminified_dir: Option<String>,
260 pub external: bool,
261 pub url: ServoUrl,
262}
263
264impl crate::unminify::ScriptSource for ModuleSource {
265 fn unminified_dir(&self) -> Option<String> {
266 self.unminified_dir.clone()
267 }
268
269 fn extract_bytes(&self) -> BytesView<'_> {
270 self.source.as_bytes()
271 }
272
273 fn rewrite_source(&mut self, source: Rc<DOMString>) {
274 self.source = source;
275 }
276
277 fn url(&self) -> ServoUrl {
278 self.url.clone()
279 }
280
281 fn is_external(&self) -> bool {
282 self.external
283 }
284}
285
286impl ModuleTree {
287 #[expect(unsafe_code)]
288 #[expect(clippy::too_many_arguments)]
289 fn create_a_javascript_module_script(
293 source: Rc<DOMString>,
294 owner: ModuleOwner,
295 url: &ServoUrl,
296 options: ScriptFetchOptions,
297 external: bool,
298 line_number: u32,
299 introduction_type: Option<&'static CStr>,
300 _can_gc: CanGc,
301 ) -> Self {
302 let cx = GlobalScope::get_cx();
303 let global = owner.global();
304 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
305
306 let module = ModuleTree {
309 url: url.clone(),
310 record: OnceCell::new(),
311 parse_error: OnceCell::new(),
312 rethrow_error: DomRefCell::new(None),
313 loaded_modules: DomRefCell::new(IndexMap::new()),
314 };
315
316 let c_url = cformat!("{url}");
317 let mut compile_options =
318 unsafe { CompileOptionsWrapper::new_raw(*cx, c_url, line_number) };
319 if let Some(introduction_type) = introduction_type {
320 compile_options.set_introduction_type(introduction_type);
321 }
322 let mut module_source = ModuleSource {
323 source,
324 unminified_dir: global.unminified_js_dir(),
325 external,
326 url: url.clone(),
327 };
328 crate::unminify::unminify_js(&mut module_source);
329
330 unsafe {
331 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
333 module_script.set(CompileModule1(
334 *cx,
335 compile_options.ptr,
336 &mut transform_str_to_source_text(&module_source.source.str()),
337 ));
338
339 if module_script.is_null() {
341 warn!("fail to compile module script of {}", url);
342
343 let _ = module
345 .parse_error
346 .set(RethrowError::from_pending_exception(cx));
347
348 return module;
350 }
351
352 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
356
357 SetModulePrivate(
358 module_script.get(),
359 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
360 );
361
362 let _ = module.record.set(ModuleObject::new(module_script.handle()));
364 }
365
366 module
368 }
369
370 #[expect(unsafe_code)]
371 fn crate_a_json_module_script(
375 source: &str,
376 global: &GlobalScope,
377 url: &ServoUrl,
378 introduction_type: Option<&'static CStr>,
379 _can_gc: CanGc,
380 ) -> Self {
381 let cx = GlobalScope::get_cx();
382 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
383
384 let module = ModuleTree {
387 url: url.clone(),
388 record: OnceCell::new(),
389 parse_error: OnceCell::new(),
390 rethrow_error: DomRefCell::new(None),
391 loaded_modules: DomRefCell::new(IndexMap::new()),
392 };
393
394 let c_url = cformat!("{url}");
399 let mut compile_options = unsafe { CompileOptionsWrapper::new_raw(*cx, c_url, 1) };
400 if let Some(introduction_type) = introduction_type {
401 compile_options.set_introduction_type(introduction_type);
402 }
403
404 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
405
406 unsafe {
407 module_script.set(CompileJsonModule1(
409 *cx,
410 compile_options.ptr,
411 &mut transform_str_to_source_text(source),
412 ));
413 }
414
415 if module_script.is_null() {
417 warn!("fail to compile module script of {}", url);
418
419 let _ = module
420 .parse_error
421 .set(RethrowError::from_pending_exception(cx));
422 return module;
423 }
424
425 let _ = module.record.set(ModuleObject::new(module_script.handle()));
427
428 module
430 }
431
432 #[expect(unsafe_code)]
436 pub(crate) fn execute_module(
437 &self,
438 global: &GlobalScope,
439 module_record: js::gc::HandleObject,
440 mut eval_result: MutableHandleValue,
441 _can_gc: CanGc,
442 ) -> Result<(), RethrowError> {
443 let cx = GlobalScope::get_cx();
444 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
445
446 unsafe {
447 let ok = ModuleEvaluate(*cx, module_record, eval_result.reborrow());
448 assert!(ok, "module evaluation failed");
449
450 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
451 if eval_result.is_object() {
452 evaluation_promise.set(eval_result.to_object());
453 }
454
455 let throw_result = ThrowOnModuleEvaluationFailure(
456 *cx,
457 evaluation_promise.handle().into(),
458 ModuleErrorBehaviour::ThrowModuleErrorsSync,
459 );
460 if !throw_result {
461 warn!("fail to evaluate module");
462
463 rooted!(in(*cx) let mut exception = UndefinedValue());
464 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
465 JS_ClearPendingException(*cx);
466
467 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
468 exception.get(),
469 ))))
470 } else {
471 debug!("module evaluated successfully");
472 Ok(())
473 }
474 }
475 }
476
477 #[expect(unsafe_code)]
478 pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
479 let module_error = self.rethrow_error.borrow();
480
481 if let Some(exception) = &*module_error {
482 let ar = enter_realm(global);
483 unsafe {
484 JS_SetPendingException(
485 *GlobalScope::get_cx(),
486 exception.handle(),
487 ExceptionStackBehavior::Capture,
488 );
489 }
490 report_pending_exception(GlobalScope::get_cx(), true, InRealm::Entered(&ar), can_gc);
491 }
492 }
493
494 pub(crate) fn resolve_module_specifier(
496 global: &GlobalScope,
497 script: Option<&ModuleScript>,
498 specifier: DOMString,
499 can_gc: CanGc,
500 ) -> Fallible<ServoUrl> {
501 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
503 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
505 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
509 None => (global, &global.api_base_url()),
514 };
515
516 let import_map = if global.is::<Window>() {
520 Some(global.import_map())
521 } else {
522 None
523 };
524 let specifier = &specifier.str();
525
526 let serialized_base_url = base_url.as_str();
528 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
530 let normalized_specifier = match &as_url {
533 Some(url) => url.as_str(),
534 None => specifier,
535 };
536
537 let mut result = None;
539 if let Some(map) = import_map {
540 for (prefix, imports) in &map.scopes {
542 let prefix = prefix.as_str();
545 if prefix == serialized_base_url ||
546 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
547 {
548 result = resolve_imports_match(
553 normalized_specifier,
554 as_url.as_ref(),
555 imports,
556 can_gc,
557 )?;
558 break;
559 }
560 }
561
562 if result.is_none() {
565 result = resolve_imports_match(
566 normalized_specifier,
567 as_url.as_ref(),
568 &map.imports,
569 can_gc,
570 )?;
571 }
572 }
573
574 if result.is_none() {
576 result = as_url.clone();
577 }
578
579 match result {
581 Some(result) => {
582 global.add_module_to_resolved_module_set(
585 serialized_base_url,
586 normalized_specifier,
587 as_url.clone(),
588 );
589 Ok(result)
591 },
592 None => Err(Error::Type(
595 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
596 .to_owned(),
597 )),
598 }
599 }
600
601 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
603 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
605 {
606 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
608 }
609 ServoUrl::parse(specifier).ok()
611 }
612}
613
614#[derive(JSTraceable, MallocSizeOf)]
615pub(crate) struct ModuleHandler {
616 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
617 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
618}
619
620impl ModuleHandler {
621 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
622 Box::new(Self {
623 task: DomRefCell::new(Some(task)),
624 })
625 }
626}
627
628impl Callback for ModuleHandler {
629 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
630 let task = self.task.borrow_mut().take().unwrap();
631 task.run_box(cx);
632 }
633}
634
635#[derive(Clone, JSTraceable)]
638pub(crate) enum ModuleOwner {
639 #[expect(dead_code)]
640 Worker(TrustedWorkerAddress),
641 Window(Trusted<HTMLScriptElement>),
642 DynamicModule(Trusted<GlobalScope>),
643}
644
645impl ModuleOwner {
646 pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
647 match &self {
648 ModuleOwner::Worker(worker) => (*worker.root().clone()).global(),
649 ModuleOwner::Window(script) => (*script.root()).global(),
650 ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
651 }
652 }
653
654 fn notify_owner_to_finish(&self, module_tree: Option<Rc<ModuleTree>>, can_gc: CanGc) {
655 match &self {
656 ModuleOwner::Worker(_) => unimplemented!(),
657 ModuleOwner::DynamicModule(_) => unimplemented!(),
658 ModuleOwner::Window(script) => {
659 let document = script.root().owner_document();
660
661 let load = match module_tree {
662 Some(module_tree) => Ok(Script::Module(module_tree)),
663 None => Err(()),
664 };
665
666 let asynch = script
667 .root()
668 .upcast::<Element>()
669 .has_attribute(&local_name!("async"));
670
671 if !asynch && (*script.root()).get_parser_inserted() {
672 document.deferred_script_loaded(&script.root(), load, can_gc);
673 } else if !asynch && !(*script.root()).get_non_blocking() {
674 document.asap_in_order_script_loaded(&script.root(), load, can_gc);
675 } else {
676 document.asap_script_loaded(&script.root(), load, can_gc);
677 };
678 },
679 }
680 }
681}
682
683struct ModuleContext {
685 owner: ModuleOwner,
687 data: Vec<u8>,
689 metadata: Option<Metadata>,
691 module_request: ModuleRequest,
693 options: ScriptFetchOptions,
695 status: Result<(), NetworkError>,
697 introduction_type: Option<&'static CStr>,
699}
700
701impl FetchResponseListener for ModuleContext {
702 fn process_request_body(&mut self, _: RequestId) {}
704
705 fn process_request_eof(&mut self, _: RequestId) {}
707
708 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
709 self.metadata = metadata.ok().map(|meta| match meta {
710 FetchMetadata::Unfiltered(m) => m,
711 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
712 });
713
714 let status = self
715 .metadata
716 .as_ref()
717 .map(|m| m.status.clone())
718 .unwrap_or_else(HttpStatus::new_error);
719
720 self.status = {
721 if status.is_error() {
722 Err(NetworkError::ResourceLoadError(
723 "No http status code received".to_owned(),
724 ))
725 } else if status.is_success() {
726 Ok(())
727 } else {
728 Err(NetworkError::ResourceLoadError(format!(
729 "HTTP error code {}",
730 status.code()
731 )))
732 }
733 };
734 }
735
736 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
737 if self.status.is_ok() {
738 self.data.append(&mut chunk);
739 }
740 }
741
742 fn process_response_eof(
745 mut self,
746 cx: &mut js::context::JSContext,
747 _: RequestId,
748 response: Result<(), NetworkError>,
749 timing: ResourceFetchTiming,
750 ) {
751 let global = self.owner.global();
752 let (url, module_type) = &self.module_request;
753
754 if let Some(window) = global.downcast::<Window>() {
755 window
756 .Document()
757 .finish_load(LoadType::Script(url.clone()), CanGc::from_cx(cx));
758 }
759
760 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
761
762 let Some(ModuleStatus::Fetching(pending)) =
763 global.get_module_map_entry(&self.module_request)
764 else {
765 return error!("Processing response for a non pending module request");
766 };
767 let promise = pending
768 .borrow_mut()
769 .take()
770 .expect("Need promise to process response");
771
772 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
775 error!("Fetching module script failed {:?}", error);
776 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
777 return promise.resolve_native(&(), CanGc::from_cx(cx));
778 }
779
780 let metadata = self.metadata.take().unwrap();
781 let final_url = metadata.final_url;
782
783 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
785
786 let mut module_script = None;
788
789 let referrer_policy = metadata
791 .headers
792 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
793 .into();
794
795 if referrer_policy != ReferrerPolicy::EmptyString {
797 self.options.referrer_policy = referrer_policy;
798 }
799
800 if let Some(mime) = mime_type {
806 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
808
809 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
812 matches!(module_type, ModuleType::JavaScript)
813 {
814 if let Some(window) = global.downcast::<Window>() {
815 substitute_with_local_script(window, &mut source_text, final_url.clone());
816 }
817
818 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
819 Rc::new(DOMString::from(source_text.clone())),
820 self.owner.clone(),
821 &final_url,
822 self.options,
823 true,
824 1,
825 self.introduction_type,
826 CanGc::from_cx(cx),
827 ));
828 module_script = Some(module_tree);
829 }
830
831 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
834 let module_tree = Rc::new(ModuleTree::crate_a_json_module_script(
835 &source_text,
836 &global,
837 &final_url,
838 self.introduction_type,
839 CanGc::from_cx(cx),
840 ));
841 module_script = Some(module_tree);
842 }
843 }
844 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
846 promise.resolve_native(&(), CanGc::from_cx(cx));
847 }
848
849 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
850 let global = &self.resource_timing_global();
851 global.report_csp_violations(violations, None, None);
852 }
853}
854
855impl ResourceTimingListener for ModuleContext {
856 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
857 let initiator_type = InitiatorType::LocalName("module".to_string());
858 let (url, _) = &self.module_request;
859 (initiator_type, url.clone())
860 }
861
862 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
863 self.owner.global()
864 }
865}
866
867#[expect(unsafe_code)]
868#[expect(non_snake_case)]
869pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
872 unsafe {
873 if GetModuleResolveHook(rt).is_some() {
874 return;
875 }
876
877 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
878 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
879 SetScriptPrivateReferenceHooks(
880 rt,
881 Some(host_add_ref_top_level_script),
882 Some(host_release_top_level_script),
883 );
884 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
885 }
886}
887
888#[expect(unsafe_code)]
889unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
890 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
891 mem::forget(val.clone());
892 mem::forget(val);
893}
894
895#[expect(unsafe_code)]
896unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
897 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
898}
899
900#[expect(unsafe_code)]
901pub(crate) unsafe extern "C" fn host_import_module_dynamically(
904 cx: *mut RawJSContext,
905 reference_private: RawHandleValue,
906 specifier: RawHandle<*mut JSObject>,
907 promise: RawHandle<*mut JSObject>,
908) -> bool {
909 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
911 let cx = &mut cx;
912 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx.into());
913
914 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
915 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
916 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
917
918 let mut realm = CurrentRealm::assert(cx);
919 let payload = Payload::PromiseRecord(promise.clone());
920 host_load_imported_module(
921 &mut realm,
922 None,
923 reference_private,
924 specifier,
925 module_type,
926 None,
927 payload,
928 );
929
930 true
931}
932
933#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
934pub(crate) struct ScriptFetchOptions {
936 #[no_trace]
937 pub(crate) referrer: Referrer,
938 pub(crate) integrity_metadata: String,
939 #[no_trace]
940 pub(crate) credentials_mode: CredentialsMode,
941 pub(crate) cryptographic_nonce: String,
942 #[no_trace]
943 pub(crate) parser_metadata: ParserMetadata,
944 #[no_trace]
945 pub(crate) referrer_policy: ReferrerPolicy,
946}
947
948impl ScriptFetchOptions {
949 pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
951 Self {
952 cryptographic_nonce: String::new(),
953 integrity_metadata: String::new(),
954 referrer: global.get_referrer(),
955 parser_metadata: ParserMetadata::NotParserInserted,
956 credentials_mode: CredentialsMode::CredentialsSameOrigin,
957 referrer_policy: ReferrerPolicy::EmptyString,
958 }
959 }
960
961 pub(crate) fn descendant_fetch_options(
963 &self,
964 url: &ServoUrl,
965 global: &GlobalScope,
966 ) -> ScriptFetchOptions {
967 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
969
970 Self {
973 referrer: self.referrer.clone(),
974 integrity_metadata: integrity,
976 cryptographic_nonce: self.cryptographic_nonce.clone(),
977 credentials_mode: self.credentials_mode,
978 parser_metadata: self.parser_metadata,
979 referrer_policy: self.referrer_policy,
980 }
981 }
982}
983
984#[expect(unsafe_code)]
985pub(crate) unsafe fn module_script_from_reference_private(
986 reference_private: &RawHandle<JSVal>,
987) -> Option<&ModuleScript> {
988 if reference_private.get().is_undefined() {
989 return None;
990 }
991 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
992}
993
994#[expect(unsafe_code)]
995#[expect(non_snake_case)]
996unsafe extern "C" fn HostResolveImportedModule(
999 cx: *mut RawJSContext,
1000 reference_private: RawHandleValue,
1001 specifier: RawHandle<*mut JSObject>,
1002) -> *mut JSObject {
1003 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1005 let mut realm = CurrentRealm::assert(&mut cx);
1006 let global_scope = GlobalScope::from_current_realm(&realm);
1007
1008 let cx = &mut realm;
1009
1010 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1012 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1013 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1014
1015 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
1016 let url = ModuleTree::resolve_module_specifier(
1017 &global_scope,
1018 module_data,
1019 DOMString::from(specifier),
1020 CanGc::from_cx(cx),
1021 );
1022
1023 assert!(url.is_ok());
1025
1026 let parsed_url = url.unwrap();
1027
1028 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1030
1031 assert!(module.as_ref().is_some_and(
1033 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1034 ));
1035
1036 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1037 unreachable!()
1038 };
1039
1040 let fetched_module_object = module_tree.get_record();
1041
1042 assert!(fetched_module_object.is_some());
1044
1045 if let Some(record) = fetched_module_object {
1047 return record.handle().get();
1048 }
1049
1050 unreachable!()
1051}
1052
1053const SLOT_MODULEPRIVATE: usize = 0;
1055
1056#[expect(unsafe_code)]
1057#[expect(non_snake_case)]
1058unsafe extern "C" fn HostPopulateImportMeta(
1061 cx: *mut RawJSContext,
1062 reference_private: RawHandleValue,
1063 meta_object: RawHandle<*mut JSObject>,
1064) -> bool {
1065 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1067 let realm = CurrentRealm::assert(&mut cx);
1068 let global_scope = GlobalScope::from_current_realm(&realm);
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 unsafe {
1077 let url_string = JS_NewStringCopyN(
1078 &mut cx,
1079 base_url.as_str().as_ptr() as *const _,
1080 base_url.as_str().len(),
1081 );
1082 rooted!(&in(cx) let url_string = url_string);
1083
1084 if !JS_DefineProperty4(
1086 &mut cx,
1087 Handle::from_raw(meta_object),
1088 c"url".as_ptr(),
1089 url_string.handle(),
1090 JSPROP_ENUMERATE.into(),
1091 ) {
1092 return false;
1093 }
1094
1095 let resolve_function = DefineFunctionWithReserved(
1097 &mut cx,
1098 meta_object.get(),
1099 c"resolve".as_ptr(),
1100 Some(import_meta_resolve),
1101 1,
1102 JSPROP_ENUMERATE.into(),
1103 );
1104
1105 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1106 assert!(!obj.is_null());
1107 SetFunctionNativeReserved(
1108 obj.get(),
1109 SLOT_MODULEPRIVATE,
1110 &reference_private.get() as *const _,
1111 );
1112 }
1113
1114 true
1115}
1116
1117#[expect(unsafe_code)]
1118unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1119 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1121 let mut realm = CurrentRealm::assert(&mut cx);
1122 let global_scope = GlobalScope::from_current_realm(&realm);
1123
1124 let cx = &mut realm;
1125
1126 let args = unsafe { CallArgs::from_vp(vp, argc) };
1127
1128 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1129 let reference_private = module_private.handle().into();
1130 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1131
1132 let specifier = unsafe {
1136 let value = HandleValue::from_raw(args.get(0));
1137
1138 match NonNull::new(ToString(cx.raw_cx(), value)) {
1139 Some(jsstr) => DOMString::from_string(jsstr_to_string(cx.raw_cx(), jsstr)),
1140 None => return false,
1141 }
1142 };
1143
1144 let url = ModuleTree::resolve_module_specifier(
1146 &global_scope,
1147 module_data,
1148 specifier,
1149 CanGc::from_cx(cx),
1150 );
1151
1152 match url {
1153 Ok(url) => {
1154 url.as_str().safe_to_jsval(
1156 cx.into(),
1157 unsafe { MutableHandleValue::from_raw(args.rval()) },
1158 CanGc::from_cx(cx),
1159 );
1160 true
1161 },
1162 Err(error) => {
1163 let resolution_error = gen_type_error(&global_scope, error, CanGc::from_cx(cx));
1164
1165 unsafe {
1166 JS_SetPendingException(
1167 cx.raw_cx(),
1168 resolution_error.handle(),
1169 ExceptionStackBehavior::Capture,
1170 );
1171 }
1172 false
1173 },
1174 }
1175}
1176
1177pub(crate) fn fetch_an_external_module_script(
1179 url: ServoUrl,
1180 owner: ModuleOwner,
1181 options: ScriptFetchOptions,
1182 can_gc: CanGc,
1183) {
1184 let referrer = owner.global().get_referrer();
1185
1186 fetch_a_single_module_script(
1189 url,
1190 owner.clone(),
1191 Destination::Script,
1192 options,
1193 referrer,
1194 None,
1195 true,
1196 Some(IntroductionType::SRC_SCRIPT),
1197 move |module_tree| {
1198 let Some(module) = module_tree else {
1199 return owner.notify_owner_to_finish(None, can_gc);
1201 };
1202
1203 fetch_the_descendants_and_link_module_script(module, Destination::Script, owner);
1205 },
1206 );
1207}
1208
1209pub(crate) fn fetch_inline_module_script(
1211 owner: ModuleOwner,
1212 module_script_text: Rc<DOMString>,
1213 url: ServoUrl,
1214 options: ScriptFetchOptions,
1215 line_number: u32,
1216 can_gc: CanGc,
1217) {
1218 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1220 module_script_text,
1221 owner.clone(),
1222 &url,
1223 options,
1224 false,
1225 line_number,
1226 Some(IntroductionType::INLINE_SCRIPT),
1227 can_gc,
1228 ));
1229
1230 fetch_the_descendants_and_link_module_script(module_tree, Destination::Script, owner);
1232}
1233
1234#[expect(unsafe_code)]
1235fn fetch_the_descendants_and_link_module_script(
1237 module_script: Rc<ModuleTree>,
1238 destination: Destination,
1239 owner: ModuleOwner,
1240) {
1241 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
1242 let mut realm = CurrentRealm::assert(&mut cx);
1243 let cx = &mut realm;
1244
1245 let global = owner.global();
1246
1247 if module_script.get_record().is_none() {
1250 let parse_error = module_script.get_parse_error().cloned();
1251
1252 module_script.set_rethrow_error(parse_error.unwrap());
1254
1255 owner.notify_owner_to_finish(Some(module_script), CanGc::from_cx(cx));
1257
1258 return;
1260 }
1261
1262 let state = Rc::new(LoadState {
1265 error_to_rethrow: RefCell::new(None),
1266 destination,
1267 fetch_client: owner.clone(),
1268 });
1269
1270 let loading_promise =
1274 load_requested_modules(cx, module_script.clone(), Some(Rc::clone(&state)));
1275
1276 let fulfillment_owner = owner.clone();
1277 let fulfilled_module = module_script.clone();
1278
1279 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1281 task!(fulfilled_steps: |cx, fulfillment_owner: ModuleOwner| {
1282 let global = fulfillment_owner.global();
1283 let mut realm = AutoRealm::new(
1284 cx,
1285 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
1286 );
1287 let cx = &mut *realm;
1288
1289 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1290
1291 let link = unsafe { ModuleLink(cx, handle) };
1293
1294 if !link {
1296 let exception = RethrowError::from_pending_exception(cx.into());
1297 fulfilled_module.set_rethrow_error(exception);
1298 }
1299
1300 fulfillment_owner.notify_owner_to_finish(Some(fulfilled_module), CanGc::from_cx(cx));
1302 }),
1303 ));
1304
1305 let rejection_owner = owner.clone();
1306 let rejected_module = module_script.clone();
1307
1308 let loading_promise_rejection = ModuleHandler::new_boxed(Box::new(
1310 task!(rejected_steps: |rejection_owner: ModuleOwner, state: Rc<LoadState>| {
1311 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1314 rejected_module.set_rethrow_error(error.clone());
1315 rejection_owner.notify_owner_to_finish(Some(rejected_module), CanGc::note());
1316 } else {
1317 rejection_owner.notify_owner_to_finish(None, CanGc::note());
1319 }
1320 }),
1321 ));
1322
1323 let handler = PromiseNativeHandler::new(
1324 &global,
1325 Some(loading_promise_fulfillment),
1326 Some(loading_promise_rejection),
1327 CanGc::from_cx(cx),
1328 );
1329
1330 let realm = enter_realm(&*global);
1331 let comp = InRealm::Entered(&realm);
1332 let _ais = AutoIncumbentScript::new(&global);
1333
1334 loading_promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
1335}
1336
1337#[expect(clippy::too_many_arguments)]
1339pub(crate) fn fetch_a_single_module_script(
1340 url: ServoUrl,
1341 owner: ModuleOwner,
1342 destination: Destination,
1343 options: ScriptFetchOptions,
1344 referrer: Referrer,
1345 module_type: Option<ModuleType>,
1346 is_top_level: bool,
1347 introduction_type: Option<&'static CStr>,
1348 on_complete: impl FnOnce(Option<Rc<ModuleTree>>) + 'static,
1349) {
1350 let global = owner.global();
1351
1352 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1356
1357 let module_request = (url.clone(), module_type);
1363 let entry = global.get_module_map_entry(&module_request);
1364
1365 let pending = match entry {
1366 Some(ModuleStatus::Fetching(pending)) => pending,
1367 Some(ModuleStatus::Loaded(module_tree)) => {
1369 return on_complete(module_tree);
1370 },
1371 None => DomRefCell::new(None),
1372 };
1373
1374 let global_scope = DomRoot::from_ref(&*global);
1375 let module_map_key = module_request.clone();
1376 let handler = ModuleHandler::new_boxed(Box::new(
1377 task!(fetch_completed: |global_scope: DomRoot<GlobalScope>| {
1378 let key = module_map_key;
1379 let module = global_scope.get_module_map_entry(&key);
1380
1381 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1382 on_complete(module_tree);
1383 }
1384 }),
1385 ));
1386
1387 let handler = PromiseNativeHandler::new(&global, Some(handler), None, CanGc::note());
1388
1389 let realm = enter_realm(&*global);
1390 let comp = InRealm::Entered(&realm);
1391 let _ais = AutoIncumbentScript::new(&global);
1392
1393 let has_pending_fetch = pending.borrow().is_some();
1394 pending
1395 .borrow_mut()
1396 .get_or_insert_with(|| Promise::new_in_current_realm(comp, CanGc::note()))
1397 .append_native_handler(&handler, comp, CanGc::note());
1398
1399 if has_pending_fetch {
1402 return;
1403 }
1404
1405 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1407
1408 let document: Option<DomRoot<Document>> = match &owner {
1409 ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1410 ModuleOwner::Window(script) => Some(script.root().owner_document()),
1411 };
1412 let webview_id = document.as_ref().map(|document| document.webview_id());
1413
1414 let mode = match destination {
1419 Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1420 _ => RequestMode::CorsMode,
1421 };
1422
1423 let destination = match module_type {
1425 ModuleType::JSON => Destination::Json,
1426 ModuleType::JavaScript | ModuleType::Unknown => destination,
1427 };
1428
1429 let request = RequestBuilder::new(webview_id, url.clone(), referrer)
1433 .destination(destination)
1434 .parser_metadata(options.parser_metadata)
1435 .integrity_metadata(options.integrity_metadata.clone())
1436 .credentials_mode(options.credentials_mode)
1437 .referrer_policy(options.referrer_policy)
1438 .mode(mode)
1439 .with_global_scope(&global)
1440 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1441
1442 let context = ModuleContext {
1443 owner,
1444 data: vec![],
1445 metadata: None,
1446 module_request,
1447 options,
1448 status: Ok(()),
1449 introduction_type,
1450 };
1451
1452 let network_listener = NetworkListener::new(
1453 context,
1454 global.task_manager().networking_task_source().to_sendable(),
1455 );
1456 match document {
1457 Some(document) => {
1458 document.loader_mut().fetch_async_with_callback(
1459 LoadType::Script(url),
1460 request,
1461 network_listener.into_callback(),
1462 );
1463 },
1464 None => global.fetch_with_network_listener(request, network_listener),
1465 };
1466}
1467
1468pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1469pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1470
1471#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1473pub(crate) struct ResolvedModule {
1474 base_url: String,
1476 specifier: String,
1478 #[no_trace]
1480 specifier_url: Option<ServoUrl>,
1481}
1482
1483impl ResolvedModule {
1484 pub(crate) fn new(
1485 base_url: String,
1486 specifier: String,
1487 specifier_url: Option<ServoUrl>,
1488 ) -> Self {
1489 Self {
1490 base_url,
1491 specifier,
1492 specifier_url,
1493 }
1494 }
1495}
1496
1497#[derive(Default, JSTraceable, MallocSizeOf)]
1499pub(crate) struct ImportMap {
1500 #[no_trace]
1501 imports: ModuleSpecifierMap,
1502 #[no_trace]
1503 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1504 #[no_trace]
1505 integrity: ModuleIntegrityMap,
1506}
1507
1508impl ImportMap {
1509 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1511 self.integrity.get(url).cloned().unwrap_or_default()
1516 }
1517}
1518
1519pub(crate) fn register_import_map(
1521 global: &GlobalScope,
1522 result: Fallible<ImportMap>,
1523 can_gc: CanGc,
1524) {
1525 match result {
1526 Ok(new_import_map) => {
1527 merge_existing_and_new_import_maps(global, new_import_map, can_gc);
1529 },
1530 Err(exception) => {
1531 throw_dom_exception(GlobalScope::get_cx(), global, exception.clone(), can_gc);
1534 },
1535 }
1536}
1537
1538fn merge_existing_and_new_import_maps(
1540 global: &GlobalScope,
1541 new_import_map: ImportMap,
1542 can_gc: CanGc,
1543) {
1544 let new_import_map_scopes = new_import_map.scopes;
1546
1547 let mut old_import_map = global.import_map_mut();
1549
1550 let mut new_import_map_imports = new_import_map.imports;
1552
1553 let resolved_module_set = global.resolved_module_set();
1554 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1556 for record in resolved_module_set.iter() {
1558 let prefix = scope_prefix.as_str();
1561 if prefix == record.base_url ||
1562 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1563 {
1564 scope_imports.retain(|key, val| {
1566 if *key == record.specifier ||
1571 (key.ends_with('\u{002f}') &&
1572 record.specifier.starts_with(key) &&
1573 (record.specifier_url.is_none() ||
1574 record
1575 .specifier_url
1576 .as_ref()
1577 .is_some_and(|u| u.is_special_scheme())))
1578 {
1579 Console::internal_warn(
1582 global,
1583 DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
1584 );
1585 false
1587 } else {
1588 true
1589 }
1590 })
1591 }
1592 }
1593
1594 if old_import_map.scopes.contains_key(&scope_prefix) {
1596 let merged_module_specifier_map = merge_module_specifier_maps(
1599 global,
1600 scope_imports,
1601 &old_import_map.scopes[&scope_prefix],
1602 can_gc,
1603 );
1604 old_import_map
1605 .scopes
1606 .insert(scope_prefix, merged_module_specifier_map);
1607 } else {
1608 old_import_map.scopes.insert(scope_prefix, scope_imports);
1610 }
1611 }
1612
1613 for (url, integrity) in &new_import_map.integrity {
1615 if old_import_map.integrity.contains_key(url) {
1617 Console::internal_warn(
1620 global,
1621 DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
1622 );
1623 continue;
1625 }
1626
1627 old_import_map
1629 .integrity
1630 .insert(url.clone(), integrity.clone());
1631 }
1632
1633 for record in resolved_module_set.iter() {
1635 new_import_map_imports.retain(|specifier, val| {
1637 if record.specifier.starts_with(specifier) {
1642 Console::internal_warn(
1645 global,
1646 DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
1647 );
1648 false
1650 } else {
1651 true
1652 }
1653 });
1654 }
1655
1656 let merged_module_specifier_map = merge_module_specifier_maps(
1659 global,
1660 new_import_map_imports,
1661 &old_import_map.imports,
1662 can_gc,
1663 );
1664 old_import_map.imports = merged_module_specifier_map;
1665
1666 old_import_map
1669 .scopes
1670 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1671}
1672
1673fn merge_module_specifier_maps(
1675 global: &GlobalScope,
1676 new_map: ModuleSpecifierMap,
1677 old_map: &ModuleSpecifierMap,
1678 _can_gc: CanGc,
1679) -> ModuleSpecifierMap {
1680 let mut merged_map = old_map.clone();
1682
1683 for (specifier, url) in new_map {
1685 if old_map.contains_key(&specifier) {
1687 Console::internal_warn(
1690 global,
1691 DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
1692 );
1693
1694 continue;
1696 }
1697
1698 merged_map.insert(specifier, url);
1700 }
1701
1702 merged_map
1703}
1704
1705pub(crate) fn parse_an_import_map_string(
1707 module_owner: ModuleOwner,
1708 input: Rc<DOMString>,
1709 base_url: ServoUrl,
1710 can_gc: CanGc,
1711) -> Fallible<ImportMap> {
1712 let parsed: JsonValue = serde_json::from_str(&input.str())
1714 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1715 let JsonValue::Object(mut parsed) = parsed else {
1718 return Err(Error::Type(
1719 c"The top-level value needs to be a JSON object.".to_owned(),
1720 ));
1721 };
1722
1723 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1725 if let Some(imports) = parsed.get("imports") {
1727 let JsonValue::Object(imports) = imports else {
1730 return Err(Error::Type(
1731 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1732 ));
1733 };
1734 sorted_and_normalized_imports = sort_and_normalize_module_specifier_map(
1737 &module_owner.global(),
1738 imports,
1739 &base_url,
1740 can_gc,
1741 );
1742 }
1743
1744 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1746 if let Some(scopes) = parsed.get("scopes") {
1748 let JsonValue::Object(scopes) = scopes else {
1751 return Err(Error::Type(
1752 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1753 ));
1754 };
1755 sorted_and_normalized_scopes =
1758 sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url, can_gc)?;
1759 }
1760
1761 let mut normalized_integrity = ModuleIntegrityMap::new();
1763 if let Some(integrity) = parsed.get("integrity") {
1765 let JsonValue::Object(integrity) = integrity else {
1768 return Err(Error::Type(
1769 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1770 ));
1771 };
1772 normalized_integrity =
1775 normalize_module_integrity_map(&module_owner.global(), integrity, &base_url, can_gc);
1776 }
1777
1778 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1782 if !parsed.is_empty() {
1783 Console::internal_warn(
1784 &module_owner.global(),
1785 DOMString::from(
1786 "Invalid top-level key was present in the import map.
1787 Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
1788 ),
1789 );
1790 }
1791
1792 Ok(ImportMap {
1794 imports: sorted_and_normalized_imports,
1795 scopes: sorted_and_normalized_scopes,
1796 integrity: normalized_integrity,
1797 })
1798}
1799
1800fn sort_and_normalize_module_specifier_map(
1802 global: &GlobalScope,
1803 original_map: &JsonMap<String, JsonValue>,
1804 base_url: &ServoUrl,
1805 can_gc: CanGc,
1806) -> ModuleSpecifierMap {
1807 let mut normalized = ModuleSpecifierMap::new();
1809
1810 for (specifier_key, value) in original_map {
1812 let Some(normalized_specifier_key) =
1815 normalize_specifier_key(global, specifier_key, base_url, can_gc)
1816 else {
1817 continue;
1819 };
1820
1821 let JsonValue::String(value) = value else {
1823 Console::internal_warn(global, DOMString::from("Addresses need to be strings."));
1826
1827 normalized.insert(normalized_specifier_key, None);
1829 continue;
1831 };
1832
1833 let Some(address_url) =
1835 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
1836 else {
1837 Console::internal_warn(
1841 global,
1842 DOMString::from(format!(
1843 "Value failed to resolve to module specifier: {value}"
1844 )),
1845 );
1846
1847 normalized.insert(normalized_specifier_key, None);
1849 continue;
1851 };
1852
1853 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
1856 Console::internal_warn(
1860 global,
1861 DOMString::from(format!(
1862 "Invalid address for specifier key '{specifier_key}': {address_url}.
1863 Since specifierKey ends with a slash, the address needs to as well."
1864 )),
1865 );
1866
1867 normalized.insert(normalized_specifier_key, None);
1869 continue;
1871 }
1872
1873 normalized.insert(normalized_specifier_key, Some(address_url));
1875 }
1876
1877 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1880 normalized
1881}
1882
1883fn sort_and_normalize_scopes(
1885 global: &GlobalScope,
1886 original_map: &JsonMap<String, JsonValue>,
1887 base_url: &ServoUrl,
1888 can_gc: CanGc,
1889) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
1890 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1892
1893 for (scope_prefix, potential_specifier_map) in original_map {
1895 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
1898 return Err(Error::Type(
1899 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
1900 .to_owned(),
1901 ));
1902 };
1903
1904 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
1906 Console::internal_warn(
1910 global,
1911 DOMString::from(format!(
1912 "Scope prefix URL was not parseable: {scope_prefix}"
1913 )),
1914 );
1915 continue;
1917 };
1918
1919 let normalized_scope_prefix = scope_prefix_url;
1921
1922 let normalized_specifier_map = sort_and_normalize_module_specifier_map(
1925 global,
1926 potential_specifier_map,
1927 base_url,
1928 can_gc,
1929 );
1930 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
1931 }
1932
1933 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1936 Ok(normalized)
1937}
1938
1939fn normalize_module_integrity_map(
1941 global: &GlobalScope,
1942 original_map: &JsonMap<String, JsonValue>,
1943 base_url: &ServoUrl,
1944 _can_gc: CanGc,
1945) -> ModuleIntegrityMap {
1946 let mut normalized = ModuleIntegrityMap::new();
1948
1949 for (key, value) in original_map {
1951 let Some(resolved_url) =
1954 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
1955 else {
1956 Console::internal_warn(
1960 global,
1961 DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
1962 );
1963 continue;
1965 };
1966
1967 let JsonValue::String(value) = value else {
1969 Console::internal_warn(
1972 global,
1973 DOMString::from("Integrity metadata values need to be strings."),
1974 );
1975 continue;
1977 };
1978
1979 normalized.insert(resolved_url, value.clone());
1981 }
1982
1983 normalized
1985}
1986
1987fn normalize_specifier_key(
1989 global: &GlobalScope,
1990 specifier_key: &str,
1991 base_url: &ServoUrl,
1992 _can_gc: CanGc,
1993) -> Option<String> {
1994 if specifier_key.is_empty() {
1996 Console::internal_warn(
1999 global,
2000 DOMString::from("Specifier keys may not be the empty string."),
2001 );
2002 return None;
2004 }
2005 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2007
2008 if let Some(url) = url {
2010 return Some(url.into_string());
2011 }
2012
2013 Some(specifier_key.to_string())
2015}
2016
2017fn resolve_imports_match(
2022 normalized_specifier: &str,
2023 as_url: Option<&ServoUrl>,
2024 specifier_map: &ModuleSpecifierMap,
2025 _can_gc: CanGc,
2026) -> Fallible<Option<ServoUrl>> {
2027 for (specifier_key, resolution_result) in specifier_map {
2029 if specifier_key == normalized_specifier {
2031 if let Some(resolution_result) = resolution_result {
2032 return Ok(Some(resolution_result.clone()));
2036 } else {
2037 return Err(Error::Type(
2039 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2040 ));
2041 }
2042 }
2043
2044 if specifier_key.ends_with('\u{002f}') &&
2049 normalized_specifier.starts_with(specifier_key) &&
2050 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2051 {
2052 let Some(resolution_result) = resolution_result else {
2055 return Err(Error::Type(
2056 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2057 ));
2058 };
2059
2060 let after_prefix = normalized_specifier
2062 .strip_prefix(specifier_key)
2063 .expect("specifier_key should be the prefix of normalized_specifier");
2064
2065 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2067
2068 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2070
2071 let Ok(url) = url else {
2074 return Err(Error::Type(
2075 c"Resolution of normalizedSpecifier was blocked since
2076 the afterPrefix portion could not be URL-parsed relative to
2077 the resolutionResult mapped to by the specifierKey prefix."
2078 .to_owned(),
2079 ));
2080 };
2081
2082 if !url.as_str().starts_with(resolution_result.as_str()) {
2085 return Err(Error::Type(
2086 c"Resolution of normalizedSpecifier was blocked due to
2087 it backtracking above its prefix specifierKey."
2088 .to_owned(),
2089 ));
2090 }
2091
2092 return Ok(Some(url));
2094 }
2095 }
2096
2097 Ok(None)
2099}