backout 275cd395f9fa and 9e4b22851976 bug 716140 for breaking linux tp on a CLOSED TREE
authorTrevor Saunders <trev.saunders@gmail.com>
Sun, 24 Mar 2013 15:43:20 -0400
changeset 136465 aab4a115f06c7ef349c357c5e18c6868721ccc97
parent 136464 3fd95a7e87de8f346ed111b536cb91ad5e15f7f1
child 136466 a3aef14dc53a7d65bcfa9d72a429a9a32a0af3cc
push id2452
push userlsblakk@mozilla.com
push dateMon, 13 May 2013 16:59:38 +0000
treeherdermozilla-beta@d4b152d29d8d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs716140
milestone22.0a1
backs out275cd395f9fadaa42da633e20780d39193d817db
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
backout 275cd395f9fa and 9e4b22851976 bug 716140 for breaking linux tp on a CLOSED TREE
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/imgStatusTracker.cpp
image/src/imgStatusTracker.h
modules/libpref/src/init/all.js
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -71,17 +71,17 @@ public:
    * to consumers.
    *
    * This can be called any time when we're midway through decoding a frame,
    * and must be called after finishing a frame (before starting a new one).
    */
   void FlushInvalidations();
 
   // We're not COM-y, so we don't get refcounts by default
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
+  NS_INLINE_DECL_REFCOUNTING(Decoder)
 
   /*
    * 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().
@@ -92,21 +92,16 @@ public:
     mSizeDecode = aSizeDecode;
   }
 
   void SetSynchronous(bool aSynchronous)
   {
     mSynchronous = aSynchronous;
   }
 
-  bool IsSynchronous() const
-  {
-    return mSynchronous;
-  }
-
   void SetObserver(imgDecoderObserver* aObserver)
   {
     MOZ_ASSERT(aObserver);
     mObserver = aObserver;
   }
 
   // The number of frames we have, including anything in-progress. Thus, this
   // is only 0 if we haven't begun any frames.
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -11,36 +11,30 @@
 #include "Decoder.h"
 #include "RasterImage.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIMultiPartChannel.h"
 #include "nsAutoPtr.h"
 #include "nsStringStream.h"
 #include "prenv.h"
-#include "prsystem.h"
 #include "ImageContainer.h"
 #include "Layers.h"
 #include "nsPresContext.h"
-#include "nsThread.h"
-#include "nsIThreadPool.h"
-#include "nsXPCOMCIDInternal.h"
-#include "nsIObserverService.h"
 
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
 #include "nsBMPDecoder.h"
 #include "nsICODecoder.h"
 #include "nsIconDecoder.h"
 #include "nsWBMPDecoder.h"
 
 #include "gfxContext.h"
 
-#include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StandardInteger.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/gfx/Scale.h"
 
 #include "sampler.h"
@@ -71,38 +65,32 @@ GetCompressedImageAccountingLog()
 // Tweakable progressive decoding parameters.  These are initialized to 0 here
 // because otherwise, we have to initialize them in a static initializer, which
 // makes us slower to start up.
 static uint32_t gDecodeBytesAtATime = 0;
 static uint32_t gMaxMSBeforeYield = 0;
 static bool gHQDownscaling = false;
 // This is interpreted as a floating-point value / 1000
 static uint32_t gHQDownscalingMinFactor = 1000;
-static bool gMultithreadedDecoding = true;
-static int32_t gDecodingThreadLimit = -1;
 
 // The maximum number of times any one RasterImage was decoded.  This is only
 // used for statistics.
 static int32_t sMaxDecodeCount = 0;
 
 static void
 InitPrefCaches()
 {
   Preferences::AddUintVarCache(&gDecodeBytesAtATime,
                                "image.mem.decode_bytes_at_a_time", 200000);
   Preferences::AddUintVarCache(&gMaxMSBeforeYield,
                                "image.mem.max_ms_before_yield", 400);
   Preferences::AddBoolVarCache(&gHQDownscaling,
                                "image.high_quality_downscaling.enabled", false);
   Preferences::AddUintVarCache(&gHQDownscalingMinFactor,
                                "image.high_quality_downscaling.min_factor", 1000);
-  Preferences::AddBoolVarCache(&gMultithreadedDecoding,
-                               "image.multithreaded_decoding.enabled", true);
-  Preferences::AddIntVarCache(&gDecodingThreadLimit,
-                              "image.multithreaded_decoding.limit", -1);
 }
 
 /* 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
  * failures. As such, we don't want to clutter up debug consoles
  * with spurious messages about NS_ENSURE_SUCCESS failures.
@@ -356,50 +344,49 @@ public:
 
 private:
   nsAutoPtr<ScaleRequest> mScaleRequest;
 };
 
 namespace mozilla {
 namespace image {
 
-/* static */ StaticRefPtr<RasterImage::DecodePool> RasterImage::DecodePool::sSingleton;
+/* static */ StaticRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
 static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
 
 #ifndef DEBUG
-NS_IMPL_THREADSAFE_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties)
+NS_IMPL_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties)
 #else
-NS_IMPL_THREADSAFE_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
-                              imgIContainerDebug)
+NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
+                   imgIContainerDebug)
 #endif
 
 //******************************************************************************
 RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
                          nsIURI* aURI /* = nullptr */) :
   ImageResource(aStatusTracker, aURI), // invoke superclass's constructor
   mSize(0,0),
   mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
   mAnim(nullptr),
   mLoopCount(-1),
   mLockCount(0),
