Bug 1079627 (Part 3) - Support multiple decoders for a single RasterImage. r=tn a=lmandel
authorSeth Fowler <seth@mozilla.com>
Thu, 15 Jan 2015 15:11:36 -0800
changeset 249806 83bcdb8506673b2164f3202ed04f589cb3ea0563
parent 249805 2e0e58532c2de49805d53a7b606304ccb57059d9
child 249807 10510070c482dafaa4e9a7e957d732864bc1b555
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)
reviewerstn, lmandel
bugs1079627
milestone37.0a2
Bug 1079627 (Part 3) - Support multiple decoders for a single RasterImage. r=tn a=lmandel
gfx/thebes/gfxPrefs.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
modules/libpref/init/all.js
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -231,26 +231,25 @@ 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(Live, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
+  DECL_GFX_PREF(Once, "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(Live, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
+  DECL_GFX_PREF(Once, "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/src/DecodePool.cpp
+++ b/image/src/DecodePool.cpp
@@ -7,17 +7,16 @@
 
 #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"
@@ -37,112 +36,98 @@ 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)
+  static void Dispatch(RasterImage* aImage,
+                       Progress aProgress,
+                       const nsIntRect& aInvalidRect,
+                       uint32_t aFlags)
   {
-    nsCOMPtr<nsIRunnable> worker = new NotifyProgressWorker(aImage);
+    MOZ_ASSERT(aImage);
+
+    nsCOMPtr<nsIRunnable> worker =
+      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags);
     NS_DispatchToMainThread(worker);
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
-    ReentrantMonitorAutoEnter lock(mImage->mDecodingMonitor);
-
-    mImage->FinishedSomeDecoding(ShutdownReason::DONE);
-
+    mImage->NotifyProgress(mProgress, mInvalidRect, mFlags);
     return NS_OK;
   }
 
 private:
-  explicit NotifyProgressWorker(RasterImage* aImage)
+  NotifyProgressWorker(RasterImage* aImage, Progress aProgress,
+                       const nsIntRect& aInvalidRect, uint32_t aFlags)
     : 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(RasterImage* aImage)
-    : mImage(aImage)
-  { }
+  explicit DecodeWorker(Decoder* aDecoder)
+    : mDecoder(aDecoder)
+  {
+    MOZ_ASSERT(mDecoder);
+  }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     MOZ_ASSERT(!NS_IsMainThread());
-
-    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);
-    }
-
+    DecodePool::Singleton()->Decode(mDecoder);
     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<RasterImage> mImage;
+  nsRefPtr<Decoder> mDecoder;
 };
 
 #ifdef MOZ_NUWA_PROCESS
 
 class RIDThreadPoolListener MOZ_FINAL : public nsIThreadPoolListener
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -189,23 +174,16 @@ 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"));
@@ -253,166 +231,111 @@ DecodePool::Observe(nsISupports*, const 
   if (threadPool) {
     threadPool->Shutdown();
   }
 
   return NS_OK;
 }
 
 void
-DecodePool::RequestDecode(RasterImage* aImage)
+DecodePool::AsyncDecode(Decoder* aDecoder)
 {
-  MOZ_ASSERT(aImage->mDecoder);
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
+  MOZ_ASSERT(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);
+  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
 
   // 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::DecodeABitOf(RasterImage* aImage)
+DecodePool::SyncDecodeIfSmall(Decoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  aImage->mDecodingMonitor.AssertCurrentThreadIn();
+  MOZ_ASSERT(aDecoder);
 
-  // If the image is waiting for decode work to be notified, go ahead and do that.
-  if (aImage->mDecodeStatus == DecodeStatus::WORK_DONE) {
-    aImage->FinishedSomeDecoding();
+  if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) {
+    Decode(aDecoder);
+    return;
   }
 
-  DecodeSomeOfImage(aImage);
+  AsyncDecode(aDecoder);
+}
 
-  aImage->FinishedSomeDecoding();
+void
+DecodePool::SyncDecodeIfPossible(Decoder* aDecoder)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  Decode(aDecoder);
+}
+
+already_AddRefed<nsIEventTarget>
+DecodePool::GetEventTarget()
+{
+  MutexAutoLock threadPoolLock(mThreadPoolMutex);
+  nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mThreadPool);
+  return target.forget();
+}
 
-  // 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);
+already_AddRefed<nsIRunnable>
+DecodePool::CreateDecodeWorker(Decoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder);
+  nsCOMPtr<nsIRunnable> worker = new DecodeWorker(aDecoder);
+  return worker.forget();
+}
+
+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);
   }
 }
 
-/* static */ void
-DecodePool::StopDecoding(RasterImage* aImage)
+void
+DecodePool::NotifyProgress(Decoder* 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;
-    }
-  }
+  MOZ_ASSERT(aDecoder);
 
-  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;
+  if (!NS_IsMainThread()) {
+    NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
+                                   aDecoder->TakeProgress(),
+                                   aDecoder->TakeInvalidRect(),
+                                   aDecoder->GetDecodeFlags());
+    return;
   }
 
-  // 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;
+  aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
+                                       aDecoder->TakeInvalidRect(),
+                                       aDecoder->GetDecodeFlags());
+}
 
-  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();
-  }
+void
+DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder);
 
-  if (bytesToDecode == 0) {
-    bytesToDecode = aImage->mSourceData.Length() - aImage->mDecoder->BytesDecoded();
+  if (!NS_IsMainThread()) {
+    NotifyDecodeCompleteWorker::Dispatch(aDecoder);
+    return;
   }
 
-  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;
+  aDecoder->Finish();
+  aDecoder->GetImage()->FinalizeDecoder(aDecoder);
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/DecodePool.h
+++ b/image/src/DecodePool.h
@@ -20,110 +20,80 @@
 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 we use when decoding large images.
+ * 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.
  *
- * 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.
+ * 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.
  */
 class DecodePool : public nsIObserver
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   static DecodePool* Singleton();
 
-  /**
-   * Ask the DecodePool to asynchronously decode this image.
-   */
-  void RequestDecode(RasterImage* aImage);
+  /// Ask the DecodePool to run @aDecoder asynchronously and return immediately.
+  void AsyncDecode(Decoder* aDecoder);
 
   /**
-   * Decode aImage for a short amount of time, and post the remainder to the
-   * queue.
+   * 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.
    */
-  void DecodeABitOf(RasterImage* aImage);
+  void SyncDecodeIfSmall(Decoder* aDecoder);
 
   /**
-   * 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!
+   * Run aDecoder synchronously if at all possible. If it can't complete
+   * synchronously because the source data isn't complete, asynchronously decode
+   * the rest.
    */
-  static void StopDecoding(RasterImage* aImage);
+  void SyncDecodeIfPossible(Decoder* aDecoder);
 
   /**
-   * 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.
+   * 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.
    *
-   * @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.
+   * @return An nsIEventTarget interface to the thread pool.
    */
   already_AddRefed<nsIEventTarget> 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.
+   * 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.
    */
-  nsresult DecodeSomeOfImage(RasterImage* aImage,
-                             DecodeUntil aDecodeUntil = DecodeUntil::TIME,
-                             uint32_t bytesToDecode = 0);
+  already_AddRefed<nsIRunnable> CreateDecodeWorker(Decoder* aDecoder);
 
 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,20 +2,22 @@
 /* -*- 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 "GeckoProfiler.h"
+#include "nsProxyRelease.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
 
 namespace mozilla {
 namespace image {
@@ -24,18 +26,21 @@ 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)
@@ -68,17 +73,17 @@ Decoder::~Decoder()
 /*
  * Common implementation of the decoder interface.
  */
 
 void
 Decoder::Init()
 {
   // No re-initializing
-  NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
+  MOZ_ASSERT(!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();
@@ -108,16 +113,79 @@ 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(),
@@ -167,31 +235,32 @@ Decoder::Write(const char* aBuffer, uint
     mNeedsToFlushData = false;
   }
 
   // Finish telemetry.
   mDecodeTime += (TimeStamp::Now() - start);
 }
 
 void
-Decoder::Finish(ShutdownReason aReason)
+Decoder::Finish()
 {
   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, we need to sent teardown
-  // notifications.
-  if (!IsSizeDecode() && !mDecodeDone) {
+  // 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()) {
 
     // 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()) {
@@ -203,46 +272,53 @@ Decoder::Finish(ShutdownReason aReason)
                          NS_ConvertUTF8toUTF16(mImage->GetURIString()),
                          EmptyString(), 0, 0, nsIScriptError::errorFlag,
                          "Image", mImage->InnerWindowID()
                        ))) {
         consoleService->LogMessage(errorObject);
       }
     }
 
