Bug 1060869 (Part 2) - Store the first frame of a RasterImage in the SurfaceCache. r=tn
authorSeth Fowler <seth@mozilla.com>
Wed, 26 Nov 2014 13:22:10 -0800
changeset 217762 5078e4014e213558abcb3e234a9cad0dfa4737b3
parent 217761 b0f351e99e1ab7d8d50d3d3205cb1feb00248b16
child 217763 6bacacfaf3393bffd5df2c75b00784197e431b6b
push id27890
push usercbook@mozilla.com
push dateThu, 27 Nov 2014 11:55:59 +0000
treeherdermozilla-central@8d185a31024e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1060869
milestone36.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 1060869 (Part 2) - Store the first frame of a RasterImage in the SurfaceCache. r=tn
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/ImageMetadata.cpp
image/src/ImageMetadata.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/imgFrame.cpp
image/src/imgFrame.h
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* This is a Cross-Platform ICO Decoder, which should work everywhere, including
  * Big-Endian machines like the PowerPC. */
 
 #include <stdlib.h>
 
 #include "mozilla/Endian.h"
+#include "mozilla/Move.h"
 #include "nsICODecoder.h"
 
 #include "RasterImage.h"
 
 namespace mozilla {
 namespace image {
 
 #define ICONCOUNTOFFSET 4
@@ -339,17 +340,17 @@ nsICODecoder::WriteInternal(const char* 
 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
       mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                            mColormap, mColormapSize,
-                                           mCurrentFrame);
+                                           Move(mRefForContainedDecoder));
       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) {
@@ -417,17 +418,17 @@ nsICODecoder::WriteInternal(const char* 
     // 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->SetSizeDecode(IsSizeDecode());
     mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                          mColormap, mColormapSize,
-                                         mCurrentFrame);
+                                         Move(mRefForContainedDecoder));
 
     // 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;
@@ -635,21 +636,27 @@ nsICODecoder::NeedsNewFrame() const
   }
 
   return Decoder::NeedsNewFrame();
 }
 
 nsresult
 nsICODecoder::AllocateFrame()
 {
+  nsresult rv;
+
   if (mContainedDecoder) {
-    nsresult rv = mContainedDecoder->AllocateFrame();
-    mCurrentFrame = mContainedDecoder->GetCurrentFrame();
+    rv = mContainedDecoder->AllocateFrame();
+    mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
     mProgress |= mContainedDecoder->TakeProgress();
     mInvalidRect.Union(mContainedDecoder->TakeInvalidRect());
     return rv;
   }
 
-  return Decoder::AllocateFrame();
+  // Grab a strong ref that we'll later hand over to the contained decoder. This
+  // lets us avoid creating a RawAccessFrameRef off-main-thread.
+  rv = Decoder::AllocateFrame();
+  mRefForContainedDecoder = GetCurrentFrameRef();
+  return rv;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #ifndef nsICODecoder_h
 #define nsICODecoder_h
 
 #include "nsAutoPtr.h"
 #include "Decoder.h"
+#include "imgFrame.h"
 #include "nsBMPDecoder.h"
 #include "nsPNGDecoder.h"
 #include "ICOFileHeaders.h"
 
 namespace mozilla {
 namespace image {
 
 class RasterImage;
@@ -77,16 +78,17 @@ private:
   uint16_t mNumIcons; // Stores the number of icons in the ICO file
   uint16_t mCurrIcon; // Stores the current dir entry index we are processing
   uint32_t mImageOffset; // Stores the offset of the image data we want
   uint8_t* mRow;      // Holds one raw line of the image
   int32_t mCurLine;   // Line index of the image that's currently being decoded
   uint32_t mRowBytes; // How many bytes of the row were already received
   int32_t mOldLine;   // Previous index of the line
   nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
+  RawAccessFrameRef mRefForContainedDecoder; // Avoid locking off-main-thread
 
   char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
   IconDirEntry mDirEntry; // Holds a decoded dir entry
   // Holds the potential bytes that can be a PNG signature
   char mSignature[PNGSIGNATURESIZE];
   // Holds the potential bytes for a bitmap information header
   char mBIHraw[40];
   // Stores whether or not the icon file we are processing has type 1 (icon)
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -11,17 +11,16 @@
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
 namespace mozilla {
 namespace image {
 
 Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
-  , mCurrentFrame(nullptr)
   , mProgress(NoProgress)
   , mImageData(nullptr)
   , mColormap(nullptr)
   , mChunkCount(0)
   , mDecodeFlags(0)
   , mBytesDecoded(0)
   , mDecodeDone(false)
   , mDataError(false)
@@ -63,28 +62,29 @@ Decoder::Init()
   InitInternal();
 
   mInitialized = true;
 }
 
 // Initializes a decoder whose image and observer is already being used by a
 // parent decoder
 void
-Decoder::InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength,
-                           uint32_t* colormap, uint32_t colormapSize,
-                           imgFrame* currentFrame)
+Decoder::InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength,
+                           uint32_t* aColormap, uint32_t aColormapSize,
+                           RawAccessFrameRef&& aFrameRef)
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
 
-  mImageData = imageData;
-  mImageDataLength = imageDataLength;
-  mColormap = colormap;
-  mColormapSize = colormapSize;
-  mCurrentFrame = currentFrame;
+  mImageData = aImageData;
+  mImageDataLength = aImageDataLength;
+  mColormap = aColormap;
+  mColormapSize = aColormapSize;
+  mCurrentFrame = Move(aFrameRef);
+
   // We have all the frame data, so we've started the frame.
   if (!IsSizeDecode()) {
     PostFrameStart();
   }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
@@ -201,21 +201,23 @@ Decoder::Finish(ShutdownReason aReason)
     } else {
       if (!IsSizeDecode()) {
         mProgress |= FLAG_DECODE_COMPLETE | FLAG_ONLOAD_UNBLOCKED;
       }
       mProgress |= FLAG_HAS_ERROR;
     }
   }
 
-  // Set image metadata before calling DecodingComplete, because DecodingComplete calls Optimize().
+  // Set image metadata before calling DecodingComplete, because
+  // DecodingComplete calls Optimize().
   mImageMetadata.SetOnImage(&mImage);
 
   if (mDecodeDone) {
-    mImage.DecodingComplete();
+    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
+    mImage.DecodingComplete(mCurrentFrame.get());
   }
 }
 
 void
 Decoder::FinishSharedDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -225,58 +227,46 @@ Decoder::FinishSharedDecoder()
 }
 
 nsresult
 Decoder::AllocateFrame()
 {
   MOZ_ASSERT(mNeedsNewFrame);
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsresult rv;
-  nsRefPtr<imgFrame> frame;
-  if (mNewFrameData.mPaletteDepth) {
-    rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
-                            mNewFrameData.mOffsetY, mNewFrameData.mWidth,
-                            mNewFrameData.mHeight, mNewFrameData.mFormat,
-                            mNewFrameData.mPaletteDepth,
-                            &mImageData, &mImageDataLength,
-                            &mColormap, &mColormapSize,
-                            getter_AddRefs(frame));
+  mCurrentFrame = mImage.EnsureFrame(mNewFrameData.mFrameNum,
+                                     mNewFrameData.mFrameRect,
+                                     mDecodeFlags,
+                                     mNewFrameData.mFormat,
+                                     mNewFrameData.mPaletteDepth,
+                                     mCurrentFrame.get());
+
+  if (mCurrentFrame) {
+    // Gather the raw pointers the decoders will use.
+    mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
+    mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
+
+    if (mNewFrameData.mFrameNum == mFrameCount) {
+      PostFrameStart();
+    }
   } else {
-    rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
-                            mNewFrameData.mOffsetY, mNewFrameData.mWidth,
-                            mNewFrameData.mHeight, mNewFrameData.mFormat,
-                            &mImageData, &mImageDataLength,
-                            getter_AddRefs(frame));
-  }
-
-  if (NS_SUCCEEDED(rv)) {
-    mCurrentFrame = frame;
-  } else {
-    mCurrentFrame = nullptr;
-  }
-
-  // Notify if appropriate
-  if (NS_SUCCEEDED(rv) && mNewFrameData.mFrameNum == mFrameCount) {
-    PostFrameStart();
-  } else if (NS_FAILED(rv)) {
     PostDataError();
   }
 
   // Mark ourselves as not needing another frame before talking to anyone else
   // so they can tell us if they need yet another.
   mNeedsNewFrame = false;
 
   // If we've received any data at all, we may have pending data that needs to
   // be flushed now that we have a frame to decode into.
   if (mBytesDecoded > 0) {
     mNeedsToFlushData = true;
   }
 
