Backed out 5 changesets (bug 1079627) for causing B2G crashes on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 12 Jan 2015 13:17:52 -0500
changeset 249210 cb26891d69e9c2c4d18a7ab648630ee5d88e8306
parent 249209 fbe8067063a0a6b06aa484deb8d302f7b261646d
child 249211 332fa08016f81abeceb85cd1265099e891e16b6c
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)
bugs1079627
milestone37.0a1
backs oute8ddeaeb82eee0c8f050ddad8c045e20c5df85c0
14cc155b0d6ea19992a0950b8f5dd6c2c74851f4
e7add8446221fcce51f580f37cd779a42c876e3e
44b622a479b60c6ecd1883a84b782e54a44221a7
c86c43915254b769d8b63cdf997a159d32905055
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
Backed out 5 changesets (bug 1079627) for causing B2G crashes on a CLOSED TREE. Backed out changeset e8ddeaeb82ee (bug 1079627) Backed out changeset 14cc155b0d6e (bug 1079627) Backed out changeset e7add8446221 (bug 1079627) Backed out changeset 44b622a479b6 (bug 1079627) Backed out changeset c86c43915254 (bug 1079627)
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/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/reftest/gif/reftest.list
modules/libpref/init/all.js
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.
--- 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)
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -18,17 +18,17 @@ 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,
--- 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
 }
 
@@ -244,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).
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -18,17 +18,17 @@ 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;
   }
 
--- 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
 }
--- 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);
 
--- 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)
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -19,17 +19,17 @@
 
 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;
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                    int32_t width, int32_t height,
--- 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,45 +2,40 @@
 /* -*- 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)
@@ -48,42 +43,27 @@ Decoder::Decoder(RasterImage* aImage)
 
 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();
@@ -113,79 +93,16 @@ Decoder::InitSharedDecoder(uint8_t* aIma
     PostFrameStart();
   }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
-nsresult
-Decoder::Decode()
-{
-  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);
-
-    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;
-
-      nsresult finalStatus = mIterator->CompletionStatus();
-      if (NS_FAILED(finalStatus)) {
-        PostDataError();
-      }
-
-      return finalStatus;
-    }
-
-    MOZ_ASSERT(newState == SourceBufferIterator::READY);
-
-    Write(mIterator->Data(), mIterator->Length());
-  }
-
-  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);
-}
-
 void
 Decoder::Write(const char* aBuffer, uint32_t aCount)
 {
   PROFILER_LABEL("ImageDecoder", "Write",
     js::ProfileEntry::Category::GRAPHICS);
 
   // We're strict about decoder errors
   MOZ_ASSERT(!HasDecoderError(),
@@ -235,90 +152,82 @@ Decoder::Write(const char* aBuffer, uint
     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);
-
-  if (HasSize()) {
-    SetSizeOnImage();
-  }
-
-  if (mDecodeDone && !IsSizeDecode()) {
-    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
+  mImageMetadata.SetOnImage(&mImage);
 
-    // 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();
-    }
-
-    mImage->OnDecodingComplete();
+  if (mDecodeDone) {
+    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
+    mImage.DecodingComplete(mCurrentFrame.get());
   }
 }
 
 void
 Decoder::FinishSharedDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -399,35 +308,28 @@ Decoder::EnsureFrame(uint32_t aFrameNum,
     return RawAccessFrameRef();
   }
 
   MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
              aPreviousFrame->GetFormat() != aFormat ||
              aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
              "Replacing first frame with the same kind of frame?");
 
-  // Reset our state.
+  // Remove the old frame from the SurfaceCache and release our reference to it.
+  IntSize prevFrameSize = aPreviousFrame->GetImageSize();
+  SurfaceCache::RemoveSurface(ImageKey(&mImage),
+                              RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0));
+  mFrameCount = 0;
   mInFrame = false;
-  RawAccessFrameRef ref = Move(mCurrentFrame);
-
-  MOZ_ASSERT(ref, "No ref to current frame?");
+  mCurrentFrame->Abort();
+  mCurrentFrame = RawAccessFrameRef();
 
-  // Reinitialize the old frame.
-  nsIntSize oldSize = ThebesIntSize(aPreviousFrame->GetImageSize());
-  bool nonPremult =
-    aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
-  if (NS_FAILED(aPreviousFrame->ReinitForDecoder(oldSize, aFrameRect, aFormat,
-                                               aPaletteDepth, nonPremult))) {
-    NS_WARNING("imgFrame::ReinitForDecoder should succeed");
-    mFrameCount = 0;
-    aPreviousFrame->Abort();
-    return RawAccessFrameRef();
-  }
-
-  return ref;
+  // 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,
@@ -462,28 +364,22 @@ Decoder::InternalAddFrame(uint32_t aFram
 
   RawAccessFrameRef ref = frame->RawAccessRef();
   if (!ref) {
     frame->Abort();
     return RawAccessFrameRef();
   }
 
   InsertOutcome outcome =
-    SurfaceCache::Insert(frame, ImageKey(mImage.get()),
+    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();
     return RawAccessFrameRef();
   }
 
   nsIntRect refreshArea;
 
   if (aFrameNum == 1) {
     MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
@@ -504,33 +400,30 @@ Decoder::InternalAddFrame(uint32_t aFram
     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) { }
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -7,69 +7,68 @@
 #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();
 
   /**
    * Initializes a decoder whose image and observer is already being used by a
    * parent decoder. Decoders may not be re-initialized.
    *
    * Notifications Sent: TODO
    */
   void InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength,
                          uint32_t* aColormap, uint32_t aColormapSize,
                          RawAccessFrameRef&& aFrameRef);
 
   /**
-   * Decodes, reading all data currently available in the SourceBuffer. If more
+   * 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.
    *
-   * data is needed, Decode() automatically ensures that it will be called again
-   * on a DecodePool thread when the data becomes available.
+   * @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
    */