-    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 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.
       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 (mDecodeDone) {
+  if (HasSize()) {
+    SetSizeOnImage();
+  }
+
+  if (mDecodeDone && !IsSizeDecode()) {
     MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
-    mImage->DecodingComplete(mCurrentFrame.get());
+
+    // 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();
   }
 }
 
 void
 Decoder::FinishSharedDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -385,16 +461,22 @@ Decoder::InternalAddFrame(uint32_t aFram
 
   InsertOutcome outcome =
     SurfaceCache::Insert(frame, ImageKey(mImage.get()),
                          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");
@@ -426,19 +508,22 @@ Decoder::InternalAddFrame(uint32_t aFram
 }
 
 void
 Decoder::SetSizeOnImage()
 {
   MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
   MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
 
-  mImage->SetSize(mImageMetadata.GetWidth(),
-                  mImageMetadata.GetHeight(),
-                  mImageMetadata.GetOrientation());
+  nsresult rv = mImage->SetSize(mImageMetadata.GetWidth(),
+                                mImageMetadata.GetHeight(),
+                                mImageMetadata.GetOrientation());
+  if (NS_FAILED(rv)) {
+    PostResizeError();
+  }
 }
 
 /*
  * 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,68 +7,69 @@
 #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
+class Decoder : public IResumable
 {
 public:
 
   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);
 
   /**
-   * Writes data to the decoder.
-   *
+   * Decodes, reading all data currently available in the SourceBuffer. If more
    * If aBuffer is null and aCount is 0, Write() flushes any buffered data to
    * the decoder. Data is buffered if the decoder wasn't able to completely
    * decode it because it needed a new frame.  If it's necessary to flush data,
    * NeedsToFlushData() will return true.
    *
-   * @param aBuffer buffer containing the data to be written
-   * @param aCount the number of bytes to write
+   * data is needed, Decode() automatically ensures that it will be called again
+   * on a DecodePool thread when the data becomes available.
    *
    * Any errors are reported by setting the appropriate state on the decoder.
-   *
-   * Notifications Sent: TODO
    */
-  void Write(const char* aBuffer, uint32_t aCount);
+  nsresult Decode();
 
   /**
-   * Informs the decoder that all the data has been written.
-   *
-   * Notifications Sent: TODO
+   * Cleans up the decoder's state and notifies our image about success or
+   * failure. May only be called on the main thread.
    */
-  void Finish(ShutdownReason aReason);
+  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);
 
   /**
    * Informs the shared decoder that all the data has been written.
    * Should only be used if InitSharedDecoder was useed
    *
    * Notifications Sent: TODO
    */
   void FinishSharedDecoder();
@@ -93,30 +94,41 @@ 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)
   {
-    NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!");
+    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
     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
@@ -128,41 +140,80 @@ 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() { return HasDataError() || HasDecoderError(); }
-  bool HasDataError() { return mDataError; }
-  bool HasDecoderError() { return NS_FAILED(mFailCode); }
-  nsresult GetDecoderError() { return mFailCode; }
+  bool HasError() const { return HasDataError() || HasDecoderError(); }
+  bool HasDataError() const { return mDataError; }
+  bool HasDecoderError() const { return NS_FAILED(mFailCode); }
+  nsresult GetDecoderError() const { return mFailCode; }
   void PostResizeError() { PostDataError(); }
-  bool GetDecodeDone() const {
-    return mDecodeDone;
+
+  bool GetDecodeDone() const
+  {
+    return mDecodeDone || (mSizeDecode && HasSize()) || HasError() || mDataDone;
   }
 
+  /**
+   * 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
   };
 
@@ -308,21 +359,32 @@ protected:
                                 imgFrame* aPreviousFrame);
 
   RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
                                      const nsIntRect& aFrameRect,
                                      uint32_t aDecodeFlags,
                                      gfx::SurfaceFormat aFormat,
                                      uint8_t aPaletteDepth,
                                      imgFrame* aPreviousFrame);
+  /**
+   * Writes data to the decoder.
+   *
+   * @param aBuffer buffer containing the data to be written
+   * @param aCount the number of bytes to write
+   *
+   * Any errors are reported by setting the appropriate state on the decoder.
+   */
+  void Write(const char* aBuffer, uint32_t aCount);
+
   /*
    * Member variables.
    *
    */
   nsRefPtr<RasterImage> mImage;
+  Maybe<SourceBufferIterator> mIterator;
   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
@@ -330,18 +392,21 @@ 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,16 +285,21 @@ 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,16 +15,17 @@
 #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"
@@ -63,30 +64,16 @@ 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
@@ -269,61 +256,52 @@ 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
-  mDecodingMonitor("RasterImage Decoding Monitor"),
-  mDecoder(nullptr),
-  mDecodeStatus(DecodeStatus::INACTIVE),
+  mSourceBuffer(new SourceBuffer()),
   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),
-  mPendingError(false)
+  mWantFullDecode(false)
 {
   mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
 
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)->Add(0);
 }
 
 //******************************************************************************
 RasterImage::~RasterImage()
 {
-  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;
+  // Make sure our SourceBuffer is marked as complete. This will ensure that any
+  // outstanding decoders terminate.
+  if (!mSourceBuffer->IsComplete()) {
+    mSourceBuffer->Complete(NS_ERROR_ABORT);
   }
 
   // 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();
@@ -356,24 +334,21 @@ 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));
   }
 