+  mDecoder(nullptr),
+  mBytesDecoded(0),
   mDecodeCount(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
-  mDecodingMutex("RasterImage"),
-  mDecoder(nullptr),
-  mBytesDecoded(0),
-  mInDecoder(false),
   mHasSize(false),
   mDecodeOnDraw(false),
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
+  mInDecoder(false),
   mAnimationFinished(false),
   mFinishing(false),
   mInUpdateImageContainer(false),
   mWantFullDecode(false),
   mScaleRequest(nullptr)
 {
   // Set up the discard tracker node.
   mDiscardTrackerNode.img = this;
@@ -426,18 +413,17 @@ RasterImage::~RasterImage()
              num_discardable_containers,
              total_source_bytes,
              discardable_source_bytes));
   }
 
   if (mDecoder) {
     // Kill off our decode request, if it's pending.  (If not, this call is
     // harmless.)
-    MutexAutoLock lock(mDecodingMutex);
-    DecodePool::StopDecoding(this);
+    DecodeWorker::Singleton()->StopDecoding(this);
     mDecoder = nullptr;
 
     // Unlock the last frame (if we have any). Our invariant is that, while we
     // have a decoder open, the last frame is always locked.
     // This would be done in ShutdownDecoder, but since mDecoder is non-null,
     // we didn't call ShutdownDecoder and we need to do it manually.
     if (mFrames.Length() > 0) {
       imgFrame *curframe = mFrames.ElementAt(mFrames.Length() - 1);
@@ -461,17 +447,17 @@ RasterImage::~RasterImage()
 
 /* static */ void
 RasterImage::Initialize()
 {
   InitPrefCaches();
 
   // Create our singletons now, so we don't have to worry about what thread
   // they're created on.
-  DecodePool::Singleton();
+  DecodeWorker::Singleton();
 }
 
 nsresult
 RasterImage::Init(const char* aMimeType,
                   uint32_t aFlags)
 {
   // We don't support re-initialization
   if (mInitialized)
@@ -788,17 +774,17 @@ RasterImage::GetHeight(int32_t *aHeight)
   if (mError) {
     *aHeight = 0;
     return NS_ERROR_FAILURE;
   }
 
   *aHeight = mSize.height;
   return NS_OK;
 }
-
+ 
 //******************************************************************************
 /* [noscript] readonly attribute nsSize intrinsicSize; */
 NS_IMETHODIMP
 RasterImage::GetIntrinsicSize(nsSize* aSize)
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
@@ -1386,18 +1372,16 @@ RasterImage::ApplyDecodeFlags(uint32_t a
 
   mFrameDecodeFlags = aNewFlags & DECODE_FLAGS_MASK;
   return true;
 }
 
 nsresult
 RasterImage::SetSize(int32_t aWidth, int32_t aHeight)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   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;
 
@@ -1543,18 +1527,16 @@ RasterImage::SetFrameAsNonPremult(uint32
   frame->SetAsNonPremult(aIsNonPremult);
 
   return NS_OK;
 }
 
 nsresult
 RasterImage::DecodingComplete()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   if (mError)
     return NS_ERROR_FAILURE;
 
   // Flag that we're done decoding.
   // XXX - these should probably be combined when we fix animated image
   // discarding with bug 500402.
   mDecoded = true;
   mHasBeenDecoded = true;
@@ -1673,18 +1655,16 @@ RasterImage::SetLoopCount(int32_t aLoopC
   //  1  one loop, two iterations
   //  ...
   mLoopCount = aLoopCount;
 }
 
 nsresult
 RasterImage::AddSourceData(const char *aBuffer, uint32_t aCount)
 {
-  MutexAutoLock lock(mDecodingMutex);
-
   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 "
@@ -1736,31 +1716,30 @@ RasterImage::AddSourceData(const char *a
 
     // We're not storing source data, so this data is probably coming straight
     // from the network. In this case, we want to display data as soon as we
     // get it, so we want to flush invalidations after every write.
     nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
     mInDecoder = true;
     mDecoder->FlushInvalidations();
     mInDecoder = false;
-
-    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 there's a decoder open, that means we want to do more decoding.
+    // Wake up the worker.
     if (mDecoder) {
-      DecodePool::Singleton()->RequestDecode(this);
+      DecodeWorker::Singleton()->RequestDecode(this);
     }
   }
 
   // Statistics
   total_source_bytes += aCount;
   if (mDiscardable)
     discardable_source_bytes += aCount;
   PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
@@ -1794,48 +1773,45 @@ get_header_str (char *buf, char *data, s
   }
 
   buf[i * 2] = 0;
 }
 
 nsresult
 RasterImage::DoImageDataComplete()
 {
-  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;
   mHasSourceData = true;
 
+  // This call should come straight from necko - no reentrancy allowed
+  NS_ABORT_IF_FALSE(!mInDecoder, "Re-entrant call to AddSourceData!");
+
   // 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);
+    nsresult rv = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  {
-    MutexAutoLock lock(mDecodingMutex);
-
-    // If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
-    // finish decoding this image.
-    if (mDecoder) {
-      DecodePool::Singleton()->RequestDecode(this);
-    }
-
-    // Free up any extra space in the backing buffer
-    mSourceData.Compact();
+  // If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
+  // finish decoding this image.
+  if (mDecoder) {
+    DecodeWorker::Singleton()->RequestDecode(this);
   }
 
+  // Free up any extra space in the backing buffer
+  mSourceData.Compact();
+
   // 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,
@@ -1864,20 +1840,17 @@ RasterImage::OnImageDataComplete(nsIRequ
 
   if (mDecodeRequest) {
     mDecodeRequest->mStatusTracker->GetDecoderObserver()->OnStopRequest(aLastPart, finalStatus);
   } else {
     mStatusTracker->GetDecoderObserver()->OnStopRequest(aLastPart, finalStatus);
   }
 
   // We just recorded OnStopRequest; we need to inform our listeners.
-  {
-    MutexAutoLock lock(mDecodingMutex);
-    FinishedSomeDecoding();
-  }
+  FinishedSomeDecoding();
 
   return finalStatus;
 }
 
 nsresult
 RasterImage::OnImageDataAvailable(nsIRequest*,
                                   nsISupports*,
                                   nsIInputStream* aInStr,
@@ -1895,18 +1868,16 @@ RasterImage::OnImageDataAvailable(nsIReq
     "WriteToRasterImage should consume everything or the image must be in error!");
 
   return rv;
 }
 
 nsresult
 RasterImage::OnNewSourceData()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   nsresult rv;
 
   if (mError)
     return NS_ERROR_FAILURE;
 
   // The source data should be complete before calling this
   NS_ABORT_IF_FALSE(mHasSourceData,
                     "Calling NewSourceData before SourceDataComplete!");
@@ -2452,18 +2423,16 @@ RasterImage::GetKeys(uint32_t *count, ch
     return NS_OK;
   }
   return mProperties->GetKeys(count, keys);
 }
 
 void
 RasterImage::Discard(bool force)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   // We should be ok for discard
   NS_ABORT_IF_FALSE(force ? CanForciblyDiscard() : 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.
@@ -2679,17 +2648,17 @@ RasterImage::ShutdownDecoder(eShutdownIn
   mFinishing = true;
   mInDecoder = true;
   decoder->Finish(aIntent);
   mInDecoder = false;
   mFinishing = false;
 
   // Kill off our decode request, if it's pending.  (If not, this call is
   // harmless.)
-  DecodePool::StopDecoding(this);
+  DecodeWorker::Singleton()->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
@@ -2714,18 +2683,16 @@ RasterImage::ShutdownDecoder(eShutdownIn
 
   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)
 {
-  mDecodingMutex.AssertCurrentThreadOwns();
-
   // We should have a decoder
   NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
 
   // Write
   nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
   mInDecoder = true;
   mDecoder->Write(aBuffer, aCount);
   mInDecoder = false;
@@ -2779,18 +2746,16 @@ RasterImage::StartDecoding()
 {
   return RequestDecodeCore(SOMEWHAT_SYNCHRONOUS);
 }
 
 
 NS_IMETHODIMP
 RasterImage::RequestDecodeCore(RequestDecodeType aDecodeType)
 {
-  MOZ_ASSERT(NS_IsMainThread());
-
   nsresult rv;
 
   if (mError)
     return NS_ERROR_FAILURE;
 
   // If we've already got a full decoder running, and have already
   // decoded some bytes, we have nothing to do
   if (mDecoder && !mDecoder->IsSizeDecode() && mBytesDecoded) {
@@ -2811,117 +2776,96 @@ RasterImage::RequestDecodeCore(RequestDe
   // a little slower).
   if (mInDecoder) {
     nsRefPtr<imgDecodeRequestor> requestor = new imgDecodeRequestor(*this);
     return NS_DispatchToCurrentThread(requestor);
   }
 
   // If we have a size decoder open, make sure we get the size
   if (mDecoder && mDecoder->IsSizeDecode()) {
-    nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
+    nsresult rv = DecodeWorker::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;
     }
   }
 
   // If we're fully decoded, we have nothing to do. This has to be after
   // DecodeUntilSizeAvailable because it can result in a synchronous decode if
   // we're already waiting on a full decode.
   if (mDecoded)
     return NS_OK;
 
-  MutexAutoLock lock(mDecodingMutex);
-
   // 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(eShutdownIntent_NotNeeded);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If we don't have a decoder, create one
+  // 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're waiting for decode work to be notified, go ahead and do that.
-  if (mDecodeRequest &&
-      mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
-    nsresult rv = FinishedSomeDecoding();
-    CONTAINER_ENSURE_SUCCESS(rv);
-  }
-
   // If we've read all the data we have, we're done
   if (mHasSourceData && mBytesDecoded == 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 && !mInDecoder && mHasSourceData && aDecodeType == SOMEWHAT_SYNCHRONOUS) {
     SAMPLE_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get());
     mDecoder->SetSynchronous(true);
 
-    DecodePool::Singleton()->DecodeABitOf(this);
+    DecodeWorker::Singleton()->DecodeABitOf(this);
 
     // DecodeABitOf can destroy mDecoder.
     if (mDecoder) {
       mDecoder->SetSynchronous(false);
     }
     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);
-  }
+  // 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.
+  DecodeWorker::Singleton()->RequestDecode(this);
 
   return NS_OK;
 }
 
 // Synchronously decodes as much data as possible
 nsresult
 RasterImage::SyncDecode()
 {
-  MutexAutoLock imgLock(mDecodingMutex);
-
-  if (mDecodeRequest) {
-    // If the image is waiting for decode work to be notified, go ahead and do that.
-    if (mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
-      nsresult rv = FinishedSomeDecoding();
-      CONTAINER_ENSURE_SUCCESS(rv);
-    }
-  }
-
   nsresult rv;
 
   SAMPLE_LABEL_PRINTF("RasterImage", "SyncDecode", "%s", GetURIString().get());;
 
   // We really have no good way of forcing a synchronous decode if we're being
   // called in a re-entrant manner (ie, from an event listener fired by a
   // decoder), because the decoding machinery is already tied up. We thus explicitly
   // disallow this type of call in the API, and check for it in API methods.
   NS_ABORT_IF_FALSE(!mInDecoder, "Yikes, forcing sync in reentrant call!");
 
   // If we have a size decoder open, make sure we get the size
   if (mDecoder && mDecoder->IsSizeDecode()) {
-    nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
+    nsresult rv = DecodeWorker::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;
     }
@@ -2967,19 +2911,16 @@ RasterImage::SyncDecode()
   mDecoder->FlushInvalidations();
   mInDecoder = false;
 
   rv = FinishedSomeDecoding();
   CONTAINER_ENSURE_SUCCESS(rv);
 
   if (mDecoder) {
     mDecoder->SetSynchronous(false);
-
-    // If our decoder's still open, there's still work to be done.
-    DecodePool::Singleton()->RequestDecode(this);
   }
 
   // All good if no errors!
   return mError ? NS_ERROR_FAILURE : NS_OK;
 }
 
 static inline bool
 IsDownscale(const gfxSize& scale)
@@ -3180,17 +3121,21 @@ RasterImage::Draw(gfxContext *aContext,
   // that case we use our animation consumers count as a proxy for lock count.
   if (mLockCount == 0 || (mAnim && mAnimationConsumers == 0)) {
     if (mStatusTracker)
       mStatusTracker->OnUnlockedDraw();
   }
 
   // We use !mDecoded && mHasSourceData to mean discarded.
   if (!mDecoded && mHasSourceData) {
-    mDrawStartTime = TimeStamp::Now();
+      mDrawStartTime = TimeStamp::Now();
+
+      // We're drawing this image, so indicate that we should decode it as soon
+      // as possible.
+      DecodeWorker::Singleton()->MarkAsASAP(this);
   }
 
   // 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);
   }
 
@@ -3254,17 +3199,16 @@ RasterImage::UnlockImage()
   // 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 && CanForciblyDiscard()) {
     PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
            ("RasterImage[0x%p] canceling decode because image "
             "is now unlocked.", this));
-    MutexAutoLock lock(mDecodingMutex);
     FinishedSomeDecoding(eShutdownIntent_NotNeeded);
     ForceDiscard();
     return NS_OK;
   }
 
   // Otherwise, we might still be a candidate for discarding in the future.  If
   // we are, add ourselves to the discard tracker.
   if (CanDiscard()) {
@@ -3289,18 +3233,16 @@ RasterImage::RequestDiscard()
 
 // Flushes up to aMaxBytes to the decoder.
 nsresult
 RasterImage::DecodeSomeData(uint32_t aMaxBytes)
 {
   // We should have a decoder if we get here
   NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!");
 
-  mDecodingMutex.AssertCurrentThreadOwns();
-
   // First, if we've just been called because we allocated a frame on the main
   // thread, let the decoder deal with the data it set aside at that time by
   // passing it a null buffer.
   if (mDecodeRequest->mAllocatedNewFrame) {
     mDecodeRequest->mAllocatedNewFrame = false;
     nsresult rv = WriteToDecoder(nullptr, 0);
     if (NS_FAILED(rv) || mDecoder->NeedsNewFrame()) {
       return rv;
@@ -3308,17 +3250,17 @@ RasterImage::DecodeSomeData(uint32_t aMa
   }
 
   // If we have nothing else to decode, return
   if (mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   // write the proper amount of data
   uint32_t bytesToDecode = std::min(aMaxBytes,
-                                    mSourceData.Length() - mBytesDecoded);
+                                  mSourceData.Length() - mBytesDecoded);
   nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
                                bytesToDecode);
 
   return rv;
 }
 
 // There are various indicators that tell us we're finished with the decode
 // task at hand and can shut down the decoder.
@@ -3364,17 +3306,16 @@ void
 RasterImage::DoError()
 {
   // If we've flagged an error before, we have nothing to do
   if (mError)
     return;
 
   // If we're mid-decode, shut down the decoder.
   if (mDecoder) {
-    MutexAutoLock lock(mDecodingMutex);
     FinishedSomeDecoding(eShutdownIntent_Error);
   }
 
   // Put the container in an error state
   mError = true;
 
   if (mDecodeRequest) {
     mDecodeRequest->mStatusTracker->GetDecoderObserver()->OnError();
@@ -3437,18 +3378,16 @@ RasterImage::GetFramesNotified(uint32_t 
 #endif
 
 nsresult
 RasterImage::FinishedSomeDecoding(eShutdownIntent aIntent /* = eShutdownIntent_Done */,
                                   DecodeRequest* aRequest /* = nullptr */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mDecodingMutex.AssertCurrentThreadOwns();
-
   nsRefPtr<DecodeRequest> request;
   if (aRequest) {
     request = aRequest;
   } else {
     request = mDecodeRequest;
   }
 
   // Ensure that, if the decoder is the last reference to the image, we don't
@@ -3500,150 +3439,145 @@ RasterImage::FinishedSomeDecoding(eShutd
           int32_t KBps = int32_t(request->mImage->mBytesDecoded /
                                  (1024 * request->mDecodeTime.ToSeconds()));
           Telemetry::Accumulate(id, KBps);
         }
       }
     }
   }
 
-  imgStatusTracker::StatusDiff diff;
+  // Then, tell the observers what happened in the decoder.
+  // If we have no request, we have not yet created a decoder, but we still
+  // need to send out notifications.
   if (request) {
-    diff = image->mStatusTracker->CalculateAndApplyDifference(request->mStatusTracker);
+    image->mStatusTracker->SyncAndSyncNotifyDifference(request->mStatusTracker);
+  } else {
+    image->mStatusTracker->SyncNotifyDecodeState();
   }
 
-  {
-    // Notifications can't go out with the decoding lock held.
-    MutexAutoUnlock unlock(mDecodingMutex);
-
-    // Then, tell the observers what happened in the decoder.
-    // If we have no request, we have not yet created a decoder, but we still
-    // need to send out notifications.
-    if (request) {
-      image->mStatusTracker->SyncNotifyDifference(diff);
+  // If we were a size decode and a full decode was requested, now's the time.
+  if (NS_SUCCEEDED(rv) && done && wasSize && image->mWantFullDecode) {
+    image->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.
+    if (!image->StoringSourceData()) {
+      rv = image->SyncDecode();
     } else {
-      image->mStatusTracker->SyncNotifyDecodeState();
+      rv = image->RequestDecode();
     }
-
-    // If we were a size decode and a full decode was requested, now's the time.
-    if (NS_SUCCEEDED(rv) && done && wasSize && image->mWantFullDecode) {
-      image->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.
-      if (!image->StoringSourceData()) {
-        rv = image->SyncDecode();
-      } else {
-        rv = image->RequestDecode();
-      }
-    }
-
   }
 
   return rv;
 }
 
-NS_IMPL_THREADSAFE_ISUPPORTS1(RasterImage::DecodePool,
-                              nsIObserver)
-
-/* static */ RasterImage::DecodePool*
-RasterImage::DecodePool::Singleton()
+/* static */ RasterImage::DecodeWorker*
+RasterImage::DecodeWorker::Singleton()
 {
   if (!sSingleton) {
-    MOZ_ASSERT(NS_IsMainThread());
-    sSingleton = new DecodePool();
+    sSingleton = new DecodeWorker();
     ClearOnShutdown(&sSingleton);
   }
 
   return sSingleton;
 }
 
-RasterImage::DecodePool::DecodePool()
+RasterImage::DecodeWorker::~DecodeWorker()
 {
-  if (gMultithreadedDecoding) {
-    mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
-    if (mThreadPool) {
-      mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
-      if (gDecodingThreadLimit <= 0) {
-        mThreadPool->SetThreadLimit(std::max(PR_GetNumberOfProcessors() - 1, 1));
-      } else {
-        mThreadPool->SetThreadLimit(static_cast<uint32_t>(gDecodingThreadLimit));
-      }
-
-      nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
-      if (obsSvc) {
-        obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
-      }
-    }
+  MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodeWorker on main thread!");
+
+  // Shut down all the decoders since we're going away.
+  DecodeRequest* request = mASAPDecodeRequests.getFirst();
+  while (request) {
+    request->mImage->FinishedSomeDecoding(eShutdownIntent_NotNeeded);
+
+    request = request->getNext();
   }
-}
-
-RasterImage::DecodePool::~DecodePool()
-{
-  MOZ_ASSERT(NS_IsMainThread(), "Must shut down DecodePool on main thread!");
-}
-
-NS_IMETHODIMP
-RasterImage::DecodePool::Observe(nsISupports *subject, const char *topic,
-                                   const PRUnichar *data)
-{
-  NS_ASSERTION(strcmp(topic, "xpcom-shutdown-threads") == 0, "oops");
-
-  if (mThreadPool) {
-    mThreadPool->Shutdown();
-    mThreadPool = nullptr;
+
+  request = mNormalDecodeRequests.getFirst();
+  while (request) {
+    request->mImage->FinishedSomeDecoding(eShutdownIntent_NotNeeded);
+
+    request = request->getNext();
   }
-
-  return NS_OK;
 }
 
 void
-RasterImage::DecodePool::RequestDecode(RasterImage* aImg)
+RasterImage::DecodeWorker::MarkAsASAP(RasterImage* aImg)
+{
+  // We can be marked as ASAP before we've been asked to decode. If we are,
+  // create the request so we have somewhere to write down our status.
+  if (!aImg->mDecodeRequest) {
+    aImg->mDecodeRequest = new DecodeRequest(aImg);
+  }
+
+  DecodeRequest* request = aImg->mDecodeRequest;
+
+  // If we're already an ASAP request, there's nothing to do here.
+  if (request->mIsASAP) {
+    return;
+  }
+
+  request->mIsASAP = true;
+
+  if (request->isInList()) {
+    // If the decode request is in a list, it must be in the normal decode
+    // requests list -- if it had been in the ASAP list, then mIsASAP would
+    // have been true above.  Move the request to the ASAP list.
+    request->removeFrom(mNormalDecodeRequests);
+    mASAPDecodeRequests.insertBack(request);
+
+    // Since request is in a list, one of the decode worker's lists is
+    // non-empty, so the worker should be pending in the event loop.
+    //
+    // (Note that this invariant only holds while we are not in Run(), because
+    // DecodeSomeOfImage adds requests to the decode worker using
+    // AddDecodeRequest, not RequestDecode, and AddDecodeRequest does not call
+    // EnsurePendingInEventLoop.  Therefore, it is an error to call MarkAsASAP
+    // from within DecodeWorker::Run.)
+    MOZ_ASSERT(mPendingInEventLoop);
+  }
+}
+
+void
+RasterImage::DecodeWorker::AddDecodeRequest(DecodeRequest* aRequest, uint32_t bytesToDecode)
+{
+  if (aRequest->isInList()) {
+    // The image is already in our list of images to decode, so we don't have
+    // to do anything here.
+    return;
+  }
+
+  aRequest->mBytesToDecode = bytesToDecode;
+
+  if (aRequest->mIsASAP) {
+    mASAPDecodeRequests.insertBack(aRequest);
+  } else {
+    mNormalDecodeRequests.insertBack(aRequest);
+  }
+}
+
+void
+RasterImage::DecodeWorker::RequestDecode(RasterImage* aImg)
 {
   MOZ_ASSERT(aImg->mDecoder);
-  MOZ_ASSERT(NS_IsMainThread());
-  aImg->mDecodingMutex.AssertCurrentThreadOwns();
 
   // If we're currently waiting on a new frame for this image, we can't do any
   // decoding.
   if (!aImg->mDecoder->NeedsNewFrame()) {
-    // No matter whether this is currently being decoded, we need to update the
-    // number of bytes we want it to decode.
-    aImg->mDecodeRequest->mBytesToDecode = aImg->mSourceData.Length() - aImg->mBytesDecoded;
-
-    if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_PENDING ||
-        aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_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;
-    }
-
-    aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_PENDING;
-    nsRefPtr<DecodeJob> job = new DecodeJob(aImg->mDecodeRequest, aImg);
-    if (!gMultithreadedDecoding || !mThreadPool) {
-      NS_DispatchToMainThread(job);
-    } else {
-      mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL);
-    }
+    AddDecodeRequest(aImg->mDecodeRequest, aImg->mSourceData.Length() - aImg->mBytesDecoded);
+    EnsurePendingInEventLoop();
   }
 }
 
 void
-RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg)
+RasterImage::DecodeWorker::DecodeABitOf(RasterImage* aImg)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  aImg->mDecodingMutex.AssertCurrentThreadOwns();
-
-  if (aImg->mDecodeRequest) {
-    // If the image is waiting for decode work to be notified, go ahead and do that.
-    if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
-      aImg->FinishedSomeDecoding();
-    }
-  }
 
   DecodeSomeOfImage(aImg);
 
   aImg->FinishedSomeDecoding();
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
   if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
@@ -3655,100 +3589,108 @@ RasterImage::DecodePool::DecodeABitOf(Ra
         !aImg->mError &&
         !aImg->IsDecodeFinished() &&
         aImg->mSourceData.Length() > aImg->mBytesDecoded) {
       RequestDecode(aImg);
     }
   }
 }
 
-/* static */ void
-RasterImage::DecodePool::StopDecoding(RasterImage* aImg)
+void
+RasterImage::DecodeWorker::EnsurePendingInEventLoop()
 {
-  aImg->mDecodingMutex.AssertCurrentThreadOwns();
-
+  if (!mPendingInEventLoop) {
+    mPendingInEventLoop = true;
+    NS_DispatchToCurrentThread(this);
+  }
+}
+
+void
+RasterImage::DecodeWorker::StopDecoding(RasterImage* aImg)
+{
   // If we haven't got a decode request, we're not currently decoding. (Having
   // a decode request doesn't imply we *are* decoding, though.)
   if (aImg->mDecodeRequest) {
-    aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_INACTIVE;
+    if (aImg->mDecodeRequest->isInList()) {
+      aImg->mDecodeRequest->remove();
+    }
   }
 }
 
 NS_IMETHODIMP
-RasterImage::DecodePool::DecodeJob::Run()
+RasterImage::DecodeWorker::Run()
 {
-  MutexAutoLock imglock(mImage->mDecodingMutex);
-
-  // If we were interrupted, we shouldn't do any work.
-  if (mRequest->mRequestStatus == DecodeRequest::REQUEST_STOPPED) {
-    return NS_OK;
-  }
-
-  // If someone came along and synchronously decoded us, there's nothing for us to do.
-  if (!mRequest->mAllocatedNewFrame && (!mImage->mDecoder || mImage->IsDecodeFinished() ||
-                                        mRequest->mRequestStatus != DecodeRequest::REQUEST_PENDING)) {
-    return NS_OK;
+  // We just got called back by the event loop; therefore, we're no longer
+  // pending.
+  mPendingInEventLoop = false;
+
+  TimeStamp eventStart = TimeStamp::Now();
+
+  // Now decode until we either run out of time or run out of images.
+  do {
+    // Try to get an ASAP request to handle.  If there isn't one, try to get a
+    // normal request.  If no normal request is pending either, then we're done
+    // here.
+    DecodeRequest* request = mASAPDecodeRequests.popFirst();
+    if (!request)
+      request = mNormalDecodeRequests.popFirst();
+    if (!request)
+      break;
+
+    // This has to be a strong pointer, because DecodeSomeOfImage may destroy
+    // image->mDecoder, which may be holding the only other reference to image.
+    nsRefPtr<RasterImage> image = request->mImage;
+    uint32_t oldFrameCount = image->mDecoder->GetFrameCount();
+    uint32_t oldByteCount = image->mBytesDecoded;
+
+    DecodeSomeOfImage(image, DECODE_TYPE_NORMAL, request->mBytesToDecode);
+
+    uint32_t bytesDecoded = image->mBytesDecoded - oldByteCount;
+
+    // If the decoder needs a new frame, enqueue an event to get it; that event
+    // will enqueue another decode request when it's done.
+    if (image->mDecoder && image->mDecoder->NeedsNewFrame()) {
+      FrameNeededWorker::GetNewFrame(image);
+    }
+
+    // If we aren't yet finished decoding and we have more data in hand, add
+    // this request to the back of the list.
+    else if (image->mDecoder &&
+             !image->mError &&
+             !image->IsDecodeFinished() &&
+             bytesDecoded < request->mBytesToDecode) {
+      AddDecodeRequest(request, request->mBytesToDecode - bytesDecoded);
+
+      // If we have a new frame, let everybody know about it.
+      if (image->mDecoder->GetFrameCount() != oldFrameCount) {
+        DecodeDoneWorker::NotifyFinishedSomeDecoding(image, request);
+      }
+    } else {
+      // Nothing more for us to do - let everyone know what happened.
+      DecodeDoneWorker::NotifyFinishedSomeDecoding(image, request);
+    }
+
+  } while ((TimeStamp::Now() - eventStart).ToMilliseconds() <= gMaxMSBeforeYield);
+
+  // If decode requests are pending, re-post ourself to the event loop.
+  if (!mASAPDecodeRequests.isEmpty() || !mNormalDecodeRequests.isEmpty()) {
+    EnsurePendingInEventLoop();
   }
 
-  mRequest->mRequestStatus = DecodeRequest::REQUEST_ACTIVE;
-
-  uint32_t oldByteCount = mImage->mBytesDecoded;
-
-  DecodeType type = DECODE_TYPE_UNTIL_DONE_BYTES;
-
-  // Multithreaded decoding can be disabled. If we've done so, we don't want to
-  // monopolize the main thread, and will allow a timeout in DecodeSomeOfImage.
-  if (NS_IsMainThread()) {
-    type = DECODE_TYPE_UNTIL_TIME;
-  }
-
-  DecodePool::Singleton()->DecodeSomeOfImage(mImage, type, mRequest->mBytesToDecode);
-
-  uint32_t bytesDecoded = mImage->mBytesDecoded - oldByteCount;
-
-  mRequest->mRequestStatus = DecodeRequest::REQUEST_WORK_DONE;
-
-  // If the decoder needs a new frame, enqueue an event to get it; that event
-  // will enqueue another decode request when it's done.
-  if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
-    FrameNeededWorker::GetNewFrame(mImage);
-  }
-  // If we aren't yet finished decoding and we have more data in hand, add
-  // this request to the back of the list.
-  else if (mImage->mDecoder &&
-           !mImage->mError &&
-           !mImage->IsDecodeFinished() &&
-           bytesDecoded < mRequest->mBytesToDecode) {
-    DecodePool::Singleton()->RequestDecode(mImage);
-  } else {
-    // Nothing more for us to do - let everyone know what happened.
-    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
-  }
+  Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY_US,
+                        uint32_t((TimeStamp::Now() - eventStart).ToMicroseconds()));
 
   return NS_OK;
 }
 
 nsresult
-RasterImage::DecodePool::DecodeUntilSizeAvailable(RasterImage* aImg)
+RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  MutexAutoLock imgLock(aImg->mDecodingMutex);
-
-  if (aImg->mDecodeRequest) {
-    // If the image is waiting for decode work to be notified, go ahead and do that.
-    if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
-      nsresult rv = aImg->FinishedSomeDecoding();
-      if (NS_FAILED(rv)) {
-        aImg->DoError();
-        return rv;
-      }
-    }
-  }
-
   nsresult rv = DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
   if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
@@ -3756,45 +3698,36 @@ RasterImage::DecodePool::DecodeUntilSize
   } else {
     rv = aImg->FinishedSomeDecoding();
   }
 
   return rv;
 }
 
 nsresult
-RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg,
-                                           DecodeType aDecodeType /* = DECODE_TYPE_UNTIL_TIME */,
-                                           uint32_t bytesToDecode /* = 0 */)
+RasterImage::DecodeWorker::DecodeSomeOfImage(RasterImage* aImg,
+                                             DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */,
+                                             uint32_t bytesToDecode /* = 0 */)
 {
   NS_ABORT_IF_FALSE(aImg->mInitialized,
                     "Worker active for uninitialized container!");
-  aImg->mDecodingMutex.AssertCurrentThreadOwns();
 
   // If an error is flagged, it probably happened while we were waiting
   // in the event queue.
   if (aImg->mError)
     return NS_OK;
 
   // 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 (!aImg->mDecoder || aImg->mDecoded)
     return NS_OK;
 
-  // If we're doing synchronous decodes, and we're waiting on a new frame for
-  // this image, get it now.
-  if (aImg->mDecoder->IsSynchronous() && aImg->mDecoder->NeedsNewFrame()) {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    aImg->mDecoder->AllocateFrame();
-    aImg->mDecodeRequest->mAllocatedNewFrame = true;
-  }
-
-  // If we're not synchronous, we can't allocate a frame right now.
-  else if (aImg->mDecoder->NeedsNewFrame()) {
+  // If we're currently waiting on a new frame for this image, we can't do any
+  // decoding right now.
+  if (aImg->mDecoder->NeedsNewFrame()) {
     return NS_OK;
   }
 
   nsRefPtr<Decoder> decoderKungFuDeathGrip = aImg->mDecoder;
 
   uint32_t maxBytes;
   if (aImg->mDecoder->IsSizeDecode()) {
     // Decode all available data if we're a size decode; they're cheap, and we
@@ -3835,17 +3768,17 @@ RasterImage::DecodePool::DecodeSomeOfIma
       aImg->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 (aDecodeType == DECODE_TYPE_UNTIL_TIME && TimeStamp::Now() >= deadline)
+    if (TimeStamp::Now() >= deadline)
       break;
   }
 
   if (aImg->mDecodeRequest) {
     aImg->mDecodeRequest->mDecodeTime += (TimeStamp::Now() - start);
     aImg->mDecodeRequest->mChunkCount += chunkCount;
   }
 
@@ -3880,34 +3813,29 @@ RasterImage::DecodePool::DecodeSomeOfIma
 RasterImage::DecodeDoneWorker::DecodeDoneWorker(RasterImage* image, DecodeRequest* request)
  : mImage(image)
  , mRequest(request)
 {}
 
 void
 RasterImage::DecodeDoneWorker::NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request)
 {
-  image->mDecodingMutex.AssertCurrentThreadOwns();
-
   nsCOMPtr<DecodeDoneWorker> worker = new DecodeDoneWorker(image, request);
   NS_DispatchToMainThread(worker);
 }
 
 NS_IMETHODIMP
 RasterImage::DecodeDoneWorker::Run()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MutexAutoLock lock(mImage->mDecodingMutex);
-
   mImage->FinishedSomeDecoding(eShutdownIntent_Done, mRequest);
 
   // If we didn't finish decoding yet, try again
   if (mImage->mDecoder && !mImage->IsDecodeFinished() &&
       mImage->mSourceData.Length() > mImage->mBytesDecoded) {
-    DecodePool::Singleton()->RequestDecode(mImage);
+    DecodeWorker::Singleton()->RequestDecode(mImage);
   }
 
   return NS_OK;
 }
 
 RasterImage::FrameNeededWorker::FrameNeededWorker(RasterImage* image)
  : mImage(image)
 {}
