Source code

Revision control

Copy as Markdown

Other Tools

function checkPropertyDescriptor(obj, property, writable, enumerable,
configurable) {
let desc = Object.getOwnPropertyDescriptor(obj, property);
assertEq(typeof desc, "object");
assertEq(desc.writable, writable);
assertEq(desc.enumerable, enumerable);
assertEq(desc.configurable, configurable);
}
function assertThrowsTypeError(thunk) {
let error;
try {
thunk();
} catch (e) {
error = e;
}
assertEq(error instanceof TypeError, true);
}
// 3.1 The FinalizationRegistry Constructor
assertEq(typeof this.FinalizationRegistry, "function");
// 3.1.1 FinalizationRegistry ( cleanupCallback )
assertThrowsTypeError(() => new FinalizationRegistry());
assertThrowsTypeError(() => new FinalizationRegistry(1));
new FinalizationRegistry(x => 0);
// 3.2 Properties of the FinalizationRegistry Constructor
assertEq(Object.getPrototypeOf(FinalizationRegistry), Function.prototype);
// 3.2.1 FinalizationRegistry.prototype
checkPropertyDescriptor(FinalizationRegistry, 'prototype', false, false, false);
// 3.3 Properties of the FinalizationRegistry Prototype Object
let proto = FinalizationRegistry.prototype;
assertEq(Object.getPrototypeOf(proto), Object.prototype);
// 3.3.1 FinalizationRegistry.prototype.constructor
assertEq(proto.constructor, FinalizationRegistry);
// 3.3.2 FinalizationRegistry.prototype.register ( target , holdings [, unregisterToken ] )
assertEq(proto.hasOwnProperty('register'), true);
assertEq(typeof proto.register, 'function');
// 3.3.3 FinalizationRegistry.prototype.unregister ( unregisterToken )
assertEq(proto.hasOwnProperty('unregister'), true);
assertEq(typeof proto.unregister, 'function');
// 3.3.4 FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
assertEq(proto.hasOwnProperty('cleanupSome'), true);
assertEq(typeof proto.cleanupSome, 'function');
// 3.3.5 FinalizationRegistry.prototype [ @@toStringTag ]
assertEq(proto[Symbol.toStringTag], "FinalizationRegistry");
checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true);
// 3.4 Properties of FinalizationRegistry Instances
let registry = new FinalizationRegistry(x => 0);
assertEq(Object.getPrototypeOf(registry), proto);
assertEq(Object.getOwnPropertyNames(registry).length, 0);
let heldValues = [];
registry = new FinalizationRegistry(value => {
heldValues.push(value);
});
// Test a single target.
heldValues = [];
registry.register({}, 42);
gc();
drainJobQueue();
assertEq(heldValues.length, 1);
assertEq(heldValues[0], 42);
// Test multiple targets.
heldValues = [];
for (let i = 0; i < 100; i++) {
registry.register({}, i);
}
gc();
drainJobQueue();
assertEq(heldValues.length, 100);
heldValues = heldValues.sort((a, b) => a - b);
for (let i = 0; i < 100; i++) {
assertEq(heldValues[i], i);
}
// Test a single object in multiple registries
heldValues = [];
let heldValues2 = [];
let registry2 = new FinalizationRegistry(value => {
heldValues2.push(value);
});
{
let object = {};
registry.register(object, 1);
registry2.register(object, 2);
object = null;
}
gc();
drainJobQueue();
assertEq(heldValues.length, 1);
assertEq(heldValues[0], 1);
assertEq(heldValues2.length, 1);
assertEq(heldValues2[0], 2);
// Unregister a single target.
heldValues = [];
let token = {};
registry.register({}, 1, token);
registry.unregister(token);
gc();
drainJobQueue();
assertEq(heldValues.length, 0);
// Unregister multiple targets.
heldValues = [];
let token2 = {};
registry.register({}, 1, token);
registry.register({}, 2, token2);
registry.register({}, 3, token);
registry.register({}, 4, token2);
registry.unregister(token);
gc();
drainJobQueue();
assertEq(heldValues.length, 2);
heldValues = heldValues.sort((a, b) => a - b);
assertEq(heldValues[0], 2);
assertEq(heldValues[1], 4);
// Watch object in another global.
let other = newGlobal({newCompartment: true});
heldValues = [];
registry.register(evalcx('({})', other), 1);
gc();
drainJobQueue();
assertEq(heldValues.length, 1);
assertEq(heldValues[0], 1);
// Pass heldValues from another global.
let heldValue = evalcx('{}', other);
heldValues = [];
registry.register({}, heldValue);
gc();
drainJobQueue();
assertEq(heldValues.length, 1);
assertEq(heldValues[0], heldValue);
// Pass unregister token from another global.
token = evalcx('({})', other);
heldValues = [];
registry.register({}, 1, token);
gc();
drainJobQueue();
assertEq(heldValues.length, 1);
assertEq(heldValues[0], 1);
heldValues = [];
registry.register({}, 1, token);
registry.unregister(token);
gc();
drainJobQueue();
assertEq(heldValues.length, 0);
// FinalizationRegistry is designed to be subclassable.
class MyRegistry extends FinalizationRegistry {
constructor(callback) {
super(callback);
}
}
let r2 = new MyRegistry(value => {
heldValues.push(value);
});
heldValues = [];
r2.register({}, 42);
gc();
drainJobQueue();
assertEq(heldValues.length, 1);
assertEq(heldValues[0], 42);
// Test cleanupSome.
heldValues = [];
let r5 = new FinalizationRegistry(v => heldValues.push(v));
r5.register({}, 1);
r5.register({}, 2);
r5.register({}, 3);
gc();
r5.cleanupSome();
assertEq(heldValues.length, 3);
heldValues = heldValues.sort((a, b) => a - b);
assertEq(heldValues[0], 1);
assertEq(heldValues[1], 2);
assertEq(heldValues[2], 3);
// Test trying to call cleanupSome in callback.
let r6 = new FinalizationRegistry(x => {
r6.cleanupSome();
});
r6.register({}, 1);
gc();
drainJobQueue();
// Test trying to call cleanupSome in callback with multiple values.
let callbackCounter7 = 0;
let r7 = new FinalizationRegistry(x => {
callbackCounter7++;
r7.cleanupSome();
});
r7.register({}, 1);
r7.register({}, 2);
r7.register({}, 3);
r7.register({}, 4);
gc();
drainJobQueue();
assertEq(callbackCounter7, 4);
// Test that targets don't keep the finalization registry alive.
let target = {};
registry = new FinalizationRegistry(value => undefined);
registry.register(target, 1);
let weakRef = new WeakRef(registry);
registry = undefined;
assertEq(typeof weakRef.deref(), 'object');
drainJobQueue();
gc();
assertEq(weakRef.deref(), undefined);
assertEq(typeof target, 'object');
// Test that targets don't keep the finalization registry alive when also
// used as the unregister token.
registry = new FinalizationRegistry(value => undefined);
registry.register(target, 1, target);
weakRef = new WeakRef(registry);
registry = undefined;
assertEq(typeof weakRef.deref(), 'object');
drainJobQueue();
gc();
assertEq(weakRef.deref(), undefined);
assertEq(typeof target, 'object');
// Test that cleanup doesn't happen if the finalization registry dies.
heldValues = [];
new FinalizationRegistry(value => {
heldValues.push(value);
}).register({}, 1);
gc();
drainJobQueue();
assertEq(heldValues.length, 0);