-  // 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;
+  // Create the initial size decoder.
+  nsresult rv = Decode(DecodeStrategy::ASYNC, DECODE_FLAGS_DEFAULT,
+                       /* aDoSizeDecode = */ true);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_FAILURE;
   }
 
   // Mark us as initialized
   mInitialized = true;
 
   return NS_OK;
 }
 
@@ -400,21 +375,17 @@ 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
 
-    UpdateImageContainer();
-
-    if (mProgressTracker) {
-      mProgressTracker->SyncNotifyProgress(NoProgress, res.dirtyRect);
-    }
+    NotifyProgress(NoProgress, res.dirtyRect);
   }
 
   if (res.animationFinished) {
     mAnimationFinished = true;
     EvaluateAnimation();
   }
 }
 
@@ -540,20 +511,16 @@ 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.
@@ -873,25 +840,17 @@ RasterImage::UpdateImageContainer()
   }
 
   mImageContainer->SetCurrentImage(image);
 }
 
 size_t
 RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
-  // 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;
+  return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf);
 }
 
 size_t
 RasterImage::SizeOfDecoded(gfxMemoryLocation aLocation,
                            MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
   n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
@@ -936,102 +895,89 @@ RasterImage::OnAddedFrame(uint32_t aNewF
     NS_DispatchToMainThread(runnable);
     return;
   }
 
   MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
              mFrameCount < aNewFrameCount,
              "Frame count running backwards");
 
-  mFrameCount = aNewFrameCount;
+  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);
+    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();
+      // 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 (mPendingAnimation && ShouldAnimate()) {
+        StartAnimation();
+      }
+
+      if (aNewFrameCount > 1) {
+        mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea);
+      }
     }
   }
-
-  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::DecodingComplete(imgFrame* aFinalFrame)
+RasterImage::OnDecodingComplete()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return;
   }
 
-  // Flag that we're done decoding.
-  // XXX - these should probably be combined when we fix animated image
-  // discarding with bug 500402.
-  mDecoded = true;
+  // Flag that we've been decoded before.
   mHasBeenDecoded = true;
 
