1use std::borrow::Cow;
9use std::cell::{OnceCell, RefCell};
10use std::ffi::CStr;
11use std::fmt::Debug;
12use std::ptr::NonNull;
13use std::rc::Rc;
14use std::{mem, ptr};
15
16use encoding_rs::UTF_8;
17use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
18use hyper_serde::Serde;
19use indexmap::IndexMap;
20use indexmap::map::Entry;
21use js::context::JSContext;
22use js::conversions::jsstr_to_string;
23use js::gc::{HandleObject, MutableHandleValue};
24use js::jsapi::{
25 CallArgs, ExceptionStackBehavior, GetFunctionNativeReserved, GetModuleResolveHook,
26 Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
27 JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
28 ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook, SetModuleMetadataHook,
29 SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks, Value,
30};
31use js::jsval::{JSVal, PrivateValue, UndefinedValue};
32use js::realm::{AutoRealm, CurrentRealm};
33use js::rust::wrappers2::{
34 CompileJsonModule1, CompileModule1, DefineFunctionWithReserved, GetModuleRequestSpecifier,
35 GetModuleRequestType, JS_ClearPendingException, JS_DefineProperty4, JS_GetPendingException,
36 JS_NewStringCopyN, JS_SetPendingException, ModuleEvaluate, ModuleLink,
37 ThrowOnModuleEvaluationFailure,
38};
39use js::rust::{Handle, HandleValue, ToString, transform_str_to_source_text};
40use mime::Mime;
41use net_traits::http_status::HttpStatus;
42use net_traits::mime_classifier::MimeClassifier;
43use net_traits::policy_container::PolicyContainer;
44use net_traits::request::{
45 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestClient,
46 RequestId, RequestMode,
47};
48use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
49use script_bindings::cell::DomRefCell;
50use script_bindings::error::Fallible;
51use script_bindings::reflector::DomObject;
52use script_bindings::settings_stack::run_a_callback;
53use script_bindings::trace::CustomTraceable;
54use serde_json::{Map as JsonMap, Value as JsonValue};
55use servo_config::pref;
56use servo_url::ServoUrl;
57
58use crate::DomTypeHolder;
59use crate::dom::bindings::conversions::SafeToJSValConvertible;
60use crate::dom::bindings::error::{
61 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
62};
63use crate::dom::bindings::inheritance::Castable;
64use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
65use crate::dom::bindings::root::DomRoot;
66use crate::dom::bindings::str::DOMString;
67use crate::dom::bindings::trace::RootedTraceableBox;
68use crate::dom::csp::{GlobalCspReporting, Violation};
69use crate::dom::globalscope::GlobalScope;
70use crate::dom::globalscope::script_execution::{ErrorReporting, fill_compile_options};
71use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
72use crate::dom::performance::performanceresourcetiming::InitiatorType;
73use crate::dom::promise::Promise;
74use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
75use crate::dom::types::{
76 Console, DedicatedWorkerGlobalScope, SharedWorkerGlobalScope, WorkerGlobalScope,
77};
78use crate::dom::window::Window;
79use crate::module_loading::{
80 LoadState, Payload, host_load_imported_module, load_requested_modules,
81};
82use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
83use crate::realms::enter_auto_realm;
84use crate::script_runtime::IntroductionType;
85use crate::task::NonSendTaskBox;
86use crate::url::ensure_blob_referenced_by_url_is_kept_alive;
87
88pub(crate) fn gen_type_error(
89 cx: &mut JSContext,
90 global: &GlobalScope,
91 error: Error,
92) -> RethrowError {
93 rooted!(&in(cx) let mut thrown = UndefinedValue());
94 error.to_jsval(cx, global, thrown.handle_mut());
95
96 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
97}
98
99#[derive(JSTraceable)]
100pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
101
102impl ModuleObject {
103 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
104 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
105 }
106
107 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
108 self.0.handle()
109 }
110}
111
112#[derive(JSTraceable)]
113pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
114
115impl RethrowError {
116 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
117 Self(RootedTraceableBox::from_box(val))
118 }
119
120 #[expect(unsafe_code)]
121 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
122 rooted!(&in(cx) let mut exception = UndefinedValue());
123 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
124 unsafe { JS_ClearPendingException(cx) };
125
126 Self::new(Heap::boxed(exception.get()))
127 }
128
129 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
130 self.0.handle()
131 }
132}
133
134impl Debug for RethrowError {
135 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
136 "RethrowError(...)".fmt(fmt)
137 }
138}
139
140impl Clone for RethrowError {
141 fn clone(&self) -> Self {
142 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
143 }
144}
145
146pub(crate) struct ModuleScript {
147 pub(crate) base_url: ServoUrl,
148 pub(crate) options: ScriptFetchOptions,
149 pub(crate) owner: Option<Trusted<GlobalScope>>,
150}
151
152impl ModuleScript {
153 pub(crate) fn new(
154 base_url: ServoUrl,
155 options: ScriptFetchOptions,
156 owner: Option<Trusted<GlobalScope>>,
157 ) -> Self {
158 ModuleScript {
159 base_url,
160 options,
161 owner,
162 }
163 }
164}
165
166pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
167
168#[derive(Clone, JSTraceable)]
169pub(crate) enum ModuleStatus {
170 Fetching(DomRefCell<Option<Rc<Promise>>>),
171 Loaded(Option<Rc<ModuleTree>>),
172}
173
174#[derive(JSTraceable, MallocSizeOf)]
175pub(crate) struct ModuleTree {
176 #[no_trace]
177 url: ServoUrl,
178 #[ignore_malloc_size_of = "mozjs"]
179 record: OnceCell<ModuleObject>,
180 #[ignore_malloc_size_of = "mozjs"]
181 parse_error: OnceCell<RethrowError>,
182 #[ignore_malloc_size_of = "mozjs"]
183 rethrow_error: DomRefCell<Option<RethrowError>>,
184 #[no_trace]
185 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
186}
187
188impl ModuleTree {
189 pub(crate) fn get_url(&self) -> ServoUrl {
190 self.url.clone()
191 }
192
193 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
194 self.record.get()
195 }
196
197 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
198 self.parse_error.get()
199 }
200
201 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
202 &self.rethrow_error
203 }
204
205 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
206 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
207 }
208
209 pub(crate) fn find_descendant_inside_module_map(
210 &self,
211 global: &GlobalScope,
212 specifier: &String,
213 module_type: ModuleType,
214 ) -> Option<Rc<ModuleTree>> {
215 self.loaded_modules
216 .borrow()
217 .get(specifier)
218 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
219 .and_then(|status| match status {
220 ModuleStatus::Fetching(_) => None,
221 ModuleStatus::Loaded(module_tree) => module_tree,
222 })
223 }
224
225 pub(crate) fn insert_module_dependency(
226 &self,
227 module: &Rc<ModuleTree>,
228 module_request_specifier: String,
229 ) {
230 let url = module.url.clone();
232 match self
233 .loaded_modules
234 .borrow_mut()
235 .entry(module_request_specifier)
236 {
237 Entry::Occupied(entry) => {
240 assert_eq!(*entry.get(), url);
242 },
243 Entry::Vacant(entry) => {
245 entry.insert(url);
248 },
249 }
250 }
251}
252
253pub(crate) struct ModuleSource<'a> {
254 pub source: Cow<'a, str>,
255 pub unminified_dir: Option<String>,
256 pub external: bool,
257 pub url: ServoUrl,
258}
259
260impl<'a> crate::unminify::ScriptSource for ModuleSource<'a> {
261 fn unminified_dir(&self) -> Option<String> {
262 self.unminified_dir.clone()
263 }
264
265 fn extract_bytes(&self) -> &[u8] {
266 self.source.as_bytes()
267 }
268
269 fn rewrite_source(&mut self, source: String) {
270 self.source = source.into();
271 }
272
273 fn url(&self) -> ServoUrl {
274 self.url.clone()
275 }
276
277 fn is_external(&self) -> bool {
278 self.external
279 }
280}
281
282impl ModuleTree {
283 #[expect(unsafe_code)]
284 #[expect(clippy::too_many_arguments)]
285 fn create_a_javascript_module_script(
287 cx: &mut JSContext,
288 source: Cow<'_, str>,
289 global: &GlobalScope,
290 url: &ServoUrl,
291 options: ScriptFetchOptions,
292 external: bool,
293 line_number: u32,
294 introduction_type: Option<&'static CStr>,
295 ) -> Self {
296 let mut realm = AutoRealm::new(
297 cx,
298 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
299 );
300 let cx = &mut *realm;
301
302 let owner = Trusted::new(global);
303
304 let module = ModuleTree {
307 url: url.clone(),
308 record: OnceCell::new(),
309 parse_error: OnceCell::new(),
310 rethrow_error: DomRefCell::new(None),
311 loaded_modules: DomRefCell::new(IndexMap::new()),
312 };
313
314 let compile_options = fill_compile_options(
315 cx,
316 url.as_str(),
317 introduction_type,
318 ErrorReporting::Unmuted,
319 true, line_number,
321 );
322
323 let mut source = if global.unminified_js_dir().is_some() {
324 let mut module_source = ModuleSource {
325 source,
326 unminified_dir: global.unminified_js_dir(),
327 external,
328 url: url.clone(),
329 };
330 crate::unminify::unminify_js(&mut module_source);
331 transform_str_to_source_text(&module_source.source)
332 } else {
333 transform_str_to_source_text(&source)
334 };
335
336 unsafe {
337 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
339 module_script.set(CompileModule1(cx, compile_options.ptr, &mut source));
340
341 if module_script.is_null() {
343 warn!("fail to compile module script of {}", url);
344
345 let _ = module
347 .parse_error
348 .set(RethrowError::from_pending_exception(cx));
349
350 return module;
352 }
353
354 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
358
359 SetModulePrivate(
360 module_script.get(),
361 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
362 );
363
364 let _ = module.record.set(ModuleObject::new(module_script.handle()));
366 }
367
368 module
370 }
371
372 #[expect(unsafe_code)]
373 fn create_a_json_module_script(
375 cx: &mut JSContext,
376 source: &str,
377 global: &GlobalScope,
378 url: &ServoUrl,
379 introduction_type: Option<&'static CStr>,
380 ) -> Self {
381 let mut realm = AutoRealm::new(
382 cx,
383 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
384 );
385 let cx = &mut *realm;
386
387 let module = ModuleTree {
390 url: url.clone(),
391 record: OnceCell::new(),
392 parse_error: OnceCell::new(),
393 rethrow_error: DomRefCell::new(None),
394 loaded_modules: DomRefCell::new(IndexMap::new()),
395 };
396
397 let compile_options = fill_compile_options(
402 cx,
403 url.as_str(),
404 introduction_type,
405 ErrorReporting::Unmuted,
406 true, 1, );
409
410 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
411
412 unsafe {
413 module_script.set(CompileJsonModule1(
415 cx,
416 compile_options.ptr,
417 &mut transform_str_to_source_text(source),
418 ));
419 }
420
421 if module_script.is_null() {
423 warn!("fail to compile module script of {}", url);
424
425 let _ = module
426 .parse_error
427 .set(RethrowError::from_pending_exception(cx));
428 return module;
429 }
430
431 let _ = module.record.set(ModuleObject::new(module_script.handle()));
433
434 module
436 }
437
438 #[expect(unsafe_code)]
441 pub(crate) fn execute_module(
442 &self,
443 cx: &mut JSContext,
444 global: &GlobalScope,
445 module_record: HandleObject,
446 mut eval_result: MutableHandleValue,
447 ) -> Result<(), RethrowError> {
448 let mut realm = AutoRealm::new(
449 cx,
450 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
451 );
452 let cx = &mut *realm;
453
454 unsafe {
455 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
456 assert!(ok, "module evaluation failed");
457
458 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
459 if eval_result.is_object() {
460 evaluation_promise.set(eval_result.to_object());
461 }
462
463 let throw_result = ThrowOnModuleEvaluationFailure(
464 cx,
465 evaluation_promise.handle(),
466 ModuleErrorBehaviour::ThrowModuleErrorsSync,
467 );
468 if !throw_result {
469 warn!("fail to evaluate module");
470
471 Err(RethrowError::from_pending_exception(cx))
472 } else {
473 debug!("module evaluated successfully");
474 Ok(())
475 }
476 }
477 }
478
479 #[expect(unsafe_code)]
480 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
481 let module_error = self.rethrow_error.borrow();
482
483 if let Some(exception) = &*module_error {
484 let mut realm = enter_auto_realm(cx, global);
485 let cx = &mut realm.current_realm();
486
487 unsafe {
488 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
489 }
490 report_pending_exception(cx);
491 }
492 }
493
494 pub(crate) fn resolve_module_specifier(
496 global: &GlobalScope,
497 script: Option<&ModuleScript>,
498 specifier: DOMString,
499 ) -> Fallible<ServoUrl> {
500 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
502 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
504 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
508 None => (global, &global.api_base_url()),
513 };
514
515 let import_map = if global.is::<Window>() {
519 Some(global.import_map())
520 } else {
521 None
522 };
523 let specifier = &specifier.str();
524
525 let serialized_base_url = base_url.as_str();
527 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
529 let normalized_specifier = match &as_url {
532 Some(url) => url.as_str(),
533 None => specifier,
534 };
535
536 let mut result = None;
538 if let Some(map) = import_map {
539 for (prefix, imports) in &map.scopes {
541 let prefix = prefix.as_str();
544 if prefix == serialized_base_url ||
545 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
546 {
547 let scope_imports_match =
550 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
551
552 if scope_imports_match.is_some() {
554 result = scope_imports_match;
555 break;
556 }
557 }
558 }
559
560 if result.is_none() {
563 result =
564 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
565 }
566 }
567
568 if result.is_none() {
570 result = as_url.clone();
571 }
572
573 match result {
575 Some(result) => {
576 global.add_module_to_resolved_module_set(
579 serialized_base_url,
580 normalized_specifier,
581 as_url.clone(),
582 );
583 Ok(result)
585 },
586 None => Err(Error::Type(
589 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
590 .to_owned(),
591 )),
592 }
593 }
594
595 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
597 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
599 {
600 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
602 }
603 ServoUrl::parse(specifier).ok()
605 }
606}
607
608#[derive(JSTraceable, MallocSizeOf)]
609pub(crate) struct ModuleHandler {
610 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
611 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
612}
613
614impl ModuleHandler {
615 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
616 Box::new(Self {
617 task: DomRefCell::new(Some(task)),
618 })
619 }
620}
621
622impl Callback for ModuleHandler {
623 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
624 let task = self.task.borrow_mut().take().unwrap();
625 task.run_box(cx);
626 }
627}
628
629#[derive(JSTraceable, MallocSizeOf)]
630struct QueueTaskHandler {
631 #[conditional_malloc_size_of]
632 promise: Rc<Promise>,
633}
634
635impl Callback for QueueTaskHandler {
636 fn callback(&self, cx: &mut CurrentRealm, _: HandleValue) {
637 let global = GlobalScope::from_current_realm(cx);
638 let promise = TrustedPromise::new(self.promise.clone());
639
640 global.task_manager().networking_task_source().queue(
641 task!(continue_module_loading: move |cx| {
642 promise.root().resolve_native(cx, &());
643 }),
644 );
645 }
646}
647
648struct ModuleContext {
650 owner: Trusted<GlobalScope>,
652 data: Vec<u8>,
654 metadata: Option<Metadata>,
656 module_request: ModuleRequest,
658 options: ScriptFetchOptions,
660 status: Result<(), NetworkError>,
662 introduction_type: Option<&'static CStr>,
664 policy_container: Option<PolicyContainer>,
666}
667
668impl FetchResponseListener for ModuleContext {
669 fn process_request_body(&mut self, _: RequestId) {}
671
672 fn process_response(
673 &mut self,
674 _: &mut js::context::JSContext,
675 _: RequestId,
676 metadata: Result<FetchMetadata, NetworkError>,
677 ) {
678 self.metadata = metadata.ok().map(|meta| match meta {
679 FetchMetadata::Unfiltered(m) => m,
680 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
681 });
682
683 let status = self
684 .metadata
685 .as_ref()
686 .map(|m| m.status.clone())
687 .unwrap_or_else(HttpStatus::new_error);
688
689 self.status = {
690 if status.is_error() {
691 Err(NetworkError::ResourceLoadError(
692 "No http status code received".to_owned(),
693 ))
694 } else if status.is_success() {
695 Ok(())
696 } else {
697 Err(NetworkError::ResourceLoadError(format!(
698 "HTTP error code {}",
699 status.code()
700 )))
701 }
702 };
703 }
704
705 fn process_response_chunk(
706 &mut self,
707 _: &mut js::context::JSContext,
708 _: RequestId,
709 mut chunk: Vec<u8>,
710 ) {
711 if self.status.is_ok() {
712 self.data.append(&mut chunk);
713 }
714 }
715
716 fn process_response_eof(
719 mut self,
720 cx: &mut js::context::JSContext,
721 _: RequestId,
722 response: Result<(), NetworkError>,
723 timing: ResourceFetchTiming,
724 ) {
725 let global = self.owner.root();
726 let (_url, module_type) = &self.module_request;
727
728 network_listener::submit_timing(cx, &self, &response, &timing);
729
730 let Some(ModuleStatus::Fetching(pending)) =
731 global.get_module_map_entry(&self.module_request)
732 else {
733 return error!("Processing response for a non pending module request");
734 };
735 let promise = pending
736 .borrow_mut()
737 .take()
738 .expect("Need promise to process response");
739
740 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
743 error!("Fetching module script failed {:?}", error);
744 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
745 return promise.resolve_native(cx, &());
746 }
747
748 let metadata = self.metadata.take().unwrap();
749
750 if let Some(policy_container) = self.policy_container {
753 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
754 "We only need a policy container when initializing a worker's globalscope.",
755 );
756 workerscope.process_response_for_workerscope(&metadata, &policy_container);
757 }
758
759 let final_url = metadata.final_url;
760
761 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
763
764 let mut module_script = None;
766
767 let referrer_policy = metadata
769 .headers
770 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
771 .into();
772
773 if referrer_policy != ReferrerPolicy::EmptyString {
775 self.options.referrer_policy = referrer_policy;
776 }
777
778 if let Some(mime) = mime_type {
784 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
786
787 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
790 matches!(module_type, ModuleType::JavaScript)
791 {
792 if let Some(window) = global.downcast::<Window>() &&
793 let Some(script_souce) = window.local_script_source()
794 {
795 substitute_with_local_script(script_souce, &mut source_text, final_url.clone());
796 }
797
798 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
799 cx,
800 source_text,
801 &global,
802 &final_url,
803 self.options,
804 true,
805 1,
806 self.introduction_type,
807 ));
808 module_script = Some(module_tree);
809 } else if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
810 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
813 cx,
814 &source_text,
815 &global,
816 &final_url,
817 self.introduction_type,
818 ));
819 module_script = Some(module_tree);
820 }
821 }
822 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
824 promise.resolve_native(cx, &());
825 }
826
827 fn process_csp_violations(
828 &mut self,
829 cx: &mut js::context::JSContext,
830 _request_id: RequestId,
831 violations: Vec<Violation>,
832 ) {
833 let global = self.owner.root();
834 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
835 scope.report_csp_violations(violations);
836 } else if let Some(scope) = global.downcast::<SharedWorkerGlobalScope>() {
837 scope.report_csp_violations(violations);
838 } else {
839 global.report_csp_violations(cx, violations, None, None);
840 }
841 }
842
843 fn process_content_length(&mut self, _request_id: RequestId, size: usize) {
844 self.data.reserve(size - self.data.len());
845 }
846}
847
848impl ResourceTimingListener for ModuleContext {
849 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
850 let initiator_type = InitiatorType::LocalName("module".to_string());
851 let (url, _) = &self.module_request;
852 (initiator_type, url.clone())
853 }
854
855 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
856 self.owner.root()
857 }
858}
859
860#[expect(unsafe_code)]
861#[expect(non_snake_case)]
862pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
865 unsafe {
866 if GetModuleResolveHook(rt).is_some() {
867 return;
868 }
869
870 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
871 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
872 SetScriptPrivateReferenceHooks(
873 rt,
874 Some(host_add_ref_top_level_script),
875 Some(host_release_top_level_script),
876 );
877 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
878 }
879}
880
881#[expect(unsafe_code)]
882unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
883 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
884 mem::forget(val.clone());
885 mem::forget(val);
886}
887
888#[expect(unsafe_code)]
889unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
890 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
891}
892
893#[expect(unsafe_code)]
894pub(crate) unsafe extern "C" fn host_import_module_dynamically(
897 cx: *mut RawJSContext,
898 reference_private: RawHandleValue,
899 specifier: RawHandle<*mut JSObject>,
900 promise: RawHandle<*mut JSObject>,
901) -> bool {
902 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
904 let cx = &mut cx;
905 let promise = Promise::new_with_js_promise(cx, unsafe { Handle::from_raw(promise) });
906
907 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
908 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
909 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
910
911 let mut realm = CurrentRealm::assert(cx);
912 let payload = Payload::PromiseRecord(promise);
913 host_load_imported_module(
914 &mut realm,
915 None,
916 reference_private,
917 specifier,
918 module_type,
919 None,
920 payload,
921 );
922
923 true
924}
925
926#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
927pub(crate) struct ScriptFetchOptions {
929 pub(crate) integrity_metadata: String,
930 #[no_trace]
931 pub(crate) credentials_mode: CredentialsMode,
932 pub(crate) cryptographic_nonce: String,
933 #[no_trace]
934 pub(crate) parser_metadata: ParserMetadata,
935 #[no_trace]
936 pub(crate) referrer_policy: ReferrerPolicy,
937 pub(crate) render_blocking: bool,
941}
942
943impl ScriptFetchOptions {
944 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
946 Self {
947 cryptographic_nonce: String::new(),
948 integrity_metadata: String::new(),
949 parser_metadata: ParserMetadata::NotParserInserted,
950 credentials_mode: CredentialsMode::CredentialsSameOrigin,
951 referrer_policy: ReferrerPolicy::EmptyString,
952 render_blocking: false,
953 }
954 }
955
956 pub(crate) fn descendant_fetch_options(
958 &self,
959 url: &ServoUrl,
960 global: &GlobalScope,
961 ) -> ScriptFetchOptions {
962 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
964
965 Self {
968 integrity_metadata: integrity,
970 cryptographic_nonce: self.cryptographic_nonce.clone(),
971 credentials_mode: self.credentials_mode,
972 parser_metadata: self.parser_metadata,
973 referrer_policy: self.referrer_policy,
974 render_blocking: self.render_blocking,
975 }
976 }
977}
978
979#[expect(unsafe_code)]
980pub(crate) unsafe fn module_script_from_reference_private(
981 reference_private: &RawHandle<JSVal>,
982) -> Option<&ModuleScript> {
983 if reference_private.get().is_undefined() {
984 return None;
985 }
986 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
987}
988
989#[expect(unsafe_code)]
990#[expect(non_snake_case)]
991unsafe extern "C" fn HostResolveImportedModule(
994 cx: *mut RawJSContext,
995 reference_private: RawHandleValue,
996 specifier: RawHandle<*mut JSObject>,
997) -> *mut JSObject {
998 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1000 let mut realm = CurrentRealm::assert(&mut cx);
1001 let global_scope = GlobalScope::from_current_realm(&mut realm);
1002
1003 let cx = &mut realm;
1004
1005 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1007 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1008 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1009
1010 let specifier = unsafe { jsstr_to_string(cx, NonNull::new(jsstr).unwrap()) };
1011 let url = ModuleTree::resolve_module_specifier(
1012 &global_scope,
1013 module_data,
1014 DOMString::from(specifier),
1015 );
1016
1017 assert!(url.is_ok());
1019
1020 let parsed_url = url.unwrap();
1021
1022 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1024
1025 assert!(module.as_ref().is_some_and(
1027 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1028 ));
1029
1030 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1031 unreachable!()
1032 };
1033
1034 let fetched_module_object = module_tree.get_record();
1035
1036 assert!(fetched_module_object.is_some());
1038
1039 if let Some(record) = fetched_module_object {
1041 return record.handle().get();
1042 }
1043
1044 unreachable!()
1045}
1046
1047const SLOT_MODULEPRIVATE: usize = 0;
1049
1050#[expect(unsafe_code)]
1051#[expect(non_snake_case)]
1052unsafe extern "C" fn HostPopulateImportMeta(
1055 cx: *mut RawJSContext,
1056 reference_private: RawHandleValue,
1057 meta_object: RawHandle<*mut JSObject>,
1058) -> bool {
1059 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1061 let mut realm = CurrentRealm::assert(&mut cx);
1062 let global_scope = GlobalScope::from_current_realm(&mut realm);
1063
1064 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1066 Some(module_data) => module_data.base_url.clone(),
1067 None => global_scope.api_base_url(),
1068 };
1069
1070 unsafe {
1071 let url_string = JS_NewStringCopyN(
1072 &mut cx,
1073 base_url.as_str().as_ptr() as *const _,
1074 base_url.as_str().len(),
1075 );
1076 rooted!(&in(cx) let url_string = url_string);
1077
1078 if !JS_DefineProperty4(
1080 &mut cx,
1081 Handle::from_raw(meta_object),
1082 c"url".as_ptr(),
1083 url_string.handle(),
1084 JSPROP_ENUMERATE.into(),
1085 ) {
1086 return false;
1087 }
1088
1089 let resolve_function = DefineFunctionWithReserved(
1091 &mut cx,
1092 meta_object.get(),
1093 c"resolve".as_ptr(),
1094 Some(import_meta_resolve),
1095 1,
1096 JSPROP_ENUMERATE.into(),
1097 );
1098
1099 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1100 assert!(!obj.is_null());
1101 SetFunctionNativeReserved(
1102 obj.get(),
1103 SLOT_MODULEPRIVATE,
1104 &reference_private.get() as *const _,
1105 );
1106 }
1107
1108 true
1109}
1110
1111#[expect(unsafe_code)]
1112unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1113 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1115 let mut realm = CurrentRealm::assert(&mut cx);
1116 let global_scope = GlobalScope::from_current_realm(&mut realm);
1117
1118 let cx = &mut realm;
1119
1120 let args = unsafe { CallArgs::from_vp(vp, argc) };
1121
1122 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1123 let reference_private = module_private.handle().into();
1124 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1125
1126 let specifier = unsafe {
1130 let value = HandleValue::from_raw(args.get(0));
1131
1132 match NonNull::new(ToString(cx, value)) {
1133 Some(jsstr) => jsstr_to_string(cx, jsstr).into(),
1134 None => return false,
1135 }
1136 };
1137
1138 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1140
1141 match url {
1142 Ok(url) => {
1143 url.as_str()
1145 .safe_to_jsval(cx, unsafe { MutableHandleValue::from_raw(args.rval()) });
1146 true
1147 },
1148 Err(error) => {
1149 let resolution_error = gen_type_error(cx, &global_scope, error);
1150
1151 unsafe {
1152 JS_SetPendingException(
1153 cx,
1154 resolution_error.handle(),
1155 ExceptionStackBehavior::Capture,
1156 );
1157 }
1158 false
1159 },
1160 }
1161}
1162
1163#[expect(clippy::too_many_arguments)]
1164pub(crate) fn fetch_a_module_worker_script_graph(
1167 cx: &mut JSContext,
1168 global: &GlobalScope,
1169 url: ServoUrl,
1170 fetch_client: RequestClient,
1171 destination: Destination,
1172 referrer: Referrer,
1173 credentials_mode: CredentialsMode,
1174 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1175) {
1176 let global_scope = DomRoot::from_ref(global);
1177
1178 let options = ScriptFetchOptions {
1183 integrity_metadata: "".into(),
1184 credentials_mode,
1185 cryptographic_nonce: "".into(),
1186 parser_metadata: ParserMetadata::NotParserInserted,
1187 referrer_policy: ReferrerPolicy::EmptyString,
1188 render_blocking: false,
1189 };
1190
1191 fetch_a_single_module_script(
1194 cx,
1195 url,
1196 fetch_client.clone(),
1197 global,
1198 destination,
1199 options,
1200 referrer,
1201 None,
1202 true,
1203 Some(IntroductionType::WORKER),
1204 move |cx, module_tree| {
1205 let Some(module) = module_tree else {
1206 return on_complete(cx, None);
1208 };
1209
1210 fetch_the_descendants_and_link_module_script(
1213 cx,
1214 &global_scope,
1215 module,
1216 fetch_client,
1217 destination,
1218 on_complete,
1219 );
1220 },
1221 );
1222}
1223
1224pub(crate) fn fetch_an_external_module_script(
1226 cx: &mut JSContext,
1227 url: ServoUrl,
1228 global: &GlobalScope,
1229 options: ScriptFetchOptions,
1230 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1231) {
1232 let referrer = global.get_referrer();
1233 let fetch_client = global.request_client(Some(cx.no_gc()));
1234 let global_scope = DomRoot::from_ref(global);
1235
1236 fetch_a_single_module_script(
1239 cx,
1240 url,
1241 fetch_client.clone(),
1242 global,
1243 Destination::Script,
1244 options,
1245 referrer,
1246 None,
1247 true,
1248 Some(IntroductionType::SRC_SCRIPT),
1249 move |cx, module_tree| {
1250 let Some(module) = module_tree else {
1251 return on_complete(cx, None);
1253 };
1254
1255 fetch_the_descendants_and_link_module_script(
1257 cx,
1258 &global_scope,
1259 module,
1260 fetch_client,
1261 Destination::Script,
1262 on_complete,
1263 );
1264 },
1265 );
1266}
1267
1268pub(crate) fn fetch_a_modulepreload_module(
1270 cx: &mut JSContext,
1271 url: ServoUrl,
1272 destination: Destination,
1273 global: &GlobalScope,
1274 options: ScriptFetchOptions,
1275 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1276) {
1277 let referrer = global.get_referrer();
1278 let fetch_client = global.request_client(Some(cx.no_gc()));
1279 let global_scope = DomRoot::from_ref(global);
1280
1281 let module_type = if let Destination::Json = destination {
1284 Some(ModuleType::JSON)
1285 } else {
1286 None
1287 };
1288
1289 fetch_a_single_module_script(
1292 cx,
1293 url,
1294 fetch_client.clone(),
1295 global,
1296 destination,
1297 options,
1298 referrer,
1299 module_type,
1300 true,
1301 Some(IntroductionType::SRC_SCRIPT),
1302 move |cx, result| {
1303 on_complete(cx, result.is_none());
1305
1306 assert!(global_scope.is::<Window>());
1308
1309 if pref!(dom_allow_preloading_module_descendants) &&
1312 let Some(module) = result
1313 {
1314 fetch_the_descendants_and_link_module_script(
1315 cx,
1316 &global_scope,
1317 module,
1318 fetch_client,
1319 destination,
1320 |_, _| {},
1321 );
1322 }
1323 },
1324 );
1325}
1326
1327#[expect(clippy::too_many_arguments)]
1328pub(crate) fn fetch_inline_module_script(
1330 cx: &mut JSContext,
1331 global: &GlobalScope,
1332 module_script_text: Cow<'_, str>,
1333 url: ServoUrl,
1334 options: ScriptFetchOptions,
1335 line_number: u32,
1336 introduction_type: Option<&'static CStr>,
1337 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1338) {
1339 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1341 cx,
1342 module_script_text,
1343 global,
1344 &url,
1345 options,
1346 false,
1347 line_number,
1348 introduction_type,
1349 ));
1350 let fetch_client = global.request_client(Some(cx.no_gc()));
1351
1352 fetch_the_descendants_and_link_module_script(
1354 cx,
1355 global,
1356 module_tree,
1357 fetch_client,
1358 Destination::Script,
1359 on_complete,
1360 );
1361}
1362
1363#[expect(unsafe_code)]
1364fn fetch_the_descendants_and_link_module_script(
1366 cx: &mut JSContext,
1367 global: &GlobalScope,
1368 module_script: Rc<ModuleTree>,
1369 fetch_client: RequestClient,
1370 destination: Destination,
1371 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1372) {
1373 if module_script.get_record().is_none() {
1376 let parse_error = module_script.get_parse_error().cloned();
1377
1378 module_script.set_rethrow_error(parse_error.unwrap());
1380
1381 on_complete(cx, Some(module_script));
1383
1384 return;
1386 }
1387
1388 let state = Rc::new(LoadState {
1391 error_to_rethrow: RefCell::new(None),
1392 destination,
1393 fetch_client,
1394 });
1395
1396 let mut realm = enter_auto_realm(cx, global);
1399 let cx = &mut realm.current_realm();
1400
1401 let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
1403
1404 let global_scope = DomRoot::from_ref(global);
1405 let fulfilled_module = module_script.clone();
1406 let fulfilled_on_complete = on_complete.clone();
1407
1408 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1410 task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
1411 let mut realm = AutoRealm::new(
1412 cx,
1413 NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
1414 );
1415 let cx = &mut *realm;
1416
1417 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1418
1419 let link = unsafe { ModuleLink(cx, handle) };
1421
1422 if !link {
1424 let exception = RethrowError::from_pending_exception(cx);
1425 fulfilled_module.set_rethrow_error(exception);
1426 }
1427
1428 fulfilled_on_complete(cx, Some(fulfilled_module));
1430 }),
1431 ));
1432
1433 let loading_promise_rejection =
1435 ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
1436 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1439 module_script.set_rethrow_error(error.clone());
1440 on_complete(cx, Some(module_script));
1441 } else {
1442 on_complete(cx, None);
1444 }
1445 })));
1446
1447 let handler = PromiseNativeHandler::new(
1448 cx,
1449 global,
1450 Some(loading_promise_fulfillment),
1451 Some(loading_promise_rejection),
1452 );
1453
1454 run_a_callback::<DomTypeHolder, _>(global, || {
1455 loading_promise.append_native_handler(cx, &handler);
1456 });
1457}
1458
1459#[expect(clippy::too_many_arguments)]
1461pub(crate) fn fetch_a_single_module_script(
1462 cx: &mut JSContext,
1463 url: ServoUrl,
1464 fetch_client: RequestClient,
1465 global: &GlobalScope,
1466 destination: Destination,
1467 options: ScriptFetchOptions,
1468 referrer: Referrer,
1469 module_type: Option<ModuleType>,
1470 is_top_level: bool,
1471 introduction_type: Option<&'static CStr>,
1472 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1473) {
1474 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1478
1479 let module_request = (url.clone(), module_type);
1485 let entry = global.get_module_map_entry(&module_request);
1486
1487 let pending = match entry {
1488 Some(ModuleStatus::Fetching(pending)) => pending,
1489 Some(ModuleStatus::Loaded(module_tree)) => {
1491 return on_complete(cx, module_tree);
1492 },
1493 None => DomRefCell::new(None),
1494 };
1495
1496 let global_scope = DomRoot::from_ref(global);
1497 let module_map_key = module_request.clone();
1498 let handler = ModuleHandler::new_boxed(Box::new(
1499 task!(fetch_completed: |cx, global_scope: DomRoot<GlobalScope>| {
1500 let key = module_map_key;
1501 let module = global_scope.get_module_map_entry(&key);
1502
1503 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1504 on_complete(cx, module_tree);
1505 }
1506 }),
1507 ));
1508
1509 let handler = PromiseNativeHandler::new(cx, global, Some(handler), None);
1510
1511 let mut realm = enter_auto_realm(cx, global);
1512 let cx = &mut realm.current_realm();
1513
1514 run_a_callback::<DomTypeHolder, _>(global, || {
1515 let has_pending_fetch = pending.borrow().is_some();
1516
1517 let promise = Promise::new_in_realm(cx);
1518
1519 if has_pending_fetch {
1522 promise.append_native_handler(cx, &handler);
1523
1524 let continue_loading_handler = PromiseNativeHandler::new(
1527 cx,
1528 global,
1529 Some(Box::new(QueueTaskHandler { promise })),
1530 None,
1531 );
1532
1533 let pending_promise = pending.borrow_mut().take();
1535 if let Some(promise) = pending_promise {
1536 promise.append_native_handler(cx, &continue_loading_handler);
1537 let _ = pending.borrow_mut().insert(promise);
1538 }
1539 return;
1540 }
1541
1542 promise.append_native_handler(cx, &handler);
1543
1544 let prev = pending.borrow_mut().replace(promise);
1545 assert!(prev.is_none());
1546
1547 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1549
1550 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1552 .then(|| fetch_client.policy_container.clone());
1553
1554 let mode = match destination {
1559 Destination::Worker | Destination::SharedWorker if is_top_level => {
1560 RequestMode::SameOrigin
1561 },
1562 _ => RequestMode::CorsMode,
1563 };
1564
1565 let destination = match module_type {
1568 ModuleType::JSON => Destination::Json,
1569 ModuleType::JavaScript | ModuleType::Unknown => destination,
1570 };
1571
1572 let request = RequestBuilder::new(
1576 global.webview_id(),
1577 ensure_blob_referenced_by_url_is_kept_alive(global, url.clone()),
1578 referrer,
1579 )
1580 .destination(destination)
1581 .parser_metadata(options.parser_metadata)
1582 .integrity_metadata(options.integrity_metadata.clone())
1583 .credentials_mode(options.credentials_mode)
1584 .referrer_policy(options.referrer_policy)
1585 .mode(mode)
1586 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1587 .client(fetch_client)
1588 .pipeline_id(Some(global.pipeline_id()));
1589
1590 let context = ModuleContext {
1591 owner: Trusted::new(global),
1592 data: vec![],
1593 metadata: None,
1594 module_request,
1595 options,
1596 status: Ok(()),
1597 introduction_type,
1598 policy_container,
1599 };
1600
1601 let task_source = global.task_manager().networking_task_source().to_sendable();
1602 global.fetch(request, context, task_source);
1603 })
1604}
1605
1606pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1607pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1608
1609#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1611pub(crate) struct ResolvedModule {
1612 base_url: String,
1614 specifier: String,
1616 #[no_trace]
1618 specifier_url: Option<ServoUrl>,
1619}
1620
1621impl ResolvedModule {
1622 pub(crate) fn new(
1623 base_url: String,
1624 specifier: String,
1625 specifier_url: Option<ServoUrl>,
1626 ) -> Self {
1627 Self {
1628 base_url,
1629 specifier,
1630 specifier_url,
1631 }
1632 }
1633}
1634
1635#[derive(Default, JSTraceable, MallocSizeOf)]
1637pub(crate) struct ImportMap {
1638 #[no_trace]
1639 imports: ModuleSpecifierMap,
1640 #[no_trace]
1641 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1642 #[no_trace]
1643 integrity: ModuleIntegrityMap,
1644}
1645
1646impl ImportMap {
1647 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1649 self.integrity.get(url).cloned().unwrap_or_default()
1654 }
1655}
1656
1657pub(crate) fn register_import_map(
1659 cx: &mut JSContext,
1660 global: &GlobalScope,
1661 result: Fallible<ImportMap>,
1662) {
1663 match result {
1664 Ok(new_import_map) => {
1665 merge_existing_and_new_import_maps(cx, global, new_import_map);
1667 },
1668 Err(exception) => {
1669 let mut realm = enter_auto_realm(cx, global);
1670 let cx = &mut realm.current_realm();
1671
1672 throw_dom_exception(cx, global, exception);
1675 report_pending_exception(cx);
1676 },
1677 }
1678}
1679
1680fn merge_existing_and_new_import_maps(
1682 cx: &mut JSContext,
1683 global: &GlobalScope,
1684 new_import_map: ImportMap,
1685) {
1686 let new_import_map_scopes = new_import_map.scopes;
1688
1689 let mut old_import_map = global.import_map_mut();
1691
1692 let mut new_import_map_imports = new_import_map.imports;
1694
1695 let resolved_module_set = global.resolved_module_set();
1696 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1698 for record in resolved_module_set.iter() {
1700 let prefix = scope_prefix.as_str();
1703 if prefix == record.base_url ||
1704 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1705 {
1706 scope_imports.retain(|key, val| {
1708 if *key == record.specifier ||
1713 (key.ends_with('\u{002f}') &&
1714 record.specifier.starts_with(key) &&
1715 (record.specifier_url.is_none() ||
1716 record
1717 .specifier_url
1718 .as_ref()
1719 .is_some_and(|u| u.is_special_scheme())))
1720 {
1721 Console::internal_warn(
1724 cx,
1725 global,
1726 format!("Ignored rule: {key} -> {val:?}."),
1727 );
1728 false
1730 } else {
1731 true
1732 }
1733 })
1734 }
1735 }
1736
1737 if old_import_map.scopes.contains_key(&scope_prefix) {
1739 let merged_module_specifier_map = merge_module_specifier_maps(
1742 cx,
1743 global,
1744 scope_imports,
1745 &old_import_map.scopes[&scope_prefix],
1746 );
1747 old_import_map
1748 .scopes
1749 .insert(scope_prefix, merged_module_specifier_map);
1750 } else {
1751 old_import_map.scopes.insert(scope_prefix, scope_imports);
1753 }
1754 }
1755
1756 for (url, integrity) in &new_import_map.integrity {
1758 if old_import_map.integrity.contains_key(url) {
1760 Console::internal_warn(cx, global, format!("Ignored rule: {url} -> {integrity}."));
1763 continue;
1765 }
1766
1767 old_import_map
1769 .integrity
1770 .insert(url.clone(), integrity.clone());
1771 }
1772
1773 for record in resolved_module_set.iter() {
1775 new_import_map_imports.retain(|specifier, val| {
1777 if record.specifier.starts_with(specifier) {
1782 Console::internal_warn(
1785 cx,
1786 global,
1787 format!("Ignored rule: {specifier} -> {val:?}."),
1788 );
1789 false
1791 } else {
1792 true
1793 }
1794 });
1795 }
1796
1797 let merged_module_specifier_map =
1800 merge_module_specifier_maps(cx, global, new_import_map_imports, &old_import_map.imports);
1801 old_import_map.imports = merged_module_specifier_map;
1802
1803 old_import_map
1806 .scopes
1807 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1808}
1809
1810fn merge_module_specifier_maps(
1812 cx: &mut JSContext,
1813 global: &GlobalScope,
1814 new_map: ModuleSpecifierMap,
1815 old_map: &ModuleSpecifierMap,
1816) -> ModuleSpecifierMap {
1817 let mut merged_map = old_map.clone();
1819
1820 for (specifier, url) in new_map {
1822 if old_map.contains_key(&specifier) {
1824 Console::internal_warn(cx, global, format!("Ignored rule: {specifier} -> {url:?}."));
1827
1828 continue;
1830 }
1831
1832 merged_map.insert(specifier, url);
1834 }
1835
1836 merged_map
1837}
1838
1839pub(crate) fn parse_an_import_map_string(
1841 cx: &mut JSContext,
1842 global: &GlobalScope,
1843 input: &str,
1844 base_url: ServoUrl,
1845) -> Fallible<ImportMap> {
1846 let parsed: JsonValue = serde_json::from_str(input)
1848 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1849 let JsonValue::Object(mut parsed) = parsed else {
1852 return Err(Error::Type(
1853 c"The top-level value needs to be a JSON object.".to_owned(),
1854 ));
1855 };
1856
1857 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1859 if let Some(imports) = parsed.get("imports") {
1861 let JsonValue::Object(imports) = imports else {
1864 return Err(Error::Type(
1865 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1866 ));
1867 };
1868 sorted_and_normalized_imports =
1871 sort_and_normalize_module_specifier_map(cx, global, imports, &base_url);
1872 }
1873
1874 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1876 if let Some(scopes) = parsed.get("scopes") {
1878 let JsonValue::Object(scopes) = scopes else {
1881 return Err(Error::Type(
1882 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1883 ));
1884 };
1885 sorted_and_normalized_scopes = sort_and_normalize_scopes(cx, global, scopes, &base_url)?;
1888 }
1889
1890 let mut normalized_integrity = ModuleIntegrityMap::new();
1892 if let Some(integrity) = parsed.get("integrity") {
1894 let JsonValue::Object(integrity) = integrity else {
1897 return Err(Error::Type(
1898 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1899 ));
1900 };
1901 normalized_integrity = normalize_module_integrity_map(cx, global, integrity, &base_url);
1904 }
1905
1906 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1910 if !parsed.is_empty() {
1911 Console::internal_warn(
1912 cx,
1913 global,
1914 "Invalid top-level key was present in the import map.
1915 Only \"imports\", \"scopes\", and \"integrity\" are allowed."
1916 .to_string(),
1917 );
1918 }
1919
1920 Ok(ImportMap {
1922 imports: sorted_and_normalized_imports,
1923 scopes: sorted_and_normalized_scopes,
1924 integrity: normalized_integrity,
1925 })
1926}
1927
1928fn sort_and_normalize_module_specifier_map(
1930 cx: &mut JSContext,
1931 global: &GlobalScope,
1932 original_map: &JsonMap<String, JsonValue>,
1933 base_url: &ServoUrl,
1934) -> ModuleSpecifierMap {
1935 let mut normalized = ModuleSpecifierMap::new();
1937
1938 for (specifier_key, value) in original_map {
1940 let Some(normalized_specifier_key) =
1943 normalize_specifier_key(cx, global, specifier_key, base_url)
1944 else {
1945 continue;
1947 };
1948
1949 let JsonValue::String(value) = value else {
1951 Console::internal_warn(cx, global, "Addresses need to be strings.".to_string());
1954
1955 normalized.insert(normalized_specifier_key, None);
1957 continue;
1959 };
1960
1961 let Some(address_url) =
1963 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
1964 else {
1965 Console::internal_warn(
1969 cx,
1970 global,
1971 format!("Value failed to resolve to module specifier: {value}"),
1972 );
1973
1974 normalized.insert(normalized_specifier_key, None);
1976 continue;
1978 };
1979
1980 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
1983 Console::internal_warn(
1987 cx,
1988 global,
1989 format!(
1990 "Invalid address for specifier key '{specifier_key}': {address_url}.
1991 Since specifierKey ends with a slash, the address needs to as well."
1992 ),
1993 );
1994
1995 normalized.insert(normalized_specifier_key, None);
1997 continue;
1999 }
2000
2001 normalized.insert(normalized_specifier_key, Some(address_url));
2003 }
2004
2005 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2008 normalized
2009}
2010
2011fn sort_and_normalize_scopes(
2013 cx: &mut JSContext,
2014 global: &GlobalScope,
2015 original_map: &JsonMap<String, JsonValue>,
2016 base_url: &ServoUrl,
2017) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2018 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2020
2021 for (scope_prefix, potential_specifier_map) in original_map {
2023 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2026 return Err(Error::Type(
2027 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
2028 .to_owned(),
2029 ));
2030 };
2031
2032 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2034 Console::internal_warn(
2038 cx,
2039 global,
2040 format!("Scope prefix URL was not parseable: {scope_prefix}"),
2041 );
2042 continue;
2044 };
2045
2046 let normalized_scope_prefix = scope_prefix_url;
2048
2049 let normalized_specifier_map =
2052 sort_and_normalize_module_specifier_map(cx, global, potential_specifier_map, base_url);
2053 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2054 }
2055
2056 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2059 Ok(normalized)
2060}
2061
2062fn normalize_module_integrity_map(
2064 cx: &mut JSContext,
2065 global: &GlobalScope,
2066 original_map: &JsonMap<String, JsonValue>,
2067 base_url: &ServoUrl,
2068) -> ModuleIntegrityMap {
2069 let mut normalized = ModuleIntegrityMap::new();
2071
2072 for (key, value) in original_map {
2074 let Some(resolved_url) =
2077 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
2078 else {
2079 Console::internal_warn(
2083 cx,
2084 global,
2085 format!("Key failed to resolve to module specifier: {key}"),
2086 );
2087 continue;
2089 };
2090
2091 let JsonValue::String(value) = value else {
2093 Console::internal_warn(
2096 cx,
2097 global,
2098 "Integrity metadata values need to be strings.".to_string(),
2099 );
2100 continue;
2102 };
2103
2104 normalized.insert(resolved_url, value.clone());
2106 }
2107
2108 normalized
2110}
2111
2112fn normalize_specifier_key(
2114 cx: &mut JSContext,
2115 global: &GlobalScope,
2116 specifier_key: &str,
2117 base_url: &ServoUrl,
2118) -> Option<String> {
2119 if specifier_key.is_empty() {
2121 Console::internal_warn(
2124 cx,
2125 global,
2126 "Specifier keys may not be the empty string.".to_string(),
2127 );
2128 return None;
2130 }
2131 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2133
2134 if let Some(url) = url {
2136 return Some(url.into_string());
2137 }
2138
2139 Some(specifier_key.to_string())
2141}
2142
2143fn resolve_imports_match(
2148 normalized_specifier: &str,
2149 as_url: Option<&ServoUrl>,
2150 specifier_map: &ModuleSpecifierMap,
2151) -> Fallible<Option<ServoUrl>> {
2152 for (specifier_key, resolution_result) in specifier_map {
2154 if specifier_key == normalized_specifier {
2156 if let Some(resolution_result) = resolution_result {
2157 return Ok(Some(resolution_result.clone()));
2161 } else {
2162 return Err(Error::Type(
2164 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2165 ));
2166 }
2167 }
2168
2169 if specifier_key.ends_with('\u{002f}') &&
2174 normalized_specifier.starts_with(specifier_key) &&
2175 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2176 {
2177 let Some(resolution_result) = resolution_result else {
2180 return Err(Error::Type(
2181 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2182 ));
2183 };
2184
2185 let after_prefix = normalized_specifier
2187 .strip_prefix(specifier_key)
2188 .expect("specifier_key should be the prefix of normalized_specifier");
2189
2190 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2192
2193 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2195
2196 let Ok(url) = url else {
2199 return Err(Error::Type(
2200 c"Resolution of normalizedSpecifier was blocked since
2201 the afterPrefix portion could not be URL-parsed relative to
2202 the resolutionResult mapped to by the specifierKey prefix."
2203 .to_owned(),
2204 ));
2205 };
2206
2207 if !url.as_str().starts_with(resolution_result.as_str()) {
2210 return Err(Error::Type(
2211 c"Resolution of normalizedSpecifier was blocked due to
2212 it backtracking above its prefix specifierKey."
2213 .to_owned(),
2214 ));
2215 }
2216
2217 return Ok(Some(url));
2219 }
2220 }
2221
2222 Ok(None)
2224}