Bug 1157954 - Report each surface in the ImageLib SurfaceCache individually in about:memory. r=dholbert
authorSeth Fowler <mark.seth.fowler@gmail.com>
Tue, 28 Apr 2015 11:46:17 -0700
changeset 271351 8fdcb497c551a6654a2e5647b86d62751b27d8ce
parent 271350 03f05d08f718828af20b62eef5a5eee959a72c97
child 271352 a1fea675c94d4c4d8adfa2fb145876ac540a6103
push id4830
push userjlund@mozilla.com
push dateMon, 29 Jun 2015 20:18:48 +0000
treeherdermozilla-beta@4c2175bb0420 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert
bugs1157954
milestone40.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 1157954 - Report each surface in the ImageLib SurfaceCache individually in about:memory. r=dholbert
image/src/DynamicImage.cpp
image/src/DynamicImage.h
image/src/FrameAnimator.cpp
image/src/FrameAnimator.h
image/src/Image.cpp
image/src/Image.h
image/src/ImageWrapper.cpp
image/src/ImageWrapper.h
image/src/RasterImage.cpp
image/src/RasterImage.h
image/src/SurfaceCache.cpp
image/src/SurfaceCache.h
image/src/VectorImage.cpp
image/src/VectorImage.h
image/src/imgFrame.h
image/src/imgLoader.cpp
--- a/image/src/DynamicImage.cpp
+++ b/image/src/DynamicImage.cpp
@@ -37,22 +37,22 @@ DynamicImage::GetProgressTracker()
 }
 
 size_t
 DynamicImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   return 0;
 }
 
-size_t
-DynamicImage::SizeOfDecoded(gfxMemoryLocation aLocation,
-                            MallocSizeOf aMallocSizeOf) const
+void
+DynamicImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                    MallocSizeOf aMallocSizeOf) const
 {
-  // We don't know the answer since gfxDrawable doesn't expose this information.
-  return 0;
+  // We can't report anything useful because gfxDrawable doesn't expose this
+  // information.
 }
 
 void
 DynamicImage::IncrementAnimationConsumers()
 { }
 
 void
 DynamicImage::DecrementAnimationConsumers()
--- a/image/src/DynamicImage.h
+++ b/image/src/DynamicImage.h
@@ -31,18 +31,18 @@ public:
   }
 
   // Inherited methods from Image.
   virtual nsresult Init(const char* aMimeType, uint32_t aFlags) override;
 
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
   virtual size_t SizeOfSourceWithComputedFallback(
                                  MallocSizeOf aMallocSizeOf) const override;
-  virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
-                               MallocSizeOf aMallocSizeOf) const override;
+  virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                     MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
 #ifdef DEBUG
   virtual uint32_t GetAnimationConsumers() override;
 #endif
 
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
--- a/image/src/FrameAnimator.cpp
+++ b/image/src/FrameAnimator.cpp
@@ -312,30 +312,61 @@ FrameAnimator::GetTimeoutForFrame(uint32
   // range.
   if (data.mRawTimeout >= 0 && data.mRawTimeout <= 10 && mLoopCount != 0) {
     return 100;
   }
 
   return data.mRawTimeout;
 }
 
-size_t
-FrameAnimator::SizeOfCompositingFrames(gfxMemoryLocation aLocation,
-                                       MallocSizeOf aMallocSizeOf) const
+static void
+DoCollectSizeOfCompositingSurfaces(const RawAccessFrameRef& aSurface,
+                                   SurfaceMemoryCounterType aType,
+                                   nsTArray<SurfaceMemoryCounter>& aCounters,
+                                   MallocSizeOf aMallocSizeOf)
 {
-  size_t n = 0;
+  // Concoct a SurfaceKey for this surface.
+  SurfaceKey key = RasterSurfaceKey(aSurface->GetImageSize(),
+                                    imgIContainer::DECODE_FLAGS_DEFAULT,
+                                    /* aFrameNum = */ 0);
+
+  // Create a counter for this surface.
+  SurfaceMemoryCounter counter(key, /* aIsLocked = */ true, aType);
+
+  // Extract the surface's memory usage information.
+  size_t heap = aSurface
+    ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_HEAP, aMallocSizeOf);
+  counter.Values().SetDecodedHeap(heap);
 
