Backout changeset b4ebefd0f7e3:a8044fd506db Bug 1117607, Bug 1118087, Bug 1118092, Bug 1118105, Bug 1030372, Bug 1079627 on CLOSED TREE
authorTom Schuster <evilpies@gmail.com>
Sun, 11 Jan 2015 20:43:32 +0100
changeset 223212 8f3cc2c90893ab18bcb9eda7961ff3fc08022a99
parent 223211 a8044fd506db631fe74958c453dec459f10e3b11
child 223213 238f460e7aedc87a4ce3fdff8b9479256d3f3d58
push id10769
push usercbook@mozilla.com
push dateMon, 12 Jan 2015 14:15:52 +0000
treeherderfx-team@0e9765732906 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1117607, 1118087, 1118092, 1118105, 1030372, 1079627
milestone37.0a1
backs outb4ebefd0f7e3a0814ea9d9f42dc0da74be3fb998
Backout changeset b4ebefd0f7e3:a8044fd506db Bug 1117607, Bug 1118087, Bug 1118092, Bug 1118105, Bug 1030372, Bug 1079627 on CLOSED TREE
gfx/thebes/gfxPrefs.h
image/decoders/nsBMPDecoder.cpp
image/decoders/nsBMPDecoder.h
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsGIFDecoder2.h
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/decoders/nsIconDecoder.cpp
image/decoders/nsIconDecoder.h
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsJPEGDecoder.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/src/DecodePool.cpp
image/src/DecodePool.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/FrameAnimator.cpp
image/src/ImageMetadata.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/SourceBuffer.cpp
image/src/SourceBuffer.h
image/src/SurfaceCache.cpp
image/src/SurfaceCache.h
image/src/imgFrame.cpp
image/src/imgFrame.h
image/src/moz.build
image/test/crashtests/crashtests.list
image/test/reftest/gif/reftest.list
image/test/reftest/ico/cur/reftest.list
layout/reftests/bugs/reftest.list
media/mtransport/runnable_utils.h
modules/libpref/init/all.js
netwerk/sctp/datachannel/DataChannel.cpp
toolkit/content/tests/chrome/chrome.ini
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -231,25 +231,26 @@ private:
 
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
 
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.enabled", ImageHQDownscalingEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.high_quality_downscaling.min_factor", ImageHQDownscalingMinFactor, uint32_t, 1000);
   DECL_GFX_PREF(Live, "image.high_quality_upscaling.max_size", ImageHQUpscalingMaxSize, uint32_t, 20971520);
-  DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
+  DECL_GFX_PREF(Live, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
   DECL_GFX_PREF(Live, "image.mem.decodeondraw",                ImageMemDecodeOnDraw, bool, false);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
+  DECL_GFX_PREF(Live, "image.mem.max_ms_before_yield",         ImageMemMaxMSBeforeYield, uint32_t, 400);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
-  DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
+  DECL_GFX_PREF(Live, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-video.enabled",            AsyncVideoEnabled, bool, true);
   DECL_GFX_PREF(Once, "layers.async-video-oop.enabled",        AsyncVideoOOPEnabled, bool, true);
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -33,17 +33,17 @@ GetBMPLog()
   return sBMPLog;
 }
 #endif
 
 // Convert from row (1..height) to absolute line (0..height-1)
 #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1))
 #define PIXEL_OFFSET(row, col) (LINE(row) * mBIH.width + col)
 
-nsBMPDecoder::nsBMPDecoder(RasterImage* aImage)
+nsBMPDecoder::nsBMPDecoder(RasterImage& aImage)
   : Decoder(aImage)
   , mPos(0)
   , mLOH(WIN_V3_HEADER_LENGTH)
   , mNumColors(0)
   , mColors(nullptr)
   , mRow(nullptr)
   , mRowBytes(0)
   , mCurLine(1)  // Otherwise decoder will never start.
@@ -434,26 +434,21 @@ nsBMPDecoder::WriteInternal(const char* 
         // + 4 because the line is padded to a 4 bit boundary, but
         // I don't want to make exact calculations here, that's unnecessary.
         // Also, it compensates rounding error.
         if (!mRow) {
           PostDataError();
           return;
         }
       }
-
-      MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
-      nsresult rv = AllocateFrame(0, nsIntRect(nsIntPoint(), GetSize()),
-                                  gfx::SurfaceFormat::B8G8R8A8);
-      if (NS_FAILED(rv)) {
+      if (!mImageData) {
+        PostDecoderError(NS_ERROR_FAILURE);
         return;
       }
 
-      MOZ_ASSERT(mImageData, "Should have a buffer now");
-
       // Prepare for transparency
       if ((mBIH.compression == BI_RLE8) || (mBIH.compression == BI_RLE4)) {
         // Clear the image, as the RLE may jump over areas
         memset(mImageData, 0, mImageDataLength);
       }
   }
 
   if (mColors && mPos >= mLOH) {
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -18,17 +18,17 @@ namespace image {
 class RasterImage;
 
 /// Decoder for BMP-Files, as used by Windows and OS/2
 
 class nsBMPDecoder : public Decoder
 {
 public:
 
-    explicit nsBMPDecoder(RasterImage* aImage);
+    explicit nsBMPDecoder(RasterImage& aImage);
     ~nsBMPDecoder();
 
     // Specifies whether or not the BMP file will contain alpha data
     // If set to true and the BMP is 32BPP, the alpha data will be
     // retrieved from the 4th byte of image data per pixel
     void SetUseAlphaData(bool useAlphaData);
 
     // Obtains the bits per pixel from the internal BIH header
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -63,17 +63,17 @@ namespace image {
     mGIFStruct.state = (s);            \
   PR_END_MACRO
 
 // Get a 16-bit value stored in little-endian format
 #define GETINT16(p)   ((p)[1]<<8|(p)[0])
 //////////////////////////////////////////////////////////////////////
 // GIF Decoder Implementation
 
-nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
+nsGIFDecoder2::nsGIFDecoder2(RasterImage& aImage)
   : Decoder(aImage)
   , mCurrentRow(-1)
   , mLastFlushedRow(-1)
   , mOldColor(0)
   , mCurrentFrameIndex(-1)
   , mCurrentPass(0)
   , mLastFlushedPass(0)
   , mGIFOpen(false)
@@ -156,52 +156,58 @@ nsGIFDecoder2::BeginGIF()
   }
 
   mGIFOpen = true;
 
   PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height);
 }
 
 //******************************************************************************
-nsresult
+void
 nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
 {
   MOZ_ASSERT(HasSize());
 
   gfx::SurfaceFormat format;
   if (mGIFStruct.is_transparent) {
     format = gfx::SurfaceFormat::B8G8R8A8;
     PostHasTransparency();
   } else {
     format = gfx::SurfaceFormat::B8G8R8X8;
   }
 
-  nsIntRect frameRect(mGIFStruct.x_offset, mGIFStruct.y_offset,
-                      mGIFStruct.width, mGIFStruct.height);
-
   // Use correct format, RGB for first frame, PAL for following frames
   // and include transparency to allow for optimization of opaque images
-  nsresult rv = NS_OK;
   if (mGIFStruct.images_decoded) {
-    // Image data is stored with original depth and palette.
-    rv = AllocateFrame(mGIFStruct.images_decoded, frameRect, format, aDepth);
+    // Image data is stored with original depth and palette
+    NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
+                 mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
+                 format, aDepth);
   } else {
-    if (!nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
+    nsRefPtr<imgFrame> currentFrame = GetCurrentFrame();
+
+    // Our first full frame is automatically created by the image decoding
+    // infrastructure. Just use it as long as it matches up.
+    if (!currentFrame->GetRect().IsEqualEdges(nsIntRect(mGIFStruct.x_offset,
+                                                        mGIFStruct.y_offset,
+                                                        mGIFStruct.width,
+                                                        mGIFStruct.height))) {
+
       // We need padding on the first frame, which means that we don't draw into
       // part of the image at all. Report that as transparency.
       PostHasTransparency();
+
+      // Regardless of depth of input, image is decoded into 24bit RGB
+      NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
+                   mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
+                   format);
     }
-
-    // Regardless of depth of input, the first frame is decoded into 24bit RGB.
-    rv = AllocateFrame(mGIFStruct.images_decoded, frameRect, format);
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
-
-  return rv;
 }
 
 
 //******************************************************************************
 void
 nsGIFDecoder2::EndImageFrame()
 {
   Opacity opacity = Opacity::SOME_TRANSPARENCY;
@@ -946,23 +952,37 @@ nsGIFDecoder2::WriteInternal(const char*
         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);
+      BeginImageFrame(realDepth);
 
-      if (NS_FAILED(BeginImageFrame(realDepth))) {
-        mGIFStruct.state = gif_error;
-        return;
+      if (NeedsNewFrame()) {
+        // 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;
+      } else {
+        // FALL THROUGH
       }
-
-      // FALL THROUGH
     }
 
     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) {
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -18,29 +18,29 @@ class RasterImage;
 
 //////////////////////////////////////////////////////////////////////
 // nsGIFDecoder2 Definition
 
 class nsGIFDecoder2 : public Decoder
 {
 public:
 
-  explicit nsGIFDecoder2(RasterImage* aImage);
+  explicit nsGIFDecoder2(RasterImage& aImage);
   ~nsGIFDecoder2();
 
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
 
 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,
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -53,17 +53,17 @@ nsICODecoder::GetNumColors()
     default:
       numColors = (uint16_t)-1;
     }
   }
   return numColors;
 }
 
 
-nsICODecoder::nsICODecoder(RasterImage* aImage)
+nsICODecoder::nsICODecoder(RasterImage& aImage)
  : Decoder(aImage)
 {
   mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
   mIsPNG = false;
   mRow = nullptr;
   mOldLine = mCurLine = 1; // Otherwise decoder will never start
 }
 
@@ -77,23 +77,20 @@ nsICODecoder::~nsICODecoder()
 void
 nsICODecoder::FinishInternal()
 {
   // We shouldn't be called in error cases
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!");
 
   // Finish the internally used decoder as well
   if (mContainedDecoder) {
-    if (!mContainedDecoder->HasError()) {
-      mContainedDecoder->FinishInternal();
-    }
+    mContainedDecoder->FinishSharedDecoder();
     mDecodeDone = mContainedDecoder->GetDecodeDone();
     mProgress |= mContainedDecoder->TakeProgress();
     mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
-    mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
   }
 }
 
 // Returns a buffer filled with the bitmap file header in little endian:
 // Signature 2 bytes 'BM'
 // FileSize      4 bytes File size in bytes
 // reserved      4 bytes unused (=0)
 // DataOffset    4 bytes File offset to Raster Data
@@ -247,17 +244,17 @@ nsICODecoder::WriteInternal(const char* 
     aCount -= 2;
   }
 
   if (mNumIcons == 0) {
     return; // Nothing to do.
   }
 
   uint16_t colorDepth = 0;
-  nsIntSize prefSize = mImage->GetRequestedResolution();
+  nsIntSize prefSize = mImage.GetRequestedResolution();
   if (prefSize.width == 0 && prefSize.height == 0) {
     prefSize.SizeTo(PREFICONSIZE, PREFICONSIZE);
   }
 
   // A measure of the difference in size between the entry we've found
   // and the requested size. We will choose the smallest image that is
   // >= requested size (i.e. we assume it's better to downscale a larger
   // icon than to upscale a smaller one).
@@ -340,18 +337,19 @@ nsICODecoder::WriteInternal(const char* 
     aCount -= toCopy;
     aBuffer += toCopy;
 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
-      mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
-      mContainedDecoder->Init();
+      mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
+                                           mColormap, mColormapSize,
+                                           Move(mRefForContainedDecoder));
       if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
         return;
       }
     }
   }
 
   // If we have a PNG, let the PNG decoder do all of the rest of the work
   if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
@@ -417,18 +415,19 @@ nsICODecoder::WriteInternal(const char* 
 
     // Init the bitmap decoder which will do most of the work for us
     // It will do everything except the AND mask which isn't present in bitmaps
     // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
     nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
     mContainedDecoder->SetSizeDecode(IsSizeDecode());
-    mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
-    mContainedDecoder->Init();
+    mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
+                                         mColormap, mColormapSize,
+                                         Move(mRefForContainedDecoder));
 
     // The ICO format when containing a BMP does not include the 14 byte
     // bitmap file header. To use the code of the BMP decoder we need to
     // generate this header ourselves and feed it to the BMP decoder.
     int8_t bfhBuffer[BMPFILEHEADERSIZE];
     if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
       PostDataError();
       return;
@@ -621,10 +620,40 @@ nsICODecoder::ProcessDirEntry(IconDirEnt
   aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
   memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
   aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
   memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
          sizeof(aTarget.mImageOffset));
   aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
 }
 
+bool
+nsICODecoder::NeedsNewFrame() const
+{
+  if (mContainedDecoder) {
+    return mContainedDecoder->NeedsNewFrame();
+  }
+
+  return Decoder::NeedsNewFrame();
+}
+
+nsresult
+nsICODecoder::AllocateFrame()
+{
+  nsresult rv;
+
+  if (mContainedDecoder) {
+    rv = mContainedDecoder->AllocateFrame();
+    mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
+    mProgress |= mContainedDecoder->TakeProgress();
+    mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
+    return rv;
+  }
+
+  // Grab a strong ref that we'll later hand over to the contained decoder. This
+  // lets us avoid creating a RawAccessFrameRef off-main-thread.
+  rv = Decoder::AllocateFrame();
+  mRefForContainedDecoder = GetCurrentFrameRef();
+  return rv;
+}
+
 } // namespace image
 } // namespace mozilla
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -18,33 +18,37 @@ namespace mozilla {
 namespace image {
 
 class RasterImage;
 
 class nsICODecoder : public Decoder
 {
 public:
 
-  explicit nsICODecoder(RasterImage* aImage);
+  explicit nsICODecoder(RasterImage& aImage);
   virtual ~nsICODecoder();
 
   // Obtains the width of the icon directory entry
   uint32_t GetRealWidth() const
   {
     return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth;
   }
 
   // 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) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
+  virtual nsresult AllocateFrame() MOZ_OVERRIDE;
+
+protected:
+  virtual bool NeedsNewFrame() const MOZ_OVERRIDE;
 
 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);
@@ -74,16 +78,17 @@ private:
   uint16_t mNumIcons; // Stores the number of icons in the ICO file
   uint16_t mCurrIcon; // Stores the current dir entry index we are processing
   uint32_t mImageOffset; // Stores the offset of the image data we want
   uint8_t* mRow;      // Holds one raw line of the image
   int32_t mCurLine;   // Line index of the image that's currently being decoded
   uint32_t mRowBytes; // How many bytes of the row were already received
   int32_t mOldLine;   // Previous index of the line
   nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
+  RawAccessFrameRef mRefForContainedDecoder; // Avoid locking off-main-thread
 
   char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
   IconDirEntry mDirEntry; // Holds a decoded dir entry
   // Holds the potential bytes that can be a PNG signature
   char mSignature[PNGSIGNATURESIZE];
   // Holds the potential bytes for a bitmap information header
   char mBIHraw[40];
   // Stores whether or not the icon file we are processing has type 1 (icon)
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -10,17 +10,17 @@
 #include "nsRect.h"
 #include "nsError.h"
 #include "RasterImage.h"
 #include <algorithm>
 
 namespace mozilla {
 namespace image {
 
-nsIconDecoder::nsIconDecoder(RasterImage* aImage)
+nsIconDecoder::nsIconDecoder(RasterImage& aImage)
  : Decoder(aImage),
    mWidth(-1),
    mHeight(-1),
    mPixBytesRead(0),
    mState(iconStateStart)
 {
   // Nothing to do
 }
@@ -68,28 +68,21 @@ nsIconDecoder::WriteInternal(const char*
         }
 
         // If We're doing a size decode, we're done
         if (IsSizeDecode()) {
           mState = iconStateFinished;
           break;
         }
 
-        {
-          MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
-          nsresult rv = AllocateFrame(0, nsIntRect(nsIntPoint(), GetSize()),
-                                      gfx::SurfaceFormat::B8G8R8A8);
-          if (NS_FAILED(rv)) {
-            mState = iconStateFinished;
-            return;
-          }
+        if (!mImageData) {
+          PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
+          return;
         }
 
-        MOZ_ASSERT(mImageData, "Should have a buffer now");
-
         // Book Keeping
         aBuffer++;
         aCount--;
         mState = iconStateReadPixels;
         break;
 
       case iconStateReadPixels: {
 
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -33,17 +33,17 @@ class RasterImage;
 //
 //
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsIconDecoder : public Decoder
 {
 public:
 
-  explicit nsIconDecoder(RasterImage* aImage);
+  explicit nsIconDecoder(RasterImage& aImage);
   virtual ~nsIconDecoder();
 
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
 
   uint8_t mWidth;
   uint8_t mHeight;
   uint32_t mPixBytesRead;
   uint32_t mState;
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -79,17 +79,17 @@ METHODDEF(boolean) fill_input_buffer (j_
 METHODDEF(void) skip_input_data (j_decompress_ptr jd, long num_bytes);
 METHODDEF(void) term_source (j_decompress_ptr jd);
 METHODDEF(void) my_error_exit (j_common_ptr cinfo);
 
 // Normal JFIF markers can't have more bytes than this.
 #define MAX_JPEG_MARKER_LENGTH  (((uint32_t)1 << 16) - 1)
 
 
-nsJPEGDecoder::nsJPEGDecoder(RasterImage* aImage,
+nsJPEGDecoder::nsJPEGDecoder(RasterImage& aImage,
                              Decoder::DecodeStyle aDecodeStyle)
  : Decoder(aImage)
  , mDecodeStyle(aDecodeStyle)
 {
   mState = JPEG_HEADER;
   mReading = true;
   mImageData = nullptr;
 
@@ -232,17 +232,17 @@ nsJPEGDecoder::WriteInternal(const char*
 
       // Step 3: read file parameters with jpeg_read_header()
       if (jpeg_read_header(&mInfo, TRUE) == JPEG_SUSPENDED) {
         PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
                ("} (JPEG_SUSPENDED)"));
         return; // I/O suspension
       }
 
-      int sampleSize = mImage->GetRequestedSampleSize();
+      int sampleSize = mImage.GetRequestedSampleSize();
       if (sampleSize > 0) {
         mInfo.scale_num = 1;
         mInfo.scale_denom = sampleSize;
       }
 
       // Used to set up image size so arrays can be allocated
       jpeg_calc_output_dimensions(&mInfo);
 
@@ -381,28 +381,24 @@ nsJPEGDecoder::WriteInternal(const char*
       }
     }
 
     // Don't allocate a giant and superfluous memory buffer
     // when not doing a progressive decode.
     mInfo.buffered_image = mDecodeStyle == PROGRESSIVE &&
                            jpeg_has_multiple_scans(&mInfo);
 
-    MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
-    nsresult rv = AllocateFrame(0, nsIntRect(nsIntPoint(), GetSize()),
-                                gfx::SurfaceFormat::B8G8R8A8);
-    if (NS_FAILED(rv)) {
+    if (!mImageData) {
       mState = JPEG_ERROR;
+      PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
       PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
              ("} (could not initialize image frame)"));
       return;
     }
 
-    MOZ_ASSERT(mImageData, "Should have a buffer now");
-
     PR_LOG(GetJPEGDecoderAccountingLog(), PR_LOG_DEBUG,
            ("        JPEGDecoderAccounting: nsJPEGDecoder::"
             "Write -- created image frame with %ux%u pixels",
             mInfo.output_width, mInfo.output_height));
 
     mState = JPEG_START_DECOMPRESS;
   }
 
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -47,17 +47,17 @@ typedef enum {
 } jstate;
 
 class RasterImage;
 struct Orientation;
 
 class nsJPEGDecoder : public Decoder
 {
 public:
-  nsJPEGDecoder(RasterImage* aImage, Decoder::DecodeStyle aDecodeStyle);
+  nsJPEGDecoder(RasterImage& aImage, Decoder::DecodeStyle aDecodeStyle);
   virtual ~nsJPEGDecoder();
 
   virtual void InitInternal() MOZ_OVERRIDE;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual void FinishInternal() MOZ_OVERRIDE;
 
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
   void NotifyDone();
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -102,17 +102,17 @@ nsPNGDecoder::AnimFrameInfo::AnimFrameIn
   }
 }
 #endif
 
 // First 8 bytes of a PNG file
 const uint8_t
 nsPNGDecoder::pngSignatureBytes[] = { 137, 80, 78, 71, 13, 10, 26, 10 };
 
-nsPNGDecoder::nsPNGDecoder(RasterImage* aImage)
+nsPNGDecoder::nsPNGDecoder(RasterImage& aImage)
  : Decoder(aImage),
    mPNG(nullptr), mInfo(nullptr),
    mCMSLine(nullptr), interlacebuf(nullptr),
    mInProfile(nullptr), mTransform(nullptr),
    mHeaderBytesRead(0), mCMSMode(0),
    mChannels(0), mFrameIsHidden(false),
    mDisablePremultipliedAlpha(false),
    mNumFrames(0)
@@ -136,69 +136,62 @@ nsPNGDecoder::~nsPNGDecoder()
     // mTransform belongs to us only if mInProfile is non-null
     if (mTransform) {
       qcms_transform_release(mTransform);
     }
   }
 }
 
 // CreateFrame() is used for both simple and animated images
-nsresult
-nsPNGDecoder::CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
-                          int32_t aWidth, int32_t aHeight,
-                          gfx::SurfaceFormat aFormat)
+void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
+                               int32_t width, int32_t height,
+                               gfx::SurfaceFormat format)
 {
   MOZ_ASSERT(HasSize());
 
-  if (aFormat == gfx::SurfaceFormat::B8G8R8A8) {
+  if (format == gfx::SurfaceFormat::B8G8R8A8) {
     PostHasTransparency();
   }
 
-  nsIntRect frameRect(aXOffset, aYOffset, aWidth, aHeight);
-  if (mNumFrames == 0 &&
-      !nsIntRect(nsIntPoint(), GetSize()).IsEqualEdges(frameRect)) {
-    // We need padding on the first frame, which means that we don't draw into
-    // part of the image at all. Report that as transparency.
-    PostHasTransparency();
+  // Our first full frame is automatically created by the image decoding
+  // infrastructure. Just use it as long as it matches up.
+  nsIntRect neededRect(x_offset, y_offset, width, height);
+  nsRefPtr<imgFrame> currentFrame = GetCurrentFrame();
+  if (!currentFrame->GetRect().IsEqualEdges(neededRect)) {
+    if (mNumFrames == 0) {
+      // We need padding on the first frame, which means that we don't draw into
+      // part of the image at all. Report that as transparency.
+      PostHasTransparency();
+    }
+
+    NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
+  } else if (mNumFrames != 0) {
+    NeedNewFrame(mNumFrames, x_offset, y_offset, width, height, format);
   }
 
-  // XXX(seth): Some tests depend on the first frame of PNGs being B8G8R8A8.
-  // This is something we should fix.
-  gfx::SurfaceFormat format = aFormat;
-  if (mNumFrames == 0) {
-    format = gfx::SurfaceFormat::B8G8R8A8;
-  }
-
-  nsresult rv = AllocateFrame(mNumFrames, frameRect, format);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  mFrameRect = frameRect;
+  mFrameRect = neededRect;
   mFrameHasNoAlpha = true;
 
   PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
          ("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
           "image frame with %dx%d pixels in container %p",
-          aWidth, aHeight,
+          width, height,
           &mImage));
 
 #ifdef PNG_APNG_SUPPORTED
   if (png_get_valid(mPNG, mInfo, PNG_INFO_acTL)) {
     mAnimInfo = AnimFrameInfo(mPNG, mInfo);
 
     if (mAnimInfo.mDispose == DisposalMethod::CLEAR) {
       // We may have to display the background under this image during
       // animation playback, so we regard it as transparent.
       PostHasTransparency();
     }
   }
 #endif
-
-  return NS_OK;
 }
 
 // set timeout and frame disposal method for the current frame
 void
 nsPNGDecoder::EndImageFrame()
 {
   if (mFrameIsHidden) {
     return;
@@ -651,21 +644,17 @@ nsPNGDecoder::info_callback(png_structp 
     png_set_progressive_frame_fn(png_ptr, nsPNGDecoder::frame_info_callback,
                                  nullptr);
   }
 
   if (png_get_first_frame_is_hidden(png_ptr, info_ptr)) {
     decoder->mFrameIsHidden = true;
   } else {
 #endif
-    nsresult rv = decoder->CreateFrame(0, 0, width, height, decoder->format);
-    if (NS_FAILED(rv)) {
-      png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
-    }
-    MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
+    decoder->CreateFrame(0, 0, width, height, decoder->format);
 #ifdef PNG_APNG_SUPPORTED
   }
 #endif
 
   if (decoder->mTransform &&
       (channels <= 2 || interlace_type == PNG_INTERLACE_ADAM7)) {
     uint32_t bpp[] = { 0, 3, 4, 3, 4 };
     decoder->mCMSLine =
@@ -678,16 +667,22 @@ nsPNGDecoder::info_callback(png_structp 
   if (interlace_type == PNG_INTERLACE_ADAM7) {
     if (height < INT32_MAX / (width * channels)) {
       decoder->interlacebuf = (uint8_t*)moz_malloc(channels * width * height);
     }
     if (!decoder->interlacebuf) {
       png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
     }
   }
+
+  if (decoder->NeedsNewFrame()) {
+    // 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:
    *
@@ -840,22 +835,23 @@ nsPNGDecoder::frame_info_callback(png_st
   // Only the first frame can be hidden, so unhide unconditionally here.
   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);
 
-  nsresult rv =
-    decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
-  if (NS_FAILED(rv)) {
-    png_longjmp(decoder->mPNG, 5); // NS_ERROR_OUT_OF_MEMORY
+  decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
+
+  if (decoder->NeedsNewFrame()) {
+    // 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);
   }
-  MOZ_ASSERT(decoder->mImageData, "Should have a buffer now");
 }
 #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
@@ -19,26 +19,26 @@
 
 namespace mozilla {
 namespace image {
 class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
-  explicit nsPNGDecoder(RasterImage* aImage);
+  explicit nsPNGDecoder(RasterImage& aImage);
   virtual ~nsPNGDecoder();
 
   virtual void InitInternal() MOZ_OVERRIDE;
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount) MOZ_OVERRIDE;
   virtual Telemetry::ID SpeedHistogram() MOZ_OVERRIDE;
 
-  nsresult CreateFrame(png_uint_32 aXOffset, png_uint_32 aYOffset,
-                       int32_t aWidth, int32_t aHeight,
-                       gfx::SurfaceFormat aFormat);
+  void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
+                   int32_t width, int32_t height,
+                   gfx::SurfaceFormat format);
   void EndImageFrame();
 
   // Check if PNG is valid ICO (32bpp RGBA)
   // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
   bool IsValidICO() const
   {
     // If there are errors in the call to png_get_IHDR, the error_callback in
     // nsPNGDecoder.cpp is called.  In this error callback we do a longjmp, so
--- a/image/src/DecodePool.cpp
+++ b/image/src/DecodePool.cpp
@@ -7,16 +7,17 @@
 
 #include <algorithm>
 
 #include "mozilla/ClearOnShutdown.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
+#include "nsProxyRelease.h"
 #include "nsXPCOMCIDInternal.h"
 #include "prsystem.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #include "gfxPrefs.h"
@@ -36,98 +37,112 @@ namespace image {
 
 class NotifyProgressWorker : public nsRunnable
 {
 public:
   /**
    * Called by the DecodePool when it's done some significant portion of
    * decoding, so that progress can be recorded and notifications can be sent.
    */
-  static void Dispatch(RasterImage* aImage,
-                       Progress aProgress,
-                       const nsIntRect& aInvalidRect,
-                       uint32_t aFlags)
+  static void Dispatch(RasterImage* aImage)
   {
-    MOZ_ASSERT(aImage);
-
-    nsCOMPtr<nsIRunnable> worker =
-      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags);
+    nsCOMPtr<nsIRunnable> worker = new NotifyProgressWorker(aImage);
     NS_DispatchToMainThread(worker);
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
-    mImage->NotifyProgress(mProgress, mInvalidRect, mFlags);
+    ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
+
+    mImage->FinishedSomeDecoding(ShutdownReason::DONE);
+
     return NS_OK;
   }
 
 private:
-  NotifyProgressWorker(RasterImage* aImage, Progress aProgress,
-                       const nsIntRect& aInvalidRect, uint32_t aFlags)
+  explicit NotifyProgressWorker(RasterImage* aImage)
     : mImage(aImage)
-    , mProgress(aProgress)
-    , mInvalidRect(aInvalidRect)
-    , mFlags(aFlags)
   { }
 
   nsRefPtr<RasterImage> mImage;
-  const Progress mProgress;
-  const nsIntRect mInvalidRect;
-  const uint32_t mFlags;
-};
-
-class NotifyDecodeCompleteWorker : public nsRunnable
-{
-public:
-  /**
-   * Called by the DecodePool when decoding is complete, so that final cleanup
-   * can be performed.
-   */
-  static void Dispatch(Decoder* aDecoder)
-  {
-    MOZ_ASSERT(aDecoder);
-
-    nsCOMPtr<nsIRunnable> worker = new NotifyDecodeCompleteWorker(aDecoder);
-    NS_DispatchToMainThread(worker);
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    DecodePool::Singleton()->NotifyDecodeComplete(mDecoder);
-    return NS_OK;
-  }
-
-private:
-  explicit NotifyDecodeCompleteWorker(Decoder* aDecoder)
-    : mDecoder(aDecoder)
-  { }
-
-  nsRefPtr<Decoder> mDecoder;
 };
 
 class DecodeWorker : public nsRunnable
 {
 public:
-  explicit DecodeWorker(Decoder* aDecoder)
-    : mDecoder(aDecoder)
-  {
-    MOZ_ASSERT(mDecoder);
-  }
+  explicit DecodeWorker(RasterImage* aImage)
+    : mImage(aImage)
+  { }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(!NS_IsMainThread());
-    DecodePool::Singleton()->Decode(mDecoder);
+
+    ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
+
+    // If we were interrupted, we shouldn't do any work.
+    if (mImage->mDecodeStatus == DecodeStatus::STOPPED) {
+      NotifyProgressWorker::Dispatch(mImage);
+      return NS_OK;
+    }
+
+    // If someone came along and synchronously decoded us, there's nothing for us to do.
+    if (!mImage->mDecoder || mImage->IsDecodeFinished()) {
+      NotifyProgressWorker::Dispatch(mImage);
+      return NS_OK;
+    }
+
+    mImage->mDecodeStatus = DecodeStatus::ACTIVE;
+
+    size_t oldByteCount = mImage->mDecoder->BytesDecoded();
+
+    size_t maxBytes = mImage->mSourceData.Length() -
+                      mImage->mDecoder->BytesDecoded();
+    DecodePool::Singleton()->DecodeSomeOfImage(mImage, DecodeUntil::DONE_BYTES,
+                                               maxBytes);
+
+    size_t bytesDecoded = mImage->mDecoder->BytesDecoded() - oldByteCount;
+
+    mImage->mDecodeStatus = DecodeStatus::WORK_DONE;
+
+    if (mImage->mDecoder &&
+        !mImage->mError &&
+        !mImage->mPendingError &&
+        !mImage->IsDecodeFinished() &&
+        bytesDecoded < maxBytes &&
+        bytesDecoded > 0) {
+      // We aren't finished decoding, and we have more data, so add this request
+      // to the back of the list.
+      DecodePool::Singleton()->RequestDecode(mImage);
+    } else {
+      // Nothing more for us to do - let everyone know what happened.
+      NotifyProgressWorker::Dispatch(mImage);
+    }
+
     return NS_OK;
   }
 
+protected:
+  virtual ~DecodeWorker()
+  {
+    // Dispatch mImage to main thread to prevent mImage from being destructed by decode thread.
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+    NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
+    if (mainThread) {
+      // Handle ambiguous nsISupports inheritance
+      RasterImage* rawImg = nullptr;
+      mImage.swap(rawImg);
+      DebugOnly<nsresult> rv = NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
+    }
+  }
+
 private:
-  nsRefPtr<Decoder> mDecoder;
+  nsRefPtr<RasterImage> mImage;
 };
 
 #ifdef MOZ_NUWA_PROCESS
 
 class RIDThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -174,16 +189,23 @@ DecodePool::Singleton()
     MOZ_ASSERT(NS_IsMainThread());
     sSingleton = new DecodePool();
     ClearOnShutdown(&sSingleton);
   }
 
   return sSingleton;
 }
 
+already_AddRefed<nsIEventTarget>
+DecodePool::GetEventTarget()
+{
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
+  return target.forget();
+}
+
 DecodePool::DecodePool()
   : mThreadPoolMutex("Thread Pool")
 {
   mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
   MOZ_RELEASE_ASSERT(mThreadPool,
                      "Should succeed in creating image decoding thread pool");
 
   mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
@@ -231,111 +253,166 @@ DecodePool::Observe(nsISupports*, const 
   if (threadPool) {
     threadPool->Shutdown();
   }
 
   return NS_OK;
 }
 
 void
-DecodePool::AsyncDecode(Decoder* aDecoder)
+DecodePool::RequestDecode(RasterImage* aImage)
 {
-  MOZ_ASSERT(aDecoder);
+  MOZ_ASSERT(aImage->mDecoder);
+  aImage->mDecodingMonitor.AssertCurrentThreadIn();
 
-  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
+  if (aImage->mDecodeStatus == DecodeStatus::PENDING ||
+      aImage->mDecodeStatus == DecodeStatus::ACTIVE) {
+    // The image is already in our list of images to decode, or currently being
+    // decoded, so we don't have to do anything else.
+    return;
+  }
+
+  aImage->mDecodeStatus = DecodeStatus::PENDING;
+  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aImage);
 
   // Dispatch to the thread pool if it exists. If it doesn't, we're currently
   // shutting down, so it's OK to just drop the job on the floor.
   MutexAutoLock threadPoolLock(mThreadPoolMutex);
   if (mThreadPool) {
     mThreadPool->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
   }
 }
 
 void
-DecodePool::SyncDecodeIfSmall(Decoder* aDecoder)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aDecoder);
-
-  if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) {
-    Decode(aDecoder);
-    return;
-  }
-
-  AsyncDecode(aDecoder);
-}
-
-void
-DecodePool::SyncDecodeIfPossible(Decoder* aDecoder)
+DecodePool::DecodeABitOf(RasterImage* aImage)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  Decode(aDecoder);
-}
+  aImage->mDecodingMonitor.AssertCurrentThreadIn();
 
-already_AddRefed<nsIEventTarget>
-DecodePool::GetEventTarget()
-{
-  MutexAutoLock threadPoolLock(mThreadPoolMutex);
-  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
-  return target.forget();
-}
+  // If the image is waiting for decode work to be notified, go ahead and do that.
+  if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
+    aImage->FinishedSomeDecoding();
+  }
+
+  DecodeSomeOfImage(aImage);
 
-already_AddRefed<nsIRunnable>
-DecodePool::CreateDecodeWorker(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
-  return worker.forget();
-}
+  aImage->FinishedSomeDecoding();
 
-void
-DecodePool::Decode(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-
-  nsresult rv = aDecoder->Decode();
-
-  if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
-    if (aDecoder->HasProgress()) {
-      NotifyProgress(aDecoder);
-    }
-    // The decoder will ensure that a new worker gets enqueued to continue
-    // decoding when more data is available.
-  } else {
-    NotifyDecodeComplete(aDecoder);
+  // 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 (aImage->mDecoder &&
+      !aImage->mError &&
+      !aImage->IsDecodeFinished() &&
+      aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded()) {
+    RequestDecode(aImage);
   }
 }
 
-void
-DecodePool::NotifyProgress(Decoder* aDecoder)
+/* static */ void
+DecodePool::StopDecoding(RasterImage* aImage)
 {
-  MOZ_ASSERT(aDecoder);
+  aImage->mDecodingMonitor.AssertCurrentThreadIn();
+
+  // If we haven't got a decode request, we're not currently decoding. (Having
+  // a decode request doesn't imply we *are* decoding, though.)
+  aImage->mDecodeStatus = DecodeStatus::STOPPED;
+}
+
+nsresult
+DecodePool::DecodeUntilSizeAvailable(RasterImage* aImage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ReentrantMonitorAutoEnter lock(aImage->mDecodingMonitor);
+
+  // If the image is waiting for decode work to be notified, go ahead and do that.
+  if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
+    nsresult rv = aImage->FinishedSomeDecoding();
+    if (NS_FAILED(rv)) {
+      aImage->DoError();
+      return rv;
+    }
+  }
 
-  if (!NS_IsMainThread()) {
-    NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
-                                   aDecoder->TakeProgress(),
-                                   aDecoder->TakeInvalidRect(),
-                                   aDecoder->GetDecodeFlags());
-    return;
+  nsresult rv = DecodeSomeOfImage(aImage, DecodeUntil::SIZE);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return aImage->FinishedSomeDecoding();
+}
+
+nsresult
+DecodePool::DecodeSomeOfImage(RasterImage* aImage,
+                              DecodeUntil aDecodeUntil /* = DecodeUntil::TIME */,
+                              uint32_t bytesToDecode /* = 0 */)
+{
+  MOZ_ASSERT(aImage->mInitialized, "Worker active for uninitialized container");
+  aImage->mDecodingMonitor.AssertCurrentThreadIn();
+
+  // If an error is flagged, it probably happened while we were waiting
+  // in the event queue.
+  if (aImage->mError) {
+    return NS_OK;
+  }
+
+  // If there is an error worker pending (say because the main thread has enqueued
+  // another decode request for us before processing the error worker) then bail out.
+  if (aImage->mPendingError) {
+    return NS_OK;
   }
 
-  aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
-                                       aDecoder->TakeInvalidRect(),
-                                       aDecoder->GetDecodeFlags());
-}
+  // 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 (!aImage->mDecoder || aImage->mDecoded) {
+    return NS_OK;
+  }
+
+  nsRefPtr<Decoder> decoderKungFuDeathGrip = aImage->mDecoder;
 
