Bug 1185800 - Add DecoderFlags and SurfaceFlags enum classes and use them instead of imgIContainer flags in all decoder-related code. r=tn
authorSeth Fowler <mark.seth.fowler@gmail.com>
Fri, 14 Aug 2015 17:56:44 -0700
changeset 257946 3a12957a6bd3166c3c0f6b5667264ddd040e6318
parent 257945 638c65c33cfab3411ff26eb0c06836062c20ca5d
child 257947 06300b68be6c0d92df32eb2f9be299f74f49cbfe
push id29238
push userryanvm@gmail.com
push dateMon, 17 Aug 2015 13:06:57 +0000
treeherdermozilla-central@a6eeb28458fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1185800
milestone43.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 1185800 - Add DecoderFlags and SurfaceFlags enum classes and use them instead of imgIContainer flags in all decoder-related code. r=tn
image/DecodePool.cpp
image/Decoder.cpp
image/Decoder.h
image/DecoderFactory.cpp
image/DecoderFactory.h
image/DecoderFlags.h
image/FrameAnimator.cpp
image/ImageOps.cpp
image/RasterImage.cpp
image/RasterImage.h
image/SurfaceCache.cpp
image/SurfaceCache.h
image/SurfaceFlags.h
image/decoders/nsICODecoder.cpp
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsPNGDecoder.cpp
image/imgLoader.cpp
image/moz.build
image/test/gtest/TestMetadata.cpp
--- a/image/DecodePool.cpp
+++ b/image/DecodePool.cpp
@@ -42,45 +42,47 @@ class NotifyProgressWorker : public nsRu
 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,
-                       uint32_t aFlags)
+                       SurfaceFlags aSurfaceFlags)
   {
     MOZ_ASSERT(aImage);
 
     nsCOMPtr<nsIRunnable> worker =
-      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aFlags);
+      new NotifyProgressWorker(aImage, aProgress, aInvalidRect, aSurfaceFlags);
     NS_DispatchToMainThread(worker);
   }
 
   NS_IMETHOD Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
-    mImage->NotifyProgress(mProgress, mInvalidRect, mFlags);
+    mImage->NotifyProgress(mProgress, mInvalidRect, mSurfaceFlags);
     return NS_OK;
   }
 
 private:
-  NotifyProgressWorker(RasterImage* aImage, Progress aProgress,
-                       const nsIntRect& aInvalidRect, uint32_t aFlags)
+  NotifyProgressWorker(RasterImage* aImage,
+                       Progress aProgress,
+                       const nsIntRect& aInvalidRect,
+                       SurfaceFlags aSurfaceFlags)
     : mImage(aImage)
     , mProgress(aProgress)
     , mInvalidRect(aInvalidRect)
-    , mFlags(aFlags)
+    , mSurfaceFlags(aSurfaceFlags)
   { }
 
   nsRefPtr<RasterImage> mImage;
   const Progress mProgress;
   const nsIntRect mInvalidRect;
