diff options
author | Linus Groh <mail@linusgroh.de> | 2021-06-08 21:48:43 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-06-08 23:53:13 +0200 |
commit | 9b35231453ddcf939dd10c40787ee7e5ef5b5fe0 (patch) | |
tree | f3170bc0a0f7761ebaf1581fd995ee53800efb09 /Userland/Libraries | |
parent | e39dd65cf0309a2d505d5bbe1270730cc812ec2e (diff) | |
download | serenity-9b35231453ddcf939dd10c40787ee7e5ef5b5fe0.zip |
LibJS: Implement Proxy.revocable()
Diffstat (limited to 'Userland/Libraries')
5 files changed, 121 insertions, 10 deletions
diff --git a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h index 1026bdc70c..c78153850f 100644 --- a/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h +++ b/Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h @@ -202,6 +202,7 @@ namespace JS { P(preventExtensions) \ P(propertyIsEnumerable) \ P(prototype) \ + P(proxy) \ P(push) \ P(race) \ P(random) \ @@ -212,6 +213,8 @@ namespace JS { P(repeat) \ P(resolve) \ P(reverse) \ + P(revocable) \ + P(revoke) \ P(round) \ P(seal) \ P(set) \ diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp index 7c8b17e704..b02948d33d 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp +++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -12,6 +13,20 @@ namespace JS { +static ProxyObject* proxy_create(GlobalObject& global_object, Value target, Value handler) +{ + auto& vm = global_object.vm(); + if (!target.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyConstructorBadType, "target", target.to_string_without_side_effects()); + return {}; + } + if (!handler.is_object()) { + vm.throw_exception<TypeError>(global_object, ErrorType::ProxyConstructorBadType, "handler", handler.to_string_without_side_effects()); + return {}; + } + return ProxyObject::create(global_object, target.as_object(), handler.as_object()); +} + ProxyConstructor::ProxyConstructor(GlobalObject& global_object) : NativeFunction(vm().names.Proxy, *global_object.function_prototype()) { @@ -22,6 +37,8 @@ void ProxyConstructor::initialize(GlobalObject& global_object) auto& vm = this->vm(); NativeFunction::initialize(global_object); define_property(vm.names.length, Value(2), Attribute::Configurable); + u8 attr = Attribute::Writable | Attribute::Configurable; + define_native_function(vm.names.revocable, revocable, 2, attr); } ProxyConstructor::~ProxyConstructor() @@ -38,18 +55,33 @@ Value ProxyConstructor::call() Value ProxyConstructor::construct(Function&) { auto& vm = this->vm(); - auto target = vm.argument(0); - auto handler = vm.argument(1); + return proxy_create(global_object(), vm.argument(0), vm.argument(1)); +} - if (!target.is_object()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructorBadType, "target", target.to_string_without_side_effects()); - return {}; - } - if (!handler.is_object()) { - vm.throw_exception<TypeError>(global_object(), ErrorType::ProxyConstructorBadType, "handler", handler.to_string_without_side_effects()); +// 28.2.2.1 Proxy.revocable, https://tc39.es/ecma262/multipage/reflection.html#sec-proxy.revocable +JS_DEFINE_NATIVE_FUNCTION(ProxyConstructor::revocable) +{ + auto* proxy = proxy_create(global_object, vm.argument(0), vm.argument(1)); + if (vm.exception()) return {}; - } - return ProxyObject::create(global_object(), target.as_object(), handler.as_object()); + + // 28.2.2.1.1 Proxy Revocation Functions, https://tc39.es/ecma262/multipage/reflection.html#sec-proxy-revocation-functions + auto* revoker = NativeFunction::create(global_object, "", [proxy_handle = make_handle(proxy)](auto&, auto&) -> Value { + auto& proxy = const_cast<ProxyObject&>(*proxy_handle.cell()); + if (proxy.is_revoked()) + return js_undefined(); + // NOTE: The spec wants us to unset [[ProxyTarget]] and [[ProxyHandler]], + // which is their way of revoking the Proxy - this might affect GC-ability, + // but AFAICT not doing that should be ok compatibility-wise. + proxy.revoke(); + return js_undefined(); + }); + revoker->define_property(vm.names.length, Value(0)); + + auto* result = Object::create_empty(global_object); + result->define_property(vm.names.proxy, proxy); + result->define_property(vm.names.revoke, revoker); + return result; } } diff --git a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h index 55d9bf178d..2a3b888f68 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h +++ b/Userland/Libraries/LibJS/Runtime/ProxyConstructor.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org> + * Copyright (c) 2021, Linus Groh <linusg@serenityos.org> * * SPDX-License-Identifier: BSD-2-Clause */ @@ -23,6 +24,8 @@ public: private: virtual bool has_constructor() const override { return true; } + + JS_DECLARE_NATIVE_FUNCTION(revocable); }; } diff --git a/Userland/Libraries/LibJS/Runtime/ProxyObject.h b/Userland/Libraries/LibJS/Runtime/ProxyObject.h index bbb4729e28..fd9cad948d 100644 --- a/Userland/Libraries/LibJS/Runtime/ProxyObject.h +++ b/Userland/Libraries/LibJS/Runtime/ProxyObject.h @@ -39,6 +39,7 @@ public: virtual bool put(const PropertyName& name, Value value, Value receiver) override; virtual bool delete_property(const PropertyName& name) override; + bool is_revoked() const { return m_is_revoked; } void revoke() { m_is_revoked = true; } private: diff --git a/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.revocable.js b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.revocable.js new file mode 100644 index 0000000000..1b8228ef19 --- /dev/null +++ b/Userland/Libraries/LibJS/Tests/builtins/Proxy/Proxy.revocable.js @@ -0,0 +1,72 @@ +test("length is 2", () => { + expect(Proxy.revocable).toHaveLength(2); +}); + +describe("errors", () => { + test("constructor argument count", () => { + expect(() => { + Proxy.revocable(); + }).toThrowWithMessage( + TypeError, + "Expected target argument of Proxy constructor to be object, got undefined" + ); + + expect(() => { + Proxy.revocable({}); + }).toThrowWithMessage( + TypeError, + "Expected handler argument of Proxy constructor to be object, got undefined" + ); + }); + + test("constructor requires objects", () => { + expect(() => { + Proxy.revocable(1, {}); + }).toThrowWithMessage( + TypeError, + "Expected target argument of Proxy constructor to be object, got 1" + ); + + expect(() => { + Proxy.revocable({}, 1); + }).toThrowWithMessage( + TypeError, + "Expected handler argument of Proxy constructor to be object, got 1" + ); + }); +}); + +describe("normal behavior", () => { + test("returns object with 'proxy' and 'revoke' properties", () => { + const revocable = Proxy.revocable( + {}, + { + get() { + return 42; + }, + } + ); + expect(typeof revocable).toBe("object"); + expect(Object.getPrototypeOf(revocable)).toBe(Object.prototype); + expect(revocable.hasOwnProperty("proxy")).toBeTrue(); + expect(revocable.hasOwnProperty("revoke")).toBeTrue(); + expect(typeof revocable.revoke).toBe("function"); + // Can't `instanceof Proxy`, but this should do the trick :^) + expect(revocable.proxy.foo).toBe(42); + }); + + test("'revoke' function revokes Proxy", () => { + const revocable = Proxy.revocable({}, {}); + expect(revocable.proxy.foo).toBeUndefined(); + expect(revocable.revoke()).toBeUndefined(); + expect(() => { + revocable.proxy.foo; + }).toThrowWithMessage(TypeError, "An operation was performed on a revoked Proxy object"); + }); + + test("'revoke' called multiple times is a noop", () => { + const revocable = Proxy.revocable({}, {}); + expect(revocable.revoke()).toBeUndefined(); + expect(revocable.revoke()).toBeUndefined(); + }); +}); |