-void
-DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
+  uint32_t maxBytes;
+  if (aImage->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 = aImage->mSourceData.Length();
+  } else {
+    // We're only guaranteed to decode this many bytes, so in particular,
+    // gfxPrefs::ImageMemDecodeBytesAtATime should be set high enough for us
+    // to read the size from most images.
+    maxBytes = gfxPrefs::ImageMemDecodeBytesAtATime();
+  }
 
-  if (!NS_IsMainThread()) {
-    NotifyDecodeCompleteWorker::Dispatch(aDecoder);
-    return;
+  if (bytesToDecode == 0) {
+    bytesToDecode = aImage->mSourceData.Length() - aImage->mDecoder->BytesDecoded();
   }
 
-  aDecoder->Finish();
-  aDecoder->GetImage()->FinalizeDecoder(aDecoder);
+  TimeStamp deadline = TimeStamp::Now() +
+                       TimeDuration::FromMilliseconds(gfxPrefs::ImageMemMaxMSBeforeYield());
+
+  // We keep decoding chunks until:
+  //  * we don't have any data left to decode,
+  //  * the decode completes,
+  //  * we're an DecodeUntil::SIZE decode and we get the size, or
+  //  * we run out of time.
+  while (aImage->mSourceData.Length() > aImage->mDecoder->BytesDecoded() &&
+         bytesToDecode > 0 &&
+         !aImage->IsDecodeFinished() &&
+         !(aDecodeUntil == DecodeUntil::SIZE && aImage->mHasSize)) {
+    uint32_t chunkSize = min(bytesToDecode, maxBytes);
+    nsresult rv = aImage->DecodeSomeData(chunkSize);
+    if (NS_FAILED(rv)) {
+      aImage->DoError();
+      return rv;
+    }
+
+    bytesToDecode -= chunkSize;
+
+    // Yield if we've been decoding for too long. We check this _after_ decoding
+    // a chunk to ensure that we don't yield without doing any decoding.
+    if (aDecodeUntil == DecodeUntil::TIME && TimeStamp::Now() >= deadline) {
+      break;
+    }
+  }
+
+  return NS_OK;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/DecodePool.h
+++ b/image/src/DecodePool.h
@@ -20,80 +20,110 @@
 class nsIThreadPool;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
 class RasterImage;
 
+MOZ_BEGIN_ENUM_CLASS(DecodeStatus, uint8_t)
+  INACTIVE,
+  PENDING,
+  ACTIVE,
+  WORK_DONE,
+  STOPPED
+MOZ_END_ENUM_CLASS(DecodeStatus)
+
+MOZ_BEGIN_ENUM_CLASS(DecodeUntil, uint8_t)
+  TIME,
+  SIZE,
+  DONE_BYTES
+MOZ_END_ENUM_CLASS(DecodeUntil)
+
+MOZ_BEGIN_ENUM_CLASS(ShutdownReason, uint8_t)
+  DONE,
+  NOT_NEEDED,
+  FATAL_ERROR
+MOZ_END_ENUM_CLASS(ShutdownReason)
+
+
 /**
- * DecodePool is a singleton class that manages decoding of raster images. It
- * owns a pool of image decoding threads that are used for asynchronous
- * decoding.
+ * DecodePool is a singleton class we use when decoding large images.
  *
- * DecodePool allows callers to run a decoder, handling management of the
- * decoder's lifecycle and whether it executes on the main thread,
- * off-main-thread in the image decoding thread pool, or on some combination of
- * the two.
+ * When we wish to decode an image larger than
+ * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode()
+ * for the image.  This adds the image to a queue of pending requests and posts
+ * the DecodePool singleton to the event queue, if it's not already pending
+ * there.
+ *
+ * When the DecodePool is run from the event queue, it decodes the image (and
+ * all others it's managing) in chunks, periodically yielding control back to
+ * the event loop.
  */
 class DecodePool : public nsIObserver
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static DecodePool* Singleton();
 
-  /// Ask the DecodePool to run @aDecoder asynchronously and return immediately.
-  void AsyncDecode(Decoder* aDecoder);
+  /**
+   * Ask the DecodePool to asynchronously decode this image.
+   */
+  void RequestDecode(RasterImage* aImage);
 
   /**
-   * Run @aDecoder synchronously if the image it's decoding is small. If the
-   * image is too large, or if the source data isn't complete yet, run @aDecoder
-   * asynchronously instead.
+   * Decode aImage for a short amount of time, and post the remainder to the
+   * queue.
    */
-  void SyncDecodeIfSmall(Decoder* aDecoder);
+  void DecodeABitOf(RasterImage* aImage);
 
   /**
-   * Run aDecoder synchronously if at all possible. If it can't complete
-   * synchronously because the source data isn't complete, asynchronously decode
-   * the rest.
+   * Ask the DecodePool to stop decoding this image.  Internally, we also
+   * call this function when we finish decoding an image.
+   *
+   * Since the DecodePool keeps raw pointers to RasterImages, make sure you
+   * call this before a RasterImage is destroyed!
    */
-  void SyncDecodeIfPossible(Decoder* aDecoder);
+  static void StopDecoding(RasterImage* aImage);
 
   /**
-   * Returns an event target interface to the DecodePool's underlying thread
-   * pool. Callers can use this event target to submit work to the image
-   * decoding thread pool.
+   * Synchronously decode the beginning of the image until we run out of
+   * bytes or we get the image's size.  Note that this done on a best-effort
+   * basis; if the size is burried too deep in the image, we'll give up.
    *
-   * @return An nsIEventTarget interface to the thread pool.
+   * @return NS_ERROR if an error is encountered, and NS_OK otherwise.  (Note
+   *         that we return NS_OK even when the size was not found.)
+   */
+  nsresult DecodeUntilSizeAvailable(RasterImage* aImage);
+
+  /**
+   * Returns an event target interface to the thread pool; primarily for
+   * OnDataAvailable delivery off main thread.
+   *
+   * @return An nsIEventTarget interface to mThreadPool.
    */
   already_AddRefed<nsIEventTarget> GetEventTarget();
 
   /**
-   * Creates a worker which can be used to attempt further decoding using the
-   * provided decoder.
-   *
-   * @return The new worker, which should be posted to the event target returned
-   *         by GetEventTarget.
+   * Decode some chunks of the given image.  If aDecodeUntil is SIZE,
+   * decode until we have the image's size, then stop. If bytesToDecode is
+   * non-0, at most bytesToDecode bytes will be decoded. if aDecodeUntil is
+   * DONE_BYTES, decode until all bytesToDecode bytes are decoded.
    */
-  already_AddRefed<nsIRunnable> CreateDecodeWorker(Decoder* aDecoder);
+  nsresult DecodeSomeOfImage(RasterImage* aImage,
+                             DecodeUntil aDecodeUntil = DecodeUntil::TIME,
+                             uint32_t bytesToDecode = 0);
 
 private:
-  friend class DecodeWorker;
-  friend class NotifyDecodeCompleteWorker;
-
   DecodePool();
   virtual ~DecodePool();
 
-  void Decode(Decoder* aDecoder);
-  void NotifyDecodeComplete(Decoder* aDecoder);
-  void NotifyProgress(Decoder* aDecoder);
-
   static StaticRefPtr<DecodePool> sSingleton;
 
   // mThreadPoolMutex protects mThreadPool. For all RasterImages R,
   // R::mDecodingMonitor must be acquired before mThreadPoolMutex
   // if both are acquired; the other order may cause deadlock.
   Mutex                     mThreadPoolMutex;
   nsCOMPtr<nsIThreadPool>   mThreadPool;
 };
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -2,314 +2,343 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
 #include "Decoder.h"
 
 #include "mozilla/gfx/2D.h"