-  const uint32_t mFlags;
+  const SurfaceFlags mSurfaceFlags;
 };
 
 class NotifyDecodeCompleteWorker : public nsRunnable
 {
 public:
   /**
    * Called by the DecodePool when decoding is complete, so that final cleanup
    * can be performed.
@@ -465,36 +467,36 @@ DecodePool::Decode(Decoder* aDecoder)
 }
 
 void
 DecodePool::NotifyProgress(Decoder* aDecoder)
 {
   MOZ_ASSERT(aDecoder);
 
   if (!NS_IsMainThread() ||
-      (aDecoder->GetFlags() & imgIContainer::FLAG_ASYNC_NOTIFY)) {
+      (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
     NotifyProgressWorker::Dispatch(aDecoder->GetImage(),
                                    aDecoder->TakeProgress(),
                                    aDecoder->TakeInvalidRect(),
-                                   aDecoder->GetDecodeFlags());
+                                   aDecoder->GetSurfaceFlags());
     return;
   }
 
   aDecoder->GetImage()->NotifyProgress(aDecoder->TakeProgress(),
                                        aDecoder->TakeInvalidRect(),
-                                       aDecoder->GetDecodeFlags());
+                                       aDecoder->GetSurfaceFlags());
 }
 
 void
 DecodePool::NotifyDecodeComplete(Decoder* aDecoder)
 {
   MOZ_ASSERT(aDecoder);
 
   if (!NS_IsMainThread() ||
-      (aDecoder->GetFlags() & imgIContainer::FLAG_ASYNC_NOTIFY)) {
+      (aDecoder->GetDecoderFlags() & DecoderFlags::ASYNC_NOTIFY)) {
     NotifyDecodeCompleteWorker::Dispatch(aDecoder);
     return;
   }
 
   aDecoder->GetImage()->FinalizeDecoder(aDecoder);
 }
 
 } // namespace image
--- a/image/Decoder.cpp
+++ b/image/Decoder.cpp
@@ -26,23 +26,21 @@ Decoder::Decoder(RasterImage* aImage)
   , mImageDataLength(0)
   , mColormap(nullptr)
   , mColormapSize(0)
   , mImage(aImage)
   , mProgress(NoProgress)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mChunkCount(0)
-  , mFlags(0)
+  , mDecoderFlags(DefaultDecoderFlags())
+  , mSurfaceFlags(DefaultSurfaceFlags())
   , mBytesDecoded(0)
   , mInitialized(false)
   , mMetadataDecode(false)
-  , mSendPartialInvalidations(false)
-  , mImageIsTransient(false)
-  , mFirstFrameDecode(false)
   , mInFrame(false)
   , mDataDone(false)
   , mDecodeDone(false)
   , mDataError(false)
   , mDecodeAborted(false)
   , mShouldReportError(false)
 { }
 
@@ -230,32 +228,34 @@ Decoder::CompleteDecode()
   }
 
   if (mDecodeDone && !IsMetadataDecode()) {
     MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
 
     // If this image wasn't animated and isn't a transient image, mark its frame
     // as optimizable. We don't support optimizing animated images and
     // optimizing transient images isn't worth it.
-    if (!HasAnimation() && !mImageIsTransient && mCurrentFrame) {
+    if (!HasAnimation() &&
+        !(mDecoderFlags & DecoderFlags::IMAGE_IS_TRANSIENT) &&
+        mCurrentFrame) {
       mCurrentFrame->SetOptimizable();
     }
   }
 }
 
 nsresult
 Decoder::AllocateFrame(uint32_t aFrameNum,
                        const nsIntSize& aTargetSize,
                        const nsIntRect& aFrameRect,
                        gfx::SurfaceFormat aFormat,
                        uint8_t aPaletteDepth)
 {
   mCurrentFrame = AllocateFrameInternal(aFrameNum, aTargetSize, aFrameRect,
-                                        GetDecodeFlags(), aFormat,
-                                        aPaletteDepth, mCurrentFrame.get());
+                                        aFormat, aPaletteDepth,
+                                        mCurrentFrame.get());
 
   if (mCurrentFrame) {
     // Gather the raw pointers the decoders will use.
     mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
     mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
 
     if (aFrameNum + 1 == mFrameCount) {
       // If we're past the first frame, PostIsAnimated() should've been called.
@@ -271,17 +271,16 @@ Decoder::AllocateFrame(uint32_t aFrameNu
 
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
 RawAccessFrameRef
 Decoder::AllocateFrameInternal(uint32_t aFrameNum,
                                const nsIntSize& aTargetSize,
                                const nsIntRect& aFrameRect,
-                               uint32_t aDecodeFlags,
                                SurfaceFormat aFormat,
                                uint8_t aPaletteDepth,
                                imgFrame* aPreviousFrame)
 {
   if (mDataError || NS_FAILED(mFailCode)) {
     return RawAccessFrameRef();
   }
 
@@ -299,35 +298,34 @@ Decoder::AllocateFrameInternal(uint32_t 
   const uint32_t bytesPerPixel = aPaletteDepth == 0 ? 4 : 1;
   if (ShouldUseSurfaceCache() &&
       !SurfaceCache::CanHold(aFrameRect.Size(), bytesPerPixel)) {
     NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
     return RawAccessFrameRef();
   }
 
   nsRefPtr<imgFrame> frame = new imgFrame();
-  bool nonPremult =
-    aDecodeFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
+  bool nonPremult = bool(mSurfaceFlags & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
   if (NS_FAILED(frame->InitForDecoder(aTargetSize, aFrameRect, aFormat,
                                       aPaletteDepth, nonPremult))) {
     NS_WARNING("imgFrame::Init should succeed");
     return RawAccessFrameRef();
   }
 
   RawAccessFrameRef ref = frame->RawAccessRef();
   if (!ref) {
     frame->Abort();
     return RawAccessFrameRef();
   }
 
   if (ShouldUseSurfaceCache()) {
     InsertOutcome outcome =
       SurfaceCache::Insert(frame, ImageKey(mImage.get()),
                            RasterSurfaceKey(aTargetSize,
-                                            aDecodeFlags,
+                                            mSurfaceFlags,
                                             aFrameNum),
                            Lifetime::Persistent);
     if (outcome == InsertOutcome::FAILURE) {
       // We couldn't insert the surface, almost certainly due to low memory. We
       // treat this as a permanent error to help the system recover; otherwise,
       // we might just end up attempting to decode this image again immediately.
       ref->Abort();
       return RawAccessFrameRef();
@@ -432,34 +430,34 @@ Decoder::PostFrameStop(Opacity aFrameOpa
   mInFrame = false;
 
   mCurrentFrame->Finish(aFrameOpacity, aDisposalMethod, aTimeout, aBlendMethod);
 
   mProgress |= FLAG_FRAME_COMPLETE;
 
   // If we're not sending partial invalidations, then we send an invalidation
   // here when the first frame is complete.
-  if (!mSendPartialInvalidations && !HasAnimation()) {
+  if (!ShouldSendPartialInvalidations() && !HasAnimation()) {
     mInvalidRect.UnionRect(mInvalidRect,
                            gfx::IntRect(gfx::IntPoint(0, 0), GetSize()));
   }
 }
 
 void
 Decoder::PostInvalidation(const nsIntRect& aRect,
                           const Maybe<nsIntRect>& aRectAtTargetSize
                             /* = Nothing() */)
 {
   // We should be mid-frame
   MOZ_ASSERT(mInFrame, "Can't invalidate when not mid-frame!");
   MOZ_ASSERT(mCurrentFrame, "Can't invalidate when not mid-frame!");
 
   // Record this invalidation, unless we're not sending partial invalidations
   // or we're past the first frame.
-  if (mSendPartialInvalidations && !HasAnimation()) {
+  if (ShouldSendPartialInvalidations() && !HasAnimation()) {
     mInvalidRect.UnionRect(mInvalidRect, aRect);
     mCurrentFrame->ImageUpdated(aRectAtTargetSize.valueOr(aRect));
   }
 }
 
 void
 Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
 {
--- a/image/Decoder.h
+++ b/image/Decoder.h
@@ -5,19 +5,21 @@
 
 #ifndef mozilla_image_Decoder_h
 #define mozilla_image_Decoder_h
 
 #include "FrameAnimator.h"
 #include "RasterImage.h"
 #include "mozilla/RefPtr.h"
 #include "DecodePool.h"
+#include "DecoderFlags.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "SourceBuffer.h"
+#include "SurfaceFlags.h"
 
 namespace mozilla {
 
 namespace Telemetry {
   enum ID : uint32_t;
 } // namespace Telemetry
 
 namespace image {
@@ -133,70 +135,46 @@ public:
    * Set the requested resolution for this decoder. Used to implement the
    * -moz-resolution media fragment.
    *
    *  XXX(seth): Support for -moz-resolution will be removed in bug 1118926.
    */
   virtual void SetResolution(const gfx::IntSize& aResolution) { }
 
   /**
-   * Set whether should send partial invalidations.
-   *
-   * If @aSend is true, we'll send partial invalidations when decoding the first
-   * frame of the image, so image notifications observers will be able to
-   * gradually draw in the image as it downloads.
-   *
-   * If @aSend is false (the default), we'll only send an invalidation when we
-   * complete the first frame.
-   *
-   * This must be called before Init() is called.
-   */
-  void SetSendPartialInvalidations(bool aSend)
-  {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mSendPartialInvalidations = aSend;
-  }
-
-  /**
    * Set an iterator to the SourceBuffer which will feed data to this decoder.
    *
    * This should be called for almost all decoders; the exceptions are the
    * contained decoders of an nsICODecoder, which will be fed manually via Write
    * instead.
    *
    * This must be called before Init() is called.
    */
   void SetIterator(SourceBufferIterator&& aIterator)
   {
     MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
     mIterator.emplace(Move(aIterator));
   }
 
   /**
-   * Set whether this decoder is associated with a transient image. The decoder
-   * may choose to avoid certain optimizations that don't pay off for
-   * short-lived images in this case.
+   * Should this decoder send partial invalidations?
    */
-  void SetImageIsTransient(bool aIsTransient)
+  bool ShouldSendPartialInvalidations() const
   {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mImageIsTransient = aIsTransient;
+    return !(mDecoderFlags & DecoderFlags::IS_REDECODE);
   }
 
   /**
-   * Set whether we should stop decoding after the first frame.
+   * Should we stop decoding after the first frame?
    */
-  void SetIsFirstFrameDecode()
+  bool IsFirstFrameDecode() const
   {
-    MOZ_ASSERT(!mInitialized, "Shouldn't be initialized yet");
-    mFirstFrameDecode = true;
+    return bool(mDecoderFlags & DecoderFlags::FIRST_FRAME_ONLY);
   }
 
-  bool IsFirstFrameDecode() const { return mFirstFrameDecode; }
-
   size_t BytesDecoded() const { return mBytesDecoded; }
 
   // The amount of time we've spent inside Write() so far for this decoder.
   TimeDuration DecodeTime() const { return mDecodeTime; }
 
   // The number of times Write() has been called so far for this decoder.
   uint32_t ChunkCount() const { return mChunkCount; }
 
@@ -250,19 +228,36 @@ public:
   bool WasAborted() const { return mDecodeAborted; }
 
   enum DecodeStyle {
       PROGRESSIVE, // produce intermediate frames representing the partial
                    // state of the image
       SEQUENTIAL   // decode to final image immediately
   };
 
-  void SetFlags(uint32_t aFlags) { mFlags = aFlags; }
-  uint32_t GetFlags() const { return mFlags; }
-  uint32_t GetDecodeFlags() const { return DecodeFlags(mFlags); }
+  /**
+   * Get or set the DecoderFlags that influence the behavior of this decoder.
+   */
+  void SetDecoderFlags(DecoderFlags aDecoderFlags)
+  {
+    MOZ_ASSERT(!mInitialized);
+    mDecoderFlags = aDecoderFlags;
+  }
+  DecoderFlags GetDecoderFlags() const { return mDecoderFlags; }
+
+  /**
+   * Get or set the SurfaceFlags that select the kind of output this decoder
+   * will produce.
+   */
+  void SetSurfaceFlags(SurfaceFlags aSurfaceFlags)
+  {
+    MOZ_ASSERT(!mInitialized);
+    mSurfaceFlags = aSurfaceFlags;
+  }
+  SurfaceFlags GetSurfaceFlags() const { return mSurfaceFlags; }
 
   bool HasSize() const { return mImageMetadata.HasSize(); }
 
   nsIntSize GetSize() const
   {
     MOZ_ASSERT(HasSize());
     return mImageMetadata.GetSize();
   }
@@ -400,17 +395,16 @@ protected:
     nsIntSize size = GetSize();
     return AllocateFrame(0, size, nsIntRect(nsIntPoint(), size),
                          gfx::SurfaceFormat::B8G8R8A8);
   }
 
   RawAccessFrameRef AllocateFrameInternal(uint32_t aFrameNum,
                                           const nsIntSize& aTargetSize,
                                           const nsIntRect& aFrameRect,
-                                          uint32_t aDecodeFlags,
                                           gfx::SurfaceFormat aFormat,
                                           uint8_t aPaletteDepth,
                                           imgFrame* aPreviousFrame);
 
 protected:
   uint8_t* mImageData;  // Pointer to image data in either Cairo or 8bit format
   uint32_t mImageDataLength;
   uint32_t* mColormap;  // Current colormap to be used in Cairo format
@@ -427,24 +421,22 @@ private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsresult mFailCode;
 
   // Telemetry data for this decoder.
   TimeDuration mDecodeTime;
   uint32_t mChunkCount;
 
-  uint32_t mFlags;
+  DecoderFlags mDecoderFlags;
+  SurfaceFlags mSurfaceFlags;
   size_t mBytesDecoded;
 
   bool mInitialized : 1;
   bool mMetadataDecode : 1;
-  bool mSendPartialInvalidations : 1;
-  bool mImageIsTransient : 1;
-  bool mFirstFrameDecode : 1;
   bool mInFrame : 1;
   bool mDataDone : 1;
   bool mDecodeDone : 1;
   bool mDataError : 1;
   bool mDecodeAborted : 1;
   bool mShouldReportError : 1;
 };
 
--- a/image/DecoderFactory.cpp
+++ b/image/DecoderFactory.cpp
@@ -104,38 +104,36 @@ DecoderFactory::GetDecoder(DecoderType a
   return decoder.forget();
 }
 
 /* static */ already_AddRefed<Decoder>
 DecoderFactory::CreateDecoder(DecoderType aType,
                               RasterImage* aImage,
                               SourceBuffer* aSourceBuffer,
                               const Maybe<IntSize>& aTargetSize,
-                              uint32_t aFlags,
+                              DecoderFlags aDecoderFlags,
+                              SurfaceFlags aSurfaceFlags,
                               int aSampleSize,
-                              const IntSize& aResolution,
-                              bool aIsRedecode,
-                              bool aImageIsTransient)
+                              const IntSize& aResolution)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
-  nsRefPtr<Decoder> decoder = GetDecoder(aType, aImage, aIsRedecode);
+  nsRefPtr<Decoder> decoder =
+    GetDecoder(aType, aImage, bool(aDecoderFlags & DecoderFlags::IS_REDECODE));
   MOZ_ASSERT(decoder, "Should have a decoder now");
 
   // Initialize the decoder.
   decoder->SetMetadataDecode(false);
   decoder->SetIterator(aSourceBuffer->Iterator());
-  decoder->SetFlags(aFlags);
+  decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::FIRST_FRAME_ONLY);
+  decoder->SetSurfaceFlags(aSurfaceFlags);
   decoder->SetSampleSize(aSampleSize);
   decoder->SetResolution(aResolution);
