Back out bug 1057904, bug 1060869, bug 923302 for bustage
authorSeth Fowler <seth@mozilla.com>
Wed, 01 Oct 2014 17:33:49 -0700
changeset 231515 e388678a921128420bbaf1342aabba7f636e08ff
parent 231514 d3cf7a1879ecc1d246f9c066abef316518c113b2
child 231516 7787a8038654ebe506d8b9c37ae7991d0d1ddc75
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1057904, 1060869, 923302
milestone35.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
Back out bug 1057904, bug 1060869, bug 923302 for bustage
image/decoders/nsICODecoder.cpp
image/decoders/nsICODecoder.h
image/src/Decoder.cpp
image/src/Decoder.h
image/src/FrameBlender.cpp
image/src/FrameBlender.h
image/src/FrameSequence.cpp
image/src/FrameSequence.h
image/src/ImageMetadata.cpp
image/src/ImageMetadata.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/SurfaceCache.cpp
image/src/SurfaceCache.h
image/src/VectorImage.cpp
image/src/imgFrame.cpp
image/src/imgFrame.h
image/src/moz.build
--- a/image/decoders/nsICODecoder.cpp
+++ b/image/decoders/nsICODecoder.cpp
@@ -4,17 +4,16 @@
  * 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
@@ -332,17 +331,17 @@ nsICODecoder::WriteInternal(const char* 
     mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes, 
                      PNGSIGNATURESIZE);
     if (mIsPNG) {
       mContainedDecoder = new nsPNGDecoder(mImage);
       mContainedDecoder->SetObserver(mObserver);
       mContainedDecoder->SetSizeDecode(IsSizeDecode());
       mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                            mColormap, mColormapSize,
-                                           Move(mRefForContainedDecoder));
+                                           mCurrentFrame);
       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) {
@@ -409,17 +408,17 @@ nsICODecoder::WriteInternal(const char* 
     // bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
     nsBMPDecoder *bmpDecoder = new nsBMPDecoder(mImage);
     mContainedDecoder = bmpDecoder;
     bmpDecoder->SetUseAlphaData(true);
     mContainedDecoder->SetObserver(mObserver);
     mContainedDecoder->SetSizeDecode(IsSizeDecode());
     mContainedDecoder->InitSharedDecoder(mImageData, mImageDataLength,
                                          mColormap, mColormapSize,
-                                         Move(mRefForContainedDecoder));
+                                         mCurrentFrame);
 
     // 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;
@@ -609,25 +608,19 @@ nsICODecoder::NeedsNewFrame() const
   }
 
   return Decoder::NeedsNewFrame();
 }
 
 nsresult
 nsICODecoder::AllocateFrame()
 {
-  nsresult rv;
-
   if (mContainedDecoder) {
-    rv = mContainedDecoder->AllocateFrame();
-    mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
+    nsresult rv = mContainedDecoder->AllocateFrame();
+    mCurrentFrame = mContainedDecoder->GetCurrentFrame();
     return rv;
   }
 
-  // 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;
+  return Decoder::AllocateFrame();
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/decoders/nsICODecoder.h
+++ b/image/decoders/nsICODecoder.h
@@ -4,17 +4,16 @@
  * 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;
@@ -76,17 +75,16 @@ 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,16 +11,17 @@
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 
 namespace mozilla {
 namespace image {
 
 Decoder::Decoder(RasterImage &aImage)
   : mImage(aImage)
+  , mCurrentFrame(nullptr)
   , mImageData(nullptr)
   , mColormap(nullptr)
   , mDecodeFlags(0)
   , mDecodeDone(false)
   , mDataError(false)
   , mFrameCount(0)
   , mFailCode(NS_OK)
   , mNeedsNewFrame(false)
@@ -55,30 +56,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* aImageData, uint32_t aImageDataLength,
-                           uint32_t* aColormap, uint32_t aColormapSize,
-                           RawAccessFrameRef&& aFrameRef)
+Decoder::InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength,
+                           uint32_t* colormap, uint32_t colormapSize,
+                           imgFrame* currentFrame)
 {
   // No re-initializing
   NS_ABORT_IF_FALSE(!mInitialized, "Can't re-initialize a decoder!");
   NS_ABORT_IF_FALSE(mObserver, "Need an observer!");
 
-  mImageData = aImageData;
-  mImageDataLength = aImageDataLength;
-  mColormap = aColormap;
-  mColormapSize = aColormapSize;
-  mCurrentFrame = Move(aFrameRef);
-
+  mImageData = imageData;
+  mImageDataLength = imageDataLength;
+  mColormap = colormap;
+  mColormapSize = colormapSize;
+  mCurrentFrame = currentFrame;
   // We have all the frame data, so we've started the frame.
   if (!IsSizeDecode()) {
     PostFrameStart();
   }
 
   // Implementation-specific initialization
   InitInternal();
   mInitialized = true;
@@ -174,23 +174,21 @@ Decoder::Finish(RasterImage::eShutdownIn
       PostDecodeDone();
     } else {
       if (mObserver) {
         mObserver->OnStopDecode(NS_ERROR_FAILURE);
       }
     }
   }
 
-  // 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) {
-    MOZ_ASSERT(HasError() || mCurrentFrame, "Should have an error or a frame");
-    mImage.DecodingComplete(mCurrentFrame.get());
+    mImage.DecodingComplete();
   }
 }
 
 void
 Decoder::FinishSharedDecoder()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -200,40 +198,52 @@ 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());
+  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));
+  } else {
+    rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
+                            mNewFrameData.mOffsetY, mNewFrameData.mWidth,
+                            mNewFrameData.mHeight, mNewFrameData.mFormat,
+                            &mImageData, &mImageDataLength,
+                            getter_AddRefs(frame));
+  }
 
-  if (mCurrentFrame) {
-    // Gather the raw pointers the decoders will use.
-    mCurrentFrame->GetImageData(&mImageData, &mImageDataLength);
-    mCurrentFrame->GetPaletteData(&mColormap, &mColormapSize);
+  if (NS_SUCCEEDED(rv)) {
+    mCurrentFrame = frame;
+  } else {
+    mCurrentFrame = nullptr;
+  }
 
-    if (mNewFrameData.mFrameNum == mFrameCount) {
-      PostFrameStart();
-    }
-  } else {
+  // 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;
 
-  return mCurrentFrame ? NS_OK : NS_ERROR_FAILURE;
+  return rv;
 }
 
 void
 Decoder::FlushInvalidations()
 {
   NS_ABORT_IF_FALSE(!HasDecoderError(),
                     "Not allowed to make more decoder calls after error!");
 
@@ -313,18 +323,18 @@ Decoder::PostFrameStart()
 
   // Update our state to reflect the new frame
   mFrameCount++;
   mInFrame = true;
 
   // 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!");
+  NS_ABORT_IF_FALSE(mFrameCount == mImage.GetNumFrames(),
+                    "Decoder frame count doesn't match image's!");
 
   // Fire notifications
   if (mObserver) {
     mObserver->OnStartFrame();
   }
 }
 
 void
@@ -378,16 +388,17 @@ 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);
 
   if (mObserver) {
     mObserver->OnStopDecode(NS_OK);
   }
 }
 
 void
 Decoder::PostDataError()
@@ -414,16 +425,14 @@ 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,
-                               nsIntRect(x_offset, y_offset, width, height),
-                               format, palette_depth);
+  mNewFrameData = NewFrameData(framenum, 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
@@ -32,19 +32,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* aImageData, uint32_t aImageDataLength,
-                         uint32_t* aColormap, uint32_t aColormapSize,
-                         RawAccessFrameRef&& aFrameRef);
+  void InitSharedDecoder(uint8_t* imageData, uint32_t imageDataLength,
+                         uint32_t* colormap, uint32_t colormapSize,
+                         imgFrame* currentFrame);
 
   /**
    * Writes data to the decoder.
    *
    * @param aBuffer buffer containing the data to be written
    * @param aCount the number of bytes to write
    *
    * Any errors are reported by setting the appropriate state on the decoder.
@@ -155,27 +155,22 @@ public:
                     uint8_t palette_depth = 0);
 
   virtual bool NeedsNewFrame() const { return mNeedsNewFrame; }
 
   // 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()
+  already_AddRefed<imgFrame> GetCurrentFrame() const
   {
-    nsRefPtr<imgFrame> frame = mCurrentFrame.get();
+    nsRefPtr<imgFrame> frame = mCurrentFrame;
     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();
@@ -224,17 +219,17 @@ protected:
   void PostDataError();
   void PostDecoderError(nsresult aFailCode);
 
   /*
    * Member variables.
    *
    */
   RasterImage &mImage;
-  RawAccessFrameRef mCurrentFrame;
+  nsRefPtr<imgFrame> mCurrentFrame;
   RefPtr<imgDecoderObserver> mObserver;
   ImageMetadata mImageMetadata;
 
   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;
 
@@ -246,32 +241,38 @@ private:
   uint32_t mFrameCount; // Number of frames, including anything in-progress
 
   nsIntRect mInvalidRect; // Tracks an invalidation region in the current frame.
 
   nsresult mFailCode;
 
   struct NewFrameData
   {
-    NewFrameData() { }
+    NewFrameData()
+    {}
 
-    NewFrameData(uint32_t aFrameNum, const nsIntRect& aFrameRect,
-                 gfx::SurfaceFormat aFormat, uint8_t aPaletteDepth)
-      : mFrameNum(aFrameNum)
-      , mFrameRect(aFrameRect)
-      , mFormat(aFormat)
-      , mPaletteDepth(aPaletteDepth)
-    { }
-
+    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)
+    {}
     uint32_t mFrameNum;
-    nsIntRect mFrameRect;
+    uint32_t mOffsetX;
+    uint32_t mOffsetY;
+    uint32_t mWidth;
+    uint32_t mHeight;
     gfx::SurfaceFormat mFormat;
     uint8_t mPaletteDepth;
   };
-
   NewFrameData mNewFrameData;
   bool mNeedsNewFrame;
   bool mInitialized;
   bool mSizeDecode;
   bool mInFrame;
   bool mIsAnimated;
 };
 
--- a/image/src/FrameBlender.cpp
+++ b/image/src/FrameBlender.cpp
@@ -1,149 +1,186 @@
 /* -*- 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 "FrameBlender.h"
 
 #include "mozilla/MemoryReporting.h"
-#include "mozilla/Move.h"
 #include "MainThreadUtils.h"
 
 #include "pixman.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace image {
 
-FrameBlender::FrameBlender()
- : mAnim(nullptr)
+FrameBlender::FrameBlender(FrameSequence* aSequenceToUse /* = nullptr */)
+ : mFrames(aSequenceToUse)
+ , mAnim(nullptr)
  , mLoopCount(-1)
 {
+  if (!mFrames) {
+    mFrames = new FrameSequence();
+  }
 }
 
 FrameBlender::~FrameBlender()
 {
   delete mAnim;
 }
 
-already_AddRefed<imgFrame>
-FrameBlender::GetFrame(uint32_t aFrameNum)
+already_AddRefed<FrameSequence>
+FrameBlender::GetFrameSequence()
 {
-  if (mAnim && mAnim->lastCompositedFrameIndex == int32_t(aFrameNum)) {
-    nsRefPtr<imgFrame> frame = mAnim->compositingFrame.get();
-    return frame.forget();
-  }
-  return RawGetFrame(aFrameNum);
+  nsRefPtr<FrameSequence> seq(mFrames);
+  return seq.forget();
 }
 
 already_AddRefed<imgFrame>
