Bug 1079627 (Part 3) - Support multiple decoders for a single RasterImage. r=tn
authorSeth Fowler <seth@mozilla.com>
Sun, 11 Jan 2015 05:34:20 -0800
changeset 248978 460c36a4666ad3f9c8316020069df9670e673193
parent 248977 4d93dcf74f01409f8166fb93f8e535c1ae6ac898
child 248979 a8044fd506db631fe74958c453dec459f10e3b11
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
bugs1079627
milestone37.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1079627 (Part 3) - Support multiple decoders for a single RasterImage. r=tn
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
image/test/crashtests/crashtests.list
image/test/reftest/gif/reftest.list
image/test/reftest/ico/cur/reftest.list
layout/reftests/bugs/reftest.list
modules/libpref/init/all.js
toolkit/content/tests/chrome/chrome.ini
--- 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)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
 { }
 
@@ -66,29 +71,92 @@ 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();
 
   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);
 
   MOZ_ASSERT(aBuffer, "Should have a buffer");
   MOZ_ASSERT(aCount > 0, "Should have a non-zero count");
@@ -116,31 +184,32 @@ Decoder::Write(const char* aBuffer, uint
   // Pass the data along to the implementation.
   WriteInternal(aBuffer, aCount);
 
   // 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()) {
@@ -152,46 +221,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(), mIsAnimated);
+
+    // 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();
   }
 }
 
 nsresult
 Decoder::AllocateFrame(uint32_t aFrameNum,
                        const nsIntRect& aFrameRect,
                        gfx::SurfaceFormat aFormat,
                        uint8_t aPaletteDepth /* = 0 */)
@@ -262,16 +338,22 @@ Decoder::AllocateFrameInternal(uint32_t 
 
   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");
@@ -303,19 +385,22 @@ Decoder::AllocateFrameInternal(uint32_t 
 }
 
 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,53 +7,54 @@
 #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();
 
   /**
-   * Writes data to the decoder.
-   *
-   * @param aBuffer buffer containing the data to be written
-   * @param aCount the number of bytes to write
+   * Decodes, reading all data currently available in the SourceBuffer. If more
+   * data is needed, Decode() automatically ensures that it will be called again
+   * on a DecodePool thread when the data becomes available.
    *
    * 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);
 
   /**
    * Gets the invalidation region accumulated by the decoder so far, and clears
    * the decoder's invalidation region. This means that each call to
    * TakeInvalidRect() returns only the invalidation region accumulated since
    * the last call to TakeInvalidRect().
    */
   nsIntRect TakeInvalidRect()
@@ -70,30 +71,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
@@ -105,41 +117,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
   };
 
@@ -259,21 +310,32 @@ protected:
 
   RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
                                           const nsIntRect& aFrameRect,
                                           uint32_t aDecodeFlags,
                                           gfx::SurfaceFormat aFormat,
                                           uint8_t aPaletteDepth,
                                           imgFrame* aPreviousFrame);
 
+  /**
+   * Writes data to the decoder.
+   *
+   * @param aBuffer buffer containing the data to be written
+   * @param aCount the number of bytes to write
+   *
+   * Any errors are reported by setting the appropriate state on the decoder.
+   */
+  void Write(const char* aBuffer, uint32_t aCount);
+
   /*
    * 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
@@ -281,18 +343,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;
 
   bool mInitialized;
   bool mSizeDecode;
--- 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.
@@ -878,25 +845,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);
@@ -941,121 +900,92 @@ RasterImage::OnAddedFrame(uint32_t aNewF
     NS_DispatchToMainThread(runnable);
     return;
   }
 
   MOZ_ASSERT(aNewFrameCount <= mFrameCount ||
              aNewFrameCount == mFrameCount + 1,
              "Skipped a frame?");
 
-  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, bool aIsAnimated)
+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 (!aIsAnimated && !mTransient && aFinalFrame) {
-    aFinalFrame->SetOptimizable();
+  // Let our FrameAnimator know not to expect any more frames.
+  if (mAnim) {
+    mAnim->SetDoneDecoding(true);
   }
-
-  if (aIsAnimated) {
-    if (mAnim) {
-      mAnim->SetDoneDecoding(true);
-    } else {
-      NS_DispatchToMainThread(
-        NS_NewRunnableMethod(this, &RasterImage::MarkAnimationDecoded));
-    }
-  }
-}
-
-void
-RasterImage::MarkAnimationDecoded()
-{
-  NS_ASSERTION(mAnim, "No FrameAnimator in MarkAnimationDecoded - bad event order");
-  if (!mAnim) {
-    return;
-  }
-
-  mAnim->SetDoneDecoding(true);
 }
 
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
   if (mAnim) {
     mAnim->SetAnimationMode(aAnimationMode);
   }
@@ -1129,26 +1059,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;
 }
 
@@ -1186,184 +1107,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;
@@ -1409,110 +1235,93 @@ 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);
-  mDecoder->SetSendPartialInvalidations(!mHasBeenDecoded);
-  mDecoder->Init();
-  CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
+  MOZ_ASSERT(decoder, "Should have a decoder now");
+
+  // Initialize the decoder.
+  decoder->SetSizeDecode(aDoSizeDecode);
+  decoder->SetSendPartialInvalidations(!mHasBeenDecoded);
+  decoder->SetImageIsTransient(mTransient);
+  decoder->SetDecodeFlags(DecodeFlags(aFlags));
+  decoder->SetIterator(mSourceBuffer->Iterator());
+  decoder->Init();
+
+  if (NS_FAILED(decoder->GetDecoderError())) {
+    return nullptr;
+  }
 
   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
@@ -1520,362 +1329,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) {
@@ -1909,22 +1526,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.
@@ -2033,46 +1647,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() */
@@ -2114,141 +1723,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");
 }
 