-  return rv;
+  return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
 void
 Decoder::SetSizeOnImage()
 {
   MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
   MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
 
@@ -333,18 +323,18 @@ Decoder::PostFrameStart()
   if (mFrameCount > 1) {
     mIsAnimated = true;
     mProgress |= FLAG_IS_ANIMATED;
   }
 
   // Decoder implementations should only call this method if they successfully
   // appended the frame to the image. So mFrameCount should always match that
   // reported by the Image.
-  NS_ABORT_IF_FALSE(mFrameCount == mImage.GetNumFrames(),
-                    "Decoder frame count doesn't match image's!");
+  MOZ_ASSERT(mFrameCount == mImage.GetNumFrames(),
+             "Decoder frame count doesn't match image's!");
 }
 
 void
 Decoder::PostFrameStop(FrameBlender::FrameAlpha aFrameAlpha /* = FrameBlender::kFrameHasAlpha */,
                        FrameBlender::FrameDisposalMethod aDisposalMethod /* = FrameBlender::kDisposeKeep */,
                        int32_t aTimeout /* = 0 */,
                        FrameBlender::FrameBlendMethod aBlendMethod /* = FrameBlender::kBlendOver */)
 {
@@ -384,17 +374,16 @@ void
 Decoder::PostDecodeDone(int32_t aLoopCount /* = 0 */)
 {
   NS_ABORT_IF_FALSE(!IsSizeDecode(), "Can't be done with decoding with size decode!");
   NS_ABORT_IF_FALSE(!mInFrame, "Can't be done decoding if we're mid-frame!");
   NS_ABORT_IF_FALSE(!mDecodeDone, "Decode already done!");
   mDecodeDone = true;
 
   mImageMetadata.SetLoopCount(aLoopCount);
-  mImageMetadata.SetIsNonPremultiplied(GetDecodeFlags() & DECODER_NO_PREMULTIPLY_ALPHA);
 
   mProgress |= FLAG_DECODE_COMPLETE;
 }
 
 void
 Decoder::PostDataError()
 {
   mDataError = true;
@@ -419,14 +408,16 @@ Decoder::NeedNewFrame(uint32_t framenum,
                       uint8_t palette_depth /* = 0 */)
 {
   // Decoders should never call NeedNewFrame without yielding back to Write().
   MOZ_ASSERT(!mNeedsNewFrame);
 
   // We don't want images going back in time or skipping frames.
   MOZ_ASSERT(framenum == mFrameCount || framenum == (mFrameCount - 1));
 
-  mNewFrameData = NewFrameData(framenum, x_offset, y_offset, width, height, format, palette_depth);
+  mNewFrameData = NewFrameData(framenum,
+                               nsIntRect(x_offset, y_offset, width, height),
+                               format, palette_depth);
   mNeedsNewFrame = true;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -31,19 +31,19 @@ public:
   void Init();
 
   /**
    * Initializes a decoder whose image and observer is already being used by a
    * parent decoder. Decoders may not be re-initialized.
    *
    * Notifications Sent: TODO
    */
-  void InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength,
-                         uint32_t* colormap, uint32_t colormapSize,
-                         imgFrame* currentFrame);
+  void InitSharedDecoder(uint8_t* aImageData, uint32_t aImageDataLength,
+                         uint32_t* aColormap, uint32_t aColormapSize,
+                         RawAccessFrameRef&& aFrameRef);
 
   /**
    * Writes data to the decoder.
    *
    * If aBuffer is null and aCount is 0, Write() flushes any buffered data to
    * the decoder. Data is buffered if the decoder wasn't able to completely
    * decode it because it needed a new frame.  If it's necessary to flush data,
    * NeedsToFlushData() will return true.
@@ -181,22 +181,27 @@ public:
   // have a new frame to decode into. Callers can use Write() to actually
   // flush the data; see the documentation for that method.
   bool NeedsToFlushData() const { return mNeedsToFlushData; }
 
   // Try to allocate a frame as described in mNewFrameData and return the
   // status code from that attempt. Clears mNewFrameData.
   virtual nsresult AllocateFrame();
 
-  already_AddRefed<imgFrame> GetCurrentFrame() const
+  already_AddRefed<imgFrame> GetCurrentFrame()
   {
-    nsRefPtr<imgFrame> frame = mCurrentFrame;
+    nsRefPtr<imgFrame> frame = mCurrentFrame.get();
     return frame.forget();
   }
 
+  RawAccessFrameRef GetCurrentFrameRef()
+  {
+    return mCurrentFrame->RawAccessRef();
+  }
+
 protected:
   virtual ~Decoder();
 
   /*
    * Internal hooks. Decoder implementations may override these and
    * only these methods.
    */
   virtual void InitInternal();
@@ -258,17 +263,17 @@ protected:
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
-  nsRefPtr<imgFrame> mCurrentFrame;
+  RawAccessFrameRef mCurrentFrame;
   ImageMetadata mImageMetadata;
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
   Progress mProgress;
 
   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
   uint32_t mColormapSize;
@@ -284,38 +289,32 @@ protected:
 
 private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsresult mFailCode;
 
   struct NewFrameData
   {
-    NewFrameData()
-    {}
+    NewFrameData() { }
 
-    NewFrameData(uint32_t num, uint32_t offsetx, uint32_t offsety,
-                 uint32_t width, uint32_t height,
-                 gfx::SurfaceFormat format, uint8_t paletteDepth)
-      : mFrameNum(num)
-      , mOffsetX(offsetx)
-      , mOffsetY(offsety)
-      , mWidth(width)
-      , mHeight(height)
-      , mFormat(format)
-      , mPaletteDepth(paletteDepth)
-    {}
+    NewFrameData(uint32_t aFrameNum, const nsIntRect& aFrameRect,
+                 gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth)
+      : mFrameNum(aFrameNum)
+      , mFrameRect(aFrameRect)
+      , mFormat(aFormat)
+      , mPaletteDepth(aPaletteDepth)
+    { }
+
     uint32_t mFrameNum;
-    uint32_t mOffsetX;
-    uint32_t mOffsetY;
-    uint32_t mWidth;
-    uint32_t mHeight;
+    nsIntRect mFrameRect;
     gfx::SurfaceFormat mFormat;
     uint8_t mPaletteDepth;
   };
+
   NewFrameData mNewFrameData;
   bool mNeedsNewFrame;
   bool mNeedsToFlushData;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
 };
--- a/image/src/ImageMetadata.cpp
+++ b/image/src/ImageMetadata.cpp
@@ -22,16 +22,12 @@ ImageMetadata::SetOnImage(RasterImage* i
     nsCOMPtr<nsISupportsPRUint32> intwrapy = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID);
     intwrapx->SetData(mHotspotX);
     intwrapy->SetData(mHotspotY);
     image->Set("hotspotX", intwrapx);
     image->Set("hotspotY", intwrapy);
   }
 
   image->SetLoopCount(mLoopCount);
