Bug 1116735 - Allocate frames in the decoder. r=tn
authorSeth Fowler <seth@mozilla.com>
Thu, 08 Jan 2015 00:01:25 -0800
changeset 248557 0933c1aef19729d4163d61f8962f11c473a26103
parent 248556 94bd476efa143f7d07844968a7f9c77f440d0672
child 248558 c96ef32cd8a5852e04e7e3924dd52cd48c709433
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstn
bugs1116735
milestone37.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 1116735 - Allocate frames in the decoder. r=tn
image/decoders/nsBMPDecoder.cpp
image/decoders/nsBMPDecoder.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/FrameAnimator.cpp
image/src/FrameAnimator.h
image/src/ImageMetadata.h
image/src/RasterImage.cpp
image/src/RasterImage.h
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -34,27 +34,32 @@ GetBMPLog()
 }
 #endif
 
 // Convert from row (1..height) to absolute line (0..height-1)
 #define LINE(row) ((mBIH.height < 0) ? (-mBIH.height - (row)) : ((row) - 1))
 #define PIXEL_OFFSET(row, col) (LINE(row) * mBIH.width + col)
 
 nsBMPDecoder::nsBMPDecoder(RasterImage& aImage)
- : Decoder(aImage)
-{
-  mColors = nullptr;
-  mRow = nullptr;
-  mCurPos = mPos = mNumColors = mRowBytes = 0;
-  mOldLine = mCurLine = 1; // Otherwise decoder will never start
-  mState = eRLEStateInitial;
-  mStateData = 0;
-  mLOH = WIN_V3_HEADER_LENGTH;
-  mUseAlphaData = mHaveAlphaData = false;
-}
+  : Decoder(aImage)
+  , mPos(0)
+  , mLOH(WIN_V3_HEADER_LENGTH)
+  , mNumColors(0)
+  , mColors(nullptr)
+  , mRow(nullptr)
+  , mRowBytes(0)
+  , mCurLine(1)  // Otherwise decoder will never start.
+  , mOldLine(1)
+  , mCurPos(0)
+  , mState(eRLEStateInitial)
+  , mStateData(0)
+  , mProcessedHeader(false)
+  , mUseAlphaData(false)
+  , mHaveAlphaData(false)
+{ }
 
 nsBMPDecoder::~nsBMPDecoder()
 {
   delete[] mColors;
   if (mRow) {
       moz_free(mRow);
   }
 }
@@ -322,20 +327,22 @@ nsBMPDecoder::WriteInternal(const char* 
       aCount -= toCopy;
       aBuffer += toCopy;
   }
 
   // At this point mPos should be >= mLOH unless aBuffer did not have enough
   // data. In the latter case aCount should be 0.
   MOZ_ASSERT(mPos >= mLOH || aCount == 0);
 
-  // HasSize is called to ensure that if at this point mPos == mLOH but
-  // we have no data left to process, the next time WriteInternal is called
+  // mProcessedHeader is checked to ensure that if at this point mPos == mLOH
+  // but we have no data left to process, the next time WriteInternal is called
   // we won't enter this condition again.
