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 });