test_weakmaps.html (9069B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=668855 5 --> 6 <head> 7 <meta charset="utf-8"> 8 <title>Test Cross-Compartment DOM WeakMaps</title> 9 <script src="/tests/SimpleTest/SimpleTest.js"></script> 10 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 11 <script type="application/javascript"> 12 /** Test for Bug 668855 **/ 13 14 SimpleTest.waitForExplicitFinish(); 15 16 // We wait to run this until the load event because it needs to access an element. 17 function go() { 18 19 /* Create a weak reference, with a single-element weak map. */ 20 let make_weak_ref = function (obj) { 21 let m = new WeakMap; 22 m.set(obj, {}); 23 return m; 24 }; 25 26 /* Check to see if a weak reference is dead. */ 27 let weak_ref_dead = function (r) { 28 return SpecialPowers.nondeterministicGetWeakMapKeys(r).length == 0; 29 } 30 31 /* Deterministically grab an arbitrary DOM element. */ 32 let get_live_dom = function () { 33 let elems = document.getElementsByTagName("a"); 34 return elems[0]; 35 }; 36 37 38 /* Test case from bug 653248, adapted into a standard test. 39 40 This is a dead cycle involving a DOM edge, so the cycle collector can free it. Keys and 41 values reachable only from XPConnect must be marked gray for this to work, and the cycle collector 42 must know the proper structure of the heap. 43 44 */ 45 let make_gray_loop_through = function (key) { 46 let map = new WeakMap; 47 let div = document.createElement("div"); 48 let obj = {m:map, k:key}; 49 div.addEventListener("foo", function() { 50 // Entrain |obj| by referencing it from a closure attached to the 51 // element. The code below doesn't matter (it won't run). Just pull a 52 // reference to obj. 53 obj.k = 1; 54 obj.m = "bar"; 55 }); 56 map.set(key, div); 57 return make_weak_ref(map); 58 }; 59 60 let make_gray_loop_through_shape = function () { 61 let key = Symbol(); 62 let map = new WeakMap; 63 let div = document.createElement("div"); 64 let obj = {m:map}; 65 obj[key] = 1; 66 div.addEventListener("foo", function() { 67 // Entrain |obj| by referencing it from a closure attached to the 68 // element. The code below doesn't matter (it won't run). Just pull a 69 // reference to obj. 70 obj.k = 1; 71 obj.m = "bar"; 72 }); 73 map.set(key, div); 74 return make_weak_ref(map); 75 }; 76 77 let weakref_through_object = make_gray_loop_through({}); 78 let weakref_through_symbol = make_gray_loop_through(Symbol()); 79 let weakref_through_shape = make_gray_loop_through_shape(); 80 81 /* Combinations of live and dead gray maps/keys. */ 82 let basic_weak_ref = null; 83 let basic_map_weak_ref = null; 84 let black_map = new WeakMap; 85 let black_key = {}; 86 87 let basic_unit_tests = function () { 88 let live_dom = get_live_dom(); 89 let dead_dom = document.createElement("div"); 90 let live_map = new WeakMap; 91 let dead_map = new WeakMap; 92 let live_key = {}; 93 let dead_key = {}; 94 95 // put the live/dead maps/keys into the appropriate DOM elements 96 live_dom.basic_unit_tests = {m:live_map, k:live_key}; 97 98 let obj = {m:dead_map, k:dead_key}; 99 // dead_dom.hook = {m:dead_map, k:dead_key}; 100 dead_dom.addEventListener("foo", function() { 101 // The code below doesn't matter (it won't run). Just pull a 102 // reference to obj. 103 obj.m = 1; 104 obj.k = "2"; 105 }); 106 107 // Create a dead value, and a weak ref to it. 108 // The loop keeps dead_dom alive unless the CC is smart enough to kill it. 109 let dead_val = {loop:dead_dom}; 110 basic_weak_ref = make_weak_ref(dead_val); 111 basic_map_weak_ref = make_weak_ref(dead_map); 112 113 // set up the actual entries. most will die. 114 live_map.set(live_key, {my_key:'live_live'}); 115 live_map.set(dead_key, dead_val); 116 live_map.set(black_key, {my_key:'live_black'}); 117 118 dead_map.set(live_key, dead_val); 119 dead_map.set(dead_key, dead_val); 120 dead_map.set(black_key, dead_val); 121 122 black_map.set(live_key, {my_key:'black_live'}); 123 black_map.set(dead_key, dead_val); 124 black_map.set(black_key, {my_key:'black_black'}); 125 126 }; 127 128 basic_unit_tests(); 129 130 131 let check_basic_unit = function () { 132 let live_dom = get_live_dom(); 133 let live_map = live_dom.basic_unit_tests.m; 134 let live_key = live_dom.basic_unit_tests.k; 135 136 // check the dead elements 137 ok(weak_ref_dead(basic_weak_ref), "Dead value was kept alive."); 138 ok(weak_ref_dead(basic_map_weak_ref), "Dead map was kept alive."); 139 140 // check the live gray map 141 is(live_map.get(live_key).my_key, 'live_live', 142 "Live key should have the same value in live map."); 143 is(live_map.get(black_key).my_key, 'live_black', 144 "Black key should have the same value in live map."); 145 is(SpecialPowers.nondeterministicGetWeakMapKeys(live_map).length, 2, 146 "Live map should have two entries."); 147 148 // check the live black map 149 is(black_map.get(live_key).my_key, 'black_live', 150 "Live key should have the same value in black map."); 151 is(black_map.get(black_key).my_key, 'black_black', 152 "Black key should have the same value in black map."); 153 is(SpecialPowers.nondeterministicGetWeakMapKeys(black_map).length, 2, 154 "Black map should have two entries."); 155 156 }; 157 158 159 /* live gray chained weak map entries, involving the cycle collector. */ 160 let chainm = new WeakMap; 161 let num_chains = 5; 162 163 let nested_cc_maps = function () { 164 let dom = get_live_dom(); 165 for(let i = 0; i < num_chains; i++) { 166 let k = {count:i}; 167 dom.key = k; 168 dom0 = document.createElement("div"); 169 chainm.set(k, {d:dom0}); 170 dom = document.createElement("div"); 171 dom0.appendChild(dom); 172 }; 173 }; 174 175 let check_nested_cc_maps = function () { 176 let dom = get_live_dom(); 177 let all_ok = true; 178 for(let i = 0; i < num_chains; i++) { 179 let k = dom.key; 180 all_ok = all_ok && k.count == i; 181 dom = chainm.get(k).d.firstChild; 182 }; 183 ok(all_ok, "Count was invalid on a key in chained weak map entries."); 184 }; 185 186 nested_cc_maps(); 187 188 189 /* black weak map, chained garbage cycle involving DOM */ 190 let garbage_map = new WeakMap; 191 192 let chained_garbage_maps = function () { 193 let dom0 = document.createElement("div"); 194 let dom = dom0; 195 for(let i = 0; i < num_chains; i++) { 196 let k = {}; 197 dom.key = k; 198 let new_dom = document.createElement("div"); 199 garbage_map.set(k, {val_child:new_dom}); 200 dom = document.createElement("div"); 201 new_dom.appendChild(dom); 202 }; 203 // tie the knot 204 dom.appendChild(dom0); 205 }; 206 207 chained_garbage_maps(); 208 209 210 /* black weak map, chained garbage cycle involving DOM, XPCWN keys */ 211 let wn_garbage_map = new WeakMap; 212 213 let wn_chained_garbage_maps = function () { 214 let dom0 = document.createElement("div"); 215 let dom = dom0; 216 for(let i = 0; i < num_chains; i++) { 217 let new_dom = document.createElement("div"); 218 wn_garbage_map.set(dom, {wn_val_child:new_dom}); 219 dom = document.createElement("div"); 220 new_dom.appendChild(dom); 221 }; 222 // tie the knot 223 dom.appendChild(dom0); 224 }; 225 226 wn_chained_garbage_maps(); 227 228 229 /* The cycle collector shouldn't remove a live wrapped native key. */ 230 231 let wn_live_map = new WeakMap; 232 233 let make_live_map = function () { 234 let live = get_live_dom(); 235 wn_live_map.set(live, {}); 236 ok(wn_live_map.has(get_live_dom()), "Live map should have live DOM node before GC."); 237 } 238 239 make_live_map(); 240 241 // We're out of ideas for unpreservable natives, now that just about 242 // everything is on webidl, so just don't test those. 243 244 SpecialPowers.forceGC(); 245 246 // GC on its own can't collect these cycles. 247 ok(!weak_ref_dead(weakref_through_object), 248 "Garbage gray cycle through object should be alive."); 249 ok(!weak_ref_dead(weakref_through_symbol), 250 "Garbage gray cycle through symbol should be alive."); 251 ok(!weak_ref_dead(weakref_through_shape), 252 "Garbage gray cycle through shape should be alive."); 253 254 SpecialPowers.forceCC(); 255 SpecialPowers.forceGC(); 256 257 ok(weak_ref_dead(weakref_through_object), 258 "Garbage gray cycle through object should be collected."); 259 ok(weak_ref_dead(weakref_through_symbol), 260 "Garbage gray cycle through symbol should be collected."); 261 ok(weak_ref_dead(weakref_through_shape), 262 "Garbage gray cycle through shape should be collected."); 263 264 check_nested_cc_maps(); 265 266 is(SpecialPowers.nondeterministicGetWeakMapKeys(garbage_map).length, 0, 267 "Chained garbage weak map entries should not leak."); 268 269 check_basic_unit(); 270 271 // fixed by Bug 680937 272 is(SpecialPowers.nondeterministicGetWeakMapKeys(wn_garbage_map).length, 0, 273 "Chained garbage WN weak map entries should not leak."); 274 275 // fixed by Bug 680937 276 is(SpecialPowers.nondeterministicGetWeakMapKeys(wn_live_map).length, 1, 277 "Live weak map wrapped native key should not be removed."); 278 279 ok(wn_live_map.has(get_live_dom()), "Live map should have live dom."); 280 281 SimpleTest.finish(); 282 } 283 </script> 284 </head> 285 <div></div> 286 <div id="mydivname"></div> 287 <body onload="go()";> 288 <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=668855" target="_blank">Mozilla Bug 668855</a> 289 <p id="display"></p> 290 </body> 291 </html>