Bug 1116716 - Store animated frames in the SurfaceCache. r=tn
authorSeth Fowler <seth@mozilla.com>
Wed, 07 Jan 2015 01:40:23 -0800
changeset 235457 6e4dce410c272c458fd4772644e759d043ba0e8b
parent 235456 3f32666fc1dc747ade2aa2d5d9db7e59cc934cd9
child 235458 39b05ad67a2f1cf14b76c349a40e9718949de383
push id366
push usercmanchester@mozilla.com
push dateThu, 08 Jan 2015 16:40:24 +0000
reviewerstn
bugs1116716
milestone37.0a1
Bug 1116716 - Store animated frames in the SurfaceCache. r=tn
image/src/FrameAnimator.cpp
image/src/FrameAnimator.h
image/src/FrameBlender.cpp
image/src/FrameBlender.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/SurfaceCache.h
image/src/imgFrame.cpp
image/src/imgFrame.h
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -6,40 +6,31 @@
 #include "FrameAnimator.h"
 #include "FrameBlender.h"
 
 #include "imgIContainer.h"
 
 namespace mozilla {
 namespace image {
 
-FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender,
-                             uint16_t aAnimationMode)
-  : mCurrentAnimationFrameIndex(0)
-  , mLoopCounter(-1)
-  , mFrameBlender(aFrameBlender)
-  , mAnimationMode(aAnimationMode)
-  , mDoneDecoding(false)
-{ }
-
 int32_t
 FrameAnimator::GetSingleLoopTime() const
 {
   // If we aren't done decoding, we don't know the image's full play time.
   if (!mDoneDecoding) {
     return -1;
   }
 
   // If we're not looping, a single loop time has no meaning
   if (mAnimationMode != imgIContainer::kNormalAnimMode) {
     return -1;
   }
 
   uint32_t looptime = 0;
-  for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) {
+  for (uint32_t i = 0; i < mImage->GetNumFrames(); ++i) {
     int32_t timeout = mFrameBlender.GetTimeoutForFrame(i);
     if (timeout >= 0) {
       looptime += static_cast<uint32_t>(timeout);
     } else {
       // If we have a frame that never times out, we're probably in an error
       // case, but let's handle it more gracefully.
       NS_WARNING("Negative frame timeout - how did this happen?");
       return -1;
@@ -80,37 +71,37 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
   NS_ASSERTION(aTime <= TimeStamp::Now(),
                "Given time appears to be in the future");
 
   uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
   uint32_t nextFrameIndex = currentFrameIndex + 1;
   int32_t timeout = 0;
 
   RefreshResult ret;
-  nsRefPtr<imgFrame> nextFrame = mFrameBlender.RawGetFrame(nextFrameIndex);
+  RawAccessFrameRef nextFrame = mFrameBlender.GetRawFrame(nextFrameIndex);
 
   // If we're done decoding, we know we've got everything we're going to get.
   // If we aren't, we only display fully-downloaded frames; everything else
   // gets delayed.
   bool canDisplay = mDoneDecoding || (nextFrame && nextFrame->ImageComplete());
 
   if (!canDisplay) {
     // Uh oh, the frame we want to show is currently being decoded (partial)
     // Wait until the next refresh driver tick and try again
     return ret;
   }
 
   // If we're done decoding the next frame, go ahead and display it now and
   // reinit with the next frame's delay time.
-  if (mFrameBlender.GetNumFrames() == nextFrameIndex) {
+  if (mImage->GetNumFrames() == nextFrameIndex) {
     // End of an animation loop...
 
     // If we are not looping forever, initialize the loop counter
-    if (mLoopCounter < 0 && mFrameBlender.GetLoopCount() >= 0) {
-      mLoopCounter = mFrameBlender.GetLoopCount();
+    if (mLoopCounter < 0 && mFrameBlender.LoopCount() >= 0) {
+      mLoopCounter = mFrameBlender.LoopCount();
     }
 
     // If animation mode is "loop once", or we're at end of loop counter,
     // it's time to stop animating
     if (mAnimationMode == imgIContainer::kLoopOnceAnimMode ||
         mLoopCounter == 0) {
       ret.animationFinished = true;
     }
@@ -135,17 +126,17 @@ FrameAnimator::AdvanceFrame(TimeStamp aT
     ret.error = true;
   }
 
   if (nextFrameIndex == 0) {
     ret.dirtyRect = mFirstFrameRefreshArea;
   } else {
     // Change frame
     if (nextFrameIndex != currentFrameIndex + 1) {
-      nextFrame = mFrameBlender.RawGetFrame(nextFrameIndex);
+      nextFrame = mFrameBlender.GetRawFrame(nextFrameIndex);
     }
 
     if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex,
                                nextFrameIndex)) {
       // something went wrong, move on to next
       NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
       nextFrame->SetCompositingFailed(true);
       mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
--- a/image/src/FrameAnimator.h
+++ b/image/src/FrameAnimator.h
@@ -13,17 +13,26 @@
 namespace mozilla {
 namespace image {
 
 class FrameBlender;
 
 class FrameAnimator
 {
 public:
-  FrameAnimator(FrameBlender& aBlender, uint16_t aAnimationMode);
+  FrameAnimator(RasterImage* aImage,
+                FrameBlender& aFrameBlender,
+                uint16_t aAnimationMode)
+    : mCurrentAnimationFrameIndex(0)
+    , mLoopCounter(-1)
+    , mImage(aImage)
+    , mFrameBlender(aFrameBlender)
+    , mAnimationMode(aAnimationMode)
+    , mDoneDecoding(false)
+  { }
 
   /**
    * Return value from RequestRefresh. Tells callers what happened in that call
    * to RequestRefresh.
    */
   struct RefreshResult
   {
     // The dirty rectangle to be re-drawn after this RequestRefresh().
@@ -153,16 +162,19 @@ private: // data
   TimeStamp mCurrentAnimationFrameTime;
 
   //! The current frame index we're on. 0 to (numFrames - 1).
   uint32_t mCurrentAnimationFrameIndex;
 
   //! number of loops remaining before animation stops (-1 no stop)
   int32_t mLoopCounter;
 
+  //! A weak pointer to our owner.
+  RasterImage* mImage;
+
   //! All the frames of the image, shared with our owner
   FrameBlender& mFrameBlender;
 
   //! The animation mode of this image. Constants defined in imgIContainer.
   uint16_t mAnimationMode;
 
   //! Whether this image is done being decoded.
   bool mDoneDecoding;
--- a/image/src/FrameBlender.cpp
+++ b/image/src/FrameBlender.cpp
@@ -12,62 +12,53 @@
 #include "pixman.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace image {
 
-FrameBlender::FrameBlender()
- : mAnim(nullptr)
- , mLoopCount(-1)
+DrawableFrameRef
+FrameBlender::GetCompositedFrame(uint32_t aFrameNum)
 {
-}
+  MOZ_ASSERT(aFrameNum != 0, "First frame is never composited");
+
+  // If we have a composited version of this frame, return that.
+  if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
+    return mCompositingFrame->DrawableRef();
+  }
 
-FrameBlender::~FrameBlender()
-{
-  delete mAnim;
+  // Otherwise return the raw frame. DoBlend is required to ensure that we only
+  // hit this case if the frame is not paletted and doesn't require compositing.
+  DrawableFrameRef ref =
+    SurfaceCache::Lookup(mImageKey,
+                         RasterSurfaceKey(mSize,
+                                          0,  // Default decode flags.
+                                          aFrameNum));
+  MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
+  return ref;
 }
 
-already_AddRefed<imgFrame>
-FrameBlender::GetFrame(uint32_t aFrameNum)
-{
-  if (mAnim && mAnim->lastCompositedFrameIndex == int32_t(aFrameNum)) {
-    nsRefPtr<imgFrame> frame = mAnim->compositingFrame.get();
-    return frame.forget();
-  }
-  return RawGetFrame(aFrameNum);
-}
-
-already_AddRefed<imgFrame>
-FrameBlender::RawGetFrame(uint32_t aFrameNum)
+RawAccessFrameRef
+FrameBlender::GetRawFrame(uint32_t aFrameNum)
 {
-  if (!mAnim) {
-    NS_ASSERTION(aFrameNum == 0,
-                 "Don't ask for a frame > 0 if we're not animated!");
-    aFrameNum = 0;
-  }
-  if (aFrameNum >= mFrames.Length()) {
-    return nullptr;
-  }
-  nsRefPtr<imgFrame> frame = mFrames[aFrameNum].get();
-  return frame.forget();
-}
-
-uint32_t
-FrameBlender::GetNumFrames() const
-{
-  return mFrames.Length();
+  DrawableFrameRef ref =
+    SurfaceCache::Lookup(mImageKey,
+                         RasterSurfaceKey(mSize,
+                                          0,  // Default decode flags.
+                                          aFrameNum));
+  return ref ? ref->RawAccessRef()
+             : RawAccessFrameRef();
 }
 
 int32_t
 FrameBlender::GetTimeoutForFrame(uint32_t aFrameNum)
 {
-  nsRefPtr<imgFrame> frame = RawGetFrame(aFrameNum);
+  RawAccessFrameRef frame = GetRawFrame(aFrameNum);
   const int32_t timeout = frame->GetRawTimeout();
 
   // Ensure a minimal time between updates so we don't throttle the UI thread.
   // consider 0 == unspecified and make it fast but not too fast.  Unless we
   // have a single loop GIF. See bug 890743, bug 125137, bug 139677, and bug
   // 207059. The behavior of recent IE and Opera versions seems to be:
   // IE 6/Win:
   //   10 - 50ms go 100ms
@@ -80,74 +71,32 @@ FrameBlender::GetTimeoutForFrame(uint32_
   // range.
   if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
     return 100;
   }
 
   return timeout;
 }
 
-void
-FrameBlender::SetLoopCount(int32_t aLoopCount)
-{
-  mLoopCount = aLoopCount;
-}
-
-int32_t
-FrameBlender::GetLoopCount() const
-{
-  return mLoopCount;
-}
-
-void
-FrameBlender::RemoveFrame(uint32_t aFrameNum)
-{
-  MOZ_ASSERT(aFrameNum < GetNumFrames(), "Deleting invalid frame!");
-  mFrames.RemoveElementAt(aFrameNum);
-}
-
-void
-FrameBlender::ClearFrames()
-{
-  mFrames.Clear();
-  mFrames.Compact();
-}
-
-void
-FrameBlender::InsertFrame(uint32_t aFrameNum, RawAccessFrameRef&& aRef)
-{
-  MOZ_ASSERT(aRef, "Need a reference to a frame");
-  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Inserting invalid frame");
-
-  mFrames.InsertElementAt(aFrameNum, Move(aRef));
-  if (GetNumFrames() == 2) {
-    MOZ_ASSERT(!mAnim, "Shouldn't have an animation context yet");
-    mAnim = new Anim();
-  }
-
-  MOZ_ASSERT(GetNumFrames() < 2 || mAnim,
-             "If we're animated we should have an animation context now");
-}
-
 //******************************************************************************
 // DoBlend gets called when the timer for animation get fired and we have to
 // update the composited frame of the animation.
 bool
 FrameBlender::DoBlend(nsIntRect* aDirtyRect,
                       uint32_t aPrevFrameIndex,
                       uint32_t aNextFrameIndex)
 {
-  nsRefPtr<imgFrame> prevFrame = RawGetFrame(aPrevFrameIndex);
-  nsRefPtr<imgFrame> nextFrame = RawGetFrame(aNextFrameIndex);
+  RawAccessFrameRef prevFrame = GetRawFrame(aPrevFrameIndex);
+  RawAccessFrameRef nextFrame = GetRawFrame(aNextFrameIndex);
 
   MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
 
   int32_t prevFrameDisposalMethod = prevFrame->GetFrameDisposalMethod();
   if (prevFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious &&
-      !mAnim->compositingPrevFrame) {
+      !mCompositingPrevFrame) {
     prevFrameDisposalMethod = FrameBlender::kDisposeClear;
   }
 
   nsIntRect prevFrameRect = prevFrame->GetRect();
   bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
                           prevFrameRect.width == mSize.width &&
                           prevFrameRect.height == mSize.height);
 
@@ -212,33 +161,34 @@ FrameBlender::DoBlend(nsIntRect* aDirtyR
   }
 
   // Optimization:
   //   Skip compositing if the last composited frame is this frame
   //   (Only one composited frame was made for this animation.  Example:
   //    Only Frame 3 of a 10 frame image required us to build a composite frame
   //    On the second loop, we do not need to rebuild the frame
   //    since it's still sitting in compositingFrame)
-  if (mAnim->lastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
+  if (mLastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
     return true;
   }
 
   bool needToBlankComposite = false;
 
   // Create the Compositing Frame
-  if (!mAnim->compositingFrame) {
+  if (!mCompositingFrame) {
     nsRefPtr<imgFrame> newFrame = new imgFrame;
-    nsresult rv = newFrame->InitForDecoder(mSize, SurfaceFormat::B8G8R8A8);
+    nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
+                                           SurfaceFormat::B8G8R8A8);
     if (NS_FAILED(rv)) {
-      mAnim->compositingFrame.reset();
+      mCompositingFrame.reset();
       return false;
     }
-    mAnim->compositingFrame = newFrame->RawAccessRef();
+    mCompositingFrame = newFrame->RawAccessRef();
     needToBlankComposite = true;
-  } else if (int32_t(aNextFrameIndex) != mAnim->lastCompositedFrameIndex+1) {
+  } else if (int32_t(aNextFrameIndex) != mLastCompositedFrameIndex+1) {
 
     // If we are not drawing on top of last composited frame,
     // then we are building a new composite frame, so let's clear it first.
     needToBlankComposite = true;
   }
 
   // More optimizations possible when next frame is not transparent
   // But if the next frame has FrameBlender::kDisposeRestorePrevious,
@@ -271,136 +221,137 @@ FrameBlender::DoBlend(nsIntRect* aDirtyR
 
   if (doDisposal) {
     // Dispose of previous: clear, restore, or keep (copy)
     switch (prevFrameDisposalMethod) {
       case FrameBlender::kDisposeClear:
         if (needToBlankComposite) {
           // If we just created the composite, it could have anything in its
           // buffer. Clear whole frame
-          ClearFrame(mAnim->compositingFrame->GetRawData(),
-                     mAnim->compositingFrame->GetRect());
+          ClearFrame(mCompositingFrame->GetRawData(),
+                     mCompositingFrame->GetRect());
         } else {
           // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mAnim->compositingFrame->GetRawData(),
-                     mAnim->compositingFrame->GetRect(),
+          ClearFrame(mCompositingFrame->GetRawData(),
+                     mCompositingFrame->GetRect(),
                      prevFrameRect);
         }
         break;
 
       case FrameBlender::kDisposeClearAll:
-        ClearFrame(mAnim->compositingFrame->GetRawData(),
-                   mAnim->compositingFrame->GetRect());
+        ClearFrame(mCompositingFrame->GetRawData(),
+                   mCompositingFrame->GetRect());
         break;
 
       case FrameBlender::kDisposeRestorePrevious:
         // It would be better to copy only the area changed back to
         // compositingFrame.
-        if (mAnim->compositingPrevFrame) {
-          CopyFrameImage(mAnim->compositingPrevFrame->GetRawData(),
-                         mAnim->compositingPrevFrame->GetRect(),
-                         mAnim->compositingFrame->GetRawData(),
-                         mAnim->compositingFrame->GetRect());
+        if (mCompositingPrevFrame) {
+          CopyFrameImage(mCompositingPrevFrame->GetRawData(),
+                         mCompositingPrevFrame->GetRect(),
+                         mCompositingFrame->GetRawData(),
+                         mCompositingFrame->GetRect());
 
           // destroy only if we don't need it for this frame's disposal
           if (nextFrameDisposalMethod !=
               FrameBlender::kDisposeRestorePrevious) {
-            mAnim->compositingPrevFrame.reset();
+            mCompositingPrevFrame.reset();
           }
         } else {
-          ClearFrame(mAnim->compositingFrame->GetRawData(),
-                     mAnim->compositingFrame->GetRect());
+          ClearFrame(mCompositingFrame->GetRawData(),
+                     mCompositingFrame->GetRect());
         }
         break;
 
       default:
         // Copy previous frame into compositingFrame before we put the new
         // frame on top
         // Assumes that the previous frame represents a full frame (it could be
         // smaller in size than the container, as long as the frame before it
         // erased itself)
         // Note: Frame 1 never gets into DoBlend(), so (aNextFrameIndex - 1)
         // will always be a valid frame number.
-        if (mAnim->lastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
+        if (mLastCompositedFrameIndex != int32_t(aNextFrameIndex - 1)) {
           if (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
             // Just copy the bits
             CopyFrameImage(prevFrame->GetRawData(),
                            prevFrame->GetRect(),
-                           mAnim->compositingFrame->GetRawData(),
-                           mAnim->compositingFrame->GetRect());
+                           mCompositingFrame->GetRawData(),
+                           mCompositingFrame->GetRect());
           } else {
             if (needToBlankComposite) {
               // Only blank composite when prev is transparent or not full.
               if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
-                ClearFrame(mAnim->compositingFrame->GetRawData(),
-                           mAnim->compositingFrame->GetRect());
+                ClearFrame(mCompositingFrame->GetRawData(),
+                           mCompositingFrame->GetRect());
               }
             }
             DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
                         prevFrame->PaletteDataLength(),
                         prevFrame->GetHasAlpha(),
-                        mAnim->compositingFrame->GetRawData(),
-                        mAnim->compositingFrame->GetRect(),
+                        mCompositingFrame->GetRawData(),
+                        mCompositingFrame->GetRect(),
                         FrameBlendMethod(prevFrame->GetBlendMethod()));
           }
         }
     }
   } else if (needToBlankComposite) {
     // If we just created the composite, it could have anything in its
     // buffers. Clear them
-    ClearFrame(mAnim->compositingFrame->GetRawData(),
-               mAnim->compositingFrame->GetRect());
+    ClearFrame(mCompositingFrame->GetRawData(),
+               mCompositingFrame->GetRect());
   }
 
   // Check if the frame we are composing wants the previous image restored after
   // it is done. Don't store it (again) if last frame wanted its image restored
   // too
   if ((nextFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious) &&
       (prevFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious)) {
     // We are storing the whole image.
     // It would be better if we just stored the area that nextFrame is going to
     // overwrite.
-    if (!mAnim->compositingPrevFrame) {
+    if (!mCompositingPrevFrame) {
       nsRefPtr<imgFrame> newFrame = new imgFrame;
-      nsresult rv = newFrame->InitForDecoder(mSize, SurfaceFormat::B8G8R8A8);
+      nsresult rv = newFrame->InitForDecoder(ThebesIntSize(mSize),
+                                             SurfaceFormat::B8G8R8A8);
       if (NS_FAILED(rv)) {
-        mAnim->compositingPrevFrame.reset();
+        mCompositingPrevFrame.reset();
         return false;
       }
 
-      mAnim->compositingPrevFrame = newFrame->RawAccessRef();
+      mCompositingPrevFrame = newFrame->RawAccessRef();
     }
 
-    CopyFrameImage(mAnim->compositingFrame->GetRawData(),
-                   mAnim->compositingFrame->GetRect(),
-                   mAnim->compositingPrevFrame->GetRawData(),
-                   mAnim->compositingPrevFrame->GetRect());
+    CopyFrameImage(mCompositingFrame->GetRawData(),
+                   mCompositingFrame->GetRect(),
+                   mCompositingPrevFrame->GetRawData(),
+                   mCompositingPrevFrame->GetRect());
   }
 
   // blit next frame into it's correct spot
   DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
               nextFrame->PaletteDataLength(),
               nextFrame->GetHasAlpha(),
-              mAnim->compositingFrame->GetRawData(),
-              mAnim->compositingFrame->GetRect(),
+              mCompositingFrame->GetRawData(),
+              mCompositingFrame->GetRect(),
               FrameBlendMethod(nextFrame->GetBlendMethod()));
 
   // Set timeout of CompositeFrame to timeout of frame we just composed
   // Bug 177948
   int32_t timeout = nextFrame->GetRawTimeout();
-  mAnim->compositingFrame->SetRawTimeout(timeout);
+  mCompositingFrame->SetRawTimeout(timeout);
 
   // Tell the image that it is fully 'downloaded'.
   nsresult rv =
-    mAnim->compositingFrame->ImageUpdated(mAnim->compositingFrame->GetRect());
+    mCompositingFrame->ImageUpdated(mCompositingFrame->GetRect());
   if (NS_FAILED(rv)) {
     return false;
   }
 
-  mAnim->lastCompositedFrameIndex = int32_t(aNextFrameIndex);
+  mLastCompositedFrameIndex = int32_t(aNextFrameIndex);
 
   return true;
 }
 
 //******************************************************************************
 // Fill aFrame with black. Does also clears the mask.
 void
 FrameBlender::ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect)
@@ -543,55 +494,32 @@ FrameBlender::DrawFrameTo(const uint8_t*
 
     pixman_image_unref(src);
     pixman_image_unref(dst);
   }
 
   return NS_OK;
 }
 
-void
-FrameBlender::Discard()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // 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!");
-
-  // Delete all the decoded frames, then clear the array.
-  ClearFrames();
-}
-
 size_t
 FrameBlender::SizeOfDecoded(gfxMemoryLocation aLocation,
                             MallocSizeOf aMallocSizeOf) const
 {
   size_t n = 0;
 
-  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
-    n += mFrames[i]->SizeOfExcludingThis(aLocation, aMallocSizeOf);
+  if (mCompositingFrame) {
+    n += mCompositingFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
   }
-
-  if (mAnim) {
-    if (mAnim->compositingFrame) {
-      n += mAnim->compositingFrame
-                ->SizeOfExcludingThis(aLocation, aMallocSizeOf);
-    }
-    if (mAnim->compositingPrevFrame) {
-      n += mAnim->compositingPrevFrame
-                ->SizeOfExcludingThis(aLocation, aMallocSizeOf);
-    }
+  if (mCompositingPrevFrame) {
+    n += mCompositingPrevFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
   }
 
   return n;
 }
 
 void
 FrameBlender::ResetAnimation()
 {
-  if (mAnim) {
-    mAnim->lastCompositedFrameIndex = -1;
-  }
+  mLastCompositedFrameIndex = -1;
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/FrameBlender.h
+++ b/image/src/FrameBlender.h
@@ -3,80 +3,70 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_imagelib_FrameBlender_h_
 #define mozilla_imagelib_FrameBlender_h_
 
 #include "mozilla/MemoryReporting.h"
+#include "gfx2DGlue.h"
 #include "gfxTypes.h"
 #include "imgFrame.h"
 #include "nsCOMPtr.h"
+#include "SurfaceCache.h"
 
 namespace mozilla {
 namespace image {
 
 /**
  * FrameBlender stores and gives access to imgFrames. It also knows how to
  * blend frames from previous to next, looping if necessary.
  *
  * All logic about when and whether to blend are external to FrameBlender.
  */
 class FrameBlender
 {
 public:
-
-  /**
-   * Create a new FrameBlender with a given frame sequence.
-   *
-   * If aSequenceToUse is not specified, it will be allocated automatically.
-   */
-  FrameBlender();
-  ~FrameBlender();
+  FrameBlender(ImageKey aImageKey, gfx::IntSize aSize)
+   : mImageKey(aImageKey)
+   , mSize(aSize)
+   , mLastCompositedFrameIndex(-1)
+   , mLoopCount(-1)
+  { }
 
   bool DoBlend(nsIntRect* aDirtyRect, uint32_t aPrevFrameIndex,
                uint32_t aNextFrameIndex);
 
   /**
-   * Get the @aIndex-th frame, including (if applicable) any results of
-   * blending.
+   * If we have a composited frame for @aFrameNum, returns it. Otherwise, returns
+   * an empty DrawableFrameRef. It is an error to call this method with
+   * aFrameNum == 0, because the first frame is never composited.
    */
-  already_AddRefed<imgFrame> GetFrame(uint32_t aIndex);
+  DrawableFrameRef GetCompositedFrame(uint32_t aFrameNum);
 
   /**
    * Get the @aIndex-th frame in the frame index, ignoring results of blending.
    */
-  already_AddRefed<imgFrame> RawGetFrame(uint32_t aIndex);
-
-  void InsertFrame(uint32_t aFrameNum, RawAccessFrameRef&& aRef);
-  void RemoveFrame(uint32_t aFrameNum);
-  void ClearFrames();
-
-  /* The total number of frames in this image. */
-  uint32_t GetNumFrames() const;
+  RawAccessFrameRef GetRawFrame(uint32_t aFrameNum);
 
   /*
    * Returns the frame's adjusted timeout. If the animation loops and the
    * timeout falls in between a certain range then the timeout is adjusted so
    * that it's never 0. If the animation does not loop then no adjustments are
    * made.
    */
   int32_t GetTimeoutForFrame(uint32_t aFrameNum);
 
   /*
    * Set number of times to loop the image.
    * @note -1 means loop forever.
    */
-  void SetLoopCount(int32_t aLoopCount);
-  int32_t GetLoopCount() const;
-
-  void Discard();
-
-  void SetSize(nsIntSize aSize) { mSize = aSize; }
+  void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
+  int32_t LoopCount() const { return mLoopCount; }
 
   size_t SizeOfDecoded(gfxMemoryLocation aLocation,
                        MallocSizeOf aMallocSizeOf) const;
 
   void ResetAnimation();
 
   // "Blend" method indicates how the current image is combined with the
   // previous image.
@@ -102,43 +92,16 @@ public:
   // A hint as to whether an individual frame is entirely opaque, or requires
   // alpha blending.
   enum FrameAlpha {
     kFrameHasAlpha,
     kFrameOpaque
   };
 
 private:
-
-  struct Anim
-  {
-    //! Track the last composited frame for Optimizations (See DoComposite code)
-    int32_t lastCompositedFrameIndex;
-
-    /** For managing blending of frames
-     *
-     * Some animations will use the compositingFrame to composite images
-     * and just hand this back to the caller when it is time to draw the frame.
-     * NOTE: When clearing compositingFrame, remember to set
-     *       lastCompositedFrameIndex to -1.  Code assume that if
-     *       lastCompositedFrameIndex >= 0 then compositingFrame exists.
-     */
-    RawAccessFrameRef compositingFrame;
-
-    /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
-     *
-     * The Previous Frame (all frames composited up to the current) needs to be
-     * stored in cases where the image specifies it wants the last frame back
-     * when it's done with the current frame.
-     */
-    RawAccessFrameRef compositingPrevFrame;
-
-    Anim() : lastCompositedFrameIndex(-1) { }
-  };
-
   /** Clears an area of <aFrame> with transparent black.
    *
    * @param aFrameData Target Frame data
    * @param aFrameRect The rectangle of the data pointed ot by aFrameData
    *
    * @note Does also clears the transparency mask
    */
   static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect);
@@ -169,19 +132,39 @@ private:
    */
   static nsresult DrawFrameTo(const uint8_t* aSrcData,
                               const nsIntRect& aSrcRect,
                               uint32_t aSrcPaletteLength, bool aSrcHasAlpha,
                               uint8_t* aDstPixels, const nsIntRect& aDstRect,
                               FrameBlendMethod aBlendMethod);
 
 private: // data
-  //! All the frames of the image
-  nsTArray<RawAccessFrameRef> mFrames;
-  nsIntSize mSize;
-  Anim* mAnim;
+  ImageKey mImageKey;
+  gfx::IntSize mSize;
+
+  //! Track the last composited frame for Optimizations (See DoComposite code)
+  int32_t mLastCompositedFrameIndex;
+
+  /** For managing blending of frames
+   *
+   * Some animations will use the compositingFrame to composite images
+   * and just hand this back to the caller when it is time to draw the frame.
+   * NOTE: When clearing compositingFrame, remember to set
+   *       lastCompositedFrameIndex to -1.  Code assume that if
+   *       lastCompositedFrameIndex >= 0 then compositingFrame exists.
+   */
+  RawAccessFrameRef mCompositingFrame;
+
+  /** the previous composited frame, for DISPOSE_RESTORE_PREVIOUS
+   *
+   * The Previous Frame (all frames composited up to the current) needs to be
+   * stored in cases where the image specifies it wants the last frame back
+   * when it's done with the current frame.
+   */
+  RawAccessFrameRef mCompositingPrevFrame;
+
   int32_t mLoopCount;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif /* mozilla_imagelib_FrameBlender_h_ */
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -184,17 +184,17 @@ public:
 
     // Everything worked, so commit to these objects and mark ourselves ready.
     mDstRef = Move(tentativeDstRef);
     mState = eReady;
 
     // Insert the new surface into the cache immediately. We need to do this so
     // that we won't start multiple scaling jobs for the same size.
     SurfaceCache::Insert(mDstRef.get(), ImageKey(mImage.get()),
-                         RasterSurfaceKey(mDstSize.ToIntSize(), mImageFlags),
+                         RasterSurfaceKey(mDstSize.ToIntSize(), mImageFlags, 0),
                          Lifetime::Transient);
 
     return true;
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     if (mState == eReady) {
@@ -238,17 +238,17 @@ public:
       mDstRef.reset();
     } else if (mState == eFinishWithError) {
       MOZ_ASSERT(NS_IsMainThread());
       NS_WARNING("HQ scaling failed");
 
       // Remove the frame from the cache since we know we don't need it.
       SurfaceCache::RemoveSurface(ImageKey(mImage.get()),
                                   RasterSurfaceKey(mDstSize.ToIntSize(),
-                                                   mImageFlags));
+                                                   mImageFlags, 0));
 
       // Release everything we're holding, too.
       mSrcRef.reset();
       mDstRef.reset();
     } else {
       // mState must be eNew, which is invalid in Run().
       MOZ_ASSERT(false, "Need to call Init() before dispatching");
     }
@@ -290,25 +290,25 @@ RasterImage::RasterImage(ProgressTracker
   mDecodeCount(0),
   mRequestedSampleSize(0),
 #ifdef DEBUG
   mFramesNotified(0),
 #endif
   mDecodingMonitor("RasterImage Decoding Monitor"),
   mDecoder(nullptr),
   mDecodeStatus(DecodeStatus::INACTIVE),
+  mFrameCount(0),
   mNotifyProgress(NoProgress),
   mNotifying(false),
   mHasSize(false),
   mDecodeOnDraw(false),
   mTransient(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
-  mHasFirstFrame(false),
   mHasBeenDecoded(false),
   mPendingAnimation(false),
   mAnimationFinished(false),
   mWantFullDecode(false),
   mPendingError(false)
 {
   mProgressTrackerInit = new ProgressTrackerInit(this, aProgressTracker);
 
@@ -540,73 +540,78 @@ RasterImage::GetType()
   return imgIContainer::TYPE_RASTER;
 }
 
 DrawableFrameRef
 RasterImage::LookupFrameInternal(uint32_t aFrameNum,
                                  const nsIntSize& aSize,
                                  uint32_t aFlags)
 {
-  if (mAnim) {
+  if (!mAnim) {
+    NS_ASSERTION(aFrameNum == 0,
+                 "Don't ask for a frame > 0 if we're not animated!");
+    aFrameNum = 0;
+  }
+
+  if (mAnim && aFrameNum > 0) {
     MOZ_ASSERT(mFrameBlender, "mAnim but no mFrameBlender?");
-    nsRefPtr<imgFrame> frame = mFrameBlender->GetFrame(aFrameNum);
-    return frame->DrawableRef();
+    MOZ_ASSERT(DecodeFlags(aFlags) == DECODE_FLAGS_DEFAULT,
+               "Can't composite frames with non-default decode flags");
+    return mFrameBlender->GetCompositedFrame(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)));
+                                               DecodeFlags(aFlags),
+                                               aFrameNum));
 }
 
 DrawableFrameRef
 RasterImage::LookupFrame(uint32_t aFrameNum,
                          const nsIntSize& aSize,
                          uint32_t aFlags,
                          bool aShouldSyncNotify /* = true */)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   DrawableFrameRef ref = LookupFrameInternal(aFrameNum, aSize, aFlags);
 
-  if (!ref && IsOpaque()) {
+  if (!ref && IsOpaque() && aFrameNum == 0) {
     // We can use non-premultiplied alpha frames when premultipled alpha is
     // requested, or vice versa, if this image is opaque. Try again with the bit
     // toggled.
     ref = LookupFrameInternal(aFrameNum, aSize,
                               aFlags ^ FLAG_DECODE_NO_PREMULTIPLY_ALPHA);
   }
 
   if (!ref) {
     // The OS threw this frame away. We need to redecode if we can.
     MOZ_ASSERT(!mAnim, "Animated frames should be locked");
 
     // Update our state so the decoder knows what to do.
     mFrameDecodeFlags = aFlags & DECODE_FLAGS_MASK;
     mDecoded = false;
-    mHasFirstFrame = false;
+    mFrameCount = 0;
     WantDecodedFrames(aFlags, aShouldSyncNotify);
 
     // 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();
     }
   }
 
-  // We will return a paletted frame if it's not marked as compositing failed
-  // so we can catch crashes for reasons we haven't investigated.
   if (ref->GetCompositingFailed()) {
     return DrawableFrameRef();
   }
 
+  MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "Should not have paletted frame");
+
   return ref;
 }
 
 uint32_t
 RasterImage::GetCurrentFrameIndex() const
 {
   if (mAnim) {
     return mAnim->GetCurrentAnimationFrameIndex();
@@ -653,25 +658,16 @@ RasterImage::IsOpaque()
 void
 RasterImage::OnSurfaceDiscarded()
 {
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 }
 
-uint32_t
-RasterImage::GetNumFrames() const
-{
-  if (mFrameBlender) {
-    return mFrameBlender->GetNumFrames();
-  }
-  return mHasFirstFrame ? 1 : 0;
-}
-
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP
 RasterImage::GetAnimated(bool *aAnimated)
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
@@ -971,82 +967,69 @@ RasterImage::InternalAddFrame(uint32_t a
   }
   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;
+  bool succeeded =
+    SurfaceCache::Insert(frame, ImageKey(this),
+                         RasterSurfaceKey(mSize.ToIntSize(),
+                                          aDecodeFlags,
+                                          aFrameNum),
+                         Lifetime::Persistent);
+  if (!succeeded) {
+    return RawAccessFrameRef();
   }
 
-  if (GetNumFrames() == 1) {
+  if (aFrameNum == 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);
+    mFrameBlender.emplace(ImageKey(this), mSize.ToIntSize());
+    mAnim = MakeUnique<FrameAnimator>(this, *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
     // 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();
 
-    // 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));
+    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).
     int32_t frameDisposalMethod = aPreviousFrame->GetFrameDisposalMethod();
     if (frameDisposalMethod == FrameBlender::kDisposeClear ||
         frameDisposalMethod == FrameBlender::kDisposeRestorePrevious) {
       mAnim->SetFirstFrameRefreshArea(aPreviousFrame->GetRect());
     }
 
     if (mPendingAnimation && ShouldAnimate()) {
       StartAnimation();
     }
   }
 
-  // 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());
-
-  MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender by now");
-  mFrameBlender->InsertFrame(aFrameNum, frame->RawAccessRef());
-
+  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());
+  }
+
+  mFrameCount++;
   return ref;
 }
 
 nsresult
 RasterImage::SetSize(int32_t aWidth, int32_t aHeight, Orientation aOrientation)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDecodingMonitor.AssertCurrentThreadIn();
@@ -1106,35 +1089,34 @@ RasterImage::EnsureFrame(uint32_t aFrame
                             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(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();
   }
 
   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;
+                              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)
@@ -1557,17 +1539,17 @@ RasterImage::Discard()
   int old_frame_count = GetNumFrames();
 
   // Delete all the decoded frames
   mFrameBlender.reset();
   SurfaceCache::RemoveImage(ImageKey(this));
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
-  mHasFirstFrame = false;
+  mFrameCount = 0;
 
   // Notify that we discarded
   if (mProgressTracker) {
     mProgressTracker->OnDiscard();
   }
 
   mDecodeStatus = DecodeStatus::INACTIVE;
 
@@ -2130,17 +2112,18 @@ RasterImage::DrawWithPreDownscaleIfNeede
                                           uint32_t aFlags)
 {
   DrawableFrameRef frameRef;
 
   if (CanScale(aFilter, aSize, aFlags)) {
     frameRef =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(aSize.ToIntSize(),
-                                            DecodeFlags(aFlags)));
+                                            DecodeFlags(aFlags),
+                                            0));
     if (!frameRef) {
       // We either didn't have a matching scaled frame or the OS threw it away.
       // Request a new one so we'll be ready next time. For now, we'll fall back
       // to aFrameRef below.
       RequestScale(aFrameRef.get(), aFlags, aSize);
     }
     if (frameRef && !frameRef->ImageComplete()) {
       frameRef.reset();  // We're still scaling, so we can't use this yet.
@@ -2275,17 +2258,17 @@ RasterImage::UnlockImage()
                     "Calling UnlockImage with mLockCount == 0!");
   if (mLockCount == 0)
     return NS_ERROR_ABORT;
 
   // Decrement our lock count
   mLockCount--;
 
   // Unlock this image's surfaces in the SurfaceCache.
-  if (mLockCount == 0) {
+  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 && !mAnim) {
@@ -2659,17 +2642,18 @@ RasterImage::OptimalImageSizeForDest(con
   }
 
   nsIntSize destSize(ceil(aDest.width), ceil(aDest.height));
 
   if (CanScale(aFilter, destSize, aFlags)) {
     DrawableFrameRef frameRef =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(destSize.ToIntSize(),
-                                            DecodeFlags(aFlags)));
+                                            DecodeFlags(aFlags),
+                                            0));
 
     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),
                              mSize, aFlags);
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -164,26 +164,27 @@ public:
 
   // Raster-specific methods
   static NS_METHOD WriteToRasterImage(nsIInputStream* aIn, void* aClosure,
                                       const char* aFromRawSegment,
                                       uint32_t aToOffset, uint32_t aCount,
                                       uint32_t* aWriteCount);
 
   /* The total number of frames in this image. */
-  uint32_t GetNumFrames() const;
+  uint32_t GetNumFrames() const { return mFrameCount; }
 
   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 */
+
   /** 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
@@ -391,31 +392,33 @@ private: // data
 
   FallibleTArray<char>       mSourceData;
 
   // Decoder and friends
   nsRefPtr<Decoder>          mDecoder;
   DecodeStatus               mDecodeStatus;
   // END LOCKED MEMBER VARIABLES
 
+  // The number of frames this image has.
+  uint32_t                   mFrameCount;
+
   // Notification state. Used to avoid recursive notifications.
   Progress                   mNotifyProgress;
   nsIntRect                  mNotifyInvalidRect;
   bool                       mNotifying:1;
 
   // Boolean flags (clustered together to conserve space):
   bool                       mHasSize:1;       // Has SetSize() been called?
   bool                       mDecodeOnDraw:1;  // Decoding on draw?
   bool                       mTransient:1;     // Is the image short-lived?
   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
--- a/image/src/SurfaceCache.h
+++ b/image/src/SurfaceCache.h
@@ -74,41 +74,39 @@ private:
     , mAnimationTime(aAnimationTime)
     , mFlags(aFlags)
   { }
 
   static uint32_t HashSIC(const SVGImageContext& aSIC) {
     return aSIC.Hash();
   }
 
-  friend SurfaceKey RasterSurfaceKey(const IntSize&, const uint32_t);
+  friend SurfaceKey RasterSurfaceKey(const IntSize&, uint32_t, uint32_t);
   friend SurfaceKey VectorSurfaceKey(const IntSize&,
                                      const Maybe<SVGImageContext>&,
-                                     const float);
+                                     float);
 
   IntSize                mSize;
   Maybe<SVGImageContext> mSVGContext;
   float                  mAnimationTime;
   uint32_t               mFlags;
 };
 
 inline SurfaceKey
 RasterSurfaceKey(const gfx::IntSize& aSize,
-                 const uint32_t aFlags)
+                 uint32_t aFlags,
+                 uint32_t aFrameNum)
 {
-  // We don't care about aAnimationTime for RasterImage because it's not
-  // currently possible to store anything but the first frame in the
-  // SurfaceCache.
-  return SurfaceKey(aSize, Nothing(), 0.0f, aFlags);
+  return SurfaceKey(aSize, Nothing(), float(aFrameNum), aFlags);
 }
 
 inline SurfaceKey
 VectorSurfaceKey(const gfx::IntSize& aSize,
                  const Maybe<SVGImageContext>& aSVGContext,
-                 const float aAnimationTime)
+                 float aAnimationTime)
 {
   // We don't care about aFlags for VectorImage because none of the flags we
   // have right now influence VectorImage's rendering. If we add a new flag that
   // *does* affect how a VectorImage renders, we'll have to change this.
   return SurfaceKey(aSize, aSVGContext, aAnimationTime, 0);
 }
 
 MOZ_BEGIN_ENUM_CLASS(Lifetime, uint8_t)
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -420,16 +420,25 @@ imgFrame::DrawableRef()
 }
 
 RawAccessFrameRef
 imgFrame::RawAccessRef()
 {
   return RawAccessFrameRef(this);
 }
 
+void
+imgFrame::SetRawAccessOnly()
+{
+  MOZ_ASSERT(mLockCount > 0, "Must hold a RawAccessFrameRef");
+  // Lock our data and throw away the key.
+  LockImageData();
+}
+
+
 imgFrame::SurfaceWithFormat
 imgFrame::SurfaceForDrawing(bool               aDoPadding,
                             bool               aDoPartialDecode,
                             bool               aDoTile,
                             gfxContext*        aContext,
                             const nsIntMargin& aPadding,
                             gfxRect&           aImageRect,
                             ImageRegion&       aRegion,
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -72,16 +72,28 @@ public:
                             const nsIntSize& aSize,
                             const SurfaceFormat aFormat,
                             GraphicsFilter aFilter,
                             uint32_t aImageFlags);
 
   DrawableFrameRef DrawableRef();
   RawAccessFrameRef RawAccessRef();
 
+  /**
+   * Make this imgFrame permanently available for raw access.
+   *
+   * This is irrevocable, and should be avoided whenever possible, since it
+   * prevents this imgFrame from being optimized and makes it impossible for its
+   * volatile buffer to be freed.
+   *
+   * It is an error to call this without already holding a RawAccessFrameRef to
+   * this imgFrame.
+   */
+  void SetRawAccessOnly();
+
   bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
             GraphicsFilter aFilter, uint32_t aImageFlags);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
 
   IntSize GetImageSize() { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }