Explorar o código

LibJS: Add all of the FinalizationRegistry.prototype methods

More specifically: cleanupSome, register & unregister.

FinalizationRegistery.prototype.cleanupSome is actually still a stage 2
proposal, but since test262 test cases already exist for it, i decided
to go for it :)
Idan Horowitz %!s(int64=4) %!d(string=hai) anos
pai
achega
e1b0719435

+ 3 - 0
Userland/Libraries/LibJS/Runtime/CommonPropertyNames.h

@@ -75,6 +75,7 @@ namespace JS {
     P(ceil)                                  \
     P(charAt)                                \
     P(charCodeAt)                            \
+    P(cleanupSome)                           \
     P(clear)                                 \
     P(clz32)                                 \
     P(concat)                                \
@@ -310,6 +311,7 @@ namespace JS {
     P(undefined)                             \
     P(unescape)                              \
     P(unicode)                               \
+    P(unregister)                            \
     P(unshift)                               \
     P(value)                                 \
     P(valueOf)                               \
@@ -321,6 +323,7 @@ struct CommonPropertyNames {
     PropertyName catch_ { "catch", PropertyName::StringMayBeNumber::No };
     PropertyName delete_ { "delete", PropertyName::StringMayBeNumber::No };
     PropertyName for_ { "for", PropertyName::StringMayBeNumber::No };
+    PropertyName register_ { "register", PropertyName::StringMayBeNumber::No };
     PropertyName return_ { "return", PropertyName::StringMayBeNumber::No };
     PropertyName throw_ { "throw", PropertyName::StringMayBeNumber::No };
 #define __ENUMERATE(x) PropertyName x { #x, PropertyName::StringMayBeNumber::No };

+ 1 - 0
Userland/Libraries/LibJS/Runtime/ErrorTypes.h

@@ -26,6 +26,7 @@
     M(DescWriteNonWritable, "Cannot write to non-writable property '{}'")                                                               \
     M(DetachedArrayBuffer, "ArrayBuffer is detached")                                                                                   \
     M(DivisionByZero, "Division by zero")                                                                                               \
+    M(FinalizationRegistrySameTargetAndValue, "Target and held value must not be the same")                                             \
     M(GetCapabilitiesExecutorCalledMultipleTimes, "GetCapabilitiesExecutor was called multiple times")                                  \
     M(InOperatorWithObject, "'in' operator must be used on an object")                                                                  \
     M(InstanceOfOperatorBadPrototype, "'prototype' property of {} is not an object")                                                    \

+ 69 - 0
Userland/Libraries/LibJS/Runtime/FinalizationRegistryPrototype.cpp

@@ -17,6 +17,11 @@ void FinalizationRegistryPrototype::initialize(GlobalObject& global_object)
 {
     auto& vm = this->vm();
     Object::initialize(global_object);
+    u8 attr = Attribute::Writable | Attribute::Configurable;
+
+    define_native_function(vm.names.cleanupSome, cleanup_some, 0, attr);
+    define_native_function(vm.names.register_, register_, 2, attr);
+    define_native_function(vm.names.unregister, unregister, 1, attr);
 
     // 26.2.3.4 FinalizationRegistry.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-finalization-registry.prototype-@@tostringtag
     define_property(vm.well_known_symbol_to_string_tag(), js_string(global_object.heap(), vm.names.FinalizationRegistry.as_string()), Attribute::Configurable);
@@ -38,4 +43,68 @@ FinalizationRegistry* FinalizationRegistryPrototype::typed_this(VM& vm, GlobalOb
     return static_cast<FinalizationRegistry*>(this_object);
 }
 
+// @STAGE 2@ FinalizationRegistry.prototype.cleanupSome ( [ callback ] ), https://github.com/tc39/proposal-cleanup-some/blob/master/spec/finalization-registry.html
+JS_DEFINE_NATIVE_FUNCTION(FinalizationRegistryPrototype::cleanup_some)
+{
+    auto* finalization_registry = typed_this(vm, global_object);
+    if (!finalization_registry)
+        return {};
+
+    auto callback = vm.argument(0);
+    if (vm.argument_count() > 0 && !callback.is_function()) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotAFunction, callback.to_string_without_side_effects());
+        return {};
+    }
+
+    finalization_registry->cleanup(callback.is_undefined() ? nullptr : &callback.as_function());
+
+    return js_undefined();
+}
+
+// 26.2.3.2 FinalizationRegistry.prototype.register ( target, heldValue [ , unregisterToken ] ), https://tc39.es/ecma262/#sec-finalization-registry.prototype.register
+JS_DEFINE_NATIVE_FUNCTION(FinalizationRegistryPrototype::register_)
+{
+    auto* finalization_registry = typed_this(vm, global_object);
+    if (!finalization_registry)
+        return {};
+
+    auto target = vm.argument(0);
+    if (!target.is_object()) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, target.to_string_without_side_effects());
+        return {};
+    }
+
+    auto held_value = vm.argument(1);
+    if (same_value(target, held_value)) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::FinalizationRegistrySameTargetAndValue);
+        return {};
+    }
+
+    auto unregister_token = vm.argument(2);
+    if (!unregister_token.is_object() && !unregister_token.is_undefined()) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, unregister_token.to_string_without_side_effects());
+        return {};
+    }
+
+    finalization_registry->add_finalization_record(target.as_cell(), held_value, unregister_token.is_undefined() ? nullptr : &unregister_token.as_object());
+
+    return js_undefined();
+}
+
+// 26.2.3.3 FinalizationRegistry.prototype.unregister ( unregisterToken ), https://tc39.es/ecma262/#sec-finalization-registry.prototype.unregister
+JS_DEFINE_NATIVE_FUNCTION(FinalizationRegistryPrototype::unregister)
+{
+    auto* finalization_registry = typed_this(vm, global_object);
+    if (!finalization_registry)
+        return {};
+
+    auto unregister_token = vm.argument(0);
+    if (!unregister_token.is_object()) {
+        vm.throw_exception<TypeError>(global_object, ErrorType::NotAnObject, unregister_token.to_string_without_side_effects());
+        return {};
+    }
+
+    return Value(finalization_registry->remove_by_token(unregister_token.as_object()));
+}
+
 }

+ 6 - 0
Userland/Libraries/LibJS/Runtime/FinalizationRegistryPrototype.h

@@ -20,4 +20,10 @@ public:
 
 private:
     static FinalizationRegistry* typed_this(VM&, GlobalObject&);
+
+    JS_DECLARE_NATIVE_FUNCTION(cleanup_some);
+    JS_DECLARE_NATIVE_FUNCTION(register_);
+    JS_DECLARE_NATIVE_FUNCTION(unregister);
+};
+
 }

+ 35 - 0
Userland/Libraries/LibJS/Tests/builtins/FinalizationRegistry/FinalizationRegistry.prototype.cleanupSome.js

@@ -0,0 +1,35 @@
+test("length is 0", () => {
+    expect(FinalizationRegistry.prototype.cleanupSome).toHaveLength(0);
+});
+
+function registerInDifferentScope(registry) {
+    registry.register({}, {});
+}
+
+test("basic functionality", () => {
+    var registry = new FinalizationRegistry(() => {});
+
+    var count = 0;
+    var increment = () => {
+        count++;
+    };
+
+    registry.cleanupSome(increment);
+
+    expect(count).toBe(0);
+
+    registerInDifferentScope(registry);
+    gc();
+
+    registry.cleanupSome(increment);
+
+    expect(count).toBe(1);
+});
+
+test("errors", () => {
+    var registry = new FinalizationRegistry(() => {});
+
+    expect(() => {
+        registry.cleanupSome(5);
+    }).toThrowWithMessage(TypeError, "is not a function");
+});

+ 35 - 0
Userland/Libraries/LibJS/Tests/builtins/FinalizationRegistry/FinalizationRegistry.prototype.register.js

@@ -0,0 +1,35 @@
+test("length is 2", () => {
+    expect(FinalizationRegistry.prototype.register).toHaveLength(2);
+});
+
+test("basic functionality", () => {
+    var registry = new FinalizationRegistry(() => {});
+
+    var target1 = {};
+    var heldValue1 = {};
+
+    registry.register(target1, heldValue1);
+
+    var target2 = {};
+    var heldValue2 = {};
+    var token = {};
+
+    registry.register(target2, heldValue2, token);
+});
+
+test("errors", () => {
+    var registry = new FinalizationRegistry(() => {});
+
+    expect(() => {
+        registry.register(5, {});
+    }).toThrowWithMessage(TypeError, "is not an object");
+
+    expect(() => {
+        var a = {};
+        registry.register(a, a);
+    }).toThrowWithMessage(TypeError, "Target and held value must not be the same");
+
+    expect(() => {
+        registry.register({}, {}, 5);
+    }).toThrowWithMessage(TypeError, "is not an object");
+});

+ 25 - 0
Userland/Libraries/LibJS/Tests/builtins/FinalizationRegistry/FinalizationRegistry.prototype.unregister.js

@@ -0,0 +1,25 @@
+test("length is 2", () => {
+    expect(FinalizationRegistry.prototype.unregister).toHaveLength(1);
+});
+
+test("basic functionality", () => {
+    var registry = new FinalizationRegistry(() => {});
+
+    var target = {};
+    var heldValue = {};
+    var token = {};
+
+    registry.register(target, heldValue, token);
+
+    expect(registry.unregister({})).toBe(false);
+    expect(registry.unregister(token)).toBe(true);
+    expect(registry.unregister(token)).toBe(false);
+});
+
+test("errors", () => {
+    var registry = new FinalizationRegistry(() => {});
+
+    expect(() => {
+        registry.unregister(5);
+    }).toThrowWithMessage(TypeError, "is not an object");
+});