Bug 716140 - Implement multithreaded decoding using a thread pool. r=seth
authorJoe Drew <joe@drew.ca>
Fri, 01 Mar 2013 18:17:24 -0500
changeset 132522 9e2bdda8c3ca3e5b2d24251df212ead22cf053b5
parent 132521 71fe5b69c83424992e8291ab2975576dc5505118
child 132523 1541b7a03e179f07fd9b15b77985444b2077b534
push idunknown
push userunknown
push dateunknown
reviewersseth
bugs716140
milestone22.0a1
Bug 716140 - Implement multithreaded decoding using a thread pool. r=seth
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/VectorImage.cpp
image/src/imgStatusTracker.cpp
image/src/imgStatusTracker.h
--- 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_REFCOUNTING(Decoder)
+  NS_INLINE_DECL_THREADSAFE_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,16 +92,21 @@ 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,30 +11,36 @@
 #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"
@@ -344,49 +350,50 @@ public:
 
 private:
   nsAutoPtr<ScaleRequest> mScaleRequest;
 };
 
 namespace mozilla {
 namespace image {
 
-/* static */ StaticRefPtr<RasterImage::DecodeWorker> RasterImage::DecodeWorker::sSingleton;
+/* static */ StaticRefPtr<RasterImage::DecodePool> RasterImage::DecodePool::sSingleton;
 static nsCOMPtr<nsIThread> sScaleWorkerThread = nullptr;
 
 #ifndef DEBUG
-NS_IMPL_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties)
+NS_IMPL_THREADSAFE_ISUPPORTS2(RasterImage, imgIContainer, nsIProperties)
 #else
-NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
-                   imgIContainerDebug)
+NS_IMPL_THREADSAFE_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;
@@ -413,17 +420,18 @@ 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.)
-    DecodeWorker::Singleton()->StopDecoding(this);
+    MutexAutoLock lock(mDecodingMutex);
+    DecodePool::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);
@@ -447,17 +455,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.
-  DecodeWorker::Singleton();
+  DecodePool::Singleton();
 }
 
 nsresult
 RasterImage::Init(const char* aMimeType,
                   uint32_t aFlags)
 {
   // We don't support re-initialization
   if (mInitialized)
@@ -774,17 +782,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;
 
@@ -1372,16 +1380,18 @@ 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;
 
@@ -1527,16 +1537,18 @@ 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;
@@ -1655,16 +1667,18 @@ 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 "
@@ -1716,30 +1730,31 @@ 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) {
-      DecodeWorker::Singleton()->RequestDecode(this);
+      DecodePool::Singleton()->RequestDecode(this);
     }
   }
 
   // Statistics
   total_source_bytes += aCount;
   if (mDiscardable)
     discardable_source_bytes += aCount;
   PR_LOG (GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
@@ -1773,45 +1788,48 @@ 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 = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
+    nsresult rv = DecodePool::Singleton()->DecodeUntilSizeAvailable(this);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If DecodeUntilSizeAvailable didn't finish the decode, let the decode worker
-  // finish decoding this image.
-  if (mDecoder) {
-    DecodeWorker::Singleton()->RequestDecode(this);
+  {
+    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();
   }
 
-  // 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,
@@ -1840,17 +1858,20 @@ 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.
-  FinishedSomeDecoding();
+  {
+    MutexAutoLock lock(mDecodingMutex);
+    FinishedSomeDecoding();
+  }
 
   return finalStatus;
 }
 
 nsresult
 RasterImage::OnImageDataAvailable(nsIRequest*,
                                   nsISupports*,
                                   nsIInputStream* aInStr,
@@ -1868,16 +1889,18 @@ 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!");
@@ -2423,16 +2446,18 @@ 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.
@@ -2648,17 +2673,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.)
-  DecodeWorker::Singleton()->StopDecoding(this);
+  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
@@ -2683,16 +2708,18 @@ 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;
@@ -2746,16 +2773,18 @@ 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) {
@@ -2776,96 +2805,117 @@ 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 = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
+    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;
     }
   }
 
   // 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);
 
-    DecodeWorker::Singleton()->DecodeABitOf(this);
+    DecodePool::Singleton()->DecodeABitOf(this);
 
     // DecodeABitOf can destroy mDecoder.
     if (mDecoder) {
       mDecoder->SetSynchronous(false);
     }
     return NS_OK;
   }
 