-  nsresult Decode();
+  void Write(const char* aBuffer, uint32_t aCount);
 
   /**
-   * Cleans up the decoder's state and notifies our image about success or
-   * failure. May only be called on the main thread.
+   * Informs the decoder that all the data has been written.
+   *
+   * Notifications Sent: TODO
    */
-  void Finish();
-
-  /**
-   * Given a maximum number of bytes we're willing to decode, @aByteLimit,
-   * returns true if we should attempt to run this decoder synchronously.
-   */
-  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();
@@ -94,41 +93,30 @@ 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
@@ -140,80 +128,41 @@ public:
    * 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
   };
 
@@ -233,21 +182,16 @@ public:
     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,
@@ -268,28 +212,16 @@ public:
   }
 
   RawAccessFrameRef GetCurrentFrameRef()
   {
     return mCurrentFrame ? mCurrentFrame->RawAccessRef()
                          : RawAccessFrameRef();
   }
 
-  /**
-   * Writes data to the decoder. Only public for the benefit of nsICODecoder;
-   * other callers should use Decode().
-   *
-   * @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);
-
-
 protected:
   virtual ~Decoder();
 
   /*
    * Internal hooks. Decoder implementations may override these and
    * only these methods.
    */
   virtual void InitInternal();
@@ -371,23 +303,21 @@ protected:
                                 imgFrame* aPreviousFrame);
 
   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
@@ -395,21 +325,18 @@ protected:
 
   // 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
   {
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -285,21 +285,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
--- 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
@@ -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,16 +540,20 @@ 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.
     ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
 
     if (!ref) {
       // We didn't successfully redecode, so just fail.
@@ -845,17 +878,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);
@@ -900,89 +941,102 @@ RasterImage::OnAddedFrame(uint32_t aNewF
     NS_DispatchToMainThread(runnable);
     return;
   }
 
   MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
              mFrameCount < aNewFrameCount,
              "Frame count running backwards");
 
-  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);
-      }
+  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 +1113,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 +1170,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,102 +1393,119 @@ 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));
+  // 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.
-    decoder->SetSize(mSize, mOrientation);
-    decoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
-                          SurfaceFormat::B8G8R8A8);
-    decoder->AllocateFrame();
+    mDecoder->SetSize(mSize, mOrientation);
+    mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
+                           SurfaceFormat::B8G8R8A8);
+    mDecoder->AllocateFrame();
   }
