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