1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
6 // Some basic tests of the lifetime of an XPCWJS with a weak reference.
8 // Create a weak reference, with a single-element weak map.
9 let make_weak_ref = function (obj) {
10 let m = new WeakMap();
15 // Check to see if a weak reference is dead.
16 let weak_ref_dead = function (r) {
17 return !SpecialPowers.nondeterministicGetWeakMapKeys(r).length;
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
25 const TEST_PREF = "wjs.pref1";
26 let wjs_weak_ref = null;
27 let observed_count = 0;
30 Services.prefs.clearUserPref(TEST_PREF);
32 // Create the observer object.
34 QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
37 info(TEST_PREF + " pref observer.");
41 // Register the weak observer.
42 Services.prefs.addObserver(TEST_PREF, observer1, true);
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.");
49 wjs_weak_ref = make_weak_ref(observer1);
51 // Exit the scope, making observer1 garbage.
55 info("Running the GC.");
56 SpecialPowers.forceGC();
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);
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.");
65 Services.prefs.clearUserPref(TEST_PREF);
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;
75 Services.prefs.clearUserPref(TEST_PREF);
77 QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
80 info(TEST_PREF + " pref observer");
83 Services.prefs.addObserver(TEST_PREF, observer2, true);
85 Services.prefs.setBoolPref(TEST_PREF, true);
86 is(observed_count, 1, "Run observer2 once after first flip.");
88 await new Promise(resolve =>
89 SpecialPowers.exactGC(() => {
90 SpecialPowers.forceCC();
91 SpecialPowers.forceGC();
92 SpecialPowers.forceCC();
94 Services.prefs.setBoolPref(TEST_PREF, false);
96 is(observed_count, 2, "Run observer2 again after second flip.");
98 Services.prefs.removeObserver(TEST_PREF, observer2);
99 Services.prefs.clearUserPref(TEST_PREF);
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;
118 Services.prefs.clearUserPref(TEST_PREF);
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(
129 someString.data = "canary";
130 canary.appendElement(someString);
131 canary.appendElement(someString);
132 is(canary.Count(), 2, "The canary array should have two elements");
134 // Create the observer object.
136 QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
138 cycle: new DOMMatrix(),
141 canary_count = this.canary.Count();
142 info(TEST_PREF + " pref observer. Canary count: " + canary_count);
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;
151 // Register the weak observer.
152 Services.prefs.addObserver(TEST_PREF, observer3, true);
154 // Invoke the observer to make sure it is doing something.
155 info("Flipping the pref " + TEST_PREF);
157 Services.prefs.setBoolPref(TEST_PREF, true);
161 "Observer ran with expected value while observer3 is alive."
163 is(observed_count, 1, "Ran observer3 once after first flip.");
165 wjs_weak_ref = make_weak_ref(observer3);
167 // Exit the scope, making observer3 and canary garbage.
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();
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.
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();
188 // Flip the pref again to make sure that the observer doesn't run.
189 info("Flipping the pref " + TEST_PREF);
191 Services.prefs.setBoolPref(TEST_PREF, false);
196 "After CC, don't run the observer with an unlinked canary."
201 "After CC, don't run the observer after it is garbage."
203 is(canary_count, -1, "After CC, don't run the observer.");
204 is(observed_count, 1, "After CC, don't run the observer.");
207 !weak_ref_dead(wjs_weak_ref),
208 "WJS with weak ref shouldn't be freed by the CC."
211 // Now that the CC has identified observer3 as garbage, running the GC again
213 info("Running the GC again.");
214 SpecialPowers.forceGC();
216 ok(weak_ref_dead(wjs_weak_ref), "WJS with weak ref should be freed.");
218 info("Flipping the pref " + TEST_PREF);
220 Services.prefs.setBoolPref(TEST_PREF, true);
222 // Note: the original implementation of weak references for WJS fails most of
223 // the prior canary_count tests, but passes these.
227 "After GC, don't run the observer with an unlinked canary."
232 "After GC, don't run the observer after it is garbage."
234 is(canary_count, -1, "After GC, don't run the observer.");
235 is(observed_count, 1, "After GC, don't run the observer.");
237 Services.prefs.clearUserPref(TEST_PREF);