Bug 918207 - Support per-tab memory profiling. r=billm,mccr8,till,smaug,nfroyd.
authorNicholas Nethercote <nnethercote@mozilla.com>
Tue, 22 Oct 2013 14:53:26 -0700
changeset 151748 2d2444eecf83111b7522f2d97f8c84926fcd76fc
parent 151747 6155ea7e8dea89a8efd1660d939433e164dd3294
child 151749 9a82f8dbb7197aa215f68317996d6763d246bc93
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbillm, mccr8, till, smaug, nfroyd
bugs918207
milestone27.0a1
Bug 918207 - Support per-tab memory profiling. r=billm,mccr8,till,smaug,nfroyd.
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsWindowMemoryReporter.h
js/public/MemoryMetrics.h
js/src/gc/Iteration.cpp
js/src/jsgc.h
js/src/jsobj.cpp
js/src/jsobj.h
js/src/vm/MemoryMetrics.cpp
js/xpconnect/src/XPCJSRuntime.cpp
layout/base/nsArenaMemoryStats.h
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
xpcom/base/nsMemoryReporterManager.h
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "amIAddonManager.h"
 #include "nsWindowMemoryReporter.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
+#include "nsIDOMWindowCollection.h"
 #include "nsIEffectiveTLDService.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
 #include "XPCJSMemoryReporter.h"
@@ -26,24 +27,80 @@ StaticRefPtr<nsWindowMemoryReporter> sWi
 nsWindowMemoryReporter::nsWindowMemoryReporter()
   : mCheckForGhostWindowsCallbackPending(false)
 {
 }
 
 NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
                    nsSupportsWeakReference)
 