@@ -3918,28 +3846,26 @@ RasterImage::FrameNeededWorker::GetNewFr
 {
   nsCOMPtr<FrameNeededWorker> worker = new FrameNeededWorker(image);
   NS_DispatchToMainThread(worker);
 }
 
 NS_IMETHODIMP
 RasterImage::FrameNeededWorker::Run()
 {
-  MutexAutoLock lock(mImage->mDecodingMutex);
   nsresult rv = NS_OK;
 
   // If we got a synchronous decode in the mean time, we don't need to do
   // anything.
   if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
     rv = mImage->mDecoder->AllocateFrame();
     mImage->mDecodeRequest->mAllocatedNewFrame = true;
   }
 
   if (NS_SUCCEEDED(rv) && mImage->mDecoder) {
-    // By definition, we're not done decoding, so enqueue us for more decoding.
-    DecodePool::Singleton()->RequestDecode(mImage);
+    DecodeWorker::Singleton()->RequestDecode(mImage);
   }
 
   return NS_OK;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -13,39 +13,38 @@
  * @author  Arron Mogge <paper@animecity.nu>
  * @author  Andrew Smith <asmith15@learn.senecac.on.ca>
  */
 
 #ifndef mozilla_imagelib_RasterImage_h_
 #define mozilla_imagelib_RasterImage_h_
 
 #include "Image.h"
