Bug 896268 - Use a stateless approach to synchronous image decoding. r=jdm
authorSeth Fowler <seth@mozilla.com>
Tue, 17 Dec 2013 14:04:24 -0800
changeset 160937 772ce6463fd861982415edc813edc634565ca08f
parent 160936 9524bfcff11000cef12b3dfc4491d01e9c56f36c
child 160938 848b57d9d85b5019183775e265137b7ac29c8972
push id25859
push userkwierso@gmail.com
push dateWed, 18 Dec 2013 05:00:23 +0000
treeherdermozilla-central@862cb6a1cc88 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjdm
bugs896268
milestone29.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 896268 - Use a stateless approach to synchronous image decoding. r=jdm
image/decoders/nsBMPDecoder.cpp
image/decoders/nsBMPDecoder.h
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsGIFDecoder2.h
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/decoders/nsIconDecoder.cpp
image/decoders/nsIconDecoder.h
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsJPEGDecoder.h
image/decoders/nsPNGDecoder.cpp
image/decoders/nsPNGDecoder.h
image/src/DecodeStrategy.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -184,17 +184,17 @@ NS_METHOD nsBMPDecoder::CalcBitShift()
     // blue
     calcBitmask(mBitFields.blue, begin, length);
     mBitFields.blueRightShift = begin;
     mBitFields.blueLeftShift = 8 - length;
     return NS_OK;
 }
 
 void
-nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
+nsBMPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy)
 {
     NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
     // aCount=0 means EOF, mCurLine=0 means we're past end of image
     if (!aCount || !mCurLine)
         return;
 
     if (mPos < BFH_INTERNAL_LENGTH) { /* In BITMAPFILEHEADER */
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -40,17 +40,17 @@ public:
     // Obtains the internal output image buffer
     uint32_t* GetImageData();
     // Obtains the size of the compressed image resource
     int32_t GetCompressedImageSize() const;
     // Obtains whether or not a BMP file had alpha data in its 4th byte
     // for 32BPP bitmaps.  Only use after the bitmap has been processed.
     bool HasAlphaData() const;
 
-    virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+    virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
     virtual void FinishInternal();
 
 private:
 
     /** Calculates the red-, green- and blueshift in mBitFields using
      * the bitmasks from mBitFields */
     NS_METHOD CalcBitShift();
 
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -539,17 +539,17 @@ static void ConvertColormap(uint32_t *aC
   // NB: can't use 32-bit reads, they might read off the end of the buffer
   while (c--) {
     from -= 3;
     *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]);
   }
 }
 
 void
-nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
+nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // These variables changed names, and renaming would make a much bigger patch :(
   const uint8_t *buf = (const uint8_t *)aBuffer;
   uint32_t len = aCount;
 
   const uint8_t *q = buf;
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -21,17 +21,17 @@ class RasterImage;
 
 class nsGIFDecoder2 : public Decoder
 {
 public:
 
   nsGIFDecoder2(RasterImage &aImage);
   ~nsGIFDecoder2();
 
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
   virtual void FinishInternal();
   virtual Telemetry::ID SpeedHistogram();
 
 private:
   /* These functions will be called when the decoder has a decoded row,
    * frame size information, etc. */
 
   void      BeginGIF();
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -204,23 +204,23 @@ nsICODecoder::SetHotSpotIfCursor() {
   if (!mIsCursor) {
     return;
   }
 
   mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
 }
 
 void
-nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
+nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   if (!aCount) {
     if (mContainedDecoder) {
-      WriteToContainedDecoder(aBuffer, aCount);
+      WriteToContainedDecoder(aBuffer, aCount, aStrategy);
     }
     return;
   }
 
   while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
     if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
       if ((*aBuffer != 1) && (*aBuffer != 2)) {
         PostDataError();
@@ -332,25 +332,25 @@ nsICODecoder::WriteInternal(const char* 
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetObserver(mObserver);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
       mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                            mColormap, mColormapSize,
                                            mCurrentFrame);
-      if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
+      if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE, aStrategy)) {
         return;
       }
     }
   }
 
   // If we have a PNG, let the PNG decoder do all of the rest of the work
   if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
-    if (!WriteToContainedDecoder(aBuffer, aCount)) {
+    if (!WriteToContainedDecoder(aBuffer, aCount, aStrategy)) {
       return;
     }
 
     if (!HasSize() && mContainedDecoder->HasSize()) {
       PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
                mContainedDecoder->GetImageMetadata().GetHeight());
     }
 