-FrameBlender::RawGetFrame(uint32_t aFrameNum)
+FrameBlender::GetFrame(uint32_t framenum) const
 {
   if (!mAnim) {
-    NS_ASSERTION(aFrameNum == 0,
-                 "Don't ask for a frame > 0 if we're not animated!");
-    aFrameNum = 0;
+    NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
+    return mFrames->GetFrame(0).GetFrame();
+  }
+  if (mAnim->lastCompositedFrameIndex == int32_t(framenum)) {
+    return mAnim->compositingFrame.GetFrame();
   }
-  if (aFrameNum >= mFrames.Length()) {
-    return nullptr;
+  return mFrames->GetFrame(framenum).GetFrame();
+}
+
+already_AddRefed<imgFrame>
+FrameBlender::RawGetFrame(uint32_t framenum) const
+{
+  if (!mAnim) {
+    NS_ASSERTION(framenum == 0, "Don't ask for a frame > 0 if we're not animated!");
+    return mFrames->GetFrame(0).GetFrame();
   }
-  nsRefPtr<imgFrame> frame = mFrames[aFrameNum].get();
-  return frame.forget();
+  return mFrames->GetFrame(framenum).GetFrame();
 }
 
 uint32_t
 FrameBlender::GetNumFrames() const
 {
-  return mFrames.Length();
+  return mFrames->GetNumFrames();
 }
 
 int32_t
-FrameBlender::GetTimeoutForFrame(uint32_t aFrameNum)
+FrameBlender::GetTimeoutForFrame(uint32_t framenum) const
 {
-  nsRefPtr<imgFrame> frame = RawGetFrame(aFrameNum);
+  nsRefPtr<imgFrame> frame = RawGetFrame(framenum);
   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
   //   >50ms go correct speed
   // Opera 7 final/Win:
   //   10ms goes 100ms
   //   >10ms go correct speed
   // It seems that there are broken tools out there that set a 0ms or 10ms
   // timeout when they really want a "default" one.  So munge values in that
   // range.
-  if (timeout >= 0 && timeout <= 10 && mLoopCount != 0) {
+  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)
+FrameBlender::RemoveFrame(uint32_t framenum)
 {
-  MOZ_ASSERT(aFrameNum < GetNumFrames(), "Deleting invalid frame!");
-  mFrames.RemoveElementAt(aFrameNum);
+  NS_ABORT_IF_FALSE(framenum < GetNumFrames(), "Deleting invalid frame!");
+
+  mFrames->RemoveFrame(framenum);
 }
 
 void
 FrameBlender::ClearFrames()
 {
-  mFrames.Clear();
-  mFrames.Compact();
+  // Forget our old frame sequence, letting whoever else has it deal with it.
+  mFrames = new FrameSequence();
 }
 
 void
-FrameBlender::InsertFrame(uint32_t aFrameNum, RawAccessFrameRef&& aRef)
+FrameBlender::InsertFrame(uint32_t framenum, imgFrame* aFrame)
 {
-  MOZ_ASSERT(aRef, "Need a reference to a frame");
-  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Inserting invalid frame");
+  NS_ABORT_IF_FALSE(framenum <= GetNumFrames(), "Inserting invalid frame!");
+  mFrames->InsertFrame(framenum, aFrame);
+  if (GetNumFrames() > 1) {
+    EnsureAnimExists();
+  }
+}
 
-  mFrames.InsertElementAt(aFrameNum, Move(aRef));
-  if (GetNumFrames() == 2) {
-    MOZ_ASSERT(!mAnim, "Shouldn't have an animation context yet");
-    mAnim = new Anim();
+already_AddRefed<imgFrame>
+FrameBlender::SwapFrame(uint32_t framenum, imgFrame* aFrame)
+{
+  NS_ABORT_IF_FALSE(framenum < GetNumFrames(), "Swapping invalid frame!");
+
+  nsRefPtr<imgFrame> ret;
+
+  // Steal the imgFrame from wherever it's currently stored
+  if (mAnim && mAnim->lastCompositedFrameIndex == int32_t(framenum)) {
+    ret = mAnim->compositingFrame.Forget();
+    mAnim->lastCompositedFrameIndex = -1;
+    nsRefPtr<imgFrame> toDelete(mFrames->SwapFrame(framenum, aFrame));
+  } else {
+    ret = mFrames->SwapFrame(framenum, aFrame);
   }
 
-  MOZ_ASSERT(GetNumFrames() < 2 || mAnim,
-             "If we're animated we should have an animation context now");
+  return ret.forget();
+}
+
+void
+FrameBlender::EnsureAnimExists()
+{
+  if (!mAnim) {
+    // Create the animation context
+    mAnim = new Anim();
+
+    // We should only get into this code path directly after we've created our
+    // second frame (hence we know we're animated).
+    MOZ_ASSERT(GetNumFrames() == 2);
+  }
 }
 
 //******************************************************************************
 // 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 = GetFrame(aPrevFrameIndex);
-  nsRefPtr<imgFrame> nextFrame = GetFrame(aNextFrameIndex);
+  if (!aDirtyRect) {
+    return false;
+  }
 
-  MOZ_ASSERT(prevFrame && nextFrame, "Should have frames here");
+  const FrameDataPair& prevFrame = mFrames->GetFrame(aPrevFrameIndex);
+  const FrameDataPair& nextFrame = mFrames->GetFrame(aNextFrameIndex);
+  if (!prevFrame.HasFrameData() || !nextFrame.HasFrameData()) {
+    return false;
+  }
 
   int32_t prevFrameDisposalMethod = prevFrame->GetFrameDisposalMethod();
   if (prevFrameDisposalMethod == FrameBlender::kDisposeRestorePrevious &&
       !mAnim->compositingPrevFrame)
     prevFrameDisposalMethod = FrameBlender::kDisposeClear;
 
   nsIntRect prevFrameRect = prevFrame->GetRect();
   bool isFullPrevFrame = (prevFrameRect.x == 0 && prevFrameRect.y == 0 &&
@@ -218,23 +255,24 @@ FrameBlender::DoBlend(nsIntRect* aDirtyR
   if (mAnim->lastCompositedFrameIndex == int32_t(aNextFrameIndex)) {
     return true;
   }
 
   bool needToBlankComposite = false;
 
   // Create the Compositing Frame
   if (!mAnim->compositingFrame) {
-    nsRefPtr<imgFrame> newFrame = new imgFrame;
-    nsresult rv = newFrame->InitForDecoder(mSize, SurfaceFormat::B8G8R8A8);
+    mAnim->compositingFrame.SetFrame(new imgFrame());
+    nsresult rv =
+      mAnim->compositingFrame->InitForDecoder(mSize, SurfaceFormat::B8G8R8A8);
     if (NS_FAILED(rv)) {
-      mAnim->compositingFrame.reset();
+      mAnim->compositingFrame.SetFrame(nullptr);
       return false;
     }
-    mAnim->compositingFrame = newFrame->RawAccessRef();
+    mAnim->compositingFrame.LockAndGetData();
     needToBlankComposite = true;
   } else if (int32_t(aNextFrameIndex) != mAnim->lastCompositedFrameIndex+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;
   }
 
@@ -267,117 +305,119 @@ 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(),
+          ClearFrame(mAnim->compositingFrame.GetFrameData(),
                      mAnim->compositingFrame->GetRect());
         } else {
           // Only blank out previous frame area (both color & Mask/Alpha)
-          ClearFrame(mAnim->compositingFrame->GetRawData(),
+          ClearFrame(mAnim->compositingFrame.GetFrameData(),
                      mAnim->compositingFrame->GetRect(),
                      prevFrameRect);
         }
         break;
 
       case FrameBlender::kDisposeClearAll:
-        ClearFrame(mAnim->compositingFrame->GetRawData(),
+        ClearFrame(mAnim->compositingFrame.GetFrameData(),
                    mAnim->compositingFrame->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(),
+          CopyFrameImage(mAnim->compositingPrevFrame.GetFrameData(),
                          mAnim->compositingPrevFrame->GetRect(),
-                         mAnim->compositingFrame->GetRawData(),
+                         mAnim->compositingFrame.GetFrameData(),
                          mAnim->compositingFrame->GetRect());
 
           // destroy only if we don't need it for this frame's disposal
           if (nextFrameDisposalMethod != FrameBlender::kDisposeRestorePrevious)
-            mAnim->compositingPrevFrame.reset();
+            mAnim->compositingPrevFrame.SetFrame(nullptr);
         } else {
-          ClearFrame(mAnim->compositingFrame->GetRawData(),
+          ClearFrame(mAnim->compositingFrame.GetFrameData(),
                      mAnim->compositingFrame->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 (isFullPrevFrame && !prevFrame->GetIsPaletted()) {
             // Just copy the bits
-            CopyFrameImage(prevFrame->GetRawData(),
+            CopyFrameImage(prevFrame.GetFrameData(),
                            prevFrame->GetRect(),
-                           mAnim->compositingFrame->GetRawData(),
+                           mAnim->compositingFrame.GetFrameData(),
                            mAnim->compositingFrame->GetRect());
           } else {
             if (needToBlankComposite) {
               // Only blank composite when prev is transparent or not full.
               if (prevFrame->GetHasAlpha() || !isFullPrevFrame) {
-                ClearFrame(mAnim->compositingFrame->GetRawData(),
+                ClearFrame(mAnim->compositingFrame.GetFrameData(),
                            mAnim->compositingFrame->GetRect());
               }
             }
-            DrawFrameTo(prevFrame->GetRawData(), prevFrameRect,
+            DrawFrameTo(prevFrame.GetFrameData(), prevFrameRect,
                         prevFrame->PaletteDataLength(),
                         prevFrame->GetHasAlpha(),
-                        mAnim->compositingFrame->GetRawData(),
+                        mAnim->compositingFrame.GetFrameData(),
                         mAnim->compositingFrame->GetRect(),
                         FrameBlendMethod(prevFrame->GetBlendMethod()));
           }
         }
     }
   } else if (needToBlankComposite) {
     // If we just created the composite, it could have anything in it's
     // buffers. Clear them
-    ClearFrame(mAnim->compositingFrame->GetRawData(),
+    ClearFrame(mAnim->compositingFrame.GetFrameData(),
                mAnim->compositingFrame->GetRect());
   }
 
   // Check if the frame we are composing wants the previous image restored afer
   // 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) {
-      nsRefPtr<imgFrame> newFrame = new imgFrame;
-      nsresult rv = newFrame->InitForDecoder(mSize, SurfaceFormat::B8G8R8A8);
+      mAnim->compositingPrevFrame.SetFrame(new imgFrame());
+      nsresult rv =
+        mAnim->compositingPrevFrame->InitForDecoder(mSize,
+                                                    SurfaceFormat::B8G8R8A8);
       if (NS_FAILED(rv)) {
-        mAnim->compositingPrevFrame.reset();
+        mAnim->compositingPrevFrame.SetFrame(nullptr);
         return false;
       }
 
-      mAnim->compositingPrevFrame = newFrame->RawAccessRef();
+      mAnim->compositingPrevFrame.LockAndGetData();
     }
 
-    CopyFrameImage(mAnim->compositingFrame->GetRawData(),
+    CopyFrameImage(mAnim->compositingFrame.GetFrameData(),
                    mAnim->compositingFrame->GetRect(),
-                   mAnim->compositingPrevFrame->GetRawData(),
+                   mAnim->compositingPrevFrame.GetFrameData(),
                    mAnim->compositingPrevFrame->GetRect());
   }
 
   // blit next frame into it's correct spot
-  DrawFrameTo(nextFrame->GetRawData(), nextFrameRect,
+  DrawFrameTo(nextFrame.GetFrameData(), nextFrameRect,
               nextFrame->PaletteDataLength(),
               nextFrame->GetHasAlpha(),
-              mAnim->compositingFrame->GetRawData(),
+              mAnim->compositingFrame.GetFrameData(),
               mAnim->compositingFrame->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);
 
@@ -545,33 +585,24 @@ FrameBlender::Discard()
   // Delete all the decoded frames, then clear the array.
   ClearFrames();
 }
 
 size_t
 FrameBlender::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                       MallocSizeOf aMallocSizeOf) const
 {
-  size_t n = 0;
-
-  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
-    n += mFrames[i]->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation,
-                                                                   aMallocSizeOf);
-  }
+  size_t n = mFrames->SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
 
   if (mAnim) {
     if (mAnim->compositingFrame) {
-      n += mAnim->compositingFrame
-                ->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation,
-                                                                aMallocSizeOf);
+      n += mAnim->compositingFrame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
     }
     if (mAnim->compositingPrevFrame) {
-      n += mAnim->compositingPrevFrame
-                ->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation,
-                                                                aMallocSizeOf);
+      n += mAnim->compositingPrevFrame->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
     }
   }
 
   return n;
 }
 
 void
 FrameBlender::ResetAnimation()
--- a/image/src/FrameBlender.h
+++ b/image/src/FrameBlender.h
@@ -4,67 +4,72 @@
  * 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 "gfxTypes.h"
-#include "imgFrame.h"
+#include "FrameSequence.h"
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace image {
 
+class imgFrame;
+
 /**
  * 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.
    */
-  explicit FrameBlender();
+  explicit FrameBlender(FrameSequence* aSequenceToUse = nullptr);
   ~FrameBlender();
 
   bool DoBlend(nsIntRect* aDirtyRect, uint32_t aPrevFrameIndex,
                uint32_t aNextFrameIndex);
 
+  already_AddRefed<FrameSequence> GetFrameSequence();
+
   /**
    * Get the @aIndex-th frame, including (if applicable) any results of
    * blending.
    */
-  already_AddRefed<imgFrame> GetFrame(uint32_t aIndex);
+  already_AddRefed<imgFrame> GetFrame(uint32_t aIndex) const;
 
   /**
    * Get the @aIndex-th frame in the frame index, ignoring results of blending.
    */
-  already_AddRefed<imgFrame> RawGetFrame(uint32_t aIndex);
+  already_AddRefed<imgFrame> RawGetFrame(uint32_t aIndex) const;
 
-  void InsertFrame(uint32_t aFrameNum, RawAccessFrameRef&& aRef);
-  void RemoveFrame(uint32_t aFrameNum);
+  void InsertFrame(uint32_t framenum, imgFrame* aFrame);
+  void RemoveFrame(uint32_t framenum);
+  already_AddRefed<imgFrame> SwapFrame(uint32_t framenum, imgFrame* aFrame);
   void ClearFrames();
 
   /* The total number of frames in this image. */
   uint32_t GetNumFrames() const;
 
   /*
    * 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);
+  int32_t GetTimeoutForFrame(uint32_t framenum) const;
 
   /*
    * Set number of times to loop the image.
    * @note -1 means loop forever.
    */
   void SetLoopCount(int32_t aLoopCount);
   int32_t GetLoopCount() const;
 
@@ -115,31 +120,33 @@ private:
     /** 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;
+    FrameDataPair 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;
+    FrameDataPair compositingPrevFrame;
 
     Anim() :
       lastCompositedFrameIndex(-1)
     {}
   };
 
+  void EnsureAnimExists();
+
   /** 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 transparancy mask
    */
   static void ClearFrame(uint8_t* aFrameData, const nsIntRect& aFrameRect);
@@ -168,17 +175,17 @@ 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;
+  nsRefPtr<FrameSequence> mFrames;
   nsIntSize mSize;
   Anim* mAnim;
   int32_t mLoopCount;
 };
 
 } // namespace image
 } // namespace mozilla
 
new file mode 100644
--- /dev/null
+++ b/image/src/FrameSequence.cpp
@@ -0,0 +1,103 @@
+/* -*- 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 "FrameSequence.h"
+
+namespace mozilla {
+namespace image {
+
+FrameSequence::~FrameSequence()
+{
+  ClearFrames();
+}
+
+const FrameDataPair&
+FrameSequence::GetFrame(uint32_t framenum) const
+{
+  if (framenum >= mFrames.Length()) {
+    static FrameDataPair empty;
+    return empty;
+  }
+
+  return mFrames[framenum];
+}
+
+uint32_t
+FrameSequence::GetNumFrames() const
+{
+  return mFrames.Length();
+}
+
+void
+FrameSequence::RemoveFrame(uint32_t framenum)
+{
+  NS_ABORT_IF_FALSE(framenum < mFrames.Length(), "Deleting invalid frame!");
+
+  mFrames.RemoveElementAt(framenum);
+}
+
+void
+FrameSequence::ClearFrames()
+{
+  // Since FrameDataPair holds an nsAutoPtr to its frame, clearing the mFrames
+  // array also deletes all the frames.
+  mFrames.Clear();
+}
+
+void
+FrameSequence::InsertFrame(uint32_t framenum, imgFrame* aFrame)
+{
+  NS_ABORT_IF_FALSE(framenum <= mFrames.Length(), "Inserting invalid frame!");
+  mFrames.InsertElementAt(framenum, aFrame);
+  if (GetNumFrames() > 1) {
+    // If we're creating our second element, we now know we're animated.
+    // Therefore, we need to lock the first frame too.
+    if (GetNumFrames() == 2) {
+      mFrames[0].LockAndGetData();
+    }
+
+    // Whenever we have more than one frame, we always lock *all* our frames
+    // so we have all the image data pointers.
+    mFrames[framenum].LockAndGetData();
+  }
+}
+
+already_AddRefed<imgFrame>
+FrameSequence::SwapFrame(uint32_t framenum, imgFrame* aFrame)
+{
+  NS_ABORT_IF_FALSE(framenum < mFrames.Length(), "Swapping invalid frame!");
+
+  FrameDataPair ret;
+
+  // Steal the imgFrame.
+  if (framenum < mFrames.Length()) {
+    ret = mFrames[framenum];
+  }
+
+  if (aFrame) {
+    mFrames.ReplaceElementAt(framenum, aFrame);
+  } else {
+    mFrames.RemoveElementAt(framenum);
+  }
+
+  return ret.GetFrame();
+}
+
+size_t
+FrameSequence::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
+                                                       MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = 0;
+  for (uint32_t i = 0; i < mFrames.Length(); ++i) {
+    FrameDataPair fdp = mFrames.SafeElementAt(i, FrameDataPair());
+    NS_ABORT_IF_FALSE(fdp, "Null frame in frame array!");
+    n += fdp->SizeOfExcludingThisWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
+  }
+
+  return n;
+}
+
+} // namespace image
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/image/src/FrameSequence.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_imagelib_FrameSequence_h_
+#define mozilla_imagelib_FrameSequence_h_
+
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Move.h"
+#include "gfxTypes.h"
+#include "imgFrame.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * FrameDataPair is a slightly-smart tuple of (frame, raw frame data) where the
+ * raw frame data is allowed to be (and is, initially) null.
+ *
+ * If you call LockAndGetData, you will be able to call GetFrameData() on that
+ * instance, and when the FrameDataPair is destructed, the imgFrame lock will
+ * be unlocked.
+ */
+class FrameDataPair
+{
+public:
+  explicit FrameDataPair(imgFrame* frame)
+    : mFrame(frame)
+    , mFrameData(nullptr)
+  {}
+
+  FrameDataPair()
+    : mFrameData(nullptr)
+  {}
+
+  FrameDataPair(const FrameDataPair& aOther)
+    : mFrame(aOther.mFrame)
+    , mFrameData(nullptr)
+  {}
+
+  FrameDataPair(FrameDataPair&& aOther)
+    : mFrame(Move(aOther.mFrame))
+    , mFrameData(aOther.mFrameData)
+  {
+    aOther.mFrameData = nullptr;
+  }
+
+  ~FrameDataPair()
+  {
+    if (mFrameData) {
+      mFrame->UnlockImageData();
+    }
+  }
+
+  FrameDataPair& operator=(const FrameDataPair& aOther)
+  {
+    if (&aOther != this) {
+      mFrame = aOther.mFrame;
+      mFrameData = nullptr;
+    }
+    return *this;
+  }
+
+  FrameDataPair& operator=(FrameDataPair&& aOther)
+  {
+    MOZ_ASSERT(&aOther != this, "Moving to self");
+    mFrame = Move(aOther.mFrame);
+    mFrameData = aOther.mFrameData;
+    aOther.mFrameData = nullptr;
+    return *this;
+  }
+
+  // Lock the frame and store its mFrameData. The frame will be unlocked (and
+  // deleted) when this FrameDataPair is deleted.
+  void LockAndGetData()
+  {
+    if (mFrame) {
+      if (NS_SUCCEEDED(mFrame->LockImageData())) {
+        if (mFrame->GetIsPaletted()) {
+          mFrameData = reinterpret_cast<uint8_t*>(mFrame->GetPaletteData());
+        } else {
+          mFrameData = mFrame->GetImageData();
+        }
+      }
+    }
+  }
+
+  // Null out this FrameDataPair and return its frame. You must ensure the
+  // frame will be deleted separately.
+  already_AddRefed<imgFrame> Forget()
+  {
+    if (mFrameData) {
+      mFrame->UnlockImageData();
+    }
+
+    mFrameData = nullptr;
+    return mFrame.forget();
+  }
+
+  bool HasFrameData() const
+  {
+    if (mFrameData) {
+      MOZ_ASSERT(!!mFrame);
+    }
+    return !!mFrameData;
+  }
+
+  uint8_t* GetFrameData() const
+  {
+    return mFrameData;
+  }
+
+  already_AddRefed<imgFrame> GetFrame() const
+  {
+    nsRefPtr<imgFrame> frame = mFrame;
+    return frame.forget();
+  }
+
+  // Resets this FrameDataPair to work with a different frame. Takes ownership
+  // of the frame, deleting the old frame (if any).
+  void SetFrame(imgFrame* frame)
+  {
+    if (mFrameData) {
+      mFrame->UnlockImageData();
+    }
+
+    mFrame = frame;
+    mFrameData = nullptr;
+  }
+
+  imgFrame* operator->() const
+  {
+    return mFrame.get();
+  }
+
+  bool operator==(imgFrame* other) const
+  {
+    return mFrame == other;
+  }
+
+  operator bool() const
+  {
+    return mFrame != nullptr;
+  }
+
+private:
+  nsRefPtr<imgFrame> mFrame;
+  uint8_t* mFrameData;
+};
+
+/**
+ * FrameSequence stores image frames (and their associated raw data pointers).
+ * It is little more than a smart array.
+ */
+class FrameSequence
+{
+  ~FrameSequence();
+
+public:
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FrameSequence)
+
+  /**
+   * Get the read-only (frame, data) pair at index aIndex.
+   */
+  const FrameDataPair& GetFrame(uint32_t aIndex) const;
+
+  /**
+   * Insert a frame into the array. FrameSequence takes ownership of the frame.
+   */
+  void InsertFrame(uint32_t framenum, imgFrame* aFrame);
+
+  /**
+   * Remove (and delete) the frame at index framenum.
+   */
+  void RemoveFrame(uint32_t framenum);
+
+  /**
+   * Swap aFrame with the frame at sequence framenum, and return that frame.
+   * You take ownership over the frame returned.
+   */
+  already_AddRefed<imgFrame> SwapFrame(uint32_t framenum, imgFrame* aFrame);
+
+  /**
+   * Remove (and delete) all frames.
+   */
+  void ClearFrames();
+
+  /* The total number of frames in this image. */
+  uint32_t GetNumFrames() const;
+
+  size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
+                                                 MallocSizeOf aMallocSizeOf) const;
+
+private: // data
+  //! All the frames of the image
+  nsTArray<FrameDataPair> mFrames;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif /* mozilla_imagelib_FrameSequence_h_ */
--- a/image/src/ImageMetadata.cpp
+++ b/image/src/ImageMetadata.cpp
@@ -22,12 +22,16 @@ 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,31 +20,37 @@ 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(); }
@@ -57,15 +63,17 @@ 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;
+  Maybe<Orientation>  mOrientation;
+
+  bool mIsNonPremultiplied;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // ImageMetadata_h___
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -206,18 +206,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),
-                         Lifetime::Transient);
+                         RasterSurfaceKey(mDstSize.ToIntSize(), mImageFlags));
 
     return true;
   }
 
   NS_IMETHOD Run() MOZ_OVERRIDE
   {
     if (mState == eReady) {
       // Collect information from the frames that we need to scale.
@@ -234,16 +233,19 @@ 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());
@@ -258,19 +260,19 @@ public:
       // We're done, so release everything.
       mSrcRef.reset();
       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));
