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