Bug 523950 - Part 10. Add mochitest for when we discard frames from an animated image. r=tnikkel
authorAndrew Osmond <aosmond@mozilla.com>
Wed, 28 Feb 2018 13:34:53 -0500
changeset 761241 58a2ebce44c3861da96b968fcbbf7ae6ef5e3da6
parent 761240 5bf2391a0e6ab6ce75e842aaf6cee9c15c010078
child 761242 d443027a615ce256f2ddd67449ff918d9fad8743
push id100926
push userrwood@mozilla.com
push dateWed, 28 Feb 2018 21:51:29 +0000
reviewerstnikkel
bugs523950
milestone60.0a1
Bug 523950 - Part 10. Add mochitest for when we discard frames from an animated image. r=tnikkel With the previous parts, for large animated images, we will now discard previous frames after we reach the threshold. This mochitest configures a very low threshold, such that it will trigger on a small animated image. It then verifies that we are already to loop the animation a couple of times.
image/test/mochitest/mochitest.ini
image/test/mochitest/rainbow.gif
image/test/mochitest/test_discardFramesAnimatedImage.html
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -65,16 +65,17 @@ support-files =
   keep.gif
   keep.png
   lime100x100.svg
   lime-anim-100x100.svg
   lime-anim-100x100-2.svg
   lime-css-anim-100x100.svg
   opaque.bmp
   purple.gif
+  rainbow.gif
   red.gif
   red.png
   ref-iframe.html
   restore-previous.gif
   restore-previous.png
   rillybad.jpg
   schrep.png
   shaver.png
@@ -128,16 +129,17 @@ skip-if = os == 'android'
 [test_bug1325080.html]
 [test_bullet_animation.html]
 skip-if = os == 'android'
 [test_changeOfSource.html]
 skip-if = os == 'android'
 [test_changeOfSource2.html]
 skip-if = os == 'android'
 [test_discardAnimatedImage.html]
