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::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/// Identity for a module which will be
139/// used to retrieve the module when we'd
140/// like to get it from module map.
141///
142/// For example, we will save module parents with
143/// module identity so that we can get module tree
144/// from a descendant no matter the parent is an
145/// inline script or a external script
146#[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    // The spec maintains load order for descendants, so we use an indexset for descendants and
175    // parents. This isn't actually necessary for parents however the IndexSet APIs don't
176    // interop with HashSet, and IndexSet isn't very expensive
177    // (https://github.com/bluss/indexmap/issues/110)
178    //
179    // By default all maps in web specs are ordered maps
180    // (https://infra.spec.whatwg.org/#ordered-map), however we can usually get away with using
181    // stdlib maps and sets because we rarely iterate over them.
182    #[custom_trace]
183    parent_identities: DomRefCell<IndexSet<ModuleIdentity>>,
184    #[no_trace]
185    descendant_urls: DomRefCell<IndexSet<ServoUrl>>,
186    // A set to memoize which descendants are under fetching
187    #[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    // A promise for owners to execute when the module tree
195    // is finished
196    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    /// recursively checks if all of the transitive descendants are
295    /// in the FetchingDescendants or later status
296    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    // We just leverage the power of Promise to run the task for `finish` the owner.
342    // Thus, we will always `resolve` it and no need to register a callback for `reject`
343    fn append_handler(
344        &self,
345        owner: ModuleOwner,
346        module_identity: ModuleIdentity,
347        fetch_options: ScriptFetchOptions,
348        can_gc: CanGc,
349    ) {
350        let this = owner.clone();
351        let identity = module_identity.clone();
352        let options = fetch_options.clone();
353
354        let handler = PromiseNativeHandler::new(
355            &owner.global(),
356            Some(ModuleHandler::new_boxed(Box::new(
357                task!(fetched_resolve: move || {
358                    this.notify_owner_to_finish(identity, options, CanGc::note());
359                }),
360            ))),
361            None,
362            can_gc,
363        );
364
365        let realm = enter_realm(&*owner.global());
366        let comp = InRealm::Entered(&realm);
367        let _ais = AutoIncumbentScript::new(&owner.global());
368
369        if let Some(promise) = self.promise.borrow().as_ref() {
370            promise.append_native_handler(&handler, comp, can_gc);
371            return;
372        }
373
374        let new_promise = Promise::new_in_current_realm(comp, can_gc);
375        new_promise.append_native_handler(&handler, comp, can_gc);
376        *self.promise.borrow_mut() = Some(new_promise);
377    }
378
379    fn append_dynamic_module_handler(
380        &self,
381        owner: ModuleOwner,
382        module_identity: ModuleIdentity,
383        dynamic_module: RootedTraceableBox<DynamicModule>,
384        can_gc: CanGc,
385    ) {
386        let this = owner.clone();
387        let identity = module_identity.clone();
388
389        let module_id = owner.global().dynamic_module_list().push(dynamic_module);
390
391        let handler = PromiseNativeHandler::new(
392            &owner.global(),
393            Some(ModuleHandler::new_boxed(Box::new(
394                task!(fetched_resolve: move || {
395                    this.finish_dynamic_module(identity, module_id, CanGc::note());
396                }),
397            ))),
398            None,
399            can_gc,
400        );
401
402        let realm = enter_realm(&*owner.global());
403        let comp = InRealm::Entered(&realm);
404        let _ais = AutoIncumbentScript::new(&owner.global());
405
406        if let Some(promise) = self.promise.borrow().as_ref() {
407            promise.append_native_handler(&handler, comp, can_gc);
408            return;
409        }
410
411        let new_promise = Promise::new_in_current_realm(comp, can_gc);
412        new_promise.append_native_handler(&handler, comp, can_gc);
413        *self.promise.borrow_mut() = Some(new_promise);
414    }
415}
416
417#[derive(Clone, Copy, Debug, JSTraceable, PartialEq, PartialOrd)]
418pub(crate) enum ModuleStatus {
419    Initial,
420    Fetching,
421    FetchingDescendants,
422    Finished,
423}
424
425struct ModuleSource {
426    source: Rc<DOMString>,
427    unminified_dir: Option<String>,
428    external: bool,
429    url: ServoUrl,
430}
431
432impl crate::unminify::ScriptSource for ModuleSource {
433    fn unminified_dir(&self) -> Option<String> {
434        self.unminified_dir.clone()
435    }
436
437    fn extract_bytes(&self) -> BytesView<'_> {
438        self.source.as_bytes()
439    }
440
441    fn rewrite_source(&mut self, source: Rc<DOMString>) {
442        self.source = source;
443    }
444
445    fn url(&self) -> ServoUrl {
446        self.url.clone()
447    }
448
449    fn is_external(&self) -> bool {
450        self.external
451    }
452}
453
454impl ModuleTree {
455    #[expect(unsafe_code)]
456    #[allow(clippy::too_many_arguments)]
457    /// <https://html.spec.whatwg.org/multipage/#creating-a-module-script>
458    /// Step 7-11.
459    /// Although the CanGc argument appears unused, it represents the GC operations that
460    /// can occur as part of compiling a script.
461    fn compile_module_script(
462        &self,
463        global: &GlobalScope,
464        owner: ModuleOwner,
465        module_script_text: Rc<DOMString>,
466        url: &ServoUrl,
467        options: ScriptFetchOptions,
468        mut module_script: RustMutableHandleObject,
469        inline: bool,
470        line_number: u64,
471        introduction_type: Option<&'static CStr>,
472        can_gc: CanGc,
473    ) -> Result<(), RethrowError> {
474        let cx = GlobalScope::get_cx();
475        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
476
477        let mut compile_options =
478            unsafe { CompileOptionsWrapper::new_raw(*cx, url.as_str(), line_number as u32) };
479        if let Some(introduction_type) = introduction_type {
480            compile_options.set_introduction_type(introduction_type);
481        }
482        let mut module_source = ModuleSource {
483            source: module_script_text,
484            unminified_dir: global.unminified_js_dir(),
485            external: !inline,
486            url: url.clone(),
487        };
488        crate::unminify::unminify_js(&mut module_source);
489
490        unsafe {
491            module_script.set(CompileModule1(
492                *cx,
493                compile_options.ptr,
494                &mut transform_str_to_source_text(&module_source.source.str()),
495            ));
496
497            if module_script.is_null() {
498                warn!("fail to compile module script of {}", url);
499
500                rooted!(in(*cx) let mut exception = UndefinedValue());
501                assert!(JS_GetPendingException(*cx, exception.handle_mut()));
502                JS_ClearPendingException(*cx);
503
504                return Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
505                    exception.get(),
506                ))));
507            }
508
509            let module_script_data = Rc::new(ModuleScript::new(url.clone(), options, Some(owner)));
510
511            SetModulePrivate(
512                module_script.get(),
513                &PrivateValue(Rc::into_raw(module_script_data) as *const _),
514            );
515
516            debug!("module script of {} compile done", url);
517
518            self.resolve_requested_module_specifiers(
519                global,
520                module_script.handle().into_handle(),
521                can_gc,
522            )
523            .map(|_| ())
524        }
525    }
526
527    #[expect(unsafe_code)]
528    /// <https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-and-link-a-module-script>
529    /// Step 5-2.
530    pub(crate) fn instantiate_module_tree(
531        &self,
532        global: &GlobalScope,
533        module_record: HandleObject,
534    ) -> Result<(), RethrowError> {
535        let cx = GlobalScope::get_cx();
536        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
537
538        unsafe {
539            if !ModuleLink(*cx, module_record) {
540                warn!("fail to link & instantiate module");
541
542                rooted!(in(*cx) let mut exception = UndefinedValue());
543                assert!(JS_GetPendingException(*cx, exception.handle_mut()));
544                JS_ClearPendingException(*cx);
545
546                Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
547                    exception.get(),
548                ))))
549            } else {
550                debug!("module instantiated successfully");
551
552                Ok(())
553            }
554        }
555    }
556
557    /// Execute the provided module, storing the evaluation return value in the provided
558    /// mutable handle. Although the CanGc appears unused, it represents the GC operations
559    /// possible when evluating arbitrary JS.
560    #[expect(unsafe_code)]
561    pub(crate) fn execute_module(
562        &self,
563        global: &GlobalScope,
564        module_record: HandleObject,
565        eval_result: MutableHandleValue,
566        _can_gc: CanGc,
567    ) -> Result<(), RethrowError> {
568        let cx = GlobalScope::get_cx();
569        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
570
571        unsafe {
572            let ok = ModuleEvaluate(*cx, module_record, eval_result);
573            assert!(ok, "module evaluation failed");
574
575            rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
576            if eval_result.is_object() {
577                evaluation_promise.set(eval_result.to_object());
578            }
579
580            let throw_result = ThrowOnModuleEvaluationFailure(
581                *cx,
582                evaluation_promise.handle().into(),
583                ModuleErrorBehaviour::ThrowModuleErrorsSync,
584            );
585            if !throw_result {
586                warn!("fail to evaluate module");
587
588                rooted!(in(*cx) let mut exception = UndefinedValue());
589                assert!(JS_GetPendingException(*cx, exception.handle_mut()));
590                JS_ClearPendingException(*cx);
591
592                Err(RethrowError(RootedTraceableBox::from_box(Heap::boxed(
593                    exception.get(),
594                ))))
595            } else {
596                debug!("module evaluated successfully");
597                Ok(())
598            }
599        }
600    }
601
602    #[expect(unsafe_code)]
603    pub(crate) fn report_error(&self, global: &GlobalScope, can_gc: CanGc) {
604        let module_error = self.rethrow_error.borrow();
605
606        if let Some(exception) = &*module_error {
607            let ar = enter_realm(global);
608            unsafe {
609                JS_SetPendingException(
610                    *GlobalScope::get_cx(),
611                    exception.handle(),
612                    ExceptionStackBehavior::Capture,
613                );
614            }
615            report_pending_exception(GlobalScope::get_cx(), true, InRealm::Entered(&ar), can_gc);
616        }
617    }
618
619    #[expect(unsafe_code)]
620    fn resolve_requested_module_specifiers(
621        &self,
622        global: &GlobalScope,
623        module_object: HandleObject,
624        can_gc: CanGc,
625    ) -> Result<IndexSet<ServoUrl>, RethrowError> {
626        let cx = GlobalScope::get_cx();
627        let _ac = JSAutoRealm::new(*cx, *global.reflector().get_jsobject());
628
629        let mut specifier_urls = IndexSet::new();
630
631        unsafe {
632            let length = GetRequestedModulesCount(*cx, module_object);
633
634            for index in 0..length {
635                let jsstr =
636                    std::ptr::NonNull::new(GetRequestedModuleSpecifier(*cx, module_object, index))
637                        .unwrap();
638                let specifier = DOMString::from_string(jsstr_to_string(*cx, jsstr));
639
640                rooted!(in(*cx) let mut private = UndefinedValue());
641                JS_GetModulePrivate(module_object.get(), private.handle_mut());
642                let private = private.handle().into_handle();
643                let script = module_script_from_reference_private(&private);
644                let url = ModuleTree::resolve_module_specifier(global, script, specifier, can_gc);
645
646                if url.is_err() {
647                    let specifier_error =
648                        gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
649
650                    return Err(specifier_error);
651                }
652
653                specifier_urls.insert(url.unwrap());
654            }
655        }
656
657        Ok(specifier_urls)
658    }
659
660    /// <https://html.spec.whatwg.org/multipage/#resolve-a-module-specifier>
661    fn resolve_module_specifier(
662        global: &GlobalScope,
663        script: Option<&ModuleScript>,
664        specifier: DOMString,
665        can_gc: CanGc,
666    ) -> Fallible<ServoUrl> {
667        // Step 1~3 to get settingsObject and baseURL
668        let script_global = script.and_then(|s| s.owner.as_ref().map(|o| o.global()));
669        // Step 1. Let settingsObject and baseURL be null.
670        let (global, base_url): (&GlobalScope, &ServoUrl) = match script {
671            // Step 2. If referringScript is not null, then:
672            // Set settingsObject to referringScript's settings object.
673            // Set baseURL to referringScript's base URL.
674            Some(s) => (script_global.as_ref().map_or(global, |g| g), &s.base_url),
675            // Step 3. Otherwise:
676            // Set settingsObject to the current settings object.
677            // Set baseURL to settingsObject's API base URL.
678            // FIXME(#37553): Is this the correct current settings object?
679            None => (global, &global.api_base_url()),
680        };
681
682        // Step 4. Let importMap be an empty import map.
683        // Step 5. If settingsObject's global object implements Window, then set importMap to settingsObject's
684        // global object's import map.
685        let import_map = if global.is::<Window>() {
686            Some(global.import_map())
687        } else {
688            None
689        };
690
691        // Step 6. Let serializedBaseURL be baseURL, serialized.
692        let serialized_base_url = base_url.as_str();
693        // Step 7. Let asURL be the result of resolving a URL-like module specifier given specifier and baseURL.
694        let as_url = Self::resolve_url_like_module_specifier(&specifier, base_url);
695        // Step 8. Let normalizedSpecifier be the serialization of asURL, if asURL is non-null;
696        // otherwise, specifier.
697        let specifier = specifier.str();
698        let normalized_specifier = match &as_url {
699            Some(url) => url.as_str(),
700            None => &specifier,
701        };
702
703        // Step 9. Let result be a URL-or-null, initially null.
704        let mut result = None;
705        if let Some(map) = import_map {
706            // Step 10. For each scopePrefix → scopeImports of importMap's scopes:
707            for (prefix, imports) in &map.scopes {
708                // Step 10.1 If scopePrefix is serializedBaseURL, or if scopePrefix ends with U+002F (/)
709                // and scopePrefix is a code unit prefix of serializedBaseURL, then:
710                let prefix = prefix.as_str();
711                if prefix == serialized_base_url ||
712                    (serialized_base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
713                {
714                    // Step 10.1.1 Let scopeImportsMatch be the result of resolving an imports match
715                    // given normalizedSpecifier, asURL, and scopeImports.
716                    // Step 10.1.2 If scopeImportsMatch is not null, then set result to scopeImportsMatch,
717                    // and break.
718                    result = resolve_imports_match(
719                        normalized_specifier,
720                        as_url.as_ref(),
721                        imports,
722                        can_gc,
723                    )?;
724                    break;
725                }
726            }
727
728            // Step 11. If result is null, set result to the result of resolving an imports match given
729            // normalizedSpecifier, asURL, and importMap's imports.
730            if result.is_none() {
731                result = resolve_imports_match(
732                    normalized_specifier,
733                    as_url.as_ref(),
734                    &map.imports,
735                    can_gc,
736                )?;
737            }
738        }
739
740        // Step 12. If result is null, set it to asURL.
741        if result.is_none() {
742            result = as_url.clone();
743        }
744
745        // Step 13. If result is not null, then:
746        match result {
747            Some(result) => {
748                // Step 13.1 Add module to resolved module set given settingsObject, serializedBaseURL,
749                // normalizedSpecifier, and asURL.
750                global.add_module_to_resolved_module_set(
751                    serialized_base_url,
752                    normalized_specifier,
753                    as_url.clone(),
754                );
755                // Step 13.2 Return result.
756                Ok(result)
757            },
758            // Step 14. Throw a TypeError indicating that specifier was a bare specifier,
759            // but was not remapped to anything by importMap.
760            None => Err(Error::Type(
761                "Specifier was a bare specifier, but was not remapped to anything by importMap."
762                    .to_owned(),
763            )),
764        }
765    }
766
767    /// <https://html.spec.whatwg.org/multipage/#resolving-a-url-like-module-specifier>
768    fn resolve_url_like_module_specifier(
769        specifier: &DOMString,
770        base_url: &ServoUrl,
771    ) -> Option<ServoUrl> {
772        // Step 1. If specifier starts with "/", "./", or "../", then:
773        if specifier.starts_with('/') ||
774            specifier.starts_with_str("./") ||
775            specifier.starts_with_str("../")
776        {
777            // Step 1.1. Let url be the result of URL parsing specifier with baseURL.
778            return ServoUrl::parse_with_base(Some(base_url), &specifier.str()).ok();
779        }
780        // Step 2. Let url be the result of URL parsing specifier (with no base URL).
781        ServoUrl::parse(&specifier.str()).ok()
782    }
783
784    /// <https://html.spec.whatwg.org/multipage/#finding-the-first-parse-error>
785    fn find_first_parse_error(
786        &self,
787        global: &GlobalScope,
788        discovered_urls: &mut HashSet<ServoUrl>,
789    ) -> (Option<NetworkError>, Option<RethrowError>) {
790        // 3.
791        discovered_urls.insert(self.url.clone());
792
793        // 4.
794        let record = self.get_record().borrow();
795        if record.is_none() {
796            return (
797                self.network_error.borrow().clone(),
798                self.rethrow_error.borrow().clone(),
799            );
800        }
801
802        let module_map = global.get_module_map().borrow();
803        let mut parse_error: Option<RethrowError> = None;
804
805        // 5-6.
806        let descendant_urls = self.descendant_urls.borrow();
807        for descendant_module in descendant_urls
808            .iter()
809            // 7.
810            .filter_map(|url| module_map.get(&url.clone()))
811        {
812            // 8-2.
813            if discovered_urls.contains(&descendant_module.url) {
814                continue;
815            }
816
817            // 8-3.
818            let (child_network_error, child_parse_error) =
819                descendant_module.find_first_parse_error(global, discovered_urls);
820
821            // Due to network error's priority higher than parse error,
822            // we will return directly when we meet a network error.
823            if child_network_error.is_some() {
824                return (child_network_error, None);
825            }
826
827            // 8-4.
828            //
829            // In case of having any network error in other descendants,
830            // we will store the "first" parse error and keep running this
831            // loop to ensure we don't have any network error.
832            if child_parse_error.is_some() && parse_error.is_none() {
833                parse_error = child_parse_error;
834            }
835        }
836
837        // Step 9.
838        (None, parse_error)
839    }
840
841    // FIXME: spec links in this function are all broken, so it’s unclear what this algorithm does
842    /// <https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-a-module-script>
843    fn fetch_module_descendants(
844        &self,
845        owner: &ModuleOwner,
846        destination: Destination,
847        options: &ScriptFetchOptions,
848        parent_identity: ModuleIdentity,
849        can_gc: CanGc,
850    ) {
851        debug!("Start to load dependencies of {}", self.url);
852
853        let global = owner.global();
854
855        self.set_status(ModuleStatus::FetchingDescendants);
856
857        let specifier_urls = {
858            let raw_record = self.record.borrow();
859            match raw_record.as_ref() {
860                // Step 1.
861                None => {
862                    self.set_status(ModuleStatus::Finished);
863                    debug!(
864                        "Module {} doesn't have module record but tried to load descendants.",
865                        self.url
866                    );
867                    return;
868                },
869                // Step 5.
870                Some(raw_record) => {
871                    self.resolve_requested_module_specifiers(&global, raw_record.handle(), can_gc)
872                },
873            }
874        };
875
876        match specifier_urls {
877            // Step 3.
878            Ok(valid_specifier_urls) if valid_specifier_urls.is_empty() => {
879                debug!("Module {} doesn't have any dependencies.", self.url);
880                self.advance_finished_and_link(&global, can_gc);
881            },
882            Ok(valid_specifier_urls) => {
883                self.descendant_urls
884                    .borrow_mut()
885                    .extend(valid_specifier_urls.clone());
886
887                let urls_to_fetch = {
888                    let mut urls = IndexSet::new();
889                    let mut visited_urls = self.visited_urls.borrow_mut();
890
891                    for parsed_url in &valid_specifier_urls {
892                        // Step 5-3.
893                        if !visited_urls.contains(parsed_url) {
894                            // Step 5-3-1.
895                            urls.insert(parsed_url.clone());
896                            // Step 5-3-2.
897                            visited_urls.insert(parsed_url.clone());
898
899                            self.insert_incomplete_fetch_url(parsed_url);
900                        }
901                    }
902                    urls
903                };
904
905                // Step 3.
906                if urls_to_fetch.is_empty() {
907                    debug!(
908                        "After checking with visited urls, module {} doesn't have dependencies to load.",
909                        &self.url
910                    );
911                    self.advance_finished_and_link(&global, can_gc);
912                    return;
913                }
914
915                // Step 8.
916
917                let visited_urls = self.visited_urls.borrow().clone();
918                let options = options.descendant_fetch_options();
919
920                for url in urls_to_fetch {
921                    // https://html.spec.whatwg.org/multipage/#internal-module-script-graph-fetching-procedure
922                    // Step 1.
923                    assert!(self.visited_urls.borrow().contains(&url));
924
925                    // Step 2.
926                    fetch_single_module_script(
927                        owner.clone(),
928                        url,
929                        visited_urls.clone(),
930                        destination,
931                        options.clone(),
932                        Some(parent_identity.clone()),
933                        false,
934                        None,
935                        // TODO: is this correct?
936                        Some(IntroductionType::IMPORTED_MODULE),
937                        can_gc,
938                    );
939                }
940            },
941            Err(error) => {
942                self.set_rethrow_error(error);
943                self.advance_finished_and_link(&global, can_gc);
944            },
945        }
946    }
947
948    /// <https://html.spec.whatwg.org/multipage/#fetch-the-descendants-of-and-link-a-module-script>
949    /// step 4-7.
950    fn advance_finished_and_link(&self, global: &GlobalScope, can_gc: CanGc) {
951        {
952            if !self.has_all_ready_descendants(global) {
953                return;
954            }
955        }
956
957        self.set_status(ModuleStatus::Finished);
958
959        debug!("Going to advance and finish for: {}", self.url);
960
961        {
962            // Notify parents of this module to finish
963            //
964            // Before notifying, if the parent module has already had zero incomplete
965            // fetches, then it means we don't need to notify it.
966            let parent_identities = self.parent_identities.borrow();
967            for parent_identity in parent_identities.iter() {
968                let parent_tree = parent_identity.get_module_tree(global);
969
970                let incomplete_count_before_remove = {
971                    let incomplete_urls = parent_tree.get_incomplete_fetch_urls().borrow();
972                    incomplete_urls.len()
973                };
974
975                if incomplete_count_before_remove > 0 {
976                    parent_tree.remove_incomplete_fetch_url(&self.url);
977                    parent_tree.advance_finished_and_link(global, can_gc);
978                }
979            }
980        }
981
982        let mut discovered_urls: HashSet<ServoUrl> = HashSet::new();
983        let (network_error, rethrow_error) =
984            self.find_first_parse_error(global, &mut discovered_urls);
985
986        match (network_error, rethrow_error) {
987            (Some(network_error), _) => {
988                self.set_network_error(network_error);
989            },
990            (None, None) => {
991                let module_record = self.get_record().borrow();
992                if let Some(record) = &*module_record {
993                    let instantiated = self.instantiate_module_tree(global, record.handle());
994
995                    if let Err(exception) = instantiated {
996                        self.set_rethrow_error(exception);
997                    }
998                }
999            },
1000            (None, Some(error)) => {
1001                self.set_rethrow_error(error);
1002            },
1003        }
1004
1005        let promise = self.promise.borrow();
1006        if let Some(promise) = promise.as_ref() {
1007            promise.resolve_native(&(), can_gc);
1008        }
1009    }
1010}
1011
1012#[derive(JSTraceable, MallocSizeOf)]
1013struct ModuleHandler {
1014    #[ignore_malloc_size_of = "Measuring trait objects is hard"]
1015    task: DomRefCell<Option<Box<dyn TaskBox>>>,
1016}
1017
1018impl ModuleHandler {
1019    pub(crate) fn new_boxed(task: Box<dyn TaskBox>) -> Box<dyn Callback> {
1020        Box::new(Self {
1021            task: DomRefCell::new(Some(task)),
1022        })
1023    }
1024}
1025
1026impl Callback for ModuleHandler {
1027    fn callback(&self, _cx: SafeJSContext, _v: HandleValue, _realm: InRealm, _can_gc: CanGc) {
1028        let task = self.task.borrow_mut().take().unwrap();
1029        task.run_box();
1030    }
1031}
1032
1033/// The owner of the module
1034/// It can be `worker` or `script` element
1035#[derive(Clone)]
1036pub(crate) enum ModuleOwner {
1037    #[expect(dead_code)]
1038    Worker(TrustedWorkerAddress),
1039    Window(Trusted<HTMLScriptElement>),
1040    DynamicModule(Trusted<DynamicModuleOwner>),
1041}
1042
1043impl ModuleOwner {
1044    pub(crate) fn global(&self) -> DomRoot<GlobalScope> {
1045        match &self {
1046            ModuleOwner::Worker(worker) => (*worker.root().clone()).global(),
1047            ModuleOwner::Window(script) => (*script.root()).global(),
1048            ModuleOwner::DynamicModule(dynamic_module) => (*dynamic_module.root()).global(),
1049        }
1050    }
1051
1052    pub(crate) fn notify_owner_to_finish(
1053        &self,
1054        module_identity: ModuleIdentity,
1055        fetch_options: ScriptFetchOptions,
1056        can_gc: CanGc,
1057    ) {
1058        match &self {
1059            ModuleOwner::Worker(_) => unimplemented!(),
1060            ModuleOwner::DynamicModule(_) => unimplemented!(),
1061            ModuleOwner::Window(script) => {
1062                let global = self.global();
1063
1064                let document = script.root().owner_document();
1065                let load = {
1066                    let module_tree = module_identity.get_module_tree(&global);
1067
1068                    let network_error = module_tree.get_network_error().borrow();
1069                    match network_error.as_ref() {
1070                        Some(network_error) => Err(network_error.clone().into()),
1071                        None => match module_identity {
1072                            ModuleIdentity::ModuleUrl(script_src) => Ok(ScriptOrigin::external(
1073                                Rc::clone(&module_tree.get_text().borrow()),
1074                                script_src.clone(),
1075                                fetch_options,
1076                                ScriptType::Module,
1077                                global.unminified_js_dir(),
1078                            )),
1079                            ModuleIdentity::ScriptId(_) => Ok(ScriptOrigin::internal(
1080                                Rc::clone(&module_tree.get_text().borrow()),
1081                                document.base_url().clone(),
1082                                fetch_options,
1083                                ScriptType::Module,
1084                                global.unminified_js_dir(),
1085                                Err(Error::NotFound(None)),
1086                            )),
1087                        },
1088                    }
1089                };
1090
1091                let asynch = script
1092                    .root()
1093                    .upcast::<Element>()
1094                    .has_attribute(&local_name!("async"));
1095
1096                if !asynch && (*script.root()).get_parser_inserted() {
1097                    document.deferred_script_loaded(&script.root(), load, can_gc);
1098                } else if !asynch && !(*script.root()).get_non_blocking() {
1099                    document.asap_in_order_script_loaded(&script.root(), load, can_gc);
1100                } else {
1101                    document.asap_script_loaded(&script.root(), load, can_gc);
1102                };
1103            },
1104        }
1105    }
1106
1107    #[expect(unsafe_code)]
1108    /// <https://html.spec.whatwg.org/multipage/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability):fetch-an-import()-module-script-graph>
1109    /// Step 6-9
1110    fn finish_dynamic_module(
1111        &self,
1112        module_identity: ModuleIdentity,
1113        dynamic_module_id: DynamicModuleId,
1114        can_gc: CanGc,
1115    ) {
1116        let global = self.global();
1117
1118        let module = global.dynamic_module_list().remove(dynamic_module_id);
1119
1120        let cx = GlobalScope::get_cx();
1121        let module_tree = module_identity.get_module_tree(&global);
1122
1123        // In the timing of executing this `finish_dynamic_module` function,
1124        // we've run `find_first_parse_error` which means we've had the highest
1125        // priority error in the tree. So, we can just get both `network_error` and
1126        // `rethrow_error` directly here.
1127        let network_error = module_tree.get_network_error().borrow().as_ref().cloned();
1128        let existing_rethrow_error = module_tree.get_rethrow_error().borrow().as_ref().cloned();
1129
1130        rooted!(in(*cx) let mut rval = UndefinedValue());
1131        if network_error.is_none() && existing_rethrow_error.is_none() {
1132            let record = module_tree
1133                .get_record()
1134                .borrow()
1135                .as_ref()
1136                .map(|record| record.handle());
1137
1138            if let Some(record) = record {
1139                let evaluated = module_tree
1140                    .execute_module(&global, record, rval.handle_mut().into(), can_gc)
1141                    .err();
1142
1143                if let Some(exception) = evaluated.clone() {
1144                    module_tree.set_rethrow_error(exception);
1145                }
1146            }
1147        }
1148
1149        // Ensure any failures related to importing this dynamic module are immediately reported.
1150        match (network_error, existing_rethrow_error) {
1151            (Some(_), _) => unsafe {
1152                let err = gen_type_error(&global, "Dynamic import failed".to_owned(), can_gc);
1153                JS_SetPendingException(*cx, err.handle(), ExceptionStackBehavior::Capture);
1154            },
1155            (None, Some(rethrow_error)) => unsafe {
1156                JS_SetPendingException(
1157                    *cx,
1158                    rethrow_error.handle(),
1159                    ExceptionStackBehavior::Capture,
1160                );
1161            },
1162            // do nothing if there's no errors
1163            (None, None) => {},
1164        };
1165
1166        debug!("Finishing dynamic import for {:?}", module_identity);
1167
1168        rooted!(in(*cx) let mut evaluation_promise = ptr::null_mut::<JSObject>());
1169        if rval.is_object() {
1170            evaluation_promise.set(rval.to_object());
1171        }
1172
1173        unsafe {
1174            let ok = FinishDynamicModuleImport(
1175                *cx,
1176                evaluation_promise.handle().into(),
1177                module.referencing_private.handle(),
1178                module.specifier.handle(),
1179                module.promise.reflector().get_jsobject().into_handle(),
1180            );
1181            if ok {
1182                assert!(!JS_IsExceptionPending(*cx));
1183            } else {
1184                warn!("failed to finish dynamic module import");
1185            }
1186        }
1187    }
1188}
1189
1190/// The context required for asynchronously loading an external module script source.
1191struct ModuleContext {
1192    /// The owner of the module that initiated the request.
1193    owner: ModuleOwner,
1194    /// The response body received to date.
1195    data: Vec<u8>,
1196    /// The response metadata received to date.
1197    metadata: Option<Metadata>,
1198    /// The initial URL requested.
1199    url: ServoUrl,
1200    /// Destination of current module context
1201    destination: Destination,
1202    /// Options for the current script fetch
1203    options: ScriptFetchOptions,
1204    /// Indicates whether the request failed, and why
1205    status: Result<(), NetworkError>,
1206    /// `introductionType` value to set in the `CompileOptionsWrapper`.
1207    introduction_type: Option<&'static CStr>,
1208}
1209
1210impl FetchResponseListener for ModuleContext {
1211    // TODO(cybai): Perhaps add custom steps to perform fetch here?
1212    fn process_request_body(&mut self, _: RequestId) {}
1213
1214    // TODO(cybai): Perhaps add custom steps to perform fetch here?
1215    fn process_request_eof(&mut self, _: RequestId) {}
1216
1217    fn process_response(&mut self, _: RequestId, metadata: Result<FetchMetadata, NetworkError>) {
1218        self.metadata = metadata.ok().map(|meta| match meta {
1219            FetchMetadata::Unfiltered(m) => m,
1220            FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
1221        });
1222
1223        let status = self
1224            .metadata
1225            .as_ref()
1226            .map(|m| m.status.clone())
1227            .unwrap_or_else(HttpStatus::new_error);
1228
1229        self.status = {
1230            if status.is_error() {
1231                Err(NetworkError::Internal(
1232                    "No http status code received".to_owned(),
1233                ))
1234            } else if status.is_success() {
1235                Ok(())
1236            } else {
1237                Err(NetworkError::Internal(format!(
1238                    "HTTP error code {}",
1239                    status.code()
1240                )))
1241            }
1242        };
1243    }
1244
1245    fn process_response_chunk(&mut self, _: RequestId, mut chunk: Vec<u8>) {
1246        if self.status.is_ok() {
1247            self.data.append(&mut chunk);
1248        }
1249    }
1250
1251    /// <https://html.spec.whatwg.org/multipage/#fetch-a-single-module-script>
1252    /// Step 9-12
1253    fn process_response_eof(
1254        mut self,
1255        _: RequestId,
1256        response: Result<ResourceFetchTiming, NetworkError>,
1257    ) {
1258        let global = self.owner.global();
1259
1260        if let Some(window) = global.downcast::<Window>() {
1261            window
1262                .Document()
1263                .finish_load(LoadType::Script(self.url.clone()), CanGc::note());
1264        }
1265
1266        // Step 9-1 & 9-2.
1267        let load = response.clone().and(self.status.clone()).and_then(|_| {
1268            // Step 9-3.
1269            let meta = self.metadata.take().unwrap();
1270
1271            if let Some(content_type) = meta.content_type.map(Serde::into_inner) {
1272                if let Ok(content_type) = Mime::from_str(&content_type.to_string()) {
1273                    let essence_mime = content_type.essence_str();
1274
1275                    if !SCRIPT_JS_MIMES.contains(&essence_mime) {
1276                        return Err(NetworkError::Internal(format!(
1277                            "Invalid MIME type: {}",
1278                            essence_mime
1279                        )));
1280                    }
1281                } else {
1282                    return Err(NetworkError::Internal(format!(
1283                        "Failed to parse MIME type: {}",
1284                        content_type
1285                    )));
1286                }
1287            } else {
1288                return Err(NetworkError::Internal("No MIME type".into()));
1289            }
1290
1291            // Step 13.4: Let referrerPolicy be the result of parsing the `Referrer-Policy` header
1292            // given response.
1293            let referrer_policy = meta
1294                .headers
1295                .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
1296                .into();
1297
1298            // Step 13.5: If referrerPolicy is not the empty string, set options's referrer policy
1299            // to referrerPolicy.
1300            if referrer_policy != ReferrerPolicy::EmptyString {
1301                self.options.referrer_policy = referrer_policy;
1302            }
1303
1304            // Step 10.
1305            let (source_text, _, _) = UTF_8.decode(&self.data);
1306            Ok(ScriptOrigin::external(
1307                Rc::new(DOMString::from(source_text)),
1308                meta.final_url,
1309                self.options.clone(),
1310                ScriptType::Module,
1311                global.unminified_js_dir(),
1312            ))
1313        });
1314
1315        let module_tree = {
1316            let module_map = global.get_module_map().borrow();
1317            module_map.get(&self.url).unwrap().clone()
1318        };
1319
1320        module_tree.remove_incomplete_fetch_url(&self.url);
1321
1322        // Step 12.
1323        match load {
1324            Err(err) => {
1325                error!("Failed to fetch {} with error {:?}", &self.url, err);
1326                module_tree.set_network_error(err);
1327                module_tree.advance_finished_and_link(&global, CanGc::note());
1328            },
1329            Ok(ref resp_mod_script) => {
1330                module_tree.set_text(resp_mod_script.text());
1331
1332                let cx = GlobalScope::get_cx();
1333                rooted!(in(*cx) let mut compiled_module: *mut JSObject = ptr::null_mut());
1334                let compiled_module_result = module_tree.compile_module_script(
1335                    &global,
1336                    self.owner.clone(),
1337                    resp_mod_script.text(),
1338                    &self.url,
1339                    self.options.clone(),
1340                    compiled_module.handle_mut(),
1341                    false,
1342                    1, // external scripts start at the first line of the file
1343                    self.introduction_type,
1344                    CanGc::note(),
1345                );
1346
1347                match compiled_module_result {
1348                    Err(exception) => {
1349                        module_tree.set_rethrow_error(exception);
1350                        module_tree.advance_finished_and_link(&global, CanGc::note());
1351                    },
1352                    Ok(_) => {
1353                        module_tree.set_record(ModuleObject::new(compiled_module.handle()));
1354
1355                        module_tree.fetch_module_descendants(
1356                            &self.owner,
1357                            self.destination,
1358                            &self.options,
1359                            ModuleIdentity::ModuleUrl(self.url.clone()),
1360                            CanGc::note(),
1361                        );
1362                    },
1363                }
1364            },
1365        }
1366
1367        if let Ok(response) = response {
1368            network_listener::submit_timing(&self, &response, CanGc::note());
1369        }
1370    }
1371
1372    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
1373        let global = &self.resource_timing_global();
1374        global.report_csp_violations(violations, None, None);
1375    }
1376}
1377
1378impl ResourceTimingListener for ModuleContext {
1379    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
1380        let initiator_type = InitiatorType::LocalName("module".to_string());
1381        (initiator_type, self.url.clone())
1382    }
1383
1384    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
1385        self.owner.global()
1386    }
1387}
1388
1389#[expect(unsafe_code)]
1390#[allow(non_snake_case)]
1391/// A function to register module hooks (e.g. listening on resolving modules,
1392/// getting module metadata, getting script private reference and resolving dynamic import)
1393pub(crate) unsafe fn EnsureModuleHooksInitialized(rt: *mut JSRuntime) {
1394    unsafe {
1395        if GetModuleResolveHook(rt).is_some() {
1396            return;
1397        }
1398
1399        SetModuleResolveHook(rt, Some(HostResolveImportedModule));
1400        SetModuleMetadataHook(rt, Some(HostPopulateImportMeta));
1401        SetScriptPrivateReferenceHooks(
1402            rt,
1403            Some(host_add_ref_top_level_script),
1404            Some(host_release_top_level_script),
1405        );
1406        SetModuleDynamicImportHook(rt, Some(host_import_module_dynamically));
1407    }
1408}
1409
1410#[expect(unsafe_code)]
1411unsafe extern "C" fn host_add_ref_top_level_script(value: *const Value) {
1412    let val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
1413    mem::forget(val.clone());
1414    mem::forget(val);
1415}
1416
1417#[expect(unsafe_code)]
1418unsafe extern "C" fn host_release_top_level_script(value: *const Value) {
1419    let _val = unsafe { Rc::from_raw((*value).to_private() as *const ModuleScript) };
1420}
1421
1422#[expect(unsafe_code)]
1423/// <https://tc39.es/ecma262/#sec-hostimportmoduledynamically>
1424/// <https://html.spec.whatwg.org/multipage/#hostimportmoduledynamically(referencingscriptormodule,-specifier,-promisecapability)>
1425pub(crate) unsafe extern "C" fn host_import_module_dynamically(
1426    cx: *mut JSContext,
1427    reference_private: RawHandleValue,
1428    specifier: RawHandle<*mut JSObject>,
1429    promise: RawHandle<*mut JSObject>,
1430) -> bool {
1431    // Step 1.
1432    let cx = unsafe { SafeJSContext::from_ptr(cx) };
1433    let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
1434    let global_scope = unsafe { GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)) };
1435    let promise = Promise::new_with_js_promise(unsafe { Handle::from_raw(promise) }, cx);
1436
1437    // Step 5 & 6.
1438    if let Err(e) = fetch_an_import_module_script_graph(
1439        &global_scope,
1440        specifier,
1441        reference_private,
1442        promise,
1443        CanGc::note(),
1444    ) {
1445        unsafe { JS_SetPendingException(*cx, e.handle(), ExceptionStackBehavior::Capture) };
1446        return false;
1447    }
1448
1449    true
1450}
1451
1452#[derive(Clone, Debug, JSTraceable, MallocSizeOf)]
1453/// <https://html.spec.whatwg.org/multipage/#script-fetch-options>
1454pub(crate) struct ScriptFetchOptions {
1455    #[no_trace]
1456    pub(crate) referrer: Referrer,
1457    pub(crate) integrity_metadata: String,
1458    #[no_trace]
1459    pub(crate) credentials_mode: CredentialsMode,
1460    pub(crate) cryptographic_nonce: String,
1461    #[no_trace]
1462    pub(crate) parser_metadata: ParserMetadata,
1463    #[no_trace]
1464    pub(crate) referrer_policy: ReferrerPolicy,
1465}
1466
1467impl ScriptFetchOptions {
1468    /// <https://html.spec.whatwg.org/multipage/#default-classic-script-fetch-options>
1469    pub(crate) fn default_classic_script(global: &GlobalScope) -> ScriptFetchOptions {
1470        Self {
1471            cryptographic_nonce: String::new(),
1472            integrity_metadata: String::new(),
1473            referrer: global.get_referrer(),
1474            parser_metadata: ParserMetadata::NotParserInserted,
1475            credentials_mode: CredentialsMode::CredentialsSameOrigin,
1476            referrer_policy: ReferrerPolicy::EmptyString,
1477        }
1478    }
1479
1480    /// <https://html.spec.whatwg.org/multipage/#descendant-script-fetch-options>
1481    fn descendant_fetch_options(&self) -> ScriptFetchOptions {
1482        Self {
1483            referrer: self.referrer.clone(),
1484            integrity_metadata: String::new(),
1485            cryptographic_nonce: self.cryptographic_nonce.clone(),
1486            credentials_mode: self.credentials_mode,
1487            parser_metadata: self.parser_metadata,
1488            referrer_policy: self.referrer_policy,
1489        }
1490    }
1491}
1492
1493#[expect(unsafe_code)]
1494unsafe fn module_script_from_reference_private(
1495    reference_private: &RawHandle<JSVal>,
1496) -> Option<&ModuleScript> {
1497    if reference_private.get().is_undefined() {
1498        return None;
1499    }
1500    unsafe { (reference_private.get().to_private() as *const ModuleScript).as_ref() }
1501}
1502
1503/// <https://html.spec.whatwg.org/multipage/#fetch-an-import()-module-script-graph>
1504#[expect(unsafe_code)]
1505fn fetch_an_import_module_script_graph(
1506    global: &GlobalScope,
1507    module_request: RawHandle<*mut JSObject>,
1508    reference_private: RawHandleValue,
1509    promise: Rc<Promise>,
1510    can_gc: CanGc,
1511) -> Result<(), RethrowError> {
1512    // Step 1.
1513    let cx = GlobalScope::get_cx();
1514    let specifier = unsafe {
1515        let jsstr = std::ptr::NonNull::new(GetModuleRequestSpecifier(*cx, module_request)).unwrap();
1516        DOMString::from_string(jsstr_to_string(*cx, jsstr))
1517    };
1518    let mut options = ScriptFetchOptions::default_classic_script(global);
1519    let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1520    if let Some(data) = module_data {
1521        options = data.options.descendant_fetch_options();
1522    }
1523    let url = ModuleTree::resolve_module_specifier(global, module_data, specifier, can_gc);
1524
1525    // Step 2.
1526    if url.is_err() {
1527        let specifier_error = gen_type_error(global, "Wrong module specifier".to_owned(), can_gc);
1528        return Err(specifier_error);
1529    }
1530
1531    let dynamic_module_id = DynamicModuleId(Uuid::new_v4());
1532
1533    // Step 3.
1534    let owner = match unsafe { module_script_from_reference_private(&reference_private) } {
1535        Some(module_data) if module_data.owner.is_some() => module_data.owner.clone().unwrap(),
1536        _ => ModuleOwner::DynamicModule(Trusted::new(&DynamicModuleOwner::new(
1537            global,
1538            promise.clone(),
1539            dynamic_module_id,
1540            can_gc,
1541        ))),
1542    };
1543
1544    let dynamic_module = RootedTraceableBox::new(DynamicModule {
1545        promise,
1546        specifier: Heap::default(),
1547        referencing_private: Heap::default(),
1548        id: dynamic_module_id,
1549    });
1550    dynamic_module.specifier.set(module_request.get());
1551    dynamic_module
1552        .referencing_private
1553        .set(reference_private.get());
1554
1555    let url = url.unwrap();
1556
1557    let mut visited_urls = HashSet::new();
1558    visited_urls.insert(url.clone());
1559
1560    fetch_single_module_script(
1561        owner,
1562        url,
1563        visited_urls,
1564        Destination::Script,
1565        options,
1566        None,
1567        true,
1568        Some(dynamic_module),
1569        Some(IntroductionType::IMPORTED_MODULE),
1570        can_gc,
1571    );
1572    Ok(())
1573}
1574
1575#[expect(unsafe_code)]
1576#[allow(non_snake_case)]
1577/// <https://tc39.es/ecma262/#sec-HostLoadImportedModule>
1578/// <https://html.spec.whatwg.org/multipage/#hostloadimportedmodule>
1579unsafe extern "C" fn HostResolveImportedModule(
1580    cx: *mut JSContext,
1581    reference_private: RawHandleValue,
1582    specifier: RawHandle<*mut JSObject>,
1583) -> *mut JSObject {
1584    let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1585    let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1586
1587    // Step 5.
1588    let module_data = unsafe { module_script_from_reference_private(&reference_private) };
1589    let jsstr =
1590        std::ptr::NonNull::new(unsafe { GetModuleRequestSpecifier(cx, specifier) }).unwrap();
1591    let specifier = DOMString::from_string(unsafe { jsstr_to_string(cx, jsstr) });
1592    let url =
1593        ModuleTree::resolve_module_specifier(&global_scope, module_data, specifier, CanGc::note());
1594
1595    // Step 6.
1596    assert!(url.is_ok());
1597
1598    let parsed_url = url.unwrap();
1599
1600    // Step 4 & 7.
1601    let module_map = global_scope.get_module_map().borrow();
1602
1603    let module_tree = module_map.get(&parsed_url);
1604
1605    // Step 9.
1606    assert!(module_tree.is_some());
1607
1608    let fetched_module_object = module_tree.unwrap().get_record().borrow();
1609
1610    // Step 8.
1611    assert!(fetched_module_object.is_some());
1612
1613    // Step 10.
1614    if let Some(record) = &*fetched_module_object {
1615        return record.handle().get();
1616    }
1617
1618    unreachable!()
1619}
1620
1621#[expect(unsafe_code)]
1622#[allow(non_snake_case)]
1623/// <https://tc39.es/ecma262/#sec-hostgetimportmetaproperties>
1624/// <https://html.spec.whatwg.org/multipage/#hostgetimportmetaproperties>
1625unsafe extern "C" fn HostPopulateImportMeta(
1626    cx: *mut JSContext,
1627    reference_private: RawHandleValue,
1628    meta_object: RawHandle<*mut JSObject>,
1629) -> bool {
1630    let in_realm_proof = AlreadyInRealm::assert_for_cx(unsafe { SafeJSContext::from_ptr(cx) });
1631    let global_scope = unsafe { GlobalScope::from_context(cx, InRealm::Already(&in_realm_proof)) };
1632
1633    // Step 2.
1634    let base_url = match unsafe { module_script_from_reference_private(&reference_private) } {
1635        Some(module_data) => module_data.base_url.clone(),
1636        None => global_scope.api_base_url(),
1637    };
1638
1639    let url_string = unsafe {
1640        JS_NewStringCopyN(
1641            cx,
1642            base_url.as_str().as_ptr() as *const _,
1643            base_url.as_str().len(),
1644        )
1645    };
1646    rooted!(in(cx) let url_string = url_string);
1647
1648    // Step 3.
1649    unsafe {
1650        JS_DefineProperty4(
1651            cx,
1652            meta_object,
1653            c"url".as_ptr(),
1654            url_string.handle().into_handle(),
1655            JSPROP_ENUMERATE.into(),
1656        )
1657    }
1658}
1659
1660/// <https://html.spec.whatwg.org/multipage/#fetch-a-module-script-tree>
1661pub(crate) fn fetch_external_module_script(
1662    owner: ModuleOwner,
1663    url: ServoUrl,
1664    destination: Destination,
1665    options: ScriptFetchOptions,
1666    can_gc: CanGc,
1667) {
1668    let mut visited_urls = HashSet::new();
1669    visited_urls.insert(url.clone());
1670
1671    // Step 1.
1672    fetch_single_module_script(
1673        owner,
1674        url,
1675        visited_urls,
1676        destination,
1677        options,
1678        None,
1679        true,
1680        None,
1681        Some(IntroductionType::SRC_SCRIPT),
1682        can_gc,
1683    )
1684}
1685
1686#[derive(JSTraceable, MallocSizeOf)]
1687#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1688pub(crate) struct DynamicModuleList {
1689    requests: Vec<RootedTraceableBox<DynamicModule>>,
1690
1691    #[ignore_malloc_size_of = "Define in uuid"]
1692    next_id: DynamicModuleId,
1693}
1694
1695impl DynamicModuleList {
1696    pub(crate) fn new() -> Self {
1697        Self {
1698            requests: vec![],
1699            next_id: DynamicModuleId(Uuid::new_v4()),
1700        }
1701    }
1702
1703    fn push(&mut self, mut module: RootedTraceableBox<DynamicModule>) -> DynamicModuleId {
1704        let id = self.next_id;
1705        self.next_id = DynamicModuleId(Uuid::new_v4());
1706        module.id = id;
1707        self.requests.push(module);
1708        id
1709    }
1710
1711    fn remove(&mut self, id: DynamicModuleId) -> RootedTraceableBox<DynamicModule> {
1712        let index = self
1713            .requests
1714            .iter()
1715            .position(|module| module.id == id)
1716            .expect("missing dynamic module");
1717        self.requests.remove(index)
1718    }
1719}
1720
1721#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
1722#[derive(JSTraceable, MallocSizeOf)]
1723struct DynamicModule {
1724    #[conditional_malloc_size_of]
1725    promise: Rc<Promise>,
1726    #[ignore_malloc_size_of = "GC types are hard"]
1727    specifier: Heap<*mut JSObject>,
1728    #[ignore_malloc_size_of = "GC types are hard"]
1729    referencing_private: Heap<JSVal>,
1730    #[ignore_malloc_size_of = "Defined in uuid"]
1731    id: DynamicModuleId,
1732}
1733
1734/// <https://html.spec.whatwg.org/multipage/#fetch-a-single-module-script>
1735#[allow(clippy::too_many_arguments)]
1736fn fetch_single_module_script(
1737    owner: ModuleOwner,
1738    url: ServoUrl,
1739    visited_urls: HashSet<ServoUrl>,
1740    destination: Destination,
1741    options: ScriptFetchOptions,
1742    parent_identity: Option<ModuleIdentity>,
1743    top_level_module_fetch: bool,
1744    dynamic_module: Option<RootedTraceableBox<DynamicModule>>,
1745    introduction_type: Option<&'static CStr>,
1746    can_gc: CanGc,
1747) {
1748    {
1749        // Step 1.
1750        let global = owner.global();
1751        let module_map = global.get_module_map().borrow();
1752
1753        debug!("Start to fetch {}", url);
1754
1755        if let Some(module_tree) = module_map.get(&url.clone()) {
1756            let status = module_tree.get_status();
1757
1758            debug!("Meet a fetched url {} and its status is {:?}", url, status);
1759
1760            match dynamic_module {
1761                Some(module) => module_tree.append_dynamic_module_handler(
1762                    owner.clone(),
1763                    ModuleIdentity::ModuleUrl(url.clone()),
1764                    module,
1765                    can_gc,
1766                ),
1767                None if top_level_module_fetch => module_tree.append_handler(
1768                    owner.clone(),
1769                    ModuleIdentity::ModuleUrl(url.clone()),
1770                    options,
1771                    can_gc,
1772                ),
1773                // do nothing if it's neither a dynamic module nor a top level module
1774                None => {},
1775            }
1776
1777            if let Some(parent_identity) = parent_identity {
1778                module_tree.insert_parent_identity(parent_identity);
1779            }
1780
1781            match status {
1782                ModuleStatus::Initial => unreachable!(
1783                    "We have the module in module map so its status should not be `initial`"
1784                ),
1785                // Step 2.
1786                ModuleStatus::Fetching => {},
1787                // Step 3.
1788                ModuleStatus::FetchingDescendants | ModuleStatus::Finished => {
1789                    module_tree.advance_finished_and_link(&global, can_gc);
1790                },
1791            }
1792
1793            return;
1794        }
1795    }
1796
1797    let global = owner.global();
1798    let is_external = true;
1799    let module_tree = ModuleTree::new(url.clone(), is_external, visited_urls);
1800    module_tree.set_status(ModuleStatus::Fetching);
1801
1802    match dynamic_module {
1803        Some(module) => module_tree.append_dynamic_module_handler(
1804            owner.clone(),
1805            ModuleIdentity::ModuleUrl(url.clone()),
1806            module,
1807            can_gc,
1808        ),
1809        None if top_level_module_fetch => module_tree.append_handler(
1810            owner.clone(),
1811            ModuleIdentity::ModuleUrl(url.clone()),
1812            options.clone(),
1813            can_gc,
1814        ),
1815        // do nothing if it's neither a dynamic module nor a top level module
1816        None => {},
1817    }
1818
1819    if let Some(parent_identity) = parent_identity {
1820        module_tree.insert_parent_identity(parent_identity);
1821    }
1822
1823    module_tree.insert_incomplete_fetch_url(&url);
1824
1825    // Step 4.
1826    global.set_module_map(url.clone(), module_tree);
1827
1828    // Step 5-6.
1829    let mode = match destination {
1830        Destination::Worker | Destination::SharedWorker if top_level_module_fetch => {
1831            RequestMode::SameOrigin
1832        },
1833        _ => RequestMode::CorsMode,
1834    };
1835
1836    let document: Option<DomRoot<Document>> = match &owner {
1837        ModuleOwner::Worker(_) | ModuleOwner::DynamicModule(_) => None,
1838        ModuleOwner::Window(script) => Some(script.root().owner_document()),
1839    };
1840    let webview_id = document.as_ref().map(|document| document.webview_id());
1841
1842    // Step 7-8.
1843    let request = RequestBuilder::new(webview_id, url.clone(), global.get_referrer())
1844        .destination(destination)
1845        .origin(global.origin().immutable().clone())
1846        .parser_metadata(options.parser_metadata)
1847        .integrity_metadata(options.integrity_metadata.clone())
1848        .credentials_mode(options.credentials_mode)
1849        .referrer_policy(options.referrer_policy)
1850        .mode(mode)
1851        .insecure_requests_policy(global.insecure_requests_policy())
1852        .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_origin())
1853        .policy_container(global.policy_container().to_owned())
1854        .cryptographic_nonce_metadata(options.cryptographic_nonce.clone());
1855
1856    let context = ModuleContext {
1857        owner,
1858        data: vec![],
1859        metadata: None,
1860        url: url.clone(),
1861        destination,
1862        options,
1863        status: Ok(()),
1864        introduction_type,
1865    };
1866
1867    let network_listener = NetworkListener::new(
1868        context,
1869        global.task_manager().networking_task_source().to_sendable(),
1870    );
1871    match document {
1872        Some(document) => {
1873            let request = document.prepare_request(request);
1874            document.loader_mut().fetch_async_with_callback(
1875                LoadType::Script(url),
1876                request,
1877                network_listener.into_callback(),
1878            );
1879        },
1880        None => global.fetch_with_network_listener(request, network_listener),
1881    }
1882}
1883
1884/// <https://html.spec.whatwg.org/multipage/#fetch-an-inline-module-script-graph>
1885pub(crate) fn fetch_inline_module_script(
1886    owner: ModuleOwner,
1887    module_script_text: Rc<DOMString>,
1888    url: ServoUrl,
1889    script_id: ScriptId,
1890    options: ScriptFetchOptions,
1891    line_number: u64,
1892    can_gc: CanGc,
1893) {
1894    let global = owner.global();
1895    let is_external = false;
1896    let module_tree = ModuleTree::new(url.clone(), is_external, HashSet::new());
1897
1898    let cx = GlobalScope::get_cx();
1899    rooted!(in(*cx) let mut compiled_module: *mut JSObject = ptr::null_mut());
1900    let compiled_module_result = module_tree.compile_module_script(
1901        &global,
1902        owner.clone(),
1903        module_script_text,
1904        &url,
1905        options.clone(),
1906        compiled_module.handle_mut(),
1907        true,
1908        line_number,
1909        Some(IntroductionType::INLINE_SCRIPT),
1910        can_gc,
1911    );
1912
1913    match compiled_module_result {
1914        Ok(_) => {
1915            module_tree.append_handler(
1916                owner.clone(),
1917                ModuleIdentity::ScriptId(script_id),
1918                options.clone(),
1919                can_gc,
1920            );
1921            module_tree.set_record(ModuleObject::new(compiled_module.handle()));
1922
1923            // We need to set `module_tree` into inline module map in case
1924            // of that the module descendants finished right after the
1925            // fetch module descendants step.
1926            global.set_inline_module_map(script_id, module_tree);
1927
1928            // Due to needed to set `module_tree` to inline module_map first,
1929            // we will need to retrieve it again so that we can do the fetch
1930            // module descendants step.
1931            let inline_module_map = global.get_inline_module_map().borrow();
1932            let module_tree = inline_module_map.get(&script_id).unwrap().clone();
1933
1934            module_tree.fetch_module_descendants(
1935                &owner,
1936                Destination::Script,
1937                &options,
1938                ModuleIdentity::ScriptId(script_id),
1939                can_gc,
1940            );
1941        },
1942        Err(exception) => {
1943            module_tree.set_rethrow_error(exception);
1944            module_tree.set_status(ModuleStatus::Finished);
1945            global.set_inline_module_map(script_id, module_tree);
1946            owner.notify_owner_to_finish(ModuleIdentity::ScriptId(script_id), options, can_gc);
1947        },
1948    }
1949}
1950
1951pub(crate) type ModuleSpecifierMap = IndexMap<String, Option<ServoUrl>>;
1952pub(crate) type ModuleIntegrityMap = IndexMap<ServoUrl, String>;
1953
1954/// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record>
1955#[derive(Default, Eq, Hash, JSTraceable, MallocSizeOf, PartialEq)]
1956pub(crate) struct ResolvedModule {
1957    /// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-serialized-base-url>
1958    base_url: String,
1959    /// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-specifier>
1960    specifier: String,
1961    /// <https://html.spec.whatwg.org/multipage/#specifier-resolution-record-as-url>
1962    #[no_trace]
1963    specifier_url: Option<ServoUrl>,
1964}
1965
1966impl ResolvedModule {
1967    pub(crate) fn new(
1968        base_url: String,
1969        specifier: String,
1970        specifier_url: Option<ServoUrl>,
1971    ) -> Self {
1972        Self {
1973            base_url,
1974            specifier,
1975            specifier_url,
1976        }
1977    }
1978}
1979
1980/// <https://html.spec.whatwg.org/multipage/#import-map-processing-model>
1981#[derive(Default, JSTraceable, MallocSizeOf)]
1982pub(crate) struct ImportMap {
1983    #[no_trace]
1984    imports: ModuleSpecifierMap,
1985    #[no_trace]
1986    scopes: IndexMap<ServoUrl, ModuleSpecifierMap>,
1987    #[no_trace]
1988    integrity: ModuleIntegrityMap,
1989}
1990
1991/// <https://html.spec.whatwg.org/multipage/#register-an-import-map>
1992pub(crate) fn register_import_map(
1993    global: &GlobalScope,
1994    result: Fallible<ImportMap>,
1995    can_gc: CanGc,
1996) {
1997    match result {
1998        Ok(new_import_map) => {
1999            // Step 2. Merge existing and new import maps, given global and result's import map.
2000            merge_existing_and_new_import_maps(global, new_import_map, can_gc);
2001        },
2002        Err(exception) => {
2003            // Step 1. If result's error to rethrow is not null, then report
2004            // an exception given by result's error to rethrow for global and return.
2005            throw_dom_exception(GlobalScope::get_cx(), global, exception.clone(), can_gc);
2006        },
2007    }
2008}
2009
2010/// <https://html.spec.whatwg.org/multipage/#merge-existing-and-new-import-maps>
2011fn merge_existing_and_new_import_maps(
2012    global: &GlobalScope,
2013    new_import_map: ImportMap,
2014    can_gc: CanGc,
2015) {
2016    // Step 1. Let newImportMapScopes be a deep copy of newImportMap's scopes.
2017    let new_import_map_scopes = new_import_map.scopes;
2018
2019    // Step 2. Let oldImportMap be global's import map.
2020    let mut old_import_map = global.import_map_mut();
2021
2022    // Step 3. Let newImportMapImports be a deep copy of newImportMap's imports.
2023    let mut new_import_map_imports = new_import_map.imports;
2024
2025    let resolved_module_set = global.resolved_module_set();
2026    // Step 4. For each scopePrefix → scopeImports of newImportMapScopes:
2027    for (scope_prefix, mut scope_imports) in new_import_map_scopes {
2028        // Step 4.1. For each record of global's resolved module set:
2029        for record in resolved_module_set.iter() {
2030            // If scopePrefix is record's serialized base URL, or if scopePrefix ends with
2031            // U+002F (/) and scopePrefix is a code unit prefix of record's serialized base URL, then:
2032            let prefix = scope_prefix.as_str();
2033            if prefix == record.base_url ||
2034                (record.base_url.starts_with(prefix) && prefix.ends_with('\u{002f}'))
2035            {
2036                // For each specifierKey → resolutionResult of scopeImports:
2037                scope_imports.retain(|key, val| {
2038                    // If specifierKey is record's specifier, or if all of the following conditions are true:
2039                    // specifierKey ends with U+002F (/);
2040                    // specifierKey is a code unit prefix of record's specifier;
2041                    // either record's specifier as a URL is null or is special,
2042                    if *key == record.specifier ||
2043                        (key.ends_with('\u{002f}') &&
2044                            record.specifier.starts_with(key) &&
2045                            (record.specifier_url.is_none() ||
2046                                record
2047                                    .specifier_url
2048                                    .as_ref()
2049                                    .map(|u| u.is_special_scheme())
2050                                    .unwrap_or_default()))
2051                    {
2052                        // The user agent may report a warning to the console indicating the ignored rule.
2053                        // They may choose to avoid reporting if the rule is identical to an existing one.
2054                        Console::internal_warn(
2055                            global,
2056                            DOMString::from(format!("Ignored rule: {key} -> {val:?}.")),
2057                        );
2058                        // Remove scopeImports[specifierKey].
2059                        false
2060                    } else {
2061                        true
2062                    }
2063                })
2064            }
2065        }
2066
2067        // Step 4.2 If scopePrefix exists in oldImportMap's scopes
2068        if old_import_map.scopes.contains_key(&scope_prefix) {
2069            // set oldImportMap's scopes[scopePrefix] to the result of
2070            // merging module specifier maps, given scopeImports and oldImportMap's scopes[scopePrefix].
2071            let merged_module_specifier_map = merge_module_specifier_maps(
2072                global,
2073                scope_imports,
2074                &old_import_map.scopes[&scope_prefix],
2075                can_gc,
2076            );
2077            old_import_map
2078                .scopes
2079                .insert(scope_prefix, merged_module_specifier_map);
2080        } else {
2081            // Step 4.3 Otherwise, set oldImportMap's scopes[scopePrefix] to scopeImports.
2082            old_import_map.scopes.insert(scope_prefix, scope_imports);
2083        }
2084    }
2085
2086    // Step 5. For each url → integrity of newImportMap's integrity:
2087    for (url, integrity) in &new_import_map.integrity {
2088        // Step 5.1 If url exists in oldImportMap's integrity, then:
2089        if old_import_map.integrity.contains_key(url) {
2090            // Step 5.1.1 The user agent may report a warning to the console indicating the ignored rule.
2091            // They may choose to avoid reporting if the rule is identical to an existing one.
2092            Console::internal_warn(
2093                global,
2094                DOMString::from(format!("Ignored rule: {url} -> {integrity}.")),
2095            );
2096            // Step 5.1.2 Continue.
2097            continue;
2098        }
2099
2100        // Step 5.2 Set oldImportMap's integrity[url] to integrity.
2101        old_import_map
2102            .integrity
2103            .insert(url.clone(), integrity.clone());
2104    }
2105
2106    // Step 6. For each record of global's resolved module set:
2107    for record in resolved_module_set.iter() {
2108        // For each specifier → url of newImportMapImports:
2109        new_import_map_imports.retain(|specifier, val| {
2110            // If specifier starts with record's specifier, then:
2111            if specifier.starts_with(&record.specifier) {
2112                // The user agent may report a warning to the console indicating the ignored rule.
2113                // They may choose to avoid reporting if the rule is identical to an existing one.
2114                Console::internal_warn(
2115                    global,
2116                    DOMString::from(format!("Ignored rule: {specifier} -> {val:?}.")),
2117                );
2118                // Remove newImportMapImports[specifier].
2119                false
2120            } else {
2121                true
2122            }
2123        });
2124    }
2125
2126    // Step 7. Set oldImportMap's imports to the result of merge module specifier maps,
2127    // given newImportMapImports and oldImportMap's imports.
2128    let merged_module_specifier_map = merge_module_specifier_maps(
2129        global,
2130        new_import_map_imports,
2131        &old_import_map.imports,
2132        can_gc,
2133    );
2134    old_import_map.imports = merged_module_specifier_map;
2135}
2136
2137/// <https://html.spec.whatwg.org/multipage/#merge-module-specifier-maps>
2138fn merge_module_specifier_maps(
2139    global: &GlobalScope,
2140    new_map: ModuleSpecifierMap,
2141    old_map: &ModuleSpecifierMap,
2142    _can_gc: CanGc,
2143) -> ModuleSpecifierMap {
2144    // Step 1. Let mergedMap be a deep copy of oldMap.
2145    let mut merged_map = old_map.clone();
2146
2147    // Step 2. For each specifier → url of newMap:
2148    for (specifier, url) in new_map {
2149        // Step 2.1 If specifier exists in oldMap, then:
2150        if old_map.contains_key(&specifier) {
2151            // Step 2.1.1 The user agent may report a warning to the console indicating the ignored rule.
2152            // They may choose to avoid reporting if the rule is identical to an existing one.
2153            Console::internal_warn(
2154                global,
2155                DOMString::from(format!("Ignored rule: {specifier} -> {url:?}.")),
2156            );
2157
2158            // Step 2.1.2 Continue.
2159            continue;
2160        }
2161
2162        // Step 2.2 Set mergedMap[specifier] to url.
2163        merged_map.insert(specifier, url);
2164    }
2165
2166    merged_map
2167}
2168
2169/// <https://html.spec.whatwg.org/multipage/#parse-an-import-map-string>
2170pub(crate) fn parse_an_import_map_string(
2171    module_owner: ModuleOwner,
2172    input: Rc<DOMString>,
2173    base_url: ServoUrl,
2174    can_gc: CanGc,
2175) -> Fallible<ImportMap> {
2176    // Step 1. Let parsed be the result of parsing a JSON string to an Infra value given input.
2177    let parsed: JsonValue = serde_json::from_str(&input.str())
2178        .map_err(|_| Error::Type("The value needs to be a JSON object.".to_owned()))?;
2179    // Step 2. If parsed is not an ordered map, then throw a TypeError indicating that the
2180    // top-level value needs to be a JSON object.
2181    let JsonValue::Object(mut parsed) = parsed else {
2182        return Err(Error::Type(
2183            "The top-level value needs to be a JSON object.".to_owned(),
2184        ));
2185    };
2186
2187    // Step 3. Let sortedAndNormalizedImports be an empty ordered map.
2188    let mut sorted_and_normalized_imports = ModuleSpecifierMap::new();
2189    // Step 4. If parsed["imports"] exists, then:
2190    if let Some(imports) = parsed.get("imports") {
2191        // Step 4.1 If parsed["imports"] is not an ordered map, then throw a TypeError
2192        // indicating that the value for the "imports" top-level key needs to be a JSON object.
2193        let JsonValue::Object(imports) = imports else {
2194            return Err(Error::Type(
2195                "The \"imports\" top-level value needs to be a JSON object.".to_owned(),
2196            ));
2197        };
2198        // Step 4.2 Set sortedAndNormalizedImports to the result of sorting and
2199        // normalizing a module specifier map given parsed["imports"] and baseURL.
2200        sorted_and_normalized_imports = sort_and_normalize_module_specifier_map(
2201            &module_owner.global(),
2202            imports,
2203            &base_url,
2204            can_gc,
2205        );
2206    }
2207
2208    // Step 5. Let sortedAndNormalizedScopes be an empty ordered map.
2209    let mut sorted_and_normalized_scopes: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2210    // Step 6. If parsed["scopes"] exists, then:
2211    if let Some(scopes) = parsed.get("scopes") {
2212        // Step 6.1 If parsed["scopes"] is not an ordered map, then throw a TypeError
2213        // indicating that the value for the "scopes" top-level key needs to be a JSON object.
2214        let JsonValue::Object(scopes) = scopes else {
2215            return Err(Error::Type(
2216                "The \"scopes\" top-level value needs to be a JSON object.".to_owned(),
2217            ));
2218        };
2219        // Step 6.2 Set sortedAndNormalizedScopes to the result of sorting and
2220        // normalizing scopes given parsed["scopes"] and baseURL.
2221        sorted_and_normalized_scopes =
2222            sort_and_normalize_scopes(&module_owner.global(), scopes, &base_url, can_gc)?;
2223    }
2224
2225    // Step 7. Let normalizedIntegrity be an empty ordered map.
2226    let mut normalized_integrity = ModuleIntegrityMap::new();
2227    // Step 8. If parsed["integrity"] exists, then:
2228    if let Some(integrity) = parsed.get("integrity") {
2229        // Step 8.1 If parsed["integrity"] is not an ordered map, then throw a TypeError
2230        // indicating that the value for the "integrity" top-level key needs to be a JSON object.
2231        let JsonValue::Object(integrity) = integrity else {
2232            return Err(Error::Type(
2233                "The \"integrity\" top-level value needs to be a JSON object.".to_owned(),
2234            ));
2235        };
2236        // Step 8.2 Set normalizedIntegrity to the result of normalizing
2237        // a module integrity map given parsed["integrity"] and baseURL.
2238        normalized_integrity =
2239            normalize_module_integrity_map(&module_owner.global(), integrity, &base_url, can_gc);
2240    }
2241
2242    // Step 9. If parsed's keys contains any items besides "imports", "scopes", or "integrity",
2243    // then the user agent should report a warning to the console indicating that an invalid
2244    // top-level key was present in the import map.
2245    parsed.retain(|k, _| !matches!(k.as_str(), "imports" | "scopes" | "integrity"));
2246    if !parsed.is_empty() {
2247        Console::internal_warn(
2248            &module_owner.global(),
2249            DOMString::from(
2250                "Invalid top-level key was present in the import map.
2251                Only \"imports\", \"scopes\", and \"integrity\" are allowed.",
2252            ),
2253        );
2254    }
2255
2256    // Step 10. Return an import map
2257    Ok(ImportMap {
2258        imports: sorted_and_normalized_imports,
2259        scopes: sorted_and_normalized_scopes,
2260        integrity: normalized_integrity,
2261    })
2262}
2263
2264/// <https://html.spec.whatwg.org/multipage/#sorting-and-normalizing-a-module-specifier-map>
2265fn sort_and_normalize_module_specifier_map(
2266    global: &GlobalScope,
2267    original_map: &JsonMap<String, JsonValue>,
2268    base_url: &ServoUrl,
2269    can_gc: CanGc,
2270) -> ModuleSpecifierMap {
2271    // Step 1. Let normalized be an empty ordered map.
2272    let mut normalized = ModuleSpecifierMap::new();
2273
2274    // Step 2. For each specifier_key -> value in originalMap
2275    for (specifier_key, value) in original_map {
2276        // Step 2.1 Let normalized_specifier_key be the result of
2277        // normalizing a specifier key given specifier_key and base_url.
2278        let Some(normalized_specifier_key) =
2279            normalize_specifier_key(global, specifier_key, base_url, can_gc)
2280        else {
2281            // Step 2.2 If normalized_specifier_key is null, then continue.
2282            continue;
2283        };
2284
2285        // Step 2.3 If value is not a string, then:
2286        let JsonValue::String(value) = value else {
2287            // Step 2.3.1 The user agent may report a warning to the console
2288            // indicating that addresses need to be strings.
2289            Console::internal_warn(global, DOMString::from("Addresses need to be strings."));
2290
2291            // Step 2.3.2 Set normalized[normalized_specifier_key] to null.
2292            normalized.insert(normalized_specifier_key, None);
2293            // Step 2.3.3 Continue.
2294            continue;
2295        };
2296
2297        // Step 2.4. Let address_url be the result of resolving a URL-like module specifier given value and baseURL.
2298        let value = DOMString::from(value.as_str());
2299        let Some(address_url) = ModuleTree::resolve_url_like_module_specifier(&value, base_url)
2300        else {
2301            // Step 2.5 If address_url is null, then:
2302            // Step 2.5.1. The user agent may report a warning to the console
2303            // indicating that the address was invalid.
2304            Console::internal_warn(
2305                global,
2306                DOMString::from(format!(
2307                    "Value failed to resolve to module specifier: {value}"
2308                )),
2309            );
2310
2311            // Step 2.5.2 Set normalized[normalized_specifier_key] to null.
2312            normalized.insert(normalized_specifier_key, None);
2313            // Step 2.5.3 Continue.
2314            continue;
2315        };
2316
2317        // Step 2.6 If specifier_key ends with U+002F (/), and the serialization of
2318        // address_url does not end with U+002F (/), then:
2319        if specifier_key.ends_with('\u{002f}') && !address_url.as_str().ends_with('\u{002f}') {
2320            // step 2.6.1. The user agent may report a warning to the console
2321            // indicating that an invalid address was given for the specifier key specifierKey;
2322            // since specifierKey ends with a slash, the address needs to as well.
2323            Console::internal_warn(
2324                global,
2325                DOMString::from(format!(
2326                    "Invalid address for specifier key '{specifier_key}': {address_url}.
2327                    Since specifierKey ends with a slash, the address needs to as well."
2328                )),
2329            );
2330
2331            // Step 2.6.2 Set normalized[normalized_specifier_key] to null.
2332            normalized.insert(normalized_specifier_key, None);
2333            // Step 2.6.3 Continue.
2334            continue;
2335        }
2336
2337        // Step 2.7 Set normalized[normalized_specifier_key] to address_url.
2338        normalized.insert(normalized_specifier_key, Some(address_url));
2339    }
2340
2341    // Step 3. Return the result of sorting in descending order normalized
2342    // with an entry a being less than an entry b if a's key is code unit less than b's key.
2343    normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2344    normalized
2345}
2346
2347/// <https://html.spec.whatwg.org/multipage/#sorting-and-normalizing-scopes>
2348fn sort_and_normalize_scopes(
2349    global: &GlobalScope,
2350    original_map: &JsonMap<String, JsonValue>,
2351    base_url: &ServoUrl,
2352    can_gc: CanGc,
2353) -> Fallible<IndexMap<ServoUrl, ModuleSpecifierMap>> {
2354    // Step 1. Let normalized be an empty ordered map.
2355    let mut normalized: IndexMap<ServoUrl, ModuleSpecifierMap> = IndexMap::new();
2356
2357    // Step 2. For each scopePrefix → potentialSpecifierMap of originalMap:
2358    for (scope_prefix, potential_specifier_map) in original_map {
2359        // Step 2.1 If potentialSpecifierMap is not an ordered map, then throw a TypeError indicating
2360        // that the value of the scope with prefix scopePrefix needs to be a JSON object.
2361        let JsonValue::Object(potential_specifier_map) = potential_specifier_map else {
2362            return Err(Error::Type(
2363                "The value of the scope with prefix scopePrefix needs to be a JSON object."
2364                    .to_owned(),
2365            ));
2366        };
2367
2368        // Step 2.2 Let scopePrefixURL be the result of URL parsing scopePrefix with baseURL.
2369        let Ok(scope_prefix_url) = ServoUrl::parse_with_base(Some(base_url), scope_prefix) else {
2370            // Step 2.3 If scopePrefixURL is failure, then:
2371            // Step 2.3.1 The user agent may report a warning
2372            // to the console that the scope prefix URL was not parseable.
2373            Console::internal_warn(
2374                global,
2375                DOMString::from(format!(
2376                    "Scope prefix URL was not parseable: {scope_prefix}"
2377                )),
2378            );
2379            // Step 2.3.2 Continue.
2380            continue;
2381        };
2382
2383        // Step 2.4 Let normalizedScopePrefix be the serialization of scopePrefixURL.
2384        let normalized_scope_prefix = scope_prefix_url;
2385
2386        // Step 2.5 Set normalized[normalizedScopePrefix] to the result of sorting and
2387        // normalizing a module specifier map given potentialSpecifierMap and baseURL.
2388        let normalized_specifier_map = sort_and_normalize_module_specifier_map(
2389            global,
2390            potential_specifier_map,
2391            base_url,
2392            can_gc,
2393        );
2394        normalized.insert(normalized_scope_prefix, normalized_specifier_map);
2395    }
2396
2397    // Step 3. Return the result of sorting in descending order normalized,
2398    // with an entry a being less than an entry b if a's key is code unit less than b's key.
2399    normalized.sort_by(|a_key, _, b_key, _| b_key.cmp(a_key));
2400    Ok(normalized)
2401}
2402
2403/// <https://html.spec.whatwg.org/multipage/#normalizing-a-module-integrity-map>
2404fn normalize_module_integrity_map(
2405    global: &GlobalScope,
2406    original_map: &JsonMap<String, JsonValue>,
2407    base_url: &ServoUrl,
2408    _can_gc: CanGc,
2409) -> ModuleIntegrityMap {
2410    // Step 1. Let normalized be an empty ordered map.
2411    let mut normalized = ModuleIntegrityMap::new();
2412
2413    // Step 2. For each key → value of originalMap:
2414    for (key, value) in original_map {
2415        // Step 2.1 Let resolvedURL be the result of
2416        // resolving a URL-like module specifier given key and baseURL.
2417        let Some(resolved_url) =
2418            ModuleTree::resolve_url_like_module_specifier(&DOMString::from(key.as_str()), base_url)
2419        else {
2420            // Step 2.2 If resolvedURL is null, then:
2421            // Step 2.2.1 The user agent may report a warning
2422            // to the console indicating that the key failed to resolve.
2423            Console::internal_warn(
2424                global,
2425                DOMString::from(format!("Key failed to resolve to module specifier: {key}")),
2426            );
2427            // Step 2.2.2 Continue.
2428            continue;
2429        };
2430
2431        // Step 2.3 If value is not a string, then:
2432        let JsonValue::String(value) = value else {
2433            // Step 2.3.1 The user agent may report a warning
2434            // to the console indicating that integrity metadata values need to be strings.
2435            Console::internal_warn(
2436                global,
2437                DOMString::from("Integrity metadata values need to be strings."),
2438            );
2439            // Step 2.3.2 Continue.
2440            continue;
2441        };
2442
2443        // Step 2.4 Set normalized[resolvedURL] to value.
2444        normalized.insert(resolved_url, value.clone());
2445    }
2446
2447    // Step 3. Return normalized.
2448    normalized
2449}
2450
2451/// <https://html.spec.whatwg.org/multipage/#normalizing-a-specifier-key>
2452fn normalize_specifier_key(
2453    global: &GlobalScope,
2454    specifier_key: &str,
2455    base_url: &ServoUrl,
2456    _can_gc: CanGc,
2457) -> Option<String> {
2458    // step 1. If specifierKey is the empty string, then:
2459    if specifier_key.is_empty() {
2460        // step 1.1 The user agent may report a warning to the console
2461        // indicating that specifier keys may not be the empty string.
2462        Console::internal_warn(
2463            global,
2464            DOMString::from("Specifier keys may not be the empty string."),
2465        );
2466        // step 1.2 Return null.
2467        return None;
2468    }
2469    // step 2. Let url be the result of resolving a URL-like module specifier, given specifierKey and baseURL.
2470    let url =
2471        ModuleTree::resolve_url_like_module_specifier(&DOMString::from(specifier_key), base_url);
2472
2473    // step 3. If url is not null, then return the serialization of url.
2474    if let Some(url) = url {
2475        return Some(url.into_string());
2476    }
2477
2478    // step 4. Return specifierKey.
2479    Some(specifier_key.to_string())
2480}
2481
2482/// <https://html.spec.whatwg.org/multipage/#resolving-an-imports-match>
2483///
2484/// When the error is thrown, it will terminate the entire resolve a module specifier algorithm
2485/// without any further fallbacks.
2486pub(crate) fn resolve_imports_match(
2487    normalized_specifier: &str,
2488    as_url: Option<&ServoUrl>,
2489    specifier_map: &ModuleSpecifierMap,
2490    _can_gc: CanGc,
2491) -> Fallible<Option<ServoUrl>> {
2492    // Step 1. For each specifierKey → resolutionResult of specifierMap:
2493    for (specifier_key, resolution_result) in specifier_map {
2494        // Step 1.1 If specifierKey is normalizedSpecifier, then:
2495        if specifier_key == normalized_specifier {
2496            if let Some(resolution_result) = resolution_result {
2497                // Step 1.1.2 Assert: resolutionResult is a URL.
2498                // This is checked by Url type already.
2499                // Step 1.1.3 Return resolutionResult.
2500                return Ok(Some(resolution_result.clone()));
2501            } else {
2502                // Step 1.1.1 If resolutionResult is null, then throw a TypeError.
2503                return Err(Error::Type(
2504                    "Resolution of specifierKey was blocked by a null entry.".to_owned(),
2505                ));
2506            }
2507        }
2508
2509        // Step 1.2 If all of the following are true:
2510        // - specifierKey ends with U+002F (/)
2511        // - specifierKey is a code unit prefix of normalizedSpecifier
2512        // - either asURL is null, or asURL is special, then:
2513        if specifier_key.ends_with('\u{002f}') &&
2514            normalized_specifier.starts_with(specifier_key) &&
2515            (as_url.is_none() || as_url.map(|u| u.is_special_scheme()).unwrap_or_default())
2516        {
2517            // Step 1.2.1 If resolutionResult is null, then throw a TypeError.
2518            // Step 1.2.2 Assert: resolutionResult is a URL.
2519            let Some(resolution_result) = resolution_result else {
2520                return Err(Error::Type(
2521                    "Resolution of specifierKey was blocked by a null entry.".to_owned(),
2522                ));
2523            };
2524
2525            // Step 1.2.3 Let afterPrefix be the portion of normalizedSpecifier after the initial specifierKey prefix.
2526            let after_prefix = normalized_specifier
2527                .strip_prefix(specifier_key)
2528                .expect("specifier_key should be the prefix of normalized_specifier");
2529
2530            // Step 1.2.4 Assert: resolutionResult, serialized, ends with U+002F (/), as enforced during parsing.
2531            debug_assert!(resolution_result.as_str().ends_with('\u{002f}'));
2532
2533            // Step 1.2.5 Let url be the result of URL parsing afterPrefix with resolutionResult.
2534            let url = ServoUrl::parse_with_base(Some(resolution_result), after_prefix);
2535
2536            // Step 1.2.6 If url is failure, then throw a TypeError
2537            // Step 1.2.7 Assert: url is a URL.
2538            let Ok(url) = url else {
2539                return Err(Error::Type(
2540                    "Resolution of normalizedSpecifier was blocked since
2541                    the afterPrefix portion could not be URL-parsed relative to
2542                    the resolutionResult mapped to by the specifierKey prefix."
2543                        .to_owned(),
2544                ));
2545            };
2546
2547            // Step 1.2.8 If the serialization of resolutionResult is not
2548            // a code unit prefix of the serialization of url, then throw a TypeError
2549            if !url.as_str().starts_with(resolution_result.as_str()) {
2550                return Err(Error::Type(
2551                    "Resolution of normalizedSpecifier was blocked due to
2552                    it backtracking above its prefix specifierKey."
2553                        .to_owned(),
2554                ));
2555            }
2556
2557            // Step 1.2.9 Return url.
2558            return Ok(Some(url));
2559        }
2560    }
2561
2562    // Step 2. Return null.
2563    Ok(None)
2564}