diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /js/xpconnect/tests/browser/browser_weak_xpcwjs.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/xpconnect/tests/browser/browser_weak_xpcwjs.js | 238 |
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); +}); |