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 248980 8f3cc2c90893ab18bcb9eda7961ff3fc08022a99
parent 248979 a8044fd506db631fe74958c453dec459f10e3b11
child 248981 238f460e7aedc87a4ce3fdff8b9479256d3f3d58
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1117607, 1118087, 1118092, 1118105, 1030372, 1079627
milestone37.0a1
backs outb4ebefd0f7e3a0814ea9d9f42dc0da74be3fb998
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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]