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 idunknown
push userunknown
push dateunknown
bugs716140
milestone22.0a1
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