summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/gc/finalizationRegistry.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/gc/finalizationRegistry.js')
-rw-r--r--js/src/jit-test/tests/gc/finalizationRegistry.js247
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);