/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Some basic tests of the lifetime of an XPCWJS with a weak reference. // Create a weak reference, with a single-element weak map. let make_weak_ref = function (obj) { let m = new WeakMap(); m.set(obj, {}); return m; }; // Check to see if a weak reference is dead. let weak_ref_dead = function (r) { return !SpecialPowers.nondeterministicGetWeakMapKeys(r).length; }; add_task(async function gc_wwjs() { // This subtest checks that a WJS with only a weak reference to it gets // cleaned up, if its JS object is garbage, after just a GC. // For the browser, this probably isn't important, but tests seem to rely // on it. const TEST_PREF = "wjs.pref1"; let wjs_weak_ref = null; let observed_count = 0; { Services.prefs.clearUserPref(TEST_PREF); // Create the observer object. let observer1 = { QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), observe() { observed_count += 1; info(TEST_PREF + " pref observer."); }, }; // Register the weak observer. Services.prefs.addObserver(TEST_PREF, observer1, true); // Invoke the observer to make sure it is doing something. info("Flipping the pref " + TEST_PREF); Services.prefs.setBoolPref(TEST_PREF, true); is(observed_count, 1, "Ran observer1 once after first flip."); wjs_weak_ref = make_weak_ref(observer1); // Exit the scope, making observer1 garbage. } // Run the GC. info("Running the GC."); SpecialPowers.forceGC(); // Flip the pref again to make sure that the observer doesn't run. info("Flipping the pref " + TEST_PREF); Services.prefs.setBoolPref(TEST_PREF, false); is(observed_count, 1, "After GC, don't run the observer."); ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed."); Services.prefs.clearUserPref(TEST_PREF); }); add_task(async function alive_wwjs() { // This subtest checks that a WJS with only a weak reference should not get // cleaned up if the underlying JS object is held alive (here, via the // variable |observer2|). const TEST_PREF = "wjs.pref2"; let observed_count = 0; Services.prefs.clearUserPref(TEST_PREF); let observer2 = { QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), observe() { observed_count += 1; info(TEST_PREF + " pref observer"); }, }; Services.prefs.addObserver(TEST_PREF, observer2, true); Services.prefs.setBoolPref(TEST_PREF, true); is(observed_count, 1, "Run observer2 once after first flip."); await new Promise(resolve => SpecialPowers.exactGC(() => { SpecialPowers.forceCC(); SpecialPowers.forceGC(); SpecialPowers.forceCC(); Services.prefs.setBoolPref(TEST_PREF, false); is(observed_count, 2, "Run observer2 again after second flip."); Services.prefs.removeObserver(TEST_PREF, observer2); Services.prefs.clearUserPref(TEST_PREF); resolve(); }) ); }); add_task(async function cc_wwjs() { // This subtest checks that a WJS with only a weak reference to it, where the // underlying JS object is part of a garbage cycle, gets cleaned up after a // cycle collection. It also checks that things held alive by the JS object // don't end up in an unlinked state, although that's mostly for fun, because // it is redundant with checking that the JS object gets cleaned up. const TEST_PREF = "wjs.pref3"; let wjs_weak_ref = null; let observed_count = 0; let canary_count; { Services.prefs.clearUserPref(TEST_PREF); // Set up a canary object that lets us detect unlinking. // (When an nsArrayCC is unlinked, all of the elements are removed.) // This is needed to distinguish the case where the observer was unlinked // without removing the weak reference from the case where we did not // collect the observer at all. let canary = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); let someString = Cc["@mozilla.org/supports-string;1"].createInstance( Ci.nsISupportsString ); someString.data = "canary"; canary.appendElement(someString); canary.appendElement(someString); is(canary.Count(), 2, "The canary array should have two elements"); // Create the observer object. let observer3 = { QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]), canary, cycle: new DOMMatrix(), observe() { observed_count += 1; canary_count = this.canary.Count(); info(TEST_PREF + " pref observer. Canary count: " + canary_count); }, }; // Set up a cycle between C++ and JS that requires the CC to collect. // |cycle| is a random WebIDL object that we can set an expando on to // create a nice clean cycle that doesn't involve any weird XPConnect stuff. observer3.cycle.backEdge = observer3; // Register the weak observer. Services.prefs.addObserver(TEST_PREF, observer3, true); // Invoke the observer to make sure it is doing something. info("Flipping the pref " + TEST_PREF); canary_count = -1; Services.prefs.setBoolPref(TEST_PREF, true); is( canary_count, 2, "Observer ran with expected value while observer3 is alive." ); is(observed_count, 1, "Ran observer3 once after first flip."); wjs_weak_ref = make_weak_ref(observer3); // Exit the scope, making observer3 and canary garbage. } // Run the GC. This is necessary to mark observer3 gray so the CC // might consider it to be garbage. This won't free it because it is held // alive from C++ (namely the DOMMatrix via its expando). info("Running the GC."); SpecialPowers.forceGC(); // Note: Don't flip the pref here. Doing so will run the observer, which will // cause it to get marked black again, preventing it from being freed. // For the same reason, don't call weak_ref_dead(wjs_weak_ref) here. // Run the CC. This should detect that the cycle between observer3 and the // DOMMatrix is garbage, unlinking the DOMMatrix and the canary. Also, the // weak reference for the WJS for observer3 should get cleared because the // underlying JS object has been identifed as garbage. You can add logging to // nsArrayCC's unlink method to see the canary getting unlinked. info("Running the CC."); SpecialPowers.forceCC(); // Flip the pref again to make sure that the observer doesn't run. info("Flipping the pref " + TEST_PREF); canary_count = -1; Services.prefs.setBoolPref(TEST_PREF, false); isnot( canary_count, 0, "After CC, don't run the observer with an unlinked canary." ); isnot( canary_count, 2, "After CC, don't run the observer after it is garbage." ); is(canary_count, -1, "After CC, don't run the observer."); is(observed_count, 1, "After CC, don't run the observer."); ok( !weak_ref_dead(wjs_weak_ref), "WJS with weak ref shouldn't be freed by the CC." ); // Now that the CC has identified observer3 as garbage, running the GC again // should free it. info("Running the GC again."); SpecialPowers.forceGC(); ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed."); info("Flipping the pref " + TEST_PREF); canary_count = -1; Services.prefs.setBoolPref(TEST_PREF, true); // Note: the original implementation of weak references for WJS fails most of // the prior canary_count tests, but passes these. isnot( canary_count, 0, "After GC, don't run the observer with an unlinked canary." ); isnot( canary_count, 2, "After GC, don't run the observer after it is garbage." ); is(canary_count, -1, "After GC, don't run the observer."); is(observed_count, 1, "After GC, don't run the observer."); Services.prefs.clearUserPref(TEST_PREF); });