event_leak_utils.js (3082B)
1 // Any copyright is dedicated to the Public Domain. 2 // http://creativecommons.org/publicdomain/zero/1.0/ 3 "use strict"; 4 5 // This function runs a number of tests where: 6 // 7 // 1. An iframe is created 8 // 2. The target callback is executed with the iframe's contentWindow as 9 // an argument. 10 // 3. The iframe is destroyed and GC is forced. 11 // 4. Verifies that the iframe's contentWindow has been GC'd. 12 // 13 // Different ways of destroying the iframe are checked. Simple 14 // remove(), destruction via bfcache, or replacement by document.open(). 15 // 16 // Please pass a target callback that exercises the API under 17 // test using the given window. The callback should try to leave the 18 // API active to increase the liklihood of provoking an API. Any activity 19 // should be canceled by the destruction of the window. 20 async function checkForEventListenerLeaks(name, target) { 21 // Test if we leak in the case where we do nothing special to 22 // the frame before removing it from the DOM. 23 await _eventListenerLeakStep(target, `${name} default`); 24 25 // Test the case where we navigate the frame before removing it 26 // from the DOM so that the window using the target API ends up 27 // in bfcache. 28 await _eventListenerLeakStep(target, `${name} bfcache`, frame => { 29 frame.src = "about:blank"; 30 return new Promise(resolve => (frame.onload = resolve)); 31 }); 32 33 // Test the case where we document.open() the frame before removing 34 // it from the DOM so that the window using the target API ends 35 // up getting replaced. 36 await _eventListenerLeakStep(target, `${name} document.open()`, frame => { 37 frame.contentDocument.open(); 38 frame.contentDocument.close(); 39 }); 40 } 41 42 // ---------------- 43 // Internal helpers 44 // ---------------- 45 46 // Utility function to create a loaded iframe. 47 async function _withFrame(doc, url) { 48 let frame = doc.createElement("iframe"); 49 frame.src = url; 50 doc.body.appendChild(frame); 51 await new Promise(resolve => (frame.onload = resolve)); 52 return frame; 53 } 54 55 // This function defines the basic form of the test cases. We create an 56 // iframe, execute the target callback to manipulate the DOM, remove the frame 57 // from the DOM, and then check to see if the frame was GC'd. The caller 58 // may optionally pass in a callback that will be executed with the 59 // frame as an argument before removing it from the DOM. 60 async function _eventListenerLeakStep(target, name, extra) { 61 let frame = await _withFrame(document, "empty.html"); 62 63 await target(frame.contentWindow); 64 65 let weakRef = SpecialPowers.Cu.getWeakReference(frame.contentWindow); 66 ok(weakRef.get(), `should be able to create a weak reference - ${name}`); 67 68 if (extra) { 69 await extra(frame); 70 } 71 72 frame.remove(); 73 frame = null; 74 75 // Perform many GC's to avoid intermittent delayed collection. 76 await new Promise(resolve => SpecialPowers.exactGC(resolve)); 77 await new Promise(resolve => SpecialPowers.exactGC(resolve)); 78 await new Promise(resolve => SpecialPowers.exactGC(resolve)); 79 80 ok( 81 !weakRef.get(), 82 `iframe content window should be garbage collected - ${name}` 83 ); 84 }