-#include "DecodePool.h"
-#include "GeckoProfiler.h"
 #include "imgIContainer.h"
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
-#include "nsProxyRelease.h"
+#include "GeckoProfiler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
 
 namespace mozilla {
 namespace image {
 
-Decoder::Decoder(RasterImage* aImage)
+Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
   , mProgress(NoProgress)
   , mImageData(nullptr)
   , mColormap(nullptr)
   , mChunkCount(0)
   , mDecodeFlags(0)
   , mBytesDecoded(0)
-  , mSendPartialInvalidations(false)
-  , mDataDone(false)
   , mDecodeDone(false)
   , mDataError(false)
-  , mDecodeAborted(false)
-  , mImageIsTransient(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
+  , mNeedsNewFrame(false)
+  , mNeedsToFlushData(false)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
 { }
 
 Decoder::~Decoder()
 {
   MOZ_ASSERT(mProgress == NoProgress,
              "Destroying Decoder without taking all its progress changes");
   MOZ_ASSERT(mInvalidRect.IsEmpty(),
              "Destroying Decoder without taking all its invalidations");
   mInitialized = false;
-
-  if (!NS_IsMainThread()) {
-    // Dispatch mImage to main thread to prevent it from being destructed by the
-    // decode thread.
-    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
-    NS_WARN_IF_FALSE(mainThread, "Couldn't get the main thread!");
-    if (mainThread) {
-      // Handle ambiguous nsISupports inheritance.
-      RasterImage* rawImg = nullptr;
-      mImage.swap(rawImg);
-      DebugOnly<nsresult> rv =
-        NS_ProxyRelease(mainThread, NS_ISUPPORTS_CAST(ImageResource*, rawImg));
-      MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to proxy release to main thread");
-    }
-  }
 }
 
 /*
  * Common implementation of the decoder interface.
  */
 
 void
 Decoder::Init()
 {
   // No re-initializing
-  MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!");
+  NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
 
   // Fire OnStartDecode at init time to support bug 512435.
   if (!IsSizeDecode()) {
       mProgress |= FLAG_DECODE_STARTED | FLAG_ONLOAD_BLOCKED;
   }
 
   // Implementation-specific initialization
   InitInternal();
 
   mInitialized = true;
 }
 
-nsresult
-Decoder::Decode()
+// Initializes a decoder whose image and observer is already being used by a
+// parent decoder
+void
+Decoder::InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength,
+                           uint32_t* aColormap, uint32_t aColormapSize,
+                           RawAccessFrameRef&& aFrameRef)
 {
-  MOZ_ASSERT(mInitialized, "Should be initialized here");
-  MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
-
-  // We keep decoding chunks until the decode completes or there are no more
-  // chunks available.
-  while (!GetDecodeDone() && !HasError()) {
-    auto newState = mIterator->AdvanceOrScheduleResume(this);
+  // No re-initializing
+  NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
 
-    if (newState == SourceBufferIterator::WAITING) {
-      // We can't continue because the rest of the data hasn't arrived from the
-      // network yet. We don't have to do anything special; the
-      // SourceBufferIterator will ensure that Decode() gets called again on a
-      // DecodePool thread when more data is available.
-      return NS_OK;
-    }
-
-    if (newState == SourceBufferIterator::COMPLETE) {
-      mDataDone = true;
+  mImageData = aImageData;
+  mImageDataLength = aImageDataLength;
+  mColormap = aColormap;
+  mColormapSize = aColormapSize;
+  mCurrentFrame = Move(aFrameRef);
 
-      nsresult finalStatus = mIterator->CompletionStatus();
-      if (NS_FAILED(finalStatus)) {
-        PostDataError();
-      }
-
-      return finalStatus;
-    }
-
-    MOZ_ASSERT(newState == SourceBufferIterator::READY);
-
-    Write(mIterator->Data(), mIterator->Length());
+  // We have all the frame data, so we've started the frame.
+  if (!IsSizeDecode()) {
+    mFrameCount++;
+    PostFrameStart();
   }
 
-  return HasError() ? NS_ERROR_FAILURE : NS_OK;
-}
-
-void
-Decoder::Resume()
-{
-  DecodePool* decodePool = DecodePool::Singleton();
-  MOZ_ASSERT(decodePool);
-
-  nsCOMPtr<nsIEventTarget> target = decodePool->GetEventTarget();
-  if (MOZ_UNLIKELY(!target)) {
-    // We're shutting down and the DecodePool's thread pool has been destroyed.
-    return;
-  }
-
-  nsCOMPtr<nsIRunnable> worker = decodePool->CreateDecodeWorker(this);
-  target->Dispatch(worker, nsIEventTarget::DISPATCH_NORMAL);
-}
-
-bool
-Decoder::ShouldSyncDecode(size_t aByteLimit)
-{
-  MOZ_ASSERT(aByteLimit > 0);
-  MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
-
-  return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
+  // Implementation-specific initialization
+  InitInternal();
+  mInitialized = true;
 }
 
 void
 Decoder::Write(const char* aBuffer, uint32_t aCount)
 {
   PROFILER_LABEL("ImageDecoder", "Write",
     js::ProfileEntry::Category::GRAPHICS);
 
-  MOZ_ASSERT(aBuffer, "Should have a buffer");
-  MOZ_ASSERT(aCount > 0, "Should have a non-zero count");
-
   // We're strict about decoder errors
   MOZ_ASSERT(!HasDecoderError(),
              "Not allowed to make more decoder calls after error!");
 
   // Begin recording telemetry data.
   TimeStamp start = TimeStamp::Now();
   mChunkCount++;
 
   // Keep track of the total number of bytes written.
   mBytesDecoded += aCount;
 
+  // If we're flushing data, clear the flag.
+  if (aBuffer == nullptr && aCount == 0) {
+    MOZ_ASSERT(mNeedsToFlushData, "Flushing when we don't need to");
+    mNeedsToFlushData = false;
+  }
+
   // If a data error occured, just ignore future data.
   if (HasDataError())
     return;
 
   if (IsSizeDecode() && HasSize()) {
     // More data came in since we found the size. We have nothing to do here.
     return;
   }
 
+  MOZ_ASSERT(!NeedsNewFrame() || HasDataError(),
+             "Should not need a new frame before writing anything");
+  MOZ_ASSERT(!NeedsToFlushData() || HasDataError(),
+             "Should not need to flush data before writing anything");
+
   // Pass the data along to the implementation.
   WriteInternal(aBuffer, aCount);
 
+  // If we need a new frame to proceed, let's create one and call it again.
+  while (NeedsNewFrame() && !HasDataError()) {
+    MOZ_ASSERT(!IsSizeDecode(), "Shouldn't need new frame for size decode");
+
+    nsresult rv = AllocateFrame();
+
+    if (NS_SUCCEEDED(rv)) {
+      // Use the data we saved when we asked for a new frame.
+      WriteInternal(nullptr, 0);
+    }
+
+    mNeedsToFlushData = false;
+  }
+
   // Finish telemetry.
   mDecodeTime += (TimeStamp::Now() - start);
 }
 
 void
-Decoder::Finish()
+Decoder::Finish(ShutdownReason aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Implementation-specific finalization
   if (!HasError())
     FinishInternal();
 
   // If the implementation left us mid-frame, finish that up.
   if (mInFrame && !HasError())
     PostFrameStop();
 
-  // If PostDecodeDone() has not been called, and this decoder wasn't aborted
-  // early because of low-memory conditions or losing a race with another
-  // decoder, we need to send teardown notifications.
-  if (!IsSizeDecode() && !mDecodeDone && !WasAborted()) {
+  // 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 =
       do_GetService(NS_CONSOLESERVICE_CONTRACTID);
     nsCOMPtr<nsIScriptError> errorObject =
       do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
 
     if (consoleService && errorObject && !HasDecoderError()) {
       nsAutoString msg(NS_LITERAL_STRING("Image corrupt or truncated: ") +
-                       NS_ConvertUTF8toUTF16(mImage->GetURIString()));
+                       NS_ConvertUTF8toUTF16(mImage.GetURIString()));
 
       if (NS_SUCCEEDED(errorObject->InitWithWindowID(
                          msg,
-                         NS_ConvertUTF8toUTF16(mImage->GetURIString()),
+                         NS_ConvertUTF8toUTF16(mImage.GetURIString()),
                          EmptyString(), 0, 0, nsIScriptError::errorFlag,
-                         "Image", mImage->InnerWindowID()
+                         "Image", mImage.InnerWindowID()
                        ))) {
         consoleService->LogMessage(errorObject);
       }
     }
 
-    // If we only have a data error, we're usable if we have at least one
-    // complete frame.
-    if (!HasDecoderError() && GetCompleteFrameCount() > 0) {
-      // We're usable, so do exactly what we should have when the decoder
-      // completed.
+    bool usable = !HasDecoderError();
+    if (aReason != ShutdownReason::NOT_NEEDED && !HasDecoderError()) {
+      // If we only have a data error, we're usable if we have at least one complete frame.
+      if (GetCompleteFrameCount() == 0) {
+        usable = false;
+      }
+    }
+
+    // If we're usable, do exactly what we should have when the decoder
+    // completed.
+    if (usable) {
       if (mInFrame) {
         PostFrameStop();
       }
       PostDecodeDone();
     } else {
-      // We're not usable. Record some final progress indicating the error.
       if (!IsSizeDecode()) {
         mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
       }
       mProgress |= FLAG_HAS_ERROR;
     }
   }
 
   // Set image metadata before calling DecodingComplete, because
   // DecodingComplete calls Optimize().
-  mImageMetadata.SetOnImage(mImage);
+  mImageMetadata.SetOnImage(&mImage);
 
-  if (HasSize()) {
-    SetSizeOnImage();
+  if (mDecodeDone) {
+    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
+    mImage.DecodingComplete(mCurrentFrame.get());
   }
-
-  if (mDecodeDone && !IsSizeDecode()) {
-    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
+}
 
-    // If this image wasn't animated and isn't a transient image, mark its frame
-    // as optimizable. We don't support optimizing animated images and
-    // optimizing transient images isn't worth it.
-    if (!mIsAnimated && !mImageIsTransient && mCurrentFrame) {
-      mCurrentFrame->SetOptimizable();
-    }
+void
+Decoder::FinishSharedDecoder()
+{
+  MOZ_ASSERT(NS_IsMainThread());
 
-    mImage->OnDecodingComplete();
+  if (!HasError()) {
+    FinishInternal();
   }
 }
 
 nsresult
-Decoder::AllocateFrame(uint32_t aFrameNum,
-                       const nsIntRect& aFrameRect,
-                       gfx::SurfaceFormat aFormat,
-                       uint8_t aPaletteDepth /* = 0 */)
+Decoder::AllocateFrame()
 {
-  mCurrentFrame = AllocateFrameInternal(aFrameNum, aFrameRect, mDecodeFlags,
-                                        aFormat, aPaletteDepth,
-                                        mCurrentFrame.get());
+  MOZ_ASSERT(mNeedsNewFrame);
+
+  mCurrentFrame = EnsureFrame(mNewFrameData.mFrameNum,
+                              mNewFrameData.mFrameRect,
+                              mDecodeFlags,
+                              mNewFrameData.mFormat,
+                              mNewFrameData.mPaletteDepth,
+                              mCurrentFrame.get());
 
   if (mCurrentFrame) {
     // Gather the raw pointers the decoders will use.
     mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
     mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
 
-    if (aFrameNum + 1 == mFrameCount) {
+    if (mNewFrameData.mFrameNum + 1 == mFrameCount) {
       PostFrameStart();
     }
   } else {
     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;
+
+  // If we've received any data at all, we may have pending data that needs to
+  // be flushed now that we have a frame to decode into.
+  if (mBytesDecoded > 0) {
+    mNeedsToFlushData = true;
+  }
+
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
 RawAccessFrameRef
-Decoder::AllocateFrameInternal(uint32_t aFrameNum,
-                               const nsIntRect& aFrameRect,
-                               uint32_t aDecodeFlags,
-                               SurfaceFormat aFormat,
-                               uint8_t aPaletteDepth,
-                               imgFrame* aPreviousFrame)
+Decoder::EnsureFrame(uint32_t aFrameNum,
+                     const nsIntRect& aFrameRect,
+                     uint32_t aDecodeFlags,
+                     SurfaceFormat aFormat,
+                     uint8_t aPaletteDepth,
+                     imgFrame* aPreviousFrame)
 {
   if (mDataError || NS_FAILED(mFailCode)) {
     return RawAccessFrameRef();
   }
 
-  if (aFrameNum != mFrameCount) {
-    MOZ_ASSERT_UNREACHABLE("Allocating frames out of order");
+  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
+  if (aFrameNum > mFrameCount) {
+    return RawAccessFrameRef();
+  }
+
+  // Adding a frame that doesn't already exist. This is the normal case.
+  if (aFrameNum == mFrameCount) {
+    return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                            aPaletteDepth, aPreviousFrame);
+  }
+
+  // We're replacing a frame. It must be the first frame; there's no reason to
+  // ever replace any other frame, since the first frame is the only one we
+  // speculatively allocate without knowing what the decoder really needs.
+  // XXX(seth): I'm not convinced there's any reason to support this at all. We
+  // should figure out how to avoid triggering this and rip it out.
+  MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?");
+  MOZ_ASSERT(mFrameCount == 1, "Should have only one frame");
+  MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
+  if (aFrameNum != 0 || !aPreviousFrame || mFrameCount != 1) {
+    return RawAccessFrameRef();
+  }
+
+  MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
+             aPreviousFrame->GetFormat() != aFormat ||
+             aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
+             "Replacing first frame with the same kind of frame?");
+
+  // Remove the old frame from the SurfaceCache.
+  IntSize prevFrameSize = aPreviousFrame->GetImageSize();
+  SurfaceCache::RemoveSurface(ImageKey(&mImage),
+                              RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0));
+  mFrameCount = 0;
+  mInFrame = false;
+
+  // Add the new frame as usual.
+  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                          aPaletteDepth, nullptr);
+}
+
+RawAccessFrameRef
+Decoder::InternalAddFrame(uint32_t aFrameNum,
+                          const nsIntRect& aFrameRect,
+                          uint32_t aDecodeFlags,
+                          SurfaceFormat aFormat,
+                          uint8_t aPaletteDepth,
+                          imgFrame* aPreviousFrame)
+{
+  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
+  if (aFrameNum > mFrameCount) {
     return RawAccessFrameRef();
   }
 
   MOZ_ASSERT(mImageMetadata.HasSize());
   nsIntSize imageSize(mImageMetadata.GetWidth(), mImageMetadata.GetHeight());
   if (imageSize.width <= 0 || imageSize.height <= 0 ||
       aFrameRect.width <= 0 || aFrameRect.height <= 0) {
     NS_WARNING("Trying to add frame with zero or negative size");
@@ -327,34 +356,26 @@ Decoder::AllocateFrameInternal(uint32_t 
   if (NS_FAILED(frame->InitForDecoder(imageSize, aFrameRect, aFormat,
                                       aPaletteDepth, nonPremult))) {
     NS_WARNING("imgFrame::Init should succeed");
     return RawAccessFrameRef();
   }
 
   RawAccessFrameRef ref = frame->RawAccessRef();
   if (!ref) {
-    frame->Abort();
     return RawAccessFrameRef();
   }
 
-  InsertOutcome outcome =
-    SurfaceCache::Insert(frame, ImageKey(mImage.get()),
+  bool succeeded =
+    SurfaceCache::Insert(frame, ImageKey(&mImage),
                          RasterSurfaceKey(imageSize.ToIntSize(),
                                           aDecodeFlags,
                                           aFrameNum),
                          Lifetime::Persistent);
-  if (outcome != InsertOutcome::SUCCESS) {
-    // We either hit InsertOutcome::FAILURE, which is a temporary failure due to
-    // low memory (we know it's not permanent because we checked CanHold()
-    // above), or InsertOutcome::FAILURE_ALREADY_PRESENT, which means that
-    // another decoder beat us to decoding this frame. Either way, we should
-    // abort this decoder rather than treat this as a real error.
-    mDecodeAborted = true;
-    ref->Abort();
+  if (!succeeded) {
     return RawAccessFrameRef();
   }
 
   nsIntRect refreshArea;
 
   if (aFrameNum == 1) {
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
     aPreviousFrame->SetRawAccessOnly();
@@ -374,33 +395,30 @@ Decoder::AllocateFrameInternal(uint32_t 
     ref->SetRawAccessOnly();
 
     // Some GIFs are huge but only have a small area that they animate. We only
     // need to refresh that small area when frame 0 comes around again.
     refreshArea.UnionRect(refreshArea, frame->GetRect());
   }
 
   mFrameCount++;
-  mImage->OnAddedFrame(mFrameCount, refreshArea);
+  mImage.OnAddedFrame(mFrameCount, refreshArea);
 
   return ref;
 }
 
 void
 Decoder::SetSizeOnImage()
 {
   MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
   MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
 
-  nsresult rv = mImage->SetSize(mImageMetadata.GetWidth(),
-                                mImageMetadata.GetHeight(),
-                                mImageMetadata.GetOrientation());
-  if (NS_FAILED(rv)) {
-    PostResizeError();
-  }
+  mImage.SetSize(mImageMetadata.GetWidth(),
+                 mImageMetadata.GetHeight(),
+                 mImageMetadata.GetOrientation());
 }
 
 /*
  * Hook stubs. Override these as necessary in decoder implementations.
  */
 
 void Decoder::InitInternal() { }
 void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount) { }
@@ -460,37 +478,28 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   MOZ_ASSERT(mCurrentFrame, "Stopping frame when we don't have one");
 
   // Update our state
   mInFrame = false;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
   mProgress |= FLAG_FRAME_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
-
-  // If we're not sending partial invalidations, then we send an invalidation
-  // here when the first frame is complete.
-  if (!mSendPartialInvalidations && !mIsAnimated) {
-    mInvalidRect.UnionRect(mInvalidRect, mCurrentFrame->GetRect());
-  }
 }
 
 void
 Decoder::PostInvalidation(nsIntRect& aRect)
 {
   // We should be mid-frame
   NS_ABORT_IF_FALSE(mInFrame, "Can't invalidate when not mid-frame!");
   NS_ABORT_IF_FALSE(mCurrentFrame, "Can't invalidate when not mid-frame!");
 
-  // Record this invalidation, unless we're not sending partial invalidations
-  // or we're past the first frame.
-  if (mSendPartialInvalidations && !mIsAnimated) {
-    mInvalidRect.UnionRect(mInvalidRect, aRect);
-    mCurrentFrame->ImageUpdated(aRect);
-  }
+  // Account for the new region
+  mInvalidRect.UnionRect(mInvalidRect, aRect);
+  mCurrentFrame->ImageUpdated(aRect);
 }
 
 void
 Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
 {
   NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
   NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
   NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
@@ -500,32 +509,42 @@ Decoder::PostDecodeDone(int32_t aLoopCou
 
   mProgress |= FLAG_DECODE_COMPLETE;
 }
 
 void
 Decoder::PostDataError()
 {
   mDataError = true;
-
-  if (mInFrame && mCurrentFrame) {
-    mCurrentFrame->Abort();
-  }
 }
 
 void
 Decoder::PostDecoderError(nsresult aFailureCode)
 {
   NS_ABORT_IF_FALSE(NS_FAILED(aFailureCode), "Not a failure code!");
 
   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!");
+}
 
-  if (mInFrame && mCurrentFrame) {
-    mCurrentFrame->Abort();
-  }
+void
+Decoder::NeedNewFrame(uint32_t framenum, uint32_t x_offset, uint32_t y_offset,
+                      uint32_t width, uint32_t height,
+                      gfx::SurfaceFormat 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,
+                               nsIntRect(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
@@ -7,54 +7,76 @@
 #define MOZILLA_IMAGELIB_DECODER_H_
 
 #include "FrameAnimator.h"
 #include "RasterImage.h"
 #include "mozilla/RefPtr.h"
 #include "DecodePool.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
-#include "SourceBuffer.h"
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
 
 namespace image {
 
-class Decoder : public IResumable
+class Decoder
 {
 public:
 
-  explicit Decoder(RasterImage* aImage);
+  explicit Decoder(RasterImage& aImage);
 
   /**
    * Initialize an image decoder. Decoders may not be re-initialized.
+   *
+   * Notifications Sent: TODO
    */
   void Init();
 
   /**
-   * Decodes, reading all data currently available in the SourceBuffer. If more
-   * data is needed, Decode() automatically ensures that it will be called again
-   * on a DecodePool thread when the data becomes available.
+   * Initializes a decoder whose image and observer is already being used by a
+   * parent decoder. Decoders may not be re-initialized.
    *
-   * Any errors are reported by setting the appropriate state on the decoder.
+   * Notifications Sent: TODO
    */
-  nsresult Decode();
+  void InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength,
+                         uint32_t* aColormap, uint32_t aColormapSize,
+                         RawAccessFrameRef&& aFrameRef);
 
   /**
-   * Cleans up the decoder's state and notifies our image about success or
-   * failure. May only be called on the main thread.
+   * Writes data to the decoder.
+   *
+   * If aBuffer is null and aCount is 0, Write() flushes any buffered data to
+   * the decoder. Data is buffered if the decoder wasn't able to completely
+   * decode it because it needed a new frame.  If it's necessary to flush data,
+   * NeedsToFlushData() will return true.
+   *
+   * @param aBuffer buffer containing the data to be written
+   * @param aCount the number of bytes to write
+   *
+   * Any errors are reported by setting the appropriate state on the decoder.
+   *
+   * Notifications Sent: TODO
    */
-  void Finish();
+  void Write(const char* aBuffer, uint32_t aCount);
 
   /**
-   * Given a maximum number of bytes we're willing to decode, @aByteLimit,
-   * returns true if we should attempt to run this decoder synchronously.
+   * Informs the decoder that all the data has been written.
+   *
+   * Notifications Sent: TODO
    */
-  bool ShouldSyncDecode(size_t aByteLimit);
+  void Finish(ShutdownReason aReason);
+
+  /**
+   * Informs the shared decoder that all the data has been written.
+   * Should only be used if InitSharedDecoder was useed
+   *
+   * Notifications Sent: TODO
+   */
+  void FinishSharedDecoder();
 
   /**
    * Gets the invalidation region accumulated by the decoder so far, and clears
    * the decoder's invalidation region. This means that each call to
    * TakeInvalidRect() returns only the invalidation region accumulated since
    * the last call to TakeInvalidRect().
    */
   nsIntRect TakeInvalidRect()
@@ -71,171 +93,118 @@ public:
    */
   Progress TakeProgress()
   {
     Progress progress = mProgress;
     mProgress = NoProgress;
     return progress;
   }
 
-  /**
-   * Returns true if there's any progress to report.
-   */
-  bool HasProgress() const
-  {
-    return mProgress != NoProgress || !mInvalidRect.IsEmpty();
-  }
-
   // We're not COM-y, so we don't get refcounts by default
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
 
-  // Implement IResumable.
-  virtual void Resume() MOZ_OVERRIDE;
-
   /*
    * State.
    */
 
   // If we're doing a "size decode", we more or less pass through the image
   // data, stopping only to scoop out the image dimensions. A size decode
   // must be enabled by SetSizeDecode() _before_calling Init().
   bool IsSizeDecode() { return mSizeDecode; }
   void SetSizeDecode(bool aSizeDecode)
   {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
+    NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!");
     mSizeDecode = aSizeDecode;
   }
 
-  /**
-   * Set whether should send partial invalidations.
-   *
-   * If @aSend is true, we'll send partial invalidations when decoding the first
-   * frame of the image, so image notifications observers will be able to
-   * gradually draw in the image as it downloads.
-   *
-   * If @aSend is false (the default), we'll only send an invalidation when we
-   * complete the first frame.
-   *
-   * This must be called before Init() is called.
-   */
-  void SetSendPartialInvalidations(bool aSend)
-  {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mSendPartialInvalidations = aSend;
-  }
-
-  /**
-   * Set an iterator to the SourceBuffer which will feed data to this decoder.
-   *
-   * This should be called for almost all decoders; the exceptions are the
-   * contained decoders of an nsICODecoder, which will be fed manually via Write
-   * instead.
-   *
-   * This must be called before Init() is called.
-   */
-  void SetIterator(SourceBufferIterator&& aIterator)
-  {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mIterator.emplace(Move(aIterator));
-  }
-
-  /**
-   * Set whether this decoder is associated with a transient image. The decoder
-   * may choose to avoid certain optimizations that don't pay off for
-   * short-lived images in this case.
-   */
-  void SetImageIsTransient(bool aIsTransient)
-  {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mImageIsTransient = aIsTransient;
-  }
-
   size_t BytesDecoded() const { return mBytesDecoded; }
 
   // The amount of time we've spent inside Write() so far for this decoder.
   TimeDuration DecodeTime() const { return mDecodeTime; }
 
   // The number of times Write() has been called so far for this decoder.
   uint32_t ChunkCount() const { return mChunkCount; }
 
   // The number of frames we have, including anything in-progress. Thus, this
   // is only 0 if we haven't begun any frames.
   uint32_t GetFrameCount() { return mFrameCount; }
 
   // The number of complete frames we have (ie, not including anything in-progress).
   uint32_t GetCompleteFrameCount() { return mInFrame ? mFrameCount - 1 : mFrameCount; }
 
   // Error tracking
-  bool HasError() const { return HasDataError() || HasDecoderError(); }
-  bool HasDataError() const { return mDataError; }
-  bool HasDecoderError() const { return NS_FAILED(mFailCode); }
-  nsresult GetDecoderError() const { return mFailCode; }
+  bool HasError() { return HasDataError() || HasDecoderError(); }
+  bool HasDataError() { return mDataError; }
+  bool HasDecoderError() { return NS_FAILED(mFailCode); }
+  nsresult GetDecoderError() { return mFailCode; }
   void PostResizeError() { PostDataError(); }
-
-  bool GetDecodeDone() const
-  {
-    return mDecodeDone || (mSizeDecode && HasSize()) || HasError() || mDataDone;
+  bool GetDecodeDone() const {
+    return mDecodeDone;
   }
 
-  /**
-   * Returns true if this decoder was aborted.
-   *
-   * This may happen due to a low-memory condition, or because another decoder
-   * was racing with this one to decode the same frames with the same flags and
-   * this decoder lost the race. Either way, this is not a permanent situation
-   * and does not constitute an error, so we don't report any errors when this
-   * happens.
-   */
-  bool WasAborted() const { return mDecodeAborted; }
-
   // flags.  Keep these in sync with imgIContainer.idl.
   // SetDecodeFlags must be called before Init(), otherwise
   // default flags are assumed.
   enum {
     DECODER_NO_PREMULTIPLY_ALPHA = 0x2,     // imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
     DECODER_NO_COLORSPACE_CONVERSION = 0x4  // imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION
   };
 
   enum DecodeStyle {
       PROGRESSIVE, // produce intermediate frames representing the partial state of the image
       SEQUENTIAL // decode to final image immediately
   };
 
   void SetDecodeFlags(uint32_t aFlags) { mDecodeFlags = aFlags; }
   uint32_t GetDecodeFlags() { return mDecodeFlags; }
 
-  nsIntSize GetSize() const { return mImageMetadata.GetSize(); }
   bool HasSize() const { return mImageMetadata.HasSize(); }
   void SetSizeOnImage();
 
+  void SetSize(const nsIntSize& aSize, const Orientation& aOrientation)
+  {
+    PostSize(aSize.width, aSize.height, aOrientation);
+  }
+
   // Use HistogramCount as an invalid Histogram ID
   virtual Telemetry::ID SpeedHistogram() { return Telemetry::HistogramCount; }
 
   ImageMetadata& GetImageMetadata() { return mImageMetadata; }
 
-  /**
-   * Returns a weak pointer to the image associated with this decoder.
-   */
-  RasterImage* GetImage() const { MOZ_ASSERT(mImage); return mImage.get(); }
+  // Tell the decoder infrastructure to allocate a frame. By default, frame 0
+  // is created as an ARGB frame with no offset and with size width * height.
+  // If decoders need something different, they must ask for it.
+  // 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,
+                    gfx::SurfaceFormat 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();
 
   already_AddRefed<imgFrame> GetCurrentFrame()
   {
     nsRefPtr<imgFrame> frame = mCurrentFrame.get();
     return frame.forget();
   }
 
   RawAccessFrameRef GetCurrentFrameRef()
   {
     return mCurrentFrame ? mCurrentFrame->RawAccessRef()
                          : RawAccessFrameRef();
   }
 
 protected:
-  friend class nsICODecoder;
-
   virtual ~Decoder();
 
   /*
    * Internal hooks. Decoder implementations may override these and
    * only these methods.
    */
   virtual void InitInternal();
   virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
@@ -290,80 +259,92 @@ 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);
 
+  // Returns true if we may have stored data that we need to flush now that we
+  // have a new frame to decode into. Callers can use Write() to actually
+  // flush the data; see the documentation for that method.
+  bool NeedsToFlushData() const { return mNeedsToFlushData; }
+
   /**
-   * Allocates a new frame, making it our new current frame if successful.
-   *
-   * The @aFrameNum parameter only exists as a sanity check; it's illegal to
-   * create a new frame anywhere but immediately after the existing frames.
-   *
+   * Ensures that a given frame number exists with the given parameters, and
+   * returns a RawAccessFrameRef for that frame.
+   * It is not possible to create sparse frame arrays; you can only append
+   * frames to the current frame array, or if there is only one frame in the
+   * array, replace that frame.
    * If a non-paletted frame is desired, pass 0 for aPaletteDepth.
    */
-  nsresult AllocateFrame(uint32_t aFrameNum,
-                         const nsIntRect& aFrameRect,
-                         gfx::SurfaceFormat aFormat,
-                         uint8_t aPaletteDepth = 0);
+  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
+                                const nsIntRect& aFrameRect,
+                                uint32_t aDecodeFlags,
+                                gfx::SurfaceFormat aFormat,
+                                uint8_t aPaletteDepth,
+                                imgFrame* aPreviousFrame);
 
-  RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
-                                          const nsIntRect& aFrameRect,
-                                          uint32_t aDecodeFlags,
-                                          gfx::SurfaceFormat aFormat,
-                                          uint8_t aPaletteDepth,
-                                          imgFrame* aPreviousFrame);
-
-  /**
-   * Writes data to the decoder.
-   *
-   * @param aBuffer buffer containing the data to be written
-   * @param aCount the number of bytes to write
-   *
-   * Any errors are reported by setting the appropriate state on the decoder.
-   */
-  void Write(const char* aBuffer, uint32_t aCount);
-
+  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
+                                     const nsIntRect& aFrameRect,
+                                     uint32_t aDecodeFlags,
+                                     gfx::SurfaceFormat aFormat,
+                                     uint8_t aPaletteDepth,
+                                     imgFrame* aPreviousFrame);
   /*
    * Member variables.
    *
    */
-  nsRefPtr<RasterImage> mImage;
-  Maybe<SourceBufferIterator> mIterator;
+  RasterImage &mImage;
   RawAccessFrameRef mCurrentFrame;
   ImageMetadata mImageMetadata;
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
   Progress mProgress;
 
   uint8_t* mImageData;       // Pointer to image data in either Cairo or 8bit format
   uint32_t mImageDataLength;
   uint32_t* mColormap;       // Current colormap to be used in Cairo format
   uint32_t mColormapSize;
 
   // Telemetry data for this decoder.
   TimeDuration mDecodeTime;
   uint32_t mChunkCount;
 
   uint32_t mDecodeFlags;
   size_t mBytesDecoded;
-  bool mSendPartialInvalidations;
-  bool mDataDone;
   bool mDecodeDone;
   bool mDataError;
-  bool mDecodeAborted;
-  bool mImageIsTransient;
 
 private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsresult mFailCode;
 
+  struct NewFrameData
+  {
+    NewFrameData() { }
+
+    NewFrameData(uint32_t aFrameNum, const nsIntRect& aFrameRect,
+                 gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth)
+      : mFrameNum(aFrameNum)
+      , mFrameRect(aFrameRect)
+      , mFormat(aFormat)
+      , mPaletteDepth(aPaletteDepth)
+    { }
+
+    uint32_t mFrameNum;
+    nsIntRect mFrameRect;
+    gfx::SurfaceFormat mFormat;
+    uint8_t mPaletteDepth;
+  };
+
+  NewFrameData mNewFrameData;
+  bool mNeedsNewFrame;
+  bool mNeedsToFlushData;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
 };
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -84,18 +84,17 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
   int32_t timeout = 0;
 
   RefreshResult ret;
   RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
 
   // If we're done decoding, we know we've got everything we're going to get.
   // If we aren't, we only display fully-downloaded frames; everything else
   // gets delayed.
-  bool canDisplay = mDoneDecoding ||
-                    (nextFrame && nextFrame->IsImageComplete());
+  bool canDisplay = mDoneDecoding || (nextFrame && nextFrame->ImageComplete());
 
   if (!canDisplay) {
     // Uh oh, the frame we want to show is currently being decoded (partial)
     // Wait until the next refresh driver tick and try again
     return ret;
   }
 
   // If we're done decoding the next frame, go ahead and display it now and
@@ -285,21 +284,16 @@ FrameAnimator::GetCompositedFrame(uint32
   MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
   return ref;
 }
 
 int32_t
 FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
 {
   RawAccessFrameRef frame = GetRawFrame(aFrameNum);
-  if (!frame) {
-    NS_WARNING("No frame; called GetTimeoutForFrame too early?");
-    return 100;
-  }
-
   AnimationData data = frame->GetAnimationData();
 
   // Ensure a minimal time between updates so we don't throttle the UI thread.
   // consider 0 == unspecified and make it fast but not too fast.  Unless we
   // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
   // 207059. The behavior of recent IE and Opera versions seems to be:
   // IE 6/Win:
   //   10 - 50ms go 100ms
@@ -596,30 +590,32 @@ FrameAnimator::DoBlend(nsIntRect* aDirty
 
     AnimationData compositingPrevFrameData =
       mCompositingPrevFrame->GetAnimationData();
 
     CopyFrameImage(compositingFrameData.mRawData,
                    compositingFrameData.mRect,
                    compositingPrevFrameData.mRawData,
                    compositingPrevFrameData.mRect);
-
-    mCompositingPrevFrame->Finish();
   }
 
   // blit next frame into it's correct spot
   DrawFrameTo(nextFrameData.mRawData, nextFrameData.mRect,
               nextFrameData.mPaletteDataLength,
               nextFrameData.mHasAlpha,
               compositingFrameData.mRawData,
               compositingFrameData.mRect,
               nextFrameData.mBlendMethod);
 
   // Tell the image that it is fully 'downloaded'.
-  mCompositingFrame->Finish();
+  nsresult rv =
+    mCompositingFrame->ImageUpdated(compositingFrameData.mRect);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
 
   mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
 
   return true;
 }
 
 //******************************************************************************
 // Fill aFrame with black. Does also clears the mask.
--- a/image/src/ImageMetadata.h
+++ b/image/src/ImageMetadata.h
@@ -48,17 +48,16 @@ public:
     }
   }
 
   bool HasSize() const { return mSize.isSome(); }
   bool HasOrientation() const { return mOrientation.isSome(); }
 
   int32_t GetWidth() const { return mSize->width; }
   int32_t GetHeight() const { return mSize->height; }
-  nsIntSize GetSize() const { return *mSize; }
   Orientation GetOrientation() const { return *mOrientation; }
 
 private:
   // The hotspot found on cursors, or -1 if none was found.
   int32_t mHotspotX;
   int32_t mHotspotY;
 
   // The loop count for animated images, or -1 for infinite loop.
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -15,17 +15,16 @@
 #include "Decoder.h"
 #include "nsAutoPtr.h"
 #include "prenv.h"
 #include "prsystem.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
 #include "nsPresContext.h"
-#include "SourceBuffer.h"
 #include "SurfaceCache.h"
 #include "FrameAnimator.h"
 
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
@@ -64,16 +63,30 @@ using std::min;
 #define DECODE_FLAGS_DEFAULT 0
 
 static uint32_t
 DecodeFlags(uint32_t aFlags)
 {
   return aFlags & DECODE_FLAGS_MASK;
 }
 
+/* Accounting for compressed data */
+#if defined(PR_LOGGING)
+static PRLogModuleInfo *
+GetCompressedImageAccountingLog()
+{
+  static PRLogModuleInfo *sLog;
+  if (!sLog)
+    sLog = PR_NewLogModule("CompressedImageAccounting");
+  return sLog;
+}
+#else
+#define GetCompressedImageAccountingLog()
+#endif
+
 // The maximum number of times any one RasterImage was decoded.  This is only
 // used for statistics.
 static int32_t sMaxDecodeCount = 0;
 
 /* We define our own error checking macros here for 2 reasons:
  *
  * 1) Most of the failures we encounter here will (hopefully) be
  * the result of decoding failures (ie, bad data) and not code
@@ -186,17 +199,17 @@ public:
       bool succeeded =
         gfx::Scale(srcData.mRawData, srcData.mSize.width, srcData.mSize.height,
                    srcData.mBytesPerRow, dstData.mRawData, mDstSize.width,
                    mDstSize.height, dstData.mBytesPerRow, srcData.mFormat);
 
       if (succeeded) {
         // Mark the frame as complete and discardable.
         mDstRef->ImageUpdated(mDstRef->GetRect());
-        MOZ_ASSERT(mDstRef->IsImageComplete(),
+        MOZ_ASSERT(mDstRef->ImageComplete(),
                    "Incomplete, but just updated the entire frame");
       }
 
       // We need to send notifications and release our references on the main
       // thread, so finish up there.
       mState = succeeded ? eFinish : eFinishWithError;
       NS_DispatchToMainThread(this);
     } else if (mState == eFinish) {
@@ -256,52 +269,61 @@ NS_IMPL_ISUPPORTS(RasterImage, imgIConta
                   imgIContainerDebug)
 #endif
 
 //******************************************************************************
 RasterImage::RasterImage(ProgressTracker* aProgressTracker,
                          ImageURL* aURI /* = nullptr */) :
   ImageResource(aURI), // invoke superclass's constructor
   mSize(0,0),
+  mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
   mLockCount(0),
   mDecodeCount(0),
   mRequestedSampleSize(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
-  mSourceBuffer(new SourceBuffer()),
+  mDecodingMonitor("RasterImage Decoding Monitor"),
+  mDecoder(nullptr),
+  mDecodeStatus(DecodeStatus::INACTIVE),
   mFrameCount(0),
   mNotifyProgress(NoProgress),
   mNotifying(false),
   mHasSize(false),
   mDecodeOnDraw(false),
   mTransient(false),
   mDiscardable(false),
   mHasSourceData(false),
+  mDecoded(false),
   mHasBeenDecoded(false),
   mPendingAnimation(false),
   mAnimationFinished(false),
-  mWantFullDecode(false)
+  mWantFullDecode(false),
+  mPendingError(false)
 {
   mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
 
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
 {
-  // Make sure our SourceBuffer is marked as complete. This will ensure that any
-  // outstanding decoders terminate.
-  if (!mSourceBuffer->IsComplete()) {
-    mSourceBuffer->Complete(NS_ERROR_ABORT);
+  if (mDecoder) {
+    // Kill off our decode request, if it's pending.  (If not, this call is
+    // harmless.)
+    ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+    DecodePool::StopDecoding(this);
+    mDecoder = nullptr;
   }
 
   // Release all frames from the surface cache.
   SurfaceCache::RemoveImage(ImageKey(this));
+
+  mAnim = nullptr;
 }
 
 /* static */ void
 RasterImage::Initialize()
 {
   // Create our singletons now, so we don't have to worry about what thread
   // they're created on.
   DecodePool::Singleton();
@@ -334,21 +356,24 @@ RasterImage::Init(const char* aMimeType,
   mDecodeOnDraw = !!(aFlags & INIT_FLAG_DECODE_ON_DRAW);
   mTransient = !!(aFlags & INIT_FLAG_TRANSIENT);
 
   // Lock this image's surfaces in the SurfaceCache if we're not discardable.
   if (!mDiscardable) {
     SurfaceCache::LockImage(ImageKey(this));
   }
 
-  // Create the initial size decoder.
-  nsresult rv = Decode(DecodeStrategy::ASYNC, DECODE_FLAGS_DEFAULT,
-                       /* aDoSizeDecode = */ true);
-  if (NS_FAILED(rv)) {
-    return NS_ERROR_FAILURE;
+  // Instantiate the decoder
+  nsresult rv = InitDecoder(/* aDoSizeDecode = */ true);
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  // If we aren't storing source data, we want to switch from a size decode to
+  // a full decode as soon as possible.
+  if (!StoringSourceData()) {
+    mWantFullDecode = true;
   }
 
   // Mark us as initialized
   mInitialized = true;
 
   return NS_OK;
 }
 
@@ -375,17 +400,21 @@ RasterImage::RequestRefresh(const TimeSt
   if (res.frameAdvanced) {
     // Notify listeners that our frame has actually changed, but do this only
     // once for all frames that we've now passed (if AdvanceFrame() was called
     // more than once).
     #ifdef DEBUG
       mFramesNotified++;
     #endif
 
-    NotifyProgress(NoProgress, res.dirtyRect);
+    UpdateImageContainer();
+
+    if (mProgressTracker) {
+      mProgressTracker->SyncNotifyProgress(NoProgress, res.dirtyRect);
+    }
   }
 
   if (res.animationFinished) {
     mAnimationFinished = true;
     EvaluateAnimation();
   }
 }
 
@@ -511,42 +540,37 @@ RasterImage::LookupFrame(uint32_t aFrame
     ref = LookupFrameInternal(aFrameNum, aSize,
                               aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
   }
 
   if (!ref) {
     // The OS threw this frame away. We need to redecode if we can.
     MOZ_ASSERT(!mAnim, "Animated frames should be locked");
 
+    // Update our state so the decoder knows what to do.
+    mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK;
+    mDecoded = false;
+    mFrameCount = 0;
     WantDecodedFrames(aFlags, aShouldSyncNotify);
 
-    // If we were able to sync decode, we should already have the frame. If we
-    // had to decode asynchronously, maybe we've gotten lucky.
+    // See if we managed to redecode enough to get the frame we want.
     ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
 
     if (!ref) {
       // We didn't successfully redecode, so just fail.
       return DrawableFrameRef();
     }
   }
 
   if (ref->GetCompositingFailed()) {
     return DrawableFrameRef();
   }
 
   MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "Should not have paletted frame");
 
-  // Sync decoding guarantees that we got the frame, but if it's owned by an
-  // async decoder that's currently running, the contents of the frame may not
-  // be available yet. Make sure we get everything.
-  if (ref && mHasSourceData && aShouldSyncNotify &&
-      (aFlags & FLAG_SYNC_DECODE)) {
-    ref->WaitUntilComplete();
-  }
-
   return ref;
 }
 
 uint32_t
 RasterImage::GetCurrentFrameIndex() const
 {
   if (mAnim) {
     return mAnim->GetCurrentAnimationFrameIndex();
@@ -845,17 +869,25 @@ RasterImage::UpdateImageContainer()
   }
 
   mImageContainer->SetCurrentImage(image);
 }
 
 size_t
 RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
-  return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf);
+  // n == 0 is possible for two reasons.
+  // - This is a zero-length image.
+  // - We're on a platform where moz_malloc_size_of always returns 0.
+  // In either case the fallback works appropriately.
+  size_t n = mSourceData.SizeOfExcludingThis(aMallocSizeOf);
+  if (n == 0) {
+    n = mSourceData.Length();
+  }
+  return n;
 }
 
 size_t
 RasterImage::SizeOfDecoded(gfxMemoryLocation aLocation,
                            MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
   n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
@@ -896,93 +928,106 @@ RasterImage::OnAddedFrame(uint32_t aNewF
 {
   if (!NS_IsMainThread()) {
     nsCOMPtr<nsIRunnable> runnable =
       new OnAddedFrameRunnable(this, aNewFrameCount, aNewRefreshArea);
     NS_DispatchToMainThread(runnable);
     return;
   }
 
-  MOZ_ASSERT(aNewFrameCount <= mFrameCount ||
-             aNewFrameCount == mFrameCount + 1,
-             "Skipped a frame?");
-
-  if (aNewFrameCount > mFrameCount) {
-    mFrameCount = aNewFrameCount;
-
-    if (aNewFrameCount == 2) {
-      // We're becoming animated, so initialize animation stuff.
-      MOZ_ASSERT(!mAnim, "Already have animation state?");
-      mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), mAnimationMode);
-
-      // We don't support discarding animated images (See bug 414259).
-      // Lock the image and throw away the key.
-      //
-      // Note that this is inefficient, since we could get rid of the source data
-      // too. However, doing this is actually hard, because we're probably
-      // mid-decode, and thus we're decoding out of the source buffer. Since we're
-      // going to fix this anyway later, and since we didn't kill the source data
-      // in the old world either, locking is acceptable for the moment.
-      LockImage();
-
-      if (mPendingAnimation && ShouldAnimate()) {
-        StartAnimation();
-      }
-
-      if (aNewFrameCount > 1) {
-        mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea);
-      }
+  MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
+             mFrameCount < aNewFrameCount,
+             "Frame count running backwards");
+
+  mFrameCount = aNewFrameCount;
+
+  if (aNewFrameCount == 2) {
+    // We're becoming animated, so initialize animation stuff.
+    MOZ_ASSERT(!mAnim, "Already have animation state?");
+    mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), mAnimationMode);
+
+    // We don't support discarding animated images (See bug 414259).
+    // Lock the image and throw away the key.
+    //
+    // Note that this is inefficient, since we could get rid of the source data
+    // too. However, doing this is actually hard, because we're probably
+    // mid-decode, and thus we're decoding out of the source buffer. Since we're
+    // going to fix this anyway later, and since we didn't kill the source data
+    // in the old world either, locking is acceptable for the moment.
+    LockImage();
+
+    if (mPendingAnimation && ShouldAnimate()) {
+      StartAnimation();
     }
   }
+
+  if (aNewFrameCount > 1) {
+    mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea);
+  }
 }
 
 nsresult
 RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  mDecodingMonitor.AssertCurrentThreadIn();
 
   if (mError)
     return NS_ERROR_FAILURE;
 
   // Ensure that we have positive values
   // XXX - Why isn't the size unsigned? Should this be changed?
   if ((aWidth < 0) || (aHeight < 0))
     return NS_ERROR_INVALID_ARG;
 
   // if we already have a size, check the new size against the old one
   if (mHasSize &&
       ((aWidth != mSize.width) ||
        (aHeight != mSize.height) ||
        (aOrientation != mOrientation))) {
     NS_WARNING("Image changed size on redecode! This should not happen!");
+
+    // Make the decoder aware of the error so that it doesn't try to call
+    // FinishInternal during ShutdownDecoder.
+    if (mDecoder)
+      mDecoder->PostResizeError();
+
     DoError();
     return NS_ERROR_UNEXPECTED;
   }
 
   // Set the size and flag that we have it
   mSize.SizeTo(aWidth, aHeight);
   mOrientation = aOrientation;
   mHasSize = true;
 
   return NS_OK;
 }
 
 void
-RasterImage::OnDecodingComplete()
+RasterImage::DecodingComplete(imgFrame* aFinalFrame)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return;
   }
 
-  // Flag that we've been decoded before.
+  // Flag that we're done decoding.
+  // XXX - these should probably be combined when we fix animated image
+  // discarding with bug 500402.
+  mDecoded = true;
   mHasBeenDecoded = true;
 
-  // Let our FrameAnimator know not to expect any more frames.
+  // If there's only 1 frame, mark it as optimizable. Optimizing animated images
+  // is not supported. Optimizing transient images isn't worth it.
+  if (GetNumFrames() == 1 && !mTransient && aFinalFrame) {
+    aFinalFrame->SetOptimizable();
+  }
+
   if (mAnim) {
     mAnim->SetDoneDecoding(true);
   }
 }
 
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
@@ -1059,17 +1104,26 @@ RasterImage::ResetAnimation()
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
   MOZ_ASSERT(mAnim, "Should have a FrameAnimator");
   mAnim->ResetAnimation();
 
-  NotifyProgress(NoProgress, mAnim->GetFirstFrameRefreshArea());
+  UpdateImageContainer();
+
+  // Note - We probably want to kick off a redecode somewhere around here when
+  // we fix bug 500402.
+
+  // Update display
+  if (mProgressTracker) {
+    nsIntRect rect = mAnim->GetFirstFrameRefreshArea();
+    mProgressTracker->SyncNotifyProgress(NoProgress, rect);
+  }
 
   // Start the animation again. It may not have been running before, if
   // mAnimationFinished was true before entering this function.
   EvaluateAnimation();
 
   return NS_OK;
 }
 