-  decoder->SetSendPartialInvalidations(!aIsRedecode);
-  decoder->SetImageIsTransient(aImageIsTransient);
-  decoder->SetIsFirstFrameDecode();
 
   // Set a target size for downscale-during-decode if applicable.
   if (aTargetSize) {
     DebugOnly<nsresult> rv = decoder->SetTargetSize(*aTargetSize);
     MOZ_ASSERT(nsresult(rv) != NS_ERROR_NOT_AVAILABLE,
                "We're downscale-during-decode but decoder doesn't support it?");
     MOZ_ASSERT(NS_SUCCEEDED(rv), "Bad downscale-during-decode target size?");
   }
@@ -147,36 +145,37 @@ DecoderFactory::CreateDecoder(DecoderTyp
 
   return decoder.forget();
 }
 
 /* static */ already_AddRefed<Decoder>
 DecoderFactory::CreateAnimationDecoder(DecoderType aType,
                                        RasterImage* aImage,
                                        SourceBuffer* aSourceBuffer,
-                                       uint32_t aFlags,
+                                       DecoderFlags aDecoderFlags,
+                                       SurfaceFlags aSurfaceFlags,
                                        const IntSize& aResolution)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   MOZ_ASSERT(aType == DecoderType::GIF || aType == DecoderType::PNG,
              "Calling CreateAnimationDecoder for non-animating DecoderType");
 
   nsRefPtr<Decoder> decoder =
     GetDecoder(aType, aImage, /* aIsRedecode = */ true);
   MOZ_ASSERT(decoder, "Should have a decoder now");
 
   // Initialize the decoder.
   decoder->SetMetadataDecode(false);
   decoder->SetIterator(aSourceBuffer->Iterator());
-  decoder->SetFlags(aFlags);
+  decoder->SetDecoderFlags(aDecoderFlags | DecoderFlags::IS_REDECODE);
+  decoder->SetSurfaceFlags(aSurfaceFlags);
   decoder->SetResolution(aResolution);
-  decoder->SetSendPartialInvalidations(false);
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
   return decoder.forget();
 }
@@ -208,38 +207,43 @@ DecoderFactory::CreateMetadataDecoder(De
   }
 
   return decoder.forget();
 }
 
 /* static */ already_AddRefed<Decoder>
 DecoderFactory::CreateAnonymousDecoder(DecoderType aType,
                                        SourceBuffer* aSourceBuffer,
-                                       uint32_t aFlags)
+                                       SurfaceFlags aSurfaceFlags)
 {
   if (aType == DecoderType::UNKNOWN) {
     return nullptr;
   }
 
   nsRefPtr<Decoder> decoder =
     GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
   MOZ_ASSERT(decoder, "Should have a decoder now");
 
   // Initialize the decoder.
   decoder->SetMetadataDecode(false);
   decoder->SetIterator(aSourceBuffer->Iterator());
-  decoder->SetFlags(aFlags);
-  decoder->SetImageIsTransient(true);
+
+  // Anonymous decoders are always transient; we don't want to optimize surfaces
+  // or do any other expensive work that might be wasted.
+  DecoderFlags decoderFlags = DecoderFlags::IMAGE_IS_TRANSIENT;
 
   // Without an image, the decoder can't store anything in the SurfaceCache, so
   // callers will only be able to retrieve the most recent frame via
   // Decoder::GetCurrentFrame(). That means that anonymous decoders should
   // always be first-frame-only decoders, because nobody ever wants the *last*
   // frame.
-  decoder->SetIsFirstFrameDecode();
+  decoderFlags |= DecoderFlags::FIRST_FRAME_ONLY;
+
+  decoder->SetDecoderFlags(decoderFlags);
+  decoder->SetSurfaceFlags(aSurfaceFlags);
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
   return decoder.forget();
 }
@@ -254,17 +258,17 @@ DecoderFactory::CreateAnonymousMetadataD
 
   nsRefPtr<Decoder> decoder =
     GetDecoder(aType, /* aImage = */ nullptr, /* aIsRedecode = */ false);
   MOZ_ASSERT(decoder, "Should have a decoder now");
 
   // Initialize the decoder.
   decoder->SetMetadataDecode(true);
   decoder->SetIterator(aSourceBuffer->Iterator());
-  decoder->SetIsFirstFrameDecode();
+  decoder->SetDecoderFlags(DecoderFlags::FIRST_FRAME_ONLY);
 
   decoder->Init();
   if (NS_FAILED(decoder->GetDecoderError())) {
     return nullptr;
   }
 
   return decoder.forget();
 }
--- a/image/DecoderFactory.h
+++ b/image/DecoderFactory.h
@@ -2,29 +2,36 @@
  *
  * 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/. */
 
 #ifndef mozilla_image_DecoderFactory_h
 #define mozilla_image_DecoderFactory_h
 
+#include "DecoderFlags.h"
+#include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/gfx/2D.h"
 #include "nsCOMPtr.h"