-  // 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();
-  }
-
+  // Let our FrameAnimator know not to expect any more frames.
   if (mAnim) {
     mAnim->SetDoneDecoding(true);
   }
 }
 
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
@@ -1108,26 +1054,17 @@ RasterImage::ResetAnimation()
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
   MOZ_ASSERT(mAnim, "Should have a FrameAnimator");
   mAnim->ResetAnimation();
 
-  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);
-  }
+  NotifyProgress(NoProgress, mAnim->GetFirstFrameRefreshArea());
 
   // Start the animation again. It may not have been running before, if
   // mAnimationFinished was true before entering this function.
   EvaluateAnimation();
 
   return NS_OK;
 }
 
@@ -1165,184 +1102,89 @@ RasterImage::SetLoopCount(int32_t aLoopC
 
 NS_IMETHODIMP_(nsIntRect)
 RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
   return aRect;
 }
 
 nsresult
-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()
+RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus,
+                                 bool aLastPart)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mError)
-    return NS_ERROR_FAILURE;
-
-  // If we've been called before, ignore. Otherwise, flag that we have everything
-  if (mHasSourceData)
-    return NS_OK;
+  // Record that we have all the data we're going to get now.
   mHasSourceData = 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);
-  }
+  // Let decoders know that there won't be any more data coming.
+  mSourceBuffer->Complete(aStatus);
 
-  {
-    ReentrantMonitorAutoEnter lock(mDecodingMonitor);
-
-    // Free up any extra space in the backing buffer
-    mSourceData.Compact();
+  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);
   }
 
-  // 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()));
+  // 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;
   }
 
