1use std::collections::{HashMap, HashSet};
9use std::ffi::CStr;
10use std::rc::Rc;
11use std::str::FromStr;
12use std::{mem, ptr};
13
14use encoding_rs::UTF_8;
15use headers::{HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
16use html5ever::local_name;
17use hyper_serde::Serde;
18use indexmap::{IndexMap, IndexSet};
19use js::conversions::jsstr_to_string;
20use js::jsapi::{
21 CompileModule1, ExceptionStackBehavior, FinishDynamicModuleImport, GetModuleRequestSpecifier,
22 GetModuleResolveHook, GetRequestedModuleSpecifier, GetRequestedModulesCount,
23 Handle as RawHandle, HandleObject, HandleValue as RawHandleValue, Heap,
24 JS_ClearPendingException, JS_DefineProperty4, JS_IsExceptionPending, JS_NewStringCopyN,
25 JSAutoRealm, JSContext, JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour,
26 ModuleEvaluate, ModuleLink, MutableHandleValue, SetModuleDynamicImportHook,
27 SetModuleMetadataHook, SetModulePrivate, SetModuleResolveHook, SetScriptPrivateReferenceHooks,
28 ThrowOnModuleEvaluationFailure, Value,
29};
30use js::jsval::{JSVal, PrivateValue, UndefinedValue};
31use js::rust::wrappers::{JS_GetModulePrivate, JS_GetPendingException, JS_SetPendingException};
32use js::rust::{
33 CompileOptionsWrapper, Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle,
34 MutableHandleObject as RustMutableHandleObject, transform_str_to_source_text,
35};
36use mime::Mime;
37use net_traits::http_status::HttpStatus;
38use net_traits::request::{
39 CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId, RequestMode,
40};
41use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
42use script_bindings::domstring::BytesView;
43use script_bindings::error::Fallible;
44use serde_json::{Map as JsonMap, Value as JsonValue};
45use servo_url::ServoUrl;
46use uuid::Uuid;
47
48use crate::document_loader::LoadType;
49use crate::dom::bindings::cell::DomRefCell;
50use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
51use crate::dom::bindings::error::{
52 Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
53};
54use crate::dom::bindings::inheritance::Castable;
55use crate::dom::bindings::refcounted::Trusted;
56use crate::dom::bindings::reflector::{DomGlobal, DomObject};
57use crate::dom::bindings::root::DomRoot;
58use crate::dom::bindings::settings_stack::AutoIncumbentScript;
59use crate::dom::bindings::str::DOMString;
60use crate::dom::bindings::trace::RootedTraceableBox;
61use crate::dom::csp::{GlobalCspReporting, Violation};
62use crate::dom::document::Document;
63use crate::dom::dynamicmoduleowner::{DynamicModuleId, DynamicModuleOwner};
64use crate::dom::element::Element;
65use crate::dom::globalscope::GlobalScope;
66use crate::dom::html::htmlscriptelement::{
67 HTMLScriptElement, SCRIPT_JS_MIMES, ScriptId, ScriptOrigin, ScriptType,
68};
69use crate::dom::node::NodeTraits;
70use crate::dom::performance::performanceresourcetiming::InitiatorType;
71use crate::dom::promise::Promise;
72use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
73use crate::dom::types::Console;
74use crate::dom::window::Window;
75use crate::dom::worker::TrustedWorkerAddress;
76use crate::network_listener::{
77 self, FetchResponseListener, NetworkListener, ResourceTimingListener,
78};
79use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
80use crate::script_runtime::{CanGc, IntroductionType, JSContext as SafeJSContext};
81use crate::task::TaskBox;
82
83fn gen_type_error(global: &GlobalScope, string: String, can_gc: CanGc) -> RethrowError {
84 rooted!(in(*GlobalScope::get_cx()) let mut thrown = UndefinedValue());
85 Error::Type(string).to_jsval(GlobalScope::get_cx(), global, thrown.handle_mut(), can_gc);
86
87 RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
88}
89
90#[derive(JSTraceable)]
91pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
92
93impl ModuleObject {
94 fn new(obj: RustHandleObject) -> ModuleObject {
95 ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
96 }
97
98 pub(crate) fn handle(&self) -> HandleObject {
99 self.0.handle().into()
100 }
101}
102
103#[derive(JSTraceable)]
104pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
105
106impl RethrowError {
107 fn handle(&self) -> Handle<'_, JSVal> {
108 self.0.handle()
109 }
110}
111
112impl Clone for RethrowError {
113 fn clone(&self) -> Self {
114 Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
115 }
116}
117
118pub(crate) struct ModuleScript {
119 base_url: ServoUrl,
120 options: ScriptFetchOptions,
121 owner: Option<ModuleOwner>,
122}
123
124impl ModuleScript {
125 pub(crate) fn new(
126 base_url: ServoUrl,
127 options: ScriptFetchOptions,
128 owner: Option<ModuleOwner>,
129 ) -> Self {
130 ModuleScript {
131 base_url,
132 options,
133 owner,
134 }
135 }
136}
137
138#[derive(Clone, Debug, Eq, Hash, JSTraceable, PartialEq)]
147pub(crate) enum ModuleIdentity {
148 ScriptId(ScriptId),
149 ModuleUrl(#[no_trace] ServoUrl),
150}
151
152impl ModuleIdentity {
153 pub(crate) fn get_module_tree(&self, global: &GlobalScope) -> Rc<ModuleTree> {
154 match self {
155 ModuleIdentity::ModuleUrl(url) => {
156 let module_map = global.get_module_map().borrow();
157 module_map.get(&url.clone()).unwrap().clone()
158 },
159 ModuleIdentity::ScriptId(script_id) => {
160 let inline_module_map = global.get_inline_module_map().borrow();
161 inline_module_map.get(script_id).unwrap().clone()
162 },
163 }
164 }
165}
166
167#[derive(JSTraceable)]
168pub(crate) struct ModuleTree {
169 #[no_trace]
170 url: ServoUrl,
171 text: DomRefCell<Rc<DOMString>>,
172 record: DomRefCell<Option<ModuleObject>>,
173 status: DomRefCell<ModuleStatus>,
174 #[custom_trace]
183 parent_identities: DomRefCell<IndexSet<ModuleIdentity>>,
184 #[no_trace]
185 descendant_urls: DomRefCell<IndexSet<ServoUrl>>,
186 #[no_trace]
188 incomplete_fetch_urls: DomRefCell<IndexSet<ServoUrl>>,
189 #[no_trace]
190 visited_urls: DomRefCell<HashSet<ServoUrl>>,
191 rethrow_error: DomRefCell<Option<RethrowError>>,
192 #[no_trace]
193 network_error: DomRefCell<Option<NetworkError>>,
194 promise: DomRefCell<Option<Rc<Promise>>>,
197 external: bool,
198}
199
200impl ModuleTree {
201 pub(crate) fn new(url: ServoUrl, external: bool, visited_urls: HashSet<ServoUrl>) -> Self {
202 ModuleTree {
203 url,
204 text: DomRefCell::new(Rc::new(DOMString::new())),
205 record: DomRefCell::new(None),
206 status: DomRefCell::new(ModuleStatus::Initial),
207 parent_identities: DomRefCell::new(IndexSet::new()),
208 descendant_urls: DomRefCell::new(IndexSet::new()),
209 incomplete_fetch_urls: DomRefCell::new(IndexSet::new()),
210 visited_urls: DomRefCell::new(visited_urls),
211 rethrow_error: DomRefCell::new(None),
212 network_error: DomRefCell::new(None),
213 promise: DomRefCell::new(None),
214 external,
215 }
216 }
217
218 pub(crate) fn get_status(&self) -> ModuleStatus {
219 *self.status.borrow()
220 }
221
222 pub(crate) fn set_status(&self, status: ModuleStatus) {
223 *self.status.borrow_mut() = status;
224 }
225
226 pub(crate) fn get_record(&self) -> &DomRefCell<Option<ModuleObject>> {
227 &self.record
228 }
229
230 pub(crate) fn set_record(&self, record: ModuleObject) {
231 *self.record.borrow_mut() = Some(record);
232 }
233
234 pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
235 &self.rethrow_error
236 }
237
238 pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
239 *self.rethrow_error.borrow_mut() = Some(rethrow_error);
240 }
241
242 pub(crate) fn get_network_error(&self) -> &DomRefCell<Option<NetworkError>> {
243 &self.network_error
244 }
245
246 pub(crate) fn set_network_error(&self, network_error: NetworkError) {
247 *self.network_error.borrow_mut() = Some(network_error);
248 }
249
250 pub(crate) fn get_text(&self) -> &DomRefCell<Rc<DOMString>> {
251 &self.text
252 }
253
254 pub(crate) fn set_text(&self, module_text: Rc<DOMString>) {
255 *self.text.borrow_mut() = module_text;
256 }
257
258 pub(crate) fn get_incomplete_fetch_urls(&self) -> &DomRefCell<IndexSet<ServoUrl>> {
259 &self.incomplete_fetch_urls
260 }
261
262 pub(crate) fn get_descendant_urls(&self) -> &DomRefCell<IndexSet<ServoUrl>> {
263 &self.descendant_urls
264 }
265
266 pub(crate) fn get_parent_urls(&self) -> IndexSet<ServoUrl> {
267 let parent_identities = self.parent_identities.borrow();
268
269 parent_identities
270 .iter()
271 .filter_map(|parent_identity| match parent_identity {
272 ModuleIdentity::ScriptId(_) => None,
273 ModuleIdentity::ModuleUrl(url) => Some(url.clone()),
274 })
275 .collect()
276 }
277
278 pub(crate) fn insert_parent_identity(&self, parent_identity: ModuleIdentity) {
279 self.parent_identities.borrow_mut().insert(parent_identity);
280 }
281
282 pub(crate) fn insert_incomplete_fetch_url(&self, dependency: &ServoUrl) {
283 self.incomplete_fetch_urls
284 .borrow_mut()
285 .insert(dependency.clone());
286 }
287
288 pub(crate) fn remove_incomplete_fetch_url(&self, dependency: &ServoUrl) {
289 self.incomplete_fetch_urls
290 .borrow_mut()
291 .shift_remove(dependency);
292 }
293
294 fn recursive_check_descendants(
297 module_tree: &ModuleTree,
298 module_map: &HashMap<ServoUrl, Rc<ModuleTree>>,
299 discovered_urls: &mut HashSet<ServoUrl>,
300 ) -> bool {
301 discovered_urls.insert(module_tree.url.clone());
302
303 let descendant_urls = module_tree.descendant_urls.borrow();
304
305 for descendant_url in descendant_urls.iter() {
306 match module_map.get(&descendant_url.clone()) {
307 None => return false,
308 Some(descendant_module) => {
309 if discovered_urls.contains(&descendant_module.url) {
310 continue;
311 }
312
313 let descendant_status = descendant_module.get_status();
314 if descendant_status < ModuleStatus::FetchingDescendants {
315 return false;
316 }
317
318 let all_ready_descendants = ModuleTree::recursive_check_descendants(
319 descendant_module,
320 module_map,
321 discovered_urls,
322 );
323
324 if !all_ready_descendants {
325 return false;
326 }
327 },
328 }
329 }
330
331 true
332 }
333
334 fn has_all_ready_descendants(&self, global: &GlobalScope) -> bool {
335 let module_map = global.get_module_map().borrow();
336 let mut discovered_urls = HashSet::new();
337
338 ModuleTree::recursive_check_descendants(self, &module_map.0, &mut discovered_urls)
339 }
340
341 fn append_handler(
344 &self,
345 owner: ModuleOwner,
346 module_identity: ModuleIdentity,
347 fetch_options: ScriptFetchOptions,
348 can_gc: CanGc,
349 ) {
350 let this = owner.clone();
351 let identity = module_identity.clone();
352 let options = fetch_options.clone();
353
354 let handler = PromiseNativeHandler::new(
355 &owner.global(),
356 Some(ModuleHandler::new_boxed(Box::new(
357 task!(fetched_resolve: move || {
358 this.notify_owner_to_finish(identity, options, CanGc::note());
359 }),
360 ))),
361 None,
362 can_gc,
363 );
364
365 let realm = enter_realm(&*owner.global());
366 let comp = InRealm::Entered(&realm);
367 let _ais = AutoIncumbentScript::new(&owner.global());
368
369 if let Some(promise) = self.promise.borrow().as_ref() {
370 promise.append_native_handler(&handler, comp, can_gc);
371 return;
372 }
373
374 let new_promise = Promise::new_in_current_realm(comp, can_gc);
375 new_promise.append_native_handler(&handler, comp, can_gc);
376 *self.promise.borrow_mut() = Some(new_promise);
377 }
378
379 fn append_dynamic_module_handler(
380 &self,
381 owner: ModuleOwner,
382 module_identity: ModuleIdentity,
383 dynamic_module: RootedTraceableBox<DynamicModule>,
384 can_gc: CanGc,
385 ) {
386 let this = owner.clone();
387 let identity = module_identity.clone();
388
389 let module_id = owner.global().dynamic_module_list().push(dynamic_module);
390
391 let handler = PromiseNativeHandler::new(
392 &owner.global(),
393 Some(ModuleHandler::new_boxed(Box::new(
394 task!(fetched_resolve: move || {
395 this.finish_dynamic_module(identity, module_id, CanGc::note());
396 }),
397 ))),
398 None,
399 can_gc,
400 );
401
402 let realm = enter_realm(&*owner.global());
403 let comp = InRealm::Entered(&realm);
404 let _ais = AutoIncumbentScript::new(&owner.global());
405
406 if let Some(promise) = self.promise.borrow().as_ref() {
407 promise.append_native_handler(&handler, comp, can_gc);
408 return;
409 }
410
411 let new_promise = Promise::new_in_current_realm(comp, can_gc);
412 new_promise.append_native_handler(&handler, comp, can_gc);
413 *self.promise.borrow_mut() = Some(new_promise);
414 }
415}
416
417#[derive(Clone, Copy, Debug, JSTraceable, PartialEq, PartialOrd)]
418pub(crate) enum ModuleStatus {
419 Initial,
420 Fetching,
421 FetchingDescendants,
422 Finished,
423}
424
425struct ModuleSource {
426 source: Rc<DOMString>,
427 unminified_dir: Option<String>,
428 external: bool,
429 url: ServoUrl,
430}
431
432impl crate::unminify::ScriptSource for ModuleSource {
433 fn unminified_dir(&self) -> Option<String> {
434 self.unminified_dir.clone()
435 }
436
437 fn extract_bytes(&self) -> BytesView<'_> {
438 self.source.as_bytes()
439 }
440
441 fn rewrite_source(&mut self, source: Rc<DOMString>) {
442 self.source = source;
443 }
444
445 fn url(&self) -> ServoUrl {
446 self.url.clone()
447 }
448
449 fn is_external(&self) -> bool {
450 self.external
451 }
452}
453
454impl ModuleTree {
455 #[expect(unsafe_code)]
456 #[allow(clippy::too_many_arguments)]
457 fn compile_module_script(
462 &self,
463 global: &GlobalScope,
464 owner: ModuleOwner,
465 module_script_text: Rc<DOMString>,
466 url: &ServoUrl,
467 options: ScriptFetchOptions,
468 mut module_script: RustMutableHandleObject,
469 inline: bool,
470 line_number: u64,
471 introduction_type: Option<&'static CStr>,
472 can_gc: CanGc,
473 ) -> Result<(), RethrowError> {
474 let cx = GlobalScope::get_cx();
475 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
476
477 let mut compile_options =
478 unsafe { CompileOptionsWrapper::new_raw(*cx, url.as_str(), line_number as u32) };
479 if let Some(introduction_type) = introduction_type {
480 compile_options.set_introduction_type(introduction_type);
481 }
482 let mut module_source = ModuleSource {
483 source: module_script_text,
484 unminified_dir: global.unminified_js_dir(),
485 external: !inline,
486 url: url.clone(),
487 };
488 crate::unminify::unminify_js(&mut module_source);
489
490 unsafe {
491 module_script.set(CompileModule1(
492 *cx,
493 compile_options.ptr,
494 &mut transform_str_to_source_text(&module_source.source.str()),
495 ));
496
497 if module_script.is_null() {
498 warn!("fail to compile module script of {}", url);
499
500 rooted!(in(*cx) let mut exception = UndefinedValue());
501 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
502 JS_ClearPendingException(*cx);
503
504 return Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
505 exception.get(),
506 ))));
507 }
508
509 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
510
511 SetModulePrivate(
512 module_script.get(),
513 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
514 );
515
516 debug!("module script of {} compile done", url);
517
518 self.resolve_requested_module_specifiers(
519 global,
520 module_script.handle().into_handle(),
521 can_gc,
522 )
523 .map(|_| ())
524 }
525 }
526
527 #[expect(unsafe_code)]
528 pub(crate) fn instantiate_module_tree(
531 &self,
532 global: &GlobalScope,
533 module_record: HandleObject,
534 ) -> Result<(), RethrowError> {
535 let cx = GlobalScope::get_cx();
536 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
537
538 unsafe {
539 if !ModuleLink(*cx, module_record) {
540 warn!("fail to link & instantiate module");
541
542 rooted!(in(*cx) let mut exception = UndefinedValue());
543 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
544 JS_ClearPendingException(*cx);
545
546 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
547 exception.get(),
548 ))))
549 } else {
550 debug!("module instantiated successfully");
551
552 Ok(())
553 }
554 }
555 }
556
557 #[expect(unsafe_code)]
561 pub(crate) fn execute_module(
562 &self,
563 global: &GlobalScope,
564 module_record: HandleObject,
565 eval_result: MutableHandleValue,
566 _can_gc: CanGc,
567 ) -> Result<(), RethrowError> {
568 let cx = GlobalScope::get_cx();
569 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
570
571 unsafe {
572 let ok = ModuleEvaluate(*cx, module_record, eval_result);
573 assert!(ok, "module evaluation failed");
574
575 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
576 if eval_result.is_object() {
577 evaluation_promise.set(eval_result.to_object());
578 }
579
580 let throw_result = ThrowOnModuleEvaluationFailure(
581 *cx,
582 evaluation_promise.handle().into(),
583 ModuleErrorBehaviour::ThrowModuleErrorsSync,
584 );
585 if !throw_result {
586 warn!("fail to evaluate module");
587
588 rooted!(in(*cx) let mut exception = UndefinedValue());
589 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
590 JS_ClearPendingException(*cx);
591
592 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
593 exception.get(),
594 ))))
595 } else {
596 debug!("module evaluated successfully");
597 Ok(())
598 }
599 }
600 }
601
602 #[expect(unsafe_code)]
603 pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
604 let module_error = self.rethrow_error.borrow();
605
606 if let Some(exception) = &*module_error {
607 let ar = enter_realm(global);
608 unsafe {
609 JS_SetPendingException(
610 *GlobalScope::get_cx(),
611 exception.handle(),
612 ExceptionStackBehavior::Capture,
613 );
614 }
615 report_pending_exception(GlobalScope::get_cx(), true, InRealm::Entered(&ar), can_gc);
616 }
617 }
618
619 #[expect(unsafe_code)]
620 fn resolve_requested_module_specifiers(
621 &self,
622 global: &GlobalScope,
623 module_object: HandleObject,
624 can_gc: CanGc,
625 ) -> Result<IndexSet<ServoUrl>, RethrowError> {
626 let cx = GlobalScope::get_cx();
627 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
628
629 let mut specifier_urls = IndexSet::new();
630
631 unsafe {
632 let length = GetRequestedModulesCount(*cx, module_object);
633
634 for index in 0..length {
635 let jsstr =
636 std::ptr::NonNull::new(GetRequestedModuleSpecifier(*cx, module_object, index))
637 .unwrap();
638 let specifier = DOMString::from_string(jsstr_to_string(*cx, jsstr));
639
640 rooted!(in(*cx) let mut private = UndefinedValue());
641 JS_GetModulePrivate(module_object.get(), private.handle_mut());
642 let private = private.handle().into_handle();
643 let script = module_script_from_reference_private(&private);
644 let url = ModuleTree::resolve_module_specifier(global, script, specifier, can_gc);
645
646 if url.is_err() {
647 let specifier_error =
648 gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
649
650 return Err(specifier_error);
651 }
652
653 specifier_urls.insert(url.unwrap());
654 }
655 }
656
657 Ok(specifier_urls)
658 }
659
660 fn resolve_module_specifier(
662 global: &GlobalScope,
663 script: Option<&ModuleScript>,
664 specifier: DOMString,
665 can_gc: CanGc,
666 ) -> Fallible<ServoUrl> {
667 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
669 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
671 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
675 None => (global, &global.api_base_url()),
680 };
681
682 let import_map = if global.is::<Window>() {
686 Some(global.import_map())
687 } else {
688 None
689 };
690
691 let serialized_base_url = base_url.as_str();
693 let as_url = Self::resolve_url_like_module_specifier(&specifier, base_url);
695 let specifier = specifier.str();
698 let normalized_specifier = match &as_url {
699 Some(url) => url.as_str(),
700 None => &specifier,
701 };
702
703 let mut result = None;
705 if let Some(map) = import_map {
706 for (prefix, imports) in &map.scopes {
708 let prefix = prefix.as_str();
711 if prefix == serialized_base_url ||
712 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
713 {
714 result = resolve_imports_match(
719 normalized_specifier,
720 as_url.as_ref(),
721 imports,
722 can_gc,
723 )?;
724 break;
725 }
726 }
727
728 if result.is_none() {
731 result = resolve_imports_match(
732 normalized_specifier,
733 as_url.as_ref(),
734 &map.imports,
735 can_gc,
736 )?;
737 }
738 }
739
740 if result.is_none() {
742 result = as_url.clone();
743 }
744
745 match result {
747 Some(result) => {
748 global.add_module_to_resolved_module_set(
751 serialized_base_url,
752 normalized_specifier,
753 as_url.clone(),
754 );
755 Ok(result)
757 },
758 None => Err(Error::Type(
761 "Specifier was a bare specifier, but was not remapped to anything by importMap."
762 .to_owned(),
763 )),
764 }
765 }
766
767 fn resolve_url_like_module_specifier(
769 specifier: &DOMString,
770 base_url: &ServoUrl,
771 ) -> Option<ServoUrl> {
772 if specifier.starts_with('/') ||
774 specifier.starts_with_str("./") ||
775 specifier.starts_with_str("../")
776 {
777 return ServoUrl::parse_with_base(Some(base_url), &specifier.str()).ok();
779 }
780 ServoUrl::parse(&specifier.str()).ok()
782 }
783
784 fn find_first_parse_error(
786 &self,
787 global: &GlobalScope,
788 discovered_urls: &mut HashSet<ServoUrl>,
789 ) -> (Option<NetworkError>, Option<RethrowError>) {
790 discovered_urls.insert(self.url.clone());
792
793 let record = self.get_record().borrow();
795 if record.is_none() {
796 return (
797 self.network_error.borrow().clone(),
798 self.rethrow_error.borrow().clone(),
799 );
800 }
801
802 let module_map = global.get_module_map().borrow();
803 let mut parse_error: Option<RethrowError> = None;
804
805 let descendant_urls = self.descendant_urls.borrow();
807 for descendant_module in descendant_urls
808 .iter()
809 .filter_map(|url| module_map.get(&url.clone()))
811 {
812 if discovered_urls.contains(&descendant_module.url) {
814 continue;
815 }
816
817 let (child_network_error, child_parse_error) =
819 descendant_module.find_first_parse_error(global, discovered_urls);
820
821 if child_network_error.is_some() {
824 return (child_network_error, None);
825 }
826
827 if child_parse_error.is_some() && parse_error.is_none() {
833 parse_error = child_parse_error;
834 }
835 }
836
837 (None, parse_error)
839 }
840
841 fn fetch_module_descendants(
844 &self,
845 owner: &ModuleOwner,
846 destination: Destination,
847 options: &ScriptFetchOptions,
848 parent_identity: ModuleIdentity,
849 can_gc: CanGc,
850 ) {
851 debug!("Start to load dependencies of {}", self.url);
852
853 let global = owner.global();
854
855 self.set_status(ModuleStatus::FetchingDescendants);
856
857 let specifier_urls = {
858 let raw_record = self.record.borrow();
859 match raw_record.as_ref() {
860 None => {
862 self.set_status(ModuleStatus::Finished);
863 debug!(
864 "Module {} doesn't have module record but tried to load descendants.",
865 self.url
866 );
867 return;
868 },
869 Some(raw_record) => {
871 self.resolve_requested_module_specifiers(&global, raw_record.handle(), can_gc)
872 },
873 }
874 };
875
876 match specifier_urls {
877 Ok(valid_specifier_urls) if valid_specifier_urls.is_empty() => {
879 debug!("Module {} doesn't have any dependencies.", self.url);
880 self.advance_finished_and_link(&global, can_gc);
881 },
882 Ok(valid_specifier_urls) => {
883 self.descendant_urls
884 .borrow_mut()
885 .extend(valid_specifier_urls.clone());
886
887 let urls_to_fetch = {
888 let mut urls = IndexSet::new();
889 let mut visited_urls = self.visited_urls.borrow_mut();
890
891 for parsed_url in &valid_specifier_urls {
892 if !visited_urls.contains(parsed_url) {
894 urls.insert(parsed_url.clone());
896 visited_urls.insert(parsed_url.clone());
898
899 self.insert_incomplete_fetch_url(parsed_url);
900 }
901 }
902 urls
903 };
904
905 if urls_to_fetch.is_empty() {
907 debug!(
908 "After checking with visited urls, module {} doesn't have dependencies to load.",
909 &self.url
910 );
911 self.advance_finished_and_link(&global, can_gc);
912 return;
913 }
914
915 let visited_urls = self.visited_urls.borrow().clone();
918 let options = options.descendant_fetch_options();
919
920 for url in urls_to_fetch {
921 assert!(self.visited_urls.borrow().contains(&url));
924
925 fetch_single_module_script(
927 owner.clone(),
928 url,
929 visited_urls.clone(),
930 destination,
931 options.clone(),
932 Some(parent_identity.clone()),
933 false,
934 None,
935 Some(IntroductionType::IMPORTED_MODULE),
937 can_gc,
938 );
939 }
940 },
941 Err(error) => {
942 self.set_rethrow_error(error);
943 self.advance_finished_and_link(&global, can_gc);
944 },
945 }
946 }
947
948 fn advance_finished_and_link(&self, global: &GlobalScope, can_gc: CanGc) {
951 {
952 if !self.has_all_ready_descendants(global) {
953 return;
954 }
955 }
956
957 self.set_status(ModuleStatus::Finished);
958
959 debug!("Going to advance and finish for: {}", self.url);
960
961 {
962 let parent_identities = self.parent_identities.borrow();
967 for parent_identity in parent_identities.iter() {
968 let parent_tree = parent_identity.get_module_tree(global);
969
970 let incomplete_count_before_remove = {
971 let incomplete_urls = parent_tree.get_incomplete_fetch_urls().borrow();
972 incomplete_urls.len()
973 };
974
975 if incomplete_count_before_remove > 0 {
976 parent_tree.remove_incomplete_fetch_url(&self.url);
977 parent_tree.advance_finished_and_link(global, can_gc);
978 }
979 }
980 }
981
982 let mut discovered_urls: HashSet<ServoUrl> = HashSet::new();
983 let (network_error, rethrow_error) =
984 self.find_first_parse_error(global, &mut discovered_urls);
985
986 match (network_error, rethrow_error) {
987 (Some(network_error), _) => {
988 self.set_network_error(network_error);
989 },
990 (None, None) => {
991 let module_record = self.get_record().borrow();
992 if let Some(record) = &*module_record {
993 let instantiated = self.instantiate_module_tree(global, record.handle());
994
995 if let Err(exception) = instantiated {
996 self.set_rethrow_error(exception);
997 }
998 }
999 },
1000 (None, Some(error)) => {
1001 self.set_rethrow_error(error);
1002 },
1003 }
1004
1005 let promise = self.promise.borrow();
1006 if let Some(promise) = promise.as_ref() {
1007 promise.resolve_native(&(), can_gc);
1008 }
1009 }
1010}
1011
1012#[derive(JSTraceable, MallocSizeOf)]
1013struct ModuleHandler {
1014 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
1015 task: DomRefCell<Option<Box<dyn TaskBox>>>,
1016}
1017
1018impl ModuleHandler {
1019 pub(crate) fn new_boxed(task: Box<dyn TaskBox>) -> Box<dyn Callback> {
1020 Box::new(Self {
1021 task: DomRefCell::new(Some(task)),
1022 })
1023 }
1024}
1025
1026impl Callback for ModuleHandler {
1027 fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
1028 let task = self.task.borrow_mut().take().unwrap();
1029 task.run_box();
1030 }
1031}
1032
1033#[derive(Clone)]
1036pub(crate) enum ModuleOwner {
1037 #[expect(dead_code)]
1038 Worker(TrustedWorkerAddress),
1039 Window(Trusted<HTMLScriptElement>),
1040 DynamicModule(Trusted<DynamicModuleOwner>),
1041}
1042
1043impl ModuleOwner {
1044 pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
1045 match &self {
1046 ModuleOwner::Worker(worker) => (*worker.root().clone()).global(),
1047 ModuleOwner::Window(script) => (*script.root()).global(),
1048 ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
1049 }
1050 }
1051
1052 pub(crate) fn notify_owner_to_finish(
1053 &self,
1054 module_identity: ModuleIdentity,
1055 fetch_options: ScriptFetchOptions,
1056 can_gc: CanGc,
1057 ) {
1058 match &self {
1059 ModuleOwner::Worker(_) => unimplemented!(),
1060 ModuleOwner::DynamicModule(_) => unimplemented!(),
1061 ModuleOwner::Window(script) => {
1062 let global = self.global();
1063
1064 let document = script.root().owner_document();
1065 let load = {
1066 let module_tree = module_identity.get_module_tree(&global);
1067
1068 let network_error = module_tree.get_network_error().borrow();
1069 match network_error.as_ref() {
1070 Some(network_error) => Err(network_error.clone().into()),
1071 None => match module_identity {
1072 ModuleIdentity::ModuleUrl(script_src) => Ok(ScriptOrigin::external(
1073 Rc::clone(&module_tree.get_text().borrow()),
1074 script_src.clone(),
1075 fetch_options,
1076 ScriptType::Module,
1077 global.unminified_js_dir(),
1078 )),
1079 ModuleIdentity::ScriptId(_) => Ok(ScriptOrigin::internal(
1080 Rc::clone(&module_tree.get_text().borrow()),
1081 document.base_url().clone(),
1082 fetch_options,
1083 ScriptType::Module,
1084 global.unminified_js_dir(),
1085 Err(Error::NotFound(None)),
1086 )),
1087 },
1088 }
1089 };
1090
1091 let asynch = script
1092 .root()
1093 .upcast::<Element>()
1094 .has_attribute(&local_name!("async"));
1095
1096 if !asynch && (*script.root()).get_parser_inserted() {
1097 document.deferred_script_loaded(&script.root(), load, can_gc);
1098 } else if !asynch && !(*script.root()).get_non_blocking() {
1099 document.asap_in_order_script_loaded(&script.root(), load, can_gc);
1100 } else {
1101 document.asap_script_loaded(&script.root(), load, can_gc);
1102 };
1103 },
1104 }
1105 }
1106
1107 #[expect(unsafe_code)]
1108 fn finish_dynamic_module(
1111 &self,
1112 module_identity: ModuleIdentity,
1113 dynamic_module_id: DynamicModuleId,
1114 can_gc: CanGc,
1115 ) {
1116 let global = self.global();
1117
1118 let module = global.dynamic_module_list().remove(dynamic_module_id);
1119
1120 let cx = GlobalScope::get_cx();
1121 let module_tree = module_identity.get_module_tree(&global);
1122
1123 let network_error = module_tree.get_network_error().borrow().as_ref().cloned();
1128 let existing_rethrow_error = module_tree.get_rethrow_error().borrow().as_ref().cloned();
1129
1130 rooted!(in(*cx) let mut rval = UndefinedValue());
1131 if network_error.is_none() && existing_rethrow_error.is_none() {
1132 let record = module_tree
1133 .get_record()
1134 .borrow()
1135 .as_ref()
1136 .map(|record| record.handle());
1137
1138 if let Some(record) = record {
1139 let evaluated = module_tree
1140 .execute_module(&global, record, rval.handle_mut().into(), can_gc)
1141 .err();
1142
1143 if let Some(exception) = evaluated.clone() {
1144 module_tree.set_rethrow_error(exception);
1145 }
1146 }
1147 }
1148
1149 match (network_error, existing_rethrow_error) {
1151 (Some(_), _) => unsafe {
1152 let err = gen_type_error(&global, "Dynamic import failed".to_owned(), can_gc);
1153 JS_SetPendingException(*cx, err.handle(), ExceptionStackBehavior::Capture);
1154 },
1155 (None, Some(rethrow_error)) => unsafe {
1156 JS_SetPendingException(
1157 *cx,
1158 rethrow_error.handle(),
1159 ExceptionStackBehavior::Capture,
1160 );
1161 },
1162 (None, None) => {},
1164 };
1165
1166 debug!("Finishing dynamic import for {:?}", module_identity);
1167
1168 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
1169 if rval.is_object() {
1170 evaluation_promise.set(rval.to_object());
1171 }
1172
1173 unsafe {
1174 let ok = FinishDynamicModuleImport(
1175 *cx,
1176 evaluation_promise.handle().into(),
1177 module.referencing_private.handle(),
1178 module.specifier.handle(),
1179 module.promise.reflector().get_jsobject().into_handle(),
1180 );
1181 if ok {
1182 assert!(!JS_IsExceptionPending(*cx));
1183 } else {
1184 warn!("failed to finish dynamic module import");
1185 }
1186 }
1187 }
1188}
1189
1190struct ModuleContext {
1192 owner: ModuleOwner,
1194 data: Vec<u8>,
1196 metadata: Option<Metadata>,
1198 url: ServoUrl,
1200 destination: Destination,
1202 options: ScriptFetchOptions,
1204 status: Result<(), NetworkError>,
1206 introduction_type: Option<&'static CStr>,
1208}
1209
1210impl FetchResponseListener for ModuleContext {
1211 fn process_request_body(&mut self, _: RequestId) {}
1213
1214 fn process_request_eof(&mut self, _: RequestId) {}
1216
1217 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
1218 self.metadata = metadata.ok().map(|meta| match meta {
1219 FetchMetadata::Unfiltered(m) => m,
1220 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
1221 });
1222
1223 let status = self
1224 .metadata
1225 .as_ref()
1226 .map(|m| m.status.clone())
1227 .unwrap_or_else(HttpStatus::new_error);
1228
1229 self.status = {
1230 if status.is_error() {
1231 Err(NetworkError::Internal(
1232 "No http status code received".to_owned(),
1233 ))
1234 } else if status.is_success() {
1235 Ok(())
1236 } else {
1237 Err(NetworkError::Internal(format!(
1238 "HTTP error code {}",
1239 status.code()
1240 )))
1241 }
1242 };
1243 }
1244
1245 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
1246 if self.status.is_ok() {
1247 self.data.append(&mut chunk);
1248 }
1249 }
1250
1251 fn process_response_eof(
1254 mut self,
1255 _: RequestId,
1256 response: Result<ResourceFetchTiming, NetworkError>,
1257 ) {
1258 let global = self.owner.global();
1259
1260 if let Some(window) = global.downcast::<Window>() {
1261 window
1262 .Document()
1263 .finish_load(LoadType::Script(self.url.clone()), CanGc::note());
1264 }
1265
1266 let load = response.clone().and(self.status.clone()).and_then(|_| {
1268 let meta = self.metadata.take().unwrap();
1270
1271 if let Some(content_type) = meta.content_type.map(Serde::into_inner) {
1272 if let Ok(content_type) = Mime::from_str(&content_type.to_string()) {
1273 let essence_mime = content_type.essence_str();
1274
1275 if !SCRIPT_JS_MIMES.contains(&essence_mime) {
1276 return Err(NetworkError::Internal(format!(
1277 "Invalid MIME type: {}",
1278 essence_mime
1279 )));
1280 }
1281 } else {
1282 return Err(NetworkError::Internal(format!(
1283 "Failed to parse MIME type: {}",
1284 content_type
1285 )));
1286 }
1287 } else {
1288 return Err(NetworkError::Internal("No MIME type".into()));
1289 }
1290
1291 let referrer_policy = meta
1294 .headers
1295 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
1296 .into();
1297
1298 if referrer_policy != ReferrerPolicy::EmptyString {
1301 self.options.referrer_policy = referrer_policy;
1302 }
1303
1304 let (source_text, _, _) = UTF_8.decode(&self.data);
1306 Ok(ScriptOrigin::external(
1307 Rc::new(DOMString::from(source_text)),
1308 meta.final_url,
1309 self.options.clone(),
1310 ScriptType::Module,
1311 global.unminified_js_dir(),
1312 ))
1313 });
1314
1315 let module_tree = {
1316 let module_map = global.get_module_map().borrow();
1317 module_map.get(&self.url).unwrap().clone()
1318 };
1319
1320 module_tree.remove_incomplete_fetch_url(&self.url);
1321
1322 match load {
1324 Err(err) => {
1325 error!("Failed to fetch {} with error {:?}", &self.url, err);
1326 module_tree.set_network_error(err);
1327 module_tree.advance_finished_and_link(&global, CanGc::note());
1328 },
1329 Ok(ref resp_mod_script) => {
1330 module_tree.set_text(resp_mod_script.text());
1331
1332 let cx = GlobalScope::get_cx();
1333 rooted!(in(*cx) let mut compiled_module: *mut JSObject = ptr::null_mut());
1334 let compiled_module_result = module_tree.compile_module_script(
1335 &global,
1336 self.owner.clone(),
1337 resp_mod_script.text(),
1338 &self.url,
1339 self.options.clone(),
1340 compiled_module.handle_mut(),
1341 false,
1342 1, self.introduction_type,
1344 CanGc::note(),
1345 );
1346
1347 match compiled_module_result {
1348 Err(exception) => {
1349 module_tree.set_rethrow_error(exception);
1350 module_tree.advance_finished_and_link(&global, CanGc::note());
1351 },
1352 Ok(_) => {
1353 module_tree.set_record(ModuleObject::new(compiled_module.handle()));
1354
1355 module_tree.fetch_module_descendants(
1356 &self.owner,
1357 self.destination,
1358 &self.options,
1359 ModuleIdentity::ModuleUrl(self.url.clone()),
1360 CanGc::note(),
1361 );
1362 },
1363 }
1364 },
1365 }
1366
1367 if let Ok(response) = response {
1368 network_listener::submit_timing(&self, &response, CanGc::note());
1369 }
1370 }
1371
1372 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1373 let global = &self.resource_timing_global();
1374 global.report_csp_violations(violations, None, None);
1375 }
1376}
1377
1378impl ResourceTimingListener for ModuleContext {
1379 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1380 let initiator_type = InitiatorType::LocalName("module".to_string());
1381 (initiator_type, self.url.clone())
1382 }
1383
1384 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1385 self.owner.global()
1386 }
1387}
1388
1389#[expect(unsafe_code)]
1390#[allow(non_snake_case)]
1391pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
1394 unsafe {
1395 if GetModuleResolveHook(rt).is_some() {
1396 return;
1397 }
1398
1399 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
1400 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
1401 SetScriptPrivateReferenceHooks(
1402 rt,
1403 Some(host_add_ref_top_level_script),
1404 Some(host_release_top_level_script),
1405 );
1406 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
1407 }
1408}
1409
1410#[expect(unsafe_code)]
1411unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
1412 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
1413 mem::forget(val.clone());
1414 mem::forget(val);
1415}
1416
1417#[expect(unsafe_code)]
1418unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
1419 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
1420}
1421
1422#[expect(unsafe_code)]
1423pub(crate) unsafe extern "C" fn host_import_module_dynamically(
1426 cx: *mut JSContext,
1427 reference_private: RawHandleValue,
1428 specifier: RawHandle<*mut JSObject>,
1429 promise: RawHandle<*mut JSObject>,
1430) -> bool {
1431 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1433 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1434 let global_scope = unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
1435 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx);
1436
1437 if let Err(e) = fetch_an_import_module_script_graph(
1439 &global_scope,
1440 specifier,
1441 reference_private,
1442 promise,
1443 CanGc::note(),
1444 ) {
1445 unsafe { JS_SetPendingException(*cx, e.handle(), ExceptionStackBehavior::Capture) };
1446 return false;
1447 }
1448
1449 true
1450}
1451
1452#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
1453pub(crate) struct ScriptFetchOptions {
1455 #[no_trace]
1456 pub(crate) referrer: Referrer,
1457 pub(crate) integrity_metadata: String,
1458 #[no_trace]
1459 pub(crate) credentials_mode: CredentialsMode,
1460 pub(crate) cryptographic_nonce: String,
1461 #[no_trace]
1462 pub(crate) parser_metadata: ParserMetadata,
1463 #[no_trace]
1464 pub(crate) referrer_policy: ReferrerPolicy,
1465}
1466
1467impl ScriptFetchOptions {
1468 pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
1470 Self {
1471 cryptographic_nonce: String::new(),
1472 integrity_metadata: String::new(),
1473 referrer: global.get_referrer(),
1474 parser_metadata: ParserMetadata::NotParserInserted,
1475 credentials_mode: CredentialsMode::CredentialsSameOrigin,
1476 referrer_policy: ReferrerPolicy::EmptyString,
1477 }
1478 }
1479
1480 fn descendant_fetch_options(&self) -> ScriptFetchOptions {
1482 Self {
1483 referrer: self.referrer.clone(),
1484 integrity_metadata: String::new(),
1485 cryptographic_nonce: self.cryptographic_nonce.clone(),
1486 credentials_mode: self.credentials_mode,
1487 parser_metadata: self.parser_metadata,
1488 referrer_policy: self.referrer_policy,
1489 }
1490 }
1491}
1492
1493#[expect(unsafe_code)]
1494unsafe fn module_script_from_reference_private(
1495 reference_private: &RawHandle<JSVal>,
1496) -> Option<&ModuleScript> {
1497 if reference_private.get().is_undefined() {
1498 return None;
1499 }
1500 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1501}
1502
1503#[expect(unsafe_code)]
1505fn fetch_an_import_module_script_graph(
1506 global: &GlobalScope,
1507 module_request: RawHandle<*mut JSObject>,
1508 reference_private: RawHandleValue,
1509 promise: Rc<Promise>,
1510 can_gc: CanGc,
1511) -> Result<(), RethrowError> {
1512 let cx = GlobalScope::get_cx();
1514 let specifier = unsafe {
1515 let jsstr = std::ptr::NonNull::new(GetModuleRequestSpecifier(*cx, module_request)).unwrap();
1516 DOMString::from_string(jsstr_to_string(*cx, jsstr))
1517 };
1518 let mut options = ScriptFetchOptions::default_classic_script(global);
1519 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1520 if let Some(data) = module_data {
1521 options = data.options.descendant_fetch_options();
1522 }
1523 let url = ModuleTree::resolve_module_specifier(global, module_data, specifier, can_gc);
1524
1525 if url.is_err() {
1527 let specifier_error = gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
1528 return Err(specifier_error);
1529 }
1530
1531 let dynamic_module_id = DynamicModuleId(Uuid::new_v4());
1532
1533 let owner = match unsafe { module_script_from_reference_private(&reference_private) } {
1535 Some(module_data) if module_data.owner.is_some() => module_data.owner.clone().unwrap(),
1536 _ => ModuleOwner::DynamicModule(Trusted::new(&DynamicModuleOwner::new(
1537 global,
1538 promise.clone(),
1539 dynamic_module_id,
1540 can_gc,
1541 ))),
1542 };
1543
1544 let dynamic_module = RootedTraceableBox::new(DynamicModule {
1545 promise,
1546 specifier: Heap::default(),
1547 referencing_private: Heap::default(),
1548 id: dynamic_module_id,
1549 });
1550 dynamic_module.specifier.set(module_request.get());
1551 dynamic_module
1552 .referencing_private
1553 .set(reference_private.get());
1554
1555 let url = url.unwrap();
1556
1557 let mut visited_urls = HashSet::new();
1558 visited_urls.insert(url.clone());
1559
1560 fetch_single_module_script(
1561 owner,
1562 url,
1563 visited_urls,
1564 Destination::Script,
1565 options,
1566 None,
1567 true,
1568 Some(dynamic_module),
1569 Some(IntroductionType::IMPORTED_MODULE),
1570 can_gc,
1571 );
1572 Ok(())
1573}
1574
1575#[expect(unsafe_code)]
1576#[allow(non_snake_case)]
1577unsafe extern "C" fn HostResolveImportedModule(
1580 cx: *mut JSContext,
1581 reference_private: RawHandleValue,
1582 specifier: RawHandle<*mut JSObject>,
1583) -> *mut JSObject {
1584 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1585 let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1586
1587 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1589 let jsstr =
1590 std::ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(cx, specifier) }).unwrap();
1591 let specifier = DOMString::from_string(unsafe { jsstr_to_string(cx, jsstr) });
1592 let url =
1593 ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier, CanGc::note());
1594
1595 assert!(url.is_ok());
1597
1598 let parsed_url = url.unwrap();
1599
1600 let module_map = global_scope.get_module_map().borrow();
1602
1603 let module_tree = module_map.get(&parsed_url);
1604
1605 assert!(module_tree.is_some());
1607
1608 let fetched_module_object = module_tree.unwrap().get_record().borrow();
1609
1610 assert!(fetched_module_object.is_some());
1612
1613 if let Some(record) = &*fetched_module_object {
1615 return record.handle().get();
1616 }
1617
1618 unreachable!()
1619}
1620
1621#[expect(unsafe_code)]
1622#[allow(non_snake_case)]
1623unsafe extern "C" fn HostPopulateImportMeta(
1626 cx: *mut JSContext,
1627 reference_private: RawHandleValue,
1628 meta_object: RawHandle<*mut JSObject>,
1629) -> bool {
1630 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1631 let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1632
1633 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1635 Some(module_data) => module_data.base_url.clone(),
1636 None => global_scope.api_base_url(),
1637 };
1638
1639 let url_string = unsafe {
1640 JS_NewStringCopyN(
1641 cx,
1642 base_url.as_str().as_ptr() as *const _,
1643 base_url.as_str().len(),
1644 )
1645 };
1646 rooted!(in(cx) let url_string = url_string);
1647
1648 unsafe {
1650 JS_DefineProperty4(
1651 cx,
1652 meta_object,
1653 c"url".as_ptr(),
1654 url_string.handle().into_handle(),
1655 JSPROP_ENUMERATE.into(),
1656 )
1657 }
1658}
1659
1660pub(crate) fn fetch_external_module_script(
1662 owner: ModuleOwner,
1663 url: ServoUrl,
1664 destination: Destination,
1665 options: ScriptFetchOptions,
1666 can_gc: CanGc,
1667) {
1668 let mut visited_urls = HashSet::new();
1669 visited_urls.insert(url.clone());
1670
1671 fetch_single_module_script(
1673 owner,
1674 url,
1675 visited_urls,
1676 destination,
1677 options,
1678 None,
1679 true,
1680 None,
1681 Some(IntroductionType::SRC_SCRIPT),
1682 can_gc,
1683 )
1684}
1685
1686#[derive(JSTraceable, MallocSizeOf)]
1687#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1688pub(crate) struct DynamicModuleList {
1689 requests: Vec<RootedTraceableBox<DynamicModule>>,
1690
1691 #[ignore_malloc_size_of = "Define in uuid"]
1692 next_id: DynamicModuleId,
1693}
1694
1695impl DynamicModuleList {
1696 pub(crate) fn new() -> Self {
1697 Self {
1698 requests: vec![],
1699 next_id: DynamicModuleId(Uuid::new_v4()),
1700 }
1701 }
1702
1703 fn push(&mut self, mut module: RootedTraceableBox<DynamicModule>) -> DynamicModuleId {
1704 let id = self.next_id;
1705 self.next_id = DynamicModuleId(Uuid::new_v4());
1706 module.id = id;
1707 self.requests.push(module);
1708 id
1709 }
1710
1711 fn remove(&mut self, id: DynamicModuleId) -> RootedTraceableBox<DynamicModule> {
1712 let index = self
1713 .requests
1714 .iter()
1715 .position(|module| module.id == id)
1716 .expect("missing dynamic module");
1717 self.requests.remove(index)
1718 }
1719}
1720
1721#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1722#[derive(JSTraceable, MallocSizeOf)]
1723struct DynamicModule {
1724 #[conditional_malloc_size_of]
1725 promise: Rc<Promise>,
1726 #[ignore_malloc_size_of = "GC types are hard"]
1727 specifier: Heap<*mut JSObject>,
1728 #[ignore_malloc_size_of = "GC types are hard"]
1729 referencing_private: Heap<JSVal>,
1730 #[ignore_malloc_size_of = "Defined in uuid"]
1731 id: DynamicModuleId,
1732}
1733
1734#[allow(clippy::too_many_arguments)]
1736fn fetch_single_module_script(
1737 owner: ModuleOwner,
1738 url: ServoUrl,
1739 visited_urls: HashSet<ServoUrl>,
1740 destination: Destination,
1741 options: ScriptFetchOptions,
1742 parent_identity: Option<ModuleIdentity>,
1743 top_level_module_fetch: bool,
1744 dynamic_module: Option<RootedTraceableBox<DynamicModule>>,
1745 introduction_type: Option<&'static CStr>,
1746 can_gc: CanGc,
1747) {
1748 {
1749 let global = owner.global();
1751 let module_map = global.get_module_map().borrow();
1752
1753 debug!("Start to fetch {}", url);
1754
1755 if let Some(module_tree) = module_map.get(&url.clone()) {
1756 let status = module_tree.get_status();
1757
1758 debug!("Meet a fetched url {} and its status is {:?}", url, status);
1759
1760 match dynamic_module {
1761 Some(module) => module_tree.append_dynamic_module_handler(
1762 owner.clone(),
1763 ModuleIdentity::ModuleUrl(url.clone()),
1764 module,
1765 can_gc,
1766 ),
1767 None if top_level_module_fetch => module_tree.append_handler(
1768 owner.clone(),
1769 ModuleIdentity::ModuleUrl(url.clone()),
1770 options,
1771 can_gc,
1772 ),
1773 None => {},
1775 }
1776
1777 if let Some(parent_identity) = parent_identity {
1778 module_tree.insert_parent_identity(parent_identity);
1779 }
1780
1781 match status {
1782 ModuleStatus::Initial => unreachable!(
1783 "We have the module in module map so its status should not be `initial`"
1784 ),
1785 ModuleStatus::Fetching => {},
1787 ModuleStatus::FetchingDescendants | ModuleStatus::Finished => {
1789 module_tree.advance_finished_and_link(&global, can_gc);
1790 },
1791 }
1792
1793 return;
1794 }
1795 }
1796
1797 let global = owner.global();
1798 let is_external = true;
1799 let module_tree = ModuleTree::new(url.clone(), is_external, visited_urls);
1800 module_tree.set_status(ModuleStatus::Fetching);
1801
1802 match dynamic_module {
1803 Some(module) => module_tree.append_dynamic_module_handler(
1804 owner.clone(),
1805 ModuleIdentity::ModuleUrl(url.clone()),
1806 module,
1807 can_gc,
1808 ),
1809 None if top_level_module_fetch => module_tree.append_handler(
1810 owner.clone(),
1811 ModuleIdentity::ModuleUrl(url.clone()),
1812 options.clone(),
1813 can_gc,
1814 ),
1815 None => {},
1817 }
1818
1819 if let Some(parent_identity) = parent_identity {
1820 module_tree.insert_parent_identity(parent_identity);
1821 }
1822
1823 module_tree.insert_incomplete_fetch_url(&url);
1824
1825 global.set_module_map(url.clone(), module_tree);
1827
1828 let mode = match destination {
1830 Destination::Worker | Destination::SharedWorker if top_level_module_fetch => {
1831 RequestMode::SameOrigin
1832 },
1833 _ => RequestMode::CorsMode,
1834 };
1835
1836 let document: Option<DomRoot<Document>> = match &owner {
1837 ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1838 ModuleOwner::Window(script) => Some(script.root().owner_document()),
1839 };
1840 let webview_id = document.as_ref().map(|document| document.webview_id());
1841
1842 let request = RequestBuilder::new(webview_id, url.clone(), global.get_referrer())
1844 .destination(destination)
1845 .origin(global.origin().immutable().clone())
1846 .parser_metadata(options.parser_metadata)
1847 .integrity_metadata(options.integrity_metadata.clone())
1848 .credentials_mode(options.credentials_mode)
1849 .referrer_policy(options.referrer_policy)
1850 .mode(mode)
1851 .insecure_requests_policy(global.insecure_requests_policy())
1852 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_origin())
1853 .policy_container(global.policy_container().to_owned())
1854 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1855
1856 let context = ModuleContext {
1857 owner,
1858 data: vec![],
1859 metadata: None,
1860 url: url.clone(),
1861 destination,
1862 options,
1863 status: Ok(()),
1864 introduction_type,
1865 };
1866
1867 let network_listener = NetworkListener::new(
1868 context,
1869 global.task_manager().networking_task_source().to_sendable(),
1870 );
1871 match document {
1872 Some(document) => {
1873 let request = document.prepare_request(request);
1874 document.loader_mut().fetch_async_with_callback(
1875 LoadType::Script(url),
1876 request,
1877 network_listener.into_callback(),
1878 );
1879 },
1880 None => global.fetch_with_network_listener(request, network_listener),
1881 }
1882}
1883
1884pub(crate) fn fetch_inline_module_script(
1886 owner: ModuleOwner,
1887 module_script_text: Rc<DOMString>,
1888 url: ServoUrl,
1889 script_id: ScriptId,
1890 options: ScriptFetchOptions,
1891 line_number: u64,
1892 can_gc: CanGc,
1893) {
1894 let global = owner.global();
1895 let is_external = false;
1896 let module_tree = ModuleTree::new(url.clone(), is_external, HashSet::new());
1897
1898 let cx = GlobalScope::get_cx();
1899 rooted!(in(*cx) let mut compiled_module: *mut JSObject = ptr::null_mut());
1900 let compiled_module_result = module_tree.compile_module_script(
1901 &global,
1902 owner.clone(),
1903 module_script_text,
1904 &url,
1905 options.clone(),
1906 compiled_module.handle_mut(),
1907 true,
1908 line_number,
1909 Some(IntroductionType::INLINE_SCRIPT),
1910 can_gc,
1911 );
1912
1913 match compiled_module_result {
1914 Ok(_) => {
1915 module_tree.append_handler(
1916 owner.clone(),
1917 ModuleIdentity::ScriptId(script_id),
1918 options.clone(),
1919 can_gc,
1920 );
1921 module_tree.set_record(ModuleObject::new(compiled_module.handle()));
1922
1923 global.set_inline_module_map(script_id, module_tree);
1927
1928 let inline_module_map = global.get_inline_module_map().borrow();
1932 let module_tree = inline_module_map.get(&script_id).unwrap().clone();
1933
1934 module_tree.fetch_module_descendants(
1935 &owner,
1936 Destination::Script,
1937 &options,
1938 ModuleIdentity::ScriptId(script_id),
1939 can_gc,
1940 );
1941 },
1942 Err(exception) => {
1943 module_tree.set_rethrow_error(exception);
1944 module_tree.set_status(ModuleStatus::Finished);
1945 global.set_inline_module_map(script_id, module_tree);
1946 owner.notify_owner_to_finish(ModuleIdentity::ScriptId(script_id), options, can_gc);
1947 },
1948 }
1949}
1950
1951pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1952pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1953
1954#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1956pub(crate) struct ResolvedModule {
1957 base_url: String,
1959 specifier: String,
1961 #[no_trace]
1963 specifier_url: Option<ServoUrl>,
1964}
1965
1966impl ResolvedModule {
1967 pub(crate) fn new(
1968 base_url: String,
1969 specifier: String,
1970 specifier_url: Option<ServoUrl>,
1971 ) -> Self {
1972 Self {
1973 base_url,
1974 specifier,
1975 specifier_url,
1976 }
1977 }
1978}
1979
1980#[derive(Default, JSTraceable, MallocSizeOf)]
1982pub(crate) struct ImportMap {
1983 #[no_trace]
1984 imports: ModuleSpecifierMap,
1985 #[no_trace]
1986 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1987 #[no_trace]
1988 integrity: ModuleIntegrityMap,
1989}
1990
1991pub(crate) fn register_import_map(
1993 global: &GlobalScope,
1994 result: Fallible<ImportMap>,
1995 can_gc: CanGc,
1996) {
1997 match result {
1998 Ok(new_import_map) => {
1999 merge_existing_and_new_import_maps(global, new_import_map, can_gc);
2001 },
2002 Err(exception) => {
2003 throw_dom_exception(GlobalScope::get_cx(), global, exception.clone(), can_gc);
2006 },
2007 }
2008}
2009
2010fn merge_existing_and_new_import_maps(
2012 global: &GlobalScope,
2013 new_import_map: ImportMap,
2014 can_gc: CanGc,
2015) {
2016 let new_import_map_scopes = new_import_map.scopes;
2018
2019 let mut old_import_map = global.import_map_mut();
2021
2022 let mut new_import_map_imports = new_import_map.imports;
2024
2025 let resolved_module_set = global.resolved_module_set();
2026 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
2028 for record in resolved_module_set.iter() {
2030 let prefix = scope_prefix.as_str();
2033 if prefix == record.base_url ||
2034 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
2035 {
2036 scope_imports.retain(|key, val| {
2038 if *key == record.specifier ||
2043 (key.ends_with('\u{002f}') &&
2044 record.specifier.starts_with(key) &&
2045 (record.specifier_url.is_none() ||
2046 record
2047 .specifier_url
2048 .as_ref()
2049 .map(|u| u.is_special_scheme())
2050 .unwrap_or_default()))
2051 {
2052 Console::internal_warn(
2055 global,
2056 DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
2057 );
2058 false
2060 } else {
2061 true
2062 }
2063 })
2064 }
2065 }
2066
2067 if old_import_map.scopes.contains_key(&scope_prefix) {
2069 let merged_module_specifier_map = merge_module_specifier_maps(
2072 global,
2073 scope_imports,
2074 &old_import_map.scopes[&scope_prefix],
2075 can_gc,
2076 );
2077 old_import_map
2078 .scopes
2079 .insert(scope_prefix, merged_module_specifier_map);
2080 } else {
2081 old_import_map.scopes.insert(scope_prefix, scope_imports);
2083 }
2084 }
2085
2086 for (url, integrity) in &new_import_map.integrity {
2088 if old_import_map.integrity.contains_key(url) {
2090 Console::internal_warn(
2093 global,
2094 DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
2095 );
2096 continue;
2098 }
2099
2100 old_import_map
2102 .integrity
2103 .insert(url.clone(), integrity.clone());
2104 }
2105
2106 for record in resolved_module_set.iter() {
2108 new_import_map_imports.retain(|specifier, val| {
2110 if specifier.starts_with(&record.specifier) {
2112 Console::internal_warn(
2115 global,
2116 DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
2117 );
2118 false
2120 } else {
2121 true
2122 }
2123 });
2124 }
2125
2126 let merged_module_specifier_map = merge_module_specifier_maps(
2129 global,
2130 new_import_map_imports,
2131 &old_import_map.imports,
2132 can_gc,
2133 );
2134 old_import_map.imports = merged_module_specifier_map;
2135}
2136
2137fn merge_module_specifier_maps(
2139 global: &GlobalScope,
2140 new_map: ModuleSpecifierMap,
2141 old_map: &ModuleSpecifierMap,
2142 _can_gc: CanGc,
2143) -> ModuleSpecifierMap {
2144 let mut merged_map = old_map.clone();
2146
2147 for (specifier, url) in new_map {
2149 if old_map.contains_key(&specifier) {
2151 Console::internal_warn(
2154 global,
2155 DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
2156 );
2157
2158 continue;
2160 }
2161
2162 merged_map.insert(specifier, url);
2164 }
2165
2166 merged_map
2167}
2168
2169pub(crate) fn parse_an_import_map_string(
2171 module_owner: ModuleOwner,
2172 input: Rc<DOMString>,
2173 base_url: ServoUrl,
2174 can_gc: CanGc,
2175) -> Fallible<ImportMap> {
2176 let parsed: JsonValue = serde_json::from_str(&input.str())
2178 .map_err(|_| Error::Type("The value needs to be a JSON object.".to_owned()))?;
2179 let JsonValue::Object(mut parsed) = parsed else {
2182 return Err(Error::Type(
2183 "The top-level value needs to be a JSON object.".to_owned(),
2184 ));
2185 };
2186
2187 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
2189 if let Some(imports) = parsed.get("imports") {
2191 let JsonValue::Object(imports) = imports else {
2194 return Err(Error::Type(
2195 "The \"imports\" top-level value needs to be a JSON object.".to_owned(),
2196 ));
2197 };
2198 sorted_and_normalized_imports = sort_and_normalize_module_specifier_map(
2201 &module_owner.global(),
2202 imports,
2203 &base_url,
2204 can_gc,
2205 );
2206 }
2207
2208 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2210 if let Some(scopes) = parsed.get("scopes") {
2212 let JsonValue::Object(scopes) = scopes else {
2215 return Err(Error::Type(
2216 "The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
2217 ));
2218 };
2219 sorted_and_normalized_scopes =
2222 sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url, can_gc)?;
2223 }
2224
2225 let mut normalized_integrity = ModuleIntegrityMap::new();
2227 if let Some(integrity) = parsed.get("integrity") {
2229 let JsonValue::Object(integrity) = integrity else {
2232 return Err(Error::Type(
2233 "The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
2234 ));
2235 };
2236 normalized_integrity =
2239 normalize_module_integrity_map(&module_owner.global(), integrity, &base_url, can_gc);
2240 }
2241
2242 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
2246 if !parsed.is_empty() {
2247 Console::internal_warn(
2248 &module_owner.global(),
2249 DOMString::from(
2250 "Invalid top-level key was present in the import map.
2251 Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
2252 ),
2253 );
2254 }
2255
2256 Ok(ImportMap {
2258 imports: sorted_and_normalized_imports,
2259 scopes: sorted_and_normalized_scopes,
2260 integrity: normalized_integrity,
2261 })
2262}
2263
2264fn sort_and_normalize_module_specifier_map(
2266 global: &GlobalScope,
2267 original_map: &JsonMap<String, JsonValue>,
2268 base_url: &ServoUrl,
2269 can_gc: CanGc,
2270) -> ModuleSpecifierMap {
2271 let mut normalized = ModuleSpecifierMap::new();
2273
2274 for (specifier_key, value) in original_map {
2276 let Some(normalized_specifier_key) =
2279 normalize_specifier_key(global, specifier_key, base_url, can_gc)
2280 else {
2281 continue;
2283 };
2284
2285 let JsonValue::String(value) = value else {
2287 Console::internal_warn(global, DOMString::from("Addresses need to be strings."));
2290
2291 normalized.insert(normalized_specifier_key, None);
2293 continue;
2295 };
2296
2297 let value = DOMString::from(value.as_str());
2299 let Some(address_url) = ModuleTree::resolve_url_like_module_specifier(&value, base_url)
2300 else {
2301 Console::internal_warn(
2305 global,
2306 DOMString::from(format!(
2307 "Value failed to resolve to module specifier: {value}"
2308 )),
2309 );
2310
2311 normalized.insert(normalized_specifier_key, None);
2313 continue;
2315 };
2316
2317 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2320 Console::internal_warn(
2324 global,
2325 DOMString::from(format!(
2326 "Invalid address for specifier key '{specifier_key}': {address_url}.
2327 Since specifierKey ends with a slash, the address needs to as well."
2328 )),
2329 );
2330
2331 normalized.insert(normalized_specifier_key, None);
2333 continue;
2335 }
2336
2337 normalized.insert(normalized_specifier_key, Some(address_url));
2339 }
2340
2341 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2344 normalized
2345}
2346
2347fn sort_and_normalize_scopes(
2349 global: &GlobalScope,
2350 original_map: &JsonMap<String, JsonValue>,
2351 base_url: &ServoUrl,
2352 can_gc: CanGc,
2353) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2354 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2356
2357 for (scope_prefix, potential_specifier_map) in original_map {
2359 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2362 return Err(Error::Type(
2363 "The value of the scope with prefix scopePrefix needs to be a JSON object."
2364 .to_owned(),
2365 ));
2366 };
2367
2368 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2370 Console::internal_warn(
2374 global,
2375 DOMString::from(format!(
2376 "Scope prefix URL was not parseable: {scope_prefix}"
2377 )),
2378 );
2379 continue;
2381 };
2382
2383 let normalized_scope_prefix = scope_prefix_url;
2385
2386 let normalized_specifier_map = sort_and_normalize_module_specifier_map(
2389 global,
2390 potential_specifier_map,
2391 base_url,
2392 can_gc,
2393 );
2394 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2395 }
2396
2397 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2400 Ok(normalized)
2401}
2402
2403fn normalize_module_integrity_map(
2405 global: &GlobalScope,
2406 original_map: &JsonMap<String, JsonValue>,
2407 base_url: &ServoUrl,
2408 _can_gc: CanGc,
2409) -> ModuleIntegrityMap {
2410 let mut normalized = ModuleIntegrityMap::new();
2412
2413 for (key, value) in original_map {
2415 let Some(resolved_url) =
2418 ModuleTree::resolve_url_like_module_specifier(&DOMString::from(key.as_str()), base_url)
2419 else {
2420 Console::internal_warn(
2424 global,
2425 DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
2426 );
2427 continue;
2429 };
2430
2431 let JsonValue::String(value) = value else {
2433 Console::internal_warn(
2436 global,
2437 DOMString::from("Integrity metadata values need to be strings."),
2438 );
2439 continue;
2441 };
2442
2443 normalized.insert(resolved_url, value.clone());
2445 }
2446
2447 normalized
2449}
2450
2451fn normalize_specifier_key(
2453 global: &GlobalScope,
2454 specifier_key: &str,
2455 base_url: &ServoUrl,
2456 _can_gc: CanGc,
2457) -> Option<String> {
2458 if specifier_key.is_empty() {
2460 Console::internal_warn(
2463 global,
2464 DOMString::from("Specifier keys may not be the empty string."),
2465 );
2466 return None;
2468 }
2469 let url =
2471 ModuleTree::resolve_url_like_module_specifier(&DOMString::from(specifier_key), base_url);
2472
2473 if let Some(url) = url {
2475 return Some(url.into_string());
2476 }
2477
2478 Some(specifier_key.to_string())
2480}
2481
2482pub(crate) fn resolve_imports_match(
2487 normalized_specifier: &str,
2488 as_url: Option<&ServoUrl>,
2489 specifier_map: &ModuleSpecifierMap,
2490 _can_gc: CanGc,
2491) -> Fallible<Option<ServoUrl>> {
2492 for (specifier_key, resolution_result) in specifier_map {
2494 if specifier_key == normalized_specifier {
2496 if let Some(resolution_result) = resolution_result {
2497 return Ok(Some(resolution_result.clone()));
2501 } else {
2502 return Err(Error::Type(
2504 "Resolution of specifierKey was blocked by a null entry.".to_owned(),
2505 ));
2506 }
2507 }
2508
2509 if specifier_key.ends_with('\u{002f}') &&
2514 normalized_specifier.starts_with(specifier_key) &&
2515 (as_url.is_none() || as_url.map(|u| u.is_special_scheme()).unwrap_or_default())
2516 {
2517 let Some(resolution_result) = resolution_result else {
2520 return Err(Error::Type(
2521 "Resolution of specifierKey was blocked by a null entry.".to_owned(),
2522 ));
2523 };
2524
2525 let after_prefix = normalized_specifier
2527 .strip_prefix(specifier_key)
2528 .expect("specifier_key should be the prefix of normalized_specifier");
2529
2530 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2532
2533 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2535
2536 let Ok(url) = url else {
2539 return Err(Error::Type(
2540 "Resolution of normalizedSpecifier was blocked since
2541 the afterPrefix portion could not be URL-parsed relative to
2542 the resolutionResult mapped to by the specifierKey prefix."
2543 .to_owned(),
2544 ));
2545 };
2546
2547 if !url.as_str().starts_with(resolution_result.as_str()) {
2550 return Err(Error::Type(
2551 "Resolution of normalizedSpecifier was blocked due to
2552 it backtracking above its prefix specifierKey."
2553 .to_owned(),
2554 ));
2555 }
2556
2557 return Ok(Some(url));
2559 }
2560 }
2561
2562 Ok(None)
2564}