+#include "nsCOMArray.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsITimer.h"
 #include "nsIRequest.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DiscardTracker.h"
-#include "nsISupportsImpl.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WeakPtr.h"
-#include "mozilla/Mutex.h"
+#include "mozilla/RefPtr.h"
 #include "gfx2DGlue.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
 #endif
 
 class nsIInputStream;
-class nsIThreadPool;
 
 #define NS_RASTERIMAGE_CID \
 { /* 376ff2c1-9bf6-418a-b143-3340c00112f7 */         \
      0x376ff2c1,                                     \
      0x9bf6,                                         \
      0x418a,                                         \
     {0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
 }
@@ -383,163 +382,170 @@ private:
     Anim() :
       firstFrameRefreshArea(),
       currentAnimationFrameIndex(0),
       lastCompositedFrameIndex(-1) {}
     ~Anim() {}
   };
 
   /**
+   * DecodeWorker keeps a linked list of DecodeRequests to keep track of the
+   * images it needs to decode.
+   *
    * Each RasterImage has a pointer to one or zero heap-allocated
    * DecodeRequests.
    */
-  struct DecodeRequest
+  struct DecodeRequest : public LinkedListElement<DecodeRequest>,
+                         public RefCounted<DecodeRequest>
   {
     DecodeRequest(RasterImage* aImage)
       : mImage(aImage)
       , mBytesToDecode(0)
-      , mRequestStatus(REQUEST_INACTIVE)
       , mChunkCount(0)
       , mAllocatedNewFrame(false)
+      , mIsASAP(false)
     {
       mStatusTracker = aImage->mStatusTracker->CloneForRecording();
     }
 
-    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodeRequest)
-
     // The status tracker that is associated with a given decode request, to
     // ensure their lifetimes are linked.
     nsRefPtr<imgStatusTracker> mStatusTracker;
 
     RasterImage* mImage;
 
     uint32_t mBytesToDecode;
 
