tor-browser

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

browser_weak_xpcwjs.js (7932B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 // Some basic tests of the lifetime of an XPCWJS with a weak reference.
      7 
      8 // Create a weak reference, with a single-element weak map.
      9 let make_weak_ref = function (obj) {
     10  let m = new WeakMap();
     11  m.set(obj, {});
     12  return m;
     13 };
     14 
     15 // Check to see if a weak reference is dead.
     16 let weak_ref_dead = function (r) {
     17  return !SpecialPowers.nondeterministicGetWeakMapKeys(r).length;
     18 };
     19 
     20 add_task(async function gc_wwjs() {
     21  // This subtest checks that a WJS with only a weak reference to it gets
     22  // cleaned up, if its JS object is garbage, after just a GC.
     23  // For the browser, this probably isn't important, but tests seem to rely
     24  // on it.
     25  const TEST_PREF = "wjs.pref1";
     26  let wjs_weak_ref = null;
     27  let observed_count = 0;
     28 
     29  {
     30    Services.prefs.clearUserPref(TEST_PREF);
     31 
     32    // Create the observer object.
     33    let observer1 = {
     34      QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
     35      observe() {
     36        observed_count += 1;
     37        info(TEST_PREF + " pref observer.");
     38      },
     39    };
     40 
     41    // Register the weak observer.
     42    Services.prefs.addObserver(TEST_PREF, observer1, true);
     43 
     44    // Invoke the observer to make sure it is doing something.
     45    info("Flipping the pref " + TEST_PREF);
     46    Services.prefs.setBoolPref(TEST_PREF, true);
     47    is(observed_count, 1, "Ran observer1 once after first flip.");
     48 
     49    wjs_weak_ref = make_weak_ref(observer1);
     50 
     51    // Exit the scope, making observer1 garbage.
     52  }
     53 
     54  // Run the GC.
     55  info("Running the GC.");
     56  SpecialPowers.forceGC();
     57 
     58  // Flip the pref again to make sure that the observer doesn't run.
     59  info("Flipping the pref " + TEST_PREF);
     60  Services.prefs.setBoolPref(TEST_PREF, false);
     61 
     62  is(observed_count, 1, "After GC, don't run the observer.");
     63  ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed.");
     64 
     65  Services.prefs.clearUserPref(TEST_PREF);
     66 });
     67 
     68 add_task(async function alive_wwjs() {
     69  // This subtest checks that a WJS with only a weak reference should not get
     70  // cleaned up if the underlying JS object is held alive (here, via the
     71  // variable |observer2|).
     72  const TEST_PREF = "wjs.pref2";
     73  let observed_count = 0;
     74 
     75  Services.prefs.clearUserPref(TEST_PREF);
     76  let observer2 = {
     77    QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
     78    observe() {
     79      observed_count += 1;
     80      info(TEST_PREF + " pref observer");
     81    },
     82  };
     83  Services.prefs.addObserver(TEST_PREF, observer2, true);
     84 
     85  Services.prefs.setBoolPref(TEST_PREF, true);
     86  is(observed_count, 1, "Run observer2 once after first flip.");
     87 
     88  await new Promise(resolve =>
     89    SpecialPowers.exactGC(() => {
     90      SpecialPowers.forceCC();
     91      SpecialPowers.forceGC();
     92      SpecialPowers.forceCC();
     93 
     94      Services.prefs.setBoolPref(TEST_PREF, false);
     95 
     96      is(observed_count, 2, "Run observer2 again after second flip.");
     97 
     98      Services.prefs.removeObserver(TEST_PREF, observer2);
     99      Services.prefs.clearUserPref(TEST_PREF);
    100 
    101      resolve();
    102    })
    103  );
    104 });
    105 
    106 add_task(async function cc_wwjs() {
    107  // This subtest checks that a WJS with only a weak reference to it, where the
    108  // underlying JS object is part of a garbage cycle, gets cleaned up after a
    109  // cycle collection. It also checks that things held alive by the JS object
    110  // don't end up in an unlinked state, although that's mostly for fun, because
    111  // it is redundant with checking that the JS object gets cleaned up.
    112  const TEST_PREF = "wjs.pref3";
    113  let wjs_weak_ref = null;
    114  let observed_count = 0;
    115  let canary_count;
    116 
    117  {
    118    Services.prefs.clearUserPref(TEST_PREF);
    119 
    120    // Set up a canary object that lets us detect unlinking.
    121    // (When an nsArrayCC is unlinked, all of the elements are removed.)
    122    // This is needed to distinguish the case where the observer was unlinked
    123    // without removing the weak reference from the case where we did not
    124    // collect the observer at all.
    125    let canary = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
    126    let someString = Cc["@mozilla.org/supports-string;1"].createInstance(
    127      Ci.nsISupportsString
    128    );
    129    someString.data = "canary";
    130    canary.appendElement(someString);
    131    canary.appendElement(someString);
    132    is(canary.Count(), 2, "The canary array should have two elements");
    133 
    134    // Create the observer object.
    135    let observer3 = {
    136      QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
    137      canary,
    138      cycle: new DOMMatrix(),
    139      observe() {
    140        observed_count += 1;
    141        canary_count = this.canary.Count();
    142        info(TEST_PREF + " pref observer. Canary count: " + canary_count);
    143      },
    144    };
    145 
    146    // Set up a cycle between C++ and JS that requires the CC to collect.
    147    // |cycle| is a random WebIDL object that we can set an expando on to
    148    // create a nice clean cycle that doesn't involve any weird XPConnect stuff.
    149    observer3.cycle.backEdge = observer3;
    150 
    151    // Register the weak observer.
    152    Services.prefs.addObserver(TEST_PREF, observer3, true);
    153 
    154    // Invoke the observer to make sure it is doing something.
    155    info("Flipping the pref " + TEST_PREF);
    156    canary_count = -1;
    157    Services.prefs.setBoolPref(TEST_PREF, true);
    158    is(
    159      canary_count,
    160      2,
    161      "Observer ran with expected value while observer3 is alive."
    162    );
    163    is(observed_count, 1, "Ran observer3 once after first flip.");
    164 
    165    wjs_weak_ref = make_weak_ref(observer3);
    166 
    167    // Exit the scope, making observer3 and canary garbage.
    168  }
    169 
    170  // Run the GC. This is necessary to mark observer3 gray so the CC
    171  // might consider it to be garbage. This won't free it because it is held
    172  // alive from C++ (namely the DOMMatrix via its expando).
    173  info("Running the GC.");
    174  SpecialPowers.forceGC();
    175 
    176  // Note: Don't flip the pref here. Doing so will run the observer, which will
    177  // cause it to get marked black again, preventing it from being freed.
    178  // For the same reason, don't call weak_ref_dead(wjs_weak_ref) here.
    179 
    180  // Run the CC. This should detect that the cycle between observer3 and the
    181  // DOMMatrix is garbage, unlinking the DOMMatrix and the canary. Also, the
    182  // weak reference for the WJS for observer3 should get cleared because the
    183  // underlying JS object has been identifed as garbage. You can add logging to
    184  // nsArrayCC's unlink method to see the canary getting unlinked.
    185  info("Running the CC.");
    186  SpecialPowers.forceCC();
    187 
    188  // Flip the pref again to make sure that the observer doesn't run.
    189  info("Flipping the pref " + TEST_PREF);
    190  canary_count = -1;
    191  Services.prefs.setBoolPref(TEST_PREF, false);
    192 
    193  isnot(
    194    canary_count,
    195    0,
    196    "After CC, don't run the observer with an unlinked canary."
    197  );
    198  isnot(
    199    canary_count,
    200    2,
    201    "After CC, don't run the observer after it is garbage."
    202  );
    203  is(canary_count, -1, "After CC, don't run the observer.");
    204  is(observed_count, 1, "After CC, don't run the observer.");
    205 
    206  ok(
    207    !weak_ref_dead(wjs_weak_ref),
    208    "WJS with weak ref shouldn't be freed by the CC."
    209  );
    210 
    211  // Now that the CC has identified observer3 as garbage, running the GC again
    212  // should free it.
    213  info("Running the GC again.");
    214  SpecialPowers.forceGC();
    215 
    216  ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed.");
    217 
    218  info("Flipping the pref " + TEST_PREF);
    219  canary_count = -1;
    220  Services.prefs.setBoolPref(TEST_PREF, true);
    221 
    222  // Note: the original implementation of weak references for WJS fails most of
    223  // the prior canary_count tests, but passes these.
    224  isnot(
    225    canary_count,
    226    0,
    227    "After GC, don't run the observer with an unlinked canary."
    228  );
    229  isnot(
    230    canary_count,
    231    2,
    232    "After GC, don't run the observer after it is garbage."
    233  );
    234  is(canary_count, -1, "After GC, don't run the observer.");
    235  is(observed_count, 1, "After GC, don't run the observer.");
    236 
    237  Services.prefs.clearUserPref(TEST_PREF);
    238 });