-  // 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);
+  if (!mDecoded) {
+    // If we get this far, dispatch the worker. We do this instead of starting
+    // any immediate decoding to guarantee that all our decode notifications are
+    // dispatched asynchronously, and to ensure we stay responsive.
+    DecodePool::Singleton()->RequestDecode(this);
+  }
 
   return NS_OK;
 }
 
 // Synchronously decodes as much data as possible
 nsresult
 RasterImage::SyncDecode()
 {
+  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 = DecodeWorker::Singleton()->DecodeUntilSizeAvailable(this);
+    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;
     }
@@ -2911,16 +2961,19 @@ 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)
@@ -3121,21 +3174,17 @@ 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();
-
-      // We're drawing this image, so indicate that we should decode it as soon
-      // as possible.
-      DecodeWorker::Singleton()->MarkAsASAP(this);
+    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);
   }
 
@@ -3199,16 +3248,17 @@ 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()) {
@@ -3233,16 +3283,18 @@ 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;
@@ -3250,17 +3302,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.
@@ -3306,16 +3358,17 @@ 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();
@@ -3378,16 +3431,18 @@ 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
@@ -3439,145 +3494,144 @@ RasterImage::FinishedSomeDecoding(eShutd
           int32_t KBps = int32_t(request->mImage->mBytesDecoded /
                                  (1024 * request->mDecodeTime.ToSeconds()));
           Telemetry::Accumulate(id, KBps);
         }
       }
     }
   }
 
-  // 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.
+  imgStatusTracker::StatusDiff diff;
   if (request) {
-    image->mStatusTracker->SyncAndSyncNotifyDifference(request->mStatusTracker);
-  } else {
-    image->mStatusTracker->SyncNotifyDecodeState();
+    diff = image->mStatusTracker->CalculateAndApplyDifference(request->mStatusTracker);
   }
 
-  // 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();
+  {
+    // 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);
     } else {
-      rv = image->RequestDecode();
+      image->mStatusTracker->SyncNotifyDecodeState();
     }
+
+    // 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;
 }
 
-/* static */ RasterImage::DecodeWorker*
-RasterImage::DecodeWorker::Singleton()
+NS_IMPL_THREADSAFE_ISUPPORTS1(RasterImage::DecodePool,
+                              nsIObserver)
+
+/* static */ RasterImage::DecodePool*
+RasterImage::DecodePool::Singleton()
 {
   if (!sSingleton) {
-    sSingleton = new DecodeWorker();
+    MOZ_ASSERT(NS_IsMainThread());
+    sSingleton = new DecodePool();
     ClearOnShutdown(&sSingleton);
   }
 
   return sSingleton;
 }
 
-RasterImage::DecodeWorker::~DecodeWorker()
+RasterImage::DecodePool::DecodePool()
+{
+  mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
+  if (mThreadPool) {
+    mThreadPool->SetName(NS_LITERAL_CSTRING("ImageDecoder"));
+    mThreadPool->SetThreadLimit(std::max(PR_GetNumberOfProcessors() - 1, 1));
+
+    nsCOMPtr<nsIObserverService> obsSvc = mozilla::services::GetObserverService();
+    if (obsSvc) {
+      obsSvc->AddObserver(this, "xpcom-shutdown-threads", false);
+    }
+  }
+}
+
+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)
 {
-  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();
+  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)
+{
+  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 (!mThreadPool) {
+      NS_DispatchToMainThread(job);
+    } else {
+      mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL);
+    }
   }
 }
 
 void
-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);
-
-  // If we're currently waiting on a new frame for this image, we can't do any
-  // decoding.
-  if (!aImg->mDecoder->NeedsNewFrame()) {
-    AddDecodeRequest(aImg->mDecodeRequest, aImg->mSourceData.Length() - aImg->mBytesDecoded);
-    EnsurePendingInEventLoop();
-  }
-}
-
-void
-RasterImage::DecodeWorker::DecodeABitOf(RasterImage* aImg)
+RasterImage::DecodePool::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()) {
@@ -3589,108 +3643,108 @@ RasterImage::DecodeWorker::DecodeABitOf(
         !aImg->mError &&
         !aImg->IsDecodeFinished() &&
         aImg->mSourceData.Length() > aImg->mBytesDecoded) {
       RequestDecode(aImg);
     }
   }
 }
 
