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