LibJS+LibWeb: Bring script fetching closer to the spec

This patch updates various parts of the script fetching implementation
to match the current specification.

Notably, the implementation of changes to the import assertions /
attributes proposal are not part of this patch(series).
This commit is contained in:
networkException 2023-10-29 02:53:53 +01:00 committed by Andreas Kling
parent 5b1d0d4d1b
commit ff6d7cf3e4
Notes: sideshowbarker 2024-07-16 18:26:46 +09:00
8 changed files with 406 additions and 47 deletions

View file

@ -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<GraphLoadingState::HostDefined> 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<CyclicModule&>(*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<CyclicModule>(*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<Module*> 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*>(module_completion.value());
// a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]).
static_cast<CyclicModule*>(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<void> CyclicModule::link(VM& vm)
{

View file

@ -49,6 +49,9 @@ public:
virtual ThrowCompletionOr<void> link(VM& vm) override final;
virtual ThrowCompletionOr<Promise*> evaluate(VM& vm) override final;
virtual PromiseCapability& load_requested_modules(Realm&, Optional<GraphLoadingState::HostDefined>);
virtual void inner_module_loading(GraphLoadingState& state);
Vector<ModuleRequest> const& requested_modules() const { return m_requested_modules; }
Vector<ModuleWithSpecifier> const& loaded_modules() const { return m_loaded_modules; }
@ -82,4 +85,6 @@ protected:
Optional<u32> m_pending_async_dependencies; // [[PendingAsyncDependencies]]
};
void continue_module_loading(Realm&, GraphLoadingState& state, ThrowCompletionOr<Module*> const&);
}

View file

@ -1,6 +1,7 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2023, networkException <networkexception@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -8,6 +9,7 @@
#include <LibJS/CyclicModule.h>
#include <LibJS/Module.h>
#include <LibJS/Runtime/ModuleNamespaceObject.h>
#include <LibJS/Runtime/ModuleRequest.h>
#include <LibJS/Runtime/Promise.h>
#include <LibJS/Runtime/VM.h>
@ -62,6 +64,51 @@ ThrowCompletionOr<u32> Module::inner_module_evaluation(VM& vm, Vector<Module*>&,
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<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>> referrer, ModuleRequest const& module_request, GraphLoadingState& payload, ThrowCompletionOr<Module*> const& result)
{
// 1. If result is a normal completion, then
if (!result.is_error()) {
auto loaded_modules = referrer.visit(
[](JS::NonnullGCPtr<JS::Script> script) -> Vector<ModuleWithSpecifier> { return script->loaded_modules(); },
[](JS::NonnullGCPtr<JS::CyclicModule> module) -> Vector<ModuleWithSpecifier> { 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<Module*>(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>(*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<Object*> Module::get_module_namespace(VM& vm)
{

View file

@ -109,4 +109,9 @@ private:
DeprecatedString m_filename;
};
class CyclicModule;
struct GraphLoadingState;
void finish_loading_imported_module(Realm&, Variant<NonnullGCPtr<Script>, NonnullGCPtr<CyclicModule>>, ModuleRequest const&, GraphLoadingState&, ThrowCompletionOr<Module*> const&);
}

View file

@ -2,6 +2,7 @@
* Copyright (c) 2020-2023, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
* Copyright (c) 2021-2022, David Tuin <davidot@serenityos.org>
* Copyright (c) 2023, networkException <networkexception@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
@ -14,6 +15,7 @@
#include <AK/RefCounted.h>
#include <AK/StackInfo.h>
#include <AK/Variant.h>
#include <LibJS/CyclicModule.h>
#include <LibJS/Heap/Heap.h>
#include <LibJS/Heap/MarkedVector.h>
#include <LibJS/Runtime/CommonPropertyNames.h>
@ -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<void(Realm&, Variant<NonnullGCPtr<Script>, NonnullGCPtr<CyclicModule>>, ModuleRequest const&, Optional<GraphLoadingState::HostDefined>, GraphLoadingState&)> host_load_imported_module;
Function<ThrowCompletionOr<NonnullGCPtr<Module>>(ScriptOrModule, ModuleRequest const&)> host_resolve_imported_module;
Function<ThrowCompletionOr<void>(ScriptOrModule, ModuleRequest, PromiseCapability const&)> host_import_module_dynamically;
Function<void(ScriptOrModule, ModuleRequest const&, PromiseCapability const&, Promise*)> host_finish_dynamic_import;

View file

@ -1,12 +1,13 @@
/*
* Copyright (c) 2021-2022, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2021-2023, Luke Wilde <lukew@serenityos.org>
* Copyright (c) 2022, networkException <networkexception@serenityos.org>
* Copyright (c) 2022-2023, networkException <networkexception@serenityos.org>
* Copyright (c) 2022-2023, Linus Groh <linusg@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/AST.h>
#include <LibJS/Heap/DeferGC.h>
#include <LibJS/Module.h>
#include <LibJS/Runtime/Array.h>
@ -32,6 +33,7 @@
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/ExceptionReporter.h>
#include <LibWeb/HTML/Scripting/Fetching.h>
#include <LibWeb/HTML/Scripting/ModuleScript.h>
#include <LibWeb/HTML/Scripting/Script.h>
#include <LibWeb/HTML/TagNames.h>
#include <LibWeb/HTML/Window.h>
@ -399,6 +401,117 @@ ErrorOr<void> 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<JS::Script>, JS::NonnullGCPtr<JS::CyclicModule>> referrer, JS::ModuleRequest const& module_request, Optional<JS::GraphLoadingState::HostDefined> load_state, JS::GraphLoadingState& payload) -> void {
// 1. Let settingsObject be the current settings object.
Optional<HTML::EnvironmentSettingsObject&> 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<HTML::Script&> 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<JS::NonnullGCPtr<JS::Script>>() || referrer.has<JS::NonnullGCPtr<JS::CyclicModule>>()) {
// 1. Set referencingScript to referrer.[[HostDefined]].
referencing_script = verify_cast<HTML::Script>(referrer.has<JS::NonnullGCPtr<JS::Script>>() ? *referrer.get<JS::NonnullGCPtr<JS::Script>>()->host_defined() : *referrer.get<JS::NonnullGCPtr<JS::CyclicModule>>()->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<HTML::EnvironmentSettingsObject&> fetch_client = *settings_object;
// 12. If loadState is not undefined, then:
if (load_state.has_value()) {
auto fetch_context = static_cast<HTML::FetchContext&>(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<HTML::Script> 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<JS::Module*> { {} };
// 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<HTML::FetchContext&>(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<HTML::JavaScriptModuleScript&>(*module_script).record();
completion = JS::ThrowCompletionOr<JS::Module*>(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<JS::NonnullGCPtr<JS::Module>> {
// 1. Let moduleMap and referencingScript be null.

View file

@ -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<ModuleLocationTuple> 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<JavaScriptModuleScript>(*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<ModuleLocationTuple> 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<ModuleLocationTuple> 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<JavaScriptModuleScript>(*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<JS::SourceTextModule&>(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<JS::Value> {
// 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<JS::Value> {
// 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();
}
}

View file

@ -69,10 +69,11 @@ Optional<AK::URL> resolve_url_like_module_specifier(DeprecatedString const& spec
WebIDL::ExceptionOr<void> fetch_classic_script(JS::NonnullGCPtr<HTMLScriptElement>, 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<ModuleLocationTuple> 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<ModuleLocationTuple> 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<ModuleLocationTuple> 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,