Backed out changeset 5c9f3fb14995 (bug 910517) for Android talos failures
authorEd Morley <emorley@mozilla.com>
Mon, 09 Sep 2013 16:07:48 +0100
changeset 159105 7cadc7e21f5628c31722107a11958d641a578d24
parent 159104 3931b9652326cf3ac5ad520d9503444c69643c3a
child 159106 2898a7f3853bf0a539b6c09f340b91eee4fc341e
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs910517
milestone26.0a1
backs out5c9f3fb14995931208c04d29ad4f6cc6616616ac
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
Backed out changeset 5c9f3fb14995 (bug 910517) for Android talos failures
addon-sdk/source/lib/sdk/test/harness.js
content/base/src/nsContentUtils.cpp
content/base/src/nsDOMFile.cpp
content/canvas/src/CanvasRenderingContext2D.cpp
content/canvas/src/WebGLContext.cpp
content/canvas/src/WebGLContext.h
content/canvas/src/WebGLContextReporter.cpp
content/canvas/src/WebGLMemoryMultiReporterWrapper.h
content/canvas/src/WebGLMemoryReporterWrapper.h
content/media/MediaDecoder.cpp
dom/base/nsScriptNameSpaceManager.cpp
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsWindowMemoryReporter.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/workers/WorkerPrivate.cpp
dom/workers/WorkerPrivate.h
extensions/spellcheck/hunspell/src/mozHunspell.cpp
gfx/gl/GfxTexturesReporter.h
gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
gfx/thebes/gfxASurface.cpp
gfx/thebes/gfxAndroidPlatform.cpp
gfx/thebes/gfxFont.cpp
gfx/thebes/gfxFont.h
gfx/thebes/gfxPlatformFontList.cpp
gfx/thebes/gfxPlatformFontList.h
gfx/thebes/gfxWindowsPlatform.cpp
gfx/thebes/gfxWindowsPlatform.h
image/src/imgLoader.cpp
image/test/mochitest/test_bug601470.html
ipc/glue/SharedMemory.cpp
js/xpconnect/src/XPCJSMemoryReporter.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcpublic.h
layout/base/nsStyleSheetService.cpp
layout/style/nsLayoutStylesheetCache.cpp
modules/libpref/src/Preferences.cpp
netwerk/cache/nsDiskCacheDevice.cpp
netwerk/cache/nsMemoryCacheDevice.cpp
netwerk/dns/nsEffectiveTLDService.cpp
startupcache/StartupCache.cpp
storage/src/mozStorageService.cpp
storage/src/mozStorageService.h
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
toolkit/components/aboutmemory/tests/test_aboutmemory.xul
toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
toolkit/components/aboutmemory/tests/test_sqliteMultiReporter.xul
toolkit/components/places/History.cpp
toolkit/components/telemetry/Telemetry.cpp
toolkit/components/telemetry/TelemetryPing.js
toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp
xpcom/base/AvailableMemoryTracker.cpp
xpcom/base/MapsMemoryReporter.cpp
xpcom/base/nsCycleCollector.cpp
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryInfoDumper.cpp
xpcom/base/nsMemoryReporterManager.cpp
xpcom/base/nsMemoryReporterManager.h
xpcom/build/nsXPComInit.cpp
xpcom/components/nsCategoryManager.cpp
xpcom/components/nsComponentManager.cpp
xpcom/ds/nsObserverService.cpp
xpcom/ds/nsObserverService.h
xpcom/reflect/xptinfo/src/xptiInterfaceInfoManager.cpp
--- a/addon-sdk/source/lib/sdk/test/harness.js
+++ b/addon-sdk/source/lib/sdk/test/harness.js
@@ -143,19 +143,16 @@ function dictDiff(last, curr) {
 }
 
 function reportMemoryUsage() {
   memory.gc();
 
   var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
             .getService(Ci.nsIMemoryReporterManager);
 
-  // XXX: this code is *so* bogus -- nsIMemoryReporter changed its |memoryUsed|
-  // field to |amount| *years* ago, and even bigger changes have happened
-  // since -- that it must just never be run.
   var reporters = mgr.enumerateReporters();
   if (reporters.hasMoreElements())
     print("\n");
 
   while (reporters.hasMoreElements()) {
     var reporter = reporters.getNext();
     reporter.QueryInterface(Ci.nsIMemoryReporter);
     print(reporter.description + ": " + reporter.memoryUsed + "\n");
@@ -374,17 +371,24 @@ function getPotentialLeaks() {
     }
   }
 
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
   let enm = mgr.enumerateReporters();
   while (enm.hasMoreElements()) {
-    let mr = enm.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    let reporter = enm.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    logReporter(reporter.process, reporter.path, reporter.kind, reporter.units,
+                reporter.amount, reporter.description);
+  }
+
+  let enm = mgr.enumerateMultiReporters();
+  while (enm.hasMoreElements()) {
+    let mr = enm.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
     mr.collectReports(logReporter, null);
   }
 
   return { compartments: compartments, windows: windows };
 }
 
 function nextIteration(tests) {
   if (tests) {
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -252,21 +252,21 @@ bool nsContentUtils::sDOMWindowDumpEnabl
 namespace {
 
 static const char kJSStackContractID[] = "@mozilla.org/js/xpc/ContextStack;1";
 static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID);
 static NS_DEFINE_CID(kCParserCID, NS_PARSER_CID);
 
 static PLDHashTable sEventListenerManagersHash;
 
-class DOMEventListenerManagersHashReporter MOZ_FINAL : public MemoryUniReporter
+class DOMEventListenerManagersHashReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   DOMEventListenerManagersHashReporter()
-    : MemoryUniReporter(
+    : MemoryReporterBase(
         "explicit/dom/event-listener-managers-hash",
         KIND_HEAP,
         UNITS_BYTES,
         "Memory used by the event listener manager's hash table.")
   {}
 
 private:
   int64_t Amount()
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -636,27 +636,27 @@ nsDOMMemoryFile::DataOwner::sDataOwnerMu
 nsDOMMemoryFile::DataOwner::sDataOwners;
 
 /* static */ bool
 nsDOMMemoryFile::DataOwner::sMemoryReporterRegistered;
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(DOMMemoryFileDataOwnerMallocSizeOf)
 
 class nsDOMMemoryFileDataOwnerMemoryReporter MOZ_FINAL
-  : public nsIMemoryReporter
+  : public nsIMemoryMultiReporter
 {
   NS_DECL_THREADSAFE_ISUPPORTS
 
   NS_IMETHOD GetName(nsACString& aName)
   {
     aName.AssignASCII("dom-memory-file-data-owner");
     return NS_OK;
   }
 
-  NS_IMETHOD CollectReports(nsIMemoryReporterCallback *aCallback,
+  NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *aCallback,
                             nsISupports *aClosure)
   {
     typedef nsDOMMemoryFile::DataOwner DataOwner;
 
     StaticMutexAutoLock lock(DataOwner::sDataOwnerMutex);
 
     if (!DataOwner::sDataOwners) {
       return NS_OK;
@@ -720,29 +720,29 @@ class nsDOMMemoryFileDataOwnerMemoryRepo
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     return NS_OK;
   }
 };
 
 NS_IMPL_ISUPPORTS1(nsDOMMemoryFileDataOwnerMemoryReporter,
-                   nsIMemoryReporter)
+                   nsIMemoryMultiReporter)
 
 /* static */ void
 nsDOMMemoryFile::DataOwner::EnsureMemoryReporterRegistered()
 {
   sDataOwnerMutex.AssertCurrentThreadOwns();
   if (sMemoryReporterRegistered) {
     return;
   }
 
   nsRefPtr<nsDOMMemoryFileDataOwnerMemoryReporter> reporter = new
     nsDOMMemoryFileDataOwnerMemoryReporter();
-  NS_RegisterMemoryReporter(reporter);
+  NS_RegisterMemoryMultiReporter(reporter);
 
   sMemoryReporterRegistered = true;
 }
 
 ////////////////////////////////////////////////////////////////////////////
 // nsDOMFileList implementation
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsDOMFileList)
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -138,21 +138,21 @@ namespace dom {
 const Float SIGMA_MAX = 100;
 
 /* Memory reporter stuff */
 static int64_t gCanvasAzureMemoryUsed = 0;
 
 // This is KIND_OTHER because it's not always clear where in memory the pixels
 // of a canvas are stored.  Furthermore, this memory will be tracked by the
 // underlying surface implementations.  See bug 655638 for details.
-class Canvas2dPixelsReporter MOZ_FINAL : public MemoryUniReporter
+class Canvas2dPixelsReporter MOZ_FINAL : public MemoryReporterBase
 {
   public:
     Canvas2dPixelsReporter()
-      : MemoryUniReporter("canvas-2d-pixels", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("canvas-2d-pixels", KIND_OTHER, UNITS_BYTES,
 "Memory used by 2D canvases. Each canvas requires (width * height * 4) bytes.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE { return gCanvasAzureMemoryUsed; }
 };
 
 class CanvasRadialGradient : public CanvasGradient
 {
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -5,17 +5,17 @@
 
 #include "WebGLContext.h"
 #include "WebGL1Context.h"
 #include "WebGLObjectModel.h"
 #include "WebGLExtensions.h"
 #include "WebGLContextUtils.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexAttribData.h"
-#include "WebGLMemoryReporterWrapper.h"
+#include "WebGLMemoryMultiReporterWrapper.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLVertexArray.h"
 #include "WebGLQuery.h"
 
 #include "AccessCheck.h"
 #include "nsIConsoleService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIClassInfoImpl.h"
@@ -174,17 +174,17 @@ WebGLContext::WebGLContext()
     mGLMaxColorAttachments = 1;
     mGLMaxDrawBuffers = 1;
     mGLMaxTransformFeedbackSeparateAttribs = 0;
 
     // See OpenGL ES 2.0.25 spec, 6.2 State Tables, table 6.13
     mPixelStorePackAlignment = 4;
     mPixelStoreUnpackAlignment = 4;
 
-    WebGLMemoryReporterWrapper::AddWebGLContext(this);
+    WebGLMemoryMultiReporterWrapper::AddWebGLContext(this);
 
     mAllowRestore = true;
     mContextLossTimerRunning = false;
     mDrawSinceContextLossTimerSet = false;
     mContextRestorer = do_CreateInstance("@mozilla.org/timer;1");
     mContextStatus = ContextNotLost;
     mContextLostErrorSet = false;
     mLoseContextOnHeapMinimize = false;
@@ -208,17 +208,17 @@ WebGLContext::WebGLContext()
     mDisableFragHighP = false;
 
     mDrawCallsSinceLastFlush = 0;
 }
 
 WebGLContext::~WebGLContext()
 {
     DestroyResourcesAndContext();
-    WebGLMemoryReporterWrapper::RemoveWebGLContext(this);
+    WebGLMemoryMultiReporterWrapper::RemoveWebGLContext(this);
     TerminateContextLossTimer();
     mContextRestorer = nullptr;
 }
 
 void
 WebGLContext::DestroyResourcesAndContext()
 {
     if (mMemoryPressureObserver) {
@@ -648,18 +648,18 @@ void WebGLContext::LoseOldestWebGLContex
 #endif
     MOZ_ASSERT(kMaxWebGLContextsPerPrincipal < kMaxWebGLContexts);
 
     // it's important to update the index on a new context before losing old contexts,
     // otherwise new unused contexts would all have index 0 and we couldn't distinguish older ones
     // when choosing which one to lose first.
     UpdateLastUseIndex();
 
-    WebGLMemoryReporterWrapper::ContextsArrayType &contexts
-      = WebGLMemoryReporterWrapper::Contexts();
+    WebGLMemoryMultiReporterWrapper::ContextsArrayType &contexts
+      = WebGLMemoryMultiReporterWrapper::Contexts();
 
     // quick exit path, should cover a majority of cases
     if (contexts.Length() <= kMaxWebGLContextsPerPrincipal) {
         return;
     }
 
     // note that here by "context" we mean "non-lost context". See the check for
     // IsContextLost() below. Indeed, the point of this function is to maybe lose
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -113,17 +113,17 @@ class WebGLContext :
     public nsIDOMWebGLRenderingContext,
     public nsICanvasRenderingContextInternal,
     public nsSupportsWeakReference,
     public WebGLRectangleObject,
     public nsWrapperCache
 {
     friend class WebGLContextUserData;
     friend class WebGLMemoryPressureObserver;
-    friend class WebGLMemoryReporterWrapper;
+    friend class WebGLMemoryMultiReporterWrapper;
     friend class WebGLExtensionLoseContext;
     friend class WebGLExtensionCompressedTextureS3TC;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTexturePVRTC;
     friend class WebGLExtensionDepthTexture;
     friend class WebGLExtensionDrawBuffers;
     friend class WebGLExtensionVertexArray;
 
--- a/content/canvas/src/WebGLContextReporter.cpp
+++ b/content/canvas/src/WebGLContextReporter.cpp
@@ -1,165 +1,165 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGLContext.h"
-#include "WebGLMemoryReporterWrapper.h"
+#include "WebGLMemoryMultiReporterWrapper.h"
 #include "nsIMemoryReporter.h"
 
 using namespace mozilla;
 
 NS_IMPL_ISUPPORTS1(WebGLMemoryPressureObserver, nsIObserver)
 
-class WebGLMemoryReporter MOZ_FINAL : public nsIMemoryReporter
+class WebGLMemoryMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter 
 {
   public:
     NS_DECL_ISUPPORTS
-    NS_DECL_NSIMEMORYREPORTER
+    NS_DECL_NSIMEMORYMULTIREPORTER
 };
 
-NS_IMPL_ISUPPORTS1(WebGLMemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(WebGLMemoryMultiReporter, nsIMemoryMultiReporter)
 
 NS_IMETHODIMP
-WebGLMemoryReporter::GetName(nsACString &aName)
+WebGLMemoryMultiReporter::GetName(nsACString &aName)
 {
   aName.AssignLiteral("webgl");
   return NS_OK;
 }
 
 NS_IMETHODIMP
-WebGLMemoryReporter::CollectReports(nsIMemoryReporterCallback* aCb,
-                                    nsISupports* aClosure)
+WebGLMemoryMultiReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb, 
+                                         nsISupports* aClosure)
 {
 #define REPORT(_path, _kind, _units, _amount, _desc)                          \
     do {                                                                      \
       nsresult rv;                                                            \
       rv = aCb->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path), _kind,    \
                          _units, _amount, NS_LITERAL_CSTRING(_desc),          \
                          aClosure);                                           \
       NS_ENSURE_SUCCESS(rv, rv);                                              \
     } while (0)
 
     REPORT("webgl-texture-memory",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_BYTES,
-           WebGLMemoryReporterWrapper::GetTextureMemoryUsed(),
+           WebGLMemoryMultiReporterWrapper::GetTextureMemoryUsed(),
            "Memory used by WebGL textures.The OpenGL"
            " implementation is free to store these textures in either video"
            " memory or main memory. This measurement is only a lower bound,"
-           " actual memory usage may be higher for example if the storage"
+           " actual memory usage may be higher for example if the storage" 
            " is strided.");
-
+   
     REPORT("webgl-texture-count",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
-           WebGLMemoryReporterWrapper::GetTextureCount(),
+           WebGLMemoryMultiReporterWrapper::GetTextureCount(), 
            "Number of WebGL textures.");
 
     REPORT("webgl-buffer-memory",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_BYTES,
-           WebGLMemoryReporterWrapper::GetBufferMemoryUsed(),
+           WebGLMemoryMultiReporterWrapper::GetBufferMemoryUsed(), 
            "Memory used by WebGL buffers. The OpenGL"
            " implementation is free to store these buffers in either video"
            " memory or main memory. This measurement is only a lower bound,"
            " actual memory usage may be higher for example if the storage"
            " is strided.");
 
     REPORT("explicit/webgl/buffer-cache-memory",
            nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
-           WebGLMemoryReporterWrapper::GetBufferCacheMemoryUsed(),
+           WebGLMemoryMultiReporterWrapper::GetBufferCacheMemoryUsed(),
            "Memory used by WebGL buffer caches. The WebGL"
            " implementation caches the contents of element array buffers"
            " only.This adds up with the webgl-buffer-memory value, but"
            " contrary to it, this one represents bytes on the heap,"
            " not managed by OpenGL.");
 
     REPORT("webgl-buffer-count",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
-           WebGLMemoryReporterWrapper::GetBufferCount(),
-           "Number of WebGL buffers.");
-
+           WebGLMemoryMultiReporterWrapper::GetBufferCount(),
+           "Number of WebGL buffers."); 
+   
     REPORT("webgl-renderbuffer-memory",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_BYTES,
-           WebGLMemoryReporterWrapper::GetRenderbufferMemoryUsed(),
+           WebGLMemoryMultiReporterWrapper::GetRenderbufferMemoryUsed(), 
            "Memory used by WebGL renderbuffers. The OpenGL"
            " implementation is free to store these renderbuffers in either"
            " video memory or main memory. This measurement is only a lower"
            " bound, actual memory usage may be higher for example if the"
            " storage is strided.");
-
+   
     REPORT("webgl-renderbuffer-count",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
-           WebGLMemoryReporterWrapper::GetRenderbufferCount(),
+           WebGLMemoryMultiReporterWrapper::GetRenderbufferCount(),
            "Number of WebGL renderbuffers.");
-
+  
     REPORT("explicit/webgl/shader",
            nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
-           WebGLMemoryReporterWrapper::GetShaderSize(),
+           WebGLMemoryMultiReporterWrapper::GetShaderSize(), 
            "Combined size of WebGL shader ASCII sources and translation"
-           " logs cached on the heap.");
+           " logs cached on the heap."); 
 
     REPORT("webgl-shader-count",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
-           WebGLMemoryReporterWrapper::GetShaderCount(),
-           "Number of WebGL shaders.");
+           WebGLMemoryMultiReporterWrapper::GetShaderCount(), 
+           "Number of WebGL shaders."); 
 
     REPORT("webgl-context-count",
            nsIMemoryReporter::KIND_OTHER, nsIMemoryReporter::UNITS_COUNT,
-           WebGLMemoryReporterWrapper::GetContextCount(),
-           "Number of WebGL contexts.");
+           WebGLMemoryMultiReporterWrapper::GetContextCount(), 
+           "Number of WebGL contexts."); 
 
 #undef REPORT
 
     return NS_OK;
 }
 
-WebGLMemoryReporterWrapper* WebGLMemoryReporterWrapper::sUniqueInstance = nullptr;
+WebGLMemoryMultiReporterWrapper* WebGLMemoryMultiReporterWrapper::sUniqueInstance = nullptr;
 
-WebGLMemoryReporterWrapper* WebGLMemoryReporterWrapper::UniqueInstance()
+WebGLMemoryMultiReporterWrapper* WebGLMemoryMultiReporterWrapper::UniqueInstance()
 {
     if (!sUniqueInstance) {
-        sUniqueInstance = new WebGLMemoryReporterWrapper;
+        sUniqueInstance = new WebGLMemoryMultiReporterWrapper;
     }
-    return sUniqueInstance;
+    return sUniqueInstance;     
 }
 
-WebGLMemoryReporterWrapper::WebGLMemoryReporterWrapper()
-{
-    mReporter = new WebGLMemoryReporter;
-    NS_RegisterMemoryReporter(mReporter);
+WebGLMemoryMultiReporterWrapper::WebGLMemoryMultiReporterWrapper()
+{ 
+    mReporter = new WebGLMemoryMultiReporter;   
+    NS_RegisterMemoryMultiReporter(mReporter);
 }
 
-WebGLMemoryReporterWrapper::~WebGLMemoryReporterWrapper()
+WebGLMemoryMultiReporterWrapper::~WebGLMemoryMultiReporterWrapper()
 {
-    NS_UnregisterMemoryReporter(mReporter);
+    NS_UnregisterMemoryMultiReporter(mReporter);
 }
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(WebGLBufferMallocSizeOf)
 
-int64_t
-WebGLMemoryReporterWrapper::GetBufferCacheMemoryUsed() {
+int64_t 
+WebGLMemoryMultiReporterWrapper::GetBufferCacheMemoryUsed() {
     const ContextsArrayType & contexts = Contexts();
     int64_t result = 0;
     for(size_t i = 0; i < contexts.Length(); ++i) {
         for (const WebGLBuffer *buffer = contexts[i]->mBuffers.getFirst();
              buffer;
              buffer = buffer->getNext())
         {
             if (buffer->Target() == LOCAL_GL_ELEMENT_ARRAY_BUFFER)
                 result += buffer->SizeOfIncludingThis(WebGLBufferMallocSizeOf);
         }
     }
     return result;
 }
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(WebGLShaderMallocSizeOf)
 
-int64_t
-WebGLMemoryReporterWrapper::GetShaderSize() {
+int64_t 
+WebGLMemoryMultiReporterWrapper::GetShaderSize() {
     const ContextsArrayType & contexts = Contexts();
     int64_t result = 0;
     for(size_t i = 0; i < contexts.Length(); ++i) {
         for (const WebGLShader *shader = contexts[i]->mShaders.getFirst();
              shader;
              shader = shader->getNext())
         {
             result += shader->SizeOfIncludingThis(WebGLShaderMallocSizeOf);
rename from content/canvas/src/WebGLMemoryReporterWrapper.h
rename to content/canvas/src/WebGLMemoryMultiReporterWrapper.h
--- a/content/canvas/src/WebGLMemoryReporterWrapper.h
+++ b/content/canvas/src/WebGLMemoryMultiReporterWrapper.h
@@ -1,42 +1,42 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 WEBGLMEMORYREPORTERWRAPPER_H_
-#define WEBGLMEMORYREPORTERWRAPPER_H_
+#ifndef WEBGLMEMORYMULTIREPORTWRAPER_H_
+#define WEBGLMEMORYMULTIREPORTWRAPER_H_
 
 #include "WebGLContext.h"
 #include "WebGLBuffer.h"
 #include "WebGLVertexAttribData.h"
 #include "WebGLShader.h"
 #include "WebGLProgram.h"
 #include "WebGLUniformLocation.h"
 #include "WebGLTexture.h"
 #include "WebGLRenderbuffer.h"
 
 namespace mozilla {
 
-class WebGLMemoryReporterWrapper
+class WebGLMemoryMultiReporterWrapper
 {
-    WebGLMemoryReporterWrapper();
-    ~WebGLMemoryReporterWrapper();
-    static WebGLMemoryReporterWrapper* sUniqueInstance;
+    WebGLMemoryMultiReporterWrapper();
+    ~WebGLMemoryMultiReporterWrapper();
+    static WebGLMemoryMultiReporterWrapper* sUniqueInstance;
 
-    // here we store plain pointers, not RefPtrs: we don't want the
-    // WebGLMemoryReporterWrapper unique instance to keep alive all
+    // here we store plain pointers, not RefPtrs: we don't want the 
+    // WebGLMemoryMultiReporterWrapper unique instance to keep alive all		
     // WebGLContexts ever created.
     typedef nsTArray<const WebGLContext*> ContextsArrayType;
     ContextsArrayType mContexts;
 
-    nsCOMPtr<nsIMemoryReporter> mReporter;
+    nsCOMPtr<nsIMemoryMultiReporter> mReporter;
 
-    static WebGLMemoryReporterWrapper* UniqueInstance();
+    static WebGLMemoryMultiReporterWrapper* UniqueInstance();
 
     static ContextsArrayType & Contexts() { return UniqueInstance()->mContexts; }
 
     friend class WebGLContext;
 
   public:
 
     static void AddWebGLContext(const WebGLContext* c) {
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -68,17 +68,17 @@ class MediaMemoryTracker
 
   typedef nsTArray<MediaDecoder*> DecodersArray;
   static DecodersArray& Decoders() {
     return UniqueInstance()->mDecoders;
   }
 
   DecodersArray mDecoders;
 
-  nsCOMPtr<nsIMemoryReporter> mReporter;
+  nsCOMPtr<nsIMemoryMultiReporter> mReporter;
 
 public:
   static void AddMediaDecoder(MediaDecoder* aDecoder)
   {
     Decoders().AppendElement(aDecoder);
   }
 
   static void RemoveMediaDecoder(MediaDecoder* aDecoder)
@@ -1719,28 +1719,28 @@ MediaDecoder::IsDASHEnabled()
 #ifdef MOZ_WMF
 bool
 MediaDecoder::IsWMFEnabled()
 {
   return WMFDecoder::IsEnabled();
 }
 #endif
 
-class MediaReporter MOZ_FINAL : public nsIMemoryReporter
+class MediaReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
 public:
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD GetName(nsACString& aName)
   {
     aName.AssignLiteral("media");
     return NS_OK;
   }
 
-  NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCb,
+  NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback* aCb,
                             nsISupports* aClosure)
   {
     int64_t video, audio;
     MediaMemoryTracker::GetAmounts(&video, &audio);
 
   #define REPORT(_path, _amount, _desc)                                       \
     do {                                                                      \
         nsresult rv;                                                          \
@@ -1756,30 +1756,30 @@ public:
 
     REPORT("explicit/media/decoded-audio", audio,
            "Memory used by decoded audio chunks.");
 
     return NS_OK;
   }
 };
 
-NS_IMPL_ISUPPORTS1(MediaReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(MediaReporter, nsIMemoryMultiReporter)
 
 MediaDecoderOwner*
 MediaDecoder::GetOwner()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mOwner;
 }
 
 MediaMemoryTracker::MediaMemoryTracker()
   : mReporter(new MediaReporter())
 {
-  NS_RegisterMemoryReporter(mReporter);
+  NS_RegisterMemoryMultiReporter(mReporter);
 }
 
 MediaMemoryTracker::~MediaMemoryTracker()
 {
-  NS_UnregisterMemoryReporter(mReporter);
+  NS_UnregisterMemoryMultiReporter(mReporter);
 }
 
 } // namespace mozilla
 
--- a/dom/base/nsScriptNameSpaceManager.cpp
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -111,21 +111,21 @@ GlobalNameHashInitEntry(PLDHashTable *ta
   new (&e->mKey) nsString(*keyStr);
 
   // This will set e->mGlobalName.mType to
   // nsGlobalNameStruct::eTypeNotInitialized
   memset(&e->mGlobalName, 0, sizeof(nsGlobalNameStruct));
   return true;
 }
 
-class ScriptNameSpaceManagerReporter MOZ_FINAL : public MemoryUniReporter
+class ScriptNameSpaceManagerReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   ScriptNameSpaceManagerReporter(nsScriptNameSpaceManager* aManager)
-    : MemoryUniReporter(
+    : MemoryReporterBase(
         "explicit/script-namespace-manager",
         KIND_HEAP,
         nsIMemoryReporter::UNITS_BYTES,
         "Memory used for the script namespace manager.")
     , mManager(aManager)
   {}
 
 private:
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -19,44 +19,44 @@
 
 using namespace mozilla;
 
 nsWindowMemoryReporter::nsWindowMemoryReporter()
   : mCheckForGhostWindowsCallbackPending(false)
 {
 }
 
-NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryReporter, nsIObserver,
+NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryMultiReporter, nsIObserver,
                    nsSupportsWeakReference)
 
 /* static */
 void
 nsWindowMemoryReporter::Init()
 {
   // The memory reporter manager will own this object.
   nsRefPtr<nsWindowMemoryReporter> windowReporter = new nsWindowMemoryReporter();
-  NS_RegisterMemoryReporter(windowReporter);
+  NS_RegisterMemoryMultiReporter(windowReporter);
 
   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(windowReporter, DOM_WINDOW_DESTROYED_TOPIC,
                     /* weakRef = */ true);
     os->AddObserver(windowReporter, "after-minimize-memory-usage",
                     /* weakRef = */ true);
   }
 
-  nsRefPtr<GhostURLsReporter> ghostURLsReporter =
+  nsRefPtr<GhostURLsReporter> ghostMultiReporter =
     new GhostURLsReporter(windowReporter);
-  NS_RegisterMemoryReporter(ghostURLsReporter);
+  NS_RegisterMemoryMultiReporter(ghostMultiReporter);
 
-  nsRefPtr<NumGhostsReporter> numGhostsReporter =
+  nsRefPtr<NumGhostsReporter> ghostReporter =
     new NumGhostsReporter(windowReporter);
-  NS_RegisterMemoryReporter(numGhostsReporter);
+  NS_RegisterMemoryReporter(ghostReporter);
 }
 
 static already_AddRefed<nsIURI>
 GetWindowURI(nsIDOMWindow *aWindow)
 {
   nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(aWindow);
   NS_ENSURE_TRUE(pWindow, nullptr);
 
@@ -116,17 +116,17 @@ typedef nsDataHashtable<nsUint64HashKey,
 
 static nsresult
 CollectWindowReports(nsGlobalWindow *aWindow,
                      amIAddonManager *addonManager,
                      nsWindowSizes *aWindowTotalSizes,
                      nsTHashtable<nsUint64HashKey> *aGhostWindowIDs,
                      WindowPaths *aWindowPaths,
                      WindowPaths *aTopWindowPaths,
-                     nsIMemoryReporterCallback *aCb,
+                     nsIMemoryMultiReporterCallback *aCb,
                      nsISupports *aClosure)
 {
   nsAutoCString windowPath("explicit/");
 
   // Avoid calling aWindow->GetTop() if there's no outer window.  It will work
   // just fine, but will spew a lot of warnings.
   nsGlobalWindow *top = NULL;
   nsCOMPtr<nsIURI> location;
@@ -314,17 +314,17 @@ GetWindows(const uint64_t& aId, nsGlobal
 NS_IMETHODIMP
 nsWindowMemoryReporter::GetName(nsACString &aName)
 {
   aName.AssignLiteral("window-objects");
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsWindowMemoryReporter::CollectReports(nsIMemoryReporterCallback* aCb,
+nsWindowMemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb,
                                        nsISupports* aClosure)
 {
   nsGlobalWindow::WindowByIdTable* windowsById =
     nsGlobalWindow::GetWindowsTable();
   NS_ENSURE_TRUE(windowsById, NS_OK);
 
   // Hold on to every window in memory so that window objects can't be
   // destroyed while we're calling the memory reporter callback.
@@ -347,19 +347,19 @@ nsWindowMemoryReporter::CollectReports(n
     nsresult rv = CollectWindowReports(windows[i], addonManager,
                                        &windowTotalSizes, &ghostWindows,
                                        &windowPaths, &topWindowPaths, aCb,
                                        aClosure);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Report JS memory usage.  We do this from here because the JS memory
-  // reporter needs to be passed |windowPaths|.
-  nsresult rv = xpc::JSReporter::CollectReports(&windowPaths, &topWindowPaths,
-                                                aCb, aClosure);
+  // multi-reporter needs to be passed |windowPaths|.
+  nsresult rv = xpc::JSMemoryMultiReporter::CollectReports(&windowPaths, &topWindowPaths,
+                                                           aCb, aClosure);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #define REPORT(_path, _amount, _desc)                                         \
   do {                                                                        \
     nsresult rv;                                                              \
     rv = aCb->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path),             \
                        nsIMemoryReporter::KIND_OTHER,                         \
                        nsIMemoryReporter::UNITS_BYTES, _amount,               \
@@ -658,36 +658,36 @@ nsWindowMemoryReporter::CheckForGhostWin
   CheckForGhostWindowsEnumeratorData ghostEnumData =
     { &nonDetachedWindowDomains, aOutGhostIDs, tldService,
       GetGhostTimeout(), TimeStamp::Now() };
   mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator,
                              &ghostEnumData);
 }
 
 NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::GhostURLsReporter,
-                   nsIMemoryReporter)
+                   nsIMemoryMultiReporter)
 
 nsWindowMemoryReporter::
 GhostURLsReporter::GhostURLsReporter(
   nsWindowMemoryReporter* aWindowReporter)
   : mWindowReporter(aWindowReporter)
 {
 }
 
 NS_IMETHODIMP
 nsWindowMemoryReporter::
 GhostURLsReporter::GetName(nsACString& aName)
 {
-  aName.AssignLiteral("ghost-windows-multi");
+  aName.AssignLiteral("ghost-windows");
   return NS_OK;
 }
 
 struct ReportGhostWindowsEnumeratorData
 {
-  nsIMemoryReporterCallback* callback;
+  nsIMemoryMultiReporterCallback* callback;
   nsISupports* closure;
   nsresult rv;
 };
 
 static PLDHashOperator
 ReportGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void* aClosure)
 {
   ReportGhostWindowsEnumeratorData *data =
@@ -724,32 +724,51 @@ ReportGhostWindowsEnumerator(nsUint64Has
   }
 
   return PL_DHASH_NEXT;
 }
 
 NS_IMETHODIMP
 nsWindowMemoryReporter::
 GhostURLsReporter::CollectReports(
-  nsIMemoryReporterCallback* aCb,
+  nsIMemoryMultiReporterCallback* aCb,
   nsISupports* aClosure)
 {
   // Get the IDs of all the ghost windows in existance.
   nsTHashtable<nsUint64HashKey> ghostWindows;
   mWindowReporter->CheckForGhostWindows(&ghostWindows);
 
   ReportGhostWindowsEnumeratorData reportGhostWindowsEnumData =
     { aCb, aClosure, NS_OK };
 
   // Call aCb->Callback() for each ghost window.
   ghostWindows.EnumerateEntries(ReportGhostWindowsEnumerator,
                                 &reportGhostWindowsEnumData);
 
   return reportGhostWindowsEnumData.rv;
 }
 
+NS_IMETHODIMP
+nsWindowMemoryReporter::
+NumGhostsReporter::GetDescription(nsACString& aDesc)
+{
+  nsPrintfCString str(
+"The number of ghost windows present (the number of nodes underneath \
+explicit/window-objects/top(none)/ghost, modulo race conditions).  A ghost \
+window is not shown in any tab, does not share a domain with any non-detached \
+windows, and has met these criteria for at least %ds \
+(memory.ghost_window_timeout_seconds) or has survived a round of about:memory's \
+minimize memory usage button.\n\n\
+Ghost windows can happen legitimately, but they are often indicative of leaks \
+in the browser or add-ons.",
+  mWindowReporter->GetGhostTimeout());
+
+  aDesc.Assign(str);
+  return NS_OK;
+}
+
 int64_t
 nsWindowMemoryReporter::NumGhostsReporter::Amount()
 {
   nsTHashtable<nsUint64HashKey> ghostWindows;
   mWindowReporter->CheckForGhostWindows(&ghostWindows);
   return ghostWindows.Count();
 }
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -103,65 +103,61 @@ public:
  *   the top level chrome window).  Exposing this ensures that each tab gets
  *   its own sub-tree, even if multiple tabs are showing the same URI.
  *
  * - <top-uri> is the URI of the top window.  Excepting special windows (such
  *   as browser.xul or hiddenWindow.html) it's what the address bar shows for
  *   the tab.
  *
  */
-class nsWindowMemoryReporter MOZ_FINAL : public nsIMemoryReporter,
+class nsWindowMemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter,
                                          public nsIObserver,
                                          public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_NSIMEMORYREPORTER
+  NS_DECL_NSIMEMORYMULTIREPORTER
   NS_DECL_NSIOBSERVER
 
   static void Init();
 
 private:
   /**
-   * GhostURLsReporter generates the list of all ghost windows' URLs.  If
-   * you're only interested in this list, running this report is faster than
-   * running nsWindowMemoryReporter.
+   * GhostURLsReporter generates the "ghost-windows" multi-report, which
+   * includes a list of all ghost windows' URLs.  If you're only interested in
+   * this list, running this report is faster than running
+   * nsWindowMemoryReporter.
    */
-  class GhostURLsReporter MOZ_FINAL : public nsIMemoryReporter
+  class GhostURLsReporter MOZ_FINAL : public nsIMemoryMultiReporter
   {
   public:
     GhostURLsReporter(nsWindowMemoryReporter* aWindowReporter);
 
     NS_DECL_ISUPPORTS
-    NS_DECL_NSIMEMORYREPORTER
+    NS_DECL_NSIMEMORYMULTIREPORTER
 
   private:
     nsRefPtr<nsWindowMemoryReporter> mWindowReporter;
   };
 
   /**
-   * nsGhostWindowReporter generates the "ghost-windows" report, which counts
-   * the number of ghost windows present.
+   * nsGhostWindowReporter generates the "ghost-windows" single-report, which
+   * counts the number of ghost windows present.
    */
-  class NumGhostsReporter MOZ_FINAL : public mozilla::MemoryUniReporter
+  class NumGhostsReporter MOZ_FINAL : public mozilla::MemoryReporterBase
   {
   public:
     NumGhostsReporter(nsWindowMemoryReporter* aWindowReporter)
-      : MemoryUniReporter("ghost-windows", KIND_OTHER, UNITS_COUNT,
-"The number of ghost windows present (the number of nodes underneath "
-"explicit/window-objects/top(none)/ghost, modulo race conditions).  A ghost "
-"window is not shown in any tab, does not share a domain with any non-detached "
-"windows, and has met these criteria for at least "
-"memory.ghost_window_timeout_seconds, or has survived a round of "
-"about:memory's minimize memory usage button.\n\n"
-"Ghost windows can happen legitimately, but they are often indicative of "
-"leaks in the browser or add-ons.")
+        // Description is "???" because we define GetDescription below.
+      : MemoryReporterBase("ghost-windows", KIND_OTHER, UNITS_COUNT, "???")
       , mWindowReporter(aWindowReporter)
     {}
 
+    NS_IMETHOD GetDescription(nsACString& aDesc);
+
   private:
     int64_t Amount() MOZ_OVERRIDE;
 
     nsRefPtr<nsWindowMemoryReporter> mWindowReporter;
   };
 
   // Protect ctor, use Init() instead.
   nsWindowMemoryReporter();
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -395,26 +395,26 @@ ContentChild::InitXPCOM()
 
 PMemoryReportRequestChild*
 ContentChild::AllocPMemoryReportRequestChild()
 {
     return new MemoryReportRequestChild();
 }
 
 // This is just a wrapper for InfallibleTArray<MemoryReport> that implements
-// nsISupports, so it can be passed to nsIMemoryReporter::CollectReports.
+// nsISupports, so it can be passed to nsIMemoryMultiReporter::CollectReports.
 class MemoryReportsWrapper MOZ_FINAL : public nsISupports {
 public:
     NS_DECL_ISUPPORTS
     MemoryReportsWrapper(InfallibleTArray<MemoryReport> *r) : mReports(r) { }
     InfallibleTArray<MemoryReport> *mReports;
 };
 NS_IMPL_ISUPPORTS0(MemoryReportsWrapper)
 
-class MemoryReportCallback MOZ_FINAL : public nsIMemoryReporterCallback
+class MemoryReportCallback MOZ_FINAL : public nsIMemoryMultiReporterCallback
 {
 public:
     NS_DECL_ISUPPORTS
 
     MemoryReportCallback(const nsACString &aProcess)
     : mProcess(aProcess)
     {
     }
@@ -432,38 +432,63 @@ public:
         wrappedReports->mReports->AppendElement(memreport);
         return NS_OK;
     }
 private:
     const nsCString mProcess;
 };
 NS_IMPL_ISUPPORTS1(
   MemoryReportCallback
-, nsIMemoryReporterCallback
+, nsIMemoryMultiReporterCallback
 )
 
 bool
 ContentChild::RecvPMemoryReportRequestConstructor(PMemoryReportRequestChild* child)
 {
+    
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
 
     InfallibleTArray<MemoryReport> reports;
 
     nsPrintfCString process("Content (%d)", getpid());
 
-    // Run each reporter.  The callback will turn each measurement into a
-    // MemoryReport.
+    // First do the vanilla memory reporters.
     nsCOMPtr<nsISimpleEnumerator> e;
     mgr->EnumerateReporters(getter_AddRefs(e));
+    bool more;
+    while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsIMemoryReporter> r;
+      e->GetNext(getter_AddRefs(r));
+
+      nsCString path;
+      int32_t kind;
+      int32_t units;
+      int64_t amount;
+      nsCString desc;
+
+      if (NS_SUCCEEDED(r->GetPath(path)) &&
+          NS_SUCCEEDED(r->GetKind(&kind)) &&
+          NS_SUCCEEDED(r->GetUnits(&units)) &&
+          NS_SUCCEEDED(r->GetAmount(&amount)) &&
+          NS_SUCCEEDED(r->GetDescription(desc)))
+      {
+        MemoryReport memreport(process, path, kind, units, amount, desc);
+        reports.AppendElement(memreport);
+      }
+    }
+
+    // Then do the memory multi-reporters, by calling CollectReports on each
+    // one, whereupon the callback will turn each measurement into a
+    // MemoryReport.
+    mgr->EnumerateMultiReporters(getter_AddRefs(e));
     nsRefPtr<MemoryReportsWrapper> wrappedReports =
         new MemoryReportsWrapper(&reports);
     nsRefPtr<MemoryReportCallback> cb = new MemoryReportCallback(process);
-    bool more;
     while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
-      nsCOMPtr<nsIMemoryReporter> r;
+      nsCOMPtr<nsIMemoryMultiReporter> r;
       e->GetNext(getter_AddRefs(r));
       r->CollectReports(cb, wrappedReports);
     }
 
     child->Send__delete__(child, reports);
     return true;
 }
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -156,64 +156,44 @@ using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 
 namespace mozilla {
 namespace dom {
 
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
 
-// This represents all the memory reports provided by a child process.
-class ChildReporter MOZ_FINAL : public nsIMemoryReporter
+// This represents a single measurement taken by a memory reporter in a child
+// process and passed to this one.  Its process is non-empty, and its amount is
+// fixed.
+class ChildMemoryReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
-    ChildReporter(const InfallibleTArray<MemoryReport>& childReports)
+    ChildMemoryReporter(const char* aProcess, const char* aPath, int32_t aKind,
+                        int32_t aUnits, int64_t aAmount,
+                        const char* aDescription)
+      : MemoryReporterBase(aPath, aKind, aUnits, aDescription)
+      , mProcess(aProcess)
+      , mAmount(aAmount)
     {
-        for (uint32_t i = 0; i < childReports.Length(); i++) {
-            MemoryReport r(childReports[i].process(),
-                           childReports[i].path(),
-                           childReports[i].kind(),
-                           childReports[i].units(),
-                           childReports[i].amount(),
-                           childReports[i].desc());
-
-            // Child reports have a non-empty process.
-            MOZ_ASSERT(!r.process().IsEmpty());
-
-            mChildReports.AppendElement(r);
-        }
     }
 
-    NS_DECL_ISUPPORTS
-
-    NS_IMETHOD GetName(nsACString& name)
+    NS_IMETHOD GetProcess(nsACString& aProcess)
     {
-        name.AssignLiteral("content-child");
-        return NS_OK;
+      aProcess.Assign(mProcess);
+      return NS_OK;
     }
 
-    NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCb,
-                              nsISupports* aClosure)
-    {
-        for (uint32_t i = 0; i < mChildReports.Length(); i++) {
-            nsresult rv;
-            MemoryReport r = mChildReports[i];
-            rv = aCb->Callback(r.process(), r.path(), r.kind(), r.units(),
-                               r.amount(), r.desc(), aClosure);
-            NS_ENSURE_SUCCESS(rv, rv);
-        }
-        return NS_OK;
-    }
-
-  private:
-    InfallibleTArray<MemoryReport> mChildReports;
+private:
+    int64_t Amount() { return mAmount; }
+
+    nsCString mProcess;
+    int64_t   mAmount;
 };
 
-NS_IMPL_ISUPPORTS1(ChildReporter, nsIMemoryReporter)
-
 class MemoryReportRequestParent : public PMemoryReportRequestParent
 {
 public:
     MemoryReportRequestParent();
     virtual ~MemoryReportRequestParent();
 
     virtual bool Recv__delete__(const InfallibleTArray<MemoryReport>& report);
 private:
@@ -224,49 +204,49 @@ private:
 };
 
 MemoryReportRequestParent::MemoryReportRequestParent()
 {
     MOZ_COUNT_CTOR(MemoryReportRequestParent);
 }
 
 bool
-MemoryReportRequestParent::Recv__delete__(const InfallibleTArray<MemoryReport>& childReports)
+MemoryReportRequestParent::Recv__delete__(const InfallibleTArray<MemoryReport>& report)
 {
-    Owner()->SetChildMemoryReports(childReports);
+    Owner()->SetChildMemoryReporters(report);
     return true;
 }
 
 MemoryReportRequestParent::~MemoryReportRequestParent()
 {
     MOZ_COUNT_DTOR(MemoryReportRequestParent);
 }
 
 /**
  * A memory reporter for ContentParent objects themselves.
  */
-class ContentParentMemoryReporter MOZ_FINAL : public nsIMemoryReporter
+class ContentParentMemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
 public:
     NS_DECL_ISUPPORTS
-    NS_DECL_NSIMEMORYREPORTER
+    NS_DECL_NSIMEMORYMULTIREPORTER
     NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(MallocSizeOf)
 };
 
-NS_IMPL_ISUPPORTS1(ContentParentMemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(ContentParentMemoryReporter, nsIMemoryMultiReporter)
 
 NS_IMETHODIMP
 ContentParentMemoryReporter::GetName(nsACString& aName)
 {
     aName.AssignLiteral("ContentParents");
     return NS_OK;
 }
 
 NS_IMETHODIMP
-ContentParentMemoryReporter::CollectReports(nsIMemoryReporterCallback* cb,
+ContentParentMemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* cb,
                                             nsISupports* aClosure)
 {
     nsAutoTArray<ContentParent*, 16> cps;
     ContentParent::GetAllEvenIfDead(cps);
 
     for (uint32_t i = 0; i < cps.Length(); i++) {
         ContentParent* cp = cps[i];
         AsyncChannel* channel = cp->GetIPCChannel();
@@ -373,17 +353,17 @@ ContentParent::MaybeTakePreallocatedAppP
 /*static*/ void
 ContentParent::StartUp()
 {
     if (XRE_GetProcessType() != GeckoProcessType_Default) {
         return;
     }
 
     nsRefPtr<ContentParentMemoryReporter> mr = new ContentParentMemoryReporter();
-    NS_RegisterMemoryReporter(mr);
+    NS_RegisterMemoryMultiReporter(mr);
 
     sCanLaunchSubprocesses = true;
 
     // Try to preallocate a process that we can transform into an app later.
     PreallocatedProcessManager::AllocateAfterDelay();
 }
 
 /*static*/ void
@@ -892,17 +872,17 @@ ContentParent::ShutDownProcess(bool aClo
     // NB: must MarkAsDead() here so that this isn't accidentally
     // returned from Get*() while in the midst of shutdown.
     MarkAsDead();
 
     // A ContentParent object might not get freed until after XPCOM shutdown has
     // shut down the cycle collector.  But by then it's too late to release any
     // CC'ed objects, so we need to null them out here, while we still can.  See
     // bug 899761.
-    mChildReporter = nullptr;
+    mMemoryReporters.Clear();
     if (mMessageManager) {
       mMessageManager->Disconnect();
       mMessageManager = nullptr;
     }
 }
 
 void
 ContentParent::MarkAsDead()
@@ -1043,18 +1023,18 @@ ContentParent::ActorDestroy(ActorDestroy
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "a11y-init-or-shutdown");
 #endif
     }
 
     if (ppm) {
       ppm->Disconnect();
     }
 
-    // unregister the child memory reporter
-    UnregisterChildMemoryReporter();
+    // clear the child memory reporters
+    ClearChildMemoryReporters();
 
     // remove the global remote preferences observers
     Preferences::RemoveObserver(this, "");
 
     RecvRemoveGeolocationListener();
 
     mConsoleService = nullptr;
 
@@ -2156,39 +2136,52 @@ ContentParent::AllocPMemoryReportRequest
 bool
 ContentParent::DeallocPMemoryReportRequestParent(PMemoryReportRequestParent* actor)
 {
   delete actor;
   return true;
 }
 
 void
-ContentParent::SetChildMemoryReports(const InfallibleTArray<MemoryReport>& childReports)
+ContentParent::SetChildMemoryReporters(const InfallibleTArray<MemoryReport>& report)
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr =
         do_GetService("@mozilla.org/memory-reporter-manager;1");
-
-    if (mChildReporter)
-        mgr->UnregisterReporter(mChildReporter);
-
-    mChildReporter = new ChildReporter(childReports);
-    mgr->RegisterReporter(mChildReporter);
+    for (int32_t i = 0; i < mMemoryReporters.Count(); i++)
+        mgr->UnregisterReporter(mMemoryReporters[i]);
+
+    for (uint32_t i = 0; i < report.Length(); i++) {
+        nsCString process  = report[i].process();
+        nsCString path     = report[i].path();
+        int32_t   kind     = report[i].kind();
+        int32_t   units    = report[i].units();
+        int64_t   amount   = report[i].amount();
+        nsCString desc     = report[i].desc();
+
+        nsRefPtr<ChildMemoryReporter> r =
+            new ChildMemoryReporter(process.get(), path.get(), kind, units,
+                                    amount, desc.get());
+
+        mMemoryReporters.AppendObject(r);
+        mgr->RegisterReporter(r);
+    }
 
     nsCOMPtr<nsIObserverService> obs =
         do_GetService("@mozilla.org/observer-service;1");
     if (obs)
         obs->NotifyObservers(nullptr, "child-memory-reporter-update", nullptr);
 }
 
 void
-ContentParent::UnregisterChildMemoryReporter()
+ContentParent::ClearChildMemoryReporters()
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr =
         do_GetService("@mozilla.org/memory-reporter-manager;1");
-    mgr->UnregisterReporter(mChildReporter);
+    for (int32_t i = 0; i < mMemoryReporters.Count(); i++)
+        mgr->UnregisterReporter(mMemoryReporters[i]);
 }
 
 PTestShellParent*
 ContentParent::AllocPTestShellParent()
 {
   return new TestShellParent();
 }
 
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -138,19 +138,18 @@ public:
     jsipc::JavaScriptParent *GetCPOWManager();
 
     void ReportChildAlreadyBlocked();
     bool RequestRunToCompletion();
 
     bool IsAlive();
     bool IsForApp();
 
-    void SetChildMemoryReports(const InfallibleTArray<MemoryReport>&
-                               childReports);
-    void UnregisterChildMemoryReporter();
+    void SetChildMemoryReporters(const InfallibleTArray<MemoryReport>& report);
+    void ClearChildMemoryReporters();
 
     GeckoChildProcessHost* Process() {
         return mSubprocess;
     }
 
     int32_t Pid();
 
     bool NeedsPermissionsUpdate() {
@@ -445,23 +444,21 @@ private:
     // details.
 
     GeckoChildProcessHost* mSubprocess;
     base::ChildPrivileges mOSPrivileges;
 
     uint64_t mChildID;
     int32_t mGeolocationWatchID;
 
-    // This is a reporter holding the reports from the child's last
-    // "child-memory-reporter-update" notification.  To update this, one can
-    // broadcast the topic "child-memory-reporter-request" using the
-    // nsIObserverService.
-    //
-    // Note that this assumes there is at most one child process at a time!
-    nsCOMPtr<nsIMemoryReporter> mChildReporter;
+    // This is a cache of all of the memory reporters
+    // registered in the child process.  To update this, one
+    // can broadcast the topic "child-memory-reporter-request" using
+    // the nsIObserverService.
+    nsCOMArray<nsIMemoryReporter> mMemoryReporters;
 
     nsString mAppManifestURL;
 
     /**
      * We cache mAppName instead of looking it up using mAppManifestURL when we
      * need it because it turns out that getting an app from the apps service is
      * expensive.
      */
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1762,17 +1762,17 @@ struct WorkerPrivate::TimeoutInfo
   mozilla::TimeDuration mInterval;
   nsCString mFilename;
   uint32_t mLineNumber;
   uint32_t mId;
   bool mIsInterval;
   bool mCanceled;
 };
 
-class WorkerPrivate::MemoryReporter MOZ_FINAL : public nsIMemoryReporter
+class WorkerPrivate::MemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
   friend class WorkerPrivate;
 
   SharedMutex mMutex;
   WorkerPrivate* mWorkerPrivate;
   nsCString mRtPath;
   bool mAlreadyMappedToAddon;
 
@@ -1803,17 +1803,17 @@ public:
   NS_IMETHOD
   GetName(nsACString& aName)
   {
     aName.AssignLiteral("workers");
     return NS_OK;
   }
 
   NS_IMETHOD
-  CollectReports(nsIMemoryReporterCallback* aCallback,
+  CollectReports(nsIMemoryMultiReporterCallback* aCallback,
                  nsISupports* aClosure)
   {
     AssertIsOnMainThread();
 
     // Assumes that WorkerJSRuntimeStats will hold a reference to mRtPath,
     // and not a copy, as TryToMapAddon() may later modify the string again.
     WorkerJSRuntimeStats rtStats(mRtPath);
 
@@ -1879,17 +1879,17 @@ private:
 
     static const size_t explicitLength = strlen("explicit/");
     addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0);
     addonId += "/";
     mRtPath.Insert(addonId, explicitLength);
   }
 };
 
-NS_IMPL_ISUPPORTS1(WorkerPrivate::MemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(WorkerPrivate::MemoryReporter, nsIMemoryMultiReporter)
 
 template <class Derived>
 WorkerPrivateParent<Derived>::WorkerPrivateParent(
                                      JSContext* aCx,
                                      JS::Handle<JSObject*> aObject,
                                      WorkerPrivate* aParent,
                                      JSContext* aParentJSContext,
                                      const nsAString& aScriptURL,
@@ -3089,17 +3089,17 @@ void
 WorkerPrivate::EnableMemoryReporter()
 {
   AssertIsOnWorkerThread();
 
   // No need to lock here since the main thread can't race until we've
   // successfully registered the reporter.
   mMemoryReporter = new MemoryReporter(this);
 
-  if (NS_FAILED(NS_RegisterMemoryReporter(mMemoryReporter))) {
+  if (NS_FAILED(NS_RegisterMemoryMultiReporter(mMemoryReporter))) {
     NS_WARNING("Failed to register memory reporter!");
     // No need to lock here since a failed registration means our memory
     // reporter can't start running. Just clean up.
     mMemoryReporter = nullptr;
 
     return;
   }
 }
@@ -3144,17 +3144,17 @@ WorkerPrivate::DisableMemoryReporter()
       }
 
       NS_ASSERTION(mBlockedForMemoryReporter, "Somehow we got unblocked!");
       mBlockedForMemoryReporter = false;
     }
   }
 
   // Finally unregister the memory reporter.
-  if (NS_FAILED(NS_UnregisterMemoryReporter(memoryReporter))) {
+  if (NS_FAILED(NS_UnregisterMemoryMultiReporter(memoryReporter))) {
     NS_WARNING("Failed to unregister memory reporter!");
   }
 }
 
 void
 WorkerPrivate::WaitForWorkerEvents(PRIntervalTime aInterval)
 {
   AssertIsOnWorkerThread();
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -29,16 +29,17 @@
 
 #include "EventTarget.h"
 #include "Queue.h"
 #include "WorkerFeature.h"
 
 class JSAutoStructuredCloneBuffer;
 class nsIChannel;
 class nsIDocument;
+class nsIMemoryMultiReporter;
 class nsIPrincipal;
 class nsIScriptContext;
 class nsIURI;
 class nsPIDOMWindow;
 class nsITimer;
 
 namespace JS {
 class RuntimeStats;
--- a/extensions/spellcheck/hunspell/src/mozHunspell.cpp
+++ b/extensions/spellcheck/hunspell/src/mozHunspell.cpp
@@ -92,21 +92,21 @@ NS_INTERFACE_MAP_BEGIN(mozHunspell)
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(mozHunspell)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_3(mozHunspell,
                            mPersonalDictionary,
                            mEncoder,
                            mDecoder)
 
-class SpellCheckReporter MOZ_FINAL : public mozilla::MemoryUniReporter
+class SpellCheckReporter MOZ_FINAL : public mozilla::MemoryReporterBase
 {
 public:
   SpellCheckReporter()
-    : MemoryUniReporter("explicit/spell-check", KIND_HEAP, UNITS_BYTES,
+    : MemoryReporterBase("explicit/spell-check", KIND_HEAP, UNITS_BYTES,
 "Memory used by the Hunspell spell checking engine's internal data structures.")
   {
 #ifdef DEBUG
     // There must be only one instance of this class, due to |sAmount|
     // being static.
     static bool hasRun = false;
     MOZ_ASSERT(!hasRun);
     hasRun = true;
--- a/gfx/gl/GfxTexturesReporter.h
+++ b/gfx/gl/GfxTexturesReporter.h
@@ -5,22 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIMemoryReporter.h"
 #include "GLTypes.h"
 
 namespace mozilla {
 namespace gl {
 
-class GfxTexturesReporter MOZ_FINAL : public MemoryUniReporter
+class GfxTexturesReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     GfxTexturesReporter()
-      : MemoryUniReporter("gfx-textures", KIND_OTHER, UNITS_BYTES,
-                          "Memory used for storing GL textures.")
+      : MemoryReporterBase("gfx-textures", KIND_OTHER, UNITS_BYTES,
+                           "Memory used for storing GL textures.")
     {
 #ifdef DEBUG
         // There must be only one instance of this class, due to |sAmount|
         // being static.  Assert this.
         static bool hasRun = false;
         MOZ_ASSERT(!hasRun);
         hasRun = true;
 #endif
@@ -40,9 +40,9 @@ public:
 
 private:
     int64_t Amount() MOZ_OVERRIDE { return sAmount; }
 
     static int64_t sAmount;
 };
 
 }
-}
+}
\ No newline at end of file
--- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
+++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
@@ -179,23 +179,23 @@ PixelFormatForContentType(gfxASurface::g
 }
 
 static gfxASurface::gfxContentType
 ContentTypeFromPixelFormat(android::PixelFormat aFormat)
 {
   return gfxASurface::ContentFromFormat(ImageFormatForPixelFormat(aFormat));
 }
 
-class GrallocReporter MOZ_FINAL : public MemoryUniReporter
+class GrallocReporter MOZ_FINAL : public MemoryReporterBase
 {
   friend class GrallocBufferActor;
 
 public:
   GrallocReporter()
-    : MemoryUniReporter("gralloc", KIND_OTHER, UNITS_BYTES,
+    : MemoryReporterBase("gralloc", KIND_OTHER, UNITS_BYTES,
 "Special RAM that can be shared between processes and directly accessed by "
 "both the CPU and GPU.  Gralloc memory is usually a relatively precious "
 "resource, with much less available than generic RAM.  When it's exhausted, "
 "graphics performance can suffer. This value can be incorrect because of race "
 "conditions.")
   {
 #ifdef DEBUG
     // There must be only one instance of this class, due to |sAmount|
--- a/gfx/thebes/gfxASurface.cpp
+++ b/gfx/thebes/gfxASurface.cpp
@@ -593,31 +593,31 @@ PR_STATIC_ASSERT(uint32_t(CAIRO_SURFACE_
 PR_STATIC_ASSERT(uint32_t(CAIRO_SURFACE_TYPE_SKIA) ==
                  uint32_t(gfxASurface::SurfaceTypeSkia));
 
 /* Surface size memory reporting */
 
 static int64_t gSurfaceMemoryUsed[gfxASurface::SurfaceTypeMax] = { 0 };
 
 class SurfaceMemoryReporter MOZ_FINAL :
-    public nsIMemoryReporter
+    public nsIMemoryMultiReporter
 {
 public:
     SurfaceMemoryReporter()
     { }
 
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD GetName(nsACString &name)
     {
         name.AssignLiteral("gfx-surface");
         return NS_OK;
     }
 
-    NS_IMETHOD CollectReports(nsIMemoryReporterCallback *aCb,
+    NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *aCb,
                               nsISupports *aClosure)
     {
         size_t len = NS_ARRAY_LENGTH(sSurfaceMemoryReporterAttrs);
         for (size_t i = 0; i < len; i++) {
             int64_t amount = gSurfaceMemoryUsed[i];
 
             if (amount != 0) {
                 const char *path = sSurfaceMemoryReporterAttrs[i].path;
@@ -634,30 +634,30 @@ public:
                 NS_ENSURE_SUCCESS(rv, rv);
             }
         }
 
         return NS_OK;
     }
 };
 
-NS_IMPL_ISUPPORTS1(SurfaceMemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(SurfaceMemoryReporter, nsIMemoryMultiReporter)
 
 void
 gfxASurface::RecordMemoryUsedForSurfaceType(gfxASurface::gfxSurfaceType aType,
                                             int32_t aBytes)
 {
     if (aType < 0 || aType >= SurfaceTypeMax) {
         NS_WARNING("Invalid type to RecordMemoryUsedForSurfaceType!");
         return;
     }
 
     static bool registered = false;
     if (!registered) {
-        NS_RegisterMemoryReporter(new SurfaceMemoryReporter());
+        NS_RegisterMemoryMultiReporter(new SurfaceMemoryReporter());
         registered = true;
     }
 
     gSurfaceMemoryUsed[aType] += aBytes;
 }
 
 void
 gfxASurface::RecordMemoryUsed(int32_t aBytes)
--- a/gfx/thebes/gfxAndroidPlatform.cpp
+++ b/gfx/thebes/gfxAndroidPlatform.cpp
@@ -25,22 +25,22 @@
 #include FT_MODULE_H
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 static FT_Library gPlatformFTLibrary = nullptr;
 
-class FreetypeReporter MOZ_FINAL : public MemoryUniReporter
+class FreetypeReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     FreetypeReporter()
-      : MemoryUniReporter("explicit/freetype", KIND_HEAP, UNITS_BYTES,
-                          "Memory used by Freetype.")
+      : MemoryReporterBase("explicit/freetype", KIND_HEAP, UNITS_BYTES,
+                           "Memory used by Freetype.")
     {
 #ifdef DEBUG
         // There must be only one instance of this class, due to |sAmount|
         // being static.
         static bool hasRun = false;
         MOZ_ASSERT(!hasRun);
         hasRun = true;
 #endif
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1248,30 +1248,30 @@ gfxFontFamily::SizeOfIncludingThis(Mallo
 /*
  * gfxFontCache - global cache of gfxFont instances.
  * Expires unused fonts after a short interval;
  * notifies fonts to age their cached shaped-word records;
  * observes memory-pressure notification and tells fonts to clear their
  * shaped-word caches to free up memory.
  */
 
-NS_IMPL_ISUPPORTS1(gfxFontCache::MemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(gfxFontCache::MemoryReporter, nsIMemoryMultiReporter)
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontCacheMallocSizeOf)
 
 NS_IMETHODIMP
 gfxFontCache::MemoryReporter::GetName(nsACString &aName)
 {
     aName.AssignLiteral("font-cache");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 gfxFontCache::MemoryReporter::CollectReports
-    (nsIMemoryReporterCallback* aCb,
+    (nsIMemoryMultiReporterCallback* aCb,
      nsISupports* aClosure)
 {
     FontCacheSizes sizes;
 
     gfxFontCache::GetCache()->SizeOfIncludingThis(&FontCacheMallocSizeOf,
                                                   &sizes);
 
     aCb->Callback(EmptyCString(),
@@ -1320,17 +1320,17 @@ MemoryPressureObserver::Observe(nsISuppo
 nsresult
 gfxFontCache::Init()
 {
     NS_ASSERTION(!gGlobalCache, "Where did this come from?");
     gGlobalCache = new gfxFontCache();
     if (!gGlobalCache) {
         return NS_ERROR_OUT_OF_MEMORY;
     }
-    NS_RegisterMemoryReporter(new MemoryReporter);
+    NS_RegisterMemoryMultiReporter(new MemoryReporter);
     return NS_OK;
 }
 
 void
 gfxFontCache::Shutdown()
 {
     delete gGlobalCache;
     gGlobalCache = nullptr;
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -972,21 +972,21 @@ public:
 
     void SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                              FontCacheSizes*   aSizes) const;
     void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
                              FontCacheSizes*   aSizes) const;
 
 protected:
     class MemoryReporter MOZ_FINAL
-        : public nsIMemoryReporter
+        : public nsIMemoryMultiReporter
     {
     public:
         NS_DECL_ISUPPORTS
-        NS_DECL_NSIMEMORYREPORTER
+        NS_DECL_NSIMEMORYMULTIREPORTER
     };
 
     void DestroyFont(gfxFont *aFont);
 
     static gfxFontCache *gGlobalCache;
 
     struct Key {
         const gfxFontEntry* mFontEntry;
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -65,30 +65,30 @@ gfxFontListPrefObserver::Observe(nsISupp
     NS_ASSERTION(!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), "invalid topic");
     // XXX this could be made to only clear out the cache for the prefs that were changed
     // but it probably isn't that big a deal.
     gfxPlatformFontList::PlatformFontList()->ClearPrefFonts();
     gfxFontCache::GetCache()->AgeAllGenerations();
     return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS1(gfxPlatformFontList::MemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(gfxPlatformFontList::MemoryReporter, nsIMemoryMultiReporter)
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontListMallocSizeOf)
 
 NS_IMETHODIMP
 gfxPlatformFontList::MemoryReporter::GetName(nsACString &aName)
 {
     aName.AssignLiteral("font-list");
     return NS_OK;
 }
 
 NS_IMETHODIMP
 gfxPlatformFontList::MemoryReporter::CollectReports
-    (nsIMemoryReporterCallback* aCb,
+    (nsIMemoryMultiReporterCallback* aCb,
      nsISupports* aClosure)
 {
     FontListSizes sizes;
     sizes.mFontListSize = 0;
     sizes.mFontTableCacheSize = 0;
     sizes.mCharMapsSize = 0;
 
     gfxPlatformFontList::PlatformFontList()->SizeOfIncludingThis(&FontListMallocSizeOf,
@@ -136,17 +136,17 @@ gfxPlatformFontList::gfxPlatformFontList
 
     // pref changes notification setup
     NS_ASSERTION(!gFontListPrefObserver,
                  "There has been font list pref observer already");
     gFontListPrefObserver = new gfxFontListPrefObserver();
     NS_ADDREF(gFontListPrefObserver);
     Preferences::AddStrongObservers(gFontListPrefObserver, kObservedPrefs);
 
-    NS_RegisterMemoryReporter(new MemoryReporter);
+    NS_RegisterMemoryMultiReporter(new MemoryReporter);
 }
 
 gfxPlatformFontList::~gfxPlatformFontList()
 {
     mSharedCmaps.Clear();
     NS_ASSERTION(gFontListPrefObserver, "There is no font list pref observer");
     Preferences::RemoveObservers(gFontListPrefObserver, kObservedPrefs);
     NS_RELEASE(gFontListPrefObserver);
--- a/gfx/thebes/gfxPlatformFontList.h
+++ b/gfx/thebes/gfxPlatformFontList.h
@@ -174,21 +174,21 @@ public:
     // add a cmap to the shared cmap set
     gfxCharacterMap* AddCmap(const gfxCharacterMap *aCharMap);
 
     // remove the cmap from the shared cmap set
     void RemoveCmap(const gfxCharacterMap *aCharMap);
 
 protected:
     class MemoryReporter MOZ_FINAL
-        : public nsIMemoryReporter
+        : public nsIMemoryMultiReporter
     {
     public:
         NS_DECL_ISUPPORTS
-        NS_DECL_NSIMEMORYREPORTER
+        NS_DECL_NSIMEMORYMULTIREPORTER
     };
 
     gfxPlatformFontList(bool aNeedFullnamePostscriptNames = true);
 
     static gfxPlatformFontList *sPlatformFontList;
 
     static PLDHashOperator FindFontForCharProc(nsStringHashKey::KeyType aKey,
                                                nsRefPtr<gfxFontFamily>& aFamilyEntry,
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -68,21 +68,21 @@ using namespace mozilla;
 #ifdef CAIRO_HAS_D2D_SURFACE
 
 static const char *kFeatureLevelPref =
   "gfx.direct3d.last_used_feature_level_idx";
 static const int kSupportedFeatureLevels[] =
   { D3D10_FEATURE_LEVEL_10_1, D3D10_FEATURE_LEVEL_10_0,
     D3D10_FEATURE_LEVEL_9_3 };
 
-class GfxD2DSurfaceCacheReporter MOZ_FINAL : public MemoryUniReporter
+class GfxD2DSurfaceCacheReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     GfxD2DSurfaceCacheReporter()
-      : MemoryUniReporter("gfx-d2d-surface-cache", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("gfx-d2d-surface-cache", KIND_OTHER, UNITS_BYTES,
 "Memory used by the Direct2D internal surface cache.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         return cairo_d2d_get_image_surface_cache_usage();
     }
 };
@@ -105,52 +105,52 @@ bool OncePreferenceDirect2DForceEnabled(
   if (preferenceValue < 0) {
     preferenceValue = Preferences::GetBool("gfx.direct2d.force-enabled", false);
   }
   return !!preferenceValue;
 }
 
 } // anonymous namespace
 
-class GfxD2DSurfaceVramReporter MOZ_FINAL : public MemoryUniReporter
+class GfxD2DSurfaceVramReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     GfxD2DSurfaceVramReporter()
-      : MemoryUniReporter("gfx-d2d-surface-vram", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("gfx-d2d-surface-vram", KIND_OTHER, UNITS_BYTES,
                            "Video memory used by D2D surfaces.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE {
       cairo_device_t *device =
           gfxWindowsPlatform::GetPlatform()->GetD2DDevice();
       return device ? cairo_d2d_get_surface_vram_usage(device) : 0;
     }
 };
 
 #endif
 
-class GfxD2DVramDrawTargetReporter MOZ_FINAL : public MemoryUniReporter
+class GfxD2DVramDrawTargetReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     GfxD2DVramDrawTargetReporter()
-      : MemoryUniReporter("gfx-d2d-vram-draw-target", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("gfx-d2d-vram-draw-target", KIND_OTHER, UNITS_BYTES,
                            "Video memory used by D2D DrawTargets.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         return Factory::GetD2DVRAMUsageDrawTarget();
     }
 };
 
-class GfxD2DVramSourceSurfaceReporter MOZ_FINAL : public MemoryUniReporter
+class GfxD2DVramSourceSurfaceReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     GfxD2DVramSourceSurfaceReporter()
-      : MemoryUniReporter("gfx-d2d-vram-source-surface",
+      : MemoryReporterBase("gfx-d2d-vram-source-surface",
                            KIND_OTHER, UNITS_BYTES,
                            "Video memory used by D2D SourceSurfaces.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         return Factory::GetD2DVRAMUsageSourceSurface();
     }
@@ -201,17 +201,17 @@ typedef HRESULT (WINAPI*D3D11CreateDevic
   D3D_FEATURE_LEVEL *pFeatureLevels,
   UINT FeatureLevels,
   UINT SDKVersion,
   ID3D11Device **ppDevice,
   D3D_FEATURE_LEVEL *pFeatureLevel,
   ID3D11DeviceContext *ppImmediateContext
 );
 
-class GPUAdapterReporter : public nsIMemoryReporter {
+class GPUAdapterMultiReporter : public nsIMemoryMultiReporter {
 
     // Callers must Release the DXGIAdapter after use or risk mem-leak
     static bool GetDXGIAdapter(IDXGIAdapter **DXGIAdapter)
     {
         ID3D10Device1 *D2D10Device;
         IDXGIDevice *DXGIDevice;
         bool result = false;
         
@@ -223,27 +223,27 @@ class GPUAdapterReporter : public nsIMem
         }
         
         return result;
     }
     
 public:
     NS_DECL_ISUPPORTS
 
-    // nsIMemoryReporter abstract method implementation
+    // nsIMemoryMultiReporter abstract method implementation
     NS_IMETHOD
     GetName(nsACString &aName)
     {
         aName.AssignLiteral("gpuadapter");
         return NS_OK;
     }
     
-    // nsIMemoryReporter abstract method implementation
+    // nsIMemoryMultiReporter abstract method implementation
     NS_IMETHOD
-    CollectReports(nsIMemoryReporterCallback* aCb,
+    CollectReports(nsIMemoryMultiReporterCallback* aCb,
                    nsISupports* aClosure)
     {
         int32_t winVers, buildNum;
         HANDLE ProcessHandle = GetCurrentProcess();
         
         int64_t dedicatedBytesUsed = 0;
         int64_t sharedBytesUsed = 0;
         int64_t committedBytesUsed = 0;
@@ -342,17 +342,17 @@ public:
         REPORT("gpu-shared", sharedBytesUsed,
                "In-process memory that is shared with the GPU.");
         
 #undef REPORT
 
         return NS_OK;
     }
 };
-NS_IMPL_ISUPPORTS1(GPUAdapterReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(GPUAdapterMultiReporter, nsIMemoryMultiReporter)
 
 static __inline void
 BuildKeyNameFromFontName(nsAString &aName)
 {
     if (aName.Length() >= LF_FACESIZE)
         aName.Truncate(LF_FACESIZE - 1);
     ToLowerCase(aName);
 }
@@ -379,26 +379,26 @@ gfxWindowsPlatform::gfxWindowsPlatform()
     NS_RegisterMemoryReporter(new GfxD2DSurfaceVramReporter());
     mD2DDevice = nullptr;
 #endif
     NS_RegisterMemoryReporter(new GfxD2DVramDrawTargetReporter());
     NS_RegisterMemoryReporter(new GfxD2DVramSourceSurfaceReporter());
 
     UpdateRenderMode();
 
-    mGPUAdapterReporter = new GPUAdapterReporter();
-    NS_RegisterMemoryReporter(mGPUAdapterReporter);
+    mGPUAdapterMultiReporter = new GPUAdapterMultiReporter();
+    NS_RegisterMemoryMultiReporter(mGPUAdapterMultiReporter);
 }
 
 gfxWindowsPlatform::~gfxWindowsPlatform()
 {
-    NS_UnregisterMemoryReporter(mGPUAdapterReporter);
-
-    mDeviceManager = nullptr;
-
+    NS_UnregisterMemoryMultiReporter(mGPUAdapterMultiReporter);
+    
+     mDeviceManager = nullptr;
+     
     ::ReleaseDC(nullptr, mScreenDC);
     // not calling FT_Done_FreeType because cairo may still hold references to
     // these FT_Faces.  See bug 458169.
 #ifdef CAIRO_HAS_D2D_SURFACE
     if (mD2DDevice) {
         cairo_release_device(mD2DDevice);
     }
 #endif
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -47,17 +47,17 @@ namespace mozilla {
 namespace layers {
 class DeviceManagerD3D9;
 }
 }
 class IDirect3DDevice9;
 class ID3D11Device;
 class IDXGIAdapter1;
 
-class nsIMemoryReporter;
+class nsIMemoryMultiReporter;
 
 // Utility to get a Windows HDC from a thebes context,
 // used by both GDI and Uniscribe font shapers
 struct DCFromContext {
     DCFromContext(gfxContext *aContext) {
         dc = nullptr;
         nsRefPtr<gfxASurface> aSurface = aContext->CurrentSurface();
         NS_ASSERTION(aSurface || !aContext->IsCairo(), "DCFromContext: null surface");
@@ -310,12 +310,12 @@ private:
     bool mD3D9DeviceInitialized;
     bool mD3D11DeviceInitialized;
 
     virtual qcms_profile* GetPlatformCMSOutputProfile();
 
     // TODO: unify this with mPrefFonts (NB: holds families, not fonts) in gfxPlatformFontList
     nsDataHashtable<nsCStringHashKey, nsTArray<nsRefPtr<gfxFontEntry> > > mPrefFonts;
 
-    nsIMemoryReporter* mGPUAdapterReporter;
+    nsIMemoryMultiReporter* mGPUAdapterMultiReporter;
 };
 
 #endif /* GFX_WINDOWS_PLATFORM_H */
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -44,32 +44,32 @@
 #include "nsILoadGroupChild.h"
 
 using namespace mozilla;
 using namespace mozilla::image;
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(ImagesMallocSizeOf)
 
 class imgMemoryReporter MOZ_FINAL :
-  public nsIMemoryReporter
+  public nsIMemoryMultiReporter
 {
 public:
   imgMemoryReporter()
   {
   }
 
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD GetName(nsACString &name)
   {
     name.Assign("images");
     return NS_OK;
   }
 
-  NS_IMETHOD CollectReports(nsIMemoryReporterCallback *callback,
+  NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *callback,
                             nsISupports *closure)
   {
     AllSizes chrome;
     AllSizes content;
 
     for (uint32_t i = 0; i < mKnownLoaders.Length(); i++) {
       mKnownLoaders[i]->mChromeCache.EnumerateRead(EntryAllSizes, &chrome);
       mKnownLoaders[i]->mCache.EnumerateRead(EntryAllSizes, &content);
@@ -216,25 +216,25 @@ private:
         *n += image->NonHeapSizeOfDecoded();
       }
     }
 
     return PL_DHASH_NEXT;
   }
 };
 
-NS_IMPL_ISUPPORTS1(imgMemoryReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(imgMemoryReporter, nsIMemoryMultiReporter)
 
 // This is used by telemetry.
 class ImagesContentUsedUncompressedReporter MOZ_FINAL
-  : public MemoryUniReporter
+  : public MemoryReporterBase
 {
 public:
   ImagesContentUsedUncompressedReporter()
-    : MemoryUniReporter("images-content-used-uncompressed",
+    : MemoryReporterBase("images-content-used-uncompressed",
                          KIND_OTHER, UNITS_BYTES,
 "This is the sum of the 'explicit/images/content/used/uncompressed-heap' "
 "and 'explicit/images/content/used/uncompressed-nonheap' numbers.  However, "
 "it is measured at a different time and so may give slightly different "
 "results.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
@@ -833,17 +833,17 @@ void imgLoader::GlobalInit()
   int32_t cachesize;
   rv = Preferences::GetInt("image.cache.size", &cachesize);
   if (NS_SUCCEEDED(rv))
     sCacheMaxSize = cachesize;
   else
     sCacheMaxSize = 5 * 1024 * 1024;
 
   sMemReporter = new imgMemoryReporter();
-  NS_RegisterMemoryReporter(sMemReporter);
+  NS_RegisterMemoryMultiReporter(sMemReporter);
   NS_RegisterMemoryReporter(new ImagesContentUsedUncompressedReporter());
 }
 
 nsresult imgLoader::InitCache()
 {
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (!os)
     return NS_ERROR_FAILURE;
--- a/image/test/mochitest/test_bug601470.html
+++ b/image/test/mochitest/test_bug601470.html
@@ -20,28 +20,24 @@ https://bugzilla.mozilla.org/show_bug.cg
 /** Test for Bug 601470 **/
 
 SimpleTest.waitForExplicitFinish();
 
 window.onload = function() {
   var mgr = SpecialPowers.Cc["@mozilla.org/memory-reporter-manager;1"]
     .getService(SpecialPowers.Ci.nsIMemoryReporterManager);
 
-  var amount = 0;
-  var handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) {
-    amount += aAmount;
+  var e = mgr.enumerateReporters();
+  var memoryCounter = 0;
+  while (e.hasMoreElements()) {
+    var mr =
+      e.getNext().QueryInterface(SpecialPowers.Ci.nsIMemoryReporter);
+    memoryCounter += mr.amount;
   }
-
-  var e = mgr.enumerateReporters();
-  while (e.hasMoreElements()) {
-    var mr = e.getNext().QueryInterface(SpecialPowers.Ci.nsIMemoryReporter);
-    mr.collectReports(handleReport, null);
-  }
-
-  ok(amount > 0, "we should be using a nonzero amount of memory");
+  ok(memoryCounter > 0, "we should be using a nonzero amount of memory");
   ok(true, "yay, didn't crash!");
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
--- a/ipc/glue/SharedMemory.cpp
+++ b/ipc/glue/SharedMemory.cpp
@@ -12,33 +12,33 @@
 #include "mozilla/Atomics.h"
 
 namespace mozilla {
 namespace ipc {
 
 static Atomic<size_t> gShmemAllocated;
 static Atomic<size_t> gShmemMapped;
 
-class ShmemAllocatedReporter MOZ_FINAL : public MemoryUniReporter
+class ShmemAllocatedReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   ShmemAllocatedReporter()
-    : MemoryUniReporter("shmem-allocated", KIND_OTHER, UNITS_BYTES,
+    : MemoryReporterBase("shmem-allocated", KIND_OTHER, UNITS_BYTES,
 "Memory shared with other processes that is accessible (but not necessarily "
 "mapped).")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE { return gShmemAllocated; }
 };
 
-class ShmemMappedReporter MOZ_FINAL : public MemoryUniReporter
+class ShmemMappedReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   ShmemMappedReporter()
-    : MemoryUniReporter("shmem-mapped", KIND_OTHER, UNITS_BYTES,
+    : MemoryReporterBase("shmem-mapped", KIND_OTHER, UNITS_BYTES,
 "Memory shared with other processes that is mapped into the address space.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE { return gShmemMapped; }
 };
 
 SharedMemory::SharedMemory()
   : mAllocSize(0)
--- a/js/xpconnect/src/XPCJSMemoryReporter.h
+++ b/js/xpconnect/src/XPCJSMemoryReporter.h
@@ -4,30 +4,30 @@
  * 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 XPCJSMemoryReporter_h
 #define XPCJSMemoryReporter_h
 
 class nsISupports;
-class nsIMemoryReporterCallback;
+class nsIMemoryMultiReporterCallback;
 
 namespace xpc {
 
 // The key is the window ID.
 typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths;
 
-// This is very nearly an instance of nsIMemoryReporter, but it's not,
+// This is very nearly an instance of nsIMemoryMultiReporter, but it's not,
 // because it's invoked by nsWindowMemoryReporter in order to get |windowPaths|
 // in CollectReports.
-class JSReporter
+class JSMemoryMultiReporter
 {
 public:
     static nsresult CollectReports(WindowPaths *windowPaths,
                                    WindowPaths *topWindowPaths,
-                                   nsIMemoryReporterCallback *cb,
+                                   nsIMemoryMultiReporterCallback *cb,
                                    nsISupports *closure);
 };
 
 }
 
 #endif
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1559,82 +1559,84 @@ GetCompartmentName(JSCompartment *c, nsC
         // (such as about:memory) have to undo this change.
         if (replaceSlashes)
             name.ReplaceChar('/', '\\');
     } else {
         name.AssignLiteral("null-principal");
     }
 }
 
-// Telemetry relies on this being a uni-reporter (rather than part of the "js"
-// reporter).
-class JSGCHeapReporter MOZ_FINAL : public MemoryUniReporter
+// Telemetry relies on this being a single reporter (rather than part of the
+// "js" multi-reporter).
+class JSGCHeapReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     JSGCHeapReporter()
-      : MemoryUniReporter("js-gc-heap", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("js-gc-heap", KIND_OTHER, UNITS_BYTES,
 "Memory used by the garbage-collected JavaScript heap.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
         return int64_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) *
                js::gc::ChunkSize;
     }
 };
 
-// Nb: js-compartments/system + js-compartments/user could be different to the
-// number of compartments reported by JSReporter if a garbage collection
-// occurred between them being consulted.  We could move these reporters into
-// JSReporter to avoid that problem, but then we couldn't easily report them
-// via telemetry, so we live with the small risk of inconsistencies.
-
-class JSCompartmentsSystemReporter MOZ_FINAL : public MemoryUniReporter
+// Nb: js-compartments/system + js-compartments/user could be
+// different to the number of compartments reported by
+// JSMemoryMultiReporter if a garbage collection occurred
+// between them being consulted.  We could move these reporters into
+// XPConnectJSCompartmentCount to avoid that problem, but then we couldn't
+// easily report them via telemetry, so we live with the small risk of
+// inconsistencies.
+
+class JSCompartmentsSystemReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     JSCompartmentsSystemReporter()
-      : MemoryUniReporter("js-compartments/system", KIND_OTHER, UNITS_COUNT,
+      : MemoryReporterBase("js-compartments/system", KIND_OTHER, UNITS_COUNT,
 "The number of JavaScript compartments for system code.  The sum of this and "
 "'js-compartments/user' might not match the number of compartments listed "
 "in the 'explicit' tree if a garbage collection occurs at an inopportune "
 "time, but such cases should be rare.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
         return JS::SystemCompartmentCount(rt);
     }
 };
 
-class JSCompartmentsUserReporter MOZ_FINAL : public MemoryUniReporter
+class JSCompartmentsUserReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     JSCompartmentsUserReporter()
-      : MemoryUniReporter("js-compartments/user", KIND_OTHER, UNITS_COUNT,
+      : MemoryReporterBase("js-compartments/user", KIND_OTHER, UNITS_COUNT,
 "The number of JavaScript compartments for user code.  The sum of this and "
 "'js-compartments/system' might not match the number of compartments listed "
 "under 'js' if a garbage collection occurs at an inopportune time, but such "
 "cases should be rare.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
         return JS::UserCompartmentCount(rt);
     }
 };
 
 // This is also a single reporter so it can be used by telemetry.
-class JSMainRuntimeTemporaryPeakReporter MOZ_FINAL : public MemoryUniReporter
+class JSMainRuntimeTemporaryPeakReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     JSMainRuntimeTemporaryPeakReporter()
-      : MemoryUniReporter("js-main-runtime-temporary-peak",
+      : MemoryReporterBase("js-main-runtime-temporary-peak",
                            KIND_OTHER, UNITS_BYTES,
 "The peak size of the transient storage in the main JSRuntime (the current "
 "size of which is reported as 'explicit/js-non-window/runtime/temporary').")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->Runtime();
@@ -1755,17 +1757,17 @@ private:
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsMallocSizeOf)
 
 namespace xpc {
 
 static nsresult
 ReportZoneStats(const JS::ZoneStats &zStats,
                 const xpc::ZoneStatsExtras &extras,
-                nsIMemoryReporterCallback *cb,
+                nsIMemoryMultiReporterCallback *cb,
                 nsISupports *closure, size_t *gcTotalOut = NULL)
 {
     const nsAutoCString& pathPrefix = extras.pathPrefix;
     size_t gcTotal = 0, gcHeapSundries = 0, otherSundries = 0;
 
     ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"),
                       zStats.gcHeapArenaAdmin,
                       "Memory on the garbage-collected JavaScript "
@@ -1939,17 +1941,17 @@ ReportZoneStats(const JS::ZoneStats &zSt
 
 #   undef STRING_LENGTH
 }
 
 static nsresult
 ReportCompartmentStats(const JS::CompartmentStats &cStats,
                        const xpc::CompartmentStatsExtras &extras,
                        amIAddonManager *addonManager,
-                       nsIMemoryReporterCallback *cb,
+                       nsIMemoryMultiReporterCallback *cb,
                        nsISupports *closure, size_t *gcTotalOut = NULL)
 {
     static const nsDependentCString addonPrefix("explicit/add-ons/");
 
     size_t gcTotal = 0, gcHeapSundries = 0, otherSundries = 0;
     nsAutoCString cJSPathPrefix = extras.jsPathPrefix;
     nsAutoCString cDOMPathPrefix = extras.domPathPrefix;
 
@@ -2081,18 +2083,18 @@ ReportCompartmentStats(const JS::Compart
                    cStats.objectsExtra.propertyIteratorData,
                    "Memory allocated on the malloc heap for data belonging to property iterator objects.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/malloc-heap/ctypes-data"),
                    cStats.objectsExtra.ctypesData,
                    "Memory allocated on the malloc heap for data belonging to ctypes objects.");
 
     // Note that we use cDOMPathPrefix here.  This is because we measure orphan
-    // DOM nodes in the JS reporter, but we want to report them in a "dom"
-    // sub-tree rather than a "js" sub-tree.
+    // DOM nodes in the JS multi-reporter, but we want to report them in a
+    // "dom" sub-tree rather than a "js" sub-tree.
     ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"),
                    cStats.objectsExtra.private_,
                    "Memory used by orphan DOM nodes that are only reachable "
                    "from JavaScript objects.");
 
     ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"),
                    cStats.shapesExtraTreeTables,
                    "Memory allocated on the malloc heap for the property tables "
@@ -2204,17 +2206,17 @@ ReportCompartmentStats(const JS::Compart
 
     return NS_OK;
 }
 
 static nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
                                  const nsACString &rtPath,
                                  amIAddonManager* addonManager,
-                                 nsIMemoryReporterCallback *cb,
+                                 nsIMemoryMultiReporterCallback *cb,
                                  nsISupports *closure, size_t *rtTotalOut)
 {
     nsresult rv;
 
     size_t gcTotal = 0;
 
     for (size_t i = 0; i < rtStats.zoneStatsVector.length(); i++) {
         const JS::ZoneStats &zStats = rtStats.zoneStatsVector[i];
@@ -2346,29 +2348,29 @@ ReportJSRuntimeExplicitTreeStats(const J
     MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal);
 
     return NS_OK;
 }
 
 nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
                                  const nsACString &rtPath,
-                                 nsIMemoryReporterCallback *cb,
+                                 nsIMemoryMultiReporterCallback *cb,
                                  nsISupports *closure, size_t *rtTotalOut)
 {
     nsCOMPtr<amIAddonManager> am =
       do_GetService("@mozilla.org/addons/integration;1");
     return ReportJSRuntimeExplicitTreeStats(rtStats, rtPath, am.get(), cb,
                                             closure, rtTotalOut);
 }
 
 
 } // namespace xpc
 
-class JSCompartmentsReporter MOZ_FINAL : public nsIMemoryReporter
+class JSCompartmentsMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
 
     NS_IMETHOD GetName(nsACString &name) {
         name.AssignLiteral("compartments");
         return NS_OK;
     }
@@ -2382,17 +2384,17 @@ class JSCompartmentsReporter MOZ_FINAL :
         GetCompartmentName(c, path, true);
         path.Insert(js::IsSystemCompartment(c)
                     ? NS_LITERAL_CSTRING("compartments/system/")
                     : NS_LITERAL_CSTRING("compartments/user/"),
                     0);
         paths->append(path);
     }
 
-    NS_IMETHOD CollectReports(nsIMemoryReporterCallback *cb,
+    NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *cb,
                               nsISupports *closure)
     {
         // First we collect the compartment paths.  Then we report them.  Doing
         // the two steps interleaved is a bad idea, because calling |cb|
         // from within CompartmentCallback() leads to all manner of assertions.
 
         // Collect.
 
@@ -2404,17 +2406,19 @@ class JSCompartmentsReporter MOZ_FINAL :
         for (size_t i = 0; i < paths.length(); i++)
             // These ones don't need a description, hence the "".
             REPORT(nsCString(paths[i]), KIND_OTHER, UNITS_COUNT, 1, "");
 
         return NS_OK;
     }
 };
 
-NS_IMPL_ISUPPORTS1(JSCompartmentsReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(JSCompartmentsMultiReporter
+                              , nsIMemoryMultiReporter
+                              )
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(OrphanMallocSizeOf)
 
 namespace xpc {
 
 static size_t
 SizeOfTreeIncludingThis(nsINode *tree)
 {
@@ -2561,32 +2565,32 @@ class XPCJSRuntimeStats : public JS::Run
 
         extras->jsPathPrefix += NS_LITERAL_CSTRING("compartment(") + cName + NS_LITERAL_CSTRING(")/");
 
         // extras->jsPathPrefix is used for almost all the compartment-specific
         // reports. At this point it has the form
         // "<something>compartment(<cname>)/".
         //
         // extras->domPathPrefix is used for DOM orphan nodes, which are
-        // counted by the JS reporter but reported as part of the DOM
-        // measurements. At this point it has the form "<something>/dom/" if
-        // this compartment belongs to an nsGlobalWindow, and
+        // counted by the JS multi-reporter but reported as part of the
+        // DOM measurements. At this point it has the form "<something>/dom/"
+        // if this compartment belongs to an nsGlobalWindow, and
         // "explicit/dom/<something>?!/" otherwise (in which case it shouldn't
         // be used, because non-nsGlobalWindow compartments shouldn't have
         // orphan DOM nodes).
 
         cstats->extra = extras;
     }
 };
 
 nsresult
-JSReporter::CollectReports(WindowPaths *windowPaths,
-                           WindowPaths *topWindowPaths,
-                           nsIMemoryReporterCallback *cb,
-                           nsISupports *closure)
+JSMemoryMultiReporter::CollectReports(WindowPaths *windowPaths,
+                                      WindowPaths *topWindowPaths,
+                                      nsIMemoryMultiReporterCallback *cb,
+                                      nsISupports *closure)
 {
     XPCJSRuntime *xpcrt = nsXPConnect::GetRuntimeInstance();
 
     // In the first step we get all the stats and stash them in a local
     // data structure.  In the second step we pass all the stashed stats to
     // the callback.  Separating these steps is important because the
     // callback may be a JS function, and executing JS while getting these
     // stats seems like a bad idea.
@@ -3046,17 +3050,17 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     // internationalization APIs work as desired.
     if (!xpc_LocalizeRuntime(runtime))
         NS_RUNTIMEABORT("xpc_LocalizeRuntime failed.");
 
     NS_RegisterMemoryReporter(new JSGCHeapReporter());
     NS_RegisterMemoryReporter(new JSCompartmentsSystemReporter());
     NS_RegisterMemoryReporter(new JSCompartmentsUserReporter());
     NS_RegisterMemoryReporter(new JSMainRuntimeTemporaryPeakReporter());
-    NS_RegisterMemoryReporter(new JSCompartmentsReporter);
+    NS_RegisterMemoryMultiReporter(new JSCompartmentsMultiReporter);
 
     // Install a JavaScript 'debugger' keyword handler in debug builds only
 #ifdef DEBUG
     if (!JS_GetGlobalDebugHooks(runtime)->debuggerHandler)
         xpc_InstallJSDebuggerKeywordHandler(runtime);
 #endif
 }
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -158,17 +158,17 @@ xpc_TryUnmarkWrappedGrayObject(nsISuppor
 extern void
 xpc_UnmarkSkippableJSHolders();
 
 // No JS can be on the stack when this is called. Probably only useful from
 // xpcshell.
 NS_EXPORT_(void)
 xpc_ActivateDebugMode();
 
-class nsIMemoryReporterCallback;
+class nsIMemoryMultiReporterCallback;
 
 // readable string conversions, static methods and members only
 class XPCStringConvert
 {
 public:
 
     // If the string shares the readable's buffer, that buffer will
     // get assigned to *sharedBuffer.  Otherwise null will be
@@ -354,17 +354,17 @@ private:
 
 // This reports all the stats in |rtStats| that belong in the "explicit" tree,
 // (which isn't all of them).
 // @see ZoneStatsExtras
 // @see CompartmentStatsExtras
 nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
                                  const nsACString &rtPath,
-                                 nsIMemoryReporterCallback *cb,
+                                 nsIMemoryMultiReporterCallback *cb,
                                  nsISupports *closure, size_t *rtTotal = NULL);
 
 /**
  * Throws an exception on cx and returns false.
  */
 bool
 Throw(JSContext *cx, nsresult rv);
 
--- a/layout/base/nsStyleSheetService.cpp
+++ b/layout/base/nsStyleSheetService.cpp
@@ -21,21 +21,21 @@
 #include "nsNetUtil.h"
 #include "nsIObserverService.h"
 #include "nsLayoutStatics.h"
 #include "nsIMemoryReporter.h"
 
 using namespace mozilla;
 
 class LayoutStyleSheetServiceReporter MOZ_FINAL
-  : public mozilla::MemoryUniReporter
+  : public mozilla::MemoryReporterBase
 {
 public:
   LayoutStyleSheetServiceReporter()
-    : MemoryUniReporter("explicit/layout/style-sheet-service",
+    : MemoryReporterBase("explicit/layout/style-sheet-service",
                          KIND_HEAP, UNITS_BYTES,
 "Memory used for style sheets held by the style sheet service.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     return nsStyleSheetService::gInstance
          ? nsStyleSheetService::gInstance->SizeOfIncludingThis(MallocSizeOf)
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -13,21 +13,21 @@
 #include "nsIMemoryReporter.h"
 #include "nsNetUtil.h"
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIXULRuntime.h"
 #include "nsCSSStyleSheet.h"
 
 class LayoutStyleSheetCacheReporter MOZ_FINAL
-  : public mozilla::MemoryUniReporter
+  : public mozilla::MemoryReporterBase
 {
 public:
   LayoutStyleSheetCacheReporter()
-    : MemoryUniReporter("explicit/layout/style-sheet-cache",
+    : MemoryReporterBase("explicit/layout/style-sheet-cache",
                          KIND_HEAP, UNITS_BYTES,
                          "Memory used for some built-in style sheets.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     return nsLayoutStylesheetCache::SizeOfIncludingThis(MallocSizeOf);
   }
--- a/modules/libpref/src/Preferences.cpp
+++ b/modules/libpref/src/Preferences.cpp
@@ -198,21 +198,21 @@ Preferences::SizeOfIncludingThisAndOther
                                              aMallocSizeOf);
   }
   // We don't measure sRootBranch and sDefaultRootBranch here because
   // DMD indicates they are not significant.
   n += pref_SizeOfPrivateData(aMallocSizeOf);
   return n;
 }
 
-class PreferencesReporter MOZ_FINAL : public MemoryUniReporter
+class PreferencesReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   PreferencesReporter()
-    : MemoryUniReporter("explicit/preferences", KIND_HEAP, UNITS_BYTES,
+    : MemoryReporterBase("explicit/preferences", KIND_HEAP, UNITS_BYTES,
                          "Memory used by the preferences system.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     return Preferences::SizeOfIncludingThisAndOtherStuff(MallocSizeOf);
   }
 };
--- a/netwerk/cache/nsDiskCacheDevice.cpp
+++ b/netwerk/cache/nsDiskCacheDevice.cpp
@@ -365,21 +365,21 @@ nsDiskCache::Truncate(PRFileDesc *  fd, 
     return NS_OK;
 }
 
 
 /******************************************************************************
  *  nsDiskCacheDevice
  *****************************************************************************/
 
-class NetworkDiskCacheReporter MOZ_FINAL : public MemoryUniReporter
+class NetworkDiskCacheReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     NetworkDiskCacheReporter(nsDiskCacheDevice* aDevice)
-      : MemoryUniReporter(
+      : MemoryReporterBase(
             "explicit/network/disk-cache",
             KIND_HEAP,
             UNITS_BYTES,
             "Memory used by the network disk cache.")
       , mDevice(aDevice)
     {}
 
 private:
--- a/netwerk/cache/nsMemoryCacheDevice.cpp
+++ b/netwerk/cache/nsMemoryCacheDevice.cpp
@@ -24,21 +24,21 @@
 // The queues hold exponentially increasing ranges of floor(log2((size/nref)))
 // values for entries.
 // Entries larger than 2^(kQueueCount-1) go in the last queue.
 // Entries with no expiration go in the first queue.
 
 const char *gMemoryDeviceID      = "memory";
 
 class NetworkMemoryCacheReporter MOZ_FINAL :
-    public mozilla::MemoryUniReporter
+    public mozilla::MemoryReporterBase
 {
 public:
     NetworkMemoryCacheReporter(nsMemoryCacheDevice* aDevice)
-      : MemoryUniReporter(
+      : MemoryReporterBase(
             "explicit/network/memory-cache",
             KIND_HEAP,
             UNITS_BYTES,
             "Memory used by the network memory cache.")
       , mDevice(aDevice)
     {}
 
 private:
--- a/netwerk/dns/nsEffectiveTLDService.cpp
+++ b/netwerk/dns/nsEffectiveTLDService.cpp
@@ -56,21 +56,21 @@ nsDomainEntry::FuncForStaticAsserts(void
 #undef ETLD_ENTRY_OFFSET
 #undef ETLD_STR_NUM
 #undef ETLD_STR_NUM1
 
 // ----------------------------------------------------------------------
 
 static nsEffectiveTLDService *gService = nullptr;
 
-class EffectiveTLDServiceReporter MOZ_FINAL : public MemoryUniReporter
+class EffectiveTLDServiceReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   EffectiveTLDServiceReporter()
-    : MemoryUniReporter("explicit/xpcom/effective-TLD-service",
+    : MemoryReporterBase("explicit/xpcom/effective-TLD-service",
                          KIND_HEAP, UNITS_BYTES,
                          "Memory used by the effective TLD service.")
   {}
 
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     return gService ? gService->SizeOfIncludingThis(MallocSizeOf) : 0;
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -48,39 +48,39 @@
 #define SC_WORDSIZE "4"
 #else
 #define SC_WORDSIZE "8"
 #endif
 
 namespace mozilla {
 namespace scache {
 
-class StartupCacheMappingReporter MOZ_FINAL : public MemoryUniReporter
+class StartupCacheMappingReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   StartupCacheMappingReporter()
-    : MemoryUniReporter("explicit/startup-cache/mapping",
+    : MemoryReporterBase("explicit/startup-cache/mapping",
                          KIND_NONHEAP, UNITS_BYTES,
 "Memory used to hold the mapping of the startup cache from file.  This memory "
 "is likely to be swapped out shortly after start-up.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     mozilla::scache::StartupCache* sc =
       mozilla::scache::StartupCache::GetSingleton();
     return sc ? sc->SizeOfMapping() : 0;
   }
 };
 
-class StartupCacheDataReporter MOZ_FINAL : public MemoryUniReporter
+class StartupCacheDataReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   StartupCacheDataReporter()
-    : MemoryUniReporter("explicit/startup-cache/data", KIND_HEAP, UNITS_BYTES,
+    : MemoryReporterBase("explicit/startup-cache/data", KIND_HEAP, UNITS_BYTES,
 "Memory used by the startup cache for things other than the file mapping.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     mozilla::scache::StartupCache* sc =
       mozilla::scache::StartupCache::GetSingleton();
     return sc ? sc->HeapSizeOfIncludingThis(MallocSizeOf) : 0;
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -52,28 +52,28 @@ namespace storage {
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Memory Reporting
 
 // We don't need an "explicit" reporter for total SQLite memory usage, because
 // the multi-reporter provides reports that add up to the total.  But it's
 // useful to have the total in the "Other Measurements" list in about:memory,
 // and more importantly, we also gather the total via telemetry.
-class StorageSQLiteUniReporter MOZ_FINAL : public MemoryUniReporter
+class StorageSQLiteReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
-  StorageSQLiteUniReporter()
-    : MemoryUniReporter("storage-sqlite", KIND_OTHER, UNITS_BYTES,
+  StorageSQLiteReporter()
+    : MemoryReporterBase("storage-sqlite", KIND_OTHER, UNITS_BYTES,
                          "Memory used by SQLite.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE { return ::sqlite3_memory_used(); }
 };
 
-class StorageSQLiteMultiReporter MOZ_FINAL : public nsIMemoryReporter
+class StorageSQLiteMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
 private:
   Service *mService;    // a weakref because Service contains a strongref to this
   nsCString mStmtDesc;
   nsCString mCacheDesc;
   nsCString mSchemaDesc;
 
 public:
@@ -92,27 +92,27 @@ public:
 
     mSchemaDesc = NS_LITERAL_CSTRING(
       "Memory (approximate) used to store the schema for all databases "
       "associated with connections to this database.");
   }
 
   NS_IMETHOD GetName(nsACString &aName)
   {
-      aName.AssignLiteral("storage-sqlite-multi");
+      aName.AssignLiteral("storage-sqlite");
       return NS_OK;
   }
 
   // Warning: To get a Connection's measurements requires holding its lock.
   // There may be a delay getting the lock if another thread is accessing the
   // Connection.  This isn't very nice if CollectReports is called from the
   // main thread!  But at the time of writing this function is only called when
   // about:memory is loaded (not, for example, when telemetry pings occur) and
   // any delays in that case aren't so bad.
-  NS_IMETHOD CollectReports(nsIMemoryReporterCallback *aCb,
+  NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *aCb,
                             nsISupports *aClosure)
   {
     nsresult rv;
     size_t totalConnSize = 0;
     {
       nsTArray<nsRefPtr<Connection> > connections;
       mService->getConnections(connections);
 
@@ -159,17 +159,18 @@ public:
                        aClosure);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
 private:
   /**
-   * Passes a single SQLite memory statistic to a memory reporter callback.
+   * Passes a single SQLite memory statistic to a memory multi-reporter
+   * callback.
    *
    * @param aCallback
    *        The callback.
    * @param aClosure
    *        The closure for the callback.
    * @param aConn
    *        The SQLite connection.
    * @param aPathHead
@@ -179,17 +180,17 @@ private:
    *        "schema".
    * @param aDesc
    *        The memory report description.
    * @param aOption
    *        The SQLite constant for getting the measurement.
    * @param aTotal
    *        The accumulator for the measurement.
    */
-  nsresult reportConn(nsIMemoryReporterCallback *aCb,
+  nsresult reportConn(nsIMemoryMultiReporterCallback *aCb,
                       nsISupports *aClosure,
                       sqlite3 *aConn,
                       const nsACString &aPathHead,
                       const nsACString &aKind,
                       const nsACString &aDesc,
                       int aOption,
                       size_t *aTotal)
   {
@@ -210,17 +211,17 @@ private:
     *aTotal += curr;
 
     return NS_OK;
   }
 };
 
 NS_IMPL_ISUPPORTS1(
   StorageSQLiteMultiReporter,
-  nsIMemoryReporter
+  nsIMemoryMultiReporter
 )
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Service
 
 NS_IMPL_ISUPPORTS2(
   Service,
   mozIStorageService,
@@ -302,18 +303,18 @@ Service::Service()
 , mSqliteVFS(nullptr)
 , mRegistrationMutex("Service::mRegistrationMutex")
 , mConnections()
 {
 }
 
 Service::~Service()
 {
-  (void)::NS_UnregisterMemoryReporter(mStorageSQLiteUniReporter);
-  (void)::NS_UnregisterMemoryReporter(mStorageSQLiteMultiReporter);
+  (void)::NS_UnregisterMemoryReporter(mStorageSQLiteReporter);
+  (void)::NS_UnregisterMemoryMultiReporter(mStorageSQLiteMultiReporter);
 
   int rc = sqlite3_vfs_unregister(mSqliteVFS);
   if (rc != SQLITE_OK)
     NS_WARNING("Failed to unregister sqlite vfs wrapper.");
 
   // Shutdown the sqlite3 API.  Warn if shutdown did not turn out okay, but
   // there is nothing actionable we can do in that case.
   rc = ::sqlite3_shutdown();
@@ -535,20 +536,20 @@ Service::initialize()
   // We need to obtain the toolkit.storage.pageSize preferences on the main
   // thread because the preference service can only be accessed there.  This
   // is cached in the service for all future Open[Unshared]Database calls.
   sDefaultPageSize =
       Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
 
   // Create and register our SQLite memory reporters.  Registration can only
   // happen on the main thread (otherwise you'll get cryptic crashes).
-  mStorageSQLiteUniReporter = new StorageSQLiteUniReporter();
+  mStorageSQLiteReporter = new StorageSQLiteReporter();
   mStorageSQLiteMultiReporter = new StorageSQLiteMultiReporter(this);
-  (void)::NS_RegisterMemoryReporter(mStorageSQLiteUniReporter);
-  (void)::NS_RegisterMemoryReporter(mStorageSQLiteMultiReporter);
+  (void)::NS_RegisterMemoryReporter(mStorageSQLiteReporter);
+  (void)::NS_RegisterMemoryMultiReporter(mStorageSQLiteMultiReporter);
 
   return NS_OK;
 }
 
 int
 Service::localeCompareStrings(const nsAString &aStr1,
                               const nsAString &aStr2,
                               int32_t aComparisonStrength)
--- a/storage/src/mozStorageService.h
+++ b/storage/src/mozStorageService.h
@@ -12,16 +12,17 @@
 #include "nsIFile.h"
 #include "nsIObserver.h"
 #include "nsTArray.h"
 #include "mozilla/Mutex.h"
 
 #include "mozIStorageService.h"
 
 class nsIMemoryReporter;
+class nsIMemoryMultiReporter;
 class nsIXPConnect;
 struct sqlite3_vfs;
 
 namespace mozilla {
 namespace storage {
 
 class Connection;
 class Service : public mozIStorageService
@@ -167,18 +168,18 @@ private:
    *
    * @note Collation implementations are platform-dependent and in general not
    *       thread-safe.  Access to this collation should be synchronized.
    */
   nsCOMPtr<nsICollation> mLocaleCollation;
 
   nsCOMPtr<nsIFile> mProfileStorageFile;
 
-  nsCOMPtr<nsIMemoryReporter> mStorageSQLiteUniReporter;
-  nsCOMPtr<nsIMemoryReporter> mStorageSQLiteMultiReporter;
+  nsCOMPtr<nsIMemoryReporter> mStorageSQLiteReporter;
+  nsCOMPtr<nsIMemoryMultiReporter> mStorageSQLiteMultiReporter;
 
   static Service *gService;
 
   static nsIXPConnect *sXPConnect;
 
   static int32_t sSynchronousPref;
   static int32_t sDefaultPageSize;
 };
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -161,65 +161,82 @@ function onUnload()
                .getService(Ci.nsIObserverService);
     os.removeObserver(gChildMemoryListener, "child-memory-reporter-update");
   }
 }
 
 //---------------------------------------------------------------------------
 
 /**
- * Iterates over each reporter.
+ * Iterates over each reporter and multi-reporter.
  *
- * @param aIgnoreReporter
- *        Function that indicates if we should skip an entire reporter, based
- *        on its name.
- * @param aIgnoreReport
- *        Function that indicates if we should skip a single report from a
- *        reporter, based on its path.
+ * @param aIgnoreSingle
+ *        Function that indicates if we should skip a single reporter, based
+ *        on its path.
+ * @param aIgnoreMulti
+ *        Function that indicates if we should skip a multi-reporter, based on
+ *        its name.
  * @param aHandleReport
- *        The function that's called for each non-skipped report.
+ *        The function that's called for each report.
  */
-function processMemoryReporters(aIgnoreReporter, aIgnoreReport, aHandleReport)
+function processMemoryReporters(aIgnoreSingle, aIgnoreMulti, aHandleReport)
 {
-  let handleReport = function(aProcess, aUnsafePath, aKind, aUnits,
-                              aAmount, aDescription) {
-    if (!aIgnoreReport(aUnsafePath)) {
-      aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
-                    aDescription, /* presence = */ undefined);
+  // Process each memory reporter with aHandleReport.
+  //
+  // - Note that copying rOrig.amount (which calls a C++ function under the
+  //   IDL covers) to r._amount for every reporter now means that the
+  //   results as consistent as possible -- measurements are made all at
+  //   once before most of the memory required to generate this page is
+  //   allocated.
+  //
+  // - After this point we never use the original memory report again.
+
+  let e = gMgr.enumerateReporters();
+  while (e.hasMoreElements()) {
+    let rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    let unsafePath = rOrig.path;
+    if (!aIgnoreSingle(unsafePath)) {
+      aHandleReport(rOrig.process, unsafePath, rOrig.kind, rOrig.units,
+                    rOrig.amount, rOrig.description);
     }
   }
 
-  let e = gMgr.enumerateReporters();
+  let e = gMgr.enumerateMultiReporters();
   while (e.hasMoreElements()) {
-    let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-    if (!aIgnoreReporter(mr.name)) {
+    let mr = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    if (!aIgnoreMulti(mr.name)) {
       // |collectReports| never passes in a |presence| argument.
+      let handleReport = function(aProcess, aUnsafePath, aKind, aUnits,
+                                  aAmount, aDescription) {
+        aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
+                      aDescription, /* presence = */ undefined);
+      }
       mr.collectReports(handleReport, null);
     }
   }
 }
 
 /**
  * Iterates over each report.
  *
  * @param aReports
  *        Array of reports, read from a file or the clipboard.
- * @param aIgnoreReport
- *        Function that indicates if we should skip a single report, based
+ * @param aIgnoreSingle
+ *        Function that indicates if we should skip a single reporter, based
  *        on its path.
  * @param aHandleReport
  *        The function that's called for each report.
  */
-function processMemoryReportsFromFile(aReports, aIgnoreReport, aHandleReport)
+function processMemoryReportsFromFile(aReports, aIgnoreSingle, aHandleReport)
 {
   // Process each memory reporter with aHandleReport.
 
   for (let i = 0; i < aReports.length; i++) {
     let r = aReports[i];
-    if (!aIgnoreReport(r.path)) {
+    if (!aIgnoreSingle(r.path)) {
       aHandleReport(r.process, r.path, r.kind, r.units, r.amount,
                     r.description, r._presence);
     }
   }
 }
 
 //---------------------------------------------------------------------------
 
@@ -569,17 +586,20 @@ function updateAboutMemoryFromReporters(
 {
   // First, clear the contents of main.  Necessary because
   // updateAboutMemoryFromReporters() might be called more than once due to the
   // "child-memory-reporter-update" observer.
   updateMainAndFooter("", SHOW_FOOTER);
 
   try {
     // Process the reports from the memory reporters.
-    appendAboutMemoryMain(processMemoryReporters, gMgr.hasMozMallocUsableSize,
+    let process = function(aIgnoreSingle, aIgnoreMulti, aHandleReport) {
+      processMemoryReporters(aIgnoreSingle, aIgnoreMulti, aHandleReport);
+    }
+    appendAboutMemoryMain(process, gMgr.hasMozMallocUsableSize,
                           /* forceShowSmaps = */ false);
 
   } catch (ex) {
     handleException(ex);
   }
 }
 
 // Increment this if the JSON format changes.
@@ -596,18 +616,18 @@ function updateAboutMemoryFromJSONObject
 {
   try {
     assertInput(aObj.version === gCurrentFileFormatVersion,
                 "data version number missing or doesn't match");
     assertInput(aObj.hasMozMallocUsableSize !== undefined,
                 "missing 'hasMozMallocUsableSize' property");
     assertInput(aObj.reports && aObj.reports instanceof Array,
                 "missing or non-array 'reports' property");
-    let process = function(aIgnoreReporter, aIgnoreReport, aHandleReport) {
-      processMemoryReportsFromFile(aObj.reports, aIgnoreReport, aHandleReport);
+    let process = function(aIgnoreSingle, aIgnoreMulti, aHandleReport) {
+      processMemoryReportsFromFile(aObj.reports, aIgnoreSingle, aHandleReport);
     }
     appendAboutMemoryMain(process, aObj.hasMozMallocUsableSize,
                           /* forceShowSmaps = */ true);
   } catch (ex) {
     handleException(ex);
   }
 }
 
@@ -948,29 +968,29 @@ function PColl()
   this._degenerates = {};
   this._heapTotal = 0;
 }
 
 /**
  * Processes reports (whether from reporters or from a file) and append the
  * main part of the page.
  *
- * @param aProcessReports
+ * @param aProcess
  *        Function that extracts the memory reports from the reporters or from
  *        file.
  * @param aHasMozMallocUsableSize
  *        Boolean indicating if moz_malloc_usable_size works.
  * @param aForceShowSmaps
  *        True if we should show the smaps memory reporters even if we're not
  *        in verbose mode.
  */
-function appendAboutMemoryMain(aProcessReports, aHasMozMallocUsableSize,
+function appendAboutMemoryMain(aProcess, aHasMozMallocUsableSize,
                                aForceShowSmaps)
 {
-  let pcollsByProcess = getPCollsByProcess(aProcessReports, aForceShowSmaps);
+  let pcollsByProcess = getPCollsByProcess(aProcess, aForceShowSmaps);
 
   // Sort the processes.
   let processes = Object.keys(pcollsByProcess);
   processes.sort(function(aProcessA, aProcessB) {
     assert(aProcessA != aProcessB,
            "Elements of Object.keys() should be unique, but " +
            "saw duplicate '" + aProcessA + "' elem.");
 
@@ -1018,62 +1038,64 @@ function appendAboutMemoryMain(aProcessR
                                      aHasMozMallocUsableSize);
   }
 }
 
 /**
  * This function reads all the memory reports, and puts that data in structures
  * that will be used to generate the page.
  *
- * @param aProcessReports
+ * @param aProcessMemoryReports
  *        Function that extracts the memory reports from the reporters or from
  *        file.
  * @param aForceShowSmaps
  *        True if we should show the smaps memory reporters even if we're not
  *        in verbose mode.
  * @return The table of PColls by process.
  */
-function getPCollsByProcess(aProcessReports, aForceShowSmaps)
+function getPCollsByProcess(aProcessMemoryReports, aForceShowSmaps)
 {
   let pcollsByProcess = {};
 
   // This regexp matches sentences and sentence fragments, i.e. strings that
   // start with a capital letter and ends with a '.'.  (The final sentence may
   // be in parentheses, so a ')' might appear after the '.'.)
   const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
 
-  // Ignore the "smaps" reporter in non-verbose mode unless we're reading from
-  // a file or the clipboard, and ignore the "compartments" and "ghost-windows"
-  // reporters all the time.  (Note that reports from these reporters can reach
-  // here via a "content-child" reporter if they were in a child process.)
+  // Ignore the "smaps" multi-reporter in non-verbose mode unless we're reading
+  // from a file or the clipboard, and ignore the "compartments" and
+  // "ghost-windows" multi-reporters all the time.  (Note that reports from
+  // these multi-reporters can reach here as single reports if they were in the
+  // child process.)
   //
-  // Also ignore the "resident-fast" reporter; we use the vanilla "resident"
+  // Also ignore the resident-fast reporter; we use the vanilla resident
   // reporter because it's more important that we get accurate results than
   // that we avoid the (small) possibility of a long pause when loading
-  // about:memory.  Furthermore, the "resident" reporter can purge pages on
-  // MacOS, which affects the results of the "resident-fast" reporter, and we
-  // don't want the measurements shown in about:memory to be affected by the
-  // (arbitrary) order of memory reporter execution.
+  // about:memory.
+  //
+  // We don't show both resident and resident-fast because running the resident
+  // reporter can purge pages on MacOS, which affects the results of the
+  // resident-fast reporter.  We don't want about:memory's results to be
+  // affected by the order of memory reporter execution.
 
-  function ignoreReporter(aName)
-  {
-    return (aName === "smaps" && !gVerbose.checked && !aForceShowSmaps) ||
-           aName === "compartments" ||
-           aName === "ghost-windows" ||
-           aName === "resident-fast";
-  }
-
-  function ignoreReport(aUnsafePath)
+  function ignoreSingle(aUnsafePath)
   {
     return (isSmapsPath(aUnsafePath) && !gVerbose.checked && !aForceShowSmaps) ||
            aUnsafePath.startsWith("compartments/") ||
            aUnsafePath.startsWith("ghost-windows/") ||
            aUnsafePath == "resident-fast";
   }
 
+  function ignoreMulti(aMRName)
+  {
+    return (aMRName === "smaps" && !gVerbose.checked && !aForceShowSmaps) ||
+            aMRName === "compartments" ||
+            aMRName === "ghost-windows";
+  }
+
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aDescription, aPresence)
   {
     if (isExplicitPath(aUnsafePath)) {
       assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP,
                   "bad explicit kind");
       assertInput(aUnits === UNITS_BYTES, "bad explicit units");
       assertInput(gSentenceRegExp.test(aDescription),
@@ -1144,17 +1166,17 @@ function getPCollsByProcess(aProcessRepo
       t._amount = aAmount;
       t._description = aDescription;
       if (aPresence !== undefined) {
         t._presence = aPresence;
       }
     }
   }
 
-  aProcessReports(ignoreReporter, ignoreReport, handleReport);
+  aProcessMemoryReports(ignoreSingle, ignoreMulti, handleReport);
 
   return pcollsByProcess;
 }
 
 //---------------------------------------------------------------------------
 
 // There are two kinds of TreeNode.
 // - Leaf TreeNodes correspond to reports.
@@ -2080,28 +2102,28 @@ function Compartment(aUnsafeName, aIsSys
 Compartment.prototype = {
   merge: function(aR) {
     this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
   }
 };
 
 function getCompartmentsByProcess()
 {
-  // Ignore anything that didn't come from the "compartments" reporter.  (Note
-  // reports from this reporter can reach here via a "content-child" reporter
-  // if they were in a child process.)
+  // Ignore anything that didn't come from the "compartments" multi-reporter.
+  // (Note that some such reports can reach here as single reports if they were
+  // in the child process.)
 
-  function ignoreReporter(aName)
+  function ignoreSingle(aUnsafePath)
   {
-    return !(aName == "compartments" || aName == "content-child");
+    return !aUnsafePath.startsWith("compartments/");
   }
 
-  function ignoreReport(aUnsafePath)
+  function ignoreMulti(aMRName)
   {
-    return !aUnsafePath.startsWith("compartments/");
+    return aMRName !== "compartments";
   }
 
   let compartmentsByProcess = {};
 
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aDescription)
   {
     let process = aProcess === "" ? gUnnamedProcessStr : aProcess;
@@ -2142,17 +2164,17 @@ function getCompartmentsByProcess()
       // Already an entry;  must be a duplicated compartment.  This can happen
       // legitimately.  Merge them.
       cOld.merge(c);
     } else {
       compartments[c._unsafeName] = c;
     }
   }
 
-  processMemoryReporters(ignoreReporter, ignoreReport, handleReport);
+  processMemoryReporters(ignoreSingle, ignoreMulti, handleReport);
 
   return compartmentsByProcess;
 }
 
 function GhostWindow(aUnsafeURL)
 {
   // Call it _unsafeName rather than _unsafeURL for symmetry with the
   // Compartment object.
@@ -2164,24 +2186,24 @@ function GhostWindow(aUnsafeURL)
 GhostWindow.prototype = {
   merge: function(aR) {
     this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
   }
 };
 
 function getGhostWindowsByProcess()
 {
-  function ignoreReporter(aName)
+  function ignoreSingle(aUnsafePath)
   {
-    return !(aName == "ghost-windows" || aName == "content-child");
+    return !aUnsafePath.startsWith('ghost-windows/')
   }
 
-  function ignoreReport(aUnsafePath)
+  function ignoreMulti(aName)
   {
-    return !aUnsafePath.startsWith('ghost-windows/')
+    return aName !== "ghost-windows";
   }
 
   let ghostWindowsByProcess = {};
 
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aDescription)
   {
     let unsafeSplit = aUnsafePath.split('/');
@@ -2203,17 +2225,17 @@ function getGhostWindowsByProcess()
     if (ghostWindowsByProcess[process][unsafeURL]) {
       ghostWindowsByProcess[process][unsafeURL].merge(ghostWindow);
     }
     else {
       ghostWindowsByProcess[process][unsafeURL] = ghostWindow;
     }
   }
 
-  processMemoryReporters(ignoreReporter, ignoreReport, handleReport);
+  processMemoryReporters(ignoreSingle, ignoreMulti, handleReport);
 
   return ghostWindowsByProcess;
 }
 
 //---------------------------------------------------------------------------
 
 function appendProcessAboutCompartmentsElementsHelper(aP, aEntries, aKindString)
 {
--- a/toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
@@ -19,69 +19,79 @@
   <![CDATA[
   "use strict";
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
-  // Remove all the real reporters;  save them to restore at the end.
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
   mgr.blockRegistration();
   var e = mgr.enumerateReporters();
   var realReporters = [];
   while (e.hasMoreElements()) {
     var r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     mgr.unregisterReporter(r);
     realReporters.push(r);
   }
+  e = mgr.enumerateMultiReporters();
+  var realMultiReporters = [];
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
+  }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
   const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
 
+  function f(aProcess, aPath, aKind, aUnits, aAmount) {
+    return {
+      process:     aProcess,
+      path:        aPath,
+      kind:        aKind,
+      units:       aUnits,
+      description: "",
+      amount:      aAmount 
+    };
+  }
+
   var fakeReporters = [
-    { name: "fake1",
-      collectReports: function(aCbObj, aClosure) {
-        function f(aP1, aP2, aK, aU, aA) {
-          aCbObj.callback(aP1, aP2, aK, aU, aA, "Desc.", aClosure);
-        }
-        // These should be ignored.
-        f("", "explicit/a",         HEAP,    BYTES, 222 * MB);
-        f("", "explicit/b/a",       HEAP,    BYTES,  85 * MB);
-        f("", "explicit/b/b",       NONHEAP, BYTES,  85 * MB);
-        f("", "other1",             OTHER,   BYTES, 111 * MB);
-        f("", "other2",             OTHER,   COUNT, 888);
-      }
-    },
-    { name: "fake2",
+    // These should be ignored.
+    f("", "explicit/a",         HEAP,    BYTES, 222 * MB),
+    f("", "explicit/b/a",       HEAP,    BYTES,  85 * MB),
+    f("", "explicit/b/b",       NONHEAP, BYTES,  85 * MB),
+    f("", "other1",             OTHER,   BYTES, 111 * MB),
+    f("", "other2",             OTHER,   COUNT, 888),
+
+    f("2nd", "explicit/c",      HEAP,    BYTES, 333 * MB),
+    f("2nd", "compartments/user/child-user-compartment",     OTHER, COUNT, 1),
+    f("2nd", "compartments/system/child-system-compartment", OTHER, COUNT, 1)
+  ];
+
+  var fakeMultiReporters = [
+    // These shouldn't show up.
+    { name: "fake",
       collectReports: function(aCbObj, aClosure) {
         function f(aP, aK, aU, aA) {
           aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
         }
-        // These shouldn't show up.
         f("explicit/a/d",     HEAP,    BYTES,  13 * MB);
         f("explicit/b/c",     NONHEAP, BYTES,  10 * MB);
-       }
-    },
-    { name: "content-child",
-      collectReports: function(aCbObj, aClosure) {
-        function f(aP1, aP2, aK, aU, aA, aD) {
-          aCbObj.callback(aP1, aP2, aK, aU, aA, aD, aClosure);
-        }
-        f("2nd", "explicit/c",      HEAP,    BYTES, 333 * MB, "Desc.");
-        f("2nd", "compartments/user/child-user-compartment",     OTHER, COUNT, 1, "");
-        f("2nd", "compartments/system/child-system-compartment", OTHER, COUNT, 1, "");
-      }
+       },
+       explicitNonHeap: 10*MB
     },
     { name: "compartments",
       collectReports: function(aCbObj, aClosure) {
         function f(aP) {
           aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
         }
         f("compartments/user/http:\\\\foo.com\\");
         f("compartments/user/https:\\\\bar.com\\bar?baz");
@@ -89,43 +99,49 @@
         // This moz-nullprincipal one is shown under "System Compartments" even
         // though its path indicates it's a user compartment.
         f("compartments/user/moz-nullprincipal:{7ddefdaf-34f1-473f-9b03-50a4568ccb06}");
         // This should show up once with a "[3]" suffix
         f("compartments/system/[System Principal]");
         f("compartments/system/[System Principal]");
         f("compartments/system/[System Principal]");
         f("compartments/system/atoms");
-      }
+      },
+      explicitNonHeap: 0
     },
     { name: "ghost-windows",
       collectReports: function(aCbObj, aClosure) {
         function f(aP) {
           aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
         }
         f("ghost-windows/https:\\\\very-long-url.com\\very-long\\oh-so-long\\really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789");
         f("ghost-windows/http:\\\\foobar.com\\foo?bar#baz");
-      }
+      },
+      explicitNonHeap: 0
     },
     // These shouldn't show up.
     { name: "smaps",
        collectReports: function(aCbObj, aClosure) {
         // The amounts are given in pages, so multiply here by 4kb.
         function f(aP, aA) {
           aCbObj.callback("", aP, NONHEAP, BYTES, aA * 4 * KB, "Desc.", aClosure);
         }
         f("smaps/vsize/a",     24);
         f("smaps/swap/a",       1);
-      }
+      },
+      explicitNonHeap: 0
     }
   ];
 
   for (var i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporterEvenIfBlocked(fakeReporters[i]);
   }
+  for (var i = 0; i < fakeMultiReporters.length; i++) {
+    mgr.registerMultiReporterEvenIfBlocked(fakeMultiReporters[i]);
+  }
   ]]>
   </script>
 
   <!-- vary the capitalization to make sure that works -->
   <iframe id="acFrame"  height="400" src="abouT:compartMENTS"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
@@ -159,24 +175,31 @@ System Compartments\n\
 child-system-compartment\n\
 \n\
 Ghost Windows\n\
 \n\
 ";
 
   function finish()
   {
-    // Unregister fake reporters and re-register the real reporters, just in
-    // case subsequent tests rely on them.
+    // Unregister fake reporters and multi-reporters, re-register the real
+    // reporters and multi-reporters, just in case subsequent tests rely on
+    // them.
     for (var i = 0; i < fakeReporters.length; i++) {
       mgr.unregisterReporter(fakeReporters[i]);
     }
+    for (var i = 0; i < fakeMultiReporters.length; i++) {
+      mgr.unregisterMultiReporter(fakeMultiReporters[i]);
+    }
     for (var i = 0; i < realReporters.length; i++) {
       mgr.registerReporterEvenIfBlocked(realReporters[i]);
     }
+    for (var i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporterEvenIfBlocked(realMultiReporters[i]);
+    }
     mgr.unblockRegistration();
 
     SimpleTest.finish();
   }
 
   // Cut+paste the entire page and check that the cut text matches what we
   // expect.  This tests the output in general and also that the cutting and
   // pasting works as expected.
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
@@ -22,132 +22,157 @@
   SimpleTest.expectAssertions(27);
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   const Cr = Components.results;
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
-  // Remove all the real reporters;  save them to restore at the end.
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
   mgr.blockRegistration();
   let e = mgr.enumerateReporters();
   let realReporters = [];
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     mgr.unregisterReporter(r);
     realReporters.push(r);
   }
+  e = mgr.enumerateMultiReporters();
+  let realMultiReporters = [];
+  while (e.hasMoreElements()) {
+    let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
+  }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
   const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
   const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
   const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
 
+  function f2(aProcess, aPath, aKind, aUnits, aAmount) {
+    return {
+      process:     aProcess,
+      path:        aPath,
+      kind:        aKind,
+      units:       aUnits,
+      description: "Desc.",
+      amount:      aAmount 
+    };
+  }
+
+  function f(aProcess, aPath, aKind, aAmount) {
+    return f2(aProcess, aPath, aKind, BYTES, aAmount);
+  }
+
   let fakeReporters = [
-    { name: "fake0",
-      collectReports: function(aCbObj, aClosure) {
-        function f(aP, aK, aU, aA) {
-          aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
-        }
-        f("heap-allocated",     OTHER,   BYTES, 500 * MB);
-        f("heap-unallocated",   OTHER,   BYTES, 100 * MB);
-        f("explicit/a",         HEAP,    BYTES, 222 * MB);
-        f("explicit/b/a",       HEAP,    BYTES,  85 * MB);
-        f("explicit/b/b",       HEAP,    BYTES,  75 * MB);
-        f("explicit/b/c/a",     HEAP,    BYTES,  70 * MB);
-        f("explicit/b/c/b",     HEAP,    BYTES,   2 * MB); // omitted
-        f("explicit/g/a",       HEAP,    BYTES,   6 * MB);
-        f("explicit/g/b",       HEAP,    BYTES,   5 * MB);
-        f("explicit/g/other",   HEAP,    BYTES,   4 * MB);
-        // A degenerate tree with the same name as a non-degenerate tree should
-        // work ok.
-        f("explicit",           OTHER,   BYTES, 888 * MB);
-        f("other1/a/b",         OTHER,   BYTES, 111 * MB);
-        f("other1/c/d",         OTHER,   BYTES,  22 * MB);
-        f("other1/c/e",         OTHER,   BYTES,  33 * MB);
-        f("other4",             OTHER,   COUNT_CUMULATIVE, 777);
-        f("other4",             OTHER,   COUNT_CUMULATIVE, 111);
-        f("other3/a/b/c/d/e",   OTHER,   PERCENTAGE, 2000);
-        f("other3/a/b/c/d/f",   OTHER,   PERCENTAGE, 10);
-        f("other3/a/b/c/d/g",   OTHER,   PERCENTAGE, 5);
-        f("other3/a/b/c/d/g",   OTHER,   PERCENTAGE, 5);
-        // Check that a rounded-up-to-100.00% value is shown as "100.0%" (i.e. one
-        // decimal point).
-        f("other6/big",         OTHER,   COUNT, 99999);
-        f("other6/small",       OTHER,   COUNT, 1);
-        // Check that a 0 / 0 is handled correctly.
-        f("other7/zero",        OTHER,   BYTES, 0);
-        // These compartments ones shouldn't be displayed.
-        f("compartments/user/foo",   OTHER, COUNT, 1);
-        f("compartments/system/foo", OTHER, COUNT, 1);
-      }
-    },
+    f("", "heap-allocated",     OTHER,   500 * MB),
+    f("", "heap-unallocated",   OTHER,   100 * MB),
+    f("", "explicit/a",         HEAP,    222 * MB),
+    f("", "explicit/b/a",       HEAP,     85 * MB),
+    f("", "explicit/b/b",       HEAP,     75 * MB),
+    f("", "explicit/b/c/a",     HEAP,     70 * MB),
+    f("", "explicit/b/c/b",     HEAP,      2 * MB), // omitted
+    f("", "explicit/g/a",       HEAP,      6 * MB),
+    f("", "explicit/g/b",       HEAP,      5 * MB),
+    f("", "explicit/g/other",   HEAP,      4 * MB),
+    // A degenerate tree with the same name as a non-degenerate tree should
+    // work ok.
+    f("", "explicit",           OTHER,   888 * MB),
+    f("", "other1/a/b",         OTHER,   111 * MB),
+    f("", "other1/c/d",         OTHER,    22 * MB),
+    f("", "other1/c/e",         OTHER,    33 * MB),
+    f2("", "other4",            OTHER,   COUNT_CUMULATIVE, 777),
+    f2("", "other4",            OTHER,   COUNT_CUMULATIVE, 111),
+    f2("", "other3/a/b/c/d/e",  OTHER,   PERCENTAGE, 2000),
+    f2("", "other3/a/b/c/d/f",  OTHER,   PERCENTAGE, 10),
+    f2("", "other3/a/b/c/d/g",  OTHER,   PERCENTAGE, 5),
+    f2("", "other3/a/b/c/d/g",  OTHER,   PERCENTAGE, 5),
+    // Check that a rounded-up-to-100.00% value is shown as "100.0%" (i.e. one
+    // decimal point).
+    f2("", "other6/big",        OTHER,   COUNT, 99999),
+    f2("", "other6/small",      OTHER,   COUNT, 1),
+    // Check that a 0 / 0 is handled correctly.
+    f("", "other7/zero",        OTHER,   0),
+    // These compartments ones shouldn't be displayed.
+    f2("", "compartments/user/foo",   OTHER, COUNT, 1),
+    f2("", "compartments/system/foo", OTHER, COUNT, 1)
+  ];
+  let fakeMultiReporters = [
     { name: "fake1",
       collectReports: function(aCbObj, aClosure) {
         function f(aP, aK, aU, aA) {
           aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
         }
-        f("explicit/c/d",     NONHEAP, BYTES,  13 * MB);
-        f("explicit/c/d",     NONHEAP, BYTES,  10 * MB); // dup
-        f("explicit/c/other", NONHEAP, BYTES,  77 * MB);
+        f("explicit/c/d",     NONHEAP, BYTES,  13 * MB),
+        f("explicit/c/d",     NONHEAP, BYTES,  10 * MB), // dup
+        f("explicit/c/other", NONHEAP, BYTES,  77 * MB),
         f("explicit/cc",      NONHEAP, BYTES,  13 * MB);
         f("explicit/cc",      NONHEAP, BYTES,  10 * MB); // dup
         f("explicit/d",       NONHEAP, BYTES, 499 * KB); // omitted
         f("explicit/e",       NONHEAP, BYTES, 100 * KB); // omitted
         f("explicit/f/g/h/i", HEAP,    BYTES,  10 * MB);
         f("explicit/f/g/h/j", HEAP,    BYTES,  10 * MB);
-      }
+      },
+      explicitNonHeap: (100 + 13 + 10)*MB + (499 + 100)*KB
     },
     { name: "fake2",
       collectReports: function(aCbObj, aClosure) {
         function f(aP, aK, aU, aA) {
           aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
         }
         f("other3",           OTHER,   COUNT, 777);
         f("other2",           OTHER,   BYTES, 222 * MB);
         f("perc2",            OTHER,   PERCENTAGE, 10000);
         f("perc1",            OTHER,   PERCENTAGE, 4567);
-      }
+      },
+      explicitNonHeap: 0
     },
     { name: "smaps",
       collectReports: function(aCbObj, aClosure) {
         // The amounts are given in pages, so multiply here by 4kb.
         function f(aP, aA) {
           aCbObj.callback("", aP, NONHEAP, BYTES, aA * 4 * KB, "Desc.", aClosure);
         }
         f("size/a",      24);
         f("swap/a",       1);
         f("swap/a",       2);
         f("size/a",       19);
         f("swap/b/c",     10);
         f("rss/a",        42);
         f("pss/a",        43);
-      }
+      },
+      explicitNonHeap: 0
     },
     { name: "compartments",
       collectReports: function(aCbObj, aClosure) {
         function f(aP) {
           aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
         }
         f("compartments/user/bar");
         f("compartments/system/bar");
-      }
+      },
+      explicitNonHeap: 0
     }
   ];
   for (let i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporterEvenIfBlocked(fakeReporters[i]);
   }
+  for (let i = 0; i < fakeMultiReporters.length; i++) {
+    mgr.registerMultiReporterEvenIfBlocked(fakeMultiReporters[i]);
+  }
 
   // mgr.explicit sums "heap-allocated" and all the appropriate NONHEAP ones:
   // - "explicit/c", "explicit/cc" x 2, "explicit/d", "explicit/e"
   // - but *not* "explicit/c/d" x 2
   // Check explicit now before we add the fake reporters for the fake 2nd
   // and subsequent processes.
   //
   // Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a
@@ -159,84 +184,69 @@
     is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception");
   }
  
   // The main process always comes first when we display about:memory.  The
   // remaining processes are sorted by their |resident| values (starting with
   // the largest).  Processes without a |resident| memory reporter are saved
   // for the end.
   let fakeReporters2 = [
-    { name: "resident-fast",
-      collectReports: function(aCbObj, aClosure) {
-        // Shouldn't reach here;  aboutMemory.js should skip the reporter.
-        // (Nb: this must come after |mgr.explicit| is accessed, otherwise it
-        // *will* be run by nsMemoryReporterManager::GetExplicit()).
-        ok(false, "'resident-fast' reporter was run");
-      }
-    },
-    { name: "fake3",
-      collectReports: function(aCbObj, aClosure) {
-        function f(aP1, aP2, aK, aU, aA) {
-          aCbObj.callback(aP1, aP2, aK, aU, aA, "Desc.", aClosure);
-        }
-        f("2nd", "heap-allocated",  OTHER,   BYTES,1000* MB);
-        f("2nd", "heap-unallocated",OTHER,   BYTES,100 * MB);
-        f("2nd", "explicit/a/b/c",  HEAP,    BYTES,497 * MB);
-        f("2nd", "explicit/a/b/c",  HEAP,    BYTES,  1 * MB); // dup: merge
-        f("2nd", "explicit/a/b/c",  HEAP,    BYTES,  1 * MB); // dup: merge
-        f("2nd", "explicit/flip\\the\\backslashes",
-                                    HEAP,    BYTES,200 * MB);
-        f("2nd", "explicit/compartment(compartment-url)",
-                                    HEAP,    BYTES,200 * MB);
-        f("2nd", "other0",          OTHER,   BYTES,666 * MB);
-        f("2nd", "other1",          OTHER,   BYTES,111 * MB);
-        // If the "smaps" reporter is in a child process it'll be passed to
-        // the main process under a different name.  The fact that we skip
-        // the "smaps" reporter in the main process won't cause these
-        // to be skipped;  the report-level skipping will be hit instead.
-        f("2nd", "size/e",          NONHEAP, BYTES,24*4*KB);
-        f("2nd", "size/f",          NONHEAP, BYTES,24*4*KB);
-        f("2nd", "resident-fast",   NONHEAP, BYTES,24*4*KB); // ignored!
+    f("2nd", "heap-allocated",  OTHER,  1000 * MB),
+    f("2nd", "heap-unallocated",OTHER,   100 * MB),
+    f("2nd", "explicit/a/b/c",  HEAP,    497 * MB),
+    f("2nd", "explicit/a/b/c",  HEAP,      1 * MB), // dup: merge
+    f("2nd", "explicit/a/b/c",  HEAP,      1 * MB), // dup: merge
+    f("2nd", "explicit/flip\\the\\backslashes",
+                                HEAP,    200 * MB),
+    f("2nd", "explicit/compartment(compartment-url)",
+                                HEAP,    200 * MB),
+    f("2nd", "other0",          OTHER,   666 * MB),
+    f("2nd", "other1",          OTHER,   111 * MB),
+    // Even though the "smaps" reporter is a multi-reporter, if its in a
+    // child process it'll be passed to the main process as single reports.
+    // The fact that we skip the "smaps" multi-reporter in the main
+    // process won't cause these to be skipped;  the fall-back skipping will
+    // be hit instead.
+    f("2nd", "size/e",          NONHEAP, 24*4*KB),
+    f("2nd", "size/f",          NONHEAP, 24*4*KB),
+
+    // Check that we can handle "heap-allocated" not being present.
+    f("3rd", "explicit/a/b",    HEAP,    333 * MB),
+    f("3rd", "explicit/a/c",    HEAP,    444 * MB),
+    f("3rd", "other1",          OTHER,     1 * MB),
+    f("3rd", "resident",        OTHER,   100 * MB),
 
-        // Check that we can handle "heap-allocated" not being present.
-        f("3rd", "explicit/a/b",    HEAP,    BYTES,333 * MB);
-        f("3rd", "explicit/a/c",    HEAP,    BYTES,444 * MB);
-        f("3rd", "other1",          OTHER,   BYTES,  1 * MB);
-        f("3rd", "resident",        OTHER,   BYTES,100 * MB);
+    // Invalid values (negative, too-big) should be identified.
+    f("4th", "heap-allocated",   OTHER,   100 * MB),
+    f("4th", "resident",         OTHER,   200 * MB),
+    f("4th", "explicit/js/compartment(http:\\\\too-big.com\\)/stuff",
+                                 HEAP,    150 * MB),
+    f("4th", "explicit/ok",      HEAP,      5 * MB),
+    f("4th", "explicit/neg1",    NONHEAP,  -2 * MB),
+    // -111 becomes "-0.00MB" in non-verbose mode, and getting the negative
+    // sign in there correctly is non-trivial.
+    f("4th",  "other1",          OTHER,   -111),
+    f("4th",  "other2",          OTHER,   -222 * MB),
+    f2("4th", "other3",          OTHER,   COUNT, -333),
+    f2("4th", "other4",          OTHER,   COUNT_CUMULATIVE, -444),
+    f2("4th", "other5",          OTHER,   PERCENTAGE, -555),
+    f2("4th", "other6",          OTHER,   PERCENTAGE, 66666),
 
-        // Invalid values (negative, too-big) should be identified.
-        f("4th", "heap-allocated",   OTHER,   BYTES,100 * MB);
-        f("4th", "resident",         OTHER,   BYTES,200 * MB);
-        f("4th", "explicit/js/compartment(http:\\\\too-big.com\\)/stuff",
-                                     HEAP,    BYTES,150 * MB);
-        f("4th", "explicit/ok",      HEAP,    BYTES,  5 * MB);
-        f("4th", "explicit/neg1",    NONHEAP, BYTES, -2 * MB);
-        // -111 becomes "-0.00MB" in non-verbose mode, and getting the negative
-        // sign in there correctly is non-trivial.
-        f("4th",  "other1",          OTHER,   BYTES,-111);
-        f("4th",  "other2",          OTHER,   BYTES,-222 * MB);
-        f("4th",  "other3",          OTHER,   COUNT, -333);
-        f("4th",  "other4",          OTHER,   COUNT_CUMULATIVE, -444);
-        f("4th",  "other5",          OTHER,   PERCENTAGE, -555);
-        f("4th",  "other6",          OTHER,   PERCENTAGE, 66666);
-
-        // If a negative value is within a collapsed sub-tree in non-verbose mode,
-        // we should get the warning at the top and the relevant sub-trees should
-        // be expanded, even in non-verbose mode.
-        f("5th", "heap-allocated",   OTHER,   BYTES,100 * MB);
-        f("5th", "explicit/big",     HEAP,    BYTES, 99 * MB);
-        f("5th", "explicit/a/pos",   HEAP,    BYTES, 40 * KB);
-        f("5th", "explicit/a/neg1",  NONHEAP, BYTES,-20 * KB);
-        f("5th", "explicit/a/neg2",  NONHEAP, BYTES,-10 * KB);
-        f("5th", "explicit/b/c/d/e", NONHEAP, BYTES, 20 * KB);
-        f("5th", "explicit/b/c/d/f", NONHEAP, BYTES,-60 * KB);
-        f("5th", "explicit/b/c/g/h", NONHEAP, BYTES, 10 * KB);
-        f("5th", "explicit/b/c/i/j", NONHEAP, BYTES,  5 * KB);
-      }
-    }
+    // If a negative value is within a collapsed sub-tree in non-verbose mode,
+    // we should get the warning at the top and the relevant sub-trees should
+    // be expanded, even in non-verbose mode.
+    f("5th", "heap-allocated",   OTHER,   100 * MB),
+    f("5th", "explicit/big",     HEAP,     99 * MB),
+    f("5th", "explicit/a/pos",   HEAP,     40 * KB),
+    f("5th", "explicit/a/neg1",  NONHEAP, -20 * KB),
+    f("5th", "explicit/a/neg2",  NONHEAP, -10 * KB),
+    f("5th", "explicit/b/c/d/e", NONHEAP,  20 * KB),
+    f("5th", "explicit/b/c/d/f", NONHEAP, -60 * KB),
+    f("5th", "explicit/b/c/g/h", NONHEAP,  10 * KB),
+    f("5th", "explicit/b/c/i/j", NONHEAP,   5 * KB)
   ];
   for (let i = 0; i < fakeReporters2.length; i++) {
     mgr.registerReporterEvenIfBlocked(fakeReporters2[i]);
   }
   fakeReporters = fakeReporters.concat(fakeReporters2);
   ]]>
   </script>
 
@@ -571,24 +581,31 @@ 104,801,280 B (100.0%) -- explicit\n\
 Other Measurements\n\
 \n\
 104,857,600 B ── heap-allocated\n\
 \n\
 "
 
   function finish()
   {
-    // Unregister fake reporters and re-register the real reporters, just in
-    // case subsequent tests rely on them.
+    // Unregister fake reporters and multi-reporters, re-register the real
+    // reporters and multi-reporters, just in case subsequent tests rely on
+    // them.
     for (let i = 0; i < fakeReporters.length; i++) {
       mgr.unregisterReporter(fakeReporters[i]);
     }
+    for (let i = 0; i < fakeMultiReporters.length; i++) {
+      mgr.unregisterMultiReporter(fakeMultiReporters[i]);
+    }
     for (let i = 0; i < realReporters.length; i++) {
       mgr.registerReporterEvenIfBlocked(realReporters[i]);
     }
+    for (let i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporterEvenIfBlocked(realMultiReporters[i]);
+    }
     mgr.unblockRegistration();
 
     SimpleTest.finish();
   }
 
   // Cut+paste the entire page and check that the cut text matches what we
   // expect.  This tests the output in general and also that the cutting and
   // pasting works as expected.
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory2.xul
@@ -17,104 +17,120 @@
   <![CDATA[
   "use strict";
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
-  // Remove all the real reporters;  save them to restore at the end.
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
   mgr.blockRegistration();
   let e = mgr.enumerateReporters();
   let realReporters = [];
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     mgr.unregisterReporter(r);
     realReporters.push(r);
   }
+  e = mgr.enumerateMultiReporters();
+  let realMultiReporters = [];
+  while (e.hasMoreElements()) {
+    let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
+  }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
   const HEAP  = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
 
-  let hiPath  = "explicit/h/i";
-  let hi2Path = "explicit/h/i2";
-  let jkPath  = "explicit/j/k";
-  let jk2Path = "explicit/j/k2";
+  function f(aPath, aKind, aAmount) {
+    return {
+      process:     "",
+      path:        aPath,
+      kind:        aKind,
+      units:       BYTES,
+      description: "Desc.",
+      amount:      aAmount
+    };
+  }
+
+  let hiReport  = f("explicit/h/i",  HEAP,  10 * MB);
+  let hi2Report = f("explicit/h/i2", HEAP,   9 * MB);
+  let jkReport  = f("explicit/j/k",  HEAP, 0.5 * MB);
+  let jk2Report = f("explicit/j/k2", HEAP, 0.3 * MB);
 
   let fakeReporters = [
-    { name: "fake1",
-      collectReports: function(aCbObj, aClosure) {
-        function f(aP, aK, aA) {
-          aCbObj.callback("", aP, aK, BYTES, aA, "Desc.", aClosure);
-        }
-        f("heap-allocated",     OTHER,   250 * MB);
-        f("explicit/a/b",       HEAP,     50 * MB);
-        f("explicit/a/c/d",     HEAP,     25 * MB);
-        f("explicit/a/c/e",     HEAP,     15 * MB);
-        f("explicit/a/f",       HEAP,     30 * MB);
-        f("explicit/g",         HEAP,    100 * MB);
-        f(hiPath,               HEAP,     10 * MB);
-        f(hi2Path,              HEAP,      9 * MB);
-        f(jkPath,               HEAP,    0.5 * MB);
-        f(jk2Path,              HEAP,    0.3 * MB);
-        f("explicit/a/l/m",     HEAP,    0.1 * MB);
-        f("explicit/a/l/n",     HEAP,    0.1 * MB);
-      }
-    }
+    f("heap-allocated",     OTHER,   250 * MB),
+    f("explicit/a/b",       HEAP,     50 * MB),
+    f("explicit/a/c/d",     HEAP,     25 * MB),
+    f("explicit/a/c/e",     HEAP,     15 * MB),
+    f("explicit/a/f",       HEAP,     30 * MB),
+    f("explicit/g",         HEAP,    100 * MB),
+    hiReport,
+    hi2Report,
+    jkReport,
+    jk2Report,
+    f("explicit/a/l/m",     HEAP,    0.1 * MB),
+    f("explicit/a/l/n",     HEAP,    0.1 * MB),
   ];
 
   for (let i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporterEvenIfBlocked(fakeReporters[i]);
   }
 
   ]]>
   </script>
 
   <iframe id="amFrame"  height="500" src="about:memory"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
   function finish()
   {
-    // Unregister fake reporters and re-register the real reporters, just in
-    // case subsequent tests rely on them.
+    // Unregister fake reporters and multi-reporters, re-register the real
+    // reporters and multi-reporters, just in case subsequent tests rely on
+    // them.
     for (let i = 0; i < fakeReporters.length; i++) {
       mgr.unregisterReporter(fakeReporters[i]);
     }
     for (let i = 0; i < realReporters.length; i++) {
       mgr.registerReporterEvenIfBlocked(realReporters[i]);
     }
+    for (let i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporterEvenIfBlocked(realMultiReporters[i]);
+    }
     mgr.unblockRegistration();
 
     SimpleTest.finish();
   }
 
   // Click on the identified element, then cut+paste the entire page and
   // check that the cut text matches what we expect.
   function test(aId, aSwap, aExpected, aNext) {
     let win = document.getElementById("amFrame").contentWindow;
     if (aId) {
       let node = win.document.getElementById(aId);
 
       // Yuk:  clicking a button is easy;  but for tree entries we need to
       // click on a child of the span identified via |id|.
       if (node.nodeName === "button") {
         if (aSwap) {
-          // We swap hipath/hi2Path and jkPath/jk2Path just before updating, to
-          // test what happens when significant nodes become insignificant and
-          // vice versa.
-          hiPath  = "explicit/j/k";
-          hi2Path = "explicit/j/k2";
-          jkPath  = "explicit/h/i";
-          jk2Path = "explicit/h/i2";
+          // We swap the paths of hiReport/hi2Report and jkReport/jk2Report
+          // just before updating, to test what happens when significant nodes
+          // become insignificant and vice versa.
+          hiReport.path  = "explicit/j/k";
+          hi2Report.path = "explicit/j/k2";
+          jkReport.path  = "explicit/h/i";
+          jk2Report.path = "explicit/h/i2";
         }
         node.click();
       } else {
         node.childNodes[0].click();
       }
     }
 
     SimpleTest.executeSoon(function() {
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
@@ -17,68 +17,84 @@
   <![CDATA[
   "use strict";
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
-  // Remove all the real reporters;  save them to restore at the end.
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
   mgr.blockRegistration();
   let e = mgr.enumerateReporters();
   let realReporters = [];
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     mgr.unregisterReporter(r);
     realReporters.push(r);
   }
+  e = mgr.enumerateMultiReporters();
+  let realMultiReporters = [];
+  while (e.hasMoreElements()) {
+    let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
+  }
 
   // Setup a minimal number of fake reporters.
   const KB = 1024;
   const MB = KB * KB;
   const HEAP  = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER = Ci.nsIMemoryReporter.KIND_OTHER;
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
 
+  function f(aPath, aKind, aAmount, aDesc) {
+    return {
+      process:     "",
+      path:        aPath,
+      kind:        aKind,
+      units:       BYTES,
+      amount:      aAmount,
+      description: aDesc
+    };
+  }
+
   let fakeReporters = [
-    { name: "fake1",
-      collectReports: function(aCbObj, aClosure) {
-        function f(aP, aK, aA, aD) {
-          aCbObj.callback("", aP, aK, BYTES, aA, aD, aClosure);
-        }
-        f("heap-allocated",     OTHER,   250 * MB, "Heap allocated.");
-        f("explicit/a/b",       HEAP,     50 * MB, "A b.");
-        f("other/a",            OTHER,   0.2 * MB, "Other a.");
-        f("other/b",            OTHER,   0.1 * MB, "Other b.");
-      }
-    }
+    f("heap-allocated",     OTHER,   250 * MB, "Heap allocated."),
+    f("explicit/a/b",       HEAP,     50 * MB, "A b."),
+    f("other/a",            OTHER,   0.2 * MB, "Other a."),
+    f("other/b",            OTHER,   0.1 * MB, "Other b."),
   ];
 
   for (let i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporterEvenIfBlocked(fakeReporters[i]);
   }
 
   ]]>
   </script>
 
   <iframe id="amFrame"  height="400" src="about:memory"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
   function finish()
   {
-    // Unregister fake reporters and re-register the real reporters, just in
-    // case subsequent tests rely on them.
+    // Unregister fake reporters and multi-reporters, re-register the real
+    // reporters and multi-reporters, just in case subsequent tests rely on
+    // them.
     for (let i = 0; i < fakeReporters.length; i++) {
       mgr.unregisterReporter(fakeReporters[i]);
     }
     for (let i = 0; i < realReporters.length; i++) {
       mgr.registerReporterEvenIfBlocked(realReporters[i]);
     }
+    for (let i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporterEvenIfBlocked(realMultiReporters[i]);
+    }
     mgr.unblockRegistration();
 
     SimpleTest.finish();
   }
 
   // Load the given file into the frame, then copy+paste the entire frame and
   // check that the cut text matches what we expect.
   function test(aFilename, aFilename2, aExpected, aDumpFirst, aNext) {
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -133,16 +133,21 @@
     is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception");
     haveExplicit = false;
   }
   dummy = mgr.resident;
 
   let e = mgr.enumerateReporters();
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    handleReport(r.process, r.path, r.kind, r.units, r.amount, r.description);
+  }
+  e = mgr.enumerateMultiReporters();
+  while (e.hasMoreElements()) {
+    let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
     r.collectReports(handleReport, null);
 
     // Access |name| to make sure it doesn't crash or assert.
     dummy = r.name;
   }
 
   function checkSpecialReport(aName, aAmounts)
   {
--- a/toolkit/components/aboutmemory/tests/test_sqliteMultiReporter.xul
+++ b/toolkit/components/aboutmemory/tests/test_sqliteMultiReporter.xul
@@ -31,23 +31,23 @@
   file.append("test_sqliteMultiReporter-fake-DB-tmp.sqlite");
 
   // Open and close the DB.
   let storage = Cc["@mozilla.org/storage/service;1"].
                 getService(Ci.mozIStorageService);
   let db = storage.openDatabase(file);
   db.close();
 
-  // Invoke all the reporters.  The SQLite multi-reporter is among
+  // Invoke all the multi-reporters.  The SQLite multi-reporter is among
   // them.  It shouldn't crash.
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
-  e = mgr.enumerateReporters();
+  e = mgr.enumerateMultiReporters();
   while (e.hasMoreElements()) {
-    let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
     r.collectReports(function(){}, null);
   }
 
   // If we haven't crashed, we've passed, but the test harness requires that
   // we explicitly check something.
   ok(true, "didn't crash");
 
   ]]>
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -1903,21 +1903,21 @@ StoreAndNotifyEmbedVisit(VisitData& aPla
     (void)NS_ProxyRelease(mainThread, aCallback, true);
   }
 
   VisitData noReferrer;
   nsCOMPtr<nsIRunnable> event = new NotifyVisitObservers(aPlace, noReferrer);
   (void)NS_DispatchToMainThread(event);
 }
 
-class HistoryLinksHashtableReporter MOZ_FINAL : public MemoryUniReporter
+class HistoryLinksHashtableReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   HistoryLinksHashtableReporter()
-    : MemoryUniReporter("explicit/history-links-hashtable",
+    : MemoryReporterBase("explicit/history-links-hashtable",
                          KIND_HEAP, UNITS_BYTES,
 "Memory used by the hashtable that records changes to the visited state of "
 "links.")
     {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     History* history = History::GetService();
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -358,21 +358,21 @@ TelemetryImpl::SizeOfIncludingThis(mozil
 
   for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
     Histogram *h = *it;
     n += h->SizeOfIncludingThis(aMallocSizeOf);
   }
   return n;
 }
 
-class TelemetryReporter MOZ_FINAL : public MemoryUniReporter
+class TelemetryReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   TelemetryReporter()
-    : MemoryUniReporter("explicit/telemetry", KIND_HEAP, UNITS_BYTES,
+    : MemoryReporterBase("explicit/telemetry", KIND_HEAP, UNITS_BYTES,
                          "Memory used by the telemetry system.")
   {}
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     return TelemetryImpl::SizeOfIncludingThis(MallocSizeOf);
   }
 };
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ b/toolkit/components/telemetry/TelemetryPing.js
@@ -3,17 +3,16 @@
  * 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/. */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 
-Cu.import("resource://gre/modules/debug.js");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 #ifndef MOZ_WIDGET_GONK
 Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
 #endif
 Cu.import("resource://gre/modules/ctypes.jsm");
 Cu.import("resource://gre/modules/ThirdPartyCookieProbe.jsm");
 Cu.import("resource://gre/modules/TelemetryFile.jsm");
@@ -454,50 +453,37 @@ TelemetryPing.prototype = {
       return;
     }
 
     let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS");
     let startTime = new Date();
     let e = mgr.enumerateReporters();
     while (e.hasMoreElements()) {
       let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-      let id = MEM_HISTOGRAMS[mr.name];
+      let id = MEM_HISTOGRAMS[mr.path];
       if (!id) {
         continue;
       }
 
-      // collectReports might throw an exception.  If so, just ignore that
+      // Reading mr.amount might throw an exception.  If so, just ignore that
       // memory reporter; we're not getting useful data out of it.
       try {
-        // Bind handleMemoryReport() so it can be called inside the closure
-        // used as the callback.
-        let boundHandleMemoryReport = this.handleMemoryReport.bind(this);
-
-        // Reporters used for telemetry should be uni-reporters!  we assert if
-        // they make more than one report.
-        let hasReported = false;
-
-        function h(process, path, kind, units, amount, desc) {
-          if (!hasReported) {
-            boundHandleMemoryReport(id, path, units, amount);
-            hasReported = true;
-          } else {
-            NS_ASSERT(false,
-                      "reporter " + mr.name + " has made more than one report");
-          }
-        }
-        mr.collectReports(h, null);
+        this.handleMemoryReport(id, mr.path, mr.units, mr.amount);
       }
       catch (e) {
       }
     }
     histogram.add(new Date() - startTime);
   },
 
-  handleMemoryReport: function(id, path, units, amount) {
+  handleMemoryReport: function handleMemoryReport(id, path, units, amount) {
+    if (amount == -1) {
+      return;
+    }
+
     let val;
     if (units == Ci.nsIMemoryReporter.UNITS_BYTES) {
       val = Math.floor(amount / 1024);
     }
     else if (units == Ci.nsIMemoryReporter.UNITS_PERCENTAGE) {
       // UNITS_PERCENTAGE amounts are 100x greater than their raw value.
       val = Math.floor(amount / 100);
     }
--- a/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierPrefixSet.cpp
@@ -31,22 +31,22 @@ using namespace mozilla;
 static const PRLogModuleInfo *gUrlClassifierPrefixSetLog = nullptr;
 #define LOG(args) PR_LOG(gUrlClassifierPrefixSetLog, PR_LOG_DEBUG, args)
 #define LOG_ENABLED() PR_LOG_TEST(gUrlClassifierPrefixSetLog, 4)
 #else
 #define LOG(args)
 #define LOG_ENABLED() (false)
 #endif
 
-class PrefixSetReporter MOZ_FINAL : public MemoryUniReporter
+class PrefixSetReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   PrefixSetReporter(nsUrlClassifierPrefixSet* aPrefixSet,
                     const nsACString& aName)
-    : MemoryUniReporter(
+    : MemoryReporterBase(
         nsPrintfCString(
           "explicit/storage/prefix-set/%s",
           (!aName.IsEmpty() ? PromiseFlatCString(aName).get() : "?!")
         ).get(),
         KIND_HEAP, UNITS_BYTES,
         "Memory used by the prefix set for a URL classifier.")
     , mPrefixSet(aPrefixSet)
   {}
--- a/xpcom/base/AvailableMemoryTracker.cpp
+++ b/xpcom/base/AvailableMemoryTracker.cpp
@@ -320,70 +320,125 @@ CreateDIBSectionHook(HDC aDC,
 
   if (doCheck) {
     CheckMemAvailable();
   }
 
   return result;
 }
 
-class LowMemoryEventsVirtualReporter MOZ_FINAL : public MemoryUniReporter
+class LowMemoryEventsVirtualReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
+  // The description is "???" because we implement GetDescription().
   LowMemoryEventsVirtualReporter()
-    : MemoryUniReporter("low-memory-events/virtual",
-                         KIND_OTHER, UNITS_COUNT_CUMULATIVE,
-"Number of low-virtual-memory events fired since startup. We fire such an "
-"event if we notice there is less than memory.low_virtual_mem_threshold_mb of "
-"virtual address space available (if zero, this behavior is disabled).  The "
-"process will probably crash if it runs out of virtual address space, so "
-"this event is dire.")
+    : MemoryReporterBase("low-memory-events/virtual",
+                         KIND_OTHER, UNITS_COUNT_CUMULATIVE, "???")
   {}
 
+  NS_IMETHOD GetDescription(nsACString &aDescription)
+  {
+    aDescription.AssignLiteral(
+      "Number of low-virtual-memory events fired since startup. ");
+
+    if (sLowVirtualMemoryThreshold == 0) {
+      aDescription.AppendLiteral(
+        "Tracking low-virtual-memory events is disabled, but you can enable it "
+        "by giving the memory.low_virtual_mem_threshold_mb pref a non-zero "
+        "value.");
+    }
+    else {
+      aDescription.Append(nsPrintfCString(
+        "We fire such an event if we notice there is less than %d MB of virtual "
+        "address space available (controlled by the "
+        "'memory.low_virtual_mem_threshold_mb' pref).  We'll likely crash if "
+        "we run out of virtual address space, so this event is somewhat dire.",
+        sLowVirtualMemoryThreshold));
+    }
+    return NS_OK;
+  }
+
 private:
   int64_t Amount() MOZ_OVERRIDE
   {
     // This memory reporter shouldn't be installed on 64-bit machines, since we
     // force-disable virtual-memory tracking there.
     MOZ_ASSERT(sizeof(void*) == 4);
 
     return sNumLowVirtualMemEvents;
   }
 };
 
-class LowCommitSpaceEventsReporter MOZ_FINAL : public MemoryUniReporter
+class LowCommitSpaceEventsReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
+  // The description is "???" because we implement GetDescription().
   LowCommitSpaceEventsReporter()
-    : MemoryUniReporter("low-commit-space-events",
-                         KIND_OTHER, UNITS_COUNT_CUMULATIVE,
-"Number of low-commit-space events fired since startup. We fire such an "
-"event if we notice there is less than memory.low_commit_space_threshold_mb of "
-"commit space available (if zero, this behavior is disabled).  Windows will "
-"likely kill the process if it runs out of commit space, so this event is "
-"dire.")
+    : MemoryReporterBase("low-commit-space-events",
+                         KIND_OTHER, UNITS_COUNT_CUMULATIVE, "???")
   {}
 
+  NS_IMETHOD GetDescription(nsACString &aDescription)
+  {
+    aDescription.AssignLiteral(
+      "Number of low-commit-space events fired since startup. ");
+
+    if (sLowCommitSpaceThreshold == 0) {
+      aDescription.Append(
+        "Tracking low-commit-space events is disabled, but you can enable it "
+        "by giving the memory.low_commit_space_threshold_mb pref a non-zero "
+        "value.");
+    }
+    else {
+      aDescription.Append(nsPrintfCString(
+        "We fire such an event if we notice there is less than %d MB of "
+        "available commit space (controlled by the "
+        "'memory.low_commit_space_threshold_mb' pref).  Windows will likely "
+        "kill us if we run out of commit space, so this event is somewhat dire.",
+        sLowCommitSpaceThreshold));
+    }
+    return NS_OK;
+  }
+
 private:
   int64_t Amount() MOZ_OVERRIDE { return sNumLowCommitSpaceEvents; }
 };
 
-class LowMemoryEventsPhysicalReporter MOZ_FINAL : public MemoryUniReporter
+class LowMemoryEventsPhysicalReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
+  // The description is "???" because we implement GetDescription().
   LowMemoryEventsPhysicalReporter()
-    : MemoryUniReporter("low-memory-events/physical",
-                         KIND_OTHER, UNITS_COUNT_CUMULATIVE,
-"Number of low-physical-memory events fired since startup. We fire such an "
-"event if we notice there is less than memory.low_physical_memory_threshold_mb "
-"of physical memory available (if zero, this behavior is disabled).  The "
-"machine will start to page if it runs out of physical memory.  This may "
-"cause it to run slowly, but it shouldn't cause it to crash.")
+    : MemoryReporterBase("low-memory-events/physical",
+                         KIND_OTHER, UNITS_COUNT_CUMULATIVE, "???")
   {}
 
+  NS_IMETHOD GetDescription(nsACString &aDescription)
+  {
+    aDescription.AssignLiteral(
+      "Number of low-physical-memory events fired since startup. ");
+
+    if (sLowPhysicalMemoryThreshold == 0) {
+      aDescription.Append(
+        "Tracking low-physical-memory events is disabled, but you can enable it "
+        "by giving the memory.low_physical_memory_threshold_mb pref a non-zero "
+        "value.");
+    }
+    else {
+      aDescription.Append(nsPrintfCString(
+        "We fire such an event if we notice there is less than %d MB of "
+        "available physical memory (controlled by the "
+        "'memory.low_physical_memory_threshold_mb' pref).  The machine will start "
+        "to page if it runs out of physical memory; this may cause it to run "
+        "slowly, but it shouldn't cause us to crash.",
+        sLowPhysicalMemoryThreshold));
+    }
+    return NS_OK;
+  }
+
 private:
   int64_t Amount() MOZ_OVERRIDE { return sNumLowPhysicalMemEvents; }
 };
 
 #endif // defined(XP_WIN)
 
 /**
  * This runnable is executed in response to a memory-pressure event; we spin
--- a/xpcom/base/MapsMemoryReporter.cpp
+++ b/xpcom/base/MapsMemoryReporter.cpp
@@ -106,79 +106,79 @@ struct CategoriesSeen {
   bool mSeenRss;
   bool mSeenPss;
   bool mSeenSize;
   bool mSeenSwap;
 };
 
 } // anonymous namespace
 
-class MapsReporter MOZ_FINAL : public nsIMemoryReporter
+class MapsReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
 public:
   MapsReporter();
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   NS_IMETHOD GetName(nsACString &aName)
   {
       aName.AssignLiteral("smaps");
       return NS_OK;
   }
 
   NS_IMETHOD
-  CollectReports(nsIMemoryReporterCallback *aCb,
+  CollectReports(nsIMemoryMultiReporterCallback *aCb,
                  nsISupports *aClosure);
 
 private:
   // Search through /proc/self/maps for libxul.so, and set mLibxulDir to the
   // the directory containing libxul.
   nsresult FindLibxul();
 
   nsresult
   ParseMapping(FILE *aFile,
-               nsIMemoryReporterCallback *aCb,
+               nsIMemoryMultiReporterCallback *aCb,
                nsISupports *aClosure,
                CategoriesSeen *aCategoriesSeen);
 
   void
   GetReporterNameAndDescription(const char *aPath,
                                 const char *aPermissions,
                                 nsACString &aName,
                                 nsACString &aDesc);
 
   nsresult
   ParseMapBody(FILE *aFile,
                const nsACString &aName,
                const nsACString &aDescription,
-               nsIMemoryReporterCallback *aCb,
+               nsIMemoryMultiReporterCallback *aCb,
                nsISupports *aClosure,
                CategoriesSeen *aCategoriesSeen);
 
   bool mSearchedForLibxul;
   nsCString mLibxulDir;
   nsTHashtable<nsCStringHashKey> mMozillaLibraries;
 };
 
-NS_IMPL_ISUPPORTS1(MapsReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(MapsReporter, nsIMemoryMultiReporter)
 
 MapsReporter::MapsReporter()
   : mSearchedForLibxul(false)
   , mMozillaLibraries(ArrayLength(mozillaLibraries))
 {
   const uint32_t len = ArrayLength(mozillaLibraries);
   for (uint32_t i = 0; i < len; i++) {
     nsAutoCString str;
     str.Assign(mozillaLibraries[i]);
     mMozillaLibraries.PutEntry(str);
   }
 }
 
 NS_IMETHODIMP
-MapsReporter::CollectReports(nsIMemoryReporterCallback *aCb,
+MapsReporter::CollectReports(nsIMemoryMultiReporterCallback *aCb,
                              nsISupports *aClosure)
 {
   CategoriesSeen categoriesSeen;
 
   FILE *f = fopen("/proc/self/smaps", "r");
   if (!f)
     return NS_ERROR_FAILURE;
 
@@ -253,17 +253,17 @@ MapsReporter::FindLibxul()
 
   fclose(f);
   return mLibxulDir.IsEmpty() ? NS_ERROR_FAILURE : NS_OK;
 }
 
 nsresult
 MapsReporter::ParseMapping(
   FILE *aFile,
-  nsIMemoryReporterCallback *aCb,
+  nsIMemoryMultiReporterCallback *aCb,
   nsISupports *aClosure,
   CategoriesSeen *aCategoriesSeen)
 {
   // We need to use native types in order to get good warnings from fscanf, so
   // let's make sure that the native types have the sizes we expect.
   static_assert(sizeof(long long) == sizeof(int64_t),
                 "size of (long long) is expected to match (int64_t)");
   static_assert(sizeof(int) == sizeof(int32_t),
@@ -456,17 +456,17 @@ MapsReporter::GetReporterNameAndDescript
   aDesc.Append(")");
 }
 
 nsresult
 MapsReporter::ParseMapBody(
   FILE *aFile,
   const nsACString &aName,
   const nsACString &aDescription,
-  nsIMemoryReporterCallback *aCb,
+  nsIMemoryMultiReporterCallback *aCb,
   nsISupports *aClosure,
   CategoriesSeen *aCategoriesSeen)
 {
   static_assert(sizeof(long long) == sizeof(int64_t),
                 "size of (long long) is expected to match (int64_t)");
 
   const int argCount = 2;
 
@@ -515,39 +515,39 @@ MapsReporter::ParseMapBody(
                      nsIMemoryReporter::UNITS_BYTES,
                      int64_t(size) * 1024, // convert from kB to bytes
                      aDescription, aClosure);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-class ResidentUniqueReporter MOZ_FINAL : public MemoryUniReporter
+class ResidentUniqueReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
   ResidentUniqueReporter()
-    : MemoryUniReporter("resident-unique", KIND_OTHER, UNITS_BYTES,
+    : MemoryReporterBase("resident-unique", KIND_OTHER, UNITS_BYTES,
 "Memory mapped by the process that is present in physical memory and not "
 "shared with any other processes.  This is also known as the process's unique "
 "set size (USS).  This is the amount of RAM we'd expect to be freed if we "
 "closed this process.")
   {}
 
 private:
   NS_IMETHOD GetAmount(int64_t *aAmount)
   {
     // You might be tempted to calculate USS by subtracting the "shared" value
     // from the "resident" value in /proc/<pid>/statm.  But at least on Linux,
     // statm's "shared" value actually counts pages backed by files, which has
     // little to do with whether the pages are actually shared.  smaps on the
     // other hand appears to give us the correct information.
     //
-    // We could calculate this data within the smaps reporter, but the overhead
-    // of the smaps reporter is considerable (we don't even run the smaps
-    // reporter in normal about:memory operation).  Hopefully this
+    // We could calculate this data during the smaps multi-reporter, but the
+    // overhead of the smaps reporter is considerable (we don't even run the
+    // smaps reporter in normal about:memory operation).  Hopefully this
     // implementation is fast enough not to matter.
 
     *aAmount = 0;
 
     FILE *f = fopen("/proc/self/smaps", "r");
     NS_ENSURE_STATE(f);
 
     int64_t total = 0;
@@ -563,16 +563,16 @@ private:
 
     fclose(f);
     return NS_OK;
   }
 };
 
 void Init()
 {
-  nsCOMPtr<nsIMemoryReporter> reporter = new MapsReporter();
-  NS_RegisterMemoryReporter(reporter);
+  nsCOMPtr<nsIMemoryMultiReporter> reporter = new MapsReporter();
+  NS_RegisterMemoryMultiReporter(reporter);
 
   NS_RegisterMemoryReporter(new ResidentUniqueReporter());
 }
 
 } // namespace MapsMemoryReporter
 } // namespace mozilla
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -912,17 +912,17 @@ private:
 
     // mVisitedRefCounted and mVisitedGCed are only used for telemetry
     uint32_t mVisitedRefCounted;
     uint32_t mVisitedGCed;
 
     CC_BeforeUnlinkCallback mBeforeUnlinkCB;
     CC_ForgetSkippableCallback mForgetSkippableCB;
 
-    nsCOMPtr<nsIMemoryReporter> mReporter;
+    nsCOMPtr<nsIMemoryMultiReporter> mReporter;
 
     nsPurpleBuffer mPurpleBuf;
 
     uint32_t mUnmergedNeeded;
     uint32_t mMergedInARow;
 
 public:
     void RegisterJSRuntime(CycleCollectedJSRuntime *aJSRuntime);
@@ -2386,32 +2386,32 @@ nsCycleCollector::CollectWhite(nsICycleC
     return count > 0;
 }
 
 
 ////////////////////////
 // Memory reporter
 ////////////////////////
 
-class CycleCollectorReporter MOZ_FINAL : public nsIMemoryReporter
+class CycleCollectorMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
   public:
-    CycleCollectorReporter(nsCycleCollector* aCollector)
+    CycleCollectorMultiReporter(nsCycleCollector* aCollector)
       : mCollector(aCollector)
     {}
 
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD GetName(nsACString& name)
     {
         name.AssignLiteral("cycle-collector");
         return NS_OK;
     }
 
-    NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCb,
+    NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback* aCb,
                               nsISupports* aClosure)
     {
         size_t objectSize, graphNodesSize, graphEdgesSize, whiteNodesSize,
                purpleBufferSize;
         mCollector->SizeOfIncludingThis(MallocSizeOf,
                                         &objectSize, &graphNodesSize,
                                         &graphEdgesSize, &whiteNodesSize,
                                         &purpleBufferSize);
@@ -2453,17 +2453,17 @@ class CycleCollectorReporter MOZ_FINAL :
     }
 
   private:
     NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(MallocSizeOf)
 
     nsCycleCollector* mCollector;
 };
 
-NS_IMPL_ISUPPORTS1(CycleCollectorReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(CycleCollectorMultiReporter, nsIMemoryMultiReporter)
 
 
 ////////////////////////////////////////////////////////////////////////
 // Collector implementation
 ////////////////////////////////////////////////////////////////////////
 
 nsCycleCollector::nsCycleCollector() :
     mCollectionInProgress(false),
@@ -2480,33 +2480,33 @@ nsCycleCollector::nsCycleCollector() :
     mReporter(nullptr),
     mUnmergedNeeded(0),
     mMergedInARow(0)
 {
 }
 
 nsCycleCollector::~nsCycleCollector()
 {
-    NS_UnregisterMemoryReporter(mReporter);
+    NS_UnregisterMemoryMultiReporter(mReporter);
 }
 
 void
 nsCycleCollector::RegisterJSRuntime(CycleCollectedJSRuntime *aJSRuntime)
 {
     if (mJSRuntime)
         Fault("multiple registrations of cycle collector JS runtime", aJSRuntime);
 
     mJSRuntime = aJSRuntime;
 
     // We can't register the reporter in nsCycleCollector() because that runs
     // before the memory reporter manager is initialized.  So we do it here
     // instead.
     static bool registered = false;
     if (!registered) {
-        NS_RegisterMemoryReporter(new CycleCollectorReporter(this));
+        NS_RegisterMemoryMultiReporter(new CycleCollectorMultiReporter(this));
         registered = true;
     }
 }
 
 void
 nsCycleCollector::ForgetJSRuntime()
 {
     if (!mJSRuntime)
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -12,32 +12,65 @@ interface nsICancelableRunnable;
 
 /*
  * Memory reporters measure Firefox's memory usage.  They are mainly 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
+/*
+ * An nsIMemoryReporter reports a single memory measurement as an object.
+ * Use this when it makes sense to gather this measurement without gathering
+ * related measurements at the same time.
+ *
+ * Note that the |amount| field may be implemented as a function, and so
+ * accessing it can trigger significant computation;  the other fields can
+ * be accessed without triggering this computation.  (Compare and contrast
+ * this with nsIMemoryMultiReporter.)  
+ *
+ * aboutMemory.js is the most important consumer of memory reports.  It
+ * places the following constraints on reports.
+ *
+ * - There must be an "explicit" tree.  It represents non-overlapping
+ *   regions of memory that have been explicitly allocated with an
+ *   OS-level allocation (e.g. mmap/VirtualAlloc/vm_allocate) or a
+ *   heap-level allocation (e.g. malloc/calloc/operator new).  Reporters
+ *   in this tree must have kind HEAP or NONHEAP, units BYTES, and a
+ *   description that is a sentence (i.e. starts with a capital letter and
+ *   ends with a period, or similar).
+ *
+ * - The "size", "rss", "pss" and "swap" trees are optional.  They
+ *   represent regions of virtual memory that the process has mapped.
+ *   Reporters in this category must have kind NONHEAP, units BYTES, and a
+ *   non-empty description.
+ *
+ * - The "compartments" and "ghost-windows" trees are optional.  They are
+ *   used by about:compartments.  Reporters in these trees must have kind
+ *   OTHER, units COUNT, an amount of 1, and a description that's an empty
+ *   string.
+ *
+ * - All other reports are unconstrained except that they must have a
+ *   description that is a sentence.
+ */
+[scriptable, uuid(b2c39f65-1799-4b92-a806-ab3cf6af3cfa)]
+interface nsIMemoryReporter : nsISupports
 {
   /*
-   * The arguments to the callback are as follows.
-   *
-   *
-   * |process|  The name of the process containing this reporter.  Each
-   * reporter initially has "" in this field, indicating that it applies to the
-   * current process.  (This is true even for reporters in a child process.)
-   * When a reporter from a child process is copied into the main process, the
-   * copy has its 'process' field set appropriately.
-   *
-   *
-   * |path|  The path that this memory usage should be reported under.  Paths
-   * are '/'-delimited, eg. "a/b/c".
+   * The name of the process containing this reporter.  Each reporter initially
+   * has "" in this field, indicating that it applies to the current process.
+   * (This is true even for reporters in a child process.)  When a reporter
+   * from a child process is copied into the main process, the copy has its
+   * 'process' field set appropriately.
+   */
+  readonly attribute ACString process;
+
+  /*
+   * The path that this memory usage should be reported under.  Paths are
+   * '/'-delimited, eg. "a/b/c".  
    *
    * Each reporter can be viewed as representing a leaf node in a tree.
    * Internal nodes of the tree don't have reporters.  So, for example, the
    * reporters "explicit/a/b", "explicit/a/c", "explicit/d/e", and
    * "explicit/d/f" define this tree:
    *
    *   explicit
    *   |--a
@@ -60,19 +93,21 @@ interface nsIMemoryReporterCallback : ns
    * If you want to include a '/' not as a path separator, e.g. because the
    * path contains a URL, you need to convert each '/' in the URL to a '\'.
    * Consumers of the path will undo this change.  Any other '\' character
    * in a path will also be changed.  This is clumsy but hasn't caused any
    * problems so far.
    *
    * The paths of all reporters form a set of trees.  Trees can be
    * "degenerate", i.e. contain a single entry with no '/'.
-   *
-   *
-   * |kind|  There are three kinds of memory reporters.
+   */
+  readonly attribute AUTF8String path;
+
+  /*
+   * There are three kinds of memory reporters.
    *
    *  - HEAP: reporters measuring memory allocated by the heap allocator,
    *    e.g. by calling malloc, calloc, realloc, memalign, operator new, or
    *    operator new[].  Reporters in this category must have units
    *    UNITS_BYTES.
    *
    *  - NONHEAP: reporters measuring memory which the program explicitly
    *    allocated, but does not live on the heap.  Such memory is commonly
@@ -80,19 +115,29 @@ interface nsIMemoryReporterCallback : ns
    *    mmap, VirtualAlloc, or vm_allocate).  Reporters in this category
    *    must have units UNITS_BYTES.
    *
    *  - OTHER: reporters which don't fit into either of these categories.
    *    They can have any units.
    *
    * The kind only matters for reporters in the "explicit" tree;
    * aboutMemory.js uses it to calculate "heap-unclassified".
-   *
-   *
-   * |units|  The units on the reporter's amount.  One of the following.
+   */
+  const int32_t KIND_NONHEAP = 0;
+  const int32_t KIND_HEAP    = 1;
+  const int32_t KIND_OTHER   = 2;
+
+  /*
+   * The reporter kind.  See KIND_* above.
+   */
+  readonly attribute int32_t kind;
+
+  /*
+   * The amount reported by a memory reporter must have one of the following
+   * units, but you may of course add new units as necessary:
    *
    *  - BYTES: The amount contains a number of bytes.
    *
    *  - COUNT: The amount is an instantaneous count of things currently in
    *    existence.  For instance, the number of tabs currently open would have
    *    units COUNT.
    *
    *  - COUNT_CUMULATIVE: The amount contains the number of times some event
@@ -106,119 +151,122 @@ interface nsIMemoryReporterCallback : ns
    *  - PERCENTAGE: The amount contains a fraction that should be expressed as
    *    a percentage.  NOTE!  The |amount| field should be given a value 100x
    *    the actual percentage;  this number will be divided by 100 when shown.
    *    This allows a fractional percentage to be shown even though |amount| is
    *    an integer.  E.g. if the actual percentage is 12.34%, |amount| should
    *    be 1234.
    *
    *    Values greater than 100% are allowed.
-   *
-   *
-   * |amount|  The numeric value reported by this memory reporter.  Accesses
-   * can fail if something goes wrong when getting the amount.
-   *
-   *
-   * |description|  A human-readable description of this memory usage report.
+   */
+  const int32_t UNITS_BYTES = 0;
+  const int32_t UNITS_COUNT = 1;
+  const int32_t UNITS_COUNT_CUMULATIVE = 2;
+  const int32_t UNITS_PERCENTAGE = 3;
+
+  /*
+   * The units on the reporter's amount.  See UNITS_* above.
    */
+  readonly attribute int32_t units;
+
+  /*
+   * The numeric value reported by this memory reporter.  Accesses can fail if
+   * something goes wrong when getting the amount.
+   */
+  readonly attribute int64_t amount;
+
+  /*
+   * A human-readable description of this memory usage report.
+   */
+  readonly attribute AUTF8String description;
+};
+
+[scriptable, function, uuid(5b15f3fa-ba15-443c-8337-7770f5f0ce5d)]
+interface nsIMemoryMultiReporterCallback : nsISupports
+{
   void callback(in ACString process, in AUTF8String path, in int32_t kind,
                 in int32_t units, in int64_t amount,
                 in AUTF8String description, in nsISupports closure);
 };
 
 /*
- * An nsIMemoryReporter reports one or more memory measurements via a
- * callback function which is called once for each measurement.
- *
- * An nsIMemoryReporter that reports a single measurement is sometimes called a
- * "uni-reporter".  One that reports multiple measurements is sometimes called
- * a "multi-reporter".
- *
- * aboutMemory.js is the most important consumer of memory reports.  It
- * places the following constraints on reports.
+ * An nsIMemoryMultiReporter reports multiple memory measurements via a
+ * callback function which is called once for each measurement.  Use this
+ * when you want to gather multiple measurements in a single operation (eg.
+ * a single traversal of a large data structure).
  *
- * - There must be an "explicit" tree.  It represents non-overlapping
- *   regions of memory that have been explicitly allocated with an
- *   OS-level allocation (e.g. mmap/VirtualAlloc/vm_allocate) or a
- *   heap-level allocation (e.g. malloc/calloc/operator new).  Reporters
- *   in this tree must have kind HEAP or NONHEAP, units BYTES, and a
- *   description that is a sentence (i.e. starts with a capital letter and
- *   ends with a period, or similar).
- *
- * - The "size", "rss", "pss" and "swap" trees are optional.  They
- *   represent regions of virtual memory that the process has mapped.
- *   Reporters in this category must have kind NONHEAP, units BYTES, and a
- *   non-empty description.
- *
- * - The "compartments" and "ghost-windows" trees are optional.  They are
- *   used by about:compartments.  Reporters in these trees must have kind
- *   OTHER, units COUNT, an amount of 1, and a description that's an empty
- *   string.
- *
- * - All other reports are unconstrained except that they must have a
- *   description that is a sentence.
+ * The arguments to the callback deliberately match the fields in
+ * nsIMemoryReporter, but note that seeing any of these arguments requires
+ * calling collectReports which will trigger all relevant computation.
+ * (Compare and contrast this with nsIMemoryReporter, which allows all
+ * fields except |amount| to be accessed without triggering computation.)
  */
-[scriptable, uuid(53248304-124b-43cd-99dc-6e5797b91618)]
-interface nsIMemoryReporter : nsISupports
+[scriptable, uuid(24d61ead-237b-4969-a6bd-73fd8fed1d99)]
+interface nsIMemoryMultiReporter : nsISupports
 {
   /*
-   * The name of the reporter.  Useful when only one reporter needs to be run.
-   * Must be unique;  if reporters share names it's likely the wrong one will
-   * be called in certain circumstances.
+   * The name of the multi-reporter.  Useful when only one multi-reporter
+   * needs to be run.  Must be unique;  if multi-reporters share names it's
+   * likely the wrong one will be called in certain circumstances.
    */
   readonly attribute ACString name;
 
   /*
-   * Run the reporter.
-   */
-  void collectReports(in nsIMemoryReporterCallback callback,
-                      in nsISupports closure);
-
-  /*
-   * Kinds.  See the |kind| comment in nsIMemoryReporterCallback.
+   * Run the multi-reporter.
    */
-  const int32_t KIND_NONHEAP = 0;
-  const int32_t KIND_HEAP    = 1;
-  const int32_t KIND_OTHER   = 2;
-
-  /*
-   * Units.  See the |units| comment in nsIMemoryReporterCallback.
-   */
-  const int32_t UNITS_BYTES = 0;
-  const int32_t UNITS_COUNT = 1;
-  const int32_t UNITS_COUNT_CUMULATIVE = 2;
-  const int32_t UNITS_PERCENTAGE = 3;
+  void collectReports(in nsIMemoryMultiReporterCallback callback,
+                      in nsISupports closure);
 };
 
-[scriptable, builtinclass, uuid(4db7040a-16f9-4249-879b-fe72729c7ef5)]
+[scriptable, builtinclass, uuid(70b0e608-8dbf-4dc7-b88f-f1c745c1b48c)]
 interface nsIMemoryReporterManager : nsISupports
 {
   /*
    * Return an enumerator of nsIMemoryReporters that are currently registered.
    */
   nsISimpleEnumerator enumerateReporters ();
 
   /*
+   * Return an enumerator of nsIMemoryMultiReporters that are currently
+   * registered.
+   */
+  nsISimpleEnumerator enumerateMultiReporters ();
+
+  /*
    * Register the given nsIMemoryReporter.  After a reporter is registered,
    * it will be available via enumerateReporters().  The Manager service
    * will hold a strong reference to the given reporter.
    */
   void registerReporter (in nsIMemoryReporter reporter);
 
   /*
+   * Register the given nsIMemoryMultiReporter.  After a multi-reporter is
+   * registered, it will be available via enumerateMultiReporters().  The
+   * Manager service will hold a strong reference to the given
+   * multi-reporter.
+   */
+  void registerMultiReporter (in nsIMemoryMultiReporter reporter);
+
+  /*
    * Unregister the given memory reporter.
    */
   void unregisterReporter (in nsIMemoryReporter reporter);
 
+  /*
+   * Unregister the given memory multi-reporter.
+   */
+  void unregisterMultiReporter (in nsIMemoryMultiReporter reporter);
+
   /**
    * These functions should only be used for testing purposes.
    */
   void blockRegistration();
   void unblockRegistration();
   void registerReporterEvenIfBlocked(in nsIMemoryReporter aReporter);
+  void registerMultiReporterEvenIfBlocked(in nsIMemoryMultiReporter aReporter);
 
   /*
    * Initialize.
    */
   void init ();
 
   /*
    * Get the resident size (aka. RSS, physical memory used).  This reporter
@@ -227,18 +275,18 @@ interface nsIMemoryReporterManager : nsI
    */
   readonly attribute int64_t resident;
 
   /*
    * Get the total size of explicit memory allocations, both at the OS-level
    * (eg. via mmap, VirtualAlloc) and at the heap level (eg. via malloc,
    * calloc, operator new).  (Nb: it covers all heap allocations, but will
    * miss any OS-level ones not covered by memory reporters.)  This reporter
-   * is special-cased because it's interesting, and is difficult to compute
-   * from JavaScript code.  Accesses can fail.
+   * is special-cased because it's interesting, and is moderately difficult
+   * to compute in JS.  Accesses can fail.
    */
   readonly attribute int64_t explicit;
 
   /*
    * This attribute indicates if moz_malloc_usable_size() works.
    */
   [infallible] readonly attribute boolean hasMozMallocUsableSize;
 
@@ -254,17 +302,20 @@ interface nsIMemoryReporterManager : nsI
 
 #include "nsStringGlue.h"
 
 // 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_RegisterMemoryMultiReporter(nsIMemoryMultiReporter* aReporter);
+
 XPCOM_API(nsresult) NS_UnregisterMemoryReporter(nsIMemoryReporter* aReporter);
+XPCOM_API(nsresult) NS_UnregisterMemoryMultiReporter(nsIMemoryMultiReporter* aReporter);
 
 #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();
 }
@@ -319,83 +370,95 @@ void RunReporters();
   static size_t fn(const void* aPtr)                                          \
   {                                                                           \
       return moz_malloc_size_of(aPtr);                                        \
   }
 
 namespace mozilla {
 
 // The following base class reduces the amount of boilerplate code required for
-// memory uni-reporters.  You just need to provide the following.
-// - The constant values: nameAndPath (which serves as both the reporters name,
-//   and the path in its single report), kind, units, and description.  They
-//   are passed to the MemoryUniReporter constructor.
-// - A (private) Amount() or (public) GetAmount() method.  It can use the
+// memory reporters.  You just need to provide the following.
+// - The constant values: path, kind, units, and description.  They are passed
+//   to the MemoryReporterBase constructor.
+// - A (private) Amount() or (public) GetAmount() method.  It can use the 
 //   MallocSizeOf method if necessary.  (There is also
 //   MallocSizeOfOn{Alloc,Free}, which can be useful.)  Use Amount() if the
 //   reporter is infallible, and GetAmount() otherwise.  (If you fail to
 //   provide one or the other, you'll get assertion failures when the memory
 //   reporter runs.)
 //
 // The class name of subclasses should match the path, minus the "explicit"
 // (if present), and with "Reporter" at the end.  For example:
 // - "explicit/dom/xyzzy"     --> DOMXyzzyReporter
 // - "js-compartments/system" --> JSCompartmentsSystemReporter
 //
-class MemoryUniReporter : public nsIMemoryReporter
+class MemoryReporterBase : public nsIMemoryReporter
 {
 public:
-  MemoryUniReporter(const char* aNameAndPath, int32_t aKind, int32_t aUnits,
-                    const char* aDescription)
-    : mNameAndPath(aNameAndPath)
+  MemoryReporterBase(const char* aPath, int32_t aKind, int32_t aUnits,
+                     const char* aDescription)
+    : mPath(aPath)
     , mKind(aKind)
     , mUnits(aUnits)
     , mDescription(aDescription)
   {}
 
-  virtual ~MemoryUniReporter() {}
+  virtual ~MemoryReporterBase() {}
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
-  NS_IMETHOD GetName(nsACString& aName)
+  NS_IMETHOD GetProcess(nsACString& aProcess)
   {
-    aName.Assign(mNameAndPath);
+    aProcess.Truncate();
     return NS_OK;
   }
 
-  NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCallback,
-                            nsISupports* aClosure)
+  NS_IMETHOD GetPath(nsACString& aPath)
   {
-    int64_t amount;
-    nsresult rv = GetAmount(&amount);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    return aCallback->Callback(EmptyCString(), mNameAndPath, mKind, mUnits,
-                               amount, mDescription, aClosure);
+    aPath.Assign(mPath);
+    return NS_OK;
   }
 
-protected:
+  NS_IMETHOD GetKind(int32_t* aKind)
+  {
+    *aKind = mKind;
+    return NS_OK;
+  }
+
+  NS_IMETHOD GetUnits(int32_t* aUnits)
+  {
+    *aUnits = mUnits;
+    return NS_OK;
+  }
+
   NS_IMETHOD GetAmount(int64_t* aAmount)
   {
     *aAmount = Amount();
     return NS_OK;
   }
 
+  NS_IMETHOD GetDescription(nsACString& aDescription)
+  {
+    aDescription.Assign(mDescription);
+    return NS_OK;
+  }
+
+protected:
   virtual int64_t Amount()
   {
     // We only reach here if neither GetAmount() nor Amount() was overridden.
     MOZ_ASSERT(false);
     return 0;
   }
 
   NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(MallocSizeOf)
   NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_ALLOC_FUN(MallocSizeOfOnAlloc)
   NS_MEMORY_REPORTER_MALLOC_SIZEOF_ON_FREE_FUN(MallocSizeOfOnFree)
 
-  const nsCString mNameAndPath;
+  const nsCString mPath;
   const int32_t   mKind;
   const int32_t   mUnits;
   const nsCString mDescription;
 };
 
 } // namespace mozilla
 
 
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -667,45 +667,42 @@ DumpReport(nsIGZFileWriter *aWriter, boo
   description.ReplaceSubstring("\n", "\\n");     // <newline> --> \n
   DUMP(aWriter, ", \"description\": \"");
   DUMP(aWriter, description);
   DUMP(aWriter, "\"}");
 
   return NS_OK;
 }
 
-class DumpReporterCallback MOZ_FINAL : public nsIMemoryReporterCallback
+class DumpMultiReporterCallback MOZ_FINAL : public nsIMemoryMultiReporterCallback
 {
-public:
-  NS_DECL_ISUPPORTS
-
-  DumpReporterCallback(bool* aIsFirstPtr) : mIsFirstPtr(aIsFirstPtr) {}
+  public:
+    NS_DECL_ISUPPORTS
 
-  NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
-      int32_t aKind, int32_t aUnits, int64_t aAmount,
-      const nsACString &aDescription,
-      nsISupports *aData)
-  {
-    nsCOMPtr<nsIGZFileWriter> writer = do_QueryInterface(aData);
-    NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE);
+      NS_IMETHOD Callback(const nsACString &aProcess, const nsACString &aPath,
+          int32_t aKind, int32_t aUnits, int64_t aAmount,
+          const nsACString &aDescription,
+          nsISupports *aData)
+      {
+        nsCOMPtr<nsIGZFileWriter> writer = do_QueryInterface(aData);
+        NS_ENSURE_TRUE(writer, NS_ERROR_FAILURE);
 
-    // The |isFirst = false| assumes that at least one single reporter is
-    // present and so will have been processed in
-    // DumpProcessMemoryReportsToGZFileWriter() below.
-    bool isFirst = *mIsFirstPtr;
-    *mIsFirstPtr = false;
-    return DumpReport(writer, isFirst, aProcess, aPath, aKind, aUnits, aAmount,
-                      aDescription);
-  }
-
-private:
-  bool* mIsFirstPtr;
+        // The |isFirst = false| assumes that at least one single reporter is
+        // present and so will have been processed in
+        // DumpProcessMemoryReportsToGZFileWriter() below.
+        return DumpReport(writer, /* isFirst = */ false, aProcess, aPath,
+            aKind, aUnits, aAmount, aDescription);
+        return NS_OK;
+      }
 };
 
-NS_IMPL_ISUPPORTS1(DumpReporterCallback, nsIMemoryReporterCallback)
+NS_IMPL_ISUPPORTS1(
+    DumpMultiReporterCallback
+    , nsIMemoryMultiReporterCallback
+    )
 
 } // namespace mozilla
 
 static void
 MakeFilename(const char *aPrefix, const nsAString &aIdentifier,
              const char *aSuffix, nsACString &aResult)
 {
   aResult = nsPrintfCString("%s-%s-%d.%s",
@@ -793,16 +790,18 @@ DMDWrite(void* aState, const char* aFmt,
   vsnprintf(state->mBuf, state->kBufSize, aFmt, ap);
   unused << state->mGZWriter->Write(state->mBuf);
 }
 #endif
 
 static nsresult
 DumpProcessMemoryReportsToGZFileWriter(nsIGZFileWriter *aWriter)
 {
+  nsresult rv;
+
   // Increment this number if the format changes.
   //
   // This is the first write to the file, and it causes |aWriter| to allocate
   // over 200 KiB of memory.
   //
   DUMP(aWriter, "{\n  \"version\": 1,\n");
 
   DUMP(aWriter, "  \"hasMozMallocUsableSize\": ");
@@ -810,25 +809,63 @@ DumpProcessMemoryReportsToGZFileWriter(n
   nsCOMPtr<nsIMemoryReporterManager> mgr =
     do_GetService("@mozilla.org/memory-reporter-manager;1");
   NS_ENSURE_STATE(mgr);
 
   DUMP(aWriter, mgr->GetHasMozMallocUsableSize() ? "true" : "false");
   DUMP(aWriter, ",\n");
   DUMP(aWriter, "  \"reports\": ");
 
-  // Process reporters.
+  // Process single reporters.
   bool isFirst = true;
   bool more;
   nsCOMPtr<nsISimpleEnumerator> e;
   mgr->EnumerateReporters(getter_AddRefs(e));
-  nsRefPtr<DumpReporterCallback> cb = new DumpReporterCallback(&isFirst);
   while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
     nsCOMPtr<nsIMemoryReporter> r;
     e->GetNext(getter_AddRefs(r));
+
+    nsCString process;
+    rv = r->GetProcess(process);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCString path;
+    rv = r->GetPath(path);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    int32_t kind;
+    rv = r->GetKind(&kind);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    int32_t units;
+    rv = r->GetUnits(&units);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    int64_t amount;
+    rv = r->GetAmount(&amount);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCString description;
+    rv = r->GetDescription(description);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = DumpReport(aWriter, isFirst, process, path, kind, units, amount,
+                    description);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    isFirst = false;
+  }
+
+  // Process multi-reporters.
+  nsCOMPtr<nsISimpleEnumerator> e2;
+  mgr->EnumerateMultiReporters(getter_AddRefs(e2));
+  nsRefPtr<DumpMultiReporterCallback> cb = new DumpMultiReporterCallback();
+  while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
+    nsCOMPtr<nsIMemoryMultiReporter> r;
+    e2->GetNext(getter_AddRefs(r));
     r->CollectReports(cb, aWriter);
   }
 
   DUMP(aWriter, "\n  ]\n}\n");
 
   return NS_OK;
 }
 
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -322,21 +322,21 @@ static nsresult GetResident(int64_t* aN)
 }
 
 static nsresult GetResidentFast(int64_t* aN)
 {
     return GetResident(aN);
 }
 
 #define HAVE_PRIVATE_REPORTER
-class PrivateReporter MOZ_FINAL : public MemoryUniReporter
+class PrivateReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     PrivateReporter()
-      : MemoryUniReporter("private", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("private", KIND_OTHER, UNITS_BYTES,
 "Memory that cannot be shared with other processes, including memory that is "
 "committed and marked MEM_PRIVATE, data that is not mapped, and executable "
 "pages that have been written to.")
     {}
 
     NS_IMETHOD GetAmount(int64_t* aAmount)
     {
         PROCESS_MEMORY_COUNTERS_EX pmcex;
@@ -351,54 +351,54 @@ public:
         *aAmount = pmcex.PrivateUsage;
         return NS_OK;
     }
 };
 
 #endif  // XP_<PLATFORM>
 
 #ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS
-class VsizeReporter MOZ_FINAL : public MemoryUniReporter
+class VsizeReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     VsizeReporter()
-      : MemoryUniReporter("vsize", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("vsize", KIND_OTHER, UNITS_BYTES,
 "Memory mapped by the process, including code and data segments, the heap, "
 "thread stacks, memory explicitly mapped by the process via mmap and similar "
 "operations, and memory shared with other processes. This is the vsize figure "
 "as reported by 'top' and 'ps'.  This figure is of limited use on Mac, where "
 "processes share huge amounts of memory with one another.  But even on other "
 "operating systems, 'resident' is a much better measure of the memory "
 "resources used by the process.")
     {}
 
     NS_IMETHOD GetAmount(int64_t* aAmount) { return GetVsize(aAmount); }
 };
 
-class ResidentReporter MOZ_FINAL : public MemoryUniReporter
+class ResidentReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     ResidentReporter()
-      : MemoryUniReporter("resident", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("resident", KIND_OTHER, UNITS_BYTES,
 "Memory mapped by the process that is present in physical memory, also known "
 "as the resident set size (RSS).  This is the best single figure to use when "
 "considering the memory resources used by the process, but it depends both on "
 "other processes being run and details of the OS kernel and so is best used "
 "for comparing the memory usage of a single process at different points in "
 "time.")
     {}
 
     NS_IMETHOD GetAmount(int64_t* aAmount) { return GetResident(aAmount); }
 };
 
-class ResidentFastReporter MOZ_FINAL : public MemoryUniReporter
+class ResidentFastReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     ResidentFastReporter()
-      : MemoryUniReporter("resident-fast", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("resident-fast", KIND_OTHER, UNITS_BYTES,
 "This is the same measurement as 'resident', but it tries to be as fast as "
 "possible at the expense of accuracy.  On most platforms this is identical to "
 "the 'resident' measurement, but on Mac it may over-count.  You should use "
 "'resident-fast' where you care about latency of collection (e.g. in "
 "telemetry).  Otherwise you should use 'resident'.")
     {}
 
     NS_IMETHOD GetAmount(int64_t* aAmount) { return GetResidentFast(aAmount); }
@@ -407,21 +407,21 @@ public:
 
 #ifdef XP_UNIX
 
 #include <sys/time.h>
 #include <sys/resource.h>
 
 #define HAVE_PAGE_FAULT_REPORTERS 1
 
-class PageFaultsSoftReporter MOZ_FINAL : public MemoryUniReporter
+class PageFaultsSoftReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     PageFaultsSoftReporter()
-      : MemoryUniReporter("page-faults-soft", KIND_OTHER,
+      : MemoryReporterBase("page-faults-soft", KIND_OTHER,
                            UNITS_COUNT_CUMULATIVE,
 "The number of soft page faults (also known as 'minor page faults') that "
 "have occurred since the process started.  A soft page fault occurs when the "
 "process tries to access a page which is present in physical memory but is "
 "not mapped into the process's address space.  For instance, a process might "
 "observe soft page faults when it loads a shared library which is already "
 "present in physical memory. A process may experience many thousands of soft "
 "page faults even when the machine has plenty of available physical memory, "
@@ -436,21 +436,21 @@ public:
         if (err != 0) {
             return NS_ERROR_FAILURE;
         }
         *aAmount = usage.ru_minflt;
         return NS_OK;
     }
 };
 
-class PageFaultsHardReporter MOZ_FINAL : public MemoryUniReporter
+class PageFaultsHardReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     PageFaultsHardReporter()
-      : MemoryUniReporter("page-faults-hard", KIND_OTHER,
+      : MemoryReporterBase("page-faults-hard", KIND_OTHER,
                            UNITS_COUNT_CUMULATIVE,
 "The number of hard page faults (also known as 'major page faults') that have "
 "occurred since the process started.  A hard page fault occurs when a process "
 "tries to access a page which is not present in physical memory. The "
 "operating system must access the disk in order to fulfill a hard page fault. "
 "When memory is plentiful, you should see very few hard page faults. But if "
 "the process tries to use more memory than your machine has available, you "
 "may see many thousands of hard page faults. Because accessing the disk is up "
@@ -474,122 +474,122 @@ public:
 /**
  ** memory reporter implementation for jemalloc and OSX malloc,
  ** to obtain info on total memory in use (that we know about,
  ** at least -- on OSX, there are sometimes other zones in use).
  **/
 
 #ifdef HAVE_JEMALLOC_STATS
 
-class HeapAllocatedReporter MOZ_FINAL : public MemoryUniReporter
+class HeapAllocatedReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     HeapAllocatedReporter()
-      : MemoryUniReporter("heap-allocated", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("heap-allocated", KIND_OTHER, UNITS_BYTES,
 "Memory mapped by the heap allocator that is currently allocated to the "
 "application.  This may exceed the amount of memory requested by the "
 "application because the allocator regularly rounds up request sizes. (The "
 "exact amount requested is not recorded.)")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         jemalloc_stats_t stats;
         jemalloc_stats(&stats);
         return (int64_t) stats.allocated;
     }
 };
 
-class HeapOverheadWasteReporter MOZ_FINAL : public MemoryUniReporter
+class HeapOverheadWasteReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     // We mark this and the other heap-overhead reporters as KIND_NONHEAP
     // because KIND_HEAP memory means "counted in heap-allocated", which this
     // is not.
     HeapOverheadWasteReporter()
-      : MemoryUniReporter("explicit/heap-overhead/waste",
+      : MemoryReporterBase("explicit/heap-overhead/waste",
                            KIND_NONHEAP, UNITS_BYTES,
 "Committed bytes which do not correspond to an active allocation and which the "
 "allocator is not intentionally keeping alive (i.e., not 'heap-bookkeeping' or "
 "'heap-page-cache').  Although the allocator will waste some space under any "
 "circumstances, a large value here may indicate that the heap is highly "
 "fragmented, or that allocator is performing poorly for some other reason.")
     {}
 private:
     int64_t Amount()
     {
         jemalloc_stats_t stats;
         jemalloc_stats(&stats);
         return stats.waste;
     }
 };
 
-class HeapOverheadBookkeepingReporter MOZ_FINAL : public MemoryUniReporter
+class HeapOverheadBookkeepingReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     HeapOverheadBookkeepingReporter()
-      : MemoryUniReporter("explicit/heap-overhead/bookkeeping",
+      : MemoryReporterBase("explicit/heap-overhead/bookkeeping",
                            KIND_NONHEAP, UNITS_BYTES,
 "Committed bytes which the heap allocator uses for internal data structures.")
     {}
 private:
     int64_t Amount()
     {
         jemalloc_stats_t stats;
         jemalloc_stats(&stats);
         return stats.bookkeeping;
     }
 };
 
-class HeapOverheadPageCacheReporter MOZ_FINAL : public MemoryUniReporter
+class HeapOverheadPageCacheReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     HeapOverheadPageCacheReporter()
-      : MemoryUniReporter("explicit/heap-overhead/page-cache",
+      : MemoryReporterBase("explicit/heap-overhead/page-cache",
                            KIND_NONHEAP, UNITS_BYTES,
 "Memory which the allocator could return to the operating system, but hasn't. "
 "The allocator keeps this memory around as an optimization, so it doesn't "
 "have to ask the OS the next time it needs to fulfill a request. This value "
 "is typically not larger than a few megabytes.")
     {}
 private:
     int64_t Amount()
     {
         jemalloc_stats_t stats;
         jemalloc_stats(&stats);
         return (int64_t) stats.page_cache;
     }
 };
 
-class HeapCommittedReporter MOZ_FINAL : public MemoryUniReporter
+class HeapCommittedReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     HeapCommittedReporter()
-      : MemoryUniReporter("heap-committed", KIND_OTHER, UNITS_BYTES,
+      : MemoryReporterBase("heap-committed", KIND_OTHER, UNITS_BYTES,
 "Memory mapped by the heap allocator that is committed, i.e. in physical "
 "memory or paged to disk.  This value corresponds to 'heap-allocated' + "
 "'heap-waste' + 'heap-bookkeeping' + 'heap-page-cache', but because "
 "these values are read at different times, the result probably won't match "
 "exactly.")
     {}
 private:
     int64_t Amount()
     {
         jemalloc_stats_t stats;
         jemalloc_stats(&stats);
         return (int64_t) (stats.allocated + stats.waste +
                           stats.bookkeeping + stats.page_cache);
     }
 };
 
-class HeapOverheadRatioReporter MOZ_FINAL : public MemoryUniReporter
+class HeapOverheadRatioReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     HeapOverheadRatioReporter()
-      : MemoryUniReporter("heap-overhead-ratio", KIND_OTHER,
+      : MemoryReporterBase("heap-overhead-ratio", KIND_OTHER,
                            UNITS_PERCENTAGE,
 "Ratio of committed, unused bytes to allocated bytes; i.e., "
 "'heap-overhead' / 'heap-allocated'.  This measures the overhead of "
 "the heap allocator relative to amount of memory allocated.")
     {}
 private:
     int64_t Amount()
     {
@@ -602,47 +602,47 @@ private:
 };
 #endif  // HAVE_JEMALLOC_STATS
 
 // Why is this here?  At first glance, you'd think it could be defined and
 // registered with nsMemoryReporterManager entirely within nsAtomTable.cpp.
 // However, the obvious time to register it is when the table is initialized,
 // and that happens before XPCOM components are initialized, which means the
 // NS_RegisterMemoryReporter call fails.  So instead we do it here.
-class AtomTablesReporter MOZ_FINAL : public MemoryUniReporter
+class AtomTablesReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     AtomTablesReporter()
-      : MemoryUniReporter("explicit/atom-tables", KIND_HEAP, UNITS_BYTES,
+      : MemoryReporterBase("explicit/atom-tables", KIND_HEAP, UNITS_BYTES,
 "Memory used by the dynamic and static atoms tables.")
     {}
 private:
     int64_t Amount() { return NS_SizeOfAtomTablesIncludingThis(MallocSizeOf); }
 };
 
 #ifdef MOZ_DMD
 
 namespace mozilla {
 namespace dmd {
 
-class DMDReporter MOZ_FINAL : public nsIMemoryReporter
+class DMDMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
 public:
-  DMDReporter()
+  DMDMultiReporter()
   {}
 
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD GetName(nsACString& aName)
   {
     aName.Assign("dmd");
     return NS_OK;
   }
 
-  NS_IMETHOD CollectReports(nsIMemoryReporterCallback* aCallback,
+  NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback* aCallback,
                             nsISupports* aClosure)
   {
     dmd::Sizes sizes;
     dmd::SizeOf(&sizes);
 
 #define REPORT(_path, _amount, _desc)                                         \
     do {                                                                      \
       nsresult rv;                                                            \
@@ -672,17 +672,17 @@ public:
            "Memory used by DMD's live block table.");
 
 #undef REPORT
 
     return NS_OK;
   }
 };
 
-NS_IMPL_ISUPPORTS1(DMDReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(DMDMultiReporter, nsIMemoryMultiReporter)
 
 } // namespace dmd
 } // namespace mozilla
 
 #endif  // MOZ_DMD
 
 /**
  ** nsMemoryReporterManager implementation
@@ -720,17 +720,17 @@ nsMemoryReporterManager::Init()
 
 #ifdef HAVE_PRIVATE_REPORTER
     RegisterReporter(new PrivateReporter);
 #endif
 
     RegisterReporter(new AtomTablesReporter);
 
 #ifdef MOZ_DMD
-    RegisterReporter(new mozilla::dmd::DMDReporter);
+    RegisterMultiReporter(new mozilla::dmd::DMDMultiReporter);
 #endif
 
 #if defined(XP_LINUX)
     nsMemoryInfoDumper::Initialize();
 #endif
 
     return NS_OK;
 }
@@ -820,16 +820,33 @@ nsMemoryReporterManager::EnumerateReport
     mozilla::MutexAutoLock autoLock(mMutex);
 
     nsRefPtr<HashtableEnumerator> enumerator =
         new HashtableEnumerator(mReporters);
     enumerator.forget(aResult);
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsMemoryReporterManager::EnumerateMultiReporters(nsISimpleEnumerator** aResult)
+{
+    // Memory multi-reporters are not necessarily threadsafe, so
+    // EnumerateMultiReporters() must be called from the main thread.
+    if (!NS_IsMainThread()) {
+        MOZ_CRASH();
+    }
+
+    mozilla::MutexAutoLock autoLock(mMutex);
+
+    nsRefPtr<HashtableEnumerator> enumerator =
+        new HashtableEnumerator(mMultiReporters);
+    enumerator.forget(aResult);
+    return NS_OK;
+}
+
 static void
 DebugAssertRefcountIsNonZero(nsISupports* aObj)
 {
 #ifdef DEBUG
     // This will probably crash if the object's refcount is 0.
     uint32_t refcnt = NS_ADDREF(aObj);
     MOZ_ASSERT(refcnt >= 2);
     NS_RELEASE(aObj);
@@ -875,31 +892,80 @@ nsMemoryReporterManager::RegisterReporte
 
 NS_IMETHODIMP
 nsMemoryReporterManager::RegisterReporterEvenIfBlocked(
     nsIMemoryReporter* aReporter)
 {
     return RegisterReporterHelper(aReporter, /* force = */ true);
 }
 
+nsresult
+nsMemoryReporterManager::RegisterMultiReporterHelper(
+    nsIMemoryMultiReporter* aReporter, bool aForce)
+{
+    // This method is thread-safe.
+    mozilla::MutexAutoLock autoLock(mMutex);
+
+    if ((mIsRegistrationBlocked && !aForce) ||
+         mMultiReporters.Contains(aReporter)) {
+        return NS_ERROR_FAILURE;
+    }
+
+    {
+        nsCOMPtr<nsIMemoryMultiReporter> kungFuDeathGrip = aReporter;
+        mMultiReporters.PutEntry(aReporter);
+    }
+
+    DebugAssertRefcountIsNonZero(aReporter);
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterMultiReporter(nsIMemoryMultiReporter* aReporter)
+{
+    return RegisterMultiReporterHelper(aReporter, /* force = */ false);
+}
+
+NS_IMETHODIMP
+nsMemoryReporterManager::RegisterMultiReporterEvenIfBlocked(
+    nsIMemoryMultiReporter* aReporter)
+{
+    return RegisterMultiReporterHelper(aReporter, /* force = */ true);
+}
+
 NS_IMETHODIMP
 nsMemoryReporterManager::UnregisterReporter(nsIMemoryReporter* aReporter)
 {
     // This method is thread-safe.
     mozilla::MutexAutoLock autoLock(mMutex);
 
     if (!mReporters.Contains(aReporter)) {
         return NS_ERROR_FAILURE;
     }
 
     mReporters.RemoveEntry(aReporter);
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsMemoryReporterManager::UnregisterMultiReporter(nsIMemoryMultiReporter* aReporter)
+{
+    // This method is thread-safe.
+    mozilla::MutexAutoLock autoLock(mMutex);
+
+    if (!mMultiReporters.Contains(aReporter)) {
+        return NS_ERROR_FAILURE;
+    }
+
+    mMultiReporters.RemoveEntry(aReporter);
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsMemoryReporterManager::BlockRegistration()
 {
     // This method is thread-safe.
     mozilla::MutexAutoLock autoLock(mMutex);
     if (mIsRegistrationBlocked) {
         return NS_ERROR_FAILURE;
     }
     mIsRegistrationBlocked = true;
@@ -925,78 +991,119 @@ nsMemoryReporterManager::GetResident(int
     return ::GetResident(aResident);
 #else
     *aResident = 0;
     return NS_ERROR_NOT_AVAILABLE;
 #endif
 }
 
 // This is just a wrapper for int64_t that implements nsISupports, so it can be
-// passed to nsIMemoryReporter::CollectReports.
+// passed to nsIMemoryMultiReporter::CollectReports.
 class Int64Wrapper MOZ_FINAL : public nsISupports {
 public:
     NS_DECL_ISUPPORTS
     Int64Wrapper() : mValue(0) { }
     int64_t mValue;
 };
 NS_IMPL_ISUPPORTS0(Int64Wrapper)
 
-class ExplicitCallback MOZ_FINAL : public nsIMemoryReporterCallback
+class ExplicitNonHeapCountingCallback MOZ_FINAL : public nsIMemoryMultiReporterCallback
 {
 public:
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
                         int32_t aKind, int32_t aUnits, int64_t aAmount,
                         const nsACString& aDescription,
-                        nsISupports* aWrappedExplicit)
+                        nsISupports* aWrappedExplicitNonHeap)
     {
-        if (aPath.Equals("heap-allocated") ||
-            (aKind == nsIMemoryReporter::KIND_NONHEAP &&
-             PromiseFlatCString(aPath).Find("explicit") == 0))
+        if (aKind == nsIMemoryReporter::KIND_NONHEAP &&
+            PromiseFlatCString(aPath).Find("explicit") == 0 &&
+            aAmount != int64_t(-1))
         {
-            Int64Wrapper* wrappedInt64 =
-                static_cast<Int64Wrapper*>(aWrappedExplicit);
-            wrappedInt64->mValue += aAmount;
+            Int64Wrapper* wrappedPRInt64 =
+                static_cast<Int64Wrapper*>(aWrappedExplicitNonHeap);
+            wrappedPRInt64->mValue += aAmount;
         }
         return NS_OK;
     }
 };
-NS_IMPL_ISUPPORTS1(ExplicitCallback, nsIMemoryReporterCallback)
+NS_IMPL_ISUPPORTS1(
+  ExplicitNonHeapCountingCallback
+, nsIMemoryMultiReporterCallback
+)
 
 NS_IMETHODIMP
 nsMemoryReporterManager::GetExplicit(int64_t* aExplicit)
 {
     NS_ENSURE_ARG_POINTER(aExplicit);
     *aExplicit = 0;
 #ifndef HAVE_JEMALLOC_STATS
     return NS_ERROR_NOT_AVAILABLE;
 #else
     nsresult rv;
     bool more;
 
-    // For each reporter we call CollectReports and filter out the
-    // non-explicit, non-NONHEAP measurements (except for "heap-allocated").
-    // That's lots of wasted work, and we used to have a GetExplicitNonHeap()
-    // method which did this more efficiently, but it ended up being more
-    // trouble than it was worth.
-
-    nsRefPtr<ExplicitCallback> cb = new ExplicitCallback();
-    nsRefPtr<Int64Wrapper> wrappedExplicitSize = new Int64Wrapper();
-
+    // Get "heap-allocated" and all the KIND_NONHEAP measurements from normal
+    // (i.e. non-multi) "explicit" reporters.
+    int64_t heapAllocated = int64_t(-1);
+    int64_t explicitNonHeapNormalSize = 0;
     nsCOMPtr<nsISimpleEnumerator> e;
     EnumerateReporters(getter_AddRefs(e));
     while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsIMemoryReporter> r;
         e->GetNext(getter_AddRefs(r));
-        r->CollectReports(cb, wrappedExplicitSize);
+
+        int32_t kind;
+        rv = r->GetKind(&kind);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        nsCString path;
+        rv = r->GetPath(path);
+        NS_ENSURE_SUCCESS(rv, rv);
+
+        // We're only interested in NONHEAP explicit reporters and
+        // the 'heap-allocated' reporter.
+        if (kind == nsIMemoryReporter::KIND_NONHEAP &&
+            path.Find("explicit") == 0)
+        {
+            // Just skip any NONHEAP reporters that fail, because
+            // "heap-allocated" is the most important one.
+            int64_t amount;
+            rv = r->GetAmount(&amount);
+            if (NS_SUCCEEDED(rv)) {
+                explicitNonHeapNormalSize += amount;
+            }
+        } else if (path.Equals("heap-allocated")) {
+            // If we don't have "heap-allocated", give up, because the result
+            // would be horribly inaccurate.
+            rv = r->GetAmount(&heapAllocated);
+            NS_ENSURE_SUCCESS(rv, rv);
+        }
     }
 
-    *aExplicit = wrappedExplicitSize->mValue;
+    // For each multi-reporter we call CollectReports and filter out the
+    // non-explicit, non-NONHEAP measurements.  That's lots of wasted work,
+    // and we used to have a GetExplicitNonHeap() method which did this more
+    // efficiently, but it ended up being more trouble than it was worth.
 
+    nsRefPtr<ExplicitNonHeapCountingCallback> cb =
+      new ExplicitNonHeapCountingCallback();
+    nsRefPtr<Int64Wrapper> wrappedExplicitNonHeapMultiSize =
+      new Int64Wrapper();
+    nsCOMPtr<nsISimpleEnumerator> e2;
+    EnumerateMultiReporters(getter_AddRefs(e2));
+    while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsIMemoryMultiReporter> r;
+      e2->GetNext(getter_AddRefs(r));
+      r->CollectReports(cb, wrappedExplicitNonHeapMultiSize);
+    }
+    int64_t explicitNonHeapMultiSize = wrappedExplicitNonHeapMultiSize->mValue;
+
+    *aExplicit = heapAllocated + explicitNonHeapNormalSize + explicitNonHeapMultiSize;
     return NS_OK;
 #endif // HAVE_JEMALLOC_STATS
 }
 
 NS_IMETHODIMP
 nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas)
 {
     void* p = malloc(16);
@@ -1091,77 +1198,130 @@ nsMemoryReporterManager::MinimizeMemoryU
   NS_ADDREF(*aResult = runnable);
 
   return NS_DispatchToMainThread(runnable);
 }
 
 // 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)
+NS_IMPL_ISUPPORTS1(MemoryReporterBase, nsIMemoryReporter)
 
 nsresult
 NS_RegisterMemoryReporter(nsIMemoryReporter* aReporter)
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
     if (!mgr) {
         return NS_ERROR_FAILURE;
     }
     return mgr->RegisterReporter(aReporter);
 }
 
 nsresult
+NS_RegisterMemoryMultiReporter(nsIMemoryMultiReporter* aReporter)
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
+    if (!mgr) {
+        return NS_ERROR_FAILURE;
+    }
+    return mgr->RegisterMultiReporter(aReporter);
+}
+
+nsresult
 NS_UnregisterMemoryReporter(nsIMemoryReporter* aReporter)
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
     if (!mgr) {
         return NS_ERROR_FAILURE;
     }
     return mgr->UnregisterReporter(aReporter);
 }
 
+nsresult
+NS_UnregisterMemoryMultiReporter(nsIMemoryMultiReporter* aReporter)
+{
+    nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
+    if (!mgr) {
+        return NS_ERROR_FAILURE;
+    }
+    return mgr->UnregisterMultiReporter(aReporter);
+}
+
 #if defined(MOZ_DMD)
 
 namespace mozilla {
 namespace dmd {
 
-class NullReporterCallback : public nsIMemoryReporterCallback
+class NullMultiReporterCallback : public nsIMemoryMultiReporterCallback
 {
 public:
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath,
                         int32_t aKind, int32_t aUnits, int64_t aAmount,
                         const nsACString& aDescription,
                         nsISupports* aData)
     {
         // Do nothing;  the reporter has already reported to DMD.
         return NS_OK;
     }
 };
 NS_IMPL_ISUPPORTS1(
-  NullReporterCallback
-, nsIMemoryReporterCallback
+  NullMultiReporterCallback
+, nsIMemoryMultiReporterCallback
 )
 
 void
 RunReporters()
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr =
         do_GetService("@mozilla.org/memory-reporter-manager;1");
 
-    nsRefPtr<NullReporterCallback> cb = new NullReporterCallback();
-
-    bool more;
+    // Do vanilla reporters.
     nsCOMPtr<nsISimpleEnumerator> e;
     mgr->EnumerateReporters(getter_AddRefs(e));
+    bool more;
     while (NS_SUCCEEDED(e->HasMoreElements(&more)) && more) {
         nsCOMPtr<nsIMemoryReporter> r;
         e->GetNext(getter_AddRefs(r));
-        r->CollectReports(cb, nullptr);
+
+        int32_t kind;
+        nsresult rv = r->GetKind(&kind);
+        if (NS_FAILED(rv)) {
+            continue;
+        }
+        nsCString path;
+        rv = r->GetPath(path);
+        if (NS_FAILED(rv)) {
+            continue;
+        }
+
+        // We're only interested in HEAP explicit reporters.  (In particular,
+        // some heap blocks are deliberately measured once inside an "explicit"
+        // reporter and once outside, which isn't a problem.  This condition
+        // prevents them being reported as double-counted.  See bug 811018
+        // comment 2.)
+        if (kind == nsIMemoryReporter::KIND_HEAP &&
+            path.Find("explicit") == 0)
+        {
+            // Just getting the amount is enough for the reporter to report to
+            // DMD.
+            int64_t amount;
+            (void)r->GetAmount(&amount);
+        }
+    }
+
+    // Do multi-reporters.
+    nsCOMPtr<nsISimpleEnumerator> e2;
+    mgr->EnumerateMultiReporters(getter_AddRefs(e2));
+    nsRefPtr<NullMultiReporterCallback> cb = new NullMultiReporterCallback();
+    while (NS_SUCCEEDED(e2->HasMoreElements(&more)) && more) {
+      nsCOMPtr<nsIMemoryMultiReporter> r;
+      e2->GetNext(getter_AddRefs(r));
+      r->CollectReports(cb, nullptr);
     }
 }
 
 } // namespace dmd
 } // namespace mozilla
 
 #endif  // defined(MOZ_DMD)
 
--- a/xpcom/base/nsMemoryReporterManager.h
+++ b/xpcom/base/nsMemoryReporterManager.h
@@ -19,17 +19,20 @@ public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTERMANAGER
 
   nsMemoryReporterManager();
   virtual ~nsMemoryReporterManager();
 
 private:
   nsresult RegisterReporterHelper(nsIMemoryReporter *reporter, bool aForce);
+  nsresult RegisterMultiReporterHelper(nsIMemoryMultiReporter *reporter,
+                                       bool aForce);
 
   nsTHashtable<nsISupportsHashKey> mReporters;
+  nsTHashtable<nsISupportsHashKey> mMultiReporters;
   Mutex mMutex;
   bool mIsRegistrationBlocked;
 };
 
 #define NS_MEMORY_REPORTER_MANAGER_CID \
 { 0xfb97e4f5, 0x32dd, 0x497a, \
 { 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 } }
--- a/xpcom/build/nsXPComInit.cpp
+++ b/xpcom/build/nsXPComInit.cpp
@@ -327,21 +327,21 @@ NS_GetTraceRefcnt(nsITraceRefcnt** resul
 
 EXPORT_XPCOM_API(nsresult)
 NS_InitXPCOM(nsIServiceManager* *result,
                              nsIFile* binDirectory)
 {
     return NS_InitXPCOM2(result, binDirectory, nullptr);
 }
 
-class ICUReporter MOZ_FINAL : public MemoryUniReporter
+class ICUReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     ICUReporter()
-      : MemoryUniReporter("explicit/icu", KIND_HEAP, UNITS_BYTES,
+      : MemoryReporterBase("explicit/icu", KIND_HEAP, UNITS_BYTES,
 "Memory used by ICU, a Unicode and globalization support library.")
     {
 #ifdef DEBUG
         // There must be only one instance of this class, due to |sAmount|
         // being static.
         static bool hasRun = false;
         MOZ_ASSERT(!hasRun);
         hasRun = true;
--- a/xpcom/components/nsCategoryManager.cpp
+++ b/xpcom/components/nsCategoryManager.cpp
@@ -396,21 +396,21 @@ CategoryEnumerator::enumfunc_createenume
 
 
 //
 // nsCategoryManager implementations
 //
 
 NS_IMPL_QUERY_INTERFACE1(nsCategoryManager, nsICategoryManager)
 
-class XPCOMCategoryManagerReporter MOZ_FINAL : public MemoryUniReporter
+class XPCOMCategoryManagerReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     XPCOMCategoryManagerReporter()
-      : MemoryUniReporter("explicit/xpcom/category-manager",
+      : MemoryReporterBase("explicit/xpcom/category-manager",
                            KIND_HEAP, UNITS_BYTES,
                            "Memory used for the XPCOM category manager.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         return nsCategoryManager::SizeOfIncludingThis(MallocSizeOf);
     }
--- a/xpcom/components/nsComponentManager.cpp
+++ b/xpcom/components/nsComponentManager.cpp
@@ -275,21 +275,21 @@ CloneAndAppend(nsIFile* aBase, const nsA
     f->AppendNative(append);
     return f.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsComponentManagerImpl
 ////////////////////////////////////////////////////////////////////////////////
 
-class XPCOMComponentManagerReporter MOZ_FINAL : public MemoryUniReporter
+class XPCOMComponentManagerReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     XPCOMComponentManagerReporter()
-      : MemoryUniReporter("explicit/xpcom/component-manager",
+      : MemoryReporterBase("explicit/xpcom/component-manager",
                            KIND_HEAP, UNITS_BYTES,
                            "Memory used for the XPCOM component manager.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         return nsComponentManagerImpl::gComponentManager
              ? nsComponentManagerImpl::gComponentManager->SizeOfIncludingThis(
--- a/xpcom/ds/nsObserverService.cpp
+++ b/xpcom/ds/nsObserverService.cpp
@@ -43,28 +43,28 @@ GetObserverServiceLog()
 }
   #define LOG(x)  PR_LOG(GetObserverServiceLog(), PR_LOG_DEBUG, x)
 #else
   #define LOG(x)
 #endif /* PR_LOGGING */
 
 namespace mozilla {
 
-class ObserverServiceReporter MOZ_FINAL : public nsIMemoryReporter
+class ObserverServiceReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
 public:
     NS_DECL_ISUPPORTS
-    NS_DECL_NSIMEMORYREPORTER
+    NS_DECL_NSIMEMORYMULTIREPORTER
 protected:
     static const size_t kSuspectReferentCount = 1000;
     static PLDHashOperator CountReferents(nsObserverList* aObserverList,
                                           void* aClosure);
 };
 
-NS_IMPL_ISUPPORTS1(ObserverServiceReporter, nsIMemoryReporter)
+NS_IMPL_ISUPPORTS1(ObserverServiceReporter, nsIMemoryMultiReporter)
 
 NS_IMETHODIMP
 ObserverServiceReporter::GetName(nsACString& aName)
 {
     aName.AssignLiteral("observer-service");
     return NS_OK;
 }
 
@@ -123,17 +123,17 @@ ObserverServiceReporter::CountReferents(
         SuspectObserver suspect(aObserverList->GetKey(), total);
         referentCount->suspectObservers.AppendElement(suspect);
     }
 
     return PL_DHASH_NEXT;
 }
 
 NS_IMETHODIMP
-ObserverServiceReporter::CollectReports(nsIMemoryReporterCallback* cb,
+ObserverServiceReporter::CollectReports(nsIMemoryMultiReporterCallback* cb,
                                         nsISupports* aClosure)
 {
     nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
     nsObserverService* service = static_cast<nsObserverService*>(os.get());
     if (!service) {
         return NS_OK;
     }
 
@@ -214,24 +214,24 @@ nsObserverService::~nsObserverService(vo
 {
     Shutdown();
 }
 
 void
 nsObserverService::RegisterReporter()
 {
     mReporter = new ObserverServiceReporter();
-    NS_RegisterMemoryReporter(mReporter);
+    NS_RegisterMemoryMultiReporter(mReporter);
 }
 
 void
 nsObserverService::Shutdown()
 {
     if (mReporter) {
-        NS_UnregisterMemoryReporter(mReporter);
+        NS_UnregisterMemoryMultiReporter(mReporter);
     }
 
     mShuttingDown = true;
 
     mObserverTopicTable.Clear();
 }
 
 nsresult
--- a/xpcom/ds/nsObserverService.h
+++ b/xpcom/ds/nsObserverService.h
@@ -10,17 +10,17 @@
 #include "nsObserverList.h"
 #include "nsTHashtable.h"
 #include "mozilla/Attributes.h"
 
 // {D07F5195-E3D1-11d2-8ACD-00105A1B8860}
 #define NS_OBSERVERSERVICE_CID \
     { 0xd07f5195, 0xe3d1, 0x11d2, { 0x8a, 0xcd, 0x0, 0x10, 0x5a, 0x1b, 0x88, 0x60 } }
 
-class nsIMemoryReporter;
+class nsIMemoryMultiReporter;
 
 namespace mozilla {
 class ObserverServiceReporter;
 } // namespace mozilla
 
 class nsObserverService MOZ_FINAL : public nsIObserverService {
   friend class mozilla::ObserverServiceReporter;
 
@@ -42,14 +42,14 @@ public:
   NS_IMETHOD UnmarkGrayStrongObservers();
 
 private:
   ~nsObserverService(void);
   void RegisterReporter();
 
   bool mShuttingDown;
   nsTHashtable<nsObserverList> mObserverTopicTable;
-  nsCOMPtr<nsIMemoryReporter> mReporter;
+  nsCOMPtr<nsIMemoryMultiReporter> mReporter;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsObserverService, NS_OBSERVERSERVICE_CID)
 
 #endif /* nsObserverService_h___ */
--- a/xpcom/reflect/xptinfo/src/xptiInterfaceInfoManager.cpp
+++ b/xpcom/reflect/xptinfo/src/xptiInterfaceInfoManager.cpp
@@ -35,21 +35,21 @@ XPTInterfaceInfoManager::SizeOfIncluding
     ReentrantMonitorAutoEnter monitor(mWorkingSet.mTableReentrantMonitor);
     // The entries themselves are allocated out of an arena accounted
     // for elsewhere, so don't measure them
     n += mWorkingSet.mIIDTable.SizeOfExcludingThis(NULL, aMallocSizeOf);
     n += mWorkingSet.mNameTable.SizeOfExcludingThis(NULL, aMallocSizeOf);
     return n;
 }
 
-class XPTIWorkingSetReporter MOZ_FINAL : public MemoryUniReporter
+class XPTIWorkingSetReporter MOZ_FINAL : public MemoryReporterBase
 {
 public:
     XPTIWorkingSetReporter()
-      : MemoryUniReporter("explicit/xpti-working-set", KIND_HEAP, UNITS_BYTES,
+      : MemoryReporterBase("explicit/xpti-working-set", KIND_HEAP, UNITS_BYTES,
                            "Memory used by the XPCOM typelib system.")
     {}
 private:
     int64_t Amount() MOZ_OVERRIDE
     {
         size_t n = gInterfaceInfoManager
                  ? gInterfaceInfoManager->SizeOfIncludingThis(MallocSizeOf)
                  : 0;