Bug 1282259 - Run IDecodingTasks instead of Decoders directly in image::DecodePool. r=dholbert
authorSeth Fowler <mark.seth.fowler@gmail.com>
Wed, 29 Jun 2016 13:43:19 -0700
changeset 343224 d51379e5475d29c015fa93e5698439f80ee31938
parent 343223 1e4b0050e62a6bc10f12f899114ee66a6b3bec12
child 343225 a5a9585754b1810fac7711d3ee1de8fe06016489
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1282259
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1282259 - Run IDecodingTasks instead of Decoders directly in image::DecodePool. r=dholbert
image/DecodePool.cpp
image/DecodePool.h
image/Decoder.cpp
image/Decoder.h
image/IDecodingTask.cpp
image/IDecodingTask.h
image/ImageOps.cpp
image/RasterImage.cpp
image/moz.build
image/test/gtest/TestDecoders.cpp
image/test/gtest/TestMetadata.cpp
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -19,101 +19,29 @@
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
 #include "gfxPrefs.h"
 
 #include "Decoder.h"
+#include "IDecodingTask.h"
 #include "RasterImage.h"
 
 using std::max;
 using std::min;
 
 namespace mozilla {
 namespace image {
 
 ///////////////////////////////////////////////////////////////////////////////
 // Helper runnables.
 ///////////////////////////////////////////////////////////////////////////////
 
-class NotifyProgressWorker : public Runnable
-{
-public:
-  /**
-   * Called by the DecodePool when it's done some significant portion of
-   * decoding, so that progress can be recorded and notifications can be sent.
-   */
-  static void Dispatch(RasterImage* aImage,
-                       Progress aProgress,
-                       const nsIntRect& aInvalidRect,
-                       SurfaceFlags aSurfaceFlags)
-  {
-    MOZ_ASSERT(aImage);
-
-    nsCOMPtr<nsIRunnable> worker =
-      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aSurfaceFlags);
-    NS_DispatchToMainThread(worker);
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mImage->NotifyProgress(mProgress, mInvalidRect, mSurfaceFlags);
-    return NS_OK;
-  }
-
-private:
-  NotifyProgressWorker(RasterImage* aImage,
-                       Progress aProgress,
-                       const nsIntRect& aInvalidRect,
-                       SurfaceFlags aSurfaceFlags)
-    : mImage(aImage)
-    , mProgress(aProgress)
-    , mInvalidRect(aInvalidRect)
-    , mSurfaceFlags(aSurfaceFlags)
-  { }
-
-  RefPtr<RasterImage> mImage;
-  const Progress mProgress;
-  const nsIntRect mInvalidRect;
-  const SurfaceFlags mSurfaceFlags;
-};
-
-class NotifyDecodeCompleteWorker : public Runnable
-{
-public:
-  /**
-   * Called by the DecodePool when decoding is complete, so that final cleanup
-   * can be performed.
-   */
-  static void Dispatch(Decoder* aDecoder)
-  {
-    MOZ_ASSERT(aDecoder);
-
-    nsCOMPtr<nsIRunnable> worker = new NotifyDecodeCompleteWorker(aDecoder);
-    NS_DispatchToMainThread(worker);
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    mDecoder->GetImage()->FinalizeDecoder(mDecoder);
-    return NS_OK;
-  }
-
-private:
-  explicit NotifyDecodeCompleteWorker(Decoder* aDecoder)
-    : mDecoder(aDecoder)
-  { }
-
-  RefPtr<Decoder> mDecoder;
-};
-
 #ifdef MOZ_NUWA_PROCESS
 
 class RegisterDecodeIOThreadWithNuwaRunnable : public Runnable
 {
 public:
   NS_IMETHOD Run()
   {
     NuwaMarkCurrentThread(static_cast<void(*)(void*)>(nullptr), nullptr);
@@ -131,21 +59,21 @@ public:
 /* static */ StaticRefPtr<DecodePool> DecodePool::sSingleton;
 /* static */ uint32_t DecodePool::sNumCores = 0;
 
 NS_IMPL_ISUPPORTS(DecodePool, nsIObserver)
 
 struct Work
 {
   enum class Type {
-    DECODE,
+    TASK,
     SHUTDOWN
   } mType;
 
-  RefPtr<Decoder> mDecoder;
+  RefPtr<IDecodingTask> mTask;
 };
 
 class DecodePoolImpl
 {
 public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(DecodePoolImpl)
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodePoolImpl)
 
@@ -184,82 +112,81 @@ public:
   void RequestShutdown()
   {
     MonitorAutoLock lock(mMonitor);
     mShuttingDown = true;
     mMonitor.NotifyAll();
   }
 
   /// Pushes a new decode work item.
-  void PushWork(Decoder* aDecoder)
+  void PushWork(IDecodingTask* aTask)
   {
-    MOZ_ASSERT(aDecoder);
-    RefPtr<Decoder> decoder(aDecoder);
+    MOZ_ASSERT(aTask);
+    RefPtr<IDecodingTask> task(aTask);
 
     MonitorAutoLock lock(mMonitor);
 
     if (mShuttingDown) {
       // Drop any new work on the floor if we're shutting down.
       return;
     }
 
-    if (aDecoder->IsMetadataDecode()) {
-      mMetadataDecodeQueue.AppendElement(Move(decoder));
+    if (task->Priority() == TaskPriority::eHigh) {
+      mHighPriorityQueue.AppendElement(Move(task));
     } else {
-      mFullDecodeQueue.AppendElement(Move(decoder));
+      mLowPriorityQueue.AppendElement(Move(task));
     }
 
     mMonitor.Notify();
   }
 
   /// Pops a new work item, blocking if necessary.
   Work PopWork()
   {
     MonitorAutoLock lock(mMonitor);
 
     do {
-      // Prioritize metadata decodes over full decodes.
-      if (!mMetadataDecodeQueue.IsEmpty()) {
-        return PopWorkFromQueue(mMetadataDecodeQueue);
+      if (!mHighPriorityQueue.IsEmpty()) {
+        return PopWorkFromQueue(mHighPriorityQueue);
       }
 
-      if (!mFullDecodeQueue.IsEmpty()) {
-        return PopWorkFromQueue(mFullDecodeQueue);
+      if (!mLowPriorityQueue.IsEmpty()) {
+        return PopWorkFromQueue(mLowPriorityQueue);
       }
 
       if (mShuttingDown) {
         Work work;
         work.mType = Work::Type::SHUTDOWN;
         return work;
       }
 
       // Nothing to do; block until some work is available.
       mMonitor.Wait();
     } while (true);
   }
 
 private:
   ~DecodePoolImpl() { }
 
-  Work PopWorkFromQueue(nsTArray<RefPtr<Decoder>>& aQueue)
+  Work PopWorkFromQueue(nsTArray<RefPtr<IDecodingTask>>& aQueue)
   {
     Work work;
-    work.mType = Work::Type::DECODE;
-    work.mDecoder = aQueue.LastElement().forget();
+    work.mType = Work::Type::TASK;
+    work.mTask = aQueue.LastElement().forget();
     aQueue.RemoveElementAt(aQueue.Length() - 1);
 
     return work;
   }
 
   nsThreadPoolNaming mThreadNaming;
 
   // mMonitor guards the queues and mShuttingDown.
   Monitor mMonitor;
-  nsTArray<RefPtr<Decoder>> mMetadataDecodeQueue;
-  nsTArray<RefPtr<Decoder>> mFullDecodeQueue;
+  nsTArray<RefPtr<IDecodingTask>> mHighPriorityQueue;
+  nsTArray<RefPtr<IDecodingTask>> mLowPriorityQueue;
   bool mShuttingDown;
 };
 
 class DecodePoolWorker : public Runnable
 {
 public:
   explicit DecodePoolWorker(DecodePoolImpl* aImpl) : mImpl(aImpl) { }
 
@@ -270,18 +197,18 @@ public:
     mImpl->InitCurrentThread();
 
     nsCOMPtr<nsIThread> thisThread;
     nsThreadManager::get()->GetCurrentThread(getter_AddRefs(thisThread));
 
     do {
       Work work = mImpl->PopWork();
       switch (work.mType) {
-        case Work::Type::DECODE:
-          DecodePool::Singleton()->Decode(work.mDecoder);
+        case Work::Type::TASK:
+          work.mTask->Run();
           break;
 
         case Work::Type::SHUTDOWN:
           DecodePoolImpl::ShutdownThread(thisThread);
           return NS_OK;
 
         default:
           MOZ_ASSERT_UNREACHABLE("Unknown work type");
@@ -404,100 +331,46 @@ DecodePool::Observe(nsISupports*, const 
   if (ioThread) {
     ioThread->Shutdown();
   }
 
   return NS_OK;
 }
 
 void
-DecodePool::AsyncDecode(Decoder* aDecoder)
+DecodePool::AsyncRun(IDecodingTask* aTask)
 {
-  MOZ_ASSERT(aDecoder);
-  mImpl->PushWork(aDecoder);
+  MOZ_ASSERT(aTask);
+  mImpl->PushWork(aTask);
 }
 
 void
-DecodePool::SyncDecodeIfSmall(Decoder* aDecoder)
+DecodePool::SyncRunIfPreferred(IDecodingTask* aTask)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aDecoder);
+  MOZ_ASSERT(aTask);
 
-  if (aDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime())) {
-    Decode(aDecoder);
+  if (aTask->ShouldPreferSyncRun()) {
+    aTask->Run();
     return;
   }
 
-  AsyncDecode(aDecoder);
+  AsyncRun(aTask);
 }
 
 void
-DecodePool::SyncDecodeIfPossible(Decoder* aDecoder)
+DecodePool::SyncRunIfPossible(IDecodingTask* aTask)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  Decode(aDecoder);
+  MOZ_ASSERT(aTask);
+  aTask->Run();
 }
 
 already_AddRefed<nsIEventTarget>
 DecodePool::GetIOEventTarget()
 {
   MutexAutoLock threadPoolLock(mMutex);
   nsCOMPtr<nsIEventTarget> target = do_QueryInterface(mIOThread);
   return target.forget();
 }
 
-void
-DecodePool::Decode(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-
-  nsresult rv = aDecoder->Decode();
-
-  if (NS_SUCCEEDED(rv) && !aDecoder->GetDecodeDone()) {
-    // If this isn't a metadata decode, notify for the progress we've made so
-    // far. It's important that metadata decode results are delivered
-    // atomically, so for those decodes we wait until NotifyDecodeComplete.
-    if (aDecoder->HasProgress() && !aDecoder->IsMetadataDecode()) {
-      NotifyProgress(aDecoder);
-    }
-    // The decoder will ensure that a new worker gets enqueued to continue
-    // decoding when more data is available.
-  } else {
-    NotifyDecodeComplete(aDecoder);
-  }
-}
-
-void
-DecodePool::NotifyProgress(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-  MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode());
-
-  if (!NS_IsMainThread() ||
-      (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
-    NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
-                                   aDecoder->TakeProgress(),
-                                   aDecoder->TakeInvalidRect(),
-                                   aDecoder->GetSurfaceFlags());
-    return;
-  }
-
-  aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
-                                       aDecoder->TakeInvalidRect(),
-                                       aDecoder->GetSurfaceFlags());
-}
-
-void
-DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
-{
-  MOZ_ASSERT(aDecoder);
-
-  if (!NS_IsMainThread() ||
-      (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
-    NotifyDecodeCompleteWorker::Dispatch(aDecoder);
-    return;
-  }
-
-  aDecoder->GetImage()->FinalizeDecoder(aDecoder);
-}
-
 } // namespace image
 } // namespace mozilla
--- a/image/DecodePool.h
+++ b/image/DecodePool.h
@@ -21,16 +21,17 @@
 class nsIThread;
 class nsIThreadPool;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
 class DecodePoolImpl;
+class IDecodingTask;
 
 /**
  * DecodePool is a singleton class that manages decoding of raster images. It
  * owns a pool of image decoding threads that are used for asynchronous
  * decoding.
  *
  * DecodePool allows callers to run a decoder, handling management of the
  * decoder's lifecycle and whether it executes on the main thread,
@@ -48,60 +49,54 @@ public:
 
   /// Returns the singleton instance.
   static DecodePool* Singleton();
 
   /// @return the number of processor cores we have available. This is not the
   /// same as the number of decoding threads we're actually using.
   static uint32_t NumberOfCores();
 
-  /// Ask the DecodePool to run @aDecoder asynchronously and return immediately.
-  void AsyncDecode(Decoder* aDecoder);
+  /// Ask the DecodePool to run @aTask asynchronously and return immediately.
+  void AsyncRun(IDecodingTask* aTask);
 
   /**
-   * Run @aDecoder synchronously if the image it's decoding is small. If the
-   * image is too large, or if the source data isn't complete yet, run @aDecoder
-   * asynchronously instead.
+   * Run @aTask synchronously if the task would prefer it. It's up to the task
+   * itself to make this decision; @see IDecodingTask::ShouldPreferSyncRun(). If
+   * @aTask doesn't prefer it, just run @aTask asynchronously and return
+   * immediately.
    */
-  void SyncDecodeIfSmall(Decoder* aDecoder);
+  void SyncRunIfPreferred(IDecodingTask* aTask);
 
   /**
-   * Run aDecoder synchronously if at all possible. If it can't complete
-   * synchronously because the source data isn't complete, asynchronously decode
-   * the rest.
+   * Run @aTask synchronously. This does not guarantee that @aTask will complete
+   * synchronously. If, for example, @aTask doesn't yet have the data it needs to
+   * run synchronously, it may recover by scheduling an async task to finish up
+   * the work when the remaining data is available.
    */
-  void SyncDecodeIfPossible(Decoder* aDecoder);
+  void SyncRunIfPossible(IDecodingTask* aTask);
 
   /**
    * Returns an event target interface to the DecodePool's I/O thread. Callers
    * who want to deliver data to workers on the DecodePool can use this event
    * target.
    *
    * @return An nsIEventTarget interface to the thread pool's I/O thread.
    */
   already_AddRefed<nsIEventTarget> GetIOEventTarget();
 
-  /**
-   * Notify about progress on aDecoder.
-   */
-  void NotifyProgress(Decoder* aDecoder);
-
 private:
   friend class DecodePoolWorker;
 
   DecodePool();
   virtual ~DecodePool();
 
-  void Decode(Decoder* aDecoder);
-  void NotifyDecodeComplete(Decoder* aDecoder);
-
   static StaticRefPtr<DecodePool> sSingleton;
   static uint32_t sNumCores;
 
-  RefPtr<DecodePoolImpl>    mImpl;
+  RefPtr<DecodePoolImpl> mImpl;
 
   // mMutex protects mThreads and mIOThread.
   Mutex                         mMutex;
   nsTArray<nsCOMPtr<nsIThread>> mThreads;
   nsCOMPtr<nsIThread>           mIOThread;
 };
 
 } // namespace image
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Decoder.h"
 
 #include "mozilla/gfx/2D.h"
 #include "DecodePool.h"
 #include "GeckoProfiler.h"