+      SurfaceCache::RemoveIfPresent(ImageKey(mImage.get()),
+                                    RasterSurfaceKey(mDstSize.ToIntSize(),
+                                                     mImageFlags));
 
       // 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");
     }
@@ -323,18 +325,16 @@ RasterImage::RasterImage(imgStatusTracke
   mNotifying(false),
   mHasSize(false),
   mDecodeOnDraw(false),
   mMultipart(false),
   mDiscardable(false),
   mHasSourceData(false),
   mDecoded(false),
   mHasBeenDecoded(false),
-  mHasFirstFrame(false),
-  mFirstFrameIsOpaque(false),
   mAnimationFinished(false),
   mFinishing(false),
   mInUpdateImageContainer(false),
   mWantFullDecode(false),
   mPendingError(false)
 {
   mStatusTrackerInit = new imgStatusTrackerInit(this, aStatusTracker);
 
@@ -366,20 +366,29 @@ 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;
+
+    // Unlock the last frame (if we have any). Our invariant is that, while we
+    // have a decoder open, the last frame is always locked.
+    // This would be done in ShutdownDecoder, but since mDecoder is non-null,
+    // we didn't call ShutdownDecoder and we need to do it manually.
+    if (GetNumFrames() > 0) {
+      nsRefPtr<imgFrame> curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
+      curframe->UnlockImageData();
+    }
   }
 
-  // Release all frames from the surface cache.
-  SurfaceCache::RemoveImage(ImageKey(this));
+  // Release any HQ scaled frames from the surface cache.
+  SurfaceCache::Discard(this);
 
   mAnim = nullptr;
 
   // Total statistics
   num_containers--;
   total_source_bytes -= mSourceData.Length();
 
   if (NS_IsMainThread()) {
@@ -423,21 +432,16 @@ 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;
@@ -568,61 +572,60 @@ 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)
+{
+  if (!mAnim) {
+    NS_ASSERTION(aFrameNum == 0, "Don't ask for a frame > 0 if we're not animated!");
+    return mFrameBlender.GetFrame(0);
+  }
+  return mFrameBlender.GetFrame(aFrameNum);
+}
+
 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());
 
-  DrawableFrameRef ref;
-  if (mAnim) {
-    MOZ_ASSERT(mFrameBlender, "mAnim but no mFrameBlender?");
-    nsRefPtr<imgFrame> frame = mFrameBlender->GetFrame(aFrameNum);
-    ref = frame->DrawableRef();
-  } else {
-    NS_ASSERTION(aFrameNum == 0,
-                 "Don't ask for a frame > 0 if we're not animated!");
-    ref = SurfaceCache::Lookup(ImageKey(this),
-                               RasterSurfaceKey(aSize.ToIntSize(),
-                                                DecodeFlags(aFlags)));
+  nsRefPtr<imgFrame> frame = LookupFrameNoDecode(aFrameNum);
+  if (!frame) {
+    return DrawableFrameRef();
   }
 
+  DrawableFrameRef ref = frame->DrawableRef();
   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();
       WantDecodedFrames(aFlags, aShouldSyncNotify);
 
       // See if we managed to entirely redecode the frame.
-      ref = SurfaceCache::Lookup(ImageKey(this),
-                                 RasterSurfaceKey(aSize.ToIntSize(),
-                                                  DecodeFlags(aFlags)));
+      frame = LookupFrameNoDecode(aFrameNum);
+      ref = frame->DrawableRef();
     }
 
     if (!ref) {
       // We didn't successfully redecode, so just fail.
       return DrawableFrameRef();
     }
   }
 