+#include "SurfaceFlags.h"
 
 class nsACString;
 
 namespace mozilla {
 namespace image {
 
 class Decoder;
 class RasterImage;
 class SourceBuffer;
 
+/**
+ * The type of decoder; this is usually determined from a MIME type using
+ * DecoderFactory::GetDecoderType().
+ */
 enum class DecoderType
 {
   PNG,
   GIF,
   JPEG,
   BMP,
   ICO,
   ICON,
@@ -37,68 +44,64 @@ public:
   /// @return the type of decoder which is appropriate for @aMimeType.
   static DecoderType GetDecoderType(const char* aMimeType);
 
   /**
    * Creates and initializes a decoder for non-animated images of type @aType.
    * (If the image *is* animated, only the first frame will be decoded.) The
    * decoder will send notifications to @aImage.
    *
-   * XXX(seth): @aIsRedecode and @aImageIsTransient should really be part of
-   * @aFlags. This requires changes to the way that decoder flags work, though.
-   * See bug 1185800.
-   *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aImage The image will own the decoder and which should receive
    *               notifications as decoding progresses.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
    * @param aTargetSize If not Nothing(), the target size which the image should
    *                    be scaled to during decoding. It's an error to specify
    *                    a target size for a decoder type which doesn't support
    *                    downscale-during-decode.
-   * @param aFlags Flags specifying what type of output the decoder should
-   *               produce; see GetDecodeFlags() in RasterImage.h.
+   * @param aDecoderFlags Flags specifying the behavior of this decoder.
+   * @param aSurfaceFlags Flags specifying the type of output this decoder
+   *                      should produce.
    * @param aSampleSize The sample size requested using #-moz-samplesize (or 0
    *                    if none).
    * @param aResolution The resolution requested using #-moz-resolution (or an
    *                    empty rect if none).
-   * @param aIsRedecode Specify 'true' if this image has been decoded before.
-   * @param aImageIsTransient Specify 'true' if this image is transient.
    */
   static already_AddRefed<Decoder>
   CreateDecoder(DecoderType aType,
                 RasterImage* aImage,
                 SourceBuffer* aSourceBuffer,
                 const Maybe<gfx::IntSize>& aTargetSize,
-                uint32_t aFlags,
+                DecoderFlags aDecoderFlags,
+                SurfaceFlags aSurfaceFlags,
                 int aSampleSize,
-                const gfx::IntSize& aResolution,
-                bool aIsRedecode,
-                bool aImageIsTransient);
+                const gfx::IntSize& aResolution);
 
   /**
    * Creates and initializes a decoder for animated images of type @aType.
    * The decoder will send notifications to @aImage.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aImage The image will own the decoder and which should receive
    *               notifications as decoding progresses.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
-   * @param aFlags Flags specifying what type of output the decoder should
-   *               produce; see GetDecodeFlags() in RasterImage.h.
+   * @param aDecoderFlags Flags specifying the behavior of this decoder.
+   * @param aSurfaceFlags Flags specifying the type of output this decoder
+   *                      should produce.
    * @param aResolution The resolution requested using #-moz-resolution (or an
    *                    empty rect if none).
    */
   static already_AddRefed<Decoder>
   CreateAnimationDecoder(DecoderType aType,
                          RasterImage* aImage,
                          SourceBuffer* aSourceBuffer,
-                         uint32_t aFlags,
+                         DecoderFlags aDecoderFlags,
+                         SurfaceFlags aSurfaceFlags,
                          const gfx::IntSize& aResolution);
 
   /**
    * Creates and initializes a metadata decoder of type @aType. This decoder
    * will only decode the image's header, extracting metadata like the size of
    * the image. No actual image data will be decoded and no surfaces will be
    * allocated. The decoder will send notifications to @aImage.
    *
@@ -121,35 +124,33 @@ public:
 
   /**
    * Creates and initializes an anonymous decoder (one which isn't associated
    * with an Image object). Only the first frame of the image will be decoded.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
-   * @param aFlags Flags specifying what type of output the decoder should
-   *               produce; see GetDecodeFlags() in RasterImage.h.
+   * @param aSurfaceFlags Flags specifying the type of output this decoder
+   *                      should produce.
    */
   static already_AddRefed<Decoder>
   CreateAnonymousDecoder(DecoderType aType,
                          SourceBuffer* aSourceBuffer,
-                         uint32_t aFlags);
+                         SurfaceFlags aSurfaceFlags);
 
   /**
    * Creates and initializes an anonymous metadata decoder (one which isn't
    * associated with an Image object). This decoder will only decode the image's
    * header, extracting metadata like the size of the image. No actual image
    * data will be decoded and no surfaces will be allocated.
    *
    * @param aType Which type of decoder to create - JPEG, PNG, etc.
    * @param aSourceBuffer The SourceBuffer which the decoder will read its data
    *                      from.
-   * @param aFlags Flags specifying what type of output the decoder should
-   *               produce; see GetDecodeFlags() in RasterImage.h.
    */
   static already_AddRefed<Decoder>
   CreateAnonymousMetadataDecoder(DecoderType aType,
                                  SourceBuffer* aSourceBuffer);
 
 private:
   virtual ~DecoderFactory() = 0;
 
new file mode 100644
--- /dev/null
+++ b/image/DecoderFlags.h
@@ -0,0 +1,42 @@
+/* -*- 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/. */
+
+#ifndef mozilla_image_DecoderFlags_h
+#define mozilla_image_DecoderFlags_h
+
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * Flags that influence decoder behavior. Note that these flags *don't*
+ * influence the logical content of the surfaces that the decoder generates, so
+ * they're not in a factor in SurfaceCache lookups and the like. These flags
+ * instead either influence which surfaces are generated at all or the tune the
+ * decoder's behavior for a particular scenario.
+ */
+enum class DecoderFlags : uint8_t
+{
+  FIRST_FRAME_ONLY               = 1 << 0,
+  IS_REDECODE                    = 1 << 1,
+  IMAGE_IS_TRANSIENT             = 1 << 2,
+  ASYNC_NOTIFY                   = 1 << 3
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DecoderFlags)
+
+/**
+ * @return the default set of decode flags.
+ */
+inline DecoderFlags
+DefaultDecoderFlags()
+{
+  return DecoderFlags();
+}
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_DecoderFlags_h
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -276,17 +276,17 @@ FrameAnimator::GetCompositedFrame(uint32
     return LookupResult(mCompositingFrame->DrawableRef(), MatchType::EXACT);
   }
 
   // Otherwise return the raw frame. DoBlend is required to ensure that we only
   // hit this case if the frame is not paletted and doesn't require compositing.
   LookupResult result =
     SurfaceCache::Lookup(ImageKey(mImage),
                          RasterSurfaceKey(mSize,
-                                          0,  // Default decode flags.
+                                          DefaultSurfaceFlags(),
                                           aFrameNum));
   MOZ_ASSERT(!result || !result.DrawableRef()->GetIsPaletted(),
              "About to return a paletted frame");
   return result;
 }
 
 int32_t
 FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
@@ -327,17 +327,17 @@ FrameAnimator::GetTimeoutForFrame(uint32
 static void
 DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
                                    SurfaceMemoryCounterType aType,
                                    nsTArray<SurfaceMemoryCounter>& aCounters,
                                    MallocSizeOf aMallocSizeOf)
 {
   // Concoct a SurfaceKey for this surface.
   SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
-                                    imgIContainer::DECODE_FLAGS_DEFAULT,
+                                    DefaultSurfaceFlags(),
                                     /* aFrameNum = */ 0);
 
   // Create a counter for this surface.
   SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
 
   // Extract the surface's memory usage information.
   size_t heap = 0, nonHeap = 0;
   aSurface->AddSizeOfExcludingThis(aMallocSizeOf, heap, nonHeap);
@@ -369,17 +369,17 @@ FrameAnimator::CollectSizeOfCompositingS
 }
 
 RawAccessFrameRef
 FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
 {
   LookupResult result =
     SurfaceCache::Lookup(ImageKey(mImage),
                          RasterSurfaceKey(mSize,
-                                          0,  // Default decode flags.
+                                          DefaultSurfaceFlags(),
                                           aFrameNum));
   return result ? result.DrawableRef()->RawAccessRef()
                 : RawAccessFrameRef();
 }
 
 //******************************************************************************
 // DoBlend gets called when the timer for animation get fired and we have to
 // update the composited frame of the animation.
--- a/image/ImageOps.cpp
+++ b/image/ImageOps.cpp
@@ -111,17 +111,19 @@ ImageOps::DecodeToSurface(nsIInputStream
     return nullptr;
   }
   sourceBuffer->Complete(NS_OK);
 
   // Create a decoder.
   DecoderType decoderType =
     DecoderFactory::GetDecoderType(PromiseFlatCString(aMimeType).get());
   nsRefPtr<Decoder> decoder =
-    DecoderFactory::CreateAnonymousDecoder(decoderType, sourceBuffer, aFlags);
+    DecoderFactory::CreateAnonymousDecoder(decoderType,
+                                           sourceBuffer,
+                                           ToSurfaceFlags(aFlags));
   if (!decoder) {
     return nullptr;
   }
 
   // Run the decoder synchronously.
   decoder->Decode();
   if (!decoder->GetDecodeDone() || decoder->HasError()) {
     return nullptr;
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -113,17 +113,19 @@ public:
 
     // Everything worked, so commit to these objects and mark ourselves ready.
     mDstRef = Move(tentativeDstRef);
     mState = eReady;
 
     // Insert the new surface into the cache immediately. We need to do this so
     // that we won't start multiple scaling jobs for the same size.
     SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()),
-                         RasterSurfaceKey(mDstSize, mImageFlags, 0),
+                         RasterSurfaceKey(mDstSize,
+                                          ToSurfaceFlags(mImageFlags),
+                                          /* aFrameNum = */ 0),
                          Lifetime::Transient);
 
     return true;
   }
 
   NS_IMETHOD Run() override
   {
     if (mState == eReady) {
@@ -163,17 +165,18 @@ public:
       mDstRef.reset();
     } else if (mState == eFinishWithError) {
       MOZ_ASSERT(NS_IsMainThread());
       NS_WARNING("HQ scaling failed");
 
       // Remove the frame from the cache since we know we don't need it.
       SurfaceCache::RemoveSurface(ImageKey(mImage.get()),
                                   RasterSurfaceKey(mDstSize,
-                                                   mImageFlags, 0));
+                                                   ToSurfaceFlags(mImageFlags),
+                                                   /* aFrameNum = */ 0));
 
       // Release everything we're holding, too.
       mSrcRef.reset();
       mDstRef.reset();
     } else {
       // mState must be eNew, which is invalid in Run().
       MOZ_ASSERT(false, "Need to call Init() before dispatching");
     }