-void
-RasterImage::DecodeWorker::EnsurePendingInEventLoop()
+/* static */ void
+RasterImage::DecodePool::StopDecoding(RasterImage* aImg)
 {
-  if (!mPendingInEventLoop) {
-    mPendingInEventLoop = true;
-    NS_DispatchToCurrentThread(this);
-  }
-}
-
-void
-RasterImage::DecodeWorker::StopDecoding(RasterImage* aImg)
-{
+  aImg->mDecodingMutex.AssertCurrentThreadOwns();
+
   // 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) {
-    if (aImg->mDecodeRequest->isInList()) {
-      aImg->mDecodeRequest->remove();
-    }
+    aImg->mDecodeRequest->mRequestStatus = DecodeRequest::REQUEST_STOPPED;
   }
 }
 
 NS_IMETHODIMP
-RasterImage::DecodeWorker::Run()
+RasterImage::DecodePool::DecodeJob::Run()
 {
-  // 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();
+  MutexAutoLock imglock(mImage->mDecodingMutex);
+
+  // If we were interrupted, we shouldn't do any work.
+  if (mRequest->mRequestStatus == DecodeRequest::REQUEST_STOPPED) {
+    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
+    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())) {
+    DecodeDoneWorker::NotifyFinishedSomeDecoding(mImage, mRequest);
+    return NS_OK;
+  }
+
+  // If we're a decode job that's been enqueued since a previous decode that
+  // still needs a new frame, we can't do anything. Wait until the
+  // FrameNeededWorker enqueues another frame.
+  if (mImage->mDecoder->NeedsNewFrame()) {
+    return NS_OK;
   }
 
-  Telemetry::Accumulate(Telemetry::IMAGE_DECODE_LATENCY_US,
-                        uint32_t((TimeStamp::Now() - eventStart).ToMicroseconds()));
+  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);
+  }
 
   return NS_OK;
 }
 
 nsresult
-RasterImage::DecodeWorker::DecodeUntilSizeAvailable(RasterImage* aImg)
+RasterImage::DecodePool::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()) {
@@ -3698,36 +3752,45 @@ RasterImage::DecodeWorker::DecodeUntilSi
   } else {
     rv = aImg->FinishedSomeDecoding();
   }
 
   return rv;
 }
 
 nsresult
-RasterImage::DecodeWorker::DecodeSomeOfImage(RasterImage* aImg,
-                                             DecodeType aDecodeType /* = DECODE_TYPE_NORMAL */,
-                                             uint32_t bytesToDecode /* = 0 */)
+RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg,
+                                           DecodeType aDecodeType /* = DECODE_TYPE_UNTIL_TIME */,
+                                           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 currently waiting on a new frame for this image, we can't do any
-  // decoding right now.
-  if (aImg->mDecoder->NeedsNewFrame()) {
+  // 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()) {
     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
@@ -3768,17 +3831,17 @@ RasterImage::DecodeWorker::DecodeSomeOfI
       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 (TimeStamp::Now() >= deadline)
+    if (aDecodeType == DECODE_TYPE_UNTIL_TIME && TimeStamp::Now() >= deadline)
       break;
   }
 
   if (aImg->mDecodeRequest) {
     aImg->mDecodeRequest->mDecodeTime += (TimeStamp::Now() - start);
     aImg->mDecodeRequest->mChunkCount += chunkCount;
   }
 
@@ -3813,29 +3876,33 @@ RasterImage::DecodeWorker::DecodeSomeOfI
 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) {
-    DecodeWorker::Singleton()->RequestDecode(mImage);
+  if (mImage->mDecoder) {
+    DecodePool::Singleton()->RequestDecode(mImage);
   }
 
   return NS_OK;
 }
 
 RasterImage::FrameNeededWorker::FrameNeededWorker(RasterImage* image)
  : mImage(image)
 {}
@@ -3846,26 +3913,28 @@ 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) {
-    DecodeWorker::Singleton()->RequestDecode(mImage);
+    // By definition, we're not done decoding, so enqueue us for more decoding.
+    DecodePool::Singleton()->RequestDecode(mImage);
   }
 
   return NS_OK;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -13,38 +13,39 @@
  * @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/RefPtr.h"
+#include "mozilla/Mutex.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} \
 }
