diff options
Diffstat (limited to 'js/xpconnect/tests/mochitest/test_weakmaps.html')
-rw-r--r-- | js/xpconnect/tests/mochitest/test_weakmaps.html | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/js/xpconnect/tests/mochitest/test_weakmaps.html b/js/xpconnect/tests/mochitest/test_weakmaps.html new file mode 100644 index 0000000000..5e00106fed --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_weakmaps.html @@ -0,0 +1,264 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=668855 +--> +<head> + <meta charset="utf-8"> + <title>Test Cross-Compartment DOM WeakMaps</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +<script type="application/javascript"> + /** Test for Bug 668855 **/ + + SimpleTest.waitForExplicitFinish(); + +// We wait to run this until the load event because it needs to access an element. +function go() { + + /* 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 == 0; + } + + /* Deterministically grab an arbitrary DOM element. */ + let get_live_dom = function () { + let elems = document.getElementsByTagName("a"); + return elems[0]; + }; + + + /* Test case from bug 653248, adapted into a standard test. + + This is a dead cycle involving a DOM edge, so the cycle collector can free it. Keys and + values reachable only from XPConnect must be marked gray for this to work, and the cycle collector + must know the proper structure of the heap. + + */ + let make_gray_loop = function () { + let map = new WeakMap; + let div = document.createElement("div"); + let key = {}; + let obj = {m:map, k:key}; + div.addEventListener("foo", function() { + // The code below doesn't matter (it won't run). Just pull a + // reference to obj. + obj.k = 1; + obj.m = "bar"; + }); + //div.entrain = {m:map, k:key}; This is not sufficient to cause a leak in Fx9 + map.set(key, div); + return make_weak_ref(map); + }; + + let weakref = make_gray_loop(); + + + /* Combinations of live and dead gray maps/keys. */ + let basic_weak_ref = null; + let basic_map_weak_ref = null; + let black_map = new WeakMap; + let black_key = {}; + + let basic_unit_tests = function () { + let live_dom = get_live_dom(); + let dead_dom = document.createElement("div"); + let live_map = new WeakMap; + let dead_map = new WeakMap; + let live_key = {}; + let dead_key = {}; + + // put the live/dead maps/keys into the appropriate DOM elements + live_dom.basic_unit_tests = {m:live_map, k:live_key}; + + let obj = {m:dead_map, k:dead_key}; + // dead_dom.hook = {m:dead_map, k:dead_key}; + dead_dom.addEventListener("foo", function() { + // The code below doesn't matter (it won't run). Just pull a + // reference to obj. + obj.m = 1; + obj.k = "2"; + }); + + // Create a dead value, and a weak ref to it. + // The loop keeps dead_dom alive unless the CC is smart enough to kill it. + let dead_val = {loop:dead_dom}; + basic_weak_ref = make_weak_ref(dead_val); + basic_map_weak_ref = make_weak_ref(dead_map); + + // set up the actual entries. most will die. + live_map.set(live_key, {my_key:'live_live'}); + live_map.set(dead_key, dead_val); + live_map.set(black_key, {my_key:'live_black'}); + + dead_map.set(live_key, dead_val); + dead_map.set(dead_key, dead_val); + dead_map.set(black_key, dead_val); + + black_map.set(live_key, {my_key:'black_live'}); + black_map.set(dead_key, dead_val); + black_map.set(black_key, {my_key:'black_black'}); + + }; + + basic_unit_tests(); + + + let check_basic_unit = function () { + let live_dom = get_live_dom(); + let live_map = live_dom.basic_unit_tests.m; + let live_key = live_dom.basic_unit_tests.k; + + // check the dead elements + ok(weak_ref_dead(basic_weak_ref), "Dead value was kept alive."); + ok(weak_ref_dead(basic_map_weak_ref), "Dead map was kept alive."); + + // check the live gray map + is(live_map.get(live_key).my_key, 'live_live', + "Live key should have the same value in live map."); + is(live_map.get(black_key).my_key, 'live_black', + "Black key should have the same value in live map."); + is(SpecialPowers.nondeterministicGetWeakMapKeys(live_map).length, 2, + "Live map should have two entries."); + + // check the live black map + is(black_map.get(live_key).my_key, 'black_live', + "Live key should have the same value in black map."); + is(black_map.get(black_key).my_key, 'black_black', + "Black key should have the same value in black map."); + is(SpecialPowers.nondeterministicGetWeakMapKeys(black_map).length, 2, + "Black map should have two entries."); + + }; + + + /* live gray chained weak map entries, involving the cycle collector. */ + let chainm = new WeakMap; + let num_chains = 5; + + let nested_cc_maps = function () { + let dom = get_live_dom(); + for(let i = 0; i < num_chains; i++) { + let k = {count:i}; + dom.key = k; + dom0 = document.createElement("div"); + chainm.set(k, {d:dom0}); + dom = document.createElement("div"); + dom0.appendChild(dom); + }; + }; + + let check_nested_cc_maps = function () { + let dom = get_live_dom(); + let all_ok = true; + for(let i = 0; i < num_chains; i++) { + let k = dom.key; + all_ok = all_ok && k.count == i; + dom = chainm.get(k).d.firstChild; + }; + ok(all_ok, "Count was invalid on a key in chained weak map entries."); + }; + + nested_cc_maps(); + + + /* black weak map, chained garbage cycle involving DOM */ + let garbage_map = new WeakMap; + + let chained_garbage_maps = function () { + let dom0 = document.createElement("div"); + let dom = dom0; + for(let i = 0; i < num_chains; i++) { + let k = {}; + dom.key = k; + let new_dom = document.createElement("div"); + garbage_map.set(k, {val_child:new_dom}); + dom = document.createElement("div"); + new_dom.appendChild(dom); + }; + // tie the knot + dom.appendChild(dom0); + }; + + chained_garbage_maps(); + + + /* black weak map, chained garbage cycle involving DOM, XPCWN keys */ + let wn_garbage_map = new WeakMap; + + let wn_chained_garbage_maps = function () { + let dom0 = document.createElement("div"); + let dom = dom0; + for(let i = 0; i < num_chains; i++) { + let new_dom = document.createElement("div"); + wn_garbage_map.set(dom, {wn_val_child:new_dom}); + dom = document.createElement("div"); + new_dom.appendChild(dom); + }; + // tie the knot + dom.appendChild(dom0); + }; + + wn_chained_garbage_maps(); + + + /* The cycle collector shouldn't remove a live wrapped native key. */ + + let wn_live_map = new WeakMap; + + let make_live_map = function () { + let live = get_live_dom(); + wn_live_map.set(live, {}); + ok(wn_live_map.has(get_live_dom()), "Live map should have live DOM node before GC."); + } + + make_live_map(); + + // We're out of ideas for unpreservable natives, now that just about + // everything is on webidl, so just don't test those. + + /* set up for running precise GC/CC then checking the results */ + + SpecialPowers.exactGC(function () { + SpecialPowers.forceCC(); + SpecialPowers.forceGC(); + SpecialPowers.forceGC(); + + ok(weak_ref_dead(weakref), "Garbage gray cycle should be collected."); + + check_nested_cc_maps(); + + is(SpecialPowers.nondeterministicGetWeakMapKeys(garbage_map).length, 0, "Chained garbage weak map entries should not leak."); + + check_basic_unit(); + + // fixed by Bug 680937 + is(SpecialPowers.nondeterministicGetWeakMapKeys(wn_garbage_map).length, 0, + "Chained garbage WN weak map entries should not leak."); + + // fixed by Bug 680937 + is(SpecialPowers.nondeterministicGetWeakMapKeys(wn_live_map).length, 1, + "Live weak map wrapped native key should not be removed."); + + ok(wn_live_map.has(get_live_dom()), "Live map should have live dom."); + + SimpleTest.finish(); + }); + +} + </script> +</head> +<div></div> +<div id="mydivname"></div> +<body onload="go()";> +<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=668855" target="_blank">Mozilla Bug 668855</a> +<p id="display"></p> +</body> +</html> |