@@ -656,33 +659,22 @@ RasterImage::GetRequestedFrameIndex(uint
 NS_IMETHODIMP_(bool)
 RasterImage::FrameIsOpaque(uint32_t aWhichFrame)
 {
   if (aWhichFrame > FRAME_MAX_VALUE) {
     NS_WARNING("aWhichFrame outside valid range!");
     return false;
   }
 
-  if (mError) {
+  if (mError)
     return false;
-  }
-
-  // False is always correct if we are still decoding the first frame.
-  if (!mHasFirstFrame) {
-    return false;
-  }
-
-  if (GetNumFrames() == 1) {
-    return mFirstFrameIsOpaque;
-  }
 
   // See if we can get an image frame.
-  MOZ_ASSERT(mFrameBlender, "We should be animated here");
   nsRefPtr<imgFrame> frame =
-    mFrameBlender->RawGetFrame(GetRequestedFrameIndex(aWhichFrame));
+    LookupFrameNoDecode(GetRequestedFrameIndex(aWhichFrame));
 
   // If we don't get a frame, the safe answer is "not opaque".
   if (!frame)
     return false;
 
   // Other, the frame is transparent if either:
   //  1. It needs a background.
   //  2. Its size doesn't cover our entire area.
@@ -694,28 +686,19 @@ RasterImage::FrameIsOpaque(uint32_t aWhi
 nsIntRect
 RasterImage::FrameRect(uint32_t aWhichFrame)
 {
   if (aWhichFrame > FRAME_MAX_VALUE) {
     NS_WARNING("aWhichFrame outside valid range!");
     return nsIntRect();
   }
 
-  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");
+  // Get the requested frame.
   nsRefPtr<imgFrame> frame =
-    mFrameBlender->RawGetFrame(GetRequestedFrameIndex(aWhichFrame));
+    LookupFrameNoDecode(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
@@ -723,20 +706,17 @@ 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
 {
-  if (mFrameBlender) {
-    return mFrameBlender->GetNumFrames();
-  }
-  return mHasFirstFrame ? 1 : 0;
+  return mFrameBlender.GetNumFrames();
 }
 
 //******************************************************************************
 /* readonly attribute boolean animated; */
 NS_IMETHODIMP
 RasterImage::GetAnimated(bool *aAnimated)
 {
   if (mError)
@@ -768,18 +748,17 @@ RasterImage::GetFirstFrameDelay()
 {
   if (mError)
     return -1;
 
   bool animated = false;
   if (NS_FAILED(GetAnimated(&animated)) || !animated)
     return -1;
 
-  MOZ_ASSERT(mFrameBlender, "Animated images should have a FrameBlender");
-  return mFrameBlender->GetTimeoutForFrame(0);
+  return mFrameBlender.GetTimeoutForFrame(0);
 }
 
 TemporaryRef<SourceSurface>
 RasterImage::CopyFrame(uint32_t aWhichFrame,
                        uint32_t aFlags,
                        bool aShouldSyncNotify /* = true */)
 {
   if (aWhichFrame > FRAME_MAX_VALUE)
@@ -794,17 +773,17 @@ RasterImage::CopyFrame(uint32_t aWhichFr
 
   if (!ApplyDecodeFlags(aFlags, aWhichFrame))
     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),
-                                          mSize, aFlags, aShouldSyncNotify);
+                                          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.
 
@@ -875,17 +854,17 @@ RasterImage::GetFrameInternal(uint32_t a
 
   if (!ApplyDecodeFlags(aFlags, aWhichFrame))
     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),
-                                          mSize, aFlags, aShouldSyncNotify);
+                                          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;
@@ -1005,23 +984,17 @@ RasterImage::HeapSizeOfSourceWithCompute
   }
   return n;
 }
 
 size_t
 RasterImage::SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                      MallocSizeOf aMallocSizeOf) const
 {
-  size_t n = 0;
-  n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
-  if (mFrameBlender) {
-    n += mFrameBlender->SizeOfDecodedWithComputedFallbackIfHeap(aLocation,
-                                                                aMallocSizeOf);
-  }
-  return n;
+  return mFrameBlender.SizeOfDecodedWithComputedFallbackIfHeap(aLocation, aMallocSizeOf);
 }
 
 size_t
 RasterImage::HeapSizeOfDecodedWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::IN_PROCESS_HEAP,
                                                  aMallocSizeOf);
 }
@@ -1035,131 +1008,134 @@ RasterImage::NonHeapSizeOfDecoded() cons
 
 size_t
 RasterImage::OutOfProcessSizeOfDecoded() const
 {
   return SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation::OUT_OF_PROCESS,
                                                  nullptr);
 }
 
-RawAccessFrameRef
-RasterImage::InternalAddFrame(uint32_t aFrameNum,
-                              const nsIntRect& aFrameRect,
-                              uint32_t aDecodeFlags,
-                              SurfaceFormat aFormat,
-                              uint8_t aPaletteDepth,
-                              imgFrame* aPreviousFrame)
+void
+RasterImage::EnsureAnimExists()
 {
-  // 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();
-  }
-
-  IntSize frameSize = aFrameRect.Size().ToIntSize();
-  if (!SurfaceCache::CanHold(frameSize)) {
-    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(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(frameSize, aDecodeFlags),
-                           Lifetime::Persistent);
-    if (!succeeded) {
-      return RawAccessFrameRef();
-    }
-    mHasFirstFrame = true;
-    return Move(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);
+  if (!mAnim) {
+
+    // Create the animation context
+    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.
     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));
-
-    // Record whether the first frame is opaque.
-    mFirstFrameIsOpaque = !aPreviousFrame->GetNeedsBackground();
-
-    // 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(frameSize, aDecodeFlags));
-
     // Notify our observers that we are starting animation.
     nsRefPtr<imgStatusTracker> statusTracker = CurrentStatusTracker();
     statusTracker->RecordImageIsAnimated();
-
-    // 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();
+  }
+}
+
+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);
+
+  // We are in the middle of decoding. This will be unlocked when we finish
+  // decoding or switch to another frame.
+  frame->LockImageData();
+
+  if (paletteData && paletteLength)
+    frame->GetPaletteData(paletteData, paletteLength);
+
+  frame->GetImageData(imageData, imageLength);
+
+  mFrameBlender.InsertFrame(framenum, frame);
+
+  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);
+
+  // We know we are in a decoder. Therefore, we must unlock the previous frame
+  // when we move on to decoding into the next frame.
+  if (GetNumFrames() > 0) {
+    nsRefPtr<imgFrame> prevframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
+    prevframe->UnlockImageData();
+  }
+
+  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(aPreviousFrame->GetRect());
-    }
-
-    if (mPendingAnimation && ShouldAnimate()) {
-      StartAnimation();
-    }
+        frameDisposalMethod == FrameBlender::kDisposeRestorePrevious)
+      mAnim->SetFirstFrameRefreshArea(firstFrame->GetRect());
   }
 
-  // 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.
+  // 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
   mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
 
-  MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender by now");
-  mFrameBlender->InsertFrame(aFrameNum, Move(ref));
-
-  return ref;
+  rv = InternalAddFrameHelper(framenum, frame, imageData, imageLength,
+                              paletteData, paletteLength, aRetFrame);
+
+  return rv;
 }
 
 bool
 RasterImage::ApplyDecodeFlags(uint32_t aNewFlags, uint32_t aWhichFrame)
 {
   if (mFrameDecodeFlags == (aNewFlags & DECODE_FLAGS_MASK))
     return true; // Not asking very much of us here.
 
@@ -1219,127 +1195,186 @@ 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;
 }
 
-RawAccessFrameRef
-RasterImage::EnsureFrame(uint32_t aFrameNum,
-                         const nsIntRect& aFrameRect,
-                         uint32_t aDecodeFlags,
+nsresult
+RasterImage::EnsureFrame(uint32_t aFrameNum, int32_t aX, int32_t aY,
+                         int32_t aWidth, int32_t aHeight,
                          SurfaceFormat aFormat,
                          uint8_t aPaletteDepth,
-                         imgFrame* aPreviousFrame)
+                         uint8_t **imageData, uint32_t *imageLength,
+                         uint32_t **paletteData, uint32_t *paletteLength,
+                         imgFrame** aRetFrame)
 {
-  if (mError) {
-    return RawAccessFrameRef();
+  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);
   }
 
-  MOZ_ASSERT(aFrameNum <= GetNumFrames(), "Invalid frame index!");
-  if (aFrameNum > GetNumFrames()) {
-    return RawAccessFrameRef();
+  if (aFrameNum > GetNumFrames())
+    return NS_ERROR_INVALID_ARG;
+
+  // Adding a frame that doesn't already exist.
+  if (aFrameNum == GetNumFrames()) {
+    return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat,
+                            aPaletteDepth, imageData, imageLength,
+                            paletteData, paletteLength, aRetFrame);
   }
 
-  // Adding a frame that doesn't already exist. This is the normal case.
-  if (aFrameNum == GetNumFrames()) {
-    return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
-                            aPaletteDepth, aPreviousFrame);
+  nsRefPtr<imgFrame> frame = mFrameBlender.RawGetFrame(aFrameNum);
+  if (!frame) {
+    return InternalAddFrame(aFrameNum, aX, aY, aWidth, aHeight, aFormat,
+                            aPaletteDepth, imageData, imageLength,
+                            paletteData, paletteLength, aRetFrame);
   }
 
-  // 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();
+  // 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;
+    }
   }
 
-  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->GetRect().Size().ToIntSize();
-  SurfaceCache::RemoveSurface(ImageKey(this),
-                              RasterSurfaceKey(prevFrameSize, aDecodeFlags));
-  mHasFirstFrame = false;
-
-  // Add the new frame as usual.
-  return InternalAddFrame(aFrameNum, aFrameRect, aDecodeFlags, aFormat,
-                          aPaletteDepth, aPreviousFrame);
+  // Not reusable, so replace the frame directly.
+
+  // We know this frame is already locked, because it's the one we're currently
+  // writing to.
+  frame->UnlockImageData();
+
+  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);
 }
 
-void
-RasterImage::DecodingComplete(imgFrame* aFinalFrame)
+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()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (mError) {
-    return;
-  }
+  if (mError)
+    return NS_ERROR_FAILURE;
 
   // 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");
-    DiscardTracker::Reset(&mDiscardTrackerNode);
-  }
-
-  bool singleFrame = GetNumFrames() == 1;
-  if (singleFrame && aFinalFrame) {
-    // Record whether the first frame is opaque.
-    mFirstFrameIsOpaque = !aFinalFrame->GetNeedsBackground();
+    rv = DiscardTracker::Reset(&mDiscardTrackerNode);
+    CONTAINER_ENSURE_SUCCESS(rv);
   }
 
   // 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 (singleFrame && !mMultipart && aFinalFrame) {
-    aFinalFrame->SetOptimizable();
+  if ((GetNumFrames() == 1) && !mMultipart) {
+    nsRefPtr<imgFrame> firstFrame = mFrameBlender.RawGetFrame(0);
+    firstFrame->SetOptimizable();
+    if (DiscardingEnabled() && CanForciblyDiscard()) {
+      firstFrame->SetDiscardable();
+    }
   }
 
   // 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 (singleFrame) {
-      // aFinalFrame must be the first frame since we only have one.
-      mMultipartDecodedFrame = aFinalFrame->DrawableRef();
+    if (GetNumFrames() == 1) {
+      mMultipartDecodedFrame = mFrameBlender.SwapFrame(GetCurrentFrameIndex(),
+                                                       mMultipartDecodedFrame);
     } 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.reset();