@@ -418,17 +418,17 @@ nsICODecoder::WriteInternal(const char* 
     // 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();
       return;
     }
-    if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
+    if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer), aStrategy)) {
       return;
     }
 
     // Setup the cursor hot spot if one is present
     SetHotSpotIfCursor();
 
     // Fix the ICO height from the BIH.
     // Fix the height on the BIH to be /2 so our BMP decoder will understand.
@@ -439,17 +439,17 @@ nsICODecoder::WriteInternal(const char* 
 
     // Fix the ICO width from the BIH.
     if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
       PostDataError();
       return;
     }
 
     // Write out the BMP's bitmap info header
-    if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
+    if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw), aStrategy)) {
       return;
     }
 
     PostSize(mContainedDecoder->GetImageMetadata().GetWidth(),
              mContainedDecoder->GetImageMetadata().GetHeight());
 
     // We have the size. If we're doing a size decode, we got what
     // we came for.
@@ -487,17 +487,17 @@ nsICODecoder::WriteInternal(const char* 
     if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
 
       // Figure out how much data the BMP decoder wants
       uint32_t toFeed = bmpDataEnd - mPos;
       if (toFeed > aCount) {
         toFeed = aCount;
       }
 
-      if (!WriteToContainedDecoder(aBuffer, toFeed)) {
+      if (!WriteToContainedDecoder(aBuffer, toFeed, aStrategy)) {
         return;
       }
 
       mPos += toFeed;
       aCount -= toFeed;
       aBuffer += toFeed;
     }
   
@@ -563,19 +563,19 @@ nsICODecoder::WriteInternal(const char* 
           }
         }
       }
     }
   }
 }
 
 bool
-nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
+nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
 {
-  mContainedDecoder->Write(aBuffer, aCount);
+  mContainedDecoder->Write(aBuffer, aCount, aStrategy);
   if (mContainedDecoder->HasDataError()) {
     mDataError = mContainedDecoder->HasDataError();
   }
   if (mContainedDecoder->HasDecoderError()) {
     PostDecoderError(mContainedDecoder->GetDecoderError());
   }
   return !HasError();
 }
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -32,25 +32,25 @@ public:
   }
 
   // Obtains the height of the icon directory entry
   uint32_t GetRealHeight() const
   {
     return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight; 
   }
 
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
   virtual void FinishInternal();
   virtual bool NeedsNewFrame() const;
   virtual nsresult AllocateFrame();
 
 private:
   // Writes to the contained decoder and sets the appropriate errors
   // Returns true if there are no errors.