-  if (mPos == mLOH && !HasSize()) {
+  if (mPos == mLOH && !mProcessedHeader) {
+      mProcessedHeader = true;
+
       ProcessInfoHeader();
       PR_LOG(GetBMPLog(), PR_LOG_DEBUG,
              ("BMP is %lix%lix%lu. compression=%lu\n",
              mBIH.width, mBIH.height, mBIH.bpp, mBIH.compression));
       // Verify we support this bit depth
       if (mBIH.bpp != 1 && mBIH.bpp != 4 && mBIH.bpp != 8 &&
           mBIH.bpp != 16 && mBIH.bpp != 24 && mBIH.bpp != 32) {
         PostDataError();
--- a/image/decoders/nsBMPDecoder.h
+++ b/image/decoders/nsBMPDecoder.h
@@ -89,16 +89,19 @@ private:
     /// Set mBFH from the raw data in mRawBuf, converting from little-endian
     /// data to native data as necessary
     void ProcessFileHeader();
 
     /// Set mBIH from the raw data in mRawBuf, converting from little-endian
     /// data to native data as necessary
     void ProcessInfoHeader();
 
+    /// True if we've already processed the BMP header.
+    bool mProcessedHeader;
+
     // Stores whether the image data may store alpha data, or if
     // the alpha data is unspecified and filled with a padding byte of 0.
     // When a 32BPP bitmap is stored in an ICO or CUR file, its 4th byte
     // is used for alpha transparency.  When it is stored in a BMP, its
     // 4th byte is reserved and is always 0.
     // Reference:
     // http://en.wikipedia.org/wiki/ICO_(file_format)#cite_note-9
     // Bitmaps where the alpha bytes are all 0 should be fully visible.
--- a/image/src/Decoder.cpp
+++ b/image/src/Decoder.cpp
@@ -1,21 +1,27 @@
 
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "Decoder.h"
+
+#include "mozilla/gfx/2D.h"
+#include "imgIContainer.h"
 #include "nsIConsoleService.h"
 #include "nsIScriptError.h"
 #include "GeckoProfiler.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
+using mozilla::gfx::IntSize;
+using mozilla::gfx::SurfaceFormat;
+
 namespace mozilla {
 namespace image {
 
 Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
   , mProgress(NoProgress)
   , mImageData(nullptr)
   , mColormap(nullptr)
@@ -77,16 +83,17 @@ Decoder::InitSharedDecoder(uint8_t* aIma
   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()) {
+    mFrameCount++;
     PostFrameStart();
   }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
 }
 
@@ -227,29 +234,29 @@ Decoder::FinishSharedDecoder()
 }
 
 nsresult
 Decoder::AllocateFrame()
 {
   MOZ_ASSERT(mNeedsNewFrame);
   MOZ_ASSERT(NS_IsMainThread());
 
-  mCurrentFrame = mImage.EnsureFrame(mNewFrameData.mFrameNum,
-                                     mNewFrameData.mFrameRect,
-                                     mDecodeFlags,
-                                     mNewFrameData.mFormat,
-                                     mNewFrameData.mPaletteDepth,
-                                     mCurrentFrame.get());
+  mCurrentFrame = 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) {
+    if (mNewFrameData.mFrameNum + 1 == mFrameCount) {
       PostFrameStart();
     }
   } else {
     PostDataError();
   }
 
   // Mark ourselves as not needing another frame before talking to anyone else
   // so they can tell us if they need yet another.
@@ -259,16 +266,151 @@ Decoder::AllocateFrame()
   // be flushed now that we have a frame to decode into.
   if (mBytesDecoded > 0) {
     mNeedsToFlushData = true;
   }
 
   return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
 }
 
+RawAccessFrameRef
+Decoder::EnsureFrame(uint32_t aFrameNum,
+                     const nsIntRect& aFrameRect,
+                     uint32_t aDecodeFlags,
+                     SurfaceFormat aFormat,
+                     uint8_t aPaletteDepth,
+                     imgFrame* aPreviousFrame)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mDataError || NS_FAILED(mFailCode)) {
+    return RawAccessFrameRef();
+  }
+
+  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
+  if (aFrameNum > mFrameCount) {
+    return RawAccessFrameRef();
+  }
+
+  // Adding a frame that doesn't already exist. This is the normal case.
+  if (aFrameNum == mFrameCount) {
+    return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                            aPaletteDepth, aPreviousFrame);
+  }
+
+  // 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(aFrameNum == 0, "Replacing a frame other than the first?");
+  MOZ_ASSERT(mFrameCount == 1, "Should have only one frame");
+  MOZ_ASSERT(aPreviousFrame, "Need the previous frame to replace");
+  if (aFrameNum != 0 || !aPreviousFrame || mFrameCount != 1) {
+    return RawAccessFrameRef();
+  }
+
+  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(&mImage),
+                              RasterSurfaceKey(prevFrameSize, aDecodeFlags, 0));
+  mFrameCount = 0;
+  mInFrame = false;
+
+  // Add the new frame as usual.
+  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
+                          aPaletteDepth, nullptr);
+}
+
+RawAccessFrameRef
+Decoder::InternalAddFrame(uint32_t aFrameNum,
+                          const nsIntRect& aFrameRect,
+                          uint32_t aDecodeFlags,
+                          SurfaceFormat aFormat,
+                          uint8_t aPaletteDepth,
+                          imgFrame* aPreviousFrame)
+{
+  MOZ_ASSERT(aFrameNum <= mFrameCount, "Invalid frame index!");
+  if (aFrameNum > mFrameCount) {
+    return RawAccessFrameRef();
+  }
+
+  MOZ_ASSERT(mImageMetadata.HasSize());
+  nsIntSize imageSize(mImageMetadata.GetWidth(), mImageMetadata.GetHeight());
+  if (imageSize.width <= 0 || imageSize.height <= 0 ||
+      aFrameRect.width <= 0 || aFrameRect.height <= 0) {
+    NS_WARNING("Trying to add frame with zero or negative size");
+    return RawAccessFrameRef();
+  }
+
+  if (!SurfaceCache::CanHold(imageSize.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(imageSize, aFrameRect, aFormat,
+                                      aPaletteDepth))) {
+    NS_WARNING("imgFrame::Init should succeed");
+    return RawAccessFrameRef();
+  }
+  frame->SetAsNonPremult(aDecodeFlags &
+                         imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
+
+  RawAccessFrameRef ref = frame->RawAccessRef();
+  if (!ref) {
+    return RawAccessFrameRef();
+  }
+
+  bool succeeded =
+    SurfaceCache::Insert(frame, ImageKey(&mImage),
+                         RasterSurfaceKey(imageSize.ToIntSize(),
+                                          aDecodeFlags,
+                                          aFrameNum),
+                         Lifetime::Persistent);
+  if (!succeeded) {
+    return RawAccessFrameRef();
+  }
+
+  nsIntRect refreshArea;
+
+  if (aFrameNum == 1) {
+    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
+    aPreviousFrame->SetRawAccessOnly();
+
+    // 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).
+    DisposalMethod disposalMethod = aPreviousFrame->GetDisposalMethod();
+    if (disposalMethod == DisposalMethod::CLEAR ||
+        disposalMethod == DisposalMethod::CLEAR_ALL ||
+        disposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
+      refreshArea = aPreviousFrame->GetRect();
+    }
+  }
+
+  if (aFrameNum > 0) {
+    ref->SetRawAccessOnly();
+
+    // 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.
+    refreshArea.UnionRect(refreshArea, frame->GetRect());
+  }
+
+  mFrameCount++;
+  mImage.OnAddedFrame(mFrameCount, refreshArea);
+
+  return ref;
+}
+
 void
 Decoder::SetSizeOnImage()
 {
   MOZ_ASSERT(mImageMetadata.HasSize(), "Should have size");
   MOZ_ASSERT(mImageMetadata.HasOrientation(), "Should have orientation");
 
   mImage.SetSize(mImageMetadata.GetWidth(),
                  mImageMetadata.GetHeight(),
@@ -311,30 +453,23 @@ Decoder::PostHasTransparency()
 
 void
 Decoder::PostFrameStart()
 {
   // We shouldn't already be mid-frame
   NS_ABORT_IF_FALSE(!mInFrame, "Starting new frame but not done with old one!");
 
   // Update our state to reflect the new frame
-  mFrameCount++;
   mInFrame = true;
 
   // If we just became animated, record that fact.
   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.
-  MOZ_ASSERT(mFrameCount == mImage.GetNumFrames(),
-             "Decoder frame count doesn't match image's!");
 }
 
 void
 Decoder::PostFrameStop(Opacity aFrameOpacity /* = Opacity::TRANSPARENT */,
                        DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
                        int32_t aTimeout /* = 0 */,
                        BlendMethod aBlendMethod /* = BlendMethod::OVER */)
 {
--- a/image/src/Decoder.h
+++ b/image/src/Decoder.h
@@ -154,16 +154,21 @@ public:
   };
 
   void SetDecodeFlags(uint32_t aFlags) { mDecodeFlags = aFlags; }
   uint32_t GetDecodeFlags() { return mDecodeFlags; }
 
   bool HasSize() const { return mImageMetadata.HasSize(); }
   void SetSizeOnImage();
 
+  void SetSize(const nsIntSize& aSize, const Orientation& aOrientation)
+  {
+    PostSize(aSize.width, aSize.height, aOrientation);
+  }
+
   // Use HistogramCount as an invalid Histogram ID
   virtual Telemetry::ID SpeedHistogram() { return Telemetry::HistogramCount; }
 
   ImageMetadata& GetImageMetadata() { return mImageMetadata; }
 
   // Tell the decoder infrastructure to allocate a frame. By default, frame 0
   // is created as an ARGB frame with no offset and with size width * height.
   // If decoders need something different, they must ask for it.
@@ -260,16 +265,37 @@ protected:
   // For animated images, specify the loop count. -1 means loop forever, 0
   // means a single iteration, stopping on the last frame.
   void PostDecodeDone(int32_t aLoopCount = 0);
 
   // Data errors are the fault of the source data, decoder errors are our fault
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
+  /**
+   * Ensures that a given frame number exists with the given parameters, and
+   * 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, or if there is only one frame in the
+   * array, replace that frame.
+   * If a non-paletted frame is desired, pass 0 for aPaletteDepth.
+   */
+  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
+                                const nsIntRect& aFrameRect,
+                                uint32_t aDecodeFlags,
+                                gfx::SurfaceFormat aFormat,
+                                uint8_t aPaletteDepth,
+                                imgFrame* aPreviousFrame);
+
+  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
+                                     const nsIntRect& aFrameRect,
+                                     uint32_t aDecodeFlags,
+                                     gfx::SurfaceFormat aFormat,
+                                     uint8_t aPaletteDepth,
+                                     imgFrame* aPreviousFrame);
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
   RawAccessFrameRef mCurrentFrame;
   ImageMetadata mImageMetadata;
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -242,22 +242,16 @@ FrameAnimator::InitAnimationFrameTimeIfN
 
 void
 FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime)
 {
   mCurrentAnimationFrameTime = aTime;
 }
 
 void
-FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect)
-{
-  mFirstFrameRefreshArea = aRect;
-}
-
-void
 FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect)
 {
   mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect);
 }
 
 uint32_t
 FrameAnimator::GetCurrentAnimationFrameIndex() const
 {
--- a/image/src/FrameAnimator.h
+++ b/image/src/FrameAnimator.h
@@ -95,21 +95,16 @@ public:
   /**
    * The animation mode of the image.
    *
    * Constants defined in imgIContainer.idl.
    */
   void SetAnimationMode(uint16_t aAnimationMode);
 
   /**
-   * Set the area to refresh when we loop around to the first frame.
-   */
-  void SetFirstFrameRefreshArea(const nsIntRect& aRect);
-
-  /**
    * Union the area to refresh when we loop around to the first frame with this
    * rect.
    */
   void UnionFirstFrameRefreshArea(const nsIntRect& aRect);
 
   /**
    * If the animation frame time has not yet been set, set it to
    * TimeStamp::Now().
--- a/image/src/ImageMetadata.h
+++ b/image/src/ImageMetadata.h
@@ -37,18 +37,20 @@ public:
   }
   void SetLoopCount(int32_t loopcount)
   {
     mLoopCount = loopcount;
   }
 
   void SetSize(int32_t width, int32_t height, Orientation orientation)
   {
-    mSize.emplace(nsIntSize(width, height));
-    mOrientation.emplace(orientation);
+    if (!HasSize()) {
+      mSize.emplace(nsIntSize(width, height));
+      mOrientation.emplace(orientation);
+    }
   }
 
   bool HasSize() const { return mSize.isSome(); }
   bool HasOrientation() const { return mOrientation.isSome(); }
 
   int32_t GetWidth() const { return mSize->width; }
   int32_t GetHeight() const { return mSize->height; }
   Orientation GetOrientation() const { return *mOrientation; }
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -925,110 +925,50 @@ RasterImage::SizeOfDecoded(gfxMemoryLoca
   size_t n = 0;
   n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
   if (mAnim) {
     n += mAnim->SizeOfCompositingFrames(aLocation, aMallocSizeOf);
   }
   return n;
 }
 
-RawAccessFrameRef
-RasterImage::InternalAddFrame(uint32_t aFrameNum,
-                              const nsIntRect& aFrameRect,
-                              uint32_t aDecodeFlags,
-                              SurfaceFormat aFormat,
-                              uint8_t aPaletteDepth,
-                              imgFrame* aPreviousFrame)
+void
+RasterImage::OnAddedFrame(uint32_t aNewFrameCount,
+                          const nsIntRect& aNewRefreshArea)
 {
-  // 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();
-  }
-
-  bool succeeded =
-    SurfaceCache::Insert(frame, ImageKey(this),
-                         RasterSurfaceKey(mSize.ToIntSize(),
-                                          aDecodeFlags,
-                                          aFrameNum),
-                         Lifetime::Persistent);
-  if (!succeeded) {
-    return RawAccessFrameRef();
-  }
-
-  if (aFrameNum == 1) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT((mFrameCount == 1 && aNewFrameCount == 1) ||
+             mFrameCount < aNewFrameCount,
+             "Frame count running backwards");
+
+  mFrameCount = aNewFrameCount;
+
+  if (aNewFrameCount == 2) {
     // We're becoming animated, so initialize animation stuff.
     MOZ_ASSERT(!mAnim, "Already have animation state?");
     mAnim = MakeUnique<FrameAnimator>(this, mSize.ToIntSize(), 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
     // 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();
 
-    MOZ_ASSERT(aPreviousFrame, "Must provide a previous frame when animated");
-    aPreviousFrame->SetRawAccessOnly();
-
-    // 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).
-    DisposalMethod disposalMethod = aPreviousFrame->GetDisposalMethod();
-    if (disposalMethod == DisposalMethod::CLEAR ||
-        disposalMethod == DisposalMethod::CLEAR_ALL ||
-        disposalMethod == DisposalMethod::RESTORE_PREVIOUS) {
-      mAnim->SetFirstFrameRefreshArea(aPreviousFrame->GetRect());
-    }
-
     if (mPendingAnimation && ShouldAnimate()) {
       StartAnimation();
     }
   }
 
-  if (aFrameNum > 0) {
-    ref->SetRawAccessOnly();
-
-    // 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());
+  if (aNewFrameCount > 1) {
+    mAnim->UnionFirstFrameRefreshArea(aNewRefreshArea);
   }
-
-  mFrameCount++;
-  return ref;
 }
 
 nsresult
 RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDecodingMonitor.AssertCurrentThreadIn();
 
@@ -1059,68 +999,16 @@ RasterImage::SetSize(int32_t aWidth, int
   // Set the size and flag that we have it
   mSize.SizeTo(aWidth, aHeight);
   mOrientation = aOrientation;
   mHasSize = true;
 
   return NS_OK;
 }
 
-RawAccessFrameRef
-RasterImage::EnsureFrame(uint32_t aFrameNum,
-                         const nsIntRect& aFrameRect,
-                         uint32_t aDecodeFlags,
-                         SurfaceFormat aFormat,
-                         uint8_t aPaletteDepth,
-                         imgFrame* aPreviousFrame)
-{
-  if (mError) {
-    return RawAccessFrameRef();
-  }
-
-  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, aFrameRect, aDecodeFlags, aFormat,
-                            aPaletteDepth, aPreviousFrame);
-  }
-
-  // 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(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(!mAnim, "Shouldn't be animated");
-  if (aFrameNum != 0 || !aPreviousFrame || GetNumFrames() != 1) {
-    return RawAccessFrameRef();
-  }
-
-  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, 0));
-  mFrameCount = 0;
-
-  // Add the new frame as usual.
-  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
-                          aPaletteDepth, nullptr);
-}
-
 void
 RasterImage::DecodingComplete(imgFrame* aFinalFrame)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mError) {
     return;
   }
@@ -1626,16 +1514,17 @@ RasterImage::InitDecoder(bool aDoSizeDec
 
   // Initialize the decoder
   mDecoder->SetSizeDecode(aDoSizeDecode);
   mDecoder->SetDecodeFlags(mFrameDecodeFlags);
   if (!aDoSizeDecode) {
     // We already have the size; tell the decoder so it can preallocate a
     // frame.  By default, we create an ARGB frame with no offset. If decoders
     // need a different type, they need to ask for it themselves.
+    mDecoder->SetSize(mSize, mOrientation);
     mDecoder->NeedNewFrame(0, 0, 0, mSize.width, mSize.height,
                            SurfaceFormat::B8G8R8A8);
     mDecoder->AllocateFrame();
   }
   mDecoder->Init();
   CONTAINER_ENSURE_SUCCESS(mDecoder->GetDecoderError());
 
   if (!aDoSizeDecode) {
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -172,47 +172,42 @@ public:
 
   virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
   virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
                                MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
 
   /* Triggers discarding. */
   void Discard();
 
-  /* Callbacks for decoders */
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Decoder callbacks.
+  //////////////////////////////////////////////////////////////////////////////
+
+  void OnAddedFrame(uint32_t aNewFrameCount, const nsIntRect& aNewRefreshArea);
 
   /** 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 a RawAccessFrameRef for that frame.
-   * It is not possible to create sparse frame arrays; you can only append
-   * 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.
+   * Number of times to loop the image.
+   * @note -1 means forever.
    */
-  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
-                                const nsIntRect& aFrameRect,
-                                uint32_t aDecodeFlags,
-                                gfx::SurfaceFormat aFormat,
-                                uint8_t aPaletteDepth,
-                                imgFrame* aPreviousFrame);
+  void     SetLoopCount(int32_t aLoopCount);
 
   /* notification that the entire image has been decoded */
   void DecodingComplete(imgFrame* aFinalFrame);
 