@@ -1107,89 +1161,184 @@ RasterImage::SetLoopCount(int32_t aLoopC
 
 NS_IMETHODIMP_(nsIntRect)
 RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
   return aRect;
 }
 
 nsresult
-RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus,
-                                 bool aLastPart)
+RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
+{
+  ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  NS_ENSURE_ARG_POINTER(aBuffer);
+  nsresult rv = NS_OK;
+
+  // We should not call this if we're not initialized
+  NS_ABORT_IF_FALSE(mInitialized, "Calling AddSourceData() on uninitialized "
+                                  "RasterImage!");
+
+  // We should not call this if we're already finished adding source data
+  NS_ABORT_IF_FALSE(!mHasSourceData, "Calling AddSourceData() after calling "
+                                     "sourceDataComplete()!");
+
+  // Image is already decoded, we shouldn't be getting data, but it could
+  // be extra garbage data at the end of a file.
+  if (mDecoded) {
+    return NS_OK;
+  }
+
+  // If we're not storing source data and we've previously gotten the size,
+  // write the data directly to the decoder. (If we haven't gotten the size,
+  // we'll queue up the data and write it out when we do.)
+  if (!StoringSourceData() && mHasSize) {
+    rv = WriteToDecoder(aBuffer, aCount);
+    CONTAINER_ENSURE_SUCCESS(rv);
+
+    rv = FinishedSomeDecoding();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // Otherwise, we're storing data in the source buffer
+  else {
+
+    // Store the data
+    char *newElem = mSourceData.AppendElements(aBuffer, aCount);
+    if (!newElem)
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    if (mDecoder) {
+      DecodePool::Singleton()->RequestDecode(this);
+    }
+  }
+
+  return NS_OK;
+}
+
+/* Note!  buf must be declared as char buf[9]; */
+// just used for logging and hashing the header
+static void
+get_header_str (char *buf, char *data, size_t data_len)
+{
+  int i;
+  int n;
+  static char hex[] = "0123456789abcdef";
+
+  n = data_len < 4 ? data_len : 4;
+
+  for (i = 0; i < n; i++) {
+    buf[i * 2]     = hex[(data[i] >> 4) & 0x0f];
+    buf[i * 2 + 1] = hex[data[i] & 0x0f];
+  }
+
+  buf[i * 2] = 0;
+}
+
+nsresult
+RasterImage::DoImageDataComplete()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // Record that we have all the data we're going to get now.
+  if (mError)
+    return NS_ERROR_FAILURE;
+
+  // If we've been called before, ignore. Otherwise, flag that we have everything
+  if (mHasSourceData)
+    return NS_OK;
   mHasSourceData = true;
 
-  // Let decoders know that there won't be any more data coming.
-  mSourceBuffer->Complete(aStatus);
-
-  if (!mHasSize) {
-    // We need to guarantee that we've gotten the image's size, or at least
-    // determined that we won't be able to get it, before we deliver the load
-    // event. That means we have to do a synchronous size decode here.
-    Decode(DecodeStrategy::SYNC_IF_POSSIBLE, DECODE_FLAGS_DEFAULT,
-           /* aDoSizeDecode = */ true);
+  // If there's a decoder open, synchronously decode the beginning of the image
+  // to check for errors and get the image's size.  (If we already have the
+  // image's size, this does nothing.)  Then kick off an async decode of the
+  // rest of the image.
+  if (mDecoder) {
+    nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  {
+    ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+
+    // Free up any extra space in the backing buffer
+    mSourceData.Compact();
   }
 
-  // Determine our final status, giving precedence to Necko failure codes. We
-  // check after running the size decode above in case it triggered an error.
-  nsresult finalStatus = mError ? NS_ERROR_FAILURE : NS_OK;
-  if (NS_FAILED(aStatus)) {
-    finalStatus = aStatus;
+  // Log header information
+  if (PR_LOG_TEST(GetCompressedImageAccountingLog(), PR_LOG_DEBUG)) {
+    char buf[9];
+    get_header_str(buf, mSourceData.Elements(), mSourceData.Length());
+    PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
+            ("CompressedImageAccounting: RasterImage::SourceDataComplete() - data "
+             "is done for container %p (%s) - header %p is 0x%s (length %d)",
+             this,
+             mSourceDataMimeType.get(),
+             mSourceData.Elements(),
+             buf,
+             mSourceData.Length()));
   }
 
-  // If loading failed, report an error.
-  if (NS_FAILED(finalStatus)) {
-    DoError();
+  return NS_OK;
+}
+
+nsresult
+RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart)
+{
+  nsresult finalStatus = DoImageDataComplete();
+
+  // Give precedence to Necko failure codes.
+  if (NS_FAILED(aStatus))
+    finalStatus = aStatus;
+
+  // We just recorded OnStopRequest; we need to inform our listeners.
+  {
+    ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+    FinishedSomeDecoding(ShutdownReason::DONE,
+                         LoadCompleteProgress(aLastPart, mError, finalStatus));
   }
 
-  // Notify our listeners, which will fire this image's load event.
-  MOZ_ASSERT(mHasSize || mError, "Need to know size before firing load event");
-  MOZ_ASSERT(!mHasSize ||
-             (mProgressTracker->GetProgress() & FLAG_SIZE_AVAILABLE),
-             "Should have notified that the size is available if we have it");
-  Progress loadProgress = LoadCompleteProgress(aLastPart, mError, finalStatus);
-  NotifyProgress(loadProgress);
-
   return finalStatus;
 }
 
 nsresult
 RasterImage::OnImageDataAvailable(nsIRequest*,
                                   nsISupports*,
                                   nsIInputStream* aInStr,
                                   uint64_t,
                                   uint32_t aCount)
 {
   nsresult rv;
 
-  // WriteToSourceBuffer always consumes everything it gets if it doesn't run
-  // out of memory.
+  // WriteToRasterImage always consumes everything it gets
+  // if it doesn't run out of memory
   uint32_t bytesRead;
-  rv = aInStr->ReadSegments(WriteToSourceBuffer, this, aCount, &bytesRead);
+  rv = aInStr->ReadSegments(WriteToRasterImage, this, aCount, &bytesRead);
 
   NS_ABORT_IF_FALSE(bytesRead == aCount || HasError() || NS_FAILED(rv),
-    "WriteToSourceBuffer should consume everything if ReadSegments succeeds or "
+    "WriteToRasterImage should consume everything if ReadSegments succeeds or "
     "the image must be in error!");
 
   return rv;
 }
 
 /* static */ already_AddRefed<nsIEventTarget>
 RasterImage::GetEventTarget()
 {
   return DecodePool::Singleton()->GetEventTarget();
 }
 
 nsresult
-RasterImage::SetSourceSizeHint(uint32_t aSizeHint)
+RasterImage::SetSourceSizeHint(uint32_t sizeHint)
 {
-  return mSourceBuffer->ExpectLength(aSizeHint);
+  if (sizeHint && StoringSourceData())
+    return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+  return NS_OK;
 }
 
 /********* Methods to implement lazy allocation of nsIProperties object *************/
 NS_IMETHODIMP
 RasterImage::Get(const char *prop, const nsIID & iid, void * *result)
 {
   if (!mProperties)
     return NS_ERROR_FAILURE;
@@ -1235,93 +1384,118 @@ RasterImage::GetKeys(uint32_t *count, ch
   }
   return mProperties->GetKeys(count, keys);
 }
 
 void
 RasterImage::Discard()
 {
   MOZ_ASSERT(NS_IsMainThread());
+
   MOZ_ASSERT(CanDiscard(), "Asked to discard but can't");
-  MOZ_ASSERT(!mAnim, "Asked to discard for animated image");
+
+  // We should never discard when we have an active decoder
+  NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
+
+  // As soon as an image becomes animated, it becomes non-discardable and any
+  // timers are cancelled.
+  NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
 
   // Delete all the decoded frames.
   SurfaceCache::RemoveImage(ImageKey(this));
 
-  // Notify that we discarded.
+  // Flag that we no longer have decoded frames for this image
+  mDecoded = false;
+  mFrameCount = 0;
+
+  // Notify that we discarded
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
+
+  mDecodeStatus = DecodeStatus::INACTIVE;
 }
 
 bool
 RasterImage::CanDiscard() {
   return mHasSourceData &&       // ...have the source data...
+         !mDecoder &&            // Can't discard with an open decoder
          !mAnim;                 // Can never discard animated images
 }
 
-// Sets up a decoder for this image.
-already_AddRefed<Decoder>
-RasterImage::CreateDecoder(bool aDoSizeDecode, uint32_t aFlags)
+// Helper method to determine if we're storing the source data in a buffer
+// or just writing it directly to the decoder
+bool
+RasterImage::StoringSourceData() const {
+  return !mTransient;
+}
+
+
+// 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)
 {
+  // 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!");
+
   // Make sure we actually get size before doing a full decode.
-  if (aDoSizeDecode) {
-    MOZ_ASSERT(!mHasSize, "Should not do unnecessary size decodes");
-  } else {
-    MOZ_ASSERT(mHasSize, "Must do a size decode before a full decode!");
+  if (!aDoSizeDecode) {
+    NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before a full decode!");
   }
 
-  // Figure out which decoder we want.
+  // Figure out which decoder we want
   eDecoderType type = GetDecoderType(mSourceDataMimeType.get());
-  if (type == eDecoderType_unknown) {
-    return nullptr;
-  }
-
-  // Instantiate the appropriate decoder.
-  nsRefPtr<Decoder> decoder;
+  CONTAINER_ENSURE_TRUE(type != eDecoderType_unknown, NS_IMAGELIB_ERROR_NO_DECODER);
+
+  // Instantiate the appropriate decoder
   switch (type) {
     case eDecoderType_png:
-      decoder = new nsPNGDecoder(this);
+      mDecoder = new nsPNGDecoder(*this);
       break;
     case eDecoderType_gif:
-      decoder = new nsGIFDecoder2(this);
+      mDecoder = new nsGIFDecoder2(*this);
       break;
     case eDecoderType_jpeg:
       // If we have all the data we don't want to waste cpu time doing
-      // a progressive decode.
-      decoder = new nsJPEGDecoder(this,
-                                  mHasBeenDecoded ? Decoder::SEQUENTIAL :
-                                                    Decoder::PROGRESSIVE);
+      // a progressive decode
+      mDecoder = new nsJPEGDecoder(*this,
+                                   mHasBeenDecoded ? Decoder::SEQUENTIAL :
+                                                     Decoder::PROGRESSIVE);
       break;
     case eDecoderType_bmp:
-      decoder = new nsBMPDecoder(this);
+      mDecoder = new nsBMPDecoder(*this);
       break;
     case eDecoderType_ico:
-      decoder = new nsICODecoder(this);
+      mDecoder = new nsICODecoder(*this);
       break;
     case eDecoderType_icon:
-      decoder = new nsIconDecoder(this);
+      mDecoder = new nsIconDecoder(*this);
       break;
     default:
-      MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
+      NS_ABORT_IF_FALSE(0, "Shouldn't get here!");
   }
 
-  MOZ_ASSERT(decoder, "Should have a decoder now");
-
-  // Initialize the decoder.
-  decoder->SetSizeDecode(aDoSizeDecode);
-  decoder->SetSendPartialInvalidations(!mHasBeenDecoded);
-  decoder->SetImageIsTransient(mTransient);
-  decoder->SetDecodeFlags(DecodeFlags(aFlags));
-  decoder->SetIterator(mSourceBuffer->Iterator());
-  decoder->Init();
-
-  if (NS_FAILED(decoder->GetDecoderError())) {
-    return nullptr;
+  // Initialize the decoder
+  mDecoder->SetSizeDecode(aDoSizeDecode);
+  mDecoder->SetDecodeFlags(mFrameDecodeFlags);
+  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->SetSize(mSize, mOrientation);
+    mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
+                           SurfaceFormat::B8G8R8A8);
+    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);
 
     if (mDecodeCount > sMaxDecodeCount) {
       // Don't subtract out 0 from the histogram, because that causes its count
@@ -1329,170 +1503,362 @@ RasterImage::CreateDecoder(bool aDoSizeD
       if (sMaxDecodeCount > 0) {
         Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Subtract(sMaxDecodeCount);
       }
       sMaxDecodeCount = mDecodeCount;
       Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Add(sMaxDecodeCount);
     }
   }
 
-  return decoder.forget();
+  return NS_OK;
 }
 
-void
+// Flushes, closes, and nulls-out a decoder. Cleans up any related decoding
+// state. It is an error to call this function when there is no initialized
+// decoder.
+//
+// aReason specifies why the shutdown is happening. If aReason is
+// ShutdownReason::DONE, an error is flagged if we didn't get what we should
+// have out of the decode. If aReason is ShutdownReason::NOT_NEEDED, we don't
+// check this. If aReason is ShutdownReason::FATAL_ERROR, we shut down in error
+// mode.
+nsresult
+RasterImage::ShutdownDecoder(ShutdownReason aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mDecodingMonitor.AssertCurrentThreadIn();
+
+  // 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();
+
+  // 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;
+
+  decoder->Finish(aReason);
+
+  // Kill off our decode request, if it's pending.  (If not, this call is
+  // harmless.)
+  DecodePool::StopDecoding(this);
+
+  nsresult decoderStatus = decoder->GetDecoderError();
+  if (NS_FAILED(decoderStatus)) {
+    DoError();
+    return decoderStatus;
+  }
+
+  // We just shut down the decoder. If we didn't get what we want, but expected
+  // to, flag an error
+  bool succeeded = wasSizeDecode ? mHasSize : mDecoded;
+  if ((aReason == ShutdownReason::DONE) && !succeeded) {
+    DoError();
+    return NS_ERROR_FAILURE;
+  }
+
+  // If we finished a full decode, and we're not meant to be storing source
+  // data, stop storing it.
+  if (!wasSizeDecode && !StoringSourceData()) {
+    mSourceData.Clear();
+  }
+
+  return NS_OK;
+}
+
+// Writes the data to the decoder, updating the total number of bytes written.
+nsresult
+RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount)
+{
+  mDecodingMonitor.AssertCurrentThreadIn();
+
+  // We should have a decoder
+  NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
+
+  // Write
+  nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
+  mDecoder->Write(aBuffer, aCount);
+
+  CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
+
+  return NS_OK;
+}
+
+// This function is called in situations where it's clear that we want the
+// frames in decoded form (Draw, LookupFrame, etc).  If we're completely decoded,
+// this method resets the discard timer (if we're discardable), since wanting
+// the frames now is a good indicator of wanting them again soon. If we're not
+// decoded, this method kicks off asynchronous decoding to generate the frames.
+nsresult
 RasterImage::WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify)
 {
+  // Request a decode, which does nothing if we're already decoded.
   if (aShouldSyncNotify) {
     // We can sync notify, which means we can also sync decode.
     if (aFlags & FLAG_SYNC_DECODE) {
-      Decode(DecodeStrategy::SYNC_IF_POSSIBLE, aFlags);
-      return;
+      return SyncDecode();
     }
-
-    // Here we are explicitly trading off flashing for responsiveness in the
-    // case that we're redecoding an image (see bug 845147).
-    Decode(mHasBeenDecoded ? DecodeStrategy::ASYNC
-                           : DecodeStrategy::SYNC_FOR_SMALL_IMAGES,
-           aFlags);
-    return;
+    return StartDecoding();
   }
 
   // We can't sync notify, so do an async decode.
-  Decode(DecodeStrategy::ASYNC, aFlags);
+  return RequestDecodeCore(ASYNCHRONOUS);
 }
 
 //******************************************************************************
 /* void requestDecode() */
 NS_IMETHODIMP
 RasterImage::RequestDecode()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
-  if (mError) {
-    return NS_ERROR_FAILURE;
-  }
-  if (!mHasSize) {
-    mWantFullDecode = true;
-    return NS_OK;
-  }
-
-  // Look up the first frame of the image, which will implicitly start decoding
-  // if it's not available right now.
-  // XXX(seth): Passing false for aShouldSyncNotify here has the effect of
-  // decoding asynchronously, but that's not obvious from the argument name.
-  // This API needs to be reworked.
-  LookupFrame(0, mSize, DECODE_FLAGS_DEFAULT, /* aShouldSyncNotify = */ false);
-
-  return NS_OK;
+  return RequestDecodeCore(SYNCHRONOUS_NOTIFY);
 }
 
 /* void startDecode() */
 NS_IMETHODIMP
 RasterImage::StartDecoding()
 {
   if (!NS_IsMainThread()) {
     return NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &RasterImage::StartDecoding));
   }
