Bug 1139641 - Return more information from SurfaceCache::Lookup and SurfaceCache::LookupBestMatch. r=dholbert
authorSeth Fowler <mark.seth.fowler@gmail.com>
Tue, 30 Jun 2015 18:57:03 -0700
changeset 284163 d0c733dc7c9f9a3c337d5a8426468eb120f9c86e
parent 284162 316c0601d7dbae60eaba2bc060de4f6d6fb79d53
child 284164 86f581db40d499c6660a7701d2fdec0bc6c525c8
push id934
push userraliiev@mozilla.com
push dateMon, 26 Oct 2015 12:58:05 +0000
treeherdermozilla-release@05704e35c1d0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1139641
milestone42.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 1139641 - Return more information from SurfaceCache::Lookup and SurfaceCache::LookupBestMatch. r=dholbert
image/FrameAnimator.cpp
image/FrameAnimator.h
image/LookupResult.h
image/RasterImage.cpp
image/RasterImage.h
image/SurfaceCache.cpp
image/SurfaceCache.h
image/VectorImage.cpp
image/imgFrame.h
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -3,16 +3,17 @@
  * 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 "FrameAnimator.h"
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "imgIContainer.h"
+#include "LookupResult.h"
 #include "MainThreadUtils.h"
 #include "RasterImage.h"
 
 #include "pixman.h"
 
 namespace mozilla {
 
 using namespace gfx;
@@ -260,35 +261,37 @@ FrameAnimator::GetCurrentAnimationFrameI
 }
 
 nsIntRect
 FrameAnimator::GetFirstFrameRefreshArea() const
 {
   return mFirstFrameRefreshArea;
 }
 
