tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gc-has-one-chance-to-call-cleanupCallback-queueMicrotaskMutationObserver.optional.window.js (4095B)


      1 // META: script=/common/gc.js
      2 // META: script=resources/maybe-garbage-collect.js
      3 // ├──> maybeGarbageCollectAndCleanupAsync
      4 // ├──> maybeGarbageCollectAsync
      5 // └──> resolveGarbageCollection
      6 /*---
      7 esid: sec-finalization-registry-target
      8 info: |
      9  FinalizationRegistry ( cleanupCallback )
     10 
     11  FinalizationRegistry.prototype.cleanupSome ( [ callback ] )
     12 
     13  ...
     14  4. If callback is not undefined and IsCallable(callback) is false, throw a TypeError exception.
     15  5. Perform ? CleanupFinalizationRegistry(finalizationRegistry, callback).
     16  6. Return undefined.
     17 
     18  Execution
     19 
     20  At any time, if an object obj is not live, an ECMAScript implementation may perform the following steps atomically:
     21 
     22  1. For each WeakRef ref such that ref.[[Target]] is obj,
     23    a. Set ref.[[Target]] to empty.
     24  2. For each FinalizationRegistry finalizationRegistry such that finalizationRegistry.[[Cells]] contains cell, such that cell.[[Target]] is obj,
     25    a. Set cell.[[Target]] to empty.
     26    b. Optionally, perform ! HostCleanupFinalizationRegistry(finalizationRegistry).
     27 ---*/
     28 
     29 
     30 let cleanupCallback = 0;
     31 let holdings = [];
     32 function cb(holding) {
     33  holdings.push(holding);
     34 }
     35 
     36 let finalizationRegistry = new FinalizationRegistry(function() {
     37  cleanupCallback += 1;
     38 });
     39 
     40 function emptyCells() {
     41  let target = {};
     42  finalizationRegistry.register(target, 'a');
     43 
     44  let prom = maybeGarbageCollectAndCleanupAsync(target);
     45  target = null;
     46 
     47  return prom;
     48 }
     49 
     50 function queueMicrotaskByMutationObserver(callback) {
     51  const textNode = document.createTextNode('');
     52  new MutationObserver(callback).observe(textNode, { characterData: true });
     53  textNode.data = 1;
     54 }
     55 
     56 promise_test(() => {
     57  return (async () => {
     58    assert_implements(
     59      typeof FinalizationRegistry.prototype.cleanupSome === 'function',
     60      'FinalizationRegistry.prototype.cleanupSome is not implemented.'
     61    );
     62 
     63    assert_implements(
     64      typeof MutationObserver === 'function',
     65      'MutationObserver is not implemented.'
     66    );
     67 
     68    let ticks = 0;
     69    await emptyCells();
     70    await queueMicrotaskByMutationObserver(() => ticks++);
     71 
     72    finalizationRegistry.cleanupSome(cb);
     73 
     74    // cleanupSome will be invoked if there are empty cells left. If the
     75    // cleanupCallback already ran, then cb won't be called.
     76    let expectedCalled = cleanupCallback === 1 ? 0 : 1;
     77    // This asserts the registered object was emptied in the previous GC.
     78    assert_equals(holdings.length, expectedCalled, 'cleanupSome callback for the first time');
     79 
     80    // At this point, we can't assert if cleanupCallback was called, because it's
     81    // optional. Although, we can finally assert it's not gonna be called anymore
     82    // for the other executions of the Garbage Collector.
     83    // The chance of having it called only happens right after the
     84    // cell.[[Target]] is set to empty.
     85    assert_true(cleanupCallback >= 0, 'cleanupCallback might be 0');
     86    assert_true(cleanupCallback <= 1, 'cleanupCallback might be 1');
     87 
     88    // Restoring the cleanupCallback variable to 0 will help us asserting the
     89    // finalizationRegistry callback is not called again.
     90    cleanupCallback = 0;
     91 
     92    await maybeGarbageCollectAsync();
     93    await queueMicrotaskByMutationObserver(() => ticks++);
     94 
     95    finalizationRegistry.cleanupSome(cb);
     96 
     97    assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called anymore, no empty cells');
     98    assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #1');
     99 
    100    await maybeGarbageCollectAsync();
    101    await queueMicrotaskByMutationObserver(() => ticks++);
    102 
    103    finalizationRegistry.cleanupSome(cb);
    104 
    105    assert_equals(holdings.length, expectedCalled, 'cleanupSome callback is not called again #2');
    106    assert_equals(cleanupCallback, 0, 'cleanupCallback is not called again #2');
    107    assert_equals(ticks, 3, 'ticks is 3');
    108 
    109    if (holdings.length) {
    110      assert_array_equals(holdings, ['a']);
    111    }
    112 
    113    await maybeGarbageCollectAsync();
    114  })().catch(resolveGarbageCollection);
    115 }, 'cleanupCallback has only one optional chance to be called for a GC that cleans up a registered target.');