-
-  if (mError) {
-    return NS_ERROR_FAILURE;
-  }
-  if (!mHasSize) {
-    mWantFullDecode = true;
-    return NS_OK;
-  }
-
-  // Look up the first frame of the image, which will implicitly start decoding
-  // if it's not available right now.
-  // XXX(seth): Passing true for aShouldSyncNotify here has the effect of
-  // synchronously decoding small images, but that's not obvious from the
-  // argument name. This API needs to be reworked.
-  LookupFrame(0, mSize, DECODE_FLAGS_DEFAULT, /* aShouldSyncNotify = */ true);
-
-  return NS_OK;
+  // Here we are explicitly trading off flashing for responsiveness in the case
+  // that we're redecoding an image (see bug 845147).
+  return RequestDecodeCore(mHasBeenDecoded ?
+    SYNCHRONOUS_NOTIFY : SYNCHRONOUS_NOTIFY_AND_SOME_DECODE);
 }
 
 bool
 RasterImage::IsDecoded()
 {
-  // XXX(seth): We need to get rid of this; it's not reliable.
-  return mHasBeenDecoded || mError;
+  return mDecoded || mError;
 }
 
 NS_IMETHODIMP
-RasterImage::Decode(DecodeStrategy aStrategy,
-                    uint32_t aFlags,
-                    bool aDoSizeDecode /* = false */)
+RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
 {
-  MOZ_ASSERT(aDoSizeDecode || NS_IsMainThread());
-
-  if (mError) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // If we don't have a size yet, we can't do any other decoding.
-  if (!mHasSize && !aDoSizeDecode) {
-    mWantFullDecode = true;
-    return NS_OK;
-  }
-
-  // Create a decoder.
-  nsRefPtr<Decoder> decoder = CreateDecoder(aDoSizeDecode, aFlags);
-  if (!decoder) {
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsresult rv;
+
+  if (mError)
     return NS_ERROR_FAILURE;
-  }
-
-  // Send out early notifications right away. (Unless this is a size decode,
-  // which doesn't send out any notifications until the end.)
-  if (!aDoSizeDecode) {
-    NotifyProgress(decoder->TakeProgress(),
-                   decoder->TakeInvalidRect(),
-                   decoder->GetDecodeFlags());
-  }
-
-  if (mHasSourceData) {
-    // If we have all the data, we can sync decode if requested.
-    if (aStrategy == DecodeStrategy::SYNC_FOR_SMALL_IMAGES) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfSmall",
-        js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
-      DecodePool::Singleton()->SyncDecodeIfSmall(decoder);
-      return NS_OK;
-    }
-
-    if (aStrategy == DecodeStrategy::SYNC_IF_POSSIBLE) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfPossible",
-        js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
-      DecodePool::Singleton()->SyncDecodeIfPossible(decoder);
+
+  // If we're already decoded, there's nothing to do.
+  if (mDecoded)
+    return NS_OK;
+
+  // If we have a size decoder open, make sure we get the size
+  if (mDecoder && mDecoder->IsSizeDecode()) {
+    nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
+    CONTAINER_ENSURE_SUCCESS(rv);
+
+    // If we didn't get the size out of the image, we won't until we get more
+    // data, so signal that we want a full decode and give up for now.
+    if (!mHasSize) {
+      mWantFullDecode = true;
       return NS_OK;
     }
   }
 
-  // Perform an async decode. We also take this path if we don't have all the
-  // source data yet, since sync decoding is impossible in that situation.
-  DecodePool::Singleton()->AsyncDecode(decoder);
+  // If the image is waiting for decode work to be notified, go ahead and do that.
+  if (mDecodeStatus == DecodeStatus::WORK_DONE &&
+      aDecodeType == SYNCHRONOUS_NOTIFY) {
+    ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+    nsresult rv = FinishedSomeDecoding();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we're fully decoded, we have nothing to do. We need this check after
+  // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result
+  // in us finishing an in-progress decode (or kicking off and finishing a
+  // synchronous decode if we're already waiting on a full decode).
+  if (mDecoded) {
+    return NS_OK;
+  }
+
+  // If we've already got a full decoder running, and have already decoded
+  // some bytes, we have nothing to do if we haven't been asked to do some
+  // sync decoding
+  if (mDecoder && !mDecoder->IsSizeDecode() && mDecoder->BytesDecoded() > 0 &&
+      aDecodeType != SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) {
+    return NS_OK;
+  }
+
+  ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+
+  // If we don't have any bytes to flush to the decoder, we can't do anything.
+  // mDecoder->BytesDecoded() can be bigger than mSourceData.Length() if we're
+  // not storing the source data.
+  if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) {
+    return NS_OK;
+  }
+
+  // After acquiring the lock we may have finished some more decoding, so
+  // we need to repeat the following three checks after getting the lock.
+
+  // If the image is waiting for decode work to be notified, go ahead and do that.
+  if (mDecodeStatus == DecodeStatus::WORK_DONE && aDecodeType != ASYNCHRONOUS) {
+    nsresult rv = FinishedSomeDecoding();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we're fully decoded, we have nothing to do. We need this check after
+  // DecodeUntilSizeAvailable and FinishedSomeDecoding because they can result
+  // in us finishing an in-progress decode (or kicking off and finishing a
+  // synchronous decode if we're already waiting on a full decode).
+  if (mDecoded) {
+    return NS_OK;
+  }
+
+  // If we've already got a full decoder running, and have already
+  // decoded some bytes, we have nothing to do.
+  if (mDecoder && !mDecoder->IsSizeDecode() && mDecoder->BytesDecoded() > 0) {
+    return NS_OK;
+  }
+
+  // If we have a size decode open, interrupt it and shut it down; or if
+  // the decoder has different flags than what we need
+  if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
+    nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  // If we don't have a decoder, create one
+  if (!mDecoder) {
+    rv = InitDecoder(/* aDoSizeDecode = */ false);
+    CONTAINER_ENSURE_SUCCESS(rv);
+
+    rv = FinishedSomeDecoding();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  MOZ_ASSERT(mDecoder);
+
+  // If we've read all the data we have, we're done
+  if (mHasSourceData && mDecoder->BytesDecoded() == 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 && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) {
+    PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf",
+      js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
+
+    DecodePool::Singleton()->DecodeABitOf(this);
+    return NS_OK;
+  }
+
+  if (!mDecoded) {
+    // 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.
+    DecodePool::Singleton()->RequestDecode(this);
+  }
+
   return NS_OK;
 }
 
+// Synchronously decodes as much data as possible
+nsresult
+RasterImage::SyncDecode()
+{
+  PROFILER_LABEL_PRINTF("RasterImage", "SyncDecode",
+    js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
+
+  // If we have a size decoder open, make sure we get the size
+  if (mDecoder && mDecoder->IsSizeDecode()) {
+    nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
+    CONTAINER_ENSURE_SUCCESS(rv);
+
+    // If we didn't get the size out of the image, we won't until we get more
+    // data, so signal that we want a full decode and give up for now.
+    if (!mHasSize) {
+      mWantFullDecode = true;
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+
+  ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+
+  // If the image is waiting for decode work to be notified, go ahead and do that.
+  if (mDecodeStatus == DecodeStatus::WORK_DONE) {
+    nsresult rv = FinishedSomeDecoding();
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  nsresult rv;
+
+  // If we're decoded already, or decoding until the size was available
+  // finished us as a side-effect, no worries
+  if (mDecoded)
+    return NS_OK;
+
+  // If we don't have any bytes to flush to the decoder, we can't do anything.
+  // mDecoder->BytesDecoded() can be bigger than mSourceData.Length() if we're
+  // not storing the source data.
+  if (mDecoder && mDecoder->BytesDecoded() > mSourceData.Length()) {
+    return NS_OK;
+  }
+
+  // If we have a decoder open with different flags than what we need, shut it
+  // down
+  if (mDecoder && mDecoder->GetDecodeFlags() != mFrameDecodeFlags) {
+    nsresult rv = FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
+    CONTAINER_ENSURE_SUCCESS(rv);
+
+    if (mDecoded && mAnim) {
+      // We can't redecode animated images, so we'll have to give up.
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+
+  // If we don't have a decoder, create one
+  if (!mDecoder) {
+    rv = InitDecoder(/* aDoSizeDecode = */ false);
+    CONTAINER_ENSURE_SUCCESS(rv);
+  }
+
+  MOZ_ASSERT(mDecoder);
+
+  // Write everything we have
+  rv = DecodeSomeData(mSourceData.Length() - mDecoder->BytesDecoded());
+  CONTAINER_ENSURE_SUCCESS(rv);
+
+  rv = FinishedSomeDecoding();
+  CONTAINER_ENSURE_SUCCESS(rv);
+  
+  // If our decoder's still open, there's still work to be done.
+  if (mDecoder) {
+    DecodePool::Singleton()->RequestDecode(this);
+  }
+
+  // All good if no errors!
+  return mError ? NS_ERROR_FAILURE : NS_OK;
+}
+
 bool
 RasterImage::CanScale(GraphicsFilter aFilter,
                       const nsIntSize& aSize,
                       uint32_t aFlags)
 {
 #ifndef MOZ_ENABLE_SKIA
   // The high-quality scaler requires Skia.
   return false;
 #else
-  // Check basic requirements: HQ downscaling is enabled, we have all the source
-  // data and know our size, the flags allow us to do it, and a 'good' filter is
-  // being used. The flags may ask us not to scale because the caller isn't
-  // drawing to the window. If we're drawing to something else (e.g. a canvas)
-  // we usually have no way of updating what we've drawn, so HQ scaling is
-  // useless.
-  if (!gfxPrefs::ImageHQDownscalingEnabled() || !mHasSize || !mHasSourceData ||
+  // Check basic requirements: HQ downscaling is enabled, we're decoded, the
+  // flags allow us to do it, and a 'good' filter is being used. The flags may
+  // ask us not to scale because the caller isn't drawing to the window. If
+  // we're drawing to something else (e.g. a canvas) we usually have no way of
+  // updating what we've drawn, so HQ scaling is useless.
+  if (!gfxPrefs::ImageHQDownscalingEnabled() || !mDecoded ||
       !(aFlags & imgIContainer::FLAG_HIGH_QUALITY_SCALING) ||
       aFilter != GraphicsFilter::FILTER_GOOD) {
     return false;
   }
 
   // We don't use the scaler for animated or transient images to avoid doing a
   // bunch of work on an image that just gets thrown away.
   if (mAnim || mTransient) {
@@ -1526,28 +1892,31 @@ RasterImage::CanScale(GraphicsFilter aFi
   gfxFloat minFactor = gfxPrefs::ImageHQDownscalingMinFactor() / 1000.0;
   return (scale.width < minFactor || scale.height < minFactor);
 #endif
 }
 
 void
 RasterImage::NotifyNewScaledFrame()
 {
-  // Send an invalidation so observers will repaint and can take advantage of
-  // the new scaled frame if possible.
-  NotifyProgress(NoProgress, nsIntRect(0, 0, mSize.width, mSize.height));
+  if (mProgressTracker) {
+    // Send an invalidation so observers will repaint and can take advantage of
+    // the new scaled frame if possible.
+    nsIntRect rect(0, 0, mSize.width, mSize.height);
+    mProgressTracker->SyncNotifyProgress(NoProgress, rect);
+  }
 }
 
 void
 RasterImage::RequestScale(imgFrame* aFrame,
                           uint32_t aFlags,
                           const nsIntSize& aSize)
 {
   // We don't scale frames which aren't fully decoded.
-  if (!aFrame->IsImageComplete()) {
+  if (!aFrame->ImageComplete()) {
     return;
   }
 
   // We can't scale frames that need padding or are single pixel.
   if (aFrame->NeedsPadding() || aFrame->IsSinglePixel()) {
     return;
   }
 
@@ -1586,17 +1955,17 @@ RasterImage::DrawWithPreDownscaleIfNeede
                                             DecodeFlags(aFlags),
                                             0));
     if (!frameRef) {
       // We either didn't have a matching scaled frame or the OS threw it away.
       // Request a new one so we'll be ready next time. For now, we'll fall back
       // to aFrameRef below.
       RequestScale(aFrameRef.get(), aFlags, aSize);
     }
-    if (frameRef && !frameRef->IsImageComplete()) {
+    if (frameRef && !frameRef->ImageComplete()) {
       frameRef.reset();  // We're still scaling, so we can't use this yet.
     }
   }
 
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
   if (!frameRef) {
     frameRef = Move(aFrameRef);
@@ -1647,41 +2016,46 @@ RasterImage::Draw(gfxContext* aContext,
     return NS_ERROR_FAILURE;
 
   NS_ENSURE_ARG_POINTER(aContext);
 
   if (IsUnlocked() && mProgressTracker) {
     mProgressTracker->OnUnlockedDraw();
   }
 
+  // We use !mDecoded && mHasSourceData to mean discarded.
+  if (!mDecoded && mHasSourceData) {
+    mDrawStartTime = TimeStamp::Now();
+  }
+
+  // If a synchronous draw is requested, flush anything that might be sitting around
+  if (aFlags & FLAG_SYNC_DECODE) {
+    nsresult rv = SyncDecode();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   // XXX(seth): For now, we deliberately don't look up a frame of size aSize
   // (though DrawWithPreDownscaleIfNeeded will do so later). It doesn't make
   // sense to do so until we support downscale-during-decode. Right now we need
   // to make sure that we always touch an mSize-sized frame so that we have
   // something to HQ scale.
-  DrawableFrameRef ref =
-    LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags);
+  DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
+                                     mSize, aFlags);
   if (!ref) {
     // Getting the frame (above) touches the image and kicks off decoding.
-    if (mDrawStartTime.IsNull()) {
-      mDrawStartTime = TimeStamp::Now();
-    }
     return NS_OK;
   }
 
-  bool shouldRecordTelemetry = !mDrawStartTime.IsNull() &&
-                               ref->IsImageComplete();
-
   DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize,
                                aRegion, aFilter, aFlags);
 
-  if (shouldRecordTelemetry) {
+  if (mDecoded && !mDrawStartTime.IsNull()) {
       TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
-      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY,
-                            int32_t(drawLatency.ToMicroseconds()));
+      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds()));
+      // clear the value of mDrawStartTime
       mDrawStartTime = TimeStamp();
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void lockImage() */
@@ -1723,59 +2097,141 @@ RasterImage::UnlockImage()
   // Decrement our lock count
   mLockCount--;
 
   // Unlock this image's surfaces in the SurfaceCache.
   if (mLockCount == 0 ) {
     SurfaceCache::UnlockImage(ImageKey(this));
   }
 
+  // If we've decoded this image once before, we're currently decoding again,
+  // and our lock count is now zero (so nothing is forcing us to keep the
+  // decoded data around), try to cancel the decode and throw away whatever
+  // we've decoded.
+  if (mHasBeenDecoded && mDecoder && mLockCount == 0 && !mAnim) {
+    PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
+           ("RasterImage[0x%p] canceling decode because image "
+            "is now unlocked.", this));
+    ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+    FinishedSomeDecoding(ShutdownReason::NOT_NEEDED);
+    return NS_OK;
+  }
+
   return NS_OK;
 }
 
 //******************************************************************************
 /* void requestDiscard() */
 NS_IMETHODIMP
 RasterImage::RequestDiscard()
 {
   if (mDiscardable &&      // Enabled at creation time...
       mLockCount == 0 &&   // ...not temporarily disabled...
+      mDecoded &&          // ...and have something to discard.
       CanDiscard()) {
     Discard();
   }
 
   return NS_OK;
 }
 
+// Flushes up to aMaxBytes to the decoder.
+nsresult
+RasterImage::DecodeSomeData(size_t aMaxBytes)
+{
+  MOZ_ASSERT(mDecoder, "Should have a decoder");
+
+  mDecodingMonitor.AssertCurrentThreadIn();
+
+  // If we have nothing else to decode, return.
+  if (mDecoder->BytesDecoded() == mSourceData.Length()) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mDecoder->BytesDecoded() < mSourceData.Length());
+
+  // write the proper amount of data
+  size_t bytesToDecode = min(aMaxBytes,
+                             mSourceData.Length() - mDecoder->BytesDecoded());
+  return WriteToDecoder(mSourceData.Elements() + mDecoder->BytesDecoded(),
+                        bytesToDecode);
+
+}
+
+// There are various indicators that tell us we're finished with the decode
+// task at hand and can shut down the decoder.
+//
+// This method may not be called if there is no decoder.
+bool
+RasterImage::IsDecodeFinished()
+{
+  // Precondition
+  mDecodingMonitor.AssertCurrentThreadIn();
+  MOZ_ASSERT(mDecoder, "Should have a decoder");
+
+  // The decode is complete if we got what we wanted.
+  if (mDecoder->IsSizeDecode()) {
+    if (mDecoder->HasSize()) {
+      return true;
+    }
+  } else if (mDecoder->GetDecodeDone()) {
+    return true;
+  }
+
+  // Otherwise, if we have all the source data and wrote all the source data,
+  // we're done.
+  //
+  // (NB - This can be the case even for non-erroneous images because
+  // Decoder::GetDecodeDone() might not return true until after we call
+  // Decoder::Finish() in ShutdownDecoder())
+  if (mHasSourceData && (mDecoder->BytesDecoded() == mSourceData.Length())) {
+    return true;
+  }
+
+  // If we get here, assume it's not finished.
+  return false;
+}
+
 // Indempotent error flagging routine. If a decoder is open, shuts it down.
 void
 RasterImage::DoError()
 {
   // If we've flagged an error before, we have nothing to do
   if (mError)
     return;
 
   // We can't safely handle errors off-main-thread, so dispatch a worker to do it.
   if (!NS_IsMainThread()) {
     HandleErrorWorker::DispatchIfNeeded(this);
     return;
   }
 
+  // Calling FinishedSomeDecoding requires us to be in the decoding monitor.
+  ReentrantMonitorAutoEnter lock(mDecodingMonitor);
+
+  // If we're mid-decode, shut down the decoder.
+  if (mDecoder) {
+    FinishedSomeDecoding(ShutdownReason::FATAL_ERROR);
+  }
+
   // Put the container in an error state.
   mError = true;
 
   // Log our error
   LOG_CONTAINER_ERROR;
 }
 
 /* static */ void
 RasterImage::HandleErrorWorker::DispatchIfNeeded(RasterImage* aImage)
 {
-  nsRefPtr<HandleErrorWorker> worker = new HandleErrorWorker(aImage);
-  NS_DispatchToMainThread(worker);
+  if (!aImage->mPendingError) {
+    aImage->mPendingError = true;
+    nsRefPtr<HandleErrorWorker> worker = new HandleErrorWorker(aImage);
+    NS_DispatchToMainThread(worker);
+  }
 }
 
 RasterImage::HandleErrorWorker::HandleErrorWorker(RasterImage* aImage)
   : mImage(aImage)
 {
   MOZ_ASSERT(mImage, "Should have image");
 }
 
@@ -1786,31 +2242,31 @@ RasterImage::HandleErrorWorker::Run()
 
   return NS_OK;
 }
 
 // nsIInputStream callback to copy the incoming image data directly to the
 // RasterImage without processing. The RasterImage is passed as the closure.
 // Always reads everything it gets, even if the data is erroneous.
 NS_METHOD
-RasterImage::WriteToSourceBuffer(nsIInputStream* /* unused */,
-                                 void*          aClosure,
-                                 const char*    aFromRawSegment,
-                                 uint32_t       /* unused */,
-                                 uint32_t       aCount,
-                                 uint32_t*      aWriteCount)
+RasterImage::WriteToRasterImage(nsIInputStream* /* unused */,
+                                void*          aClosure,
+                                const char*    aFromRawSegment,
+                                uint32_t       /* unused */,
+                                uint32_t       aCount,
+                                uint32_t*      aWriteCount)
 {
   // Retrieve the RasterImage
   RasterImage* image = static_cast<RasterImage*>(aClosure);
 
   // Copy the source data. Unless we hit OOM, we squelch the return value
   // here, because returning an error means that ReadSegments stops
   // reading data, violating our invariant that we read everything we get.
   // If we hit OOM then we fail and the load is aborted.
-  nsresult rv = image->mSourceBuffer->Append(aFromRawSegment, aCount);
+  nsresult rv = image->AddSourceData(aFromRawSegment, aCount);
   if (rv == NS_ERROR_OUT_OF_MEMORY) {
     image->DoError();
     return rv;
   }
 
   // We wrote everything we got
   *aWriteCount = aCount;
 
@@ -1832,40 +2288,134 @@ RasterImage::GetFramesNotified(uint32_t 
   NS_ENSURE_ARG_POINTER(aFramesNotified);
 
   *aFramesNotified = mFramesNotified;
 
   return NS_OK;
 }
 #endif
 
-void
-RasterImage::NotifyProgress(Progress aProgress,
-                            const nsIntRect& aInvalidRect /* = nsIntRect() */,
-                            uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */)
+nsresult
+RasterImage::RequestDecodeIfNeeded(nsresult aStatus,
+                                   ShutdownReason aReason,
+                                   bool aDone,
+                                   bool aWasSize)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If we were a size decode and a full decode was requested, now's the time.
+  if (NS_SUCCEEDED(aStatus) &&
+      aReason == ShutdownReason::DONE &&
+      aDone &&
+      aWasSize &&
+      mWantFullDecode) {
+    mWantFullDecode = false;
+
+    // If we're not meant to be storing source data and we just got the size,
+    // we need to synchronously flush all the data we got to a full decoder.
+    // When that decoder is shut down, we'll also clear our source data.
+    return StoringSourceData() ? RequestDecode()
+                               : SyncDecode();
+  }
+
+  // We don't need a full decode right now, so just return the existing status.
+  return aStatus;
+}
+
+nsresult
+RasterImage::FinishedSomeDecoding(ShutdownReason aReason /* = ShutdownReason::DONE */,
+                                  Progress aProgress /* = NoProgress */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // Ensure that we stay alive long enough to finish notifying.
+  mDecodingMonitor.AssertCurrentThreadIn();
+
+  // Ensure that, if the decoder is the last reference to the image, we don't
+  // destroy it by destroying the decoder.
   nsRefPtr<RasterImage> image(this);
 
-  bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT;
+  bool done = false;
+  bool wasSize = false;
+  bool wasDefaultFlags = false;
+  nsIntRect invalidRect;
+  nsresult rv = NS_OK;
   Progress progress = aProgress;
-  nsIntRect invalidRect = aInvalidRect;
-
+
+  if (image->mDecoder) {
+    invalidRect = image->mDecoder->TakeInvalidRect();
+    progress |= image->mDecoder->TakeProgress();
+    wasDefaultFlags = image->mDecoder->GetDecodeFlags() == DECODE_FLAGS_DEFAULT;
+
+    if (!image->mDecoder->IsSizeDecode() && image->mDecoder->ChunkCount()) {
+      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS,
+                            image->mDecoder->ChunkCount());
+    }
+
+    if (!image->mHasSize && image->mDecoder->HasSize()) {
+      image->mDecoder->SetSizeOnImage();
+    }
+
+    // If the decode finished, or we're specifically being told to shut down,
+    // tell the image and shut down the decoder.
+    if (image->IsDecodeFinished() || aReason != ShutdownReason::DONE) {
+      done = true;
+
+      // Hold on to a reference to the decoder until we're done with it
+      nsRefPtr<Decoder> decoder = image->mDecoder;
+
+      wasSize = decoder->IsSizeDecode();
+
+      // Do some telemetry if this isn't a size decode.
+      if (!wasSize) {
+        Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
+                              int32_t(decoder->DecodeTime().ToMicroseconds()));
+
+        // We record the speed for only some decoders. The rest have
+        // SpeedHistogram return HistogramCount.
+        Telemetry::ID id = decoder->SpeedHistogram();
+        if (id < Telemetry::HistogramCount) {
+          int32_t KBps = int32_t(decoder->BytesDecoded() /
+                                 (1024 * decoder->DecodeTime().ToSeconds()));
+          Telemetry::Accumulate(id, KBps);
+        }
+      }
+
+      // We need to shut down the decoder first, in order to ensure all
+      // decoding routines have been finished.
+      rv = image->ShutdownDecoder(aReason);
+      if (NS_FAILED(rv)) {
+        image->DoError();
+      }
+
+      // If there were any final changes, grab them.
+      invalidRect.Union(decoder->TakeInvalidRect());
+      progress |= decoder->TakeProgress();
+    }
+  }
+
+  if (GetCurrentFrameIndex() > 0) {
+    // Don't send invalidations for animated frames after the first; let
+    // RequestRefresh take care of that.
+    invalidRect = nsIntRect();
+  }
+  if (mHasBeenDecoded && !invalidRect.IsEmpty()) {
+    // Don't send partial invalidations if we've been decoded before.
+    invalidRect = mDecoded ? GetFirstFrameRect()
+                           : nsIntRect();
+  }
   if (!invalidRect.IsEmpty() && wasDefaultFlags) {
     // Update our image container since we're invalidating.
     UpdateImageContainer();
   }
 
   if (mNotifying) {
     // Accumulate the progress changes. We don't permit recursive notifications
     // because they cause subtle concurrency bugs, so we'll delay sending out
     // the notifications until we pop back to the lowest invocation of
-    // NotifyProgress on the stack.
+    // FinishedSomeDecoding on the stack.
     mNotifyProgress |= progress;
     mNotifyInvalidRect.Union(invalidRect);
   } else {
     MOZ_ASSERT(mNotifyProgress == NoProgress && mNotifyInvalidRect.IsEmpty(),
                "Shouldn't have an accumulated change at this point");
 
     progress = image->mProgressTracker->Difference(progress);
 
@@ -1880,68 +2430,18 @@ RasterImage::NotifyProgress(Progress aPr
       // notifications for them next.
       progress = image->mProgressTracker->Difference(mNotifyProgress);
       mNotifyProgress = NoProgress;
 
       invalidRect = mNotifyInvalidRect;
       mNotifyInvalidRect = nsIntRect();
     }
   }
-}
-
-void
-RasterImage::FinalizeDecoder(Decoder* aDecoder)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aDecoder);
-  MOZ_ASSERT(mError || mHasSize || !aDecoder->HasSize(),
-             "Should have handed off size by now");
-
-  // Send out any final notifications.
-  NotifyProgress(aDecoder->TakeProgress(),
-                 aDecoder->TakeInvalidRect(),
-                 aDecoder->GetDecodeFlags());
-
-  bool wasSize = aDecoder->IsSizeDecode();
-  bool done = aDecoder->GetDecodeDone();
-
-  if (!wasSize && aDecoder->ChunkCount()) {
-    Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS,
-                          aDecoder->ChunkCount());
-  }
-
-  if (done) {
-    // Do some telemetry if this isn't a size decode.
-    if (!wasSize) {
-      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_TIME,
-                            int32_t(aDecoder->DecodeTime().ToMicroseconds()));
-
-      // We record the speed for only some decoders. The rest have
-      // SpeedHistogram return HistogramCount.
-      Telemetry::ID id = aDecoder->SpeedHistogram();
-      if (id < Telemetry::HistogramCount) {
-        int32_t KBps = int32_t(aDecoder->BytesDecoded() /
-                               (1024 * aDecoder->DecodeTime().ToSeconds()));
-        Telemetry::Accumulate(id, KBps);
-      }
-    }
-
-    // Detect errors.
-    if (aDecoder->HasError() && !aDecoder->WasAborted()) {
-      DoError();
-    } else if (wasSize && !mHasSize) {
-      DoError();
-    }
-  }
-
-  // If we were a size decode and a full decode was requested, now's the time.
-  if (done && wasSize && mWantFullDecode) {
-    mWantFullDecode = false;
-    RequestDecode();
-  }
+
+  return RequestDecodeIfNeeded(rv, aReason, done, wasSize);
 }
 
 already_AddRefed<imgIContainer>
 RasterImage::Unwrap()
 {
   nsCOMPtr<imgIContainer> self(this);
   return self.forget();
 }
