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 #[allow(unsafe_code, clippy::too_many_arguments)]
456 fn compile_module_script(
461 &self,
462 global: &GlobalScope,
463 owner: ModuleOwner,
464 module_script_text: Rc<DOMString>,
465 url: &ServoUrl,
466 options: ScriptFetchOptions,
467 mut module_script: RustMutableHandleObject,
468 inline: bool,
469 line_number: u64,
470 introduction_type: Option<&'static CStr>,
471 can_gc: CanGc,
472 ) -> Result<(), RethrowError> {
473 let cx = GlobalScope::get_cx();
474 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
475
476 let mut compile_options =
477 unsafe { CompileOptionsWrapper::new(*cx, url.as_str(), line_number as u32) };
478 if let Some(introduction_type) = introduction_type {
479 compile_options.set_introduction_type(introduction_type);
480 }
481 let mut module_source = ModuleSource {
482 source: module_script_text,
483 unminified_dir: global.unminified_js_dir(),
484 external: !inline,
485 url: url.clone(),
486 };
487 crate::unminify::unminify_js(&mut module_source);
488
489 unsafe {
490 module_script.set(CompileModule1(
491 *cx,
492 compile_options.ptr,
493 &mut transform_str_to_source_text(&module_source.source.str()),
494 ));
495
496 if module_script.is_null() {
497 warn!("fail to compile module script of {}", url);
498
499 rooted!(in(*cx) let mut exception = UndefinedValue());
500 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
501 JS_ClearPendingException(*cx);
502
503 return Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
504 exception.get(),
505 ))));
506 }
507
508 let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
509
510 SetModulePrivate(
511 module_script.get(),
512 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
513 );
514
515 debug!("module script of {} compile done", url);
516
517 self.resolve_requested_module_specifiers(
518 global,
519 module_script.handle().into_handle(),
520 can_gc,
521 )
522 .map(|_| ())
523 }
524 }
525
526 #[expect(unsafe_code)]
527 pub(crate) fn instantiate_module_tree(
530 &self,
531 global: &GlobalScope,
532 module_record: HandleObject,
533 ) -> Result<(), RethrowError> {
534 let cx = GlobalScope::get_cx();
535 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
536
537 unsafe {
538 if !ModuleLink(*cx, module_record) {
539 warn!("fail to link & instantiate module");
540
541 rooted!(in(*cx) let mut exception = UndefinedValue());
542 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
543 JS_ClearPendingException(*cx);
544
545 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
546 exception.get(),
547 ))))
548 } else {
549 debug!("module instantiated successfully");
550
551 Ok(())
552 }
553 }
554 }
555
556 #[expect(unsafe_code)]
560 pub(crate) fn execute_module(
561 &self,
562 global: &GlobalScope,
563 module_record: HandleObject,
564 eval_result: MutableHandleValue,
565 _can_gc: CanGc,
566 ) -> Result<(), RethrowError> {
567 let cx = GlobalScope::get_cx();
568 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
569
570 unsafe {
571 let ok = ModuleEvaluate(*cx, module_record, eval_result);
572 assert!(ok, "module evaluation failed");
573
574 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
575 if eval_result.is_object() {
576 evaluation_promise.set(eval_result.to_object());
577 }
578
579 let throw_result = ThrowOnModuleEvaluationFailure(
580 *cx,
581 evaluation_promise.handle().into(),
582 ModuleErrorBehaviour::ThrowModuleErrorsSync,
583 );
584 if !throw_result {
585 warn!("fail to evaluate module");
586
587 rooted!(in(*cx) let mut exception = UndefinedValue());
588 assert!(JS_GetPendingException(*cx, exception.handle_mut()));
589 JS_ClearPendingException(*cx);
590
591 Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
592 exception.get(),
593 ))))
594 } else {
595 debug!("module evaluated successfully");
596 Ok(())
597 }
598 }
599 }
600
601 #[expect(unsafe_code)]
602 pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
603 let module_error = self.rethrow_error.borrow();
604
605 if let Some(exception) = &*module_error {
606 let ar = enter_realm(global);
607 unsafe {
608 JS_SetPendingException(
609 *GlobalScope::get_cx(),
610 exception.handle(),
611 ExceptionStackBehavior::Capture,
612 );
613 }
614 report_pending_exception(GlobalScope::get_cx(), true, InRealm::Entered(&ar), can_gc);
615 }
616 }
617
618 #[expect(unsafe_code)]
619 fn resolve_requested_module_specifiers(
620 &self,
621 global: &GlobalScope,
622 module_object: HandleObject,
623 can_gc: CanGc,
624 ) -> Result<IndexSet<ServoUrl>, RethrowError> {
625 let cx = GlobalScope::get_cx();
626 let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
627
628 let mut specifier_urls = IndexSet::new();
629
630 unsafe {
631 let length = GetRequestedModulesCount(*cx, module_object);
632
633 for index in 0..length {
634 let jsstr =
635 std::ptr::NonNull::new(GetRequestedModuleSpecifier(*cx, module_object, index))
636 .unwrap();
637 let specifier = DOMString::from_string(jsstr_to_string(*cx, jsstr));
638
639 rooted!(in(*cx) let mut private = UndefinedValue());
640 JS_GetModulePrivate(module_object.get(), private.handle_mut());
641 let private = private.handle().into_handle();
642 let script = module_script_from_reference_private(&private);
643 let url = ModuleTree::resolve_module_specifier(global, script, specifier, can_gc);
644
645 if url.is_err() {
646 let specifier_error =
647 gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
648
649 return Err(specifier_error);
650 }
651
652 specifier_urls.insert(url.unwrap());
653 }
654 }
655
656 Ok(specifier_urls)
657 }
658
659 fn resolve_module_specifier(
661 global: &GlobalScope,
662 script: Option<&ModuleScript>,
663 specifier: DOMString,
664 can_gc: CanGc,
665 ) -> Fallible<ServoUrl> {
666 let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
668 let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
670 Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
674 None => (global, &global.api_base_url()),
679 };
680
681 let import_map = if global.is::<Window>() {
685 Some(global.import_map())
686 } else {
687 None
688 };
689
690 let serialized_base_url = base_url.as_str();
692 let as_url = Self::resolve_url_like_module_specifier(&specifier, base_url);
694 let specifier = specifier.str();
697 let normalized_specifier = match &as_url {
698 Some(url) => url.as_str(),
699 None => &specifier,
700 };
701
702 let mut result = None;
704 if let Some(map) = import_map {
705 for (prefix, imports) in &map.scopes {
707 let prefix = prefix.as_str();
710 if prefix == serialized_base_url ||
711 (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
712 {
713 result = resolve_imports_match(
718 normalized_specifier,
719 as_url.as_ref(),
720 imports,
721 can_gc,
722 )?;
723 break;
724 }
725 }
726
727 if result.is_none() {
730 result = resolve_imports_match(
731 normalized_specifier,
732 as_url.as_ref(),
733 &map.imports,
734 can_gc,
735 )?;
736 }
737 }
738
739 if result.is_none() {
741 result = as_url.clone();
742 }
743
744 match result {
746 Some(result) => {
747 global.add_module_to_resolved_module_set(
750 serialized_base_url,
751 normalized_specifier,
752 as_url.clone(),
753 );
754 Ok(result)
756 },
757 None => Err(Error::Type(
760 "Specifier was a bare specifier, but was not remapped to anything by importMap."
761 .to_owned(),
762 )),
763 }
764 }
765
766 fn resolve_url_like_module_specifier(
768 specifier: &DOMString,
769 base_url: &ServoUrl,
770 ) -> Option<ServoUrl> {
771 if specifier.starts_with('/') ||
773 specifier.starts_with_str("./") ||
774 specifier.starts_with_str("../")
775 {
776 return ServoUrl::parse_with_base(Some(base_url), &specifier.str()).ok();
778 }
779 ServoUrl::parse(&specifier.str()).ok()
781 }
782
783 fn find_first_parse_error(
785 &self,
786 global: &GlobalScope,
787 discovered_urls: &mut HashSet<ServoUrl>,
788 ) -> (Option<NetworkError>, Option<RethrowError>) {
789 discovered_urls.insert(self.url.clone());
791
792 let record = self.get_record().borrow();
794 if record.is_none() {
795 return (
796 self.network_error.borrow().clone(),
797 self.rethrow_error.borrow().clone(),
798 );
799 }
800
801 let module_map = global.get_module_map().borrow();
802 let mut parse_error: Option<RethrowError> = None;
803
804 let descendant_urls = self.descendant_urls.borrow();
806 for descendant_module in descendant_urls
807 .iter()
808 .filter_map(|url| module_map.get(&url.clone()))
810 {
811 if discovered_urls.contains(&descendant_module.url) {
813 continue;
814 }
815
816 let (child_network_error, child_parse_error) =
818 descendant_module.find_first_parse_error(global, discovered_urls);
819
820 if child_network_error.is_some() {
823 return (child_network_error, None);
824 }
825
826 if child_parse_error.is_some() && parse_error.is_none() {
832 parse_error = child_parse_error;
833 }
834 }
835
836 (None, parse_error)
838 }
839
840 fn fetch_module_descendants(
843 &self,
844 owner: &ModuleOwner,
845 destination: Destination,
846 options: &ScriptFetchOptions,
847 parent_identity: ModuleIdentity,
848 can_gc: CanGc,
849 ) {
850 debug!("Start to load dependencies of {}", self.url);
851
852 let global = owner.global();
853
854 self.set_status(ModuleStatus::FetchingDescendants);
855
856 let specifier_urls = {
857 let raw_record = self.record.borrow();
858 match raw_record.as_ref() {
859 None => {
861 self.set_status(ModuleStatus::Finished);
862 debug!(
863 "Module {} doesn't have module record but tried to load descendants.",
864 self.url
865 );
866 return;
867 },
868 Some(raw_record) => {
870 self.resolve_requested_module_specifiers(&global, raw_record.handle(), can_gc)
871 },
872 }
873 };
874
875 match specifier_urls {
876 Ok(valid_specifier_urls) if valid_specifier_urls.is_empty() => {
878 debug!("Module {} doesn't have any dependencies.", self.url);
879 self.advance_finished_and_link(&global, can_gc);
880 },
881 Ok(valid_specifier_urls) => {
882 self.descendant_urls
883 .borrow_mut()
884 .extend(valid_specifier_urls.clone());
885
886 let urls_to_fetch = {
887 let mut urls = IndexSet::new();
888 let mut visited_urls = self.visited_urls.borrow_mut();
889
890 for parsed_url in &valid_specifier_urls {
891 if !visited_urls.contains(parsed_url) {
893 urls.insert(parsed_url.clone());
895 visited_urls.insert(parsed_url.clone());
897
898 self.insert_incomplete_fetch_url(parsed_url);
899 }
900 }
901 urls
902 };
903
904 if urls_to_fetch.is_empty() {
906 debug!(
907 "After checking with visited urls, module {} doesn't have dependencies to load.",
908 &self.url
909 );
910 self.advance_finished_and_link(&global, can_gc);
911 return;
912 }
913
914 let visited_urls = self.visited_urls.borrow().clone();
917 let options = options.descendant_fetch_options();
918
919 for url in urls_to_fetch {
920 assert!(self.visited_urls.borrow().contains(&url));
923
924 fetch_single_module_script(
926 owner.clone(),
927 url,
928 visited_urls.clone(),
929 destination,
930 options.clone(),
931 Some(parent_identity.clone()),
932 false,
933 None,
934 Some(IntroductionType::IMPORTED_MODULE),
936 can_gc,
937 );
938 }
939 },
940 Err(error) => {
941 self.set_rethrow_error(error);
942 self.advance_finished_and_link(&global, can_gc);
943 },
944 }
945 }
946
947 fn advance_finished_and_link(&self, global: &GlobalScope, can_gc: CanGc) {
950 {
951 if !self.has_all_ready_descendants(global) {
952 return;
953 }
954 }
955
956 self.set_status(ModuleStatus::Finished);
957
958 debug!("Going to advance and finish for: {}", self.url);
959
960 {
961 let parent_identities = self.parent_identities.borrow();
966 for parent_identity in parent_identities.iter() {
967 let parent_tree = parent_identity.get_module_tree(global);
968
969 let incomplete_count_before_remove = {
970 let incomplete_urls = parent_tree.get_incomplete_fetch_urls().borrow();
971 incomplete_urls.len()
972 };
973
974 if incomplete_count_before_remove > 0 {
975 parent_tree.remove_incomplete_fetch_url(&self.url);
976 parent_tree.advance_finished_and_link(global, can_gc);
977 }
978 }
979 }
980
981 let mut discovered_urls: HashSet<ServoUrl> = HashSet::new();
982 let (network_error, rethrow_error) =
983 self.find_first_parse_error(global, &mut discovered_urls);
984
985 match (network_error, rethrow_error) {
986 (Some(network_error), _) => {
987 self.set_network_error(network_error);
988 },
989 (None, None) => {
990 let module_record = self.get_record().borrow();
991 if let Some(record) = &*module_record {
992 let instantiated = self.instantiate_module_tree(global, record.handle());
993
994 if let Err(exception) = instantiated {
995 self.set_rethrow_error(exception);
996 }
997 }
998 },
999 (None, Some(error)) => {
1000 self.set_rethrow_error(error);
1001 },
1002 }
1003
1004 let promise = self.promise.borrow();
1005 if let Some(promise) = promise.as_ref() {
1006 promise.resolve_native(&(), can_gc);
1007 }
1008 }
1009}
1010
1011#[derive(JSTraceable, MallocSizeOf)]
1012struct ModuleHandler {
1013 #[ignore_malloc_size_of = "Measuring trait objects is hard"]
1014 task: DomRefCell<Option<Box<dyn TaskBox>>>,
1015}
1016
1017impl ModuleHandler {
1018 pub(crate) fn new_boxed(task: Box<dyn TaskBox>) -> Box<dyn Callback> {
1019 Box::new(Self {
1020 task: DomRefCell::new(Some(task)),
1021 })
1022 }
1023}
1024
1025impl Callback for ModuleHandler {
1026 fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
1027 let task = self.task.borrow_mut().take().unwrap();
1028 task.run_box();
1029 }
1030}
1031
1032#[derive(Clone)]
1035pub(crate) enum ModuleOwner {
1036 #[expect(dead_code)]
1037 Worker(TrustedWorkerAddress),
1038 Window(Trusted<HTMLScriptElement>),
1039 DynamicModule(Trusted<DynamicModuleOwner>),
1040}
1041
1042impl ModuleOwner {
1043 pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
1044 match &self {
1045 ModuleOwner::Worker(worker) => (*worker.root().clone()).global(),
1046 ModuleOwner::Window(script) => (*script.root()).global(),
1047 ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
1048 }
1049 }
1050
1051 pub(crate) fn notify_owner_to_finish(
1052 &self,
1053 module_identity: ModuleIdentity,
1054 fetch_options: ScriptFetchOptions,
1055 can_gc: CanGc,
1056 ) {
1057 match &self {
1058 ModuleOwner::Worker(_) => unimplemented!(),
1059 ModuleOwner::DynamicModule(_) => unimplemented!(),
1060 ModuleOwner::Window(script) => {
1061 let global = self.global();
1062
1063 let document = script.root().owner_document();
1064 let load = {
1065 let module_tree = module_identity.get_module_tree(&global);
1066
1067 let network_error = module_tree.get_network_error().borrow();
1068 match network_error.as_ref() {
1069 Some(network_error) => Err(network_error.clone().into()),
1070 None => match module_identity {
1071 ModuleIdentity::ModuleUrl(script_src) => Ok(ScriptOrigin::external(
1072 Rc::clone(&module_tree.get_text().borrow()),
1073 script_src.clone(),
1074 fetch_options,
1075 ScriptType::Module,
1076 global.unminified_js_dir(),
1077 )),
1078 ModuleIdentity::ScriptId(_) => Ok(ScriptOrigin::internal(
1079 Rc::clone(&module_tree.get_text().borrow()),
1080 document.base_url().clone(),
1081 fetch_options,
1082 ScriptType::Module,
1083 global.unminified_js_dir(),
1084 Err(Error::NotFound(None)),
1085 )),
1086 },
1087 }
1088 };
1089
1090 let asynch = script
1091 .root()
1092 .upcast::<Element>()
1093 .has_attribute(&local_name!("async"));
1094
1095 if !asynch && (*script.root()).get_parser_inserted() {
1096 document.deferred_script_loaded(&script.root(), load, can_gc);
1097 } else if !asynch && !(*script.root()).get_non_blocking() {
1098 document.asap_in_order_script_loaded(&script.root(), load, can_gc);
1099 } else {
1100 document.asap_script_loaded(&script.root(), load, can_gc);
1101 };
1102 },
1103 }
1104 }
1105
1106 #[expect(unsafe_code)]
1107 fn finish_dynamic_module(
1110 &self,
1111 module_identity: ModuleIdentity,
1112 dynamic_module_id: DynamicModuleId,
1113 can_gc: CanGc,
1114 ) {
1115 let global = self.global();
1116
1117 let module = global.dynamic_module_list().remove(dynamic_module_id);
1118
1119 let cx = GlobalScope::get_cx();
1120 let module_tree = module_identity.get_module_tree(&global);
1121
1122 let network_error = module_tree.get_network_error().borrow().as_ref().cloned();
1127 let existing_rethrow_error = module_tree.get_rethrow_error().borrow().as_ref().cloned();
1128
1129 rooted!(in(*cx) let mut rval = UndefinedValue());
1130 if network_error.is_none() && existing_rethrow_error.is_none() {
1131 let record = module_tree
1132 .get_record()
1133 .borrow()
1134 .as_ref()
1135 .map(|record| record.handle());
1136
1137 if let Some(record) = record {
1138 let evaluated = module_tree
1139 .execute_module(&global, record, rval.handle_mut().into(), can_gc)
1140 .err();
1141
1142 if let Some(exception) = evaluated.clone() {
1143 module_tree.set_rethrow_error(exception);
1144 }
1145 }
1146 }
1147
1148 match (network_error, existing_rethrow_error) {
1150 (Some(_), _) => unsafe {
1151 let err = gen_type_error(&global, "Dynamic import failed".to_owned(), can_gc);
1152 JS_SetPendingException(*cx, err.handle(), ExceptionStackBehavior::Capture);
1153 },
1154 (None, Some(rethrow_error)) => unsafe {
1155 JS_SetPendingException(
1156 *cx,
1157 rethrow_error.handle(),
1158 ExceptionStackBehavior::Capture,
1159 );
1160 },
1161 (None, None) => {},
1163 };
1164
1165 debug!("Finishing dynamic import for {:?}", module_identity);
1166
1167 rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
1168 if rval.is_object() {
1169 evaluation_promise.set(rval.to_object());
1170 }
1171
1172 unsafe {
1173 let ok = FinishDynamicModuleImport(
1174 *cx,
1175 evaluation_promise.handle().into(),
1176 module.referencing_private.handle(),
1177 module.specifier.handle(),
1178 module.promise.reflector().get_jsobject().into_handle(),
1179 );
1180 if ok {
1181 assert!(!JS_IsExceptionPending(*cx));
1182 } else {
1183 warn!("failed to finish dynamic module import");
1184 }
1185 }
1186 }
1187}
1188
1189struct ModuleContext {
1191 owner: ModuleOwner,
1193 data: Vec<u8>,
1195 metadata: Option<Metadata>,
1197 url: ServoUrl,
1199 destination: Destination,
1201 options: ScriptFetchOptions,
1203 status: Result<(), NetworkError>,
1205 introduction_type: Option<&'static CStr>,
1207}
1208
1209impl FetchResponseListener for ModuleContext {
1210 fn process_request_body(&mut self, _: RequestId) {}
1212
1213 fn process_request_eof(&mut self, _: RequestId) {}
1215
1216 fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
1217 self.metadata = metadata.ok().map(|meta| match meta {
1218 FetchMetadata::Unfiltered(m) => m,
1219 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
1220 });
1221
1222 let status = self
1223 .metadata
1224 .as_ref()
1225 .map(|m| m.status.clone())
1226 .unwrap_or_else(HttpStatus::new_error);
1227
1228 self.status = {
1229 if status.is_error() {
1230 Err(NetworkError::Internal(
1231 "No http status code received".to_owned(),
1232 ))
1233 } else if status.is_success() {
1234 Ok(())
1235 } else {
1236 Err(NetworkError::Internal(format!(
1237 "HTTP error code {}",
1238 status.code()
1239 )))
1240 }
1241 };
1242 }
1243
1244 fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
1245 if self.status.is_ok() {
1246 self.data.append(&mut chunk);
1247 }
1248 }
1249
1250 fn process_response_eof(
1253 mut self,
1254 _: RequestId,
1255 response: Result<ResourceFetchTiming, NetworkError>,
1256 ) {
1257 let global = self.owner.global();
1258
1259 if let Some(window) = global.downcast::<Window>() {
1260 window
1261 .Document()
1262 .finish_load(LoadType::Script(self.url.clone()), CanGc::note());
1263 }
1264
1265 let load = response.clone().and(self.status.clone()).and_then(|_| {
1267 let meta = self.metadata.take().unwrap();
1269
1270 if let Some(content_type) = meta.content_type.map(Serde::into_inner) {
1271 if let Ok(content_type) = Mime::from_str(&content_type.to_string()) {
1272 let essence_mime = content_type.essence_str();
1273
1274 if !SCRIPT_JS_MIMES.contains(&essence_mime) {
1275 return Err(NetworkError::Internal(format!(
1276 "Invalid MIME type: {}",
1277 essence_mime
1278 )));
1279 }
1280 } else {
1281 return Err(NetworkError::Internal(format!(
1282 "Failed to parse MIME type: {}",
1283 content_type
1284 )));
1285 }
1286 } else {
1287 return Err(NetworkError::Internal("No MIME type".into()));
1288 }
1289
1290 let referrer_policy = meta
1293 .headers
1294 .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
1295 .into();
1296
1297 if referrer_policy != ReferrerPolicy::EmptyString {
1300 self.options.referrer_policy = referrer_policy;
1301 }
1302
1303 let (source_text, _, _) = UTF_8.decode(&self.data);
1305 Ok(ScriptOrigin::external(
1306 Rc::new(DOMString::from(source_text)),
1307 meta.final_url,
1308 self.options.clone(),
1309 ScriptType::Module,
1310 global.unminified_js_dir(),
1311 ))
1312 });
1313
1314 let module_tree = {
1315 let module_map = global.get_module_map().borrow();
1316 module_map.get(&self.url).unwrap().clone()
1317 };
1318
1319 module_tree.remove_incomplete_fetch_url(&self.url);
1320
1321 match load {
1323 Err(err) => {
1324 error!("Failed to fetch {} with error {:?}", &self.url, err);
1325 module_tree.set_network_error(err);
1326 module_tree.advance_finished_and_link(&global, CanGc::note());
1327 },
1328 Ok(ref resp_mod_script) => {
1329 module_tree.set_text(resp_mod_script.text());
1330
1331 let cx = GlobalScope::get_cx();
1332 rooted!(in(*cx) let mut compiled_module: *mut JSObject = ptr::null_mut());
1333 let compiled_module_result = module_tree.compile_module_script(
1334 &global,
1335 self.owner.clone(),
1336 resp_mod_script.text(),
1337 &self.url,
1338 self.options.clone(),
1339 compiled_module.handle_mut(),
1340 false,
1341 1, self.introduction_type,
1343 CanGc::note(),
1344 );
1345
1346 match compiled_module_result {
1347 Err(exception) => {
1348 module_tree.set_rethrow_error(exception);
1349 module_tree.advance_finished_and_link(&global, CanGc::note());
1350 },
1351 Ok(_) => {
1352 module_tree.set_record(ModuleObject::new(compiled_module.handle()));
1353
1354 module_tree.fetch_module_descendants(
1355 &self.owner,
1356 self.destination,
1357 &self.options,
1358 ModuleIdentity::ModuleUrl(self.url.clone()),
1359 CanGc::note(),
1360 );
1361 },
1362 }
1363 },
1364 }
1365
1366 if let Ok(response) = response {
1367 network_listener::submit_timing(&self, &response, CanGc::note());
1368 }
1369 }
1370
1371 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1372 let global = &self.resource_timing_global();
1373 global.report_csp_violations(violations, None, None);
1374 }
1375}
1376
1377impl ResourceTimingListener for ModuleContext {
1378 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1379 let initiator_type = InitiatorType::LocalName("module".to_string());
1380 (initiator_type, self.url.clone())
1381 }
1382
1383 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1384 self.owner.global()
1385 }
1386}
1387
1388#[allow(unsafe_code, non_snake_case)]
1389pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
1392 unsafe {
1393 if GetModuleResolveHook(rt).is_some() {
1394 return;
1395 }
1396
1397 SetModuleResolveHook(rt, Some(HostResolveImportedModule));
1398 SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
1399 SetScriptPrivateReferenceHooks(
1400 rt,
1401 Some(host_add_ref_top_level_script),
1402 Some(host_release_top_level_script),
1403 );
1404 SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
1405 }
1406}
1407
1408#[expect(unsafe_code)]
1409unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
1410 let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
1411 mem::forget(val.clone());
1412 mem::forget(val);
1413}
1414
1415#[expect(unsafe_code)]
1416unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
1417 let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
1418}
1419
1420#[expect(unsafe_code)]
1421pub(crate) unsafe extern "C" fn host_import_module_dynamically(
1424 cx: *mut JSContext,
1425 reference_private: RawHandleValue,
1426 specifier: RawHandle<*mut JSObject>,
1427 promise: RawHandle<*mut JSObject>,
1428) -> bool {
1429 let cx = unsafe { SafeJSContext::from_ptr(cx) };
1431 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1432 let global_scope = unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
1433 let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx);
1434
1435 if let Err(e) = fetch_an_import_module_script_graph(
1437 &global_scope,
1438 specifier,
1439 reference_private,
1440 promise,
1441 CanGc::note(),
1442 ) {
1443 unsafe { JS_SetPendingException(*cx, e.handle(), ExceptionStackBehavior::Capture) };
1444 return false;
1445 }
1446
1447 true
1448}
1449
1450#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
1451pub(crate) struct ScriptFetchOptions {
1453 #[no_trace]
1454 pub(crate) referrer: Referrer,
1455 pub(crate) integrity_metadata: String,
1456 #[no_trace]
1457 pub(crate) credentials_mode: CredentialsMode,
1458 pub(crate) cryptographic_nonce: String,
1459 #[no_trace]
1460 pub(crate) parser_metadata: ParserMetadata,
1461 #[no_trace]
1462 pub(crate) referrer_policy: ReferrerPolicy,
1463}
1464
1465impl ScriptFetchOptions {
1466 pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
1468 Self {
1469 cryptographic_nonce: String::new(),
1470 integrity_metadata: String::new(),
1471 referrer: global.get_referrer(),
1472 parser_metadata: ParserMetadata::NotParserInserted,
1473 credentials_mode: CredentialsMode::CredentialsSameOrigin,
1474 referrer_policy: ReferrerPolicy::EmptyString,
1475 }
1476 }
1477
1478 fn descendant_fetch_options(&self) -> ScriptFetchOptions {
1480 Self {
1481 referrer: self.referrer.clone(),
1482 integrity_metadata: String::new(),
1483 cryptographic_nonce: self.cryptographic_nonce.clone(),
1484 credentials_mode: self.credentials_mode,
1485 parser_metadata: self.parser_metadata,
1486 referrer_policy: self.referrer_policy,
1487 }
1488 }
1489}
1490
1491#[expect(unsafe_code)]
1492unsafe fn module_script_from_reference_private(
1493 reference_private: &RawHandle<JSVal>,
1494) -> Option<&ModuleScript> {
1495 if reference_private.get().is_undefined() {
1496 return None;
1497 }
1498 unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1499}
1500
1501#[expect(unsafe_code)]
1503fn fetch_an_import_module_script_graph(
1504 global: &GlobalScope,
1505 module_request: RawHandle<*mut JSObject>,
1506 reference_private: RawHandleValue,
1507 promise: Rc<Promise>,
1508 can_gc: CanGc,
1509) -> Result<(), RethrowError> {
1510 let cx = GlobalScope::get_cx();
1512 let specifier = unsafe {
1513 let jsstr = std::ptr::NonNull::new(GetModuleRequestSpecifier(*cx, module_request)).unwrap();
1514 DOMString::from_string(jsstr_to_string(*cx, jsstr))
1515 };
1516 let mut options = ScriptFetchOptions::default_classic_script(global);
1517 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1518 if let Some(data) = module_data {
1519 options = data.options.descendant_fetch_options();
1520 }
1521 let url = ModuleTree::resolve_module_specifier(global, module_data, specifier, can_gc);
1522
1523 if url.is_err() {
1525 let specifier_error = gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
1526 return Err(specifier_error);
1527 }
1528
1529 let dynamic_module_id = DynamicModuleId(Uuid::new_v4());
1530
1531 let owner = match unsafe { module_script_from_reference_private(&reference_private) } {
1533 Some(module_data) if module_data.owner.is_some() => module_data.owner.clone().unwrap(),
1534 _ => ModuleOwner::DynamicModule(Trusted::new(&DynamicModuleOwner::new(
1535 global,
1536 promise.clone(),
1537 dynamic_module_id,
1538 can_gc,
1539 ))),
1540 };
1541
1542 let dynamic_module = RootedTraceableBox::new(DynamicModule {
1543 promise,
1544 specifier: Heap::default(),
1545 referencing_private: Heap::default(),
1546 id: dynamic_module_id,
1547 });
1548 dynamic_module.specifier.set(module_request.get());
1549 dynamic_module
1550 .referencing_private
1551 .set(reference_private.get());
1552
1553 let url = url.unwrap();
1554
1555 let mut visited_urls = HashSet::new();
1556 visited_urls.insert(url.clone());
1557
1558 fetch_single_module_script(
1559 owner,
1560 url,
1561 visited_urls,
1562 Destination::Script,
1563 options,
1564 None,
1565 true,
1566 Some(dynamic_module),
1567 Some(IntroductionType::IMPORTED_MODULE),
1568 can_gc,
1569 );
1570 Ok(())
1571}
1572
1573#[allow(unsafe_code, non_snake_case)]
1574unsafe extern "C" fn HostResolveImportedModule(
1577 cx: *mut JSContext,
1578 reference_private: RawHandleValue,
1579 specifier: RawHandle<*mut JSObject>,
1580) -> *mut JSObject {
1581 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1582 let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1583
1584 let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1586 let jsstr =
1587 std::ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(cx, specifier) }).unwrap();
1588 let specifier = DOMString::from_string(unsafe { jsstr_to_string(cx, jsstr) });
1589 let url =
1590 ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier, CanGc::note());
1591
1592 assert!(url.is_ok());
1594
1595 let parsed_url = url.unwrap();
1596
1597 let module_map = global_scope.get_module_map().borrow();
1599
1600 let module_tree = module_map.get(&parsed_url);
1601
1602 assert!(module_tree.is_some());
1604
1605 let fetched_module_object = module_tree.unwrap().get_record().borrow();
1606
1607 assert!(fetched_module_object.is_some());
1609
1610 if let Some(record) = &*fetched_module_object {
1612 return record.handle().get();
1613 }
1614
1615 unreachable!()
1616}
1617
1618#[allow(unsafe_code, non_snake_case)]
1619unsafe extern "C" fn HostPopulateImportMeta(
1622 cx: *mut JSContext,
1623 reference_private: RawHandleValue,
1624 meta_object: RawHandle<*mut JSObject>,
1625) -> bool {
1626 let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1627 let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1628
1629 let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1631 Some(module_data) => module_data.base_url.clone(),
1632 None => global_scope.api_base_url(),
1633 };
1634
1635 let url_string = unsafe {
1636 JS_NewStringCopyN(
1637 cx,
1638 base_url.as_str().as_ptr() as *const _,
1639 base_url.as_str().len(),
1640 )
1641 };
1642 rooted!(in(cx) let url_string = url_string);
1643
1644 unsafe {
1646 JS_DefineProperty4(
1647 cx,
1648 meta_object,
1649 c"url".as_ptr(),
1650 url_string.handle().into_handle(),
1651 JSPROP_ENUMERATE.into(),
1652 )
1653 }
1654}
1655
1656pub(crate) fn fetch_external_module_script(
1658 owner: ModuleOwner,
1659 url: ServoUrl,
1660 destination: Destination,
1661 options: ScriptFetchOptions,
1662 can_gc: CanGc,
1663) {
1664 let mut visited_urls = HashSet::new();
1665 visited_urls.insert(url.clone());
1666
1667 fetch_single_module_script(
1669 owner,
1670 url,
1671 visited_urls,
1672 destination,
1673 options,
1674 None,
1675 true,
1676 None,
1677 Some(IntroductionType::SRC_SCRIPT),
1678 can_gc,
1679 )
1680}
1681
1682#[derive(JSTraceable, MallocSizeOf)]
1683#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1684pub(crate) struct DynamicModuleList {
1685 requests: Vec<RootedTraceableBox<DynamicModule>>,
1686
1687 #[ignore_malloc_size_of = "Define in uuid"]
1688 next_id: DynamicModuleId,
1689}
1690
1691impl DynamicModuleList {
1692 pub(crate) fn new() -> Self {
1693 Self {
1694 requests: vec![],
1695 next_id: DynamicModuleId(Uuid::new_v4()),
1696 }
1697 }
1698
1699 fn push(&mut self, mut module: RootedTraceableBox<DynamicModule>) -> DynamicModuleId {
1700 let id = self.next_id;
1701 self.next_id = DynamicModuleId(Uuid::new_v4());
1702 module.id = id;
1703 self.requests.push(module);
1704 id
1705 }
1706
1707 fn remove(&mut self, id: DynamicModuleId) -> RootedTraceableBox<DynamicModule> {
1708 let index = self
1709 .requests
1710 .iter()
1711 .position(|module| module.id == id)
1712 .expect("missing dynamic module");
1713 self.requests.remove(index)
1714 }
1715}
1716
1717#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1718#[derive(JSTraceable, MallocSizeOf)]
1719struct DynamicModule {
1720 #[conditional_malloc_size_of]
1721 promise: Rc<Promise>,
1722 #[ignore_malloc_size_of = "GC types are hard"]
1723 specifier: Heap<*mut JSObject>,
1724 #[ignore_malloc_size_of = "GC types are hard"]
1725 referencing_private: Heap<JSVal>,
1726 #[ignore_malloc_size_of = "Defined in uuid"]
1727 id: DynamicModuleId,
1728}
1729
1730#[allow(clippy::too_many_arguments)]
1732fn fetch_single_module_script(
1733 owner: ModuleOwner,
1734 url: ServoUrl,
1735 visited_urls: HashSet<ServoUrl>,
1736 destination: Destination,
1737 options: ScriptFetchOptions,
1738 parent_identity: Option<ModuleIdentity>,
1739 top_level_module_fetch: bool,
1740 dynamic_module: Option<RootedTraceableBox<DynamicModule>>,
1741 introduction_type: Option<&'static CStr>,
1742 can_gc: CanGc,
1743) {
1744 {
1745 let global = owner.global();
1747 let module_map = global.get_module_map().borrow();
1748
1749 debug!("Start to fetch {}", url);
1750
1751 if let Some(module_tree) = module_map.get(&url.clone()) {
1752 let status = module_tree.get_status();
1753
1754 debug!("Meet a fetched url {} and its status is {:?}", url, status);
1755
1756 match dynamic_module {
1757 Some(module) => module_tree.append_dynamic_module_handler(
1758 owner.clone(),
1759 ModuleIdentity::ModuleUrl(url.clone()),
1760 module,
1761 can_gc,
1762 ),
1763 None if top_level_module_fetch => module_tree.append_handler(
1764 owner.clone(),
1765 ModuleIdentity::ModuleUrl(url.clone()),
1766 options,
1767 can_gc,
1768 ),
1769 None => {},
1771 }
1772
1773 if let Some(parent_identity) = parent_identity {
1774 module_tree.insert_parent_identity(parent_identity);
1775 }
1776
1777 match status {
1778 ModuleStatus::Initial => unreachable!(
1779 "We have the module in module map so its status should not be `initial`"
1780 ),
1781 ModuleStatus::Fetching => {},
1783 ModuleStatus::FetchingDescendants | ModuleStatus::Finished => {
1785 module_tree.advance_finished_and_link(&global, can_gc);
1786 },
1787 }
1788
1789 return;
1790 }
1791 }
1792
1793 let global = owner.global();
1794 let is_external = true;
1795 let module_tree = ModuleTree::new(url.clone(), is_external, visited_urls);
1796 module_tree.set_status(ModuleStatus::Fetching);
1797
1798 match dynamic_module {
1799 Some(module) => module_tree.append_dynamic_module_handler(
1800 owner.clone(),
1801 ModuleIdentity::ModuleUrl(url.clone()),
1802 module,
1803 can_gc,
1804 ),
1805 None if top_level_module_fetch => module_tree.append_handler(
1806 owner.clone(),
1807 ModuleIdentity::ModuleUrl(url.clone()),
1808 options.clone(),
1809 can_gc,
1810 ),
1811 None => {},
1813 }
1814
1815 if let Some(parent_identity) = parent_identity {
1816 module_tree.insert_parent_identity(parent_identity);
1817 }
1818
1819 module_tree.insert_incomplete_fetch_url(&url);
1820
1821 global.set_module_map(url.clone(), module_tree);
1823
1824 let mode = match destination {
1826 Destination::Worker | Destination::SharedWorker if top_level_module_fetch => {
1827 RequestMode::SameOrigin
1828 },
1829 _ => RequestMode::CorsMode,
1830 };
1831
1832 let document: Option<DomRoot<Document>> = match &owner {
1833 ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1834 ModuleOwner::Window(script) => Some(script.root().owner_document()),
1835 };
1836 let webview_id = document.as_ref().map(|document| document.webview_id());
1837
1838 let request = RequestBuilder::new(webview_id, url.clone(), global.get_referrer())
1840 .destination(destination)
1841 .origin(global.origin().immutable().clone())
1842 .parser_metadata(options.parser_metadata)
1843 .integrity_metadata(options.integrity_metadata.clone())
1844 .credentials_mode(options.credentials_mode)
1845 .referrer_policy(options.referrer_policy)
1846 .mode(mode)
1847 .insecure_requests_policy(global.insecure_requests_policy())
1848 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_origin())
1849 .policy_container(global.policy_container().to_owned())
1850 .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1851
1852 let context = ModuleContext {
1853 owner,
1854 data: vec![],
1855 metadata: None,
1856 url: url.clone(),
1857 destination,
1858 options,
1859 status: Ok(()),
1860 introduction_type,
1861 };
1862
1863 let network_listener = NetworkListener::new(
1864 context,
1865 global.task_manager().networking_task_source().to_sendable(),
1866 );
1867 match document {
1868 Some(document) => {
1869 let request = document.prepare_request(request);
1870 document.loader_mut().fetch_async_with_callback(
1871 LoadType::Script(url),
1872 request,
1873 network_listener.into_callback(),
1874 );
1875 },
1876 None => global.fetch_with_network_listener(request, network_listener),
1877 }
1878}
1879
1880pub(crate) fn fetch_inline_module_script(
1882 owner: ModuleOwner,
1883 module_script_text: Rc<DOMString>,
1884 url: ServoUrl,
1885 script_id: ScriptId,
1886 options: ScriptFetchOptions,
1887 line_number: u64,
1888 can_gc: CanGc,
1889) {
1890 let global = owner.global();
1891 let is_external = false;
1892 let module_tree = ModuleTree::new(url.clone(), is_external, HashSet::new());
1893
1894 let cx = GlobalScope::get_cx();
1895 rooted!(in(*cx) let mut compiled_module: *mut JSObject = ptr::null_mut());
1896 let compiled_module_result = module_tree.compile_module_script(
1897 &global,
1898 owner.clone(),
1899 module_script_text,
1900 &url,
1901 options.clone(),
1902 compiled_module.handle_mut(),
1903 true,
1904 line_number,
1905 Some(IntroductionType::INLINE_SCRIPT),
1906 can_gc,
1907 );
1908
1909 match compiled_module_result {
1910 Ok(_) => {
1911 module_tree.append_handler(
1912 owner.clone(),
1913 ModuleIdentity::ScriptId(script_id),
1914 options.clone(),
1915 can_gc,
1916 );
1917 module_tree.set_record(ModuleObject::new(compiled_module.handle()));
1918
1919 global.set_inline_module_map(script_id, module_tree);
1923
1924 let inline_module_map = global.get_inline_module_map().borrow();
1928 let module_tree = inline_module_map.get(&script_id).unwrap().clone();
1929
1930 module_tree.fetch_module_descendants(
1931 &owner,
1932 Destination::Script,
1933 &options,
1934 ModuleIdentity::ScriptId(script_id),
1935 can_gc,
1936 );
1937 },
1938 Err(exception) => {
1939 module_tree.set_rethrow_error(exception);
1940 module_tree.set_status(ModuleStatus::Finished);
1941 global.set_inline_module_map(script_id, module_tree);
1942 owner.notify_owner_to_finish(ModuleIdentity::ScriptId(script_id), options, can_gc);
1943 },
1944 }
1945}
1946
1947pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1948pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1949
1950#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1952pub(crate) struct ResolvedModule {
1953 base_url: String,
1955 specifier: String,
1957 #[no_trace]
1959 specifier_url: Option<ServoUrl>,
1960}
1961
1962impl ResolvedModule {
1963 pub(crate) fn new(
1964 base_url: String,
1965 specifier: String,
1966 specifier_url: Option<ServoUrl>,
1967 ) -> Self {
1968 Self {
1969 base_url,
1970 specifier,
1971 specifier_url,
1972 }
1973 }
1974}
1975
1976#[derive(Default, JSTraceable, MallocSizeOf)]
1978pub(crate) struct ImportMap {
1979 #[no_trace]
1980 imports: ModuleSpecifierMap,
1981 #[no_trace]
1982 scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1983 #[no_trace]
1984 integrity: ModuleIntegrityMap,
1985}
1986
1987pub(crate) fn register_import_map(
1989 global: &GlobalScope,
1990 result: Fallible<ImportMap>,
1991 can_gc: CanGc,
1992) {
1993 match result {
1994 Ok(new_import_map) => {
1995 merge_existing_and_new_import_maps(global, new_import_map, can_gc);
1997 },
1998 Err(exception) => {
1999 throw_dom_exception(GlobalScope::get_cx(), global, exception.clone(), can_gc);
2002 },
2003 }
2004}
2005
2006fn merge_existing_and_new_import_maps(
2008 global: &GlobalScope,
2009 new_import_map: ImportMap,
2010 can_gc: CanGc,
2011) {
2012 let new_import_map_scopes = new_import_map.scopes;
2014
2015 let mut old_import_map = global.import_map_mut();
2017
2018 let mut new_import_map_imports = new_import_map.imports;
2020
2021 let resolved_module_set = global.resolved_module_set();
2022 for (scope_prefix, mut scope_imports) in new_import_map_scopes {
2024 for record in resolved_module_set.iter() {
2026 let prefix = scope_prefix.as_str();
2029 if prefix == record.base_url ||
2030 (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
2031 {
2032 scope_imports.retain(|key, val| {
2034 if *key == record.specifier ||
2039 (key.ends_with('\u{002f}') &&
2040 record.specifier.starts_with(key) &&
2041 (record.specifier_url.is_none() ||
2042 record
2043 .specifier_url
2044 .as_ref()
2045 .map(|u| u.is_special_scheme())
2046 .unwrap_or_default()))
2047 {
2048 Console::internal_warn(
2051 global,
2052 DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
2053 );
2054 false
2056 } else {
2057 true
2058 }
2059 })
2060 }
2061 }
2062
2063 if old_import_map.scopes.contains_key(&scope_prefix) {
2065 let merged_module_specifier_map = merge_module_specifier_maps(
2068 global,
2069 scope_imports,
2070 &old_import_map.scopes[&scope_prefix],
2071 can_gc,
2072 );
2073 old_import_map
2074 .scopes
2075 .insert(scope_prefix, merged_module_specifier_map);
2076 } else {
2077 old_import_map.scopes.insert(scope_prefix, scope_imports);
2079 }
2080 }
2081
2082 for (url, integrity) in &new_import_map.integrity {
2084 if old_import_map.integrity.contains_key(url) {
2086 Console::internal_warn(
2089 global,
2090 DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
2091 );
2092 continue;
2094 }
2095
2096 old_import_map
2098 .integrity
2099 .insert(url.clone(), integrity.clone());
2100 }
2101
2102 for record in resolved_module_set.iter() {
2104 new_import_map_imports.retain(|specifier, val| {
2106 if specifier.starts_with(&record.specifier) {
2108 Console::internal_warn(
2111 global,
2112 DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
2113 );
2114 false
2116 } else {
2117 true
2118 }
2119 });
2120 }
2121
2122 let merged_module_specifier_map = merge_module_specifier_maps(
2125 global,
2126 new_import_map_imports,
2127 &old_import_map.imports,
2128 can_gc,
2129 );
2130 old_import_map.imports = merged_module_specifier_map;
2131}
2132
2133fn merge_module_specifier_maps(
2135 global: &GlobalScope,
2136 new_map: ModuleSpecifierMap,
2137 old_map: &ModuleSpecifierMap,
2138 _can_gc: CanGc,
2139) -> ModuleSpecifierMap {
2140 let mut merged_map = old_map.clone();
2142
2143 for (specifier, url) in new_map {
2145 if old_map.contains_key(&specifier) {
2147 Console::internal_warn(
2150 global,
2151 DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
2152 );
2153
2154 continue;
2156 }
2157
2158 merged_map.insert(specifier, url);
2160 }
2161
2162 merged_map
2163}
2164
2165pub(crate) fn parse_an_import_map_string(
2167 module_owner: ModuleOwner,
2168 input: Rc<DOMString>,
2169 base_url: ServoUrl,
2170 can_gc: CanGc,
2171) -> Fallible<ImportMap> {
2172 let parsed: JsonValue = serde_json::from_str(&input.str())
2174 .map_err(|_| Error::Type("The value needs to be a JSON object.".to_owned()))?;
2175 let JsonValue::Object(mut parsed) = parsed else {
2178 return Err(Error::Type(
2179 "The top-level value needs to be a JSON object.".to_owned(),
2180 ));
2181 };
2182
2183 let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
2185 if let Some(imports) = parsed.get("imports") {
2187 let JsonValue::Object(imports) = imports else {
2190 return Err(Error::Type(
2191 "The \"imports\" top-level value needs to be a JSON object.".to_owned(),
2192 ));
2193 };
2194 sorted_and_normalized_imports = sort_and_normalize_module_specifier_map(
2197 &module_owner.global(),
2198 imports,
2199 &base_url,
2200 can_gc,
2201 );
2202 }
2203
2204 let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2206 if let Some(scopes) = parsed.get("scopes") {
2208 let JsonValue::Object(scopes) = scopes else {
2211 return Err(Error::Type(
2212 "The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
2213 ));
2214 };
2215 sorted_and_normalized_scopes =
2218 sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url, can_gc)?;
2219 }
2220
2221 let mut normalized_integrity = ModuleIntegrityMap::new();
2223 if let Some(integrity) = parsed.get("integrity") {
2225 let JsonValue::Object(integrity) = integrity else {
2228 return Err(Error::Type(
2229 "The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
2230 ));
2231 };
2232 normalized_integrity =
2235 normalize_module_integrity_map(&module_owner.global(), integrity, &base_url, can_gc);
2236 }
2237
2238 parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
2242 if !parsed.is_empty() {
2243 Console::internal_warn(
2244 &module_owner.global(),
2245 DOMString::from(
2246 "Invalid top-level key was present in the import map.
2247 Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
2248 ),
2249 );
2250 }
2251
2252 Ok(ImportMap {
2254 imports: sorted_and_normalized_imports,
2255 scopes: sorted_and_normalized_scopes,
2256 integrity: normalized_integrity,
2257 })
2258}
2259
2260fn sort_and_normalize_module_specifier_map(
2262 global: &GlobalScope,
2263 original_map: &JsonMap<String, JsonValue>,
2264 base_url: &ServoUrl,
2265 can_gc: CanGc,
2266) -> ModuleSpecifierMap {
2267 let mut normalized = ModuleSpecifierMap::new();
2269
2270 for (specifier_key, value) in original_map {
2272 let Some(normalized_specifier_key) =
2275 normalize_specifier_key(global, specifier_key, base_url, can_gc)
2276 else {
2277 continue;
2279 };
2280
2281 let JsonValue::String(value) = value else {
2283 Console::internal_warn(global, DOMString::from("Addresses need to be strings."));
2286
2287 normalized.insert(normalized_specifier_key, None);
2289 continue;
2291 };
2292
2293 let value = DOMString::from(value.as_str());
2295 let Some(address_url) = ModuleTree::resolve_url_like_module_specifier(&value, base_url)
2296 else {
2297 Console::internal_warn(
2301 global,
2302 DOMString::from(format!(
2303 "Value failed to resolve to module specifier: {value}"
2304 )),
2305 );
2306
2307 normalized.insert(normalized_specifier_key, None);
2309 continue;
2311 };
2312
2313 if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2316 Console::internal_warn(
2320 global,
2321 DOMString::from(format!(
2322 "Invalid address for specifier key '{specifier_key}': {address_url}.
2323 Since specifierKey ends with a slash, the address needs to as well."
2324 )),
2325 );
2326
2327 normalized.insert(normalized_specifier_key, None);
2329 continue;
2331 }
2332
2333 normalized.insert(normalized_specifier_key, Some(address_url));
2335 }
2336
2337 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2340 normalized
2341}
2342
2343fn sort_and_normalize_scopes(
2345 global: &GlobalScope,
2346 original_map: &JsonMap<String, JsonValue>,
2347 base_url: &ServoUrl,
2348 can_gc: CanGc,
2349) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2350 let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2352
2353 for (scope_prefix, potential_specifier_map) in original_map {
2355 let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2358 return Err(Error::Type(
2359 "The value of the scope with prefix scopePrefix needs to be a JSON object."
2360 .to_owned(),
2361 ));
2362 };
2363
2364 let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2366 Console::internal_warn(
2370 global,
2371 DOMString::from(format!(
2372 "Scope prefix URL was not parseable: {scope_prefix}"
2373 )),
2374 );
2375 continue;
2377 };
2378
2379 let normalized_scope_prefix = scope_prefix_url;
2381
2382 let normalized_specifier_map = sort_and_normalize_module_specifier_map(
2385 global,
2386 potential_specifier_map,
2387 base_url,
2388 can_gc,
2389 );
2390 normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2391 }
2392
2393 normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2396 Ok(normalized)
2397}
2398
2399fn normalize_module_integrity_map(
2401 global: &GlobalScope,
2402 original_map: &JsonMap<String, JsonValue>,
2403 base_url: &ServoUrl,
2404 _can_gc: CanGc,
2405) -> ModuleIntegrityMap {
2406 let mut normalized = ModuleIntegrityMap::new();
2408
2409 for (key, value) in original_map {
2411 let Some(resolved_url) =
2414 ModuleTree::resolve_url_like_module_specifier(&DOMString::from(key.as_str()), base_url)
2415 else {
2416 Console::internal_warn(
2420 global,
2421 DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
2422 );
2423 continue;
2425 };
2426
2427 let JsonValue::String(value) = value else {
2429 Console::internal_warn(
2432 global,
2433 DOMString::from("Integrity metadata values need to be strings."),
2434 );
2435 continue;
2437 };
2438
2439 normalized.insert(resolved_url, value.clone());
2441 }
2442
2443 normalized
2445}
2446
2447fn normalize_specifier_key(
2449 global: &GlobalScope,
2450 specifier_key: &str,
2451 base_url: &ServoUrl,
2452 _can_gc: CanGc,
2453) -> Option<String> {
2454 if specifier_key.is_empty() {
2456 Console::internal_warn(
2459 global,
2460 DOMString::from("Specifier keys may not be the empty string."),
2461 );
2462 return None;
2464 }
2465 let url =
2467 ModuleTree::resolve_url_like_module_specifier(&DOMString::from(specifier_key), base_url);
2468
2469 if let Some(url) = url {
2471 return Some(url.into_string());
2472 }
2473
2474 Some(specifier_key.to_string())
2476}
2477
2478pub(crate) fn resolve_imports_match(
2483 normalized_specifier: &str,
2484 as_url: Option<&ServoUrl>,
2485 specifier_map: &ModuleSpecifierMap,
2486 _can_gc: CanGc,
2487) -> Fallible<Option<ServoUrl>> {
2488 for (specifier_key, resolution_result) in specifier_map {
2490 if specifier_key == normalized_specifier {
2492 if let Some(resolution_result) = resolution_result {
2493 return Ok(Some(resolution_result.clone()));
2497 } else {
2498 return Err(Error::Type(
2500 "Resolution of specifierKey was blocked by a null entry.".to_owned(),
2501 ));
2502 }
2503 }
2504
2505 if specifier_key.ends_with('\u{002f}') &&
2510 normalized_specifier.starts_with(specifier_key) &&
2511 (as_url.is_none() || as_url.map(|u| u.is_special_scheme()).unwrap_or_default())
2512 {
2513 let Some(resolution_result) = resolution_result else {
2516 return Err(Error::Type(
2517 "Resolution of specifierKey was blocked by a null entry.".to_owned(),
2518 ));
2519 };
2520
2521 let after_prefix = normalized_specifier
2523 .strip_prefix(specifier_key)
2524 .expect("specifier_key should be the prefix of normalized_specifier");
2525
2526 debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2528
2529 let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2531
2532 let Ok(url) = url else {
2535 return Err(Error::Type(
2536 "Resolution of normalizedSpecifier was blocked since
2537 the afterPrefix portion could not be URL-parsed relative to
2538 the resolutionResult mapped to by the specifierKey prefix."
2539 .to_owned(),
2540 ));
2541 };
2542
2543 if !url.as_str().starts_with(resolution_result.as_str()) {
2546 return Err(Error::Type(
2547 "Resolution of normalizedSpecifier was blocked due to
2548 it backtracking above its prefix specifierKey."
2549 .to_owned(),
2550 ));
2551 }
2552
2553 return Ok(Some(url));
2555 }
2556 }
2557
2558 Ok(None)
2560}