-
-  for (uint32_t i = 0; i < image->GetNumFrames(); i++) {
-    image->SetFrameAsNonPremult(i, mIsNonPremultiplied);
-  }
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/ImageMetadata.h
+++ b/image/src/ImageMetadata.h
@@ -20,37 +20,31 @@ class RasterImage;
 // The metadata about an image that decoders accumulate as they decode.
 class ImageMetadata
 {
 public:
   ImageMetadata()
     : mHotspotX(-1)
     , mHotspotY(-1)
     , mLoopCount(-1)
-    , mIsNonPremultiplied(false)
   {}
 
   // Set the metadata this object represents on an image.
   void SetOnImage(RasterImage* image);
 
   void SetHotspot(uint16_t hotspotx, uint16_t hotspoty)
   {
     mHotspotX = hotspotx;
     mHotspotY = hotspoty;
   }
   void SetLoopCount(int32_t loopcount)
   {
     mLoopCount = loopcount;
   }
 
-  void SetIsNonPremultiplied(bool nonPremult)
-  {
-    mIsNonPremultiplied = nonPremult;
-  }
-
   void SetSize(int32_t width, int32_t height, Orientation orientation)
   {
     mSize.emplace(nsIntSize(width, height));
     mOrientation.emplace(orientation);
   }
 
   bool HasSize() const { return mSize.isSome(); }
   bool HasOrientation() const { return mOrientation.isSome(); }
@@ -63,17 +57,15 @@ private:
   // The hotspot found on cursors, or -1 if none was found.
   int32_t mHotspotX;
   int32_t mHotspotY;
 
   // The loop count for animated images, or -1 for infinite loop.
   int32_t mLoopCount;
 
   Maybe<nsIntSize> mSize;
-  Maybe<Orientation>  mOrientation;
-
-  bool mIsNonPremultiplied;
+  Maybe<Orientation> mOrientation;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // ImageMetadata_h___
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -228,19 +228,16 @@ public:
                    dstData, mDstSize.width, mDstSize.height, dstStride,
                    srcFormat);
 
       if (succeeded) {
         // Mark the frame as complete and discardable.
         mDstRef->ImageUpdated(mDstRef->GetRect());
         MOZ_ASSERT(mDstRef->ImageComplete(),
                    "Incomplete, but just updated the entire frame");
-        if (DiscardingEnabled()) {
-          mDstRef->SetDiscardable();
-        }
       }
 
       // We need to send notifications and release our references on the main
       // thread, so finish up there.
       mState = succeeded ? eFinish : eFinishWithError;
       NS_DispatchToMainThread(this);
     } else if (mState == eFinish) {
       MOZ_ASSERT(NS_IsMainThread());
@@ -317,17 +314,19 @@ RasterImage::RasterImage(ProgressTracker
   mNotifyProgress(NoProgress),
   mNotifying(false),
   mHasSize(false),
   mDecodeOnDraw(false),
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
+  mHasFirstFrame(false),
   mHasBeenDecoded(false),
+  mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false),
   mPendingError(false)
 {
   mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
 
   // Set up the discard tracker node.
   mDiscardTrackerNode.img = this;
@@ -359,17 +358,17 @@ RasterImage::~RasterImage()
   if (mDecoder) {
     // Kill off our decode request, if it's pending.  (If not, this call is
     // harmless.)
     ReentrantMonitorAutoEnter lock(mDecodingMonitor);
     DecodePool::StopDecoding(this);
     mDecoder = nullptr;
   }
 
-  // Release any HQ scaled frames from the surface cache.
+  // Release all frames from the surface cache.
   SurfaceCache::RemoveImage(ImageKey(this));
 
   mAnim = nullptr;
 
   // Total statistics
   num_containers--;
   total_source_bytes -= mSourceData.Length();
 
@@ -414,16 +413,21 @@ RasterImage::Init(const char* aMimeType,
   mMultipart = !!(aFlags & INIT_FLAG_MULTIPART);
 
   // Statistics
   if (mDiscardable) {
     num_discardable_containers++;
     discardable_source_bytes += mSourceData.Length();
   }
 
+  // Lock this image's surfaces in the SurfaceCache if we're not discardable.
+  if (!mDiscardable) {
+    SurfaceCache::LockImage(ImageKey(this));
+  }
+
   // Instantiate the decoder
   nsresult rv = InitDecoder(/* aDoSizeDecode = */ true);
   CONTAINER_ENSURE_SUCCESS(rv);
 
   // If we aren't storing source data, we want to switch from a size decode to
   // a full decode as soon as possible.
   if (!StoringSourceData()) {
     mWantFullDecode = true;
@@ -553,60 +557,64 @@ RasterImage::GetType(uint16_t *aType)
 //******************************************************************************
 /* [noscript, notxpcom] uint16_t GetType(); */
 NS_IMETHODIMP_(uint16_t)
 RasterImage::GetType()
 {
   return imgIContainer::TYPE_RASTER;
 }
 
-already_AddRefed<imgFrame>
-RasterImage::LookupFrameNoDecode(uint32_t aFrameNum)
+DrawableFrameRef
+RasterImage::LookupFrameInternal(uint32_t aFrameNum,
+                                 const nsIntSize& aSize,
+                                 uint32_t aFlags)
 {
-  if (!mAnim) {
-    NS_ASSERTION(aFrameNum == 0, "Don't ask for a frame > 0 if we're not animated!");
-    return mFrameBlender.GetFrame(0);
+  if (mAnim) {
+    MOZ_ASSERT(mFrameBlender, "mAnim but no mFrameBlender?");
+    nsRefPtr<imgFrame> frame = mFrameBlender->GetFrame(aFrameNum);
+    return frame->DrawableRef();
   }
-  return mFrameBlender.GetFrame(aFrameNum);
+
+  NS_ASSERTION(aFrameNum == 0,
+               "Don't ask for a frame > 0 if we're not animated!");
+
+  return SurfaceCache::Lookup(ImageKey(this),
+                              RasterSurfaceKey(aSize.ToIntSize(),
+                                               DecodeFlags(aFlags)));
 }
 
 DrawableFrameRef
 RasterImage::LookupFrame(uint32_t aFrameNum,
+                         const nsIntSize& aSize,
                          uint32_t aFlags,
                          bool aShouldSyncNotify /* = true */)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (mMultipart &&
       aFrameNum == GetCurrentFrameIndex() &&
       mMultipartDecodedFrame) {
     // In the multipart case we prefer to use mMultipartDecodedFrame, which is
     // the most recent one we completely decoded, rather than display the real
     // current frame and risk severe tearing.
     return mMultipartDecodedFrame->DrawableRef();
   }
 
-  // Try our best to start decoding if it's necessary.
-  nsresult rv = WantDecodedFrames(aFlags, aShouldSyncNotify);
-  CONTAINER_ENSURE_TRUE(NS_SUCCEEDED(rv), DrawableFrameRef());
-
-  nsRefPtr<imgFrame> frame = LookupFrameNoDecode(aFrameNum);
-  if (!frame) {
-    return DrawableFrameRef();
-  }
-
-  DrawableFrameRef ref = frame->DrawableRef();
+  DrawableFrameRef ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
+
   if (!ref) {
     // The OS threw this frame away. We need to discard and redecode.
     MOZ_ASSERT(!mAnim, "Animated frames should be locked");
     if (CanForciblyDiscardAndRedecode()) {
-      ForceDiscard();
+      Discard(/* aForce = */ true, /* aNotify = */ false);
+      ApplyDecodeFlags(aFlags);
       WantDecodedFrames(aFlags, aShouldSyncNotify);
 
-      // See if we managed to entirely redecode the frame.
-      frame = LookupFrameNoDecode(aFrameNum);
-      ref = frame->DrawableRef();
+      // See if we managed to redecode enough to get the frame we want.
+      ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
     }
 
     if (!ref) {
       // We didn't successfully redecode, so just fail.
       return DrawableFrameRef();
     }
   }
 
@@ -667,19 +675,28 @@ RasterImage::IsOpaque()
 nsIntRect
 RasterImage::FrameRect(uint32_t aWhichFrame)
 {
   if (aWhichFrame > FRAME_MAX_VALUE) {
     NS_WARNING("aWhichFrame outside valid range!");
     return nsIntRect();
   }
 
-  // Get the requested frame.
+  if (!mHasFirstFrame) {
+    return nsIntRect();
+  }
+
+  if (GetNumFrames() == 1) {
+    return nsIntRect(0, 0, mSize.width, mSize.height);
+  }
+
+  // We must be animated, so get the requested frame from our FrameBlender.
+  MOZ_ASSERT(mFrameBlender, "We should be animated here");
   nsRefPtr<imgFrame> frame =
-    LookupFrameNoDecode(GetRequestedFrameIndex(aWhichFrame));
+    mFrameBlender->RawGetFrame(GetRequestedFrameIndex(aWhichFrame));
 
   // If we have the frame, use that rectangle.
   if (frame) {
     return frame->GetRect();
   }
 
   // If the frame doesn't exist, we return the empty rectangle. It's not clear
   // whether this is appropriate in general, but at the moment the only
@@ -687,17 +704,20 @@ RasterImage::FrameRect(uint32_t aWhichFr
   // dirty rectangles to send out batched observer updates). This should
   // probably be revisited when we fix bug 503973.
   return nsIntRect();
 }
 
 uint32_t
 RasterImage::GetNumFrames() const
 {
-  return mFrameBlender.GetNumFrames();
+  if (mFrameBlender) {
+    return mFrameBlender->GetNumFrames();
+  }
+  return mHasFirstFrame ? 1 : 0;
 }
 
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP
 RasterImage::GetAnimated(bool *aAnimated)
 {
   if (mError)
@@ -729,17 +749,18 @@ RasterImage::GetFirstFrameDelay()
 {
   if (mError)
     return -1;
 
   bool animated = false;
   if (NS_FAILED(GetAnimated(&animated)) || !animated)
     return -1;
 
-  return mFrameBlender.GetTimeoutForFrame(0);
+  MOZ_ASSERT(mFrameBlender, "Animated images should have a FrameBlender");
+  return mFrameBlender->GetTimeoutForFrame(0);
 }
 
 TemporaryRef<SourceSurface>
 RasterImage::CopyFrame(uint32_t aWhichFrame,
                        uint32_t aFlags,
                        bool aShouldSyncNotify /* = true */)
 {
   if (aWhichFrame > FRAME_MAX_VALUE)
@@ -750,17 +771,17 @@ RasterImage::CopyFrame(uint32_t aWhichFr
 
   if (!ApplyDecodeFlags(aFlags))
     return nullptr;
 
   // Get the frame. If it's not there, it's probably the caller's fault for
   // not waiting for the data to be loaded from the network or not passing
   // FLAG_SYNC_DECODE
   DrawableFrameRef frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
-                                          aFlags, aShouldSyncNotify);
+                                          mSize, aFlags, aShouldSyncNotify);
   if (!frameRef) {
     // The OS threw this frame away and we couldn't redecode it right now.
     return nullptr;
   }
 
   // Create a 32-bit image surface of our size, but draw using the frame's
   // rect, implicitly padding the frame out to the image's size.
 
@@ -827,17 +848,17 @@ RasterImage::GetFrameInternal(uint32_t a
 
   if (!ApplyDecodeFlags(aFlags))
     return nullptr;
 
   // Get the frame. If it's not there, it's probably the caller's fault for
   // not waiting for the data to be loaded from the network or not passing
   // FLAG_SYNC_DECODE
   DrawableFrameRef frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
-                                          aFlags, aShouldSyncNotify);
+                                          mSize, aFlags, aShouldSyncNotify);
   if (!frameRef) {
     // The OS threw this frame away and we couldn't redecode it.
     return nullptr;
   }
 
   // If this frame covers the entire image, we can just reuse its existing
   // surface.
   RefPtr<SourceSurface> frameSurf;
@@ -955,17 +976,20 @@ RasterImage::HeapSizeOfSourceWithCompute
   }
   return n;
 }
 
 size_t
 RasterImage::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                      MallocSizeOf aMallocSizeOf) const
 {
-  return mFrameBlender.SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
+  return mFrameBlender
+       ? mFrameBlender->SizeOfDecodedWithComputedFallbackIfHeap(aLocation,
+                                                                aMallocSizeOf)
+       : 0;
 }
 
 size_t
 RasterImage::HeapSizeOfDecodedWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::IN_PROCESS_HEAP,
                                                  aMallocSizeOf);
 }
@@ -979,125 +1003,124 @@ RasterImage::NonHeapSizeOfDecoded() cons
 
 size_t
 RasterImage::OutOfProcessSizeOfDecoded() const
 {
   return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::OUT_OF_PROCESS,
                                                  nullptr);
 }
 
