Bug 716140 - Make animated image formats (PNG and GIF) explicitly pause decoding and ask for a new image frame when they need new frames.
authorJoe Drew <joe@drew.ca>
Mon, 28 Jan 2013 12:27:35 -0500
changeset 136031 682938749810c2effd0aede42b7a25c7a055133b
parent 136030 c86eb1ff89168e43b8668fc41f3e22f3c0eb5fbd
child 136032 16cd97be284693480058cddb76a9d73f9272cc02
push id2452
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 16:59:38 +0000
treeherdermozilla-beta@d4b152d29d8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs716140
milestone22.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 716140 - Make animated image formats (PNG and GIF) explicitly pause decoding and ask for a new image frame when they need new frames.
image/decoders/GIF2.h
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsGIFDecoder2.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/src/Decoder.cpp
image/src/Decoder.h
layout/media/symbols.def.in
--- a/image/decoders/GIF2.h
+++ b/image/decoders/GIF2.h
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef _GIF_H_
 #define _GIF_H_
 
 #define MAX_LZW_BITS          12
 #define MAX_BITS            4097 /* 2^MAX_LZW_BITS+1 */
 #define MAX_COLORS           256
-#define MAX_HOLD_SIZE        256
+#define MIN_HOLD_SIZE        256
 
 enum { GIF_TRAILER                     = 0x3B }; //';'
 enum { GIF_IMAGE_SEPARATOR             = 0x2C }; //','
 enum { GIF_EXTENSION_INTRODUCER        = 0x21 }; //'!'
 enum { GIF_GRAPHIC_CONTROL_LABEL       = 0xF9 };
 enum { GIF_COMMENT_LABEL               = 0xFE };
 enum { GIF_PLAIN_TEXT_LABEL            = 0x01 };
 enum { GIF_APPLICATION_EXTENSION_LABEL = 0xFF };
