1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/xpconnect/tests/chrome/test_weakmaps.xul Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,272 @@ 1.4 +<?xml version="1.0"?> 1.5 +<?xml-stylesheet type="text/css" href="chrome://global/skin"?> 1.6 +<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?> 1.7 +<!-- 1.8 +https://bugzilla.mozilla.org/show_bug.cgi?id=668855 1.9 +--> 1.10 +<window title="Mozilla Bug " 1.11 + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 1.12 + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> 1.13 + 1.14 + <!-- test results are displayed in the html:body --> 1.15 + <body xmlns="http://www.w3.org/1999/xhtml"> 1.16 + <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=" 1.17 + target="_blank">Mozilla Bug 668855</a> 1.18 + </body> 1.19 + 1.20 + <!-- test code goes here --> 1.21 + <script type="application/javascript"> 1.22 + <![CDATA[ 1.23 + /** Test for Bug 668855 **/ 1.24 + 1.25 + let Cu = Components.utils; 1.26 + let Ci = Components.interfaces; 1.27 + 1.28 + /* Create a weak reference, with a single-element weak map. */ 1.29 + let make_weak_ref = function (obj) { 1.30 + let m = new WeakMap; 1.31 + m.set(obj, {}); 1.32 + return m; 1.33 + }; 1.34 + 1.35 + /* Check to see if a weak reference is dead. */ 1.36 + let weak_ref_dead = function (r) { 1.37 + return Cu.nondeterministicGetWeakMapKeys(r).length == 0; 1.38 + } 1.39 + 1.40 + /* Deterministically grab an arbitrary DOM element. */ 1.41 + let get_live_dom = function () { 1.42 + let elems = document.getElementsByTagName("a"); 1.43 + return elems[0]; 1.44 + }; 1.45 + 1.46 + 1.47 + /* Test case from bug 653248, adapted into a standard test. 1.48 + 1.49 + This is a dead cycle involving a DOM edge, so the cycle collector can free it. Keys and 1.50 + values reachable only from XPConnect must be marked gray for this to work, and the cycle collector 1.51 + must know the proper structure of the heap. 1.52 + 1.53 + */ 1.54 + let make_gray_loop = function () { 1.55 + let map = new WeakMap; 1.56 + let div = document.createElement("div"); 1.57 + let key = {}; 1.58 + div.setUserData("entrain", {m:map, k:key}, null); 1.59 + //div.entrain = {m:map, k:key}; This is not sufficient to cause a leak in Fx9 1.60 + map.set(key, div); 1.61 + return make_weak_ref(map); 1.62 + }; 1.63 + 1.64 + let weakref = make_gray_loop(); 1.65 + 1.66 + 1.67 + /* Combinations of live and dead gray maps/keys. */ 1.68 + let basic_weak_ref = null; 1.69 + let basic_map_weak_ref = null; 1.70 + let black_map = new WeakMap; 1.71 + let black_key = {}; 1.72 + 1.73 + let basic_unit_tests = function () { 1.74 + let live_dom = get_live_dom(); 1.75 + let dead_dom = document.createElement("div"); 1.76 + let live_map = new WeakMap; 1.77 + let dead_map = new WeakMap; 1.78 + let live_key = {}; 1.79 + let dead_key = {}; 1.80 + 1.81 + // put the live/dead maps/keys into the appropriate DOM elements 1.82 + live_dom.basic_unit_tests = {m:live_map, k:live_key}; 1.83 + dead_dom.setUserData("hook", {m:dead_map, k:dead_key}, null); 1.84 + // dead_dom.hook = {m:dead_map, k:dead_key}; 1.85 + 1.86 + // Create a dead value, and a weak ref to it. 1.87 + // The loop keeps dead_dom alive unless the CC is smart enough to kill it. 1.88 + let dead_val = {loop:dead_dom}; 1.89 + basic_weak_ref = make_weak_ref(dead_val); 1.90 + basic_map_weak_ref = make_weak_ref(dead_map); 1.91 + 1.92 + // set up the actual entries. most will die. 1.93 + live_map.set(live_key, {my_key:'live_live'}); 1.94 + live_map.set(dead_key, dead_val); 1.95 + live_map.set(black_key, {my_key:'live_black'}); 1.96 + 1.97 + dead_map.set(live_key, dead_val); 1.98 + dead_map.set(dead_key, dead_val); 1.99 + dead_map.set(black_key, dead_val); 1.100 + 1.101 + black_map.set(live_key, {my_key:'black_live'}); 1.102 + black_map.set(dead_key, dead_val); 1.103 + black_map.set(black_key, {my_key:'black_black'}); 1.104 + 1.105 + }; 1.106 + 1.107 + basic_unit_tests(); 1.108 + 1.109 + 1.110 + let check_basic_unit = function () { 1.111 + let live_dom = get_live_dom(); 1.112 + let live_map = live_dom.basic_unit_tests.m; 1.113 + let live_key = live_dom.basic_unit_tests.k; 1.114 + 1.115 + // check the dead elements 1.116 + ok(weak_ref_dead(basic_weak_ref), "Dead value was kept alive."); 1.117 + ok(weak_ref_dead(basic_map_weak_ref), "Dead map was kept alive."); 1.118 + 1.119 + // check the live gray map 1.120 + is(live_map.get(live_key).my_key, 'live_live', 1.121 + "Live key should have the same value in live map."); 1.122 + is(live_map.get(black_key).my_key, 'live_black', 1.123 + "Black key should have the same value in live map."); 1.124 + is(Cu.nondeterministicGetWeakMapKeys(live_map).length, 2, 1.125 + "Live map should have two entries."); 1.126 + 1.127 + // check the live black map 1.128 + is(black_map.get(live_key).my_key, 'black_live', 1.129 + "Live key should have the same value in black map."); 1.130 + is(black_map.get(black_key).my_key, 'black_black', 1.131 + "Black key should have the same value in black map."); 1.132 + is(Cu.nondeterministicGetWeakMapKeys(black_map).length, 2, 1.133 + "Black map should have two entries."); 1.134 + 1.135 + }; 1.136 + 1.137 + 1.138 + /* live gray chained weak map entries, involving the cycle collector. */ 1.139 + let chainm = new WeakMap; 1.140 + let num_chains = 5; 1.141 + 1.142 + let nested_cc_maps = function () { 1.143 + let dom = get_live_dom(); 1.144 + for(let i = 0; i < num_chains; i++) { 1.145 + let k = {count:i}; 1.146 + dom.key = k; 1.147 + dom0 = document.createElement("div"); 1.148 + chainm.set(k, {d:dom0}); 1.149 + dom = document.createElement("div"); 1.150 + dom0.appendChild(dom); 1.151 + }; 1.152 + }; 1.153 + 1.154 + let check_nested_cc_maps = function () { 1.155 + let dom = get_live_dom(); 1.156 + let all_ok = true; 1.157 + for(let i = 0; i < num_chains; i++) { 1.158 + let k = dom.key; 1.159 + all_ok = all_ok && k.count == i; 1.160 + dom = chainm.get(k).d.firstChild; 1.161 + }; 1.162 + ok(all_ok, "Count was invalid on a key in chained weak map entries."); 1.163 + }; 1.164 + 1.165 + nested_cc_maps(); 1.166 + 1.167 + 1.168 + /* black weak map, chained garbage cycle involving DOM */ 1.169 + let garbage_map = new WeakMap; 1.170 + 1.171 + let chained_garbage_maps = function () { 1.172 + let dom0 = document.createElement("div"); 1.173 + let dom = dom0; 1.174 + for(let i = 0; i < num_chains; i++) { 1.175 + let k = {}; 1.176 + dom.key = k; 1.177 + let new_dom = document.createElement("div"); 1.178 + garbage_map.set(k, {val_child:new_dom}); 1.179 + dom = document.createElement("div"); 1.180 + new_dom.appendChild(dom); 1.181 + }; 1.182 + // tie the knot 1.183 + dom.appendChild(dom0); 1.184 + }; 1.185 + 1.186 + chained_garbage_maps(); 1.187 + 1.188 + 1.189 + /* black weak map, chained garbage cycle involving DOM, XPCWN keys */ 1.190 + let wn_garbage_map = new WeakMap; 1.191 + 1.192 + let wn_chained_garbage_maps = function () { 1.193 + let dom0 = document.createElement("div"); 1.194 + let dom = dom0; 1.195 + for(let i = 0; i < num_chains; i++) { 1.196 + let new_dom = document.createElement("div"); 1.197 + wn_garbage_map.set(dom, {wn_val_child:new_dom}); 1.198 + dom = document.createElement("div"); 1.199 + new_dom.appendChild(dom); 1.200 + }; 1.201 + // tie the knot 1.202 + dom.appendChild(dom0); 1.203 + }; 1.204 + 1.205 + wn_chained_garbage_maps(); 1.206 + 1.207 + 1.208 + /* The cycle collector shouldn't remove a live wrapped native key. */ 1.209 + 1.210 + let wn_live_map = new WeakMap; 1.211 + 1.212 + let make_live_map = function () { 1.213 + let live = get_live_dom(); 1.214 + wn_live_map.set(live, {}); 1.215 + ok(wn_live_map.has(get_live_dom()), "Live map should have live DOM node before GC."); 1.216 + } 1.217 + 1.218 + make_live_map(); 1.219 + 1.220 + let unpreservable_native_key = function () { 1.221 + // We only allow natives that support wrapper preservation to be used as weak 1.222 + // map keys. We should be able to try to add unpreservable natives as keys without 1.223 + // crashing (bug 711616), but we should throw an error (bug 761620). 1.224 + 1.225 + let dummy_test_map = new WeakMap; 1.226 + 1.227 + let rule_fail = false; 1.228 + let got_rule = false; 1.229 + try { 1.230 + var rule = document.styleSheets[0].cssRules[0]; 1.231 + got_rule = true; 1.232 + dummy_test_map.set(rule, 1); 1.233 + } catch (e) { 1.234 + rule_fail = true; 1.235 + } 1.236 + ok(got_rule, "Got the CSS rule"); 1.237 + ok(rule_fail, "Using a CSS rule as a weak map key should produce an exception because it can't be wrapper preserved."); 1.238 + 1.239 + } 1.240 + 1.241 + unpreservable_native_key(); 1.242 + 1.243 + /* set up for running precise GC/CC then checking the results */ 1.244 + 1.245 + SimpleTest.waitForExplicitFinish(); 1.246 + 1.247 + Cu.schedulePreciseGC(function () { 1.248 + SpecialPowers.DOMWindowUtils.cycleCollect(); 1.249 + SpecialPowers.DOMWindowUtils.garbageCollect(); 1.250 + SpecialPowers.DOMWindowUtils.garbageCollect(); 1.251 + 1.252 + ok(weak_ref_dead(weakref), "Garbage gray cycle should be collected."); 1.253 + 1.254 + check_nested_cc_maps(); 1.255 + 1.256 + is(Cu.nondeterministicGetWeakMapKeys(garbage_map).length, 0, "Chained garbage weak map entries should not leak."); 1.257 + 1.258 + check_basic_unit(); 1.259 + 1.260 + // fixed by Bug 680937 1.261 + is(Cu.nondeterministicGetWeakMapKeys(wn_garbage_map).length, 0, 1.262 + "Chained garbage WN weak map entries should not leak."); 1.263 + 1.264 + // fixed by Bug 680937 1.265 + is(Cu.nondeterministicGetWeakMapKeys(wn_live_map).length, 1, 1.266 + "Live weak map wrapped native key should not be removed."); 1.267 + 1.268 + ok(wn_live_map.has(get_live_dom()), "Live map should have live dom."); 1.269 + 1.270 + SimpleTest.finish(); 1.271 + }); 1.272 + 1.273 + ]]> 1.274 + </script> 1.275 +</window>