-/* static */
-void
+static nsresult
+AddNonJSSizeOfWindowAndItsDescendents(nsGlobalWindow* aWindow,
+                                      nsTabSizes* aSizes)
+{
+  // Measure the window.
+  nsWindowSizes windowSizes(moz_malloc_size_of);
+  aWindow->AddSizeOfIncludingThis(&windowSizes);
+  windowSizes.addToTabSizes(aSizes);
+
+  // Measure the inner window, if there is one.
+  nsWindowSizes innerWindowSizes(moz_malloc_size_of);
+  nsGlobalWindow* inner = aWindow->GetCurrentInnerWindowInternal();
+  if (inner) {
+    inner->AddSizeOfIncludingThis(&innerWindowSizes);
+    innerWindowSizes.addToTabSizes(aSizes);
+  }
+
+  nsCOMPtr<nsIDOMWindowCollection> frames;
+  nsresult rv = aWindow->GetFrames(getter_AddRefs(frames));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  uint32_t length;
+  rv = frames->GetLength(&length);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Measure this window's descendents.
+  for (uint32_t i = 0; i < length; i++) {
+      nsCOMPtr<nsIDOMWindow> child;
+      rv = frames->Item(i, getter_AddRefs(child));
+      NS_ENSURE_SUCCESS(rv, rv);
+      NS_ENSURE_STATE(child);
+
+      nsGlobalWindow* childWin =
+        static_cast<nsGlobalWindow*>(static_cast<nsIDOMWindow *>(child.get()));
+
+      rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes);
+      NS_ENSURE_SUCCESS(rv, rv);
+  }
+  return NS_OK;
+}
+
+static nsresult
+NonJSSizeOfTab(nsPIDOMWindow* aWindow, size_t* aDomSize, size_t* aStyleSize, size_t* aOtherSize)
+{
+  nsGlobalWindow* window = static_cast<nsGlobalWindow*>(aWindow);
+
+  nsTabSizes sizes;
+  nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(window, &sizes);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aDomSize   = sizes.mDom;
+  *aStyleSize = sizes.mStyle;
+  *aOtherSize = sizes.mOther;
+  return NS_OK;
+}
+
+/* static */ void
 nsWindowMemoryReporter::Init()
 {
   MOZ_ASSERT(!sWindowReporter);
   sWindowReporter = new nsWindowMemoryReporter();
   ClearOnShutdown(&sWindowReporter);
   NS_RegisterMemoryReporter(sWindowReporter);
+  RegisterNonJSSizeOfTab(NonJSSizeOfTab);
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     // DOM_WINDOW_DESTROYED_TOPIC announces what we call window "detachment",
     // when a window's docshell is set to NULL.
     os->AddObserver(sWindowReporter, DOM_WINDOW_DESTROYED_TOPIC,
                     /* weakRef = */ true);
     os->AddObserver(sWindowReporter, "after-minimize-memory-usage",
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -7,48 +7,69 @@
 #ifndef nsWindowMemoryReporter_h__
 #define nsWindowMemoryReporter_h__
 
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "nsDataHashtable.h"
 #include "nsWeakReference.h"
 #include "nsAutoPtr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/PodOperations.h"
 #include "mozilla/TimeStamp.h"
 #include "nsArenaMemoryStats.h"
-#include "mozilla/Attributes.h"
 
 // This should be used for any nsINode sub-class that has fields of its own
 // that it needs to measure;  any sub-class that doesn't use it will inherit
 // SizeOfExcludingThis from its super-class.  SizeOfIncludingThis() need not be
 // defined, it is inherited from nsINode.
 #define NS_DECL_SIZEOF_EXCLUDING_THIS \
   virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 
 class nsWindowSizes {
+#define FOR_EACH_SIZE(macro) \
+  macro(DOM,   mDOMElementNodes) \
+  macro(DOM,   mDOMTextNodes) \
+  macro(DOM,   mDOMCDATANodes) \
+  macro(DOM,   mDOMCommentNodes) \
+  macro(DOM,   mDOMEventTargets) \
+  macro(DOM,   mDOMOther) \
+  macro(Style, mStyleSheets) \
+  macro(Other, mLayoutPresShell) \
+  macro(Style, mLayoutStyleSets) \
+  macro(Other, mLayoutTextRuns) \
+  macro(Other, mLayoutPresContext) \
+  macro(Other, mPropertyTables) \
+
 public:
-  nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf) {
-    memset(this, 0, sizeof(nsWindowSizes));
-    mMallocSizeOf = aMallocSizeOf;
+  nsWindowSizes(mozilla::MallocSizeOf aMallocSizeOf)
+    :
+      #define ZERO_SIZE(kind, mSize)  mSize(0),
+      FOR_EACH_SIZE(ZERO_SIZE)
+      #undef ZERO_SIZE
+      mArenaStats(),
+      mMallocSizeOf(aMallocSizeOf)
+  {}
+
+  void addToTabSizes(nsTabSizes *sizes) const {
+    #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize);
+    FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+    #undef ADD_TO_TAB_SIZES
+    mArenaStats.addToTabSizes(sizes);
   }
-  mozilla::MallocSizeOf mMallocSizeOf;
+
+  #define DECL_SIZE(kind, mSize) size_t mSize;
+  FOR_EACH_SIZE(DECL_SIZE);
+  #undef DECL_SIZE
   nsArenaMemoryStats mArenaStats;
-  size_t mDOMElementNodes;
-  size_t mDOMTextNodes;
-  size_t mDOMCDATANodes;
-  size_t mDOMCommentNodes;
-  size_t mDOMEventTargets;
-  size_t mDOMOther;
-  size_t mStyleSheets;
-  size_t mLayoutPresShell;
-  size_t mLayoutStyleSets;
-  size_t mLayoutTextRuns;
-  size_t mLayoutPresContext;
-  size_t mPropertyTables;
+  mozilla::MallocSizeOf mMallocSizeOf;
+
+#undef FOR_EACH_SIZE
 };
 
 /**
  * nsWindowMemoryReporter is responsible for the 'explicit/window-objects'
  * memory reporter.
  *
  * We classify DOM window objects into one of three categories:
  *
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -20,16 +20,47 @@
 #include "jspubtd.h"
 
 #include "js/HashTable.h"
 #include "js/Utility.h"
 #include "js/Vector.h"
 
 class nsISupports;      // Needed for ObjectPrivateVisitor.
 
+namespace JS {
+
+struct TabSizes
+{
+    enum Kind {
+        Objects,
+        Strings,
+        Private,
+        Other
+    };
+
+    TabSizes() { mozilla::PodZero(this); }
+
+    void add(Kind kind, size_t n) {
+        switch (kind) {
+            case Objects: objects  += n; break;
+            case Strings: strings  += n; break;
+            case Private: private_ += n; break;
+            case Other:   other    += n; break;
+            default:      MOZ_CRASH("bad TabSizes kind");
+        }
+    }
+
+    size_t objects;
+    size_t strings;
+    size_t private_;
+    size_t other;
+};
+
+} // namespace JS
+
 namespace js {
 
 // In memory reporting, we have concept of "sundries", line items which are too
 // small to be worth reporting individually.  Under some circumstances, a memory
 // reporter gets tossed into the sundries bucket if it's smaller than
 // MemoryReportingSundriesThreshold() bytes.
 //
 // We need to define this value here, rather than in the code which actually
@@ -52,42 +83,43 @@ struct InefficientNonFlatteningStringHas
 // without updating all the required methods.  So we define a single macro list
 // in each class to name the fields (and notable characteristics of them), and
 // then use the following macros to transform those lists into the required
 // methods.
 //
 // In some classes, one or more of the macro arguments aren't used.  We use '_'
 // for those.
 //
-#define DECL_SIZE(gc, mSize)                      size_t mSize;
-#define ZERO_SIZE(gc, mSize)                      mSize(0),
-#define COPY_OTHER_SIZE(gc, mSize)                mSize(other.mSize),
-#define ADD_OTHER_SIZE(gc, mSize)                 mSize += other.mSize;
-#define ADD_SIZE_TO_N_IF_LIVE_GC_THING(gc, mSize) n += (gc) ? mSize : 0;
+#define DECL_SIZE(kind, gc, mSize)                      size_t mSize;
+#define ZERO_SIZE(kind, gc, mSize)                      mSize(0),
+#define COPY_OTHER_SIZE(kind, gc, mSize)                mSize(other.mSize),
+#define ADD_OTHER_SIZE(kind, gc, mSize)                 mSize += other.mSize;
+#define ADD_SIZE_TO_N_IF_LIVE_GC_THING(kind, gc, mSize) n += (js::gc == js::IsLiveGCThing) ? mSize : 0;
+#define ADD_TO_TAB_SIZES(kind, gc, mSize)               sizes->add(JS::TabSizes::kind, mSize);
 
 // Used to annotate which size_t fields measure live GC things and which don't.
 enum {
     NotLiveGCThing = false,
     IsLiveGCThing = true
 };
 
 struct ZoneStatsPod
 {
 #define FOR_EACH_SIZE(macro) \
-    macro(NotLiveGCThing, gcHeapArenaAdmin) \
-    macro(NotLiveGCThing, unusedGCThings) \
-    macro(IsLiveGCThing,  lazyScriptsGCHeap) \
-    macro(NotLiveGCThing, lazyScriptsMallocHeap) \
-    macro(IsLiveGCThing,  ionCodesGCHeap) \
-    macro(IsLiveGCThing,  typeObjectsGCHeap) \
-    macro(NotLiveGCThing, typeObjectsMallocHeap) \
-    macro(NotLiveGCThing, typePool) \
-    macro(IsLiveGCThing,  stringsShortGCHeap) \
-    macro(IsLiveGCThing,  stringsNormalGCHeap) \
-    macro(NotLiveGCThing, stringsNormalMallocHeap)
+    macro(Other,   NotLiveGCThing, gcHeapArenaAdmin) \
+    macro(Other,   NotLiveGCThing, unusedGCThings) \
+    macro(Other,   IsLiveGCThing,  lazyScriptsGCHeap) \
+    macro(Other,   NotLiveGCThing, lazyScriptsMallocHeap) \
+    macro(Other,   IsLiveGCThing,  ionCodesGCHeap) \
+    macro(Other,   IsLiveGCThing,  typeObjectsGCHeap) \
+    macro(Other,   NotLiveGCThing, typeObjectsMallocHeap) \
+    macro(Other,   NotLiveGCThing, typePool) \
+    macro(Strings, IsLiveGCThing,  stringsShortGCHeap) \
+    macro(Strings, IsLiveGCThing,  stringsNormalGCHeap) \
+    macro(Strings, NotLiveGCThing, stringsNormalMallocHeap)
 
     ZoneStatsPod()
       : FOR_EACH_SIZE(ZERO_SIZE)
         extra()
     {}
 
     void add(const ZoneStatsPod &other) {
         FOR_EACH_SIZE(ADD_OTHER_SIZE)
@@ -96,71 +128,80 @@ struct ZoneStatsPod
 
     size_t sizeOfLiveGCThings() const {
         size_t n = 0;
         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
         // Do nothing with |extra|.
         return n;
     }
 
+    void addToTabSizes(JS::TabSizes *sizes) const {
+        FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+        // Do nothing with |extra|.
+    }
+
     FOR_EACH_SIZE(DECL_SIZE)
     void *extra;    // This field can be used by embedders.
 
 #undef FOR_EACH_SIZE
 };
 
 } // namespace js
 
 namespace JS {
 
 // Data for tracking memory usage of things hanging off objects.
 struct ObjectsExtraSizes
 {
 #define FOR_EACH_SIZE(macro) \
-    macro(js::NotLiveGCThing, mallocHeapSlots) \
-    macro(js::NotLiveGCThing, mallocHeapElementsNonAsmJS) \
-    macro(js::NotLiveGCThing, mallocHeapElementsAsmJS) \
-    macro(js::NotLiveGCThing, nonHeapElementsAsmJS) \
-    macro(js::NotLiveGCThing, nonHeapCodeAsmJS) \
-    macro(js::NotLiveGCThing, mallocHeapAsmJSModuleData) \
-    macro(js::NotLiveGCThing, mallocHeapArgumentsData) \
-    macro(js::NotLiveGCThing, mallocHeapRegExpStatics) \
-    macro(js::NotLiveGCThing, mallocHeapPropertyIteratorData) \
-    macro(js::NotLiveGCThing, mallocHeapCtypesData)
+    macro(Objects, NotLiveGCThing, mallocHeapSlots) \
+    macro(Objects, NotLiveGCThing, mallocHeapElementsNonAsmJS) \
+    macro(Objects, NotLiveGCThing, mallocHeapElementsAsmJS) \
+    macro(Objects, NotLiveGCThing, nonHeapElementsAsmJS) \
+    macro(Objects, NotLiveGCThing, nonHeapCodeAsmJS) \
+    macro(Objects, NotLiveGCThing, mallocHeapAsmJSModuleData) \
+    macro(Objects, NotLiveGCThing, mallocHeapArgumentsData) \
+    macro(Objects, NotLiveGCThing, mallocHeapRegExpStatics) \
+    macro(Objects, NotLiveGCThing, mallocHeapPropertyIteratorData) \
+    macro(Objects, NotLiveGCThing, mallocHeapCtypesData)
 
     ObjectsExtraSizes()
       : FOR_EACH_SIZE(ZERO_SIZE)
         dummy()
     {}
 
     void add(const ObjectsExtraSizes &other) {
         FOR_EACH_SIZE(ADD_OTHER_SIZE)
     }
 
     size_t sizeOfLiveGCThings() const {
         size_t n = 0;
         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
         return n;
     }
 
+    void addToTabSizes(TabSizes *sizes) const {
+        FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+    }
+
     FOR_EACH_SIZE(DECL_SIZE)
     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
 
 #undef FOR_EACH_SIZE
 };
 
 // Data for tracking JIT-code memory usage.
 struct CodeSizes
 {
 #define FOR_EACH_SIZE(macro) \
-    macro(_, ion) \
-    macro(_, baseline) \
-    macro(_, regexp) \
-    macro(_, other) \
-    macro(_, unused)
+    macro(_, _, ion) \
+    macro(_, _, baseline) \
+    macro(_, _, regexp) \
+    macro(_, _, other) \
+    macro(_, _, unused)
 
     CodeSizes()
       : FOR_EACH_SIZE(ZERO_SIZE)
         dummy()
     {}
 
     FOR_EACH_SIZE(DECL_SIZE)
     int dummy;  // present just to absorb the trailing comma from FOR_EACH_SIZE(ZERO_SIZE)
@@ -251,27 +292,27 @@ struct NotableStringInfo : public String
     char *buffer;
 };
 
 // These measurements relate directly to the JSRuntime, and not to zones and
 // compartments within it.
 struct RuntimeSizes
 {
 #define FOR_EACH_SIZE(macro) \
-    macro(_, object) \
-    macro(_, atomsTable) \
-    macro(_, contexts) \
-    macro(_, dtoa) \
-    macro(_, temporary) \
-    macro(_, regexpData) \
-    macro(_, interpreterStack) \
-    macro(_, gcMarker) \
-    macro(_, mathCache) \
-    macro(_, scriptData) \
-    macro(_, scriptSources)
+    macro(_, _, object) \
+    macro(_, _, atomsTable) \
+    macro(_, _, contexts) \
+    macro(_, _, dtoa) \
+    macro(_, _, temporary) \
+    macro(_, _, regexpData) \
+    macro(_, _, interpreterStack) \
+    macro(_, _, gcMarker) \
+    macro(_, _, mathCache) \
+    macro(_, _, scriptData) \
+    macro(_, _, scriptSources)
 
     RuntimeSizes()
       : FOR_EACH_SIZE(ZERO_SIZE)
         code()
     {}
 
     FOR_EACH_SIZE(DECL_SIZE)
     CodeSizes code;
@@ -330,45 +371,45 @@ struct ZoneStats : js::ZoneStatsPod
 
     StringsHashMap strings;
     js::Vector<NotableStringInfo, 0, js::SystemAllocPolicy> notableStrings;
 };
 
 struct CompartmentStats
 {
 #define FOR_EACH_SIZE(macro) \
-    macro(js::IsLiveGCThing,  objectsGCHeapOrdinary) \
-    macro(js::IsLiveGCThing,  objectsGCHeapFunction) \
-    macro(js::IsLiveGCThing,  objectsGCHeapDenseArray) \
-    macro(js::IsLiveGCThing,  objectsGCHeapSlowArray) \
-    macro(js::IsLiveGCThing,  objectsGCHeapCrossCompartmentWrapper) \
-    macro(js::NotLiveGCThing, objectsPrivate) \
-    macro(js::IsLiveGCThing,  shapesGCHeapTreeGlobalParented) \
-    macro(js::IsLiveGCThing,  shapesGCHeapTreeNonGlobalParented) \
-    macro(js::IsLiveGCThing,  shapesGCHeapDict) \
-    macro(js::IsLiveGCThing,  shapesGCHeapBase) \
-    macro(js::NotLiveGCThing, shapesMallocHeapTreeTables) \
-    macro(js::NotLiveGCThing, shapesMallocHeapDictTables) \
-    macro(js::NotLiveGCThing, shapesMallocHeapTreeShapeKids) \
-    macro(js::NotLiveGCThing, shapesMallocHeapCompartmentTables) \
-    macro(js::IsLiveGCThing,  scriptsGCHeap) \
-    macro(js::NotLiveGCThing, scriptsMallocHeapData) \
-    macro(js::NotLiveGCThing, baselineData) \
-    macro(js::NotLiveGCThing, baselineStubsFallback) \
-    macro(js::NotLiveGCThing, baselineStubsOptimized) \
-    macro(js::NotLiveGCThing, ionData) \
-    macro(js::NotLiveGCThing, typeInferenceTypeScripts) \
-    macro(js::NotLiveGCThing, typeInferencePendingArrays) \
-    macro(js::NotLiveGCThing, typeInferenceAllocationSiteTables) \
-    macro(js::NotLiveGCThing, typeInferenceArrayTypeTables) \
-    macro(js::NotLiveGCThing, typeInferenceObjectTypeTables) \
-    macro(js::NotLiveGCThing, compartmentObject) \
-    macro(js::NotLiveGCThing, crossCompartmentWrappersTable) \
-    macro(js::NotLiveGCThing, regexpCompartment) \
-    macro(js::NotLiveGCThing, debuggeesSet)
+    macro(Objects, IsLiveGCThing,  objectsGCHeapOrdinary) \
+    macro(Objects, IsLiveGCThing,  objectsGCHeapFunction) \
+    macro(Objects, IsLiveGCThing,  objectsGCHeapDenseArray) \
+    macro(Objects, IsLiveGCThing,  objectsGCHeapSlowArray) \
+    macro(Objects, IsLiveGCThing,  objectsGCHeapCrossCompartmentWrapper) \
+    macro(Private, NotLiveGCThing, objectsPrivate) \
+    macro(Other,   IsLiveGCThing,  shapesGCHeapTreeGlobalParented) \
+    macro(Other,   IsLiveGCThing,  shapesGCHeapTreeNonGlobalParented) \
+    macro(Other,   IsLiveGCThing,  shapesGCHeapDict) \
+    macro(Other,   IsLiveGCThing,  shapesGCHeapBase) \
+    macro(Other,   NotLiveGCThing, shapesMallocHeapTreeTables) \
+    macro(Other,   NotLiveGCThing, shapesMallocHeapDictTables) \
+    macro(Other,   NotLiveGCThing, shapesMallocHeapTreeShapeKids) \
+    macro(Other,   NotLiveGCThing, shapesMallocHeapCompartmentTables) \
+    macro(Other,   IsLiveGCThing,  scriptsGCHeap) \
+    macro(Other,   NotLiveGCThing, scriptsMallocHeapData) \
+    macro(Other,   NotLiveGCThing, baselineData) \
+    macro(Other,   NotLiveGCThing, baselineStubsFallback) \
+    macro(Other,   NotLiveGCThing, baselineStubsOptimized) \
+    macro(Other,   NotLiveGCThing, ionData) \
+    macro(Other,   NotLiveGCThing, typeInferenceTypeScripts) \
+    macro(Other,   NotLiveGCThing, typeInferencePendingArrays) \
+    macro(Other,   NotLiveGCThing, typeInferenceAllocationSiteTables) \
+    macro(Other,   NotLiveGCThing, typeInferenceArrayTypeTables) \
+    macro(Other,   NotLiveGCThing, typeInferenceObjectTypeTables) \
+    macro(Other,   NotLiveGCThing, compartmentObject) \
+    macro(Other,   NotLiveGCThing, crossCompartmentWrappersTable) \
+    macro(Other,   NotLiveGCThing, regexpCompartment) \
+    macro(Other,   NotLiveGCThing, debuggeesSet)
 
     CompartmentStats()
       : FOR_EACH_SIZE(ZERO_SIZE)
         objectsExtra(),
         extra()
     {}
 
     CompartmentStats(const CompartmentStats &other)
@@ -386,32 +427,38 @@ struct CompartmentStats
     size_t sizeOfLiveGCThings() const {
         size_t n = 0;
         FOR_EACH_SIZE(ADD_SIZE_TO_N_IF_LIVE_GC_THING)
         n += objectsExtra.sizeOfLiveGCThings();
         // Do nothing with |extra|.
         return n;
     }
 
+    void addToTabSizes(TabSizes *sizes) const {
+        FOR_EACH_SIZE(ADD_TO_TAB_SIZES);
+        objectsExtra.addToTabSizes(sizes);
+        // Do nothing with |extra|.
+    }
+
     FOR_EACH_SIZE(DECL_SIZE)
     ObjectsExtraSizes  objectsExtra;
     void               *extra;  // This field can be used by embedders.
 
 #undef FOR_EACH_SIZE
 };
 
 struct RuntimeStats
 {
 #define FOR_EACH_SIZE(macro) \
-    macro(_, gcHeapChunkTotal) \
-    macro(_, gcHeapDecommittedArenas) \
-    macro(_, gcHeapUnusedChunks) \
-    macro(_, gcHeapUnusedArenas) \
-    macro(_, gcHeapChunkAdmin) \
-    macro(_, gcHeapGCThings) \
+    macro(_, _, gcHeapChunkTotal) \
+    macro(_, _, gcHeapDecommittedArenas) \
+    macro(_, _, gcHeapUnusedChunks) \
+    macro(_, _, gcHeapUnusedArenas) \
+    macro(_, _, gcHeapChunkAdmin) \
+    macro(_, _, gcHeapGCThings) \
 
     RuntimeStats(mozilla::MallocSizeOf mallocSizeOf)
       : FOR_EACH_SIZE(ZERO_SIZE)
         runtime(),
         cTotals(),
         zTotals(),
         compartmentStatsVector(),
         zoneStatsVector(),
@@ -483,11 +530,22 @@ extern JS_PUBLIC_API(size_t)
 SystemCompartmentCount(JSRuntime *rt);
 
 extern JS_PUBLIC_API(size_t)
 UserCompartmentCount(JSRuntime *rt);
 
 extern JS_PUBLIC_API(size_t)
 PeakSizeOfTemporary(const JSRuntime *rt);
 
+extern JS_PUBLIC_API(bool)
+AddSizeOfTab(JSRuntime *rt, JSObject *obj, mozilla::MallocSizeOf mallocSizeOf,
+             ObjectPrivateVisitor *opv, TabSizes *sizes);
+
 } // namespace JS
 
+#undef DECL_SIZE
+#undef ZERO_SIZE
+#undef COPY_OTHER_SIZE
+#undef ADD_OTHER_SIZE
+#undef ADD_SIZE_TO_N_IF_LIVE_GC_THING
+#undef ADD_TO_TAB_SIZES
+
 #endif /* js_MemoryMetrics_h */
--- a/js/src/gc/Iteration.cpp
+++ b/js/src/gc/Iteration.cpp
@@ -21,43 +21,66 @@ void
 js::TraceRuntime(JSTracer *trc)
 {
     JS_ASSERT(!IS_GC_MARKING_TRACER(trc));
 
     AutoPrepareForTracing prep(trc->runtime);
     MarkRuntime(trc);
 }
 
+static void
+IterateCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data,
+                               JSIterateCompartmentCallback compartmentCallback,
+                               IterateArenaCallback arenaCallback,
+                               IterateCellCallback cellCallback)
+{
+    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
+        (*compartmentCallback)(rt, data, comp);
+
+    for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) {
+        JSGCTraceKind traceKind = MapAllocToTraceKind(AllocKind(thingKind));
+        size_t thingSize = Arena::thingSize(AllocKind(thingKind));
+
+        for (ArenaIter aiter(zone, AllocKind(thingKind)); !aiter.done(); aiter.next()) {
+            ArenaHeader *aheader = aiter.get();
+            (*arenaCallback)(rt, data, aheader->getArena(), traceKind, thingSize);
+            for (CellIterUnderGC iter(aheader); !iter.done(); iter.next())
+                (*cellCallback)(rt, data, iter.getCell(), traceKind, thingSize);
+        }
+    }
+}
+
 void
 js::IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data,
                                         IterateZoneCallback zoneCallback,
                                         JSIterateCompartmentCallback compartmentCallback,
                                         IterateArenaCallback arenaCallback,
                                         IterateCellCallback cellCallback)
 {
     AutoPrepareForTracing prop(rt);
 
     for (ZonesIter zone(rt); !zone.done(); zone.next()) {
         (*zoneCallback)(rt, data, zone);
-
-        for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
-            (*compartmentCallback)(rt, data, comp);
-
-        for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) {
-            JSGCTraceKind traceKind = MapAllocToTraceKind(AllocKind(thingKind));
-            size_t thingSize = Arena::thingSize(AllocKind(thingKind));
+        IterateCompartmentsArenasCells(rt, zone, data,
+                                       compartmentCallback, arenaCallback, cellCallback);
+    }
+}
 