@@ -1962,17 +2462,17 @@ RasterImage::OptimalImageSizeForDest(con
 
   if (CanScale(aFilter, destSize, aFlags)) {
     DrawableFrameRef frameRef =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(destSize.ToIntSize(),
                                             DecodeFlags(aFlags),
                                             0));
 
-    if (frameRef && frameRef->IsImageComplete()) {
+    if (frameRef && frameRef->ImageComplete()) {
         return destSize;  // We have an existing HQ scale for this size.
     }
     if (!frameRef) {
       // We could HQ scale to this size, but we haven't. Request a scale now.
       frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
                              mSize, aFlags);
       if (frameRef) {
         RequestScale(frameRef.get(), aFlags, destSize);
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -24,16 +24,17 @@
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DecodePool.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnum.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/UniquePtr.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
@@ -126,23 +127,16 @@ class LayerManager;
 class ImageContainer;
 class Image;
 }
 
 namespace image {
 
 class Decoder;
 class FrameAnimator;
-class SourceBuffer;
-
-MOZ_BEGIN_ENUM_CLASS(DecodeStrategy, uint8_t)
-  ASYNC,
-  SYNC_FOR_SMALL_IMAGES,
-  SYNC_IF_POSSIBLE
-MOZ_END_ENUM_CLASS(DecodeStrategy)
 
 class RasterImage MOZ_FINAL : public ImageResource
                             , public nsIProperties
                             , public SupportsWeakPtr<RasterImage>
 #ifdef DEBUG
                             , public imgIContainerDebug
 #endif
 {
@@ -163,20 +157,20 @@ public:
 
   // Methods inherited from Image
   nsresult Init(const char* aMimeType,
                 uint32_t aFlags) MOZ_OVERRIDE;
 
   virtual void OnSurfaceDiscarded() MOZ_OVERRIDE;
 
   // Raster-specific methods
-  static NS_METHOD WriteToSourceBuffer(nsIInputStream* aIn, void* aClosure,
-                                       const char* aFromRawSegment,
-                                       uint32_t aToOffset, uint32_t aCount,
-                                       uint32_t* aWriteCount);
+  static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure,
+                                      const char* aFromRawSegment,
+                                      uint32_t aToOffset, uint32_t aCount,
+                                      uint32_t* aWriteCount);
 
   /* The total number of frames in this image. */
   uint32_t GetNumFrames() const { return mFrameCount; }
 
   virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
   virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
                                MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
 
@@ -197,46 +191,34 @@ public:
   nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
 
   /**
    * Number of times to loop the image.
    * @note -1 means forever.
    */
   void     SetLoopCount(int32_t aLoopCount);
 
-  /// Notification that the entire image has been decoded.
-  void OnDecodingComplete();
-
-  /**
-   * Sends the provided progress notifications to ProgressTracker.
-   *
-   * Main-thread only.
-   *
-   * @param aProgress    The progress notifications to send.
-   * @param aInvalidRect An invalidation rect to send.
-   * @param aFlags       The decode flags used by the decoder that generated
-   *                     these notifications, or DECODE_FLAGS_DEFAULT if the
-   *                     notifications don't come from a decoder.
-   */
-  void NotifyProgress(Progress aProgress,
-                      const nsIntRect& aInvalidRect = nsIntRect(),
-                      uint32_t aFlags = 0);
-
-  /**
-   * Records telemetry and does final teardown of the provided decoder.
-   *
-   * Main-thread only.
-   */
-  void FinalizeDecoder(Decoder* aDecoder);
+  /* notification that the entire image has been decoded */
+  void DecodingComplete(imgFrame* aFinalFrame);
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Network callbacks.
   //////////////////////////////////////////////////////////////////////////////
 
+  /* Add compressed source data to the imgContainer.
+   *
+   * The decoder will use this data, either immediately or at draw time, to
+   * decode the image.
+   *
+   * XXX This method's only caller (WriteToContainer) ignores the return
+   * value. Should this just return void?
+   */
+  nsresult AddSourceData(const char *aBuffer, uint32_t aCount);
+
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
                                         nsISupports* aContext,
                                         nsIInputStream* aInStr,
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) MOZ_OVERRIDE;
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
                                        nsresult aStatus,
@@ -250,17 +232,17 @@ public:
    * appropriately preallocating the source data buffer.
    *
    * We take this approach rather than having the source data management code do
    * something more complicated (like chunklisting) because HTTP is by far the
    * dominant source of images, and the Content-Length header is quite reliable.
    * Thus, pre-allocation simplifies code and reduces the total number of
    * allocations.
    */
-  nsresult SetSourceSizeHint(uint32_t aSizeHint);
+  nsresult SetSourceSizeHint(uint32_t sizeHint);
 
   /* Provide a hint for the requested resolution of the resulting image. */
   void SetRequestedResolution(const nsIntSize requestedResolution) {
     mRequestedResolution = requestedResolution;
   }
 
   nsIntSize GetRequestedResolution() {
     return mRequestedResolution;
@@ -280,16 +262,24 @@ public:
       GetURI()->GetSpec(spec);
     }
     return spec;
   }
 
   static void Initialize();
 
 private:
+  friend class DecodePool;
+  friend class DecodeWorker;
+  friend class FrameNeededWorker;
+  friend class NotifyProgressWorker;
+
+  nsresult FinishedSomeDecoding(ShutdownReason aReason = ShutdownReason::DONE,
+                                Progress aProgress = NoProgress);
+
   void DrawWithPreDownscaleIfNeeded(DrawableFrameRef&& aFrameRef,
                                     gfxContext* aContext,
                                     const nsIntSize& aSize,
                                     const ImageRegion& aRegion,
                                     GraphicsFilter aFilter,
                                     uint32_t aFlags);
 
   TemporaryRef<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame,
@@ -309,44 +299,54 @@ private:
   uint32_t GetCurrentFrameIndex() const;
   uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
 
   nsIntRect GetFirstFrameRect();
 
   size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                  MallocSizeOf aMallocSizeOf) const;
 
+  nsresult DoImageDataComplete();
+
   already_AddRefed<layers::Image> GetCurrentImage();
   void UpdateImageContainer();
 
+  enum RequestDecodeType {
+      ASYNCHRONOUS,
+      SYNCHRONOUS_NOTIFY,
+      SYNCHRONOUS_NOTIFY_AND_SOME_DECODE
+  };
+  NS_IMETHOD RequestDecodeCore(RequestDecodeType aDecodeType);
+
   // We would like to just check if we have a zero lock count, but we can't do
   // that for animated images because in EnsureAnimExists we lock the image and
   // never unlock so that animated images always have their lock count >= 1. In
   // that case we use our animation consumers count as a proxy for lock count.
   bool IsUnlocked() { return (mLockCount == 0 || (mAnim && mAnimationConsumers == 0)); }
 
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Decoding.
-  //////////////////////////////////////////////////////////////////////////////
-
-  already_AddRefed<Decoder> CreateDecoder(bool aDoSizeDecode, uint32_t aFlags);
-
-  void WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify);
-
-  NS_IMETHOD Decode(DecodeStrategy aStrategy, uint32_t aFlags,
-                    bool aDoSizeDecode = false);
-
 private: // data
   nsIntSize                  mSize;
   Orientation                mOrientation;
 
+  // Whether our frames were decoded using any special flags.
+  // Some flags (e.g. unpremultiplied data) may not be compatible
+  // with the browser's needs for displaying the image to the user.
+  // As such, we may need to redecode if we're being asked for
+  // a frame with different flags.  0 indicates default flags.
+  //
+  // Valid flag bits are imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
+  // and imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION.
+  uint32_t                   mFrameDecodeFlags;
+
   nsCOMPtr<nsIProperties>   mProperties;
 
-  /// If this image is animated, a FrameAnimator which manages its animation.
+  //! All the frames of the image.
+  // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
+  // that the frames actually exist (they may have been discarded to save memory, or
+  // we maybe decoding on draw).
   UniquePtr<FrameAnimator> mAnim;
 
   // Image locking.
   uint32_t                   mLockCount;
 
   // Source data members
   nsCString                  mSourceDataMimeType;
 
@@ -365,53 +365,82 @@ private: // data
 
   // If not cached in mImageContainer, this might have our image container
   WeakPtr<layers::ImageContainer> mImageContainerCache;
 
 #ifdef DEBUG
   uint32_t                       mFramesNotified;
 #endif
 
-  // The source data for this image.
-  nsRefPtr<SourceBuffer>     mSourceBuffer;
+  // Below are the pieces of data that can be accessed on more than one thread
+  // at once, and hence need to be locked by mDecodingMonitor.
+
+  // BEGIN LOCKED MEMBER VARIABLES
+  ReentrantMonitor           mDecodingMonitor;
+
+  FallibleTArray<char>       mSourceData;
+
+  // Decoder and friends
+  nsRefPtr<Decoder>          mDecoder;
+  DecodeStatus               mDecodeStatus;
+  // END LOCKED MEMBER VARIABLES
 
   // The number of frames this image has.
   uint32_t                   mFrameCount;
 
   // Notification state. Used to avoid recursive notifications.
   Progress                   mNotifyProgress;
   nsIntRect                  mNotifyInvalidRect;
   bool                       mNotifying:1;
 
   // Boolean flags (clustered together to conserve space):
   bool                       mHasSize:1;       // Has SetSize() been called?
   bool                       mDecodeOnDraw:1;  // Decoding on draw?
   bool                       mTransient:1;     // Is the image short-lived?
   bool                       mDiscardable:1;   // Is container discardable?
   bool                       mHasSourceData:1; // Do we have source data?
-  bool                       mHasBeenDecoded:1; // Decoded at least once?
+
+  // Do we have the frames in decoded form?
+  bool                       mDecoded:1;
+  bool                       mHasBeenDecoded:1;
 
   // Whether we're waiting to start animation. If we get a StartAnimation() call
   // but we don't yet have more than one frame, mPendingAnimation is set so that
   // we know to start animation later if/when we have more frames.
   bool                       mPendingAnimation:1;
 
   // Whether the animation can stop, due to running out
   // of frames, or no more owning request
   bool                       mAnimationFinished:1;
 
   // Whether, once we are done doing a size decode, we should immediately kick
   // off a full decode.
   bool                       mWantFullDecode:1;
 
+  // Set when a decode worker detects an error off-main-thread. Once the error
+  // is handled on the main thread, mError is set, but mPendingError is used to
+  // stop decode work immediately.
+  bool                       mPendingError:1;
+
+  // Decoding
+  nsresult RequestDecodeIfNeeded(nsresult aStatus, ShutdownReason aReason,
+                                 bool aDone, bool aWasSize);
+  nsresult WantDecodedFrames(uint32_t aFlags, bool aShouldSyncNotify);
+  nsresult SyncDecode();
+  nsresult InitDecoder(bool aDoSizeDecode);
+  nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
+  nsresult DecodeSomeData(size_t aMaxBytes);
+  bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
 
   // Initializes ProgressTracker and resets it on RasterImage destruction.
   nsAutoPtr<ProgressTrackerInit> mProgressTrackerInit;
 
+  nsresult ShutdownDecoder(ShutdownReason aReason);
+
 
   //////////////////////////////////////////////////////////////////////////////
   // Scaling.
   //////////////////////////////////////////////////////////////////////////////
 
   // Initiates an HQ scale for the given frame, if possible.
   void RequestScale(imgFrame* aFrame, uint32_t aFlags, const nsIntSize& aSize);
 
@@ -442,16 +471,17 @@ private: // data
   private:
     explicit HandleErrorWorker(RasterImage* aImage);
 
     nsRefPtr<RasterImage> mImage;
   };
 
   // Helpers
   bool CanDiscard();
+  bool StoringSourceData() const;
 
 protected:
   explicit RasterImage(ProgressTracker* aProgressTracker = nullptr,
                        ImageURL* aURI = nullptr);
 
   bool ShouldAnimate() MOZ_OVERRIDE;
 
   friend class ImageFactory;
deleted file mode 100644
--- a/image/src/SourceBuffer.cpp
+++ /dev/null
@@ -1,470 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-#include "SourceBuffer.h"
-
-#include <algorithm>
-#include <cmath>
-#include <cstring>
-#include "mozilla/Likely.h"
-#include "MainThreadUtils.h"
-#include "SurfaceCache.h"
-
-using std::max;
-using std::min;
-
-namespace mozilla {
-namespace image {
-
-//////////////////////////////////////////////////////////////////////////////
-// SourceBufferIterator implementation.
-//////////////////////////////////////////////////////////////////////////////
-
-SourceBufferIterator::State
-SourceBufferIterator::AdvanceOrScheduleResume(IResumable* aConsumer)
-{
-  MOZ_ASSERT(mOwner);
-  return mOwner->AdvanceIteratorOrScheduleResume(*this, aConsumer);
-}
-
-bool
-SourceBufferIterator::RemainingBytesIsNoMoreThan(size_t aBytes) const
-{
-  MOZ_ASSERT(mOwner);
-  return mOwner->RemainingBytesIsNoMoreThan(*this, aBytes);
-}
-
-
-//////////////////////////////////////////////////////////////////////////////
-// SourceBuffer implementation.
-//////////////////////////////////////////////////////////////////////////////
-
-SourceBuffer::SourceBuffer()
-  : mMutex("image::SourceBuffer")
-{ }
-
-SourceBuffer::~SourceBuffer()
-{
-  // FallibleTArray doesn't call destructors so we have to manually release.
-  for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
-    mChunks[i].Release();
-  }
-}
-
-nsresult
-SourceBuffer::AppendChunk(Maybe<Chunk>&& aChunk)
-{
-  mMutex.AssertCurrentThreadOwns();
-
-#ifdef DEBUG
-  if (mChunks.Length() > 0) {
-    NS_WARNING("Appending an extra chunk for SourceBuffer");
-  }
-#endif
-
-  if (MOZ_UNLIKELY(!aChunk)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (MOZ_UNLIKELY(aChunk->AllocationFailed())) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  if (MOZ_UNLIKELY(!mChunks.AppendElement(Move(*aChunk)))) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  return NS_OK;
-}
-
-Maybe<SourceBuffer::Chunk>
-SourceBuffer::CreateChunk(size_t aCapacity)
-{
-  if (MOZ_UNLIKELY(aCapacity == 0)) {
-    MOZ_ASSERT_UNREACHABLE("Appending a chunk of zero size?");
-    return Nothing();
-  }
-
-  // Protect against overflow.
-  if (MOZ_UNLIKELY(SIZE_MAX - aCapacity < MIN_CHUNK_CAPACITY)) {
-    return Nothing();
-  }
-
-  // Round up to the next multiple of MIN_CHUNK_CAPACITY (which should be the
-  // size of a page).
-  size_t roundedCapacity =
-    (aCapacity + MIN_CHUNK_CAPACITY - 1) & ~(MIN_CHUNK_CAPACITY - 1);
-  MOZ_ASSERT(roundedCapacity >= aCapacity, "Bad math?");
-  MOZ_ASSERT(roundedCapacity - aCapacity < MIN_CHUNK_CAPACITY, "Bad math?");
-
-  // Use the size of the SurfaceCache as an additional heuristic to avoid
-  // allocating huge buffers. Generally images do not get smaller when decoded,
-  // so if we could store the source data in the SurfaceCache, we assume that
-  // there's no way we'll be able to store the decoded version.
-  if (MOZ_UNLIKELY(!SurfaceCache::CanHold(roundedCapacity))) {
-    return Nothing();
-  }
-
-  return Some(Chunk(roundedCapacity));
-}
-
-size_t
-SourceBuffer::FibonacciCapacityWithMinimum(size_t aMinCapacity)
-{
-  mMutex.AssertCurrentThreadOwns();
-
-  // We grow the source buffer using a Fibonacci growth rate.
-
-  size_t length = mChunks.Length();
-
-  if (length == 0) {
-    return aMinCapacity;
-  }
-
-  if (length == 1) {
-    return max(2 * mChunks[0].Capacity(), aMinCapacity);
-  }
-
-  return max(mChunks[length - 1].Capacity() + mChunks[length - 2].Capacity(),
-             aMinCapacity);
-}
-
-void
-SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
-{
-  mMutex.AssertCurrentThreadOwns();
-
-  if (MOZ_UNLIKELY(NS_IsMainThread())) {
-    NS_WARNING("SourceBuffer consumer on the main thread needed to wait");
-  }
-
-  mWaitingConsumers.AppendElement(aConsumer);
-}
-
-void
-SourceBuffer::ResumeWaitingConsumers()
-{
-  mMutex.AssertCurrentThreadOwns();
-
-  if (mWaitingConsumers.Length() == 0) {
-    return;
-  }
-
-  for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) {
-    mWaitingConsumers[i]->Resume();
-    mWaitingConsumers[i] = nullptr;
-  }
-
-  mWaitingConsumers.Clear();
-}
-
-nsresult
-SourceBuffer::ExpectLength(size_t aExpectedLength)
-{
-  MOZ_ASSERT(aExpectedLength > 0, "Zero expected size?");
-
-  MutexAutoLock lock(mMutex);
-
-  if (MOZ_UNLIKELY(mStatus)) {
-    MOZ_ASSERT_UNREACHABLE("ExpectLength after SourceBuffer is complete");
-    return NS_OK;
-  }
-
-  if (MOZ_UNLIKELY(mChunks.Length() > 0)) {
-    MOZ_ASSERT_UNREACHABLE("Duplicate or post-Append call to ExpectLength");
-    return NS_OK;
-  }
-
-  if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aExpectedLength))))) {
-    return HandleError(NS_ERROR_OUT_OF_MEMORY);
-  }
-
-  return NS_OK;
-}
-
-nsresult
-SourceBuffer::Append(const char* aData, size_t aLength)
-{
-  MOZ_ASSERT(aData, "Should have a buffer");
-  MOZ_ASSERT(aLength > 0, "Writing a zero-sized chunk");
-
-  size_t currentChunkCapacity = 0;
-  size_t currentChunkLength = 0;
-  char* currentChunkData = nullptr;
-  size_t currentChunkRemaining = 0;
-  size_t forCurrentChunk = 0;
-  size_t forNextChunk = 0;
-  size_t nextChunkCapacity = 0;
-
-  {
-    MutexAutoLock lock(mMutex);
-
-    if (MOZ_UNLIKELY(mStatus)) {
-      // This SourceBuffer is already complete; ignore further data.
-      return NS_ERROR_FAILURE;
-    }
-
-    if (MOZ_UNLIKELY(mChunks.Length() == 0)) {
-      if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(CreateChunk(aLength))))) {
-        return HandleError(NS_ERROR_OUT_OF_MEMORY);
-      }
-    }
-
-    // Copy out the current chunk's information so we can release the lock.
-    // Note that this wouldn't be safe if multiple producers were allowed!
-    Chunk& currentChunk = mChunks.LastElement();
-    currentChunkCapacity = currentChunk.Capacity();
-    currentChunkLength = currentChunk.Length();
-    currentChunkData = currentChunk.Data();
-
-    // Partition this data between the current chunk and the next chunk.
-    // (Because we always allocate a chunk big enough to fit everything passed
-    // to Append, we'll never need more than those two chunks to store
-    // everything.)
-    currentChunkRemaining = currentChunkCapacity - currentChunkLength;
-    forCurrentChunk = min(aLength, currentChunkRemaining);
-    forNextChunk = aLength - forCurrentChunk;
-
-    // If we'll need another chunk, determine what its capacity should be while
-    // we still hold the lock.
-    nextChunkCapacity = forNextChunk > 0
-                      ? FibonacciCapacityWithMinimum(forNextChunk)
-                      : 0;
-  }
-
-  // Write everything we can fit into the current chunk.
-  MOZ_ASSERT(currentChunkLength + forCurrentChunk <= currentChunkCapacity);
-  memcpy(currentChunkData + currentChunkLength, aData, forCurrentChunk);
-
-  // If there's something left, create a new chunk and write it there.
-  Maybe<Chunk> nextChunk;
-  if (forNextChunk > 0) {
-    MOZ_ASSERT(nextChunkCapacity >= forNextChunk, "Next chunk too small?");
-    nextChunk = CreateChunk(nextChunkCapacity);
-    if (MOZ_LIKELY(nextChunk && !nextChunk->AllocationFailed())) {
-      memcpy(nextChunk->Data(), aData + forCurrentChunk, forNextChunk);
-      nextChunk->AddLength(forNextChunk);
-    }
-  }
-
-  // Update shared data structures.
-  {
-    MutexAutoLock lock(mMutex);
-
-    // Update the length of the current chunk.
-    Chunk& currentChunk = mChunks.LastElement();
-    MOZ_ASSERT(currentChunk.Data() == currentChunkData, "Multiple producers?");
-    MOZ_ASSERT(currentChunk.Length() == currentChunkLength,
-               "Multiple producers?");
-
-    currentChunk.AddLength(forCurrentChunk);
-
-    // If we created a new chunk, add it to the series.
-    if (forNextChunk > 0) {
-      if (MOZ_UNLIKELY(!nextChunk)) {
-        return HandleError(NS_ERROR_OUT_OF_MEMORY);
-      }
-
-      if (MOZ_UNLIKELY(NS_FAILED(AppendChunk(Move(nextChunk))))) {
-        return HandleError(NS_ERROR_OUT_OF_MEMORY);
-      }
-    }
-
-    // Resume any waiting readers now that there's new data.
-    ResumeWaitingConsumers();
-  }
-
-  return NS_OK;
-}
-
-void
-SourceBuffer::Complete(nsresult aStatus)
-{
-  MutexAutoLock lock(mMutex);
-
-  if (MOZ_UNLIKELY(mStatus)) {
-    MOZ_ASSERT_UNREACHABLE("Called Complete more than once");
-    return;
-  }
-
-  if (MOZ_UNLIKELY(NS_SUCCEEDED(aStatus) && IsEmpty())) {
-    // It's illegal to succeed without writing anything.
-    aStatus = NS_ERROR_FAILURE;
-  }
-
-  mStatus = Some(aStatus);
-
-  // Resume any waiting consumers now that we're complete.
-  ResumeWaitingConsumers();
-}
-
-bool
-SourceBuffer::IsComplete()
-{
-  MutexAutoLock lock(mMutex);
-  return bool(mStatus);
-}
-
-size_t
-SourceBuffer::SizeOfIncludingThisWithComputedFallback(MallocSizeOf
-                                                        aMallocSizeOf) const
-{
-  MutexAutoLock lock(mMutex);
-
-  size_t n = aMallocSizeOf(this);
-  n += mChunks.SizeOfExcludingThis(aMallocSizeOf);
-
-  for (uint32_t i = 0 ; i < mChunks.Length() ; ++i) {
-    size_t chunkSize = aMallocSizeOf(mChunks[i].Data());
-
-    if (chunkSize == 0) {
-      // We're on a platform where moz_malloc_size_of always returns 0.
-      chunkSize = mChunks[i].Capacity();
-    }
-
-    n += chunkSize;
-  }
-
-  return n;
-}
-
-bool
-SourceBuffer::RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
-                                         size_t aBytes) const
-{
-  MutexAutoLock lock(mMutex);
-
-  // If we're not complete, we always say no.
-  if (!mStatus) {
-    return false;
-  }
-
-  // If the iterator's at the end, the answer is trivial.
-  if (!aIterator.HasMore()) {
-    return true;
-  }
-
-  uint32_t iteratorChunk = aIterator.mData.mIterating.mChunk;
-  size_t iteratorOffset = aIterator.mData.mIterating.mOffset;
-  size_t iteratorLength = aIterator.mData.mIterating.mLength;
-
-  // Include the bytes the iterator is currently pointing to in the limit, so
-  // that the current chunk doesn't have to be a special case.
-  size_t bytes = aBytes + iteratorOffset + iteratorLength;
-
-  // Count the length over all of our chunks, starting with the one that the
-  // iterator is currently pointing to. (This is O(N), but N is expected to be
-  // ~1, so it doesn't seem worth caching the length separately.)
-  size_t lengthSoFar = 0;
-  for (uint32_t i = iteratorChunk ; i < mChunks.Length() ; ++i) {
-    lengthSoFar += mChunks[i].Length();
-    if (lengthSoFar > bytes) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-SourceBufferIterator::State
-SourceBuffer::AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
-                                              IResumable* aConsumer)
-{
-  MutexAutoLock lock(mMutex);
-
-  if (MOZ_UNLIKELY(!aIterator.HasMore())) {
-    MOZ_ASSERT_UNREACHABLE("Should not advance a completed iterator");
-    return SourceBufferIterator::COMPLETE;
-  }
-
-  if (MOZ_UNLIKELY(mStatus && NS_FAILED(*mStatus))) {
-    // This SourceBuffer is complete due to an error; all reads fail.
-    return aIterator.SetComplete(*mStatus);
-  }
-
-  if (MOZ_UNLIKELY(mChunks.Length() == 0)) {
-    // We haven't gotten an initial chunk yet.
-    AddWaitingConsumer(aConsumer);
-    return aIterator.SetWaiting();
-  }
-
-  uint32_t iteratorChunkIdx = aIterator.mData.mIterating.mChunk;
-  MOZ_ASSERT(iteratorChunkIdx < mChunks.Length());
-
-  const Chunk& currentChunk = mChunks[iteratorChunkIdx];
-  size_t iteratorEnd = aIterator.mData.mIterating.mOffset +
-                       aIterator.mData.mIterating.mLength;
-  MOZ_ASSERT(iteratorEnd <= currentChunk.Length());
-  MOZ_ASSERT(iteratorEnd <= currentChunk.Capacity());
-
-  if (iteratorEnd < currentChunk.Length()) {
-    // There's more data in the current chunk.
-    return aIterator.SetReady(iteratorChunkIdx, currentChunk.Data(),
-                              iteratorEnd, currentChunk.Length() - iteratorEnd);
-  }
-
-  if (iteratorEnd == currentChunk.Capacity() &&
-      !IsLastChunk(iteratorChunkIdx)) {
-    // Advance to the next chunk.
-    const Chunk& nextChunk = mChunks[iteratorChunkIdx + 1];
-    return aIterator.SetReady(iteratorChunkIdx + 1, nextChunk.Data(), 0,
-                              nextChunk.Length());
-  }
-
-  MOZ_ASSERT(IsLastChunk(iteratorChunkIdx), "Should've advanced");
-
-  if (mStatus) {
-    // There's no more data and this SourceBuffer completed successfully.
-    MOZ_ASSERT(NS_SUCCEEDED(*mStatus), "Handled failures earlier");
-    return aIterator.SetComplete(*mStatus);
-  }
-
-  // We're not complete, but there's no more data right now. Arrange to wake up
-  // the consumer when we get more data.
-  AddWaitingConsumer(aConsumer);
-  return aIterator.SetWaiting();
-}
-
-nsresult
-SourceBuffer::HandleError(nsresult aError)
-{
-  MOZ_ASSERT(NS_FAILED(aError), "Should have an error here");
-  MOZ_ASSERT(aError == NS_ERROR_OUT_OF_MEMORY,
-             "Unexpected error; may want to notify waiting readers, which "
-             "HandleError currently doesn't do");
-
-  mMutex.AssertCurrentThreadOwns();
-
-  NS_WARNING("SourceBuffer encountered an unrecoverable error");
-
-  // Record the error.
-  mStatus = Some(aError);
-
-  // Drop our references to waiting readers.
-  for (uint32_t i = 0 ; i < mWaitingConsumers.Length() ; ++i) {
-    mWaitingConsumers[i] = nullptr;
-  }
-  mWaitingConsumers.Clear();
-
-  return *mStatus;
-}
-
-bool
-SourceBuffer::IsEmpty()
-{
-  mMutex.AssertCurrentThreadOwns();
-  return mChunks.Length() == 0 ||
-         mChunks[0].Length() == 0;
-}
-
-bool
-SourceBuffer::IsLastChunk(uint32_t aChunk)
-{
-  mMutex.AssertCurrentThreadOwns();
-  return aChunk + 1 == mChunks.Length();
-}
-
-} // namespace image
-} // namespace mozilla
deleted file mode 100644
--- a/image/src/SourceBuffer.h
+++ /dev/null
@@ -1,371 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-/**
- * SourceBuffer is a single producer, multiple consumer data structure used for
- * storing image source (compressed) data.
- */
-
-#ifndef MOZILLA_IMAGELIB_SOURCEBUFFER_H_
-#define MOZILLA_IMAGELIB_SOURCEBUFFER_H_
-
-#include "mozilla/Maybe.h"
-#include "mozilla/MemoryReporting.h"
-#include "mozilla/Mutex.h"
-#include "mozilla/Move.h"
-#include "mozilla/MemoryReporting.h"
-#include "mozilla/RefPtr.h"
-#include "mozilla/UniquePtr.h"
-#include "nsRefPtr.h"
-#include "nsTArray.h"
-
-namespace mozilla {
-namespace image {
-
-class SourceBuffer;
-
-/**
- * IResumable is an interface for classes that can schedule themselves to resume
- * their work later. An implementation of IResumable generally should post a
- * runnable to some event target which continues the work of the task.
- */
-struct IResumable
-{
-  MOZ_DECLARE_REFCOUNTED_TYPENAME(IResumable)
-
-  // Subclasses may or may not be XPCOM classes, so we just require that they
-  // implement AddRef and Release.
-  NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
-  NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
-
-  virtual void Resume() = 0;
-
-protected:
-  virtual ~IResumable() { }
-};
-
-/**
- * SourceBufferIterator is a class that allows consumers of image source data to
- * read the contents of a SourceBuffer sequentially.
- *
- * Consumers can advance through the SourceBuffer by calling
- * AdvanceOrScheduleResume() repeatedly. After every advance, they should call
- * check the return value, which will tell them the iterator's new state.
- *
- * If WAITING is returned, AdvanceOrScheduleResume() has arranged
- * to call the consumer's Resume() method later, so the consumer should save its
- * state if needed and stop running.
- *
- * If the iterator's new state is READY, then the consumer can call Data() and
- * Length() to read new data from the SourceBuffer.
- *
- * Finally, in the COMPLETE state the consumer can call CompletionStatus() to
- * get the status passed to SourceBuffer::Complete().
- */
-class SourceBufferIterator MOZ_FINAL
-{
-public:
-  enum State {
-    START,    // The iterator is at the beginning of the buffer.
-    READY,    // The iterator is pointing to new data.
-    WAITING,  // The iterator is blocked and the caller must yield.
-    COMPLETE  // The iterator is pointing to the end of the buffer.
-  };
-
-  explicit SourceBufferIterator(SourceBuffer* aOwner)
-    : mOwner(aOwner)
-    , mState(START)
-  {
-    MOZ_ASSERT(aOwner);
-    mData.mIterating.mChunk = 0;
-    mData.mIterating.mData = nullptr;
-    mData.mIterating.mOffset = 0;
-    mData.mIterating.mLength = 0;
-  }
-
-  SourceBufferIterator(SourceBufferIterator&& aOther)
-    : mOwner(Move(aOther.mOwner))
-    , mState(aOther.mState)
-    , mData(aOther.mData)
-  { }
-
-  SourceBufferIterator& operator=(SourceBufferIterator&& aOther)
-  {
-    mOwner = Move(aOther.mOwner);
-    mState = aOther.mState;
-    mData = aOther.mData;
-    return *this;
-  }
-
-  /**
-   * Returns true if there are no more than @aBytes remaining in the
-   * SourceBuffer. If the SourceBuffer is not yet complete, returns false.
-   */
-  bool RemainingBytesIsNoMoreThan(size_t aBytes) const;
-
-  /**
-   * Advances the iterator through the SourceBuffer if possible. If not,
-   * arranges to call the @aConsumer's Resume() method when more data is
-   * available.
-   */
-  State AdvanceOrScheduleResume(IResumable* aConsumer);
-
-  /// If at the end, returns the status passed to SourceBuffer::Complete().
-  nsresult CompletionStatus() const
-  {
-    MOZ_ASSERT(mState == COMPLETE, "Calling CompletionStatus() in the wrong state");
-    return mState == COMPLETE ? mData.mAtEnd.mStatus : NS_OK;
-  }
-
-  /// If we're ready to read, returns a pointer to the new data.
-  const char* Data() const
-  {
-    MOZ_ASSERT(mState == READY, "Calling Data() in the wrong state");
-    return mState == READY ? mData.mIterating.mData + mData.mIterating.mOffset
-                           : nullptr;
-  }
-
-  /// If we're ready to read, returns the length of the new data.
-  size_t Length() const
-  {
-    MOZ_ASSERT(mState == READY, "Calling Length() in the wrong state");
-    return mState == READY ? mData.mIterating.mLength : 0;
-  }
-
-private:
-  friend class SourceBuffer;
-
-  SourceBufferIterator(const SourceBufferIterator&) = delete;
-  SourceBufferIterator& operator=(const SourceBufferIterator&) = delete;
-
-  bool HasMore() const { return mState != COMPLETE; }
-
-  State SetReady(uint32_t aChunk, const char* aData,
-                size_t aOffset, size_t aLength)
-  {
-    MOZ_ASSERT(mState != COMPLETE);
-    mData.mIterating.mChunk = aChunk;
-    mData.mIterating.mData = aData;
-    mData.mIterating.mOffset = aOffset;
-    mData.mIterating.mLength = aLength;
-    return mState = READY;
-  }
-
-  State SetWaiting()
-  {
-    MOZ_ASSERT(mState != COMPLETE);
-    MOZ_ASSERT(mState != WAITING, "Did we get a spurious wakeup somehow?");
-    return mState = WAITING;
-  }
-
-  State SetComplete(nsresult aStatus)
-  {
-    mData.mAtEnd.mStatus = aStatus;
-    return mState = COMPLETE;
-  }
-
-  nsRefPtr<SourceBuffer> mOwner;
-
-  State mState;
-
-  /**
-   * This union contains our iteration state if we're still iterating (for
-   * states START, READY, and WAITING) and the status the SourceBuffer was
-   * completed with if we're in state COMPLETE.
-   */
-  union {
-    struct {
-      uint32_t mChunk;
-      const char* mData;
-      size_t mOffset;
-      size_t mLength;
-    } mIterating;
-    struct {
-      nsresult mStatus;
-    } mAtEnd;
-  } mData;
-};
-
-/**
- * SourceBuffer is a parallel data structure used for storing image source
- * (compressed) data.
- *
- * SourceBuffer is a single producer, multiple consumer data structure. The
- * single producer calls Append() to append data to the buffer. In parallel,
- * multiple consumers can call Iterator(), which returns a SourceBufferIterator
- * that they can use to iterate through the buffer. The SourceBufferIterator
- * returns a series of pointers which remain stable for lifetime of the
- * SourceBuffer, and the data they point to is immutable, ensuring that the
- * producer never interferes with the consumers.
- *
- * In order to avoid blocking, SourceBuffer works with SourceBufferIterator to
- * keep a list of consumers which are waiting for new data, and to resume them
- * when the producer appends more. All consumers must implement the IResumable
- * interface to make this possible.
- *
- * XXX(seth): We should add support for compacting a SourceBuffer. To do this,
- * we need to have SourceBuffer keep track of how many live
- * SourceBufferIterator's point to it. When the SourceBuffer is complete and no
- * live SourceBufferIterator's for it remain, we can compact its contents into a
- * single chunk.
- */
-class SourceBuffer MOZ_FINAL
-{
-public:
-  MOZ_DECLARE_REFCOUNTED_TYPENAME(image::SourceBuffer)
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(image::SourceBuffer)
-
-  SourceBuffer();
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Producer methods.
-  //////////////////////////////////////////////////////////////////////////////
-
-  /**
-   * If the producer knows how long the source data will be, it should call
-   * ExpectLength, which enables SourceBuffer to preallocate its buffer.
-   */
-  nsresult ExpectLength(size_t aExpectedLength);
-
-  /// Append the provided data to the buffer.
-  nsresult Append(const char* aData, size_t aLength);
-
-  /**
-   * Mark the buffer complete, with a status that will be available to
-   * consumers. Further calls to Append() are forbidden after Complete().
-   */
-  void Complete(nsresult aStatus);
-
-  /// Returns true if the buffer is complete.
-  bool IsComplete();
-
-  /// Memory reporting.
-  size_t SizeOfIncludingThisWithComputedFallback(MallocSizeOf) const;
-
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Consumer methods.
-  //////////////////////////////////////////////////////////////////////////////
-
-  /// Returns an iterator to this SourceBuffer.
-  SourceBufferIterator Iterator() { return SourceBufferIterator(this); }
-
-
-private:
-  friend class SourceBufferIterator;
-
-  virtual ~SourceBuffer();
-
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Chunk type and chunk-related methods.
-  //////////////////////////////////////////////////////////////////////////////
-
-  class Chunk
-  {
-  public:
-    explicit Chunk(size_t aCapacity)
-      : mCapacity(aCapacity)
-      , mLength(0)
-      , mData(MakeUnique<char[]>(mCapacity))
-    {
-      MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
-    }
-
-    Chunk(Chunk&& aOther)
-      : mCapacity(aOther.mCapacity)
-      , mLength(aOther.mLength)
-      , mData(Move(aOther.mData))
-    {
-      aOther.mCapacity = aOther.mLength = 0;
-    }
-
-    Chunk& operator=(Chunk&& aOther)
-    {
-      mCapacity = aOther.mCapacity;
-      mLength = aOther.mLength;
-      mData = Move(aOther.mData);
-      aOther.mCapacity = aOther.mLength = 0;
-      return *this;
-    }
-
-    bool AllocationFailed() const { return !mData; }
-    size_t Capacity() const { return mCapacity; }
-    size_t Length() const { return mLength; }
-    char* Data() const { return mData.get(); }
-
-    void AddLength(size_t aAdditionalLength)
-    {
-      MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity);
-      mLength += aAdditionalLength;
-    }
-
-    void Release()
-    {
-      mCapacity = mLength = 0;
-      mData.reset();
-    }
-
-  private:
-    Chunk(const Chunk&) = delete;
-    Chunk& operator=(const Chunk&) = delete;
-
-    size_t mCapacity;
-    size_t mLength;
-    UniquePtr<char[]> mData;
-  };
-
-  nsresult AppendChunk(Maybe<Chunk>&& aChunk);
-  Maybe<Chunk> CreateChunk(size_t aCapacity);
-  size_t FibonacciCapacityWithMinimum(size_t aMinCapacity);
-
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Iterator / consumer methods.
-  //////////////////////////////////////////////////////////////////////////////
-
-  void AddWaitingConsumer(IResumable* aConsumer);
-  void ResumeWaitingConsumers();
-
-  typedef SourceBufferIterator::State State;
-
-  State AdvanceIteratorOrScheduleResume(SourceBufferIterator& aIterator,
-                                        IResumable* aConsumer);
-  bool RemainingBytesIsNoMoreThan(const SourceBufferIterator& aIterator,
-                                  size_t aBytes) const;
-
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Helper methods.
-  //////////////////////////////////////////////////////////////////////////////
-
-  nsresult HandleError(nsresult aError);
-  bool IsEmpty();
-  bool IsLastChunk(uint32_t aChunk);
-
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Member variables.
-  //////////////////////////////////////////////////////////////////////////////
-
-  static const size_t MIN_CHUNK_CAPACITY = 4096;
-
-  /// All private members are protected by mMutex.
-  mutable Mutex mMutex;
-
-  /// The data in this SourceBuffer, stored as a series of Chunks.
-  FallibleTArray<Chunk> mChunks;
-
-  /// Consumers which are waiting to be notified when new data is available.
-  nsTArray<nsRefPtr<IResumable>> mWaitingConsumers;
-
-  /// If present, marks this SourceBuffer complete with the given final status.
-  Maybe<nsresult> mStatus;
-};
-
-} // namespace image
-} // namespace mozilla
-
-#endif // MOZILLA_IMAGELIB_SOURCEBUFFER_H_
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -299,32 +299,29 @@ private:
     UnregisterWeakMemoryReporter(this);
   }
 
 public:
   void InitMemoryReporter() { RegisterWeakMemoryReporter(this); }
 
   Mutex& GetMutex() { return mMutex; }
 