-    enum DecodeRequestStatus
-    {
-      REQUEST_INACTIVE,
-      REQUEST_PENDING,
-      REQUEST_ACTIVE,
-      REQUEST_WORK_DONE,
-      REQUEST_STOPPED
-    } mRequestStatus;
-
     /* Keeps track of how much time we've burned decoding this particular decode
      * request. */
     TimeDuration mDecodeTime;
 
     /* The number of chunks it took to decode this image. */
     int32_t mChunkCount;
 
     /* True if a new frame has been allocated, but DecodeSomeData hasn't yet
      * been called to flush data to it */
     bool mAllocatedNewFrame;
+
+    /* True if we need to handle this decode as soon as possible. */
+    bool mIsASAP;
   };
 
   /*
-   * DecodePool is a singleton class we use when decoding large images.
+   * DecodeWorker is a singleton class we use when decoding large images.
    *
    * When we wish to decode an image larger than
-   * image.mem.max_bytes_for_sync_decode, we call DecodePool::RequestDecode()
+   * image.mem.max_bytes_for_sync_decode, we call DecodeWorker::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
+   * the DecodeWorker 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
+   * When the DecodeWorker 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.
+   *
+   * An image being decoded may have one of two priorities: normal or ASAP.  ASAP
+   * images are always decoded before normal images.  (We currently give ASAP
+   * priority to images which appear onscreen but are not yet decoded.)
    */
