tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>