From a2c3db8367a0470827553e370e3620742cdc9877 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 6 Dec 2023 11:35:40 +0100 Subject: [PATCH] LibJS: Add basic support for module code with top-level await For now, we handle this by creating a synthetic async function to wrap the top-level module code. This allows us to piggyback on the async function driver wrapper mechanism. --- .../Runtime/ECMAScriptFunctionObject.cpp | 6 ++++- .../LibJS/Runtime/ECMAScriptFunctionObject.h | 4 +++ Userland/Libraries/LibJS/SourceTextModule.cpp | 26 ++++++++++++++++++- .../ShadowRealm.prototype.importValue.js | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp index f3ca477afb..2ecbe8235f 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp @@ -1192,7 +1192,11 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body() } } - auto declaration_result = function_declaration_instantiation(); + auto declaration_result = [&]() -> ThrowCompletionOr { + if (is_module_wrapper()) + return {}; + return function_declaration_instantiation(); + }(); if (m_kind == FunctionKind::Normal || m_kind == FunctionKind::Generator || m_kind == FunctionKind::AsyncGenerator) { if (declaration_result.is_error()) diff --git a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h index aa8704e71d..32256877d7 100644 --- a/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h +++ b/Userland/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.h @@ -49,6 +49,9 @@ public: void make_method(Object& home_object); + [[nodiscard]] bool is_module_wrapper() const { return m_is_module_wrapper; } + void set_is_module_wrapper(bool b) { m_is_module_wrapper = b; } + Statement const& ecmascript_code() const { return m_ecmascript_code; } Vector const& formal_parameters() const { return m_formal_parameters; } @@ -153,6 +156,7 @@ private: HashTable m_parameter_names; Vector m_functions_to_initialize; bool m_arguments_object_needed { false }; + bool m_is_module_wrapper { false }; Vector m_var_names_to_initialize_binding; Vector m_function_names_to_initialize_binding; diff --git a/Userland/Libraries/LibJS/SourceTextModule.cpp b/Userland/Libraries/LibJS/SourceTextModule.cpp index 56a760e3fd..decb1142b3 100644 --- a/Userland/Libraries/LibJS/SourceTextModule.cpp +++ b/Userland/Libraries/LibJS/SourceTextModule.cpp @@ -9,9 +9,11 @@ #include #include #include +#include #include #include #include +#include #include namespace JS { @@ -748,7 +750,29 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GCPtr>(vm, m_ecmascript_code, *capability, *module_context); + + // AD-HOC: We implement asynchronous execution via synthetic generator functions, + // so we fake "AsyncBlockStart" here by creating an async function to wrap + // the top-level module code. + // FIXME: Improve this situation, so we can match the spec better. + + auto module_wrapper_function = ECMAScriptFunctionObject::create( + realm(), "module code with top-level await", StringView {}, this->m_ecmascript_code, + {}, 0, {}, environment(), nullptr, FunctionKind::Async, true, false, false); + module_wrapper_function->set_is_module_wrapper(true); + + // AD-HOC: We push/pop the moduleContext around the call to ensure that the async execution context + // captures the module execution context. + vm.push_execution_context(*module_context); + auto result = call(vm, Value { module_wrapper_function }, js_undefined(), ReadonlySpan {}); + vm.pop_execution_context(); + + // AD-HOC: This is basically analogous to what AsyncBlockStart would do. + if (result.is_throw_completion()) { + MUST(call(vm, *capability->reject(), js_undefined(), result.throw_completion().value().value())); + } else { + MUST(call(vm, *capability->resolve(), js_undefined(), result.value())); + } } // 11. Return unused. diff --git a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js index 74ea9b115b..9b7ab9677a 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js +++ b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js @@ -40,7 +40,7 @@ describe("normal behavior", () => { expect(passed).toBeTrue(); }); - test.xfail("value from async module", () => { + test("value from async module", () => { const shadowRealm = new ShadowRealm(); const promise = shadowRealm.importValue("./async-module.mjs", "foo"); expect(promise).toBeInstanceOf(Promise);