+      mMultipartDecodedFrame = nullptr;
     }
   }
 
   if (mAnim) {
     mAnim->SetDoneDecoding(true);
   }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 RasterImage::SetAnimationMode(uint16_t aAnimationMode)
 {
   if (mAnim) {
     mAnim->SetAnimationMode(aAnimationMode);
   }
@@ -1351,27 +1386,22 @@ RasterImage::SetAnimationMode(uint16_t a
 nsresult
 RasterImage::StartAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
   NS_ABORT_IF_FALSE(ShouldAnimate(), "Should not animate!");
 
-  // 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;
-  }
-
+  EnsureAnimExists();
+
+  nsRefPtr<imgFrame> currentFrame = LookupFrameNoDecode(GetCurrentFrameIndex());
   // A timeout of -1 means we should display this frame forever.
-  if (mDecoded &&
-      mFrameBlender->GetTimeoutForFrame(GetCurrentFrameIndex()) < 0) {
+  if (currentFrame &&
+      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();
@@ -1401,30 +1431,26 @@ RasterImage::StopAnimation()
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 RasterImage::ResetAnimation()
 {
   if (mError)
     return NS_ERROR_FAILURE;
 
-  mPendingAnimation = false;
-
-  if (mAnimationMode == kDontAnimMode || !mAnim ||
-      mAnim->GetCurrentAnimationFrameIndex() == 0) {
+  if (mAnimationMode == kDontAnimMode ||
+      !mAnim || mAnim->GetCurrentAnimationFrameIndex() == 0)
     return NS_OK;
-  }
 
   mAnimationFinished = false;
 
   if (mAnimating)
     StopAnimation();
 
-  MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender");
-  mFrameBlender->ResetAnimation();
+  mFrameBlender.ResetAnimation();
   mAnim->ResetAnimation();
 
   UpdateImageContainer();
 
   // Note - We probably want to kick off a redecode somewhere around here when
   // we fix bug 500402.
 
   // Update display
@@ -1463,18 +1489,17 @@ 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
-    MOZ_ASSERT(mFrameBlender, "Should have a FrameBlender");
-    mFrameBlender->SetLoopCount(aLoopCount);
+    mFrameBlender.SetLoopCount(aLoopCount);
   }
 }
 
 NS_IMETHODIMP_(nsIntRect)
 RasterImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
   return aRect;
 }
@@ -1510,24 +1535,23 @@ RasterImage::AddSourceData(const char *a
   // 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 && mBytesDecoded == 0) {
     // Our previous state may have been animated, so let's clean up
     if (mAnimating)
       StopAnimation();
     mAnimationFinished = false;
-    mPendingAnimation = false;
     if (mAnim) {
       mAnim = nullptr;
     }
-    if (mFrameBlender) {
-      nsRefPtr<imgFrame> firstFrame = mFrameBlender->RawGetFrame(0);
-      mMultipartDecodedFrame = firstFrame->DrawableRef();
-      mFrameBlender.reset();
+    // If there's only one frame, this could cause flickering
+    int old_frame_count = GetNumFrames();
+    if (old_frame_count > 1) {
+      mFrameBlender.ClearFrames();
     }
   }
 
   // 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, DECODE_SYNC);
@@ -1735,17 +1759,16 @@ 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;
   mDecodeRequest = nullptr;
 
   if (mAnim) {
     mAnim->SetDoneDecoding(false);
   }
 
   // We always need the size first.
@@ -1826,25 +1849,23 @@ RasterImage::Discard(bool force)
   // 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.reset();
-  SurfaceCache::RemoveImage(this);
+  mFrameBlender.Discard();
 
   // Clear the last decoded multipart frame.
-  mMultipartDecodedFrame.reset();
+  mMultipartDecodedFrame = nullptr;
 
   // Flag that we no longer have decoded frames for this image
   mDecoded = false;
-  mHasFirstFrame = false;
 
   // Notify that we discarded
   if (mStatusTracker)
     mStatusTracker->OnDiscard();
 
   mDecodeRequest = nullptr;
 
   if (force)
@@ -1951,16 +1972,24 @@ RasterImage::InitDecoder(bool aDoSizeDec
       break;
     case eDecoderType_icon:
       mDecoder = new nsIconDecoder(*this);
       break;
     default:
       NS_ABORT_IF_FALSE(0, "Shouldn't get here!");
   }
 
+  // If we already have frames, we're probably in the multipart/x-mixed-replace
+  // case. Regardless, we need to lock the last frame. Our invariant is that,
+  // while we have a decoder open, the last frame is always locked.
+  if (GetNumFrames() > 0) {
+    nsRefPtr<imgFrame> curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
+    curframe->LockImageData();
+  }
+
   // Initialize the decoder
   if (!mDecodeRequest) {
     mDecodeRequest = new DecodeRequest(this);
   }
   MOZ_ASSERT(mDecodeRequest->mStatusTracker);
   MOZ_ASSERT(mDecodeRequest->mStatusTracker->GetDecoderObserver());
   mDecoder->SetObserver(mDecodeRequest->mStatusTracker->GetDecoderObserver());
   mDecoder->SetSizeDecode(aDoSizeDecode);
@@ -2026,16 +2055,23 @@ RasterImage::ShutdownDecoder(eShutdownIn
   mDecoder = nullptr;
 
   mFinishing = true;
   mInDecoder = true;
   decoder->Finish(aIntent);
   mInDecoder = false;
   mFinishing = false;
 
+  // Unlock the last frame (if we have any). Our invariant is that, while we
+  // have a decoder open, the last frame is always locked.
+  if (GetNumFrames() > 0) {
+    nsRefPtr<imgFrame> curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
+    curframe->UnlockImageData();
+  }
+
   // Kill off our decode request, if it's pending.  (If not, this call is
   // harmless.)
   DecodePool::StopDecoding(this);
 
   nsresult decoderStatus = decoder->GetDecoderError();
   if (NS_FAILED(decoderStatus)) {
     DoError();
     return decoderStatus;
@@ -2631,29 +2667,23 @@ 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),
-                                     mSize, aFlags);
+                                     aFlags);
   if (!ref) {
     return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
   }
 
-  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();
   }
 
@@ -2671,21 +2701,16 @@ 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()
 {
@@ -2701,21 +2726,16 @@ 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 "
@@ -3569,18 +3589,17 @@ 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),
-                             mSize, aFlags);
+      frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame), 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,17 +24,16 @@
 #include "nsIProperties.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
 #include "nsThreadUtils.h"
 #include "DecodeStrategy.h"
 #include "DiscardTracker.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
-#include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/UniquePtr.h"
 #ifdef DEBUG
