LibJS: Remove FinalizationRegistry cleanupSome

The cleanupSome proposal was withdrawn, and other engines no longer
expose this method by default. Stop installing it on
FinalizationRegistry.prototype while keeping the internal cleanup path
used for queued cleanup jobs.

Add a test-js helper for invoking that internal cleanup path directly.
Move the FinalizationRegistry cleanup coverage onto it so callbacks
unregistering later dead records remain covered without exposing the
withdrawn API to JavaScript programs.

Fixes #2228
This commit is contained in:
Andreas Kling 2026-05-14 23:14:31 +02:00 committed by Andreas Kling
parent 209579c964
commit 8ff0577dea
5 changed files with 30 additions and 32 deletions

View file

@ -74,7 +74,6 @@ namespace JS {
P(ceil) \
P(charAt) \
P(charCodeAt) \
P(cleanupSome) \
P(clear) \
P(clz32) \
P(codePointAt) \

View file

@ -23,7 +23,6 @@ void FinalizationRegistryPrototype::initialize(Realm& realm)
Base::initialize(realm);
u8 attr = Attribute::Writable | Attribute::Configurable;
define_native_function(realm, vm.names.cleanupSome, cleanup_some, 0, attr);
define_native_function(realm, vm.names.register_, register_, 2, attr);
define_native_function(realm, vm.names.unregister, unregister, 1, attr);
@ -31,28 +30,6 @@ void FinalizationRegistryPrototype::initialize(Realm& realm)
define_direct_property(vm.well_known_symbol_to_string_tag(), PrimitiveString::create(vm, vm.names.FinalizationRegistry.as_string()), Attribute::Configurable);
}
// @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 callback = vm.argument(0);
// 1. Let finalizationRegistry be the this value.
// 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]).
auto finalization_registry = TRY(typed_this_object(vm));
// 3. If callback is present and IsCallable(callback) is false, throw a TypeError exception.
if (vm.argument_count() > 0 && !callback.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, callback);
// IMPLEMENTATION DEFINED: The specification for this function hasn't been updated to accommodate for JobCallback records.
// This just follows how the constructor immediately converts the callback to a JobCallback using HostMakeJobCallback.
// 4. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
TRY(finalization_registry->cleanup(callback.is_undefined() ? GC::Ptr<JobCallback> {} : vm.host_make_job_callback(callback.as_function())));
// 5. Return undefined.
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_)
{

View file

@ -22,7 +22,6 @@ public:
private:
explicit FinalizationRegistryPrototype(Realm&);
JS_DECLARE_NATIVE_FUNCTION(cleanup_some);
JS_DECLARE_NATIVE_FUNCTION(register_);
JS_DECLARE_NATIVE_FUNCTION(unregister);
};

View file

@ -1,5 +1,5 @@
test("length is 0", () => {
expect(FinalizationRegistry.prototype.cleanupSome).toHaveLength(0);
test("cleanupSome is not exposed", () => {
expect(FinalizationRegistry.prototype.cleanupSome).toBeUndefined();
});
function registerInDifferentScope(registry) {
@ -16,7 +16,7 @@ test.xfail("basic functionality", () => {
count++;
};
registry.cleanupSome(increment);
cleanupFinalizationRegistry(registry, increment);
expect(count).toBe(0);
@ -24,7 +24,7 @@ test.xfail("basic functionality", () => {
markAsGarbage("target");
gc();
registry.cleanupSome(increment);
cleanupFinalizationRegistry(registry, increment);
expect(count).toBe(1);
});
@ -45,16 +45,20 @@ test("callback can unregister the next record after the current record dies", ()
markAsGarbage("__finalizationRegistrySecond");
gc();
registry.cleanupSome();
cleanupFinalizationRegistry(registry);
expect(heldValues).toEqual(["first"]);
expect(registry.unregister(token2)).toBeFalse();
});
test("errors", () => {
test("cleanup helper errors", () => {
var registry = new FinalizationRegistry(() => {});
expect(() => {
registry.cleanupSome(5);
cleanupFinalizationRegistry(registry, 5);
}).toThrowWithMessage(TypeError, "is not a function");
expect(() => {
cleanupFinalizationRegistry({});
}).toThrowWithMessage(TypeError, "Not an object of type FinalizationRegistry");
});

View file

@ -9,6 +9,7 @@
#include <AK/StringView.h>
#include <LibJS/Runtime/ArrayBuffer.h>
#include <LibJS/Runtime/Date.h>
#include <LibJS/Runtime/FinalizationRegistry.h>
#include <LibJS/Runtime/TypedArray.h>
#include <LibJS/Runtime/ValueInlines.h>
#include <LibTest/JavaScriptTestRunner.h>
@ -98,6 +99,24 @@ TESTJS_GLOBAL_FUNCTION(mark_as_garbage, markAsGarbage)
return JS::js_undefined();
}
TESTJS_GLOBAL_FUNCTION(cleanup_finalization_registry, cleanupFinalizationRegistry)
{
auto finalization_registry = vm.argument(0).as_if<JS::FinalizationRegistry>();
if (!finalization_registry)
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAnObjectOfType, "FinalizationRegistry");
auto callback = vm.argument(1);
if (vm.argument_count() > 1 && !callback.is_function())
return vm.throw_completion<JS::TypeError>(JS::ErrorType::NotAFunction, callback);
GC::Ptr<JS::JobCallback> cleanup_callback;
if (!callback.is_undefined())
cleanup_callback = vm.host_make_job_callback(callback.as_function());
TRY(finalization_registry->cleanup(cleanup_callback));
return JS::js_undefined();
}
TESTJS_GLOBAL_FUNCTION(detach_array_buffer, detachArrayBuffer)
{
auto array_buffer = vm.argument(0).as_if<JS::ArrayBuffer>();