-  /**
-   * Number of times to loop the image.
-   * @note -1 means forever.
-   */
-  void     SetLoopCount(int32_t aLoopCount);
+
+  //////////////////////////////////////////////////////////////////////////////
+  // Network callbacks.
+  //////////////////////////////////////////////////////////////////////////////
 
   /* Add compressed source data to the imgContainer.
    *
    * The decoder will use this data, either immediately or at draw time, to
    * decode the image.
    *
    * XXX This method's only caller (WriteToContainer) ignores the return
    * value. Should this just return void?
@@ -304,22 +299,16 @@ private:
   uint32_t GetCurrentFrameIndex() const;
   uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
 
   nsIntRect GetFirstFrameRect();
 
   size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                  MallocSizeOf aMallocSizeOf) const;
 
-  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
-                                     const nsIntRect& aFrameRect,
-                                     uint32_t aDecodeFlags,
-                                     gfx::SurfaceFormat aFormat,
-                                     uint8_t aPaletteDepth,
-                                     imgFrame* aPreviousFrame);
   nsresult DoImageDataComplete();
 
   already_AddRefed<layers::Image> GetCurrentImage();
   void UpdateImageContainer();
 
   enum RequestDecodeType {
       ASYNCHRONOUS,
       SYNCHRONOUS_NOTIFY,