+#include "IDecodingTask.h"
 #include "imgIContainer.h"
 #include "nsProxyRelease.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "mozilla/Telemetry.h"
 
 using mozilla::gfx::IntSize;
 using mozilla::gfx::SurfaceFormat;
@@ -76,28 +77,25 @@ Decoder::Init()
 
   // Implementation-specific initialization
   InitInternal();
 
   mInitialized = true;
 }
 
 nsresult
-Decoder::Decode(IResumable* aOnResume)
+Decoder::Decode(NotNull<IResumable*> aOnResume)
 {
   MOZ_ASSERT(mInitialized, "Should be initialized here");
   MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
 
-  // If no IResumable was provided, default to |this|.
-  IResumable* onResume = aOnResume ? aOnResume : this;
-
   // We keep decoding chunks until the decode completes or there are no more
   // chunks available.
   while (!GetDecodeDone() && !HasError()) {
-    auto newState = mIterator->AdvanceOrScheduleResume(onResume);
+    auto newState = mIterator->AdvanceOrScheduleResume(aOnResume.get());
 
     if (newState == SourceBufferIterator::WAITING) {
       // We can't continue because the rest of the data hasn't arrived from the
       // network yet. We don't have to do anything special; the
       // SourceBufferIterator will ensure that Decode() gets called again on a
       // DecodePool thread when more data is available.
       return NS_OK;
     }
@@ -118,24 +116,16 @@ Decoder::Decode(IResumable* aOnResume)
 
     Write(mIterator->Data(), mIterator->Length());
   }
 
   CompleteDecode();
   return HasError() ? NS_ERROR_FAILURE : NS_OK;
 }
 
