<!DOCTYPE HTML> <html> <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=686905 --> <head> <title>Test that animated images can be discarded</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <script src="/tests/SimpleTest/WindowSnapshot.js"></script> <script type="text/javascript" src="imgutils.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=686905">Mozilla Bug 686905</a> <p id="display"></p> <div id="content"> <div id="container"> <canvas id="canvas" width="100" height="100"></canvas> <img id="infinitepng" src="infinite-apng.png"> <img id="infinitegif" src="animated1.gif"> <img id="infinitewebp" src="infinite.webp"> <img id="infiniteavif" src="infinite.avif"> <img id="finitepng" src="restore-previous.png"> <img id="finitegif" src="animated-gif.gif"> <img id="finitewebp" src="keep.webp"> <img id="finiteavif" src="animated-avif.avif"> </div> </div> <pre id="test"> <script class="testbody" type="text/javascript"> /** Test for Bug 686905. **/ SimpleTest.waitForExplicitFinish(); var gFinished = false; var gNumDiscards = 0; window.onload = function() { // Enable discarding for the test. SpecialPowers.pushPrefEnv({ 'set':[['image.mem.discardable',true], ['image.avif.sequence.enabled',true]] }, runTest); } var gImgs = ['infinitepng', 'infinitegif', 'infinitewebp', 'infiniteavif', 'finitepng', 'finitegif', 'finitewebp', 'finiteavif']; // If we are currently counting frame updates. var gCountingFrameUpdates = false; // The number of frame update notifications for the images in gImgs that happen // after discarding. (The last two images are finite looping so we don't expect // them to get incremented but it's possible if they don't finish their // animation before we discard them.) var gNumFrameUpdates = [0, 0, 0, 0, 0, 0]; // The last snapshot of the image. Used to check that the image actually changes. var gLastSnapShot = [null, null, null, null, null, null]; // Number of observed changes in the snapshot. var gNumSnapShotChanges = [0, 0, 0, 0, 0, 0]; // If we've removed the observer. var gRemovedObserver = [false, false, false, false, false, false]; // 2 would probably be a good enough test, we arbitrarily choose 4. var kNumFrameUpdatesToExpect = 4; function runTest() { var animatedDiscardable = SpecialPowers.getBoolPref('image.mem.animated.discardable'); if (!animatedDiscardable) { ok(true, "discarding of animated images is disabled, nothing to test"); SimpleTest.finish(); return; } setTimeout(step2, 0); } function step2() { // Draw the images to canvas to force them to be decoded. for (let i = 0; i < gImgs.length; i++) { drawCanvas(document.getElementById(gImgs[i])); } for (let i = 0; i < gImgs.length; i++) { addCallbacks(document.getElementById(gImgs[i]), i); } setTimeout(step3, 0); } function step3() { document.getElementById("container").style.display = "none"; document.documentElement.offsetLeft; // force that style to take effect for (var i = 0; i < gImgs.length; i++) { requestDiscard(document.getElementById(gImgs[i])); } // the discard observers will call step4 } function step4() { gCountingFrameUpdates = true; document.getElementById("container").style.display = ""; // Draw the images to canvas to force them to be decoded again. for (var i = 0; i < gImgs.length; i++) { drawCanvas(document.getElementById(gImgs[i])); } } function checkIfFinished() { if (gFinished) { return; } if ((gNumFrameUpdates[0] >= kNumFrameUpdatesToExpect) && (gNumFrameUpdates[1] >= kNumFrameUpdatesToExpect) && (gNumFrameUpdates[2] >= kNumFrameUpdatesToExpect) && (gNumSnapShotChanges[0] >= kNumFrameUpdatesToExpect) && (gNumSnapShotChanges[1] >= kNumFrameUpdatesToExpect) && (gNumSnapShotChanges[2] >= kNumFrameUpdatesToExpect)) { ok(true, "got expected frame updates"); gFinished = true; SimpleTest.finish(); } } // arrayIndex is the index into the arrays gNumFrameUpdates and gNumDecodes // to increment when a frame update notification is received. function addCallbacks(anImage, arrayIndex) { var observer = new ImageDecoderObserverStub(); observer.discard = function () { gNumDiscards++; ok(true, "got image discard"); if (arrayIndex >= 3) { // The last two images are finite, so we don't expect any frame updates, // this image is done the test, so remove the observer. if (!gRemovedObserver[arrayIndex]) { gRemovedObserver[arrayIndex] = true; imgLoadingContent.removeObserver(scriptedObserver); } } if (gNumDiscards == gImgs.length) { step4(); } }; observer.frameUpdate = function () { if (!gCountingFrameUpdates) { return; } // Do this off a setTimeout since nsImageLoadingContent uses a scriptblocker // when it notifies us and calling drawWindow can call will paint observers // which can dispatch a scrollport event, and events assert if dispatched // when there is a scriptblocker. setTimeout(function () { gNumFrameUpdates[arrayIndex]++; var r = document.getElementById(gImgs[arrayIndex]).getBoundingClientRect(); var snapshot = snapshotRect(window, r, "rgba(0,0,0,0)"); if (gLastSnapShot[arrayIndex] != null) { if (snapshot.toDataURL() != gLastSnapShot[arrayIndex].toDataURL()) { gNumSnapShotChanges[arrayIndex]++; } } gLastSnapShot[arrayIndex] = snapshot; if (gNumFrameUpdates[arrayIndex] >= kNumFrameUpdatesToExpect && gNumSnapShotChanges[arrayIndex] >= kNumFrameUpdatesToExpect) { if (!gRemovedObserver[arrayIndex]) { gRemovedObserver[arrayIndex] = true; imgLoadingContent.removeObserver(scriptedObserver); } } if (!gFinished) { // because we do this in a setTimeout we can have several in flight // so don't call ok if we've already finished. ok(true, "got frame update"); } checkIfFinished(); }, 0); }; observer = SpecialPowers.wrapCallbackObject(observer); var scriptedObserver = SpecialPowers.Cc["@mozilla.org/image/tools;1"] .getService(SpecialPowers.Ci.imgITools) .createScriptedObserver(observer); var imgLoadingContent = SpecialPowers.wrap(anImage); imgLoadingContent.addObserver(scriptedObserver); } function requestDiscard(anImage) { var request = SpecialPowers.wrap(anImage) .getRequest(SpecialPowers.Ci.nsIImageLoadingContent.CURRENT_REQUEST); setTimeout(() => request.requestDiscard(), 0); } function drawCanvas(anImage) { var canvas = document.getElementById('canvas'); var context = canvas.getContext('2d'); context.clearRect(0,0,100,100); var cleared = canvas.toDataURL(); context.drawImage(anImage, 0, 0); ok(true, "we got through the drawImage call without an exception being thrown"); ok(cleared != canvas.toDataURL(), "drawImage drew something"); } </script> </pre> </body> </html>