test_discardFramesAnimatedImage.html (8024B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=523950 5 --> 6 <head> 7 <title>Test that animated images can discard frames and redecode</title> 8 <script src="/tests/SimpleTest/SimpleTest.js"></script> 9 <script src="/tests/SimpleTest/WindowSnapshot.js"></script> 10 <script type="text/javascript" src="imgutils.js"></script> 11 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 12 </head> 13 <body> 14 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=523950">Mozilla Bug 523950</a> 15 <p id="display"></p> 16 <div id="content"> 17 <div id="container"> 18 <canvas id="canvas" width="100" height="100"></canvas> 19 <img id="rainbow.gif"/> 20 </div> 21 </div> 22 <pre id="test"> 23 <script class="testbody" type="text/javascript"> 24 25 /** Test for Bug 523950. */ 26 SimpleTest.waitForExplicitFinish(); 27 28 var gFinished = false; 29 30 var gNumOnloads = 0; 31 32 var gNumDiscards = 0; 33 34 window.onload = function() { 35 // Enable minimal frame discard thresholds for the test. 36 SpecialPowers.pushPrefEnv({ 37 'set':[['image.animated.decode-on-demand.threshold-kb',0], 38 ['image.animated.decode-on-demand.batch-size',1], 39 ['image.mem.discardable',true], 40 ['image.mem.animated.discardable',true]] 41 }, runTest); 42 } 43 44 var gImgs = ['rainbow.gif']; 45 // If we are currently counting frame updates. 46 var gCountingFrameUpdates = false; 47 // The number of frame update notifications for the images in gImgs that happen 48 // after discarding. (The last two images are finite looping so we don't expect 49 // them to get incremented but it's possible if they don't finish their 50 // animation before we discard them.) 51 var gNumFrameUpdates = [0]; 52 // The last snapshot of the image. Used to check that the image actually changes. 53 var gLastSnapShot = [null]; 54 // Number of observed changes in the snapshot. 55 var gNumSnapShotChanges = [0]; 56 // If we've removed the observer. 57 var gRemovedObserver = [false]; 58 59 // rainbow.gif has 9 frames, so we choose arbitrarily 22 to include two loops. 60 var kNumFrameUpdatesToExpect = 22; 61 62 function runTest() { 63 var thresholdKb = 64 SpecialPowers.getIntPref('image.animated.decode-on-demand.threshold-kb'); 65 var batchSize = 66 SpecialPowers.getIntPref('image.animated.decode-on-demand.batch-size'); 67 var discardable = 68 SpecialPowers.getBoolPref('image.mem.discardable'); 69 var animDiscardable = 70 SpecialPowers.getBoolPref('image.mem.animated.discardable'); 71 if (thresholdKb > 0 || batchSize > 1 || !discardable || !animDiscardable) { 72 ok(true, "discarding frames of animated images is disabled, nothing to test"); 73 SimpleTest.finish(); 74 return; 75 } 76 77 setTimeout(step2, 0); 78 } 79 80 function step2() { 81 // Only set the src after setting the pref. 82 for (var i = 0; i < gImgs.length; i++) { 83 var elm = document.getElementById(gImgs[i]); 84 elm.src = gImgs[i]; 85 elm.onload = checkIfLoaded; 86 } 87 } 88 89 function step3() { 90 // Draw the images to canvas to force them to be decoded. 91 for (let i = 0; i < gImgs.length; i++) { 92 drawCanvas(document.getElementById(gImgs[i])); 93 } 94 95 for (let i = 0; i < gImgs.length; i++) { 96 addCallbacks(document.getElementById(gImgs[i]), i); 97 } 98 99 setTimeout(step4, 0); 100 } 101 102 function step4() { 103 ok(true, "now accepting frame updates"); 104 gCountingFrameUpdates = true; 105 } 106 107 function step5() { 108 ok(true, "discarding images"); 109 110 document.getElementById("container").style.display = "none"; 111 document.documentElement.offsetLeft; // force that style to take effect 112 113 // Reset our state to let us do it all again after discarding. 114 resetState(); 115 116 // Draw the images to canvas to force them to be decoded. 117 for (var i = 0; i < gImgs.length; i++) { 118 requestDiscard(document.getElementById(gImgs[i])); 119 } 120 121 // the discard observers will call step6 122 } 123 124 function step6() { 125 // Repeat the cycle now that we discarded everything. 126 ok(gNumDiscards >= gImgs.length, "discard complete, restarting animations"); 127 document.getElementById("container").style.display = ""; 128 129 // Draw the images to canvas to force them to be decoded. 130 for (var i = 0; i < gImgs.length; i++) { 131 drawCanvas(document.getElementById(gImgs[i])); 132 } 133 134 setTimeout(step4, 0); 135 } 136 137 function checkIfLoaded() { 138 ++gNumOnloads; 139 if (gNumOnloads != gImgs.length) { 140 return; 141 } 142 143 ok(true, "got onloads"); 144 setTimeout(step3, 0); 145 } 146 147 function resetState() { 148 gFinished = false; 149 gCountingFrameUpdates = false; 150 for (let i = 0; i < gNumFrameUpdates.length; ++i) { 151 gNumFrameUpdates[i] = 0; 152 } 153 for (let i = 0; i < gNumSnapShotChanges.length; ++i) { 154 gNumSnapShotChanges[i] = 0; 155 } 156 for (let i = 0; i < gLastSnapShot.length; ++i) { 157 gLastSnapShot[i] = null; 158 } 159 } 160 161 function checkIfFinished() { 162 if (gFinished) { 163 return; 164 } 165 166 for (var i = 0; i < gNumFrameUpdates.length; ++i) { 167 if (gNumFrameUpdates[i] < kNumFrameUpdatesToExpect || 168 gNumSnapShotChanges[i] < kNumFrameUpdatesToExpect) { 169 return; 170 } 171 } 172 173 ok(true, "got expected frame updates"); 174 gFinished = true; 175 176 if (gNumDiscards == 0) { 177 // If we haven't discarded any complete images, we should do so, and 178 // verify the animation again. 179 setTimeout(step5, 0); 180 return; 181 } 182 183 SimpleTest.finish(); 184 } 185 186 // arrayIndex is the index into the arrays gNumFrameUpdates and gNumDecodes 187 // to increment when a frame update notification is received. 188 function addCallbacks(anImage, arrayIndex) { 189 var observer = new ImageDecoderObserverStub(); 190 observer.discard = function () { 191 gNumDiscards++; 192 ok(true, "got image discard"); 193 if (gNumDiscards == gImgs.length) { 194 step6(); 195 } 196 }; 197 observer.frameUpdate = function () { 198 if (!gCountingFrameUpdates) { 199 return; 200 } 201 202 // Do this off a setTimeout since nsImageLoadingContent uses a scriptblocker 203 // when it notifies us and calling drawWindow can call will paint observers 204 // which can dispatch a scrollport event, and events assert if dispatched 205 // when there is a scriptblocker. 206 setTimeout(function () { 207 gNumFrameUpdates[arrayIndex]++; 208 209 var r = document.getElementById(gImgs[arrayIndex]).getBoundingClientRect(); 210 var snapshot = snapshotRect(window, r, "rgba(0,0,0,0)"); 211 if (gLastSnapShot[arrayIndex] != null) { 212 if (snapshot.toDataURL() != gLastSnapShot[arrayIndex].toDataURL()) { 213 gNumSnapShotChanges[arrayIndex]++; 214 } 215 } 216 gLastSnapShot[arrayIndex] = snapshot; 217 218 if (gNumFrameUpdates[arrayIndex] >= kNumFrameUpdatesToExpect && 219 gNumSnapShotChanges[arrayIndex] >= kNumFrameUpdatesToExpect && 220 gNumDiscards >= gImgs.length) { 221 if (!gRemovedObserver[arrayIndex]) { 222 ok(true, "removing observer for " + arrayIndex); 223 gRemovedObserver[arrayIndex] = true; 224 imgLoadingContent.removeObserver(scriptedObserver); 225 } 226 } 227 if (!gFinished) { 228 // because we do this in a setTimeout we can have several in flight 229 // so don't call ok if we've already finished. 230 ok(true, "got frame update"); 231 } 232 checkIfFinished(); 233 }, 0); 234 }; 235 observer = SpecialPowers.wrapCallbackObject(observer); 236 237 var scriptedObserver = SpecialPowers.Cc["@mozilla.org/image/tools;1"] 238 .getService(SpecialPowers.Ci.imgITools) 239 .createScriptedObserver(observer); 240 241 var imgLoadingContent = SpecialPowers.wrap(anImage); 242 imgLoadingContent.addObserver(scriptedObserver); 243 } 244 245 function requestDiscard(anImage) { 246 var request = SpecialPowers.wrap(anImage) 247 .getRequest(SpecialPowers.Ci.nsIImageLoadingContent.CURRENT_REQUEST); 248 setTimeout(() => request.requestDiscard(), 0); 249 } 250 251 function drawCanvas(anImage) { 252 var canvas = document.getElementById('canvas'); 253 var context = canvas.getContext('2d'); 254 255 context.clearRect(0,0,100,100); 256 var cleared = canvas.toDataURL(); 257 258 context.drawImage(anImage, 0, 0); 259 ok(true, "we got through the drawImage call without an exception being thrown"); 260 261 ok(cleared != canvas.toDataURL(), "drawImage drew something"); 262 } 263 264 </script> 265 </pre> 266 </body> 267 </html>