script/
script_module.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5//! The script module mod contains common traits and structs
6//! related to `type=module` for script thread or worker threads.
7
8use std::cell::{OnceCell, RefCell};
9use std::ffi::CStr;
10use std::fmt::Debug;
11use std::rc::Rc;
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;
19use indexmap::map::Entry;
20use js::conversions::jsstr_to_string;
21use js::gc::MutableHandleValue;
22use js::jsapi::{
23    CompileJsonModule1, CompileModule1, ExceptionStackBehavior, GetModuleRequestSpecifier,
24    GetModuleRequestType, GetModuleResolveHook, Handle as RawHandle, HandleValue as RawHandleValue,
25    Heap, JS_ClearPendingException, JS_DefineProperty4, JS_NewStringCopyN, JSAutoRealm, JSContext,
26    JSObject, JSPROP_ENUMERATE, JSRuntime, ModuleErrorBehaviour, ModuleType,
27    SetModuleDynamicImportHook, SetModuleMetadataHook, SetModulePrivate, SetModuleResolveHook,
28    SetScriptPrivateReferenceHooks, ThrowOnModuleEvaluationFailure, Value,
29};
30use js::jsval::{JSVal, PrivateValue, UndefinedValue};
31use js::realm::CurrentRealm;
32use js::rust::wrappers::{
33    JS_GetPendingException, JS_SetPendingException, ModuleEvaluate, ModuleLink,
34};
35use js::rust::{
36    CompileOptionsWrapper, Handle, HandleObject as RustHandleObject, HandleValue, IntoHandle,
37    transform_str_to_source_text,
38};
39use mime::Mime;
40use net_traits::http_status::HttpStatus;
41use net_traits::mime_classifier::MimeClassifier;
42use net_traits::request::{
43    CredentialsMode, Destination, ParserMetadata, Referrer, RequestBuilder, RequestId, RequestMode,
44};
45use net_traits::{FetchMetadata, Metadata, NetworkError, ReferrerPolicy, ResourceFetchTiming};
46use script_bindings::domstring::BytesView;
47use script_bindings::error::Fallible;
48use script_bindings::trace::CustomTraceable;
49use serde_json::{Map as JsonMap, Value as JsonValue};
50use servo_url::ServoUrl;
51
52use crate::document_loader::LoadType;
53use crate::dom::bindings::cell::DomRefCell;
54use crate::dom::bindings::codegen::Bindings::WindowBinding::Window_Binding::WindowMethods;
55use crate::dom::bindings::error::{
56    Error, ErrorToJsval, report_pending_exception, throw_dom_exception,
57};
58use crate::dom::bindings::inheritance::Castable;
59use crate::dom::bindings::refcounted::Trusted;
60use crate::dom::bindings::reflector::{DomGlobal, DomObject};
61use crate::dom::bindings::root::DomRoot;
62use crate::dom::bindings::settings_stack::AutoIncumbentScript;
63use crate::dom::bindings::str::DOMString;
64use crate::dom::bindings::trace::RootedTraceableBox;
65use crate::dom::csp::{GlobalCspReporting, Violation};
66use crate::dom::document::Document;
67use crate::dom::element::Element;
68use crate::dom::globalscope::GlobalScope;
69use crate::dom::html::htmlscriptelement::{HTMLScriptElement, SCRIPT_JS_MIMES, Script};
70use crate::dom::htmlscriptelement::substitute_with_local_script;
71use crate::dom::node::NodeTraits;
72use crate::dom::performance::performanceresourcetiming::InitiatorType;
73use crate::dom::promise::Promise;
74use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
75use crate::dom::types::Console;
76use crate::dom::window::Window;
77use crate::dom::worker::TrustedWorkerAddress;
78use crate::fetch::RequestWithGlobalScope;
79use crate::module_loading::{
80    LoadState, Payload, host_load_imported_module, load_requested_modules,
81};
82use crate::network_listener::{
83    self, FetchResponseListener, NetworkListener, ResourceTimingListener,
84};
85use crate::realms::{AlreadyInRealm, InRealm, enter_realm};
86use crate::script_runtime::{CanGc, IntroductionType, JSContext as SafeJSContext};
87use crate::task::NonSendTaskBox;
88
89pub(crate) fn gen_type_error(global: &GlobalScope, error: Error, can_gc: CanGc) -> RethrowError {
90    rooted!(in(*GlobalScope::get_cx()) let mut thrown = UndefinedValue());
91    error.to_jsval(GlobalScope::get_cx(), global, thrown.handle_mut(), can_gc);
92
93    RethrowError(RootedTraceableBox::from_box(Heap::boxed(thrown.get())))
94}
95
96#[derive(JSTraceable)]
97pub(crate) struct ModuleObject(RootedTraceableBox<Heap<*mut JSObject>>);
98
99impl ModuleObject {
100    pub(crate) fn new(obj: RustHandleObject) -> ModuleObject {
101        ModuleObject(RootedTraceableBox::from_box(Heap::boxed(obj.get())))
102    }
103
104    pub(crate) fn handle(&'_ self) -> js::gc::HandleObject<'_> {
105        self.0.handle()
106    }
107}
108
109#[derive(JSTraceable)]
110pub(crate) struct RethrowError(RootedTraceableBox<Heap<JSVal>>);
111
112impl RethrowError {
113    pub(crate) fn new(val: Box<Heap<JSVal>>) -> Self {
114        Self(RootedTraceableBox::from_box(val))
115    }
116
117    #[expect(unsafe_code)]
118    pub(crate) fn from_pending_exception(cx: SafeJSContext) -> Self {
119        rooted!(in(*cx) let mut exception = UndefinedValue());
120        assert!(unsafe { JS_GetPendingException(*cx, exception.handle_mut()) });
121        unsafe { JS_ClearPendingException(*cx) };
122
123        Self::new(Heap::boxed(exception.get()))
124    }
125
126    pub(crate) fn handle(&self) -> Handle<'_, JSVal> {
127        self.0.handle()
128    }
129}
130
131impl Debug for RethrowError {
132    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
133        "RethrowError(...)".fmt(fmt)
134    }
135}
136
137impl Clone for RethrowError {
138    fn clone(&self) -> Self {
139        Self(RootedTraceableBox::from_box(Heap::boxed(self.0.get())))
140    }
141}
142
143pub(crate) struct ModuleScript {
144    pub(crate) base_url: ServoUrl,
145    pub(crate) options: ScriptFetchOptions,
146    owner: Option<ModuleOwner>,
147}
148
149impl ModuleScript {
150    pub(crate) fn new(
151        base_url: ServoUrl,
152        options: ScriptFetchOptions,
153        owner: Option<ModuleOwner>,
154    ) -> Self {
155        ModuleScript {
156            base_url,
157            options,
158            owner,
159        }
160    }
161}
162
163pub(crate) type ModuleRequest = (ServoUrl, ModuleType);
164
165#[derive(Clone, JSTraceable)]
166pub(crate) enum ModuleStatus {
167    Fetching(DomRefCell<Option<Rc<Promise>>>),
168    Loaded(Option<Rc<ModuleTree>>),
169}
170
171#[derive(JSTraceable, MallocSizeOf)]
172pub(crate) struct ModuleTree {
173    #[no_trace]
174    url: ServoUrl,
175    #[ignore_malloc_size_of = "mozjs"]
176    record: OnceCell<ModuleObject>,
177    #[ignore_malloc_size_of = "mozjs"]
178    parse_error: OnceCell<RethrowError>,
179    #[ignore_malloc_size_of = "mozjs"]
180    rethrow_error: DomRefCell<Option<RethrowError>>,
181    #[no_trace]
182    loaded_modules: DomRefCell<IndexMap<String, ServoUrl>>,
183}
184
185impl ModuleTree {
186    pub(crate) fn get_url(&self) -> ServoUrl {
187        self.url.clone()
188    }
189
190    pub(crate) fn get_record(&self) -> Option<&ModuleObject> {
191        self.record.get()
192    }
193
194    pub(crate) fn get_parse_error(&self) -> Option<&RethrowError> {
195        self.parse_error.get()
196    }
197
198    pub(crate) fn get_rethrow_error(&self) -> &DomRefCell<Option<RethrowError>> {
199        &self.rethrow_error
200    }
201
202    pub(crate) fn set_rethrow_error(&self, rethrow_error: RethrowError) {
203        *self.rethrow_error.borrow_mut() = Some(rethrow_error);
204    }
205
206    pub(crate) fn find_descendant_inside_module_map(
207        &self,
208        global: &GlobalScope,
209        specifier: &String,
210        module_type: ModuleType,
211    ) -> Option<Rc<ModuleTree>> {
212        self.loaded_modules
213            .borrow()
214            .get(specifier)
215            .and_then(|url| global.get_module_map_entry(&(url.clone(), module_type)))
216            .and_then(|status| match status {
217                ModuleStatus::Fetching(_) => None,
218                ModuleStatus::Loaded(module_tree) => module_tree,
219            })
220    }
221
222    pub(crate) fn insert_module_dependency(
223        &self,
224        module: &Rc<ModuleTree>,
225        module_request_specifier: String,
226    ) {
227        // Store the url which is used to retrieve the module from module map when needed.
228        let url = module.url.clone();
229        match self
230            .loaded_modules
231            .borrow_mut()
232            .entry(module_request_specifier)
233        {
234            // a. If referrer.[[LoadedModules]] contains a LoadedModuleRequest Record record such that
235            // ModuleRequestsEqual(record, moduleRequest) is true, then
236            Entry::Occupied(entry) => {
237                // i. Assert: record.[[Module]] and result.[[Value]] are the same Module Record.
238                assert_eq!(*entry.get(), url);
239            },
240            // b. Else,
241            Entry::Vacant(entry) => {
242                // i. Append the LoadedModuleRequest Record { [[Specifier]]: moduleRequest.[[Specifier]],
243                // [[Attributes]]: moduleRequest.[[Attributes]], [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]].
244                entry.insert(url);
245            },
246        }
247    }
248}
249
250struct ModuleSource {
251    source: Rc<DOMString>,
252    unminified_dir: Option<String>,
253    external: bool,
254    url: ServoUrl,
255}
256
257impl crate::unminify::ScriptSource for ModuleSource {
258    fn unminified_dir(&self) -> Option<String> {
259        self.unminified_dir.clone()
260    }
261
262    fn extract_bytes(&self) -> BytesView<'_> {
263        self.source.as_bytes()
264    }
265
266    fn rewrite_source(&mut self, source: Rc<DOMString>) {
267        self.source = source;
268    }
269
270    fn url(&self) -> ServoUrl {
271        self.url.clone()
272    }
273
274    fn is_external(&self) -> bool {
275        self.external
276    }
277}
278
279impl ModuleTree {
280    #[expect(unsafe_code)]
281    #[expect(clippy::too_many_arguments)]
282    /// <https://html.spec.whatwg.org/multipage/#creating-a-javascript-module-script>
283    /// Although the CanGc argument appears unused, it represents the GC operations that
284    /// can occur as part of compiling a script.
285    fn create_a_javascript_module_script(
286        source: Rc<DOMString>,
287        owner: ModuleOwner,
288        url: &ServoUrl,
289        options: ScriptFetchOptions,
290        external: bool,
291        line_number: u32,
292        introduction_type: Option<&'static CStr>,
293        _can_gc: CanGc,
294    ) -> Self {
295        let cx = GlobalScope::get_cx();
296        let global = owner.global();
297        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
298
299        // Step 2. Let script be a new module script that this algorithm will subsequently initialize.
300        // Step 6. Set script's parse error and error to rethrow to null.
301        let module = ModuleTree {
302            url: url.clone(),
303            record: OnceCell::new(),
304            parse_error: OnceCell::new(),
305            rethrow_error: DomRefCell::new(None),
306            loaded_modules: DomRefCell::new(IndexMap::new()),
307        };
308
309        let mut compile_options =
310            unsafe { CompileOptionsWrapper::new_raw(*cx, url.as_str(), line_number) };
311        if let Some(introduction_type) = introduction_type {
312            compile_options.set_introduction_type(introduction_type);
313        }
314        let mut module_source = ModuleSource {
315            source,
316            unminified_dir: global.unminified_js_dir(),
317            external,
318            url: url.clone(),
319        };
320        crate::unminify::unminify_js(&mut module_source);
321
322        unsafe {
323            // Step 7. Let result be ParseModule(source, settings's realm, script).
324            rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
325            module_script.set(CompileModule1(
326                *cx,
327                compile_options.ptr,
328                &mut transform_str_to_source_text(&module_source.source.str()),
329            ));
330
331            // Step 8. If result is a list of errors, then:
332            if module_script.is_null() {
333                warn!("fail to compile module script of {}", url);
334
335                // Step 8.1. Set script's parse error to result[0].
336                let _ = module
337                    .parse_error
338                    .set(RethrowError::from_pending_exception(cx));
339
340                // Step 8.2. Return script.
341                return module;
342            }
343
344            // Step 3. Set script's settings object to settings.
345            // Step 4. Set script's base URL to baseURL.
346            // Step 5. Set script's fetch options to options.
347            let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
348
349            SetModulePrivate(
350                module_script.get(),
351                &PrivateValue(Rc::into_raw(module_script_data) as *const _),
352            );
353
354            // Step 9. Set script's record to result.
355            let _ = module.record.set(ModuleObject::new(module_script.handle()));
356        }
357
358        // Step 10. Return script.
359        module
360    }
361
362    #[expect(unsafe_code)]
363    /// <https://html.spec.whatwg.org/multipage/#creating-a-json-module-script>
364    /// Although the CanGc argument appears unused, it represents the GC operations that
365    /// can occur as part of compiling a script.
366    fn crate_a_json_module_script(
367        source: &str,
368        global: &GlobalScope,
369        url: &ServoUrl,
370        introduction_type: Option<&'static CStr>,
371        _can_gc: CanGc,
372    ) -> Self {
373        let cx = GlobalScope::get_cx();
374        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
375
376        // Step 1. Let script be a new module script that this algorithm will subsequently initialize.
377        // Step 4. Set script's parse error and error to rethrow to null.
378        let module = ModuleTree {
379            url: url.clone(),
380            record: OnceCell::new(),
381            parse_error: OnceCell::new(),
382            rethrow_error: DomRefCell::new(None),
383            loaded_modules: DomRefCell::new(IndexMap::new()),
384        };
385
386        // Step 2. Set script's settings object to settings.
387        // Step 3. Set script's base URL and fetch options to null.
388        // Note: We don't need to call `SetModulePrivate` for json scripts
389
390        let mut compile_options = unsafe { CompileOptionsWrapper::new_raw(*cx, url.as_str(), 1) };
391        if let Some(introduction_type) = introduction_type {
392            compile_options.set_introduction_type(introduction_type);
393        }
394
395        rooted!(in(*cx) let mut module_script: *mut JSObject = std::ptr::null_mut());
396
397        unsafe {
398            // Step 5. Let result be ParseJSONModule(source).
399            module_script.set(CompileJsonModule1(
400                *cx,
401                compile_options.ptr,
402                &mut transform_str_to_source_text(source),
403            ));
404        }
405
406        // If this throws an exception, catch it, and set script's parse error to that exception, and return script.
407        if module_script.is_null() {
408            warn!("fail to compile module script of {}", url);
409
410            let _ = module
411                .parse_error
412                .set(RethrowError::from_pending_exception(cx));
413            return module;
414        }
415
416        // Step 6. Set script's record to result.
417        let _ = module.record.set(ModuleObject::new(module_script.handle()));
418
419        // Step 7. Return script.
420        module
421    }
422
423    #[expect(unsafe_code)]
424    /// <https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-and-link-a-module-script>
425    /// Step 5-2.
426    pub(crate) fn instantiate_module_tree(
427        global: &GlobalScope,
428        module_record: js::gc::HandleObject,
429    ) -> Result<(), RethrowError> {
430        let cx = GlobalScope::get_cx();
431        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
432
433        unsafe {
434            if !ModuleLink(*cx, module_record) {
435                warn!("fail to link & instantiate module");
436
437                rooted!(in(*cx) let mut exception = UndefinedValue());
438                assert!(JS_GetPendingException(*cx, exception.handle_mut()));
439                JS_ClearPendingException(*cx);
440
441                Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
442                    exception.get(),
443                ))))
444            } else {
445                debug!("module instantiated successfully");
446
447                Ok(())
448            }
449        }
450    }
451
452    /// Execute the provided module, storing the evaluation return value in the provided
453    /// mutable handle. Although the CanGc appears unused, it represents the GC operations
454    /// possible when evluating arbitrary JS.
455    #[expect(unsafe_code)]
456    pub(crate) fn execute_module(
457        &self,
458        global: &GlobalScope,
459        module_record: js::gc::HandleObject,
460        mut eval_result: MutableHandleValue,
461        _can_gc: CanGc,
462    ) -> Result<(), RethrowError> {
463        let cx = GlobalScope::get_cx();
464        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
465
466        unsafe {
467            let ok = ModuleEvaluate(*cx, module_record, eval_result.reborrow());
468            assert!(ok, "module evaluation failed");
469
470            rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
471            if eval_result.is_object() {
472                evaluation_promise.set(eval_result.to_object());
473            }
474
475            let throw_result = ThrowOnModuleEvaluationFailure(
476                *cx,
477                evaluation_promise.handle().into(),
478                ModuleErrorBehaviour::ThrowModuleErrorsSync,
479            );
480            if !throw_result {
481                warn!("fail to evaluate module");
482
483                rooted!(in(*cx) let mut exception = UndefinedValue());
484                assert!(JS_GetPendingException(*cx, exception.handle_mut()));
485                JS_ClearPendingException(*cx);
486
487                Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
488                    exception.get(),
489                ))))
490            } else {
491                debug!("module evaluated successfully");
492                Ok(())
493            }
494        }
495    }
496
497    #[expect(unsafe_code)]
498    pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
499        let module_error = self.rethrow_error.borrow();
500
501        if let Some(exception) = &*module_error {
502            let ar = enter_realm(global);
503            unsafe {
504                JS_SetPendingException(
505                    *GlobalScope::get_cx(),
506                    exception.handle(),
507                    ExceptionStackBehavior::Capture,
508                );
509            }
510            report_pending_exception(GlobalScope::get_cx(), true, InRealm::Entered(&ar), can_gc);
511        }
512    }
513
514    /// <https://html.spec.whatwg.org/multipage/#resolve-a-module-specifier>
515    pub(crate) fn resolve_module_specifier(
516        global: &GlobalScope,
517        script: Option<&ModuleScript>,
518        specifier: DOMString,
519        can_gc: CanGc,
520    ) -> Fallible<ServoUrl> {
521        // Step 1~3 to get settingsObject and baseURL
522        let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
523        // Step 1. Let settingsObject and baseURL be null.
524        let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
525            // Step 2. If referringScript is not null, then:
526            // Set settingsObject to referringScript's settings object.
527            // Set baseURL to referringScript's base URL.
528            Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
529            // Step 3. Otherwise:
530            // Set settingsObject to the current settings object.
531            // Set baseURL to settingsObject's API base URL.
532            // FIXME(#37553): Is this the correct current settings object?
533            None => (global, &global.api_base_url()),
534        };
535
536        // Step 4. Let importMap be an empty import map.
537        // Step 5. If settingsObject's global object implements Window, then set importMap to settingsObject's
538        // global object's import map.
539        let import_map = if global.is::<Window>() {
540            Some(global.import_map())
541        } else {
542            None
543        };
544
545        // Step 6. Let serializedBaseURL be baseURL, serialized.
546        let serialized_base_url = base_url.as_str();
547        // Step 7. Let asURL be the result of resolving a URL-like module specifier given specifier and baseURL.
548        let as_url = Self::resolve_url_like_module_specifier(&specifier, base_url);
549        // Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL is non-null;
550        // otherwise, specifier.
551        let specifier = specifier.str();
552        let normalized_specifier = match &as_url {
553            Some(url) => url.as_str(),
554            None => &specifier,
555        };
556
557        // Step 9. Let result be a URL-or-null, initially null.
558        let mut result = None;
559        if let Some(map) = import_map {
560            // Step 10. For each scopePrefix → scopeImports of importMap's scopes:
561            for (prefix, imports) in &map.scopes {
562                // Step 10.1 If scopePrefix is serializedBaseURL, or if scopePrefix ends with U+002F (/)
563                // and scopePrefix is a code unit prefix of serializedBaseURL, then:
564                let prefix = prefix.as_str();
565                if prefix == serialized_base_url ||
566                    (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
567                {
568                    // Step 10.1.1 Let scopeImportsMatch be the result of resolving an imports match
569                    // given normalizedSpecifier, asURL, and scopeImports.
570                    // Step 10.1.2 If scopeImportsMatch is not null, then set result to scopeImportsMatch,
571                    // and break.
572                    result = resolve_imports_match(
573                        normalized_specifier,
574                        as_url.as_ref(),
575                        imports,
576                        can_gc,
577                    )?;
578                    break;
579                }
580            }
581
582            // Step 11. If result is null, set result to the result of resolving an imports match given
583            // normalizedSpecifier, asURL, and importMap's imports.
584            if result.is_none() {
585                result = resolve_imports_match(
586                    normalized_specifier,
587                    as_url.as_ref(),
588                    &map.imports,
589                    can_gc,
590                )?;
591            }
592        }
593
594        // Step 12. If result is null, set it to asURL.
595        if result.is_none() {
596            result = as_url.clone();
597        }
598
599        // Step 13. If result is not null, then:
600        match result {
601            Some(result) => {
602                // Step 13.1 Add module to resolved module set given settingsObject, serializedBaseURL,
603                // normalizedSpecifier, and asURL.
604                global.add_module_to_resolved_module_set(
605                    serialized_base_url,
606                    normalized_specifier,
607                    as_url.clone(),
608                );
609                // Step 13.2 Return result.
610                Ok(result)
611            },
612            // Step 14. Throw a TypeError indicating that specifier was a bare specifier,
613            // but was not remapped to anything by importMap.
614            None => Err(Error::Type(
615                "Specifier was a bare specifier, but was not remapped to anything by importMap."
616                    .to_owned(),
617            )),
618        }
619    }
620
621    /// <https://html.spec.whatwg.org/multipage/#resolving-a-url-like-module-specifier>
622    fn resolve_url_like_module_specifier(
623        specifier: &DOMString,
624        base_url: &ServoUrl,
625    ) -> Option<ServoUrl> {
626        // Step 1. If specifier starts with "/", "./", or "../", then:
627        if specifier.starts_with('/') ||
628            specifier.starts_with_str("./") ||
629            specifier.starts_with_str("../")
630        {
631            // Step 1.1. Let url be the result of URL parsing specifier with baseURL.
632            return ServoUrl::parse_with_base(Some(base_url), &specifier.str()).ok();
633        }
634        // Step 2. Let url be the result of URL parsing specifier (with no base URL).
635        ServoUrl::parse(&specifier.str()).ok()
636    }
637}
638
639#[derive(JSTraceable, MallocSizeOf)]
640pub(crate) struct ModuleHandler {
641    #[ignore_malloc_size_of = "Measuring trait objects is hard"]
642    task: DomRefCell<Option<Box<dyn NonSendTaskBox>>>,
643}
644
645impl ModuleHandler {
646    pub(crate) fn new_boxed(task: Box<dyn NonSendTaskBox>) -> Box<dyn Callback> {
647        Box::new(Self {
648            task: DomRefCell::new(Some(task)),
649        })
650    }
651}
652
653impl Callback for ModuleHandler {
654    fn callback(&self, cx: &mut CurrentRealm, _v: HandleValue) {
655        let task = self.task.borrow_mut().take().unwrap();
656        task.run_box(cx);
657    }
658}
659
660/// The owner of the module
661/// It can be `worker` or `script` element
662#[derive(Clone, JSTraceable)]
663pub(crate) enum ModuleOwner {
664    #[expect(dead_code)]
665    Worker(TrustedWorkerAddress),
666    Window(Trusted<HTMLScriptElement>),
667    DynamicModule(Trusted<GlobalScope>),
668}
669
670impl ModuleOwner {
671    pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
672        match &self {
673            ModuleOwner::Worker(worker) => (*worker.root().clone()).global(),
674            ModuleOwner::Window(script) => (*script.root()).global(),
675            ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
676        }
677    }
678
679    fn notify_owner_to_finish(&self, module_tree: Option<Rc<ModuleTree>>, can_gc: CanGc) {
680        match &self {
681            ModuleOwner::Worker(_) => unimplemented!(),
682            ModuleOwner::DynamicModule(_) => unimplemented!(),
683            ModuleOwner::Window(script) => {
684                let document = script.root().owner_document();
685
686                let load = match module_tree {
687                    Some(module_tree) => Ok(Script::Module(module_tree)),
688                    None => Err(()),
689                };
690
691                let asynch = script
692                    .root()
693                    .upcast::<Element>()
694                    .has_attribute(&local_name!("async"));
695
696                if !asynch && (*script.root()).get_parser_inserted() {
697                    document.deferred_script_loaded(&script.root(), load, can_gc);
698                } else if !asynch && !(*script.root()).get_non_blocking() {
699                    document.asap_in_order_script_loaded(&script.root(), load, can_gc);
700                } else {
701                    document.asap_script_loaded(&script.root(), load, can_gc);
702                };
703            },
704        }
705    }
706}
707
708/// The context required for asynchronously loading an external module script source.
709struct ModuleContext {
710    /// The owner of the module that initiated the request.
711    owner: ModuleOwner,
712    /// The response body received to date.
713    data: Vec<u8>,
714    /// The response metadata received to date.
715    metadata: Option<Metadata>,
716    /// Url and type of the requested module.
717    module_request: ModuleRequest,
718    /// Options for the current script fetch
719    options: ScriptFetchOptions,
720    /// Indicates whether the request failed, and why
721    status: Result<(), NetworkError>,
722    /// `introductionType` value to set in the `CompileOptionsWrapper`.
723    introduction_type: Option<&'static CStr>,
724}
725
726impl FetchResponseListener for ModuleContext {
727    // TODO(cybai): Perhaps add custom steps to perform fetch here?
728    fn process_request_body(&mut self, _: RequestId) {}
729
730    // TODO(cybai): Perhaps add custom steps to perform fetch here?
731    fn process_request_eof(&mut self, _: RequestId) {}
732
733    fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
734        self.metadata = metadata.ok().map(|meta| match meta {
735            FetchMetadata::Unfiltered(m) => m,
736            FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
737        });
738
739        let status = self
740            .metadata
741            .as_ref()
742            .map(|m| m.status.clone())
743            .unwrap_or_else(HttpStatus::new_error);
744
745        self.status = {
746            if status.is_error() {
747                Err(NetworkError::ResourceLoadError(
748                    "No http status code received".to_owned(),
749                ))
750            } else if status.is_success() {
751                Ok(())
752            } else {
753                Err(NetworkError::ResourceLoadError(format!(
754                    "HTTP error code {}",
755                    status.code()
756                )))
757            }
758        };
759    }
760
761    fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
762        if self.status.is_ok() {
763            self.data.append(&mut chunk);
764        }
765    }
766
767    /// <https://html.spec.whatwg.org/multipage/#fetch-a-single-module-script>
768    /// Step 13
769    fn process_response_eof(
770        mut self,
771        _: RequestId,
772        response: Result<(), NetworkError>,
773        timing: ResourceFetchTiming,
774    ) {
775        let global = self.owner.global();
776        let (url, module_type) = &self.module_request;
777
778        if let Some(window) = global.downcast::<Window>() {
779            window
780                .Document()
781                .finish_load(LoadType::Script(url.clone()), CanGc::note());
782        }
783
784        network_listener::submit_timing(&self, &response, &timing, CanGc::note());
785
786        let Some(ModuleStatus::Fetching(pending)) =
787            global.get_module_map_entry(&self.module_request)
788        else {
789            return error!("Processing response for a non pending module request");
790        };
791        let promise = pending
792            .borrow_mut()
793            .take()
794            .expect("Need promise to process response");
795
796        // Step 1. If any of the following are true: bodyBytes is null or failure; or response's status is not an ok status,
797        // then set moduleMap[(url, moduleType)] to null, run onComplete given null, and abort these steps.
798        if let (Err(error), _) | (_, Err(error)) = (response.as_ref(), self.status.as_ref()) {
799            error!("Fetching module script failed {:?}", error);
800            global.set_module_map(self.module_request, ModuleStatus::Loaded(None));
801            return promise.resolve_native(&(), CanGc::note());
802        }
803
804        let metadata = self.metadata.take().unwrap();
805        let final_url = metadata.final_url;
806
807        // Step 2. Let mimeType be the result of extracting a MIME type from response's header list.
808        let mime_type: Option<Mime> = metadata.content_type.map(Serde::into_inner).map(Into::into);
809
810        // Step 3. Let moduleScript be null.
811        let mut module_script = None;
812
813        // Step 4. Let referrerPolicy be the result of parsing the `Referrer-Policy` header given response. [REFERRERPOLICY]
814        let referrer_policy = metadata
815            .headers
816            .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
817            .into();
818
819        // Step 5. If referrerPolicy is not the empty string, set options's referrer policy to referrerPolicy.
820        if referrer_policy != ReferrerPolicy::EmptyString {
821            self.options.referrer_policy = referrer_policy;
822        }
823
824        // TODO Step 6. If mimeType's essence is "application/wasm" and moduleType is "javascript-or-wasm", then set
825        // moduleScript to the result of creating a WebAssembly module script given bodyBytes, settingsObject, response's URL, and options.
826
827        // TODO handle CSS module scripts on the next mozjs ESR bump.
828
829        if let Some(mime) = mime_type {
830            // Step 7.1 Let sourceText be the result of UTF-8 decoding bodyBytes.
831            let (mut source_text, _, _) = UTF_8.decode(&self.data);
832
833            // Step 7.2 If mimeType is a JavaScript MIME type and moduleType is "javascript-or-wasm", then set moduleScript
834            // to the result of creating a JavaScript module script given sourceText, settingsObject, response's URL, and options.
835            if SCRIPT_JS_MIMES.contains(&mime.essence_str()) &&
836                matches!(module_type, ModuleType::JavaScript)
837            {
838                if let Some(window) = global.downcast::<Window>() {
839                    substitute_with_local_script(window, &mut source_text, final_url.clone());
840                }
841
842                let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
843                    Rc::new(DOMString::from(source_text.clone())),
844                    self.owner.clone(),
845                    &final_url,
846                    self.options,
847                    true,
848                    1,
849                    self.introduction_type,
850                    CanGc::note(),
851                ));
852                module_script = Some(module_tree);
853            }
854
855            // Step 7.4 If mimeType is a JSON MIME type and moduleType is "json",
856            // then set moduleScript to the result of creating a JSON module script given sourceText and settingsObject.
857            if MimeClassifier::is_json(&mime) && matches!(module_type, ModuleType::JSON) {
858                let module_tree = Rc::new(ModuleTree::crate_a_json_module_script(
859                    &source_text,
860                    &global,
861                    &final_url,
862                    self.introduction_type,
863                    CanGc::note(),
864                ));
865                module_script = Some(module_tree);
866            }
867        }
868        // Step 8. Set moduleMap[(url, moduleType)] to moduleScript, and run onComplete given moduleScript.
869        global.set_module_map(self.module_request, ModuleStatus::Loaded(module_script));
870        promise.resolve_native(&(), CanGc::note());
871    }
872
873    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
874        let global = &self.resource_timing_global();
875        global.report_csp_violations(violations, None, None);
876    }
877}
878
879impl ResourceTimingListener for ModuleContext {
880    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
881        let initiator_type = InitiatorType::LocalName("module".to_string());
882        let (url, _) = &self.module_request;
883        (initiator_type, url.clone())
884    }
885
886    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
887        self.owner.global()
888    }
889}
890
891#[expect(unsafe_code)]
892#[expect(non_snake_case)]
893/// A function to register module hooks (e.g. listening on resolving modules,
894/// getting module metadata, getting script private reference and resolving dynamic import)
895pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
896    unsafe {
897        if GetModuleResolveHook(rt).is_some() {
898            return;
899        }
900
901        SetModuleResolveHook(rt, Some(HostResolveImportedModule));
902        SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
903        SetScriptPrivateReferenceHooks(
904            rt,
905            Some(host_add_ref_top_level_script),
906            Some(host_release_top_level_script),
907        );
908        SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
909    }
910}
911
912#[expect(unsafe_code)]
913unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
914    let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
915    mem::forget(val.clone());
916    mem::forget(val);
917}
918
919#[expect(unsafe_code)]
920unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
921    let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
922}
923
924#[expect(unsafe_code)]
925/// <https://tc39.es/ecma262/#sec-hostimportmoduledynamically>
926/// <https://html.spec.whatwg.org/multipage/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)>
927pub(crate) unsafe extern "C" fn host_import_module_dynamically(
928    cx: *mut JSContext,
929    reference_private: RawHandleValue,
930    specifier: RawHandle<*mut JSObject>,
931    promise: RawHandle<*mut JSObject>,
932) -> bool {
933    // Step 1.
934    let cx = unsafe { SafeJSContext::from_ptr(cx) };
935    let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx);
936
937    let jsstr = ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(*cx, specifier) }).unwrap();
938    let module_type = unsafe { GetModuleRequestType(*cx, specifier) };
939    let specifier = unsafe { jsstr_to_string(*cx, jsstr) };
940
941    let payload = Payload::PromiseRecord(promise.clone());
942    host_load_imported_module(
943        cx,
944        None,
945        reference_private,
946        specifier,
947        module_type,
948        None,
949        payload,
950    );
951
952    true
953}
954
955#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
956/// <https://html.spec.whatwg.org/multipage/#script-fetch-options>
957pub(crate) struct ScriptFetchOptions {
958    #[no_trace]
959    pub(crate) referrer: Referrer,
960    pub(crate) integrity_metadata: String,
961    #[no_trace]
962    pub(crate) credentials_mode: CredentialsMode,
963    pub(crate) cryptographic_nonce: String,
964    #[no_trace]
965    pub(crate) parser_metadata: ParserMetadata,
966    #[no_trace]
967    pub(crate) referrer_policy: ReferrerPolicy,
968}
969
970impl ScriptFetchOptions {
971    /// <https://html.spec.whatwg.org/multipage/#default-classic-script-fetch-options>
972    pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
973        Self {
974            cryptographic_nonce: String::new(),
975            integrity_metadata: String::new(),
976            referrer: global.get_referrer(),
977            parser_metadata: ParserMetadata::NotParserInserted,
978            credentials_mode: CredentialsMode::CredentialsSameOrigin,
979            referrer_policy: ReferrerPolicy::EmptyString,
980        }
981    }
982
983    /// <https://html.spec.whatwg.org/multipage/#descendant-script-fetch-options>
984    pub(crate) fn descendant_fetch_options(&self) -> ScriptFetchOptions {
985        Self {
986            referrer: self.referrer.clone(),
987            integrity_metadata: String::new(),
988            cryptographic_nonce: self.cryptographic_nonce.clone(),
989            credentials_mode: self.credentials_mode,
990            parser_metadata: self.parser_metadata,
991            referrer_policy: self.referrer_policy,
992        }
993    }
994}
995
996#[expect(unsafe_code)]
997pub(crate) unsafe fn module_script_from_reference_private(
998    reference_private: &RawHandle<JSVal>,
999) -> Option<&ModuleScript> {
1000    if reference_private.get().is_undefined() {
1001        return None;
1002    }
1003    unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1004}
1005
1006#[expect(unsafe_code)]
1007#[expect(non_snake_case)]
1008/// <https://tc39.es/ecma262/#sec-HostLoadImportedModule>
1009/// <https://html.spec.whatwg.org/multipage/#hostloadimportedmodule>
1010unsafe extern "C" fn HostResolveImportedModule(
1011    cx: *mut JSContext,
1012    reference_private: RawHandleValue,
1013    specifier: RawHandle<*mut JSObject>,
1014) -> *mut JSObject {
1015    let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1016    let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1017
1018    // Step 5.
1019    let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1020    let jsstr =
1021        std::ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(cx, specifier) }).unwrap();
1022    let module_type = unsafe { GetModuleRequestType(cx, specifier) };
1023
1024    let specifier = DOMString::from_string(unsafe { jsstr_to_string(cx, jsstr) });
1025    let url =
1026        ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier, CanGc::note());
1027
1028    // Step 6.
1029    assert!(url.is_ok());
1030
1031    let parsed_url = url.unwrap();
1032
1033    // Step 4 & 7.
1034    let module = global_scope.get_module_map_entry(&(parsed_url, module_type));
1035
1036    // Step 9.
1037    assert!(module.as_ref().is_some_and(
1038        |status| matches!(status, ModuleStatus::Loaded(module_tree) if module_tree.is_some())
1039    ));
1040
1041    let ModuleStatus::Loaded(Some(module_tree)) = module.unwrap() else {
1042        unreachable!()
1043    };
1044
1045    let fetched_module_object = module_tree.get_record();
1046
1047    // Step 8.
1048    assert!(fetched_module_object.is_some());
1049
1050    // Step 10.
1051    if let Some(record) = fetched_module_object {
1052        return record.handle().get();
1053    }
1054
1055    unreachable!()
1056}
1057
1058#[expect(unsafe_code)]
1059#[expect(non_snake_case)]
1060/// <https://tc39.es/ecma262/#sec-hostgetimportmetaproperties>
1061/// <https://html.spec.whatwg.org/multipage/#hostgetimportmetaproperties>
1062unsafe extern "C" fn HostPopulateImportMeta(
1063    cx: *mut JSContext,
1064    reference_private: RawHandleValue,
1065    meta_object: RawHandle<*mut JSObject>,
1066) -> bool {
1067    let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1068    let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1069
1070    // Step 2.
1071    let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1072        Some(module_data) => module_data.base_url.clone(),
1073        None => global_scope.api_base_url(),
1074    };
1075
1076    let url_string = unsafe {
1077        JS_NewStringCopyN(
1078            cx,
1079            base_url.as_str().as_ptr() as *const _,
1080            base_url.as_str().len(),
1081        )
1082    };
1083    rooted!(in(cx) let url_string = url_string);
1084
1085    // Step 3.
1086    unsafe {
1087        JS_DefineProperty4(
1088            cx,
1089            meta_object,
1090            c"url".as_ptr(),
1091            url_string.handle().into_handle(),
1092            JSPROP_ENUMERATE.into(),
1093        )
1094    }
1095}
1096
1097/// <https://html.spec.whatwg.org/multipage/#fetch-a-module-script-tree>
1098pub(crate) fn fetch_an_external_module_script(
1099    url: ServoUrl,
1100    owner: ModuleOwner,
1101    options: ScriptFetchOptions,
1102    can_gc: CanGc,
1103) {
1104    let referrer = owner.global().get_referrer();
1105
1106    // Step 1. Fetch a single module script given url, settingsObject, "script", options, settingsObject, "client", true,
1107    // and with the following steps given result:
1108    fetch_a_single_module_script(
1109        url,
1110        owner.clone(),
1111        Destination::Script,
1112        options,
1113        referrer,
1114        None,
1115        true,
1116        Some(IntroductionType::SRC_SCRIPT),
1117        move |module_tree| {
1118            let Some(module) = module_tree else {
1119                // Step 1.1. If result is null, run onComplete given null, and abort these steps.
1120                return owner.notify_owner_to_finish(None, can_gc);
1121            };
1122
1123            // Step 1.2. Fetch the descendants of and link result given settingsObject, "script", and onComplete.
1124            fetch_the_descendants_and_link_module_script(
1125                module,
1126                Destination::Script,
1127                owner,
1128                can_gc,
1129            );
1130        },
1131    );
1132}
1133
1134/// <https://html.spec.whatwg.org/multipage/#fetch-an-inline-module-script-graph>
1135pub(crate) fn fetch_inline_module_script(
1136    owner: ModuleOwner,
1137    module_script_text: Rc<DOMString>,
1138    url: ServoUrl,
1139    options: ScriptFetchOptions,
1140    line_number: u32,
1141    can_gc: CanGc,
1142) {
1143    // Step 1. Let script be the result of creating a JavaScript module script using sourceText, settingsObject, baseURL, and options.
1144    let module_tree = Rc::new(ModuleTree::create_a_javascript_module_script(
1145        module_script_text,
1146        owner.clone(),
1147        &url,
1148        options,
1149        false,
1150        line_number,
1151        Some(IntroductionType::INLINE_SCRIPT),
1152        can_gc,
1153    ));
1154
1155    // Step 2. Fetch the descendants of and link script, given settingsObject, "script", and onComplete.
1156    fetch_the_descendants_and_link_module_script(module_tree, Destination::Script, owner, can_gc);
1157}
1158
1159/// <https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-and-link-a-module-script>
1160fn fetch_the_descendants_and_link_module_script(
1161    module_script: Rc<ModuleTree>,
1162    destination: Destination,
1163    owner: ModuleOwner,
1164    can_gc: CanGc,
1165) {
1166    let global = owner.global();
1167
1168    // Step 1. Let record be moduleScript's record.
1169    // Step 2. If record is null, then:
1170    if module_script.get_record().is_none() {
1171        let parse_error = module_script.get_parse_error().cloned();
1172
1173        // Step 2.1. Set moduleScript's error to rethrow to moduleScript's parse error.
1174        module_script.set_rethrow_error(parse_error.unwrap());
1175
1176        // Step 2.2. Run onComplete given moduleScript.
1177        owner.notify_owner_to_finish(Some(module_script), can_gc);
1178
1179        // Step 2.3. Return.
1180        return;
1181    }
1182
1183    // Step 3. Let state be Record
1184    // { [[ErrorToRethrow]]: null, [[Destination]]: destination, [[PerformFetch]]: null, [[FetchClient]]: fetchClient }.
1185    let state = Rc::new(LoadState {
1186        error_to_rethrow: RefCell::new(None),
1187        destination,
1188        fetch_client: owner.clone(),
1189    });
1190
1191    // TODO Step 4. If performFetch was given, set state.[[PerformFetch]] to performFetch.
1192
1193    // Step 5. Let loadingPromise be record.LoadRequestedModules(state).
1194    let loading_promise =
1195        load_requested_modules(&global, module_script.clone(), Some(Rc::clone(&state)));
1196
1197    let fulfillment_owner = owner.clone();
1198    let fulfilled_module = module_script.clone();
1199
1200    // Step 6. Upon fulfillment of loadingPromise, run the following steps:
1201    let loading_promise_fulfillment = ModuleHandler::new_boxed(Box::new(
1202        task!(fulfilled_steps: |fulfillment_owner: ModuleOwner| {
1203            let global = fulfillment_owner.global();
1204
1205            if let Some(record) = fulfilled_module.get_record() {
1206                // Step 6.1. Perform record.Link().
1207                let instantiated = ModuleTree::instantiate_module_tree(&global, record.handle());
1208
1209                // If this throws an exception, catch it, and set moduleScript's error to rethrow to that exception.
1210                if let Err(exception) = instantiated {
1211                    fulfilled_module.set_rethrow_error(exception);
1212                }
1213            }
1214
1215            // Step 6.2. Run onComplete given moduleScript.
1216            fulfillment_owner.notify_owner_to_finish(Some(fulfilled_module), CanGc::note());
1217        }),
1218    ));
1219
1220    let rejection_owner = owner.clone();
1221    let rejected_module = module_script.clone();
1222
1223    // Step 7. Upon rejection of loadingPromise, run the following steps:
1224    let loading_promise_rejection = ModuleHandler::new_boxed(Box::new(
1225        task!(rejected_steps: |rejection_owner: ModuleOwner, state: Rc<LoadState>| {
1226            // Step 7.1. If state.[[ErrorToRethrow]] is not null, set moduleScript's error to rethrow to state.[[ErrorToRethrow]]
1227            // and run onComplete given moduleScript.
1228            if let Some(error) = state.error_to_rethrow.borrow().as_ref() {
1229                rejected_module.set_rethrow_error(error.clone());
1230                rejection_owner.notify_owner_to_finish(Some(rejected_module), CanGc::note());
1231            } else {
1232                // Step 7.2. Otherwise, run onComplete given null.
1233                rejection_owner.notify_owner_to_finish(None, CanGc::note());
1234            }
1235        }),
1236    ));
1237
1238    let handler = PromiseNativeHandler::new(
1239        &global,
1240        Some(loading_promise_fulfillment),
1241        Some(loading_promise_rejection),
1242        can_gc,
1243    );
1244
1245    let realm = enter_realm(&*global);
1246    let comp = InRealm::Entered(&realm);
1247    let _ais = AutoIncumbentScript::new(&global);
1248
1249    loading_promise.append_native_handler(&handler, comp, can_gc);
1250}
1251
1252/// <https://html.spec.whatwg.org/multipage/#fetch-a-single-module-script>
1253#[expect(clippy::too_many_arguments)]
1254pub(crate) fn fetch_a_single_module_script(
1255    url: ServoUrl,
1256    owner: ModuleOwner,
1257    destination: Destination,
1258    options: ScriptFetchOptions,
1259    referrer: Referrer,
1260    module_type: Option<ModuleType>,
1261    is_top_level: bool,
1262    introduction_type: Option<&'static CStr>,
1263    on_complete: impl FnOnce(Option<Rc<ModuleTree>>) + 'static,
1264) {
1265    let global = owner.global();
1266
1267    // Step 1. Let moduleType be "javascript-or-wasm".
1268    // Step 2. If moduleRequest was given, then set moduleType to the result of running the
1269    // module type from module request steps given moduleRequest.
1270    let module_type = module_type.unwrap_or(ModuleType::JavaScript);
1271
1272    // TODO Step 3. Assert: the result of running the module type allowed steps given moduleType and settingsObject is true.
1273    // Otherwise, we would not have reached this point because a failure would have been raised
1274    // when inspecting moduleRequest.[[Attributes]] in HostLoadImportedModule or fetch a single imported module script.
1275
1276    // Step 4. Let moduleMap be settingsObject's module map.
1277    let module_request = (url.clone(), module_type);
1278    let entry = global.get_module_map_entry(&module_request);
1279
1280    let pending = match entry {
1281        Some(ModuleStatus::Fetching(pending)) => pending,
1282        // Step 6. If moduleMap[(url, moduleType)] exists, run onComplete given moduleMap[(url, moduleType)], and return.
1283        Some(ModuleStatus::Loaded(module_tree)) => {
1284            return on_complete(module_tree);
1285        },
1286        None => DomRefCell::new(None),
1287    };
1288
1289    let global_scope = DomRoot::from_ref(&*global);
1290    let module_map_key = module_request.clone();
1291    let handler = ModuleHandler::new_boxed(Box::new(
1292        task!(fetch_completed: |global_scope: DomRoot<GlobalScope>| {
1293            let key = module_map_key;
1294            let module = global_scope.get_module_map_entry(&key);
1295
1296            if let Some(ModuleStatus::Loaded(module_tree)) = module {
1297                on_complete(module_tree);
1298            }
1299        }),
1300    ));
1301
1302    let handler = PromiseNativeHandler::new(&global, Some(handler), None, CanGc::note());
1303
1304    let realm = enter_realm(&*global);
1305    let comp = InRealm::Entered(&realm);
1306    let _ais = AutoIncumbentScript::new(&global);
1307
1308    let has_pending_fetch = pending.borrow().is_some();
1309    pending
1310        .borrow_mut()
1311        .get_or_insert_with(|| Promise::new_in_current_realm(comp, CanGc::note()))
1312        .append_native_handler(&handler, comp, CanGc::note());
1313
1314    // Step 5. If moduleMap[(url, moduleType)] is "fetching", wait in parallel until that entry's value changes,
1315    // then queue a task on the networking task source to proceed with running the following steps.
1316    if has_pending_fetch {
1317        return;
1318    }
1319
1320    // Step 7. Set moduleMap[(url, moduleType)] to "fetching".
1321    global.set_module_map(module_request.clone(), ModuleStatus::Fetching(pending));
1322
1323    let document: Option<DomRoot<Document>> = match &owner {
1324        ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1325        ModuleOwner::Window(script) => Some(script.root().owner_document()),
1326    };
1327    let webview_id = document.as_ref().map(|document| document.webview_id());
1328
1329    // Step 8. Let request be a new request whose URL is url, mode is "cors", referrer is referrer, and client is fetchClient.
1330
1331    // Step 10. If destination is "worker", "sharedworker", or "serviceworker", and isTopLevel is true,
1332    // then set request's mode to "same-origin".
1333    let mode = match destination {
1334        Destination::Worker | Destination::SharedWorker if is_top_level => RequestMode::SameOrigin,
1335        _ => RequestMode::CorsMode,
1336    };
1337
1338    // Step 9. Set request's destination to the result of running the fetch destination from module type steps given destination and moduleType.
1339    let destination = match module_type {
1340        ModuleType::JSON => Destination::Json,
1341        ModuleType::JavaScript | ModuleType::Unknown => destination,
1342    };
1343
1344    // TODO Step 11. Set request's initiator type to "script".
1345
1346    // Step 12. Set up the module script request given request and options.
1347    let request = RequestBuilder::new(webview_id, url.clone(), referrer)
1348        .destination(destination)
1349        .parser_metadata(options.parser_metadata)
1350        .integrity_metadata(options.integrity_metadata.clone())
1351        .credentials_mode(options.credentials_mode)
1352        .referrer_policy(options.referrer_policy)
1353        .mode(mode)
1354        .with_global_scope(&global)
1355        .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1356
1357    let context = ModuleContext {
1358        owner,
1359        data: vec![],
1360        metadata: None,
1361        module_request,
1362        options,
1363        status: Ok(()),
1364        introduction_type,
1365    };
1366
1367    let network_listener = NetworkListener::new(
1368        context,
1369        global.task_manager().networking_task_source().to_sendable(),
1370    );
1371    match document {
1372        Some(document) => {
1373            document.loader_mut().fetch_async_with_callback(
1374                LoadType::Script(url),
1375                request,
1376                network_listener.into_callback(),
1377            );
1378        },
1379        None => global.fetch_with_network_listener(request, network_listener),
1380    };
1381}
1382
1383pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1384pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1385
1386/// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record>
1387#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1388pub(crate) struct ResolvedModule {
1389    /// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-serialized-base-url>
1390    base_url: String,
1391    /// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-specifier>
1392    specifier: String,
1393    /// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-as-url>
1394    #[no_trace]
1395    specifier_url: Option<ServoUrl>,
1396}
1397
1398impl ResolvedModule {
1399    pub(crate) fn new(
1400        base_url: String,
1401        specifier: String,
1402        specifier_url: Option<ServoUrl>,
1403    ) -> Self {
1404        Self {
1405            base_url,
1406            specifier,
1407            specifier_url,
1408        }
1409    }
1410}
1411
1412/// <https://html.spec.whatwg.org/multipage/#import-map-processing-model>
1413#[derive(Default, JSTraceable, MallocSizeOf)]
1414pub(crate) struct ImportMap {
1415    #[no_trace]
1416    imports: ModuleSpecifierMap,
1417    #[no_trace]
1418    scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1419    #[no_trace]
1420    integrity: ModuleIntegrityMap,
1421}
1422
1423/// <https://html.spec.whatwg.org/multipage/#register-an-import-map>
1424pub(crate) fn register_import_map(
1425    global: &GlobalScope,
1426    result: Fallible<ImportMap>,
1427    can_gc: CanGc,
1428) {
1429    match result {
1430        Ok(new_import_map) => {
1431            // Step 2. Merge existing and new import maps, given global and result's import map.
1432            merge_existing_and_new_import_maps(global, new_import_map, can_gc);
1433        },
1434        Err(exception) => {
1435            // Step 1. If result's error to rethrow is not null, then report
1436            // an exception given by result's error to rethrow for global and return.
1437            throw_dom_exception(GlobalScope::get_cx(), global, exception.clone(), can_gc);
1438        },
1439    }
1440}
1441
1442/// <https://html.spec.whatwg.org/multipage/#merge-existing-and-new-import-maps>
1443fn merge_existing_and_new_import_maps(
1444    global: &GlobalScope,
1445    new_import_map: ImportMap,
1446    can_gc: CanGc,
1447) {
1448    // Step 1. Let newImportMapScopes be a deep copy of newImportMap's scopes.
1449    let new_import_map_scopes = new_import_map.scopes;
1450
1451    // Step 2. Let oldImportMap be global's import map.
1452    let mut old_import_map = global.import_map_mut();
1453
1454    // Step 3. Let newImportMapImports be a deep copy of newImportMap's imports.
1455    let mut new_import_map_imports = new_import_map.imports;
1456
1457    let resolved_module_set = global.resolved_module_set();
1458    // Step 4. For each scopePrefix → scopeImports of newImportMapScopes:
1459    for (scope_prefix, mut scope_imports) in new_import_map_scopes {
1460        // Step 4.1. For each record of global's resolved module set:
1461        for record in resolved_module_set.iter() {
1462            // If scopePrefix is record's serialized base URL, or if scopePrefix ends with
1463            // U+002F (/) and scopePrefix is a code unit prefix of record's serialized base URL, then:
1464            let prefix = scope_prefix.as_str();
1465            if prefix == record.base_url ||
1466                (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
1467            {
1468                // For each specifierKey → resolutionResult of scopeImports:
1469                scope_imports.retain(|key, val| {
1470                    // If specifierKey is record's specifier, or if all of the following conditions are true:
1471                    // specifierKey ends with U+002F (/);
1472                    // specifierKey is a code unit prefix of record's specifier;
1473                    // either record's specifier as a URL is null or is special,
1474                    if *key == record.specifier ||
1475                        (key.ends_with('\u{002f}') &&
1476                            record.specifier.starts_with(key) &&
1477                            (record.specifier_url.is_none() ||
1478                                record
1479                                    .specifier_url
1480                                    .as_ref()
1481                                    .map(|u| u.is_special_scheme())
1482                                    .unwrap_or_default()))
1483                    {
1484                        // The user agent may report a warning to the console indicating the ignored rule.
1485                        // They may choose to avoid reporting if the rule is identical to an existing one.
1486                        Console::internal_warn(
1487                            global,
1488                            DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
1489                        );
1490                        // Remove scopeImports[specifierKey].
1491                        false
1492                    } else {
1493                        true
1494                    }
1495                })
1496            }
1497        }
1498
1499        // Step 4.2 If scopePrefix exists in oldImportMap's scopes
1500        if old_import_map.scopes.contains_key(&scope_prefix) {
1501            // set oldImportMap's scopes[scopePrefix] to the result of
1502            // merging module specifier maps, given scopeImports and oldImportMap's scopes[scopePrefix].
1503            let merged_module_specifier_map = merge_module_specifier_maps(
1504                global,
1505                scope_imports,
1506                &old_import_map.scopes[&scope_prefix],
1507                can_gc,
1508            );
1509            old_import_map
1510                .scopes
1511                .insert(scope_prefix, merged_module_specifier_map);
1512        } else {
1513            // Step 4.3 Otherwise, set oldImportMap's scopes[scopePrefix] to scopeImports.
1514            old_import_map.scopes.insert(scope_prefix, scope_imports);
1515        }
1516    }
1517
1518    // Step 5. For each url → integrity of newImportMap's integrity:
1519    for (url, integrity) in &new_import_map.integrity {
1520        // Step 5.1 If url exists in oldImportMap's integrity, then:
1521        if old_import_map.integrity.contains_key(url) {
1522            // Step 5.1.1 The user agent may report a warning to the console indicating the ignored rule.
1523            // They may choose to avoid reporting if the rule is identical to an existing one.
1524            Console::internal_warn(
1525                global,
1526                DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
1527            );
1528            // Step 5.1.2 Continue.
1529            continue;
1530        }
1531
1532        // Step 5.2 Set oldImportMap's integrity[url] to integrity.
1533        old_import_map
1534            .integrity
1535            .insert(url.clone(), integrity.clone());
1536    }
1537
1538    // Step 6. For each record of global's resolved module set:
1539    for record in resolved_module_set.iter() {
1540        // For each specifier → url of newImportMapImports:
1541        new_import_map_imports.retain(|specifier, val| {
1542            // If specifier starts with record's specifier, then:
1543            if specifier.starts_with(&record.specifier) {
1544                // The user agent may report a warning to the console indicating the ignored rule.
1545                // They may choose to avoid reporting if the rule is identical to an existing one.
1546                Console::internal_warn(
1547                    global,
1548                    DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
1549                );
1550                // Remove newImportMapImports[specifier].
1551                false
1552            } else {
1553                true
1554            }
1555        });
1556    }
1557
1558    // Step 7. Set oldImportMap's imports to the result of merge module specifier maps,
1559    // given newImportMapImports and oldImportMap's imports.
1560    let merged_module_specifier_map = merge_module_specifier_maps(
1561        global,
1562        new_import_map_imports,
1563        &old_import_map.imports,
1564        can_gc,
1565    );
1566    old_import_map.imports = merged_module_specifier_map;
1567}
1568
1569/// <https://html.spec.whatwg.org/multipage/#merge-module-specifier-maps>
1570fn merge_module_specifier_maps(
1571    global: &GlobalScope,
1572    new_map: ModuleSpecifierMap,
1573    old_map: &ModuleSpecifierMap,
1574    _can_gc: CanGc,
1575) -> ModuleSpecifierMap {
1576    // Step 1. Let mergedMap be a deep copy of oldMap.
1577    let mut merged_map = old_map.clone();
1578
1579    // Step 2. For each specifier → url of newMap:
1580    for (specifier, url) in new_map {
1581        // Step 2.1 If specifier exists in oldMap, then:
1582        if old_map.contains_key(&specifier) {
1583            // Step 2.1.1 The user agent may report a warning to the console indicating the ignored rule.
1584            // They may choose to avoid reporting if the rule is identical to an existing one.
1585            Console::internal_warn(
1586                global,
1587                DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
1588            );
1589
1590            // Step 2.1.2 Continue.
1591            continue;
1592        }
1593
1594        // Step 2.2 Set mergedMap[specifier] to url.
1595        merged_map.insert(specifier, url);
1596    }
1597
1598    merged_map
1599}
1600
1601/// <https://html.spec.whatwg.org/multipage/#parse-an-import-map-string>
1602pub(crate) fn parse_an_import_map_string(
1603    module_owner: ModuleOwner,
1604    input: Rc<DOMString>,
1605    base_url: ServoUrl,
1606    can_gc: CanGc,
1607) -> Fallible<ImportMap> {
1608    // Step 1. Let parsed be the result of parsing a JSON string to an Infra value given input.
1609    let parsed: JsonValue = serde_json::from_str(&input.str())
1610        .map_err(|_| Error::Type("The value needs to be a JSON object.".to_owned()))?;
1611    // Step 2. If parsed is not an ordered map, then throw a TypeError indicating that the
1612    // top-level value needs to be a JSON object.
1613    let JsonValue::Object(mut parsed) = parsed else {
1614        return Err(Error::Type(
1615            "The top-level value needs to be a JSON object.".to_owned(),
1616        ));
1617    };
1618
1619    // Step 3. Let sortedAndNormalizedImports be an empty ordered map.
1620    let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
1621    // Step 4. If parsed["imports"] exists, then:
1622    if let Some(imports) = parsed.get("imports") {
1623        // Step 4.1 If parsed["imports"] is not an ordered map, then throw a TypeError
1624        // indicating that the value for the "imports" top-level key needs to be a JSON object.
1625        let JsonValue::Object(imports) = imports else {
1626            return Err(Error::Type(
1627                "The \"imports\" top-level value needs to be a JSON object.".to_owned(),
1628            ));
1629        };
1630        // Step 4.2 Set sortedAndNormalizedImports to the result of sorting and
1631        // normalizing a module specifier map given parsed["imports"] and baseURL.
1632        sorted_and_normalized_imports = sort_and_normalize_module_specifier_map(
1633            &module_owner.global(),
1634            imports,
1635            &base_url,
1636            can_gc,
1637        );
1638    }
1639
1640    // Step 5. Let sortedAndNormalizedScopes be an empty ordered map.
1641    let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1642    // Step 6. If parsed["scopes"] exists, then:
1643    if let Some(scopes) = parsed.get("scopes") {
1644        // Step 6.1 If parsed["scopes"] is not an ordered map, then throw a TypeError
1645        // indicating that the value for the "scopes" top-level key needs to be a JSON object.
1646        let JsonValue::Object(scopes) = scopes else {
1647            return Err(Error::Type(
1648                "The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
1649            ));
1650        };
1651        // Step 6.2 Set sortedAndNormalizedScopes to the result of sorting and
1652        // normalizing scopes given parsed["scopes"] and baseURL.
1653        sorted_and_normalized_scopes =
1654            sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url, can_gc)?;
1655    }
1656
1657    // Step 7. Let normalizedIntegrity be an empty ordered map.
1658    let mut normalized_integrity = ModuleIntegrityMap::new();
1659    // Step 8. If parsed["integrity"] exists, then:
1660    if let Some(integrity) = parsed.get("integrity") {
1661        // Step 8.1 If parsed["integrity"] is not an ordered map, then throw a TypeError
1662        // indicating that the value for the "integrity" top-level key needs to be a JSON object.
1663        let JsonValue::Object(integrity) = integrity else {
1664            return Err(Error::Type(
1665                "The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
1666            ));
1667        };
1668        // Step 8.2 Set normalizedIntegrity to the result of normalizing
1669        // a module integrity map given parsed["integrity"] and baseURL.
1670        normalized_integrity =
1671            normalize_module_integrity_map(&module_owner.global(), integrity, &base_url, can_gc);
1672    }
1673
1674    // Step 9. If parsed's keys contains any items besides "imports", "scopes", or "integrity",
1675    // then the user agent should report a warning to the console indicating that an invalid
1676    // top-level key was present in the import map.
1677    parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
1678    if !parsed.is_empty() {
1679        Console::internal_warn(
1680            &module_owner.global(),
1681            DOMString::from(
1682                "Invalid top-level key was present in the import map.
1683                Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
1684            ),
1685        );
1686    }
1687
1688    // Step 10. Return an import map
1689    Ok(ImportMap {
1690        imports: sorted_and_normalized_imports,
1691        scopes: sorted_and_normalized_scopes,
1692        integrity: normalized_integrity,
1693    })
1694}
1695
1696/// <https://html.spec.whatwg.org/multipage/#sorting-and-normalizing-a-module-specifier-map>
1697fn sort_and_normalize_module_specifier_map(
1698    global: &GlobalScope,
1699    original_map: &JsonMap<String, JsonValue>,
1700    base_url: &ServoUrl,
1701    can_gc: CanGc,
1702) -> ModuleSpecifierMap {
1703    // Step 1. Let normalized be an empty ordered map.
1704    let mut normalized = ModuleSpecifierMap::new();
1705
1706    // Step 2. For each specifier_key -> value in originalMap
1707    for (specifier_key, value) in original_map {
1708        // Step 2.1 Let normalized_specifier_key be the result of
1709        // normalizing a specifier key given specifier_key and base_url.
1710        let Some(normalized_specifier_key) =
1711            normalize_specifier_key(global, specifier_key, base_url, can_gc)
1712        else {
1713            // Step 2.2 If normalized_specifier_key is null, then continue.
1714            continue;
1715        };
1716
1717        // Step 2.3 If value is not a string, then:
1718        let JsonValue::String(value) = value else {
1719            // Step 2.3.1 The user agent may report a warning to the console
1720            // indicating that addresses need to be strings.
1721            Console::internal_warn(global, DOMString::from("Addresses need to be strings."));
1722
1723            // Step 2.3.2 Set normalized[normalized_specifier_key] to null.
1724            normalized.insert(normalized_specifier_key, None);
1725            // Step 2.3.3 Continue.
1726            continue;
1727        };
1728
1729        // Step 2.4. Let address_url be the result of resolving a URL-like module specifier given value and baseURL.
1730        let value = DOMString::from(value.as_str());
1731        let Some(address_url) = ModuleTree::resolve_url_like_module_specifier(&value, base_url)
1732        else {
1733            // Step 2.5 If address_url is null, then:
1734            // Step 2.5.1. The user agent may report a warning to the console
1735            // indicating that the address was invalid.
1736            Console::internal_warn(
1737                global,
1738                DOMString::from(format!(
1739                    "Value failed to resolve to module specifier: {value}"
1740                )),
1741            );
1742
1743            // Step 2.5.2 Set normalized[normalized_specifier_key] to null.
1744            normalized.insert(normalized_specifier_key, None);
1745            // Step 2.5.3 Continue.
1746            continue;
1747        };
1748
1749        // Step 2.6 If specifier_key ends with U+002F (/), and the serialization of
1750        // address_url does not end with U+002F (/), then:
1751        if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
1752            // step 2.6.1. The user agent may report a warning to the console
1753            // indicating that an invalid address was given for the specifier key specifierKey;
1754            // since specifierKey ends with a slash, the address needs to as well.
1755            Console::internal_warn(
1756                global,
1757                DOMString::from(format!(
1758                    "Invalid address for specifier key '{specifier_key}': {address_url}.
1759                    Since specifierKey ends with a slash, the address needs to as well."
1760                )),
1761            );
1762
1763            // Step 2.6.2 Set normalized[normalized_specifier_key] to null.
1764            normalized.insert(normalized_specifier_key, None);
1765            // Step 2.6.3 Continue.
1766            continue;
1767        }
1768
1769        // Step 2.7 Set normalized[normalized_specifier_key] to address_url.
1770        normalized.insert(normalized_specifier_key, Some(address_url));
1771    }
1772
1773    // Step 3. Return the result of sorting in descending order normalized
1774    // with an entry a being less than an entry b if a's key is code unit less than b's key.
1775    normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1776    normalized
1777}
1778
1779/// <https://html.spec.whatwg.org/multipage/#sorting-and-normalizing-scopes>
1780fn sort_and_normalize_scopes(
1781    global: &GlobalScope,
1782    original_map: &JsonMap<String, JsonValue>,
1783    base_url: &ServoUrl,
1784    can_gc: CanGc,
1785) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
1786    // Step 1. Let normalized be an empty ordered map.
1787    let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
1788
1789    // Step 2. For each scopePrefix → potentialSpecifierMap of originalMap:
1790    for (scope_prefix, potential_specifier_map) in original_map {
1791        // Step 2.1 If potentialSpecifierMap is not an ordered map, then throw a TypeError indicating
1792        // that the value of the scope with prefix scopePrefix needs to be a JSON object.
1793        let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
1794            return Err(Error::Type(
1795                "The value of the scope with prefix scopePrefix needs to be a JSON object."
1796                    .to_owned(),
1797            ));
1798        };
1799
1800        // Step 2.2 Let scopePrefixURL be the result of URL parsing scopePrefix with baseURL.
1801        let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
1802            // Step 2.3 If scopePrefixURL is failure, then:
1803            // Step 2.3.1 The user agent may report a warning
1804            // to the console that the scope prefix URL was not parseable.
1805            Console::internal_warn(
1806                global,
1807                DOMString::from(format!(
1808                    "Scope prefix URL was not parseable: {scope_prefix}"
1809                )),
1810            );
1811            // Step 2.3.2 Continue.
1812            continue;
1813        };
1814
1815        // Step 2.4 Let normalizedScopePrefix be the serialization of scopePrefixURL.
1816        let normalized_scope_prefix = scope_prefix_url;
1817
1818        // Step 2.5 Set normalized[normalizedScopePrefix] to the result of sorting and
1819        // normalizing a module specifier map given potentialSpecifierMap and baseURL.
1820        let normalized_specifier_map = sort_and_normalize_module_specifier_map(
1821            global,
1822            potential_specifier_map,
1823            base_url,
1824            can_gc,
1825        );
1826        normalized.insert(normalized_scope_prefix, normalized_specifier_map);
1827    }
1828
1829    // Step 3. Return the result of sorting in descending order normalized,
1830    // with an entry a being less than an entry b if a's key is code unit less than b's key.
1831    normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
1832    Ok(normalized)
1833}
1834
1835/// <https://html.spec.whatwg.org/multipage/#normalizing-a-module-integrity-map>
1836fn normalize_module_integrity_map(
1837    global: &GlobalScope,
1838    original_map: &JsonMap<String, JsonValue>,
1839    base_url: &ServoUrl,
1840    _can_gc: CanGc,
1841) -> ModuleIntegrityMap {
1842    // Step 1. Let normalized be an empty ordered map.
1843    let mut normalized = ModuleIntegrityMap::new();
1844
1845    // Step 2. For each key → value of originalMap:
1846    for (key, value) in original_map {
1847        // Step 2.1 Let resolvedURL be the result of
1848        // resolving a URL-like module specifier given key and baseURL.
1849        let Some(resolved_url) =
1850            ModuleTree::resolve_url_like_module_specifier(&DOMString::from(key.as_str()), base_url)
1851        else {
1852            // Step 2.2 If resolvedURL is null, then:
1853            // Step 2.2.1 The user agent may report a warning
1854            // to the console indicating that the key failed to resolve.
1855            Console::internal_warn(
1856                global,
1857                DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
1858            );
1859            // Step 2.2.2 Continue.
1860            continue;
1861        };
1862
1863        // Step 2.3 If value is not a string, then:
1864        let JsonValue::String(value) = value else {
1865            // Step 2.3.1 The user agent may report a warning
1866            // to the console indicating that integrity metadata values need to be strings.
1867            Console::internal_warn(
1868                global,
1869                DOMString::from("Integrity metadata values need to be strings."),
1870            );
1871            // Step 2.3.2 Continue.
1872            continue;
1873        };
1874
1875        // Step 2.4 Set normalized[resolvedURL] to value.
1876        normalized.insert(resolved_url, value.clone());
1877    }
1878
1879    // Step 3. Return normalized.
1880    normalized
1881}
1882
1883/// <https://html.spec.whatwg.org/multipage/#normalizing-a-specifier-key>
1884fn normalize_specifier_key(
1885    global: &GlobalScope,
1886    specifier_key: &str,
1887    base_url: &ServoUrl,
1888    _can_gc: CanGc,
1889) -> Option<String> {
1890    // step 1. If specifierKey is the empty string, then:
1891    if specifier_key.is_empty() {
1892        // step 1.1 The user agent may report a warning to the console
1893        // indicating that specifier keys may not be the empty string.
1894        Console::internal_warn(
1895            global,
1896            DOMString::from("Specifier keys may not be the empty string."),
1897        );
1898        // step 1.2 Return null.
1899        return None;
1900    }
1901    // step 2. Let url be the result of resolving a URL-like module specifier, given specifierKey and baseURL.
1902    let url =
1903        ModuleTree::resolve_url_like_module_specifier(&DOMString::from(specifier_key), base_url);
1904
1905    // step 3. If url is not null, then return the serialization of url.
1906    if let Some(url) = url {
1907        return Some(url.into_string());
1908    }
1909
1910    // step 4. Return specifierKey.
1911    Some(specifier_key.to_string())
1912}
1913
1914/// <https://html.spec.whatwg.org/multipage/#resolving-an-imports-match>
1915///
1916/// When the error is thrown, it will terminate the entire resolve a module specifier algorithm
1917/// without any further fallbacks.
1918pub(crate) fn resolve_imports_match(
1919    normalized_specifier: &str,
1920    as_url: Option<&ServoUrl>,
1921    specifier_map: &ModuleSpecifierMap,
1922    _can_gc: CanGc,
1923) -> Fallible<Option<ServoUrl>> {
1924    // Step 1. For each specifierKey → resolutionResult of specifierMap:
1925    for (specifier_key, resolution_result) in specifier_map {
1926        // Step 1.1 If specifierKey is normalizedSpecifier, then:
1927        if specifier_key == normalized_specifier {
1928            if let Some(resolution_result) = resolution_result {
1929                // Step 1.1.2 Assert: resolutionResult is a URL.
1930                // This is checked by Url type already.
1931                // Step 1.1.3 Return resolutionResult.
1932                return Ok(Some(resolution_result.clone()));
1933            } else {
1934                // Step 1.1.1 If resolutionResult is null, then throw a TypeError.
1935                return Err(Error::Type(
1936                    "Resolution of specifierKey was blocked by a null entry.".to_owned(),
1937                ));
1938            }
1939        }
1940
1941        // Step 1.2 If all of the following are true:
1942        // - specifierKey ends with U+002F (/)
1943        // - specifierKey is a code unit prefix of normalizedSpecifier
1944        // - either asURL is null, or asURL is special, then:
1945        if specifier_key.ends_with('\u{002f}') &&
1946            normalized_specifier.starts_with(specifier_key) &&
1947            (as_url.is_none() || as_url.map(|u| u.is_special_scheme()).unwrap_or_default())
1948        {
1949            // Step 1.2.1 If resolutionResult is null, then throw a TypeError.
1950            // Step 1.2.2 Assert: resolutionResult is a URL.
1951            let Some(resolution_result) = resolution_result else {
1952                return Err(Error::Type(
1953                    "Resolution of specifierKey was blocked by a null entry.".to_owned(),
1954                ));
1955            };
1956
1957            // Step 1.2.3 Let afterPrefix be the portion of normalizedSpecifier after the initial specifierKey prefix.
1958            let after_prefix = normalized_specifier
1959                .strip_prefix(specifier_key)
1960                .expect("specifier_key should be the prefix of normalized_specifier");
1961
1962            // Step 1.2.4 Assert: resolutionResult, serialized, ends with U+002F (/), as enforced during parsing.
1963            debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
1964
1965            // Step 1.2.5 Let url be the result of URL parsing afterPrefix with resolutionResult.
1966            let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
1967
1968            // Step 1.2.6 If url is failure, then throw a TypeError
1969            // Step 1.2.7 Assert: url is a URL.
1970            let Ok(url) = url else {
1971                return Err(Error::Type(
1972                    "Resolution of normalizedSpecifier was blocked since
1973                    the afterPrefix portion could not be URL-parsed relative to
1974                    the resolutionResult mapped to by the specifierKey prefix."
1975                        .to_owned(),
1976                ));
1977            };
1978
1979            // Step 1.2.8 If the serialization of resolutionResult is not
1980            // a code unit prefix of the serialization of url, then throw a TypeError
1981            if !url.as_str().starts_with(resolution_result.as_str()) {
1982                return Err(Error::Type(
1983                    "Resolution of normalizedSpecifier was blocked due to
1984                    it backtracking above its prefix specifierKey."
1985                        .to_owned(),
1986                ));
1987            }
1988
1989            // Step 1.2.9 Return url.
1990            return Ok(Some(url));
1991        }
1992    }
1993
1994    // Step 2. Return null.
1995    Ok(None)
1996}