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::settings_stack::run_a_callback;
55use script_bindings::trace::CustomTraceable;
56use serde_json::{Map as JsonMap, Value as JsonValue};
57use servo_url::ServoUrl;
58
59use crate::DomTypeHolder;
60use crate::document_loader::LoadType;
61use crate::dom::bindings::cell::DomRefCell;
62use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
63use crate::dom::bindings::conversions::SafeToJSValConvertible;
64use crate::dom::bindings::error::{
65 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
66};
67use crate::dom::bindings::inheritance::Castable;
68use crate::dom::bindings::refcounted::Trusted;
69use crate::dom::bindings::reflector::{DomGlobal, DomObject};
70use crate::dom::bindings::root::DomRoot;
71use crate::dom::bindings::str::DOMString;
72use crate::dom::bindings::trace::RootedTraceableBox;
73use crate::dom::csp::{GlobalCspReporting, Violation};
74use crate::dom::document::Document;
75use crate::dom::element::Element;
76use crate::dom::globalscope::GlobalScope;
77use crate::dom::html::htmlscriptelement::{HTMLScriptElement, SCRIPT_JS_MIMES, Script};
78use crate::dom::htmlscriptelement::substitute_with_local_script;
79use crate::dom::node::NodeTraits;
80use crate::dom::performance::performanceresourcetiming::InitiatorType;
81use crate::dom::promise::Promise;
82use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
83use crate::dom::types::Console;
84use crate::dom::window::Window;
85use crate::dom::worker::TrustedWorkerAddress;
86use crate::fetch::RequestWithGlobalScope;
87use crate::module_loading::{
88 LoadState, Payload, host_load_imported_module, load_requested_modules,
89};
90use crate::network_listener::{
91 self, FetchResponseListener, NetworkListener, ResourceTimingListener,
92};
93use crate::realms::{InRealm, enter_realm};
94use crate::script_runtime::{CanGc, IntroductionType, JSContext as SafeJSContext};
95use crate::task::NonSendTaskBox;
96
97pub(crate) fn gen_type_error(global: &GlobalScope, error: Error, can_gc: CanGc) -> RethrowError {
98 rooted!(in(*GlobalScope::get_cx()) let mut thrown = UndefinedValue());
99 error.to_jsval(GlobalScope::get_cx(), global, thrown.handle_mut(), can_gc);
100
101 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
102}
103
104#[derive(JSTraceable)]
105pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
106
107impl ModuleObject {
108 pub(crate) fn new(obj: RustHandleObject) -> ModuleObject {
109 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
110 }
111
112 pub(crate) fn handle(&'_ self) -> js::gc::HandleObject<'_> {
113 self.0.handle()
114 }
115}
116
117#[derive(JSTraceable)]
118pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
119
120impl RethrowError {
121 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
122 Self(RootedTraceableBox::from_box(val))
123 }
124
125 #[expect(unsafe_code)]
126 pub(crate) fn from_pending_exception(cx: SafeJSContext) -> Self {
127 rooted!(in(*cx) let mut exception = UndefinedValue());
128 assert!(unsafe { JS_GetPendingException(*cx, exception.handle_mut()) });
129 unsafe { JS_ClearPendingException(*cx) };
130
131 Self::new(Heap::boxed(exception.get()))
132 }
133
134 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
135 self.0.handle()
136 }
137}
138
139impl Debug for RethrowError {
140 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
141 "RethrowError(...)".fmt(fmt)
142 }
143}
144
145impl Clone for RethrowError {
146 fn clone(&self) -> Self {
147 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
148 }
149}
150
151pub(crate) struct ModuleScript {
152 pub(crate) base_url: ServoUrl,
153 pub(crate) options: ScriptFetchOptions,
154 owner: Option<ModuleOwner>,
155}
156
157impl ModuleScript {
158 pub(crate) fn new(
159 base_url: ServoUrl,
160 options: ScriptFetchOptions,
161 owner: Option<ModuleOwner>,
162 ) -> Self {
163 ModuleScript {
164 base_url,
165 options,
166 owner,
167 }
168 }
169}
170
171pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
172
173#[derive(Clone, JSTraceable)]
174pub(crate) enum ModuleStatus {
175 Fetching(DomRefCell<Option<Rc<Promise>>>),
176 Loaded(Option<Rc<ModuleTree>>),
177}
178
179#[derive(JSTraceable, MallocSizeOf)]
180pub(crate) struct ModuleTree {
181 #[no_trace]
182 url: ServoUrl,
183 #[ignore_malloc_size_of = "mozjs"]
184 record: OnceCell<ModuleObject>,
185 #[ignore_malloc_size_of = "mozjs"]
186 parse_error: OnceCell<RethrowError>,
187 #[ignore_malloc_size_of = "mozjs"]
188 rethrow_error: DomRefCell<Option<RethrowError>>,
189 #[no_trace]
190 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
191}
192
193impl ModuleTree {
194 pub(crate) fn get_url(&self) -> ServoUrl {
195 self.url.clone()
196 }
197
198 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
199 self.record.get()
200 }
201
202 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
203 self.parse_error.get()
204 }
205
206 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
207 &self.rethrow_error
208 }
209
210 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
211 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
212 }
213
214 pub(crate) fn find_descendant_inside_module_map(
215 &self,
216 global: &GlobalScope,
217 specifier: &String,
218 module_type: ModuleType,
219 ) -> Option<Rc<ModuleTree>> {
220 self.loaded_modules
221 .borrow()
222 .get(specifier)
223 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
224 .and_then(|status| match status {
225 ModuleStatus::Fetching(_) => None,
226 ModuleStatus::Loaded(module_tree) => module_tree,
227 })
228 }
229
230 pub(crate) fn insert_module_dependency(
231 &self,
232 module: &Rc<ModuleTree>,
233 module_request_specifier: String,
234 ) {
235 let url = module.url.clone();
237 match self
238 .loaded_modules
239 .borrow_mut()
240 .entry(module_request_specifier)
241 {
242 Entry::Occupied(entry) => {
245 assert_eq!(*entry.get(), url);
247 },
248 Entry::Vacant(entry) => {
250 entry.insert(url);
253 },
254 }
255 }
256}
257
258pub(crate) struct ModuleSource {
259 pub source: Rc<DOMString>,
260 pub unminified_dir: Option<String>,
261 pub external: bool,
262 pub url: ServoUrl,
263}
264
265impl crate::unminify::ScriptSource for ModuleSource {
266 fn unminified_dir(&self) -> Option<String> {
267 self.unminified_dir.clone()
268 }
269
270 fn extract_bytes(&self) -> BytesView<'_> {
271 self.source.as_bytes()
272 }
273
274 fn rewrite_source(&mut self, source: Rc<DOMString>) {
275 self.source = source;
276 }
277
278 fn url(&self) -> ServoUrl {
279 self.url.clone()
280 }
281
282 fn is_external(&self) -> bool {
283 self.external
284 }
285}
286
287impl ModuleTree {
288 #[expect(unsafe_code)]
289 #[expect(clippy::too_many_arguments)]
290 fn create_a_javascript_module_script(
294 source: Rc<DOMString>,
295 owner: ModuleOwner,
296 url: &ServoUrl,
297 options: ScriptFetchOptions,
298 external: bool,
299 line_number: u32,
300 introduction_type: Option<&'static CStr>,
301 _can_gc: CanGc,
302 ) -> Self {
303 let cx = GlobalScope::get_cx();
304 let global = owner.global();
305 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
306
307 let module = ModuleTree {
310 url: url.clone(),
311 record: OnceCell::new(),
312 parse_error: OnceCell::new(),
313 rethrow_error: DomRefCell::new(None),
314 loaded_modules: DomRefCell::new(IndexMap::new()),
315 };
316
317 let compile_options = fill_module_compile_options(cx, url, introduction_type, line_number);
318
319 let mut module_source = ModuleSource {
320 source,
321 unminified_dir: global.unminified_js_dir(),
322 external,
323 url: url.clone(),
324 };
325 crate::unminify::unminify_js(&mut module_source);
326
327 unsafe {
328 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
330 module_script.set(CompileModule1(
331 *cx,
332 compile_options.ptr,
333 &mut transform_str_to_source_text(&module_source.source.str()),
334 ));
335
336 if module_script.is_null() {
338 warn!("fail to compile module script of {}", url);
339
340 let _ = module
342 .parse_error
343 .set(RethrowError::from_pending_exception(cx));
344
345 return module;
347 }
348
349 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
353
354 SetModulePrivate(
355 module_script.get(),
356 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
357 );
358
359 let _ = module.record.set(ModuleObject::new(module_script.handle()));
361 }
362
363 module
365 }
366
367 #[expect(unsafe_code)]
368 fn create_a_json_module_script(
372 source: &str,
373 global: &GlobalScope,
374 url: &ServoUrl,
375 introduction_type: Option<&'static CStr>,
376 _can_gc: CanGc,
377 ) -> Self {
378 let cx = GlobalScope::get_cx();
379 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
380
381 let module = ModuleTree {
384 url: url.clone(),
385 record: OnceCell::new(),
386 parse_error: OnceCell::new(),
387 rethrow_error: DomRefCell::new(None),
388 loaded_modules: DomRefCell::new(IndexMap::new()),
389 };
390
391 let compile_options = fill_module_compile_options(cx, url, introduction_type, 1);
396
397 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
398
399 unsafe {
400 module_script.set(CompileJsonModule1(
402 *cx,
403 compile_options.ptr,
404 &mut transform_str_to_source_text(source),
405 ));
406 }
407
408 if module_script.is_null() {
410 warn!("fail to compile module script of {}", url);
411
412 let _ = module
413 .parse_error
414 .set(RethrowError::from_pending_exception(cx));
415 return module;
416 }
417
418 let _ = module.record.set(ModuleObject::new(module_script.handle()));
420
421 module
423 }
424
425 #[expect(unsafe_code)]
429 pub(crate) fn execute_module(
430 &self,
431 global: &GlobalScope,
432 module_record: js::gc::HandleObject,
433 mut eval_result: MutableHandleValue,
434 _can_gc: CanGc,
435 ) -> Result<(), RethrowError> {
436 let cx = GlobalScope::get_cx();
437 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
438
439 unsafe {
440 let ok = ModuleEvaluate(*cx, module_record, eval_result.reborrow());
441 assert!(ok, "module evaluation failed");
442
443 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
444 if eval_result.is_object() {
445 evaluation_promise.set(eval_result.to_object());
446 }
447
448 let throw_result = ThrowOnModuleEvaluationFailure(
449 *cx,
450 evaluation_promise.handle().into(),
451 ModuleErrorBehaviour::ThrowModuleErrorsSync,
452 );
453 if !throw_result {
454 warn!("fail to evaluate module");
455
456 rooted!(in(*cx) let mut exception = UndefinedValue());
457 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
458 JS_ClearPendingException(*cx);
459
460 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
461 exception.get(),
462 ))))
463 } else {
464 debug!("module evaluated successfully");
465 Ok(())
466 }
467 }
468 }
469
470 #[expect(unsafe_code)]
471 pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
472 let module_error = self.rethrow_error.borrow();
473
474 if let Some(exception) = &*module_error {
475 let ar = enter_realm(global);
476 unsafe {
477 JS_SetPendingException(
478 *GlobalScope::get_cx(),
479 exception.handle(),
480 ExceptionStackBehavior::Capture,
481 );
482 }
483 report_pending_exception(GlobalScope::get_cx(), InRealm::Entered(&ar), can_gc);
484 }
485 }
486
487 pub(crate) fn resolve_module_specifier(
489 global: &GlobalScope,
490 script: Option<&ModuleScript>,
491 specifier: DOMString,
492 ) -> Fallible<ServoUrl> {
493 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
495 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
497 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
501 None => (global, &global.api_base_url()),
506 };
507
508 let import_map = if global.is::<Window>() {
512 Some(global.import_map())
513 } else {
514 None
515 };
516 let specifier = &specifier.str();
517
518 let serialized_base_url = base_url.as_str();
520 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
522 let normalized_specifier = match &as_url {
525 Some(url) => url.as_str(),
526 None => specifier,
527 };
528
529 let mut result = None;
531 if let Some(map) = import_map {
532 for (prefix, imports) in &map.scopes {
534 let prefix = prefix.as_str();
537 if prefix == serialized_base_url ||
538 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
539 {
540 let scope_imports_match =
543 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
544
545 if scope_imports_match.is_some() {
547 result = scope_imports_match;
548 break;
549 }
550 }
551 }
552
553 if result.is_none() {
556 result =
557 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
558 }
559 }
560
561 if result.is_none() {
563 result = as_url.clone();
564 }
565
566 match result {
568 Some(result) => {
569 global.add_module_to_resolved_module_set(
572 serialized_base_url,
573 normalized_specifier,
574 as_url.clone(),
575 );
576 Ok(result)
578 },
579 None => Err(Error::Type(
582 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
583 .to_owned(),
584 )),
585 }
586 }
587
588 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
590 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
592 {
593 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
595 }
596 ServoUrl::parse(specifier).ok()
598 }
599}
600
601#[derive(JSTraceable, MallocSizeOf)]
602pub(crate) struct ModuleHandler {
603 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
604 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
605}
606
607impl ModuleHandler {
608 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
609 Box::new(Self {
610 task: DomRefCell::new(Some(task)),
611 })
612 }
613}
614
615impl Callback for ModuleHandler {
616 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
617 let task = self.task.borrow_mut().take().unwrap();
618 task.run_box(cx);
619 }
620}
621
622#[derive(Clone, JSTraceable)]
625pub(crate) enum ModuleOwner {
626 #[expect(dead_code)]
627 Worker(TrustedWorkerAddress),
628 Window(Trusted<HTMLScriptElement>),
629 DynamicModule(Trusted<GlobalScope>),
630}
631
632impl ModuleOwner {
633 pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
634 match &self {
635 ModuleOwner::Worker(worker) => (*worker.root()).global(),
636 ModuleOwner::Window(script) => (*script.root()).global(),
637 ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
638 }
639 }
640
641 fn notify_owner_to_finish(&self, module_tree: Option<Rc<ModuleTree>>, can_gc: CanGc) {
642 match &self {
643 ModuleOwner::Worker(_) => unimplemented!(),
644 ModuleOwner::DynamicModule(_) => unimplemented!(),
645 ModuleOwner::Window(script) => {
646 let document = script.root().owner_document();
647
648 let load = match module_tree {
649 Some(module_tree) => Ok(Script::Module(module_tree)),
650 None => Err(()),
651 };
652
653 let asynch = script
654 .root()
655 .upcast::<Element>()
656 .has_attribute(&local_name!("async"));
657
658 if !asynch && (*script.root()).get_parser_inserted() {
659 document.deferred_script_loaded(&script.root(), load, can_gc);
660 } else if !asynch && !(*script.root()).get_non_blocking() {
661 document.asap_in_order_script_loaded(&script.root(), load, can_gc);
662 } else {
663 document.asap_script_loaded(&script.root(), load, can_gc);
664 };
665 },
666 }
667 }
668}
669
670struct ModuleContext {
672 owner: ModuleOwner,
674 data: Vec<u8>,
676 metadata: Option<Metadata>,
678 module_request: ModuleRequest,
680 options: ScriptFetchOptions,
682 status: Result<(), NetworkError>,
684 introduction_type: Option<&'static CStr>,
686}
687
688impl FetchResponseListener for ModuleContext {
689 fn process_request_body(&mut self, _: RequestId) {}
691
692 fn process_request_eof(&mut self, _: RequestId) {}
694
695 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
696 self.metadata = metadata.ok().map(|meta| match meta {
697 FetchMetadata::Unfiltered(m) => m,
698 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
699 });
700
701 let status = self
702 .metadata
703 .as_ref()
704 .map(|m| m.status.clone())
705 .unwrap_or_else(HttpStatus::new_error);
706
707 self.status = {
708 if status.is_error() {
709 Err(NetworkError::ResourceLoadError(
710 "No http status code received".to_owned(),
711 ))
712 } else if status.is_success() {
713 Ok(())
714 } else {
715 Err(NetworkError::ResourceLoadError(format!(
716 "HTTP error code {}",
717 status.code()
718 )))
719 }
720 };
721 }
722
723 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
724 if self.status.is_ok() {
725 self.data.append(&mut chunk);
726 }
727 }
728
729 fn process_response_eof(
732 mut self,
733 cx: &mut js::context::JSContext,
734 _: RequestId,
735 response: Result<(), NetworkError>,
736 timing: ResourceFetchTiming,
737 ) {
738 let global = self.owner.global();
739 let (url, module_type) = &self.module_request;
740
741 if let Some(window) = global.downcast::<Window>() {
742 window
743 .Document()
744 .finish_load(LoadType::Script(url.clone()), cx);
745 }
746
747 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
748
749 let Some(ModuleStatus::Fetching(pending)) =
750 global.get_module_map_entry(&self.module_request)
751 else {
752 return error!("Processing response for a non pending module request");
753 };
754 let promise = pending
755 .borrow_mut()
756 .take()
757 .expect("Need promise to process response");
758
759 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
762 error!("Fetching module script failed {:?}", error);
763 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
764 return promise.resolve_native(&(), CanGc::from_cx(cx));
765 }
766
767 let metadata = self.metadata.take().unwrap();
768 let final_url = metadata.final_url;
769
770 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
772
773 let mut module_script = None;
775
776 let referrer_policy = metadata
778 .headers
779 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
780 .into();
781
782 if referrer_policy != ReferrerPolicy::EmptyString {
784 self.options.referrer_policy = referrer_policy;
785 }
786
787 if let Some(mime) = mime_type {
793 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
795
796 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
799 matches!(module_type, ModuleType::JavaScript)
800 {
801 if let Some(window) = global.downcast::<Window>() {
802 substitute_with_local_script(window, &mut source_text, final_url.clone());
803 }
804
805 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
806 Rc::new(DOMString::from(source_text.clone())),
807 self.owner.clone(),
808 &final_url,
809 self.options,
810 true,
811 1,
812 self.introduction_type,
813 CanGc::from_cx(cx),
814 ));
815 module_script = Some(module_tree);
816 }
817
818 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
821 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
822 &source_text,
823 &global,
824 &final_url,
825 self.introduction_type,
826 CanGc::from_cx(cx),
827 ));
828 module_script = Some(module_tree);
829 }
830 }
831 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
833 promise.resolve_native(&(), CanGc::from_cx(cx));
834 }
835
836 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
837 let global = &self.resource_timing_global();
838 global.report_csp_violations(violations, None, None);
839 }
840}
841
842impl ResourceTimingListener for ModuleContext {
843 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
844 let initiator_type = InitiatorType::LocalName("module".to_string());
845 let (url, _) = &self.module_request;
846 (initiator_type, url.clone())
847 }
848
849 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
850 self.owner.global()
851 }
852}
853
854#[expect(unsafe_code)]
855#[expect(non_snake_case)]
856pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
859 unsafe {
860 if GetModuleResolveHook(rt).is_some() {
861 return;
862 }
863
864 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
865 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
866 SetScriptPrivateReferenceHooks(
867 rt,
868 Some(host_add_ref_top_level_script),
869 Some(host_release_top_level_script),
870 );
871 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
872 }
873}
874
875#[expect(unsafe_code)]
876unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
877 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
878 mem::forget(val.clone());
879 mem::forget(val);
880}
881
882#[expect(unsafe_code)]
883unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
884 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
885}
886
887#[expect(unsafe_code)]
888pub(crate) unsafe extern "C" fn host_import_module_dynamically(
891 cx: *mut RawJSContext,
892 reference_private: RawHandleValue,
893 specifier: RawHandle<*mut JSObject>,
894 promise: RawHandle<*mut JSObject>,
895) -> bool {
896 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
898 let cx = &mut cx;
899 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx.into());
900
901 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
902 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
903 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
904
905 let mut realm = CurrentRealm::assert(cx);
906 let payload = Payload::PromiseRecord(promise);
907 host_load_imported_module(
908 &mut realm,
909 None,
910 reference_private,
911 specifier,
912 module_type,
913 None,
914 payload,
915 );
916
917 true
918}
919
920#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
921pub(crate) struct ScriptFetchOptions {
923 #[no_trace]
924 pub(crate) referrer: Referrer,
925 pub(crate) integrity_metadata: String,
926 #[no_trace]
927 pub(crate) credentials_mode: CredentialsMode,
928 pub(crate) cryptographic_nonce: String,
929 #[no_trace]
930 pub(crate) parser_metadata: ParserMetadata,
931 #[no_trace]
932 pub(crate) referrer_policy: ReferrerPolicy,
933}
934
935impl ScriptFetchOptions {
936 pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
938 Self {
939 cryptographic_nonce: String::new(),
940 integrity_metadata: String::new(),
941 referrer: global.get_referrer(),
942 parser_metadata: ParserMetadata::NotParserInserted,
943 credentials_mode: CredentialsMode::CredentialsSameOrigin,
944 referrer_policy: ReferrerPolicy::EmptyString,
945 }
946 }
947
948 pub(crate) fn descendant_fetch_options(
950 &self,
951 url: &ServoUrl,
952 global: &GlobalScope,
953 ) -> ScriptFetchOptions {
954 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
956
957 Self {
960 referrer: self.referrer.clone(),
961 integrity_metadata: integrity,
963 cryptographic_nonce: self.cryptographic_nonce.clone(),
964 credentials_mode: self.credentials_mode,
965 parser_metadata: self.parser_metadata,
966 referrer_policy: self.referrer_policy,
967 }
968 }
969}
970
971#[expect(unsafe_code)]
972pub(crate) unsafe fn module_script_from_reference_private(
973 reference_private: &RawHandle<JSVal>,
974) -> Option<&ModuleScript> {
975 if reference_private.get().is_undefined() {
976 return None;
977 }
978 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
979}
980
981#[expect(unsafe_code)]
982#[expect(non_snake_case)]
983unsafe extern "C" fn HostResolveImportedModule(
986 cx: *mut RawJSContext,
987 reference_private: RawHandleValue,
988 specifier: RawHandle<*mut JSObject>,
989) -> *mut JSObject {
990 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
992 let mut realm = CurrentRealm::assert(&mut cx);
993 let global_scope = GlobalScope::from_current_realm(&realm);
994
995 let cx = &mut realm;
996
997 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
999 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1000 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1001
1002 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
1003 let url = ModuleTree::resolve_module_specifier(
1004 &global_scope,
1005 module_data,
1006 DOMString::from(specifier),
1007 );
1008
1009 assert!(url.is_ok());
1011
1012 let parsed_url = url.unwrap();
1013
1014 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1016
1017 assert!(module.as_ref().is_some_and(
1019 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1020 ));
1021
1022 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1023 unreachable!()
1024 };
1025
1026 let fetched_module_object = module_tree.get_record();
1027
1028 assert!(fetched_module_object.is_some());
1030
1031 if let Some(record) = fetched_module_object {
1033 return record.handle().get();
1034 }
1035
1036 unreachable!()
1037}
1038
1039const SLOT_MODULEPRIVATE: usize = 0;
1041
1042#[expect(unsafe_code)]
1043#[expect(non_snake_case)]
1044unsafe extern "C" fn HostPopulateImportMeta(
1047 cx: *mut RawJSContext,
1048 reference_private: RawHandleValue,
1049 meta_object: RawHandle<*mut JSObject>,
1050) -> bool {
1051 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1053 let realm = CurrentRealm::assert(&mut cx);
1054 let global_scope = GlobalScope::from_current_realm(&realm);
1055
1056 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1058 Some(module_data) => module_data.base_url.clone(),
1059 None => global_scope.api_base_url(),
1060 };
1061
1062 unsafe {
1063 let url_string = JS_NewStringCopyN(
1064 &mut cx,
1065 base_url.as_str().as_ptr() as *const _,
1066 base_url.as_str().len(),
1067 );
1068 rooted!(&in(cx) let url_string = url_string);
1069
1070 if !JS_DefineProperty4(
1072 &mut cx,
1073 Handle::from_raw(meta_object),
1074 c"url".as_ptr(),
1075 url_string.handle(),
1076 JSPROP_ENUMERATE.into(),
1077 ) {
1078 return false;
1079 }
1080
1081 let resolve_function = DefineFunctionWithReserved(
1083 &mut cx,
1084 meta_object.get(),
1085 c"resolve".as_ptr(),
1086 Some(import_meta_resolve),
1087 1,
1088 JSPROP_ENUMERATE.into(),
1089 );
1090
1091 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1092 assert!(!obj.is_null());
1093 SetFunctionNativeReserved(
1094 obj.get(),
1095 SLOT_MODULEPRIVATE,
1096 &reference_private.get() as *const _,
1097 );
1098 }
1099
1100 true
1101}
1102
1103#[expect(unsafe_code)]
1104unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1105 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1107 let mut realm = CurrentRealm::assert(&mut cx);
1108 let global_scope = GlobalScope::from_current_realm(&realm);
1109
1110 let cx = &mut realm;
1111
1112 let args = unsafe { CallArgs::from_vp(vp, argc) };
1113
1114 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1115 let reference_private = module_private.handle().into();
1116 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1117
1118 let specifier = unsafe {
1122 let value = HandleValue::from_raw(args.get(0));
1123
1124 match NonNull::new(ToString(cx.raw_cx(), value)) {
1125 Some(jsstr) => jsstr_to_string(cx.raw_cx(), jsstr).into(),
1126 None => return false,
1127 }
1128 };
1129
1130 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1132
1133 match url {
1134 Ok(url) => {
1135 url.as_str().safe_to_jsval(
1137 cx.into(),
1138 unsafe { MutableHandleValue::from_raw(args.rval()) },
1139 CanGc::from_cx(cx),
1140 );
1141 true
1142 },
1143 Err(error) => {
1144 let resolution_error = gen_type_error(&global_scope, error, CanGc::from_cx(cx));
1145
1146 unsafe {
1147 JS_SetPendingException(
1148 cx.raw_cx(),
1149 resolution_error.handle(),
1150 ExceptionStackBehavior::Capture,
1151 );
1152 }
1153 false
1154 },
1155 }
1156}
1157
1158pub(crate) fn fetch_an_external_module_script(
1160 url: ServoUrl,
1161 owner: ModuleOwner,
1162 options: ScriptFetchOptions,
1163 can_gc: CanGc,
1164) {
1165 let referrer = owner.global().get_referrer();
1166
1167 fetch_a_single_module_script(
1170 url,
1171 owner.clone(),
1172 Destination::Script,
1173 options,
1174 referrer,
1175 None,
1176 true,
1177 Some(IntroductionType::SRC_SCRIPT),
1178 move |module_tree| {
1179 let Some(module) = module_tree else {
1180 return owner.notify_owner_to_finish(None, can_gc);
1182 };
1183
1184 fetch_the_descendants_and_link_module_script(module, Destination::Script, owner);
1186 },
1187 );
1188}
1189
1190pub(crate) fn fetch_inline_module_script(
1192 owner: ModuleOwner,
1193 module_script_text: Rc<DOMString>,
1194 url: ServoUrl,
1195 options: ScriptFetchOptions,
1196 line_number: u32,
1197 can_gc: CanGc,
1198) {
1199 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1201 module_script_text,
1202 owner.clone(),
1203 &url,
1204 options,
1205 false,
1206 line_number,
1207 Some(IntroductionType::INLINE_SCRIPT),
1208 can_gc,
1209 ));
1210
1211 fetch_the_descendants_and_link_module_script(module_tree, Destination::Script, owner);
1213}
1214
1215#[expect(unsafe_code)]
1216fn fetch_the_descendants_and_link_module_script(
1218 module_script: Rc<ModuleTree>,
1219 destination: Destination,
1220 owner: ModuleOwner,
1221) {
1222 let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
1223 let mut realm = CurrentRealm::assert(&mut cx);
1224 let cx = &mut realm;
1225
1226 let global = owner.global();
1227
1228 if module_script.get_record().is_none() {
1231 let parse_error = module_script.get_parse_error().cloned();
1232
1233 module_script.set_rethrow_error(parse_error.unwrap());
1235
1236 owner.notify_owner_to_finish(Some(module_script), CanGc::from_cx(cx));
1238
1239 return;
1241 }
1242
1243 let state = Rc::new(LoadState {
1246 error_to_rethrow: RefCell::new(None),
1247 destination,
1248 fetch_client: owner.clone(),
1249 });
1250
1251 let loading_promise =
1255 load_requested_modules(cx, module_script.clone(), Some(Rc::clone(&state)));
1256
1257 let fulfillment_owner = owner.clone();
1258 let fulfilled_module = module_script.clone();
1259
1260 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1262 task!(fulfilled_steps: |cx, fulfillment_owner: ModuleOwner| {
1263 let global = fulfillment_owner.global();
1264 let mut realm = AutoRealm::new(
1265 cx,
1266 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
1267 );
1268 let cx = &mut *realm;
1269
1270 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1271
1272 let link = unsafe { ModuleLink(cx, handle) };
1274
1275 if !link {
1277 let exception = RethrowError::from_pending_exception(cx.into());
1278 fulfilled_module.set_rethrow_error(exception);
1279 }
1280
1281 fulfillment_owner.notify_owner_to_finish(Some(fulfilled_module), CanGc::from_cx(cx));
1283 }),
1284 ));
1285
1286 let rejection_owner = owner;
1287 let rejected_module = module_script;
1288
1289 let loading_promise_rejection = ModuleHandler::new_boxed(Box::new(
1291 task!(rejected_steps: |rejection_owner: ModuleOwner, state: Rc<LoadState>| {
1292 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1295 rejected_module.set_rethrow_error(error.clone());
1296 rejection_owner.notify_owner_to_finish(Some(rejected_module), CanGc::note());
1297 } else {
1298 rejection_owner.notify_owner_to_finish(None, CanGc::note());
1300 }
1301 }),
1302 ));
1303
1304 let handler = PromiseNativeHandler::new(
1305 &global,
1306 Some(loading_promise_fulfillment),
1307 Some(loading_promise_rejection),
1308 CanGc::from_cx(cx),
1309 );
1310
1311 let realm = enter_realm(&*global);
1312 let comp = InRealm::Entered(&realm);
1313 run_a_callback::<DomTypeHolder, _>(&global, || {
1314 loading_promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
1315 });
1316}
1317
1318#[expect(clippy::too_many_arguments)]
1320pub(crate) fn fetch_a_single_module_script(
1321 url: ServoUrl,
1322 owner: ModuleOwner,
1323 destination: Destination,
1324 options: ScriptFetchOptions,
1325 referrer: Referrer,
1326 module_type: Option<ModuleType>,
1327 is_top_level: bool,
1328 introduction_type: Option<&'static CStr>,
1329 on_complete: impl FnOnce(Option<Rc<ModuleTree>>) + 'static,
1330) {
1331 let global = owner.global();
1332
1333 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1337
1338 let module_request = (url.clone(), module_type);
1344 let entry = global.get_module_map_entry(&module_request);
1345
1346 let pending = match entry {
1347 Some(ModuleStatus::Fetching(pending)) => pending,
1348 Some(ModuleStatus::Loaded(module_tree)) => {
1350 return on_complete(module_tree);
1351 },
1352 None => DomRefCell::new(None),
1353 };
1354
1355 let global_scope = DomRoot::from_ref(&*global);
1356 let module_map_key = module_request.clone();
1357 let handler = ModuleHandler::new_boxed(Box::new(
1358 task!(fetch_completed: |global_scope: DomRoot<GlobalScope>| {
1359 let key = module_map_key;
1360 let module = global_scope.get_module_map_entry(&key);
1361
1362 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1363 on_complete(module_tree);
1364 }
1365 }),
1366 ));
1367
1368 let handler = PromiseNativeHandler::new(&global, Some(handler), None, CanGc::note());
1369
1370 let realm = enter_realm(&*global);
1371 let comp = InRealm::Entered(&realm);
1372 run_a_callback::<DomTypeHolder, _>(&global, || {
1373 let has_pending_fetch = pending.borrow().is_some();
1374 pending
1375 .borrow_mut()
1376 .get_or_insert_with(|| Promise::new_in_current_realm(comp, CanGc::note()))
1377 .append_native_handler(&handler, comp, CanGc::note());
1378
1379 if has_pending_fetch {
1382 return;
1383 }
1384
1385 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1387
1388 let document: Option<DomRoot<Document>> = match &owner {
1389 ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1390 ModuleOwner::Window(script) => Some(script.root().owner_document()),
1391 };
1392 let webview_id = document.as_ref().map(|document| document.webview_id());
1393
1394 let mode = match destination {
1399 Destination::Worker | Destination::SharedWorker if is_top_level => {
1400 RequestMode::SameOrigin
1401 },
1402 _ => RequestMode::CorsMode,
1403 };
1404
1405 let destination = match module_type {
1407 ModuleType::JSON => Destination::Json,
1408 ModuleType::JavaScript | ModuleType::Unknown => destination,
1409 };
1410
1411 let request = RequestBuilder::new(webview_id, url.clone(), referrer)
1415 .destination(destination)
1416 .parser_metadata(options.parser_metadata)
1417 .integrity_metadata(options.integrity_metadata.clone())
1418 .credentials_mode(options.credentials_mode)
1419 .referrer_policy(options.referrer_policy)
1420 .mode(mode)
1421 .with_global_scope(&global)
1422 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1423
1424 let context = ModuleContext {
1425 owner,
1426 data: vec![],
1427 metadata: None,
1428 module_request,
1429 options,
1430 status: Ok(()),
1431 introduction_type,
1432 };
1433
1434 let network_listener = NetworkListener::new(
1435 context,
1436 global.task_manager().networking_task_source().to_sendable(),
1437 );
1438 match document {
1439 Some(document) => {
1440 document.loader_mut().fetch_async_with_callback(
1441 LoadType::Script(url),
1442 request,
1443 network_listener.into_callback(),
1444 );
1445 },
1446 None => global.fetch_with_network_listener(request, network_listener),
1447 };
1448 })
1449}
1450
1451#[expect(unsafe_code)]
1452fn fill_module_compile_options(
1453 cx: SafeJSContext,
1454 url: &ServoUrl,
1455 introduction_type: Option<&'static CStr>,
1456 line_number: u32,
1457) -> CompileOptionsWrapper {
1458 let mut options =
1459 unsafe { CompileOptionsWrapper::new_raw(*cx, cformat!("{url}"), line_number) };
1460 if let Some(introduction_type) = introduction_type {
1461 options.set_introduction_type(introduction_type);
1462 }
1463
1464 options.set_muted_errors(false);
1466
1467 options.set_is_run_once(true);
1469 options.set_no_script_rval(true);
1470
1471 options
1472}
1473
1474pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1475pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1476
1477#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1479pub(crate) struct ResolvedModule {
1480 base_url: String,
1482 specifier: String,
1484 #[no_trace]
1486 specifier_url: Option<ServoUrl>,
1487}
1488
1489impl ResolvedModule {
1490 pub(crate) fn new(
1491 base_url: String,
1492 specifier: String,
1493 specifier_url: Option<ServoUrl>,
1494 ) -> Self {
1495 Self {
1496 base_url,
1497 specifier,
1498 specifier_url,
1499 }
1500 }
1501}
1502
1503#[derive(Default, JSTraceable, MallocSizeOf)]
1505pub(crate) struct ImportMap {
1506 #[no_trace]
1507 imports: ModuleSpecifierMap,
1508 #[no_trace]
1509 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1510 #[no_trace]
1511 integrity: ModuleIntegrityMap,
1512}
1513
1514impl ImportMap {
1515 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1517 self.integrity.get(url).cloned().unwrap_or_default()
1522 }
1523}
1524
1525pub(crate) fn register_import_map(
1527 global: &GlobalScope,
1528 result: Fallible<ImportMap>,
1529 can_gc: CanGc,
1530) {
1531 match result {
1532 Ok(new_import_map) => {
1533 merge_existing_and_new_import_maps(global, new_import_map, can_gc);
1535 },
1536 Err(exception) => {
1537 throw_dom_exception(GlobalScope::get_cx(), global, exception, can_gc);
1540 },
1541 }
1542}
1543
1544fn merge_existing_and_new_import_maps(
1546 global: &GlobalScope,
1547 new_import_map: ImportMap,
1548 can_gc: CanGc,
1549) {
1550 let new_import_map_scopes = new_import_map.scopes;
1552
1553 let mut old_import_map = global.import_map_mut();
1555
1556 let mut new_import_map_imports = new_import_map.imports;
1558
1559 let resolved_module_set = global.resolved_module_set();
1560 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1562 for record in resolved_module_set.iter() {
1564 let prefix = scope_prefix.as_str();
1567 if prefix == record.base_url ||
1568 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1569 {
1570 scope_imports.retain(|key, val| {
1572 if *key == record.specifier ||
1577 (key.ends_with('\u{002f}') &&
1578 record.specifier.starts_with(key) &&
1579 (record.specifier_url.is_none() ||
1580 record
1581 .specifier_url
1582 .as_ref()
1583 .is_some_and(|u| u.is_special_scheme())))
1584 {
1585 Console::internal_warn(
1588 global,
1589 DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
1590 );
1591 false
1593 } else {
1594 true
1595 }
1596 })
1597 }
1598 }
1599
1600 if old_import_map.scopes.contains_key(&scope_prefix) {
1602 let merged_module_specifier_map = merge_module_specifier_maps(
1605 global,
1606 scope_imports,
1607 &old_import_map.scopes[&scope_prefix],
1608 can_gc,
1609 );
1610 old_import_map
1611 .scopes
1612 .insert(scope_prefix, merged_module_specifier_map);
1613 } else {
1614 old_import_map.scopes.insert(scope_prefix, scope_imports);
1616 }
1617 }
1618
1619 for (url, integrity) in &new_import_map.integrity {
1621 if old_import_map.integrity.contains_key(url) {
1623 Console::internal_warn(
1626 global,
1627 DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
1628 );
1629 continue;
1631 }
1632
1633 old_import_map
1635 .integrity
1636 .insert(url.clone(), integrity.clone());
1637 }
1638
1639 for record in resolved_module_set.iter() {
1641 new_import_map_imports.retain(|specifier, val| {
1643 if record.specifier.starts_with(specifier) {
1648 Console::internal_warn(
1651 global,
1652 DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
1653 );
1654 false
1656 } else {
1657 true
1658 }
1659 });
1660 }
1661
1662 let merged_module_specifier_map = merge_module_specifier_maps(
1665 global,
1666 new_import_map_imports,
1667 &old_import_map.imports,
1668 can_gc,
1669 );
1670 old_import_map.imports = merged_module_specifier_map;
1671
1672 old_import_map
1675 .scopes
1676 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1677}
1678
1679fn merge_module_specifier_maps(
1681 global: &GlobalScope,
1682 new_map: ModuleSpecifierMap,
1683 old_map: &ModuleSpecifierMap,
1684 _can_gc: CanGc,
1685) -> ModuleSpecifierMap {
1686 let mut merged_map = old_map.clone();
1688
1689 for (specifier, url) in new_map {
1691 if old_map.contains_key(&specifier) {
1693 Console::internal_warn(
1696 global,
1697 DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
1698 );
1699
1700 continue;
1702 }
1703
1704 merged_map.insert(specifier, url);
1706 }
1707
1708 merged_map
1709}
1710
1711pub(crate) fn parse_an_import_map_string(
1713 module_owner: ModuleOwner,
1714 input: Rc<DOMString>,
1715 base_url: ServoUrl,
1716) -> Fallible<ImportMap> {
1717 let parsed: JsonValue = serde_json::from_str(&input.str())
1719 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1720 let JsonValue::Object(mut parsed) = parsed else {
1723 return Err(Error::Type(
1724 c"The top-level value needs to be a JSON object.".to_owned(),
1725 ));
1726 };
1727
1728 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1730 if let Some(imports) = parsed.get("imports") {
1732 let JsonValue::Object(imports) = imports else {
1735 return Err(Error::Type(
1736 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1737 ));
1738 };
1739 sorted_and_normalized_imports =
1742 sort_and_normalize_module_specifier_map(&module_owner.global(), imports, &base_url);
1743 }
1744
1745 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1747 if let Some(scopes) = parsed.get("scopes") {
1749 let JsonValue::Object(scopes) = scopes else {
1752 return Err(Error::Type(
1753 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1754 ));
1755 };
1756 sorted_and_normalized_scopes =
1759 sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url)?;
1760 }
1761
1762 let mut normalized_integrity = ModuleIntegrityMap::new();
1764 if let Some(integrity) = parsed.get("integrity") {
1766 let JsonValue::Object(integrity) = integrity else {
1769 return Err(Error::Type(
1770 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1771 ));
1772 };
1773 normalized_integrity =
1776 normalize_module_integrity_map(&module_owner.global(), integrity, &base_url);
1777 }
1778
1779 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1783 if !parsed.is_empty() {
1784 Console::internal_warn(
1785 &module_owner.global(),
1786 DOMString::from(
1787 "Invalid top-level key was present in the import map.
1788 Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
1789 ),
1790 );
1791 }
1792
1793 Ok(ImportMap {
1795 imports: sorted_and_normalized_imports,
1796 scopes: sorted_and_normalized_scopes,
1797 integrity: normalized_integrity,
1798 })
1799}
1800
1801fn sort_and_normalize_module_specifier_map(
1803 global: &GlobalScope,
1804 original_map: &JsonMap<String, JsonValue>,
1805 base_url: &ServoUrl,
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)
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) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
1889 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1891
1892 for (scope_prefix, potential_specifier_map) in original_map {
1894 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
1897 return Err(Error::Type(
1898 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
1899 .to_owned(),
1900 ));
1901 };
1902
1903 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
1905 Console::internal_warn(
1909 global,
1910 DOMString::from(format!(
1911 "Scope prefix URL was not parseable: {scope_prefix}"
1912 )),
1913 );
1914 continue;
1916 };
1917
1918 let normalized_scope_prefix = scope_prefix_url;
1920
1921 let normalized_specifier_map =
1924 sort_and_normalize_module_specifier_map(global, potential_specifier_map, base_url);
1925 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
1926 }
1927
1928 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1931 Ok(normalized)
1932}
1933
1934fn normalize_module_integrity_map(
1936 global: &GlobalScope,
1937 original_map: &JsonMap<String, JsonValue>,
1938 base_url: &ServoUrl,
1939) -> ModuleIntegrityMap {
1940 let mut normalized = ModuleIntegrityMap::new();
1942
1943 for (key, value) in original_map {
1945 let Some(resolved_url) =
1948 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
1949 else {
1950 Console::internal_warn(
1954 global,
1955 DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
1956 );
1957 continue;
1959 };
1960
1961 let JsonValue::String(value) = value else {
1963 Console::internal_warn(
1966 global,
1967 DOMString::from("Integrity metadata values need to be strings."),
1968 );
1969 continue;
1971 };
1972
1973 normalized.insert(resolved_url, value.clone());
1975 }
1976
1977 normalized
1979}
1980
1981fn normalize_specifier_key(
1983 global: &GlobalScope,
1984 specifier_key: &str,
1985 base_url: &ServoUrl,
1986) -> Option<String> {
1987 if specifier_key.is_empty() {
1989 Console::internal_warn(
1992 global,
1993 DOMString::from("Specifier keys may not be the empty string."),
1994 );
1995 return None;
1997 }
1998 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2000
2001 if let Some(url) = url {
2003 return Some(url.into_string());
2004 }
2005
2006 Some(specifier_key.to_string())
2008}
2009
2010fn resolve_imports_match(
2015 normalized_specifier: &str,
2016 as_url: Option<&ServoUrl>,
2017 specifier_map: &ModuleSpecifierMap,
2018) -> Fallible<Option<ServoUrl>> {
2019 for (specifier_key, resolution_result) in specifier_map {
2021 if specifier_key == normalized_specifier {
2023 if let Some(resolution_result) = resolution_result {
2024 return Ok(Some(resolution_result.clone()));
2028 } else {
2029 return Err(Error::Type(
2031 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2032 ));
2033 }
2034 }
2035
2036 if specifier_key.ends_with('\u{002f}') &&
2041 normalized_specifier.starts_with(specifier_key) &&
2042 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2043 {
2044 let Some(resolution_result) = resolution_result else {
2047 return Err(Error::Type(
2048 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2049 ));
2050 };
2051
2052 let after_prefix = normalized_specifier
2054 .strip_prefix(specifier_key)
2055 .expect("specifier_key should be the prefix of normalized_specifier");
2056
2057 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2059
2060 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2062
2063 let Ok(url) = url else {
2066 return Err(Error::Type(
2067 c"Resolution of normalizedSpecifier was blocked since
2068 the afterPrefix portion could not be URL-parsed relative to
2069 the resolutionResult mapped to by the specifierKey prefix."
2070 .to_owned(),
2071 ));
2072 };
2073
2074 if !url.as_str().starts_with(resolution_result.as_str()) {
2077 return Err(Error::Type(
2078 c"Resolution of normalizedSpecifier was blocked due to
2079 it backtracking above its prefix specifierKey."
2080 .to_owned(),
2081 ));
2082 }
2083
2084 return Ok(Some(url));
2086 }
2087 }
2088
2089 Ok(None)
2091}