From 6319dedbcd4d6e448b4cc2615e62eba5ac576887 Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Fri, 15 Nov 2024 15:11:48 +0000 Subject: [PATCH] LibJS: Perform TLA async function construction in the module context Previously it was only pushing the module context for the call to capture the module execution context. This is incorrect, as the capture occurs upon function construction. This resulted in it capturing the execution context that execute_module was called from, instead of the newly created module_context. https://github.com/LadybirdBrowser/ladybird/blob/f87041bf3a8c76dce547dbd1f64d32b119f5468c/Libraries/LibJS/Runtime/ECMAScriptFunctionObject.cpp#L92 This can be demonstrated with the following setup: index.html: ```html ``` scriptA.mjs: ```js function foo() { return {a: "b"}; } export let test = await foo(); ``` Before this fix, this would throw: ``` [TypeError] 1 is not a function (evaluated from 'foo') at module code with top-level await at module code with top-level await at at ``` Fixes #2245. --- Libraries/LibJS/SourceTextModule.cpp | 10 ++++--- .../ShadowRealm.prototype.importValue.js | 27 +++++++++++++++++++ .../builtins/ShadowRealm/async-module.mjs | 6 +++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Libraries/LibJS/SourceTextModule.cpp b/Libraries/LibJS/SourceTextModule.cpp index ff200d5fbe3..956b584ccc9 100644 --- a/Libraries/LibJS/SourceTextModule.cpp +++ b/Libraries/LibJS/SourceTextModule.cpp @@ -760,6 +760,10 @@ ThrowCompletionOr SourceTextModule::execute_module(VM& vm, GC::Ptr SourceTextModule::execute_module(VM& vm, GC::Ptrset_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(); + auto result = call(vm, Value { module_wrapper_function }, js_undefined(), ReadonlySpan {}); + // 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())); diff --git a/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js b/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js index 9b7ab9677a1..ffbad80f2af 100644 --- a/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js +++ b/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js @@ -53,6 +53,33 @@ describe("normal behavior", () => { expect(value).not.toHaveProperty("default", null); expect(value).not.toHaveProperty("bar", null); + expect(value).not.toHaveProperty("baz", null); + expect(value).not.toHaveProperty("qux", null); + passed = true; + }) + .catch(value => { + error = value; + }); + runQueuedPromiseJobs(); + expect(error).toBeNull(); + expect(passed).toBeTrue(); + }); + + test("value from async module from top-level awaited function", () => { + const shadowRealm = new ShadowRealm(); + const promise = shadowRealm.importValue("./async-module.mjs", "qux"); + expect(promise).toBeInstanceOf(Promise); + let error = null; + let passed = false; + promise + .then(value => { + expect(value).toBe("'qux' export"); + expect(typeof value).toBe("string"); + + expect(value).not.toHaveProperty("default", null); + expect(value).not.toHaveProperty("foo", null); + expect(value).not.toHaveProperty("bar", null); + expect(value).not.toHaveProperty("baz", null); passed = true; }) .catch(value => { diff --git a/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs b/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs index a95ab095aad..703789f6719 100644 --- a/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs +++ b/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs @@ -9,3 +9,9 @@ export default "Default export"; await Promise.resolve(2); export const bar = "'bar' export"; + +async function baz() { + return "'qux' export"; +} + +export const qux = await baz();