-            for (ArenaIter aiter(zone, AllocKind(thingKind)); !aiter.done(); aiter.next()) {
-                ArenaHeader *aheader = aiter.get();
-                (*arenaCallback)(rt, data, aheader->getArena(), traceKind, thingSize);
-                for (CellIterUnderGC iter(aheader); !iter.done(); iter.next())
-                    (*cellCallback)(rt, data, iter.getCell(), traceKind, thingSize);
-            }
-        }
-    }
+void
+js::IterateZoneCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data,
+                                       IterateZoneCallback zoneCallback,
+                                       JSIterateCompartmentCallback compartmentCallback,
+                                       IterateArenaCallback arenaCallback,
+                                       IterateCellCallback cellCallback)
+{
+    AutoPrepareForTracing prop(rt);
+
+    (*zoneCallback)(rt, data, zone);
+    IterateCompartmentsArenasCells(rt, zone, data,
+                                   compartmentCallback, arenaCallback, cellCallback);
 }
 
 void
 js::IterateChunks(JSRuntime *rt, void *data, IterateChunkCallback chunkCallback)
 {
     AutoPrepareForTracing prep(rt);
 
     for (js::GCChunkSet::Range r = rt->gcChunkSet.all(); !r.empty(); r.popFront())
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1269,28 +1269,39 @@ MarkStackRangeConservatively(JSTracer *t
 typedef void (*IterateChunkCallback)(JSRuntime *rt, void *data, gc::Chunk *chunk);
 typedef void (*IterateZoneCallback)(JSRuntime *rt, void *data, JS::Zone *zone);
 typedef void (*IterateArenaCallback)(JSRuntime *rt, void *data, gc::Arena *arena,
                                      JSGCTraceKind traceKind, size_t thingSize);
 typedef void (*IterateCellCallback)(JSRuntime *rt, void *data, void *thing,
                                     JSGCTraceKind traceKind, size_t thingSize);
 
 /*
- * This function calls |compartmentCallback| on every compartment,
- * |arenaCallback| on every in-use arena, and |cellCallback| on every in-use
- * cell in the GC heap.
+ * This function calls |zoneCallback| on every zone, |compartmentCallback| on
+ * every compartment, |arenaCallback| on every in-use arena, and |cellCallback|
+ * on every in-use cell in the GC heap.
  */
 extern void
 IterateZonesCompartmentsArenasCells(JSRuntime *rt, void *data,
                                     IterateZoneCallback zoneCallback,
                                     JSIterateCompartmentCallback compartmentCallback,
                                     IterateArenaCallback arenaCallback,
                                     IterateCellCallback cellCallback);
 
 /*
+ * This function is like IterateZonesCompartmentsArenasCells, but does it for a
+ * single zone.
+ */
+extern void
+IterateZoneCompartmentsArenasCells(JSRuntime *rt, Zone *zone, void *data,
+                                   IterateZoneCallback zoneCallback,
+                                   JSIterateCompartmentCallback compartmentCallback,
+                                   IterateArenaCallback arenaCallback,
+                                   IterateCellCallback cellCallback);
+
+/*
  * Invoke chunkCallback on every in-use chunk.
  */
 extern void
 IterateChunks(JSRuntime *rt, void *data, IterateChunkCallback chunkCallback);
 
 typedef void (*IterateScriptCallback)(JSRuntime *rt, void *data, JSScript *script);
 
 /*
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -38,16 +38,17 @@
 #include "frontend/BytecodeCompiler.h"
 #include "gc/Marking.h"
 #include "jit/AsmJSModule.h"
 #include "jit/BaselineJIT.h"
 #include "js/MemoryMetrics.h"
 #include "js/OldDebugAPI.h"
 #include "vm/ArgumentsObject.h"
 #include "vm/Interpreter.h"
+#include "vm/ProxyObject.h"
 #include "vm/RegExpStaticsObject.h"
 #include "vm/Shape.h"
 
 #include "jsatominlines.h"
 #include "jsboolinlines.h"
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 
@@ -5675,18 +5676,34 @@ JSObject::addSizeOfExcludingThis(mozilla
             sizes->mallocHeapElementsAsmJS += mallocSizeOf(elements);
 #endif
         } else {
             sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements);
         }
     }
 
     // Other things may be measured in the future if DMD indicates it is worthwhile.
-    // Note that sizes->private_ is measured elsewhere.
-    if (is<ArgumentsObject>()) {
+    if (is<JSFunction>() ||
+        is<JSObject>() ||
+        is<ArrayObject>() ||
+        is<CallObject>() ||
+        is<RegExpObject>() ||
+        is<ProxyObject>())
+    {
+        // Do nothing.  But this function is hot, and we win by getting the
+        // common cases out of the way early.  Some stats on the most common
+        // classes, as measured during a vanilla browser session:
+        // - (53.7%, 53.7%): Function
+        // - (18.0%, 71.7%): Object
+        // - (16.9%, 88.6%): Array
+        // - ( 3.9%, 92.5%): Call
+        // - ( 2.8%, 95.3%): RegExp
+        // - ( 1.0%, 96.4%): Proxy
+
+    } else if (is<ArgumentsObject>()) {
         sizes->mallocHeapArgumentsData += as<ArgumentsObject>().sizeOfMisc(mallocSizeOf);
     } else if (is<RegExpStaticsObject>()) {
         sizes->mallocHeapRegExpStatics += as<RegExpStaticsObject>().sizeOfData(mallocSizeOf);
     } else if (is<PropertyIteratorObject>()) {
         sizes->mallocHeapPropertyIteratorData += as<PropertyIteratorObject>().sizeOfMisc(mallocSizeOf);
 #ifdef JS_ION
     } else if (is<AsmJSModuleObject>()) {
         as<AsmJSModuleObject>().addSizeOfMisc(mallocSizeOf, &sizes->nonHeapCodeAsmJS,
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -373,17 +373,17 @@ class JSObject : public js::ObjectImpl
      * The number of allocated slots is not stored explicitly, and changes to
      * the slots must track changes in the slot span.
      */
     static bool growSlots(js::ThreadSafeContext *cx, js::HandleObject obj, uint32_t oldCount,
                           uint32_t newCount);
     static void shrinkSlots(js::ThreadSafeContext *cx, js::HandleObject obj, uint32_t oldCount,
                             uint32_t newCount);
 
-    bool hasDynamicSlots() const { return slots != nullptr; }
+    bool hasDynamicSlots() const { return !!slots; }
 
   protected:
     static inline bool updateSlotsForSpan(js::ThreadSafeContext *cx,
                                           js::HandleObject obj, size_t oldSpan, size_t newSpan);
 
   public:
     /*
      * Trigger the write barrier on a range of slots that will no longer be
--- a/js/src/vm/MemoryMetrics.cpp
+++ b/js/src/vm/MemoryMetrics.cpp
@@ -18,18 +18,19 @@
 #include "jit/Ion.h"
 #include "vm/ArrayObject.h"
 #include "vm/Runtime.h"
 #include "vm/Shape.h"
 #include "vm/String.h"
 #include "vm/WrapperObject.h"
 
 using mozilla::DebugOnly;
+using mozilla::MallocSizeOf;
+using mozilla::MoveRef;
 using mozilla::OldMove;
-using mozilla::MoveRef;
 using mozilla::PodEqual;
 
 using namespace js;
 
 using JS::RuntimeStats;
 using JS::ObjectPrivateVisitor;
 using JS::ZoneStats;
 using JS::CompartmentStats;
@@ -263,23 +264,20 @@ StatsCellCallback(JSRuntime *rt, void *d
             cStats->objectsGCHeapDenseArray += thingSize;
         else if (obj->is<CrossCompartmentWrapperObject>())
             cStats->objectsGCHeapCrossCompartmentWrapper += thingSize;
         else
             cStats->objectsGCHeapOrdinary += thingSize;
 
         obj->addSizeOfExcludingThis(rtStats->mallocSizeOf_, &cStats->objectsExtra);
 
-        // JSObject::sizeOfExcludingThis() doesn't measure objectsPrivate,
-        // so we do it here.
         if (ObjectPrivateVisitor *opv = closure->opv) {
             nsISupports *iface;
-            if (opv->getISupports_(obj, &iface) && iface) {
+            if (opv->getISupports_(obj, &iface) && iface)
                 cStats->objectsPrivate += opv->sizeOfIncludingThis(iface);
-            }
         }
         break;
       }
 
       case JSTRACE_STRING: {
         JSString *str = static_cast<JSString *>(thing);
 
         size_t strCharsSize = str->sizeOfExcludingThis(rtStats->mallocSizeOf_);
@@ -333,17 +331,16 @@ StatsCellCallback(JSRuntime *rt, void *d
         cStats->shapesGCHeapBase += thingSize;
         break;
       }
 
       case JSTRACE_SCRIPT: {
         JSScript *script = static_cast<JSScript *>(thing);
         CompartmentStats *cStats = GetCompartmentStats(script->compartment());
         cStats->scriptsGCHeap += thingSize;
-
         cStats->scriptsMallocHeapData += script->sizeOfData(rtStats->mallocSizeOf_);
         cStats->typeInferenceTypeScripts += script->sizeOfTypeScript(rtStats->mallocSizeOf_);
 #ifdef JS_ION
         jit::AddSizeOfBaselineData(script, rtStats->mallocSizeOf_, &cStats->baselineData,
                                    &cStats->baselineStubsFallback);
         cStats->ionData += jit::SizeOfIonData(script, rtStats->mallocSizeOf_);
 #endif
 
@@ -441,24 +438,22 @@ JS::CollectRuntimeStats(JSRuntime *rt, R
 
     IterateChunks(rt, &rtStats->gcHeapDecommittedArenas,
                   DecommittedArenasChunkCallback);
 
     // Take the per-compartment measurements.
     StatsClosure closure(rtStats, opv);
     if (!closure.init())
         return false;
-    rtStats->runtime.scriptSources = 0;
     IterateZonesCompartmentsArenasCells(rt, &closure, StatsZoneCallback, StatsCompartmentCallback,
                                         StatsArenaCallback, StatsCellCallback);
 
     // Take the "explicit/js/runtime/" measurements.
     rt->addSizeOfIncludingThis(rtStats->mallocSizeOf_, &rtStats->runtime);
 
-    rtStats->gcHeapGCThings = 0;
     for (size_t i = 0; i < rtStats->zoneStatsVector.length(); i++) {
         ZoneStats &zStats = rtStats->zoneStatsVector[i];
 
         rtStats->zTotals.add(zStats);
 
         // Move any strings which take up more than the sundries threshold
         // (counting all of their copies together) into notableStrings.
         FindNotableStrings(zStats);
@@ -526,8 +521,67 @@ JS::UserCompartmentCount(JSRuntime *rt)
 }
 
 JS_PUBLIC_API(size_t)
 JS::PeakSizeOfTemporary(const JSRuntime *rt)
 {
     return rt->tempLifoAlloc.peakSizeOfExcludingThis();
 }
 
+namespace JS {
+
+JS_PUBLIC_API(bool)
+AddSizeOfTab(JSRuntime *rt, JSObject *obj, MallocSizeOf mallocSizeOf, ObjectPrivateVisitor *opv,
+             TabSizes *sizes)
+{
+    class SimpleJSRuntimeStats : public JS::RuntimeStats
+    {
+      public:
+        SimpleJSRuntimeStats(MallocSizeOf mallocSizeOf)
+          : JS::RuntimeStats(mallocSizeOf)
+        {}
+
+        virtual void initExtraZoneStats(JS::Zone *zone, JS::ZoneStats *zStats)
+            MOZ_OVERRIDE
+        {}
+
+        virtual void initExtraCompartmentStats(
+            JSCompartment *c, JS::CompartmentStats *cStats) MOZ_OVERRIDE
+        {}
+    };
+
+    SimpleJSRuntimeStats rtStats(mallocSizeOf);
+
+    JS::Zone *zone = GetObjectZone(obj);
+
+    if (!rtStats.compartmentStatsVector.reserve(zone->compartments.length()))
+        return false;
+
+    if (!rtStats.zoneStatsVector.reserve(1))
+        return false;
+
+    // Take the per-compartment measurements.
+    StatsClosure closure(&rtStats, opv);
+    if (!closure.init())
+        return false;
+    IterateZoneCompartmentsArenasCells(rt, zone, &closure, StatsZoneCallback,
+                                       StatsCompartmentCallback, StatsArenaCallback,
+                                       StatsCellCallback);
+
+    JS_ASSERT(rtStats.zoneStatsVector.length() == 1);
+    rtStats.zTotals.add(rtStats.zoneStatsVector[0]);
+
+    for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) {
+        CompartmentStats &cStats = rtStats.compartmentStatsVector[i];
+        rtStats.cTotals.add(cStats);
+    }
+
+    for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
+        comp->compartmentStats = NULL;
+
+    rtStats.zTotals.addToTabSizes(sizes);
+    rtStats.cTotals.addToTabSizes(sizes);
+
+    return true;
+}
+
+} // namespace JS
+
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2411,17 +2411,17 @@ SizeOfTreeIncludingThis(nsINode *tree)
 class OrphanReporter : public JS::ObjectPrivateVisitor
 {
   public:
     OrphanReporter(GetISupportsFun aGetISupports)
       : JS::ObjectPrivateVisitor(aGetISupports)
     {
     }
 
-    virtual size_t sizeOfIncludingThis(nsISupports *aSupports) {
+    virtual size_t sizeOfIncludingThis(nsISupports *aSupports) MOZ_OVERRIDE {
         size_t n = 0;
         nsCOMPtr<nsINode> node = do_QueryInterface(aSupports);
         // https://bugzilla.mozilla.org/show_bug.cgi?id=773533#c11 explains
         // that we have to skip XBL elements because they violate certain
         // assumptions.  Yuk.
         if (node && !node->IsInDoc() &&
             !(node->IsElement() && node->AsElement()->IsInNamespace(kNameSpaceID_XBL)))
         {
@@ -2669,16 +2669,35 @@ JSReporter::CollectReports(WindowPaths *
 
     REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect"),
                  KIND_HEAP, xpconnect,
                  "Memory used by XPConnect.");
 
     return NS_OK;
 }
 
+static nsresult
+JSSizeOfTab(JSObject *obj, size_t *jsObjectsSize, size_t *jsStringsSize,
+            size_t *jsPrivateSize, size_t *jsOtherSize)
+{
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
+
+    TabSizes sizes;
+    OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject);
+    NS_ENSURE_TRUE(JS::AddSizeOfTab(rt, obj, moz_malloc_size_of,
+                                    &orphanReporter, &sizes),
+                   NS_ERROR_OUT_OF_MEMORY);
+
+    *jsObjectsSize = sizes.objects;
+    *jsStringsSize = sizes.strings;
+    *jsPrivateSize = sizes.private_;
+    *jsOtherSize   = sizes.other;
+    return NS_OK;
+}
+
 } // namespace xpc
 
 #ifdef MOZ_CRASHREPORTER
 static bool
 DiagnosticMemoryCallback(void *ptr, size_t size)
 {
     return CrashReporter::RegisterAppMemory(ptr, size) == NS_OK;
 }
@@ -3035,16 +3054,17 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
 
     // Register memory reporters and distinguished amount functions.
     NS_RegisterMemoryReporter(new JSMainRuntimeCompartmentsReporter);
     NS_RegisterMemoryReporter(new JSMainRuntimeTemporaryPeakReporter());
     RegisterJSMainRuntimeGCHeapDistinguishedAmount(JSMainRuntimeGCHeapDistinguishedAmount);
     RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(JSMainRuntimeTemporaryPeakDistinguishedAmount);
     RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount);
     RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount);
+    mozilla::RegisterJSSizeOfTab(JSSizeOfTab);
 
     // Install a JavaScript 'debugger' keyword handler in debug builds only
 #ifdef DEBUG
     if (!JS_GetGlobalDebugHooks(runtime)->debuggerHandler)
         xpc_InstallJSDebuggerKeywordHandler(runtime);
 #endif
 }
 
--- a/layout/base/nsArenaMemoryStats.h
+++ b/layout/base/nsArenaMemoryStats.h
@@ -1,20 +1,81 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsArenaMemoryStats_h
 #define nsArenaMemoryStats_h
 
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+
+class nsTabSizes {
+public:
+  enum Kind {
+      DOM,        // DOM stuff.
+      Style,      // Style stuff.
+      Other       // Everything else.
+  };
+
+  nsTabSizes() { mozilla::PodZero(this); }
+
+  void add(Kind kind, size_t n)
+  {
+    switch (kind) {
+      case DOM:   mDom   += n; break;
+      case Style: mStyle += n; break;
+      case Other: mOther += n; break;
+      default:    MOZ_CRASH("bad nsTabSizes kind");
+    }
+  }
+
+  size_t mDom;
+  size_t mStyle;
+  size_t mOther;
+};
+
 #define FRAME_ID_STAT_FIELD(classname) mArena##classname
 
 struct nsArenaMemoryStats {
-#define FRAME_ID(classname) size_t FRAME_ID_STAT_FIELD(classname);
-#include "nsFrameIdList.h"
-#undef FRAME_ID
-  size_t mLineBoxes;
-  size_t mRuleNodes;
-  size_t mStyleContexts;
-  size_t mOther;
+#define FOR_EACH_SIZE(macro) \
+  macro(Other, mLineBoxes) \
+  macro(Style, mRuleNodes) \
+  macro(Style, mStyleContexts) \
+  macro(Other, mOther)
+
+  nsArenaMemoryStats()
+    :
+      #define ZERO_SIZE(kind, mSize) mSize(0),
+      FOR_EACH_SIZE(ZERO_SIZE)
+      #undef ZERO_SIZE
+      #define FRAME_ID(classname) FRAME_ID_STAT_FIELD(classname)(),
+      #include "nsFrameIdList.h"
+      #undef FRAME_ID
+      dummy()
+  {}
+
+  void addToTabSizes(nsTabSizes *sizes) const
+  {
+    #define ADD_TO_TAB_SIZES(kind, mSize) sizes->add(nsTabSizes::kind, mSize);
+    FOR_EACH_SIZE(ADD_TO_TAB_SIZES)
+    #undef ADD_TO_TAB_SIZES
+    #define FRAME_ID(classname) \
+      sizes->add(nsTabSizes::Other, FRAME_ID_STAT_FIELD(classname));
+    #include "nsFrameIdList.h"
+    #undef FRAME_ID
+  }
+
+  #define DECL_SIZE(kind, mSize) size_t mSize;
+  FOR_EACH_SIZE(DECL_SIZE)
+  #undef DECL_SIZE
+  #define FRAME_ID(classname) size_t FRAME_ID_STAT_FIELD(classname);
+  #include "nsFrameIdList.h"
+  #undef FRAME_ID
+  int dummy;  // present just to absorb the trailing comma from FRAME_ID in the
+              // constructor
+
+#undef FOR_EACH_SIZE
 };
 
 #endif // nsArenaMemoryStats_h
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -150,16 +150,31 @@
       // and this file hasn't been updated appropriately.
       dummy = mgr[amounts[i]];
       ok(dummy !== undefined,
          "accessed an unknown distinguished amount: " + amounts[i]);
     } catch (ex) {
     }
   }
 
+  // Run sizeOfTab() to make sure it doesn't crash.  We can't check the result
+  // values because they're non-deterministic.
+  let jsObjectsSize = {};
+  let jsStringsSize = {};
+  let jsOtherSize = {};
+  let domSize = {};
+  let styleSize = {};
+  let otherSize = {};
+  let totalSize = {};
+  let jsMilliseconds = {};
+  let nonJSMilliseconds = {};
+  mgr.sizeOfTab(window, jsObjectsSize, jsStringsSize, jsOtherSize,
+                domSize, styleSize, otherSize, totalSize,
+                jsMilliseconds, nonJSMilliseconds);
+
   let e = mgr.enumerateReporters();
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     r.collectReports(handleReport, null);
 
     // Access |name| to make sure it doesn't crash or assert.
     dummy = r.name;
   }
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -1,22 +1,23 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-interface nsISimpleEnumerator;
+interface nsICancelableRunnable;
+interface nsIDOMWindow;
 interface nsIRunnable;
-interface nsICancelableRunnable;
+interface nsISimpleEnumerator;
 
 /*
- * Memory reporters measure Firefox's memory usage.  They are mainly used to
+ * Memory reporters measure Firefox's memory usage.  They are primarily used to
  * generate the about:memory page.  You should read
  * https://wiki.mozilla.org/Memory_Reporting before writing a memory
  * reporter.
  */
 
 [scriptable, function, uuid(3a61be3b-b93b-461a-a4f8-388214f558b1)]
 interface nsIMemoryReporterCallback : nsISupports
 {
@@ -303,22 +304,42 @@ interface nsIMemoryReporterManager : nsI
   [infallible] readonly attribute boolean hasMozMallocUsableSize;
 
   /*
    * Run a series of GC/CC's in an attempt to minimize the application's memory
    * usage.  When we're finished, we invoke the given runnable if it's not
    * null.  Returns a reference to the runnable used for carrying out the task.
    */
   nsICancelableRunnable minimizeMemoryUsage(in nsIRunnable callback);
+
+  /*
+   * Measure the memory that is known to be owned by this tab, split up into
+   * several broad categories.  Note that this will be an underestimate of the
+   * true number, due to imperfect memory reporter coverage (corresponding to
+   * about:memory's "heap-unclassified"), and due to some memory shared between
+   * tabs not being counted.
+   *
+   * The time taken for the measurement (split into JS and non-JS parts) is
+   * also returned.
+   */
+  void sizeOfTab(in nsIDOMWindow window,
+                 out int64_t jsObjectsSize, out int64_t jsStringsSize,
+                 out int64_t jsOtherSize, out int64_t domSize,
+                 out int64_t styleSize, out int64_t otherSize,
+                 out int64_t totalSize,
+                 out double jsMilliseconds, out double nonJSMilliseconds);
 };
 
 %{C++
 
+#include "js/TypeDecls.h"
 #include "nsStringGlue.h"
 
+class nsPIDOMWindow;
+
 // Note that the memory reporters are held in an nsCOMArray, which means
 // that individual reporters should be referenced with |nsIMemoryReporter *|
 // instead of nsCOMPtr<nsIMemoryReporter>.
 
 XPCOM_API(nsresult) NS_RegisterMemoryReporter(nsIMemoryReporter* aReporter);
 XPCOM_API(nsresult) NS_UnregisterMemoryReporter(nsIMemoryReporter* aReporter);
 
 namespace mozilla {
@@ -350,16 +371,31 @@ DECL_UNREGISTER_DISTINGUISHED_AMOUNT(Sto
 DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual)
 DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
 
 DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)
 
 #undef DECL_REGISTER_DISTINGUISHED_AMOUNT
 #undef DECL_UNREGISTER_DISTINGUISHED_AMOUNT
 
+// Likewise for per-tab measurement.
+
+typedef nsresult (*JSSizeOfTabFn)(JSObject* aObj,
+                                  size_t* aJsObjectsSize,
+                                  size_t* aJsStringSize,
+                                  size_t* aJsPrivateSize,
+                                  size_t* aJsOtherSize);
+typedef nsresult (*NonJSSizeOfTabFn)(nsPIDOMWindow* aWindow,
+                                     size_t* aDomSize,
+                                     size_t* aStyleSize,
+                                     size_t* aOtherSize);
+
+nsresult RegisterJSSizeOfTab(JSSizeOfTabFn aSizeOfTabFn);
+nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn);
+
 }
 
 #if defined(MOZ_DMD)
 namespace mozilla {
 namespace dmd {
 // This runs all the memory reporters but does nothing with the results;  i.e.
 // it does the minimal amount of work possible for DMD to do its thing.
 void RunReporters();
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -7,17 +7,20 @@
 #include "nsAtomTable.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsServiceManagerUtils.h"
 #include "nsMemoryReporterManager.h"
 #include "nsISimpleEnumerator.h"
 #include "nsThreadUtils.h"
+#include "nsIDOMWindow.h"
+#include "nsPIDOMWindow.h"
 #include "nsIObserverService.h"
+#include "nsIGlobalObject.h"
 #if defined(XP_LINUX)
 #include "nsMemoryInfoDumper.h"
 #endif
 #include "mozilla/Attributes.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Services.h"
 #include "mozilla/Telemetry.h"
 
@@ -840,16 +843,17 @@ HashtableEnumerator::GetNext(nsISupports
 
 } // anonymous namespace
 
 nsMemoryReporterManager::nsMemoryReporterManager()
   : mMutex("nsMemoryReporterManager::mMutex"),
     mIsRegistrationBlocked(false)
 {
     PodZero(&mAmountFns);
+    PodZero(&mSizeOfTabFns);
 }
 
 nsMemoryReporterManager::~nsMemoryReporterManager()
 {
 }
 
 NS_IMETHODIMP
 nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator** aResult)
@@ -1262,16 +1266,66 @@ nsMemoryReporterManager::MinimizeMemoryU
 
   nsRefPtr<nsICancelableRunnable> runnable =
     new MinimizeMemoryUsageRunnable(aCallback);
   NS_ADDREF(*aResult = runnable);
 
   return NS_DispatchToMainThread(runnable);
 }
 
+NS_IMETHODIMP
+nsMemoryReporterManager::SizeOfTab(nsIDOMWindow* aTopWindow,
+                                   int64_t* aJSObjectsSize,
+                                   int64_t* aJSStringsSize,
+                                   int64_t* aJSOtherSize,
+                                   int64_t* aDomSize,
+                                   int64_t* aStyleSize,
+                                   int64_t* aOtherSize,
+                                   int64_t* aTotalSize,
+                                   double*  aJSMilliseconds,
+                                   double*  aNonJSMilliseconds)
+{
+    nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aTopWindow);
+    nsCOMPtr<nsPIDOMWindow> piWindow = do_QueryInterface(aTopWindow);
+    NS_ENSURE_TRUE(!!global && !!piWindow, NS_ERROR_FAILURE);
+
+    TimeStamp t1 = TimeStamp::Now();
+
+    // Measure JS memory consumption (and possibly some non-JS consumption, via
+    // |jsPrivateSize|).
+    size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize;
+    nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(),
+                                    &jsObjectsSize, &jsStringsSize,
+                                    &jsPrivateSize, &jsOtherSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    TimeStamp t2 = TimeStamp::Now();
+
+    // Measure non-JS memory consumption.
+    size_t domSize, styleSize, otherSize;
+    mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize);
+
+    TimeStamp t3 = TimeStamp::Now();
+
+    *aTotalSize = 0;
+    #define DO(aN, n) { *aN = (n); *aTotalSize += (n); }
+    DO(aJSObjectsSize, jsObjectsSize);
+    DO(aJSStringsSize, jsStringsSize);
+    DO(aJSOtherSize,   jsOtherSize);
+    DO(aDomSize,       jsPrivateSize + domSize);
+    DO(aStyleSize,     styleSize);
+    DO(aOtherSize,     otherSize);
+    #undef DO
+
+    *aJSMilliseconds    = (t2 - t1).ToMilliseconds();
+    *aNonJSMilliseconds = (t3 - t2).ToMilliseconds();
+
+    return NS_OK;
+}
+
 // Most memory reporters don't need thread safety, but some do.  Make them all
 // thread-safe just to be safe.  Memory reporters are created and destroyed
 // infrequently enough that the performance cost should be negligible.
 NS_IMPL_ISUPPORTS1(MemoryUniReporter, nsIMemoryReporter)
 
 nsresult
 NS_RegisterMemoryReporter(nsIMemoryReporter* aReporter)
 {
@@ -1289,44 +1343,41 @@ NS_UnregisterMemoryReporter(nsIMemoryRep
     if (!mgr) {
         return NS_ERROR_FAILURE;
     }
     return mgr->UnregisterReporter(aReporter);
 }
 
 namespace mozilla {
 
+#define GET_MEMORY_REPORTER_MANAGER(mgr)                                      \
+    nsCOMPtr<nsIMemoryReporterManager> imgr =                                 \
+        do_GetService("@mozilla.org/memory-reporter-manager;1");              \
+    nsRefPtr<nsMemoryReporterManager> mgr =                                   \
+        static_cast<nsMemoryReporterManager*>(imgr.get());                    \
+    if (!mgr) {                                                               \
+        return NS_ERROR_FAILURE;                                              \
+    }
+
 // Macro for generating functions that register distinguished amount functions
 // with the memory reporter manager.
 #define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name)                      \
     nsresult                                                                  \
     Register##name##DistinguishedAmount(kind##AmountFn aAmountFn)             \
     {                                                                         \
-        nsCOMPtr<nsIMemoryReporterManager> imgr =                             \
-            do_GetService("@mozilla.org/memory-reporter-manager;1");          \
-        nsRefPtr<nsMemoryReporterManager> mgr =                               \
-            static_cast<nsMemoryReporterManager*>(imgr.get());                \
-        if (!mgr) {                                                           \
-            return NS_ERROR_FAILURE;                                          \
-        }                                                                     \
+        GET_MEMORY_REPORTER_MANAGER(mgr)                                      \
         mgr->mAmountFns.m##name = aAmountFn;                                  \
         return NS_OK;                                                         \
     }
 
 #define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name)                          \
     nsresult                                                                  \
     Unregister##name##DistinguishedAmount()                                   \
     {                                                                         \
-        nsCOMPtr<nsIMemoryReporterManager> imgr =                             \
-            do_GetService("@mozilla.org/memory-reporter-manager;1");          \
-        nsRefPtr<nsMemoryReporterManager> mgr =                               \
-            static_cast<nsMemoryReporterManager*>(imgr.get());                \
-        if (!mgr) {                                                           \
-            return NS_ERROR_FAILURE;                                          \
-        }                                                                     \
+        GET_MEMORY_REPORTER_MANAGER(mgr)                                      \
         mgr->mAmountFns.m##name = nullptr;                                    \
         return NS_OK;                                                         \
     }
 
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap)
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak)
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem)
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser)
@@ -1339,16 +1390,32 @@ DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(S
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsVirtual)
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical)
 
 DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows)
 
 #undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT
 #undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT
 
