diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index ed9086a572..fc21f4bb17 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -164,32 +165,7 @@ ThrowCompletionOr shadow_realm_import_value(GlobalObject& global_object, TRY(vm.push_execution_context(eval_context, eval_realm.global_object())); // 10. Perform ! HostImportModuleDynamically(null, specifierString, innerCapability). - // FIXME: We don't have this yet. We generally have very little support for modules and imports. - // So, in the meantime we just do the "Failure path" step, and pretend to call FinishDynamicImport - // with the rejected promise. This should be easy to complete once those missing module AOs are added. - - // HostImportModuleDynamically: At some future time, the host environment must perform - // FinishDynamicImport(referencingScriptOrModule, specifier, promiseCapability, promise), - // where promise is a Promise rejected with an error representing the cause of failure. - auto* promise = Promise::create(global_object); - promise->reject(Error::create(global_object, String::formatted("Import of '{}' from '{}' failed", export_name_string, specifier_string))); - - // FinishDynamicImport, 5. Perform ! PerformPromiseThen(innerPromise, onFulfilled, onRejected). - promise->perform_then( - NativeFunction::create(global_object, "", [](auto&, auto&) -> ThrowCompletionOr { - // Not called because we hardcoded a rejection above. - TODO(); - }), - NativeFunction::create(global_object, "", [reject = make_handle(inner_capability.reject)](auto& vm, auto& global_object) -> ThrowCompletionOr { - auto error = vm.argument(0); - - // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « error »). - MUST(call(global_object, reject.cell(), js_undefined(), error)); - - // b. Return undefined. - return js_undefined(); - }), - {}); + vm.host_import_module_dynamically(Empty {}, ModuleRequest { move(specifier_string) }, inner_capability); // 11. Suspend evalContext and remove it from the execution context stack. // NOTE: We don't support this concept yet. @@ -208,7 +184,9 @@ ThrowCompletionOr shadow_realm_import_value(GlobalObject& global_object, "", [string = move(export_name_string)](auto& vm, auto& global_object) -> ThrowCompletionOr { // 1. Assert: exports is a module namespace exotic object. + VERIFY(vm.argument(0).is_object()); auto& exports = vm.argument(0).as_object(); + VERIFY(is(exports)); // 2. Let f be the active function object. auto* function = vm.running_execution_context().function; 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 d79c9bc1b7..8b1efef9f2 100644 --- a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js +++ b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js @@ -3,10 +3,10 @@ describe("normal behavior", () => { expect(ShadowRealm.prototype.importValue).toHaveLength(2); }); - test("basic functionality", () => { + test("fails if module cannot be loaded", () => { // NOTE: The actual import is currently not implemented and always pretends to fail for now. const shadowRealm = new ShadowRealm(); - const promise = shadowRealm.importValue("./myModule.js", "foo"); + const promise = shadowRealm.importValue("./file_should_not_exist.js", "foo"); let error; promise.catch(value => { error = value; @@ -14,7 +14,53 @@ describe("normal behavior", () => { expect(promise).toBeInstanceOf(Promise); runQueuedPromiseJobs(); expect(error).toBeInstanceOf(TypeError); - expect(error.message).toBe("Import of 'foo' from './myModule.js' failed"); + expect(error.message).toBe("Cannot find/open module: './file_should_not_exist.js'"); + }); + + test("basic functionality", () => { + const shadowRealm = new ShadowRealm(); + const promise = shadowRealm.importValue("./external-module.mjs", "foo"); + expect(promise).toBeInstanceOf(Promise); + let error = null; + let passed = false; + promise + .then(value => { + expect(value).toBe("Well hello shadows"); + expect(typeof value).toBe("string"); + + expect(value).not.toHaveProperty("default", null); + expect(value).not.toHaveProperty("bar", null); + passed = true; + }) + .catch(value => { + error = value; + }); + runQueuedPromiseJobs(); + expect(error).toBeNull(); + expect(passed).toBeTrue(); + }); + + test("value from async module", () => { + const shadowRealm = new ShadowRealm(); + const promise = shadowRealm.importValue("./async-module.mjs", "foo"); + expect(promise).toBeInstanceOf(Promise); + let error = null; + let passed = false; + promise + .then(value => { + expect(value).toBe("Well hello async shadows"); + expect(typeof value).toBe("string"); + + expect(value).not.toHaveProperty("default", null); + expect(value).not.toHaveProperty("bar", null); + passed = true; + }) + .catch(value => { + error = value; + }); + runQueuedPromiseJobs(); + expect(error).toBeNull(); + expect(passed).toBeTrue(); }); }); diff --git a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs new file mode 100644 index 0000000000..a95ab095aa --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/async-module.mjs @@ -0,0 +1,11 @@ +await Promise.resolve(0); + +export const foo = "Well hello async shadows"; + +await 1; + +export default "Default export"; + +await Promise.resolve(2); + +export const bar = "'bar' export"; diff --git a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/external-module.mjs b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/external-module.mjs new file mode 100644 index 0000000000..faa59c3841 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/external-module.mjs @@ -0,0 +1,5 @@ +export const foo = "Well hello shadows"; + +export default "Default export"; + +export const bar = "'bar' export";