mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 02:17:35 +00:00
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.
This commit is contained in:
parent
2ffb30a996
commit
5910a41adb
6 changed files with 177 additions and 0 deletions
|
@ -242,6 +242,7 @@ namespace JS {
|
|||
P(id) \
|
||||
P(ignoreCase) \
|
||||
P(imul) \
|
||||
P(importValue) \
|
||||
P(includes) \
|
||||
P(index) \
|
||||
P(indexOf) \
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
|
||||
private:
|
||||
JS_DECLARE_NATIVE_FUNCTION(evaluate);
|
||||
JS_DECLARE_NATIVE_FUNCTION(import_value);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue