script/
module_loading.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//! An implementation of ecma262's [LoadRequestedModules](https://tc39.es/ecma262/#sec-LoadRequestedModules)
6//! Partly inspired by mozjs implementation: <https://searchfox.org/firefox-main/source/js/src/vm/Modules.cpp#1450>
7//! Since we can't access ModuleObject internals (eg. ModuleRequest records), we deviate from the spec in some aspects.
8
9#![expect(unsafe_code)]
10
11use std::cell::{Cell, RefCell};
12use std::collections::HashSet;
13use std::rc::Rc;
14
15use js::conversions::jsstr_to_string;
16use js::jsapi::{HandleValue as RawHandleValue, IsCyclicModule, JSObject, ModuleType};
17use js::jsval::{ObjectValue, UndefinedValue};
18use js::realm::{AutoRealm, CurrentRealm};
19use js::rust::wrappers2::{
20    GetModuleNamespace, GetRequestedModuleSpecifier, GetRequestedModuleType,
21    GetRequestedModulesCount, JS_GetModulePrivate, ModuleEvaluate, ModuleLink,
22};
23use js::rust::{HandleValue, IntoHandle};
24use net_traits::request::{Destination, Referrer};
25use script_bindings::settings_stack::run_a_callback;
26use servo_url::ServoUrl;
27
28use crate::DomTypeHolder;
29use crate::dom::bindings::error::Error;
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::reflector::DomObject;
32use crate::dom::bindings::root::DomRoot;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::promise::Promise;
35use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
36use crate::realms::{InRealm, enter_auto_realm};
37use crate::script_module::{
38    ModuleHandler, ModuleObject, ModuleOwner, ModuleTree, RethrowError, ScriptFetchOptions,
39    fetch_a_single_module_script, gen_type_error, module_script_from_reference_private,
40};
41use crate::script_runtime::{CanGc, IntroductionType};
42
43#[derive(JSTraceable, MallocSizeOf)]
44struct OnRejectedHandler {
45    #[conditional_malloc_size_of]
46    promise: Rc<Promise>,
47}
48
49impl Callback for OnRejectedHandler {
50    fn callback(&self, cx: &mut CurrentRealm, v: HandleValue) {
51        // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
52        self.promise.reject(cx.into(), v, CanGc::from_cx(cx));
53    }
54}
55
56pub(crate) enum Payload {
57    GraphRecord(Rc<GraphLoadingState>),
58    PromiseRecord(Rc<Promise>),
59}
60
61#[derive(JSTraceable)]
62pub(crate) struct LoadState {
63    pub(crate) error_to_rethrow: RefCell<Option<RethrowError>>,
64    #[no_trace]
65    pub(crate) destination: Destination,
66    pub(crate) fetch_client: ModuleOwner,
67}
68
69/// <https://tc39.es/ecma262/#graphloadingstate-record>
70pub(crate) struct GraphLoadingState {
71    /// [[PromiseCapability]]
72    promise: Rc<Promise>,
73    /// [[IsLoading]]
74    is_loading: Cell<bool>,
75    /// [[PendingModulesCount]]
76    pending_modules_count: Cell<u32>,
77    /// [[Visited]]
78    visited: RefCell<HashSet<ServoUrl>>,
79    /// [[HostDefined]]
80    load_state: Option<Rc<LoadState>>,
81}
82
83/// <https://tc39.es/ecma262/#sec-LoadRequestedModules>
84pub(crate) fn load_requested_modules(
85    cx: &mut CurrentRealm,
86    module: Rc<ModuleTree>,
87    load_state: Option<Rc<LoadState>>,
88) -> Rc<Promise> {
89    // Step 1. If hostDefined is not present, let hostDefined be empty.
90    //
91    // Not required, since we implement it as an `Option`
92
93    // Step 2. Let pc be ! NewPromiseCapability(%Promise%).
94    let mut realm = CurrentRealm::assert(cx);
95    let promise = Promise::new_in_realm(&mut realm);
96
97    // Step 3. Let state be the GraphLoadingState Record
98    // { [[IsLoading]]: true, [[PendingModulesCount]]: 1, [[Visited]]: « », [[PromiseCapability]]: pc, [[HostDefined]]: hostDefined }.
99    let state = GraphLoadingState {
100        promise: promise.clone(),
101        is_loading: Cell::new(true),
102        pending_modules_count: Cell::new(1),
103        visited: RefCell::new(HashSet::new()),
104        load_state,
105    };
106
107    // Step 4. Perform InnerModuleLoading(state, module).
108    inner_module_loading(cx, &Rc::new(state), module);
109
110    // Step 5. Return pc.[[Promise]].
111    promise
112}
113
114/// <https://tc39.es/ecma262/#sec-InnerModuleLoading>
115fn inner_module_loading(
116    cx: &mut CurrentRealm,
117    state: &Rc<GraphLoadingState>,
118    module: Rc<ModuleTree>,
119) {
120    // Step 1. Assert: state.[[IsLoading]] is true.
121    assert!(state.is_loading.get());
122
123    let module_handle = module.get_record().map(|module| module.handle()).unwrap();
124
125    let module_url = module.get_url();
126    let visited_contains_module = state.visited.borrow().contains(&module_url);
127
128    // Step 2. If module is a Cyclic Module Record, module.[[Status]] is new, and state.[[Visited]] does not contain module, then
129    // Note: mozjs doesn't expose a way to check the ModuleStatus of a ModuleObject.
130    if unsafe { IsCyclicModule(module_handle.get()) } && !visited_contains_module {
131        // a. Append module to state.[[Visited]].
132        state.visited.borrow_mut().insert(module_url);
133
134        // b. Let requestedModulesCount be the number of elements in module.[[RequestedModules]].
135        let requested_modules_count = unsafe { GetRequestedModulesCount(cx, module_handle) };
136
137        // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount.
138        let pending_modules_count = state.pending_modules_count.get();
139        state
140            .pending_modules_count
141            .set(pending_modules_count + requested_modules_count);
142
143        // d. For each ModuleRequest Record request of module.[[RequestedModules]], do
144        for index in 0..requested_modules_count {
145            // i. If AllImportAttributesSupported(request.[[Attributes]]) is false, then
146            // Note: Gecko will call hasFirstUnsupportedAttributeKey on each module request,
147            // GetRequestedModuleSpecifier will do it for us.
148            // In addition it will also check if specifier has an unknown module type.
149            let jsstr = unsafe { GetRequestedModuleSpecifier(cx, module_handle, index) };
150
151            if jsstr.is_null() {
152                // 1. Let error be ThrowCompletion(a newly created SyntaxError object).
153                let error = RethrowError::from_pending_exception(cx.into());
154
155                // See Step 7. of `host_load_imported_module`.
156                state.load_state.as_ref().inspect(|load_state| {
157                    load_state
158                        .error_to_rethrow
159                        .borrow_mut()
160                        .get_or_insert(error.clone());
161                });
162
163                // 2. Perform ContinueModuleLoading(state, error).
164                continue_module_loading(cx, state, Err(error));
165            } else {
166                let specifier =
167                    unsafe { jsstr_to_string(cx.raw_cx(), std::ptr::NonNull::new(jsstr).unwrap()) };
168                let module_type = unsafe { GetRequestedModuleType(cx, module_handle, index) };
169
170                let realm = CurrentRealm::assert(cx);
171                let global = GlobalScope::from_current_realm(&realm);
172
173                // ii. Else if module.[[LoadedModules]] contains a LoadedModuleRequest Record record
174                // such that ModuleRequestsEqual(record, request) is true, then
175                let loaded_module =
176                    module.find_descendant_inside_module_map(&global, &specifier, module_type);
177
178                match loaded_module {
179                    // 1. Perform InnerModuleLoading(state, record.[[Module]]).
180                    Some(module) => inner_module_loading(cx, state, module),
181                    // iii. Else,
182                    None => {
183                        rooted!(&in(cx) let mut referrer = UndefinedValue());
184                        unsafe { JS_GetModulePrivate(module_handle.get(), referrer.handle_mut()) };
185
186                        // 1. Perform HostLoadImportedModule(module, request, state.[[HostDefined]], state).
187                        host_load_imported_module(
188                            cx,
189                            Some(module.clone()),
190                            referrer.handle().into_handle(),
191                            specifier,
192                            module_type,
193                            state.load_state.clone(),
194                            Payload::GraphRecord(state.clone()),
195                        );
196                    },
197                }
198            }
199
200            // iv. If state.[[IsLoading]] is false, return unused.
201            if !state.is_loading.get() {
202                return;
203            }
204        }
205    }
206
207    // Step 3. Assert: state.[[PendingModulesCount]] ≥ 1.
208    assert!(state.pending_modules_count.get() >= 1);
209
210    // Step 4. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] - 1.
211    let pending_modules_count = state.pending_modules_count.get();
212    state.pending_modules_count.set(pending_modules_count - 1);
213
214    // Step 5. If state.[[PendingModulesCount]] = 0, then
215    if state.pending_modules_count.get() == 0 {
216        // a. Set state.[[IsLoading]] to false.
217        state.is_loading.set(false);
218
219        // b. For each Cyclic Module Record loaded of state.[[Visited]], do
220        // i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked.
221        // Note: mozjs defaults to the unlinked status.
222
223        // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »).
224        state.promise.resolve_native(&(), CanGc::from_cx(cx));
225    }
226
227    // Step 6. Return unused.
228}
229
230/// <https://tc39.es/ecma262/#sec-ContinueModuleLoading>
231fn continue_module_loading(
232    cx: &mut CurrentRealm,
233    state: &Rc<GraphLoadingState>,
234    module_completion: Result<Rc<ModuleTree>, RethrowError>,
235) {
236    // Step 1. If state.[[IsLoading]] is false, return unused.
237    if !state.is_loading.get() {
238        return;
239    }
240
241    match module_completion {
242        // Step 2. If moduleCompletion is a normal completion, then
243        // a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]).
244        Ok(module) => inner_module_loading(cx, state, module),
245
246        // Step 3. Else,
247        Err(exception) => {
248            // a. Set state.[[IsLoading]] to false.
249            state.is_loading.set(false);
250
251            // b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »).
252            state
253                .promise
254                .reject(cx.into(), exception.handle(), CanGc::from_cx(cx));
255        },
256    }
257
258    // Step 4. Return unused.
259}
260
261/// <https://tc39.es/ecma262/#sec-FinishLoadingImportedModule>
262fn finish_loading_imported_module(
263    cx: &mut CurrentRealm,
264    referrer_module: Option<Rc<ModuleTree>>,
265    module_request_specifier: String,
266    payload: Payload,
267    result: Result<Rc<ModuleTree>, RethrowError>,
268) {
269    match payload {
270        // Step 2. If payload is a GraphLoadingState Record, then
271        Payload::GraphRecord(state) => {
272            let module_tree =
273                referrer_module.expect("Module must not be None in non dynamic imports");
274
275            // Step 1. If result is a normal completion, then
276            if let Ok(ref module) = result {
277                module_tree.insert_module_dependency(module, module_request_specifier);
278            }
279
280            // a. Perform ContinueModuleLoading(payload, result).
281            continue_module_loading(cx, &state, result);
282        },
283
284        // Step 3. Else,
285        // a. Perform ContinueDynamicImport(payload, result).
286        Payload::PromiseRecord(promise) => continue_dynamic_import(cx, promise, result),
287    }
288
289    // 4. Return unused.
290}
291
292/// <https://tc39.es/ecma262/#sec-ContinueDynamicImport>
293fn continue_dynamic_import(
294    cx: &mut CurrentRealm,
295    promise: Rc<Promise>,
296    module_completion: Result<Rc<ModuleTree>, RethrowError>,
297) {
298    // Step 1. If moduleCompletion is an abrupt completion, then
299    if let Err(exception) = module_completion {
300        // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « moduleCompletion.[[Value]] »).
301        promise.reject(cx.into(), exception.handle(), CanGc::from_cx(cx));
302
303        // b. Return unused.
304        return;
305    }
306
307    let realm = CurrentRealm::assert(cx);
308    let global = GlobalScope::from_current_realm(&realm);
309
310    // Step 2. Let module be moduleCompletion.[[Value]].
311    let module = module_completion.unwrap();
312    let record = ModuleObject::new(module.get_record().map(|module| module.handle()).unwrap());
313
314    // Step 3. Let loadPromise be module.LoadRequestedModules().
315    let load_promise = load_requested_modules(cx, module, None);
316
317    // Step 4. Let rejectedClosure be a new Abstract Closure with parameters (reason)
318    // that captures promiseCapability and performs the following steps when called:
319    // Step 5. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
320    // Note: implemented by OnRejectedHandler.
321
322    let global_scope = global.clone();
323    let inner_promise = promise.clone();
324    let fulfilled_promise = promise.clone();
325
326    // Step 6. Let linkAndEvaluateClosure be a new Abstract Closure with no parameters that captures
327    // module, promiseCapability, and onRejected and performs the following steps when called:
328    // Step 7. Let linkAndEvaluate be CreateBuiltinFunction(linkAndEvaluateClosure, 0, "", « »).
329    let link_and_evaluate = ModuleHandler::new_boxed(Box::new(
330        task!(link_and_evaluate: |cx, global_scope: DomRoot<GlobalScope>, inner_promise: Rc<Promise>, record: ModuleObject| {
331            let mut realm = AutoRealm::new(
332                cx,
333                std::ptr::NonNull::new(global_scope.reflector().get_jsobject().get()).unwrap(),
334            );
335            let in_realm_proof = (&mut realm.current_realm()).into();
336            let cx = &mut *realm;
337            // a. Let link be Completion(module.Link()).
338            let link = unsafe { ModuleLink(cx, record.handle()) };
339
340            // b. If link is an abrupt completion, then
341            if !link {
342                // i. Perform ! Call(promiseCapability.[[Reject]], undefined, « link.[[Value]] »).
343                let exception = RethrowError::from_pending_exception(cx.into());
344                inner_promise.reject(cx.into(), exception.handle(), CanGc::from_cx(cx));
345
346                // ii. Return NormalCompletion(undefined).
347                return;
348            }
349
350            rooted!(&in(cx) let mut rval = UndefinedValue());
351            rooted!(&in(cx) let mut evaluate_promise = std::ptr::null_mut::<JSObject>());
352
353            // c. Let evaluatePromise be module.Evaluate().
354            assert!(unsafe { ModuleEvaluate(cx, record.handle(), rval.handle_mut()) });
355
356            if !rval.is_object() {
357                let error = RethrowError::from_pending_exception(cx.into());
358                return inner_promise.reject(cx.into(), error.handle(), CanGc::from_cx(cx));
359            }
360            evaluate_promise.set(rval.to_object());
361            let evaluate_promise = Promise::new_with_js_promise(evaluate_promise.handle(), cx.into());
362
363            // d. Let fulfilledClosure be a new Abstract Closure with no parameters that captures
364            // module and promiseCapability and performs the following steps when called:
365            // e. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »).
366            let on_fulfilled = ModuleHandler::new_boxed(Box::new(
367                task!(on_fulfilled: |cx, fulfilled_promise: Rc<Promise>, record: ModuleObject| {
368                    rooted!(&in(cx) let mut rval: *mut JSObject = std::ptr::null_mut());
369                    rooted!(&in(cx) let mut namespace = UndefinedValue());
370
371                    // i. Let namespace be GetModuleNamespace(module).
372                    rval.set(unsafe { GetModuleNamespace(cx, record.handle()) });
373                    namespace.handle_mut().set(ObjectValue(rval.get()));
374
375                    // ii. Perform ! Call(promiseCapability.[[Resolve]], undefined, « namespace »).
376                    fulfilled_promise.resolve(cx.into(), namespace.handle(), CanGc::from_cx(cx));
377
378                    // iii. Return NormalCompletion(undefined).
379            })));
380
381            // f. Perform PerformPromiseThen(evaluatePromise, onFulfilled, onRejected).
382            let handler = PromiseNativeHandler::new(
383                &global_scope,
384                Some(on_fulfilled),
385                Some(Box::new(OnRejectedHandler {
386                    promise: inner_promise,
387                })),
388                CanGc::note(),
389            );
390            let in_realm = InRealm::Already(&in_realm_proof);
391            evaluate_promise.append_native_handler(&handler, in_realm, CanGc::from_cx(cx));
392
393            // g. Return unused.
394        }),
395    ));
396
397    let mut realm = enter_auto_realm(cx, &*global);
398    let mut realm = realm.current_realm();
399    run_a_callback::<DomTypeHolder, _>(&*global, || {
400        // Step 8. Perform PerformPromiseThen(loadPromise, linkAndEvaluate, onRejected).
401        let handler = PromiseNativeHandler::new(
402            &global,
403            Some(link_and_evaluate),
404            Some(Box::new(OnRejectedHandler {
405                promise: promise.clone(),
406            })),
407            CanGc::from_cx(&mut realm),
408        );
409        let in_realm_proof = (&mut realm).into();
410        let in_realm = InRealm::Already(&in_realm_proof);
411        load_promise.append_native_handler(&handler, in_realm, CanGc::from_cx(&mut realm));
412    });
413    // Step 9. Return unused.
414}
415
416/// <https://html.spec.whatwg.org/multipage/#hostloadimportedmodule>
417pub(crate) fn host_load_imported_module(
418    cx: &mut CurrentRealm,
419    referrer_module: Option<Rc<ModuleTree>>,
420    referrer: RawHandleValue,
421    specifier: String,
422    module_type: ModuleType,
423    load_state: Option<Rc<LoadState>>,
424    payload: Payload,
425) {
426    // Step 1. Let settingsObject be the current settings object.
427    let realm = CurrentRealm::assert(cx);
428    let global_scope = GlobalScope::from_current_realm(&realm);
429
430    // TODO Step 2. If settingsObject's global object implements WorkletGlobalScope or ServiceWorkerGlobalScope and loadState is undefined, then:
431
432    // Step 3. Let referencingScript be null.
433    // Step 6.1. Set referencingScript to referrer.[[HostDefined]].
434    let referencing_script = unsafe { module_script_from_reference_private(&referrer) };
435
436    // Step 6. If referrer is a Script Record or a Cyclic Module Record, then:
437    let (original_fetch_options, fetch_referrer) = match referencing_script {
438        Some(module) => (
439            // Step 6.4. Set originalFetchOptions to referencingScript's fetch options.
440            module.options.clone(),
441            // Step 6.3. Set fetchReferrer to referencingScript's base URL.
442            Referrer::ReferrerUrl(module.base_url.clone()),
443        ),
444        None => (
445            // Step 4. Let originalFetchOptions be the default script fetch options.
446            ScriptFetchOptions::default_classic_script(&global_scope),
447            // Step 5. Let fetchReferrer be "client".
448            global_scope.get_referrer(),
449        ),
450    };
451
452    // Step 6.2. Set settingsObject to referencingScript's settings object.
453    // Note: We later set fetchClient to the `ModuleOwner` provided by loadState,
454    // which provides the `GlobalScope` that we will use for fetching.
455
456    // Step 7 If referrer is a Cyclic Module Record and moduleRequest is equal to the first element of referrer.[[RequestedModules]], then:
457    // Note: These substeps are implemented by `GetRequestedModuleSpecifier`,
458    // setting loadState.[[ErrorToRethrow]] is done by `inner_module_loading`.
459
460    // Step 8 Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]],
461    // catching any exceptions. If they throw an exception, let resolutionError be the thrown exception.
462    let url = ModuleTree::resolve_module_specifier(
463        &global_scope,
464        referencing_script,
465        specifier.clone().into(),
466    );
467
468    // Step 9 If the previous step threw an exception, then:
469    if let Err(error) = url {
470        let resolution_error = gen_type_error(&global_scope, error, CanGc::from_cx(cx));
471
472        // Step 9.1. If loadState is not undefined and loadState.[[ErrorToRethrow]] is null,
473        // set loadState.[[ErrorToRethrow]] to resolutionError.
474        load_state.as_ref().inspect(|load_state| {
475            load_state
476                .error_to_rethrow
477                .borrow_mut()
478                .get_or_insert(resolution_error.clone());
479        });
480
481        // Step 9.2. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, ThrowCompletion(resolutionError)).
482        finish_loading_imported_module(
483            cx,
484            referrer_module,
485            specifier,
486            payload,
487            Err(resolution_error),
488        );
489
490        // Step 9.3. Return.
491        return;
492    };
493
494    let url = url.unwrap();
495
496    // Step 10. Let fetchOptions be the result of getting the descendant script fetch options given
497    // originalFetchOptions, url, and settingsObject.
498    let fetch_options = original_fetch_options.descendant_fetch_options(&url, &global_scope);
499
500    // Step 13. If loadState is not undefined, then:
501    // Note: loadState is undefined only in dynamic imports
502    let (destination, fetch_client) = match load_state.as_ref() {
503        // Step 13.1. Set destination to loadState.[[Destination]].
504        // Step 13.2. Set fetchClient to loadState.[[FetchClient]].
505        Some(load_state) => (load_state.destination, load_state.fetch_client.clone()),
506        None => (
507            // Step 11. Let destination be "script".
508            Destination::Script,
509            // Step 12. Let fetchClient be settingsObject.
510            ModuleOwner::DynamicModule(Trusted::new(&global_scope)),
511        ),
512    };
513
514    let on_single_fetch_complete = move |module_tree: Option<Rc<ModuleTree>>| {
515        let mut cx = unsafe { script_bindings::script_runtime::temp_cx() };
516        let mut realm = CurrentRealm::assert(&mut cx);
517        let cx = &mut realm;
518
519        // Step 1. Let completion be null.
520        let completion = match module_tree {
521            // Step 2. If moduleScript is null, then set completion to ThrowCompletion(a new TypeError).
522            None => Err(gen_type_error(
523                &global_scope,
524                Error::Type(c"Module fetching failed".to_owned()),
525                CanGc::from_cx(cx),
526            )),
527            Some(module_tree) => {
528                // Step 3. Otherwise, if moduleScript's parse error is not null, then:
529                // Step 3.1 Let parseError be moduleScript's parse error.
530                if let Some(parse_error) = module_tree.get_parse_error() {
531                    // Step 3.3 If loadState is not undefined and loadState.[[ErrorToRethrow]] is null,
532                    // set loadState.[[ErrorToRethrow]] to parseError.
533                    load_state.as_ref().inspect(|load_state| {
534                        load_state
535                            .error_to_rethrow
536                            .borrow_mut()
537                            .get_or_insert(parse_error.clone());
538                    });
539
540                    // Step 3.2 Set completion to ThrowCompletion(parseError).
541                    Err(parse_error.clone())
542                } else {
543                    // Step 4. Otherwise, set completion to NormalCompletion(moduleScript's record).
544                    Ok(module_tree)
545                }
546            },
547        };
548
549        // Step 5. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion).
550        finish_loading_imported_module(cx, referrer_module, specifier, payload, completion);
551    };
552
553    // Step 14 Fetch a single imported module script given url, fetchClient, destination, fetchOptions, settingsObject,
554    // fetchReferrer, moduleRequest, and onSingleFetchComplete as defined below.
555    // If loadState is not undefined and loadState.[[PerformFetch]] is not null, pass loadState.[[PerformFetch]] along as well.
556    // Note: we don't have access to the requested `ModuleObject`, so we pass only its type.
557    fetch_a_single_imported_module_script(
558        url,
559        fetch_client,
560        destination,
561        fetch_options,
562        fetch_referrer,
563        module_type,
564        on_single_fetch_complete,
565    );
566}
567
568/// <https://html.spec.whatwg.org/multipage/#fetch-a-single-imported-module-script>
569fn fetch_a_single_imported_module_script(
570    url: ServoUrl,
571    owner: ModuleOwner,
572    destination: Destination,
573    options: ScriptFetchOptions,
574    referrer: Referrer,
575    module_type: ModuleType,
576    on_complete: impl FnOnce(Option<Rc<ModuleTree>>) + 'static,
577) {
578    // TODO Step 1. Assert: moduleRequest.[[Attributes]] does not contain any Record entry such that entry.[[Key]] is not "type",
579    // because we only asked for "type" attributes in HostGetSupportedImportAttributes.
580
581    // TODO Step 2. Let moduleType be the result of running the module type from module request steps given moduleRequest.
582
583    // Step 3. If the result of running the module type allowed steps given moduleType and settingsObject is false,
584    // then run onComplete given null, and return.
585    match module_type {
586        ModuleType::Unknown => return on_complete(None),
587        ModuleType::JavaScript | ModuleType::JSON => (),
588    }
589
590    // Step 4. Fetch a single module script given url, fetchClient, destination, options, settingsObject, referrer,
591    // moduleRequest, false, and onComplete. If performFetch was given, pass it along as well.
592    fetch_a_single_module_script(
593        url,
594        owner,
595        destination,
596        options,
597        referrer,
598        Some(module_type),
599        false,
600        Some(IntroductionType::IMPORTED_MODULE),
601        on_complete,
602    );
603}