@@ -420,43 +423,44 @@ RasterImage::LookupFrameInternal(uint32_
 {
   if (!mAnim) {
     NS_ASSERTION(aFrameNum == 0,
                  "Don't ask for a frame > 0 if we're not animated!");
     aFrameNum = 0;
   }
 
   if (mAnim && aFrameNum > 0) {
-    MOZ_ASSERT(DecodeFlags(aFlags) == DECODE_FLAGS_DEFAULT,
-               "Can't composite frames with non-default decode flags");
+    MOZ_ASSERT(ToSurfaceFlags(aFlags) == DefaultSurfaceFlags(),
+               "Can't composite frames with non-default surface flags");
     return mAnim->GetCompositedFrame(aFrameNum);
   }
 
-  Maybe<uint32_t> alternateFlags;
+  Maybe<SurfaceFlags> alternateFlags;
   if (IsOpaque()) {
     // If we're opaque, we can always substitute a frame that was decoded with a
     // different decode flag for premultiplied alpha, because that can only
     // matter for frames with transparency.
-    alternateFlags = Some(aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
+    alternateFlags.emplace(ToSurfaceFlags(aFlags) ^
+                             SurfaceFlags::NO_PREMULTIPLY_ALPHA);
   }
 
   // We don't want any substitution for sync decodes (except the premultiplied
   // alpha optimization above), so we use SurfaceCache::Lookup in this case.
   if (aFlags & FLAG_SYNC_DECODE) {
     return SurfaceCache::Lookup(ImageKey(this),
                                 RasterSurfaceKey(aSize,
-                                                 DecodeFlags(aFlags),
+                                                 ToSurfaceFlags(aFlags),
                                                  aFrameNum),
                                 alternateFlags);
   }
 
   // We'll return the best match we can find to the requested frame.
   return SurfaceCache::LookupBestMatch(ImageKey(this),
                                        RasterSurfaceKey(aSize,
-                                                        DecodeFlags(aFlags),
+                                                        ToSurfaceFlags(aFlags),
                                                         aFrameNum),
                                        alternateFlags);
 }
 
 DrawableFrameRef
 RasterImage::LookupFrame(uint32_t aFrameNum,
                          const IntSize& aSize,
                          uint32_t aFlags)
@@ -1403,41 +1407,54 @@ RasterImage::Decode(const IntSize& aSize
   }
 
   MOZ_ASSERT(mDownscaleDuringDecode || aSize == mSize,
              "Can only decode to our intrinsic size if we're not allowed to "
              "downscale-during-decode");
 
   Maybe<IntSize> targetSize = mSize != aSize ? Some(aSize) : Nothing();
 
+  // Determine which flags we need to decode this image with.
+  DecoderFlags decoderFlags = DefaultDecoderFlags();
+  if (aFlags & FLAG_ASYNC_NOTIFY) {
+    decoderFlags |= DecoderFlags::ASYNC_NOTIFY;
+  }
+  if (mTransient) {
+    decoderFlags |= DecoderFlags::IMAGE_IS_TRANSIENT;
+  }
+  if (mHasBeenDecoded) {
+    decoderFlags |= DecoderFlags::IS_REDECODE;
+  }
+
   // Create a decoder.
   nsRefPtr<Decoder> decoder;
   if (mAnim) {
     decoder = DecoderFactory::CreateAnimationDecoder(mDecoderType, this,
-                                                     mSourceBuffer, aFlags,
+                                                     mSourceBuffer, decoderFlags,
+                                                     ToSurfaceFlags(aFlags),
                                                      mRequestedResolution);
   } else {
     decoder = DecoderFactory::CreateDecoder(mDecoderType, this, mSourceBuffer,
-                                            targetSize, aFlags,
+                                            targetSize, decoderFlags,
+                                            ToSurfaceFlags(aFlags),
                                             mRequestedSampleSize,
-                                            mRequestedResolution,
-                                            mHasBeenDecoded, mTransient);
+                                            mRequestedResolution);
   }
 
   // Make sure DecoderFactory was able to create a decoder successfully.
   if (!decoder) {
     return NS_ERROR_FAILURE;
   }
 
   // Add a placeholder for the first frame to the SurfaceCache so we won't
   // trigger any more decoders with the same parameters.
   InsertOutcome outcome =
     SurfaceCache::InsertPlaceholder(ImageKey(this),
                                     RasterSurfaceKey(aSize,
-                                                     decoder->GetDecodeFlags(),
+                                                     decoder->GetSurfaceFlags(),
                                                      /* aFrameNum = */ 0));
   if (outcome != InsertOutcome::SUCCESS) {
     return NS_ERROR_FAILURE;
   }
 
   // Report telemetry.
   Telemetry::GetHistogramById(Telemetry::IMAGE_DECODE_COUNT)
     ->Subtract(mDecodeCount);
@@ -1636,17 +1653,17 @@ RasterImage::RequestScale(imgFrame* aFra
 
   // We also can't scale if we can't lock the image data for this frame.
   RawAccessFrameRef frameRef = aFrame->RawAccessRef();
   if (!frameRef) {
     return;
   }
 
   nsRefPtr<ScaleRunner> runner =
-    new ScaleRunner(this, DecodeFlags(aFlags), aSize, Move(frameRef));
+    new ScaleRunner(this, aFlags, aSize, Move(frameRef));
   if (runner->Init()) {
     if (!sScaleWorkerThread) {
       NS_NewNamedThread("Image Scaler", getter_AddRefs(sScaleWorkerThread));
       ClearOnShutdown(&sScaleWorkerThread);
     }
 
     sScaleWorkerThread->Dispatch(runner, NS_DISPATCH_NORMAL);
   }
@@ -1661,18 +1678,18 @@ RasterImage::DrawWithPreDownscaleIfNeede
                                           uint32_t aFlags)
 {
   DrawableFrameRef frameRef;
 
   if (CanScale(aFilter, aSize, aFlags)) {
     LookupResult result =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(aSize,
-                                            DecodeFlags(aFlags),
-                                            0));
+                                            ToSurfaceFlags(aFlags),
+                                            /* aFrameNum = */ 0));
     if (!result) {
       // We either didn't have a matching scaled frame or the OS threw it away.
       // Request a new one so we'll be ready next time. For now, we'll fall back
       // to aFrameRef below.
       RequestScale(aFrameRef.get(), aFlags, aSize);
     }
     if (result && result.DrawableRef()->IsImageComplete()) {
       frameRef = Move(result.DrawableRef());  // The scaled version is ready.
@@ -1741,17 +1758,17 @@ RasterImage::Draw(gfxContext* aContext,
 
   if (mError) {
     return DrawResult::BAD_IMAGE;
   }
 
   // Illegal -- you can't draw with non-default decode flags.
   // (Disabling colorspace conversion might make sense to allow, but
   // we don't currently.)
-  if (DecodeFlags(aFlags) != DECODE_FLAGS_DEFAULT) {
+  if (ToSurfaceFlags(aFlags) != DefaultSurfaceFlags()) {
     return DrawResult::BAD_ARGS;
   }
 
   if (!aContext) {
     return DrawResult::BAD_ARGS;
   }
 
   if (IsUnlocked() && mProgressTracker) {
@@ -1932,24 +1949,25 @@ RasterImage::GetFramesNotified(uint32_t*
 
   return NS_OK;
 }
 #endif
 
 void
 RasterImage::NotifyProgress(Progress aProgress,
                             const IntRect& aInvalidRect /* = IntRect() */,
-                            uint32_t aFlags /* = DECODE_FLAGS_DEFAULT */)
+                            SurfaceFlags aSurfaceFlags
+                              /* = DefaultSurfaceFlags() */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Ensure that we stay alive long enough to finish notifying.
   nsRefPtr<RasterImage> image(this);
 
-  bool wasDefaultFlags = aFlags == DECODE_FLAGS_DEFAULT;
+  bool wasDefaultFlags = aSurfaceFlags == DefaultSurfaceFlags();
 
   if (!aInvalidRect.IsEmpty() && wasDefaultFlags) {
     // Update our image container since we're invalidating.
     UpdateImageContainer();
   }
 
   // Tell the observers what happened.
   image->mProgressTracker->SyncNotifyProgress(aProgress, aInvalidRect);
@@ -1984,17 +2002,17 @@ RasterImage::FinalizeDecoder(Decoder* aD
     if (mAnim) {
       mAnim->SetDoneDecoding(true);
     }
   }
 
   // Send out any final notifications.
   NotifyProgress(aDecoder->TakeProgress(),
                  aDecoder->TakeInvalidRect(),
-                 aDecoder->GetDecodeFlags());
+                 aDecoder->GetSurfaceFlags());
 
   bool wasMetadata = aDecoder->IsMetadataDecode();
   bool done = aDecoder->GetDecodeDone();
 
   if (!wasMetadata && aDecoder->ChunkCount()) {
     Telemetry::Accumulate(Telemetry::IMAGE_DECODE_CHUNKS,
                           aDecoder->ChunkCount());
   }
@@ -2089,18 +2107,18 @@ RasterImage::OptimalImageSizeForDest(con
 
   if (aFilter == GraphicsFilter::FILTER_GOOD &&
       CanDownscaleDuringDecode(destSize, aFlags)) {
     return destSize;
   } else if (CanScale(aFilter, destSize, aFlags)) {
     LookupResult result =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(destSize,
-                                            DecodeFlags(aFlags),
-                                            0));
+                                            ToSurfaceFlags(aFlags),
+                                            /* aFrameNum = */ 0));
 
     if (result && result.DrawableRef()->IsImageComplete()) {
       return destSize;  // We have an existing HQ scale for this size.
     }
     if (!result) {
       // We could HQ scale to this size, but we haven't. Request a scale now.
       DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
                                          mSize, aFlags);
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -130,27 +130,16 @@ class Image;
 
 namespace image {
 
 class Decoder;
 class FrameAnimator;
 class ImageMetadata;
 class SourceBuffer;
 
-/**
- * Given a set of imgIContainer FLAG_* flags, returns those flags that can
- * affect the output of decoders.
- */
-inline MOZ_CONSTEXPR uint32_t
-DecodeFlags(uint32_t aFlags)
-{
-  return aFlags & (imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA |
-                   imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION);
-}
-
 class RasterImage final : public ImageResource
                         , public nsIProperties
                         , public SupportsWeakPtr<RasterImage>
 #ifdef DEBUG
                         , public imgIContainerDebug
 #endif
 {
   // (no public constructor - use ImageFactory)
@@ -191,23 +180,23 @@ public:
 
   /**
    * Sends the provided progress notifications to ProgressTracker.
    *
    * Main-thread only.
    *
    * @param aProgress    The progress notifications to send.
    * @param aInvalidRect An invalidation rect to send.
-   * @param aFlags       The decode flags used by the decoder that generated
-   *                     these notifications, or DECODE_FLAGS_DEFAULT if the
+   * @param aFlags       The surface flags used by the decoder that generated
+   *                     these notifications, or DefaultSurfaceFlags() if the
    *                     notifications don't come from a decoder.
    */
   void NotifyProgress(Progress aProgress,
                       const nsIntRect& aInvalidRect = nsIntRect(),
-                      uint32_t aFlags = DECODE_FLAGS_DEFAULT);
+                      SurfaceFlags aSurfaceFlags = DefaultSurfaceFlags());
 
   /**
    * Records telemetry and does final teardown of the provided decoder.
    *
    * Main-thread only.
    */
   void FinalizeDecoder(Decoder* aDecoder);
 
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -278,17 +278,17 @@ public:
   {
     nsRefPtr<CachedSurface> surface;
     mSurfaces.Get(aSurfaceKey, getter_AddRefs(surface));
     return surface.forget();
   }
 
   Pair<already_AddRefed<CachedSurface>, MatchType>
   LookupBestMatch(const SurfaceKey&      aSurfaceKey,
-                  const Maybe<uint32_t>& aAlternateFlags)
+                  const Maybe<SurfaceFlags>& aAlternateFlags)
   {
     // Try for an exact match first.
     nsRefPtr<CachedSurface> exactMatch;
     mSurfaces.Get(aSurfaceKey, getter_AddRefs(exactMatch));
     if (exactMatch && exactMatch->IsDecoded()) {
       return MakePair(exactMatch.forget(), MatchType::EXACT);
     }
 
@@ -330,23 +330,23 @@ public:
 
   void SetLocked(bool aLocked) { mLocked = aLocked; }
   bool IsLocked() const { return mLocked; }
 
 private:
   struct MatchContext
   {
     MatchContext(const SurfaceKey& aIdealKey,
-                 const Maybe<uint32_t>& aAlternateFlags)
+                 const Maybe<SurfaceFlags>& aAlternateFlags)
       : mIdealKey(aIdealKey)
       , mAlternateFlags(aAlternateFlags)
     { }
 
     const SurfaceKey& mIdealKey;
-    const Maybe<uint32_t> mAlternateFlags;
+    const Maybe<SurfaceFlags> mAlternateFlags;
     nsRefPtr<CachedSurface> mBestMatch;
   };
 
   static PLDHashOperator TryToImproveMatch(const SurfaceKey& aSurfaceKey,
                                            CachedSurface*    aSurface,
                                            void*             aContext)
   {
     auto context = static_cast<MatchContext*>(aContext);
@@ -639,17 +639,17 @@ public:
 
     MOZ_ASSERT(surface->GetSurfaceKey() == aSurfaceKey,
                "Lookup() not returning an exact match?");
     return LookupResult(Move(ref), MatchType::EXACT);
   }
 
   LookupResult LookupBestMatch(const ImageKey         aImageKey,
                                const SurfaceKey&      aSurfaceKey,
-                               const Maybe<uint32_t>& aAlternateFlags)
+                               const Maybe<SurfaceFlags>& aAlternateFlags)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       // No cached surfaces for this image.
       return LookupResult(MatchType::NOT_FOUND);
     }
 
     // Repeatedly look up the best match, trying again if the resulting surface