+  size_t nonHeap = aSurface
+    ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr);
+  counter.Values().SetDecodedNonHeap(nonHeap);
+
+  // Record it.
+  aCounters.AppendElement(counter);
+}
+
+void
+FrameAnimator::CollectSizeOfCompositingSurfaces(
+    nsTArray<SurfaceMemoryCounter>& aCounters,
+    MallocSizeOf aMallocSizeOf) const
+{
   if (mCompositingFrame) {
-    n += mCompositingFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
-  }
-  if (mCompositingPrevFrame) {
-    n += mCompositingPrevFrame->SizeOfExcludingThis(aLocation, aMallocSizeOf);
+    DoCollectSizeOfCompositingSurfaces(mCompositingFrame,
+                                       SurfaceMemoryCounterType::COMPOSITING,
+                                       aCounters,
+                                       aMallocSizeOf);
   }
 
-  return n;
+  if (mCompositingPrevFrame) {
+    DoCollectSizeOfCompositingSurfaces(mCompositingPrevFrame,
+                                       SurfaceMemoryCounterType::COMPOSITING_PREV,
+                                       aCounters,
+                                       aMallocSizeOf);
+  }
 }
 
 RawAccessFrameRef
 FrameAnimator::GetRawFrame(uint32_t aFrameNum) const
 {
   DrawableFrameRef ref =
     SurfaceCache::Lookup(ImageKey(mImage),
                          RasterSurfaceKey(mSize,
--- a/image/src/FrameAnimator.h
+++ b/image/src/FrameAnimator.h
@@ -143,18 +143,24 @@ public:
 
   /*
    * Set number of times to loop the image.
    * @note -1 means loop forever.
    */
   void SetLoopCount(int32_t aLoopCount) { mLoopCount = aLoopCount; }
   int32_t LoopCount() const { return mLoopCount; }
 
-  size_t SizeOfCompositingFrames(gfxMemoryLocation aLocation,
-                                 MallocSizeOf aMallocSizeOf) const;
+  /**
+   * Collect an accounting of the memory occupied by the compositing surfaces we
+   * use during animation playback. All of the actual animation frames are
+   * stored in the SurfaceCache, so we don't need to report them here.
+   */
+  void CollectSizeOfCompositingSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                        MallocSizeOf aMallocSizeOf) const;
+
 private: // methods
   /**
    * Gets the length of a single loop of this image, in milliseconds.
    *
    * If this image is not finished decoding, is not animated, or it is animated
    * but does not loop, returns -1. Can return 0 in the case of an animated
    * image that has a 0ms delay between its frames and does not loop.
    */
--- a/image/src/Image.cpp
+++ b/image/src/Image.cpp
@@ -7,16 +7,56 @@
 
 #include "Image.h"
 #include "nsRefreshDriver.h"
 #include "mozilla/TimeStamp.h"
 
 namespace mozilla {
 namespace image {
 
+///////////////////////////////////////////////////////////////////////////////
+// Memory Reporting
+///////////////////////////////////////////////////////////////////////////////
+
+ImageMemoryCounter::ImageMemoryCounter(Image* aImage,
+                                       MallocSizeOf aMallocSizeOf,
+                                       bool aIsUsed)
+  : mIsUsed(aIsUsed)
+{
+  MOZ_ASSERT(aImage);
+
+  // Extract metadata about the image.
+  nsRefPtr<ImageURL> imageURL(aImage->GetURI());
+  if (imageURL) {
+    imageURL->GetSpec(mURI);
+  }
+
+  int32_t width = 0;
+  int32_t height = 0;
+  aImage->GetWidth(&width);
+  aImage->GetHeight(&height);
+  mIntrinsicSize.SizeTo(width, height);
+
+  mType = aImage->GetType();
+
+  // Populate memory counters for source and decoded data.
+  mValues.SetSource(aImage->SizeOfSourceWithComputedFallback(aMallocSizeOf));
+  aImage->CollectSizeOfSurfaces(mSurfaces, aMallocSizeOf);
+
+  // Compute totals.
+  for (const SurfaceMemoryCounter& surfaceCounter : mSurfaces) {
+    mValues += surfaceCounter.Values();
+  }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Image Base Types
+///////////////////////////////////////////////////////////////////////////////
+
 // Constructor
 ImageResource::ImageResource(ImageURL* aURI) :
   mURI(aURI),
   mInnerWindowId(0),
   mAnimationConsumers(0),
   mAnimationMode(kNormalAnimMode),
   mInitialized(false),
   mAnimating(false),
--- a/image/src/Image.h
+++ b/image/src/Image.h
@@ -5,26 +5,132 @@
 
 #ifndef mozilla_image_src_Image_h
 #define mozilla_image_src_Image_h
 
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "gfx2DGlue.h"                // for gfxMemoryLocation
 #include "imgIContainer.h"
-#include "ProgressTracker.h"
 #include "ImageURL.h"
 #include "nsStringFwd.h"
+#include "ProgressTracker.h"
+#include "SurfaceCache.h"
 
 class nsIRequest;
 class nsIInputStream;
 
 namespace mozilla {
 namespace image {
 
+class Image;
+
+///////////////////////////////////////////////////////////////////////////////
+// Memory Reporting
+///////////////////////////////////////////////////////////////////////////////
+
+struct MemoryCounter
+{
+  MemoryCounter()
+    : mSource(0)
+    , mDecodedHeap(0)
+    , mDecodedNonHeap(0)
+  { }
+
+  void SetSource(size_t aCount) { mSource = aCount; }
+  size_t Source() const { return mSource; }
+  void SetDecodedHeap(size_t aCount) { mDecodedHeap = aCount; }
+  size_t DecodedHeap() const { return mDecodedHeap; }
+  void SetDecodedNonHeap(size_t aCount) { mDecodedNonHeap = aCount; }
+  size_t DecodedNonHeap() const { return mDecodedNonHeap; }
+
+  MemoryCounter& operator+=(const MemoryCounter& aOther)
+  {
+    mSource += aOther.mSource;
+    mDecodedHeap += aOther.mDecodedHeap;
+    mDecodedNonHeap += aOther.mDecodedNonHeap;
+    return *this;
+  }
+
+private:
+  size_t mSource;
+  size_t mDecodedHeap;
+  size_t mDecodedNonHeap;
+};
+
+enum class SurfaceMemoryCounterType
+{
+  NORMAL,
+  COMPOSITING,
+  COMPOSITING_PREV
+};
+
+struct SurfaceMemoryCounter
+{
+  SurfaceMemoryCounter(const SurfaceKey& aKey,
+                       bool aIsLocked,
+                       SurfaceMemoryCounterType aType =
+                         SurfaceMemoryCounterType::NORMAL)
+    : mKey(aKey)
+    , mType(aType)
+    , mIsLocked(aIsLocked)
+  { }
+
+  const SurfaceKey& Key() const { return mKey; }
+  Maybe<gfx::IntSize>& SubframeSize() { return mSubframeSize; }
+  const Maybe<gfx::IntSize>& SubframeSize() const { return mSubframeSize; }
+  MemoryCounter& Values() { return mValues; }
+  const MemoryCounter& Values() const { return mValues; }
+  SurfaceMemoryCounterType Type() const { return mType; }
+  bool IsLocked() const { return mIsLocked; }
+
+private:
+  const SurfaceKey mKey;
+  Maybe<gfx::IntSize> mSubframeSize;
+  MemoryCounter mValues;
+  const SurfaceMemoryCounterType mType;
+  const bool mIsLocked;
+};
+
+struct ImageMemoryCounter
+{
+  ImageMemoryCounter(Image* aImage,
+                     MallocSizeOf aMallocSizeOf,
+                     bool aIsUsed);
+
+  nsCString& URI() { return mURI; }
+  const nsCString& URI() const { return mURI; }
+  const nsTArray<SurfaceMemoryCounter>& Surfaces() const { return mSurfaces; }
+  const gfx::IntSize IntrinsicSize() const { return mIntrinsicSize; }
+  const MemoryCounter& Values() const { return mValues; }
+  uint16_t Type() const { return mType; }
+  bool IsUsed() const { return mIsUsed; }
+
+  bool IsNotable() const
+  {
+    const size_t NotableThreshold = 16 * 1024;
+    size_t total = mValues.Source() + mValues.DecodedHeap()
+                                    + mValues.DecodedNonHeap();
+    return total >= NotableThreshold;
+  }
+
+private:
+  nsCString mURI;
+  nsTArray<SurfaceMemoryCounter> mSurfaces;
+  gfx::IntSize mIntrinsicSize;
+  MemoryCounter mValues;
+  uint16_t mType;
+  const bool mIsUsed;
+};
+
+
+///////////////////////////////////////////////////////////////////////////////
+// Image Base Types
+///////////////////////////////////////////////////////////////////////////////
+
 class Image : public imgIContainer
 {
 public:
   // Mimetype translation
   enum eDecoderType {
     eDecoderType_png     = 0,
     eDecoderType_gif     = 1,
     eDecoderType_jpeg    = 2,
@@ -79,24 +185,26 @@ public:
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() = 0;
   virtual void SetProgressTracker(ProgressTracker* aProgressTracker) {}
 
   /**
    * The size, in bytes, occupied by the compressed source data of the image.
    * If MallocSizeOf does not work on this platform, uses a fallback approach to
    * ensure that something reasonable is always returned.
    */
-  virtual size_t SizeOfSourceWithComputedFallback(
-    MallocSizeOf aMallocSizeOf) const = 0;
+  virtual size_t
+    SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const = 0;
 
   /**
-   * The size, in bytes, occupied by the image's decoded data.
+   * Collect an accounting of the memory occupied by the image's surfaces (which
+   * together make up its decoded data). Each surface is recorded as a separate
+   * SurfaceMemoryCounter, stored in @aCounters.
    */
-  virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
-                               MallocSizeOf aMallocSizeOf) const = 0;
+  virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                     MallocSizeOf aMallocSizeOf) const = 0;
 
   virtual void IncrementAnimationConsumers() = 0;
   virtual void DecrementAnimationConsumers() = 0;
 #ifdef DEBUG
   virtual uint32_t GetAnimationConsumers() = 0;
 #endif
 
   /**
--- a/image/src/ImageWrapper.cpp
+++ b/image/src/ImageWrapper.cpp
@@ -34,21 +34,21 @@ ImageWrapper::GetProgressTracker()
 }
 
 size_t
 ImageWrapper::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   return mInnerImage->SizeOfSourceWithComputedFallback(aMallocSizeOf);
 }
 
-size_t
-ImageWrapper::SizeOfDecoded(gfxMemoryLocation aLocation,
-                            MallocSizeOf aMallocSizeOf) const
+void
+ImageWrapper::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                    MallocSizeOf aMallocSizeOf) const
 {
-  return mInnerImage->SizeOfDecoded(aLocation, aMallocSizeOf);
+  mInnerImage->CollectSizeOfSurfaces(aCounters, aMallocSizeOf);
 }
 
 void
 ImageWrapper::IncrementAnimationConsumers()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Main thread only to encourage serialization "
                                 "with DecrementAnimationConsumers");
   mInnerImage->IncrementAnimationConsumers();
--- a/image/src/ImageWrapper.h
+++ b/image/src/ImageWrapper.h
@@ -22,21 +22,19 @@ public:
   NS_DECL_IMGICONTAINER
 
   // Inherited methods from Image.
   virtual nsresult Init(const char* aMimeType, uint32_t aFlags) override;
 
   virtual already_AddRefed<ProgressTracker> GetProgressTracker() override;
 
   virtual size_t
-  SizeOfSourceWithComputedFallback( MallocSizeOf aMallocSizeOf) const
-      override;
-  virtual size_t
-  SizeOfDecoded(gfxMemoryLocation aLocation,
-                MallocSizeOf aMallocSizeOf) const override;
+    SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const override;
+  virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                     MallocSizeOf aMallocSizeOf) const override;
 
   virtual void IncrementAnimationConsumers() override;
   virtual void DecrementAnimationConsumers() override;
 #ifdef DEBUG
   virtual uint32_t GetAnimationConsumers() override;
 #endif
 
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -883,26 +883,24 @@ RasterImage::UpdateImageContainer()
 }
 
 size_t
 RasterImage::SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf) const
 {
   return mSourceBuffer->SizeOfIncludingThisWithComputedFallback(aMallocSizeOf);
 }
 
-size_t
-RasterImage::SizeOfDecoded(gfxMemoryLocation aLocation,
-                           MallocSizeOf aMallocSizeOf) const
+void
+RasterImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                   MallocSizeOf aMallocSizeOf) const
 {
-  size_t n = 0;
-  n += SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
+  SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf);
   if (mAnim) {
-    n += mAnim->SizeOfCompositingFrames(aLocation, aMallocSizeOf);
+    mAnim->CollectSizeOfCompositingSurfaces(aCounters, aMallocSizeOf);
   }
-  return n;
 }
 
 class OnAddedFrameRunnable : public nsRunnable
 {
 public:
   OnAddedFrameRunnable(RasterImage* aImage,
                        uint32_t aNewFrameCount,
                        const IntRect& aNewRefreshArea)
--- a/image/src/RasterImage.h
+++ b/image/src/RasterImage.h
@@ -177,18 +177,18 @@ public:
                                        uint32_t aToOffset, uint32_t aCount,
                                        uint32_t* aWriteCount);
 
   /* The total number of frames in this image. */
   uint32_t GetNumFrames() const { return mFrameCount; }
 
   virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
     const override;
-  virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
-                               MallocSizeOf aMallocSizeOf) const override;
+  virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                     MallocSizeOf aMallocSizeOf) const override;
 
   /* Triggers discarding. */
   void Discard();
 
 
   //////////////////////////////////////////////////////////////////////////////
   // Decoder callbacks.
   //////////////////////////////////////////////////////////////////////////////
--- a/image/src/SurfaceCache.cpp
+++ b/image/src/SurfaceCache.cpp
@@ -159,43 +159,51 @@ public:
 
   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; }
   bool IsDecoded() const { return mSurface->IsImageComplete(); }
 
-  // A helper type used by SurfaceCacheImpl::SizeOfSurfacesSum.
-  struct SizeOfSurfacesSum
+  // A helper type used by SurfaceCacheImpl::CollectSizeOfSurfaces.
+  struct MOZ_STACK_CLASS SurfaceMemoryReport
   {
-    SizeOfSurfacesSum(gfxMemoryLocation aLocation,
-                      MallocSizeOf      aMallocSizeOf)
-      : mLocation(aLocation)
+    SurfaceMemoryReport(nsTArray<SurfaceMemoryCounter>& aCounters,
+                        MallocSizeOf                    aMallocSizeOf)
+      : mCounters(aCounters)
       , mMallocSizeOf(aMallocSizeOf)
-      , mSum(0)
     { }
 
     void Add(CachedSurface* aCachedSurface)
     {
       MOZ_ASSERT(aCachedSurface, "Should have a CachedSurface");
 
-      if (!aCachedSurface->mSurface) {
-        return;
+      SurfaceMemoryCounter counter(aCachedSurface->GetSurfaceKey(),
+                                   aCachedSurface->IsLocked());
+
+      if (aCachedSurface->mSurface) {
+        counter.SubframeSize() = Some(aCachedSurface->mSurface->GetSize());
+
+        size_t heap = aCachedSurface->mSurface
+          ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_HEAP,
+                                mMallocSizeOf);
+        counter.Values().SetDecodedHeap(heap);
+
+        size_t nonHeap = aCachedSurface->mSurface
+          ->SizeOfExcludingThis(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr);
+        counter.Values().SetDecodedNonHeap(nonHeap);
       }
-      mSum += aCachedSurface->mSurface->SizeOfExcludingThis(mLocation,
-                                                            mMallocSizeOf);
+
+      mCounters.AppendElement(counter);
     }
 
-    size_t Result() const { return mSum; }
-
   private:
-    gfxMemoryLocation mLocation;
-    MallocSizeOf      mMallocSizeOf;
-    size_t            mSum;
+    nsTArray<SurfaceMemoryCounter>& mCounters;
+    MallocSizeOf                    mMallocSizeOf;
   };
 
 private:
   nsExpirationState  mExpirationState;
   nsRefPtr<imgFrame> mSurface;
   DrawableFrameRef   mDrawableRef;
   const Cost         mCost;
   const ImageKey     mImageKey;
@@ -784,38 +792,36 @@ public:
                             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)
+  void CollectSizeOfSurfaces(const ImageKey                  aImageKey,
+                             nsTArray<SurfaceMemoryCounter>& aCounters,
+                             MallocSizeOf                    aMallocSizeOf)
   {
     nsRefPtr<ImageSurfaceCache> cache = GetImageCache(aImageKey);
     if (!cache) {
-      return 0;  // No surfaces for this image.
+      return;  // 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();
+    // Report all surfaces in the per-image cache.
+    CachedSurface::SurfaceMemoryReport report(aCounters, aMallocSizeOf);
+    cache->ForEach(DoCollectSizeOfSurface, &report);
   }
 
-  static PLDHashOperator DoSizeOfSurfacesSum(const SurfaceKey&,
-                                             CachedSurface*    aSurface,
-                                             void*             aSum)
+  static PLDHashOperator DoCollectSizeOfSurface(const SurfaceKey&,
+                                                CachedSurface*    aSurface,
+                                                void*             aReport)
   {
-    auto sum = static_cast<CachedSurface::SizeOfSurfacesSum*>(aSum);
-    sum->Add(aSurface);
+    auto report = static_cast<CachedSurface::SurfaceMemoryReport*>(aReport);
+    report->Add(aSurface);
     return PL_DHASH_NEXT;
   }
 
 private:
   already_AddRefed<ImageSurfaceCache> GetImageCache(const ImageKey aImageKey)
   {
     nsRefPtr<ImageSurfaceCache> imageCache;
     mImageCaches.Get(aImageKey, getter_AddRefs(imageCache));
@@ -1070,23 +1076,23 @@ SurfaceCache::RemoveImage(Image* aImageK
 SurfaceCache::DiscardAll()
 {
   if (sInstance) {
     MutexAutoLock lock(sInstance->GetMutex());
     sInstance->DiscardAll();
   }
 }
 
-/* static */ size_t
-SurfaceCache::SizeOfSurfaces(const ImageKey    aImageKey,
-                             gfxMemoryLocation aLocation,
-                             MallocSizeOf      aMallocSizeOf)
+/* static */ void
+SurfaceCache::CollectSizeOfSurfaces(const ImageKey                  aImageKey,
+                                    nsTArray<SurfaceMemoryCounter>& aCounters,
+                                    MallocSizeOf                    aMallocSizeOf)
 {
   if (!sInstance) {
-    return 0;
+    return;
   }
 
   MutexAutoLock lock(sInstance->GetMutex());
-  return sInstance->SizeOfSurfaces(aImageKey, aLocation, aMallocSizeOf);
+  return sInstance->CollectSizeOfSurfaces(aImageKey, aCounters, aMallocSizeOf);
 }
 
 } // namespace image
 } // namespace mozilla
--- a/image/src/SurfaceCache.h
+++ b/image/src/SurfaceCache.h
@@ -22,16 +22,17 @@
 #include "SVGImageContext.h"         // for SVGImageContext
 
 namespace mozilla {
 namespace image {
 
 class DrawableFrameRef;
 class Image;
 class imgFrame;
+struct SurfaceMemoryCounter;
 
 /*
  * ImageKey contains the information we need to look up all cached surfaces for
  * a particular image.
  */
 typedef Image* ImageKey;
 
 /*
@@ -387,31 +388,29 @@ struct SurfaceCache
    *
    * All surfaces are evictable except for persistent surfaces associated with
    * locked images. Non-evictable surfaces can only be removed by
    * RemoveSurface() or RemoveImage().
    */
   static void DiscardAll();
 
   /**
-   * Computes the size of the surfaces stored for the given image at the given
-   * memory location.
+   * Collects an accounting of the surfaces contained in the SurfaceCache for
+   * the given image, along with their size and various other metadata.
    *
    * 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.
+   * @param aCounters     An array into which the report for each surface will
+   *                      be written.
+   * @param aMallocSizeOf A fallback malloc memory reporting function.
    */
-  static size_t SizeOfSurfaces(const ImageKey    aImageKey,
-                               gfxMemoryLocation aLocation,
-                               MallocSizeOf      aMallocSizeOf);
+  static void CollectSizeOfSurfaces(const ImageKey    aImageKey,
+                                    nsTArray<SurfaceMemoryCounter>& aCounters,
+                                    MallocSizeOf      aMallocSizeOf);
 
 private:
   virtual ~SurfaceCache() = 0;  // Forbid instantiation.
 };
 
 } // namespace image
 } // namespace mozilla
 
--- a/image/src/VectorImage.cpp
+++ b/image/src/VectorImage.cpp
@@ -392,21 +392,21 @@ VectorImage::SizeOfSourceWithComputedFal
     // size accurately, so we just use a constant value of 100KB, which will
     // generally underestimate the true size.
     return 100 * 1024;
   }
 
   return windowSizes.getTotalSize();
 }
 
-size_t
-VectorImage::SizeOfDecoded(gfxMemoryLocation aLocation,
-                           MallocSizeOf aMallocSizeOf) const
+void
+VectorImage::CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                   MallocSizeOf aMallocSizeOf) const
 {
-  return SurfaceCache::SizeOfSurfaces(ImageKey(this), aLocation, aMallocSizeOf);
+  SurfaceCache::CollectSizeOfSurfaces(ImageKey(this), aCounters, aMallocSizeOf);
 }
 
 nsresult
 VectorImage::OnImageDataComplete(nsIRequest* aRequest,
                                  nsISupports* aContext,
                                  nsresult aStatus,
                                  bool aLastPart)
 {
--- a/image/src/VectorImage.h
+++ b/image/src/VectorImage.h
@@ -34,18 +34,18 @@ public:
   // (no public constructor - use ImageFactory)
 
   // Methods inherited from Image
   nsresult Init(const char* aMimeType,
                 uint32_t aFlags) override;
 
   virtual size_t SizeOfSourceWithComputedFallback(MallocSizeOf aMallocSizeOf)
     const override;
-  virtual size_t SizeOfDecoded(gfxMemoryLocation aLocation,
-                               MallocSizeOf aMallocSizeOf) const override;
+  virtual void CollectSizeOfSurfaces(nsTArray<SurfaceMemoryCounter>& aCounters,
+                                     MallocSizeOf aMallocSizeOf) const override;
 
   virtual nsresult OnImageDataAvailable(nsIRequest* aRequest,
                                         nsISupports* aContext,
                                         nsIInputStream* aInStr,
                                         uint64_t aSourceOffset,
                                         uint32_t aCount) override;
   virtual nsresult OnImageDataComplete(nsIRequest* aRequest,
                                        nsISupports* aContext,
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -231,17 +231,17 @@ public:
    * aborted.
    *
    * Note that calling this on the main thread _blocks the main thread_. Be very
    * careful in your use of this method to avoid excessive main thread jank or
    * deadlock.
    */
   void WaitUntilComplete() const;
 
-  IntSize GetImageSize() { return mImageSize; }
+  IntSize GetImageSize() const { return mImageSize; }
   nsIntRect GetRect() const;
   IntSize GetSize() const { return mSize; }
   bool NeedsPadding() const { return mOffset != nsIntPoint(0, 0); }
   void GetImageData(uint8_t** aData, uint32_t* length) const;
   uint8_t* GetImageData() const;
 
   bool GetIsPaletted() const;
   void GetPaletteData(uint32_t** aPalette, uint32_t* length) const;
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -112,77 +112,16 @@ public:
   void UnregisterLoader(imgLoader* aLoader)
   {
     mKnownLoaders.RemoveElement(aLoader);
   }
 
 private:
   nsTArray<imgLoader*> mKnownLoaders;
 
-  struct MemoryCounter
-  {
-    MemoryCounter()
-      : mSource(0)
-      , mDecodedHeap(0)
-      , mDecodedNonHeap(0)
-    { }
-
-    void SetSource(size_t aCount) { mSource = aCount; }
-    size_t Source() const { return mSource; }
-    void SetDecodedHeap(size_t aCount) { mDecodedHeap = aCount; }
-    size_t DecodedHeap() const { return mDecodedHeap; }
-    void SetDecodedNonHeap(size_t aCount) { mDecodedNonHeap = aCount; }
-    size_t DecodedNonHeap() const { return mDecodedNonHeap; }
-
-    MemoryCounter& operator+=(const MemoryCounter& aOther)
-    {
-      mSource += aOther.mSource;
-      mDecodedHeap += aOther.mDecodedHeap;
-      mDecodedNonHeap += aOther.mDecodedNonHeap;
-      return *this;
-    }
-
-  private:
-    size_t mSource;
-    size_t mDecodedHeap;
-    size_t mDecodedNonHeap;
-  };
-
-  struct ImageMemoryCounter
-  {
-    ImageMemoryCounter(uint16_t aType, const nsACString& aURI, bool aIsUsed)
-      : mURI(aURI)
-      , mType(aType)
-      , mIsUsed(aIsUsed)
-    {
-      MOZ_ASSERT(!mURI.IsEmpty(), "Should have a URI for all images");
-    }
-
-    nsCString& URI() { return mURI; }
-    const nsCString& URI() const { return mURI; }
-    uint16_t Type() const { return mType; }
-    MemoryCounter& Values() { return mValues; }
-    const MemoryCounter& Values() const { return mValues; }
-    bool IsUsed() const { return mIsUsed; }
-
-    bool IsNotable() const
-    {
-      const size_t NotableThreshold = 16 * 1024;
-      size_t total = mValues.Source() + mValues.DecodedHeap()
-                                      + mValues.DecodedNonHeap();
-      return total >= NotableThreshold;
-    }
-
-  private:
-    nsCString mURI;
-    uint16_t mType;
-    MemoryCounter mValues;
-    bool mIsUsed;
-  };
-
   struct MemoryTotal
   {
     MemoryTotal& operator+=(const ImageMemoryCounter& aImageCounter)
     {
       if (aImageCounter.Type() == imgIContainer::TYPE_RASTER) {
         if (aImageCounter.IsUsed()) {
           mUsedRasterCounter += aImageCounter.Values();
         } else {
@@ -211,27 +150,27 @@ private:
     MemoryCounter mUnusedRasterCounter;
     MemoryCounter mUsedVectorCounter;
     MemoryCounter mUnusedVectorCounter;
   };
 
   // Reports all images of a single kind, e.g. all used chrome images.
   nsresult ReportCounterArray(nsIHandleReportCallback* aHandleReport,
                               nsISupports* aData,
-                              const nsTArray<ImageMemoryCounter>& aCounterArray,
+                              nsTArray<ImageMemoryCounter>& aCounterArray,
                               const char* aPathPrefix,
                               bool aAnonymize = false)
   {
     nsresult rv;
     MemoryTotal summaryTotal;
     MemoryTotal nonNotableTotal;
 
     // Report notable images, and compute total and non-notable aggregate sizes.
     for (uint32_t i = 0; i < aCounterArray.Length(); i++) {
-      ImageMemoryCounter counter = aCounterArray[i];
+      ImageMemoryCounter& counter = aCounterArray[i];
 
       if (aAnonymize) {
         counter.URI().Truncate();
         counter.URI().AppendPrintf("<anonymized-%u>", i);
       } else {
         // The URI could be an extremely long data: URI. Truncate if needed.
         static const size_t max = 256;
         if (counter.URI().Length() > max) {
@@ -239,17 +178,17 @@ private:
           counter.URI().AppendLiteral(" (truncated)");
         }
         counter.URI().ReplaceChar('/', '\\');
       }
 
       summaryTotal += counter;
 
       if (counter.IsNotable()) {
-        rv = ReportCounter(aHandleReport, aData, aPathPrefix, counter);
+        rv = ReportImage(aHandleReport, aData, aPathPrefix, counter);
         NS_ENSURE_SUCCESS(rv, rv);
       } else {
         nonNotableTotal += counter;
       }
     }
 
     // Report non-notable images in aggregate.
     rv = ReportTotal(aHandleReport, aData, /* aExplicit = */ true,
@@ -259,36 +198,90 @@ private:
     // Report a summary in aggregate, outside of the explicit tree.
     rv = ReportTotal(aHandleReport, aData, /* aExplicit = */ false,
                      aPathPrefix, "", summaryTotal);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
-  static nsresult ReportCounter(nsIHandleReportCallback* aHandleReport,
-                                nsISupports* aData,
-                                const char* aPathPrefix,
-                                const ImageMemoryCounter& aCounter)
+  static nsresult ReportImage(nsIHandleReportCallback* aHandleReport,
+                              nsISupports* aData,
+                              const char* aPathPrefix,
+                              const ImageMemoryCounter& aCounter)
   {
     nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/"));
     pathPrefix.Append(aPathPrefix);
     pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER
                         ? "/raster/"
                         : "/vector/");
     pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
     pathPrefix.Append("image(");
+    pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
+    pathPrefix.Append("x");
+    pathPrefix.AppendInt(aCounter.IntrinsicSize().height);
+    pathPrefix.Append(", ");
+
     if (aCounter.URI().IsEmpty()) {
       pathPrefix.Append("<unknown URI>");
     } else {
       pathPrefix.Append(aCounter.URI());
     }
+
     pathPrefix.Append(")/");
 
-    return ReportValues(aHandleReport, aData, pathPrefix, aCounter.Values());
+    return ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter);
+  }
+
+  static nsresult ReportSurfaces(nsIHandleReportCallback* aHandleReport,
+                                 nsISupports* aData,
+                                 const nsACString& aPathPrefix,
+                                 const ImageMemoryCounter& aCounter)
+  {
+    for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
+      nsAutoCString surfacePathPrefix(aPathPrefix);
+      surfacePathPrefix.Append(counter.IsLocked() ? "locked/" : "unlocked/");
+      surfacePathPrefix.Append("surface(");
+
+      if (counter.SubframeSize() &&
+          *counter.SubframeSize() != counter.Key().Size()) {
+        surfacePathPrefix.AppendInt(counter.SubframeSize()->width);
+        surfacePathPrefix.Append("x");
+        surfacePathPrefix.AppendInt(counter.SubframeSize()->height);
+        surfacePathPrefix.Append(" subframe of ");
+      }
+
+      surfacePathPrefix.AppendInt(counter.Key().Size().width);
+      surfacePathPrefix.Append("x");
+      surfacePathPrefix.AppendInt(counter.Key().Size().height);
+
+      if (counter.Type() == SurfaceMemoryCounterType::NORMAL) {
+        surfacePathPrefix.Append("@");
+        surfacePathPrefix.AppendFloat(counter.Key().AnimationTime());
+
+        if (counter.Key().Flags() != imgIContainer::DECODE_FLAGS_DEFAULT) {
+          surfacePathPrefix.Append(", flags:");
+          surfacePathPrefix.AppendInt(counter.Key().Flags(), /* aRadix = */ 16);
+        }
+      } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING) {
+        surfacePathPrefix.Append(", compositing frame");
+      } else if (counter.Type() == SurfaceMemoryCounterType::COMPOSITING_PREV) {
+        surfacePathPrefix.Append(", compositing prev frame");
+      } else {
+        MOZ_ASSERT_UNREACHABLE("Unknown counter type");
+      }
+
+      surfacePathPrefix.Append(")/");
+
+      nsresult rv = ReportValues(aHandleReport, aData, surfacePathPrefix,
+                                 counter.Values());
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    return NS_OK;
   }
 
   static nsresult ReportTotal(nsIHandleReportCallback* aHandleReport,
                               nsISupports* aData,
                               bool aExplicit,
                               const char* aPathPrefix,
                               const char* aPathInfix,
                               const MemoryTotal& aTotal)
@@ -405,30 +398,19 @@ private:
                                       nsTArray<ImageMemoryCounter>* aArray,
                                       bool aIsUsed)
   {
     nsRefPtr<Image> image = aRequest->GetImage();
     if (!image) {
       return;
     }
 
-    nsRefPtr<ImageURL> imageURL(image->GetURI());
-    nsAutoCString spec;
-    imageURL->GetSpec(spec);
-
-    ImageMemoryCounter counter(image->GetType(), spec, aIsUsed);
-
-    counter.Values().SetSource(image->
-        SizeOfSourceWithComputedFallback(ImagesMallocSizeOf));
-    counter.Values().SetDecodedHeap(image->
-        SizeOfDecoded(gfxMemoryLocation::IN_PROCESS_HEAP, ImagesMallocSizeOf));
-    counter.Values().SetDecodedNonHeap(image->
-        SizeOfDecoded(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr));
-
-    aArray->AppendElement(counter);
+    ImageMemoryCounter counter(image, ImagesMallocSizeOf, aIsUsed);
+
+    aArray->AppendElement(Move(counter));
   }
 
   static PLDHashOperator DoRecordCounterUsedDecoded(const ImageCacheKey&,
                                                     imgCacheEntry* aEntry,
                                                     void* aUserArg)
   {
     if (aEntry->HasNoProxies()) {
       return PL_DHASH_NEXT;
@@ -439,20 +421,22 @@ private:
     if (!image) {
       return PL_DHASH_NEXT;
     }
 
     // Both this and EntryImageSizes measure images/content/raster/used/decoded
     // memory.  This function's measurement is secondary -- the result doesn't
     // go in the "explicit" tree -- so we use moz_malloc_size_of instead of
     // ImagesMallocSizeOf to prevent DMD from seeing it reported twice.
+    ImageMemoryCounter counter(image, moz_malloc_size_of, /* aIsUsed = */ true);
+
     auto n = static_cast<size_t*>(aUserArg);
-    *n += image->SizeOfDecoded(gfxMemoryLocation::IN_PROCESS_HEAP,
-                               moz_malloc_size_of);
-    *n += image->SizeOfDecoded(gfxMemoryLocation::IN_PROCESS_NONHEAP, nullptr);
+    *n += counter.Values().DecodedHeap();
+    *n += counter.Values().DecodedNonHeap();
+
     return PL_DHASH_NEXT;
   }
 };
 
 NS_IMPL_ISUPPORTS(imgMemoryReporter, nsIMemoryReporter)
 
 NS_IMPL_ISUPPORTS(nsProgressNotificationProxy,
                   nsIProgressEventSink,