-void
-Decoder::Resume()
-{
-  DecodePool* decodePool = DecodePool::Singleton();
-  MOZ_ASSERT(decodePool);
-  decodePool->AsyncDecode(this);
-}
-
 bool
 Decoder::ShouldSyncDecode(size_t aByteLimit)
 {
   MOZ_ASSERT(aByteLimit > 0);
   MOZ_ASSERT(mIterator, "Should have a SourceBufferIterator");
 
   return mIterator->RemainingBytesIsNoMoreThan(aByteLimit);
 }
@@ -450,17 +440,17 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   if (!ShouldSendPartialInvalidations() && mFrameCount == 1) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
   }
 
   // If we are going to keep decoding we should notify now about the first frame being done.
   if (mImage && mFrameCount == 1 && HasAnimation()) {
     MOZ_ASSERT(HasProgress());
-    DecodePool::Singleton()->NotifyProgress(this);
+    IDecodingTask::NotifyProgress(WrapNotNull(this));
   }
 }
 
 void
 Decoder::PostInvalidation(const nsIntRect& aRect,
                           const Maybe<nsIntRect>& aRectAtTargetSize
                             /* = Nothing() */)
 {
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_Decoder_h
 #define mozilla_image_Decoder_h
 
 #include "FrameAnimator.h"
 #include "RasterImage.h"
+#include "mozilla/NotNull.h"
 #include "mozilla/RefPtr.h"
 #include "DecodePool.h"
 #include "DecoderFlags.h"
 #include "Downscaler.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "SourceBuffer.h"
 #include "SurfaceFlags.h"
@@ -20,38 +21,37 @@
 namespace mozilla {
 
 namespace Telemetry {
   enum ID : uint32_t;
 } // namespace Telemetry
 
 namespace image {
 
-class Decoder : public IResumable
+class Decoder
 {
 public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder)
 
   explicit Decoder(RasterImage* aImage);
 
   /**
    * Initialize an image decoder. Decoders may not be re-initialized.
    */
   void Init();
 
   /**
    * Decodes, reading all data currently available in the SourceBuffer.
    *
    * If more data is needed, Decode() will schedule @aOnResume to be called when
-   * more data is available. If @aOnResume is null or unspecified, the default
-   * implementation resumes decoding on a DecodePool thread. Most callers should
-   * use the default implementation.
+   * more data is available.
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    */
-  nsresult Decode(IResumable* aOnResume = nullptr);
+  nsresult Decode(NotNull<IResumable*> aOnResume);
 
   /**
    * Given a maximum number of bytes we're willing to decode, @aByteLimit,
    * returns true if we should attempt to run this decoder synchronously.
    */
   bool ShouldSyncDecode(size_t aByteLimit);
 
   /**
@@ -82,22 +82,16 @@ public:
   /**
    * Returns true if there's any progress to report.
    */
   bool HasProgress() const
   {
     return mProgress != NoProgress || !mInvalidRect.IsEmpty();
   }
 
-  // We're not COM-y, so we don't get refcounts by default
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Decoder, override)
-
-  // Implement IResumable.
-  virtual void Resume() override;
-
   /*
    * State.
    */
 
   /**
    * If we're doing a metadata decode, we only decode the image's headers, which
    * is enough to determine the image's intrinsic size. A metadata decode is
    * enabled by calling SetMetadataDecode() *before* calling Init().
new file mode 100644
--- /dev/null
+++ b/image/IDecodingTask.cpp
@@ -0,0 +1,154 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IDecodingTask.h"
+
+#include "gfxPrefs.h"
+#include "nsThreadUtils.h"
+
+#include "Decoder.h"
+#include "DecodePool.h"
+#include "RasterImage.h"
+
+namespace mozilla {
+namespace image {
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for sending notifications to the image associated with a decoder.
+///////////////////////////////////////////////////////////////////////////////
+
+/* static */ void
+IDecodingTask::NotifyProgress(NotNull<Decoder*> aDecoder)
+{
+  MOZ_ASSERT(aDecoder->HasProgress() && !aDecoder->IsMetadataDecode());
+
+  // Synchronously notify if we can.
+  if (NS_IsMainThread() &&
+      !(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
+    aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
+                                         aDecoder->TakeInvalidRect(),
+                                         aDecoder->GetSurfaceFlags());
+    return;
+  }
+
+  // We're forced to notify asynchronously.
+  NotNull<RefPtr<Decoder>> decoder = aDecoder;
+  NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
+    decoder->GetImage()->NotifyProgress(decoder->TakeProgress(),
+                                        decoder->TakeInvalidRect(),
+                                        decoder->GetSurfaceFlags());
+  }));
+}
+
+static void
+NotifyDecodeComplete(NotNull<Decoder*> aDecoder)
+{
+  // Synchronously notify if we can.
+  if (NS_IsMainThread() &&
+      !(aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
+    aDecoder->GetImage()->FinalizeDecoder(aDecoder);
+    return;
+  }
+
+  // We're forced to notify asynchronously.
+  NotNull<RefPtr<Decoder>> decoder = aDecoder;
+  NS_DispatchToMainThread(NS_NewRunnableFunction([=]() -> void {
+    decoder->GetImage()->FinalizeDecoder(decoder.get());
+  }));
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// IDecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+void
+IDecodingTask::Resume()
+{
+  DecodePool::Singleton()->AsyncRun(this);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// DecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+DecodingTask::DecodingTask(NotNull<Decoder*> aDecoder)
+  : mDecoder(aDecoder)
+{
+  MOZ_ASSERT(!mDecoder->IsMetadataDecode(),
+             "Use MetadataDecodingTask for metadata decodes");
+}
+
+void
+DecodingTask::Run()
+{
+  nsresult rv = mDecoder->Decode(WrapNotNull(this));
+
+  if (NS_SUCCEEDED(rv) && !mDecoder->GetDecodeDone()) {
+    // Notify for the progress we've made so far.
+    if (mDecoder->HasProgress()) {
+      NotifyProgress(mDecoder);
+    }
+
+    // We don't need to do anything else for this case. The decoder itself will
+    // ensure that we get reenqueued when more data is available.
+    return;
+  }
+
+  NotifyDecodeComplete(mDecoder);
+}
+
+bool
+DecodingTask::ShouldPreferSyncRun() const
+{
+  return mDecoder->ShouldSyncDecode(gfxPrefs::ImageMemDecodeBytesAtATime());
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// MetadataDecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+MetadataDecodingTask::MetadataDecodingTask(NotNull<Decoder*> aDecoder)
+  : mDecoder(aDecoder)
+{
+  MOZ_ASSERT(mDecoder->IsMetadataDecode(),
+             "Use DecodingTask for non-metadata decodes");
+}
+
+void
+MetadataDecodingTask::Run()
+{
+  nsresult rv = mDecoder->Decode(WrapNotNull(this));
+
+  if (NS_SUCCEEDED(rv) && !mDecoder->GetDecodeDone()) {
+    // It's important that metadata decode results are delivered atomically, so
+    // we'll wait until NotifyDecodeComplete() to report any progress.  We don't
+    // need to do anything else for this case. The decoder itself will ensure
+    // that we get reenqueued when more data is available.
+    return;
+  }
+
+  NotifyDecodeComplete(mDecoder);
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// AnonymousDecodingTask implementation.
+///////////////////////////////////////////////////////////////////////////////
+
+AnonymousDecodingTask::AnonymousDecodingTask(NotNull<Decoder*> aDecoder)
+  : mDecoder(aDecoder)
+{ }
+
+void
+AnonymousDecodingTask::Run()
+{
+  mDecoder->Decode(WrapNotNull(this));
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/IDecodingTask.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * An interface for tasks which can execute on the ImageLib DecodePool, and
+ * various implementations.
+ */
+
+#ifndef mozilla_image_IDecodingTask_h
+#define mozilla_image_IDecodingTask_h
+
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+
+#include "SourceBuffer.h"
+
+namespace mozilla {
+namespace image {
+
+class Decoder;
+
+/// A priority hint that DecodePool can use when scheduling an IDecodingTask.
+enum class TaskPriority : uint8_t
+{
+  eLow,
+  eHigh
+};
+
+/**
+ * An interface for tasks which can execute on the ImageLib DecodePool.
+ */
+class IDecodingTask : public IResumable
+{
+public:
+  /// Run the task.
+  virtual void Run() = 0;
+
+  /// @return true if, given the option, this task prefers to run synchronously.
+  virtual bool ShouldPreferSyncRun() const = 0;
+
+  /// @return a priority hint that DecodePool can use when scheduling this task.
+  virtual TaskPriority Priority() const = 0;
+
+  /// A default implementation of IResumable which resubmits the task to the
+  /// DecodePool. Subclasses can override this if they need different behavior.
+  void Resume() override;
+
+  // Notify the Image associated with a Decoder of its progress, sending a
+  // runnable to the main thread if necessary.
+  // XXX(seth): This is a hack that will be removed soon.
+  static void NotifyProgress(NotNull<Decoder*> aDecoder);
+
+protected:
+  virtual ~IDecodingTask() { }
+};
+
+
+/**
+ * An IDecodingTask implementation for full decodes of images.
+ */
+class DecodingTask final : public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DecodingTask, override)
+
+  explicit DecodingTask(NotNull<Decoder*> aDecoder);
+
+  void Run() override;
+  bool ShouldPreferSyncRun() const override;
+
+  // Full decodes are low priority compared to metadata decodes because they
+  // don't block layout or page load.
+  TaskPriority Priority() const override { return TaskPriority::eLow; }
+
+private:
+  virtual ~DecodingTask() { }
+
+  NotNull<RefPtr<Decoder>> mDecoder;
+};
+
+
+/**
+ * An IDecodingTask implementation for metadata decodes of images.
+ */
+class MetadataDecodingTask final : public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataDecodingTask, override)
+
+  explicit MetadataDecodingTask(NotNull<Decoder*> aDecoder);
+
+  void Run() override;
+
+  // Metadata decodes are very fast (since they only need to examine an image's
+  // header) so there's no reason to refuse to run them synchronously if the
+  // caller will allow us to.
+  bool ShouldPreferSyncRun() const override { return true; }
+
+  // Metadata decodes run at the highest priority because they block layout and
+  // page load.
+  TaskPriority Priority() const override { return TaskPriority::eHigh; }
+
+private:
+  virtual ~MetadataDecodingTask() { }
+
+  NotNull<RefPtr<Decoder>> mDecoder;
+};
+
+
+/**
+ * An IDecodingTask implementation for anonymous decoders - that is, decoders
+ * with no associated Image object.
+ */
+class AnonymousDecodingTask final : public IDecodingTask
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AnonymousDecodingTask, override)
+
+  explicit AnonymousDecodingTask(NotNull<Decoder*> aDecoder);
+
+  void Run() override;
+
+  bool ShouldPreferSyncRun() const override { return true; }
+  TaskPriority Priority() const override { return TaskPriority::eLow; }
+
+  // Anonymous decoders normally get all their data at once. We have tests where
+  // they don't; in these situations, the test re-runs them manually. So no
+  // matter what, we don't want to resume by posting a task to the DecodePool.
+  void Resume() override { }
+
+private:
+  virtual ~AnonymousDecodingTask() { }
+
+  NotNull<RefPtr<Decoder>> mDecoder;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_IDecodingTask_h
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -7,16 +7,17 @@
 #include "ImageOps.h"
 
 #include "ClippedImage.h"
 #include "DecodePool.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "DynamicImage.h"
 #include "FrozenImage.h"
+#include "IDecodingTask.h"
 #include "Image.h"
 #include "imgIContainer.h"
 #include "mozilla/gfx/2D.h"
 #include "nsStreamUtils.h"
 #include "OrientedImage.h"
 #include "SourceBuffer.h"
 
 using namespace mozilla::gfx;
@@ -122,17 +123,18 @@ ImageOps::DecodeToSurface(nsIInputStream
                                            sourceBuffer,
                                            Nothing(),
                                            ToSurfaceFlags(aFlags));
   if (!decoder) {
     return nullptr;
   }
 
   // Run the decoder synchronously.
-  decoder->Decode();
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
+  task->Run();
   if (!decoder->GetDecodeDone() || decoder->HasError()) {
     return nullptr;
   }
 
   // Pull out the surface.
   RawAccessFrameRef frame = decoder->GetCurrentFrameRef();
   if (!frame) {
     return nullptr;
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -10,16 +10,17 @@
 #include "RasterImage.h"
 
 #include "gfxPlatform.h"
 #include "nsComponentManagerUtils.h"
 #include "nsError.h"
 #include "Decoder.h"
 #include "prenv.h"
 #include "prsystem.h"
+#include "IDecodingTask.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
 #include "LookupResult.h"
 #include "nsIConsoleService.h"
 #include "nsIInputStream.h"
 #include "nsIScriptError.h"
 #include "nsISupportsPrimitives.h"
@@ -1225,43 +1226,43 @@ RasterImage::RequestDecodeForSize(const 
   // Look up the first frame of the image, which will implicitly start decoding
   // if it's not available right now.
   LookupFrame(0, aSize, flags);
 
   return NS_OK;
 }
 
 static void
-LaunchDecoder(Decoder* aDecoder,
-              RasterImage* aImage,
-              uint32_t aFlags,
-              bool aHaveSourceData)
+LaunchDecodingTask(IDecodingTask* aTask,
+                   RasterImage* aImage,
+                   uint32_t aFlags,
+                   bool aHaveSourceData)
 {
   if (aHaveSourceData) {
     // If we have all the data, we can sync decode if requested.
     if (aFlags & imgIContainer::FLAG_SYNC_DECODE) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfPossible",
+      PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPossible",
         js::ProfileEntry::Category::GRAPHICS,
         "%s", aImage->GetURIString().get());
-      DecodePool::Singleton()->SyncDecodeIfPossible(aDecoder);
+      DecodePool::Singleton()->SyncRunIfPossible(aTask);
       return;
     }
 
     if (aFlags & imgIContainer::FLAG_SYNC_DECODE_IF_FAST) {
-      PROFILER_LABEL_PRINTF("DecodePool", "SyncDecodeIfSmall",
+      PROFILER_LABEL_PRINTF("DecodePool", "SyncRunIfPreferred",
         js::ProfileEntry::Category::GRAPHICS,
         "%s", aImage->GetURIString().get());
-      DecodePool::Singleton()->SyncDecodeIfSmall(aDecoder);
+      DecodePool::Singleton()->SyncRunIfPreferred(aTask);
       return;
     }
   }
 
   // Perform an async decode. We also take this path if we don't have all the
   // source data yet, since sync decoding is impossible in that situation.
-  DecodePool::Singleton()->AsyncDecode(aDecoder);
+  DecodePool::Singleton()->AsyncRun(aTask);
 }
 
 NS_IMETHODIMP
 RasterImage::Decode(const IntSize& aSize, uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
@@ -1331,17 +1332,18 @@ RasterImage::Decode(const IntSize& aSize
                                                      /* aFrameNum = */ 0));
   if (outcome != InsertOutcome::SUCCESS) {
     return NS_ERROR_FAILURE;
   }
 
   mDecodeCount++;
 
   // We're ready to decode; start the decoder.
-  LaunchDecoder(decoder, this, aFlags, mHasSourceData);
+  RefPtr<IDecodingTask> task = new DecodingTask(WrapNotNull(decoder));
+  LaunchDecodingTask(task, this, aFlags, mHasSourceData);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RasterImage::DecodeMetadata(uint32_t aFlags)
 {
   if (mError) {
     return NS_ERROR_FAILURE;
@@ -1355,17 +1357,18 @@ RasterImage::DecodeMetadata(uint32_t aFl
                                           mRequestedSampleSize);
 
   // Make sure DecoderFactory was able to create a decoder successfully.
   if (!decoder) {
     return NS_ERROR_FAILURE;
   }
 
   // We're ready to decode; start the decoder.
-  LaunchDecoder(decoder, this, aFlags, mHasSourceData);
+  RefPtr<IDecodingTask> task = new MetadataDecodingTask(WrapNotNull(decoder));
+  LaunchDecodingTask(task, this, aFlags, mHasSourceData);
   return NS_OK;
 }
 
 void
 RasterImage::RecoverFromInvalidFrames(const IntSize& aSize, uint32_t aFlags)
 {
   if (!mHasSize) {
     return;
--- a/image/moz.build
+++ b/image/moz.build
@@ -53,16 +53,17 @@ EXPORTS += [
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrozenImage.cpp',
+    'IDecodingTask.cpp',
     'Image.cpp',
     'ImageCacheKey.cpp',
     'ImageFactory.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',
     'MultipartImage.cpp',
--- a/image/test/gtest/TestDecoders.cpp
+++ b/image/test/gtest/TestDecoders.cpp
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 
 #include "Common.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "decoders/nsBMPDecoder.h"
+#include "IDecodingTask.h"
 #include "imgIContainer.h"
 #include "imgITools.h"
 #include "ImageFactory.h"
 #include "mozilla/gfx/2D.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
 #include "nsIRunnable.h"
@@ -111,42 +112,33 @@ void WithSingleChunkDecode(const ImageTe
 
   // Create a decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aOutputSize,
                                            DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
   // Run the full decoder synchronously.
-  decoder->Decode();
+  task->Run();
 
   // Call the lambda to verify the expected results.
   aResultChecker(decoder);
 }
 
 static void
 CheckDecoderSingleChunk(const ImageTestCase& aTestCase)
 {
   WithSingleChunkDecode(aTestCase, Nothing(), [&](Decoder* aDecoder) {
     CheckDecoderResults(aTestCase, aDecoder);
   });
 }
 
-class NoResume : public IResumable
-{
-public:
-  NS_INLINE_DECL_REFCOUNTING(NoResume, override)
-  virtual void Resume() override { }
-
-private:
-  ~NoResume() { }
-};
-
 static void
 CheckDecoderMultiChunk(const ImageTestCase& aTestCase)
 {
   nsCOMPtr<nsIInputStream> inputStream = LoadFile(aTestCase.mPath);
   ASSERT_TRUE(inputStream != nullptr);
 
   // Figure out how much data we have.
   uint64_t length;
@@ -157,35 +149,32 @@ CheckDecoderMultiChunk(const ImageTestCa
   RefPtr<SourceBuffer> sourceBuffer = new SourceBuffer();
   sourceBuffer->ExpectLength(length);
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
                                            DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
-  // Decode synchronously, using a |NoResume| IResumable so the Decoder doesn't
-  // attempt to schedule itself on a nonexistent DecodePool when we write more
-  // data into the SourceBuffer.
-  RefPtr<NoResume> noResume = new NoResume();
   for (uint64_t read = 0; read < length ; ++read) {
     uint64_t available = 0;
     rv = inputStream->Available(&available);
     ASSERT_TRUE(available > 0);
     ASSERT_TRUE(NS_SUCCEEDED(rv));
 
     rv = sourceBuffer->AppendFromInputStream(inputStream, 1);
     ASSERT_TRUE(NS_SUCCEEDED(rv));
 
-    decoder->Decode(noResume);
+    task->Run();
   }
 
   sourceBuffer->Complete(NS_OK);
-  decoder->Decode(noResume);
+  task->Run();
   
   CheckDecoderResults(aTestCase, decoder);
 }
 
 static void
 CheckDownscaleDuringDecode(const ImageTestCase& aTestCase)
 {
   // This function expects that |aTestCase| consists of 25 lines of green,
--- a/image/test/gtest/TestMetadata.cpp
+++ b/image/test/gtest/TestMetadata.cpp
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gtest/gtest.h"
 
 #include "Common.h"
 #include "Decoder.h"
 #include "DecoderFactory.h"
 #include "decoders/nsBMPDecoder.h"
+#include "IDecodingTask.h"
 #include "imgIContainer.h"
 #include "imgITools.h"
 #include "ImageFactory.h"
 #include "mozilla/gfx/2D.h"
 #include "nsComponentManagerUtils.h"
 #include "nsCOMPtr.h"
 #include "nsIInputStream.h"
 #include "nsIRunnable.h"
@@ -54,23 +55,24 @@ CheckMetadata(const ImageTestCase& aTest
   sourceBuffer->Complete(NS_OK);
 
   // Create a metadata decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(aTestCase.mMimeType);
   RefPtr<Decoder> decoder =
     DecoderFactory::CreateAnonymousMetadataDecoder(decoderType, sourceBuffer);
   ASSERT_TRUE(decoder != nullptr);
+  RefPtr<IDecodingTask> task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
   if (aBMPWithinICO == BMPWithinICO::YES) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
   }
 
   // Run the metadata decoder synchronously.
-  decoder->Decode();
+  task->Run();
 
   // Ensure that the metadata decoder didn't make progress it shouldn't have
   // (which would indicate that it decoded past the header of the image).
   Progress metadataProgress = decoder->TakeProgress();
   EXPECT_TRUE(0 == (metadataProgress & ~(FLAG_SIZE_AVAILABLE |
                                          FLAG_HAS_TRANSPARENCY |
                                          FLAG_IS_ANIMATED)));
 
@@ -98,23 +100,24 @@ CheckMetadata(const ImageTestCase& aTest
   EXPECT_EQ(bool(aTestCase.mFlags & TEST_CASE_IS_ANIMATED),
             bool(metadataProgress & FLAG_IS_ANIMATED));
 
   // Create a full decoder, so we can compare the result.
   decoder =
     DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, Nothing(),
                                            DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
+  task = new AnonymousDecodingTask(WrapNotNull(decoder));
 
   if (aBMPWithinICO == BMPWithinICO::YES) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetIsWithinICO();
   }
 
   // Run the full decoder synchronously.
-  decoder->Decode();
+  task->Run();
 
   EXPECT_TRUE(decoder->GetDecodeDone() && !decoder->HasError());
   Progress fullProgress = decoder->TakeProgress();
 
   // If the metadata decoder set a progress bit, the full decoder should also
   // have set the same bit.
   EXPECT_EQ(fullProgress, metadataProgress | fullProgress);