browser_animatedImageLeak.js (10504B)
1 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* vim: set sts=2 sw=2 et tw=80: */ 3 "use strict"; 4 5 requestLongerTimeout(4); 6 7 /* 8 * This tests that when we have an animated image in a minimized window we 9 * don't leak. 10 * We've encountered this bug in 3 different ways: 11 * -bug 1830753 - images in top level chrome processes 12 * (we avoid processing them due to their CompositorBridgeChild being paused) 13 * -bug 1839109 - images in content processes 14 * (we avoid processing them due to their refresh driver being throttled, this 15 * would also fix the above case) 16 * -bug 1875100 - images that are in a content iframe that is not the content 17 * of a tab, so something like an extension iframe in the sidebar 18 * (this was fixed by making the content of a tab declare that it manually 19 * manages its activeness and having all other iframes inherit their 20 * activeness from their parent) 21 * In order to hit this bug we require 22 * -the same image to be in a minimized window and in a non-mininmized window 23 * so that the image is animated. 24 * -the animated image to go over the 25 * image.animated.decode-on-demand.threshold-kb threshold so that we do not 26 * keep all of its frames around (if we keep all its frame around then we 27 * don't try to keep allocating frames and not freeing the old ones) 28 * -it has to be the same Image object in memory, not just the same uri 29 * Then the visible copy of the image keeps generating new frames, those frames 30 * get sent to the minimized copies of the image but they never get processed 31 * or marked displayed so they can never be freed/reused. 32 * 33 * Note that due to bug 1889840, in order to test this we can't use an image 34 * loaded at the top level (see the last point above). We must use an html page 35 * that contains the image. 36 */ 37 38 // this test is based in part on https://searchfox.org/mozilla-central/rev/c09764753ea40725eb50decad2c51edecbd33308/browser/components/extensions/test/browser/browser_ext_sidebarAction.js 39 40 async function pushPrefs1() { 41 await SpecialPowers.pushPrefEnv({ 42 set: [ 43 ["image.animated.decode-on-demand.threshold-kb", 1], 44 ["image.animated.decode-on-demand.batch-size", 2], 45 ], 46 }); 47 } 48 49 // maximize = false then minimize all windows but the last one 50 // this tests that minimized windows don't accumulate animated images frames 51 // maximize = true then maximize the last window 52 // this tests that occluded windows don't accumulate animated images frames 53 async function openWindows(maximize, taskToPerformBeforeSizeChange) { 54 let wins = [null, null, null, null]; 55 for (let i = 0; i < wins.length; i++) { 56 let win = await BrowserTestUtils.openNewBrowserWindow(); 57 await win.delayedStartupPromise; 58 await taskToPerformBeforeSizeChange(win); 59 60 if ( 61 (!maximize && i < wins.length - 1) || 62 (maximize && i == wins.length - 1) 63 ) { 64 // the window might be maximized already, but it won't be minimized, only wait for 65 // size change if the size is actually changing. 66 if (!maximize || (maximize && win.windowState != win.STATE_MAXIMIZED)) { 67 let promiseSizeModeChange = BrowserTestUtils.waitForEvent( 68 win, 69 "sizemodechange" 70 ); 71 if (maximize) { 72 win.maximize(); 73 } else { 74 win.minimize(); 75 } 76 await promiseSizeModeChange; 77 } 78 } 79 80 wins[i] = win; 81 } 82 return wins; 83 } 84 85 async function pushPrefs2() { 86 // wait so that at least one frame of the animation has been shown while the 87 // below pref is not set so that the counter gets reset. 88 await new Promise(resolve => setTimeout(resolve, 500)); 89 90 await SpecialPowers.pushPrefEnv({ 91 set: [["gfx.testing.assert-render-textures-increase", 75]], 92 }); 93 } 94 95 async function waitForEnoughFrames() { 96 // we want to wait for over 75 frames of the image, it has a delay of 200ms 97 // Windows debug test machines seem to animate at about 10 fps though 98 await new Promise(resolve => setTimeout(resolve, 20000)); 99 } 100 101 async function closeWindows(wins) { 102 for (let i = 0; i < wins.length; i++) { 103 await BrowserTestUtils.closeWindow(wins[i]); 104 } 105 } 106 107 async function popPrefs() { 108 await SpecialPowers.popPrefEnv(); 109 await SpecialPowers.popPrefEnv(); 110 } 111 112 add_task(async () => { 113 async function runTest(theTestPath, maximize) { 114 await pushPrefs1(); 115 116 let wins = await openWindows(maximize, async function (win) { 117 let tab = await BrowserTestUtils.openNewForegroundTab( 118 win.gBrowser, 119 theTestPath 120 ); 121 }); 122 123 await pushPrefs2(); 124 125 await waitForEnoughFrames(); 126 127 await closeWindows(wins); 128 129 await popPrefs(); 130 131 ok(true, "got here without assserting"); 132 } 133 134 function fileURL(filename) { 135 let ifile = getChromeDir(getResolvedURI(gTestPath)); 136 ifile.append(filename); 137 return Services.io.newFileURI(ifile).spec; 138 } 139 140 // This tests the image in content process case 141 let contentURL = fileURL("helper_animatedImageLeak.html"); 142 await runTest(contentURL, /* maximize = */ true); 143 await runTest(contentURL, /* maximize = */ false); 144 145 // This tests the image in chrome process case 146 let chromeURL = getRootDirectory(gTestPath) + "helper_animatedImageLeak.html"; 147 await runTest(chromeURL, /* maximize = */ true); 148 await runTest(chromeURL, /* maximize = */ false); 149 }); 150 151 // Now we test the image in a sidebar loaded via an extension case. 152 153 /* 154 * The data uri below is a 2kb apng that is 3000x200 with 22 frames with delay 155 * of 200ms, it just toggles the color of one pixel from black to red so it's 156 * tiny. We use the same data uri (although that is not important to this test) 157 * in helper_animatedImageLeak.html. 158 */ 159 160 /* 161 * This is just data to create a simple extension that creates a sidebar with 162 * an image in it. 163 */ 164 let extData = { 165 manifest: { 166 sidebar_action: { 167 default_panel: "sidebar.html", 168 }, 169 }, 170 useAddonManager: "temporary", 171 172 files: { 173 "sidebar.html": ` 174 <!DOCTYPE html> 175 <html> 176 <head> 177 <meta charset="UTF-8"> 178 <script src="sidebar.js"></script> 179 </head> 180 <body><p>Sidebar</p> 181 <img src="data:image/png;base64,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"/> 182 </body> 183 </html> 184 `, 185 186 "sidebar.js": function () { 187 window.onload = () => { 188 browser.test.sendMessage("sidebar"); 189 }; 190 }, 191 }, 192 }; 193 194 function getExtData(manifestUpdates = {}) { 195 return { 196 ...extData, 197 manifest: { 198 ...extData.manifest, 199 ...manifestUpdates, 200 }, 201 }; 202 } 203 204 async function sendMessage(ext, msg, data = undefined) { 205 ext.sendMessage({ msg, data }); 206 await ext.awaitMessage("done"); 207 } 208 209 add_task(async function sidebar_initial_install() { 210 async function runTest(maximize) { 211 await pushPrefs1(); 212 213 ok( 214 document.getElementById("sidebar-box").hidden, 215 "sidebar box is not visible" 216 ); 217 218 let extension = ExtensionTestUtils.loadExtension(getExtData()); 219 await extension.startup(); 220 await extension.awaitMessage("sidebar"); 221 222 // Test sidebar is opened on install 223 ok( 224 !document.getElementById("sidebar-box").hidden, 225 "sidebar box is visible" 226 ); 227 228 // the sidebar appears on all new windows automatically. 229 let wins = await openWindows(maximize, async function (win) { 230 await extension.awaitMessage("sidebar"); 231 }); 232 233 await pushPrefs2(); 234 235 await waitForEnoughFrames(); 236 237 await extension.unload(); 238 // Test that the sidebar was closed on unload. 239 ok( 240 document.getElementById("sidebar-box").hidden, 241 "sidebar box is not visible" 242 ); 243 244 await closeWindows(wins); 245 246 await popPrefs(); 247 248 ok(true, "got here without assserting"); 249 } 250 251 await runTest(/* maximize = */ true); 252 await runTest(/* maximize = */ false); 253 });