@@ -1055,17 +1055,18 @@ SurfaceCache::Shutdown()
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
   sInstance = nullptr;
 }
 
 /* static */ LookupResult
 SurfaceCache::Lookup(const ImageKey         aImageKey,
                      const SurfaceKey&      aSurfaceKey,
-                     const Maybe<uint32_t>& aAlternateFlags /* = Nothing() */)
+                     const Maybe<SurfaceFlags>& aAlternateFlags
+                       /* = Nothing() */)
 {
   if (!sInstance) {
     return LookupResult(MatchType::NOT_FOUND);
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
 
   LookupResult result = sInstance->Lookup(aImageKey, aSurfaceKey);
@@ -1075,17 +1076,17 @@ SurfaceCache::Lookup(const ImageKey     
   }
 
   return result;
 }
 
 /* static */ LookupResult
 SurfaceCache::LookupBestMatch(const ImageKey         aImageKey,
                               const SurfaceKey&      aSurfaceKey,
-                              const Maybe<uint32_t>& aAlternateFlags
+                              const Maybe<SurfaceFlags>& aAlternateFlags
                                 /* = Nothing() */)
 {
   if (!sInstance) {
     return LookupResult(MatchType::NOT_FOUND);
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->LookupBestMatch(aImageKey, aSurfaceKey, aAlternateFlags);
--- a/image/SurfaceCache.h
+++ b/image/SurfaceCache.h
@@ -14,16 +14,17 @@
 #include "mozilla/Maybe.h"           // for Maybe
 #include "mozilla/MemoryReporting.h" // for MallocSizeOf
 #include "mozilla/HashFunctions.h"   // for HashGeneric and AddToHash
 #include "gfx2DGlue.h"
 #include "gfxPoint.h"                // for gfxSize
 #include "nsCOMPtr.h"                // for already_AddRefed
 #include "mozilla/gfx/Point.h"       // for mozilla::gfx::IntSize
 #include "mozilla/gfx/2D.h"          // for SourceSurface
+#include "SurfaceFlags.h"
 #include "SVGImageContext.h"         // for SVGImageContext
 
 namespace mozilla {
 namespace image {
 
 class Image;
 class imgFrame;
 class LookupResult;
@@ -54,73 +55,75 @@ public:
            aOther.mAnimationTime == mAnimationTime &&
            aOther.mFlags == mFlags;
   }
 
   uint32_t Hash() const
   {
     uint32_t hash = HashGeneric(mSize.width, mSize.height);
     hash = AddToHash(hash, mSVGContext.map(HashSIC).valueOr(0));
-    hash = AddToHash(hash, mAnimationTime, mFlags);
+    hash = AddToHash(hash, mAnimationTime, uint32_t(mFlags));
     return hash;
   }
 
   IntSize Size() const { return mSize; }
   Maybe<SVGImageContext> SVGContext() const { return mSVGContext; }
   float AnimationTime() const { return mAnimationTime; }
-  uint32_t Flags() const { return mFlags; }
+  SurfaceFlags Flags() const { return mFlags; }
 
-  SurfaceKey WithNewFlags(uint32_t aFlags) const
+  SurfaceKey WithNewFlags(SurfaceFlags aFlags) const
   {
     return SurfaceKey(mSize, mSVGContext, mAnimationTime, aFlags);
   }
 
 private:
   SurfaceKey(const IntSize& aSize,
              const Maybe<SVGImageContext>& aSVGContext,
              const float aAnimationTime,
-             const uint32_t aFlags)
+             const SurfaceFlags aFlags)
     : mSize(aSize)
     , mSVGContext(aSVGContext)
     , mAnimationTime(aAnimationTime)
     , mFlags(aFlags)
   { }
 
   static uint32_t HashSIC(const SVGImageContext& aSIC) {
     return aSIC.Hash();
   }
 
-  friend SurfaceKey RasterSurfaceKey(const IntSize&, uint32_t, uint32_t);
+  friend SurfaceKey RasterSurfaceKey(const IntSize&,
+                                     SurfaceFlags,
+                                     uint32_t);
   friend SurfaceKey VectorSurfaceKey(const IntSize&,
                                      const Maybe<SVGImageContext>&,
                                      float);
 
   IntSize                mSize;
   Maybe<SVGImageContext> mSVGContext;
   float                  mAnimationTime;
-  uint32_t               mFlags;
+  SurfaceFlags           mFlags;
 };
 
 inline SurfaceKey
 RasterSurfaceKey(const gfx::IntSize& aSize,
-                 uint32_t aFlags,
+                 SurfaceFlags aFlags,
                  uint32_t aFrameNum)
 {
   return SurfaceKey(aSize, Nothing(), float(aFrameNum), aFlags);
 }
 
 inline SurfaceKey
 VectorSurfaceKey(const gfx::IntSize& aSize,
                  const Maybe<SVGImageContext>& aSVGContext,
                  float aAnimationTime)
 {
   // We don't care about aFlags for VectorImage because none of the flags we
   // have right now influence VectorImage's rendering. If we add a new flag that
   // *does* affect how a VectorImage renders, we'll have to change this.
-  return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0);
+  return SurfaceKey(aSize, aSVGContext, aAnimationTime, DefaultSurfaceFlags());
 }
 
 enum class Lifetime : uint8_t {
   Transient,
   Persistent
 };
 
 enum class InsertOutcome : uint8_t {
@@ -191,17 +194,18 @@ struct SurfaceCache
    *                        lock each time.
    *
    * @return                a LookupResult, which will either contain a
    *                        DrawableFrameRef to the requested surface, or an
    *                        empty DrawableFrameRef if the surface was not found.
    */
   static LookupResult Lookup(const ImageKey    aImageKey,
                              const SurfaceKey& aSurfaceKey,
-                             const Maybe<uint32_t>& aAlternateFlags = Nothing());
+                             const Maybe<SurfaceFlags>& aAlternateFlags
+                               = Nothing());
 
   /**
    * Looks up the best matching surface in the cache and returns a drawable
    * reference to the imgFrame containing it.
    *
    * Returned surfaces may vary from the requested surface only in terms of
    * size, unless @aAlternateFlags is specified.
    *
@@ -219,17 +223,17 @@ struct SurfaceCache
    *                        DrawableFrameRef to a surface similar to the
    *                        requested surface, or an empty DrawableFrameRef if
    *                        the surface was not found. Callers can use
    *                        LookupResult::IsExactMatch() to check whether the
    *                        returned surface exactly matches @aSurfaceKey.
    */
   static LookupResult LookupBestMatch(const ImageKey    aImageKey,
                                       const SurfaceKey& aSurfaceKey,
-                                      const Maybe<uint32_t>& aAlternateFlags
+                                      const Maybe<SurfaceFlags>& aAlternateFlags
                                         = Nothing());
 
   /**
    * Insert a surface into the cache. If a surface with the same ImageKey and
    * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT.
    * If a matching placeholder is already present, the placeholder is removed.
    *
    * Each surface in the cache has a lifetime, either Transient or Persistent.
new file mode 100644
--- /dev/null
+++ b/image/SurfaceFlags.h
@@ -0,0 +1,56 @@
+/* -*- 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/. */
+
+#ifndef mozilla_image_SurfaceFlags_h
+#define mozilla_image_SurfaceFlags_h
+
+#include "imgIContainer.h"
+#include "mozilla/TypedEnumBits.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * Flags that change the output a decoder generates. Because different
+ * combinations of these flags result in logically different surfaces, these
+ * flags must be taken into account in SurfaceCache lookups.
+ */
+enum class SurfaceFlags : uint8_t
+{
+  NO_PREMULTIPLY_ALPHA     = 1 << 0,
+  NO_COLORSPACE_CONVERSION = 1 << 1
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SurfaceFlags)
+
+/**
+ * @return the default set of surface flags.
+ */
+inline SurfaceFlags
+DefaultSurfaceFlags()
+{
+  return SurfaceFlags();
+}
+
+/**
+ * Given a set of imgIContainer FLAG_* flags, returns a set of SurfaceFlags with
+ * the corresponding flags set.
+ */
+inline SurfaceFlags
+ToSurfaceFlags(uint32_t aFlags)
+{
+  SurfaceFlags flags = DefaultSurfaceFlags();
+  if (aFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
+    flags |= SurfaceFlags::NO_PREMULTIPLY_ALPHA;
+  }
+  if (aFlags & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
+    flags |= SurfaceFlags::NO_COLORSPACE_CONVERSION;
+  }
+  return flags;
+}
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_SurfaceFlags_h
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -355,20 +355,18 @@ nsICODecoder::WriteInternal(const char* 
     aCount -= toCopy;
     aBuffer += toCopy;
 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
-      mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
-      if (mFirstFrameDecode) {
-        mContainedDecoder->SetIsFirstFrameDecode();
-      }
+      mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
+      mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
       mContainedDecoder->Init();
       if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
         return;
       }
     }
   }
 
   // If we have a PNG, let the PNG decoder do all of the rest of the work