-void
-RasterImage::EnsureAnimExists()
+RawAccessFrameRef
+RasterImage::InternalAddFrame(uint32_t aFrameNum,
+                              const nsIntRect& aFrameRect,
+                              uint32_t aDecodeFlags,
+                              SurfaceFormat aFormat,
+                              uint8_t aPaletteDepth,
+                              imgFrame* aPreviousFrame)
 {
-  if (!mAnim) {
-
-    // Create the animation context
-    mAnim = MakeUnique<FrameAnimator>(mFrameBlender, mAnimationMode);
+  // We assume that we're in the middle of decoding because we unlock the
+  // previous frame when we create a new frame, and only when decoding do we
+  // lock frames.
+  MOZ_ASSERT(mDecoder, "Only decoders may add frames!");
+
+  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Invalid frame index!");
+  if (aFrameNum > GetNumFrames()) {
+    return RawAccessFrameRef();
+  }
+
+  if (mSize.width <= 0 || mSize.height <= 0) {
+    NS_WARNING("Trying to add frame with zero or negative size");
+    return RawAccessFrameRef();
+  }
+
+  if (!SurfaceCache::CanHold(mSize.ToIntSize())) {
+    NS_WARNING("Trying to add frame that's too large for the SurfaceCache");
+    return RawAccessFrameRef();
+  }
+
+  nsRefPtr<imgFrame> frame = new imgFrame();
+  if (NS_FAILED(frame->InitForDecoder(mSize, aFrameRect, aFormat,
+                                      aPaletteDepth))) {
+    NS_WARNING("imgFrame::Init should succeed");
+    return RawAccessFrameRef();
+  }
+  frame->SetAsNonPremult(aDecodeFlags & FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
+
+  RawAccessFrameRef ref = frame->RawAccessRef();
+  if (!ref) {
+    return RawAccessFrameRef();
+  }
+
+  if (GetNumFrames() == 0) {
+    bool succeeded =
+      SurfaceCache::Insert(frame, ImageKey(this),
+                           RasterSurfaceKey(mSize.ToIntSize(), aDecodeFlags),
+                           Lifetime::Persistent);
+    if (!succeeded) {
+      return RawAccessFrameRef();
+    }
+    mHasFirstFrame = true;
+    return ref;
+  }
+
+  if (GetNumFrames() == 1) {
+    // We're becoming animated, so initialize animation stuff.
+    MOZ_ASSERT(!mFrameBlender, "Already have a FrameBlender?");
+    MOZ_ASSERT(!mAnim, "Already have animation state?");
+    mFrameBlender.emplace();
+    mFrameBlender->SetSize(mSize);
+    mAnim = MakeUnique<FrameAnimator>(*mFrameBlender, mAnimationMode);
 
     // We don't support discarding animated images (See bug 414259).
     // Lock the image and throw away the key.
     //
-    // Note that this is inefficient, since we could get rid of the source
-    // data too. However, doing this is actually hard, because we're probably
-    // calling ensureAnimExists mid-decode, and thus we're decoding out of
-    // the source buffer. Since we're going to fix this anyway later, and
-    // since we didn't kill the source data in the old world either, locking
-    // is acceptable for the moment.
+    // Note that this is inefficient, since we could get rid of the source data
+    // too. However, doing this is actually hard, because we're probably
+    // mid-decode, and thus we're decoding out of the source buffer. Since we're
+    // going to fix this anyway later, and since we didn't kill the source data
+    // in the old world either, locking is acceptable for the moment.
     LockImage();
-  }
-}
-
-nsresult
-RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
-                                    uint8_t **imageData, uint32_t *imageLength,
-                                    uint32_t **paletteData, uint32_t *paletteLength,
-                                    imgFrame** aRetFrame)
-{
-  NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!");
-  if (framenum > GetNumFrames())
-    return NS_ERROR_INVALID_ARG;
-
-  nsRefPtr<imgFrame> frame = aFrame;
-  RawAccessFrameRef ref = frame->RawAccessRef();
-  if (!ref) {
-    // Probably the OS discarded the frame. Exceedingly unlikely since we just
-    // created it, but it could happen.
-    return NS_ERROR_FAILURE;
+
+    // Insert the first frame into the FrameBlender.
+    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
+    RawAccessFrameRef ref = aPreviousFrame->RawAccessRef();
+    if (!ref) {
+      return RawAccessFrameRef();  // Let's keep the FrameBlender consistent...
+    }
+    mFrameBlender->InsertFrame(0, Move(ref));
+
+    // Remove it from the SurfaceCache. (It's not really doing any harm there,
+    // but keeping it there could cause it to be counted twice in our memory
+    // statistics.)
+    SurfaceCache::RemoveSurface(ImageKey(this),
+                                RasterSurfaceKey(mSize.ToIntSize(),
+                                                 aDecodeFlags));
+
+    // If we dispose of the first frame by clearing it, then the first frame's
+    // refresh area is all of itself.
+    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR).
+    int32_t frameDisposalMethod = aPreviousFrame->GetFrameDisposalMethod();
+    if (frameDisposalMethod == FrameBlender::kDisposeClear ||
+        frameDisposalMethod == FrameBlender::kDisposeRestorePrevious) {
+      mAnim->SetFirstFrameRefreshArea(aPreviousFrame->GetRect());
+    }
+
+    if (mPendingAnimation && ShouldAnimate()) {
+      StartAnimation();
+    }
   }
 
-  if (paletteData && paletteLength)
-    frame->GetPaletteData(paletteData, paletteLength);
-
-  frame->GetImageData(imageData, imageLength);
-
-  mFrameBlender.InsertFrame(framenum, Move(ref));
-
-  frame.forget(aRetFrame);
-  return NS_OK;
-}
-
-nsresult
-RasterImage::InternalAddFrame(uint32_t framenum,
-                              int32_t aX, int32_t aY,
-                              int32_t aWidth, int32_t aHeight,
-                              SurfaceFormat aFormat,
-                              uint8_t aPaletteDepth,
-                              uint8_t **imageData,
-                              uint32_t *imageLength,
-                              uint32_t **paletteData,
-                              uint32_t *paletteLength,
-                              imgFrame** aRetFrame)
-{
-  // We assume that we're in the middle of decoding because we unlock the
-  // previous frame when we create a new frame, and only when decoding do we
-  // lock frames.
-  NS_ABORT_IF_FALSE(mDecoder, "Only decoders may add frames!");
-
-  NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Invalid frame index!");
-  if (framenum > GetNumFrames())
-    return NS_ERROR_INVALID_ARG;
-
-  nsRefPtr<imgFrame> frame(new imgFrame());
-
-  nsIntRect frameRect(aX, aY, aWidth, aHeight);
-  nsresult rv = frame->InitForDecoder(frameRect, aFormat, aPaletteDepth);
-  if (!(mSize.width > 0 && mSize.height > 0))
-    NS_WARNING("Shouldn't call InternalAddFrame with zero size");
-  if (!NS_SUCCEEDED(rv))
-    NS_WARNING("imgFrame::Init should succeed");
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (GetNumFrames() == 0) {
-    return InternalAddFrameHelper(framenum, frame, imageData, imageLength,
-                                  paletteData, paletteLength, aRetFrame);
-  }
-
-  if (GetNumFrames() == 1) {
-    // Since we're about to add our second frame, initialize animation stuff
-    EnsureAnimExists();
-
-    // If we dispose of the first frame by clearing it, then the
-    // First Frame's refresh area is all of itself.
-    // RESTORE_PREVIOUS is invalid (assumed to be DISPOSE_CLEAR)
-    nsRefPtr<imgFrame> firstFrame = mFrameBlender.RawGetFrame(0);
-    int32_t frameDisposalMethod = firstFrame->GetFrameDisposalMethod();
-    if (frameDisposalMethod == FrameBlender::kDisposeClear ||
-        frameDisposalMethod == FrameBlender::kDisposeRestorePrevious)
-      mAnim->SetFirstFrameRefreshArea(firstFrame->GetRect());
-  }
-
-  // Calculate firstFrameRefreshArea
-  // Some gifs are huge but only have a small area that they animate
-  // We only need to refresh that small area when Frame 0 comes around again
+  // Some GIFs are huge but only have a small area that they animate. We only
+  // need to refresh that small area when frame 0 comes around again.
   mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
 
-  rv = InternalAddFrameHelper(framenum, frame, imageData, imageLength,
-                              paletteData, paletteLength, aRetFrame);
-
-  return rv;
+  MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender by now");
+  mFrameBlender->InsertFrame(aFrameNum, frame->RawAccessRef());
+
+  return ref;
 }
 
 bool
 RasterImage::ApplyDecodeFlags(uint32_t aNewFlags)
 {
   if (mFrameDecodeFlags == (aNewFlags & DECODE_FLAGS_MASK))
     return true; // Not asking very much of us here.
 
@@ -1157,180 +1180,123 @@ RasterImage::SetSize(int32_t aWidth, int
     return NS_ERROR_UNEXPECTED;
   }
 
   // Set the size and flag that we have it
   mSize.SizeTo(aWidth, aHeight);
   mOrientation = aOrientation;
   mHasSize = true;
 
-  mFrameBlender.SetSize(mSize);
-
   return NS_OK;
 }
 
-nsresult
-RasterImage::EnsureFrame(uint32_t aFrameNum, int32_t aX, int32_t aY,
-                         int32_t aWidth, int32_t aHeight,
+RawAccessFrameRef
+RasterImage::EnsureFrame(uint32_t aFrameNum,
+                         const nsIntRect& aFrameRect,
+                         uint32_t aDecodeFlags,
                          SurfaceFormat aFormat,
                          uint8_t aPaletteDepth,
-                         uint8_t **imageData, uint32_t *imageLength,
-                         uint32_t **paletteData, uint32_t *paletteLength,
-                         imgFrame** aRetFrame)
+                         imgFrame* aPreviousFrame)
 {
-  if (mError)
-    return NS_ERROR_FAILURE;
-
-  NS_ENSURE_ARG_POINTER(imageData);
-  NS_ENSURE_ARG_POINTER(imageLength);
-  NS_ENSURE_ARG_POINTER(aRetFrame);
-  NS_ABORT_IF_FALSE(aFrameNum <= GetNumFrames(), "Invalid frame index!");
-
-  if (aPaletteDepth > 0) {
-    NS_ENSURE_ARG_POINTER(paletteData);
-    NS_ENSURE_ARG_POINTER(paletteLength);
+  if (mError) {
+    return RawAccessFrameRef();
   }
 
-  if (aFrameNum > GetNumFrames())
-    return NS_ERROR_INVALID_ARG;
-
-  // Adding a frame that doesn't already exist.
+  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Invalid frame index!");
+  if (aFrameNum > GetNumFrames()) {
+    return RawAccessFrameRef();
+  }
+
+  // Adding a frame that doesn't already exist. This is the normal case.
   if (aFrameNum == GetNumFrames()) {
-    return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat,
-                            aPaletteDepth, imageData, imageLength,
-                            paletteData, paletteLength, aRetFrame);
-  }
-
-  nsRefPtr<imgFrame> frame = mFrameBlender.RawGetFrame(aFrameNum);
-  if (!frame) {
-    return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat,
-                            aPaletteDepth, imageData, imageLength,
-                            paletteData, paletteLength, aRetFrame);
+    return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                            aPaletteDepth, aPreviousFrame);
   }
 
