1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 06:07:44 +00:00

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 :)
This commit is contained in:
Idan Horowitz 2021-06-15 22:21:24 +03:00 committed by Linus Groh
parent de9fa6622a
commit e1b0719435
7 changed files with 174 additions and 0 deletions

View file

@ -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 };

View file

@ -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") \

View file

@ -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()));
}
}

View file

@ -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);
};
}

View file

@ -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");
});

View file

@ -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");
});

View file

@ -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");
});