diff --git a/Userland/Libraries/LibJS/CyclicModule.cpp b/Userland/Libraries/LibJS/CyclicModule.cpp index fc62789e96..59a9663834 100644 --- a/Userland/Libraries/LibJS/CyclicModule.cpp +++ b/Userland/Libraries/LibJS/CyclicModule.cpp @@ -33,6 +33,127 @@ void CyclicModule::visit_edges(Cell::Visitor& visitor) visitor.visit(loaded_module.module); } +// 16.2.1.5.1 LoadRequestedModules ( [ hostDefined ] ), https://tc39.es/ecma262/#sec-LoadRequestedModules +PromiseCapability& CyclicModule::load_requested_modules(JS::Realm& realm, Optional host_defined) +{ + // 1. If hostDefined is not present, let hostDefined be EMPTY. + // NOTE: The empty state is handled by hostDefined being an optional without value. + + // 2. Let pc be ! NewPromiseCapability(%Promise%). + auto promise_capability = MUST(new_promise_capability(realm.vm(), realm.intrinsics().promise_constructor())); + + // 3. Let state be the GraphLoadingState Record { [[IsLoading]]: true, [[PendingModulesCount]]: 1, [[Visited]]: « », [[PromiseCapability]]: pc, [[HostDefined]]: hostDefined }. + auto state = GraphLoadingState { .promise_capability = promise_capability, .is_loading = true, .pending_module_count = 1, .visited = {}, .host_defined = move(host_defined) }; + + // 4. Perform InnerModuleLoading(state, module). + inner_module_loading(state); + + // NOTE: This is likely a spec bug, see https://matrixlogs.bakkot.com/WHATWG/2023-02-13#L1 + // FIXME: 5. Return pc.[[Promise]]. + return promise_capability; +} + +// 16.2.1.5.1.1 InnerModuleLoading ( state, module ), https://tc39.es/ecma262/#sec-InnerModuleLoading +void CyclicModule::inner_module_loading(JS::GraphLoadingState& state) +{ + // 1. Assert: state.[[IsLoading]] is true. + VERIFY(state.is_loading); + + // 2. If module is a Cyclic Module Record, module.[[Status]] is NEW, and state.[[Visited]] does not contain module, then + if (m_status == ModuleStatus::New && !state.visited.contains(this)) { + // a. Append module to state.[[Visited]]. + state.visited.set(this); + + // b. Let requestedModulesCount be the number of elements in module.[[RequestedModules]]. + auto requested_modules_count = m_requested_modules.size(); + + // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. + state.pending_module_count += requested_modules_count; + + // d. For each String required of module.[[RequestedModules]], do + for (auto const& required : m_requested_modules) { + bool found_record_in_loaded_modules = false; + + // i. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is required, then + for (auto const& record : m_loaded_modules) { + if (record.specifier == required.module_specifier) { + // 1. Let record be that Record. + + // 2. Perform InnerModuleLoading(state, record.[[Module]]). + static_cast(*record.module).inner_module_loading(state); + + found_record_in_loaded_modules = true; + break; + } + } + + // ii. Else, + if (!found_record_in_loaded_modules) { + // 1. Perform HostLoadImportedModule(module, required, state.[[HostDefined]], state). + vm().host_load_imported_module(realm(), NonnullGCPtr(*this), required, state.host_defined, state); + + // 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters the graph loading process through ContinueModuleLoading. + } + + // iii. If state.[[IsLoading]] is false, return UNUSED. + if (!state.is_loading) + return; + } + } + + // 3. Assert: state.[[PendingModulesCount]] ≥ 1. + VERIFY(state.pending_module_count >= 1); + + // 4. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] - 1. + --state.pending_module_count; + + // 5. If state.[[PendingModulesCount]] = 0, then + if (state.pending_module_count == 0) { + // a. Set state.[[IsLoading]] to false. + state.is_loading = false; + + // b. For each Cyclic Module Record loaded of state.[[Visited]], do + for (auto const& loaded : state.visited) { + // i. If loaded.[[Status]] is NEW, set loaded.[[Status]] to UNLINKED. + if (loaded->m_status == ModuleStatus::New) + loaded->m_status = ModuleStatus::Linked; + } + + // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »). + MUST(call(vm(), *state.promise_capability->resolve(), js_undefined(), js_undefined())); + } + + // 6. Return unused. +} + +// 16.2.1.5.1.2 ContinueModuleLoading ( state, moduleCompletion ), https://tc39.es/ecma262/#sec-ContinueModuleLoading +void continue_module_loading(Realm& realm, GraphLoadingState& state, ThrowCompletionOr const& module_completion) +{ + // 1. If state.[[IsLoading]] is false, return UNUSED. + if (state.is_loading) + return; + + // 2. If moduleCompletion is a normal completion, then + if (!module_completion.is_error()) { + auto* module = const_cast(module_completion.value()); + + // a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]). + static_cast(module)->inner_module_loading(state); + } + // 3. Else, + else { + // a. Set state.[[IsLoading]] to false. + state.is_loading = false; + + auto value = module_completion.throw_completion().value(); + + // b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »). + MUST(call(realm.vm(), *state.promise_capability->reject(), js_undefined(), *value)); + } + + // 4. Return UNUSED. +} + // 16.2.1.5.1 Link ( ), https://tc39.es/ecma262/#sec-moduledeclarationlinking ThrowCompletionOr CyclicModule::link(VM& vm) { diff --git a/Userland/Libraries/LibJS/CyclicModule.h b/Userland/Libraries/LibJS/CyclicModule.h index 88ec77047b..f3daaacfa9 100644 --- a/Userland/Libraries/LibJS/CyclicModule.h +++ b/Userland/Libraries/LibJS/CyclicModule.h @@ -49,6 +49,9 @@ public: virtual ThrowCompletionOr link(VM& vm) override final; virtual ThrowCompletionOr evaluate(VM& vm) override final; + virtual PromiseCapability& load_requested_modules(Realm&, Optional); + virtual void inner_module_loading(GraphLoadingState& state); + Vector const& requested_modules() const { return m_requested_modules; } Vector const& loaded_modules() const { return m_loaded_modules; } @@ -82,4 +85,6 @@ protected: Optional m_pending_async_dependencies; // [[PendingAsyncDependencies]] }; +void continue_module_loading(Realm&, GraphLoadingState& state, ThrowCompletionOr const&); + } diff --git a/Userland/Libraries/LibJS/Module.cpp b/Userland/Libraries/LibJS/Module.cpp index 38ad302488..7190b961d0 100644 --- a/Userland/Libraries/LibJS/Module.cpp +++ b/Userland/Libraries/LibJS/Module.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2021, Andreas Kling * Copyright (c) 2022, David Tuin + * Copyright (c) 2023, networkException * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,6 +9,7 @@ #include #include #include +#include #include #include @@ -62,6 +64,51 @@ ThrowCompletionOr Module::inner_module_evaluation(VM& vm, Vector&, return index; } +// 16.2.1.9 FinishLoadingImportedModule ( referrer, specifier, payload, result ), https://tc39.es/ecma262/#sec-FinishLoadingImportedModule +// FIXME: We currently implement an outdated version of https://tc39.es/proposal-import-attributes, as such it is not possible to +// use the exact steps from https://tc39.es/proposal-import-attributes/#sec-HostLoadImportedModule here. +// FIXME: Support Realm for referrer. +void finish_loading_imported_module(Realm& realm, Variant, JS::NonnullGCPtr> referrer, ModuleRequest const& module_request, GraphLoadingState& payload, ThrowCompletionOr const& result) +{ + // 1. If result is a normal completion, then + if (!result.is_error()) { + auto loaded_modules = referrer.visit( + [](JS::NonnullGCPtr script) -> Vector { return script->loaded_modules(); }, + [](JS::NonnullGCPtr module) -> Vector { return module->loaded_modules(); }); + + bool found_record = false; + + // a.a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then + for (auto const& record : loaded_modules) { + if (record.specifier == module_request.module_specifier) { + // i. Assert: That Record's [[Module]] is result.[[Value]]. + VERIFY(record.module == result.value()); + + found_record = true; + } + } + + // b. Else, + if (!found_record) { + auto* module = const_cast(result.value()); + + // i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. + loaded_modules.append(ModuleWithSpecifier { + .specifier = module_request.module_specifier, + .module = NonnullGCPtr(*module) }); + } + } + + // FIXME: 2. If payload is a GraphLoadingState Record, then + // a. Perform ContinueModuleLoading(payload, result) + continue_module_loading(realm, payload, result); + + // FIXME: Else, + // FIXME: a. Perform ContinueDynamicImport(payload, result). + + // 4. Return unused. +} + // 16.2.1.10 GetModuleNamespace ( module ), https://tc39.es/ecma262/#sec-getmodulenamespace ThrowCompletionOr Module::get_module_namespace(VM& vm) { diff --git a/Userland/Libraries/LibJS/Module.h b/Userland/Libraries/LibJS/Module.h index 2e497efb53..c085eafdc8 100644 --- a/Userland/Libraries/LibJS/Module.h +++ b/Userland/Libraries/LibJS/Module.h @@ -109,4 +109,9 @@ private: DeprecatedString m_filename; }; +class CyclicModule; +struct GraphLoadingState; + +void finish_loading_imported_module(Realm&, Variant, NonnullGCPtr>, ModuleRequest const&, GraphLoadingState&, ThrowCompletionOr const&); + } diff --git a/Userland/Libraries/LibJS/Runtime/VM.h b/Userland/Libraries/LibJS/Runtime/VM.h index 36530a2704..1c7008c13c 100644 --- a/Userland/Libraries/LibJS/Runtime/VM.h +++ b/Userland/Libraries/LibJS/Runtime/VM.h @@ -2,6 +2,7 @@ * Copyright (c) 2020-2023, Andreas Kling * Copyright (c) 2020-2023, Linus Groh * Copyright (c) 2021-2022, David Tuin + * Copyright (c) 2023, networkException * * SPDX-License-Identifier: BSD-2-Clause */ @@ -14,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -222,6 +224,13 @@ public: ScriptOrModule get_active_script_or_module() const; + // NOTE: The host defined implementation described in the web spec https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule + // currently references proposal-import-attributes. + // Our implementation of this proposal is outdated however, as such we try to adapt the proposal and living standard + // to match our implementation for now. + // 16.2.1.8 HostLoadImportedModule ( referrer, moduleRequest, hostDefined, payload ), https://tc39.es/proposal-import-attributes/#sec-HostLoadImportedModule + Function, NonnullGCPtr>, ModuleRequest const&, Optional, GraphLoadingState&)> host_load_imported_module; + Function>(ScriptOrModule, ModuleRequest const&)> host_resolve_imported_module; Function(ScriptOrModule, ModuleRequest, PromiseCapability const&)> host_import_module_dynamically; Function host_finish_dynamic_import; diff --git a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp index dd42da33e8..f11c99c1b6 100644 --- a/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Userland/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -1,12 +1,13 @@ /* * Copyright (c) 2021-2022, Andreas Kling * Copyright (c) 2021-2023, Luke Wilde - * Copyright (c) 2022, networkException + * Copyright (c) 2022-2023, networkException * Copyright (c) 2022-2023, Linus Groh * * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -32,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -399,6 +401,117 @@ ErrorOr initialize_main_thread_vm() return { "type"sv }; }; + // 8.1.6.5.3 HostLoadImportedModule(referrer, moduleRequest, loadState, payload), https://html.spec.whatwg.org/multipage/webappapis.html#hostloadimportedmodule + s_main_thread_vm->host_load_imported_module = [](JS::Realm& realm, Variant, JS::NonnullGCPtr> referrer, JS::ModuleRequest const& module_request, Optional load_state, JS::GraphLoadingState& payload) -> void { + // 1. Let settingsObject be the current settings object. + Optional settings_object = HTML::current_settings_object(); + + // FIXME: 2. If settingsObject's global object implements WorkletGlobalScope or ServiceWorkerGlobalScope and loadState is undefined, then: + + // 3. Let referencingScript be null. + Optional referencing_script; + + // FIXME: 4. Let fetchOptions be the default classic script fetch options. + auto fetch_options = HTML::default_classic_script_fetch_options(); + + // 5. Let fetchReferrer be "client". + auto fetch_referrer = Fetch::Infrastructure::Request::Referrer::Client; + + // 6. If referrer is a Script Record or a Module Record, then: + if (referrer.has>() || referrer.has>()) { + // 1. Set referencingScript to referrer.[[HostDefined]]. + referencing_script = verify_cast(referrer.has>() ? *referrer.get>()->host_defined() : *referrer.get>()->host_defined()); + + // 2. Set settingsObject to referencingScript's settings object. + settings_object = referencing_script->settings_object(); + + // FIXME: 3. Set fetchOptions to the new descendant script fetch options for referencingScript's fetch options. + + // FIXME: 4. Assert: fetchOptions is not null, as referencingScript is a classic script or a JavaScript module script. + + // FIXME: 5. Set fetchReferrer to referrer's base URL. + } + + // 7. Disallow further import maps given settingsObject. + settings_object->disallow_further_import_maps(); + + // 8. Let url be the result of resolving a module specifier given referencingScript and moduleRequest.[[Specifier]], + // catching any exceptions. If they throw an exception, let resolutionError be the thrown exception. + auto url = HTML::resolve_module_specifier(referencing_script, module_request.module_specifier); + + // 9. If the previous step threw an exception, then: + if (url.is_exception()) { + // 1. Let completion be Completion Record { [[Type]]: throw, [[Value]]: resolutionError, [[Target]]: empty }. + auto completion = dom_exception_to_throw_completion(main_thread_vm(), url.exception()); + + // 2. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion). + JS::finish_loading_imported_module(realm, referrer, module_request, payload, completion); + + // 3. Return. + return; + } + + // 10. Let destination be "script". + auto destination = Fetch::Infrastructure::Request::Destination::Script; + + // 11. Let fetchClient be settingsObject. + Optional fetch_client = *settings_object; + + // 12. If loadState is not undefined, then: + if (load_state.has_value()) { + auto fetch_context = static_cast(load_state.value()); + + // 1. Set destination to loadState.[[Destination]]. + destination = fetch_context.destination; + + // 2. Set fetchClient loadState.[[FetchClient]]. + fetch_client = fetch_context.fetch_client; + } + + auto on_single_fetch_complete = HTML::create_on_fetch_script_complete(realm.heap(), [&referrer, &realm, &load_state, &module_request, &payload](JS::GCPtr const& module_script) -> void { + // onSingleFetchComplete given moduleScript is the following algorithm: + // 1. Let completion be null. + // NOTE: Our JS::Completion does not support non JS::Value types for its [[Value]], a such we + // use JS::ThrowCompletionOr here. + auto completion = JS::ThrowCompletionOr { {} }; + + // 2. If moduleScript is null, then set completion to Completion Record { [[Type]]: throw, [[Value]]: a new TypeError, [[Target]]: empty }. + if (!module_script) { + completion = JS::throw_completion(JS::TypeError::create(realm, DeprecatedString::formatted("Loading imported module '{}' failed.", module_request.module_specifier))); + } + // 3. Otherwise, if moduleScript's parse error is not null, then: + else if (!module_script->parse_error().is_empty()) { + // 1. Let parseError be moduleScript's parse error. + auto parse_error = module_script->parse_error(); + + // 2. Set completion to Completion Record { [[Type]]: throw, [[Value]]: parseError, [[Target]]: empty }. + completion = JS::throw_completion(parse_error); + + // 3. If loadState is not undefined and loadState.[[ParseError]] is null, set loadState.[[ParseError]] to parseError. + if (load_state.has_value()) { + auto load_state_as_fetch_context = static_cast(load_state.value()); + if (load_state_as_fetch_context.parse_error->is_empty()) { + load_state_as_fetch_context.parse_error = parse_error; + } + } + } + // 4. Otherwise, set completion to Completion Record { [[Type]]: normal, [[Value]]: result's record, [[Target]]: empty }. + else { + auto* record = static_cast(*module_script).record(); + + completion = JS::ThrowCompletionOr(record); + } + + // 5. Perform FinishLoadingImportedModule(referrer, moduleRequest, payload, completion). + JS::finish_loading_imported_module(realm, referrer, module_request, payload, completion); + }); + + // 13. Fetch a single imported module script given url, fetchClient, destination, fetchOptions, settingsObject, fetchReferrer, + // moduleRequest, and onSingleFetchComplete as defined below. + // If loadState is not undefined and loadState.[[PerformFetch]] is not null, pass loadState.[[PerformFetch]] along as well.# + HTML::fetch_single_imported_module_script(realm, url.release_value(), *fetch_client, destination, fetch_options, *settings_object, fetch_referrer, module_request, on_single_fetch_complete); + }; + // 8.1.6.5.3 HostResolveImportedModule(referencingScriptOrModule, moduleRequest), https://html.spec.whatwg.org/multipage/webappapis.html#hostresolveimportedmodule(referencingscriptormodule,-modulerequest) s_main_thread_vm->host_resolve_imported_module = [](JS::ScriptOrModule const& referencing_string_or_module, JS::ModuleRequest const& module_request) -> JS::ThrowCompletionOr> { // 1. Let moduleMap and referencingScript be null. diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp index 5d0a059a2c..024b223550 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.cpp @@ -582,7 +582,7 @@ void fetch_single_module_script(JS::Realm& realm, // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-module-script-tree void fetch_external_module_script_graph(JS::Realm& realm, AK::URL const& url, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const& options, OnFetchScriptComplete on_complete) { - // 1. Disallow further import maps given settings object. + // 1. Disallow further import maps given settingsObject. settings_object.disallow_further_import_maps(); auto steps = create_on_fetch_script_complete(realm.heap(), [&realm, &settings_object, on_complete, url](auto result) mutable { @@ -592,26 +592,22 @@ void fetch_external_module_script_graph(JS::Realm& realm, AK::URL const& url, En return; } - // 2. Let visited set be « (url, "javascript") ». - HashTable visited_set; - visited_set.set({ url, "javascript"sv }); - - // 3. Fetch the descendants of and link result given settings object, "script", visited set, and onComplete. + // 2. Fetch the descendants of and link result given settingsObject, "script", and onComplete. auto& module_script = verify_cast(*result); - fetch_descendants_of_and_link_a_module_script(realm, module_script, settings_object, Fetch::Infrastructure::Request::Destination::Script, move(visited_set), on_complete); + fetch_descendants_of_and_link_a_module_script(realm, module_script, settings_object, Fetch::Infrastructure::Request::Destination::Script, on_complete); }); - // 2. Fetch a single module script given url, settings object, "script", options, settings object, "client", true, and with the following steps given result: + // 2. Fetch a single module script given url, settingsObject, "script", options, settingsObject, "client", true, and with the following steps given result: fetch_single_module_script(realm, url, settings_object, Fetch::Infrastructure::Request::Destination::Script, options, settings_object, Web::Fetch::Infrastructure::Request::Referrer::Client, {}, TopLevelModule::Yes, steps); } // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-an-inline-module-script-graph void fetch_inline_module_script_graph(JS::Realm& realm, DeprecatedString const& filename, DeprecatedString const& source_text, AK::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete) { - // 1. Disallow further import maps given settings object. + // 1. Disallow further import maps given settingsObject. settings_object.disallow_further_import_maps(); - // 2. Let script be the result of creating a JavaScript module script using source text, settings object, base URL, and options. + // 2. Let script be the result of creating a JavaScript module script using sourceText, settingsObject, baseURL, and options. auto script = JavaScriptModuleScript::create(filename, source_text.view(), settings_object, base_url).release_value_but_fixme_should_propagate_errors(); // 3. If script is null, run onComplete given null, and return. @@ -620,53 +616,115 @@ void fetch_inline_module_script_graph(JS::Realm& realm, DeprecatedString const& return; } - // 4. Let visited set be an empty set. - HashTable visited_set; + // 5. Fetch the descendants of and link script, given settingsObject, "script", and onComplete. + fetch_descendants_of_and_link_a_module_script(realm, *script, settings_object, Fetch::Infrastructure::Request::Destination::Script, on_complete); +} - // 5. Fetch the descendants of and link script, given settings object, the destination "script", visited set, and onComplete. - fetch_descendants_of_and_link_a_module_script(realm, *script, settings_object, Fetch::Infrastructure::Request::Destination::Script, visited_set, on_complete); +// https://html.spec.whatwg.org/multipage/webappapis.html#fetch-a-single-imported-module-script +void fetch_single_imported_module_script(JS::Realm& realm, + AK::URL const& url, + EnvironmentSettingsObject& fetch_client, + Fetch::Infrastructure::Request::Destination destination, + ScriptFetchOptions const& options, + EnvironmentSettingsObject& settings_object, + Fetch::Infrastructure::Request::Referrer referrer, + JS::ModuleRequest const& module_request, + OnFetchScriptComplete on_complete) +{ + // 1. Assert: moduleRequest.[[Attributes]] does not contain any Record entry such that entry.[[Key]] is not "type", + // because we only asked for "type" attributes in HostGetSupportedImportAttributes. + for (auto const& entry : module_request.assertions) + VERIFY(entry.key == "type"sv); + + // 2. Let moduleType be the result of running the module type from module request steps given moduleRequest. + auto module_type = module_type_from_module_request(module_request); + + // 3. If the result of running the module type allowed steps given moduleType and settingsObject is false, + // then run onComplete given null, and return. + if (!settings_object.module_type_allowed(module_type)) { + on_complete->function()(nullptr); + return; + } + + // 4. Fetch a single module script given url, fetchClient, destination, options, settingsObject, referrer, moduleRequest, false, + // and onComplete. If performFetch was given, pass it along as well. + fetch_single_module_script(realm, url, fetch_client, destination, options, settings_object, referrer, module_request, TopLevelModule::No, on_complete); } // https://html.spec.whatwg.org/multipage/webappapis.html#fetch-the-descendants-of-and-link-a-module-script -void fetch_descendants_of_and_link_a_module_script(JS::Realm& realm, JavaScriptModuleScript& module_script, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination destination, HashTable const& visited_set, OnFetchScriptComplete on_complete) +void fetch_descendants_of_and_link_a_module_script(JS::Realm& realm, + JavaScriptModuleScript& module_script, + EnvironmentSettingsObject& fetch_client, + Fetch::Infrastructure::Request::Destination destination, + OnFetchScriptComplete on_complete) { - auto on_fetch_descendants_complete = create_on_fetch_script_complete(realm.heap(), [&fetch_client_settings_object, on_complete](auto result) { - // onFetchDescendantsComplete given result is the following algorithm: - // 1. If result is null, then run onComplete given result, and abort these steps. - if (!result) { - on_complete->function()(nullptr); - return; - } + // 1. Let record be moduleScript's record. + auto* record = module_script.record(); - TemporaryExecutionContext execution_context { fetch_client_settings_object }; + // 2. If record is null, then: + if (!record) { + // 1. Set moduleScript's error to rethrow to moduleScript's parse error. + module_script.set_error_to_rethrow(module_script.parse_error()); - // FIXME: 2. Let parse error be the result of finding the first parse error given result. + // 2. Run onComplete given moduleScript. + on_complete->function()(module_script); - // 3. If parse error is null, then: - if (auto& module_script = verify_cast(*result); module_script.record()) { - // 1. Let record be result's record. - auto const& record = *module_script.record(); + // 3. Return. + return; + } - // 2. Perform record.Link(). - auto linking_result = const_cast(record).link(result->vm()); + // 3. Let state be Record { [[ParseError]]: null, [[Destination]]: destination, [[PerformFetch]]: null, [[FetchClient]]: fetchClient }. + auto state = FetchContext { {}, destination, {}, fetch_client }; - // If this throws an exception, set result's error to rethrow to that exception. - if (linking_result.is_throw_completion()) { - result->set_error_to_rethrow(linking_result.release_error().value().value()); - } - } else { - // FIXME: 4. Otherwise, set result's error to rethrow to parse error. - TODO(); - } + // FIXME: 4. If performFetch was given, set state.[[PerformFetch]] to performFetch. - // 5. Run onComplete given result. - on_complete->function()(result); + // FIXME: These should most likely be steps in the spec. + // NOTE: For reasons beyond my understanding, we cannot use TemporaryExecutionContext here. + // Calling perform_a_microtask_checkpoint() on the fetch_client's responsible_event_loop + // prevents this from functioning properly. HTMLParser::the_end would be run before + // HTMLScriptElement::prepare_script had a chance to setup the callback to mark_done properly, + // resulting in the event loop hanging forever awaiting for the script to be ready for parser + // execution. + realm.vm().push_execution_context(fetch_client.realm_execution_context()); + fetch_client.prepare_to_run_callback(); + + // 5. Let loadingPromise be record.LoadRequestedModules(state). + auto& loading_promise = record->load_requested_modules(realm, state); + + // 6. Upon fulfillment of loadingPromise, run the following steps: + WebIDL::upon_fulfillment(loading_promise, [&realm, record, &module_script, on_complete](auto const&) -> WebIDL::ExceptionOr { + // 1. Perform record.Link(). + auto linking_result = record->link(realm.vm()); + + // If this throws an exception, set result's error to rethrow to that exception. + if (linking_result.is_throw_completion()) + module_script.set_error_to_rethrow(linking_result.release_error().value().value()); + + // 2. Run onComplete given moduleScript. + on_complete->function()(module_script); + + return JS::js_undefined(); }); - // 1. Fetch the descendants of module script, given fetch client settings object, destination, visited set, and onFetchDescendantsComplete as defined below. - // If performFetch was given, pass it along as well. - // FIXME: Pass performFetch if given. - fetch_descendants_of_a_module_script(realm, module_script, fetch_client_settings_object, destination, visited_set, on_fetch_descendants_complete); + // 7. Upon rejection of loadingPromise, run the following steps: + WebIDL::upon_rejection(loading_promise, [&state, &module_script, on_complete](auto const&) -> WebIDL::ExceptionOr { + // 1. If state.[[ParseError]] is not null, set moduleScript's error to rethrow to state.[[ParseError]] and run + // onComplete given moduleScript. + if (state.parse_error != nullptr) { + module_script.set_error_to_rethrow(*state.parse_error); + + on_complete->function()(module_script); + } + // 2. Otherwise, run onComplete given null. + else { + on_complete->function()(nullptr); + } + + return JS::js_undefined(); + }); + + fetch_client.clean_up_after_running_callback(); + realm.vm().pop_execution_context(); } } diff --git a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h index 0613ef2d9c..a662e95d15 100644 --- a/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h +++ b/Userland/Libraries/LibWeb/HTML/Scripting/Fetching.h @@ -69,10 +69,11 @@ Optional resolve_url_like_module_specifier(DeprecatedString const& spec WebIDL::ExceptionOr fetch_classic_script(JS::NonnullGCPtr, AK::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions options, CORSSettingAttribute cors_setting, String character_encoding, OnFetchScriptComplete on_complete); void fetch_internal_module_script_graph(JS::Realm&, JS::ModuleRequest const& module_request, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, Script& referring_script, HashTable const& visited_set, OnFetchScriptComplete on_complete); void fetch_external_module_script_graph(JS::Realm&, AK::URL const&, EnvironmentSettingsObject& settings_object, ScriptFetchOptions const&, OnFetchScriptComplete on_complete); -void fetch_inline_module_script_graph(JS::Realm& realm, DeprecatedString const& filename, DeprecatedString const& source_text, AK::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete); +void fetch_inline_module_script_graph(JS::Realm&, DeprecatedString const& filename, DeprecatedString const& source_text, AK::URL const& base_url, EnvironmentSettingsObject& settings_object, OnFetchScriptComplete on_complete); +void fetch_single_imported_module_script(JS::Realm&, AK::URL const&, EnvironmentSettingsObject& fetch_client, Fetch::Infrastructure::Request::Destination, ScriptFetchOptions const&, EnvironmentSettingsObject& settings_object, Fetch::Infrastructure::Request::Referrer, JS::ModuleRequest const&, OnFetchScriptComplete on_complete); void fetch_descendants_of_a_module_script(JS::Realm&, JavaScriptModuleScript& module_script, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, HashTable visited_set, OnFetchScriptComplete callback); -void fetch_descendants_of_and_link_a_module_script(JS::Realm&, JavaScriptModuleScript& module_script, EnvironmentSettingsObject& fetch_client_settings_object, Fetch::Infrastructure::Request::Destination, HashTable const& visited_set, OnFetchScriptComplete on_complete); +void fetch_descendants_of_and_link_a_module_script(JS::Realm&, JavaScriptModuleScript&, EnvironmentSettingsObject&, Fetch::Infrastructure::Request::Destination, OnFetchScriptComplete on_complete); enum class TopLevelModule { Yes,