summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/tests/browser/browser_weak_xpcwjs.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/xpconnect/tests/browser/browser_weak_xpcwjs.js')
-rw-r--r--js/xpconnect/tests/browser/browser_weak_xpcwjs.js238
1 files changed, 238 insertions, 0 deletions
diff --git a/js/xpconnect/tests/browser/browser_weak_xpcwjs.js b/js/xpconnect/tests/browser/browser_weak_xpcwjs.js
new file mode 100644
index 0000000000..b8c8c2f85d
--- /dev/null
+++ b/js/xpconnect/tests/browser/browser_weak_xpcwjs.js
@@ -0,0 +1,238 @@
+/* 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);
+});