1use std::cell::{OnceCell, RefCell};
9use std::ffi::CStr;
10use std::fmt::Debug;
11use std::ptr::NonNull;
12use std::rc::Rc;
13use std::{mem, ptr};
14
15use encoding_rs::UTF_8;
16use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
17use hyper_serde::Serde;
18use indexmap::IndexMap;
19use indexmap::map::Entry;
20use js::context::JSContext;
21use js::conversions::jsstr_to_string;
22use js::gc::{HandleObject, MutableHandleValue};
23use js::jsapi::{
24 CallArgs, ExceptionStackBehavior, GetFunctionNativeReserved, GetModuleResolveHook,
25 Handle as RawHandle, HandleValue as RawHandleValue, Heap, JS_GetFunctionObject,
26 JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
27 ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook, SetModuleMetadataHook,
28 SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks, Value,
29};
30use js::jsval::{JSVal, PrivateValue, UndefinedValue};
31use js::realm::{AutoRealm, CurrentRealm};
32use js::rust::wrappers2::{
33 CompileJsonModule1, CompileModule1, DefineFunctionWithReserved, GetModuleRequestSpecifier,
34 GetModuleRequestType, JS_ClearPendingException, JS_DefineProperty4, JS_GetPendingException,
35 JS_NewStringCopyN, JS_SetPendingException, ModuleEvaluate, ModuleLink,
36 ThrowOnModuleEvaluationFailure,
37};
38use js::rust::{
39 CompileOptionsWrapper, Handle, HandleValue, ToString, transform_str_to_source_text,
40};
41use mime::Mime;
42use net_traits::blob_url_store::UrlWithBlobClaim;
43use net_traits::http_status::HttpStatus;
44use net_traits::mime_classifier::MimeClassifier;
45use net_traits::policy_container::PolicyContainer;
46use net_traits::request::{
47 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
48 RequestClient, RequestId, RequestMode,
49};
50use net_traits::response::HttpsState;
51use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
52use script_bindings::cformat;
53use script_bindings::domstring::BytesView;
54use script_bindings::error::Fallible;
55use script_bindings::settings_stack::run_a_callback;
56use script_bindings::trace::CustomTraceable;
57use serde_json::{Map as JsonMap, Value as JsonValue};
58use servo_base::id::PipelineId;
59use servo_config::pref;
60use servo_url::{ImmutableOrigin, ServoUrl};
61
62use crate::DomTypeHolder;
63use crate::dom::bindings::cell::DomRefCell;
64use crate::dom::bindings::conversions::SafeToJSValConvertible;
65use crate::dom::bindings::error::{
66 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
67};
68use crate::dom::bindings::inheritance::Castable;
69use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
70use crate::dom::bindings::reflector::DomObject;
71use crate::dom::bindings::root::DomRoot;
72use crate::dom::bindings::str::DOMString;
73use crate::dom::bindings::trace::RootedTraceableBox;
74use crate::dom::csp::{GlobalCspReporting, Violation};
75use crate::dom::globalscope::GlobalScope;
76use crate::dom::html::htmlscriptelement::{SCRIPT_JS_MIMES, substitute_with_local_script};
77use crate::dom::performance::performanceresourcetiming::InitiatorType;
78use crate::dom::promise::Promise;
79use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
80use crate::dom::types::{Console, DedicatedWorkerGlobalScope, WorkerGlobalScope};
81use crate::dom::window::Window;
82use crate::module_loading::{
83 LoadState, Payload, host_load_imported_module, load_requested_modules,
84};
85use crate::network_listener::{self, FetchResponseListener, ResourceTimingListener};
86use crate::realms::{InRealm, enter_auto_realm};
87use crate::script_runtime::{CanGc, IntroductionType};
88use crate::task::NonSendTaskBox;
89
90pub(crate) fn gen_type_error(
91 cx: &mut JSContext,
92 global: &GlobalScope,
93 error: Error,
94) -> RethrowError {
95 rooted!(&in(cx) let mut thrown = UndefinedValue());
96 error.to_jsval(cx.into(), global, thrown.handle_mut(), CanGc::from_cx(cx));
97
98 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
99}
100
101#[derive(JSTraceable)]
102pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
103
104impl ModuleObject {
105 pub(crate) fn new(obj: HandleObject) -> ModuleObject {
106 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
107 }
108
109 pub(crate) fn handle(&'_ self) -> HandleObject<'_> {
110 self.0.handle()
111 }
112}
113
114#[derive(JSTraceable)]
115pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
116
117impl RethrowError {
118 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
119 Self(RootedTraceableBox::from_box(val))
120 }
121
122 #[expect(unsafe_code)]
123 pub(crate) fn from_pending_exception(cx: &mut JSContext) -> Self {
124 rooted!(&in(cx) let mut exception = UndefinedValue());
125 assert!(unsafe { JS_GetPendingException(cx, exception.handle_mut()) });
126 unsafe { JS_ClearPendingException(cx) };
127
128 Self::new(Heap::boxed(exception.get()))
129 }
130
131 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
132 self.0.handle()
133 }
134}
135
136impl Debug for RethrowError {
137 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
138 "RethrowError(...)".fmt(fmt)
139 }
140}
141
142impl Clone for RethrowError {
143 fn clone(&self) -> Self {
144 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
145 }
146}
147
148pub(crate) struct ModuleScript {
149 pub(crate) base_url: ServoUrl,
150 pub(crate) options: ScriptFetchOptions,
151 pub(crate) owner: Option<Trusted<GlobalScope>>,
152}
153
154impl ModuleScript {
155 pub(crate) fn new(
156 base_url: ServoUrl,
157 options: ScriptFetchOptions,
158 owner: Option<Trusted<GlobalScope>>,
159 ) -> Self {
160 ModuleScript {
161 base_url,
162 options,
163 owner,
164 }
165 }
166}
167
168pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
169
170#[derive(Clone, JSTraceable)]
171pub(crate) enum ModuleStatus {
172 Fetching(DomRefCell<Option<Rc<Promise>>>),
173 Loaded(Option<Rc<ModuleTree>>),
174}
175
176#[derive(JSTraceable, MallocSizeOf)]
177pub(crate) struct ModuleTree {
178 #[no_trace]
179 url: ServoUrl,
180 #[ignore_malloc_size_of = "mozjs"]
181 record: OnceCell<ModuleObject>,
182 #[ignore_malloc_size_of = "mozjs"]
183 parse_error: OnceCell<RethrowError>,
184 #[ignore_malloc_size_of = "mozjs"]
185 rethrow_error: DomRefCell<Option<RethrowError>>,
186 #[no_trace]
187 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
188}
189
190impl ModuleTree {
191 pub(crate) fn get_url(&self) -> ServoUrl {
192 self.url.clone()
193 }
194
195 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
196 self.record.get()
197 }
198
199 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
200 self.parse_error.get()
201 }
202
203 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
204 &self.rethrow_error
205 }
206
207 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
208 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
209 }
210
211 pub(crate) fn find_descendant_inside_module_map(
212 &self,
213 global: &GlobalScope,
214 specifier: &String,
215 module_type: ModuleType,
216 ) -> Option<Rc<ModuleTree>> {
217 self.loaded_modules
218 .borrow()
219 .get(specifier)
220 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
221 .and_then(|status| match status {
222 ModuleStatus::Fetching(_) => None,
223 ModuleStatus::Loaded(module_tree) => module_tree,
224 })
225 }
226
227 pub(crate) fn insert_module_dependency(
228 &self,
229 module: &Rc<ModuleTree>,
230 module_request_specifier: String,
231 ) {
232 let url = module.url.clone();
234 match self
235 .loaded_modules
236 .borrow_mut()
237 .entry(module_request_specifier)
238 {
239 Entry::Occupied(entry) => {
242 assert_eq!(*entry.get(), url);
244 },
245 Entry::Vacant(entry) => {
247 entry.insert(url);
250 },
251 }
252 }
253}
254
255pub(crate) struct ModuleSource {
256 pub source: Rc<DOMString>,
257 pub unminified_dir: Option<String>,
258 pub external: bool,
259 pub url: ServoUrl,
260}
261
262impl crate::unminify::ScriptSource for ModuleSource {
263 fn unminified_dir(&self) -> Option<String> {
264 self.unminified_dir.clone()
265 }
266
267 fn extract_bytes(&self) -> BytesView<'_> {
268 self.source.as_bytes()
269 }
270
271 fn rewrite_source(&mut self, source: Rc<DOMString>) {
272 self.source = source;
273 }
274
275 fn url(&self) -> ServoUrl {
276 self.url.clone()
277 }
278
279 fn is_external(&self) -> bool {
280 self.external
281 }
282}
283
284impl ModuleTree {
285 #[expect(unsafe_code)]
286 #[expect(clippy::too_many_arguments)]
287 fn create_a_javascript_module_script(
289 cx: &mut JSContext,
290 source: Rc<DOMString>,
291 global: &GlobalScope,
292 url: &ServoUrl,
293 options: ScriptFetchOptions,
294 external: bool,
295 line_number: u32,
296 introduction_type: Option<&'static CStr>,
297 ) -> Self {
298 let mut realm = AutoRealm::new(
299 cx,
300 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
301 );
302 let cx = &mut *realm;
303
304 let owner = Trusted::new(global);
305
306 let module = ModuleTree {
309 url: url.clone(),
310 record: OnceCell::new(),
311 parse_error: OnceCell::new(),
312 rethrow_error: DomRefCell::new(None),
313 loaded_modules: DomRefCell::new(IndexMap::new()),
314 };
315
316 let compile_options = fill_module_compile_options(cx, url, introduction_type, line_number);
317
318 let mut module_source = ModuleSource {
319 source,
320 unminified_dir: global.unminified_js_dir(),
321 external,
322 url: url.clone(),
323 };
324 crate::unminify::unminify_js(&mut module_source);
325
326 unsafe {
327 rooted!(&in(cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
329 module_script.set(CompileModule1(
330 cx,
331 compile_options.ptr,
332 &mut transform_str_to_source_text(&module_source.source.str()),
333 ));
334
335 if module_script.is_null() {
337 warn!("fail to compile module script of {}", url);
338
339 let _ = module
341 .parse_error
342 .set(RethrowError::from_pending_exception(cx));
343
344 return module;
346 }
347
348 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
352
353 SetModulePrivate(
354 module_script.get(),
355 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
356 );
357
358 let _ = module.record.set(ModuleObject::new(module_script.handle()));
360 }
361
362 module
364 }
365
366 #[expect(unsafe_code)]
367 fn create_a_json_module_script(
369 cx: &mut JSContext,
370 source: &str,
371 global: &GlobalScope,
372 url: &ServoUrl,
373 introduction_type: Option<&'static CStr>,
374 ) -> Self {
375 let mut realm = AutoRealm::new(
376 cx,
377 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
378 );
379 let cx = &mut *realm;
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)]
428 pub(crate) fn execute_module(
429 &self,
430 cx: &mut JSContext,
431 global: &GlobalScope,
432 module_record: HandleObject,
433 mut eval_result: MutableHandleValue,
434 ) -> Result<(), RethrowError> {
435 let mut realm = AutoRealm::new(
436 cx,
437 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
438 );
439 let cx = &mut *realm;
440
441 unsafe {
442 let ok = ModuleEvaluate(cx, module_record, eval_result.reborrow());
443 assert!(ok, "module evaluation failed");
444
445 rooted!(&in(cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
446 if eval_result.is_object() {
447 evaluation_promise.set(eval_result.to_object());
448 }
449
450 let throw_result = ThrowOnModuleEvaluationFailure(
451 cx,
452 evaluation_promise.handle(),
453 ModuleErrorBehaviour::ThrowModuleErrorsSync,
454 );
455 if !throw_result {
456 warn!("fail to evaluate module");
457
458 Err(RethrowError::from_pending_exception(cx))
459 } else {
460 debug!("module evaluated successfully");
461 Ok(())
462 }
463 }
464 }
465
466 #[expect(unsafe_code)]
467 pub(crate) fn report_error(&self, cx: &mut JSContext, global: &GlobalScope) {
468 let module_error = self.rethrow_error.borrow();
469
470 if let Some(exception) = &*module_error {
471 let mut realm = enter_auto_realm(cx, global);
472 let cx = &mut realm.current_realm();
473
474 let in_realm_proof = cx.into();
475 let in_realm = InRealm::Already(&in_realm_proof);
476
477 unsafe {
478 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
479 }
480 report_pending_exception(cx.into(), in_realm, CanGc::from_cx(cx));
481 }
482 }
483
484 pub(crate) fn resolve_module_specifier(
486 global: &GlobalScope,
487 script: Option<&ModuleScript>,
488 specifier: DOMString,
489 ) -> Fallible<ServoUrl> {
490 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.root()));
492 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
494 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
498 None => (global, &global.api_base_url()),
503 };
504
505 let import_map = if global.is::<Window>() {
509 Some(global.import_map())
510 } else {
511 None
512 };
513 let specifier = &specifier.str();
514
515 let serialized_base_url = base_url.as_str();
517 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
519 let normalized_specifier = match &as_url {
522 Some(url) => url.as_str(),
523 None => specifier,
524 };
525
526 let mut result = None;
528 if let Some(map) = import_map {
529 for (prefix, imports) in &map.scopes {
531 let prefix = prefix.as_str();
534 if prefix == serialized_base_url ||
535 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
536 {
537 let scope_imports_match =
540 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
541
542 if scope_imports_match.is_some() {
544 result = scope_imports_match;
545 break;
546 }
547 }
548 }
549
550 if result.is_none() {
553 result =
554 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
555 }
556 }
557
558 if result.is_none() {
560 result = as_url.clone();
561 }
562
563 match result {
565 Some(result) => {
566 global.add_module_to_resolved_module_set(
569 serialized_base_url,
570 normalized_specifier,
571 as_url.clone(),
572 );
573 Ok(result)
575 },
576 None => Err(Error::Type(
579 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
580 .to_owned(),
581 )),
582 }
583 }
584
585 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
587 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
589 {
590 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
592 }
593 ServoUrl::parse(specifier).ok()
595 }
596}
597
598#[derive(JSTraceable, MallocSizeOf)]
599pub(crate) struct ModuleHandler {
600 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
601 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
602}
603
604impl ModuleHandler {
605 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
606 Box::new(Self {
607 task: DomRefCell::new(Some(task)),
608 })
609 }
610}
611
612impl Callback for ModuleHandler {
613 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
614 let task = self.task.borrow_mut().take().unwrap();
615 task.run_box(cx);
616 }
617}
618
619#[derive(JSTraceable, MallocSizeOf)]
620struct QueueTaskHandler {
621 #[conditional_malloc_size_of]
622 promise: Rc<Promise>,
623}
624
625impl Callback for QueueTaskHandler {
626 fn callback(&self, cx: &mut CurrentRealm, _: HandleValue) {
627 let global = GlobalScope::from_current_realm(cx);
628 let promise = TrustedPromise::new(self.promise.clone());
629
630 global.task_manager().networking_task_source().queue(
631 task!(continue_module_loading: move |cx| {
632 promise.root().resolve_native(&(), CanGc::from_cx(cx));
633 }),
634 );
635 }
636}
637
638#[derive(Clone)]
639pub(crate) struct ModuleFetchClient {
640 pub insecure_requests_policy: InsecureRequestsPolicy,
641 pub has_trustworthy_ancestor_origin: bool,
642 pub policy_container: PolicyContainer,
643 pub client: RequestClient,
644 pub pipeline_id: PipelineId,
645 pub origin: ImmutableOrigin,
646 pub https_state: HttpsState,
647}
648
649impl ModuleFetchClient {
650 pub(crate) fn from_global_scope(global: &GlobalScope) -> Self {
651 Self {
652 insecure_requests_policy: global.insecure_requests_policy(),
653 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_or_current_origin(),
654 policy_container: global.policy_container(),
655 client: global.request_client(),
656 pipeline_id: global.pipeline_id(),
657 origin: global.origin().immutable().clone(),
658 https_state: global.get_https_state(),
659 }
660 }
661}
662
663struct ModuleContext {
665 owner: Trusted<GlobalScope>,
667 data: Vec<u8>,
669 metadata: Option<Metadata>,
671 module_request: ModuleRequest,
673 options: ScriptFetchOptions,
675 status: Result<(), NetworkError>,
677 introduction_type: Option<&'static CStr>,
679 policy_container: Option<PolicyContainer>,
681}
682
683impl FetchResponseListener for ModuleContext {
684 fn process_request_body(&mut self, _: RequestId) {}
686
687 fn process_response(
688 &mut self,
689 _: &mut js::context::JSContext,
690 _: RequestId,
691 metadata: Result<FetchMetadata, NetworkError>,
692 ) {
693 self.metadata = metadata.ok().map(|meta| match meta {
694 FetchMetadata::Unfiltered(m) => m,
695 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
696 });
697
698 let status = self
699 .metadata
700 .as_ref()
701 .map(|m| m.status.clone())
702 .unwrap_or_else(HttpStatus::new_error);
703
704 self.status = {
705 if status.is_error() {
706 Err(NetworkError::ResourceLoadError(
707 "No http status code received".to_owned(),
708 ))
709 } else if status.is_success() {
710 Ok(())
711 } else {
712 Err(NetworkError::ResourceLoadError(format!(
713 "HTTP error code {}",
714 status.code()
715 )))
716 }
717 };
718 }
719
720 fn process_response_chunk(
721 &mut self,
722 _: &mut js::context::JSContext,
723 _: RequestId,
724 mut chunk: Vec<u8>,
725 ) {
726 if self.status.is_ok() {
727 self.data.append(&mut chunk);
728 }
729 }
730
731 fn process_response_eof(
734 mut self,
735 cx: &mut js::context::JSContext,
736 _: RequestId,
737 response: Result<(), NetworkError>,
738 timing: ResourceFetchTiming,
739 ) {
740 let global = self.owner.root();
741 let (_url, module_type) = &self.module_request;
742
743 network_listener::submit_timing(cx, &self, &response, &timing);
744
745 let Some(ModuleStatus::Fetching(pending)) =
746 global.get_module_map_entry(&self.module_request)
747 else {
748 return error!("Processing response for a non pending module request");
749 };
750 let promise = pending
751 .borrow_mut()
752 .take()
753 .expect("Need promise to process response");
754
755 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
758 error!("Fetching module script failed {:?}", error);
759 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
760 return promise.resolve_native(&(), CanGc::from_cx(cx));
761 }
762
763 let metadata = self.metadata.take().unwrap();
764
765 if let Some(policy_container) = self.policy_container {
768 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
769 "We only need a policy container when initializing a worker's globalscope.",
770 );
771 workerscope.process_response_for_workerscope(&metadata, &policy_container);
772 }
773
774 let final_url = metadata.final_url;
775
776 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
778
779 let mut module_script = None;
781
782 let referrer_policy = metadata
784 .headers
785 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
786 .into();
787
788 if referrer_policy != ReferrerPolicy::EmptyString {
790 self.options.referrer_policy = referrer_policy;
791 }
792
793 if let Some(mime) = mime_type {
799 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
801
802 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
805 matches!(module_type, ModuleType::JavaScript)
806 {
807 if let Some(window) = global.downcast::<Window>() {
808 substitute_with_local_script(window, &mut source_text, final_url.clone());
809 }
810
811 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
812 cx,
813 Rc::new(DOMString::from(source_text.clone())),
814 &global,
815 &final_url,
816 self.options,
817 true,
818 1,
819 self.introduction_type,
820 ));
821 module_script = Some(module_tree);
822 }
823
824 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
827 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
828 cx,
829 &source_text,
830 &global,
831 &final_url,
832 self.introduction_type,
833 ));
834 module_script = Some(module_tree);
835 }
836 }
837 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
839 promise.resolve_native(&(), CanGc::from_cx(cx));
840 }
841
842 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
843 let global = self.owner.root();
844 if let Some(scope) = global.downcast::<DedicatedWorkerGlobalScope>() {
845 scope.report_csp_violations(violations);
846 } else {
847 global.report_csp_violations(violations, None, None);
848 }
849 }
850}
851
852impl ResourceTimingListener for ModuleContext {
853 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
854 let initiator_type = InitiatorType::LocalName("module".to_string());
855 let (url, _) = &self.module_request;
856 (initiator_type, url.clone())
857 }
858
859 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
860 self.owner.root()
861 }
862}
863
864#[expect(unsafe_code)]
865#[expect(non_snake_case)]
866pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
869 unsafe {
870 if GetModuleResolveHook(rt).is_some() {
871 return;
872 }
873
874 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
875 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
876 SetScriptPrivateReferenceHooks(
877 rt,
878 Some(host_add_ref_top_level_script),
879 Some(host_release_top_level_script),
880 );
881 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
882 }
883}
884
885#[expect(unsafe_code)]
886unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
887 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
888 mem::forget(val.clone());
889 mem::forget(val);
890}
891
892#[expect(unsafe_code)]
893unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
894 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
895}
896
897#[expect(unsafe_code)]
898pub(crate) unsafe extern "C" fn host_import_module_dynamically(
901 cx: *mut RawJSContext,
902 reference_private: RawHandleValue,
903 specifier: RawHandle<*mut JSObject>,
904 promise: RawHandle<*mut JSObject>,
905) -> bool {
906 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
908 let cx = &mut cx;
909 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx.into());
910
911 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
912 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
913 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
914
915 let mut realm = CurrentRealm::assert(cx);
916 let payload = Payload::PromiseRecord(promise);
917 host_load_imported_module(
918 &mut realm,
919 None,
920 reference_private,
921 specifier,
922 module_type,
923 None,
924 payload,
925 );
926
927 true
928}
929
930#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
931pub(crate) struct ScriptFetchOptions {
933 pub(crate) integrity_metadata: String,
934 #[no_trace]
935 pub(crate) credentials_mode: CredentialsMode,
936 pub(crate) cryptographic_nonce: String,
937 #[no_trace]
938 pub(crate) parser_metadata: ParserMetadata,
939 #[no_trace]
940 pub(crate) referrer_policy: ReferrerPolicy,
941 pub(crate) render_blocking: bool,
945}
946
947impl ScriptFetchOptions {
948 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
950 Self {
951 cryptographic_nonce: String::new(),
952 integrity_metadata: String::new(),
953 parser_metadata: ParserMetadata::NotParserInserted,
954 credentials_mode: CredentialsMode::CredentialsSameOrigin,
955 referrer_policy: ReferrerPolicy::EmptyString,
956 render_blocking: false,
957 }
958 }
959
960 pub(crate) fn descendant_fetch_options(
962 &self,
963 url: &ServoUrl,
964 global: &GlobalScope,
965 ) -> ScriptFetchOptions {
966 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
968
969 Self {
972 integrity_metadata: integrity,
974 cryptographic_nonce: self.cryptographic_nonce.clone(),
975 credentials_mode: self.credentials_mode,
976 parser_metadata: self.parser_metadata,
977 referrer_policy: self.referrer_policy,
978 render_blocking: self.render_blocking,
979 }
980 }
981}
982
983#[expect(unsafe_code)]
984pub(crate) unsafe fn module_script_from_reference_private(
985 reference_private: &RawHandle<JSVal>,
986) -> Option<&ModuleScript> {
987 if reference_private.get().is_undefined() {
988 return None;
989 }
990 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
991}
992
993#[expect(unsafe_code)]
994#[expect(non_snake_case)]
995unsafe extern "C" fn HostResolveImportedModule(
998 cx: *mut RawJSContext,
999 reference_private: RawHandleValue,
1000 specifier: RawHandle<*mut JSObject>,
1001) -> *mut JSObject {
1002 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1004 let mut realm = CurrentRealm::assert(&mut cx);
1005 let global_scope = GlobalScope::from_current_realm(&realm);
1006
1007 let cx = &mut realm;
1008
1009 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1011 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1012 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1013
1014 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
1015 let url = ModuleTree::resolve_module_specifier(
1016 &global_scope,
1017 module_data,
1018 DOMString::from(specifier),
1019 );
1020
1021 assert!(url.is_ok());
1023
1024 let parsed_url = url.unwrap();
1025
1026 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1028
1029 assert!(module.as_ref().is_some_and(
1031 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1032 ));
1033
1034 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1035 unreachable!()
1036 };
1037
1038 let fetched_module_object = module_tree.get_record();
1039
1040 assert!(fetched_module_object.is_some());
1042
1043 if let Some(record) = fetched_module_object {
1045 return record.handle().get();
1046 }
1047
1048 unreachable!()
1049}
1050
1051const SLOT_MODULEPRIVATE: usize = 0;
1053
1054#[expect(unsafe_code)]
1055#[expect(non_snake_case)]
1056unsafe extern "C" fn HostPopulateImportMeta(
1059 cx: *mut RawJSContext,
1060 reference_private: RawHandleValue,
1061 meta_object: RawHandle<*mut JSObject>,
1062) -> bool {
1063 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1065 let realm = CurrentRealm::assert(&mut cx);
1066 let global_scope = GlobalScope::from_current_realm(&realm);
1067
1068 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1070 Some(module_data) => module_data.base_url.clone(),
1071 None => global_scope.api_base_url(),
1072 };
1073
1074 unsafe {
1075 let url_string = JS_NewStringCopyN(
1076 &mut cx,
1077 base_url.as_str().as_ptr() as *const _,
1078 base_url.as_str().len(),
1079 );
1080 rooted!(&in(cx) let url_string = url_string);
1081
1082 if !JS_DefineProperty4(
1084 &mut cx,
1085 Handle::from_raw(meta_object),
1086 c"url".as_ptr(),
1087 url_string.handle(),
1088 JSPROP_ENUMERATE.into(),
1089 ) {
1090 return false;
1091 }
1092
1093 let resolve_function = DefineFunctionWithReserved(
1095 &mut cx,
1096 meta_object.get(),
1097 c"resolve".as_ptr(),
1098 Some(import_meta_resolve),
1099 1,
1100 JSPROP_ENUMERATE.into(),
1101 );
1102
1103 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1104 assert!(!obj.is_null());
1105 SetFunctionNativeReserved(
1106 obj.get(),
1107 SLOT_MODULEPRIVATE,
1108 &reference_private.get() as *const _,
1109 );
1110 }
1111
1112 true
1113}
1114
1115#[expect(unsafe_code)]
1116unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1117 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1119 let mut realm = CurrentRealm::assert(&mut cx);
1120 let global_scope = GlobalScope::from_current_realm(&realm);
1121
1122 let cx = &mut realm;
1123
1124 let args = unsafe { CallArgs::from_vp(vp, argc) };
1125
1126 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1127 let reference_private = module_private.handle().into();
1128 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1129
1130 let specifier = unsafe {
1134 let value = HandleValue::from_raw(args.get(0));
1135
1136 match NonNull::new(ToString(cx.raw_cx(), value)) {
1137 Some(jsstr) => jsstr_to_string(cx.raw_cx(), jsstr).into(),
1138 None => return false,
1139 }
1140 };
1141
1142 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1144
1145 match url {
1146 Ok(url) => {
1147 url.as_str().safe_to_jsval(
1149 cx.into(),
1150 unsafe { MutableHandleValue::from_raw(args.rval()) },
1151 CanGc::from_cx(cx),
1152 );
1153 true
1154 },
1155 Err(error) => {
1156 let resolution_error = gen_type_error(cx, &global_scope, error);
1157
1158 unsafe {
1159 JS_SetPendingException(
1160 cx,
1161 resolution_error.handle(),
1162 ExceptionStackBehavior::Capture,
1163 );
1164 }
1165 false
1166 },
1167 }
1168}
1169
1170#[expect(clippy::too_many_arguments)]
1171pub(crate) fn fetch_a_module_worker_script_graph(
1174 cx: &mut JSContext,
1175 global: &GlobalScope,
1176 url: ServoUrl,
1177 fetch_client: ModuleFetchClient,
1178 destination: Destination,
1179 referrer: Referrer,
1180 credentials_mode: CredentialsMode,
1181 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1182) {
1183 let global_scope = DomRoot::from_ref(global);
1184
1185 let options = ScriptFetchOptions {
1190 integrity_metadata: "".into(),
1191 credentials_mode,
1192 cryptographic_nonce: "".into(),
1193 parser_metadata: ParserMetadata::NotParserInserted,
1194 referrer_policy: ReferrerPolicy::EmptyString,
1195 render_blocking: false,
1196 };
1197
1198 fetch_a_single_module_script(
1201 cx,
1202 url,
1203 fetch_client.clone(),
1204 global,
1205 destination,
1206 options,
1207 referrer,
1208 None,
1209 true,
1210 Some(IntroductionType::WORKER),
1211 move |cx, module_tree| {
1212 let Some(module) = module_tree else {
1213 return on_complete(cx, None);
1215 };
1216
1217 fetch_the_descendants_and_link_module_script(
1220 cx,
1221 &global_scope,
1222 module,
1223 fetch_client,
1224 destination,
1225 on_complete,
1226 );
1227 },
1228 );
1229}
1230
1231pub(crate) fn fetch_an_external_module_script(
1233 cx: &mut JSContext,
1234 url: ServoUrl,
1235 global: &GlobalScope,
1236 options: ScriptFetchOptions,
1237 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1238) {
1239 let referrer = global.get_referrer();
1240 let fetch_client = ModuleFetchClient::from_global_scope(global);
1241 let global_scope = DomRoot::from_ref(global);
1242
1243 fetch_a_single_module_script(
1246 cx,
1247 url,
1248 fetch_client.clone(),
1249 global,
1250 Destination::Script,
1251 options,
1252 referrer,
1253 None,
1254 true,
1255 Some(IntroductionType::SRC_SCRIPT),
1256 move |cx, module_tree| {
1257 let Some(module) = module_tree else {
1258 return on_complete(cx, None);
1260 };
1261
1262 fetch_the_descendants_and_link_module_script(
1264 cx,
1265 &global_scope,
1266 module,
1267 fetch_client,
1268 Destination::Script,
1269 on_complete,
1270 );
1271 },
1272 );
1273}
1274
1275pub(crate) fn fetch_a_modulepreload_module(
1277 cx: &mut JSContext,
1278 url: ServoUrl,
1279 destination: Destination,
1280 global: &GlobalScope,
1281 options: ScriptFetchOptions,
1282 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1283) {
1284 let referrer = global.get_referrer();
1285 let fetch_client = ModuleFetchClient::from_global_scope(global);
1286 let global_scope = DomRoot::from_ref(global);
1287
1288 let module_type = if let Destination::Json = destination {
1291 Some(ModuleType::JSON)
1292 } else {
1293 None
1294 };
1295
1296 fetch_a_single_module_script(
1299 cx,
1300 url,
1301 fetch_client.clone(),
1302 global,
1303 destination,
1304 options,
1305 referrer,
1306 module_type,
1307 true,
1308 Some(IntroductionType::SRC_SCRIPT),
1309 move |cx, result| {
1310 on_complete(cx, result.is_none());
1312
1313 assert!(global_scope.is::<Window>());
1315
1316 if pref!(dom_allow_preloading_module_descendants) {
1319 if let Some(module) = result {
1320 fetch_the_descendants_and_link_module_script(
1321 cx,
1322 &global_scope,
1323 module,
1324 fetch_client,
1325 destination,
1326 |_, _| {},
1327 );
1328 }
1329 }
1330 },
1331 );
1332}
1333
1334#[expect(clippy::too_many_arguments)]
1335pub(crate) fn fetch_inline_module_script(
1337 cx: &mut JSContext,
1338 global: &GlobalScope,
1339 module_script_text: Rc<DOMString>,
1340 url: ServoUrl,
1341 options: ScriptFetchOptions,
1342 line_number: u32,
1343 introduction_type: Option<&'static CStr>,
1344 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1345) {
1346 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1348 cx,
1349 module_script_text,
1350 global,
1351 &url,
1352 options,
1353 false,
1354 line_number,
1355 introduction_type,
1356 ));
1357 let fetch_client = ModuleFetchClient::from_global_scope(global);
1358
1359 fetch_the_descendants_and_link_module_script(
1361 cx,
1362 global,
1363 module_tree,
1364 fetch_client,
1365 Destination::Script,
1366 on_complete,
1367 );
1368}
1369
1370#[expect(unsafe_code)]
1371fn fetch_the_descendants_and_link_module_script(
1373 cx: &mut JSContext,
1374 global: &GlobalScope,
1375 module_script: Rc<ModuleTree>,
1376 fetch_client: ModuleFetchClient,
1377 destination: Destination,
1378 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + Clone + 'static,
1379) {
1380 if module_script.get_record().is_none() {
1383 let parse_error = module_script.get_parse_error().cloned();
1384
1385 module_script.set_rethrow_error(parse_error.unwrap());
1387
1388 on_complete(cx, Some(module_script));
1390
1391 return;
1393 }
1394
1395 let state = Rc::new(LoadState {
1398 error_to_rethrow: RefCell::new(None),
1399 destination,
1400 fetch_client,
1401 });
1402
1403 let mut realm = enter_auto_realm(cx, global);
1406 let cx = &mut realm.current_realm();
1407
1408 let loading_promise = load_requested_modules(cx, module_script.clone(), Some(state.clone()));
1410
1411 let global_scope = DomRoot::from_ref(global);
1412 let fulfilled_module = module_script.clone();
1413 let fulfilled_on_complete = on_complete.clone();
1414
1415 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1417 task!(fulfilled_steps: |cx, global_scope: DomRoot<GlobalScope>| {
1418 let mut realm = AutoRealm::new(
1419 cx,
1420 NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
1421 );
1422 let cx = &mut *realm;
1423
1424 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1425
1426 let link = unsafe { ModuleLink(cx, handle) };
1428
1429 if !link {
1431 let exception = RethrowError::from_pending_exception(cx);
1432 fulfilled_module.set_rethrow_error(exception);
1433 }
1434
1435 fulfilled_on_complete(cx, Some(fulfilled_module));
1437 }),
1438 ));
1439
1440 let loading_promise_rejection =
1442 ModuleHandler::new_boxed(Box::new(task!(rejected_steps: |cx, state: Rc<LoadState>| {
1443 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1446 module_script.set_rethrow_error(error.clone());
1447 on_complete(cx, Some(module_script));
1448 } else {
1449 on_complete(cx, None);
1451 }
1452 })));
1453
1454 let handler = PromiseNativeHandler::new(
1455 global,
1456 Some(loading_promise_fulfillment),
1457 Some(loading_promise_rejection),
1458 CanGc::from_cx(cx),
1459 );
1460
1461 let in_realm_proof = cx.into();
1462 let comp = InRealm::Already(&in_realm_proof);
1463
1464 run_a_callback::<DomTypeHolder, _>(global, || {
1465 loading_promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
1466 });
1467}
1468
1469#[expect(clippy::too_many_arguments)]
1471pub(crate) fn fetch_a_single_module_script(
1472 cx: &mut JSContext,
1473 url: ServoUrl,
1474 fetch_client: ModuleFetchClient,
1475 global: &GlobalScope,
1476 destination: Destination,
1477 options: ScriptFetchOptions,
1478 referrer: Referrer,
1479 module_type: Option<ModuleType>,
1480 is_top_level: bool,
1481 introduction_type: Option<&'static CStr>,
1482 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1483) {
1484 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1488
1489 let module_request = (url.clone(), module_type);
1495 let entry = global.get_module_map_entry(&module_request);
1496
1497 let pending = match entry {
1498 Some(ModuleStatus::Fetching(pending)) => pending,
1499 Some(ModuleStatus::Loaded(module_tree)) => {
1501 return on_complete(cx, module_tree);
1502 },
1503 None => DomRefCell::new(None),
1504 };
1505
1506 let global_scope = DomRoot::from_ref(global);
1507 let module_map_key = module_request.clone();
1508 let handler = ModuleHandler::new_boxed(Box::new(
1509 task!(fetch_completed: |cx, global_scope: DomRoot<GlobalScope>| {
1510 let key = module_map_key;
1511 let module = global_scope.get_module_map_entry(&key);
1512
1513 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1514 on_complete(cx, module_tree);
1515 }
1516 }),
1517 ));
1518
1519 let handler = PromiseNativeHandler::new(global, Some(handler), None, CanGc::from_cx(cx));
1520
1521 let mut realm = enter_auto_realm(cx, global);
1522 let cx = &mut realm.current_realm();
1523
1524 let in_realm_proof = cx.into();
1525 let comp = InRealm::Already(&in_realm_proof);
1526
1527 run_a_callback::<DomTypeHolder, _>(global, || {
1528 let has_pending_fetch = pending.borrow().is_some();
1529
1530 let promise = Promise::new_in_realm(cx);
1531
1532 if has_pending_fetch {
1535 promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
1536
1537 let continue_loading_handler = PromiseNativeHandler::new(
1540 global,
1541 Some(Box::new(QueueTaskHandler { promise })),
1542 None,
1543 CanGc::from_cx(cx),
1544 );
1545
1546 let pending_promise = pending.borrow_mut().take();
1548 if let Some(promise) = pending_promise {
1549 promise.append_native_handler(&continue_loading_handler, comp, CanGc::from_cx(cx));
1550 let _ = pending.borrow_mut().insert(promise);
1551 }
1552 return;
1553 }
1554
1555 promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
1556
1557 let prev = pending.borrow_mut().replace(promise);
1558 assert!(prev.is_none());
1559
1560 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1562
1563 let policy_container = (is_top_level && global.is::<WorkerGlobalScope>())
1565 .then(|| fetch_client.policy_container.clone());
1566
1567 let webview_id = global.webview_id();
1568
1569 let mode = match destination {
1574 Destination::Worker | Destination::SharedWorker if is_top_level => {
1575 RequestMode::SameOrigin
1576 },
1577 _ => RequestMode::CorsMode,
1578 };
1579
1580 let destination = match module_type {
1583 ModuleType::JSON => Destination::Json,
1584 ModuleType::JavaScript | ModuleType::Unknown => destination,
1585 };
1586
1587 let request = RequestBuilder::new(
1591 webview_id,
1592 UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone()),
1593 referrer,
1594 )
1595 .destination(destination)
1596 .parser_metadata(options.parser_metadata)
1597 .integrity_metadata(options.integrity_metadata.clone())
1598 .credentials_mode(options.credentials_mode)
1599 .referrer_policy(options.referrer_policy)
1600 .mode(mode)
1601 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1602 .insecure_requests_policy(fetch_client.insecure_requests_policy)
1603 .has_trustworthy_ancestor_origin(fetch_client.has_trustworthy_ancestor_origin)
1604 .policy_container(fetch_client.policy_container)
1605 .client(fetch_client.client)
1606 .pipeline_id(Some(fetch_client.pipeline_id))
1607 .origin(fetch_client.origin)
1608 .https_state(fetch_client.https_state);
1609
1610 let context = ModuleContext {
1611 owner: Trusted::new(global),
1612 data: vec![],
1613 metadata: None,
1614 module_request,
1615 options,
1616 status: Ok(()),
1617 introduction_type,
1618 policy_container,
1619 };
1620
1621 let task_source = global.task_manager().networking_task_source().to_sendable();
1622 global.fetch(request, context, task_source);
1623 })
1624}
1625
1626fn fill_module_compile_options(
1627 cx: &mut JSContext,
1628 url: &ServoUrl,
1629 introduction_type: Option<&'static CStr>,
1630 line_number: u32,
1631) -> CompileOptionsWrapper {
1632 let mut options = CompileOptionsWrapper::new(cx, cformat!("{url}"), line_number);
1633 if let Some(introduction_type) = introduction_type {
1634 options.set_introduction_type(introduction_type);
1635 }
1636
1637 options.set_muted_errors(false);
1639
1640 options.set_is_run_once(true);
1642 options.set_no_script_rval(true);
1643
1644 options
1645}
1646
1647pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1648pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1649
1650#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1652pub(crate) struct ResolvedModule {
1653 base_url: String,
1655 specifier: String,
1657 #[no_trace]
1659 specifier_url: Option<ServoUrl>,
1660}
1661
1662impl ResolvedModule {
1663 pub(crate) fn new(
1664 base_url: String,
1665 specifier: String,
1666 specifier_url: Option<ServoUrl>,
1667 ) -> Self {
1668 Self {
1669 base_url,
1670 specifier,
1671 specifier_url,
1672 }
1673 }
1674}
1675
1676#[derive(Default, JSTraceable, MallocSizeOf)]
1678pub(crate) struct ImportMap {
1679 #[no_trace]
1680 imports: ModuleSpecifierMap,
1681 #[no_trace]
1682 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1683 #[no_trace]
1684 integrity: ModuleIntegrityMap,
1685}
1686
1687impl ImportMap {
1688 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1690 self.integrity.get(url).cloned().unwrap_or_default()
1695 }
1696}
1697
1698pub(crate) fn register_import_map(
1700 cx: &mut JSContext,
1701 global: &GlobalScope,
1702 result: Fallible<ImportMap>,
1703) {
1704 match result {
1705 Ok(new_import_map) => {
1706 merge_existing_and_new_import_maps(global, new_import_map);
1708 },
1709 Err(exception) => {
1710 throw_dom_exception(cx.into(), global, exception, CanGc::from_cx(cx));
1713 },
1714 }
1715}
1716
1717fn merge_existing_and_new_import_maps(global: &GlobalScope, new_import_map: ImportMap) {
1719 let new_import_map_scopes = new_import_map.scopes;
1721
1722 let mut old_import_map = global.import_map_mut();
1724
1725 let mut new_import_map_imports = new_import_map.imports;
1727
1728 let resolved_module_set = global.resolved_module_set();
1729 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1731 for record in resolved_module_set.iter() {
1733 let prefix = scope_prefix.as_str();
1736 if prefix == record.base_url ||
1737 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1738 {
1739 scope_imports.retain(|key, val| {
1741 if *key == record.specifier ||
1746 (key.ends_with('\u{002f}') &&
1747 record.specifier.starts_with(key) &&
1748 (record.specifier_url.is_none() ||
1749 record
1750 .specifier_url
1751 .as_ref()
1752 .is_some_and(|u| u.is_special_scheme())))
1753 {
1754 Console::internal_warn(global, format!("Ignored rule: {key} -> {val:?}."));
1757 false
1759 } else {
1760 true
1761 }
1762 })
1763 }
1764 }
1765
1766 if old_import_map.scopes.contains_key(&scope_prefix) {
1768 let merged_module_specifier_map = merge_module_specifier_maps(
1771 global,
1772 scope_imports,
1773 &old_import_map.scopes[&scope_prefix],
1774 );
1775 old_import_map
1776 .scopes
1777 .insert(scope_prefix, merged_module_specifier_map);
1778 } else {
1779 old_import_map.scopes.insert(scope_prefix, scope_imports);
1781 }
1782 }
1783
1784 for (url, integrity) in &new_import_map.integrity {
1786 if old_import_map.integrity.contains_key(url) {
1788 Console::internal_warn(global, format!("Ignored rule: {url} -> {integrity}."));
1791 continue;
1793 }
1794
1795 old_import_map
1797 .integrity
1798 .insert(url.clone(), integrity.clone());
1799 }
1800
1801 for record in resolved_module_set.iter() {
1803 new_import_map_imports.retain(|specifier, val| {
1805 if record.specifier.starts_with(specifier) {
1810 Console::internal_warn(global, format!("Ignored rule: {specifier} -> {val:?}."));
1813 false
1815 } else {
1816 true
1817 }
1818 });
1819 }
1820
1821 let merged_module_specifier_map =
1824 merge_module_specifier_maps(global, new_import_map_imports, &old_import_map.imports);
1825 old_import_map.imports = merged_module_specifier_map;
1826
1827 old_import_map
1830 .scopes
1831 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1832}
1833
1834fn merge_module_specifier_maps(
1836 global: &GlobalScope,
1837 new_map: ModuleSpecifierMap,
1838 old_map: &ModuleSpecifierMap,
1839) -> ModuleSpecifierMap {
1840 let mut merged_map = old_map.clone();
1842
1843 for (specifier, url) in new_map {
1845 if old_map.contains_key(&specifier) {
1847 Console::internal_warn(global, format!("Ignored rule: {specifier} -> {url:?}."));
1850
1851 continue;
1853 }
1854
1855 merged_map.insert(specifier, url);
1857 }
1858
1859 merged_map
1860}
1861
1862pub(crate) fn parse_an_import_map_string(
1864 global: &GlobalScope,
1865 input: Rc<DOMString>,
1866 base_url: ServoUrl,
1867) -> Fallible<ImportMap> {
1868 let parsed: JsonValue = serde_json::from_str(&input.str())
1870 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1871 let JsonValue::Object(mut parsed) = parsed else {
1874 return Err(Error::Type(
1875 c"The top-level value needs to be a JSON object.".to_owned(),
1876 ));
1877 };
1878
1879 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1881 if let Some(imports) = parsed.get("imports") {
1883 let JsonValue::Object(imports) = imports else {
1886 return Err(Error::Type(
1887 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1888 ));
1889 };
1890 sorted_and_normalized_imports =
1893 sort_and_normalize_module_specifier_map(global, imports, &base_url);
1894 }
1895
1896 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1898 if let Some(scopes) = parsed.get("scopes") {
1900 let JsonValue::Object(scopes) = scopes else {
1903 return Err(Error::Type(
1904 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1905 ));
1906 };
1907 sorted_and_normalized_scopes = sort_and_normalize_scopes(global, scopes, &base_url)?;
1910 }
1911
1912 let mut normalized_integrity = ModuleIntegrityMap::new();
1914 if let Some(integrity) = parsed.get("integrity") {
1916 let JsonValue::Object(integrity) = integrity else {
1919 return Err(Error::Type(
1920 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1921 ));
1922 };
1923 normalized_integrity = normalize_module_integrity_map(global, integrity, &base_url);
1926 }
1927
1928 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1932 if !parsed.is_empty() {
1933 Console::internal_warn(
1934 global,
1935 "Invalid top-level key was present in the import map.
1936 Only \"imports\", \"scopes\", and \"integrity\" are allowed."
1937 .to_string(),
1938 );
1939 }
1940
1941 Ok(ImportMap {
1943 imports: sorted_and_normalized_imports,
1944 scopes: sorted_and_normalized_scopes,
1945 integrity: normalized_integrity,
1946 })
1947}
1948
1949fn sort_and_normalize_module_specifier_map(
1951 global: &GlobalScope,
1952 original_map: &JsonMap<String, JsonValue>,
1953 base_url: &ServoUrl,
1954) -> ModuleSpecifierMap {
1955 let mut normalized = ModuleSpecifierMap::new();
1957
1958 for (specifier_key, value) in original_map {
1960 let Some(normalized_specifier_key) =
1963 normalize_specifier_key(global, specifier_key, base_url)
1964 else {
1965 continue;
1967 };
1968
1969 let JsonValue::String(value) = value else {
1971 Console::internal_warn(global, "Addresses need to be strings.".to_string());
1974
1975 normalized.insert(normalized_specifier_key, None);
1977 continue;
1979 };
1980
1981 let Some(address_url) =
1983 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
1984 else {
1985 Console::internal_warn(
1989 global,
1990 format!("Value failed to resolve to module specifier: {value}"),
1991 );
1992
1993 normalized.insert(normalized_specifier_key, None);
1995 continue;
1997 };
1998
1999 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2002 Console::internal_warn(
2006 global,
2007 format!(
2008 "Invalid address for specifier key '{specifier_key}': {address_url}.
2009 Since specifierKey ends with a slash, the address needs to as well."
2010 ),
2011 );
2012
2013 normalized.insert(normalized_specifier_key, None);
2015 continue;
2017 }
2018
2019 normalized.insert(normalized_specifier_key, Some(address_url));
2021 }
2022
2023 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2026 normalized
2027}
2028
2029fn sort_and_normalize_scopes(
2031 global: &GlobalScope,
2032 original_map: &JsonMap<String, JsonValue>,
2033 base_url: &ServoUrl,
2034) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2035 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2037
2038 for (scope_prefix, potential_specifier_map) in original_map {
2040 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2043 return Err(Error::Type(
2044 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
2045 .to_owned(),
2046 ));
2047 };
2048
2049 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2051 Console::internal_warn(
2055 global,
2056 format!("Scope prefix URL was not parseable: {scope_prefix}"),
2057 );
2058 continue;
2060 };
2061
2062 let normalized_scope_prefix = scope_prefix_url;
2064
2065 let normalized_specifier_map =
2068 sort_and_normalize_module_specifier_map(global, potential_specifier_map, base_url);
2069 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2070 }
2071
2072 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2075 Ok(normalized)
2076}
2077
2078fn normalize_module_integrity_map(
2080 global: &GlobalScope,
2081 original_map: &JsonMap<String, JsonValue>,
2082 base_url: &ServoUrl,
2083) -> ModuleIntegrityMap {
2084 let mut normalized = ModuleIntegrityMap::new();
2086
2087 for (key, value) in original_map {
2089 let Some(resolved_url) =
2092 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
2093 else {
2094 Console::internal_warn(
2098 global,
2099 format!("Key failed to resolve to module specifier: {key}"),
2100 );
2101 continue;
2103 };
2104
2105 let JsonValue::String(value) = value else {
2107 Console::internal_warn(
2110 global,
2111 "Integrity metadata values need to be strings.".to_string(),
2112 );
2113 continue;
2115 };
2116
2117 normalized.insert(resolved_url, value.clone());
2119 }
2120
2121 normalized
2123}
2124
2125fn normalize_specifier_key(
2127 global: &GlobalScope,
2128 specifier_key: &str,
2129 base_url: &ServoUrl,
2130) -> Option<String> {
2131 if specifier_key.is_empty() {
2133 Console::internal_warn(
2136 global,
2137 "Specifier keys may not be the empty string.".to_string(),
2138 );
2139 return None;
2141 }
2142 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2144
2145 if let Some(url) = url {
2147 return Some(url.into_string());
2148 }
2149
2150 Some(specifier_key.to_string())
2152}
2153
2154fn resolve_imports_match(
2159 normalized_specifier: &str,
2160 as_url: Option<&ServoUrl>,
2161 specifier_map: &ModuleSpecifierMap,
2162) -> Fallible<Option<ServoUrl>> {
2163 for (specifier_key, resolution_result) in specifier_map {
2165 if specifier_key == normalized_specifier {
2167 if let Some(resolution_result) = resolution_result {
2168 return Ok(Some(resolution_result.clone()));
2172 } else {
2173 return Err(Error::Type(
2175 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2176 ));
2177 }
2178 }
2179
2180 if specifier_key.ends_with('\u{002f}') &&
2185 normalized_specifier.starts_with(specifier_key) &&
2186 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2187 {
2188 let Some(resolution_result) = resolution_result else {
2191 return Err(Error::Type(
2192 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2193 ));
2194 };
2195
2196 let after_prefix = normalized_specifier
2198 .strip_prefix(specifier_key)
2199 .expect("specifier_key should be the prefix of normalized_specifier");
2200
2201 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2203
2204 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2206
2207 let Ok(url) = url else {
2210 return Err(Error::Type(
2211 c"Resolution of normalizedSpecifier was blocked since
2212 the afterPrefix portion could not be URL-parsed relative to
2213 the resolutionResult mapped to by the specifierKey prefix."
2214 .to_owned(),
2215 ));
2216 };
2217
2218 if !url.as_str().starts_with(resolution_result.as_str()) {
2221 return Err(Error::Type(
2222 c"Resolution of normalizedSpecifier was blocked due to
2223 it backtracking above its prefix specifierKey."
2224 .to_owned(),
2225 ));
2226 }
2227
2228 return Ok(Some(url));
2230 }
2231 }
2232
2233 Ok(None)
2235}