Bug 962670 - Store decoded images in VolatileBuffers, r=seth,jrmuizel
authorMichael Wu <mwu@mozilla.com>
Mon, 24 Feb 2014 22:37:51 -0500
changeset 183519 2429468e82dd922dc123c751c94038e7f6d237de
parent 183518 744810b7957014874ef54447e214b5902393c7c2
child 183520 6175ca5a9da44ef2c3dc6e6af6f1c3cc27040199
push idunknown
push userunknown
push dateunknown
reviewersseth, jrmuizel
bugs962670
milestone30.0a1
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,77 @@ 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)
+      // Manually addref and release to make sure the cairo surface isn't lost
+      NS_ADDREF(sur);
+      gfxQuartzImageSurface *quartzSur = new gfxQuartzImageSurface(sur);
+      // quartzSur does not hold on to the gfxImageSurface
+      NS_RELEASE(sur);
+      return quartzSur;
+#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 +210,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 {