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