-  InsertOutcome Insert(imgFrame*         aSurface,
-                       const Cost        aCost,
-                       const ImageKey    aImageKey,
-                       const SurfaceKey& aSurfaceKey,
-                       Lifetime          aLifetime)
+  bool Insert(imgFrame*         aSurface,
+              const Cost        aCost,
+              const ImageKey    aImageKey,
+              const SurfaceKey& aSurfaceKey,
+              Lifetime          aLifetime)
   {
-    // If this is a duplicate surface, refuse to replace the original.
-    if (MOZ_UNLIKELY(Lookup(aImageKey, aSurfaceKey))) {
-      return InsertOutcome::FAILURE_ALREADY_PRESENT;
-    }
+    MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey),
+               "Inserting a duplicate surface into the SurfaceCache");
 
     // If this is bigger than we can hold after discarding everything we can,
     // refuse to cache it.
-    if (MOZ_UNLIKELY(!CanHoldAfterDiscarding(aCost))) {
-      return InsertOutcome::FAILURE;
-    }
+    if (!CanHoldAfterDiscarding(aCost))
+      return false;
 
     // Remove elements in order of cost until we can fit this in the cache. Note
     // that locked surfaces aren't in mCosts, so we never remove them here.
     while (aCost > mAvailableCost) {
       MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit");
       Remove(mCosts.LastElement().GetSurface());
     }
 
@@ -339,26 +336,26 @@ public:
     nsRefPtr<CachedSurface> surface =
       new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime);
 
     // We require that locking succeed if the image is locked and the surface is
     // persistent; the caller may need to know this to handle errors correctly.
     if (cache->IsLocked() && aLifetime == Lifetime::Persistent) {
       surface->SetLocked(true);
       if (!surface->IsLocked()) {
-        return InsertOutcome::FAILURE;
+        return false;
       }
     }
 
     // Insert.
     MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost");
     cache->Insert(aSurfaceKey, surface);
     StartTracking(surface);
 
-    return InsertOutcome::SUCCESS;
+    return true;
   }
 
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     ImageKey imageKey = aSurface->GetImageKey();
 
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
@@ -793,24 +790,24 @@ SurfaceCache::Lookup(const ImageKey    a
   if (!sInstance) {
     return DrawableFrameRef();
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->Lookup(aImageKey, aSurfaceKey);
 }
 
-/* static */ InsertOutcome
+/* static */ bool
 SurfaceCache::Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey,
                      Lifetime          aLifetime)
 {
   if (!sInstance) {
-    return InsertOutcome::FAILURE;
+    return false;
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   Cost cost = ComputeCost(aSurfaceKey.Size());
   return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
 }
 
 /* static */ bool
@@ -819,26 +816,16 @@ SurfaceCache::CanHold(const IntSize& aSi
   if (!sInstance) {
     return false;
   }
 
   Cost cost = ComputeCost(aSize);
   return sInstance->CanHold(cost);
 }
 
-/* static */ bool
-SurfaceCache::CanHold(size_t aSize)
-{
-  if (!sInstance) {
-    return false;
-  }
-
-  return sInstance->CanHold(aSize);
-}
-
 /* static */ void
 SurfaceCache::LockImage(Image* aImageKey)
 {
   if (sInstance) {
     MutexAutoLock lock(sInstance->GetMutex());
     return sInstance->LockImage(aImageKey);
   }
 }
--- a/image/src/SurfaceCache.h
+++ b/image/src/SurfaceCache.h
@@ -109,22 +109,16 @@ VectorSurfaceKey(const gfx::IntSize& aSi
   return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0);
 }
 
 MOZ_BEGIN_ENUM_CLASS(Lifetime, uint8_t)
   Transient,
   Persistent
 MOZ_END_ENUM_CLASS(Lifetime)
 
-MOZ_BEGIN_ENUM_CLASS(InsertOutcome, uint8_t)
-  SUCCESS,                 // Success (but see Insert documentation).
-  FAILURE,                 // Couldn't insert (e.g., for capacity reasons).
-  FAILURE_ALREADY_PRESENT  // A surface with the same key is already present.
-MOZ_END_ENUM_CLASS(InsertOutcome)
-
 /**
  * SurfaceCache is an imagelib-global service that allows caching of temporary
  * surfaces. Surfaces normally expire from the cache automatically if they go
  * too long without being accessed.
  *
  * SurfaceCache does not hold surfaces directly; instead, it holds imgFrame
  * objects, which hold surfaces but also layer on additional features specific
  * to imagelib's needs like animation, padding support, and transparent support
@@ -174,74 +168,69 @@ struct SurfaceCache
    *
    * @return a DrawableFrameRef to the imgFrame wrapping the requested surface,
    *         or an empty DrawableFrameRef if not found.
    */
   static DrawableFrameRef Lookup(const ImageKey    aImageKey,
                                  const SurfaceKey& aSurfaceKey);
 
   /**
-   * Insert a surface into the cache. If a surface with the same ImageKey and
-   * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT.
+   * Insert a surface into the cache. It is an error to call this function
+   * without first calling Lookup to verify that the surface is not already in
+   * the cache.
    *
    * Each surface in the cache has a lifetime, either Transient or Persistent.
    * Transient surfaces can expire from the cache at any time. Persistent
    * surfaces can ordinarily also expire from the cache at any time, but if the
    * image they're associated with is locked, then these surfaces will never
    * expire. This means that surfaces which cannot be rematerialized should be
    * inserted with a persistent lifetime *after* the image is locked with
    * LockImage(); if you use the other order, the surfaces might expire before
    * LockImage() gets called.
    *
    * If a surface cannot be rematerialized, it may be important to know whether
-   * it was inserted into the cache successfully. Insert() returns FAILURE if it
+   * it was inserted into the cache successfully. Insert() returns false if it
    * failed to insert the surface, which could happen because of capacity
    * reasons, or because it was already freed by the OS. If you aren't inserting
    * a surface with persistent lifetime, or if the surface isn't associated with
-   * a locked image, checking for SUCCESS or FAILURE is useless: the surface
-   * might expire immediately after being inserted, even though Insert()
-   * returned SUCCESS. Thus, many callers do not need to check the result of
-   * Insert() at all.
+   * a locked image, the return value is useless: the surface might expire
+   * immediately after being inserted, even though Insert() returned true. Thus,
+   * most callers do not need to check the return value.
    *
    * @param aTarget      The new surface (wrapped in an imgFrame) to insert into
    *                     the cache.
    * @param aImageKey    Key data identifying which image the surface belongs to.
    * @param aSurfaceKey  Key data which uniquely identifies the requested surface.
    * @param aLifetime    Whether this is a transient surface that can always be
    *                     allowed to expire, or a persistent surface that
    *                     shouldn't expire if the image is locked.
-   * @return SUCCESS if the surface was inserted successfully. (But see above
-   *           for more information about when you should check this.)
-   *         FAILURE if the surface could not be inserted, e.g. for capacity
-   *           reasons. (But see above for more information about when you
-   *           should check this.)
-   *         FAILURE_ALREADY_PRESENT if a surface with the same ImageKey and
-   *           SurfaceKey already exists in the cache.
+   * @return false if the surface could not be inserted. Only check this if
+   *         inserting a persistent surface associated with a locked image (see
+   *         above for more information).
    */
-  static InsertOutcome Insert(imgFrame*         aSurface,
-                              const ImageKey    aImageKey,
-                              const SurfaceKey& aSurfaceKey,
-                              Lifetime          aLifetime);
+  static bool Insert(imgFrame*         aSurface,
+                     const ImageKey    aImageKey,
+                     const SurfaceKey& aSurfaceKey,
+                     Lifetime          aLifetime);
 
   /**
    * Checks if a surface of a given size could possibly be stored in the cache.
    * If CanHold() returns false, Insert() will always fail to insert the
    * surface, but the inverse is not true: Insert() may take more information
    * into account than just image size when deciding whether to cache the
    * surface, so Insert() may still fail even if CanHold() returns true.
    *
    * Use CanHold() to avoid the need to create a temporary surface when we know
    * for sure the cache can't hold it.
    *
    * @param aSize  The dimensions of a surface in pixels.
    *
    * @return false if the surface cache can't hold a surface of that size.
    */
   static bool CanHold(const IntSize& aSize);