-  return NS_OK;
-}
-
-nsresult
-RasterImage::OnImageDataComplete(nsIRequest*, nsISupports*, nsresult aStatus, bool aLastPart)
-{
-  nsresult finalStatus = DoImageDataComplete();
+  // If loading failed, report an error.
+  if (NS_FAILED(finalStatus)) {
+    DoError();
+  }
 
-  // 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;
 
-  // WriteToRasterImage always consumes everything it gets
-  // if it doesn't run out of memory
+  // WriteToSourceBuffer always consumes everything it gets if it doesn't run
+  // out of memory.
   uint32_t bytesRead;
-  rv = aInStr->ReadSegments(WriteToRasterImage, this, aCount, &bytesRead);
+  rv = aInStr->ReadSegments(WriteToSourceBuffer, this, aCount, &bytesRead);
 
   NS_ABORT_IF_FALSE(bytesRead == aCount || HasError() || NS_FAILED(rv),
-    "WriteToRasterImage should consume everything if ReadSegments succeeds or "
+    "WriteToSourceBuffer 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 sizeHint)
+RasterImage::SetSourceSizeHint(uint32_t aSizeHint)
 {
-  if (sizeHint && StoringSourceData())
-    return mSourceData.SetCapacity(sizeHint) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
-  return NS_OK;
+  return mSourceBuffer->ExpectLength(aSizeHint);
 }
 
 /********* 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;
@@ -1388,119 +1230,102 @@ 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");
-
-  // 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!");
+  MOZ_ASSERT(!mAnim, "Asked to discard for animated image");
 
   // Delete all the decoded frames.
   SurfaceCache::RemoveImage(ImageKey(this));
 
-  // Flag that we no longer have decoded frames for this image
-  mDecoded = false;
-  mFrameCount = 0;
-
-  // Notify that we discarded
+  // 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
 }
 
-// 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)
+// Sets up a decoder for this image.
+already_AddRefed<Decoder>
+RasterImage::CreateDecoder(bool aDoSizeDecode, uint32_t aFlags)
 {
-  // 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) {
-    NS_ABORT_IF_FALSE(mHasSize, "Must do a size decode before 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!");
   }
 
-  // Figure out which decoder we want
+  // Figure out which decoder we want.
   eDecoderType type = GetDecoderType(mSourceDataMimeType.get());
-  CONTAINER_ENSURE_TRUE(type != eDecoderType_unknown, NS_IMAGELIB_ERROR_NO_DECODER);
+  if (type == eDecoderType_unknown) {
+    return nullptr;
+  }
 
   // Instantiate the appropriate decoder.
+  nsRefPtr<Decoder> decoder;
   switch (type) {
     case eDecoderType_png:
-      mDecoder = new nsPNGDecoder(this);
+      decoder = new nsPNGDecoder(this);
       break;
     case eDecoderType_gif:
-      mDecoder = new nsGIFDecoder2(this);
+      decoder = 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.
-      mDecoder = new nsJPEGDecoder(this,
-                                   mHasBeenDecoded ? Decoder::SEQUENTIAL :
-                                                     Decoder::PROGRESSIVE);
+      decoder = new nsJPEGDecoder(this,
+                                  mHasBeenDecoded ? Decoder::SEQUENTIAL :
+                                                    Decoder::PROGRESSIVE);
       break;
     case eDecoderType_bmp:
-      mDecoder = new nsBMPDecoder(this);
+      decoder = new nsBMPDecoder(this);
       break;
     case eDecoderType_ico:
-      mDecoder = new nsICODecoder(this);
+      decoder = new nsICODecoder(this);
       break;
     case eDecoderType_icon:
-      mDecoder = new nsIconDecoder(this);
+      decoder = new nsIconDecoder(this);
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown decoder type");
   }
 
-  // Initialize the decoder
-  mDecoder->SetSizeDecode(aDoSizeDecode);
-  mDecoder->SetDecodeFlags(mFrameDecodeFlags);
+  MOZ_ASSERT(decoder, "Should have a decoder now");
+
+  // Initialize the decoder.
+  decoder->SetSizeDecode(aDoSizeDecode);
+  decoder->SetSendPartialInvalidations(!mHasBeenDecoded);
+  decoder->SetImageIsTransient(mTransient);
+  decoder->SetDecodeFlags(DecodeFlags(aFlags));
   if (!aDoSizeDecode) {
     // We already have the size; tell the decoder so it can preallocate a
     // frame.  By default, we create an ARGB frame with no offset. If decoders
     // need a different type, they need to ask for it themselves.
-    mDecoder->SetSize(mSize, mOrientation);
-    mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
-                           SurfaceFormat::B8G8R8A8);
-    mDecoder->AllocateFrame();
+    decoder->SetSize(mSize, mOrientation);
+    decoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
+                          SurfaceFormat::B8G8R8A8);
+    decoder->AllocateFrame();
   }
-  mDecoder->SetSendPartialInvalidations(!mHasBeenDecoded);
-  mDecoder->Init();
-  CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
+  decoder->SetIterator(mSourceBuffer->Iterator());
+  decoder->Init();
+
+  if (NS_FAILED(decoder->GetDecoderError())) {
+    return nullptr;
+  }
 
   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
@@ -1508,362 +1333,170 @@ RasterImage::InitDecoder(bool aDoSizeDec
       if (sMaxDecodeCount > 0) {
         Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Subtract(sMaxDecodeCount);
       }
       sMaxDecodeCount = mDecodeCount;
       Telemetry::GetHistogramById(Telemetry::IMAGE_MAX_DECODE_COUNT)->Add(sMaxDecodeCount);
     }
   }
 
-  return NS_OK;
+  return decoder.forget();
 }
 
-// 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
+void
 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) {
-      return SyncDecode();
+      Decode(DecodeStrategy::SYNC_IF_POSSIBLE, aFlags);
+      return;
     }
-    return StartDecoding();
+
+    // 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;
   }
 
   // We can't sync notify, so do an async decode.
-  return RequestDecodeCore(ASYNCHRONOUS);
+  Decode(DecodeStrategy::ASYNC, aFlags);
 }
 
 //******************************************************************************
 /* void requestDecode() */
 NS_IMETHODIMP
 RasterImage::RequestDecode()
 {
-  return RequestDecodeCore(SYNCHRONOUS_NOTIFY);
+  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;
 }
 
 /* void startDecode() */
 NS_IMETHODIMP
 RasterImage::StartDecoding()
 {
   if (!NS_IsMainThread()) {
     return NS_DispatchToMainThread(
       NS_NewRunnableMethod(this, &RasterImage::StartDecoding));
   }
-  // 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);
+
+  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;
 }
 
 bool
 RasterImage::IsDecoded()
 {
-  return mDecoded || mError;
+  // XXX(seth): We need to get rid of this; it's not reliable.
+  return mHasBeenDecoded || mError;
 }
 
 NS_IMETHODIMP
-RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
+RasterImage::Decode(DecodeStrategy aStrategy,
+                    uint32_t aFlags,
+                    bool aDoSizeDecode /* = false */)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aDoSizeDecode || NS_IsMainThread());
+
+  if (mError) {
+    return NS_ERROR_FAILURE;
+  }
 
-  nsresult rv;
+  // If we don't have a size yet, we can't do any other decoding.
+  if (!mHasSize && !aDoSizeDecode) {
+    mWantFullDecode = true;
+    return NS_OK;
+  }
 
-  if (mError)
+  // Create a decoder.
+  nsRefPtr<Decoder> decoder = CreateDecoder(aDoSizeDecode, aFlags);
+  if (!decoder) {
     return NS_ERROR_FAILURE;
+  }
 
-  // If we're already decoded, there's nothing to do.
-  if (mDecoded)
-    return NS_OK;
+  // 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 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 (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 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;
+    if (aStrategy == DecodeStrategy::SYNC_IF_POSSIBLE) {
+      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfPossible",
+        js::ProfileEntry::Category::GRAPHICS, "%s", GetURIString().get());
+      DecodePool::Singleton()->SyncDecodeIfPossible(decoder);
       return NS_OK;
     }
   }
 
