diff options
Diffstat (limited to 'js/src/jit-test/tests/gc/finalizationRegistry.js')
-rw-r--r-- | js/src/jit-test/tests/gc/finalizationRegistry.js | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/gc/finalizationRegistry.js b/js/src/jit-test/tests/gc/finalizationRegistry.js new file mode 100644 index 0000000000..0450df8457 --- /dev/null +++ b/js/src/jit-test/tests/gc/finalizationRegistry.js @@ -0,0 +1,247 @@ +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); |