Sfoglia il codice sorgente

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.
Andreas Kling 1 anno fa
parent
commit
a2c3db8367

+ 5 - 1
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<void> {
+        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())

+ 4 - 0
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<FunctionParameter> const& formal_parameters() const { return m_formal_parameters; }
 
@@ -153,6 +156,7 @@ private:
     HashTable<DeprecatedFlyString> m_parameter_names;
     Vector<FunctionDeclaration const&> m_functions_to_initialize;
     bool m_arguments_object_needed { false };
+    bool m_is_module_wrapper { false };
     Vector<VariableNameToInitialize> m_var_names_to_initialize_binding;
     Vector<DeprecatedFlyString> m_function_names_to_initialize_binding;
 

+ 25 - 1
Userland/Libraries/LibJS/SourceTextModule.cpp

@@ -9,9 +9,11 @@
 #include <AK/QuickSort.h>
 #include <LibJS/Bytecode/Interpreter.h>
 #include <LibJS/Parser.h>
+#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
 #include <LibJS/Runtime/ECMAScriptFunctionObject.h>
 #include <LibJS/Runtime/GlobalEnvironment.h>
 #include <LibJS/Runtime/ModuleEnvironment.h>
+#include <LibJS/Runtime/PromiseCapability.h>
 #include <LibJS/SourceTextModule.h>
 
 namespace JS {
@@ -748,7 +750,29 @@ ThrowCompletionOr<void> SourceTextModule::execute_module(VM& vm, GCPtr<PromiseCa
         VERIFY(capability != nullptr);
 
         // b. Perform AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext).
-        async_block_start<NonnullRefPtr<Statement const>>(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<Value> {});
+        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.

+ 1 - 1
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);