-  class DecodePool : public nsIObserver
+  class DecodeWorker : public nsRunnable
   {
   public:
-    NS_DECL_ISUPPORTS
-    NS_DECL_NSIOBSERVER
-
-    static DecodePool* Singleton();
+    static DecodeWorker* Singleton();
 
     /**
-     * Ask the DecodePool to asynchronously decode this image.
+     * Ask the DecodeWorker to asynchronously decode this image.
      */
     void RequestDecode(RasterImage* aImg);
 
     /**
      * Decode aImg for a short amount of time, and post the remainder to the
      * queue.
      */
     void DecodeABitOf(RasterImage* aImg);
 
     /**
-     * Ask the DecodePool to stop decoding this image.  Internally, we also
+     * Give this image ASAP priority; it will be decoded before all non-ASAP
+     * images.  You can call MarkAsASAP before or after you call RequestDecode
+     * for the image, but if you MarkAsASAP before you call RequestDecode, you
+     * still need to call RequestDecode.
+     *
+     * StopDecoding() resets the image's ASAP flag.
+     */
+    void MarkAsASAP(RasterImage* aImg);
+
+    /**
+     * Ask the DecodeWorker 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
+     * Since the DecodeWorker keeps raw pointers to RasterImages, make sure you
      * call this before a RasterImage is destroyed!
      */
-    static void StopDecoding(RasterImage* aImg);
+    void StopDecoding(RasterImage* aImg);
 
     /**
      * Synchronously decode the beginning of the image until we run out of
      * bytes or we get the image's size.  Note that this done on a best-effort
      * basis; if the size is burried too deep in the image, we'll give up.
      *
      * @return 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* aImg);
 
-    virtual ~DecodePool();
+    NS_IMETHOD Run();
+
+    virtual ~DecodeWorker();
 
   private: /* statics */
-    static StaticRefPtr<DecodePool> sSingleton;
+    static StaticRefPtr<DecodeWorker> sSingleton;
 
   private: /* methods */
-    DecodePool();
+    DecodeWorker()
+      : mPendingInEventLoop(false)
+    {}
+
+    /* Post ourselves to the event loop if we're not currently pending. */
+    void EnsurePendingInEventLoop();
+
+    /* Add the given request to the appropriate list of decode requests, but
+     * don't ensure that we're pending in the event loop. */
+    void AddDecodeRequest(DecodeRequest* aRequest, uint32_t bytesToDecode);
 
     enum DecodeType {
-      DECODE_TYPE_UNTIL_TIME,
-      DECODE_TYPE_UNTIL_SIZE,
-      DECODE_TYPE_UNTIL_DONE_BYTES
+      DECODE_TYPE_NORMAL,
+      DECODE_TYPE_UNTIL_SIZE
     };
 
     /* Decode some chunks of the given image.  If aDecodeType is UNTIL_SIZE,
      * decode until we have the image's size, then stop. If bytesToDecode is
-     * non-0, at most bytesToDecode bytes will be decoded. if aDecodeType is
-     * UNTIL_DONE_BYTES, decode until all bytesToDecode bytes are decoded.
-     */
+     * non-0, at most bytesToDecode bytes will be decoded. */
     nsresult DecodeSomeOfImage(RasterImage* aImg,
-                               DecodeType aDecodeType = DECODE_TYPE_UNTIL_TIME,
+                               DecodeType aDecodeType = DECODE_TYPE_NORMAL,
                                uint32_t bytesToDecode = 0);
 
-    /* A decode job dispatched to a thread pool by DecodePool.
-     */
-    class DecodeJob : public nsRunnable
-    {
-    public:
-      DecodeJob(DecodeRequest* aRequest, RasterImage* aImg)
-        : mRequest(aRequest)
-        , mImage(aImg)
-      {}
-
-      NS_IMETHOD Run();
-
-    private:
-      nsRefPtr<DecodeRequest> mRequest;
-      nsRefPtr<RasterImage> mImage;
-    };
+    /* Create a new DecodeRequest suitable for doing some decoding and set it
+     * as aImg's mDecodeRequest. */
+    void CreateRequestForImage(RasterImage* aImg);
 
   private: /* members */
 
-    nsCOMPtr<nsIThreadPool> mThreadPool;
+    LinkedList<DecodeRequest> mASAPDecodeRequests;
+    LinkedList<DecodeRequest> mNormalDecodeRequests;
+
+    /* True if we've posted ourselves to the event loop and expect Run() to
+     * be called sometime in the future. */
+    bool mPendingInEventLoop;
   };
 
   class DecodeDoneWorker : public nsRunnable
   {
   public:
     /**
-     * Called by the DecodePool with an image when it's done some significant
+     * Called by the DecodeWorker with an image when it's done some significant
      * portion of decoding that needs to be notified about.
      *
      * Ensures the decode state accumulated by the decoding process gets
      * applied to the image.
      */
     static void NotifyFinishedSomeDecoding(RasterImage* image, DecodeRequest* request);
 
     NS_IMETHOD Run();
@@ -552,17 +558,17 @@ private:
     nsRefPtr<RasterImage> mImage;
     nsRefPtr<DecodeRequest> mRequest;
   };
 
   class FrameNeededWorker : public nsRunnable
   {
   public:
     /**
-     * Called by the DecodeJob with an image when it's been told by the
+     * Called by the DecodeWorker with an image when it's been told by the
      * decoder that it needs a new frame to be allocated on the main thread.
      *
      * Dispatches an event to do so, which will further dispatch a
      * DecodeRequest event to continue decoding.
      */
     static void GetNewFrame(RasterImage* image);
 
     NS_IMETHOD Run();
@@ -743,61 +749,52 @@ private: // data
   //! # loops remaining before animation stops (-1 no stop)
   int32_t                    mLoopCount;
   
   // Discard members
   uint32_t                   mLockCount;
   DiscardTracker::Node       mDiscardTrackerNode;
 
   // Source data members
+  FallibleTArray<char>       mSourceData;
   nsCString                  mSourceDataMimeType;
 
   friend class DiscardTracker;
 
+  // Decoder and friends
+  nsRefPtr<Decoder>              mDecoder;
+  nsRefPtr<DecodeRequest>        mDecodeRequest;
+  uint32_t                       mBytesDecoded;
+
   // How many times we've decoded this image.
   // This is currently only used for statistics
   int32_t                        mDecodeCount;
 
   // If the image contains multiple resolutions, a hint as to which one should be used
   nsIntSize                  mRequestedResolution;
 
   // Cached value for GetImageContainer.
   nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
 
 #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 mDecodingMutex.
-
-  // BEGIN LOCKED MEMBER VARIABLES
-  mozilla::Mutex             mDecodingMutex;
-
-  FallibleTArray<char>       mSourceData;
-
-  // Decoder and friends
-  nsRefPtr<Decoder>          mDecoder;
-  nsRefPtr<DecodeRequest>    mDecodeRequest;
-  uint32_t                   mBytesDecoded;
-  // END LOCKED MEMBER VARIABLES
-
-  bool                       mInDecoder;
-
   // Boolean flags (clustered together to conserve space):
   bool                       mHasSize:1;       // Has SetSize() been called?
   bool                       mDecodeOnDraw:1;  // Decoding on draw?
   bool                       mMultipart:1;     // Multipart?
   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                       mInDecoder:1;
 
   // Whether the animation can stop, due to running out
   // of frames, or no more owning request
   bool                       mAnimationFinished:1;
 
   // Whether we're calling Decoder::Finish() from ShutdownDecoder.
   bool                       mFinishing:1;
 
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -383,18 +383,17 @@ VectorImage::OnImageDataComplete(nsIRequ
   if (NS_FAILED(aStatus))
     finalStatus = aStatus;
 
   // Actually fire OnStopRequest.
   if (mStatusTracker) {
     nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
     imgDecoderObserver* observer = clone->GetDecoderObserver();
     observer->OnStopRequest(aLastPart, finalStatus);
-    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
-    mStatusTracker->SyncNotifyDifference(diff);
+    mStatusTracker->SyncAndSyncNotifyDifference(clone);
   }
   return finalStatus;
 }
 
 nsresult
 VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
                                   nsISupports* aContext,
                                   nsIInputStream* aInStr,
@@ -842,18 +841,17 @@ VectorImage::OnStartRequest(nsIRequest* 
 
   // Sending StartDecode will block page load until the document's ready.  (We
   // unblock it by sending StopDecode in OnSVGDocumentLoaded or
   // OnSVGDocumentError.)
   if (mStatusTracker) {
     nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
     imgDecoderObserver* observer = clone->GetDecoderObserver();
     observer->OnStartDecode();
-    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
-    mStatusTracker->SyncNotifyDifference(diff);
+    mStatusTracker->SyncAndSyncNotifyDifference(clone);
   }
 
   // Create a listener to wait until the SVG document is fully loaded, which
   // will signal that this image is ready to render. Certain error conditions
   // will prevent us from ever getting this notification, so we also create a
   // listener that waits for parsing to complete and cancels the
   // SVGLoadEventListener if needed. The listeners are automatically attached
   // to the document by their constructors.
@@ -930,18 +928,17 @@ VectorImage::OnSVGDocumentLoaded()
     nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
     imgDecoderObserver* observer = clone->GetDecoderObserver();
 
     observer->OnStartContainer(); // Signal that width/height are available.
     observer->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
     observer->OnStopFrame();
     observer->OnStopDecode(NS_OK); // Unblock page load.
 
-    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
-    mStatusTracker->SyncNotifyDifference(diff);
+    mStatusTracker->SyncAndSyncNotifyDifference(clone);
   }
 
   EvaluateAnimation();
 }
 
 void
 VectorImage::OnSVGDocumentError()
 {
@@ -953,18 +950,17 @@ VectorImage::OnSVGDocumentError()
   mError = true;
 
   if (mStatusTracker) {
     nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
     imgDecoderObserver* observer = clone->GetDecoderObserver();
 
     // Unblock page load.
     observer->OnStopDecode(NS_ERROR_FAILURE);
-    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
-    mStatusTracker->SyncNotifyDifference(diff);
+    mStatusTracker->SyncAndSyncNotifyDifference(clone);
   }
 }
 
 //------------------------------------------------------------------------------
 // nsIStreamListener method
 
 //******************************************************************************
 /* void onDataAvailable(in nsIRequest request, in nsISupports ctxt,
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -512,36 +512,34 @@ imgStatusTracker::SyncNotifyState(nsTObs
     NOTIFY_IMAGE_OBSERVERS(OnStopDecode());
   }
 
   if (state & stateRequestStopped) {
     NOTIFY_IMAGE_OBSERVERS(OnStopRequest(hadLastPart));
   }
 }
 
-imgStatusTracker::StatusDiff
-imgStatusTracker::CalculateAndApplyDifference(imgStatusTracker* other)
+void
+imgStatusTracker::SyncAndSyncNotifyDifference(imgStatusTracker* other)
 {
-  LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncAndCalculateDifference");
+  LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncAndSyncNotifyDifference");
 
   // We must not modify or notify for the start-load state, which happens from Necko callbacks.
   uint32_t loadState = mState & stateRequestStarted;
-
-  StatusDiff diff;
-  diff.mDiffState = ~mState & other->mState & ~stateRequestStarted;
-  diff.mUnblockedOnload = mState & stateBlockingOnload && !(other->mState & stateBlockingOnload);
-  diff.mFoundError = (mImageStatus != imgIRequest::STATUS_ERROR) && (other->mImageStatus == imgIRequest::STATUS_ERROR);
+  uint32_t diffState = ~mState & other->mState & ~stateRequestStarted;
+  bool unblockedOnload = mState & stateBlockingOnload && !(other->mState & stateBlockingOnload);
+  bool foundError = (mImageStatus != imgIRequest::STATUS_ERROR) && (other->mImageStatus == imgIRequest::STATUS_ERROR);
 
   // Now that we've calculated the difference in state, synchronize our state
   // with the other tracker.
 
   // First, actually synchronize our state.
-  diff.mInvalidRect = mInvalidRect.Union(other->mInvalidRect);
-  mState |= diff.mDiffState | loadState;
-  if (diff.mUnblockedOnload) {
+  mInvalidRect = mInvalidRect.Union(other->mInvalidRect);
+  mState |= diffState | loadState;
+  if (unblockedOnload) {
     mState &= ~stateBlockingOnload;
   }
   mImageStatus = other->mImageStatus;
   mIsMultipart = other->mIsMultipart;
   mHadLastPart = other->mHadLastPart;
   mImageStatus |= other->mImageStatus;
 
   // The error state is sticky and overrides all other bits.
@@ -549,44 +547,36 @@ imgStatusTracker::CalculateAndApplyDiffe
     mImageStatus = imgIRequest::STATUS_ERROR;
   } else {
     // Unset the bits that can get unset as part of the decoding process.
     if (!(other->mImageStatus & imgIRequest::STATUS_DECODE_STARTED)) {
       mImageStatus &= ~imgIRequest::STATUS_DECODE_STARTED;
     }
   }
 
-  // Reset the invalid rectangles for another go.
-  other->mInvalidRect.SetEmpty();
-  mInvalidRect.SetEmpty();
-
-  return diff;
-}
+  SyncNotifyState(mConsumers, !!mImage, diffState, mInvalidRect, mHadLastPart);
 
-void
-imgStatusTracker::SyncNotifyDifference(imgStatusTracker::StatusDiff diff)
-{
-  LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncNotifyDifference");
-
-  SyncNotifyState(mConsumers, !!mImage, diff.mDiffState, diff.mInvalidRect, mHadLastPart);
-
-  if (diff.mUnblockedOnload) {
+  if (unblockedOnload) {
     nsTObserverArray<imgRequestProxy*>::ForwardIterator iter(mConsumers);
     while (iter.HasMore()) {
       // Hold on to a reference to this proxy, since notifying the state can
       // cause it to disappear.
       nsRefPtr<imgRequestProxy> proxy = iter.GetNext();
 
       if (!proxy->NotificationsDeferred()) {
         SendUnblockOnload(proxy);
       }
     }
   }
 
-  if (diff.mFoundError) {
+  // Reset the invalid rectangles for another go.
+  other->mInvalidRect.SetEmpty();
+  mInvalidRect.SetEmpty();
+
+  if (foundError) {
     FireFailureNotification();
   }
 }
 
 imgStatusTracker*
 imgStatusTracker::CloneForRecording()
 {
   imgStatusTracker* clone = new imgStatusTracker(*this);
--- a/image/src/imgStatusTracker.h
+++ b/image/src/imgStatusTracker.h
@@ -18,21 +18,21 @@ namespace mozilla {
 namespace image {
 class Image;
 } // namespace image
 } // namespace mozilla
 
 
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
 #include "nsTObserverArray.h"
 #include "nsIRunnable.h"
 #include "nscore.h"
 #include "imgDecoderObserver.h"
-#include "nsISupportsImpl.h"
 
 enum {
   stateRequestStarted    = 1u << 0,
   stateHasSize           = 1u << 1,
   stateDecodeStarted     = 1u << 2,
   stateDecodeStopped     = 1u << 3,
   stateFrameStopped      = 1u << 4,
   stateRequestStopped    = 1u << 5,
@@ -46,21 +46,19 @@ enum {
  * to imgRequestProxys, both synchronously (i.e., the status now) and
  * asynchronously (the status later).
  *
  * When a new proxy needs to be notified of the current state of an image, call
  * the Notify() method on this class with the relevant proxy as its argument,
  * and the notifications will be replayed to the proxy asynchronously.
  */
 