-  decoder->SetIterator(mSourceBuffer->Iterator());
-  decoder->Init();
-
-  if (NS_FAILED(decoder->GetDecoderError())) {
-    return nullptr;
-  }
+  mDecoder->SetSendPartialInvalidations(!mHasBeenDecoded);
+  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
@@ -1338,170 +1513,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) {
@@ -1535,19 +1902,22 @@ 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.
@@ -1656,41 +2026,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() */
@@ -1732,59 +2107,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");
 }
 
@@ -1795,31 +2252,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;
 
@@ -1841,40 +2298,124 @@ 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 (!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);
 
@@ -1889,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();
 }
--- 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,463 +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")
-{ }
-
-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();
-
-  size_t length = mChunks.Length();
-
-#if defined(MOZILLA_IMAGELIB_SOURCEBUFFER_USE_FIBONACCI)
-  // We grow the source buffer using a Fibonacci growth rate.
-
-  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);
-#else
-  // We grow the source buffer using a slow exponential rate - 1.25x.
-
-  if (length == 0) {
-    return aMinCapacity;
-  }
-
-  return max(size_t(mChunks[length - 1].Capacity() * 1.25), aMinCapacity);
-#endif
-}
-
-void
-SourceBuffer::AddWaitingConsumer(IResumable* aConsumer)
-{
-  mMutex.AssertCurrentThreadOwns();
-
-  MOZ_ASSERT(!mStatus, "Waiting when we're complete?");
-
-  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.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) {
-    n += mChunks[i].Capacity();
-  }
-
-  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.
-  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,372 +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;
-
-  ~SourceBuffer() { }
-
-  //////////////////////////////////////////////////////////////////////////////
-  // Chunk type and chunk-related methods.
-  //////////////////////////////////////////////////////////////////////////////
-
-  class Chunk
-  {
-  public:
-    explicit Chunk(size_t aCapacity)
-      : mCapacity(aCapacity)
-      , mLength(0)
-    {
-      MOZ_ASSERT(aCapacity > 0, "Creating zero-capacity chunk");
-      mData = static_cast<char*>(nsTArrayFallibleAllocator::Malloc(aCapacity));
-    }
-
-    ~Chunk()
-    {
-      nsTArrayFallibleAllocator::Free(mData);
-    }
-    
-
-    Chunk(Chunk&& aOther)
-      : mCapacity(aOther.mCapacity)
-      , mLength(aOther.mLength)
-      , mData(aOther.mData)
-    {
-      aOther.mCapacity = aOther.mLength = 0;
-      aOther.mData = nullptr;
-    }
-
-    Chunk& operator=(Chunk&& aOther)
-    {
-      mCapacity = aOther.mCapacity;
-      mLength = aOther.mLength;
-      mData = aOther.mData;
-      aOther.mCapacity = aOther.mLength = 0;
-      aOther.mData = nullptr;
-      return *this;
-    }
-
-    bool AllocationFailed() const { return !mData; }
-    size_t Capacity() const { return mCapacity; }
-    size_t Length() const { return mLength; }
-    char* Data() const { return mData; }
-
-    void AddLength(size_t aAdditionalLength)
-    {
-      MOZ_ASSERT(mLength + aAdditionalLength <= mCapacity);
-      mLength += aAdditionalLength;
-    }
-
-  private:
-    Chunk(const Chunk&) = delete;
-    Chunk& operator=(const Chunk&) = delete;
-
-    size_t mCapacity;
-    size_t mLength;
-    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
@@ -819,26 +819,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
@@ -231,17 +231,16 @@ struct SurfaceCache
    * 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
@@ -161,90 +161,16 @@ imgFrame::~imgFrame()
   MOZ_ASSERT(mAborted || IsImageCompleteInternal());
 #endif
 
   moz_free(mPalettedImageData);
   mPalettedImageData = nullptr;
 }
 
 nsresult