@@ -182,39 +181,53 @@ public:
     return 0;
   }
 
   /* Triggers discarding. */
   void Discard(bool force = false);
   void ForceDiscard() { Discard(/* force = */ 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 a RawAccessFrameRef for that frame.
+   * returns pointers to the data storage 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.
+   * frames to the current frame array.
    */
-  RawAccessFrameRef EnsureFrame(uint32_t aFrameNum,
-                                const nsIntRect& aFrameRect,
-                                uint32_t aDecodeFlags,
-                                gfx::SurfaceFormat aFormat,
-                                uint8_t aPaletteDepth,
-                                imgFrame* aPreviousFrame);
+  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);
 
   /* notification that the entire image has been decoded */
-  void DecodingComplete(imgFrame* aFinalFrame);
+  nsresult DecodingComplete();
 
   /**
    * Number of times to loop the image.
    * @note -1 means forever.
    */
   void     SetLoopCount(int32_t aLoopCount);
 
   /* Add compressed source data to the imgContainer.
@@ -529,34 +542,36 @@ 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);
 
-  DrawableFrameRef LookupFrame(uint32_t aFrameNum,
-                               const nsIntSize& aSize,
-                               uint32_t aFlags,
-                               bool aShouldSyncNotify = true);
+  already_AddRefed<imgFrame> LookupFrameNoDecode(uint32_t aFrameNum);
+  DrawableFrameRef LookupFrame(uint32_t aFrameNum, uint32_t aFlags, bool aShouldSyncNotify = true);
   uint32_t GetCurrentFrameIndex() const;
   uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
 
   size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxMemoryLocation aLocation,
                                                  MallocSizeOf aMallocSizeOf) const;
 
   void EnsureAnimExists();
 
-  RawAccessFrameRef InternalAddFrame(uint32_t aFrameNum,
-                                     const nsIntRect& aFrameRect,
-                                     uint32_t aDecodeFlags,
-                                     gfx::SurfaceFormat aFormat,
-                                     uint8_t aPaletteDepth,
-                                     imgFrame* aPreviousFrame);
+  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);
+
   nsresult DoImageDataComplete();
 
   bool ApplyDecodeFlags(uint32_t aNewFlags, uint32_t aWhichFrame);
 
   already_AddRefed<layers::Image> GetCurrentImage();
   void UpdateImageContainer();
 
   void SetInUpdateImageContainer(bool aInUpdate) { mInUpdateImageContainer = aInUpdate; }
@@ -583,21 +598,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.
-  Maybe<FrameBlender>       mFrameBlender;
+  //! All the frames of the image
+  FrameBlender              mFrameBlender;
 
-  //! The last frame we decoded for multipart images.
-  DrawableFrameRef          mMultipartDecodedFrame;
+  // The last frame we decoded for multipart images.
+  nsRefPtr<imgFrame>        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;
 
@@ -656,24 +671,16 @@ private: // data
   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                       mHasBeenDecoded:1;
 
-  // Data about the first frame.
-  bool                       mHasFirstFrame:1;
-  bool                       mFirstFrameIsOpaque: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 we're calling Decoder::Finish() from ShutdownDecoder.
   bool                       mFinishing:1;
 
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -49,30 +49,30 @@ class SurfaceCacheImpl;
 // The single surface cache instance.
 static StaticRefPtr<SurfaceCacheImpl> sInstance;
 
 
 ///////////////////////////////////////////////////////////////////////////////
 // SurfaceCache Implementation
 ///////////////////////////////////////////////////////////////////////////////
 
-/**
+/*
  * Cost models the cost of storing a surface in the cache. Right now, this is
  * simply an estimate of the size of the surface in bytes, but in the future it
  * may be worth taking into account the cost of rematerializing the surface as
  * well.
  */
 typedef size_t Cost;
 
 static Cost ComputeCost(const IntSize& aSize)
 {
   return aSize.width * aSize.height * 4;  // width * height * 4 bytes (32bpp)
 }
 
-/**
+/*
  * Since we want to be able to make eviction decisions based on cost, we need to
  * be able to look up the CachedSurface which has a certain cost as well as the
  * cost associated with a certain CachedSurface. To make this possible, in data
  * structures we actually store a CostEntry, which contains a weak pointer to
  * its associated surface.
  *
  * To make usage of the weak pointer safe, SurfaceCacheImpl always calls
  * StartTracking after a surface is stored in the cache and StopTracking before
@@ -103,131 +103,79 @@ public:
            (mCost == aOther.mCost && mSurface < aOther.mSurface);
   }
 
 private:
   CachedSurface* mSurface;
   Cost           mCost;
 };
 
-/**
+/*
  * A CachedSurface associates a surface with a key that uniquely identifies that
  * surface.
  */
 class CachedSurface
 {
   ~CachedSurface() {}
 public:
   NS_INLINE_DECL_REFCOUNTING(CachedSurface)
 
-  CachedSurface(imgFrame*          aSurface,
-                const Cost         aCost,
-                const ImageKey     aImageKey,
-                const SurfaceKey&  aSurfaceKey,
-                const Lifetime     aLifetime)
+  CachedSurface(imgFrame*         aSurface,
+                const IntSize     aTargetSize,
+                const Cost        aCost,
+                const ImageKey    aImageKey,
+                const SurfaceKey& aSurfaceKey)
     : mSurface(aSurface)
+    , mTargetSize(aTargetSize)
     , mCost(aCost)
     , mImageKey(aImageKey)
     , mSurfaceKey(aSurfaceKey)
-    , mLifetime(aLifetime)
   {
-    MOZ_ASSERT(mSurface, "Must have a valid surface");
+    MOZ_ASSERT(mSurface, "Must have a valid SourceSurface");
     MOZ_ASSERT(mImageKey, "Must have a valid image key");
   }
 
   DrawableFrameRef DrawableRef() const
   {
     return mSurface->DrawableRef();
   }
 
-  void SetLocked(bool aLocked)
-  {
-    if (aLocked && mLifetime == Lifetime::Persistent) {
-      // This may fail, and that's OK. We make no guarantees about whether
-      // locking is successful if you call SurfaceCache::LockImage() after
-      // SurfaceCache::Insert().
-      mDrawableRef = mSurface->DrawableRef();
-    } else {
-      mDrawableRef.reset();
-    }
-  }
-
-  bool IsLocked() const { return bool(mDrawableRef); }
-
   ImageKey GetImageKey() const { return mImageKey; }
   SurfaceKey GetSurfaceKey() const { return mSurfaceKey; }
   CostEntry GetCostEntry() { return image::CostEntry(this, mCost); }
   nsExpirationState* GetExpirationState() { return &mExpirationState; }
-  Lifetime GetLifetime() const { return mLifetime; }
-
-  // A helper type used by SurfaceCacheImpl::SizeOfSurfacesSum.
-  struct SizeOfSurfacesSum
-  {
-    SizeOfSurfacesSum(gfxMemoryLocation aLocation,
-                      MallocSizeOf      aMallocSizeOf)
-      : mLocation(aLocation)
-      , mMallocSizeOf(aMallocSizeOf)
-      , mSum(0)
-    { }
-
-    void Add(CachedSurface* aCachedSurface)
-    {
-      if (!aCachedSurface || !aCachedSurface->mSurface) {
-        return;
-      }
-
-      mSum += aCachedSurface->mSurface->
-        SizeOfExcludingThisWithComputedFallbackIfHeap(mLocation, mMallocSizeOf);
-    }
-
-    size_t Result() const { return mSum; }
-
-  private:
-    gfxMemoryLocation mLocation;
-    MallocSizeOf      mMallocSizeOf;
-    size_t            mSum;
-  };
 
 private:
   nsExpirationState  mExpirationState;
   nsRefPtr<imgFrame> mSurface;
-  DrawableFrameRef   mDrawableRef;
+  const IntSize      mTargetSize;
   const Cost         mCost;
   const ImageKey     mImageKey;
   const SurfaceKey   mSurfaceKey;
-  const Lifetime     mLifetime;
 };
 
 /*
  * An ImageSurfaceCache is a per-image surface cache. For correctness we must be
  * able to remove all surfaces associated with an image when the image is
  * destroyed or invalidated. Since this will happen frequently, it makes sense
  * to make it cheap by storing the surfaces for each image separately.
- *
- * ImageSurfaceCache also keeps track of whether its associated image is locked
- * or unlocked.
  */
 class ImageSurfaceCache
 {
-  ~ImageSurfaceCache() { }
+  ~ImageSurfaceCache() {}
 public:
-  ImageSurfaceCache() : mLocked(false) { }
-
   NS_INLINE_DECL_REFCOUNTING(ImageSurfaceCache)
 
   typedef nsRefPtrHashtable<nsGenericHashKey<SurfaceKey>, CachedSurface> SurfaceTable;
 
   bool IsEmpty() const { return mSurfaces.Count() == 0; }
   
   void Insert(const SurfaceKey& aKey, CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
-    MOZ_ASSERT(!mLocked || aSurface->GetLifetime() != Lifetime::Persistent ||
-               aSurface->IsLocked(),
-               "Inserting an unlocked persistent surface for a locked image");
     mSurfaces.Put(aKey, aSurface);
   }
 
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     MOZ_ASSERT(mSurfaces.GetWeak(aSurface->GetSurfaceKey()),
         "Should not be removing a surface we don't have");
@@ -242,22 +190,18 @@ public:
     return surface.forget();
   }
 
   void ForEach(SurfaceTable::EnumReadFunction aFunction, void* aData)
   {
     mSurfaces.EnumerateRead(aFunction, aData);
   }
 
-  void SetLocked(bool aLocked) { mLocked = aLocked; }
-  bool IsLocked() const { return mLocked; }
-
 private:
   SurfaceTable mSurfaces;
-  bool         mLocked;
 };
 
 /*
  * SurfaceCacheImpl is responsible for determining which surfaces will be cached
  * and managing the surface cache data structures. Rather than interact with
  * SurfaceCacheImpl directly, client code interacts with SurfaceCache, which
  * maintains high-level invariants and encapsulates the details of the surface
  * cache's implementation.
@@ -269,17 +213,16 @@ public:
 
   SurfaceCacheImpl(uint32_t aSurfaceCacheExpirationTimeMS,
                    uint32_t aSurfaceCacheSize)
     : mExpirationTracker(MOZ_THIS_IN_INITIALIZER_LIST(),
                          aSurfaceCacheExpirationTimeMS)
     , mMemoryPressureObserver(new MemoryPressureObserver)
     , mMaxCost(aSurfaceCacheSize)
     , mAvailableCost(aSurfaceCacheSize)
-    , mLockedCost(0)
   {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os)
       os->AddObserver(mMemoryPressureObserver, "memory-pressure", false);
   }
 
 private:
   virtual ~SurfaceCacheImpl()
@@ -291,120 +234,91 @@ private:
     UnregisterWeakMemoryReporter(this);
   }
 
 public:
   void InitMemoryReporter() {
     RegisterWeakMemoryReporter(this);
   }
 
-  bool Insert(imgFrame*         aSurface,
+  void Insert(imgFrame*         aSurface,
+              IntSize           aTargetSize,
               const Cost        aCost,
               const ImageKey    aImageKey,
-              const SurfaceKey& aSurfaceKey,
-              Lifetime          aLifetime)
+              const SurfaceKey& aSurfaceKey)
   {
     MOZ_ASSERT(!Lookup(aImageKey, aSurfaceKey),
                "Inserting a duplicate surface into the SurfaceCache");
 
-    // If this is bigger than we can hold after discarding everything we can,
-    // refuse to cache it.
-    if (!CanHoldAfterDiscarding(aCost))
-      return false;
+    // If this is bigger than the maximum cache size, refuse to cache it.
+    if (!CanHold(aCost))
+      return;
 
-    // Remove elements in order of cost until we can fit this in the cache. Note
-    // that locked surfaces aren't in mCosts, so we never remove them here.
+    nsRefPtr<CachedSurface> surface =
+      new CachedSurface(aSurface, aTargetSize, aCost, aImageKey, aSurfaceKey);
+
+    // Remove elements in order of cost until we can fit this in the cache.
     while (aCost > mAvailableCost) {
       MOZ_ASSERT(!mCosts.IsEmpty(), "Removed everything and it still won't fit");
       Remove(mCosts.LastElement().GetSurface());
     }
 
     // Locate the appropriate per-image cache. If there's not an existing cache
     // for this image, create it.
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       cache = new ImageSurfaceCache;
       mImageCaches.Put(aImageKey, cache);
     }
 
-    nsRefPtr<CachedSurface> surface =
-      new CachedSurface(aSurface, aCost, aImageKey, aSurfaceKey, aLifetime);
-
-    // We require that locking succeed if the image is locked and the surface is
-    // persistent; the caller may need to know this to handle errors correctly.
-    if (cache->IsLocked() && aLifetime == Lifetime::Persistent) {
-      surface->SetLocked(true);
-      if (!surface->IsLocked()) {
-        return false;
-      }
-    }
-
     // Insert.
     MOZ_ASSERT(aCost <= mAvailableCost, "Inserting despite too large a cost");
     cache->Insert(aSurfaceKey, surface);
     StartTracking(surface);
-
-    return true;
   }
 
   void Remove(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     const ImageKey imageKey = aSurface->GetImageKey();
 
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(imageKey);
     MOZ_ASSERT(cache, "Shouldn't try to remove a surface with no image cache");
 
     StopTracking(aSurface);
     cache->Remove(aSurface);
 
-    // Remove the per-image cache if it's unneeded now. (Keep it if the image is
-    // locked, since the per-image cache is where we store that state.)
-    if (cache->IsEmpty() && !cache->IsLocked()) {
+    // Remove the per-image cache if it's unneeded now.
+    if (cache->IsEmpty()) {
       mImageCaches.Remove(imageKey);
     }
   }
 
   void StartTracking(CachedSurface* aSurface)
   {
     CostEntry costEntry = aSurface->GetCostEntry();
     MOZ_ASSERT(costEntry.GetCost() <= mAvailableCost,
                "Cost too large and the caller didn't catch it");
 
     mAvailableCost -= costEntry.GetCost();
-
-    if (aSurface->IsLocked()) {
-      mLockedCost += costEntry.GetCost();
-      MOZ_ASSERT(mLockedCost <= mMaxCost, "Locked more than we can hold?");
-    } else {
-      mCosts.InsertElementSorted(costEntry);
-      mExpirationTracker.AddObject(aSurface);
-    }
+    mCosts.InsertElementSorted(costEntry);
+    mExpirationTracker.AddObject(aSurface);
   }
 
   void StopTracking(CachedSurface* aSurface)
   {
     MOZ_ASSERT(aSurface, "Should have a surface");
     CostEntry costEntry = aSurface->GetCostEntry();
 
-    if (aSurface->IsLocked()) {
-      MOZ_ASSERT(mLockedCost >= costEntry.GetCost(), "Costs don't balance");
-      mLockedCost -= costEntry.GetCost();
-      // XXX(seth): It'd be nice to use an O(log n) lookup here. This is O(n).
-      MOZ_ASSERT(!mCosts.Contains(costEntry),
-                 "Shouldn't have a cost entry for a locked surface");
-    } else {
-      mExpirationTracker.RemoveObject(aSurface);
-      DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
-      MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
-    }
+    mExpirationTracker.RemoveObject(aSurface);
+    DebugOnly<bool> foundInCosts = mCosts.RemoveElementSorted(costEntry);
+    mAvailableCost += costEntry.GetCost();
 
-    mAvailableCost += costEntry.GetCost();
-    MOZ_ASSERT(mAvailableCost <= mMaxCost,
-               "More available cost than we started with");
+    MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
+    MOZ_ASSERT(mAvailableCost <= mMaxCost, "More available cost than we started with");
   }
 
   DrawableFrameRef Lookup(const ImageKey    aImageKey,
                           const SurfaceKey& aSurfaceKey)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache)
       return DrawableFrameRef();  // No cached surfaces for this image.
@@ -416,25 +330,22 @@ public:
     DrawableFrameRef ref = surface->DrawableRef();
     if (!ref) {
       // The surface was released by the operating system. Remove the cache
       // entry as well.
       Remove(surface);
       return DrawableFrameRef();
     }
 
-    if (!surface->IsLocked()) {
-      mExpirationTracker.MarkUsed(surface);
-    }
-
+    mExpirationTracker.MarkUsed(surface);
     return ref;
   }
 
-  void RemoveSurface(const ImageKey    aImageKey,
-                     const SurfaceKey& aSurfaceKey)
+  void RemoveIfPresent(const ImageKey    aImageKey,
+                       const SurfaceKey& aSurfaceKey)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache)
       return;  // No cached surfaces for this image.
 
     nsRefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
     if (!surface)
       return;  // Lookup in the per-image cache missed.
@@ -442,186 +353,77 @@ public:
     Remove(surface);
   }
 
   bool CanHold(const Cost aCost) const
   {
     return aCost <= mMaxCost;
   }
 
-  void LockImage(const ImageKey aImageKey)
-  {
-    nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
-    if (!cache) {
-      cache = new ImageSurfaceCache;
-      mImageCaches.Put(aImageKey, cache);
-    }
-
-    cache->SetLocked(true);
-
-    // Try to lock all the surfaces the per-image cache is holding.
-    cache->ForEach(DoLockSurface, this);
-  }
-
-  void UnlockImage(const ImageKey aImageKey)
-  {
-    nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
-    if (!cache)
-      return;  // Already unlocked and removed.
-
-    cache->SetLocked(false);
-
-    // Unlock all the surfaces the per-image cache is holding.
-    cache->ForEach(DoUnlockSurface, this);
-  }
-
-  void RemoveImage(const ImageKey aImageKey)
+  void Discard(const ImageKey aImageKey)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache)
       return;  // No cached surfaces for this image, so nothing to do.
 
     // Discard all of the cached surfaces for this image.
     // XXX(seth): This is O(n^2) since for each item in the cache we are
     // removing an element from the costs array. Since n is expected to be
     // small, performance should be good, but if usage patterns change we should
     // change the data structure used for mCosts.
     cache->ForEach(DoStopTracking, this);
 
     // The per-image cache isn't needed anymore, so remove it as well.
-    // This implicitly unlocks the image if it was locked.
     mImageCaches.Remove(aImageKey);
   }
 
   void DiscardAll()
   {
     // Remove in order of cost because mCosts is an array and the other data
-    // structures are all hash tables. Note that locked surfaces (persistent
-    // surfaces belonging to locked images) are not removed, since they aren't
-    // present in mCosts.
+    // structures are all hash tables.
     while (!mCosts.IsEmpty()) {
       Remove(mCosts.LastElement().GetSurface());
     }
   }
 
   static PLDHashOperator DoStopTracking(const SurfaceKey&,
                                         CachedSurface*    aSurface,
                                         void*             aCache)
   {
     static_cast<SurfaceCacheImpl*>(aCache)->StopTracking(aSurface);
     return PL_DHASH_NEXT;
   }
 
-  static PLDHashOperator DoLockSurface(const SurfaceKey&,
-                                       CachedSurface*    aSurface,
-                                       void*             aCache)
+  NS_IMETHOD
+  CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData,
+                 bool aAnonymize)
   {
-    if (aSurface->GetLifetime() == Lifetime::Transient ||
-        aSurface->IsLocked()) {
-      return PL_DHASH_NEXT;
-    }
-
-    auto cache = static_cast<SurfaceCacheImpl*>(aCache);
-    cache->StopTracking(aSurface);
-
-    // Lock the surface. This can fail.
-    aSurface->SetLocked(true);
-    cache->StartTracking(aSurface);
-
-    return PL_DHASH_NEXT;
-  }
-
-  static PLDHashOperator DoUnlockSurface(const SurfaceKey&,
-                                         CachedSurface*    aSurface,
-                                         void*             aCache)
-  {
-    if (aSurface->GetLifetime() == Lifetime::Transient ||
-        !aSurface->IsLocked()) {
-      return PL_DHASH_NEXT;
-    }
-
-    auto cache = static_cast<SurfaceCacheImpl*>(aCache);
-    cache->StopTracking(aSurface);
-
-    aSurface->SetLocked(false);
-    cache->StartTracking(aSurface);
-
-    return PL_DHASH_NEXT;
+    return MOZ_COLLECT_REPORT(
+      "imagelib-surface-cache", KIND_OTHER, UNITS_BYTES,
+      SizeOfSurfacesEstimate(),
+      "Memory used by the imagelib temporary surface cache.");
   }
 
-  NS_IMETHOD
-  CollectReports(nsIHandleReportCallback* aHandleReport,
-                 nsISupports*             aData,
-                 bool                     aAnonymize) MOZ_OVERRIDE
+  // XXX(seth): This is currently only an estimate and, since we don't know
+  // which surfaces are in GPU memory and which aren't, it's reported as
+  // KIND_OTHER and will also show up in heap-unclassified. Bug 923302 will
+  // make this nicer.
+  Cost SizeOfSurfacesEstimate() const
   {
-    // We have explicit memory reporting for the surface cache which is more
-    // accurate than the cost metrics we report here, but these metrics are
-    // still useful to report, since they control the cache's behavior.
-    nsresult rv;
-
-    rv = MOZ_COLLECT_REPORT("imagelib-surface-cache-estimated-total",
-                            KIND_OTHER, UNITS_BYTES,
-                            (mMaxCost - mAvailableCost),
-                            "Estimated total memory used by the imagelib "
-                            "surface cache.");
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    rv = MOZ_COLLECT_REPORT("imagelib-surface-cache-estimated-locked",
-                            KIND_OTHER, UNITS_BYTES,
-                            mLockedCost,
-                            "Estimated memory used by locked surfaces in the "
-                            "imagelib surface cache.");
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return NS_OK;
-  }
-
-  size_t SizeOfSurfaces(const ImageKey    aImageKey,
-                        gfxMemoryLocation aLocation,
-                        MallocSizeOf      aMallocSizeOf)
-  {
-    nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
-    if (!cache) {
-      return 0;  // No surfaces for this image.
-    }
-
-    // Sum the size of all surfaces in the per-image cache.
-    CachedSurface::SizeOfSurfacesSum sum(aLocation, aMallocSizeOf);
-    cache->ForEach(DoSizeOfSurfacesSum, &sum);
-
-    return sum.Result();
-  }
-
-  static PLDHashOperator DoSizeOfSurfacesSum(const SurfaceKey&,
-                                             CachedSurface*    aSurface,
-                                             void*             aSum)
-  {
-    auto sum = static_cast<CachedSurface::SizeOfSurfacesSum*>(aSum);
-    sum->Add(aSurface);
-    return PL_DHASH_NEXT;
+    return mMaxCost - mAvailableCost;
   }
 
 private:
   already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
   {
     nsRefPtr<ImageSurfaceCache> imageCache;
     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
     return imageCache.forget();
   }
 
-  // This is similar to CanHold() except that it takes into account the costs of
-  // locked surfaces. It's used internally in Insert(), but it's not exposed
-  // publicly because if we start permitting multithreaded access to the surface
-  // cache, which seems likely, then the result would be meaningless: another
-  // thread could insert a persistent surface or lock an image at any time.
-  bool CanHoldAfterDiscarding(const Cost aCost) const
-  {
-    return aCost + mLockedCost <= mMaxCost;
-  }
-
   struct SurfaceTracker : public nsExpirationTracker<CachedSurface, 2>
   {
     SurfaceTracker(SurfaceCacheImpl* aCache, uint32_t aSurfaceCacheExpirationTimeMS)
       : nsExpirationTracker<CachedSurface, 2>(aSurfaceCacheExpirationTimeMS)
       , mCache(aCache)
     { }
 
   protected:
@@ -635,19 +437,17 @@ private:
   private:
     SurfaceCacheImpl* const mCache;  // Weak pointer to owner.
   };
 
   struct MemoryPressureObserver : public nsIObserver
   {
     NS_DECL_ISUPPORTS
 
-    NS_IMETHOD Observe(nsISupports*,
-                       const char* aTopic,
-                       const char16_t*) MOZ_OVERRIDE
+    NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*)
     {
       if (sInstance && strcmp(aTopic, "memory-pressure") == 0) {
         sInstance->DiscardAll();
       }
       return NS_OK;
     }
 
   private:
@@ -656,17 +456,16 @@ private:
 
 
   nsTArray<CostEntry>                                       mCosts;
   nsRefPtrHashtable<nsPtrHashKey<Image>, ImageSurfaceCache> mImageCaches;
   SurfaceTracker                                            mExpirationTracker;
   nsRefPtr<MemoryPressureObserver>                          mMemoryPressureObserver;
   const Cost                                                mMaxCost;
   Cost                                                      mAvailableCost;
-  Cost                                                      mLockedCost;
 };
 
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl, nsIMemoryReporter)
 NS_IMPL_ISUPPORTS(SurfaceCacheImpl::MemoryPressureObserver, nsIObserver)
 
 ///////////////////////////////////////////////////////////////////////////////
 // Public API
 ///////////////////////////////////////////////////////////////////////////////
@@ -723,96 +522,63 @@ SurfaceCache::Lookup(const ImageKey    a
   MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return DrawableFrameRef();
   }
 
   return sInstance->Lookup(aImageKey, aSurfaceKey);
 }
 
-/* static */ bool
+/* static */ void
 SurfaceCache::Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