-DrawableFrameRef
+LookupResult
 FrameAnimator::GetCompositedFrame(uint32_t aFrameNum)
 {
   MOZ_ASSERT(aFrameNum != 0, "First frame is never composited");
 
   // If we have a composited version of this frame, return that.
   if (mLastCompositedFrameIndex == int32_t(aFrameNum)) {
-    return mCompositingFrame->DrawableRef();
+    return LookupResult(mCompositingFrame->DrawableRef(),
+                        /* aIsExactMatch = */ true);
   }
 
   // Otherwise return the raw frame. DoBlend is required to ensure that we only
   // hit this case if the frame is not paletted and doesn't require compositing.
-  DrawableFrameRef ref =
+  LookupResult result =
     SurfaceCache::Lookup(ImageKey(mImage),
                          RasterSurfaceKey(mSize,
                                           0,  // Default decode flags.
                                           aFrameNum));
-  MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "About to return a paletted frame");
-  return ref;
+  MOZ_ASSERT(!result || !result.DrawableRef()->GetIsPaletted(),
+             "About to return a paletted frame");
+  return result;
 }
 
 int32_t
 FrameAnimator::GetTimeoutForFrame(uint32_t aFrameNum) const
 {
   RawAccessFrameRef frame = GetRawFrame(aFrameNum);
   if (!frame) {
     NS_WARNING("No frame; called GetTimeoutForFrame too early?");
@@ -362,23 +365,23 @@ FrameAnimator::CollectSizeOfCompositingS
                                        aCounters,
                                        aMallocSizeOf);
   }
 }
 
 RawAccessFrameRef
 FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
 {
-  DrawableFrameRef ref =
+  LookupResult result =
     SurfaceCache::Lookup(ImageKey(mImage),
                          RasterSurfaceKey(mSize,
                                           0,  // Default decode flags.
                                           aFrameNum));
-  return ref ? ref->RawAccessRef()
-             : RawAccessFrameRef();
+  return result ? result.DrawableRef()->RawAccessRef()
+                : RawAccessFrameRef();
 }
 
 //******************************************************************************
 // DoBlend gets called when the timer for animation get fired and we have to
 // update the composited frame of the animation.
 bool
 FrameAnimator::DoBlend(nsIntRect* aDirtyRect,
                        uint32_t aPrevFrameIndex,
--- a/image/FrameAnimator.h
+++ b/image/FrameAnimator.h
@@ -123,20 +123,20 @@ public:
 
   /**
    * Get the area we refresh when we loop around to the first frame.
    */
   nsIntRect GetFirstFrameRefreshArea() const;
 
   /**
    * If we have a composited frame for @aFrameNum, returns it. Otherwise,
-   * returns an empty DrawableFrameRef. It is an error to call this method with
+   * returns an empty LookupResult. It is an error to call this method with
    * aFrameNum == 0, because the first frame is never composited.
    */
-  DrawableFrameRef GetCompositedFrame(uint32_t aFrameNum);
+  LookupResult GetCompositedFrame(uint32_t aFrameNum);
 
   /*
    * Returns the frame's adjusted timeout. If the animation loops and the
    * timeout falls in between a certain range then the timeout is adjusted so
    * that it's never 0. If the animation does not loop then no adjustments are
    * made.
    */
   int32_t GetTimeoutForFrame(uint32_t aFrameNum) const;
new file mode 100644
--- /dev/null
+++ b/image/LookupResult.h
@@ -0,0 +1,69 @@
+/* -*- 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/. */
+
+/**
+ * LookupResult is the return type of SurfaceCache's Lookup*() functions. It
+ * combines a surface with relevant metadata tracked by SurfaceCache.
+ */
+
+#ifndef mozilla_image_LookupResult_h
+#define mozilla_image_LookupResult_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Move.h"
+#include "imgFrame.h"
+
+namespace mozilla {
+namespace image {
+
+/**
+ * LookupResult is the return type of SurfaceCache's Lookup*() functions. It
+ * combines a surface with relevant metadata tracked by SurfaceCache.
+ */
+class MOZ_STACK_CLASS LookupResult
+{
+public:
+  LookupResult()
+    : mIsExactMatch(false)
+  { }
+
+  LookupResult(LookupResult&& aOther)
+    : mDrawableRef(Move(aOther.mDrawableRef))
+    , mIsExactMatch(aOther.mIsExactMatch)
+  { }
+
+  LookupResult(DrawableFrameRef&& aDrawableRef, bool aIsExactMatch)
+    : mDrawableRef(Move(aDrawableRef))
+    , mIsExactMatch(aIsExactMatch)
+  { }
+
+  LookupResult& operator=(LookupResult&& aOther)
+  {
+    MOZ_ASSERT(&aOther != this, "Self-move-assignment is not supported");
+    mDrawableRef = Move(aOther.mDrawableRef);
+    mIsExactMatch = aOther.mIsExactMatch;
+    return *this;
+  }
+
+  DrawableFrameRef& DrawableRef() { return mDrawableRef; }
+  const DrawableFrameRef& DrawableRef() const { return mDrawableRef; }
+
+  /// @return true if this LookupResult contains a surface.
+  explicit operator bool() const { return bool(mDrawableRef); }
+
+  /// @return true if the surface is an exact match for the Lookup*() arguments.
+  bool IsExactMatch() const { return mIsExactMatch; }
+
+private:
+  LookupResult(const LookupResult&) = delete;
+
+  DrawableFrameRef mDrawableRef;
+  bool mIsExactMatch;
+};
+
+} // namespace image
+} // namespace mozilla
+
+#endif // mozilla_image_LookupResult_h
--- a/image/RasterImage.cpp
+++ b/image/RasterImage.cpp
@@ -15,16 +15,17 @@
 #include "nsError.h"
 #include "Decoder.h"
 #include "nsAutoPtr.h"
 #include "prenv.h"
 #include "prsystem.h"
 #include "ImageContainer.h"
 #include "ImageRegion.h"
 #include "Layers.h"
+#include "LookupResult.h"
 #include "nsPresContext.h"
 #include "SourceBuffer.h"
 #include "SurfaceCache.h"
 #include "FrameAnimator.h"
 
 #include "nsPNGDecoder.h"
 #include "nsGIFDecoder2.h"
 #include "nsJPEGDecoder.h"
@@ -464,17 +465,17 @@ NS_IMETHODIMP
 RasterImage::GetType(uint16_t* aType)
 {
   NS_ENSURE_ARG_POINTER(aType);
 
   *aType = imgIContainer::TYPE_RASTER;
   return NS_OK;
 }
 
-DrawableFrameRef
+LookupResult
 RasterImage::LookupFrameInternal(uint32_t aFrameNum,
                                  const IntSize& aSize,
                                  uint32_t aFlags)
 {
   if (!mAnim) {
     NS_ASSERTION(aFrameNum == 0,
                  "Don't ask for a frame > 0 if we're not animated!");
     aFrameNum = 0;
@@ -517,54 +518,55 @@ RasterImage::LookupFrame(uint32_t aFrame
                          const IntSize& aSize,
                          uint32_t aFlags)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   IntSize requestedSize = CanDownscaleDuringDecode(aSize, aFlags)
                         ? aSize : mSize;
 
-  DrawableFrameRef ref = LookupFrameInternal(aFrameNum, requestedSize, aFlags);
+  LookupResult result = LookupFrameInternal(aFrameNum, requestedSize, aFlags);
 
-  if (!ref && !mHasSize) {
+  if (!result && !mHasSize) {
     // We can't request a decode without knowing our intrinsic size. Give up.
     return DrawableFrameRef();
   }
 
-  if (!ref || ref->GetImageSize() != requestedSize) {
+  if (!result || !result.IsExactMatch()) {
     // The OS threw this frame away. We need to redecode if we can.
     MOZ_ASSERT(!mAnim, "Animated frames should be locked");
 
     Decode(Some(requestedSize), aFlags);
 
     // If we can sync decode, we should already have the frame.
     if (aFlags & FLAG_SYNC_DECODE) {
-      ref = LookupFrameInternal(aFrameNum, requestedSize, aFlags);
+      result = LookupFrameInternal(aFrameNum, requestedSize, aFlags);
     }
   }
 
-  if (!ref) {
+  if (!result) {
     // We still weren't able to get a frame. Give up.
     return DrawableFrameRef();
   }
 
-  if (ref->GetCompositingFailed()) {
+  if (result.DrawableRef()->GetCompositingFailed()) {
     return DrawableFrameRef();
   }
 
-  MOZ_ASSERT(!ref || !ref->GetIsPaletted(), "Should not have paletted frame");
+  MOZ_ASSERT(!result.DrawableRef()->GetIsPaletted(),
+             "Should not have a paletted frame");
 
   // Sync decoding guarantees that we got the frame, but if it's owned by an
   // async decoder that's currently running, the contents of the frame may not
   // be available yet. Make sure we get everything.
-  if (ref && mHasSourceData && (aFlags & FLAG_SYNC_DECODE)) {
-    ref->WaitUntilComplete();
+  if (mHasSourceData && (aFlags & FLAG_SYNC_DECODE)) {
+    result.DrawableRef()->WaitUntilComplete();
   }
 
-  return ref;
+  return Move(result.DrawableRef());
 }
 
 uint32_t
 RasterImage::GetCurrentFrameIndex() const
 {
   if (mAnim) {
     return mAnim->GetCurrentAnimationFrameIndex();
   }
@@ -1830,29 +1832,29 @@ RasterImage::DrawWithPreDownscaleIfNeede
                                           const IntSize& aSize,
                                           const ImageRegion& aRegion,
                                           GraphicsFilter aFilter,
                                           uint32_t aFlags)
 {
   DrawableFrameRef frameRef;
 
   if (CanScale(aFilter, aSize, aFlags)) {
-    frameRef =
+    LookupResult result =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(aSize,
                                             DecodeFlags(aFlags),
                                             0));
-    if (!frameRef) {
+    if (!result) {
       // We either didn't have a matching scaled frame or the OS threw it away.
       // Request a new one so we'll be ready next time. For now, we'll fall back
       // to aFrameRef below.
       RequestScale(aFrameRef.get(), aFlags, aSize);
     }
-    if (frameRef && !frameRef->IsImageComplete()) {
-      frameRef.reset();  // We're still scaling, so we can't use this yet.
+    if (result && result.DrawableRef()->IsImageComplete()) {
+      frameRef = Move(result.DrawableRef());  // The scaled version is ready.
     }
   }
 
   gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
   ImageRegion region(aRegion);
   bool frameIsComplete = true;  // We already checked HQ scaled frames.
   if (!frameRef) {
     // There's no HQ scaled frame available, so we'll have to use the frame
@@ -2242,31 +2244,31 @@ RasterImage::OptimalImageSizeForDest(con
   }
 
   IntSize destSize(ceil(aDest.width), ceil(aDest.height));
 
   if (aFilter == GraphicsFilter::FILTER_GOOD &&
       CanDownscaleDuringDecode(destSize, aFlags)) {
     return destSize;
   } else if (CanScale(aFilter, destSize, aFlags)) {
-    DrawableFrameRef frameRef =
+    LookupResult result =
       SurfaceCache::Lookup(ImageKey(this),
                            RasterSurfaceKey(destSize,
                                             DecodeFlags(aFlags),
                                             0));
 
-    if (frameRef && frameRef->IsImageComplete()) {
-        return destSize;  // We have an existing HQ scale for this size.
+    if (result && result.DrawableRef()->IsImageComplete()) {
+      return destSize;  // We have an existing HQ scale for this size.
     }
-    if (!frameRef) {
+    if (!result) {
       // We could HQ scale to this size, but we haven't. Request a scale now.
-      frameRef = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
-                             mSize, aFlags);
-      if (frameRef) {
-        RequestScale(frameRef.get(), aFlags, destSize);
+      DrawableFrameRef ref = LookupFrame(GetRequestedFrameIndex(aWhichFrame),
+                                         mSize, aFlags);
+      if (ref) {
+        RequestScale(ref.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.
   return mSize;
 }
--- a/image/RasterImage.h
+++ b/image/RasterImage.h
@@ -18,16 +18,17 @@
 #define mozilla_image_RasterImage_h
 
 #include "Image.h"
 #include "nsCOMPtr.h"
 #include "imgIContainer.h"
 #include "nsIProperties.h"
 #include "nsTArray.h"
 #include "imgFrame.h"
+#include "LookupResult.h"
 #include "nsThreadUtils.h"
 #include "DecodePool.h"
 #include "Orientation.h"
 #include "nsIObserver.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Pair.h"
@@ -296,19 +297,19 @@ private:
                                           uint32_t aFlags);
 
   already_AddRefed<gfx::SourceSurface> CopyFrame(uint32_t aWhichFrame,
                                              uint32_t aFlags);
 
   Pair<DrawResult, RefPtr<gfx::SourceSurface>>
     GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags);
 
-  DrawableFrameRef LookupFrameInternal(uint32_t aFrameNum,
-                                       const gfx::IntSize& aSize,
-                                       uint32_t aFlags);
+  LookupResult LookupFrameInternal(uint32_t aFrameNum,
+                                   const gfx::IntSize& aSize,
+                                   uint32_t aFlags);
   DrawableFrameRef LookupFrame(uint32_t aFrameNum,
                                const nsIntSize& aSize,
                                uint32_t aFlags);
   uint32_t GetCurrentFrameIndex() const;
   uint32_t GetRequestedFrameIndex(uint32_t aWhichFrame) const;
 
   nsIntRect GetFirstFrameRect();
 
--- a/image/SurfaceCache.cpp
+++ b/image/SurfaceCache.cpp
@@ -20,16 +20,17 @@
 #include "mozilla/StaticPtr.h"
 #include "nsIMemoryReporter.h"
 #include "gfx2DGlue.h"
 #include "gfxPattern.h"  // Workaround for flaw in bug 921753 part 2.
 #include "gfxPlatform.h"
 #include "gfxPrefs.h"
 #include "imgFrame.h"
 #include "Image.h"
+#include "LookupResult.h"
 #include "nsAutoPtr.h"
 #include "nsExpirationTracker.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 #include "nsSize.h"
 #include "nsTArray.h"
 #include "prsystem.h"
 #include "ShutdownTracker.h"
@@ -538,67 +539,67 @@ public:
       MOZ_ASSERT(foundInCosts, "Lost track of costs for this surface");
     }
 
     mAvailableCost += costEntry.GetCost();
     MOZ_ASSERT(mAvailableCost <= mMaxCost,
                "More available cost than we started with");
   }
 
-  DrawableFrameRef Lookup(const ImageKey    aImageKey,
-                          const SurfaceKey& aSurfaceKey)
+  LookupResult Lookup(const ImageKey    aImageKey,
+                      const SurfaceKey& aSurfaceKey)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
-      return DrawableFrameRef();  // No cached surfaces for this image.
+      return LookupResult();  // No cached surfaces for this image.
     }
 
     nsRefPtr<CachedSurface> surface = cache->Lookup(aSurfaceKey);
     if (!surface) {
-      return DrawableFrameRef();  // Lookup in the per-image cache missed.
+      return LookupResult();  // Lookup in the per-image cache missed.
     }
 
     DrawableFrameRef ref = surface->DrawableRef();
     if (!ref) {
       // The surface was released by the operating system. Remove the cache
       // entry as well.
       Remove(surface);
-      return DrawableFrameRef();
+      return LookupResult();
     }
 
     if (cache->IsLocked()) {
       LockSurface(surface);
     } else {
       mExpirationTracker.MarkUsed(surface);
     }
 
-    return ref;
+    return LookupResult(Move(ref), /* aIsExactMatch = */ true);
   }
 
-  DrawableFrameRef LookupBestMatch(const ImageKey         aImageKey,
-                                   const SurfaceKey&      aSurfaceKey,
-                                   const Maybe<uint32_t>& aAlternateFlags)
+  LookupResult LookupBestMatch(const ImageKey         aImageKey,
+                               const SurfaceKey&      aSurfaceKey,
+                               const Maybe<uint32_t>& aAlternateFlags)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
-      return DrawableFrameRef();  // No cached surfaces for this image.
+      return LookupResult();  // No cached surfaces for this image.
     }
 
     // Repeatedly look up the best match, trying again if the resulting surface
     // has been freed by the operating system, until we can either lock a
     // surface for drawing or there are no matching surfaces left.
     // XXX(seth): This is O(N^2), but N is expected to be very small. If we
     // encounter a performance problem here we can revisit this.
 
     nsRefPtr<CachedSurface> surface;
     DrawableFrameRef ref;
     while (true) {
       surface = cache->LookupBestMatch(aSurfaceKey, aAlternateFlags);
       if (!surface) {
-        return DrawableFrameRef();  // Lookup in the per-image cache missed.
+        return LookupResult();  // Lookup in the per-image cache missed.
       }
 
       ref = surface->DrawableRef();
       if (ref) {
         break;
       }
 
       // The surface was released by the operating system. Remove the cache
@@ -607,17 +608,25 @@ public:
     }
 
     if (cache->IsLocked()) {
       LockSurface(surface);
     } else {
       mExpirationTracker.MarkUsed(surface);
     }
 
-    return ref;
+    SurfaceKey key = surface->GetSurfaceKey();
+    const bool isExactMatch = key.Size() == aSurfaceKey.Size();
+
+    MOZ_ASSERT(isExactMatch ==
+      (key == aSurfaceKey ||
+         (aAlternateFlags && key == aSurfaceKey.WithNewFlags(*aAlternateFlags))),
+      "Result differs in a way other than size or alternate flags");
+
+    return LookupResult(Move(ref), isExactMatch);
   }
 
   void RemoveSurface(const ImageKey    aImageKey,
                      const SurfaceKey& aSurfaceKey)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
       return;  // No cached surfaces for this image.
@@ -965,44 +974,44 @@ SurfaceCache::Initialize()
 /* static */ void
 SurfaceCache::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sInstance, "No singleton - was Shutdown() called twice?");
   sInstance = nullptr;
 }
 
-/* static */ DrawableFrameRef
+/* static */ LookupResult
 SurfaceCache::Lookup(const ImageKey         aImageKey,
                      const SurfaceKey&      aSurfaceKey,
                      const Maybe<uint32_t>& aAlternateFlags /* = Nothing() */)
 {
   if (!sInstance) {
-    return DrawableFrameRef();
+    return LookupResult();
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
 
-  DrawableFrameRef ref = sInstance->Lookup(aImageKey, aSurfaceKey);
-  if (!ref && aAlternateFlags) {
-    ref = sInstance->Lookup(aImageKey,
-                            aSurfaceKey.WithNewFlags(*aAlternateFlags));
+  LookupResult result = sInstance->Lookup(aImageKey, aSurfaceKey);
+  if (!result && aAlternateFlags) {
+    result = sInstance->Lookup(aImageKey,
+                               aSurfaceKey.WithNewFlags(*aAlternateFlags));
   }
 
-  return ref;
+  return result;
 }
 
-/* static */ DrawableFrameRef
+/* static */ LookupResult
 SurfaceCache::LookupBestMatch(const ImageKey         aImageKey,
                               const SurfaceKey&      aSurfaceKey,
                               const Maybe<uint32_t>& aAlternateFlags
                                 /* = Nothing() */)
 {
   if (!sInstance) {
-    return DrawableFrameRef();
+    return LookupResult();
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
   return sInstance->LookupBestMatch(aImageKey, aSurfaceKey, aAlternateFlags);
 }
 
 /* static */ InsertOutcome
 SurfaceCache::Insert(imgFrame*         aSurface,
--- a/image/SurfaceCache.h
+++ b/image/SurfaceCache.h
@@ -19,19 +19,19 @@
 #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;
+class LookupResult;
 struct SurfaceMemoryCounter;
 
 /*
  * ImageKey contains the information we need to look up all cached surfaces for
  * a particular image.
  */
 typedef Image* ImageKey;
 
@@ -169,40 +169,39 @@ struct SurfaceCache
    * Look up the imgFrame containing a surface in the cache and returns a
    * drawable reference to that imgFrame.
    *
    * If the image associated with the surface is locked, then the surface will
    * be locked before it is returned.
    *
    * 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
+   * removed from the cache and an empty LookupResult 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.
    *
    * @param aImageKey       Key data identifying which image the surface belongs
    *                        to.
    *
    * @param aSurfaceKey     Key data which uniquely identifies the requested
    *                        surface.
    *
    * @param aAlternateFlags If not Nothing(), a different set of flags than the
    *                        ones specified in @aSurfaceKey which are also
    *                        acceptable to the caller. This is more efficient
    *                        than calling Lookup() twice, which requires taking a
    *                        lock each time.
    *
-   * @return                a DrawableFrameRef to the imgFrame wrapping the
-   *                        requested surface, or an empty DrawableFrameRef if
-   *                        not found.
+   * @return                a LookupResult, which will either contain a
+   *                        DrawableFrameRef to the requested surface, or an
+   *                        empty DrawableFrameRef if the surface was not found.
    */
-  static DrawableFrameRef Lookup(const ImageKey    aImageKey,
-                                 const SurfaceKey& aSurfaceKey,
-                                 const Maybe<uint32_t>& aAlternateFlags
-                                   = Nothing());
+  static LookupResult Lookup(const ImageKey    aImageKey,
+                             const SurfaceKey& aSurfaceKey,
+                             const Maybe<uint32_t>& aAlternateFlags = Nothing());
 
   /**
    * Looks up the best matching surface in the cache and returns a drawable
    * reference to the imgFrame containing it.
    *
    * Returned surfaces may vary from the requested surface only in terms of
    * size, unless @aAlternateFlags is specified.
    *
@@ -211,23 +210,27 @@ struct SurfaceCache
    *
    * @param aSurfaceKey  Key data which identifies the ideal surface to return.
    *
    * @param aAlternateFlags If not Nothing(), a different set of flags than the
    *                        ones specified in @aSurfaceKey which are also
    *                        acceptable to the caller. This is much more
    *                        efficient than calling LookupBestMatch() twice.
    *
-   * @return a DrawableFrameRef to the imgFrame wrapping a surface similar to
-   *         the requested surface, or an empty DrawableFrameRef if not found.
+   * @return                a LookupResult, which will either contain a
+   *                        DrawableFrameRef to a surface similar to the
+   *                        requested surface, or an empty DrawableFrameRef if
+   *                        the surface was not found. Callers can use
+   *                        LookupResult::IsExactMatch() to check whether the
+   *                        returned surface exactly matches @aSurfaceKey.
    */
-  static DrawableFrameRef LookupBestMatch(const ImageKey    aImageKey,
-                                          const SurfaceKey& aSurfaceKey,
-                                          const Maybe<uint32_t>& aAlternateFlags
-                                            = Nothing());
+  static LookupResult LookupBestMatch(const ImageKey    aImageKey,
+                                      const SurfaceKey& aSurfaceKey,
+                                      const Maybe<uint32_t>& aAlternateFlags
+                                        = Nothing());
 
   /**
    * Insert a surface into the cache. If a surface with the same ImageKey and
    * SurfaceKey is already in the cache, Insert returns FAILURE_ALREADY_PRESENT.
    *
    * Each surface in the cache has a lifetime, either Transient or Persistent.
    * Transient surfaces can expire from the cache at any time. Persistent
    * surfaces, on the other hand, will never expire as long as they remain
--- a/image/VectorImage.cpp
+++ b/image/VectorImage.cpp
@@ -22,16 +22,17 @@
 #include "nsMimeTypes.h"
 #include "nsPresContext.h"
 #include "nsRect.h"
 #include "nsString.h"
 #include "nsStubDocumentObserver.h"
 #include "nsSVGEffects.h" // for nsSVGRenderingObserver
 #include "nsWindowMemoryReporter.h"
 #include "ImageRegion.h"
+#include "LookupResult.h"
 #include "Orientation.h"
 #include "SVGDocumentWrapper.h"
 #include "nsIDOMEventListener.h"
 #include "SurfaceCache.h"
 
 // undef the GetCurrentTime macro defined in WinBase.h from the MS Platform SDK
 #undef GetCurrentTime
 
@@ -824,28 +825,28 @@ VectorImage::Draw(gfxContext* aContext,
   SVGDrawingParameters params(aContext, aSize, aRegion, aFilter,
                               aSVGContext, animTime, aFlags);
 
   if (aFlags & FLAG_BYPASS_SURFACE_CACHE) {
     CreateSurfaceAndShow(params);
     return DrawResult::SUCCESS;
   }
 
-  DrawableFrameRef frameRef =
+  LookupResult result =
     SurfaceCache::Lookup(ImageKey(this),
                          VectorSurfaceKey(params.size,
                                           params.svgContext,
                                           params.animationTime));
 
   // Draw.
-  if (frameRef) {
-    RefPtr<SourceSurface> surface = frameRef->GetSurface();
+  if (result) {
+    RefPtr<SourceSurface> surface = result.DrawableRef()->GetSurface();
     if (surface) {
       nsRefPtr<gfxDrawable> svgDrawable =
-        new gfxSurfaceDrawable(surface, frameRef->GetSize());
+        new gfxSurfaceDrawable(surface, result.DrawableRef()->GetSize());
       Show(svgDrawable, params);
       return DrawResult::SUCCESS;
     }
 
     // We lost our surface due to some catastrophic event.
     RecoverFromLossOfSurfaces();
   }
 
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -439,16 +439,18 @@ public:
 
   void reset()
   {
     mFrame = nullptr;
     mRef = nullptr;
   }
 
 private:
+  DrawableFrameRef(const DrawableFrameRef& aOther) = delete;
+
   nsRefPtr<imgFrame> mFrame;
   VolatileBufferPtr<uint8_t> mRef;
 };
 
 /**
  * A reference to an imgFrame that holds the imgFrame's surface in memory in a
  * format appropriate for access as raw data. If you have a RawAccessFrameRef
  * |ref| and |if (ref)| is true, then calls to GetImageData(), GetPaletteData(),
@@ -521,15 +523,17 @@ public:
   {
     if (mFrame) {
       mFrame->UnlockImageData();
     }
     mFrame = nullptr;
   }
 
 private:
+  RawAccessFrameRef(const RawAccessFrameRef& aOther) = delete;
+
   nsRefPtr<imgFrame> mFrame;
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_imgFrame_h