Bug 962670 - Store decoded images in VolatileBuffers, r=seth,jrmuizel
☠☠ backed out by 088af92c1a44 ☠ ☠
authorMichael Wu <mwu@mozilla.com>
Mon, 24 Feb 2014 22:37:51 -0500
changeset 189620 965fe5b195ab6c8e325b958e9e2a19021c2a4075
parent 189619 63f6e23b1c8bcd15e5cc0f5435eff06e6a986d4e
child 189621 af24dbc2c559553741a9b58847f4399118a92105
push id3503
push userraliiev@mozilla.com
push dateMon, 28 Apr 2014 18:51:11 +0000
treeherdermozilla-beta@c95ac01e332e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersseth, jrmuizel
bugs962670
milestone30.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 962670 - Store decoded images in VolatileBuffers, r=seth,jrmuizel
gfx/layers/ImageContainer.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/imgFrame.cpp
image/src/imgFrame.h
--- a/gfx/layers/ImageContainer.h
+++ b/gfx/layers/ImageContainer.h
@@ -21,16 +21,17 @@
 #include "nsAutoRef.h"                  // for nsCountedRef
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for Image::Release, etc
 #include "nsRect.h"                     // for nsIntRect
 #include "nsSize.h"                     // for nsIntSize
 #include "nsTArray.h"                   // for nsTArray
 #include "mozilla/Atomics.h"
+#include "mozilla/WeakPtr.h"
 #include "nsThreadUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "nsDataHashtable.h"
 
 #ifndef XPCOM_GLUE_AVOID_NSPR
 /**
  * We need to be able to hold a reference to a gfxASurface from Image
  * subclasses. This is potentially a problem since Images can be addrefed
@@ -375,17 +376,17 @@ struct RemoteImageData {
  * a layer transaction.
  * 3) Remote. Initiated by calling SetRemoteImageData on the ImageContainer
  * before any other activity.
  * The ImageContainer uses a shared memory block containing a cross-process mutex
  * to communicate with the compositor thread. SetCurrentImage synchronously
  * updates the shared state to point to the new image and the old image
  * is immediately released (not true in Normal or Asynchronous modes).
  */