-                     const SurfaceKey& aSurfaceKey,
-                     Lifetime          aLifetime)
+                     const SurfaceKey& aSurfaceKey)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!sInstance) {
-    return false;
+  if (sInstance) {
+    Cost cost = ComputeCost(aSurfaceKey.Size());
+    sInstance->Insert(aSurface, aSurfaceKey.Size(), cost, aImageKey,
+                      aSurfaceKey);
   }
-
-  Cost cost = ComputeCost(aSurfaceKey.Size());
-  return sInstance->Insert(aSurface, cost, aImageKey, aSurfaceKey, aLifetime);
 }
 
 /* static */ bool
 SurfaceCache::CanHold(const IntSize& aSize)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!sInstance) {
     return false;
   }
 
   Cost cost = ComputeCost(aSize);
   return sInstance->CanHold(cost);
 }
 
 /* static */ void
-SurfaceCache::LockImage(Image* aImageKey)
+SurfaceCache::RemoveIfPresent(const ImageKey    aImageKey,
+                              const SurfaceKey& aSurfaceKey)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
-    return sInstance->LockImage(aImageKey);
+    sInstance->RemoveIfPresent(aImageKey, aSurfaceKey);
   }
 }
 
 /* static */ void
-SurfaceCache::UnlockImage(Image* aImageKey)
+SurfaceCache::Discard(Image* aImageKey)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
-    return sInstance->UnlockImage(aImageKey);
-  }
-}
-
-/* static */ void
-SurfaceCache::RemoveSurface(const ImageKey    aImageKey,
-                            const SurfaceKey& aSurfaceKey)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (sInstance) {
-    sInstance->RemoveSurface(aImageKey, aSurfaceKey);
-  }
-}
-
-/* static */ void
-SurfaceCache::RemoveImage(Image* aImageKey)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (sInstance) {
-    sInstance->RemoveImage(aImageKey);
+    sInstance->Discard(aImageKey);
   }
 }
 
 /* static */ void
 SurfaceCache::DiscardAll()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (sInstance) {
     sInstance->DiscardAll();
   }
 }
 
-/* static */ size_t
-SurfaceCache::SizeOfSurfaces(const ImageKey    aImageKey,
-                             gfxMemoryLocation aLocation,
-                             MallocSizeOf      aMallocSizeOf)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  if (!sInstance) {
-    return 0;
-  }
-
-  return sInstance->SizeOfSurfaces(aImageKey, aLocation, aMallocSizeOf);
-}
-
 } // namespace image
 } // namespace mozilla
--- a/image/src/SurfaceCache.h
+++ b/image/src/SurfaceCache.h
@@ -6,25 +6,23 @@
 /**
  * SurfaceCache is a service for caching temporary surfaces and decoded image
  * data in imagelib.
  */
 
 #ifndef MOZILLA_IMAGELIB_SURFACECACHE_H_
 #define MOZILLA_IMAGELIB_SURFACECACHE_H_
 