-  static bool CanHold(size_t aSize);
 
   /**
    * Locks an image, preventing any of that image's surfaces from expiring
    * unless they have a transient lifetime.
    *
    * Regardless of locking, any of an image's surfaces may be removed using
    * RemoveSurface(), and all of an image's surfaces are removed by
    * RemoveImage(), whether the image is locked or not.
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -125,24 +125,23 @@ static bool AllowedImageAndFrameDimensio
   if (!imageRect.Contains(aFrameRect)) {
     return false;
   }
   return true;
 }
 
 
 imgFrame::imgFrame()
-  : mMonitor("imgFrame")
+  : mMutex("imgFrame")
   , mDecoded(0, 0, 0, 0)
   , mLockCount(0)
   , mTimeout(100)
   , mDisposalMethod(DisposalMethod::NOT_SPECIFIED)
   , mBlendMethod(BlendMethod::OVER)
   , mHasNoAlpha(false)
-  , mAborted(false)
   , mPalettedImageData(nullptr)
   , mPaletteDepth(0)
   , mNonPremult(false)
   , mSinglePixel(false)
   , mCompositingFailed(false)
   , mOptimizable(false)
 {
   static bool hasCheckedOptimize = false;
@@ -151,83 +150,74 @@ imgFrame::imgFrame()
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
   }
 }
 
 imgFrame::~imgFrame()
 {
-#ifdef DEBUG
-  MonitorAutoLock lock(mMonitor);
-  MOZ_ASSERT(mAborted || IsImageCompleteInternal());
-#endif
-
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
 }
 
 nsresult
 imgFrame::InitForDecoder(const nsIntSize& aImageSize,
                          const nsIntRect& aRect,
                          SurfaceFormat aFormat,
                          uint8_t aPaletteDepth /* = 0 */,
                          bool aNonPremult /* = false */)
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) {
     NS_WARNING("Should have legal image size");
-    mAborted = true;
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aImageSize.ToIntSize();
   mOffset.MoveTo(aRect.x, aRect.y);
   mSize.SizeTo(aRect.width, aRect.height);
 
   mFormat = aFormat;
   mPaletteDepth = aPaletteDepth;
   mNonPremult = aNonPremult;
 
   if (aPaletteDepth != 0) {
     // We're creating for a paletted image.
     if (aPaletteDepth > 8) {
       NS_WARNING("Should have legal palette depth");
       NS_ERROR("This Depth is not supported");
-      mAborted = true;
       return NS_ERROR_FAILURE;
     }
 
     // Use the fallible allocator here. Paletted images always use 1 byte per
     // pixel, so calculating the amount of memory we need is straightforward.
     mPalettedImageData =
       static_cast<uint8_t*>(moz_malloc(PaletteDataLength() +
                                        (mSize.width * mSize.height)));
     if (!mPalettedImageData)
       NS_WARNING("moz_malloc for paletted image data should succeed");
     NS_ENSURE_TRUE(mPalettedImageData, NS_ERROR_OUT_OF_MEMORY);
   } else {
     MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitForDecoder() twice?");
 
     mVBuf = AllocateBufferForImage(mSize, mFormat);
     if (!mVBuf) {
-      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
     if (mVBuf->OnHeap()) {
       int32_t stride = VolatileSurfaceStride(mSize, mFormat);
       VolatileBufferPtr<uint8_t> ptr(mVBuf);
       memset(ptr, 0, stride * mSize.height);
     }
     mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
 
     if (!mImageSurface) {
       NS_WARNING("Failed to create VolatileDataSourceSurface");
-      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   return NS_OK;
 }
 
 nsresult
@@ -236,17 +226,16 @@ imgFrame::InitWithDrawable(gfxDrawable* 
                            const SurfaceFormat aFormat,
                            GraphicsFilter aFilter,
                            uint32_t aImageFlags)
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!AllowedImageSize(aSize.width, aSize.height)) {
     NS_WARNING("Should have legal image size");
-    mAborted = true;
     return NS_ERROR_FAILURE;
   }
 
   mImageSize = aSize.ToIntSize();
   mOffset.MoveTo(0, 0);
   mSize.SizeTo(aSize.width, aSize.height);
 
   mFormat = aFormat;
@@ -259,24 +248,22 @@ imgFrame::InitWithDrawable(gfxDrawable* 
 
   if (canUseDataSurface) {
     // It's safe to use data surfaces for content on this platform, so we can
     // get away with using volatile buffers.
     MOZ_ASSERT(!mImageSurface, "Called imgFrame::InitWithDrawable() twice?");
 
     mVBuf = AllocateBufferForImage(mSize, mFormat);
     if (!mVBuf) {
-      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     int32_t stride = VolatileSurfaceStride(mSize, mFormat);
     VolatileBufferPtr<uint8_t> ptr(mVBuf);
     if (!ptr) {
-      mAborted = true;
       return NS_ERROR_OUT_OF_MEMORY;
     }
     if (mVBuf->OnHeap()) {
       memset(ptr, 0, stride * mSize.height);
     }
     mImageSurface = CreateLockedSurface(mVBuf, mSize, mFormat);
 
     target = gfxPlatform::GetPlatform()->
@@ -288,50 +275,44 @@ imgFrame::InitWithDrawable(gfxDrawable* 
     // the documentation for this method.
     MOZ_ASSERT(!mOptSurface, "Called imgFrame::InitWithDrawable() twice?");
 
     target = gfxPlatform::GetPlatform()->
         CreateOffscreenContentDrawTarget(mSize, mFormat);
   }
 
   if (!target) {
-    mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Draw using the drawable the caller provided.
   nsIntRect imageRect(0, 0, mSize.width, mSize.height);
   nsRefPtr<gfxContext> ctx = new gfxContext(target);
   gfxUtils::DrawPixelSnapped(ctx, aDrawable, ThebesIntSize(mSize),
                              ImageRegion::Create(imageRect),
                              mFormat, aFilter, aImageFlags);
 
   if (canUseDataSurface && !mImageSurface) {
     NS_WARNING("Failed to create VolatileDataSourceSurface");
-    mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (!canUseDataSurface) {
     // We used an offscreen surface, which is an "optimized" surface from
     // imgFrame's perspective.
     mOptSurface = target->Snapshot();
   }
 
-  // If we reach this point, we should regard ourselves as complete.
-  mDecoded = GetRect();
-  MOZ_ASSERT(IsImageComplete());
-
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount == 1,
              "Should only optimize when holding the lock exclusively");
 
   // Don't optimize during shutdown because gfxPlatform may not be available.
   if (ShutdownTracker::ShutdownHasStarted())
     return NS_OK;
 
   if (!mOptimizable || gDisableOptimize)
@@ -469,17 +450,17 @@ imgFrame::SurfaceForDrawing(bool        
                             bool               aDoTile,
                             gfxContext*        aContext,
                             const nsIntMargin& aPadding,
                             gfxRect&           aImageRect,
                             ImageRegion&       aRegion,
                             SourceSurface*     aSurface)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
 
   IntSize size(int32_t(aImageRect.Width()), int32_t(aImageRect.Height()));
   if (!aDoPadding && !aDoPartialDecode) {
     NS_ASSERTION(!mSinglePixel, "This should already have been handled");
     return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, ThebesIntSize(size)), mFormat);
   }
 
   gfxRect available = gfxRect(mDecoded.x, mDecoded.y, mDecoded.width, mDecoded.height);
@@ -530,25 +511,25 @@ bool imgFrame::Draw(gfxContext* aContext
 
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!");
   NS_ASSERTION(!aRegion.IsRestricted() ||
                !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
   NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
 
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
 
   nsIntMargin padding(mOffset.y,
                       mImageSize.width - (mOffset.x + mSize.width),
                       mImageSize.height - (mOffset.y + mSize.height),
                       mOffset.x);
 
   bool doPadding = padding != nsIntMargin(0,0,0,0);
-  bool doPartialDecode = !IsImageCompleteInternal();
+  bool doPartialDecode = !ImageCompleteInternal();
 
   if (mSinglePixel && !doPadding && !doPartialDecode) {
     if (mSinglePixelColor.a == 0.0) {
       return true;
     }
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
     dt->FillRect(ToRect(aRegion.Rect()),
                  ColorPattern(mSinglePixelColor),
@@ -583,47 +564,40 @@ bool imgFrame::Draw(gfxContext* aContext
                                aFilter, aImageFlags);
   }
   return true;
 }
 
 nsresult
 imgFrame::ImageUpdated(const nsIntRect& aUpdateRect)
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   return ImageUpdatedInternal(aUpdateRect);
 }
 
 nsresult
 imgFrame::ImageUpdatedInternal(const nsIntRect& aUpdateRect)
 {
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
 
   mDecoded.UnionRect(mDecoded, aUpdateRect);
 
   // clamp to bounds, in case someone sends a bogus updateRect (I'm looking at
   // you, gif decoder)
   nsIntRect boundsRect(mOffset, nsIntSize(mSize.width, mSize.height));
   mDecoded.IntersectRect(mDecoded, boundsRect);
 
-  // If the image is now complete, wake up anyone who's waiting.
-  if (IsImageCompleteInternal()) {
-    mMonitor.NotifyAll();
-  }
-
   return NS_OK;
 }
 
 void
-imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
-                 DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
-                 int32_t aRawTimeout /* = 0 */,
-                 BlendMethod aBlendMethod /* = BlendMethod::OVER */)
+imgFrame::Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
+                 int32_t aRawTimeout, BlendMethod aBlendMethod)
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   if (aFrameOpacity == Opacity::OPAQUE) {
     mHasNoAlpha = true;
   }
 
   mDisposalMethod = aDisposalMethod;
   mTimeout = aRawTimeout;
@@ -634,34 +608,34 @@ imgFrame::Finish(Opacity aFrameOpacity /
 nsIntRect imgFrame::GetRect() const
 {
   return nsIntRect(mOffset, nsIntSize(mSize.width, mSize.height));
 }
 
 int32_t
 imgFrame::GetStride() const
 {
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
 
   if (mImageSurface) {
     return mImageSurface->Stride();
   }
 
   return VolatileSurfaceStride(mSize, mFormat);
 }
 
 SurfaceFormat imgFrame::GetFormat() const
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   return mFormat;
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
 
   if (mVBuf)
     return mSize.width * BytesPerPixel(mFormat);
 
   if (mPaletteDepth)
     return mSize.width;
 
   return 0;
@@ -670,24 +644,24 @@ uint32_t imgFrame::GetImageBytesPerRow()
 uint32_t imgFrame::GetImageDataLength() const
 {
   return GetImageBytesPerRow() * mSize.height;
 }
 
 void
 imgFrame::GetImageData(uint8_t** aData, uint32_t* aLength) const
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   GetImageDataInternal(aData, aLength);
 }
 
 void
 imgFrame::GetImageDataInternal(uint8_t** aData, uint32_t* aLength) const
 {
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   if (mImageSurface) {
     *aData = mVBufPtr;
     MOZ_ASSERT(*aData, "mImageSurface is non-null, but mVBufPtr is null in GetImageData");
   } else if (mPalettedImageData) {
     *aData = mPalettedImageData + PaletteDataLength();
     MOZ_ASSERT(*aData, "mPalettedImageData is non-null, but result is null in GetImageData");
@@ -731,17 +705,17 @@ uint32_t* imgFrame::GetPaletteData() con
   uint32_t length;
   GetPaletteData(&data, &length);
   return data;
 }
 
 nsresult
 imgFrame::LockImageData()
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
 
   MOZ_ASSERT(mLockCount >= 0, "Unbalanced locks and unlocks");
   if (mLockCount < 0) {
     return NS_ERROR_FAILURE;
   }
 
   mLockCount++;
 
@@ -763,17 +737,17 @@ imgFrame::LockImageData()
 
   return Deoptimize();
 }
 
 nsresult
 imgFrame::Deoptimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
   MOZ_ASSERT(!mImageSurface);
 
   if (!mImageSurface) {
     if (mVBuf) {
       VolatileBufferPtr<uint8_t> ref(mVBuf);
       if (ref.WasBufferPurged())
         return NS_ERROR_FAILURE;
 
@@ -832,17 +806,17 @@ imgFrame::Deoptimize()
   mVBufPtr = mVBuf;
   return NS_OK;
 }
 
 void
 imgFrame::AssertImageDataLocked() const
 {
 #ifdef DEBUG
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 #endif
 }
 
 class UnlockImageDataRunnable : public nsRunnable
 {
 public:
   explicit UnlockImageDataRunnable(imgFrame* aTarget)
@@ -855,26 +829,23 @@ public:
 
 private:
   nsRefPtr<imgFrame> mTarget;
 };
 
 nsresult
 imgFrame::UnlockImageData()
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
 
   MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
   if (mLockCount <= 0) {
     return NS_ERROR_FAILURE;
   }
 
-  MOZ_ASSERT(mLockCount > 1 || IsImageCompleteInternal() || mAborted,
-             "Should have marked complete or aborted before unlocking");
-
   // If we're about to become unlocked, we don't need to hold on to our data
   // surface anymore. (But we don't need to do anything for paletted images,
   // which don't have surfaces.)
   if (mLockCount == 1 && !mPalettedImageData) {
     // We can't safely optimize off-main-thread, so create a runnable to do it.
     if (!NS_IsMainThread()) {
       nsCOMPtr<nsIRunnable> runnable = new UnlockImageDataRunnable(this);
       NS_DispatchToMainThread(runnable);
@@ -922,24 +893,24 @@ imgFrame::IsSinglePixel() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mSinglePixel;
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   return GetSurfaceInternal();
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurfaceInternal()
 {
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
 
   if (mOptSurface) {
     if (mOptSurface->IsValid())
       return mOptSurface;
     else
       mOptSurface = nullptr;
   }
 
@@ -954,34 +925,34 @@ imgFrame::GetSurfaceInternal()
     return nullptr;
 
   return CreateLockedSurface(mVBuf, mSize, mFormat);
 }
 
 TemporaryRef<DrawTarget>
 imgFrame::GetDrawTarget()
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
 
   uint8_t* data;
   uint32_t length;
   GetImageDataInternal(&data, &length);
   if (!data) {
     return nullptr;
   }
 
   int32_t stride = GetStride();
   return gfxPlatform::GetPlatform()->
     CreateDrawTargetForData(data, mSize, stride, mFormat);
 }
 
 AnimationData
 imgFrame::GetAnimationData() const
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
   uint8_t* data;
   if (mPalettedImageData) {
     data = mPalettedImageData;
   } else {
     uint32_t length;
     GetImageDataInternal(&data, &length);
@@ -991,67 +962,38 @@ imgFrame::GetAnimationData() const
 
   return AnimationData(data, PaletteDataLength(), mTimeout, GetRect(),
                        mBlendMethod, mDisposalMethod, hasAlpha);
 }
 
 ScalingData
 imgFrame::GetScalingData() const
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
   MOZ_ASSERT(!GetIsPaletted(), "GetScalingData can't handle paletted images");
 
   uint8_t* data;
   uint32_t length;
   GetImageDataInternal(&data, &length);
 
   return ScalingData(data, mSize, GetImageBytesPerRow(), mFormat);
 }
 
-void
-imgFrame::Abort()
+bool
+imgFrame::ImageComplete() const
 {
-  MonitorAutoLock lock(mMonitor);
-
-  mAborted = true;
-
-  // Wake up anyone who's waiting.
-  if (IsImageCompleteInternal()) {
-    mMonitor.NotifyAll();
-  }
+  MutexAutoLock lock(mMutex);
+  return ImageCompleteInternal();
 }
 
 bool
-imgFrame::IsImageComplete() const
-{
-  MonitorAutoLock lock(mMonitor);
-  return IsImageCompleteInternal();
-}
-
-void
-imgFrame::WaitUntilComplete() const
+imgFrame::ImageCompleteInternal() const
 {
-  MonitorAutoLock lock(mMonitor);
-
-  while (true) {
-    // Return if we're aborted or complete.
-    if (mAborted || IsImageCompleteInternal()) {
-      return;
-    }
-
-    // Not complete yet, so we'll have to wait.
-    mMonitor.Wait();
-  }
-}
-
-bool
-imgFrame::IsImageCompleteInternal() const
-{
-  mMonitor.AssertCurrentThreadOwns();
+  mMutex.AssertCurrentThreadOwns();
   return mDecoded.IsEqualInterior(nsIntRect(mOffset.x, mOffset.y,
                                             mSize.width, mSize.height));
 }
 
 bool imgFrame::GetCompositingFailed() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mCompositingFailed;
@@ -1062,17 +1004,17 @@ void imgFrame::SetCompositingFailed(bool
   MOZ_ASSERT(NS_IsMainThread());
   mCompositingFailed = val;
 }
 
 size_t
 imgFrame::SizeOfExcludingThis(gfxMemoryLocation aLocation,
                               MallocSizeOf aMallocSizeOf) const
 {
-  MonitorAutoLock lock(mMonitor);
+  MutexAutoLock lock(mMutex);
 
   // aMallocSizeOf is only used if aLocation==gfxMemoryLocation::IN_PROCESS_HEAP.  It
   // should be nullptr otherwise.
   NS_ABORT_IF_FALSE(
     (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP &&  aMallocSizeOf) ||
     (aLocation != gfxMemoryLocation::IN_PROCESS_HEAP && !aMallocSizeOf),
     "mismatch between aLocation and aMallocSizeOf");
 
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -3,18 +3,18 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * 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 imgFrame_h
 #define imgFrame_h
 
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/Monitor.h"
 #include "mozilla/Move.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/TypedEnum.h"
 #include "mozilla/VolatileBuffer.h"
 #include "gfxDrawable.h"
 #include "imgIContainer.h"
 #include "MainThreadUtils.h"
 
 namespace mozilla {
 namespace image {
@@ -177,58 +177,29 @@ public:
   bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
             GraphicsFilter aFilter, uint32_t aImageFlags);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
 
   /**
    * Mark this imgFrame as completely decoded, and set final options.
    *
-   * You must always call either Finish() or Abort() before releasing the last
-   * RawAccessFrameRef pointing to an imgFrame.
-   *
    * @param aFrameOpacity    Whether this imgFrame is opaque.
    * @param aDisposalMethod  For animation frames, how this imgFrame is cleared
    *                         from the compositing frame before the next frame is
    *                         displayed.
    * @param aRawTimeout      For animation frames, the timeout in milliseconds
    *                         before the next frame is displayed. This timeout is
    *                         not necessarily the timeout that will actually be
    *                         used; see FrameAnimator::GetTimeoutForFrame.
    * @param aBlendMethod     For animation frames, a blending method to be used
    *                         when compositing this frame.
    */
-  void Finish(Opacity aFrameOpacity = Opacity::SOME_TRANSPARENCY,
-              DisposalMethod aDisposalMethod = DisposalMethod::KEEP,
-              int32_t aRawTimeout = 0,
-              BlendMethod aBlendMethod = BlendMethod::OVER);
-
-  /**
-   * Mark this imgFrame as aborted. This informs the imgFrame that if it isn't
-   * completely decoded now, it never will be.
-   *
-   * You must always call either Finish() or Abort() before releasing the last
-   * RawAccessFrameRef pointing to an imgFrame.
-   */
-  void Abort();
-
-  /**
-   * Returns true if this imgFrame is completely decoded.
-   */
-  bool IsImageComplete() const;
-
-  /**
-   * Blocks until this imgFrame is either completely decoded, or is marked as
-   * aborted.
-   *
-   * Note that calling this on the main thread _blocks the main thread_. Be very
-   * careful in your use of this method to avoid excessive main thread jank or
-   * deadlock.
-   */
-  void WaitUntilComplete() const;
+  void Finish(Opacity aFrameOpacity, DisposalMethod aDisposalMethod,
+              int32_t aRawTimeout, BlendMethod aBlendMethod);
 
   IntSize GetImageSize() { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }
   bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
   void GetImageData(uint8_t **aData, uint32_t *length) const;
   uint8_t* GetImageData() const;
 
@@ -242,16 +213,18 @@ public:
    *
    * This should only be used for assertions.
    */
   SurfaceFormat GetFormat() const;
 
   AnimationData GetAnimationData() const;
   ScalingData GetScalingData() const;
 
+  bool ImageComplete() const;
+
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   void SetOptimizable();
 
   Color SinglePixelColor() const;
   bool IsSinglePixel() const;
 
@@ -267,17 +240,17 @@ private: // methods
 
   nsresult LockImageData();
   nsresult UnlockImageData();
   nsresult Optimize();
   nsresult Deoptimize();
 
   void AssertImageDataLocked() const;
 
-  bool IsImageCompleteInternal() const;
+  bool ImageCompleteInternal() const;
   nsresult ImageUpdatedInternal(const nsIntRect& aUpdateRect);
   void GetImageDataInternal(uint8_t **aData, uint32_t *length) const;
   uint32_t GetImageBytesPerRow() const;
   uint32_t GetImageDataLength() const;
   int32_t GetStride() const;
   TemporaryRef<SourceSurface> GetSurfaceInternal();
 
   uint32_t PaletteDataLength() const
@@ -305,20 +278,20 @@ private: // methods
                                       SourceSurface*     aSurface);
 
 private: // data
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
   friend class UnlockImageDataRunnable;
 
   //////////////////////////////////////////////////////////////////////////////
-  // Thread-safe mutable data, protected by mMonitor.
+  // Thread-safe mutable data, protected by mMutex.
   //////////////////////////////////////////////////////////////////////////////
 
-  mutable Monitor mMonitor;
+  mutable Mutex mMutex;
 
   RefPtr<DataSourceSurface> mImageSurface;
   RefPtr<SourceSurface> mOptSurface;
 
   RefPtr<VolatileBuffer> mVBuf;
   VolatileBufferPtr<uint8_t> mVBufPtr;
 
   nsIntRect mDecoded;
@@ -329,17 +302,16 @@ private: // data
   //! Raw timeout for this frame. (See FrameAnimator::GetTimeoutForFrame.)
   int32_t mTimeout; // -1 means display forever.
 
   DisposalMethod mDisposalMethod;
   BlendMethod    mBlendMethod;
   SurfaceFormat  mFormat;
 
   bool mHasNoAlpha;
-  bool mAborted;
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Effectively const data, only mutated in the Init methods.
   //////////////////////////////////////////////////////////////////////////////
 
   IntSize      mImageSize;
   IntSize      mSize;
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -28,17 +28,16 @@ UNIFIED_SOURCES += [
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',
     'MultipartImage.cpp',
     'OrientedImage.cpp',
     'ScriptedNotificationObserver.cpp',
     'ShutdownTracker.cpp',
-    'SourceBuffer.cpp',
     'SurfaceCache.cpp',
     'SVGDocumentWrapper.cpp',
     'VectorImage.cpp',
 ]
 
 # These files can't be unified because of ImageLogging.h #include order issues.
 SOURCES += [
     'imgLoader.cpp',
--- a/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -11,17 +11,17 @@ load colormap-range.gif
 # they have image sizes of 65535x65535 which is larger than we allow
 load invalid-size.gif
 # this image has a valid size for the first frame, but the second frame is 65535x65535
 # AddressSanitizer sometimes fails with a CHECK on an allocation caused by this.
 skip-if(AddressSanitizer) load invalid-size-second-frame.gif
 
 # Animated gifs with a very large canvas, but tiny actual content.
 load delaytest.html?523528-1.gif
-skip load delaytest.html?523528-2.gif  # disabled due to bug 1079627
+load delaytest.html?523528-2.gif
 
 # this would have exposed the leak discovered in bug 642902
 load invalid-icc-profile.jpg
 
 # maximum (256) width and height icons that we currently (due to bug 668068)
 # interpret as 0-width and 0-height.
 load 256-width.ico
 load 256-height.ico
--- a/image/test/reftest/gif/reftest.list
+++ b/image/test/reftest/gif/reftest.list
@@ -12,18 +12,17 @@
 == small-background-size.gif small-background-size-ref.gif
 == small-background-size-2.gif small-background-size-2-ref.gif
 
 # a transparent gif that disposes previous frames with clear; we must properly
 # clear each frame to pass.
 random == delaytest.html?transparent-animation.gif transparent-animation-finalframe.gif # incorrect timing dependence (bug 558678)
 
 # test for bug 641198
-# Marked random because of increased failure frequency after bug 1117607.
-skip == test_bug641198.html animation2a-finalframe.gif # bug 773482
+skip-if(B2G) random-if(Android) == test_bug641198.html animation2a-finalframe.gif # bug 773482
 
 # Bug 1062886: a gif with a single color and an offset
 == one-color-offset.gif one-color-offset-ref.gif
 
 # Bug 1068230
 == tile-transform.html tile-transform-ref.html
 
 # webcam-simulacrum.mgif is a hand-edited file containing red.gif and blue.gif,
@@ -43,10 +42,9 @@ skip == test_bug641198.html animation2a-
 # \r\n
 # <contents of blue.gif> (no newline)
 # --BOUNDARYOMG--\r\n
 # 
 # (The boundary is arbitrary, and just has to be defined as something that
 # won't be in the text of the contents themselves. --$(boundary)\r\n means
 # "Here is the beginning of a boundary," and --$(boundary)-- means "All done
 # sending you parts.")
-skip-if(B2G) random-if(browserIsRemote) HTTP == webcam.html blue.gif # bug 773482
-
+skip-if(B2G) HTTP == webcam.html blue.gif # bug 773482
--- a/image/test/reftest/ico/cur/reftest.list
+++ b/image/test/reftest/ico/cur/reftest.list
@@ -1,4 +1,4 @@
 # ICO BMP and PNG mixed tests
 
-random == wrapper.html?pointer.cur wrapper.html?pointer.png
+== wrapper.html?pointer.cur wrapper.html?pointer.png
 
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -966,17 +966,17 @@ fails == 413027-3.html 413027-3-ref.html
 == 413286-2b.html 413286-2-ref.html
 == 413286-2c.html 413286-2-ref.html
 == 413286-3.html 413286-3-ref.html
 == 413286-4a.html 413286-4-ref.html
 == 413286-4b.html 413286-4-ref.html
 == 413286-5.html 413286-5-ref.html
 == 413286-6.html 413286-6-ref.html
 skip-if(cocoaWidget) == 413292-1.html 413292-1-ref.html # disabling due to failure loading on some mac tinderboxes. See bug 432954
-fuzzy-if(Android&&AndroidVersion>=15,11,15) fuzzy-if(B2G,11,17) == 413361-1.html 413361-1-ref.html
+fuzzy-if(Android&&AndroidVersion>=15,11,15) == 413361-1.html 413361-1-ref.html
 == 413840-background-unchanged.html 413840-background-unchanged-ref.html
 == 413840-ltr-offsets.html 413840-ltr-offsets-ref.html
 == 413840-rtl-offsets.html 413840-rtl-offsets-ref.html
 == 413840-pushed-line-bullet.html 413840-pushed-line-bullet-ref.html
 == 413840-bullet-first-line.html 413840-bullet-first-line-ref.html
 == 413982.html 413982-ref.html
 == 414123.xhtml 414123-ref.xhtml
 == 414638.html 414638-ref.html
--- a/media/mtransport/runnable_utils.h
+++ b/media/mtransport/runnable_utils.h
@@ -31,20 +31,17 @@ RunOnThreadInternal(nsIEventTarget *thre
     nsresult rv;
     rv = thread->IsOnCurrentThread(&on);
 
     // If the target thread has already shut down, we don't want to assert.
     if (rv != NS_ERROR_NOT_INITIALIZED) {
       MOZ_ASSERT(NS_SUCCEEDED(rv));
     }
 
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      // we're going to destroy the runnable on this thread!
-      return rv;
-    }
+    NS_ENSURE_SUCCESS(rv, rv);
     if (!on) {
       return thread->Dispatch(runnable_ref, flags);
     }
   }
   return runnable_ref->Run();
 }
 
 template<RunnableResult result>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3786,16 +3786,19 @@ pref("image.mem.discardable", true);
 pref("image.mem.decodeondraw", true);
 
 // Allows image locking of decoded image data in content processes.
 pref("image.mem.allow_locking_in_content_processes", true);
 
 // Chunk size for calls to the image decoders
 pref("image.mem.decode_bytes_at_a_time", 16384);
 
+// The longest time we can spend in an iteration of an async decode
+pref("image.mem.max_ms_before_yield", 5);
+
 // Minimum timeout for expiring unused images from the surface cache, in
 // milliseconds. This controls how long we store cached temporary surfaces.
 pref("image.mem.surfacecache.min_expiration_ms", 60000); // 60ms
 
 // Maximum size for the surface cache, in kilobytes.
 pref("image.mem.surfacecache.max_size_kb", 1048576); // 1GB
 
 // The surface cache's size, within the constraints of the maximum size set
--- a/netwerk/sctp/datachannel/DataChannel.cpp
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -36,17 +36,16 @@
 #include "nsIObserver.h"
 #include "mozilla/Services.h"
 #include "nsProxyRelease.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
 #include "nsNetUtil.h"
 #include "mozilla/StaticPtr.h"
-#include "mozilla/unused.h"
 #ifdef MOZ_PEERCONNECTION
 #include "mtransport/runnable_utils.h"
 #endif
 
 #define DATACHANNEL_LOG(args) LOG(args)
 #include "DataChannel.h"
 #include "DataChannelProtocol.h"
 
@@ -2306,17 +2305,17 @@ DataChannelConnection::SendBinary(DataCh
 
 class ReadBlobRunnable : public nsRunnable {
 public:
   ReadBlobRunnable(DataChannelConnection* aConnection, uint16_t aStream,
     nsIInputStream* aBlob) :
     mConnection(aConnection),
     mStream(aStream),
     mBlob(aBlob)
-  {}
+  { }
 
   NS_IMETHODIMP Run() {
     // ReadBlob() is responsible to releasing the reference
     DataChannelConnection *self = mConnection;
     self->ReadBlob(mConnection.forget(), mStream, mBlob);
     return NS_OK;
   }
 
@@ -2345,89 +2344,49 @@ DataChannelConnection::SendBlob(uint16_t
     }
   }
 
   nsCOMPtr<nsIRunnable> runnable = new ReadBlobRunnable(this, stream, aBlob);
   mInternalIOThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
   return 0;
 }
 
-class DataChannelBlobSendRunnable : public nsRunnable
-{
-public:
-  DataChannelBlobSendRunnable(already_AddRefed<DataChannelConnection>& aConnection,
-                              uint16_t aStream)
-    : mConnection(aConnection)
-    , mStream(aStream) {}
-
-  ~DataChannelBlobSendRunnable()
-  {
-    if (!NS_IsMainThread() && mConnection) {
-      MOZ_ASSERT(false);
-      // explicitly leak the connection if destroyed off mainthread
-      unused << mConnection.forget().take();
-    }
-  }
-
-  NS_IMETHODIMP Run()
-  {
-    ASSERT_WEBRTC(NS_IsMainThread());
-
-    mConnection->SendBinaryMsg(mStream, mData);
-    mConnection = nullptr;
-    return NS_OK;
-  }
-
-  // explicitly public so we can avoid allocating twice and copying
-  nsCString mData;
-
-private:
-  // Note: we can be destroyed off the target thread, so be careful not to let this
-  // get Released()ed on the temp thread!
-  nsRefPtr<DataChannelConnection> mConnection;
-  uint16_t mStream;
-};
-
 void
 DataChannelConnection::ReadBlob(already_AddRefed<DataChannelConnection> aThis,
                                 uint16_t aStream, nsIInputStream* aBlob)
 {
   // NOTE: 'aThis' has been forgotten by the caller to avoid releasing
   // it off mainthread; if PeerConnectionImpl has released then we want
   // ~DataChannelConnection() to run on MainThread
 
   // XXX to do this safely, we must enqueue these atomically onto the
-  // output socket.  We need a sender thread(s?) to enqueue data into the
+  // output socket.  We need a sender thread(s?) to enque data into the
   // socket and to avoid main-thread IO that might block.  Even on a
   // background thread, we may not want to block on one stream's data.
   // I.e. run non-blocking and service multiple channels.
 
   // For now as a hack, send as a single blast of queued packets which may
   // be deferred until buffer space is available.
+  nsCString temp;
   uint64_t len;
   nsCOMPtr<nsIThread> mainThread;
   NS_GetMainThread(getter_AddRefs(mainThread));
 
-  // Must not let Dispatching it cause the DataChannelConnection to get
-  // released on the wrong thread.  Using WrapRunnable(nsRefPtr<DataChannelConnection>(aThis),...
-  // will occasionally cause aThis to get released on this thread.  Also, an explicit Runnable
-  // lets us avoid copying the blob data an extra time.
-  nsRefPtr<DataChannelBlobSendRunnable> runnable = new DataChannelBlobSendRunnable(aThis,
-                                                                                   aStream);
-  // avoid copying the blob data by passing the mData from the runnable
   if (NS_FAILED(aBlob->Available(&len)) ||
-      NS_FAILED(NS_ReadInputStreamToString(aBlob, runnable->mData, len))) {
+      NS_FAILED(NS_ReadInputStreamToString(aBlob, temp, len))) {
     // Bug 966602:  Doesn't return an error to the caller via onerror.
     // We must release DataChannelConnection on MainThread to avoid issues (bug 876167)
-    // aThis is now owned by the runnable; release it there
-    NS_ProxyRelease(mainThread, runnable);
+    NS_ProxyRelease(mainThread, aThis.take());
     return;
   }
   aBlob->Close();
-  NS_DispatchToMainThread(runnable, NS_DISPATCH_NORMAL);
+  RUN_ON_THREAD(mainThread, WrapRunnable(nsRefPtr<DataChannelConnection>(aThis),
+                               &DataChannelConnection::SendBinaryMsg,
+                               aStream, temp),
+                NS_DISPATCH_NORMAL);
 }
 
 void
 DataChannelConnection::GetStreamIds(std::vector<uint16_t>* aStreamList)
 {
   ASSERT_WEBRTC(NS_IsMainThread());
   for (uint32_t i = 0; i < mStreams.Length(); ++i) {
     if (mStreams[i]) {
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -133,17 +133,16 @@ skip-if = buildapp == 'mulet'
 [test_popup_scaled.xul]
 [test_popup_tree.xul]
 [test_popuphidden.xul]
 [test_popupincontent.xul]
 [test_popupremoving.xul]
 [test_popupremoving_frame.xul]
 [test_position.xul]
 [test_preferences.xul]
-skip-if = toolkit != "cocoa"
 [test_preferences_beforeaccept.xul]
 support-files = window_preferences_beforeaccept.xul
 [test_preferences_onsyncfrompreference.xul]
 support-files = window_preferences_onsyncfrompreference.xul
 [test_progressmeter.xul]
 [test_props.xul]
 [test_radio.xul]
 [test_richlist_direction.xul]