-class ImageContainer {
+class ImageContainer : public SupportsWeakPtr<ImageContainer> {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ImageContainer)
 public:
 
   enum { DISABLE_ASYNC = 0x0, ENABLE_ASYNC = 0x01 };
 
   ImageContainer(int flag = 0);
 
   ~ImageContainer();
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -917,18 +917,34 @@ RasterImage::GetFrame(uint32_t aWhichFra
 
   nsRefPtr<gfxASurface> framesurf;
 
   // If this frame covers the entire image, we can just reuse its existing
   // surface.
   nsIntRect framerect = frame->GetRect();
   if (framerect.x == 0 && framerect.y == 0 &&
       framerect.width == mSize.width &&
-      framerect.height == mSize.height)
+      framerect.height == mSize.height) {
     frame->GetSurface(getter_AddRefs(framesurf));
+    if (!framesurf && !frame->IsSinglePixel()) {
+      // No reason to be optimized away here - the OS threw out the data
+      if (!(aFlags & FLAG_SYNC_DECODE))
+        return nullptr;
+
+      // Unconditionally call ForceDiscard() here because GetSurface can only
+      // return null when we can forcibly discard and redecode. There are two
+      // other cases where GetSurface() can return null - when it is a single
+      // pixel image, which we check before getting here, or when this is an
+      // indexed image, in which case we shouldn't be in this function at all.
+      // The only remaining possibility is that SetDiscardable() was called on
+      // this imgFrame, which implies the image can be redecoded.
+      ForceDiscard();
+      return GetFrame(aWhichFrame, aFlags);
+    }
+  }
 
   // The image doesn't have a surface because it's been optimized away. Create
   // one.
   if (!framesurf) {
     nsRefPtr<gfxImageSurface> imgsurf;
     CopyFrame(aWhichFrame, aFlags, getter_AddRefs(imgsurf));
     framesurf = imgsurf;
   }
@@ -942,17 +958,25 @@ RasterImage::GetCurrentImage()
   if (!mDecoded) {
     // We can't call StartDecoding because that can synchronously notify
     // which can cause DOM modification
     RequestDecodeCore(ASYNCHRONOUS);
     return nullptr;
   }
 
   nsRefPtr<gfxASurface> imageSurface = GetFrame(FRAME_CURRENT, FLAG_NONE);
-  NS_ENSURE_TRUE(imageSurface, nullptr);
+  if (!imageSurface) {
+    // The OS threw out some or all of our buffer. Start decoding again.
+    // GetFrame will only return null in the case that the image was
+    // discarded. We already checked that the image is decoded, so other
+    // error paths are not possible.
+    ForceDiscard();
+    RequestDecodeCore(ASYNCHRONOUS);
+    return nullptr;
+  }
 
   if (!mImageContainer) {
     mImageContainer = LayerManager::CreateImageContainer();
   }
 
   CairoImage::Data cairoData;
   cairoData.mDeprecatedSurface = imageSurface;
   GetWidth(&cairoData.mSize.width);
@@ -976,30 +1000,41 @@ RasterImage::GetImageContainer(LayerMana
     *_retval = nullptr;
     return NS_OK;
   }
 
   if (IsUnlocked() && mStatusTracker) {
     mStatusTracker->OnUnlockedDraw();
   }
 
+  if (!mImageContainer) {
+    mImageContainer = mImageContainerCache;
+  }
+
   if (mImageContainer) {
     *_retval = mImageContainer;
     NS_ADDREF(*_retval);
     return NS_OK;
   }
 
   nsRefPtr<layers::Image> image = GetCurrentImage();
   if (!image) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   mImageContainer->SetCurrentImageInTransaction(image);
 
   *_retval = mImageContainer;
   NS_ADDREF(*_retval);
+  // We only need to be careful about holding on to the image when it is
+  // discardable by the OS.
+  if (CanForciblyDiscardAndRedecode()) {
+    mImageContainerCache = mImageContainer->asWeakPtr();
+    mImageContainer = nullptr;
+  }
+
   return NS_OK;
 }
 
 void
 RasterImage::UpdateImageContainer()
 {
   if (!mImageContainer || IsInUpdateImageContainer()) {
     return;
@@ -1387,16 +1422,22 @@ RasterImage::DecodingComplete()
   }
 
   // If there's only 1 frame, optimize it. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
   if ((GetNumFrames() == 1) && !mMultipart) {
+    // CanForciblyDiscard is used instead of CanForciblyDiscardAndRedecode
+    // because we know decoding is complete at this point and this is not
+    // an animation
+    if (DiscardingEnabled() && CanForciblyDiscard()) {
+      mFrameBlender.RawGetFrame(0)->SetDiscardable();
+    }
     rv = mFrameBlender.RawGetFrame(0)->Optimize();
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Double-buffer our frame in the multipart case, since we'll start decoding
   // into the first frame again immediately and this produces severe tearing.
   if (mMultipart) {
     if (GetNumFrames() == 1) {
@@ -2088,35 +2129,35 @@ RasterImage::ShutdownDecoder(eShutdownIn
                     "Invalid shutdown intent");
 
   // Ensure that the decoder is initialized
   NS_ABORT_IF_FALSE(mDecoder, "Calling ShutdownDecoder() with no active decoder!");
 
   // Figure out what kind of decode we were doing before we get rid of our decoder
   bool wasSizeDecode = mDecoder->IsSizeDecode();
 
-  // 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) {
-    imgFrame *curframe = mFrameBlender.RawGetFrame(GetNumFrames() - 1);
-    curframe->UnlockImageData();
-  }
-
   // Finalize the decoder
   // null out mDecoder, _then_ check for errors on the close (otherwise the
   // error routine might re-invoke ShutdownDecoder)
   nsRefPtr<Decoder> decoder = mDecoder;
   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) {
+    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;
@@ -2672,16 +2713,27 @@ RasterImage::Draw(gfxContext *aContext,
 
   uint32_t frameIndex = aWhichFrame == FRAME_FIRST ? 0
                                                    : GetCurrentImgFrameIndex();
   imgFrame* frame = GetDrawableImgFrame(frameIndex);
   if (!frame) {
     return NS_OK; // Getting the frame (above) touches the image and kicks off decoding
   }
 
+  nsRefPtr<gfxASurface> surf;
+  if (!frame->IsSinglePixel()) {
+    frame->GetSurface(getter_AddRefs(surf));
+    if (!surf) {
+      // The OS threw out some or all of our buffer. Start decoding again.
+      ForceDiscard();
+      WantDecodedFrames();
+      return NS_OK;
+    }
+  }
+
   DrawWithPreDownscaleIfNeeded(frame, aContext, aFilter, aUserSpaceToImageSpace, aFill, aSubimage, 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();
   }
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -654,16 +654,19 @@ private: // data
   nsIntSize                  mRequestedResolution;
 
   // A hint for image decoder that directly scale the image to smaller buffer
   int                        mRequestedSampleSize;
 
   // Cached value for GetImageContainer.
   nsRefPtr<mozilla::layers::ImageContainer> mImageContainer;
 
+  // If not cached in mImageContainer, this might have our image container
+  WeakPtr<mozilla::layers::ImageContainer> mImageContainerCache;
+
 #ifdef DEBUG
   uint32_t                       mFramesNotified;
 #endif
 
   // Below are the pieces of data that can be accessed on more than one thread
   // at once, and hence need to be locked by mDecodingMonitor.
 
   // BEGIN LOCKED MEMBER VARIABLES
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -7,16 +7,17 @@
 #include "imgFrame.h"
 #include "DiscardTracker.h"
 
 #include "prenv.h"
 
 #include "gfx2DGlue.h"
 #include "gfxPlatform.h"
 #include "gfxUtils.h"
+#include "gfxAlphaRecovery.h"
 
 static bool gDisableOptimize = false;
 
 #include "cairo.h"
 #include "GeckoProfiler.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "nsMargin.h"
@@ -30,16 +31,58 @@ static bool gDisableOptimize = false;
 #define USE_WIN_SURFACE 1
 
 #endif
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 
+static cairo_user_data_key_t kVolatileBuffer;
+
+static void
+VolatileBufferRelease(void *vbuf)
+{
+  delete static_cast<VolatileBufferPtr<unsigned char>*>(vbuf);
+}
+
+gfxImageSurface *
+LockedImageSurface::CreateSurface(VolatileBuffer *vbuf,
+                                  const gfxIntSize& size,
+                                  gfxImageFormat format)
+{
+  VolatileBufferPtr<unsigned char> *vbufptr =
+    new VolatileBufferPtr<unsigned char>(vbuf);
+  MOZ_ASSERT(!vbufptr->WasBufferPurged(), "Expected image data!");
+
+  long stride = gfxImageSurface::ComputeStride(size, format);
+  gfxImageSurface *img = new gfxImageSurface(*vbufptr, size, stride, format);
+  if (!img || img->CairoStatus()) {
+    delete img;
+    delete vbufptr;
+    return nullptr;
+  }
+
+  img->SetData(&kVolatileBuffer, vbufptr, VolatileBufferRelease);
+  return img;
+}
+
+TemporaryRef<VolatileBuffer>
+LockedImageSurface::AllocateBuffer(const gfxIntSize& size,
+                                   gfxImageFormat format)
+{
+  long stride = gfxImageSurface::ComputeStride(size, format);
+  RefPtr<VolatileBuffer> buf = new VolatileBuffer();
+  if (buf->Init(stride * size.height,
+                1 << gfxAlphaRecovery::GoodAlignmentLog2()))
+    return buf;
+
+  return nullptr;
+}
+
 // Returns true if an image of aWidth x aHeight is allowed and legal.
 static bool AllowedImageSize(int32_t aWidth, int32_t aHeight)
 {
   // reject over-wide or over-tall images
   const int32_t k64KLimit = 0x0000FFFF;
   if (MOZ_UNLIKELY(aWidth > k64KLimit || aHeight > k64KLimit )) {
     NS_WARNING("image too big");
     return false;
@@ -103,16 +146,17 @@ imgFrame::imgFrame() :
   mTimeout(100),
   mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mLockCount(0),
   mBlendMethod(1), /* imgIContainer::kBlendOver */
   mSinglePixel(false),
   mFormatChanged(false),
   mCompositingFailed(false),
   mNonPremult(false),
+  mDiscardable(false),
   mInformedDiscardTracker(false),
   mDirty(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
@@ -169,22 +213,27 @@ nsresult imgFrame::Init(int32_t aX, int3
         // no error
         mImageSurface = mWinSurface->GetAsImageSurface();
       } else {
         mWinSurface = nullptr;
       }
     }
 #endif
 
-    // For other platforms we create the image surface first and then
-    // possibly wrap it in a device surface.  This branch is also used
-    // on Windows if we're not using device surfaces or if we couldn't
-    // create one.
-    if (!mImageSurface)
-      mImageSurface = new gfxImageSurface(gfxIntSize(mSize.width, mSize.height), mFormat);
+    // For other platforms, space for the image surface is first allocated in
+    // a volatile buffer and then wrapped by a LockedImageSurface.
+    // This branch is also used on Windows if we're not using device surfaces
+    // or if we couldn't create one.
+    if (!mImageSurface) {
+      mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat);
+      if (!mVBuf) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
+      mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
+    }
 
     if (!mImageSurface || mImageSurface->CairoStatus()) {
       mImageSurface = nullptr;
       // guess
       if (!mImageSurface) {
         NS_WARNING("Allocation of gfxImageSurface should succeed");
       } else if (!mImageSurface->CairoStatus()) {
         NS_WARNING("gfxImageSurface should have good CairoStatus");
@@ -246,16 +295,17 @@ nsresult imgFrame::Optimize()
         if (mFormat == gfxImageFormat::ARGB32)
           inputType = gfxRGBA::PACKED_ARGB_PREMULTIPLIED;
 
         mSinglePixelColor = gfxRGBA(firstPixel, inputType);
 
         mSinglePixel = true;
 
         // blow away the older surfaces (if they exist), to release their memory
+        mVBuf = nullptr;
         mImageSurface = nullptr;
         mOptSurface = nullptr;
 #ifdef USE_WIN_SURFACE
         mWinSurface = nullptr;
 #endif
 #ifdef XP_MACOSX
         mQuartzSurface = nullptr;
 #endif
@@ -295,16 +345,17 @@ nsresult imgFrame::Optimize()
     mQuartzSurface->Flush();
   }
 #endif
 
   if (mOptSurface == nullptr)
     mOptSurface = gfxPlatform::GetPlatform()->OptimizeImage(mImageSurface, mFormat);
 
   if (mOptSurface) {
+    mVBuf = nullptr;
     mImageSurface = nullptr;
 #ifdef USE_WIN_SURFACE
     mWinSurface = nullptr;
 #endif
 #ifdef XP_MACOSX
     mQuartzSurface = nullptr;
 #endif
   }
@@ -478,20 +529,23 @@ bool imgFrame::GetNeedsBackground() cons
   return (mFormat == gfxImageFormat::ARGB32 || !ImageComplete());
 }
 
 uint32_t imgFrame::GetImageBytesPerRow() const
 {
   if (mImageSurface)
     return mImageSurface->Stride();
 
+  if (mVBuf)
+    return gfxImageSurface::ComputeStride(mSize, mFormat);
+
   if (mPaletteDepth)
     return mSize.width;
 
-  NS_ERROR("GetImageBytesPerRow called with mImageSurface == null and mPaletteDepth == 0");
+  NS_ERROR("GetImageBytesPerRow called with mImageSurface == null, mVBuf == null and mPaletteDepth == 0");
 
   return 0;
 }
 
 uint32_t imgFrame::GetImageDataLength() const
 {
   return GetImageBytesPerRow() * mSize.height;
 }
@@ -564,50 +618,70 @@ nsresult imgFrame::LockImageData()
   if (mLockCount != 1) {
     return NS_OK;
   }
 
   // Paletted images don't have surfaces, so there's nothing to do.
   if (mPalettedImageData)
     return NS_OK;
 
-  if ((mOptSurface || mSinglePixel) && !mImageSurface) {
-    // Recover the pixels
-    mImageSurface = new gfxImageSurface(gfxIntSize(mSize.width, mSize.height),
-                                        gfxImageFormat::ARGB32);
-    if (!mImageSurface || mImageSurface->CairoStatus())
-      return NS_ERROR_OUT_OF_MEMORY;
+  if (!mImageSurface) {
+    if (mVBuf) {
+      VolatileBufferPtr<uint8_t> ref(mVBuf);
+      if (ref.WasBufferPurged())
+        return NS_ERROR_FAILURE;
+
+      mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
+      if (!mImageSurface || mImageSurface->CairoStatus())
+        return NS_ERROR_OUT_OF_MEMORY;
+    } else if (mOptSurface || mSinglePixel) {
+      // Recover the pixels
+      mVBuf = LockedImageSurface::AllocateBuffer(mSize, mFormat);
+      if (!mVBuf) {
+        return NS_ERROR_OUT_OF_MEMORY;
+      }
 
-    gfxContext context(mImageSurface);
-    context.SetOperator(gfxContext::OPERATOR_SOURCE);
-    if (mSinglePixel)
-      context.SetDeviceColor(mSinglePixelColor);
-    else
-      context.SetSource(mOptSurface);
-    context.Paint();
+      mImageSurface = LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
+      if (!mImageSurface || mImageSurface->CairoStatus())
+        return NS_ERROR_OUT_OF_MEMORY;
 
-    mOptSurface = nullptr;
+      gfxContext context(mImageSurface);
+      context.SetOperator(gfxContext::OPERATOR_SOURCE);
+      if (mSinglePixel)
+        context.SetDeviceColor(mSinglePixelColor);
+      else
+        context.SetSource(mOptSurface);
+      context.Paint();
+
+      mOptSurface = nullptr;
 #ifdef USE_WIN_SURFACE
-    mWinSurface = nullptr;
+      mWinSurface = nullptr;
 #endif
 #ifdef XP_MACOSX
-    mQuartzSurface = nullptr;
+      mQuartzSurface = nullptr;
 #endif
+    }
   }
 
   // We might write to the bits in this image surface, so we need to make the
   // surface ready for that.
   if (mImageSurface)
     mImageSurface->Flush();
 
 #ifdef USE_WIN_SURFACE
   if (mWinSurface)
     mWinSurface->Flush();
 #endif
 
+#ifdef XP_MACOSX
+  if (!mQuartzSurface && !ShouldUseImageSurfaces()) {
+    mQuartzSurface = new gfxQuartzImageSurface(mImageSurface);
+  }
+#endif
+
   return NS_OK;
 }
 
 nsresult imgFrame::UnlockImageData()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!");
@@ -653,16 +727,23 @@ nsresult imgFrame::UnlockImageData()
 
 #ifdef XP_MACOSX
   // The quartz image surface (ab)uses the flush method to get the
   // cairo_image_surface data into a CGImage, so we have to call Flush() here.
   if (mQuartzSurface)
     mQuartzSurface->Flush();
 #endif
 
+  if (mVBuf && mDiscardable) {
+    mImageSurface = nullptr;
+#ifdef XP_MACOSX
+    mQuartzSurface = nullptr;
+#endif
+  }
+
   return NS_OK;
 }
 
 void imgFrame::ApplyDirtToSurfaces()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MutexAutoLock lock(mDirtyMutex);
@@ -692,16 +773,22 @@ void imgFrame::ApplyDirtToSurfaces()
     if (mQuartzSurface)
       mQuartzSurface->Flush();
 #endif
 
     mDirty = false;
   }
 }
 
+void imgFrame::SetDiscardable()
+{
+  MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called");
+  mDiscardable = true;
+}
+
 int32_t imgFrame::GetRawTimeout() const
 {
   return mTimeout;
 }
 
 void imgFrame::SetRawTimeout(int32_t aTimeout)
 {
   mTimeout = aTimeout;
@@ -790,30 +877,35 @@ imgFrame::SizeOfExcludingThisWithCompute
 
 #ifdef USE_WIN_SURFACE
   if (mWinSurface && aLocation == mWinSurface->GetMemoryLocation()) {
     n += mWinSurface->KnownMemoryUsed();
   } else
 #endif
 #ifdef XP_MACOSX
   if (mQuartzSurface && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
-    n += mSize.width * mSize.height * 4;
-  } else
+    n += aMallocSizeOf(mQuartzSurface);
+  }
 #endif
   if (mImageSurface && aLocation == mImageSurface->GetMemoryLocation()) {
     size_t n2 = 0;
     if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) { // HEAP: measure
       n2 = mImageSurface->SizeOfIncludingThis(aMallocSizeOf);
     }
     if (n2 == 0) {  // non-HEAP or computed fallback for HEAP
       n2 = mImageSurface->KnownMemoryUsed();
     }
     n += n2;
   }
 
+  if (mVBuf && aLocation == gfxMemoryLocation::IN_PROCESS_HEAP) {
+    n += aMallocSizeOf(mVBuf);
+    n += mVBuf->HeapSizeOfExcludingThis(aMallocSizeOf);
+  }
+
   if (mOptSurface && aLocation == mOptSurface->GetMemoryLocation()) {
     size_t n2 = 0;
     if (aLocation == gfxMemoryLocation::IN_PROCESS_HEAP &&
         mOptSurface->SizeOfIsMeasured()) {
       // HEAP: measure (but only if the sub-class is capable of measuring)
       n2 = mOptSurface->SizeOfIncludingThis(aMallocSizeOf);
     }
     if (n2 == 0) {  // non-HEAP or computed fallback for HEAP
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -4,31 +4,47 @@
  * 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 imgFrame_h
 #define imgFrame_h
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
+#include "mozilla/VolatileBuffer.h"
 #include "nsRect.h"
 #include "nsPoint.h"
 #include "nsSize.h"
 #include "gfxPattern.h"
 #include "gfxDrawable.h"
 #include "gfxImageSurface.h"
 #if defined(XP_WIN)
 #include "gfxWindowsSurface.h"
 #elif defined(XP_MACOSX)
 #include "gfxQuartzImageSurface.h"
 #endif
 #include "nsAutoPtr.h"
 #include "imgIContainer.h"
 #include "gfxColor.h"
 
+/*
+ * This creates a gfxImageSurface which will unlock the buffer on destruction
+ */
+
+class LockedImageSurface
+{
+public:
+  static gfxImageSurface *
+  CreateSurface(mozilla::VolatileBuffer *vbuf,
+                const gfxIntSize& size,
+                gfxImageFormat format);
+  static mozilla::TemporaryRef<mozilla::VolatileBuffer>
+  AllocateBuffer(const gfxIntSize& size, gfxImageFormat format);
+};
+
 class imgFrame
 {
 public:
   imgFrame();
   ~imgFrame();
 
   nsresult Init(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, gfxImageFormat aFormat, uint8_t aPaletteDepth = 0);
   nsresult Optimize();
@@ -67,45 +83,72 @@ public:
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   nsresult LockImageData();
   nsresult UnlockImageData();
   void ApplyDirtToSurfaces();
 
-  nsresult GetSurface(gfxASurface **aSurface) const
+  void SetDiscardable();
+
+  nsresult GetSurface(gfxASurface **aSurface)
   {
     *aSurface = ThebesSurface();
     NS_IF_ADDREF(*aSurface);
     return NS_OK;
   }
 
-  nsresult GetPattern(gfxPattern **aPattern) const
+  nsresult GetPattern(gfxPattern **aPattern)
   {
     if (mSinglePixel)
       *aPattern = new gfxPattern(mSinglePixelColor);
     else
       *aPattern = new gfxPattern(ThebesSurface());
     NS_ADDREF(*aPattern);
     return NS_OK;
   }
 
-  gfxASurface* ThebesSurface() const
+  bool IsSinglePixel()
+  {
+    return mSinglePixel;
+  }
+
+  gfxASurface* ThebesSurface()
   {
     if (mOptSurface)
       return mOptSurface;
 #if defined(XP_WIN)
     if (mWinSurface)
       return mWinSurface;
 #elif defined(XP_MACOSX)
     if (mQuartzSurface)
       return mQuartzSurface;
 #endif
-    return mImageSurface;
+    if (mImageSurface)
+      return mImageSurface;
+    if (mVBuf) {
+      mozilla::VolatileBufferPtr<uint8_t> ref(mVBuf);
+      if (ref.WasBufferPurged())
+        return nullptr;
+
+      gfxImageSurface *sur =
+        LockedImageSurface::CreateSurface(mVBuf, mSize, mFormat);
+#if defined(XP_MACOSX)
+      return new gfxQuartzImageSurface(sur);
+#else
+      return sur;
+#endif
+    }
+    // We can return null here if we're single pixel optimized
+    // or a paletted image. However, one has to check for paletted
+    // image data first before attempting to get a surface, so
+    // this is only valid for single pixel optimized images
+    MOZ_ASSERT(mSinglePixel, "No image surface and not a single pixel!");
+    return nullptr;
   }
 
   size_t SizeOfExcludingThisWithComputedFallbackIfHeap(
            gfxMemoryLocation aLocation,
            mozilla::MallocSizeOf aMallocSizeOf) const;
 
   uint8_t GetPaletteDepth() const { return mPaletteDepth; }
   uint32_t PaletteDataLength() const {
@@ -162,23 +205,26 @@ private: // data
   gfxRGBA      mSinglePixelColor;
 
   int32_t      mTimeout; // -1 means display forever
   int32_t      mDisposalMethod;
 
   /** Indicates how many readers currently have locked this frame */
   int32_t mLockCount;
 
+  mozilla::RefPtr<mozilla::VolatileBuffer> mVBuf;
+
   gfxImageFormat mFormat;
   uint8_t      mPaletteDepth;
   int8_t       mBlendMethod;
   bool mSinglePixel;
   bool mFormatChanged;
   bool mCompositingFailed;
   bool mNonPremult;
+  bool mDiscardable;
 
   /** Have we called DiscardTracker::InformAllocation()? */
   bool mInformedDiscardTracker;
 
   bool mDirty;
 };
 
 namespace mozilla {