-#include "mozilla/Maybe.h"           // for Maybe
-#include "mozilla/MemoryReporting.h" // for MallocSizeOf
-#include "mozilla/HashFunctions.h"   // for HashGeneric and AddToHash
-#include "gfx2DGlue.h"               // for gfxMemoryLocation
-#include "gfxPoint.h"                // for gfxSize
-#include "nsCOMPtr.h"                // for already_AddRefed
-#include "mozilla/gfx/Point.h"       // for mozilla::gfx::IntSize
-#include "mozilla/gfx/2D.h"          // for SourceSurface
-#include "SVGImageContext.h"         // for SVGImageContext
+#include "mozilla/Maybe.h"          // for Maybe
+#include "mozilla/HashFunctions.h"  // for HashGeneric and AddToHash
+#include "gfxPoint.h"               // for gfxSize
+#include "nsCOMPtr.h"               // for already_AddRefed
+#include "mozilla/gfx/Point.h"      // for mozilla::gfx::IntSize
+#include "mozilla/gfx/2D.h"         // for SourceSurface
+#include "SVGImageContext.h"        // for SVGImageContext
 
 namespace mozilla {
 namespace image {
 
 class DrawableFrameRef;
 class Image;
 class imgFrame;
 
@@ -106,217 +104,117 @@ VectorSurfaceKey(const gfx::IntSize& aSi
                  const 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)
-  Transient,
-  Persistent
-MOZ_END_ENUM_CLASS(Lifetime)
-
 /**
  * SurfaceCache is an imagelib-global service that allows caching of temporary
- * surfaces. Surfaces normally expire from the cache automatically if they go
- * too long without being accessed.
+ * surfaces. Surfaces expire from the cache automatically if they go too long
+ * without being accessed.
  *
  * SurfaceCache does not hold surfaces directly; instead, it holds imgFrame
  * objects, which hold surfaces but also layer on additional features specific
  * to imagelib's needs like animation, padding support, and transparent support
  * for volatile buffers.
  *
- * Sometime it's useful to temporarily prevent surfaces from expiring from the
- * cache. This is most often because losing the data could harm the user
- * experience (for example, we often don't want to allow surfaces that are
- * currently visible to expire) or because it's not possible to rematerialize
- * the surface. SurfaceCache supports this through the use of image locking and
- * surface lifetimes; see the comments for Insert() and LockImage() for more
- * details.
- *
- * Any image which stores surfaces in the SurfaceCache *must* ensure that it
- * calls RemoveImage() before it is destroyed. See the comments for
- * RemoveImage() for more details.
- *
  * SurfaceCache is not thread-safe; it should only be accessed from the main
  * thread.
  */
 struct SurfaceCache
 {
   typedef gfx::IntSize IntSize;
 
-  /**
+  /*
    * Initialize static data. Called during imagelib module initialization.
    */
   static void Initialize();
 
-  /**
+  /*
    * Release static data. Called during imagelib module shutdown.
    */
   static void Shutdown();
 
-  /**
+  /*
    * Look up the imgFrame containing a surface in the cache and returns a
    * drawable reference to that imgFrame.
    *
    * If the imgFrame was found in the cache, but had stored its surface in a
    * volatile buffer which was discarded by the OS, then it is automatically
-   * removed from the cache and an empty DrawableFrameRef is returned. Note that
-   * this will never happen to persistent surfaces associated with a locked
-   * image; the cache keeps a strong reference to such surfaces internally.
+   * removed from the cache and an empty DrawableFrameRef is returned.
    *
    * @param aImageKey    Key data identifying which image the surface belongs to.
    * @param aSurfaceKey  Key data which uniquely identifies the requested surface.
    *
    * @return a DrawableFrameRef to the imgFrame wrapping the requested surface,
    *         or an empty DrawableFrameRef if not found.
    */
   static DrawableFrameRef Lookup(const ImageKey    aImageKey,
                                  const SurfaceKey& aSurfaceKey);
 
-  /**
+  /*
    * Insert a surface into the cache. It is an error to call this function
    * without first calling Lookup to verify that the surface is not already in
    * the cache.
    *
-   * Each surface in the cache has a lifetime, either Transient or Persistent.
-   * Transient surfaces can expire from the cache at any time. Persistent
-   * surfaces can ordinarily also expire from the cache at any time, but if the
-   * image they're associated with is locked, then these surfaces will never
-   * expire. This means that surfaces which cannot be rematerialized should be
-   * inserted with a persistent lifetime *after* the image is locked with
-   * LockImage(); if you use the other order, the surfaces might expire before
-   * LockImage() gets called.
-   *
-   * If a surface cannot be rematerialized, it may be important to know whether
-   * it was inserted into the cache successfully. Insert() returns false if it
-   * failed to insert the surface, which could happen because of capacity
-   * reasons, or because it was already freed by the OS. If you aren't inserting
-   * a surface with persistent lifetime, or if the surface isn't associated with
-   * a locked image, the return value is useless: the surface might expire
-   * immediately after being inserted, even though Insert() returned true. Thus,
-   * most callers do not need to check the return value.
-   *
    * @param aTarget      The new surface (wrapped in an imgFrame) to insert into
    *                     the cache.
    * @param aImageKey    Key data identifying which image the surface belongs to.
    * @param aSurfaceKey  Key data which uniquely identifies the requested surface.
-   * @param aLifetime    Whether this is a transient surface that can always be
-   *                     allowed to expire, or a persistent surface that
-   *                     shouldn't expire if the image is locked.
-   * @return false if the surface could not be inserted. Only check this if
-   *         inserting a persistent surface associated with a locked image (see
-   *         above for more information).
    */
-  static bool Insert(imgFrame*         aSurface,
+  static void Insert(imgFrame*         aSurface,
                      const ImageKey    aImageKey,
-                     const SurfaceKey& aSurfaceKey,
-                     Lifetime          aLifetime);
+                     const SurfaceKey& aSurfaceKey);
 
-  /**
+  /*
    * Checks if a surface of a given size could possibly be stored in the cache.
    * If CanHold() returns false, Insert() will always fail to insert the
    * surface, but the inverse is not true: Insert() may take more information
    * into account than just image size when deciding whether to cache the
    * surface, so Insert() may still fail even if CanHold() returns true.
    *
    * Use CanHold() to avoid the need to create a temporary surface when we know
    * for sure the cache can't hold it.
    *
    * @param aSize  The dimensions of a surface in pixels.
    *
    * @return false if the surface cache can't hold a surface of that size.
    */
   static bool CanHold(const IntSize& aSize);
 
-  /**
-   * Locks an image, preventing any of that image's surfaces from expiring
-   * unless they have a transient lifetime.
-   *
-   * Regardless of locking, any of an image's surfaces may be removed using
-   * RemoveSurface(), and all of an image's surfaces are removed by
-   * RemoveImage(), whether the image is locked or not.
-   *
-   * It's safe to call LockImage() on an image that's already locked; this has
-   * no effect.
-   *
-   * You must always unlock any image you lock. You may do this explicitly by
-   * calling UnlockImage(), or implicitly by calling RemoveImage(). Since you're
-   * required to call RemoveImage() when you destroy an image, this doesn't
-   * impose any additional requirements, but it's preferable to call
-   * UnlockImage() earlier if it's possible.
-   *
-   * @param aImageKey    The image to lock.
-   */
-  static void LockImage(const ImageKey aImageKey);
-
-  /**
-   * Unlocks an image, allowing any of its surfaces to expire at any time.
-   *
-   * It's OK to call UnlockImage() on an image that's already unlocked; this has
-   * no effect.
-   *
-   * @param aImageKey    The image to lock.
-   */
-  static void UnlockImage(const ImageKey aImageKey);
-
-  /**
-   * Removes a surface from the cache, if it's present. If it's not present,
-   * RemoveSurface() has no effect.
+  /*
+   * Removes a surface from the cache, if it's present.
    *
    * Use this function to remove individual surfaces that have become invalid.
-   * Prefer RemoveImage() or DiscardAll() when they're applicable, as they have
-   * much better performance than calling this function repeatedly.
+   * Prefer Discard() or DiscardAll() when they're applicable, as they have much
+   * better performance than calling this function repeatedly.
    *
    * @param aImageKey    Key data identifying which image the surface belongs to.
    * @param aSurfaceKey  Key data which uniquely identifies the requested surface.
    */
-  static void RemoveSurface(const ImageKey    aImageKey,
-                            const SurfaceKey& aSurfaceKey);
-
-  /**
-   * Removes all cached surfaces associated with the given image from the cache.
-   * If the image is locked, it is automatically unlocked.
-   *
-   * This MUST be called, at a minimum, when an Image which could be storing
-   * surfaces in the surface cache is destroyed. If another image were allocated
-   * at the same address it could result in subtle, difficult-to-reproduce bugs.
+  static void RemoveIfPresent(const ImageKey    aImageKey,
+                              const SurfaceKey& aSurfaceKey);
+  /*
+   * Evicts any cached surfaces associated with the given image from the cache.
+   * This MUST be called, at a minimum, when the image is destroyed. If
+   * another image were allocated at the same address it could result in
+   * subtle, difficult-to-reproduce bugs.
    *
    * @param aImageKey  The image which should be removed from the cache.
    */
-  static void RemoveImage(const ImageKey aImageKey);
+  static void Discard(const ImageKey aImageKey);
 
-  /**
-   * Evicts all evictable surfaces from the cache.
-   *
-   * All surfaces are evictable except for persistent surfaces associated with
-   * locked images. Non-evictable surfaces can only be removed by
-   * RemoveSurface() or RemoveImage().
+  /*
+   * Evicts all caches surfaces from ths cache.
    */
   static void DiscardAll();
 
-  /**
-   * Computes the size of the surfaces stored for the given image at the given
-   * memory location.
-   *
-   * This is intended for use with memory reporting.
-   *
-   * @param aImageKey     The image to report memory usage for.
-   * @param aLocation     The location (heap, nonheap, etc.) of the memory to
-   *                      report on.
-   * @param aMallocSizeOf A fallback malloc memory reporting function. This
-   *                      should be null unless we're reporting on in-process
-   *                      heap memory.
-   */
-  static size_t SizeOfSurfaces(const ImageKey    aImageKey,
-                               gfxMemoryLocation aLocation,
-                               MallocSizeOf      aMallocSizeOf);
-
 private:
   virtual ~SurfaceCache() = 0;  // Forbid instantiation.
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // MOZILLA_IMAGELIB_SURFACECACHE_H_
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -333,17 +333,17 @@ VectorImage::VectorImage(imgStatusTracke
   mHasPendingInvalidation(false)
 {
   mStatusTrackerInit = new imgStatusTrackerInit(this, aStatusTracker);
 }
 
 VectorImage::~VectorImage()
 {
   CancelAllListeners();
-  SurfaceCache::RemoveImage(ImageKey(this));
+  SurfaceCache::Discard(this);
 }
 
 //------------------------------------------------------------------------------
 // Methods inherited from Image.h
 
 nsresult
 VectorImage::Init(const char* aMimeType,
                   uint32_t aFlags)
@@ -379,40 +379,35 @@ VectorImage::HeapSizeOfSourceWithCompute
 }
 
 size_t
 VectorImage::HeapSizeOfDecodedWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   // If implementing this, we'll need to restructure our callers to make sure
   // any amount we return is attributed to the vector images measure (i.e.
   // "explicit/images/{content,chrome}/vector/{used,unused}/...")
-  // XXX(seth): Same goes for the other *SizeOfDecoded() methods. We'll do this
-  // in bug 921300 or one of its blockers. For now it seems worthwhile to get
-  // this memory accounted for, even if it gets listed under 'raster'. It does
-  // make some perverse sense, since we are after all reporting on raster data
-  // here - it just happens to be computed from a vector document.
-  return SurfaceCache::SizeOfSurfaces(ImageKey(this),
-                                      gfxMemoryLocation::IN_PROCESS_HEAP,
-                                      aMallocSizeOf);
+  return 0;
 }
 
 size_t
 VectorImage::NonHeapSizeOfDecoded() const
 {
-  return SurfaceCache::SizeOfSurfaces(ImageKey(this),
-                                      gfxMemoryLocation::IN_PROCESS_NONHEAP,
-                                      nullptr);
+  // If implementing this, we'll need to restructure our callers to make sure
+  // any amount we return is attributed to the vector images measure (i.e.
+  // "explicit/images/{content,chrome}/vector/{used,unused}/...")
+  return 0;
 }
 
 size_t
 VectorImage::OutOfProcessSizeOfDecoded() const
 {
-  return SurfaceCache::SizeOfSurfaces(ImageKey(this),
-                                      gfxMemoryLocation::OUT_OF_PROCESS,
-                                      nullptr);
+  // If implementing this, we'll need to restructure our callers to make sure
+  // any amount we return is attributed to the vector images measure (i.e.
+  // "explicit/images/{content,chrome}/vector/{used,unused}/...")
+  return 0;
 }
 
 MOZ_DEFINE_MALLOC_SIZE_OF(WindowsMallocSizeOf);
 
 size_t
 VectorImage::HeapSizeOfVectorImageDocument(nsACString* aDocURL) const
 {
   nsIDocument* doc = mSVGDocumentWrapper->GetDocument();
@@ -570,17 +565,17 @@ VectorImage::SendInvalidationNotificatio
   // InvalidateObserversOnNextRefreshDriverTick, because RequestRefresh is never
   // called for them. Ordinarily this isn't needed, since we send out
   // invalidation notifications in OnSVGDocumentLoaded, but in rare cases the
   // SVG document may not be 100% ready to render at that time. In those cases
   // we would miss the subsequent invalidations if we didn't send out the
   // notifications directly in |InvalidateObservers...|.
 
   if (mStatusTracker) {
-    SurfaceCache::RemoveImage(ImageKey(this));
+    SurfaceCache::Discard(this);
     mStatusTracker->FrameChanged(&nsIntRect::GetMaxSizedIntRect());
     mStatusTracker->OnStopFrame();
   }
 }
 
 NS_IMETHODIMP_(nsIntRect)
 VectorImage::GetImageSpaceInvalidationRect(const nsIntRect& aRect)
 {
@@ -914,18 +909,17 @@ VectorImage::CreateSurfaceAndShow(const 
   RefPtr<SourceSurface> surface = frame->GetSurface();
   if (!surface)
     return Show(svgDrawable, aParams);
 
   // Attempt to cache the frame.
   SurfaceCache::Insert(frame, ImageKey(this),
                        VectorSurfaceKey(aParams.size,
                                         aParams.svgContext,
-                                        aParams.animationTime),
-                       Lifetime::Transient);
+                                        aParams.animationTime));
 
   // Draw.
   nsRefPtr<gfxDrawable> drawable =
     new gfxSurfaceDrawable(surface, ThebesIntSize(aParams.size));
   Show(drawable, aParams);
 }
 
 
@@ -983,17 +977,17 @@ VectorImage::UnlockImage()
   return NS_OK;
 }
 
 //******************************************************************************
 /* void requestDiscard() */
 NS_IMETHODIMP
 VectorImage::RequestDiscard()
 {
-  SurfaceCache::RemoveImage(ImageKey(this));
+  SurfaceCache::Discard(this);
   return NS_OK;
 }
 
 //******************************************************************************
 /* void resetAnimation (); */
 NS_IMETHODIMP
 VectorImage::ResetAnimation()
 {
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -118,16 +118,17 @@ imgFrame::imgFrame() :
   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;
     }
@@ -409,17 +410,19 @@ 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.
-  mImageSurface = nullptr;
+  if (mDiscardable) {
+    mImageSurface = nullptr;
+  }
 #endif
 
   return NS_OK;
 }
 
 DrawableFrameRef
 imgFrame::DrawableRef()
 {
@@ -578,27 +581,18 @@ imgFrame::GetStride() const
 
 SurfaceFormat imgFrame::GetFormat() const
 {
   return mFormat;
 }
 
 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.
-  if (mFormat == SurfaceFormat::B8G8R8A8 && !mHasNoAlpha) {
-    return true;
-  }
-
-  return false;
+  // We need a background painted if we have alpha or we're incomplete.
+  return (mFormat == SurfaceFormat::B8G8R8A8 || !ImageComplete());
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
   if (mVBuf)
     return mSize.width * BytesPerPixel(mFormat);
 
   if (mPaletteDepth)
@@ -660,26 +654,16 @@ void imgFrame::GetPaletteData(uint32_t *
 uint32_t* imgFrame::GetPaletteData() const
 {
   uint32_t* data;
   uint32_t length;
   GetPaletteData(&data, &length);
   return data;
 }
 
-uint8_t*
-imgFrame::GetRawData() const
-{
-  MOZ_ASSERT(mLockCount, "Should be locked to call GetRawData()");
-  if (mPalettedImageData) {
-    return mPalettedImageData;
-  }
-  return GetImageData();
-}
-
 nsresult imgFrame::LockImageData()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
   if (mLockCount < 0) {
     return NS_ERROR_FAILURE;
   }
@@ -787,16 +771,23 @@ 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
@@ -59,18 +59,19 @@ public:
 
   /**
    * Initialize this imgFrame with a new surface and draw the provided
    * gfxDrawable into it.
    *
    * This is appropriate to use when drawing content into an imgFrame, as it
    * uses the same graphics backend as normal content drawing. The downside is
    * that the underlying surface may not be stored in a volatile buffer on all
-   * platforms, and raw access to the surface (using RawAccessRef()) may be much
-   * more expensive than in the InitForDecoder() case.
+   * platforms, and raw access to the surface (using RawAccessRef() or
+   * LockImageData()) may be much more expensive than in the InitForDecoder()
+   * case.
    */
   nsresult InitWithDrawable(gfxDrawable* aDrawable,
                             const nsIntSize& aSize,
                             const SurfaceFormat aFormat,
                             GraphicsFilter aFilter,
                             uint32_t aImageFlags);
 
   DrawableFrameRef DrawableRef();
@@ -91,33 +92,36 @@ public:
   uint32_t GetImageBytesPerRow() const;
   uint32_t GetImageDataLength() const;
   bool GetIsPaletted() const;
   bool GetHasAlpha() const;
   void GetImageData(uint8_t **aData, uint32_t *length) const;
   uint8_t* GetImageData() const;
   void GetPaletteData(uint32_t **aPalette, uint32_t *length) const;
   uint32_t* GetPaletteData() const;
-  uint8_t* GetRawData() const;
 
   int32_t GetRawTimeout() const;
   void SetRawTimeout(int32_t aTimeout);
 
   int32_t GetFrameDisposalMethod() const;
   void SetFrameDisposalMethod(int32_t aFrameDisposalMethod);
   int32_t GetBlendMethod() const;
   void SetBlendMethod(int32_t aBlendMethod);
   bool ImageComplete() const;
 
   void SetHasNoAlpha();
   void SetAsNonPremult(bool aIsNonPremult);
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
+  nsresult LockImageData();
+  nsresult UnlockImageData();
+
+  void SetDiscardable();
   void SetOptimizable();
 
   TemporaryRef<SourceSurface> GetSurface();
   TemporaryRef<DrawTarget> GetDrawTarget();
 
   Color
   SinglePixelColor()
   {
@@ -142,18 +146,16 @@ public:
 
     return ((1 << mPaletteDepth) * sizeof(uint32_t));
   }
 
 private: // methods
 
   ~imgFrame();
 
-  nsresult LockImageData();
-  nsresult UnlockImageData();
   nsresult Optimize();
 
   struct SurfaceWithFormat {
     nsRefPtr<gfxDrawable> mDrawable;
     SurfaceFormat mFormat;
     SurfaceWithFormat() {}
     SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat)
      : mDrawable(aDrawable), mFormat(aFormat) {}
@@ -200,16 +202,17 @@ 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;
 };
--- a/image/src/moz.build
+++ b/image/src/moz.build
@@ -16,16 +16,17 @@ EXPORTS += [
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'Decoder.cpp',
     'DiscardTracker.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrameBlender.cpp',
+    'FrameSequence.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageFactory.cpp',
     'ImageMetadata.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',
     'imgFrame.cpp',
     'imgTools.cpp',