diff options
author | Linus Groh <mail@linusgroh.de> | 2021-10-15 01:10:05 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-10-15 09:36:21 +0100 |
commit | 5910a41adb2b3f46a5f32be3a62bee9828923356 (patch) | |
tree | 507a50d68d6673000f9cd10b0f7abd576669b781 | |
parent | 2ffb30a99690bb9bb1b03d71ce50adae2a83324d (diff) | |
download | serenity-5910a41adb2b3f46a5f32be3a62bee9828923356.zip |
LibJS: Implement ShadowRealm.prototype.importValue()
Well... sort of. This adds the function itself and all the scaffolding
from the ShadowRealm API (and basically completes its implementation).
However, we do not nearly have enough support for modules and imports,
so we currently pretend whatever was attempted to be imported failed -
once we have HostImportModuleDynamically it should be relatively easy to
complete the implementation.
6 files changed, 177 insertions, 0 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 02bcce5378..cd57be17e1 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -242,6 +242,7 @@ namespace JS { P(id) \ P(ignoreCase) \ P(imul) \ + P(importValue) \ P(includes) \ P(index) \ P(indexOf) \ diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp index 0d966394e8..c9369ae74f 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.cpp @@ -8,6 +8,9 @@ #include <LibJS/Parser.h> #include <LibJS/Runtime/AbstractOperations.h> #include <LibJS/Runtime/DeclarativeEnvironment.h> +#include <LibJS/Runtime/NativeFunction.h> +#include <LibJS/Runtime/PromiseConstructor.h> +#include <LibJS/Runtime/PromiseReaction.h> #include <LibJS/Runtime/ShadowRealm.h> #include <LibJS/Runtime/WrappedFunction.h> @@ -146,6 +149,118 @@ ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject& global_object, // NOTE: Also see "Editor's Note" in the spec regarding the TypeError above. } +// 3.1.2 ShadowRealmImportValue ( specifierString, exportNameString, callerRealm, evalRealm, evalContext ), https://tc39.es/proposal-shadowrealm/#sec-shadowrealmimportvalue +ThrowCompletionOr<Value> shadow_realm_import_value(GlobalObject& global_object, String specifier_string, String export_name_string, Realm& caller_realm, Realm& eval_realm, ExecutionContext& eval_context) +{ + auto& vm = global_object.vm(); + + // 1. Assert: Type(specifierString) is String. + // 2. Assert: Type(exportNameString) is String. + // 3. Assert: callerRealm is a Realm Record. + // 4. Assert: evalRealm is a Realm Record. + // 5. Assert: evalContext is an execution context associated to a ShadowRealm instance's [[ExecutionContext]]. + + // 6. Let innerCapability be ! NewPromiseCapability(%Promise%). + auto inner_capability = new_promise_capability(global_object, global_object.promise_constructor()); + VERIFY(!vm.exception()); + + // 7. Let runningContext be the running execution context. + // 8. If runningContext is not already suspended, suspend runningContext. + // NOTE: We don't support this concept yet. + + // 9. Push evalContext onto the execution context stack; evalContext is now the running execution context. + 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&) -> Value { + // Not called because we hardcoded a rejection above. + TODO(); + }), + NativeFunction::create(global_object, "", [reject = make_handle(inner_capability.reject)](auto& vm, auto& global_object) -> Value { + 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(); + }), + {}); + + // 11. Suspend evalContext and remove it from the execution context stack. + // NOTE: We don't support this concept yet. + vm.pop_execution_context(); + + // 12. Resume the context that is now on the top of the execution context stack as the running execution context. + // NOTE: We don't support this concept yet. + + // 13. Let steps be the steps of an ExportGetter function as described below. + // 14. Let onFulfilled be ! CreateBuiltinFunction(steps, 1, "", « [[ExportNameString]] », callerRealm). + // 15. Set onFulfilled.[[ExportNameString]] to exportNameString. + // FIXME: Support passing a realm to NativeFunction::create() + (void)caller_realm; + auto* on_fulfilled = NativeFunction::create( + global_object, + "", + [string = move(export_name_string)](auto& vm, auto& global_object) -> Value { + // 1. Assert: exports is a module namespace exotic object. + auto& exports = vm.argument(0).as_object(); + + // 2. Let f be the active function object. + auto* function = vm.running_execution_context().function; + + // 3. Let string be f.[[ExportNameString]]. + // 4. Assert: Type(string) is String. + + // 5. Let hasOwn be ? HasOwnProperty(exports, string). + auto has_own = TRY_OR_DISCARD(exports.has_own_property(string)); + + // 6. If hasOwn is false, throw a TypeError exception. + if (!has_own) { + vm.template throw_exception<TypeError>(global_object, ErrorType::MissingRequiredProperty, string); + return {}; + } + + // 7. Let value be ? Get(exports, string). + auto value = TRY_OR_DISCARD(exports.get(string)); + + // 8. Let realm be f.[[Realm]]. + auto* realm = function->realm(); + VERIFY(realm); + + // 9. Return ? GetWrappedValue(realm, value). + return TRY_OR_DISCARD(get_wrapped_value(global_object, *realm, value)); + }); + on_fulfilled->define_direct_property(vm.names.length, Value(1), Attribute::Configurable); + on_fulfilled->define_direct_property(vm.names.name, js_string(vm, String::empty()), Attribute::Configurable); + + // 16. Let promiseCapability be ! NewPromiseCapability(%Promise%). + auto promise_capability = new_promise_capability(global_object, global_object.promise_constructor()); + VERIFY(!vm.exception()); + + // NOTE: Even though the spec tells us to use %ThrowTypeError%, it's not observable if we actually do. + // Throw a nicer TypeError forwarding the import error message instead (we know the argument is an Error object). + auto* throw_type_error = NativeFunction::create(global_object, {}, [](auto& vm, auto& global_object) -> Value { + vm.template throw_exception<TypeError>(global_object, vm.argument(0).as_object().get_without_side_effects(vm.names.message).as_string().string()); + return {}; + }); + + // 17. Return ! PerformPromiseThen(innerCapability.[[Promise]], onFulfilled, callerRealm.[[Intrinsics]].[[%ThrowTypeError%]], promiseCapability). + return verify_cast<Promise>(inner_capability.promise)->perform_then(on_fulfilled, throw_type_error, promise_capability); +} + // 3.1.3 GetWrappedValue ( callerRealm, value ), https://tc39.es/proposal-shadowrealm/#sec-getwrappedvalue ThrowCompletionOr<Value> get_wrapped_value(GlobalObject& global_object, Realm& caller_realm, Value value) { diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealm.h b/Userland/Libraries/LibJS/Runtime/ShadowRealm.h index 79b203a3ce..9e57ff86a8 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealm.h +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealm.h @@ -33,6 +33,7 @@ private: }; ThrowCompletionOr<Value> perform_shadow_realm_eval(GlobalObject&, StringView source_text, Realm& caller_realm, Realm& eval_realm); +ThrowCompletionOr<Value> shadow_realm_import_value(GlobalObject&, String specifier_string, String export_name_string, Realm& caller_realm, Realm& eval_realm, ExecutionContext& eval_context); ThrowCompletionOr<Value> get_wrapped_value(GlobalObject&, Realm& caller_realm, Value); } diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp b/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp index 7221118254..157abd8fe8 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.cpp @@ -23,6 +23,7 @@ void ShadowRealmPrototype::initialize(GlobalObject& global_object) u8 attr = Attribute::Writable | Attribute::Configurable; define_native_function(vm.names.evaluate, evaluate, 1, attr); + define_native_function(vm.names.importValue, import_value, 2, attr); // 3.4.3 ShadowRealm.prototype [ @@toStringTag ], https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype-@@tostringtag define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, vm.names.ShadowRealm.as_string()), Attribute::Configurable); @@ -55,4 +56,35 @@ JS_DEFINE_NATIVE_FUNCTION(ShadowRealmPrototype::evaluate) return TRY_OR_DISCARD(perform_shadow_realm_eval(global_object, source_text.as_string().string(), *caller_realm, eval_realm)); } +// 3.4.2 ShadowRealm.prototype.importValue ( specifier, exportName ), https://tc39.es/proposal-shadowrealm/#sec-shadowrealm.prototype.importvalue +JS_DEFINE_NATIVE_FUNCTION(ShadowRealmPrototype::import_value) +{ + auto specifier = vm.argument(0); + auto export_name = vm.argument(1); + + // 1. Let O be this value. + // 2. Perform ? ValidateShadowRealmObject(O). + auto* object = typed_this_object(global_object); + if (vm.exception()) + return {}; + + // 3. Let specifierString be ? ToString(specifier). + auto specifier_string = TRY_OR_DISCARD(specifier.to_string(global_object)); + + // 4. Let exportNameString be ? ToString(exportName). + auto export_name_string = TRY_OR_DISCARD(export_name.to_string(global_object)); + + // 5. Let callerRealm be the current Realm Record. + auto* caller_realm = vm.current_realm(); + + // 6. Let evalRealm be O.[[ShadowRealm]]. + auto& eval_realm = object->shadow_realm(); + + // 7. Let evalContext be O.[[ExecutionContext]]. + auto& eval_context = object->execution_context(); + + // 8. Return ? ShadowRealmImportValue(specifierString, exportNameString, callerRealm, evalRealm, evalContext). + return TRY_OR_DISCARD(shadow_realm_import_value(global_object, move(specifier_string), move(export_name_string), *caller_realm, eval_realm, eval_context)); +} + } diff --git a/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.h b/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.h index 8aa0e41b07..74786eb20b 100644 --- a/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.h +++ b/Userland/Libraries/LibJS/Runtime/ShadowRealmPrototype.h @@ -21,6 +21,7 @@ public: private: JS_DECLARE_NATIVE_FUNCTION(evaluate); + JS_DECLARE_NATIVE_FUNCTION(import_value); }; } diff --git a/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js new file mode 100644 index 0000000000..d79c9bc1b7 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/ShadowRealm/ShadowRealm.prototype.importValue.js @@ -0,0 +1,27 @@ +describe("normal behavior", () => { + test("length is 2", () => { + expect(ShadowRealm.prototype.importValue).toHaveLength(2); + }); + + test("basic functionality", () => { + // 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"); + let error; + promise.catch(value => { + error = value; + }); + expect(promise).toBeInstanceOf(Promise); + runQueuedPromiseJobs(); + expect(error).toBeInstanceOf(TypeError); + expect(error.message).toBe("Import of 'foo' from './myModule.js' failed"); + }); +}); + +describe("errors", () => { + test("this value must be a ShadowRealm object", () => { + expect(() => { + ShadowRealm.prototype.importValue.call("foo"); + }).toThrowWithMessage(TypeError, "Not an object of type ShadowRealm"); + }); +}); |