@@ -382,170 +383,163 @@ 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 : public LinkedListElement<DecodeRequest>,
-                         public RefCounted<DecodeRequest>
+  struct 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;
   };
 
   /*
-   * DecodeWorker is a singleton class we use when decoding large images.
+   * DecodePool 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 DecodeWorker::RequestDecode()
+   * 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 DecodeWorker singleton to the event queue, if it's not already pending
+   * the DecodePool singleton to the event queue, if it's not already pending
    * there.
    *
-   * When the DecodeWorker is run from the event queue, it decodes the image (and
+   * 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.
-   *
-   * 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 DecodeWorker : public nsRunnable
+  class DecodePool : public nsIObserver
   {
   public:
-    static DecodeWorker* Singleton();
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIOBSERVER
+
+    static DecodePool* Singleton();
 
     /**
-     * Ask the DecodeWorker to asynchronously decode this image.
+     * Ask the DecodePool 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);
 
     /**
-     * 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
+     * Ask the DecodePool to stop decoding this image.  Internally, we also
      * call this function when we finish decoding an image.
      *
-     * Since the DecodeWorker keeps raw pointers to RasterImages, make sure you
+     * Since the DecodePool keeps raw pointers to RasterImages, make sure you
      * call this before a RasterImage is destroyed!
      */
-    void StopDecoding(RasterImage* aImg);
+    static 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);
 
-    NS_IMETHOD Run();
-
-    virtual ~DecodeWorker();
+    virtual ~DecodePool();
 
   private: /* statics */
-    static StaticRefPtr<DecodeWorker> sSingleton;
+    static StaticRefPtr<DecodePool> sSingleton;
 
   private: /* methods */
-    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);
+    DecodePool();
 
     enum DecodeType {
-      DECODE_TYPE_NORMAL,
-      DECODE_TYPE_UNTIL_SIZE
+      DECODE_TYPE_UNTIL_TIME,
+      DECODE_TYPE_UNTIL_SIZE,
+      DECODE_TYPE_UNTIL_DONE_BYTES
     };
 
     /* 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. */
+     * non-0, at most bytesToDecode bytes will be decoded. if aDecodeType is
+     * UNTIL_DONE_BYTES, decode until all bytesToDecode bytes are decoded.
+     */
     nsresult DecodeSomeOfImage(RasterImage* aImg,
-                               DecodeType aDecodeType = DECODE_TYPE_NORMAL,
+                               DecodeType aDecodeType = DECODE_TYPE_UNTIL_TIME,
                                uint32_t bytesToDecode = 0);
 
-    /* Create a new DecodeRequest suitable for doing some decoding and set it
-     * as aImg's mDecodeRequest. */
-    void CreateRequestForImage(RasterImage* aImg);
+    /* 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;
+    };
 
   private: /* members */
 