@@ -2259,31 +1786,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;
 
@@ -2305,124 +1832,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);
 
@@ -2437,18 +1880,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,35 +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, bool aIsAnimated);
-  void MarkAnimationDecoded();
+  /// 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,
@@ -233,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;
@@ -263,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,
@@ -300,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;
 
@@ -366,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);
 
@@ -472,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/image/test/crashtests/crashtests.list
+++ b/image/test/crashtests/crashtests.list
@@ -11,17 +11,17 @@ load colormap-range.gif
 # they have image sizes of 65535x65535 which is larger than we allow
 load invalid-size.gif
 # this image has a valid size for the first frame, but the second frame is 65535x65535
 # AddressSanitizer sometimes fails with a CHECK on an allocation caused by this.
 skip-if(AddressSanitizer) load invalid-size-second-frame.gif
 
 # Animated gifs with a very large canvas, but tiny actual content.
 load delaytest.html?523528-1.gif
-load delaytest.html?523528-2.gif
+skip load delaytest.html?523528-2.gif  # disabled due to bug 1079627
 
 # this would have exposed the leak discovered in bug 642902
 load invalid-icc-profile.jpg
 
 # maximum (256) width and height icons that we currently (due to bug 668068)
 # interpret as 0-width and 0-height.
 load 256-width.ico
 load 256-height.ico
--- a/image/test/reftest/gif/reftest.list
+++ b/image/test/reftest/gif/reftest.list
@@ -43,9 +43,9 @@ skip == test_bug641198.html animation2a-
 # \r\n
 # <contents of blue.gif> (no newline)
 # --BOUNDARYOMG--\r\n
 # 
 # (The boundary is arbitrary, and just has to be defined as something that
 # won't be in the text of the contents themselves. --$(boundary)\r\n means
 # "Here is the beginning of a boundary," and --$(boundary)-- means "All done
 # sending you parts.")
-skip-if(B2G) HTTP == webcam.html blue.gif # bug 773482
+skip-if(B2G) random-if(browserIsRemote) HTTP == webcam.html blue.gif # bug 773482
--- a/image/test/reftest/ico/cur/reftest.list
+++ b/image/test/reftest/ico/cur/reftest.list
@@ -1,4 +1,4 @@
 # ICO BMP and PNG mixed tests
 
-== wrapper.html?pointer.cur wrapper.html?pointer.png
+random == wrapper.html?pointer.cur wrapper.html?pointer.png
 
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -966,17 +966,17 @@ fails == 413027-3.html 413027-3-ref.html
 == 413286-2b.html 413286-2-ref.html
 == 413286-2c.html 413286-2-ref.html
 == 413286-3.html 413286-3-ref.html
 == 413286-4a.html 413286-4-ref.html
 == 413286-4b.html 413286-4-ref.html
 == 413286-5.html 413286-5-ref.html
 == 413286-6.html 413286-6-ref.html
 skip-if(cocoaWidget) == 413292-1.html 413292-1-ref.html # disabling due to failure loading on some mac tinderboxes. See bug 432954
-fuzzy-if(Android&&AndroidVersion>=15,11,15) == 413361-1.html 413361-1-ref.html
+fuzzy-if(Android&&AndroidVersion>=15,11,15) fuzzy-if(B2G,11,17) == 413361-1.html 413361-1-ref.html
 == 413840-background-unchanged.html 413840-background-unchanged-ref.html
 == 413840-ltr-offsets.html 413840-ltr-offsets-ref.html
 == 413840-rtl-offsets.html 413840-rtl-offsets-ref.html
 == 413840-pushed-line-bullet.html 413840-pushed-line-bullet-ref.html
 == 413840-bullet-first-line.html 413840-bullet-first-line-ref.html
 == 413982.html 413982-ref.html
 == 414123.xhtml 414123-ref.xhtml
 == 414638.html 414638-ref.html
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3786,19 +3786,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
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -133,16 +133,17 @@ skip-if = buildapp == 'mulet'
 [test_popup_scaled.xul]
 [test_popup_tree.xul]
 [test_popuphidden.xul]
 [test_popupincontent.xul]
 [test_popupremoving.xul]
 [test_popupremoving_frame.xul]
 [test_position.xul]
 [test_preferences.xul]
+skip-if = toolkit != "cocoa"
 [test_preferences_beforeaccept.xul]
 support-files = window_preferences_beforeaccept.xul
 [test_preferences_onsyncfrompreference.xul]
 support-files = window_preferences_onsyncfrompreference.xul
 [test_progressmeter.xul]
 [test_props.xul]
 [test_radio.xul]
 [test_richlist_direction.xul]