finalizationRegistry.js (6723B)
1 function checkPropertyDescriptor(obj, property, writable, enumerable, 2 configurable) { 3 let desc = Object.getOwnPropertyDescriptor(obj, property); 4 assertEq(typeof desc, "object"); 5 assertEq(desc.writable, writable); 6 assertEq(desc.enumerable, enumerable); 7 assertEq(desc.configurable, configurable); 8 } 9 10 function assertThrowsTypeError(thunk) { 11 let error; 12 try { 13 thunk(); 14 } catch (e) { 15 error = e; 16 } 17 assertEq(error instanceof TypeError, true); 18 } 19 20 // 3.1 The FinalizationRegistry Constructor 21 assertEq(typeof this.FinalizationRegistry, "function"); 22 23 // 3.1.1 FinalizationRegistry ( cleanupCallback ) 24 assertThrowsTypeError(() => new FinalizationRegistry()); 25 assertThrowsTypeError(() => new FinalizationRegistry(1)); 26 new FinalizationRegistry(x => 0); 27 28 // 3.2 Properties of the FinalizationRegistry Constructor 29 assertEq(Object.getPrototypeOf(FinalizationRegistry), Function.prototype); 30 31 // 3.2.1 FinalizationRegistry.prototype 32 checkPropertyDescriptor(FinalizationRegistry, 'prototype', false, false, false); 33 34 // 3.3 Properties of the FinalizationRegistry Prototype Object 35 let proto = FinalizationRegistry.prototype; 36 assertEq(Object.getPrototypeOf(proto), Object.prototype); 37 38 // 3.3.1 FinalizationRegistry.prototype.constructor 39 assertEq(proto.constructor, FinalizationRegistry); 40 41 // 3.3.2 FinalizationRegistry.prototype.register ( target , holdings [, unregisterToken ] ) 42 assertEq(proto.hasOwnProperty('register'), true); 43 assertEq(typeof proto.register, 'function'); 44 45 // 3.3.3 FinalizationRegistry.prototype.unregister ( unregisterToken ) 46 assertEq(proto.hasOwnProperty('unregister'), true); 47 assertEq(typeof proto.unregister, 'function'); 48 49 // 3.3.4 FinalizationRegistry.prototype.cleanupSome ( [ callback ] ) 50 assertEq(proto.hasOwnProperty('cleanupSome'), true); 51 assertEq(typeof proto.cleanupSome, 'function'); 52 53 // 3.3.5 FinalizationRegistry.prototype [ @@toStringTag ] 54 assertEq(proto[Symbol.toStringTag], "FinalizationRegistry"); 55 checkPropertyDescriptor(proto, Symbol.toStringTag, false, false, true); 56 57 // 3.4 Properties of FinalizationRegistry Instances 58 let registry = new FinalizationRegistry(x => 0); 59 assertEq(Object.getPrototypeOf(registry), proto); 60 assertEq(Object.getOwnPropertyNames(registry).length, 0); 61 62 let heldValues = []; 63 registry = new FinalizationRegistry(value => { 64 heldValues.push(value); 65 }); 66 67 // Test a single target. 68 heldValues = []; 69 registry.register({}, 42); 70 gc(); 71 drainJobQueue(); 72 assertEq(heldValues.length, 1); 73 assertEq(heldValues[0], 42); 74 75 // Test multiple targets. 76 heldValues = []; 77 for (let i = 0; i < 100; i++) { 78 registry.register({}, i); 79 } 80 gc(); 81 drainJobQueue(); 82 assertEq(heldValues.length, 100); 83 heldValues = heldValues.sort((a, b) => a - b); 84 for (let i = 0; i < 100; i++) { 85 assertEq(heldValues[i], i); 86 } 87 88 // Test a single object in multiple registries 89 heldValues = []; 90 let heldValues2 = []; 91 let registry2 = new FinalizationRegistry(value => { 92 heldValues2.push(value); 93 }); 94 { 95 let object = {}; 96 registry.register(object, 1); 97 registry2.register(object, 2); 98 object = null; 99 } 100 gc(); 101 drainJobQueue(); 102 assertEq(heldValues.length, 1); 103 assertEq(heldValues[0], 1); 104 assertEq(heldValues2.length, 1); 105 assertEq(heldValues2[0], 2); 106 107 // Unregister a single target. 108 heldValues = []; 109 let token = {}; 110 registry.register({}, 1, token); 111 registry.unregister(token); 112 gc(); 113 drainJobQueue(); 114 assertEq(heldValues.length, 0); 115 116 // Unregister multiple targets. 117 heldValues = []; 118 let token2 = {}; 119 registry.register({}, 1, token); 120 registry.register({}, 2, token2); 121 registry.register({}, 3, token); 122 registry.register({}, 4, token2); 123 registry.unregister(token); 124 gc(); 125 drainJobQueue(); 126 assertEq(heldValues.length, 2); 127 heldValues = heldValues.sort((a, b) => a - b); 128 assertEq(heldValues[0], 2); 129 assertEq(heldValues[1], 4); 130 131 // Watch object in another global. 132 let other = newGlobal({newCompartment: true}); 133 heldValues = []; 134 registry.register(evalcx('({})', other), 1); 135 gc(); 136 drainJobQueue(); 137 assertEq(heldValues.length, 1); 138 assertEq(heldValues[0], 1); 139 140 // Pass heldValues from another global. 141 let heldValue = evalcx('{}', other); 142 heldValues = []; 143 registry.register({}, heldValue); 144 gc(); 145 drainJobQueue(); 146 assertEq(heldValues.length, 1); 147 assertEq(heldValues[0], heldValue); 148 149 // Pass unregister token from another global. 150 token = evalcx('({})', other); 151 heldValues = []; 152 registry.register({}, 1, token); 153 gc(); 154 drainJobQueue(); 155 assertEq(heldValues.length, 1); 156 assertEq(heldValues[0], 1); 157 heldValues = []; 158 registry.register({}, 1, token); 159 registry.unregister(token); 160 gc(); 161 drainJobQueue(); 162 assertEq(heldValues.length, 0); 163 164 // FinalizationRegistry is designed to be subclassable. 165 class MyRegistry extends FinalizationRegistry { 166 constructor(callback) { 167 super(callback); 168 } 169 } 170 let r2 = new MyRegistry(value => { 171 heldValues.push(value); 172 }); 173 heldValues = []; 174 r2.register({}, 42); 175 gc(); 176 drainJobQueue(); 177 assertEq(heldValues.length, 1); 178 assertEq(heldValues[0], 42); 179 180 // Test cleanupSome. 181 heldValues = []; 182 let r5 = new FinalizationRegistry(v => heldValues.push(v)); 183 r5.register({}, 1); 184 r5.register({}, 2); 185 r5.register({}, 3); 186 gc(); 187 r5.cleanupSome(); 188 assertEq(heldValues.length, 3); 189 heldValues = heldValues.sort((a, b) => a - b); 190 assertEq(heldValues[0], 1); 191 assertEq(heldValues[1], 2); 192 assertEq(heldValues[2], 3); 193 194 // Test trying to call cleanupSome in callback. 195 let r6 = new FinalizationRegistry(x => { 196 r6.cleanupSome(); 197 }); 198 r6.register({}, 1); 199 gc(); 200 drainJobQueue(); 201 202 // Test trying to call cleanupSome in callback with multiple values. 203 let callbackCounter7 = 0; 204 let r7 = new FinalizationRegistry(x => { 205 callbackCounter7++; 206 r7.cleanupSome(); 207 }); 208 r7.register({}, 1); 209 r7.register({}, 2); 210 r7.register({}, 3); 211 r7.register({}, 4); 212 gc(); 213 drainJobQueue(); 214 assertEq(callbackCounter7, 4); 215 216 // Test that targets don't keep the finalization registry alive. 217 let target = {}; 218 registry = new FinalizationRegistry(value => undefined); 219 registry.register(target, 1); 220 let weakRef = new WeakRef(registry); 221 registry = undefined; 222 assertEq(typeof weakRef.deref(), 'object'); 223 drainJobQueue(); 224 gc(); 225 assertEq(weakRef.deref(), undefined); 226 assertEq(typeof target, 'object'); 227 228 // Test that targets don't keep the finalization registry alive when also 229 // used as the unregister token. 230 registry = new FinalizationRegistry(value => undefined); 231 registry.register(target, 1, target); 232 weakRef = new WeakRef(registry); 233 registry = undefined; 234 assertEq(typeof weakRef.deref(), 'object'); 235 drainJobQueue(); 236 gc(); 237 assertEq(weakRef.deref(), undefined); 238 assertEq(typeof target, 'object'); 239 240 // Test that cleanup doesn't happen if the finalization registry dies. 241 heldValues = []; 242 new FinalizationRegistry(value => { 243 heldValues.push(value); 244 }).register({}, 1); 245 gc(); 246 drainJobQueue(); 247 assertEq(heldValues.length, 0);