-class imgStatusTracker
+class imgStatusTracker : public mozilla::RefCounted<imgStatusTracker>
 {
 public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(imgStatusTracker)
-
   // aImage is the image that this status tracker will pass to the
   // imgRequestProxys in SyncNotify() and EmulateRequestFinished(), and must be
   // alive as long as this instance is, because we hold a weak reference to it.
   imgStatusTracker(mozilla::image::Image* aImage);
   ~imgStatusTracker();
 
   // Image-setter, for imgStatusTrackers created by imgRequest::Init, which
   // are created before their Image is created.  This method should only
@@ -190,32 +188,17 @@ public:
   bool IsMultipart() const { return mIsMultipart; }
 
   // Weak pointer getters - no AddRefs.
   inline mozilla::image::Image* GetImage() const { return mImage; }
 
   inline imgDecoderObserver* GetDecoderObserver() { return mTrackerObserver.get(); }
 
   imgStatusTracker* CloneForRecording();
-
-  struct StatusDiff
-  {
-    uint32_t mDiffState;
-    bool mUnblockedOnload;
-    bool mFoundError;
-    nsIntRect mInvalidRect;
-  };
-
-  // Calculate the difference between this and other, apply that difference to
-  // ourselves, and return it for passing to SyncNotifyDifference.
-  StatusDiff CalculateAndApplyDifference(imgStatusTracker* other);
-
-  // Notify for the difference found in CalculateAndApplyDifference. No
-  // decoding locks may be held.
-  void SyncNotifyDifference(StatusDiff diff);
+  void SyncAndSyncNotifyDifference(imgStatusTracker* other);
 
   nsIntRect GetInvalidRect() const { return mInvalidRect; }
 
 private:
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
   friend class imgStatusTrackerObserver;
   friend class imgStatusTrackerNotifyingObserver;
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -3865,24 +3865,16 @@ pref("image.mem.decode_bytes_at_a_time",
 
 // The longest time we can spend in an iteration of an async decode
 pref("image.mem.max_ms_before_yield", 5);
 
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 pref("image.mem.max_decoded_image_kb", 51200);
 
-// Whether we decode images on multiple background threads rather than the
-// foreground thread.
-pref("image.multithreaded_decoding.enabled", true);
-
-// How many threads we'll use for multithreaded decoding. If < 0, will be
-// automatically determined based on the system's number of cores.
-pref("image.multithreaded_decoding.limit", -1);
-
 // WebGL prefs
 pref("gl.msaa-level", 2);
 pref("webgl.force-enabled", false);
 pref("webgl.disabled", false);
 pref("webgl.shader_validator", true);
 pref("webgl.prefer-native-gl", false);
 pref("webgl.min_capability_mode", false);
 pref("webgl.disable-extensions", false);