-  // 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);
-  }
-
+  // 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);
   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'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 ||
+  // 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 ||
       !(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) {
@@ -1897,22 +1530,19 @@ RasterImage::CanScale(GraphicsFilter aFi
   gfxFloat minFactor = gfxPrefs::ImageHQDownscalingMinFactor() / 1000.0;
   return (scale.width < minFactor || scale.height < minFactor);
 #endif
 }
 
 void
 RasterImage::NotifyNewScaledFrame()
 {
-  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);
-  }
+  // 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));
 }
 
 void
 RasterImage::RequestScale(imgFrame* aFrame,
                           uint32_t aFlags,
                           const nsIntSize& aSize)
 {
   // We don't scale frames which aren't fully decoded.
@@ -2021,46 +1651,41 @@ 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 (mDecoded && !mDrawStartTime.IsNull()) {
+  if (shouldRecordTelemetry) {
       TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
-      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds()));
-      // clear the value of mDrawStartTime
+      Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY,
+                            int32_t(drawLatency.ToMicroseconds()));
       mDrawStartTime = TimeStamp();
   }
 
   return NS_OK;
 }
 
 //******************************************************************************
 /* void lockImage() */
@@ -2102,141 +1727,59 @@ 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)
 {
-  if (!aImage->mPendingError) {
-    aImage->mPendingError = true;
-    nsRefPtr<HandleErrorWorker> worker = new HandleErrorWorker(aImage);
-    NS_DispatchToMainThread(worker);
-  }
+  nsRefPtr<HandleErrorWorker> worker = new HandleErrorWorker(aImage);
+  NS_DispatchToMainThread(worker);
 }
 
 RasterImage::HandleErrorWorker::HandleErrorWorker(RasterImage* aImage)
   : mImage(aImage)
 {
   MOZ_ASSERT(mImage, "Should have image");
 }
 
