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::MutableHandleValue;
23use js::jsapi::{
24 CallArgs, CompileJsonModule1, CompileModule1, ExceptionStackBehavior,
25 GetFunctionNativeReserved, GetModuleResolveHook, Handle as RawHandle,
26 HandleValue as RawHandleValue, Heap, JS_ClearPendingException, JS_GetFunctionObject,
27 JSAutoRealm, JSContext as RawJSContext, JSObject, JSPROP_ENUMERATE, JSRuntime,
28 ModuleErrorBehaviour, ModuleType, SetFunctionNativeReserved, SetModuleDynamicImportHook,
29 SetModuleMetadataHook, SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks,
30 ThrowOnModuleEvaluationFailure, Value,
31};
32use js::jsval::{JSVal, PrivateValue, UndefinedValue};
33use js::realm::{AutoRealm, CurrentRealm};
34use js::rust::wrappers::{JS_GetPendingException, JS_SetPendingException, ModuleEvaluate};
35use js::rust::wrappers2::{
36 DefineFunctionWithReserved, GetModuleRequestSpecifier, GetModuleRequestType,
37 JS_DefineProperty4, JS_NewStringCopyN, ModuleLink,
38};
39use js::rust::{
40 CompileOptionsWrapper, Handle, HandleObject as RustHandleObject, HandleValue, ToString,
41 transform_str_to_source_text,
42};
43use mime::Mime;
44use net_traits::blob_url_store::UrlWithBlobClaim;
45use net_traits::http_status::HttpStatus;
46use net_traits::mime_classifier::MimeClassifier;
47use net_traits::policy_container::PolicyContainer;
48use net_traits::request::{
49 CredentialsMode, Destination, InsecureRequestsPolicy, ParserMetadata, Referrer, RequestBuilder,
50 RequestClient, RequestId, RequestMode,
51};
52use net_traits::response::HttpsState;
53use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
54use script_bindings::cformat;
55use script_bindings::domstring::BytesView;
56use script_bindings::error::Fallible;
57use script_bindings::settings_stack::run_a_callback;
58use script_bindings::trace::CustomTraceable;
59use serde_json::{Map as JsonMap, Value as JsonValue};
60use servo_base::id::PipelineId;
61use servo_config::pref;
62use servo_url::{ImmutableOrigin, ServoUrl};
63
64use crate::DomTypeHolder;
65use crate::dom::bindings::cell::DomRefCell;
66use crate::dom::bindings::conversions::SafeToJSValConvertible;
67use crate::dom::bindings::error::{
68 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
69};
70use crate::dom::bindings::inheritance::Castable;
71use crate::dom::bindings::refcounted::Trusted;
72use crate::dom::bindings::reflector::{DomGlobal, DomObject};
73use crate::dom::bindings::root::DomRoot;
74use crate::dom::bindings::str::DOMString;
75use crate::dom::bindings::trace::RootedTraceableBox;
76use crate::dom::csp::{GlobalCspReporting, Violation};
77use crate::dom::document::Document;
78use crate::dom::globalscope::GlobalScope;
79use crate::dom::html::htmlscriptelement::{
80 HTMLScriptElement, SCRIPT_JS_MIMES, Script, substitute_with_local_script,
81};
82use crate::dom::htmlscriptelement::finish_fetching_a_script;
83use crate::dom::node::NodeTraits;
84use crate::dom::performance::performanceresourcetiming::InitiatorType;
85use crate::dom::promise::Promise;
86use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
87use crate::dom::types::{Console, DedicatedWorkerGlobalScope, WorkerGlobalScope};
88use crate::dom::window::Window;
89use crate::module_loading::{
90 LoadState, Payload, host_load_imported_module, load_requested_modules,
91};
92use crate::network_listener::{
93 self, FetchResponseListener, NetworkListener, ResourceTimingListener,
94};
95use crate::realms::{InRealm, enter_realm};
96use crate::script_runtime::{CanGc, IntroductionType, JSContext as SafeJSContext};
97use crate::task::NonSendTaskBox;
98
99pub(crate) fn gen_type_error(global: &GlobalScope, error: Error, can_gc: CanGc) -> RethrowError {
100 rooted!(in(*GlobalScope::get_cx()) let mut thrown = UndefinedValue());
101 error.to_jsval(GlobalScope::get_cx(), global, thrown.handle_mut(), can_gc);
102
103 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
104}
105
106#[derive(JSTraceable)]
107pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
108
109impl ModuleObject {
110 pub(crate) fn new(obj: RustHandleObject) -> ModuleObject {
111 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
112 }
113
114 pub(crate) fn handle(&'_ self) -> js::gc::HandleObject<'_> {
115 self.0.handle()
116 }
117}
118
119#[derive(JSTraceable)]
120pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
121
122impl RethrowError {
123 pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
124 Self(RootedTraceableBox::from_box(val))
125 }
126
127 #[expect(unsafe_code)]
128 pub(crate) fn from_pending_exception(cx: SafeJSContext) -> Self {
129 rooted!(in(*cx) let mut exception = UndefinedValue());
130 assert!(unsafe { JS_GetPendingException(*cx, exception.handle_mut()) });
131 unsafe { JS_ClearPendingException(*cx) };
132
133 Self::new(Heap::boxed(exception.get()))
134 }
135
136 pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
137 self.0.handle()
138 }
139}
140
141impl Debug for RethrowError {
142 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
143 "RethrowError(...)".fmt(fmt)
144 }
145}
146
147impl Clone for RethrowError {
148 fn clone(&self) -> Self {
149 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
150 }
151}
152
153pub(crate) struct ModuleScript {
154 pub(crate) base_url: ServoUrl,
155 pub(crate) options: ScriptFetchOptions,
156 pub(crate) owner: Option<ModuleOwner>,
157}
158
159impl ModuleScript {
160 pub(crate) fn new(
161 base_url: ServoUrl,
162 options: ScriptFetchOptions,
163 owner: Option<ModuleOwner>,
164 ) -> Self {
165 ModuleScript {
166 base_url,
167 options,
168 owner,
169 }
170 }
171}
172
173pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
174
175#[derive(Clone, JSTraceable)]
176pub(crate) enum ModuleStatus {
177 Fetching(DomRefCell<Option<Rc<Promise>>>),
178 Loaded(Option<Rc<ModuleTree>>),
179}
180
181#[derive(JSTraceable, MallocSizeOf)]
182pub(crate) struct ModuleTree {
183 #[no_trace]
184 url: ServoUrl,
185 #[ignore_malloc_size_of = "mozjs"]
186 record: OnceCell<ModuleObject>,
187 #[ignore_malloc_size_of = "mozjs"]
188 parse_error: OnceCell<RethrowError>,
189 #[ignore_malloc_size_of = "mozjs"]
190 rethrow_error: DomRefCell<Option<RethrowError>>,
191 #[no_trace]
192 loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
193}
194
195impl ModuleTree {
196 pub(crate) fn get_url(&self) -> ServoUrl {
197 self.url.clone()
198 }
199
200 pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
201 self.record.get()
202 }
203
204 pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
205 self.parse_error.get()
206 }
207
208 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
209 &self.rethrow_error
210 }
211
212 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
213 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
214 }
215
216 pub(crate) fn find_descendant_inside_module_map(
217 &self,
218 global: &GlobalScope,
219 specifier: &String,
220 module_type: ModuleType,
221 ) -> Option<Rc<ModuleTree>> {
222 self.loaded_modules
223 .borrow()
224 .get(specifier)
225 .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
226 .and_then(|status| match status {
227 ModuleStatus::Fetching(_) => None,
228 ModuleStatus::Loaded(module_tree) => module_tree,
229 })
230 }
231
232 pub(crate) fn insert_module_dependency(
233 &self,
234 module: &Rc<ModuleTree>,
235 module_request_specifier: String,
236 ) {
237 let url = module.url.clone();
239 match self
240 .loaded_modules
241 .borrow_mut()
242 .entry(module_request_specifier)
243 {
244 Entry::Occupied(entry) => {
247 assert_eq!(*entry.get(), url);
249 },
250 Entry::Vacant(entry) => {
252 entry.insert(url);
255 },
256 }
257 }
258}
259
260pub(crate) struct ModuleSource {
261 pub source: Rc<DOMString>,
262 pub unminified_dir: Option<String>,
263 pub external: bool,
264 pub url: ServoUrl,
265}
266
267impl crate::unminify::ScriptSource for ModuleSource {
268 fn unminified_dir(&self) -> Option<String> {
269 self.unminified_dir.clone()
270 }
271
272 fn extract_bytes(&self) -> BytesView<'_> {
273 self.source.as_bytes()
274 }
275
276 fn rewrite_source(&mut self, source: Rc<DOMString>) {
277 self.source = source;
278 }
279
280 fn url(&self) -> ServoUrl {
281 self.url.clone()
282 }
283
284 fn is_external(&self) -> bool {
285 self.external
286 }
287}
288
289impl ModuleTree {
290 #[expect(unsafe_code)]
291 #[expect(clippy::too_many_arguments)]
292 fn create_a_javascript_module_script(
296 source: Rc<DOMString>,
297 owner: ModuleOwner,
298 url: &ServoUrl,
299 options: ScriptFetchOptions,
300 external: bool,
301 line_number: u32,
302 introduction_type: Option<&'static CStr>,
303 _can_gc: CanGc,
304 ) -> Self {
305 let cx = GlobalScope::get_cx();
306 let global = owner.global();
307 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
308
309 let module = ModuleTree {
312 url: url.clone(),
313 record: OnceCell::new(),
314 parse_error: OnceCell::new(),
315 rethrow_error: DomRefCell::new(None),
316 loaded_modules: DomRefCell::new(IndexMap::new()),
317 };
318
319 let compile_options = fill_module_compile_options(cx, url, introduction_type, line_number);
320
321 let mut module_source = ModuleSource {
322 source,
323 unminified_dir: global.unminified_js_dir(),
324 external,
325 url: url.clone(),
326 };
327 crate::unminify::unminify_js(&mut module_source);
328
329 unsafe {
330 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
332 module_script.set(CompileModule1(
333 *cx,
334 compile_options.ptr,
335 &mut transform_str_to_source_text(&module_source.source.str()),
336 ));
337
338 if module_script.is_null() {
340 warn!("fail to compile module script of {}", url);
341
342 let _ = module
344 .parse_error
345 .set(RethrowError::from_pending_exception(cx));
346
347 return module;
349 }
350
351 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
355
356 SetModulePrivate(
357 module_script.get(),
358 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
359 );
360
361 let _ = module.record.set(ModuleObject::new(module_script.handle()));
363 }
364
365 module
367 }
368
369 #[expect(unsafe_code)]
370 fn create_a_json_module_script(
374 source: &str,
375 global: &GlobalScope,
376 url: &ServoUrl,
377 introduction_type: Option<&'static CStr>,
378 _can_gc: CanGc,
379 ) -> Self {
380 let cx = GlobalScope::get_cx();
381 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
382
383 let module = ModuleTree {
386 url: url.clone(),
387 record: OnceCell::new(),
388 parse_error: OnceCell::new(),
389 rethrow_error: DomRefCell::new(None),
390 loaded_modules: DomRefCell::new(IndexMap::new()),
391 };
392
393 let compile_options = fill_module_compile_options(cx, url, introduction_type, 1);
398
399 rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
400
401 unsafe {
402 module_script.set(CompileJsonModule1(
404 *cx,
405 compile_options.ptr,
406 &mut transform_str_to_source_text(source),
407 ));
408 }
409
410 if module_script.is_null() {
412 warn!("fail to compile module script of {}", url);
413
414 let _ = module
415 .parse_error
416 .set(RethrowError::from_pending_exception(cx));
417 return module;
418 }
419
420 let _ = module.record.set(ModuleObject::new(module_script.handle()));
422
423 module
425 }
426
427 #[expect(unsafe_code)]
431 pub(crate) fn execute_module(
432 &self,
433 global: &GlobalScope,
434 module_record: js::gc::HandleObject,
435 mut eval_result: MutableHandleValue,
436 _can_gc: CanGc,
437 ) -> Result<(), RethrowError> {
438 let cx = GlobalScope::get_cx();
439 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
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().into(),
453 ModuleErrorBehaviour::ThrowModuleErrorsSync,
454 );
455 if !throw_result {
456 warn!("fail to evaluate module");
457
458 rooted!(in(*cx) let mut exception = UndefinedValue());
459 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
460 JS_ClearPendingException(*cx);
461
462 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
463 exception.get(),
464 ))))
465 } else {
466 debug!("module evaluated successfully");
467 Ok(())
468 }
469 }
470 }
471
472 #[expect(unsafe_code)]
473 pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
474 let module_error = self.rethrow_error.borrow();
475
476 if let Some(exception) = &*module_error {
477 let ar = enter_realm(global);
478 unsafe {
479 JS_SetPendingException(
480 *GlobalScope::get_cx(),
481 exception.handle(),
482 ExceptionStackBehavior::Capture,
483 );
484 }
485 report_pending_exception(GlobalScope::get_cx(), InRealm::Entered(&ar), can_gc);
486 }
487 }
488
489 pub(crate) fn resolve_module_specifier(
491 global: &GlobalScope,
492 script: Option<&ModuleScript>,
493 specifier: DOMString,
494 ) -> Fallible<ServoUrl> {
495 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
497 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
499 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
503 None => (global, &global.api_base_url()),
508 };
509
510 let import_map = if global.is::<Window>() {
514 Some(global.import_map())
515 } else {
516 None
517 };
518 let specifier = &specifier.str();
519
520 let serialized_base_url = base_url.as_str();
522 let as_url = Self::resolve_url_like_module_specifier(specifier, base_url);
524 let normalized_specifier = match &as_url {
527 Some(url) => url.as_str(),
528 None => specifier,
529 };
530
531 let mut result = None;
533 if let Some(map) = import_map {
534 for (prefix, imports) in &map.scopes {
536 let prefix = prefix.as_str();
539 if prefix == serialized_base_url ||
540 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
541 {
542 let scope_imports_match =
545 resolve_imports_match(normalized_specifier, as_url.as_ref(), imports)?;
546
547 if scope_imports_match.is_some() {
549 result = scope_imports_match;
550 break;
551 }
552 }
553 }
554
555 if result.is_none() {
558 result =
559 resolve_imports_match(normalized_specifier, as_url.as_ref(), &map.imports)?;
560 }
561 }
562
563 if result.is_none() {
565 result = as_url.clone();
566 }
567
568 match result {
570 Some(result) => {
571 global.add_module_to_resolved_module_set(
574 serialized_base_url,
575 normalized_specifier,
576 as_url.clone(),
577 );
578 Ok(result)
580 },
581 None => Err(Error::Type(
584 c"Specifier was a bare specifier, but was not remapped to anything by importMap."
585 .to_owned(),
586 )),
587 }
588 }
589
590 fn resolve_url_like_module_specifier(specifier: &str, base_url: &ServoUrl) -> Option<ServoUrl> {
592 if specifier.starts_with('/') || specifier.starts_with("./") || specifier.starts_with("../")
594 {
595 return ServoUrl::parse_with_base(Some(base_url), specifier).ok();
597 }
598 ServoUrl::parse(specifier).ok()
600 }
601}
602
603#[derive(JSTraceable, MallocSizeOf)]
604pub(crate) struct ModuleHandler {
605 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
606 task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
607}
608
609impl ModuleHandler {
610 pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
611 Box::new(Self {
612 task: DomRefCell::new(Some(task)),
613 })
614 }
615}
616
617impl Callback for ModuleHandler {
618 fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
619 let task = self.task.borrow_mut().take().unwrap();
620 task.run_box(cx);
621 }
622}
623
624#[derive(Clone, JSTraceable)]
627pub(crate) enum ModuleOwner {
628 Worker(Trusted<WorkerGlobalScope>),
629 Window(Trusted<HTMLScriptElement>),
630 DynamicModule(Trusted<GlobalScope>),
631}
632
633impl ModuleOwner {
634 pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
635 match &self {
636 ModuleOwner::Worker(scope) => scope.root().global(),
637 ModuleOwner::Window(script) => (*script.root()).global(),
638 ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
639 }
640 }
641
642 fn notify_owner_to_finish(&self, cx: &mut JSContext, module_tree: Option<Rc<ModuleTree>>) {
643 match &self {
644 ModuleOwner::Worker(scope) => {
645 scope
646 .root()
647 .on_complete(cx, module_tree.map(Script::Module));
648 },
649 ModuleOwner::DynamicModule(_) => {},
650 ModuleOwner::Window(script) => {
651 let script = script.root();
652
653 let load = match module_tree {
654 Some(module_tree) => Ok(Script::Module(module_tree)),
655 None => Err(()),
656 };
657
658 finish_fetching_a_script(&script, script.get_script_kind(), load, cx);
659 },
660 }
661 }
662}
663
664#[derive(Clone)]
665pub(crate) struct ModuleFetchClient {
666 pub insecure_requests_policy: InsecureRequestsPolicy,
667 pub has_trustworthy_ancestor_origin: bool,
668 pub policy_container: PolicyContainer,
669 pub client: RequestClient,
670 pub pipeline_id: PipelineId,
671 pub origin: ImmutableOrigin,
672 pub https_state: HttpsState,
673}
674
675impl ModuleFetchClient {
676 pub(crate) fn from_global_scope(global: &GlobalScope) -> Self {
677 Self {
678 insecure_requests_policy: global.insecure_requests_policy(),
679 has_trustworthy_ancestor_origin: global.has_trustworthy_ancestor_or_current_origin(),
680 policy_container: global.policy_container(),
681 client: global.request_client(),
682 pipeline_id: global.pipeline_id(),
683 origin: global.origin().immutable().clone(),
684 https_state: global.get_https_state(),
685 }
686 }
687}
688
689struct ModuleContext {
691 owner: ModuleOwner,
693 data: Vec<u8>,
695 metadata: Option<Metadata>,
697 module_request: ModuleRequest,
699 options: ScriptFetchOptions,
701 status: Result<(), NetworkError>,
703 introduction_type: Option<&'static CStr>,
705 policy_container: Option<PolicyContainer>,
707}
708
709impl FetchResponseListener for ModuleContext {
710 fn process_request_body(&mut self, _: RequestId) {}
712
713 fn process_response(
714 &mut self,
715 _: &mut js::context::JSContext,
716 _: RequestId,
717 metadata: Result<FetchMetadata, NetworkError>,
718 ) {
719 self.metadata = metadata.ok().map(|meta| match meta {
720 FetchMetadata::Unfiltered(m) => m,
721 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
722 });
723
724 let status = self
725 .metadata
726 .as_ref()
727 .map(|m| m.status.clone())
728 .unwrap_or_else(HttpStatus::new_error);
729
730 self.status = {
731 if status.is_error() {
732 Err(NetworkError::ResourceLoadError(
733 "No http status code received".to_owned(),
734 ))
735 } else if status.is_success() {
736 Ok(())
737 } else {
738 Err(NetworkError::ResourceLoadError(format!(
739 "HTTP error code {}",
740 status.code()
741 )))
742 }
743 };
744 }
745
746 fn process_response_chunk(
747 &mut self,
748 _: &mut js::context::JSContext,
749 _: RequestId,
750 mut chunk: Vec<u8>,
751 ) {
752 if self.status.is_ok() {
753 self.data.append(&mut chunk);
754 }
755 }
756
757 fn process_response_eof(
760 mut self,
761 cx: &mut js::context::JSContext,
762 _: RequestId,
763 response: Result<(), NetworkError>,
764 timing: ResourceFetchTiming,
765 ) {
766 let global = self.owner.global();
767 let (_url, module_type) = &self.module_request;
768
769 network_listener::submit_timing(cx, &self, &response, &timing);
770
771 let Some(ModuleStatus::Fetching(pending)) =
772 global.get_module_map_entry(&self.module_request)
773 else {
774 return error!("Processing response for a non pending module request");
775 };
776 let promise = pending
777 .borrow_mut()
778 .take()
779 .expect("Need promise to process response");
780
781 if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
784 error!("Fetching module script failed {:?}", error);
785 global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
786 return promise.resolve_native(&(), CanGc::from_cx(cx));
787 }
788
789 let metadata = self.metadata.take().unwrap();
790
791 if let Some(policy_container) = self.policy_container {
794 let workerscope = global.downcast::<WorkerGlobalScope>().expect(
795 "We only need a policy container when initializing a worker's globalscope.",
796 );
797 workerscope.process_response_for_workerscope(&metadata, &policy_container);
798 }
799
800 let final_url = metadata.final_url;
801
802 let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
804
805 let mut module_script = None;
807
808 let referrer_policy = metadata
810 .headers
811 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
812 .into();
813
814 if referrer_policy != ReferrerPolicy::EmptyString {
816 self.options.referrer_policy = referrer_policy;
817 }
818
819 if let Some(mime) = mime_type {
825 let (mut source_text, _) = UTF_8.decode_with_bom_removal(&self.data);
827
828 if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
831 matches!(module_type, ModuleType::JavaScript)
832 {
833 if let Some(window) = global.downcast::<Window>() {
834 substitute_with_local_script(window, &mut source_text, final_url.clone());
835 }
836
837 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
838 Rc::new(DOMString::from(source_text.clone())),
839 self.owner.clone(),
840 &final_url,
841 self.options,
842 true,
843 1,
844 self.introduction_type,
845 CanGc::from_cx(cx),
846 ));
847 module_script = Some(module_tree);
848 }
849
850 if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
853 let module_tree = Rc::new(ModuleTree::create_a_json_module_script(
854 &source_text,
855 &global,
856 &final_url,
857 self.introduction_type,
858 CanGc::from_cx(cx),
859 ));
860 module_script = Some(module_tree);
861 }
862 }
863 global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
865 promise.resolve_native(&(), CanGc::from_cx(cx));
866 }
867
868 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
869 match &self.owner {
870 ModuleOwner::Worker(scope) => {
871 if let Some(scope) = scope.root().downcast::<DedicatedWorkerGlobalScope>() {
872 scope.report_csp_violations(violations);
873 }
874 },
875 _ => {
876 let global = &self.resource_timing_global();
877 global.report_csp_violations(violations, None, None);
878 },
879 };
880 }
881}
882
883impl ResourceTimingListener for ModuleContext {
884 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
885 let initiator_type = InitiatorType::LocalName("module".to_string());
886 let (url, _) = &self.module_request;
887 (initiator_type, url.clone())
888 }
889
890 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
891 self.owner.global()
892 }
893}
894
895#[expect(unsafe_code)]
896#[expect(non_snake_case)]
897pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
900 unsafe {
901 if GetModuleResolveHook(rt).is_some() {
902 return;
903 }
904
905 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
906 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
907 SetScriptPrivateReferenceHooks(
908 rt,
909 Some(host_add_ref_top_level_script),
910 Some(host_release_top_level_script),
911 );
912 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
913 }
914}
915
916#[expect(unsafe_code)]
917unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
918 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
919 mem::forget(val.clone());
920 mem::forget(val);
921}
922
923#[expect(unsafe_code)]
924unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
925 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
926}
927
928#[expect(unsafe_code)]
929pub(crate) unsafe extern "C" fn host_import_module_dynamically(
932 cx: *mut RawJSContext,
933 reference_private: RawHandleValue,
934 specifier: RawHandle<*mut JSObject>,
935 promise: RawHandle<*mut JSObject>,
936) -> bool {
937 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
939 let cx = &mut cx;
940 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx.into());
941
942 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
943 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
944 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
945
946 let mut realm = CurrentRealm::assert(cx);
947 let payload = Payload::PromiseRecord(promise);
948 host_load_imported_module(
949 &mut realm,
950 None,
951 reference_private,
952 specifier,
953 module_type,
954 None,
955 payload,
956 );
957
958 true
959}
960
961#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
962pub(crate) struct ScriptFetchOptions {
964 pub(crate) integrity_metadata: String,
965 #[no_trace]
966 pub(crate) credentials_mode: CredentialsMode,
967 pub(crate) cryptographic_nonce: String,
968 #[no_trace]
969 pub(crate) parser_metadata: ParserMetadata,
970 #[no_trace]
971 pub(crate) referrer_policy: ReferrerPolicy,
972 pub(crate) render_blocking: bool,
976}
977
978impl ScriptFetchOptions {
979 pub(crate) fn default_classic_script() -> ScriptFetchOptions {
981 Self {
982 cryptographic_nonce: String::new(),
983 integrity_metadata: String::new(),
984 parser_metadata: ParserMetadata::NotParserInserted,
985 credentials_mode: CredentialsMode::CredentialsSameOrigin,
986 referrer_policy: ReferrerPolicy::EmptyString,
987 render_blocking: false,
988 }
989 }
990
991 pub(crate) fn descendant_fetch_options(
993 &self,
994 url: &ServoUrl,
995 global: &GlobalScope,
996 ) -> ScriptFetchOptions {
997 let integrity = global.import_map().resolve_a_module_integrity_metadata(url);
999
1000 Self {
1003 integrity_metadata: integrity,
1005 cryptographic_nonce: self.cryptographic_nonce.clone(),
1006 credentials_mode: self.credentials_mode,
1007 parser_metadata: self.parser_metadata,
1008 referrer_policy: self.referrer_policy,
1009 render_blocking: self.render_blocking,
1010 }
1011 }
1012}
1013
1014#[expect(unsafe_code)]
1015pub(crate) unsafe fn module_script_from_reference_private(
1016 reference_private: &RawHandle<JSVal>,
1017) -> Option<&ModuleScript> {
1018 if reference_private.get().is_undefined() {
1019 return None;
1020 }
1021 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1022}
1023
1024#[expect(unsafe_code)]
1025#[expect(non_snake_case)]
1026unsafe extern "C" fn HostResolveImportedModule(
1029 cx: *mut RawJSContext,
1030 reference_private: RawHandleValue,
1031 specifier: RawHandle<*mut JSObject>,
1032) -> *mut JSObject {
1033 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1035 let mut realm = CurrentRealm::assert(&mut cx);
1036 let global_scope = GlobalScope::from_current_realm(&realm);
1037
1038 let cx = &mut realm;
1039
1040 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1042 let jsstr = unsafe { GetModuleRequestSpecifier(cx, Handle::from_raw(specifier)) };
1043 let module_type = unsafe { GetModuleRequestType(cx, Handle::from_raw(specifier)) };
1044
1045 let specifier = unsafe { jsstr_to_string(cx.raw_cx(), NonNull::new(jsstr).unwrap()) };
1046 let url = ModuleTree::resolve_module_specifier(
1047 &global_scope,
1048 module_data,
1049 DOMString::from(specifier),
1050 );
1051
1052 assert!(url.is_ok());
1054
1055 let parsed_url = url.unwrap();
1056
1057 let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1059
1060 assert!(module.as_ref().is_some_and(
1062 |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1063 ));
1064
1065 let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1066 unreachable!()
1067 };
1068
1069 let fetched_module_object = module_tree.get_record();
1070
1071 assert!(fetched_module_object.is_some());
1073
1074 if let Some(record) = fetched_module_object {
1076 return record.handle().get();
1077 }
1078
1079 unreachable!()
1080}
1081
1082const SLOT_MODULEPRIVATE: usize = 0;
1084
1085#[expect(unsafe_code)]
1086#[expect(non_snake_case)]
1087unsafe extern "C" fn HostPopulateImportMeta(
1090 cx: *mut RawJSContext,
1091 reference_private: RawHandleValue,
1092 meta_object: RawHandle<*mut JSObject>,
1093) -> bool {
1094 let mut cx = unsafe { JSContext::from_ptr(NonNull::new(cx).unwrap()) };
1096 let realm = CurrentRealm::assert(&mut cx);
1097 let global_scope = GlobalScope::from_current_realm(&realm);
1098
1099 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1101 Some(module_data) => module_data.base_url.clone(),
1102 None => global_scope.api_base_url(),
1103 };
1104
1105 unsafe {
1106 let url_string = JS_NewStringCopyN(
1107 &mut cx,
1108 base_url.as_str().as_ptr() as *const _,
1109 base_url.as_str().len(),
1110 );
1111 rooted!(&in(cx) let url_string = url_string);
1112
1113 if !JS_DefineProperty4(
1115 &mut cx,
1116 Handle::from_raw(meta_object),
1117 c"url".as_ptr(),
1118 url_string.handle(),
1119 JSPROP_ENUMERATE.into(),
1120 ) {
1121 return false;
1122 }
1123
1124 let resolve_function = DefineFunctionWithReserved(
1126 &mut cx,
1127 meta_object.get(),
1128 c"resolve".as_ptr(),
1129 Some(import_meta_resolve),
1130 1,
1131 JSPROP_ENUMERATE.into(),
1132 );
1133
1134 rooted!(&in(cx) let obj = JS_GetFunctionObject(resolve_function));
1135 assert!(!obj.is_null());
1136 SetFunctionNativeReserved(
1137 obj.get(),
1138 SLOT_MODULEPRIVATE,
1139 &reference_private.get() as *const _,
1140 );
1141 }
1142
1143 true
1144}
1145
1146#[expect(unsafe_code)]
1147unsafe extern "C" fn import_meta_resolve(cx: *mut RawJSContext, argc: u32, vp: *mut JSVal) -> bool {
1148 let mut cx = unsafe { JSContext::from_ptr(ptr::NonNull::new(cx).unwrap()) };
1150 let mut realm = CurrentRealm::assert(&mut cx);
1151 let global_scope = GlobalScope::from_current_realm(&realm);
1152
1153 let cx = &mut realm;
1154
1155 let args = unsafe { CallArgs::from_vp(vp, argc) };
1156
1157 rooted!(&in(cx) let module_private = unsafe { *GetFunctionNativeReserved(args.callee(), SLOT_MODULEPRIVATE) });
1158 let reference_private = module_private.handle().into();
1159 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1160
1161 let specifier = unsafe {
1165 let value = HandleValue::from_raw(args.get(0));
1166
1167 match NonNull::new(ToString(cx.raw_cx(), value)) {
1168 Some(jsstr) => jsstr_to_string(cx.raw_cx(), jsstr).into(),
1169 None => return false,
1170 }
1171 };
1172
1173 let url = ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier);
1175
1176 match url {
1177 Ok(url) => {
1178 url.as_str().safe_to_jsval(
1180 cx.into(),
1181 unsafe { MutableHandleValue::from_raw(args.rval()) },
1182 CanGc::from_cx(cx),
1183 );
1184 true
1185 },
1186 Err(error) => {
1187 let resolution_error = gen_type_error(&global_scope, error, CanGc::from_cx(cx));
1188
1189 unsafe {
1190 JS_SetPendingException(
1191 cx.raw_cx(),
1192 resolution_error.handle(),
1193 ExceptionStackBehavior::Capture,
1194 );
1195 }
1196 false
1197 },
1198 }
1199}
1200
1201pub(crate) fn fetch_a_module_worker_script_graph(
1204 cx: &mut JSContext,
1205 url: ServoUrl,
1206 fetch_client: ModuleFetchClient,
1207 owner: ModuleOwner,
1208 referrer: Referrer,
1209 credentials_mode: CredentialsMode,
1210) {
1211 let options = ScriptFetchOptions {
1216 integrity_metadata: "".into(),
1217 credentials_mode,
1218 cryptographic_nonce: "".into(),
1219 parser_metadata: ParserMetadata::NotParserInserted,
1220 referrer_policy: ReferrerPolicy::EmptyString,
1221 render_blocking: false,
1222 };
1223
1224 fetch_a_single_module_script(
1227 cx,
1228 url,
1229 fetch_client.clone(),
1230 owner.clone(),
1231 Destination::Worker,
1232 options,
1233 referrer,
1234 None,
1235 true,
1236 Some(IntroductionType::WORKER),
1237 move |cx, module_tree| {
1238 let Some(module) = module_tree else {
1239 return owner.notify_owner_to_finish(cx, None);
1241 };
1242
1243 fetch_the_descendants_and_link_module_script(
1246 cx,
1247 module,
1248 fetch_client,
1249 Destination::Worker,
1250 owner,
1251 );
1252 },
1253 );
1254}
1255
1256pub(crate) fn fetch_an_external_module_script(
1258 cx: &mut JSContext,
1259 url: ServoUrl,
1260 owner: ModuleOwner,
1261 options: ScriptFetchOptions,
1262) {
1263 let referrer = owner.global().get_referrer();
1264 let fetch_client = ModuleFetchClient::from_global_scope(&owner.global());
1265
1266 fetch_a_single_module_script(
1269 cx,
1270 url,
1271 fetch_client.clone(),
1272 owner.clone(),
1273 Destination::Script,
1274 options,
1275 referrer,
1276 None,
1277 true,
1278 Some(IntroductionType::SRC_SCRIPT),
1279 move |cx, module_tree| {
1280 let Some(module) = module_tree else {
1281 return owner.notify_owner_to_finish(cx, None);
1283 };
1284
1285 fetch_the_descendants_and_link_module_script(
1287 cx,
1288 module,
1289 fetch_client,
1290 Destination::Script,
1291 owner,
1292 );
1293 },
1294 );
1295}
1296
1297pub(crate) fn fetch_a_modulepreload_module(
1299 cx: &mut JSContext,
1300 url: ServoUrl,
1301 destination: Destination,
1302 global: &GlobalScope,
1303 options: ScriptFetchOptions,
1304 on_complete: impl FnOnce(&mut JSContext, bool) + 'static,
1305) {
1306 let referrer = global.get_referrer();
1307 let fetch_client = ModuleFetchClient::from_global_scope(global);
1308 let owner = ModuleOwner::DynamicModule(Trusted::new(global));
1309
1310 let module_type = if let Destination::Json = destination {
1313 Some(ModuleType::JSON)
1314 } else {
1315 None
1316 };
1317
1318 fetch_a_single_module_script(
1321 cx,
1322 url,
1323 fetch_client.clone(),
1324 owner.clone(),
1325 destination,
1326 options,
1327 referrer,
1328 module_type,
1329 true,
1330 Some(IntroductionType::SRC_SCRIPT),
1331 move |cx, result| {
1332 on_complete(cx, result.is_none());
1334
1335 assert!(owner.global().is::<Window>());
1337
1338 if pref!(dom_allow_preloading_module_descendants) {
1341 if let Some(module) = result {
1342 fetch_the_descendants_and_link_module_script(
1343 cx,
1344 module,
1345 fetch_client,
1346 destination,
1347 owner,
1348 );
1349 }
1350 }
1351 },
1352 );
1353}
1354
1355pub(crate) fn fetch_inline_module_script(
1357 cx: &mut JSContext,
1358 owner: ModuleOwner,
1359 module_script_text: Rc<DOMString>,
1360 url: ServoUrl,
1361 options: ScriptFetchOptions,
1362 line_number: u32,
1363 introduction_type: Option<&'static CStr>,
1364) {
1365 let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1367 module_script_text,
1368 owner.clone(),
1369 &url,
1370 options,
1371 false,
1372 line_number,
1373 introduction_type,
1374 CanGc::from_cx(cx),
1375 ));
1376 let fetch_client = ModuleFetchClient::from_global_scope(&owner.global());
1377
1378 fetch_the_descendants_and_link_module_script(
1380 cx,
1381 module_tree,
1382 fetch_client,
1383 Destination::Script,
1384 owner,
1385 );
1386}
1387
1388#[expect(unsafe_code)]
1389fn fetch_the_descendants_and_link_module_script(
1391 cx: &mut JSContext,
1392 module_script: Rc<ModuleTree>,
1393 fetch_client: ModuleFetchClient,
1394 destination: Destination,
1395 owner: ModuleOwner,
1396) {
1397 let global = owner.global();
1398
1399 if module_script.get_record().is_none() {
1402 let parse_error = module_script.get_parse_error().cloned();
1403
1404 module_script.set_rethrow_error(parse_error.unwrap());
1406
1407 owner.notify_owner_to_finish(cx, Some(module_script));
1409
1410 return;
1412 }
1413
1414 let state = Rc::new(LoadState {
1417 error_to_rethrow: RefCell::new(None),
1418 destination,
1419 fetch_client,
1420 });
1421
1422 let loading_promise = load_requested_modules(
1426 &mut CurrentRealm::assert(cx),
1427 module_script.clone(),
1428 Some(Rc::clone(&state)),
1429 );
1430
1431 let fulfillment_owner = owner.clone();
1432 let fulfilled_module = module_script.clone();
1433
1434 let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1436 task!(fulfilled_steps: |cx, fulfillment_owner: ModuleOwner| {
1437 let global = fulfillment_owner.global();
1438 let mut realm = AutoRealm::new(
1439 cx,
1440 NonNull::new(global.reflector().get_jsobject().get()).unwrap(),
1441 );
1442 let cx = &mut *realm;
1443
1444 let handle = fulfilled_module.get_record().map(|module| module.handle()).unwrap();
1445
1446 let link = unsafe { ModuleLink(cx, handle) };
1448
1449 if !link {
1451 let exception = RethrowError::from_pending_exception(cx.into());
1452 fulfilled_module.set_rethrow_error(exception);
1453 }
1454
1455 fulfillment_owner.notify_owner_to_finish(cx, Some(fulfilled_module));
1457 }),
1458 ));
1459
1460 let rejection_owner = owner;
1461 let rejected_module = module_script;
1462
1463 let loading_promise_rejection = ModuleHandler::new_boxed(Box::new(
1465 task!(rejected_steps: |cx, rejection_owner: ModuleOwner, state: Rc<LoadState>| {
1466 if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1469 rejected_module.set_rethrow_error(error.clone());
1470 rejection_owner.notify_owner_to_finish(cx, Some(rejected_module));
1471 } else {
1472 rejection_owner.notify_owner_to_finish(cx, None);
1474 }
1475 }),
1476 ));
1477
1478 let handler = PromiseNativeHandler::new(
1479 &global,
1480 Some(loading_promise_fulfillment),
1481 Some(loading_promise_rejection),
1482 CanGc::from_cx(cx),
1483 );
1484
1485 let realm = enter_realm(&*global);
1486 let comp = InRealm::Entered(&realm);
1487 run_a_callback::<DomTypeHolder, _>(&global, || {
1488 loading_promise.append_native_handler(&handler, comp, CanGc::from_cx(cx));
1489 });
1490}
1491
1492#[expect(clippy::too_many_arguments)]
1494pub(crate) fn fetch_a_single_module_script(
1495 cx: &mut JSContext,
1496 url: ServoUrl,
1497 fetch_client: ModuleFetchClient,
1498 owner: ModuleOwner,
1499 destination: Destination,
1500 options: ScriptFetchOptions,
1501 referrer: Referrer,
1502 module_type: Option<ModuleType>,
1503 is_top_level: bool,
1504 introduction_type: Option<&'static CStr>,
1505 on_complete: impl FnOnce(&mut JSContext, Option<Rc<ModuleTree>>) + 'static,
1506) {
1507 let global = owner.global();
1508
1509 let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1513
1514 let module_request = (url.clone(), module_type);
1520 let entry = global.get_module_map_entry(&module_request);
1521
1522 let pending = match entry {
1523 Some(ModuleStatus::Fetching(pending)) => pending,
1524 Some(ModuleStatus::Loaded(module_tree)) => {
1526 return on_complete(cx, module_tree);
1527 },
1528 None => DomRefCell::new(None),
1529 };
1530
1531 let global_scope = DomRoot::from_ref(&*global);
1532 let module_map_key = module_request.clone();
1533 let handler = ModuleHandler::new_boxed(Box::new(
1534 task!(fetch_completed: |cx, global_scope: DomRoot<GlobalScope>| {
1535 let key = module_map_key;
1536 let module = global_scope.get_module_map_entry(&key);
1537
1538 if let Some(ModuleStatus::Loaded(module_tree)) = module {
1539 on_complete(cx, module_tree);
1540 }
1541 }),
1542 ));
1543
1544 let handler = PromiseNativeHandler::new(&global, Some(handler), None, CanGc::note());
1545
1546 let realm = enter_realm(&*global);
1547 let comp = InRealm::Entered(&realm);
1548 run_a_callback::<DomTypeHolder, _>(&global, || {
1549 let has_pending_fetch = pending.borrow().is_some();
1550 let pending_option = pending.borrow_mut().take();
1552 let new_pending =
1553 pending_option.unwrap_or_else(|| Promise::new_in_current_realm(comp, CanGc::note()));
1554 new_pending.append_native_handler(&handler, comp, CanGc::note());
1555 let _ = pending.borrow_mut().insert(new_pending);
1556
1557 if has_pending_fetch {
1560 return;
1561 }
1562
1563 let policy_container = (is_top_level && matches!(owner, ModuleOwner::Worker(_)))
1565 .then(|| fetch_client.policy_container.clone());
1566
1567 global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1569
1570 let document: Option<DomRoot<Document>> = match &owner {
1571 ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1572 ModuleOwner::Window(script) => Some(script.root().owner_document()),
1573 };
1574 let webview_id = document.as_ref().map(|document| document.webview_id());
1575
1576 let mode = match destination {
1581 Destination::Worker | Destination::SharedWorker if is_top_level => {
1582 RequestMode::SameOrigin
1583 },
1584 _ => RequestMode::CorsMode,
1585 };
1586
1587 let destination = match module_type {
1590 ModuleType::JSON => Destination::Json,
1591 ModuleType::JavaScript | ModuleType::Unknown => destination,
1592 };
1593
1594 let request = RequestBuilder::new(
1598 webview_id,
1599 UrlWithBlobClaim::from_url_without_having_claimed_blob(url.clone()),
1600 referrer,
1601 )
1602 .destination(destination)
1603 .parser_metadata(options.parser_metadata)
1604 .integrity_metadata(options.integrity_metadata.clone())
1605 .credentials_mode(options.credentials_mode)
1606 .referrer_policy(options.referrer_policy)
1607 .mode(mode)
1608 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone())
1609 .insecure_requests_policy(fetch_client.insecure_requests_policy)
1610 .has_trustworthy_ancestor_origin(fetch_client.has_trustworthy_ancestor_origin)
1611 .policy_container(fetch_client.policy_container)
1612 .client(fetch_client.client)
1613 .pipeline_id(Some(fetch_client.pipeline_id))
1614 .origin(fetch_client.origin)
1615 .https_state(fetch_client.https_state);
1616
1617 let context = ModuleContext {
1618 owner,
1619 data: vec![],
1620 metadata: None,
1621 module_request,
1622 options,
1623 status: Ok(()),
1624 introduction_type,
1625 policy_container,
1626 };
1627
1628 let network_listener = NetworkListener::new(
1629 context,
1630 global.task_manager().networking_task_source().to_sendable(),
1631 );
1632 match document {
1633 Some(document) => {
1634 document
1635 .loader_mut()
1636 .fetch_async_background(request, network_listener.into_callback());
1637 },
1638 None => global.fetch_with_network_listener(request, network_listener),
1639 };
1640 })
1641}
1642
1643#[expect(unsafe_code)]
1644fn fill_module_compile_options(
1645 cx: SafeJSContext,
1646 url: &ServoUrl,
1647 introduction_type: Option<&'static CStr>,
1648 line_number: u32,
1649) -> CompileOptionsWrapper {
1650 let mut options =
1651 unsafe { CompileOptionsWrapper::new_raw(*cx, cformat!("{url}"), line_number) };
1652 if let Some(introduction_type) = introduction_type {
1653 options.set_introduction_type(introduction_type);
1654 }
1655
1656 options.set_muted_errors(false);
1658
1659 options.set_is_run_once(true);
1661 options.set_no_script_rval(true);
1662
1663 options
1664}
1665
1666pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1667pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1668
1669#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1671pub(crate) struct ResolvedModule {
1672 base_url: String,
1674 specifier: String,
1676 #[no_trace]
1678 specifier_url: Option<ServoUrl>,
1679}
1680
1681impl ResolvedModule {
1682 pub(crate) fn new(
1683 base_url: String,
1684 specifier: String,
1685 specifier_url: Option<ServoUrl>,
1686 ) -> Self {
1687 Self {
1688 base_url,
1689 specifier,
1690 specifier_url,
1691 }
1692 }
1693}
1694
1695#[derive(Default, JSTraceable, MallocSizeOf)]
1697pub(crate) struct ImportMap {
1698 #[no_trace]
1699 imports: ModuleSpecifierMap,
1700 #[no_trace]
1701 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1702 #[no_trace]
1703 integrity: ModuleIntegrityMap,
1704}
1705
1706impl ImportMap {
1707 pub(crate) fn resolve_a_module_integrity_metadata(&self, url: &ServoUrl) -> String {
1709 self.integrity.get(url).cloned().unwrap_or_default()
1714 }
1715}
1716
1717pub(crate) fn register_import_map(
1719 global: &GlobalScope,
1720 result: Fallible<ImportMap>,
1721 can_gc: CanGc,
1722) {
1723 match result {
1724 Ok(new_import_map) => {
1725 merge_existing_and_new_import_maps(global, new_import_map, can_gc);
1727 },
1728 Err(exception) => {
1729 throw_dom_exception(GlobalScope::get_cx(), global, exception, can_gc);
1732 },
1733 }
1734}
1735
1736fn merge_existing_and_new_import_maps(
1738 global: &GlobalScope,
1739 new_import_map: ImportMap,
1740 can_gc: CanGc,
1741) {
1742 let new_import_map_scopes = new_import_map.scopes;
1744
1745 let mut old_import_map = global.import_map_mut();
1747
1748 let mut new_import_map_imports = new_import_map.imports;
1750
1751 let resolved_module_set = global.resolved_module_set();
1752 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1754 for record in resolved_module_set.iter() {
1756 let prefix = scope_prefix.as_str();
1759 if prefix == record.base_url ||
1760 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1761 {
1762 scope_imports.retain(|key, val| {
1764 if *key == record.specifier ||
1769 (key.ends_with('\u{002f}') &&
1770 record.specifier.starts_with(key) &&
1771 (record.specifier_url.is_none() ||
1772 record
1773 .specifier_url
1774 .as_ref()
1775 .is_some_and(|u| u.is_special_scheme())))
1776 {
1777 Console::internal_warn(global, format!("Ignored rule: {key} -> {val:?}."));
1780 false
1782 } else {
1783 true
1784 }
1785 })
1786 }
1787 }
1788
1789 if old_import_map.scopes.contains_key(&scope_prefix) {
1791 let merged_module_specifier_map = merge_module_specifier_maps(
1794 global,
1795 scope_imports,
1796 &old_import_map.scopes[&scope_prefix],
1797 can_gc,
1798 );
1799 old_import_map
1800 .scopes
1801 .insert(scope_prefix, merged_module_specifier_map);
1802 } else {
1803 old_import_map.scopes.insert(scope_prefix, scope_imports);
1805 }
1806 }
1807
1808 for (url, integrity) in &new_import_map.integrity {
1810 if old_import_map.integrity.contains_key(url) {
1812 Console::internal_warn(global, format!("Ignored rule: {url} -> {integrity}."));
1815 continue;
1817 }
1818
1819 old_import_map
1821 .integrity
1822 .insert(url.clone(), integrity.clone());
1823 }
1824
1825 for record in resolved_module_set.iter() {
1827 new_import_map_imports.retain(|specifier, val| {
1829 if record.specifier.starts_with(specifier) {
1834 Console::internal_warn(global, format!("Ignored rule: {specifier} -> {val:?}."));
1837 false
1839 } else {
1840 true
1841 }
1842 });
1843 }
1844
1845 let merged_module_specifier_map = merge_module_specifier_maps(
1848 global,
1849 new_import_map_imports,
1850 &old_import_map.imports,
1851 can_gc,
1852 );
1853 old_import_map.imports = merged_module_specifier_map;
1854
1855 old_import_map
1858 .scopes
1859 .sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1860}
1861
1862fn merge_module_specifier_maps(
1864 global: &GlobalScope,
1865 new_map: ModuleSpecifierMap,
1866 old_map: &ModuleSpecifierMap,
1867 _can_gc: CanGc,
1868) -> ModuleSpecifierMap {
1869 let mut merged_map = old_map.clone();
1871
1872 for (specifier, url) in new_map {
1874 if old_map.contains_key(&specifier) {
1876 Console::internal_warn(global, format!("Ignored rule: {specifier} -> {url:?}."));
1879
1880 continue;
1882 }
1883
1884 merged_map.insert(specifier, url);
1886 }
1887
1888 merged_map
1889}
1890
1891pub(crate) fn parse_an_import_map_string(
1893 module_owner: ModuleOwner,
1894 input: Rc<DOMString>,
1895 base_url: ServoUrl,
1896) -> Fallible<ImportMap> {
1897 let parsed: JsonValue = serde_json::from_str(&input.str())
1899 .map_err(|_| Error::Type(c"The value needs to be a JSON object.".to_owned()))?;
1900 let JsonValue::Object(mut parsed) = parsed else {
1903 return Err(Error::Type(
1904 c"The top-level value needs to be a JSON object.".to_owned(),
1905 ));
1906 };
1907
1908 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1910 if let Some(imports) = parsed.get("imports") {
1912 let JsonValue::Object(imports) = imports else {
1915 return Err(Error::Type(
1916 c"The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1917 ));
1918 };
1919 sorted_and_normalized_imports =
1922 sort_and_normalize_module_specifier_map(&module_owner.global(), imports, &base_url);
1923 }
1924
1925 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1927 if let Some(scopes) = parsed.get("scopes") {
1929 let JsonValue::Object(scopes) = scopes else {
1932 return Err(Error::Type(
1933 c"The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1934 ));
1935 };
1936 sorted_and_normalized_scopes =
1939 sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url)?;
1940 }
1941
1942 let mut normalized_integrity = ModuleIntegrityMap::new();
1944 if let Some(integrity) = parsed.get("integrity") {
1946 let JsonValue::Object(integrity) = integrity else {
1949 return Err(Error::Type(
1950 c"The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1951 ));
1952 };
1953 normalized_integrity =
1956 normalize_module_integrity_map(&module_owner.global(), integrity, &base_url);
1957 }
1958
1959 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1963 if !parsed.is_empty() {
1964 Console::internal_warn(
1965 &module_owner.global(),
1966 "Invalid top-level key was present in the import map.
1967 Only \"imports\", \"scopes\", and \"integrity\" are allowed."
1968 .to_string(),
1969 );
1970 }
1971
1972 Ok(ImportMap {
1974 imports: sorted_and_normalized_imports,
1975 scopes: sorted_and_normalized_scopes,
1976 integrity: normalized_integrity,
1977 })
1978}
1979
1980fn sort_and_normalize_module_specifier_map(
1982 global: &GlobalScope,
1983 original_map: &JsonMap<String, JsonValue>,
1984 base_url: &ServoUrl,
1985) -> ModuleSpecifierMap {
1986 let mut normalized = ModuleSpecifierMap::new();
1988
1989 for (specifier_key, value) in original_map {
1991 let Some(normalized_specifier_key) =
1994 normalize_specifier_key(global, specifier_key, base_url)
1995 else {
1996 continue;
1998 };
1999
2000 let JsonValue::String(value) = value else {
2002 Console::internal_warn(global, "Addresses need to be strings.".to_string());
2005
2006 normalized.insert(normalized_specifier_key, None);
2008 continue;
2010 };
2011
2012 let Some(address_url) =
2014 ModuleTree::resolve_url_like_module_specifier(value.as_str(), base_url)
2015 else {
2016 Console::internal_warn(
2020 global,
2021 format!("Value failed to resolve to module specifier: {value}"),
2022 );
2023
2024 normalized.insert(normalized_specifier_key, None);
2026 continue;
2028 };
2029
2030 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2033 Console::internal_warn(
2037 global,
2038 format!(
2039 "Invalid address for specifier key '{specifier_key}': {address_url}.
2040 Since specifierKey ends with a slash, the address needs to as well."
2041 ),
2042 );
2043
2044 normalized.insert(normalized_specifier_key, None);
2046 continue;
2048 }
2049
2050 normalized.insert(normalized_specifier_key, Some(address_url));
2052 }
2053
2054 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2057 normalized
2058}
2059
2060fn sort_and_normalize_scopes(
2062 global: &GlobalScope,
2063 original_map: &JsonMap<String, JsonValue>,
2064 base_url: &ServoUrl,
2065) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2066 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2068
2069 for (scope_prefix, potential_specifier_map) in original_map {
2071 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2074 return Err(Error::Type(
2075 c"The value of the scope with prefix scopePrefix needs to be a JSON object."
2076 .to_owned(),
2077 ));
2078 };
2079
2080 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2082 Console::internal_warn(
2086 global,
2087 format!("Scope prefix URL was not parseable: {scope_prefix}"),
2088 );
2089 continue;
2091 };
2092
2093 let normalized_scope_prefix = scope_prefix_url;
2095
2096 let normalized_specifier_map =
2099 sort_and_normalize_module_specifier_map(global, potential_specifier_map, base_url);
2100 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2101 }
2102
2103 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2106 Ok(normalized)
2107}
2108
2109fn normalize_module_integrity_map(
2111 global: &GlobalScope,
2112 original_map: &JsonMap<String, JsonValue>,
2113 base_url: &ServoUrl,
2114) -> ModuleIntegrityMap {
2115 let mut normalized = ModuleIntegrityMap::new();
2117
2118 for (key, value) in original_map {
2120 let Some(resolved_url) =
2123 ModuleTree::resolve_url_like_module_specifier(key.as_str(), base_url)
2124 else {
2125 Console::internal_warn(
2129 global,
2130 format!("Key failed to resolve to module specifier: {key}"),
2131 );
2132 continue;
2134 };
2135
2136 let JsonValue::String(value) = value else {
2138 Console::internal_warn(
2141 global,
2142 "Integrity metadata values need to be strings.".to_string(),
2143 );
2144 continue;
2146 };
2147
2148 normalized.insert(resolved_url, value.clone());
2150 }
2151
2152 normalized
2154}
2155
2156fn normalize_specifier_key(
2158 global: &GlobalScope,
2159 specifier_key: &str,
2160 base_url: &ServoUrl,
2161) -> Option<String> {
2162 if specifier_key.is_empty() {
2164 Console::internal_warn(
2167 global,
2168 "Specifier keys may not be the empty string.".to_string(),
2169 );
2170 return None;
2172 }
2173 let url = ModuleTree::resolve_url_like_module_specifier(specifier_key, base_url);
2175
2176 if let Some(url) = url {
2178 return Some(url.into_string());
2179 }
2180
2181 Some(specifier_key.to_string())
2183}
2184
2185fn resolve_imports_match(
2190 normalized_specifier: &str,
2191 as_url: Option<&ServoUrl>,
2192 specifier_map: &ModuleSpecifierMap,
2193) -> Fallible<Option<ServoUrl>> {
2194 for (specifier_key, resolution_result) in specifier_map {
2196 if specifier_key == normalized_specifier {
2198 if let Some(resolution_result) = resolution_result {
2199 return Ok(Some(resolution_result.clone()));
2203 } else {
2204 return Err(Error::Type(
2206 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2207 ));
2208 }
2209 }
2210
2211 if specifier_key.ends_with('\u{002f}') &&
2216 normalized_specifier.starts_with(specifier_key) &&
2217 (as_url.is_none() || as_url.is_some_and(|u| u.is_special_scheme()))
2218 {
2219 let Some(resolution_result) = resolution_result else {
2222 return Err(Error::Type(
2223 c"Resolution of specifierKey was blocked by a null entry.".to_owned(),
2224 ));
2225 };
2226
2227 let after_prefix = normalized_specifier
2229 .strip_prefix(specifier_key)
2230 .expect("specifier_key should be the prefix of normalized_specifier");
2231
2232 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2234
2235 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2237
2238 let Ok(url) = url else {
2241 return Err(Error::Type(
2242 c"Resolution of normalizedSpecifier was blocked since
2243 the afterPrefix portion could not be URL-parsed relative to
2244 the resolutionResult mapped to by the specifierKey prefix."
2245 .to_owned(),
2246 ));
2247 };
2248
2249 if !url.as_str().starts_with(resolution_result.as_str()) {
2252 return Err(Error::Type(
2253 c"Resolution of normalizedSpecifier was blocked due to
2254 it backtracking above its prefix specifierKey."
2255 .to_owned(),
2256 ));
2257 }
2258
2259 return Ok(Some(url));
2261 }
2262 }
2263
2264 Ok(None)
2266}