Bug 716140 - Allocate frames asynchronously with a separate worker dispatched to the main thread. r=seth
authorJoe Drew <joe@drew.ca>
Wed, 27 Feb 2013 14:23:08 -0500
changeset 132072 d1f978369c50ff398618c6ce04d1dc33d6883d81
parent 132071 0a2fc100f05ffc7e8b6800c3c4aa69a52906d9c7
child 132073 3255fbc643599dea9feef8a0ffb25e128faca0a4
push idunknown
push userunknown
push dateunknown
reviewersseth
bugs716140
milestone22.0a1
Bug 716140 - Allocate frames asynchronously with a separate worker dispatched to the main thread. r=seth
image/decoders/nsBMPDecoder.cpp
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -128,17 +128,17 @@ nsBMPDecoder::FinishInternal()
 {
     // We shouldn't be called in error cases
     NS_ABORT_IF_FALSE(!HasError(), "Can't call FinishInternal on error!");
 
     // We should never make multiple frames
     NS_ABORT_IF_FALSE(GetFrameCount() <= 1, "Multiple BMP frames?");
 
     // Send notifications if appropriate
-    if (!IsSizeDecode() && (GetFrameCount() == 1)) {
+    if (!IsSizeDecode() && HasSize()) {
 
         // Invalidate
         nsIntRect r(0, 0, mBIH.width, GetHeight());
         PostInvalidation(r);
 
         if (mUseAlphaData) {
           PostFrameStop(RasterImage::kFrameHasAlpha);
         } else {
@@ -220,20 +220,20 @@ nsBMPDecoder::WriteInternal(const char* 
         if (toCopy > aCount)
             toCopy = aCount;
         memcpy(mRawBuf + (mPos - BFH_INTERNAL_LENGTH), aBuffer, toCopy);
         mPos += toCopy;
         aCount -= toCopy;
         aBuffer += toCopy;
     }
 
-    // GetNumFrames is called to ensure that if at this point mPos == mLOH but
+    // HasSize is called to ensure that if at this point mPos == mLOH but
     // we have no data left to process, the next time WriteInternal is called
     // we won't enter this condition again.
-    if (mPos == mLOH && GetFrameCount() == 0) {
+    if (mPos == mLOH && !HasSize()) {
         ProcessInfoHeader();
         PR_LOG(GetBMPLog(), PR_LOG_DEBUG, ("BMP is %lix%lix%lu. compression=%lu\n",
                mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
         // Verify we support this bit depth
         if (mBIH.bpp != 1 && mBIH.bpp != 4 && mBIH.bpp != 8 &&
             mBIH.bpp != 16 && mBIH.bpp != 24 && mBIH.bpp != 32) {
           PostDataError();
           return;
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -213,18 +213,22 @@ nsICODecoder::WriteInternal(const char* 
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   if (IsSizeDecode() && HasSize()) {
     // More data came in since we found the size. We have nothing to do here.
     return;
   }
 
-  if (!aCount) // aCount=0 means EOF
+  if (!aCount) {
+    if (mContainedDecoder) {
+      WriteToContainedDecoder(aBuffer, aCount);
+    }
     return;
+  }
 
   while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
     if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
       if ((*aBuffer != 1) && (*aBuffer != 2)) {
         PostDataError();
         return;
       }
       mIsCursor = (*aBuffer == 2);
@@ -580,10 +584,30 @@ nsICODecoder::ProcessDirEntry(IconDirEnt
   aTarget.mBitCount = LITTLE_TO_NATIVE16(aTarget.mBitCount);
   memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
   aTarget.mBytesInRes = LITTLE_TO_NATIVE32(aTarget.mBytesInRes);
   memcpy(&aTarget.mImageOffset, mDirEntryArray + 12, 
          sizeof(aTarget.mImageOffset));
   aTarget.mImageOffset = LITTLE_TO_NATIVE32(aTarget.mImageOffset);
 }
 
+bool
+nsICODecoder::NeedsNewFrame() const
+{
+  if (mContainedDecoder) {
+    return mContainedDecoder->NeedsNewFrame();
+  }
+
+  return Decoder::NeedsNewFrame();
+}
+
+nsresult
+nsICODecoder::AllocateFrame()
+{
+  if (mContainedDecoder) {
+    return mContainedDecoder->AllocateFrame();
+  }
+
+  return Decoder::AllocateFrame();
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -34,16 +34,18 @@ public:
   // Obtains the height of the icon directory entry
   uint32_t GetRealHeight() const
   {
     return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight; 
   }
 
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
   virtual void FinishInternal();
+  virtual bool NeedsNewFrame() const;
+  virtual nsresult AllocateFrame();
 
 private:
   // Writes to the contained decoder and sets the appropriate errors
   // Returns true if there are no errors.
   bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
 
   // Processes a single dir entry of the icon resource
   void ProcessDirEntry(IconDirEntry& aTarget);
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -115,17 +115,18 @@ nsPNGDecoder::pngSignatureBytes[] = { 13
 
 nsPNGDecoder::nsPNGDecoder(RasterImage &aImage)
  : Decoder(aImage),
    mPNG(nullptr), mInfo(nullptr),
    mCMSLine(nullptr), interlacebuf(nullptr),
    mInProfile(nullptr), mTransform(nullptr),
    mHeaderBuf(nullptr), mHeaderBytesRead(0),
    mChannels(0), mFrameIsHidden(false),
-   mCMSMode(0), mDisablePremultipliedAlpha(false)
+   mCMSMode(0), mDisablePremultipliedAlpha(false),
+   mNumFrames(0)
 {
 }
 
 nsPNGDecoder::~nsPNGDecoder()
 {
   if (mPNG)
     png_destroy_read_struct(&mPNG, mInfo ? &mInfo : NULL, NULL);
   if (mCMSLine)
@@ -143,17 +144,17 @@ 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)
 {
-  NeedNewFrame(GetFrameCount(), x_offset, y_offset, width, height, format);
+  NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
 
   mFrameRect.x = x_offset;
   mFrameRect.y = y_offset;
   mFrameRect.width = width;
   mFrameRect.height = height;
 
   PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
@@ -165,16 +166,18 @@ void nsPNGDecoder::CreateFrame(png_uint_
 }
 
 // set timeout and frame disposal method for the current frame
 void nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden)
     return;
 
+  mNumFrames++;
+
   RasterImage::FrameAlpha alpha;
   if (mFrameHasNoAlpha)
     alpha = RasterImage::kFrameOpaque;
   else
     alpha = RasterImage::kFrameHasAlpha;
 
   AnimFrameInfo animInfo;
 
@@ -784,18 +787,17 @@ nsPNGDecoder::row_callback(png_structp p
       break;
       default:
         longjmp(png_jmpbuf(decoder->mPNG), 1);
     }
 
     if (!rowHasNoAlpha)
       decoder->mFrameHasNoAlpha = false;
 
-    uint32_t numFrames = decoder->GetFrameCount();
-    if (numFrames <= 1) {
+    if (decoder->mNumFrames <= 1) {
       // Only do incremental image display for the first frame
       // XXXbholley - this check should be handled in the superclass
       nsIntRect r(0, row_num, width, 1);
       decoder->PostInvalidation(r);
     }
   }
 }
 
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -84,16 +84,19 @@ public:
 
   uint8_t mChannels;
   bool mFrameHasNoAlpha;
   bool mFrameIsHidden;
 
   // whether CMS or premultiplied alpha are forced off
   uint32_t mCMSMode;
   bool mDisablePremultipliedAlpha;
+
+  // The number of frames we've finished.
+  uint32_t mNumFrames;
   
   /*
    * libpng callbacks
    *
    * We put these in the class so that they can access protected members.
    */
   static void PNGAPI info_callback(png_structp png_ptr, png_infop info_ptr);
   static void PNGAPI row_callback(png_structp png_ptr, png_bytep new_row,
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -23,16 +23,17 @@ Decoder::Decoder(RasterImage &aImage)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mNeedsNewFrame(false)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
+  , mSynchronous(false)
 {
 }
 
 Decoder::~Decoder()
 {
   mInitialized = false;
 }
 
@@ -68,16 +69,20 @@ Decoder::InitSharedDecoder(uint8_t* imag
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
   NS_ABORT_IF_FALSE(mObserver, "Need an observer!");
 
   mImageData = imageData;
   mImageDataLength = imageDataLength;
   mColormap = colormap;
   mColormapSize = colormapSize;
   mCurrentFrame = currentFrame;
+  // We have all the frame data, so we've started the frame.
+  if (!IsSizeDecode()) {
+    PostFrameStart();
+  }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
 void
 Decoder::Write(const char* aBuffer, uint32_t aCount)
@@ -87,54 +92,40 @@ Decoder::Write(const char* aBuffer, uint
   // We're strict about decoder errors
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
 
   // If a data error occured, just ignore future data
   if (HasDataError())
     return;
 
-  nsresult rv = NS_OK;
-
-  // Preallocate a frame if we've been asked to.
-  if (mNeedsNewFrame) {
-    rv = AllocateFrame();
-    if (NS_FAILED(rv)) {
-      PostDataError();
-      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()) {
+  // If we're a synchronous decoder and we need a new frame to proceed, let's
+  // create one and call it again.
+  while (mSynchronous && NeedsNewFrame() && !HasDataError()) {
     nsresult rv = AllocateFrame();
 
     if (NS_SUCCEEDED(rv)) {
       // 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();
 
   // If the implementation left us mid-frame, finish that up.
-  if (mInFrame && !HasDecoderError())
+  if (mInFrame && !HasError())
     PostFrameStop();
 
   // If PostDecodeDone() has not been called, we need to sent teardown
   // notifications.
   if (!IsSizeDecode() && !mDecodeDone) {
 
     // Log data errors to the error console
     nsCOMPtr<nsIConsoleService> consoleService =
@@ -190,34 +181,37 @@ Decoder::FinishSharedDecoder()
     FinishInternal();
   }
 }
 
 nsresult
 Decoder::AllocateFrame()
 {
   MOZ_ASSERT(mNeedsNewFrame);
+  MOZ_ASSERT(NS_IsMainThread());
 
   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, &mCurrentFrame);
   } else {
     rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
                             mNewFrameData.mOffsetY, mNewFrameData.mWidth,
                             mNewFrameData.mHeight, mNewFrameData.mFormat,
                             &mImageData, &mImageDataLength, &mCurrentFrame);
   }
 
-  if (NS_SUCCEEDED(rv)) {
+  if (NS_SUCCEEDED(rv) && mNewFrameData.mFrameNum == mFrameCount) {
     PostFrameStart();
+  } else if (NS_FAILED(rv)) {
+    PostDataError();
   }
 
   // Mark ourselves as not needing another frame before talking to anyone else
   // so they can tell us if they need yet another.
   mNeedsNewFrame = false;
 
   return rv;
 }
@@ -398,16 +392,16 @@ Decoder::NeedNewFrame(uint32_t framenum,
                       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));
+  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
@@ -87,16 +87,21 @@ public:
   // must be enabled by SetSizeDecode() _before_calling Init().
   bool IsSizeDecode() { return mSizeDecode; }
   void SetSizeDecode(bool aSizeDecode)
   {
     NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!");
     mSizeDecode = aSizeDecode;
   }
 
+  void SetSynchronous(bool aSynchronous)
+  {
+    mSynchronous = aSynchronous;
+  }
+
   void SetObserver(imgDecoderObserver* aObserver)
   {
     MOZ_ASSERT(aObserver);
     mObserver = aObserver;
   }
 
   // The number of frames we have, including anything in-progress. Thus, this
   // is only 0 if we haven't begun any frames.
@@ -146,16 +151,22 @@ public:
   // 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);
 
+  virtual bool NeedsNewFrame() const { return mNeedsNewFrame; }
+
+  // Try to allocate a frame as described in mNewFrameData and return the
+  // status code from that attempt. Clears mNewFrameData.
+  virtual nsresult AllocateFrame();
+
 protected:
 
   /*
    * Internal hooks. Decoder implementations may override these and
    * only these methods.
    */
   virtual void InitInternal();
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
@@ -196,20 +207,16 @@ 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);
 
-  // Try to allocate a frame as described in mNewFrameData and return the
-  // status code from that attempt. Clears mNewFrameData.
-  nsresult AllocateFrame();
-
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
   imgFrame* mCurrentFrame;
   RefPtr<imgDecoderObserver> mObserver;
   ImageMetadata mImageMetadata;
@@ -255,14 +262,15 @@ private:
     uint8_t mPaletteDepth;
   };
   NewFrameData mNewFrameData;
   bool mNeedsNewFrame;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
+  bool mSynchronous;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_DECODER_H_
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -415,16 +415,25 @@ RasterImage::~RasterImage()
              discardable_source_bytes));
   }
 
   if (mDecoder) {
     // Kill off our decode request, if it's pending.  (If not, this call is
     // harmless.)
     DecodeWorker::Singleton()->StopDecoding(this);
     mDecoder = nullptr;
+
+    // Unlock the last frame (if we have any). Our invariant is that, while we
+    // have a decoder open, the last frame is always locked.
+    // This would be done in ShutdownDecoder, but since mDecoder is non-null,
+    // we didn't call ShutdownDecoder and we need to do it manually.
+    if (mFrames.Length() > 0) {
+      imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
+      curframe->UnlockImageData();
+    }
   }
 
   delete mAnim;
 
   for (unsigned int i = 0; i < mFrames.Length(); ++i)
     delete mFrames[i];
 
   // Total statistics
@@ -900,18 +909,18 @@ RasterImage::FrameIsOpaque(uint32_t aWhi
     NS_WARNING("aWhichFrame outside valid range!");
     return false;
   }
 
   if (mError)
     return false;
 
   // See if we can get an image frame.
-  imgFrame* frame = aWhichFrame == FRAME_FIRST ? GetImgFrame(0)
-                                               : GetCurrentImgFrame();
+  imgFrame* frame = aWhichFrame == FRAME_FIRST ? GetImgFrameNoDecode(0)
+                                               : GetImgFrameNoDecode(GetCurrentImgFrameIndex());
 
   // If we don't get a frame, the safe answer is "not opaque".
   if (!frame)
     return false;
 
   // Other, the frame is transparent if either:
   //  1. It needs a background.
   //  2. Its size doesn't cover our entire area.
@@ -927,17 +936,16 @@ RasterImage::FrameRect(uint32_t aWhichFr
     NS_WARNING("aWhichFrame outside valid range!");
     return nsIntRect();
   }
 
   // Get the requested frame.
   imgFrame* frame = aWhichFrame == FRAME_FIRST ? GetImgFrameNoDecode(0)
                                                : GetImgFrameNoDecode(GetCurrentImgFrameIndex());
 
-
   // If we have the frame, use that rectangle.
   if (frame) {
     return frame->GetRect();
   }
 
   // If the frame doesn't exist, we return the empty rectangle. It's not clear
   // whether this is appropriate in general, but at the moment the only
   // consumer of this method is imgStatusTracker (when it wants to figure out
@@ -1257,18 +1265,18 @@ RasterImage::InternalAddFrameHelper(uint
                                     imgFrame** aRetFrame)
 {
   NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Invalid frame index!");
   if (framenum > mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   nsAutoPtr<imgFrame> frame(aFrame);
 
-  // We are in the middle of decoding. This will be unlocked when we finish the
-  // decoder->Write() call.
+  // We are in the middle of decoding. This will be unlocked when we finish
+  // decoding or switch to another frame.
   frame->LockImageData();
 
   if (paletteData && paletteLength)
     frame->GetPaletteData(paletteData, paletteLength);
 
   frame->GetImageData(imageData, imageLength);
 
   *aRetFrame = frame;
@@ -1288,17 +1296,17 @@ RasterImage::InternalAddFrame(uint32_t f
                               uint32_t *imageLength,
                               uint32_t **paletteData,
                               uint32_t *paletteLength,
                               imgFrame** aRetFrame)
 {
   // We assume that we're in the middle of decoding because we unlock the
   // previous frame when we create a new frame, and only when decoding do we
   // lock frames.
-  NS_ABORT_IF_FALSE(mInDecoder, "Only decoders may add frames!");
+  NS_ABORT_IF_FALSE(mDecoder, "Only decoders may add frames!");
 
   NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Invalid frame index!");
   if (framenum > mFrames.Length())
     return NS_ERROR_INVALID_ARG;
 
   nsAutoPtr<imgFrame> frame(new imgFrame());
 
   nsresult rv = frame->Init(aX, aY, aWidth, aHeight, aFormat, aPaletteDepth);
@@ -1424,17 +1432,17 @@ RasterImage::EnsureFrame(uint32_t aFrame
     return NS_ERROR_INVALID_ARG;
 
   // Adding a frame that doesn't already exist.
   if (aFrameNum == mFrames.Length())
     return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat, 
                             aPaletteDepth, imageData, imageLength,
                             paletteData, paletteLength, aRetFrame);
 
-  imgFrame *frame = GetImgFrame(aFrameNum);
+  imgFrame *frame = GetImgFrameNoDecode(aFrameNum);
   if (!frame)
     return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat, 
                             aPaletteDepth, imageData, imageLength,
                             paletteData, paletteLength, aRetFrame);
 
   // See if we can re-use the frame that already exists.
   nsIntRect rect = frame->GetRect();
   if (rect.x == aX && rect.y == aY && rect.width == aWidth &&
@@ -1894,16 +1902,17 @@ RasterImage::OnNewSourceData()
   // The decoder was shut down and we didn't flag an error, so we should be decoded
   NS_ABORT_IF_FALSE(mDecoded, "Should be decoded in NewSourceData");
 
   // Reset some flags
   mDecoded = false;
   mHasSourceData = false;
   mHasSize = false;
   mWantFullDecode = true;
+  mDecodeRequest = nullptr;
 
   // We always need the size first.
   rv = InitDecoder(/* aDoSizeDecode = */ true);
   CONTAINER_ENSURE_SUCCESS(rv);
 
   return NS_OK;
 }
 
@@ -2443,16 +2452,18 @@ RasterImage::Discard(bool force)
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
 
   // Notify that we discarded
   if (mStatusTracker)
     mStatusTracker->OnDiscard();
 
+  mDecodeRequest = nullptr;
+
   if (force)
     DiscardTracker::Remove(&mDiscardTrackerNode);
 
   // Log
   PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
           "Total Containers: %d, Discardable containers: %d, "
@@ -2496,17 +2507,17 @@ bool
 RasterImage::StoringSourceData() const {
   return (mDecodeOnDraw || mDiscardable);
 }
 
 
 // Sets up a decoder for this image. It is an error to call this function
 // when decoding is already in process (ie - when mDecoder is non-null).
 nsresult
-RasterImage::InitDecoder(bool aDoSizeDecode)
+RasterImage::InitDecoder(bool aDoSizeDecode, bool aIsSynchronous /* = false */)
 {
   // Ensure that the decoder is not already initialized
   NS_ABORT_IF_FALSE(!mDecoder, "Calling InitDecoder() while already decoding!");
   
   // We shouldn't be firing up a decoder if we already have the frames decoded
   NS_ABORT_IF_FALSE(!mDecoded, "Calling InitDecoder() but already decoded!");
 
   // Since we're not decoded, we should not have a discard timer active
@@ -2547,29 +2558,39 @@ RasterImage::InitDecoder(bool aDoSizeDec
       break;
     case eDecoderType_wbmp:
       mDecoder = new nsWBMPDecoder(*this);
       break;
     default:
       NS_ABORT_IF_FALSE(0, "Shouldn't get here!");
   }
 
+  // If we already have frames, we're probably in the multipart/x-mixed-replace
+  // case. Regardless, we need to lock the last frame. Our invariant is that,
+  // while we have a decoder open, the last frame is always locked.
+  if (mFrames.Length() > 0) {
+    imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
+    curframe->LockImageData();
+  }
+
   // Initialize the decoder
   if (!mDecodeRequest) {
     mDecodeRequest = new DecodeRequest(this);
   }
   mDecoder->SetObserver(mDecodeRequest->mStatusTracker->GetDecoderObserver());
   mDecoder->SetSizeDecode(aDoSizeDecode);
   mDecoder->SetDecodeFlags(mFrameDecodeFlags);
+  mDecoder->SetSynchronous(aIsSynchronous);
   if (!aDoSizeDecode) {
     // We already have the size; tell the decoder so it can preallocate a
     // frame.  By default, we create an ARGB frame with no offset. If decoders
     // need a different type, they need to ask for it themselves.
     mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
                            gfxASurface::ImageFormatARGB32);
+    mDecoder->AllocateFrame();
   }
   mDecoder->Init();
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   if (!aDoSizeDecode) {
     Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Subtract(mDecodeCount);
     mDecodeCount++;
     Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(mDecodeCount);
@@ -2606,16 +2627,23 @@ RasterImage::ShutdownDecoder(eShutdownIn
                     "Invalid shutdown intent");
 
   // Ensure that the decoder is initialized
   NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!");
 
   // Figure out what kind of decode we were doing before we get rid of our decoder
   bool wasSizeDecode = mDecoder->IsSizeDecode();
 
+  // Unlock the last frame (if we have any). Our invariant is that, while we
+  // have a decoder open, the last frame is always locked.
+  if (mFrames.Length() > 0) {
+    imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
+    curframe->UnlockImageData();
+  }
+
   // Finalize the decoder
   // null out mDecoder, _then_ check for errors on the close (otherwise the
   // error routine might re-invoke ShutdownDecoder)
   nsRefPtr<Decoder> decoder = mDecoder;
   mDecoder = nullptr;
 
   mFinishing = true;
   mInDecoder = true;
@@ -2658,42 +2686,25 @@ RasterImage::ShutdownDecoder(eShutdownIn
 
 // Writes the data to the decoder, updating the total number of bytes written.
 nsresult
 RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount)
 {
   // We should have a decoder
   NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
 
-  // The decoder will start decoding into the current frame (if we have one).
-  // When it needs to add another frame, we will unlock this frame and lock the
-  // new frame.
-  // Our invariant is that, while in the decoder, the last frame is always
-  // locked, and all others are unlocked.
-  if (mFrames.Length() > 0) {
-    imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
-    curframe->LockImageData();
-  }
-
   // Write
   nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
   mInDecoder = true;
   mDecoder->Write(aBuffer, aCount);
   mInDecoder = false;
 
-  // We unlock the current frame, even if that frame is different from the
-  // frame we entered the decoder with. (See above.)
-  if (mFrames.Length() > 0) {
-    imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
-    curframe->UnlockImageData();
-  }
-
   if (!mDecoder)
     return NS_ERROR_FAILURE;
-    
+
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   // Keep track of the total number of bytes written over the lifetime of the
   // decoder
   mBytesDecoded += aCount;
 
   return NS_OK;
 }
@@ -2805,17 +2816,24 @@ RasterImage::RequestDecodeCore(RequestDe
   if (mHasSourceData && mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   // If we can do decoding now, do so.  Small images will decode completely,
   // large images will decode a bit and post themselves to the event loop
   // to finish decoding.
   if (!mDecoded && !mInDecoder && mHasSourceData && aDecodeType == SOMEWHAT_SYNCHRONOUS) {
     SAMPLE_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get());
+    mDecoder->SetSynchronous(true);
+
     DecodeWorker::Singleton()->DecodeABitOf(this);
+
+    // DecodeABitOf can destroy mDecoder.
+    if (mDecoder) {
+      mDecoder->SetSynchronous(false);
+    }
     return NS_OK;
   }
 
   // If we get this far, dispatch the worker. We do this instead of starting
   // any immediate decoding to guarantee that all our decode notifications are
   // dispatched asynchronously, and to ensure we stay responsive.
   DecodeWorker::Singleton()->RequestDecode(this);
 
@@ -2856,25 +2874,33 @@ RasterImage::SyncDecode()
 
   // If we have a decoder open with different flags than what we need, shut it
   // down
   if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
     nsresult rv = FinishedSomeDecoding(eShutdownIntent_NotNeeded);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If we don't have a decoder, create one 
+  // If we're currently waiting on a new frame for this image, we have to create
+  // it now.
+  if (mDecoder && mDecoder->NeedsNewFrame()) {
+    mDecoder->AllocateFrame();
+    mDecodeRequest->mAllocatedNewFrame = true;
+  }
+
+  // If we don't have a decoder, create one
   if (!mDecoder) {
-    rv = InitDecoder(/* aDoSizeDecode = */ false);
+    rv = InitDecoder(/* aDoSizeDecode = */ false, /* aIsSynchronous = */ true);
     CONTAINER_ENSURE_SUCCESS(rv);
+  } else {
+    mDecoder->SetSynchronous(true);
   }
 
   // Write everything we have
-  rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
-                      mSourceData.Length() - mBytesDecoded);
+  rv = DecodeSomeData(mSourceData.Length() - mBytesDecoded);
   CONTAINER_ENSURE_SUCCESS(rv);
 
   // When we're doing a sync decode, we want to get as much information from the
   // image as possible. We've send the decoder all of our data, so now's a good
   // time  to flush any invalidations (in case we don't have all the data and what
   // we got left us mid-frame).
   nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
   mInDecoder = true;
@@ -2885,16 +2911,18 @@ RasterImage::SyncDecode()
   if (mDecoder && IsDecodeFinished()) {
     // We have to shut down the decoder *now*, so we explicitly shut down the
     // decoder, and let FinishedSomeDecoding handle the rest for us.
     nsRefPtr<DecodeRequest> request = mDecodeRequest;
     nsresult rv = ShutdownDecoder(eShutdownIntent_Done);
     CONTAINER_ENSURE_SUCCESS(rv);
     rv = FinishedSomeDecoding(eShutdownIntent_Done, request);
     CONTAINER_ENSURE_SUCCESS(rv);
+  } else if (mDecoder) {
+    mDecoder->SetSynchronous(false);
   }
 
   // All good if no errors!
   return mError ? NS_ERROR_FAILURE : NS_OK;
 }
 
 static inline bool
 IsDownscale(const gfxSize& scale)
@@ -3207,17 +3235,28 @@ RasterImage::RequestDiscard()
 
 // Flushes up to aMaxBytes to the decoder.
 nsresult
 RasterImage::DecodeSomeData(uint32_t aMaxBytes)
 {
   // We should have a decoder if we get here
   NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!");
 
-  // If we have nothing to decode, return
+  // First, if we've just been called because we allocated a frame on the main
+  // thread, let the decoder deal with the data it set aside at that time by
+  // passing it a null buffer.
+  if (mDecodeRequest->mAllocatedNewFrame) {
+    mDecodeRequest->mAllocatedNewFrame = false;
+    nsresult rv = WriteToDecoder(nullptr, 0);
+    if (NS_FAILED(rv) || mDecoder->NeedsNewFrame()) {
+      return rv;
+    }
+  }
+
+  // If we have nothing else to decode, return
   if (mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   // write the proper amount of data
   uint32_t bytesToDecode = std::min(aMaxBytes,
                                   mSourceData.Length() - mBytesDecoded);
   nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
                                bytesToDecode);
@@ -3243,22 +3282,27 @@ RasterImage::IsDecodeFinished()
     if (mHasSize)
       decodeFinished = true;
   }
   else {
     if (mDecoded)
       decodeFinished = true;
   }
 
-  // ...or if we have all the source data and wrote all the source data.
+  // ... but if we're waiting for a new frame, we're not done.
+  if (mDecoder && mDecoder->NeedsNewFrame())
+    decodeFinished = false;
+
+  // Otherwise, if we have all the source data and wrote all the source data,
+  // we're done.
   //
   // (NB - This can be distinct from the above case even for non-erroneous
   // images because the decoder might not call DecodingComplete() until we
   // call Close() in ShutdownDecoder())
-  if (mHasSourceData && (mBytesDecoded == mSourceData.Length()))
+  else if (mHasSourceData && (mBytesDecoded == mSourceData.Length()))
     decodeFinished = true;
 
   return decodeFinished;
 }
 
 // Indempotent error flagging routine. If a decoder is open, shuts it down.
 void
 RasterImage::DoError()
@@ -3512,35 +3556,46 @@ RasterImage::DecodeWorker::AddDecodeRequ
   }
 }
 
 void
 RasterImage::DecodeWorker::RequestDecode(RasterImage* aImg)
 {
   MOZ_ASSERT(aImg->mDecoder);
 
-  AddDecodeRequest(aImg->mDecodeRequest, aImg->mSourceData.Length() - aImg->mBytesDecoded);
-  EnsurePendingInEventLoop();
+  // If we're currently waiting on a new frame for this image, we can't do any
+  // decoding.
+  if (!aImg->mDecoder->NeedsNewFrame()) {
+    AddDecodeRequest(aImg->mDecodeRequest, aImg->mSourceData.Length() - aImg->mBytesDecoded);
+    EnsurePendingInEventLoop();
+  }
 }
 
 void
 RasterImage::DecodeWorker::DecodeABitOf(RasterImage* aImg)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   DecodeSomeOfImage(aImg);
+
   aImg->FinishedSomeDecoding();
 
-  // If we aren't yet finished decoding and we have more data in hand, add
-  // this request to the back of the priority list.
-  if (aImg->mDecoder &&
-      !aImg->mError &&
-      !aImg->IsDecodeFinished() &&
-      aImg->mSourceData.Length() > aImg->mBytesDecoded) {
-    RequestDecode(aImg);
+  // If the decoder needs a new frame, enqueue an event to get it; that event
+  // will enqueue another decode request when it's done.
+  if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
+    FrameNeededWorker::GetNewFrame(aImg);
+  } else {
+    // If we aren't yet finished decoding and we have more data in hand, add
+    // this request to the back of the priority list.
+    if (aImg->mDecoder &&
+        !aImg->mError &&
+        !aImg->IsDecodeFinished() &&
+        aImg->mSourceData.Length() > aImg->mBytesDecoded) {
+      RequestDecode(aImg);
+    }
   }
 }
 
 void
 RasterImage::DecodeWorker::EnsurePendingInEventLoop()
 {
   if (!mPendingInEventLoop) {
     mPendingInEventLoop = true;
@@ -3578,33 +3633,39 @@ RasterImage::DecodeWorker::Run()
     if (!request)
       request = mNormalDecodeRequests.popFirst();
     if (!request)
       break;
 
     // This has to be a strong pointer, because DecodeSomeOfImage may destroy
     // image->mDecoder, which may be holding the only other reference to image.
     nsRefPtr<RasterImage> image = request->mImage;
-    uint32_t oldCount = image->mDecoder->GetFrameCount();
+    uint32_t oldFrameCount = image->mDecoder->GetFrameCount();
     uint32_t oldByteCount = image->mBytesDecoded;
 
     DecodeSomeOfImage(image, DECODE_TYPE_NORMAL, request->mBytesToDecode);
 
     uint32_t bytesDecoded = image->mBytesDecoded - oldByteCount;
 
+    // If the decoder needs a new frame, enqueue an event to get it; that event
+    // will enqueue another decode request when it's done.
+    if (image->mDecoder && image->mDecoder->NeedsNewFrame()) {
+      FrameNeededWorker::GetNewFrame(image);
+    }
+
     // If we aren't yet finished decoding and we have more data in hand, add
     // this request to the back of the list.
-    if (image->mDecoder &&
-        !image->mError &&
-        !image->IsDecodeFinished() &&
-        bytesDecoded < request->mBytesToDecode) {
+    else if (image->mDecoder &&
+             !image->mError &&
+             !image->IsDecodeFinished() &&
+             bytesDecoded < request->mBytesToDecode) {
       AddDecodeRequest(request, request->mBytesToDecode - bytesDecoded);
 
       // If we have a new frame, let everybody know about it.
-      if (image->mDecoder->GetFrameCount() != oldCount) {
+      if (image->mDecoder->GetFrameCount() != oldFrameCount) {
         DecodeDoneWorker::NotifyFinishedSomeDecoding(image, request);
       }
     } else {
       // Nothing more for us to do - let everyone know what happened.
       DecodeDoneWorker::NotifyFinishedSomeDecoding(image, request);
     }
 
   } while ((TimeStamp::Now() - eventStart).ToMilliseconds() <= gMaxMSBeforeYield);
@@ -3625,17 +3686,25 @@ RasterImage::DecodeWorker::DecodeUntilSi
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv = DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  return aImg->FinishedSomeDecoding();
+  // If the decoder needs a new frame, enqueue an event to get it; that event
+  // will enqueue another decode request when it's done.
+  if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
+    FrameNeededWorker::GetNewFrame(aImg);
+  } else {
+    rv = aImg->FinishedSomeDecoding();
+  }
+
+  return rv;
 }
 
 nsresult
 RasterImage::DecodeWorker::DecodeSomeOfImage(RasterImage* aImg,
                                              DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */,
                                              uint32_t bytesToDecode /* = 0 */)
 {
   NS_ABORT_IF_FALSE(aImg->mInitialized,
@@ -3646,16 +3715,22 @@ RasterImage::DecodeWorker::DecodeSomeOfI
   if (aImg->mError)
     return NS_OK;
 
   // If mDecoded or we don't have a decoder, we must have finished already (for
   // example, a synchronous decode request came while the worker was pending).
   if (!aImg->mDecoder || aImg->mDecoded)
     return NS_OK;
 
+  // If we're currently waiting on a new frame for this image, we can't do any
+  // decoding right now.
+  if (aImg->mDecoder->NeedsNewFrame()) {
+    return NS_OK;
+  }
+
   nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
 
   uint32_t maxBytes;
   if (aImg->mDecoder->IsSizeDecode()) {
     // Decode all available data if we're a size decode; they're cheap, and we
     // want them to be more or less synchronous.
     maxBytes = aImg->mSourceData.Length();
   } else {
@@ -3673,20 +3748,24 @@ RasterImage::DecodeWorker::DecodeSomeOfI
   TimeStamp start = TimeStamp::Now();
   TimeStamp deadline = start + TimeDuration::FromMilliseconds(gMaxMSBeforeYield);
 
   // We keep decoding chunks until:
   //  * we don't have any data left to decode,
   //  * the decode completes,
   //  * we're an UNTIL_SIZE decode and we get the size, or
   //  * we run out of time.
-  while (aImg->mSourceData.Length() > aImg->mBytesDecoded &&
-         bytesToDecode > 0 &&
-         !aImg->IsDecodeFinished() &&
-         !(aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize)) {
+  // We also try to decode at least one "chunk" if we've allocated a new frame,
+  // even if we have no more data to send to the decoder.
+  while ((aImg->mSourceData.Length() > aImg->mBytesDecoded &&
+          bytesToDecode > 0 &&
+          !aImg->IsDecodeFinished() &&
+          !(aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize) &&
+          !aImg->mDecoder->NeedsNewFrame()) ||
+         (aImg->mDecodeRequest && aImg->mDecodeRequest->mAllocatedNewFrame)) {
     chunkCount++;
     uint32_t chunkSize = std::min(bytesToDecode, maxBytes);
     nsresult rv = aImg->DecodeSomeData(chunkSize);
     if (NS_FAILED(rv)) {
       aImg->DoError();
       return rv;
     }
 
@@ -3752,10 +3831,41 @@ RasterImage::DecodeDoneWorker::Run()
   if (mImage->mDecoder && !mImage->IsDecodeFinished() &&
       mImage->mSourceData.Length() > mImage->mBytesDecoded) {
     DecodeWorker::Singleton()->RequestDecode(mImage);
   }
 
   return NS_OK;
 }
 
+RasterImage::FrameNeededWorker::FrameNeededWorker(RasterImage* image)
+ : mImage(image)
+{}
+
+
+void
+RasterImage::FrameNeededWorker::GetNewFrame(RasterImage* image)
+{
+  nsCOMPtr<FrameNeededWorker> worker = new FrameNeededWorker(image);
+  NS_DispatchToMainThread(worker);
+}
+
+NS_IMETHODIMP
+RasterImage::FrameNeededWorker::Run()
+{
+  nsresult rv = NS_OK;
+
+  // If we got a synchronous decode in the mean time, we don't need to do
+  // anything.
+  if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
+    rv = mImage->mDecoder->AllocateFrame();
+    mImage->mDecodeRequest->mAllocatedNewFrame = true;
+  }
+
+  if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
+    DecodeWorker::Singleton()->RequestDecode(mImage);
+  }
+
+  return NS_OK;
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -386,16 +386,17 @@ private:
    */
   struct DecodeRequest : public LinkedListElement<DecodeRequest>,
                          public RefCounted<DecodeRequest>
   {
     DecodeRequest(RasterImage* aImage)
       : mImage(aImage)
       , mBytesToDecode(0)
       , mChunkCount(0)
+      , mAllocatedNewFrame(false)
       , mIsASAP(false)
     {
       mStatusTracker = aImage->mStatusTracker->CloneForRecording();
     }
 
     // The status tracker that is associated with a given decode request, to
     // ensure their lifetimes are linked.
     nsRefPtr<imgStatusTracker> mStatusTracker;
@@ -406,16 +407,20 @@ private:
 
     /* Keeps track of how much time we've burned decoding this particular decode
      * request. */
     TimeDuration mDecodeTime;
 
     /* The number of chunks it took to decode this image. */
     int32_t mChunkCount;
 
+    /* True if a new frame has been allocated, but DecodeSomeData hasn't yet
+     * been called to flush data to it */
+    bool mAllocatedNewFrame;
+
     /* True if we need to handle this decode as soon as possible. */
     bool mIsASAP;
   };
 
   /*
    * DecodeWorker is a singleton class we use when decoding large images.
    *
    * When we wish to decode an image larger than
@@ -540,16 +545,38 @@ private:
     DecodeDoneWorker(RasterImage* image, DecodeRequest* request);
 
   private: /* members */
 
     nsRefPtr<RasterImage> mImage;
     nsRefPtr<DecodeRequest> mRequest;
   };
 
+  class FrameNeededWorker : public nsRunnable
+  {
+  public:
+    /**
+     * Called by the DecodeWorker with an image when it's been told by the
+     * decoder that it needs a new frame to be allocated on the main thread.
+     *
+     * Dispatches an event to do so, which will further dispatch a
+     * DecodeRequest event to continue decoding.
+     */
+    static void GetNewFrame(RasterImage* image);
+
+    NS_IMETHOD Run();
+
+  private: /* methods */
+    FrameNeededWorker(RasterImage* image);
+
+  private: /* members */
+
+    nsRefPtr<RasterImage> mImage;
+  };
+
   nsresult FinishedSomeDecoding(eShutdownIntent intent = eShutdownIntent_Done,
                                 DecodeRequest* request = nullptr);
 
   void DrawWithPreDownscaleIfNeeded(imgFrame *aFrame,
                                     gfxContext *aContext,
                                     gfxPattern::GraphicsFilter aFilter,
                                     const gfxMatrix &aUserSpaceToImageSpace,
                                     const gfxRect &aFill,
@@ -764,17 +791,17 @@ private: // data
 
   // Whether, once we are done doing a size decode, we should immediately kick
   // off a full decode.
   bool                       mWantFullDecode:1;
 
   // Decoding
   nsresult WantDecodedFrames();
   nsresult SyncDecode();
-  nsresult InitDecoder(bool aDoSizeDecode);
+  nsresult InitDecoder(bool aDoSizeDecode, bool aIsSynchronous = false);
   nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
   nsresult DecodeSomeData(uint32_t aMaxBytes);
   bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
 
   inline bool CanScale(gfxPattern::GraphicsFilter aFilter, gfxSize aScale);
 
   struct ScaleResult