-  // See if we can re-use the frame that already exists.
-  nsIntRect rect = frame->GetRect();
-  if (rect.x == aX && rect.y == aY && rect.width == aWidth &&
-      rect.height == aHeight && frame->GetFormat() == aFormat &&
-      frame->GetPaletteDepth() == aPaletteDepth) {
-    frame->GetImageData(imageData, imageLength);
-    if (paletteData) {
-      frame->GetPaletteData(paletteData, paletteLength);
-    }
-
-    // We can re-use the frame if it has image data.
-    if (*imageData && paletteData && *paletteData) {
-      frame.forget(aRetFrame);
-      return NS_OK;
-    }
-    if (*imageData && !paletteData) {
-      frame.forget(aRetFrame);
-      return NS_OK;
-    }
+  // We're replacing a frame. It must be the first frame; there's no reason to
+  // ever replace any other frame, since the first frame is the only one we
+  // speculatively allocate without knowing what the decoder really needs.
+  // XXX(seth): I'm not convinced there's any reason to support this at all. We
+  // should figure out how to avoid triggering this and rip it out.
+  MOZ_ASSERT(mHasFirstFrame, "Should have the first frame");
+  MOZ_ASSERT(aFrameNum == 0, "Replacing a frame other than the first?");
+  MOZ_ASSERT(GetNumFrames() == 1, "Should have only one frame");
+  MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
+  MOZ_ASSERT(!mFrameBlender && !mAnim, "Shouldn't be animated");
+  if (aFrameNum != 0 || !aPreviousFrame || GetNumFrames() != 1) {
+    return RawAccessFrameRef();
   }
 
-  // Not reusable, so replace the frame directly.
-  mFrameBlender.RemoveFrame(aFrameNum);
-  nsRefPtr<imgFrame> newFrame(new imgFrame());
-  nsIntRect frameRect(aX, aY, aWidth, aHeight);
-  nsresult rv = newFrame->InitForDecoder(frameRect, aFormat, aPaletteDepth);
-  NS_ENSURE_SUCCESS(rv, rv);
-  return InternalAddFrameHelper(aFrameNum, newFrame, imageData, imageLength,
-                                paletteData, paletteLength, aRetFrame);
+  MOZ_ASSERT(!aPreviousFrame->GetRect().IsEqualEdges(aFrameRect) ||
+             aPreviousFrame->GetFormat() != aFormat ||
+             aPreviousFrame->GetPaletteDepth() != aPaletteDepth,
+             "Replacing first frame with the same kind of frame?");
+
+  // Remove the old frame from the SurfaceCache.
+  IntSize prevFrameSize = aPreviousFrame->GetImageSize();
+  SurfaceCache::RemoveSurface(ImageKey(this),
+                              RasterSurfaceKey(prevFrameSize, aDecodeFlags));
+  mHasFirstFrame = false;
+
+  // Add the new frame as usual.
+  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                          aPaletteDepth, nullptr);
 }
 
-nsresult
-RasterImage::EnsureFrame(uint32_t aFramenum, int32_t aX, int32_t aY,
-                         int32_t aWidth, int32_t aHeight,
-                         SurfaceFormat aFormat,
-                         uint8_t** imageData, uint32_t* imageLength,
-                         imgFrame** aFrame)
-{
-  return EnsureFrame(aFramenum, aX, aY, aWidth, aHeight, aFormat,
-                     /* aPaletteDepth = */ 0, imageData, imageLength,
-                     /* aPaletteData = */ nullptr,
-                     /* aPaletteLength = */ nullptr,
-                     aFrame);
-}
-
-nsresult
-RasterImage::SetFrameAsNonPremult(uint32_t aFrameNum, bool aIsNonPremult)
-{
-  if (mError)
-    return NS_ERROR_FAILURE;
-
-  NS_ABORT_IF_FALSE(aFrameNum < GetNumFrames(), "Invalid frame index!");
-  if (aFrameNum >= GetNumFrames())
-    return NS_ERROR_INVALID_ARG;
-
-  nsRefPtr<imgFrame> frame = mFrameBlender.RawGetFrame(aFrameNum);
-  NS_ABORT_IF_FALSE(frame, "Calling SetFrameAsNonPremult on frame that doesn't exist!");
-  NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
-
-  frame->SetAsNonPremult(aIsNonPremult);
-
-  return NS_OK;
-}
-
-nsresult
-RasterImage::DecodingComplete()
+void
+RasterImage::DecodingComplete(imgFrame* aFinalFrame)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mError)
-    return NS_ERROR_FAILURE;
+  if (mError) {
+    return;
+  }
 
   // Flag that we're done decoding.
   // XXX - these should probably be combined when we fix animated image
   // discarding with bug 500402.
   mDecoded = true;
   mHasBeenDecoded = true;
 
-  nsresult rv;
-
   // We now have one of the qualifications for discarding. Re-evaluate.
   if (CanDiscard()) {
     NS_ABORT_IF_FALSE(!DiscardingActive(),
                       "We shouldn't have been discardable before this");
-    rv = DiscardTracker::Reset(&mDiscardTrackerNode);
-    CONTAINER_ENSURE_SUCCESS(rv);
+    DiscardTracker::Reset(&mDiscardTrackerNode);
   }
 
+  bool singleFrame = GetNumFrames() == 1;
+
   // If there's only 1 frame, mark it as optimizable. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