+#define DEFINE_REGISTER_SIZE_OF_TAB(name)                                     \
+    nsresult                                                                  \
+    Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn)                 \
+    {                                                                         \
+        GET_MEMORY_REPORTER_MANAGER(mgr)                                      \
+        mgr->mSizeOfTabFns.m##name = aSizeOfTabFn;                            \
+        return NS_OK;                                                         \
+    }
+
+DEFINE_REGISTER_SIZE_OF_TAB(JS);
+DEFINE_REGISTER_SIZE_OF_TAB(NonJS);
+
+#undef DEFINE_REGISTER_SIZE_OF_TAB
+
+#undef GET_MEMORY_REPORTER_MANAGER
+
 }
 
 #if defined(MOZ_DMD)
 
 namespace mozilla {
 namespace dmd {
 
 class NullReporterCallback : public nsIMemoryReporterCallback
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -34,16 +34,23 @@ public:
 
     mozilla::InfallibleAmountFn mLowMemoryEventsVirtual;
     mozilla::InfallibleAmountFn mLowMemoryEventsPhysical;
 
     mozilla::InfallibleAmountFn mGhostWindows;
   };
   AmountFns mAmountFns;
 
+  // Functions that measure per-tab memory consumption.
+  struct SizeOfTabFns {
+    mozilla::JSSizeOfTabFn    mJS;
+    mozilla::NonJSSizeOfTabFn mNonJS;
+  };
+  SizeOfTabFns mSizeOfTabFns;
+
 private:
   nsresult RegisterReporterHelper(nsIMemoryReporter *aReporter, bool aForce);
 
   nsTHashtable<nsISupportsHashKey> mReporters;
   Mutex mMutex;
   bool mIsRegistrationBlocked;
 };