-  bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount);
+  bool WriteToContainedDecoder(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
 
   // Processes a single dir entry of the icon resource
   void ProcessDirEntry(IconDirEntry& aTarget);
   // Sets the hotspot property of if we have a cursor
   void SetHotSpotIfCursor();
   // Creates a bitmap file header buffer, returns true if successful
   bool FillBitmapFileHeaderBuffer(int8_t *bfh);
   // Fixes the ICO height to match that of the BIH.
--- a/image/decoders/nsIconDecoder.cpp
+++ b/image/decoders/nsIconDecoder.cpp
@@ -25,17 +25,17 @@ nsIconDecoder::nsIconDecoder(RasterImage
 {
   // Nothing to do
 }
 
 nsIconDecoder::~nsIconDecoder()
 { }
 
 void
-nsIconDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
+nsIconDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // We put this here to avoid errors about crossing initialization with case
   // jumps on linux.
   uint32_t bytesToRead = 0;
 
   // Loop until the input data is gone
--- a/image/decoders/nsIconDecoder.h
+++ b/image/decoders/nsIconDecoder.h
@@ -36,17 +36,17 @@ class RasterImage;
 
 class nsIconDecoder : public Decoder
 {
 public:
 
   nsIconDecoder(RasterImage &aImage);
   virtual ~nsIconDecoder();
 
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
 
   uint8_t mWidth;
   uint8_t mHeight;
   uint32_t mPixBytesRead;
   uint32_t mState;
 };
 
 enum {
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -176,25 +176,30 @@ nsJPEGDecoder::InitInternal()
 void
 nsJPEGDecoder::FinishInternal()
 {
   /* If we're not in any sort of error case, flush the decoder.
    *
    * XXXbholley - It seems wrong that this should be necessary, but at the
    * moment I'm just folding the contents of Flush() into Close() so that
    * we can get rid of it.
+   *
+   * XXX(seth): It'd be great to get rid of this. For now, we treat this as a
+   * write to a synchronous decoder, which means that this must be called only
+   * on the main thread. (That's asserted in Decoder::Finish and
+   * Decoder::FinishSharedDecoder.)
    */
   if ((mState != JPEG_DONE && mState != JPEG_SINK_NON_JPEG_TRAILER) &&
       (mState != JPEG_ERROR) &&
       !IsSizeDecode())
-    this->Write(nullptr, 0);
+    this->Write(nullptr, 0, DECODE_SYNC);
 }
 
 void
-nsJPEGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
+nsJPEGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy)
 {
   mSegment = (const JOCTET *)aBuffer;
   mSegmentLen = aCount;
 
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   /* Return here if there is a fatal error within libjpeg. */
   nsresult error_code;
--- a/image/decoders/nsJPEGDecoder.h
+++ b/image/decoders/nsJPEGDecoder.h
@@ -51,17 +51,17 @@ class Orientation;
 
 class nsJPEGDecoder : public Decoder
 {
 public:
   nsJPEGDecoder(RasterImage &aImage, Decoder::DecodeStyle aDecodeStyle);
   virtual ~nsJPEGDecoder();
 
   virtual void InitInternal();
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
   virtual void FinishInternal();
 
   virtual Telemetry::ID SpeedHistogram();
   void NotifyDone();
 
 protected:
   Orientation ReadOrientationFromEXIF();
   void OutputScanlines(bool* suspend);
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -281,17 +281,17 @@ nsPNGDecoder::InitInternal()
   png_set_progressive_read_fn(mPNG, static_cast<png_voidp>(this),
                               nsPNGDecoder::info_callback,
                               nsPNGDecoder::row_callback,
                               nsPNGDecoder::end_callback);
 
 }
 
 void
-nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
+nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy)
 {
   NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
 
   // If we only want width/height, we don't need to go through libpng
   if (IsSizeDecode()) {
 
     // Are we done?
     if (mHeaderBytesRead == BYTES_NEEDED_FOR_DIMENSIONS)
--- a/image/decoders/nsPNGDecoder.h
+++ b/image/decoders/nsPNGDecoder.h
@@ -23,17 +23,17 @@ class RasterImage;
 
 class nsPNGDecoder : public Decoder
 {
 public:
   nsPNGDecoder(RasterImage &aImage);
   virtual ~nsPNGDecoder();
 
   virtual void InitInternal();
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
   virtual Telemetry::ID SpeedHistogram();
 
   void CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
                    int32_t width, int32_t height,
                    gfxImageFormat format);
   void EndImageFrame();
 
   // Check if PNG is valid ICO (32bpp RGBA)
new file mode 100644
--- /dev/null
+++ b/image/src/DecodeStrategy.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+/** @file
+ * An enumeration used by RasterImage and Decoder to specify which 'strategy' to
+ * use for image decoding - synchronous or asynchronous.
+ */
+
+#ifndef mozilla_imagelib_DecodeStrategy_h_
+#define mozilla_imagelib_DecodeStrategy_h_
+
+namespace mozilla {
+namespace image {
+
+enum DecodeStrategy {
+  // DECODE_SYNC requests a synchronous decode, which will continue decoding
+  // frames as long as it has more source data. It returns to the caller only
+  // once decoding is complete (or until it needs more source data before
+  // continuing). Because DECODE_SYNC can involve allocating new imgFrames, it
+  // can only be run on the main thread.
+  DECODE_SYNC,
+
+  // DECODE_ASYNC requests an asynchronous decode, which will continue decoding
+  // until it either finishes a frame or runs out of source data. Because
+  // DECODE_ASYNC does not allocate new imgFrames, it can be safely run off the
+  // main thread. (And hence workers in the decode pool always use it.)
+  DECODE_ASYNC
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -24,17 +24,16 @@ Decoder::Decoder(RasterImage &aImage)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mNeedsNewFrame(false)
   , mInitialized(false)
   , mSizeDecode(false)
   , mInFrame(false)
   , mIsAnimated(false)
-  , mSynchronous(false)
 {
 }
 
 Decoder::~Decoder()
 {
   mInitialized = false;
 }
 
@@ -81,51 +80,54 @@ Decoder::InitSharedDecoder(uint8_t* imag
   }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
 void
-Decoder::Write(const char* aBuffer, uint32_t aCount)
+Decoder::Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
 {
   PROFILER_LABEL("ImageDecoder", "Write");
+  MOZ_ASSERT(NS_IsMainThread() || aStrategy == DECODE_ASYNC);
 
   // We're strict about decoder errors
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
 
   // If a data error occured, just ignore future data
   if (HasDataError())
     return;
 
   if (IsSizeDecode() && HasSize()) {
     // More data came in since we found the size. We have nothing to do here.
     return;
   }
 
   // Pass the data along to the implementation
-  WriteInternal(aBuffer, aCount);
+  WriteInternal(aBuffer, aCount, aStrategy);
 
   // If we're a synchronous decoder and we need a new frame to proceed, let's
   // create one and call it again.
-  while (mSynchronous && NeedsNewFrame() && !HasDataError()) {
+  while (aStrategy == DECODE_SYNC && NeedsNewFrame() && !HasDataError()) {
     nsresult rv = AllocateFrame();
 
     if (NS_SUCCEEDED(rv)) {
       // Tell the decoder to use the data it saved when it asked for a new frame.
-      WriteInternal(nullptr, 0);
+      WriteInternal(nullptr, 0, aStrategy);
     }
   }
 }
 
 void
 Decoder::Finish(RasterImage::eShutdownIntent aShutdownIntent)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   // Implementation-specific finalization
   if (!HasError())
     FinishInternal();
 
   // If the implementation left us mid-frame, finish that up.
   if (mInFrame && !HasError())
     PostFrameStop();
 
@@ -181,16 +183,18 @@ Decoder::Finish(RasterImage::eShutdownIn
   if (mDecodeDone) {
     mImage.DecodingComplete();
   }
 }
 
 void
 Decoder::FinishSharedDecoder()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (!HasError()) {
     FinishInternal();
   }
 }
 
 nsresult
 Decoder::AllocateFrame()
 {
@@ -275,17 +279,17 @@ Decoder::SetSizeOnImage()
                  mImageMetadata.GetOrientation());
 }
 
 /*
  * Hook stubs. Override these as necessary in decoder implementations.
  */
 
 void Decoder::InitInternal() { }
-void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount) { }
+void Decoder::WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy) { }
 void Decoder::FinishInternal() { }
 
 /*
  * Progress Notifications
  */
 
 void
 Decoder::PostSize(int32_t aWidth,
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_IMAGELIB_DECODER_H_
 #define MOZILLA_IMAGELIB_DECODER_H_
 
 #include "RasterImage.h"
 #include "imgDecoderObserver.h"
 #include "mozilla/RefPtr.h"
+#include "DecodeStrategy.h"
 #include "ImageMetadata.h"
 #include "Orientation.h"
 #include "mozilla/Telemetry.h"
 
 namespace mozilla {
 namespace image {
 
 class Decoder
@@ -45,17 +46,17 @@ public:
    *
    * @param aBuffer buffer containing the data to be written
    * @param aCount the number of bytes to write
    *
    * Any errors are reported by setting the appropriate state on the decoder.
    *
    * Notifications Sent: TODO
    */
-  void Write(const char* aBuffer, uint32_t aCount);
+  void Write(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
 
   /**
    * Informs the decoder that all the data has been written.
    *
    * Notifications Sent: TODO
    */
   void Finish(RasterImage::eShutdownIntent aShutdownIntent);
 
@@ -89,21 +90,16 @@ public:
   // must be enabled by SetSizeDecode() _before_calling Init().
   bool IsSizeDecode() { return mSizeDecode; }
   void SetSizeDecode(bool aSizeDecode)
   {
     NS_ABORT_IF_FALSE(!mInitialized, "Can't set size decode after Init()!");
     mSizeDecode = aSizeDecode;
   }
 
-  bool IsSynchronous() const
-  {
-    return mSynchronous;
-  }
-
   void SetObserver(imgDecoderObserver* aObserver)
   {
     MOZ_ASSERT(aObserver);
     mObserver = aObserver;
   }
 
   // The number of frames we have, including anything in-progress. Thus, this
   // is only 0 if we haven't begun any frames.
@@ -172,17 +168,17 @@ public:
 
 protected:
 
   /*
    * Internal hooks. Decoder implementations may override these and
    * only these methods.
    */
   virtual void InitInternal();
-  virtual void WriteInternal(const char* aBuffer, uint32_t aCount);
+  virtual void WriteInternal(const char* aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
   virtual void FinishInternal();
 
   /*
    * Progress notifications.
    */
 
   // Called by decoders when they determine the size of the image. Informs
   // the image of its size and sends notifications.
@@ -236,27 +232,16 @@ protected:
   uint32_t* mColormap;       // Current colormap to be used in Cairo format
   uint32_t mColormapSize;
 
   uint32_t mDecodeFlags;
   bool mDecodeDone;
   bool mDataError;
 
 private:
-  // Decode in synchronous mode. This is unsafe off-main-thread since it may
-  // attempt to allocate frames. To ensure that we never accidentally leave the
-  // decoder in synchronous mode, this should only be called by
-  // AutoSetSyncDecode.
-  void SetSynchronous(bool aSynchronous)
-  {
-    mSynchronous = aSynchronous;
-  }
-
-  friend class AutoSetSyncDecode;
-
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
 
   nsresult mFailCode;
 
   struct NewFrameData
   {
@@ -283,43 +268,14 @@ private:
     uint8_t mPaletteDepth;
   };
   NewFrameData mNewFrameData;
   bool mNeedsNewFrame;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
-  bool mSynchronous;
-};
-
-// A RAII helper class to automatically pair a call to SetSynchronous(true)
-// with a call to SetSynchronous(false), since failing to do so can lead us
-// to try to allocate frames off-main-thread, which is unsafe. Synchronous
-// decoding may only happen within the scope of an AutoSetSyncDecode. Nested
-// AutoSetSyncDecode's are OK.
-class AutoSetSyncDecode
-{
-public:
-  AutoSetSyncDecode(Decoder* aDecoder)
-    : mDecoder(aDecoder)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mDecoder);
-
-    mOriginalValue = mDecoder->IsSynchronous();
-    mDecoder->SetSynchronous(true);
-  }
-
-  ~AutoSetSyncDecode()
-  {
-    mDecoder->SetSynchronous(mOriginalValue);
-  }
-
-private:
-  nsRefPtr<Decoder> mDecoder;
-  bool              mOriginalValue;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_DECODER_H_
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -1579,18 +1579,17 @@ RasterImage::AddSourceData(const char *a
     }
   }
 
   // If we're not storing source data and we've previously gotten the size,
   // write the data directly to the decoder. (If we haven't gotten the size,
   // we'll queue up the data and write it out when we do.)
   if (!StoringSourceData() && mHasSize) {
     {
-      AutoSetSyncDecode syncDecode(mDecoder);
-      rv = WriteToDecoder(aBuffer, aCount);
+      rv = WriteToDecoder(aBuffer, aCount, DECODE_SYNC);
       CONTAINER_ENSURE_SUCCESS(rv);
     }
 
     // We're not storing source data, so this data is probably coming straight
     // from the network. In this case, we want to display data as soon as we
     // get it, so we want to flush invalidations after every write.
     nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
     mInDecoder = true;
@@ -2124,27 +2123,27 @@ RasterImage::ShutdownDecoder(eShutdownIn
 
   mBytesDecoded = 0;
 
   return NS_OK;
 }
 
 // Writes the data to the decoder, updating the total number of bytes written.
 nsresult
-RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount)
+RasterImage::WriteToDecoder(const char *aBuffer, uint32_t aCount, DecodeStrategy aStrategy)
 {
   mDecodingMutex.AssertCurrentThreadOwns();
 
   // We should have a decoder
   NS_ABORT_IF_FALSE(mDecoder, "Trying to write to null decoder!");
 
   // Write
   nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
   mInDecoder = true;
-  mDecoder->Write(aBuffer, aCount);
+  mDecoder->Write(aBuffer, aCount, aStrategy);
   mInDecoder = false;
 
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   // Keep track of the total number of bytes written over the lifetime of the
   // decoder
   mBytesDecoded += aCount;
 
@@ -2302,18 +2301,17 @@ RasterImage::RequestDecodeCore(RequestDe
   if (mHasSourceData && mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   // If we can do decoding now, do so.  Small images will decode completely,
   // large images will decode a bit and post themselves to the event loop
   // to finish decoding.
   if (!mDecoded && !mInDecoder && mHasSourceData && aDecodeType == SYNCHRONOUS_NOTIFY_AND_SOME_DECODE) {
     PROFILER_LABEL_PRINTF("RasterImage", "DecodeABitOf", "%s", GetURIString().get());
-    AutoSetSyncDecode syncDecode(mDecoder);
-    DecodePool::Singleton()->DecodeABitOf(this);
+    DecodePool::Singleton()->DecodeABitOf(this, DECODE_SYNC);
     return NS_OK;
   }
 
   if (!mDecoded) {
     // If we get this far, dispatch the worker. We do this instead of starting
     // any immediate decoding to guarantee that all our decode notifications are
     // dispatched asynchronously, and to ensure we stay responsive.
     DecodePool::Singleton()->RequestDecode(this);
@@ -2385,23 +2383,19 @@ RasterImage::SyncDecode()
   }
 
   // If we don't have a decoder, create one
   if (!mDecoder) {
     rv = InitDecoder(/* aDoSizeDecode = */ false);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  {
-    AutoSetSyncDecode syncDecode(mDecoder);
-
-    // Write everything we have
-    rv = DecodeSomeData(mSourceData.Length() - mBytesDecoded);
-    CONTAINER_ENSURE_SUCCESS(rv);
-  }
+  // Write everything we have
+  rv = DecodeSomeData(mSourceData.Length() - mBytesDecoded, DECODE_SYNC);
+  CONTAINER_ENSURE_SUCCESS(rv);
 
   // When we're doing a sync decode, we want to get as much information from the
   // image as possible. We've send the decoder all of our data, so now's a good
   // time  to flush any invalidations (in case we don't have all the data and what
   // we got left us mid-frame).
   nsRefPtr<Decoder> kungFuDeathGrip = mDecoder;
   mInDecoder = true;
   mDecoder->FlushInvalidations();
@@ -2734,45 +2728,46 @@ RasterImage::RequestDiscard()
     ForceDiscard();
   }
 
   return NS_OK;
 }
 
 // Flushes up to aMaxBytes to the decoder.
 nsresult
-RasterImage::DecodeSomeData(uint32_t aMaxBytes)
+RasterImage::DecodeSomeData(uint32_t aMaxBytes, DecodeStrategy aStrategy)
 {
   // We should have a decoder if we get here
   NS_ABORT_IF_FALSE(mDecoder, "trying to decode without decoder!");
 
   mDecodingMutex.AssertCurrentThreadOwns();
 
   // First, if we've just been called because we allocated a frame on the main
   // thread, let the decoder deal with the data it set aside at that time by
   // passing it a null buffer.
   if (mDecodeRequest->mAllocatedNewFrame) {
     mDecodeRequest->mAllocatedNewFrame = false;
-    nsresult rv = WriteToDecoder(nullptr, 0);
+    nsresult rv = WriteToDecoder(nullptr, 0, aStrategy);
     if (NS_FAILED(rv) || mDecoder->NeedsNewFrame()) {
       return rv;
     }
   }
 
   // If we have nothing else to decode, return
   if (mBytesDecoded == mSourceData.Length())
     return NS_OK;
 
   MOZ_ASSERT(mBytesDecoded < mSourceData.Length());
 
   // write the proper amount of data
   uint32_t bytesToDecode = std::min(aMaxBytes,
                                     mSourceData.Length() - mBytesDecoded);
   nsresult rv = WriteToDecoder(mSourceData.Elements() + mBytesDecoded,
-                               bytesToDecode);
+                               bytesToDecode,
+                               aStrategy);
 
   return rv;
 }
 
 // There are various indicators that tell us we're finished with the decode
 // task at hand and can shut down the decoder.
 //
 // This method may not be called if there is no decoder.
@@ -3187,29 +3182,29 @@ RasterImage::DecodePool::RequestDecode(R
       NS_DispatchToMainThread(job);
     } else {
       mThreadPool->Dispatch(job, nsIEventTarget::DISPATCH_NORMAL);
     }
   }
 }
 
 void
-RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg)
+RasterImage::DecodePool::DecodeABitOf(RasterImage* aImg, DecodeStrategy aStrategy)
 {
   MOZ_ASSERT(NS_IsMainThread());
   aImg->mDecodingMutex.AssertCurrentThreadOwns();
 
   if (aImg->mDecodeRequest) {
     // If the image is waiting for decode work to be notified, go ahead and do that.
     if (aImg->mDecodeRequest->mRequestStatus == DecodeRequest::REQUEST_WORK_DONE) {
       aImg->FinishedSomeDecoding();
     }
   }
 
-  DecodeSomeOfImage(aImg);
+  DecodeSomeOfImage(aImg, aStrategy);
 
   aImg->FinishedSomeDecoding();
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
   if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
     FrameNeededWorker::GetNewFrame(aImg);
   } else {
@@ -3267,17 +3262,17 @@ RasterImage::DecodePool::DecodeJob::Run(
   DecodeType type = DECODE_TYPE_UNTIL_DONE_BYTES;
 
   // Multithreaded decoding can be disabled. If we've done so, we don't want to
   // monopolize the main thread, and will allow a timeout in DecodeSomeOfImage.
   if (NS_IsMainThread()) {
     type = DECODE_TYPE_UNTIL_TIME;
   }
 
-  DecodePool::Singleton()->DecodeSomeOfImage(mImage, type, mRequest->mBytesToDecode);
+  DecodePool::Singleton()->DecodeSomeOfImage(mImage, DECODE_ASYNC, type, mRequest->mBytesToDecode);
 
   uint32_t bytesDecoded = mImage->mBytesDecoded - oldByteCount;
 
   mRequest->mRequestStatus = DecodeRequest::REQUEST_WORK_DONE;
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
   if (mImage->mDecoder && mImage->mDecoder->NeedsNewFrame()) {
@@ -3313,17 +3308,19 @@ RasterImage::DecodePool::DecodeUntilSize
       nsresult rv = aImg->FinishedSomeDecoding();
       if (NS_FAILED(rv)) {
         aImg->DoError();
         return rv;
       }
     }
   }
 
-  nsresult rv = DecodeSomeOfImage(aImg, DECODE_TYPE_UNTIL_SIZE);
+  // We use DECODE_ASYNC here because we just want to get the size information
+  // here and defer the rest of the work.
+  nsresult rv = DecodeSomeOfImage(aImg, DECODE_ASYNC, DECODE_TYPE_UNTIL_SIZE);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // If the decoder needs a new frame, enqueue an event to get it; that event
   // will enqueue another decode request when it's done.
   if (aImg->mDecoder && aImg->mDecoder->NeedsNewFrame()) {
     FrameNeededWorker::GetNewFrame(aImg);
@@ -3331,16 +3328,17 @@ RasterImage::DecodePool::DecodeUntilSize
     rv = aImg->FinishedSomeDecoding();
   }
 
   return rv;
 }
 
 nsresult
 RasterImage::DecodePool::DecodeSomeOfImage(RasterImage* aImg,
+                                           DecodeStrategy aStrategy,
                                            DecodeType aDecodeType /* = DECODE_TYPE_UNTIL_TIME */,
                                            uint32_t bytesToDecode /* = 0 */)
 {
   NS_ABORT_IF_FALSE(aImg->mInitialized,
                     "Worker active for uninitialized container!");
   aImg->mDecodingMutex.AssertCurrentThreadOwns();
 
   // If an error is flagged, it probably happened while we were waiting
@@ -3350,17 +3348,17 @@ RasterImage::DecodePool::DecodeSomeOfIma
 
   // If mDecoded or we don't have a decoder, we must have finished already (for
   // example, a synchronous decode request came while the worker was pending).
   if (!aImg->mDecoder || aImg->mDecoded)
     return NS_OK;
 
   // If we're doing synchronous decodes, and we're waiting on a new frame for
   // this image, get it now.
-  if (aImg->mDecoder->IsSynchronous() && aImg->mDecoder->NeedsNewFrame()) {
+  if (aStrategy == DECODE_SYNC && aImg->mDecoder->NeedsNewFrame()) {
     MOZ_ASSERT(NS_IsMainThread());
 
     aImg->mDecoder->AllocateFrame();
     aImg->mDecodeRequest->mAllocatedNewFrame = true;
   }
 
   // If we're not synchronous, we can't allocate a frame right now.
   else if (aImg->mDecoder->NeedsNewFrame()) {
@@ -3399,17 +3397,17 @@ RasterImage::DecodePool::DecodeSomeOfIma
   while ((aImg->mSourceData.Length() > aImg->mBytesDecoded &&
           bytesToDecode > 0 &&
           !aImg->IsDecodeFinished() &&
           !(aDecodeType == DECODE_TYPE_UNTIL_SIZE && aImg->mHasSize) &&
           !aImg->mDecoder->NeedsNewFrame()) ||
          (aImg->mDecodeRequest && aImg->mDecodeRequest->mAllocatedNewFrame)) {
     chunkCount++;
     uint32_t chunkSize = std::min(bytesToDecode, maxBytes);
-    nsresult rv = aImg->DecodeSomeData(chunkSize);
+    nsresult rv = aImg->DecodeSomeData(chunkSize, aStrategy);
     if (NS_FAILED(rv)) {
       aImg->DoError();
       return rv;
     }
 
     bytesToDecode -= chunkSize;
 
     // Yield if we've been decoding for too long. We check this _after_ decoding
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -20,16 +20,17 @@
 #include "Image.h"
 #include "FrameBlender.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
+#include "DecodeStrategy.h"
 #include "DiscardTracker.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/Mutex.h"
@@ -303,16 +304,18 @@ public:
   // Decoder shutdown
   enum eShutdownIntent {
     eShutdownIntent_Done        = 0,
     eShutdownIntent_NotNeeded   = 1,
     eShutdownIntent_Error       = 2,
     eShutdownIntent_AllCount    = 3
   };
 
+  // Decode strategy
+
 private:
   already_AddRefed<imgStatusTracker> CurrentStatusTracker()
   {
     nsRefPtr<imgStatusTracker> statusTracker;
     statusTracker = mDecodeRequest ? mDecodeRequest->mStatusTracker
                                    : mStatusTracker;
     MOZ_ASSERT(statusTracker);
     return statusTracker.forget();
@@ -395,17 +398,17 @@ private:
      * Ask the DecodePool to asynchronously decode this image.
      */
     void RequestDecode(RasterImage* aImg);
 
     /**
      * Decode aImg for a short amount of time, and post the remainder to the
      * queue.
      */
-    void DecodeABitOf(RasterImage* aImg);
+    void DecodeABitOf(RasterImage* aImg, DecodeStrategy aStrategy);
 
     /**
      * Ask the DecodePool to stop decoding this image.  Internally, we also
      * call this function when we finish decoding an image.
      *
      * Since the DecodePool keeps raw pointers to RasterImages, make sure you
      * call this before a RasterImage is destroyed!
      */
@@ -444,16 +447,17 @@ private:
     };
 
     /* Decode some chunks of the given image.  If aDecodeType is UNTIL_SIZE,
      * decode until we have the image's size, then stop. If bytesToDecode is
      * non-0, at most bytesToDecode bytes will be decoded. if aDecodeType is
      * UNTIL_DONE_BYTES, decode until all bytesToDecode bytes are decoded.
      */
     nsresult DecodeSomeOfImage(RasterImage* aImg,
+                               DecodeStrategy aStrategy,
                                DecodeType aDecodeType = DECODE_TYPE_UNTIL_TIME,
                                uint32_t bytesToDecode = 0);
 
     /* A decode job dispatched to a thread pool by DecodePool.
      */
     class DecodeJob : public nsRunnable
     {
     public:
@@ -694,18 +698,18 @@ private: // data
   // Decoding
   nsresult RequestDecodeIfNeeded(nsresult aStatus,
                                  eShutdownIntent aIntent,
                                  bool aDone,
                                  bool aWasSize);
   nsresult WantDecodedFrames();
   nsresult SyncDecode();
   nsresult InitDecoder(bool aDoSizeDecode);
-  nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount);
-  nsresult DecodeSomeData(uint32_t aMaxBytes);
+  nsresult WriteToDecoder(const char *aBuffer, uint32_t aCount, DecodeStrategy aStrategy);
+  nsresult DecodeSomeData(uint32_t aMaxBytes, DecodeStrategy aStrategy);
   bool     IsDecodeFinished();
   TimeStamp mDrawStartTime;
 
   inline bool CanQualityScale(const gfx::Size& scale);
   inline bool CanScale(GraphicsFilter aFilter, gfx::Size aScale, uint32_t aFlags);
 
   struct ScaleResult
   {