-  if ((GetNumFrames() == 1) && !mMultipart) {
-    nsRefPtr<imgFrame> firstFrame = mFrameBlender.RawGetFrame(0);
-    firstFrame->SetOptimizable();
-    if (DiscardingEnabled() && CanForciblyDiscard()) {
-      firstFrame->SetDiscardable();
-    }
+  if (singleFrame && !mMultipart && aFinalFrame) {
+    aFinalFrame->SetOptimizable();
   }
 
   // Double-buffer our frame in the multipart case, since we'll start decoding
   // into the first frame again immediately and this produces severe tearing.
   if (mMultipart) {
-    if (GetNumFrames() == 1) {
-      mMultipartDecodedFrame = mFrameBlender.GetFrame(GetCurrentFrameIndex());
+    if (singleFrame && aFinalFrame) {
+      // aFinalFrame must be the first frame since we only have one.
+      mMultipartDecodedFrame = aFinalFrame->DrawableRef();
     } else {
       // Don't double buffer for animated multipart images. It entails more
       // complexity and it's not really needed since we already are smart about
       // not displaying the still-decoding frame of an animated image. We may
       // have already stored an extra frame, though, so we'll release it here.
-      mMultipartDecodedFrame = nullptr;
+      mMultipartDecodedFrame.reset();
     }
   }
 
   if (mAnim) {
     mAnim->SetDoneDecoding(true);
   }
-
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
   if (mAnim) {
     mAnim->SetAnimationMode(aAnimationMode);
   }
@@ -1342,22 +1308,27 @@ RasterImage::SetAnimationMode(uint16_t a
 nsresult
 RasterImage::StartAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   NS_ABORT_IF_FALSE(ShouldAnimate(), "Should not animate!");
 
-  EnsureAnimExists();
-
-  nsRefPtr<imgFrame> currentFrame = LookupFrameNoDecode(GetCurrentFrameIndex());
+  // If we don't have mAnim yet, then we're not ready to animate.  Setting
+  // mPendingAnimation will cause us to start animating as soon as we have a
+  // second frame, which causes mAnim to be constructed.
+  mPendingAnimation = !mAnim;
+  if (mPendingAnimation) {
+    return NS_OK;
+  }
+
   // A timeout of -1 means we should display this frame forever.
-  if (currentFrame &&
-      mFrameBlender.GetTimeoutForFrame(GetCurrentFrameIndex()) < 0) {
+  MOZ_ASSERT(mFrameBlender, "Have an animation but no FrameBlender?");
+  if (mFrameBlender->GetTimeoutForFrame(GetCurrentFrameIndex()) < 0) {
     mAnimationFinished = true;
     return NS_ERROR_ABORT;
   }
 
   if (mAnim) {
     // We need to set the time that this initial frame was first displayed, as
     // this is used in AdvanceFrame().
     mAnim->InitAnimationFrameTimeIfNecessary();
@@ -1387,26 +1358,30 @@ RasterImage::StopAnimation()
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 RasterImage::ResetAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
-  if (mAnimationMode == kDontAnimMode ||
-      !mAnim || mAnim->GetCurrentAnimationFrameIndex() == 0)
+  mPendingAnimation = false;
+
+  if (mAnimationMode == kDontAnimMode || !mAnim ||
+      mAnim->GetCurrentAnimationFrameIndex() == 0) {
     return NS_OK;
+  }
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
-  mFrameBlender.ResetAnimation();
+  MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender");
+  mFrameBlender->ResetAnimation();
   mAnim->ResetAnimation();
 
   UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display
@@ -1445,17 +1420,18 @@ RasterImage::GetFrameIndex(uint32_t aWhi
 void
 RasterImage::SetLoopCount(int32_t aLoopCount)
 {
   if (mError)
     return;
 
   if (mAnim) {
     // No need to set this if we're not an animation
-    mFrameBlender.SetLoopCount(aLoopCount);
+    MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender");
+    mFrameBlender->SetLoopCount(aLoopCount);
   }
 }
 
 NS_IMETHODIMP_(nsIntRect)
 RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
   return aRect;
 }
@@ -1484,28 +1460,36 @@ RasterImage::AddSourceData(const char *a
   if (mDecoded) {
     return NS_OK;
   }
 
   // Starting a new part's frames, let's clean up before we add any
   // This needs to happen just before we start getting EnsureFrame() call(s),
   // so that there's no gap for anything to miss us.
   if (mMultipart && (!mDecoder || mDecoder->BytesDecoded() == 0)) {
-    // Our previous state may have been animated, so let's clean up
-    if (mAnimating)
+    // Our previous state may have been animated, so let's clean up.
+    if (mAnimating) {
       StopAnimation();
+    }
     mAnimationFinished = false;
+    mPendingAnimation = false;
     if (mAnim) {
       mAnim = nullptr;
     }
-    // If there's only one frame, this could cause flickering
-    int old_frame_count = GetNumFrames();
-    if (old_frame_count > 1) {
-      mFrameBlender.ClearFrames();
+
+    // If we had a FrameBlender, clean it up. We'll hold on to the first frame
+    // so we have something to draw until the next frame is decoded.
+    if (mFrameBlender) {
+      nsRefPtr<imgFrame> firstFrame = mFrameBlender->RawGetFrame(0);
+      mMultipartDecodedFrame = firstFrame->DrawableRef();
+      mFrameBlender.reset();
     }
+
+    // Remove everything stored in the surface cache for this image.
+    SurfaceCache::RemoveImage(ImageKey(this));
   }
 
   // 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) {
     rv = WriteToDecoder(aBuffer, aCount, DecodeStrategy::SYNC);
     CONTAINER_ENSURE_SUCCESS(rv);
@@ -1688,16 +1672,17 @@ RasterImage::OnNewSourceData()
 
   // The decoder was shut down and we didn't flag an error, so we should be decoded
   NS_ABORT_IF_FALSE(mDecoded, "Should be decoded in NewSourceData");
 
   // Reset some flags
   mDecoded = false;
   mHasSourceData = false;
   mHasSize = false;
+  mHasFirstFrame = false;
   mWantFullDecode = true;
   mDecodeStatus = DecodeStatus::INACTIVE;
 
   if (mAnim) {
     mAnim->SetDoneDecoding(false);
   }
 
   // We always need the size first.
@@ -1766,50 +1751,54 @@ RasterImage::GetKeys(uint32_t *count, ch
     *count = 0;
     *keys = nullptr;
     return NS_OK;
   }
   return mProperties->GetKeys(count, keys);
 }
 
 void
-RasterImage::Discard(bool force)
+RasterImage::Discard(bool aForce, bool aNotify)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // We should be ok for discard
-  NS_ABORT_IF_FALSE(force ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!");
+  NS_ABORT_IF_FALSE(aForce ? CanForciblyDiscard() : CanDiscard(), "Asked to discard but can't!");
 
   // We should never discard when we have an active decoder
   NS_ABORT_IF_FALSE(!mDecoder, "Asked to discard with open decoder!");
 
   // As soon as an image becomes animated, it becomes non-discardable and any
   // timers are cancelled.
   NS_ABORT_IF_FALSE(!mAnim, "Asked to discard for animated image!");
 
   // For post-operation logging
   int old_frame_count = GetNumFrames();
 
   // Delete all the decoded frames
-  mFrameBlender.Discard();
+  mFrameBlender.reset();
+  SurfaceCache::RemoveImage(ImageKey(this));
 
   // Clear the last decoded multipart frame.
-  mMultipartDecodedFrame = nullptr;
+  mMultipartDecodedFrame.reset();
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
+  mHasFirstFrame = false;
 
   // Notify that we discarded
-  if (mProgressTracker)
+  if (aNotify && mProgressTracker) {
     mProgressTracker->OnDiscard();
+  }
 
   mDecodeStatus = DecodeStatus::INACTIVE;
 
-  if (force)
+  if (aForce) {
     DiscardTracker::Remove(&mDiscardTrackerNode);
+  }
 
   // Log
   PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
          ("CompressedImageAccounting: discarded uncompressed image "
           "data from RasterImage %p (%s) - %d frames (cached count: %d); "
           "Total Containers: %d, Discardable containers: %d, "
           "Total source bytes: %lld, Source bytes for discardable containers %lld",
           this,
@@ -1829,40 +1818,38 @@ RasterImage::CanDiscard() {
           mDiscardable &&        // ...Enabled at creation time...
           (mLockCount == 0) &&   // ...not temporarily disabled...
           mHasSourceData &&      // ...have the source data...
           mDecoded);             // ...and have something to discard.
 }
 
 bool
 RasterImage::CanForciblyDiscard() {
-  return mDiscardable &&         // ...Enabled at creation time...
-         mHasSourceData;         // ...have the source data...
+  return mHasSourceData;         // ...have the source data...
 }
 
 bool
 RasterImage::CanForciblyDiscardAndRedecode() {
-  return mDiscardable &&         // ...Enabled at creation time...
-         mHasSourceData &&       // ...have the source data...
+  return mHasSourceData &&       // ...have the source data...
          !mDecoder &&            // Can't discard with an open decoder
          !mAnim;                 // Can never discard animated images
 }
 
 // Helper method to tell us whether the clock is currently running for
 // discarding this image. Mainly for assertions.
 bool
 RasterImage::DiscardingActive() {
   return mDiscardTrackerNode.isInList();
 }
 
 // Helper method to determine if we're storing the source data in a buffer
 // or just writing it directly to the decoder
 bool
 RasterImage::StoringSourceData() const {
-  return (mDecodeOnDraw || mDiscardable);
+  return !mMultipart;
 }
 
 
 // Sets up a decoder for this image. It is an error to call this function
 // when decoding is already in process (ie - when mDecoder is non-null).
 nsresult
 RasterImage::InitDecoder(bool aDoSizeDecode)
 {
@@ -2426,36 +2413,25 @@ RasterImage::DrawWithPreDownscaleIfNeede
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
   if (!frameRef) {
     frameRef = Move(aFrameRef);
   }
 
   // By now we may have a frame with the requested size. If not, we need to
   // adjust the drawing parameters accordingly.
-  nsIntRect finalFrameRect = frameRef->GetRect();
-  if (finalFrameRect.Size() != aSize) {
-    gfx::Size scale(double(aSize.width) / mSize.width,
-                    double(aSize.height) / mSize.height);
+  IntSize finalSize = frameRef->GetImageSize();
+  if (ThebesIntSize(finalSize) != aSize) {
+    gfx::Size scale(double(aSize.width) / finalSize.width,
+                    double(aSize.height) / finalSize.height);
     aContext->Multiply(gfxMatrix::Scaling(scale.width, scale.height));
     region.Scale(1.0 / scale.width, 1.0 / scale.height);
   }
 
-  // We can only use padding if we're using the original |aFrameRef|, unscaled.
-  // (If so, we moved it into |frameRef|, so |aFrameRef| is empty.) Because of
-  // this restriction, we don't scale frames that require padding.
-  nsIntMargin padding(0, 0, 0, 0);
-  if (!aFrameRef) {
-    padding = nsIntMargin(finalFrameRect.y,
-                          mSize.width - finalFrameRect.XMost(),
-                          mSize.height - finalFrameRect.YMost(),
-                          finalFrameRect.x);
-  }
-
-  frameRef->Draw(aContext, region, padding, aFilter, aFlags);
+  frameRef->Draw(aContext, region, aFilter, aFlags);
 }
 
 //******************************************************************************
 /* [noscript] void draw(in gfxContext aContext,
  *                      in gfxGraphicsFilter aFilter,
  *                      [const] in gfxMatrix aUserSpaceToImageSpace,
  *                      [const] in gfxRect aFill,
  *                      [const] in nsIntRect aSubimage,
@@ -2523,23 +2499,30 @@ RasterImage::Draw(gfxContext* aContext,
   }
 
   // If a synchronous draw is requested, flush anything that might be sitting around
   if (aFlags & FLAG_SYNC_DECODE) {
     nsresult rv = SyncDecode();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // XXX(seth): For now, we deliberately don't look up a frame of size aSize
+  // (though DrawWithPreDownscaleIfNeeded will do so later). It doesn't make
+  // sense to do so until we support downscale-during-decode. Right now we need
+  // to make sure that we always touch an mSize-sized frame so that we have
+  // something to HQ scale.
   DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
-                                     aFlags);
+                                     mSize, aFlags);
   if (!ref) {
-    return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
+    // Getting the frame (above) touches the image and kicks off decoding.
+    return NS_OK;
   }
 
-  DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize, aRegion, aFilter, aFlags);
+  DrawWithPreDownscaleIfNeeded(Move(ref), aContext, aSize,
+                               aRegion, aFilter, aFlags);
 
   if (mDecoded && !mDrawStartTime.IsNull()) {
       TimeDuration drawLatency = TimeStamp::Now() - mDrawStartTime;
       Telemetry::Accumulate(Telemetry::IMAGE_DECODE_ON_DRAW_LATENCY, int32_t(drawLatency.ToMicroseconds()));
       // clear the value of mDrawStartTime
       mDrawStartTime = TimeStamp();
   }
 
@@ -2557,16 +2540,21 @@ RasterImage::LockImage()
     return NS_ERROR_FAILURE;
 
   // Cancel the discard timer if it's there
   DiscardTracker::Remove(&mDiscardTrackerNode);
 
   // Increment the lock count
   mLockCount++;
 
+  // Lock this image's surfaces in the SurfaceCache.
+  if (mLockCount == 1) {
+    SurfaceCache::LockImage(ImageKey(this));
+  }
+
   return NS_OK;
 }
 
 //******************************************************************************
 /* void unlockImage() */
 NS_IMETHODIMP
 RasterImage::UnlockImage()
 {
@@ -2582,16 +2570,21 @@ RasterImage::UnlockImage()
     return NS_ERROR_ABORT;
 
   // We're locked, so discarding should not be active
   NS_ABORT_IF_FALSE(!DiscardingActive(), "Locked, but discarding activated");
 
   // Decrement our lock count
   mLockCount--;
 
+  // Unlock this image's surfaces in the SurfaceCache.
+  if (mLockCount == 0) {
+    SurfaceCache::UnlockImage(ImageKey(this));
+  }
+
   // If we've decoded this image once before, we're currently decoding again,
   // and our lock count is now zero (so nothing is forcing us to keep the
   // decoded data around), try to cancel the decode and throw away whatever
   // we've decoded.
   if (mHasBeenDecoded && mDecoder &&
       mLockCount == 0 && CanForciblyDiscard()) {
     PR_LOG(GetCompressedImageAccountingLog(), PR_LOG_DEBUG,
            ("RasterImage[0x%p] canceling decode because image "
@@ -2969,17 +2962,18 @@ RasterImage::OptimalImageSizeForDest(con
                            RasterSurfaceKey(destSize.ToIntSize(),
                                             DecodeFlags(aFlags)));
 
     if (frameRef && frameRef->ImageComplete()) {
         return destSize;  // We have an existing HQ scale for this size.
     }
     if (!frameRef) {
       // We could HQ scale to this size, but we haven't. Request a scale now.
-      frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame), aFlags);
+      frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
+                             mSize, aFlags);
       if (frameRef) {
         RequestScale(frameRef.get(), aFlags, destSize);
       }
     }
   }
 
   // We either can't HQ scale to this size or the scaled version isn't ready
   // yet. Use our intrinsic size for now.
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -24,16 +24,17 @@
 #include "nsIProperties.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DecodePool.h"
 #include "DiscardTracker.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
+#include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TypedEnum.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/UniquePtr.h"
 #ifdef DEBUG
   #include "imgIContainerDebug.h"
@@ -175,57 +176,43 @@ public:
   virtual size_t NonHeapSizeOfDecoded() const;
   virtual size_t OutOfProcessSizeOfDecoded() const;
 
   virtual size_t HeapSizeOfVectorImageDocument(nsACString* aDocURL = nullptr) const MOZ_OVERRIDE {
     return 0;
   }
 
   /* Triggers discarding. */
-  void Discard(bool force = false);
-  void ForceDiscard() { Discard(/* force = */ true); }
+  void Discard(bool aForce = false, bool aNotify = true);
+  void ForceDiscard() { Discard(/* aForce = */ true); }
 
   /* Callbacks for decoders */
-  nsresult SetFrameAsNonPremult(uint32_t aFrameNum, bool aIsNonPremult);
-
   /** Sets the size and inherent orientation of the container. This should only
    * be called by the decoder. This function may be called multiple times, but
    * will throw an error if subsequent calls do not match the first.
    */
   nsresult SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation);
 
   /**
    * Ensures that a given frame number exists with the given parameters, and
-   * returns pointers to the data storage for that frame.
+   * returns a RawAccessFrameRef for that frame.
    * It is not possible to create sparse frame arrays; you can only append
-   * frames to the current frame array.
+   * frames to the current frame array, or if there is only one frame in the
+   * array, replace that frame.
+   * If a non-paletted frame is desired, pass 0 for aPaletteDepth.
    */
-  nsresult EnsureFrame(uint32_t aFramenum, int32_t aX, int32_t aY,
-                       int32_t aWidth, int32_t aHeight,
-                       gfx::SurfaceFormat aFormat,
-                       uint8_t aPaletteDepth,
-                       uint8_t** imageData,
-                       uint32_t* imageLength,
-                       uint32_t** paletteData,
-                       uint32_t* paletteLength,
-                       imgFrame** aFrame);
-
-  /**
-   * A shorthand for EnsureFrame, above, with aPaletteDepth = 0 and paletteData
-   * and paletteLength set to null.
-   */
-  nsresult EnsureFrame(uint32_t aFramenum, int32_t aX, int32_t aY,
-                       int32_t aWidth, int32_t aHeight,
-                       gfx::SurfaceFormat aFormat,
-                       uint8_t** imageData,
-                       uint32_t* imageLength,
-                       imgFrame** aFrame);
+  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
+                                const nsIntRect& aFrameRect,
+                                uint32_t aDecodeFlags,
+                                gfx::SurfaceFormat aFormat,
+                                uint8_t aPaletteDepth,
+                                imgFrame* aPreviousFrame);
 
   /* notification that the entire image has been decoded */
-  nsresult DecodingComplete();
+  void DecodingComplete(imgFrame* aFinalFrame);
 
   /**
    * Number of times to loop the image.
    * @note -1 means forever.
    */
   void     SetLoopCount(int32_t aLoopCount);
 
   /* Add compressed source data to the imgContainer.
@@ -309,38 +296,37 @@ private:
 
   TemporaryRef<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame,
                                              uint32_t aFlags,
                                              bool aShouldSyncNotify = true);
   TemporaryRef<gfx::SourceSurface> GetFrameInternal(uint32_t aWhichFrame,
                                                     uint32_t aFlags,
                                                     bool aShouldSyncNotify = true);
 
-  already_AddRefed<imgFrame> LookupFrameNoDecode(uint32_t aFrameNum);
-  DrawableFrameRef LookupFrame(uint32_t aFrameNum, uint32_t aFlags, bool aShouldSyncNotify = true);
+  DrawableFrameRef LookupFrameInternal(uint32_t aFrameNum,
+                                       const nsIntSize& aSize,
+                                       uint32_t aFlags);
+  DrawableFrameRef LookupFrame(uint32_t aFrameNum,
+                               const nsIntSize& aSize,
+                               uint32_t aFlags,
+                               bool aShouldSyncNotify = true);
   uint32_t GetCurrentFrameIndex() const;
   uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
 
   nsIntRect GetFirstFrameRect();
 
   size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                  MallocSizeOf aMallocSizeOf) const;
 
-  void EnsureAnimExists();
-
-  nsresult InternalAddFrameHelper(uint32_t framenum, imgFrame *frame,
-                                  uint8_t **imageData, uint32_t *imageLength,
-                                  uint32_t **paletteData, uint32_t *paletteLength,
-                                  imgFrame** aRetFrame);
-  nsresult InternalAddFrame(uint32_t framenum, int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight,
-                            gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth,
-                            uint8_t **imageData, uint32_t *imageLength,
-                            uint32_t **paletteData, uint32_t *paletteLength,
-                            imgFrame** aRetFrame);
-
+  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
+                                     const nsIntRect& aFrameRect,
+                                     uint32_t aDecodeFlags,
+                                     gfx::SurfaceFormat aFormat,
+                                     uint8_t aPaletteDepth,
+                                     imgFrame* aPreviousFrame);
   nsresult DoImageDataComplete();
 
   bool ApplyDecodeFlags(uint32_t aNewFlags);
 
   already_AddRefed<layers::Image> GetCurrentImage();
   void UpdateImageContainer();
 
   enum RequestDecodeType {
@@ -365,21 +351,21 @@ private: // data
   // with the browser's needs for displaying the image to the user.
   // As such, we may need to redecode if we're being asked for
   // a frame with different flags.  0 indicates default flags.
   //
   // Valid flag bits are imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA
   // and imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION.
   uint32_t                   mFrameDecodeFlags;
 
-  //! All the frames of the image
-  FrameBlender              mFrameBlender;
+  //! All the frames of the image.
+  Maybe<FrameBlender>       mFrameBlender;
 
-  // The last frame we decoded for multipart images.
-  nsRefPtr<imgFrame>        mMultipartDecodedFrame;
+  //! The last frame we decoded for multipart images.
+  DrawableFrameRef          mMultipartDecodedFrame;
 
   nsCOMPtr<nsIProperties>   mProperties;
 
   // IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
   // that the frames actually exist (they may have been discarded to save memory, or
   // we maybe decoding on draw).
   UniquePtr<FrameAnimator> mAnim;
 
@@ -434,18 +420,23 @@ private: // data
   bool                       mHasSize:1;       // Has SetSize() been called?
   bool                       mDecodeOnDraw:1;  // Decoding on draw?
   bool                       mMultipart:1;     // Multipart?
   bool                       mDiscardable:1;   // Is container discardable?
   bool                       mHasSourceData:1; // Do we have source data?
 
   // Do we have the frames in decoded form?
   bool                       mDecoded:1;
+  bool                       mHasFirstFrame:1;
   bool                       mHasBeenDecoded:1;
 
+  // Whether we're waiting to start animation. If we get a StartAnimation() call
+  // but we don't yet have more than one frame, mPendingAnimation is set so that
+  // we know to start animation later if/when we have more frames.
+  bool                       mPendingAnimation:1;
 
   // Whether the animation can stop, due to running out
   // of frames, or no more owning request
   bool                       mAnimationFinished:1;
 
   // Whether, once we are done doing a size decode, we should immediately kick
   // off a full decode.
   bool                       mWantFullDecode:1;
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -106,29 +106,45 @@ static bool AllowedImageSize(int32_t aWi
   if (MOZ_UNLIKELY(aHeight > SHRT_MAX)) {
     NS_WARNING("image too big");
     return false;
   }
 #endif
   return true;
 }
 
+static bool AllowedImageAndFrameDimensions(const nsIntSize& aImageSize,
+                                           const nsIntRect& aFrameRect)
+{
+  if (!AllowedImageSize(aImageSize.width, aImageSize.height)) {
+    return false;
+  }
+  if (!AllowedImageSize(aFrameRect.width, aFrameRect.height)) {
+    return false;
+  }
+  nsIntRect imageRect(0, 0, aImageSize.width, aImageSize.height);
+  if (!imageRect.Contains(aFrameRect)) {
+    return false;
+  }
+  return true;
+}
+
+
 imgFrame::imgFrame() :
   mDecoded(0, 0, 0, 0),
   mDecodedMutex("imgFrame::mDecoded"),
   mPalettedImageData(nullptr),
   mTimeout(100),
   mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mLockCount(0),
   mBlendMethod(1), /* imgIContainer::kBlendOver */
   mSinglePixel(false),
   mCompositingFailed(false),
   mHasNoAlpha(false),
   mNonPremult(false),
-  mDiscardable(false),
   mOptimizable(false),
   mInformedDiscardTracker(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
@@ -142,27 +158,29 @@ imgFrame::~imgFrame()
   mPalettedImageData = nullptr;
 
   if (mInformedDiscardTracker) {
     DiscardTracker::InformDeallocation(4 * mSize.height * mSize.width);
   }
 }
 
 nsresult
-imgFrame::InitForDecoder(const nsIntRect& aRect,
+imgFrame::InitForDecoder(const nsIntSize& aImageSize,
+                         const nsIntRect& aRect,
                          SurfaceFormat aFormat,
                          uint8_t aPaletteDepth /* = 0 */)
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
-  if (!AllowedImageSize(aRect.width, aRect.height)) {
+  if (!AllowedImageAndFrameDimensions(aImageSize, aRect)) {
     NS_WARNING("Should have legal image size");
     return NS_ERROR_FAILURE;
   }
 
+  mImageSize = aImageSize.ToIntSize();
   mOffset.MoveTo(aRect.x, aRect.y);
   mSize.SizeTo(aRect.width, aRect.height);
 
   mFormat = aFormat;
   mPaletteDepth = aPaletteDepth;
 
   if (aPaletteDepth != 0) {
     // We're creating for a paletted image.
@@ -216,16 +234,17 @@ imgFrame::InitWithDrawable(gfxDrawable* 
 {
   // Assert for properties that should be verified by decoders,
   // warn for properties related to bad content.
   if (!AllowedImageSize(aSize.width, aSize.height)) {
     NS_WARNING("Should have legal image size");
     return NS_ERROR_FAILURE;
   }
 
+  mImageSize = aSize.ToIntSize();
   mOffset.MoveTo(0, 0);
   mSize.SizeTo(aSize.width, aSize.height);
 
   mFormat = aFormat;
   mPaletteDepth = 0;
 
   // Inform the discard tracker that we are going to allocate some memory.
   mInformedDiscardTracker =
@@ -410,19 +429,17 @@ nsresult imgFrame::Optimize()
     mImageSurface = nullptr;
   }
 
 #ifdef MOZ_WIDGET_ANDROID
   // On Android, free mImageSurface unconditionally if we're discardable. This
   // allows the operating system to free our volatile buffer.
   // XXX(seth): We'd eventually like to do this on all platforms, but right now
   // converting raw memory to a SourceSurface is expensive on some backends.
-  if (mDiscardable) {
-    mImageSurface = nullptr;
-  }
+  mImageSurface = nullptr;
 #endif
 
   return NS_OK;
 }
 
 DrawableFrameRef
 imgFrame::DrawableRef()
 {
@@ -487,64 +504,66 @@ imgFrame::SurfaceForDrawing(bool        
   aImageRect = gfxRect(0, 0, mSize.width, mSize.height);
 
   gfxIntSize availableSize(mDecoded.width, mDecoded.height);
   return SurfaceWithFormat(new gfxSurfaceDrawable(aSurface, availableSize),
                            mFormat);
 }
 
 bool imgFrame::Draw(gfxContext* aContext, const ImageRegion& aRegion,
-                    const nsIntMargin& aPadding, GraphicsFilter aFilter,
-                    uint32_t aImageFlags)
+                    GraphicsFilter aFilter, uint32_t aImageFlags)
 {
   PROFILER_LABEL("imgFrame", "Draw",
     js::ProfileEntry::Category::GRAPHICS);
 
   NS_ASSERTION(!aRegion.Rect().IsEmpty(), "Drawing empty region!");
   NS_ASSERTION(!aRegion.IsRestricted() ||
                !aRegion.Rect().Intersect(aRegion.Restriction()).IsEmpty(),
                "We must be allowed to sample *some* source pixels!");
   NS_ASSERTION(!mPalettedImageData, "Directly drawing a paletted image!");
 
-  bool doPadding = aPadding != nsIntMargin(0,0,0,0);
+  nsIntMargin padding(mOffset.y,
+                      mImageSize.width - (mOffset.x + mSize.width),
+                      mImageSize.height - (mOffset.y + mSize.height),
+                      mOffset.x);
+
+  bool doPadding = padding != nsIntMargin(0,0,0,0);
   bool doPartialDecode = !ImageComplete();
 
   if (mSinglePixel && !doPadding && !doPartialDecode) {
     if (mSinglePixelColor.a == 0.0) {
       return true;
     }
     RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
     dt->FillRect(ToRect(aRegion.Rect()),
                  ColorPattern(mSinglePixelColor),
                  DrawOptions(1.0f,
                              CompositionOpForOp(aContext->CurrentOperator())));
     return true;
   }
 
-  gfxRect imageRect(0, 0, mSize.width + aPadding.LeftRight(),
-                    mSize.height + aPadding.TopBottom());
-
   RefPtr<SourceSurface> surf = GetSurface();
   if (!surf && !mSinglePixel) {
     return false;
   }
 
+  gfxRect imageRect(0, 0, mImageSize.width, mImageSize.height);
   bool doTile = !imageRect.Contains(aRegion.Rect()) &&
                 !(aImageFlags & imgIContainer::FLAG_CLAMP);
   ImageRegion region(aRegion);
   // SurfaceForDrawing changes the current transform, and we need it to still
   // be changed when we call gfxUtils::DrawPixelSnapped. We still need to
   // restore it before returning though.
   // XXXjwatt In general having functions require someone further up the stack
   // to undo transform changes that they make is bad practice. We should
   // change how this code works.
   gfxContextMatrixAutoSaveRestore autoSR(aContext);
   SurfaceWithFormat surfaceResult =
     SurfaceForDrawing(doPadding, doPartialDecode, doTile, aContext,
-                      aPadding, imageRect, region, surf);
+                      padding, imageRect, region, surf);
 
   if (surfaceResult.IsValid()) {
     gfxUtils::DrawPixelSnapped(aContext, surfaceResult.mDrawable,
                                imageRect.Size(), region, surfaceResult.mFormat,
                                aFilter, aImageFlags);
   }
   return true;
 }
@@ -574,44 +593,19 @@ imgFrame::GetStride() const
 {
   if (mImageSurface) {
     return mImageSurface->Stride();
   }
 
   return VolatileSurfaceStride(mSize, mFormat);
 }
 
-<<<<<<< found
 SurfaceFormat imgFrame::GetFormat() const
 {
-||||||| expected
-bool imgFrame::GetNeedsBackground() const
-{
-  // We need a background painted if we have alpha or we're incomplete.
-=======
-bool imgFrame::GetNeedsBackground() const
-{
-  // We need a background painted if we're incomplete.
-  if (!ImageComplete()) {
-    return true;
-  }
-
-  // We need a background painted if we might not be opaque.
->>>>>>> replacement
-<<<<<<< found
   return mFormat;
-||||||| expected
-  return (mFormat == SurfaceFormat::B8G8R8A8 || !ImageComplete());
-=======
-  if (mFormat == SurfaceFormat::B8G8R8A8 && !mHasNoAlpha) {
-    return true;
-  }
-
-  return false;
->>>>>>> replacement
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
   if (mVBuf)
     return mSize.width * BytesPerPixel(mFormat);
 
   if (mPaletteDepth)
@@ -800,23 +794,16 @@ nsresult imgFrame::UnlockImageData()
   }
 
   mLockCount--;
 
   return NS_OK;
 }
 
 void
-imgFrame::SetDiscardable()
-{
-  MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called");
-  mDiscardable = true;
-}
-
-void
 imgFrame::SetOptimizable()
 {
   MOZ_ASSERT(mLockCount, "Expected to be locked when SetOptimizable is called");
   mOptimizable = true;
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -39,25 +39,26 @@ public:
   /**
    * Initialize this imgFrame with an empty surface and prepare it for being
    * written to by a decoder.
    *
    * This is appropriate for use with decoded images, but it should not be used
    * when drawing content into an imgFrame, as it may use a different graphics
    * backend than normal content drawing.
    */
-  nsresult InitForDecoder(const nsIntRect& aRect,
+  nsresult InitForDecoder(const nsIntSize& aImageSize,
+                          const nsIntRect& aRect,
                           SurfaceFormat aFormat,
                           uint8_t aPaletteDepth = 0);
 
   nsresult InitForDecoder(const nsIntSize& aSize,
                           SurfaceFormat aFormat,
                           uint8_t aPaletteDepth = 0)
   {
-    return InitForDecoder(nsIntRect(0, 0, aSize.width, aSize.height),
+    return InitForDecoder(aSize, nsIntRect(0, 0, aSize.width, aSize.height),
                           aFormat, aPaletteDepth);
   }
 
 
   /**
    * Initialize this imgFrame with a new surface and draw the provided
    * gfxDrawable into it.
    *
@@ -72,21 +73,21 @@ public:
                             const SurfaceFormat aFormat,
                             GraphicsFilter aFilter,
                             uint32_t aImageFlags);
 
   DrawableFrameRef DrawableRef();
   RawAccessFrameRef RawAccessRef();
 
   bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
-            const nsIntMargin& aPadding, GraphicsFilter aFilter,
-            uint32_t aImageFlags);
+            GraphicsFilter aFilter, uint32_t aImageFlags);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
 
+  IntSize GetImageSize() { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }
   bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
   int32_t GetStride() const;
   SurfaceFormat GetFormat() const;
   uint32_t GetImageBytesPerRow() const;
   uint32_t GetImageDataLength() const;
   bool GetIsPaletted() const;
@@ -107,17 +108,16 @@ public:
   bool ImageComplete() const;
 
   void SetHasNoAlpha();
   void SetAsNonPremult(bool aIsNonPremult);
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
-  void SetDiscardable();
   void SetOptimizable();
 
   TemporaryRef<SourceSurface> GetSurface();
   TemporaryRef<DrawTarget> GetDrawTarget();
 
   Color
   SinglePixelColor()
   {
@@ -168,16 +168,17 @@ private: // methods
                                       gfxRect&           aImageRect,
                                       ImageRegion&       aRegion,
                                       SourceSurface*     aSurface);
 
 private: // data
   RefPtr<DataSourceSurface> mImageSurface;
   RefPtr<SourceSurface> mOptSurface;
 
+  IntSize      mImageSize;
   IntSize      mSize;
   nsIntPoint   mOffset;
 
   nsIntRect    mDecoded;
 
   mutable Mutex mDecodedMutex;
 
   // The palette and image data for images that are paletted, since Cairo
@@ -200,17 +201,16 @@ private: // data
 
   SurfaceFormat mFormat;
   uint8_t      mPaletteDepth;
   int8_t       mBlendMethod;
   bool mSinglePixel;
   bool mCompositingFailed;
   bool mHasNoAlpha;
   bool mNonPremult;
-  bool mDiscardable;
   bool mOptimizable;
 
   /** Have we called DiscardTracker::InformAllocation()? */
   bool mInformedDiscardTracker;
 
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
 };