+[test_discardFramesAnimatedImage.html]
 [test_drawDiscardedImage.html]
 [test_error_events.html]
 [test_image_crossorigin_data_url.html]
 [test_ImageContentLoaded.html]
 [test_has_transparency.html]
 skip-if = os == 'android'
 [test_net_failedtoprocess.html]
 skip-if = os == 'android'
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a247a80df043d9e0e7ecb0ede468c8774522e72e
GIT binary patch
literal 1572
zc${<hbhEHbOkqf2_`tyMABg_{SNzGsAi}`Fp!lEL&ow02*)hP?NY8+o5hCQ7o0y*J
zo0y)NoXwk_n46nuYoKRhYGP{2paWFE05XYzsko(o<>|Nli|1^))xG)Np5Od!k36P5
z>s<Eg)V6or$3OX8|JM8b*SYWi_<6Y6k1YDw;iEPE%(Bm%y<W?&toqt@Hf#Hxb>F%9
zx{p8E^s~or_4PN~e)IO9e*R_G-@fy&zyI0ypI<`5!XqN1qGQI29Vc$Q_`#7G0dyR~
x-9$LDp#kjKA>q{j|A&lI8HSKkK`tE{PW?Z0oC<d75OFFfXAKdjHVm9otpRsT9_0W4
new file mode 100644
--- /dev/null
+++ b/image/test/mochitest/test_discardFramesAnimatedImage.html
@@ -0,0 +1,268 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=523950
+-->
+<head>
+  <title>Test that animated images can discard frames and redecode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" 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=523950">Mozilla Bug 523950</a>
+<p id="display"></p>
+<div id="content">
+  <div id="container">
+    <canvas id="canvas" width="100" height="100"></canvas>
+    <img id="rainbow.gif"/>
+  </div>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 523950. **/
+SimpleTest.waitForExplicitFinish();
+
+var gFinished = false;
+
+var gNumOnloads = 0;
+
+var gNumDiscards = 0;
+
+window.onload = function() {
+  // Enable minimal frame discard thresholds for the test.
+  SpecialPowers.pushPrefEnv({
+    'set':[['image.animated.decode-on-demand.threshold-kb',0],
+           ['image.animated.decode-on-demand.batch-size',1],
+           ['image.mem.discardable',true],
+           ['image.mem.animated.discardable',true]]
+  }, runTest);
+}
+
+var gImgs = ['rainbow.gif'];
+// 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];
+// The last snapshot of the image. Used to check that the image actually changes.
+var gLastSnapShot = [null];
+// Number of observed changes in the snapshot.
+var gNumSnapShotChanges = [0];
+// If we've removed the observer.
+var gRemovedObserver = [false];
+
+// rainbow.gif has 9 frames, so we choose arbitrarily 22 to include two loops.
+var kNumFrameUpdatesToExpect = 22;
+
+function runTest() {
+  var thresholdKb =
+    SpecialPowers.getIntPref('image.animated.decode-on-demand.threshold-kb');
+  var batchSize =
+    SpecialPowers.getIntPref('image.animated.decode-on-demand.batch-size');
+  var discardable =
+    SpecialPowers.getBoolPref('image.mem.discardable');
+  var animDiscardable =
+    SpecialPowers.getBoolPref('image.mem.animated.discardable');
+  if (thresholdKb > 0 || batchSize > 1 || !discardable || !animDiscardable) {
+    ok(true, "discarding frames of animated images is disabled, nothing to test");
+    SimpleTest.finish();
+    return;
+  }
+
+  setTimeout(step2, 0);
+}
+
+function step2() {
+  // Only set the src after setting the pref.
+  for (var i = 0; i < gImgs.length; i++) {
+    var elm = document.getElementById(gImgs[i]);
+    elm.src = gImgs[i];
+    elm.onload = checkIfLoaded;
+  }
+}
+
+function step3() {
+  // Draw the images to canvas to force them to be decoded.
+  for (var i = 0; i < gImgs.length; i++) {
+    drawCanvas(document.getElementById(gImgs[i]));
+  }
+
+  for (var i = 0; i < gImgs.length; i++) {
+    addCallbacks(document.getElementById(gImgs[i]), i);
+  }
+
+  setTimeout(step4, 0);
+}
+
+function step4() {
+  ok(true, "now accepting frame updates");
+  gCountingFrameUpdates = true;
+}
+
+function step5() {
+  ok(true, "discarding images");
+
+  document.getElementById("container").style.display = "none";
+  document.documentElement.offsetLeft; // force that style to take effect
+
+  // Reset our state to let us do it all again after discarding.
+  resetState();
+
+  // Draw the images to canvas to force them to be decoded.
+  for (var i = 0; i < gImgs.length; i++) {
+    requestDiscard(document.getElementById(gImgs[i]));
+  }
+
+  // the discard observers will call step6
+}
+
+function step6() {
+  // Repeat the cycle now that we discarded everything.
+  ok(gNumDiscards >= gImgs.length, "discard complete, restarting animations");
+  document.getElementById("container").style.display = "";
+
+  // Draw the images to canvas to force them to be decoded.
+  for (var i = 0; i < gImgs.length; i++) {
+    drawCanvas(document.getElementById(gImgs[i]));
+  }
+
+  setTimeout(step4, 0);
+}
+
+function checkIfLoaded() {
+  ++gNumOnloads;
+  if (gNumOnloads != gImgs.length) {
+    return;
+  }
+
+  ok(true, "got onloads");
+  setTimeout(step3, 0);
+}
+
+function resetState() {
+  gFinished = false;
+  gCountingFrameUpdates = false;
+  for (var i = 0; i < gNumFrameUpdates.length; ++i) {
+    gNumFrameUpdates[i] = 0;
+  }
+  for (var i = 0; i < gNumSnapShotChanges.length; ++i) {
+    gNumSnapShotChanges[i] = 0;
+  }
+  for (var i = 0; i < gLastSnapShot.length; ++i) {
+    gLastSnapShot[i] = null;
+  }
+}
+
+function checkIfFinished() {
+  if (gFinished) {
+    return;
+  }
+
+  for (var i = 0; i < gNumFrameUpdates.length; ++i) {
+    if (gNumFrameUpdates[i] < kNumFrameUpdatesToExpect ||
+        gNumSnapShotChanges[i] < kNumFrameUpdatesToExpect) {
+      return;
+    }
+  }
+
+  ok(true, "got expected frame updates");
+  gFinished = true;
+
+  if (gNumDiscards == 0) {
+    // If we haven't discarded any complete images, we should do so, and
+    // verify the animation again.
+    setTimeout(step5, 0);
+    return;
+  }
+
+  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 (gNumDiscards == gImgs.length) {
+      step6();
+    }
+  };
+  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 &&
+	  gNumDiscards >= gImgs.length) {
+        if (!gRemovedObserver[arrayIndex]) {
+	  ok(true, "removing observer for " + 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>
+