-    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;
+    nsCOMPtr<nsIThreadPool> mThreadPool;
   };
 
   class DecodeDoneWorker : public nsRunnable
   {
   public:
     /**
-     * Called by the DecodeWorker with an image when it's done some significant
+     * Called by the DecodePool 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();
@@ -558,17 +552,17 @@ private:
     nsRefPtr<RasterImage> mImage;
     nsRefPtr<DecodeRequest> mRequest;
   };
 
   class FrameNeededWorker : public nsRunnable
   {
   public:
     /**
-     * Called by the DecodeWorker with an image when it's been told by the
+     * Called by the DecodeJob 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();
@@ -749,52 +743,61 @@ 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,17 +383,18 @@ 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);
-    mStatusTracker->SyncAndSyncNotifyDifference(clone);
+    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
+    mStatusTracker->SyncNotifyDifference(diff);
   }
   return finalStatus;
 }
 
 nsresult
 VectorImage::OnImageDataAvailable(nsIRequest* aRequest,
                                   nsISupports* aContext,
                                   nsIInputStream* aInStr,
@@ -841,17 +842,18 @@ 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();
-    mStatusTracker->SyncAndSyncNotifyDifference(clone);
+    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
+    mStatusTracker->SyncNotifyDifference(diff);
   }
 
   // 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.
@@ -928,17 +930,18 @@ 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.
 
-    mStatusTracker->SyncAndSyncNotifyDifference(clone);
+    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
+    mStatusTracker->SyncNotifyDifference(diff);
   }
 
   EvaluateAnimation();
 }
 
 void
 VectorImage::OnSVGDocumentError()
 {
@@ -950,17 +953,18 @@ VectorImage::OnSVGDocumentError()
   mError = true;
 
   if (mStatusTracker) {
     nsRefPtr<imgStatusTracker> clone = mStatusTracker->CloneForRecording();
     imgDecoderObserver* observer = clone->GetDecoderObserver();
 
     // Unblock page load.
     observer->OnStopDecode(NS_ERROR_FAILURE);
-    mStatusTracker->SyncAndSyncNotifyDifference(clone);
+    imgStatusTracker::StatusDiff diff = mStatusTracker->CalculateAndApplyDifference(clone);
+    mStatusTracker->SyncNotifyDifference(diff);
   }
 }
 
 //------------------------------------------------------------------------------
 // nsIStreamListener method
 
 //******************************************************************************
 /* void onDataAvailable(in nsIRequest request, in nsISupports ctxt,
--- a/image/src/imgStatusTracker.cpp
+++ b/image/src/imgStatusTracker.cpp
@@ -512,34 +512,36 @@ imgStatusTracker::SyncNotifyState(nsTObs
     NOTIFY_IMAGE_OBSERVERS(OnStopDecode());
   }
 
   if (state & stateRequestStopped) {
     NOTIFY_IMAGE_OBSERVERS(OnStopRequest(hadLastPart));
   }
 }
 
-void
-imgStatusTracker::SyncAndSyncNotifyDifference(imgStatusTracker* other)
+imgStatusTracker::StatusDiff
+imgStatusTracker::CalculateAndApplyDifference(imgStatusTracker* other)
 {
-  LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncAndSyncNotifyDifference");
+  LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncAndCalculateDifference");
 
   // We must not modify or notify for the start-load state, which happens from Necko callbacks.
   uint32_t loadState = mState & stateRequestStarted;
-  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);
+
+  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);
 
   // Now that we've calculated the difference in state, synchronize our state
   // with the other tracker.
 
   // First, actually synchronize our state.
-  mInvalidRect = mInvalidRect.Union(other->mInvalidRect);
-  mState |= diffState | loadState;
-  if (unblockedOnload) {
+  diff.mInvalidRect = mInvalidRect.Union(other->mInvalidRect);
+  mState |= diff.mDiffState | loadState;
+  if (diff.mUnblockedOnload) {
     mState &= ~stateBlockingOnload;
   }
   mImageStatus = other->mImageStatus;
   mIsMultipart = other->mIsMultipart;
   mHadLastPart = other->mHadLastPart;
   mImageStatus |= other->mImageStatus;
 
   // The error state is sticky and overrides all other bits.
@@ -547,36 +549,44 @@ imgStatusTracker::SyncAndSyncNotifyDiffe
     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;
     }
   }
 
-  SyncNotifyState(mConsumers, !!mImage, diffState, mInvalidRect, mHadLastPart);
+  // Reset the invalid rectangles for another go.
+  other->mInvalidRect.SetEmpty();
+  mInvalidRect.SetEmpty();
+
+  return diff;
+}
 
-  if (unblockedOnload) {
+void
+imgStatusTracker::SyncNotifyDifference(imgStatusTracker::StatusDiff diff)
+{
+  LOG_SCOPE(GetImgLog(), "imgStatusTracker::SyncNotifyDifference");
+
+  SyncNotifyState(mConsumers, !!mImage, diff.mDiffState, diff.mInvalidRect, mHadLastPart);
+
+  if (diff.mUnblockedOnload) {
     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);
       }
     }
   }
 
-  // Reset the invalid rectangles for another go.
-  other->mInvalidRect.SetEmpty();
-  mInvalidRect.SetEmpty();
-
-  if (foundError) {
+  if (diff.mFoundError) {
     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,19 +46,21 @@ 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 : public mozilla::RefCounted<imgStatusTracker>
+class 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
@@ -188,17 +190,32 @@ 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();
-  void SyncAndSyncNotifyDifference(imgStatusTracker* other);
+
+  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);
 
   nsIntRect GetInvalidRect() const { return mInvalidRect; }
 
 private:
   friend class imgStatusNotifyRunnable;
   friend class imgRequestNotifyRunnable;
   friend class imgStatusTrackerObserver;
   friend class imgStatusTrackerNotifyingObserver;