@@ -23,16 +23,17 @@ enum { GIF_APPLICATION_EXTENSION_LABEL =
 */
 // List of possible parsing states
 typedef enum {
     gif_type,
     gif_global_header,
     gif_global_colormap,
     gif_image_start,
     gif_image_header,
+    gif_image_header_continue,
     gif_image_colormap,
     gif_image_body,
     gif_lzw_start,
     gif_lzw,
     gif_sub_block,
     gif_extension,
     gif_control_extension,
     gif_consume_block,
@@ -90,17 +91,17 @@ typedef struct gif_struct {
     int loop_count;             /* Netscape specific extension block to control
                                    the number of animation loops a GIF renders. */
 
     bool progressive_display;    /* If TRUE, do Haeberli interlace hack */
     bool interlaced;             /* TRUE, if scanlines arrive interlaced order */
     bool is_transparent;         /* TRUE, if tpixel is valid */
 
     uint16_t  prefix[MAX_BITS];          /* LZW decoding tables */
-    uint8_t   hold[MAX_HOLD_SIZE];       /* Accumulation buffer */
+    uint8_t*  hold;                      /* Accumulation buffer */
     uint32_t  global_colormap[MAX_COLORS];   /* Default colormap if local not supplied */
     uint8_t   suffix[MAX_BITS];          /* LZW decoding tables */
     uint8_t   stack[MAX_BITS];           /* Base of LZW decoder stack */
 
 } gif_struct;
 
 #endif
 
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -50,18 +50,16 @@ mailing address.
 #include <algorithm>
 
 namespace mozilla {
 namespace image {
 
 /*
  * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's'
  *
- * Note, the hold will never need to be bigger than 256 bytes to gather up in the hold,
- * as each GIF block (except colormaps) can never be bigger than 256 bytes.
  * Colormaps are directly copied in the resp. global_colormap or the local_colormap of the PAL image frame
  * So a fixed buffer in gif_struct is good enough.
  * This buffer is only needed to copy left-over data from one GifWrite call to the next
  */
 #define GETN(n,s)                      \
   PR_BEGIN_MACRO                       \
     mGIFStruct.bytes_to_consume = (n); \
     mGIFStruct.state = (s);            \
@@ -91,19 +89,18 @@ nsGIFDecoder2::nsGIFDecoder2(RasterImage
 
   // Start with the version (GIF89a|GIF87a)
   mGIFStruct.state = gif_type;
   mGIFStruct.bytes_to_consume = 6;
 }
 
 nsGIFDecoder2::~nsGIFDecoder2()
 {
-  if (mGIFStruct.local_colormap) {
-    moz_free(mGIFStruct.local_colormap);
-  }
+  moz_free(mGIFStruct.local_colormap);
+  moz_free(mGIFStruct.hold);
 }
 
 void
 nsGIFDecoder2::FinishInternal()
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!");
 
   // If the GIF got cut off, handle it anyway
@@ -159,71 +156,39 @@ void nsGIFDecoder2::BeginGIF()
   PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
 
   // If we're doing a size decode, we have what we came for
   if (IsSizeDecode())
     return;
 }
 
 //******************************************************************************
-nsresult nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
+void nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
 {
-  uint32_t imageDataLength;
-  nsresult rv;
   gfxASurface::gfxImageFormat format;
   if (mGIFStruct.is_transparent)
     format = gfxASurface::ImageFormatARGB32;
   else
     format = gfxASurface::ImageFormatRGB24;
 
   // Use correct format, RGB for first frame, PAL for following frames
   // and include transparency to allow for optimization of opaque images
   if (mGIFStruct.images_decoded) {
     // Image data is stored with original depth and palette
-    rv = mImage.EnsureFrame(mGIFStruct.images_decoded,
-                            mGIFStruct.x_offset, mGIFStruct.y_offset,
-                            mGIFStruct.width, mGIFStruct.height,
-                            format, aDepth, &mImageData, &imageDataLength,
-                            &mColormap, &mColormapSize);
-
-    // While EnsureFrame can reuse frames, we unconditionally increment
-    // mGIFStruct.images_decoded when we're done with a frame, so we both can
-    // and need to zero out the colormap and image data after every call to
-    // EnsureFrame.
-    if (NS_SUCCEEDED(rv) && mColormap) {
-      memset(mColormap, 0, mColormapSize);
-    }
+    NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
+                 mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
+                 format, aDepth);
   } else {
     // Regardless of depth of input, image is decoded into 24bit RGB
-    rv = mImage.EnsureFrame(mGIFStruct.images_decoded,
-                            mGIFStruct.x_offset, mGIFStruct.y_offset,
-                            mGIFStruct.width, mGIFStruct.height,
-                            format, &mImageData, &imageDataLength);
-  }
-
-  if (NS_FAILED(rv))
-    return rv;
-
-  memset(mImageData, 0, imageDataLength);
-
-  // Tell the superclass we're starting a frame
-  PostFrameStart();
-
-  if (!mGIFStruct.images_decoded) {
-    // Send a onetime invalidation for the first frame if it has a y-axis offset. 
-    // Otherwise, the area may never be refreshed and the placeholder will remain
-    // on the screen. (Bug 37589)
-    if (mGIFStruct.y_offset > 0) {
-      nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
-      PostInvalidation(r);
-    }
+    NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
+                 mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
+                 format);
   }
 
   mCurrentFrame = mGIFStruct.images_decoded;
-  return NS_OK;
 }
 
 
 //******************************************************************************
 void nsGIFDecoder2::EndImageFrame()
 {
   RasterImage::FrameAlpha alpha = RasterImage::kFrameHasAlpha;
 
@@ -580,43 +545,47 @@ nsGIFDecoder2::WriteInternal(const char 
   const uint8_t *q = buf;
 
   // Add what we have sofar to the block
   // If previous call to me left something in the hold first complete current block
   // Or if we are filling the colormaps, first complete the colormap
   uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap :
                (mGIFStruct.state == gif_image_colormap) ? (uint8_t*)mColormap :
                (mGIFStruct.bytes_in_hold) ? mGIFStruct.hold : nullptr;
-  if (p) {
+
+  if (len == 0 && buf == nullptr) {
+    // We've just gotten the frame we asked for. Time to use the data we
+    // stashed away.
+    len = mGIFStruct.bytes_in_hold;
+    q = buf = p;
+  } else if (p) {
     // Add what we have sofar to the block
     uint32_t l = std::min(len, mGIFStruct.bytes_to_consume);
     memcpy(p+mGIFStruct.bytes_in_hold, buf, l);
 
     if (l < mGIFStruct.bytes_to_consume) {
       // Not enough in 'buf' to complete current block, get more
       mGIFStruct.bytes_in_hold += l;
       mGIFStruct.bytes_to_consume -= l;
       return;
     }
-    // Reset hold buffer count
-    mGIFStruct.bytes_in_hold = 0;
     // Point 'q' to complete block in hold (or in colormap)
     q = p;
   }
 
   // Invariant:
   //    'q' is start of current to be processed block (hold, colormap or buf)
   //    'bytes_to_consume' is number of bytes to consume from 'buf'
   //    'buf' points to the bytes to be consumed from the input buffer
   //    'len' is number of bytes left in input buffer from position 'buf'.
   //    At entrance of the for loop will 'buf' will be moved 'bytes_to_consume'
   //    to point to next buffer, 'len' is adjusted accordingly.
   //    So that next round in for loop, q gets pointed to the next buffer.
 
-  for (;len >= mGIFStruct.bytes_to_consume; q=buf) {
+  for (;len >= mGIFStruct.bytes_to_consume; q=buf, mGIFStruct.bytes_in_hold = 0) {
     // Eat the current block from the buffer, q keeps pointed at current block
     buf += mGIFStruct.bytes_to_consume;
     len -= mGIFStruct.bytes_to_consume;
 
     switch (mGIFStruct.state)
     {
     case gif_lzw:
       if (!DoLzw(q)) {
@@ -921,20 +890,50 @@ nsGIFDecoder2::WriteInternal(const char 
       if (q[8] & 0x80)
         depth = (q[8]&0x07) + 1;
       uint32_t realDepth = depth;
       while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) {
         realDepth++;
       } 
       // Mask to limit the color values within the colormap
       mColorMask = 0xFF >> (8 - realDepth);
-      nsresult rv = BeginImageFrame(realDepth);
-      if (NS_FAILED(rv) || !mImageData) {
-        mGIFStruct.state = gif_error;
-        break;
+      BeginImageFrame(realDepth);
+
+      // We now need a new frame from the decoder framework. We leave all our
+      // data in the buffer as if it wasn't consumed, copy to our hold and return
+      // to the decoder framework.
+      uint32_t size = len + mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold;
+      if (size) {
+        if (SetHold(q, mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold, buf, len)) {
+          // Back into the decoder infrastructure so we can get called again.
+          GETN(9, gif_image_header_continue);
+          return;
+        }
+      }
+    }
+    break;
+
+    case gif_image_header_continue:
+    {
+      // While decoders can reuse frames, we unconditionally increment
+      // mGIFStruct.images_decoded when we're done with a frame, so we both can
+      // and need to zero out the colormap and image data after every new frame.
+      memset(mImageData, 0, mImageDataLength);
+      if (mColormap) {
+        memset(mColormap, 0, mColormapSize);
+      }
+
+      if (!mGIFStruct.images_decoded) {
+        // Send a onetime invalidation for the first frame if it has a y-axis offset. 
+        // Otherwise, the area may never be refreshed and the placeholder will remain
+        // on the screen. (Bug 37589)
+        if (mGIFStruct.y_offset > 0) {
+          nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
+          PostInvalidation(r);
+        }
       }
 
       if (q[8] & 0x40) {
         mGIFStruct.interlaced = true;
         mGIFStruct.ipass = 1;
       } else {
         mGIFStruct.interlaced = false;
         mGIFStruct.ipass = 0;
@@ -943,17 +942,26 @@ nsGIFDecoder2::WriteInternal(const char 
       /* Only apply the Haeberli display hack on the first frame */
       mGIFStruct.progressive_display = (mGIFStruct.images_decoded == 0);
 
       /* Clear state from last image */
       mGIFStruct.irow = 0;
       mGIFStruct.rows_remaining = mGIFStruct.height;
       mGIFStruct.rowp = mImageData;
 
-      /* bits per pixel is q[8]&0x07 */
+      /* Depth of colors is determined by colormap */
+      /* (q[8] & 0x80) indicates local colormap */
+      /* bits per pixel is (q[8]&0x07 + 1) when local colormap is set */
+      uint32_t depth = mGIFStruct.global_colormap_depth;
+      if (q[8] & 0x80)
+        depth = (q[8]&0x07) + 1;
+      uint32_t realDepth = depth;
+      while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) {
+        realDepth++;
+      }
 
       if (q[8] & 0x80) /* has a local colormap? */
       {
         mGIFStruct.local_colormap_size = 1 << depth;
         if (!mGIFStruct.images_decoded) {
           // First frame has local colormap, allocate space for it
           // as the image frame doesn't have its own palette
           mColormapSize = sizeof(uint32_t) << realDepth;
@@ -1022,17 +1030,17 @@ nsGIFDecoder2::WriteInternal(const char 
       } else {
         /* See if there are any more images in this sequence. */
         EndImageFrame();
         GETN(1, gif_image_start);
       }
       break;
 
     case gif_done:
-      PostDecodeDone();
+      PostDecodeDone(mGIFStruct.loop_count - 1);
       mGIFOpen = false;
       goto done;
 
     case gif_error:
       PostDataError();
       return;
 
     // We shouldn't ever get here.
@@ -1041,39 +1049,67 @@ nsGIFDecoder2::WriteInternal(const char 
     }
   }
 
   // if an error state is set but no data remains, code flow reaches here
   if (mGIFStruct.state == gif_error) {
       PostDataError();
       return;
   }
-  
+
   // Copy the leftover into mGIFStruct.hold
-  mGIFStruct.bytes_in_hold = len;
   if (len) {
     // Add what we have sofar to the block
-    uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap :
-                 (mGIFStruct.state == gif_image_colormap) ? (uint8_t*)mColormap :
-                 mGIFStruct.hold;
-    memcpy(p, buf, len);
+    if (mGIFStruct.state != gif_global_colormap && mGIFStruct.state != gif_image_colormap) {
+      if (!SetHold(buf, len)) {
+        PostDataError();
+        return;
+      }
+    } else {
+      uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap :
+                                                               (uint8_t*)mColormap;
+      memcpy(p, buf, len);
+      mGIFStruct.bytes_in_hold = len;
+    }
+
     mGIFStruct.bytes_to_consume -= len;
   }
 
 // We want to flush before returning if we're on the first frame
 done:
   if (!mGIFStruct.images_decoded) {
     FlushImageData();
     mLastFlushedRow = mCurrentRow;
     mLastFlushedPass = mCurrentPass;
   }
 
   return;
 }
 
+bool
+nsGIFDecoder2::SetHold(const uint8_t* buf1, uint32_t count1, const uint8_t* buf2 /* = nullptr */, uint32_t count2 /* = 0 */)
+{
+  // We have to handle the case that buf currently points to hold
+  uint8_t* newHold = (uint8_t *) moz_malloc(std::max(uint32_t(MIN_HOLD_SIZE), count1 + count2));
+  if (!newHold) {
+    mGIFStruct.state = gif_error;
+    return false;
+  }
+
+  memcpy(newHold, buf1, count1);
+  if (buf2) {
+    memcpy(newHold + count1, buf2, count2);
+  }
+
+  moz_free(mGIFStruct.hold);
+  mGIFStruct.hold = newHold;
+  mGIFStruct.bytes_in_hold = count1 + count2;
+  return true;
+}
+
 Telemetry::ID
 nsGIFDecoder2::SpeedHistogram()
 {
   return Telemetry::IMAGE_DECODE_SPEED_GIF;
 }
 
 
 } // namespace image
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -30,24 +30,26 @@ public:
   virtual void FinishInternal();
   virtual Telemetry::ID SpeedHistogram();
 
 private:
   /* These functions will be called when the decoder has a decoded row,
    * frame size information, etc. */
 
   void      BeginGIF();
-  nsresult  BeginImageFrame(uint16_t aDepth);
+  void      BeginImageFrame(uint16_t aDepth);
   void      EndImageFrame();
   void      FlushImageData();
   void      FlushImageData(uint32_t fromRow, uint32_t rows);
 
   nsresult  GifWrite(const uint8_t * buf, uint32_t numbytes);
   uint32_t  OutputRow();
   bool      DoLzw(const uint8_t *q);
+  bool      SetHold(const uint8_t* buf, uint32_t count,
+                    const uint8_t* buf2 = nullptr, uint32_t count2 = 0);
 
   inline int ClearCode() const { return 1 << mGIFStruct.datasize; }
 
   int32_t mCurrentRow;
   int32_t mLastFlushedRow;
 
   uint32_t mOldColor;        // The old value of the transparent pixel
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -143,31 +143,23 @@ nsPNGDecoder::~nsPNGDecoder()
     nsMemory::Free(mHeaderBuf);
 }
 
 // CreateFrame() is used for both simple and animated images
 void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                                int32_t width, int32_t height,
                                gfxASurface::gfxImageFormat format)
 {
-  uint32_t imageDataLength;
-  nsresult rv = mImage.EnsureFrame(GetFrameCount(), x_offset, y_offset,
-                                   width, height, format,
-                                   &mImageData, &imageDataLength);
-  if (NS_FAILED(rv))
-    longjmp(png_jmpbuf(mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
+  NeedNewFrame(GetFrameCount(), x_offset, y_offset, width, height, format);
 
   mFrameRect.x = x_offset;
   mFrameRect.y = y_offset;
   mFrameRect.width = width;
   mFrameRect.height = height;
 
-  // Tell the superclass we're starting a frame
-  PostFrameStart();
-
   PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
           "image frame with %dx%d pixels in container %p",
           width, height,
           &mImage));
 
   mFrameHasNoAlpha = true;
 }
@@ -287,20 +279,16 @@ nsPNGDecoder::InitInternal()
                               nsPNGDecoder::row_callback,
                               nsPNGDecoder::end_callback);
 
 }
 
 void
 nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
 {
-  // We use gotos, so we need to declare variables here
-  uint32_t width = 0;
-  uint32_t height = 0;
-
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // If we only want width/height, we don't need to go through libpng
   if (IsSizeDecode()) {
 
     // Are we done?
     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
       return;
@@ -317,18 +305,18 @@ nsPNGDecoder::WriteInternal(const char *
       // Check that the signature bytes are right
       if (memcmp(mHeaderBuf, nsPNGDecoder::pngSignatureBytes, 
                  sizeof(pngSignatureBytes))) {
         PostDataError();
         return;
       }
 
       // Grab the width and height, accounting for endianness (thanks libpng!)
-      width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
-      height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
+      uint32_t width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
+      uint32_t height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
 
       // Too big?
       if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) {
         PostDataError();
         return;
       }
 
       // Post our size to the superclass
@@ -663,17 +651,22 @@ nsPNGDecoder::info_callback(png_structp 
 
   /* Reject any ancillary chunk after IDAT with a bad CRC (bug #397593).
    * It would be better to show the default frame (if one has already been
    * successfully decoded) before bailing, but it's simpler to just bail
    * out with an error message.
    */
   png_set_crc_action(png_ptr, PNG_CRC_NO_CHANGE, PNG_CRC_ERROR_QUIT);
 
-  return;
+  if (!decoder->mFrameIsHidden) {
+    /* We know that we need a new frame, so pause input so the decoder
+     * infrastructure can give it to us.
+     */
+    png_process_data_pause(png_ptr, /* save = */ 1);
+  }
 }
 
 void
 nsPNGDecoder::row_callback(png_structp png_ptr, png_bytep new_row,
                            png_uint_32 row_num, int pass)
 {
   /* libpng comments:
    *
@@ -824,16 +817,21 @@ nsPNGDecoder::frame_info_callback(png_st
   decoder->mFrameIsHidden = false;
 
   x_offset = png_get_next_frame_x_offset(png_ptr, decoder->mInfo);
   y_offset = png_get_next_frame_y_offset(png_ptr, decoder->mInfo);
   width = png_get_next_frame_width(png_ptr, decoder->mInfo);
   height = png_get_next_frame_height(png_ptr, decoder->mInfo);
 
   decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
+
+  /* We know that we need a new frame, so pause input so the decoder
+   * infrastructure can give it to us.
+   */
+  png_process_data_pause(png_ptr, /* save = */ 1);
 #endif
 }
 
 void
 nsPNGDecoder::end_callback(png_structp png_ptr, png_infop info_ptr)
 {
   /* libpng comments:
    *
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -54,17 +54,18 @@ public:
         png_height; // Unused
 
     int png_bit_depth,
         png_color_type;
 
     if (png_get_IHDR(mPNG, mInfo, &png_width, &png_height, &png_bit_depth,
                      &png_color_type, NULL, NULL, NULL)) {
 
-      return (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA &&
+      return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
+               png_color_type == PNG_COLOR_TYPE_RGB) &&
               png_bit_depth == 8);
     } else {
       return false;
     }
   }
 
 public:
   png_structp mPNG;
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -17,16 +17,17 @@ Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
   , mImageData(nullptr)
   , mColormap(nullptr)
   , mDecodeFlags(0)
   , mDecodeDone(false)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
+  , mNeedsNewFrame(false)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
 {
 }
 
 Decoder::~Decoder()
@@ -77,16 +78,50 @@ Decoder::Write(const char* aBuffer, uint
                     "Not allowed to make more decoder calls after error!");
 
   // If a data error occured, just ignore future data
   if (HasDataError())
     return;
 
   // Pass the data along to the implementation
   WriteInternal(aBuffer, aCount);
+
+  // If the decoder told us that it needs a new frame to proceed, let's create
+  // one and call it again.
+  while (mNeedsNewFrame && !HasDataError()) {
+    nsresult rv;
+    if (mNewFrameData.mPaletteDepth) {
+      rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
+                              mNewFrameData.mOffsetY, mNewFrameData.mWidth,
+                              mNewFrameData.mHeight, mNewFrameData.mFormat,
+                              mNewFrameData.mPaletteDepth,
+                              &mImageData, &mImageDataLength,
+                              &mColormap, &mColormapSize);
+    } else {
+      rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
+                              mNewFrameData.mOffsetY, mNewFrameData.mWidth,
+                              mNewFrameData.mHeight, mNewFrameData.mFormat,
+                              &mImageData, &mImageDataLength);
+    }
+
+    // Release our new frame data before talking to anyone else so they can
+    // tell us if they need yet another.
+    mNeedsNewFrame = false;
+
+    if (NS_SUCCEEDED(rv)) {
+      // We've now created our frame, so be sure we keep track of it correctly.
+      PostFrameStart();
+
+      // Tell the decoder to use the data it saved when it asked for a new frame.
+      WriteInternal(nullptr, 0);
+    } else {
+      PostDataError();
+      break;
+    }
+  }
 }
 
 void
 Decoder::Finish(RasterImage::eShutdownIntent aShutdownIntent)
 {
   // Implementation-specific finalization
   if (!HasError())
     FinishInternal();
@@ -312,10 +347,26 @@ Decoder::PostDecoderError(nsresult aFail
 
   mFailCode = aFailureCode;
 
   // XXXbholley - we should report the image URI here, but imgContainer
   // needs to know its URI first
   NS_WARNING("Image decoding error - This is probably a bug!");
 }
 
+void
+Decoder::NeedNewFrame(uint32_t framenum, uint32_t x_offset, uint32_t y_offset,
+                      uint32_t width, uint32_t height,
+                      gfxASurface::gfxImageFormat format,
+                      uint8_t palette_depth /* = 0 */)
+{
+  // Decoders should never call NeedNewFrame without yielding back to Write().
+  MOZ_ASSERT(!mNeedsNewFrame);
+
+  // We don't want images going back in time or skipping frames.
+  MOZ_ASSERT(framenum == mFrameCount || framenum == (mFrameCount + 1));
+
+  mNewFrameData = NewFrameData(framenum, x_offset, y_offset, width, height, format, palette_depth);
+  mNeedsNewFrame = true;
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -179,16 +179,25 @@ protected:
   // For animated images, specify the loop count. -1 means loop forever, 0
   // means a single iteration, stopping on the last frame.
   void PostDecodeDone(int32_t aLoopCount = 0);
 
   // Data errors are the fault of the source data, decoder errors are our fault
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
+  // This is called by decoders when they need a new frame. These decoders
+  // must then save the data they have been sent but not yet processed and
+  // return from WriteInternal. When the new frame is created, WriteInternal
+  // will be called again with nullptr and 0 as arguments.
+  void NeedNewFrame(uint32_t frameNum, uint32_t x_offset, uint32_t y_offset,
+                    uint32_t width, uint32_t height,
+                    gfxASurface::gfxImageFormat format,
+                    uint8_t palette_depth = 0);
+
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
   RefPtr<imgDecoderObserver> mObserver;
   ImageMetadata mImageMetadata;
 
@@ -203,16 +212,42 @@ protected:
 
 private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
 
   nsresult mFailCode;
 
+  struct NewFrameData
+  {
+    NewFrameData()
+    {}
+
+    NewFrameData(uint32_t num, uint32_t offsetx, uint32_t offsety,
+                 uint32_t width, uint32_t height,
+                 gfxASurface::gfxImageFormat format, uint8_t paletteDepth)
+      : mFrameNum(num)
+      , mOffsetX(offsetx)
+      , mOffsetY(offsety)
+      , mWidth(width)
+      , mHeight(height)
+      , mFormat(format)
+      , mPaletteDepth(paletteDepth)
+    {}
+    uint32_t mFrameNum;
+    uint32_t mOffsetX;
+    uint32_t mOffsetY;
+    uint32_t mWidth;
+    uint32_t mHeight;
+    gfxASurface::gfxImageFormat mFormat;
+    uint8_t mPaletteDepth;
+  };
+  NewFrameData mNewFrameData;
+  bool mNeedsNewFrame;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
 };
 
 } // namespace image
 } // namespace mozilla
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -205,16 +205,17 @@ MOZ_PNG_get_IHDR
 MOZ_PNG_get_iCCP
 MOZ_PNG_get_io_ptr
 MOZ_PNG_get_progressive_ptr
 MOZ_PNG_get_sRGB
 MOZ_PNG_get_tRNS
 MOZ_PNG_get_valid
 MOZ_PNG_longjmp
 MOZ_PNG_process_data
+MOZ_PNG_process_data_pause
 MOZ_PNG_progressive_combine_row
 MOZ_PNG_read_update_info
 MOZ_PNG_set_cHRM
 MOZ_PNG_set_crc_action
 MOZ_PNG_set_gAMA
 MOZ_PNG_set_gamma
 MOZ_PNG_set_gray_to_rgb
 MOZ_PNG_set_expand