test_discardAnimatedImage.html (8452B)
1 <!DOCTYPE HTML> 2 <html> 3 <!-- 4 https://bugzilla.mozilla.org/show_bug.cgi?id=686905 5 --> 6 <head> 7 <title>Test that animated images can be discarded</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=686905">Mozilla Bug 686905</a> 15 <p id="display"></p> 16 <div id="content"> 17 <div id="container"> 18 <canvas id="canvas" width="100" height="100"></canvas> 19 20 <!-- NOTE if adding a new image here you need to adjust four other places: 21 * add it to the gImgs array below 22 * add it to the 4 arrays below that 23 * add it to the condition in checkIfFinished if it's infinite 24 * potentially update the starting index of finite imgs in 25 addCallbacks observer.discard. 26 --> 27 <img id="infinitepng" src="infinite-apng.png"> 28 <img id="infinitegif" src="animated1.gif"> 29 <img id="infinitewebp" src="infinite.webp"> 30 <img id="infiniteavif" src="infinite.avif"> 31 32 <img id="corruptinfinitegif" src="1835509.gif"> 33 34 <img id="finitepng" src="restore-previous.png"> 35 <img id="finitegif" src="animated-gif.gif"> 36 <img id="finitewebp" src="keep.webp"> 37 <img id="finiteavif" src="animated-avif.avif"> 38 <!-- NOTE see above the steps you need to do if adding an img here --> 39 40 </div> 41 </div> 42 <pre id="test"> 43 <script class="testbody" type="text/javascript"> 44 45 /** Test for Bug 686905. */ 46 SimpleTest.waitForExplicitFinish(); 47 48 var gFinished = false; 49 50 var gNumDiscards = 0; 51 52 window.onload = function() { 53 // Enable discarding for the test. 54 SpecialPowers.pushPrefEnv({ 55 'set':[['image.mem.discardable',true], 56 ['image.avif.sequence.enabled',true]] 57 }, runTest); 58 } 59 60 var gImgs = ['infinitepng', 'infinitegif', 'infinitewebp', 'infiniteavif', 61 'corruptinfinitegif', 62 'finitepng', 'finitegif', 'finitewebp', 'finiteavif']; 63 // If we are currently counting frame updates. 64 var gCountingFrameUpdates = false; 65 // The number of frame update notifications for the images in gImgs that happen 66 // after discarding. (The last two images are finite looping so we don't expect 67 // them to get incremented but it's possible if they don't finish their 68 // animation before we discard them.) 69 var gNumFrameUpdates = [0, 0, 0, 0, 0, 0, 0, 0, 0]; 70 // The last snapshot of the image. Used to check that the image actually changes. 71 var gLastSnapShot = [null, null, null, null, null, null, null, null, null]; 72 // Number of observed changes in the snapshot. 73 var gNumSnapShotChanges = [0, 0, 0, 0, 0, 0, 0, 0, 0]; 74 // If we've removed the observer. 75 var gRemovedObserver = [false, false, false, false, false, false, false, false, false]; 76 77 // 2 would probably be a good enough test, we arbitrarily choose 4. 78 var kNumFrameUpdatesToExpect = 5; 79 80 function runTest() { 81 let numImgsInDoc = document.getElementsByTagName("img").length; 82 ok(gImgs.length == numImgsInDoc, "gImgs missing img"); 83 ok(gNumFrameUpdates.length == numImgsInDoc, "gNumFrameUpdates missing img"); 84 ok(gLastSnapShot.length == numImgsInDoc, "gLastSnapShot missing img"); 85 ok(gNumSnapShotChanges.length == numImgsInDoc, "gNumSnapShotChanges missing img"); 86 ok(gRemovedObserver.length == numImgsInDoc, "gRemovedObserver missing img"); 87 88 var animatedDiscardable = 89 SpecialPowers.getBoolPref('image.mem.animated.discardable'); 90 if (!animatedDiscardable) { 91 ok(true, "discarding of animated images is disabled, nothing to test"); 92 SimpleTest.finish(); 93 return; 94 } 95 96 setTimeout(step2, 0); 97 } 98 99 function step2() { 100 // Draw the images to canvas to force them to be decoded. 101 for (let i = 0; i < gImgs.length; i++) { 102 drawCanvas(document.getElementById(gImgs[i])); 103 } 104 105 for (let i = 0; i < gImgs.length; i++) { 106 addCallbacks(document.getElementById(gImgs[i]), i); 107 } 108 109 setTimeout(step3, 0); 110 } 111 112 function step3() { 113 document.getElementById("container").style.display = "none"; 114 document.documentElement.offsetLeft; // force that style to take effect 115 116 for (var i = 0; i < gImgs.length; i++) { 117 requestDiscard(document.getElementById(gImgs[i])); 118 } 119 120 // the discard observers will call step4 121 } 122 123 function step4() { 124 gCountingFrameUpdates = true; 125 document.getElementById("container").style.display = ""; 126 127 // Draw the images to canvas to force them to be decoded again. 128 for (var i = 0; i < gImgs.length; i++) { 129 drawCanvas(document.getElementById(gImgs[i])); 130 } 131 } 132 133 function checkIfFinished() { 134 if (gFinished) { 135 return; 136 } 137 138 if ((gNumFrameUpdates[0] >= kNumFrameUpdatesToExpect) && 139 (gNumFrameUpdates[1] >= kNumFrameUpdatesToExpect) && 140 (gNumFrameUpdates[2] >= kNumFrameUpdatesToExpect) && 141 (gNumFrameUpdates[3] >= kNumFrameUpdatesToExpect) && 142 (gNumFrameUpdates[4] >= kNumFrameUpdatesToExpect) && 143 (gNumSnapShotChanges[0] >= kNumFrameUpdatesToExpect) && 144 (gNumSnapShotChanges[1] >= kNumFrameUpdatesToExpect) && 145 (gNumSnapShotChanges[2] >= kNumFrameUpdatesToExpect) && 146 (gNumSnapShotChanges[3] >= kNumFrameUpdatesToExpect) && 147 (gNumSnapShotChanges[4] >= kNumFrameUpdatesToExpect)) { 148 ok(true, "got expected frame updates"); 149 gFinished = true; 150 SimpleTest.finish(); 151 } 152 } 153 154 // arrayIndex is the index into the arrays gNumFrameUpdates and gNumDecodes 155 // to increment when a frame update notification is received. 156 function addCallbacks(anImage, arrayIndex) { 157 var observer = new ImageDecoderObserverStub(); 158 observer.discard = function () { 159 gNumDiscards++; 160 ok(true, "got image discard"); 161 if (arrayIndex >= 5) { 162 // The last four images are finite, so we don't expect any frame updates, 163 // this image is done the test, so remove the observer. 164 if (!gRemovedObserver[arrayIndex]) { 165 gRemovedObserver[arrayIndex] = true; 166 imgLoadingContent.removeObserver(scriptedObserver); 167 } 168 } 169 if (gNumDiscards == gImgs.length) { 170 step4(); 171 } 172 }; 173 observer.frameUpdate = function () { 174 if (!gCountingFrameUpdates) { 175 return; 176 } 177 178 // Do this off a setTimeout since nsImageLoadingContent uses a scriptblocker 179 // when it notifies us and calling drawWindow can call will paint observers 180 // which can dispatch a scrollport event, and events assert if dispatched 181 // when there is a scriptblocker. 182 setTimeout(function () { 183 gNumFrameUpdates[arrayIndex]++; 184 185 var r = document.getElementById(gImgs[arrayIndex]).getBoundingClientRect(); 186 var snapshot = snapshotRect(window, r, "rgba(0,0,0,0)"); 187 if (gLastSnapShot[arrayIndex] != null) { 188 if (snapshot.toDataURL() != gLastSnapShot[arrayIndex].toDataURL()) { 189 gNumSnapShotChanges[arrayIndex]++; 190 } 191 } 192 gLastSnapShot[arrayIndex] = snapshot; 193 194 if (gNumFrameUpdates[arrayIndex] >= kNumFrameUpdatesToExpect && 195 gNumSnapShotChanges[arrayIndex] >= kNumFrameUpdatesToExpect) { 196 if (!gRemovedObserver[arrayIndex]) { 197 gRemovedObserver[arrayIndex] = true; 198 imgLoadingContent.removeObserver(scriptedObserver); 199 } 200 } 201 if (!gFinished) { 202 // because we do this in a setTimeout we can have several in flight 203 // so don't call ok if we've already finished. 204 ok(true, "got frame update"); 205 } 206 checkIfFinished(); 207 }, 0); 208 }; 209 observer = SpecialPowers.wrapCallbackObject(observer); 210 211 var scriptedObserver = SpecialPowers.Cc["@mozilla.org/image/tools;1"] 212 .getService(SpecialPowers.Ci.imgITools) 213 .createScriptedObserver(observer); 214 215 var imgLoadingContent = SpecialPowers.wrap(anImage); 216 imgLoadingContent.addObserver(scriptedObserver); 217 } 218 219 function requestDiscard(anImage) { 220 var request = SpecialPowers.wrap(anImage) 221 .getRequest(SpecialPowers.Ci.nsIImageLoadingContent.CURRENT_REQUEST); 222 setTimeout(() => request.requestDiscard(), 0); 223 } 224 225 function drawCanvas(anImage) { 226 var canvas = document.getElementById('canvas'); 227 var context = canvas.getContext('2d'); 228 229 context.clearRect(0,0,100,100); 230 var cleared = canvas.toDataURL(); 231 232 context.drawImage(anImage, 0, 0); 233 ok(true, "we got through the drawImage call without an exception being thrown"); 234 235 ok(cleared != canvas.toDataURL(), "drawImage drew something"); 236 } 237 238 </script> 239 </pre> 240 </body> 241 </html>