-imgFrame::ReinitForDecoder(const nsIntSize& aImageSize,
-                           const nsIntRect& aRect,
-                           SurfaceFormat aFormat,
-                           uint8_t aPaletteDepth /* = 0 */,
-                           bool aNonPremult /* = false */)
-{
-  MonitorAutoLock lock(mMonitor);
-
-  if (mDecoded.x != 0 || mDecoded.y != 0 ||
-      mDecoded.width != 0 || mDecoded.height != 0) {
-    MOZ_ASSERT_UNREACHABLE("Shouldn't reinit after write");
-    return NS_ERROR_FAILURE;
-  }
-  if (mAborted) {
-    MOZ_ASSERT_UNREACHABLE("Shouldn't reinit if aborted");
-    return NS_ERROR_FAILURE;
-  }
-  if (mLockCount < 1) {
-    MOZ_ASSERT_UNREACHABLE("Shouldn't reinit unless locked");
-    return NS_ERROR_FAILURE;
-  }
-
-  // Restore everything (except mLockCount, which we need to keep) to how it was
-  // when we were first created.
-  // XXX(seth): This is probably a little excessive, but I want to be *really*
-  // sure that nothing got missed.
-  mDecoded = nsIntRect(0, 0, 0, 0);
-  mTimeout = 100;
-  mDisposalMethod = DisposalMethod::NOT_SPECIFIED;
-  mBlendMethod = BlendMethod::OVER;
-  mHasNoAlpha = false;
-  mAborted = false;
-  mPaletteDepth = 0;
-  mNonPremult = false;
-  mSinglePixel = false;
-  mCompositingFailed = false;
-  mOptimizable = false;
-  mImageSize = IntSize();
-  mSize = IntSize();
-  mOffset = nsIntPoint();
-  mSinglePixelColor = Color();
-
-  // Release all surfaces.
-  mImageSurface = nullptr;
-  mOptSurface = nullptr;
-  mVBuf = nullptr;
-  mVBufPtr = nullptr;
-  moz_free(mPalettedImageData);
-  mPalettedImageData = nullptr;
-
-  // Reinitialize.
-  nsresult rv = InitForDecoder(aImageSize, aRect, aFormat,
-                               aPaletteDepth, aNonPremult);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
-  // We were locked before; perform the same actions we would've performed when
-  // we originally got locked.
-  if (mImageSurface) {
-    mVBufPtr = mVBuf;
-    return NS_OK;
-  }
-
-  if (!mPalettedImageData) {
-    MOZ_ASSERT_UNREACHABLE("We got optimized somehow during reinit");
-    return NS_ERROR_FAILURE;
-  }
-
-  // Paletted images don't have surfaces, so there's nothing to do.
-  return NS_OK;
-}
-
-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.
@@ -1084,17 +1010,19 @@ imgFrame::GetScalingData() const
 void
 imgFrame::Abort()
 {
   MonitorAutoLock lock(mMonitor);
 
   mAborted = true;
 
   // Wake up anyone who's waiting.
-  mMonitor.NotifyAll();
+  if (IsImageCompleteInternal()) {
+    mMonitor.NotifyAll();
+  }
 }
 
 bool
 imgFrame::IsImageComplete() const
 {
   MonitorAutoLock lock(mMonitor);
   return IsImageCompleteInternal();
 }
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -154,29 +154,16 @@ public:
    * more expensive than in the InitForDecoder() case.
    */
   nsresult InitWithDrawable(gfxDrawable* aDrawable,
                             const nsIntSize& aSize,
                             const SurfaceFormat aFormat,
                             GraphicsFilter aFilter,
                             uint32_t aImageFlags);
 
-  /**
-   * Reinitializes an existing imgFrame with new parameters. You must be holding
-   * a RawAccessFrameRef to the imgFrame, and it must never have been written
-   * to, marked finished, or aborted.
-   *
-   * XXX(seth): We will remove this in bug 1117607.
-   */
-  nsresult ReinitForDecoder(const nsIntSize& aImageSize,
-                            const nsIntRect& aRect,
-                            SurfaceFormat aFormat,
-                            uint8_t aPaletteDepth = 0,
-                            bool aNonPremult = false);
-
   DrawableFrameRef DrawableRef();
   RawAccessFrameRef RawAccessRef();
 
   /**
    * Make this imgFrame permanently available for raw access.
    *
    * This is irrevocable, and should be avoided whenever possible, since it
    * prevents this imgFrame from being optimized and makes it impossible for its
--- 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/reftest/gif/reftest.list
+++ b/image/test/reftest/gif/reftest.list
@@ -42,10 +42,9 @@ skip-if(B2G) random-if(Android) == test_
 # \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.")
-# (Disabled due to bug 1079627.)
-#skip-if(B2G) HTTP == webcam.html blue.gif # bug 773482
+skip-if(B2G) HTTP == webcam.html blue.gif # bug 773482
--- 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/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]