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