@@ -435,20 +433,18 @@ nsICODecoder::WriteInternal(const char* 
 
     // Init the bitmap decoder which will do most of the work for us
     // It will do everything except the AND mask which isn't present in bitmaps
     // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
     nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
     mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
-    mContainedDecoder->SetSendPartialInvalidations(mSendPartialInvalidations);
-    if (mFirstFrameDecode) {
-      mContainedDecoder->SetIsFirstFrameDecode();
-    }
+    mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
+    mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
     mContainedDecoder->Init();
 
     // The ICO format when containing a BMP does not include the 14 byte
     // bitmap file header. To use the code of the BMP decoder we need to
     // generate this header ourselves and feed it to the BMP decoder.
     int8_t bfhBuffer[BMPFILEHEADERSIZE];
     if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
       PostDataError();
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -149,17 +149,17 @@ nsJPEGDecoder::SetTargetSize(const nsInt
 
   return NS_OK;
 }
 
 void
 nsJPEGDecoder::InitInternal()
 {
   mCMSMode = gfxPlatform::GetCMSMode();
-  if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
+  if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
     mCMSMode = eCMSMode_Off;
   }
 
   // We set up the normal JPEG error routines, then override error_exit.
   mInfo.err = jpeg_std_error(&mErr.pub);
   //   mInfo.err = jpeg_std_error(&mErr.pub);
   mErr.pub.error_exit = my_error_exit;
   // Establish the setjmp return context for my_error_exit to use.
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -228,21 +228,21 @@ nsPNGDecoder::EndImageFrame()
   PostFrameStop(opacity, mAnimInfo.mDispose, mAnimInfo.mTimeout,
                 mAnimInfo.mBlend);
 }
 
 void
 nsPNGDecoder::InitInternal()
 {
   mCMSMode = gfxPlatform::GetCMSMode();
-  if (GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION) {
+  if (GetSurfaceFlags() & SurfaceFlags::NO_COLORSPACE_CONVERSION) {
     mCMSMode = eCMSMode_Off;
   }
   mDisablePremultipliedAlpha =
-    GetDecodeFlags() & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
+    bool(GetSurfaceFlags() & SurfaceFlags::NO_PREMULTIPLY_ALPHA);
 
 #ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
   static png_byte color_chunks[]=
        { 99,  72,  82,  77, '\0',   // cHRM
         105,  67,  67,  80, '\0'};  // iCCP
   static png_byte unused_chunks[]=
        { 98,  75,  71,  68, '\0',   // bKGD
         104,  73,  83,  84, '\0',   // hIST
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -271,19 +271,20 @@ private:
       surfacePathPrefix.AppendInt(counter.Key().Size().width);
       surfacePathPrefix.Append("x");
       surfacePathPrefix.AppendInt(counter.Key().Size().height);
 
       if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
         surfacePathPrefix.Append("@");
         surfacePathPrefix.AppendFloat(counter.Key().AnimationTime());
 
-        if (counter.Key().Flags() != imgIContainer::DECODE_FLAGS_DEFAULT) {
+        if (counter.Key().Flags() != DefaultSurfaceFlags()) {
           surfacePathPrefix.Append(", flags:");
-          surfacePathPrefix.AppendInt(counter.Key().Flags(), /* aRadix = */ 16);
+          surfacePathPrefix.AppendInt(uint32_t(counter.Key().Flags()),
+                                      /* aRadix = */ 16);
         }
       } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
         surfacePathPrefix.Append(", compositing frame");
       } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) {
         surfacePathPrefix.Append(", compositing prev frame");
       } else {
         MOZ_ASSERT_UNREACHABLE("Unknown counter type");
       }
--- a/image/moz.build
+++ b/image/moz.build
@@ -41,16 +41,17 @@ EXPORTS += [
     'ImageOps.h',
     'ImageRegion.h',
     'imgLoader.h',
     'imgRequest.h',
     'imgRequestProxy.h',
     'IProgressObserver.h',
     'Orientation.h',
     'SurfaceCache.h',
+    'SurfaceFlags.h',
 ]
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
     'DynamicImage.cpp',
--- a/image/test/gtest/TestMetadata.cpp
+++ b/image/test/gtest/TestMetadata.cpp
@@ -106,17 +106,17 @@ CheckMetadata(const ImageTestCase& aTest
   EXPECT_EQ(expectTransparency, bool(metadataProgress & FLAG_HAS_TRANSPARENCY));
 
   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,
-                                           imgIContainer::DECODE_FLAGS_DEFAULT);
+                                           DefaultSurfaceFlags());
   ASSERT_TRUE(decoder != nullptr);
 
   if (aBMPAlpha == BMPAlpha::ENABLED) {
     static_cast<nsBMPDecoder*>(decoder.get())->SetUseAlphaData(true);
   }
 
   // Run the full decoder synchronously.
   decoder->Decode();
@@ -236,19 +236,19 @@ TEST(ImageMetadata, NoFrameDelayGIFFullD
 
   EXPECT_TRUE(bool(imageProgress & FLAG_HAS_TRANSPARENCY) == false);
   EXPECT_TRUE(bool(imageProgress & FLAG_IS_ANIMATED) == true);
 
   // Ensure that we decoded both frames of the image.
   LookupResult firstFrameLookupResult =
     SurfaceCache::Lookup(ImageKey(image.get()),
                          RasterSurfaceKey(imageSize,
-                                          imgIContainer::DECODE_FLAGS_DEFAULT,
+                                          DefaultSurfaceFlags(),
                                           /* aFrameNum = */ 0));
   EXPECT_EQ(MatchType::EXACT, firstFrameLookupResult.Type());
                                                              
   LookupResult secondFrameLookupResult =
     SurfaceCache::Lookup(ImageKey(image.get()),
                          RasterSurfaceKey(imageSize,
-                                          imgIContainer::DECODE_FLAGS_DEFAULT,
+                                          DefaultSurfaceFlags(),
                                           /* aFrameNum = */ 1));
   EXPECT_EQ(MatchType::EXACT, secondFrameLookupResult.Type());
 }