@@ -2247,31 +1790,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::WriteToRasterImage(nsIInputStream* /* unused */,
-                                void*          aClosure,
-                                const char*    aFromRawSegment,
-                                uint32_t       /* unused */,
-                                uint32_t       aCount,
-                                uint32_t*      aWriteCount)
+RasterImage::WriteToSourceBuffer(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->AddSourceData(aFromRawSegment, aCount);
+  nsresult rv = image->mSourceBuffer->Append(aFromRawSegment, aCount);
   if (rv == NS_ERROR_OUT_OF_MEMORY) {
     image->DoError();
     return rv;
   }
 
   // We wrote everything we got
   *aWriteCount = aCount;
 
@@ -2293,124 +1836,40 @@ RasterImage::GetFramesNotified(uint32_t 
   NS_ENSURE_ARG_POINTER(aFramesNotified);
 
   *aFramesNotified = mFramesNotified;
 
   return NS_OK;
 }
 #endif
 
-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 */)
+void
+RasterImage::NotifyProgress(Progress aProgress,
+                            const nsIntRect& aInvalidRect /* = nsIntRect() */,
+                            uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mDecodingMonitor.AssertCurrentThreadIn();
-
-  // Ensure that, if the decoder is the last reference to the image, we don't
-  // destroy it by destroying the decoder.
+  // Ensure that we stay alive long enough to finish notifying.
   nsRefPtr<RasterImage> image(this);
 
-  bool done = false;
-  bool wasSize = false;
-  bool wasDefaultFlags = false;
-  nsIntRect invalidRect;
-  nsresult rv = NS_OK;
+  bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT;
   Progress progress = aProgress;
-
-  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();
-    }
-  }
+  nsIntRect invalidRect = aInvalidRect;
 
   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
-    // FinishedSomeDecoding on the stack.
+    // NotifyProgress 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);
 
@@ -2425,18 +1884,68 @@ RasterImage::FinishedSomeDecoding(Shutdo
       // notifications for them next.
       progress = image->mProgressTracker->Difference(mNotifyProgress);
       mNotifyProgress = NoProgress;
 
       invalidRect = mNotifyInvalidRect;
       mNotifyInvalidRect = nsIntRect();
     }
   }
+}
 
-  return RequestDecodeIfNeeded(rv, aReason, done, wasSize);
+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();
+  }
 }
 
 already_AddRefed<imgIContainer>
 RasterImage::Unwrap()
 {
   nsCOMPtr<imgIContainer> self(this);
   return self.forget();
 }
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -24,17 +24,16 @@
 #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
 
@@ -127,16 +126,23 @@ 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
 {
@@ -157,20 +163,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 WriteToRasterImage(nsIInputStream* aIn, void* aClosure,
-                                      const char* aFromRawSegment,
-                                      uint32_t aToOffset, uint32_t aCount,
-                                      uint32_t* aWriteCount);
+  static NS_METHOD WriteToSourceBuffer(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;
 
@@ -191,34 +197,46 @@ 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 DecodingComplete(imgFrame* aFinalFrame);
+  /// 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);
 
 
   //////////////////////////////////////////////////////////////////////////////
   // 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,
@@ -232,17 +250,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 sizeHint);
+  nsresult SetSourceSizeHint(uint32_t aSizeHint);
 
   /* Provide a hint for the requested resolution of the resulting image. */
   void SetRequestedResolution(const nsIntSize requestedResolution) {
     mRequestedResolution = requestedResolution;
   }
 
   nsIntSize GetRequestedResolution() {
     return mRequestedResolution;
@@ -262,24 +280,16 @@ 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,
@@ -299,54 +309,44 @@ 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;
 
-  //! 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).
+  /// If this image is animated, a FrameAnimator which manages its animation.
   UniquePtr<FrameAnimator> mAnim;
 
   // Image locking.
   uint32_t                   mLockCount;
 
   // Source data members
   nsCString                  mSourceDataMimeType;
 
@@ -365,82 +365,53 @@ private: // data
 
   // If not cached in mImageContainer, this might have our image container
   WeakPtr<layers::ImageContainer> mImageContainerCache;
 
 #ifdef DEBUG
   uint32_t                       mFramesNotified;
 #endif
 
-  // 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 source data for this image.
+  nsRefPtr<SourceBuffer>     mSourceBuffer;
 
   // 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?
-
-  // Do we have the frames in decoded form?
-  bool                       mDecoded:1;
-  bool                       mHasBeenDecoded:1;
+  bool                       mHasBeenDecoded:1; // Decoded at least once?
 
   // 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);
 
@@ -471,17 +442,16 @@ 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;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3780,19 +3780,16 @@ 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