Bug 633653 - revamp about:memory. r=vlad,sdwilsh,dvander,gavin,ehsan,edwsmith; sr=benjamin.
authorNicholas Nethercote <nnethercote@mozilla.com>
Tue, 03 May 2011 17:12:58 -0700
changeset 69060 1f0635e935d9a56880ea2f9ad4e3afaa1c8437c3
parent 69059 dcdb0421464db15fbeae3f884fffbf2759ddaf96
child 69061 eb15fab87e7e7b2bdfcbf19a8648543be3f892ab
push idunknown
push userunknown
push dateunknown
reviewersvlad, sdwilsh, dvander, gavin, ehsan, edwsmith, benjamin
bugs633653
milestone6.0a1
Bug 633653 - revamp about:memory. r=vlad,sdwilsh,dvander,gavin,ehsan,edwsmith; sr=benjamin.
content/canvas/src/nsCanvasRenderingContext2D.cpp
dom/ipc/ContentChild.cpp
gfx/thebes/gfxASurface.cpp
gfx/thebes/gfxWindowsPlatform.cpp
ipc/glue/SharedMemory.cpp
js/src/assembler/jit/ExecutableAllocator.cpp
js/src/assembler/jit/ExecutableAllocator.h
js/src/assembler/jit/ExecutableAllocatorOS2.cpp
js/src/assembler/jit/ExecutableAllocatorPosix.cpp
js/src/assembler/jit/ExecutableAllocatorSymbian.cpp
js/src/assembler/jit/ExecutableAllocatorWin.cpp
js/src/jscntxt.h
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/jstracer.cpp
js/src/jstracer.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/MethodJIT.cpp
js/src/methodjit/MethodJIT.h
js/src/nanojit/Allocator.cpp
js/src/nanojit/Allocator.h
js/src/nanojit/CodeAlloc.cpp
js/src/nanojit/CodeAlloc.h
js/src/shell/js.cpp
js/src/xpconnect/src/xpcjsruntime.cpp
layout/base/nsPresShell.cpp
modules/libpr0n/src/imgLoader.cpp
storage/src/mozStorageConnection.cpp
storage/src/mozStorageService.cpp
toolkit/components/aboutmemory/Makefile.in
toolkit/components/aboutmemory/content/aboutMemory.css
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/content/aboutMemory.xhtml
toolkit/components/aboutmemory/tests/Makefile.in
toolkit/components/aboutmemory/tests/chrome/Makefile.in
toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
xpcom/base/nsIMemoryReporter.idl
xpcom/base/nsMemoryReporterManager.cpp
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -168,18 +168,19 @@ static PRBool FloatValidate (double f1, 
 static nsIMemoryReporter *gCanvasMemoryReporter = nsnull;
 static PRInt64 gCanvasMemoryUsed = 0;
 
 static PRInt64 GetCanvasMemoryUsed(void *) {
     return gCanvasMemoryUsed;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(CanvasMemory,
-                             "content/canvas/2d_pixel_bytes",
-                             "Total memory used by 2D canvas (width * height * 4)",
+                             "heap-used/content/canvas/2d-pixel-bytes",
+                             "Memory used by 2D canvases. Each canvas "
+                             "requires (width * height * 4) bytes.",
                              GetCanvasMemoryUsed,
                              NULL)
 
 static void
 CopyContext(gfxContext* dest, gfxContext* src)
 {
     dest->Multiply(src->CurrentMatrix());
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -314,17 +314,19 @@ ContentChild::RecvPMemoryReportRequestCo
 
       nsCString path;
       nsCString desc;
       PRInt64 memoryUsed;
       report->GetPath(getter_Copies(path));
       report->GetDescription(getter_Copies(desc));
       report->GetMemoryUsed(&memoryUsed);
 
-      MemoryReport memreport(nsPrintfCString("Content Process - %d - ", getpid()),
+      static const int maxLength = 31;   // big enough; pid is only a few chars
+      MemoryReport memreport(nsPrintfCString(maxLength, "Content (%d)",
+                                             getpid()),
                              path,
                              desc,
                              memoryUsed);
 
       reports.AppendElement(memreport);
 
     }
 
--- a/gfx/thebes/gfxASurface.cpp
+++ b/gfx/thebes/gfxASurface.cpp
@@ -516,54 +516,54 @@ gfxASurface::MovePixels(const nsIntRect&
     ctx->SetSource(this, gfxPoint(srcOrigin.x, srcOrigin.y));
     ctx->Rectangle(gfxRect(dest.x, dest.y, dest.width, dest.height));
     ctx->Fill();
 }
 
 /** Memory reporting **/
 
 static const char *sSurfaceNamesForSurfaceType[] = {
-    "gfx/surface/image",
-    "gfx/surface/pdf",
-    "gfx/surface/ps",
-    "gfx/surface/xlib",
-    "gfx/surface/xcb",
-    "gfx/surface/glitz",
-    "gfx/surface/quartz",
-    "gfx/surface/win32",
-    "gfx/surface/beos",
-    "gfx/surface/directfb",
-    "gfx/surface/svg",
-    "gfx/surface/os2",
-    "gfx/surface/win32printing",
-    "gfx/surface/quartzimage",
-    "gfx/surface/script",
-    "gfx/surface/qpainter",
-    "gfx/surface/recording",
-    "gfx/surface/vg",
-    "gfx/surface/gl",
-    "gfx/surface/drm",
-    "gfx/surface/tee",
-    "gfx/surface/xml",
-    "gfx/surface/skia",
-    "gfx/surface/d2d"
+    "heap-used/gfx/surface/image",
+    "heap-used/gfx/surface/pdf",
+    "heap-used/gfx/surface/ps",
+    "heap-used/gfx/surface/xlib",
+    "heap-used/gfx/surface/xcb",
+    "heap-used/gfx/surface/glitz",
+    "heap-used/gfx/surface/quartz",
+    "heap-used/gfx/surface/win32",
+    "heap-used/gfx/surface/beos",
+    "heap-used/gfx/surface/directfb",
+    "heap-used/gfx/surface/svg",
+    "heap-used/gfx/surface/os2",
+    "heap-used/gfx/surface/win32printing",
+    "heap-used/gfx/surface/quartzimage",
+    "heap-used/gfx/surface/script",
+    "heap-used/gfx/surface/qpainter",
+    "heap-used/gfx/surface/recording",
+    "heap-used/gfx/surface/vg",
+    "heap-used/gfx/surface/gl",
+    "heap-used/gfx/surface/drm",
+    "heap-used/gfx/surface/tee",
+    "heap-used/gfx/surface/xml",
+    "heap-used/gfx/surface/skia",
+    "heap-used/gfx/surface/d2d"
 };
 
 PR_STATIC_ASSERT(NS_ARRAY_LENGTH(sSurfaceNamesForSurfaceType) == gfxASurface::SurfaceTypeMax);
 #ifdef CAIRO_HAS_D2D_SURFACE
 PR_STATIC_ASSERT(PRUint32(CAIRO_SURFACE_TYPE_D2D) == PRUint32(gfxASurface::SurfaceTypeD2D));
 #endif
 PR_STATIC_ASSERT(PRUint32(CAIRO_SURFACE_TYPE_SKIA) == PRUint32(gfxASurface::SurfaceTypeSkia));
 
 static const char *
 SurfaceMemoryReporterPathForType(gfxASurface::gfxSurfaceType aType)
 {
     if (aType < 0 ||
         aType >= gfxASurface::SurfaceTypeMax)
-        return "gfx/surface/unknown";
+        return "heap-used/gfx/surface/unknown";
 
     return sSurfaceNamesForSurfaceType[aType];
 }
 
 /* Surface size memory reporting */
 static nsIMemoryReporter *gSurfaceMemoryReporters[gfxASurface::SurfaceTypeMax] = { 0 };
 static PRInt64 gSurfaceMemoryUsed[gfxASurface::SurfaceTypeMax] = { 0 };
 
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -91,22 +91,22 @@ class D2DCacheReporter :
 {
 public:
     D2DCacheReporter()
     { }
 
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD GetPath(char **memoryPath) {
-        *memoryPath = strdup("gfx/d2d/surfacecache");
+        *memoryPath = strdup("gfx-d2d-surfacecache");
         return NS_OK;
     }
 
     NS_IMETHOD GetDescription(char **desc) {
-        *desc = strdup("Memory used by Direct2D internal surface cache.");
+        *desc = strdup("Memory used by the Direct2D internal surface cache.");
         return NS_OK;
     }
 
     NS_IMETHOD GetMemoryUsed(PRInt64 *memoryUsed) {
         *memoryUsed = cairo_d2d_get_image_surface_cache_usage();
         return NS_OK;
     }
 }; 
@@ -118,17 +118,17 @@ class D2DVRAMReporter :
 {
 public:
     D2DVRAMReporter()
     { }
 
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD GetPath(char **memoryPath) {
-        *memoryPath = strdup("gfx/d2d/surfacevram");
+        *memoryPath = strdup("gfx-d2d-surfacevram");
         return NS_OK;
     }
 
     NS_IMETHOD GetDescription(char **desc) {
         *desc = strdup("Video memory used by D2D surfaces");
         return NS_OK;
     }
 
--- a/ipc/glue/SharedMemory.cpp
+++ b/ipc/glue/SharedMemory.cpp
@@ -47,23 +47,25 @@ namespace mozilla {
 namespace ipc {
 
 static PRInt64 gShmemAllocated;
 static PRInt64 gShmemMapped;
 static PRInt64 GetShmemAllocated(void*) { return gShmemAllocated; }
 static PRInt64 GetShmemMapped(void*) { return gShmemMapped; }
 
 NS_MEMORY_REPORTER_IMPLEMENT(ShmemAllocated,
-                             "shmem/allocated",
-                             "Shmem bytes accessible (not necessarily mapped)",
+                             "shmem-allocated",
+                             "Memory shared with other processes that is "
+                             "accessible (but not necessarily mapped).",
                              GetShmemAllocated,
                              nsnull)
 NS_MEMORY_REPORTER_IMPLEMENT(ShmemMapped,
-                             "shmem/mapped",
-                             "Shmem bytes mapped into address space",
+                             "shmem-mapped",
+                             "Memory shared with other processes that is "
+                             "mapped into the address space.",
                              GetShmemMapped,
                              nsnull)
 
 SharedMemory::SharedMemory()
   : mAllocSize(0)
   , mMappedSize(0)
 {
   // NB: SharedMemory is main-thread-only at the moment, but that may
--- a/js/src/assembler/jit/ExecutableAllocator.cpp
+++ b/js/src/assembler/jit/ExecutableAllocator.cpp
@@ -27,11 +27,27 @@
 
 #if ENABLE_ASSEMBLER
 
 namespace JSC {
 
 size_t ExecutableAllocator::pageSize = 0;
 size_t ExecutableAllocator::largeAllocSize = 0;
 
+ExecutablePool::~ExecutablePool()
+{
+    m_allocator->releasePoolPages(this);
+}
+
+size_t
+ExecutableAllocator::getCodeSize() const
+{
+    size_t n = 0;
+    for (ExecPoolHashSet::Range r = m_pools.all(); !r.empty(); r.popFront()) {
+        ExecutablePool* pool = r.front();
+        n += pool->m_allocation.size;
+    }
+    return n;
+}
+
 }
 
 #endif // HAVE(ASSEMBLER)
--- a/js/src/assembler/jit/ExecutableAllocator.h
+++ b/js/src/assembler/jit/ExecutableAllocator.h
@@ -26,16 +26,17 @@
 #ifndef ExecutableAllocator_h
 #define ExecutableAllocator_h
 
 #include <stddef.h> // for ptrdiff_t
 #include <limits>
 #include "assembler/wtf/Assertions.h"
 
 #include "jsapi.h"
+#include "jshashtable.h"
 #include "jsprvtd.h"
 #include "jsvector.h"
 #include "jslock.h"
 
 #if WTF_CPU_SPARC
 #ifdef linux  // bugzilla 502369
 static void sync_instruction_memory(caddr_t v, u_int len)
 {
@@ -73,30 +74,33 @@ extern  "C" void sync_instruction_memory
 #endif
 
 #if ENABLE_ASSEMBLER
 
 //#define DEBUG_STRESS_JSC_ALLOCATOR
 
 namespace JSC {
 
+  class ExecutableAllocator;
+
   // These are reference-counted. A new one starts with a count of 1. 
   class ExecutablePool {
 
     JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR;
     friend class ExecutableAllocator;
 private:
     struct Allocation {
         char* pages;
         size_t size;
 #if WTF_PLATFORM_SYMBIAN
         RChunk* chunk;
 #endif
     };
 
+    ExecutableAllocator* m_allocator;
     char* m_freePtr;
     char* m_end;
     Allocation m_allocation;
 
     // Reference count for automatic reclamation.
     unsigned m_refCount;
 
 public:
@@ -121,48 +125,39 @@ private:
     // pools have multiple holders, and they have one holder per chunk
     // of generated code, and they only hold 16KB or so of code.
     void addRef()
     {
         JS_ASSERT(m_refCount);
         ++m_refCount;
     }
 
-private:
-    ExecutablePool(Allocation a)
-      : m_freePtr(a.pages), m_end(m_freePtr + a.size), m_allocation(a), m_refCount(1),
-        m_destroy(false), m_gcNumber(0)
+    ExecutablePool(ExecutableAllocator* allocator, Allocation a)
+      : m_allocator(allocator), m_freePtr(a.pages), m_end(m_freePtr + a.size), m_allocation(a),
+        m_refCount(1), m_destroy(false), m_gcNumber(0)
     { }
 
-    ~ExecutablePool()
-    {
-        if (m_allocation.pages)
-            ExecutablePool::systemRelease(m_allocation);
-    }
+    ~ExecutablePool();
 
     void* alloc(size_t n)
     {
         JS_ASSERT(n <= available());
         void *result = m_freePtr;
         m_freePtr += n;
         return result;
     }
     
     size_t available() const { 
         JS_ASSERT(m_end >= m_freePtr);
         return m_end - m_freePtr;
     }
-
-    // On OOM, this will return an Allocation where pages is NULL.
-    static Allocation systemAlloc(size_t n);
-    static void systemRelease(const Allocation& alloc);
 };
 
 class ExecutableAllocator {
-    enum ProtectionSeting { Writable, Executable };
+    enum ProtectionSetting { Writable, Executable };
 
 public:
     ExecutableAllocator()
     {
         if (!pageSize) {
             pageSize = determinePageSize();
             /*
              * On Windows, VirtualAlloc effectively allocates in 64K chunks.
@@ -170,23 +165,24 @@ public:
              * address is always a multiple of 64K, so each allocation uses up
              * 64K of address space.)  So a size less than that would be
              * pointless.  But it turns out that 64KB is a reasonable size for
              * all platforms.  (This assumes 4KB pages.)
              */
             largeAllocSize = pageSize * 16;
         }
 
-        JS_ASSERT(m_smallAllocationPools.empty());
+        JS_ASSERT(m_smallPools.empty());
     }
 
     ~ExecutableAllocator()
     {
-        for (size_t i = 0; i < m_smallAllocationPools.length(); i++)
-            m_smallAllocationPools[i]->release(/* willDestroy = */true);
+        for (size_t i = 0; i < m_smallPools.length(); i++)
+            m_smallPools[i]->release(/* willDestroy = */true);
+        JS_ASSERT(m_pools.empty());     // if this asserts we have a pool leak
     }
 
     // alloc() returns a pointer to some memory, and also (by reference) a
     // pointer to reference-counted pool. The caller owns a reference to the
     // pool; i.e. alloc() increments the count before returning the object.
     void* alloc(size_t n, ExecutablePool** poolp)
     {
         // Round 'n' up to a multiple of word size; if all allocations are of
@@ -204,16 +200,24 @@ public:
 
         // This alloc is infallible because poolForSize() just obtained
         // (found, or created if necessary) a pool that had enough space.
         void *result = (*poolp)->alloc(n);
         JS_ASSERT(result);
         return result;
     }
 
+    void releasePoolPages(ExecutablePool *pool) {
+        JS_ASSERT(pool->m_allocation.pages);
+        systemRelease(pool->m_allocation);
+        m_pools.remove(m_pools.lookup(pool));   // this asserts if |pool| is not in m_pools
+    }
+
+    size_t getCodeSize() const;
+
 private:
     static size_t pageSize;
     static size_t largeAllocSize;
 
     static const size_t OVERSIZE_ALLOCATION = size_t(-1);
 
     static size_t roundUpAllocationSize(size_t request, size_t granularity)
     {
@@ -228,43 +232,57 @@ private:
         
         // Round up to next page boundary
         size_t size = request + (granularity - 1);
         size = size & ~(granularity - 1);
         JS_ASSERT(size >= request);
         return size;
     }
 
+    // On OOM, this will return an Allocation where pages is NULL.
+    static ExecutablePool::Allocation systemAlloc(size_t n);
+    static void systemRelease(const ExecutablePool::Allocation& alloc);
+
     ExecutablePool* createPool(size_t n)
     {
         size_t allocSize = roundUpAllocationSize(n, pageSize);
         if (allocSize == OVERSIZE_ALLOCATION)
             return NULL;
+
+        if (!m_pools.initialized() && !m_pools.init())
+            return NULL;
+
 #ifdef DEBUG_STRESS_JSC_ALLOCATOR
-        ExecutablePool::Allocation a = ExecutablePool::systemAlloc(size_t(4294967291));
+        ExecutablePool::Allocation a = systemAlloc(size_t(4294967291));
 #else
-        ExecutablePool::Allocation a = ExecutablePool::systemAlloc(allocSize);
+        ExecutablePool::Allocation a = systemAlloc(allocSize);
 #endif
         if (!a.pages)
             return NULL;
 
-        return js::OffTheBooks::new_<ExecutablePool>(a);
+        ExecutablePool *pool = js::OffTheBooks::new_<ExecutablePool>(this, a);
+        if (!pool) {
+            systemRelease(a);
+            return NULL;
+        }
+        m_pools.put(pool);
+        return pool;
     }
 
     ExecutablePool* poolForSize(size_t n)
     {
 #ifndef DEBUG_STRESS_JSC_ALLOCATOR
         // Try to fit in an existing small allocator.  Use the pool with the
         // least available space that is big enough (best-fit).  This is the
         // best strategy because (a) it maximizes the chance of the next
         // allocation fitting in a small pool, and (b) it minimizes the
         // potential waste when a small pool is next abandoned.
         ExecutablePool *minPool = NULL;
-        for (size_t i = 0; i < m_smallAllocationPools.length(); i++) {
-            ExecutablePool *pool = m_smallAllocationPools[i];
+        for (size_t i = 0; i < m_smallPools.length(); i++) {
+            ExecutablePool *pool = m_smallPools[i];
             if (n <= pool->available() && (!minPool || pool->available() < minPool->available()))
                 minPool = pool;
         }
         if (minPool) {
             minPool->addRef();
             return minPool;
         }
 #endif
@@ -274,36 +292,36 @@ private:
             return createPool(n);
 
         // Create a new allocator
         ExecutablePool* pool = createPool(largeAllocSize);
         if (!pool)
             return NULL;
   	    // At this point, local |pool| is the owner.
 
-        if (m_smallAllocationPools.length() < maxSmallPools) {
+        if (m_smallPools.length() < maxSmallPools) {
             // We haven't hit the maximum number of live pools;  add the new pool.
-            m_smallAllocationPools.append(pool);
+            m_smallPools.append(pool);
             pool->addRef();
         } else {
             // Find the pool with the least space.
             int iMin = 0;
-            for (size_t i = 1; i < m_smallAllocationPools.length(); i++)
-                if (m_smallAllocationPools[i]->available() <
-                    m_smallAllocationPools[iMin]->available())
+            for (size_t i = 1; i < m_smallPools.length(); i++)
+                if (m_smallPools[i]->available() <
+                    m_smallPools[iMin]->available())
                 {
                     iMin = i;
                 }
 
             // If the new allocator will result in more free space than the small
             // pool with the least space, then we will use it instead
-            ExecutablePool *minPool = m_smallAllocationPools[iMin];
+            ExecutablePool *minPool = m_smallPools[iMin];
             if ((pool->available() - n) > minPool->available()) {
                 minPool->release();
-                m_smallAllocationPools[iMin] = pool;
+                m_smallPools[iMin] = pool;
                 pool->addRef();
             }
         }
 
    	    // Pass ownership to the caller.
         return pool;
     }
 
@@ -406,22 +424,31 @@ public:
     }
 #else
     #error "The cacheFlush support is missing on this platform."
 #endif
 
 private:
 
 #if ENABLE_ASSEMBLER_WX_EXCLUSIVE
-    static void reprotectRegion(void*, size_t, ProtectionSeting);
+    static void reprotectRegion(void*, size_t, ProtectionSetting);
 #endif
 
+    // These are strong references;  they keep pools alive.
     static const size_t maxSmallPools = 4;
-    typedef js::Vector<ExecutablePool *, maxSmallPools, js::SystemAllocPolicy > SmallExecPoolVector;
-    SmallExecPoolVector m_smallAllocationPools;
+    typedef js::Vector<ExecutablePool *, maxSmallPools, js::SystemAllocPolicy> SmallExecPoolVector;
+    SmallExecPoolVector m_smallPools;
+
+    // All live pools are recorded here, just for stats purposes.  These are
+    // weak references;  they don't keep pools alive.  When a pool is destroyed
+    // its reference is removed from m_pools.
+    typedef js::HashSet<ExecutablePool *, js::DefaultHasher<ExecutablePool *>, js::SystemAllocPolicy>
+            ExecPoolHashSet;
+    ExecPoolHashSet m_pools;    // All pools, just for stats purposes.
+
     static size_t determinePageSize();
 };
 
 }
 
 #endif // ENABLE(ASSEMBLER)
 
 #endif // !defined(ExecutableAllocator)
--- a/js/src/assembler/jit/ExecutableAllocatorOS2.cpp
+++ b/js/src/assembler/jit/ExecutableAllocatorOS2.cpp
@@ -33,27 +33,27 @@
 
 namespace JSC {
 
 size_t ExecutableAllocator::determinePageSize()
 {
     return 4096u;
 }
 
-ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
+ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
     void* allocation = NULL;
     if (DosAllocMem(&allocation, n, OBJ_ANY|PAG_COMMIT|PAG_READ|PAG_WRITE) &&
         DosAllocMem(&allocation, n, PAG_COMMIT|PAG_READ|PAG_WRITE))
         CRASH();
     ExecutablePool::Allocation alloc = {reinterpret_cast<char*>(allocation), n};
     return alloc;
 }
 
-void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
+void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
     DosFreeMem(alloc.pages);
 }
 
 #if ENABLE_ASSEMBLER_WX_EXCLUSIVE
 #error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform."
 #endif
 
--- a/js/src/assembler/jit/ExecutableAllocatorPosix.cpp
+++ b/js/src/assembler/jit/ExecutableAllocatorPosix.cpp
@@ -33,33 +33,33 @@
 
 namespace JSC {
 
 size_t ExecutableAllocator::determinePageSize()
 {
     return getpagesize();
 }
 
-ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
+ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
     void* allocation = mmap(NULL, n, INITIAL_PROTECTION_FLAGS, MAP_PRIVATE | MAP_ANON, VM_TAG_FOR_EXECUTABLEALLOCATOR_MEMORY, 0);
     if (allocation == MAP_FAILED)
         allocation = NULL;
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
-void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
+void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 { 
     int result = munmap(alloc.pages, alloc.size);
     ASSERT_UNUSED(result, !result);
 }
 
 #if WTF_ENABLE_ASSEMBLER_WX_EXCLUSIVE
-void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSeting setting)
+void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
 {
     if (!pageSize)
         intializePageSize();
 
     // Calculate the start of the page containing this region,
     // and account for this extra memory within size.
     intptr_t startPtr = reinterpret_cast<intptr_t>(start);
     intptr_t pageStartPtr = startPtr & ~(pageSize - 1);
--- a/js/src/assembler/jit/ExecutableAllocatorSymbian.cpp
+++ b/js/src/assembler/jit/ExecutableAllocatorSymbian.cpp
@@ -42,28 +42,28 @@ size_t ExecutableAllocator::determinePag
     return MOVING_MEM_PAGE_SIZE;
 #else
     TInt page_size;
     UserHal::PageSizeInBytes(page_size);
     return page_size;
 #endif
 }
 
-ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
+ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
     RChunk* codeChunk = new RChunk();
 
     TInt errorCode = codeChunk->CreateLocalCode(n, n);
 
     char* allocation = reinterpret_cast<char*>(codeChunk->Base());
     ExecutablePool::Allocation alloc = { allocation, n, codeChunk };
     return alloc;
 }
 
-void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
+void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 { 
     alloc.chunk->Close();
     delete alloc.chunk;
 }
 
 #if ENABLE_ASSEMBLER_WX_EXCLUSIVE
 #error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform."
 #endif
--- a/js/src/assembler/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/assembler/jit/ExecutableAllocatorWin.cpp
@@ -34,24 +34,24 @@ namespace JSC {
 
 size_t ExecutableAllocator::determinePageSize()
 {
     SYSTEM_INFO system_info;
     GetSystemInfo(&system_info);
     return system_info.dwPageSize;
 }
 
-ExecutablePool::Allocation ExecutablePool::systemAlloc(size_t n)
+ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
     void *allocation = VirtualAlloc(0, n, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
     ExecutablePool::Allocation alloc = {reinterpret_cast<char*>(allocation), n};
     return alloc;
 }
 
-void ExecutablePool::systemRelease(const ExecutablePool::Allocation& alloc)
+void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 { 
     VirtualFree(alloc.pages, 0, MEM_RELEASE); 
 }
 
 #if ENABLE_ASSEMBLER_WX_EXCLUSIVE
 #error "ASSEMBLER_WX_EXCLUSIVE not yet suported on this platform."
 #endif
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -697,19 +697,20 @@ struct JSRuntime {
     FunctionCountMap    methodReadBarrierCountMap;
     FunctionCountMap    unjoinedFunctionCountMap;
 #endif
 
     JSWrapObjectCallback wrapObjectCallback;
     JSPreWrapCallback    preWrapObjectCallback;
 
 #ifdef JS_METHODJIT
-    uint32               mjitMemoryUsed;
+    /* This measures the size of JITScripts, native maps and IC structs. */
+    size_t               mjitDataSize;
 #endif
-    uint32               stringMemoryUsed;
+    size_t               stringMemoryUsed;
 
     JSRuntime();
     ~JSRuntime();
 
     bool init(uint32 maxbytes);
 
     void setGCTriggerFactor(uint32 factor);
     void setGCLastBytes(size_t lastBytes);
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -160,16 +160,24 @@ JSCompartment::init()
     if (!(jaegerCompartment = rt->new_<mjit::JaegerCompartment>()))
         return false;
     return jaegerCompartment->Initialize();
 #else
     return true;
 #endif
 }
 
+#ifdef JS_METHODJIT
+size_t
+JSCompartment::getMjitCodeSize() const
+{
+    return jaegerCompartment->execAlloc()->getCodeSize();
+}
+#endif
+
 bool
 JSCompartment::arenaListsAreEmpty()
 {
   for (unsigned i = 0; i < FINALIZE_LIMIT; i++) {
        if (!arenas[i].isEmpty() || arenas[i].hasToBeFinalized)
            return false;
   }
   return true;
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -280,16 +280,20 @@ struct TraceMonitor {
 
     /* Sweep any cache entry pointing to dead GC things. */
     void sweep(JSContext *cx);
 
     /* Mark any tracer stacks that are active. */
     void mark(JSTracer *trc);
 
     bool outOfMemory() const;
+
+    JS_FRIEND_API(void) getCodeAllocStats(size_t &total, size_t &frag_size, size_t &free_size) const;
+    JS_FRIEND_API(size_t) getVMAllocatorsMainSize() const;
+    JS_FRIEND_API(size_t) getVMAllocatorsReserveSize() const;
 };
 
 namespace mjit {
 class JaegerCompartment;
 }
 }
 
 /* Number of potentially reusable scriptsToGC to search for the eval cache. */
@@ -404,16 +408,23 @@ struct JS_FRIEND_API(JSCompartment) {
 #endif
 
     void                         *data;
     bool                         active;  // GC flag, whether there are active frames
     js::WrapperMap               crossCompartmentWrappers;
 
 #ifdef JS_METHODJIT
     js::mjit::JaegerCompartment  *jaegerCompartment;
+    /*
+     * This function is here so that xpconnect/src/xpcjsruntime.cpp doesn't
+     * need to see the declaration of JaegerCompartment, which would require
+     * #including MethodJIT.h into xpconnect/src/xpcjsruntime.cpp, which is
+     * difficult due to reasons explained in bug 483677.
+     */
+    size_t getMjitCodeSize() const;
 #endif
 
     /*
      * Shared scope property tree, and arena-pool for allocating its nodes.
      */
     js::PropertyTree             propertyTree;
 
 #ifdef DEBUG
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -2456,16 +2456,44 @@ TraceRecorder::~TraceRecorder()
 inline bool
 TraceMonitor::outOfMemory() const
 {
     return dataAlloc->outOfMemory() ||
            tempAlloc->outOfMemory() ||
            traceAlloc->outOfMemory();
 }
 
+void
+TraceMonitor::getCodeAllocStats(size_t &total, size_t &frag_size, size_t &free_size) const
+{
+    if (codeAlloc) {
+        codeAlloc->getStats(total, frag_size, free_size);
+    } else {
+        total = 0;
+        frag_size = 0;
+        free_size = 0;
+    }
+}
+
+size_t
+TraceMonitor::getVMAllocatorsMainSize() const
+{
+    return dataAlloc->getBytesAllocated() +
+           traceAlloc->getBytesAllocated() +
+           tempAlloc->getBytesAllocated();
+}
+
+size_t
+TraceMonitor::getVMAllocatorsReserveSize() const
+{
+    return dataAlloc->mReserveSize +
+           traceAlloc->mReserveSize +
+           tempAlloc->mReserveSize;
+}
+
 /*
  * This function destroys the recorder after a successful recording, possibly
  * starting a suspended outer recorder.
  */
 AbortableRecordingStatus
 TraceRecorder::finishSuccessfully()
 {
     JS_ASSERT(!traceMonitor->profile);
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -404,16 +404,17 @@ struct VMSideExit : public nanojit::Side
 
 class VMAllocator : public nanojit::Allocator
 {
 public:
     VMAllocator(JSRuntime *rt, char* reserve, size_t reserveSize)
       : mOutOfMemory(false)
       , mSize(0)
       , mReserve(reserve)
+      , mReserveSize(reserveSize)
       , mReserveCurr(uintptr_t(reserve))
       , mReserveLimit(uintptr_t(reserve + reserveSize))
       , mRt(rt)
     {}
 
     ~VMAllocator() {
         js::UnwantedForeground::free_(mReserve);
     }
@@ -465,16 +466,17 @@ public:
         memset(current_top, 0, current_limit - current_top);
     }
 
     bool mOutOfMemory;
     size_t mSize;
 
     /* See nanojit::Allocator::allocChunk() for details on these. */
     char* mReserve;
+    size_t mReserveSize;
     uintptr_t mReserveCurr;
     uintptr_t mReserveLimit;
 
     /* To keep track of allocation. */
     JSRuntime* mRt;
 };
 
 struct FrameInfo {
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -399,61 +399,61 @@ mjit::Compiler::finishThisUp(JITScript *
     }
 
 #ifdef JS_CPU_ARM
     masm.forceFlushConstantPool();
     stubcc.masm.forceFlushConstantPool();
 #endif
     JaegerSpew(JSpew_Insns, "## Fast code (masm) size = %u, Slow code (stubcc) size = %u.\n", masm.size(), stubcc.size());
 
-    size_t totalSize = masm.size() +
-                       stubcc.size() +
-                       doubleList.length() * sizeof(double) +
-                       jumpTableOffsets.length() * sizeof(void *);
+    size_t codeSize = masm.size() +
+                      stubcc.size() +
+                      doubleList.length() * sizeof(double) +
+                      jumpTableOffsets.length() * sizeof(void *);
 
     JSC::ExecutablePool *execPool;
     uint8 *result =
-        (uint8 *)script->compartment->jaegerCompartment->execAlloc()->alloc(totalSize, &execPool);
+        (uint8 *)script->compartment->jaegerCompartment->execAlloc()->alloc(codeSize, &execPool);
     if (!result) {
         js_ReportOutOfMemory(cx);
         return Compile_Error;
     }
     JS_ASSERT(execPool);
-    JSC::ExecutableAllocator::makeWritable(result, totalSize);
+    JSC::ExecutableAllocator::makeWritable(result, codeSize);
     masm.executableCopy(result);
     stubcc.masm.executableCopy(result + masm.size());
     
-    JSC::LinkBuffer fullCode(result, totalSize);
+    JSC::LinkBuffer fullCode(result, codeSize);
     JSC::LinkBuffer stubCode(result + masm.size(), stubcc.size());
 
     size_t nNmapLive = 0;
     for (size_t i = 0; i < script->length; i++) {
         analyze::Bytecode *opinfo = analysis->maybeCode(i);
         if (opinfo && opinfo->safePoint)
             nNmapLive++;
     }
 
     /* Please keep in sync with JITScript::scriptDataSize! */
-    size_t totalBytes = sizeof(JITScript) +
-                        sizeof(NativeMapEntry) * nNmapLive +
+    size_t dataSize = sizeof(JITScript) +
+                      sizeof(NativeMapEntry) * nNmapLive +
 #if defined JS_MONOIC
-                        sizeof(ic::GetGlobalNameIC) * getGlobalNames.length() +
-                        sizeof(ic::SetGlobalNameIC) * setGlobalNames.length() +
-                        sizeof(ic::CallICInfo) * callICs.length() +
-                        sizeof(ic::EqualityICInfo) * equalityICs.length() +
-                        sizeof(ic::TraceICInfo) * traceICs.length() +
+                      sizeof(ic::GetGlobalNameIC) * getGlobalNames.length() +
+                      sizeof(ic::SetGlobalNameIC) * setGlobalNames.length() +
+                      sizeof(ic::CallICInfo) * callICs.length() +
+                      sizeof(ic::EqualityICInfo) * equalityICs.length() +
+                      sizeof(ic::TraceICInfo) * traceICs.length() +
 #endif
 #if defined JS_POLYIC
-                        sizeof(ic::PICInfo) * pics.length() +
-                        sizeof(ic::GetElementIC) * getElemICs.length() +
-                        sizeof(ic::SetElementIC) * setElemICs.length() +
+                       sizeof(ic::PICInfo) * pics.length() +
+                       sizeof(ic::GetElementIC) * getElemICs.length() +
+                       sizeof(ic::SetElementIC) * setElemICs.length() +
 #endif
-                        sizeof(CallSite) * callSites.length();
-
-    uint8 *cursor = (uint8 *)cx->calloc_(totalBytes);
+                       sizeof(CallSite) * callSites.length();
+
+    uint8 *cursor = (uint8 *)cx->calloc_(dataSize);
     if (!cursor) {
         execPool->release();
         js_ReportOutOfMemory(cx);
         return Compile_Error;
     }
 
     JITScript *jit = new(cursor) JITScript;
     cursor += sizeof(JITScript);
@@ -803,22 +803,22 @@ mjit::Compiler::finishThisUp(JITScript *
         CallSite &to = jitCallSites[i];
         InternalCallSite &from = callSites[i];
         uint32 codeOffset = from.ool
                             ? masm.size() + from.returnOffset
                             : from.returnOffset;
         to.initialize(codeOffset, from.pc - script->code, from.id);
     }
 
-    JS_ASSERT(size_t(cursor - (uint8*)jit) == totalBytes);
+    JS_ASSERT(size_t(cursor - (uint8*)jit) == dataSize);
 
     *jitp = jit;
 
     /* We tolerate a race in the stats. */
-    cx->runtime->mjitMemoryUsed += totalSize + totalBytes;
+    cx->runtime->mjitDataSize += dataSize;
 
     return Compile_Okay;
 }
 
 class SrcNoteLineScanner {
     ptrdiff_t offset;
     jssrcnote *sn;
 
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -904,26 +904,26 @@ void
 mjit::ReleaseScriptCode(JSContext *cx, JSScript *script)
 {
     // NB: The recompiler may call ReleaseScriptCode, in which case it
     // will get called again when the script is destroyed, so we
     // must protect against calling ReleaseScriptCode twice.
     JITScript *jscr;
 
     if ((jscr = script->jitNormal)) {
-        cx->runtime->mjitMemoryUsed -= jscr->scriptDataSize() + jscr->mainCodeSize();
+        cx->runtime->mjitDataSize -= jscr->scriptDataSize();
 
         jscr->~JITScript();
         cx->free_(jscr);
         script->jitNormal = NULL;
         script->jitArityCheckNormal = NULL;
     }
 
     if ((jscr = script->jitCtor)) {
-        cx->runtime->mjitMemoryUsed -= jscr->scriptDataSize() + jscr->mainCodeSize();
+        cx->runtime->mjitDataSize -= jscr->scriptDataSize();
 
         jscr->~JITScript();
         cx->free_(jscr);
         script->jitCtor = NULL;
         script->jitArityCheckCtor = NULL;
     }
 }
 
--- a/js/src/methodjit/MethodJIT.h
+++ b/js/src/methodjit/MethodJIT.h
@@ -410,18 +410,16 @@ struct JITScript {
 
     void nukeScriptDependentICs();
     void sweepCallICs(JSContext *cx, bool purgeAll);
     void purgeMICs();
     void purgePICs();
 
     size_t scriptDataSize();
 
-    size_t mainCodeSize() { return code.m_size; } /* doesn't account for fragmentation */
-
     jsbytecode *nativeToPC(void *returnAddress) const;
 
   private:
     /* Helpers used to navigate the variable-length sections. */
     char *nmapSectionLimit() const;
     char *monoICSectionsLimit() const;
     char *polyICSectionsLimit() const;
 };
--- a/js/src/nanojit/Allocator.cpp
+++ b/js/src/nanojit/Allocator.cpp
@@ -84,20 +84,32 @@ namespace nanojit
     {
         if (nbytes < MIN_CHUNK_SZB)
             nbytes = MIN_CHUNK_SZB;
         size_t chunkbytes = sizeof(Chunk) + nbytes - sizeof(int64_t);
         void* mem = allocChunk(chunkbytes, fallible);
         if (mem) {
             Chunk* chunk = (Chunk*) mem;
             chunk->prev = current_chunk;
+            chunk->size = chunkbytes;
             current_chunk = chunk;
             current_top = (char*)chunk->data;
             current_limit = (char*)mem + chunkbytes;
             return true;
         } else {
             NanoAssert(fallible);
             return false;
         }
     }
+
+    size_t Allocator::getBytesAllocated()
+    {
+        size_t n = 0;
+        Chunk *c = current_chunk;
+        while (c) {
+            n += c->size;
+            c = c->prev;
+        }
+        return n;
+    }
 }
 
 #endif // FEATURE_NANOJIT
--- a/js/src/nanojit/Allocator.h
+++ b/js/src/nanojit/Allocator.h
@@ -85,23 +85,26 @@ namespace nanojit
                 p = current_top;
                 current_top += nbytes;
             } else {
                 p = allocSlow(nbytes, /* fallible = */true);
             }
             return p;
         }
 
+        size_t getBytesAllocated();
+
     protected:
         void* allocSlow(size_t nbytes, bool fallible = false);
         bool fill(size_t minbytes, bool fallible);
 
         class Chunk {
         public:
             Chunk* prev;
+            size_t size;
             int64_t data[1]; // int64_t forces 8-byte alignment.
         };
 
         Chunk* current_chunk;
         char* current_top;
         char* current_limit;
 
         // allocator SPI
--- a/js/src/nanojit/CodeAlloc.cpp
+++ b/js/src/nanojit/CodeAlloc.cpp
@@ -93,32 +93,37 @@ namespace nanojit
         uintptr_t end = (uintptr_t)alignUp(term, bytesPerPage);
         return (CodeList*) (end - (uintptr_t)bytesPerAlloc);
     }
 
     static int round(size_t x) {
         return (int)((x + 512) >> 10);
     }
 
-    void CodeAlloc::logStats() {
-        size_t total = 0;
-        size_t frag_size = 0;
-        size_t free_size = 0;
+    void CodeAlloc::getStats(size_t& total, size_t& frag_size, size_t& free_size) {
+        total = 0;
+        frag_size = 0;
+        free_size = 0;
         int free_count = 0;
         for (CodeList* hb = heapblocks; hb != 0; hb = hb->next) {
             total += bytesPerAlloc;
             for (CodeList* b = hb->lower; b != 0; b = b->lower) {
                 if (b->isFree) {
                     free_count++;
                     free_size += b->blockSize();
                     if (b->size() < minAllocSize)
                         frag_size += b->blockSize();
                 }
             }
         }
+    }
+
+    void CodeAlloc::logStats() {
+        size_t total, frag_size, free_size;
+        getStats(total, frag_size, free_size);
         avmplus::AvmLog("code-heap: %dk free %dk fragmented %d\n",
             round(total), round(free_size), frag_size);
     }
 
     inline void CodeAlloc::markBlockWrite(CodeList* b) {
         NanoAssert(b->terminator != NULL);
         CodeList* term = b->terminator;
         if (term->isExec) {
--- a/js/src/nanojit/CodeAlloc.h
+++ b/js/src/nanojit/CodeAlloc.h
@@ -212,16 +212,19 @@ namespace nanojit
         /** return the number of bytes in all the code blocks in "code", including block overhead */
 #ifdef PERFM
         static size_t size(const CodeList* code);
 #endif
 
         /** return the total number of bytes held by this CodeAlloc. */
         size_t size();
 
+        /** get stats about heap usage */
+        void getStats(size_t& total, size_t& frag_size, size_t& free_size);
+
         /** print out stats about heap usage */
         void logStats();
 
         /** protect all code managed by this CodeAlloc */
         void markAllExec();
 
         /** protect all mem in the block list */
         void markExec(CodeList* &blocks);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4565,20 +4565,36 @@ Deserialize(JSContext *cx, uintN argc, j
                                 JS_STRUCTURED_CLONE_VERSION, &v, NULL, NULL)) {
         return false;
     }
     JS_SET_RVAL(cx, vp, v);
     return true;
 }
 
 JSBool
-MJitStats(JSContext *cx, uintN argc, jsval *vp)
+MJitCodeStats(JSContext *cx, uintN argc, jsval *vp)
 {
 #ifdef JS_METHODJIT
-     JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->mjitMemoryUsed));
+    JSRuntime *rt = cx->runtime;
+    AutoLockGC lock(rt);
+    size_t n = 0;
+    for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
+        n += (*c)->getMjitCodeSize();
+    JS_SET_RVAL(cx, vp, INT_TO_JSVAL(n));
+#else
+    JS_SET_RVAL(cx, vp, JSVAL_VOID);
+#endif
+    return true;
+}
+
+JSBool
+MJitDataStats(JSContext *cx, uintN argc, jsval *vp)
+{
+#ifdef JS_METHODJIT
+     JS_SET_RVAL(cx, vp, INT_TO_JSVAL(cx->runtime->mjitDataSize));
 #else
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
 #endif
     return true;
 }
 
 JSBool
 StringStats(JSContext *cx, uintN argc, jsval *vp)
@@ -4728,17 +4744,18 @@ static JSFunctionSpec shell_functions[] 
     JS_FN("parse",          Parse,          1,0),
     JS_FN("timeout",        Timeout,        1,0),
     JS_FN("elapsed",        Elapsed,        0,0),
     JS_FN("parent",         Parent,         1,0),
     JS_FN("wrap",           Wrap,           1,0),
     JS_FN("serialize",      Serialize,      1,0),
     JS_FN("deserialize",    Deserialize,    1,0),
 #ifdef JS_METHODJIT
-    JS_FN("mjitstats",      MJitStats,      0,0),
+    JS_FN("mjitcodestats",  MJitCodeStats,  0,0),
+    JS_FN("mjitdatastats",  MJitDataStats,  0,0),
 #endif
     JS_FN("stringstats",    StringStats,    0,0),
     JS_FN("newGlobal",      NewGlobal,      1,0),
     JS_FN("parseLegacyJSON",ParseLegacyJSON,1,0),
     JS_FS_END
 };
 
 static const char shell_help_header[] =
@@ -4862,17 +4879,18 @@ static const char *const shell_help_mess
 "  Get/Set the limit in seconds for the execution time for the current context.\n"
 "  A negative value (default) means that the execution time is unlimited.",
 "elapsed()                Execution time elapsed for the current context.",
 "parent(obj)              Returns the parent of obj.",
 "wrap(obj)                Wrap an object into a noop wrapper.",
 "serialize(sd)            Serialize sd using JS_WriteStructuredClone. Returns a TypedArray.",
 "deserialize(a)           Deserialize data generated by serialize.",
 #ifdef JS_METHODJIT
-"mjitstats()              Return stats on mjit memory usage.",
+"mjitcodestats()          Return stats on mjit code memory usage.",
+"mjitdatastats()          Return stats on mjit data memory usage.",
 #endif
 "stringstats()            Return stats on string memory usage.",
 "newGlobal(kind)          Return a new global object, in the current\n"
 "                         compartment if kind === 'same-compartment' or in a\n"
 "                         new compartment if kind === 'new-compartment'",
 "parseLegacyJSON(str)     Parse str as legacy JSON, returning the result if the\n"
 "                         parse succeeded and throwing a SyntaxError if not.",
 
--- a/js/src/xpconnect/src/xpcjsruntime.cpp
+++ b/js/src/xpconnect/src/xpcjsruntime.cpp
@@ -1273,53 +1273,150 @@ private:
     }
 
 protected:
     PRUint32 mNumGCChunksInUse;
 };
 
 static XPConnectGCChunkAllocator gXPCJSChunkAllocator;
 
-NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSRuntimeGCChunks,
-                             "js/gc-heap",
-                             "Main JS GC heap",
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSGCHeap,
+                             "heap-used/js/gc-heap",
+                             "Memory used by the garbage-collected JavaScript "
+                             "heap.",
                              XPConnectGCChunkAllocator::GetGCChunkBytesInUse,
                              &gXPCJSChunkAllocator)
 
-/* FIXME: use API provided by bug 623271 */
-#include "jscntxt.h"
+static PRInt64
+GetPerCompartmentSize(PRInt64 (*f)(JSCompartment *c))
+{
+    JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
+    js::AutoLockGC lock(rt);
+    PRInt64 n = 0;
+    for (JSCompartment **c = rt->compartments.begin(); c != rt->compartments.end(); ++c)
+        n += f(*c);
+    return n;
+}
+
+#ifdef JS_METHODJIT
 
 static PRInt64
-GetJSMethodJitCodeMemoryInUse(void *data)
+GetCompartmentMjitCodeSize(JSCompartment *c)
+{
+    return c->getMjitCodeSize();
+}
+
+static PRInt64
+GetJSMjitCode(void *data)
+{
+    return GetPerCompartmentSize(GetCompartmentMjitCodeSize);
+}
+
+static PRInt64
+GetJSMJitData(void *data)
 {
     JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
-#ifdef JS_METHODJIT
-    return rt->mjitMemoryUsed;
-#else
+    return rt->mjitDataSize;
+}
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMjitCode,
+                             "mapped/js/mjit-code",
+                             "Memory mapped by the method JIT to hold "
+                             "generated code.",
+                             GetJSMjitCode,
+                             NULL)
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMjitData,
+                             "heap-used/js/mjit-data",
+                             "Memory allocated by the method JIT for the "
+                             "following data: JITScripts, native maps, and "
+                             "inline cache structs.",
+                             GetJSMJitData,
+                             NULL)
+#endif  // JS_METHODJIT
+
+#ifdef JS_TRACER
+
+static PRInt64
+GetCompartmentTjitCode(JSCompartment *c)
+{
+    js::TraceMonitor &tm = c->traceMonitor;
+    if (tm.codeAlloc) {
+        size_t total, frag_size, free_size;
+        tm.getCodeAllocStats(total, frag_size, free_size);
+        return total;
+    }
     return 0;
-#endif
+}
+
+static PRInt64
+GetCompartmentTjitDataAllocatorsMain(JSCompartment *c)
+{
+    js::TraceMonitor &tm = c->traceMonitor;
+    return tm.dataAlloc ? tm.getVMAllocatorsMainSize() : 0;
 }
 
-NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSMethodJitCode,
-                             "js/mjit-code",
-                             "Memory in use by method-JIT for compiled code",
-                             GetJSMethodJitCodeMemoryInUse,
+static PRInt64
+GetCompartmentTjitDataAllocatorsReserve(JSCompartment *c)
+{
+    js::TraceMonitor &tm = c->traceMonitor;
+    return tm.dataAlloc ? tm.getVMAllocatorsReserveSize() : 0;
+}
+
+static PRInt64
+GetJSTjitCode(void *data)
+{
+    return GetPerCompartmentSize(GetCompartmentTjitCode);
+}
+
+static PRInt64
+GetJSTjitDataAllocatorsMain(void *data)
+{
+    return GetPerCompartmentSize(GetCompartmentTjitDataAllocatorsMain);
+}
+
+static PRInt64
+GetJSTjitDataAllocatorsReserve(void *data)
+{
+    return GetPerCompartmentSize(GetCompartmentTjitDataAllocatorsReserve);
+}
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitCode,
+                             "mapped/js/tjit-code",
+                             "Memory mapped by the trace JIT to hold "
+                             "generated code.",
+                             GetJSTjitCode,
                              NULL)
 
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitDataAllocatorsMain,
+                             "heap-used/js/tjit-data/allocators/main",
+                             "Memory allocated by the trace JIT's "
+                             "VMAllocators.",
+                             GetJSTjitDataAllocatorsMain,
+                             NULL)
+
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSTjitDataAllocatorsReserve,
+                             "heap-used/js/tjit-data/allocators/reserve",
+                             "Memory allocated by the trace JIT and held in "
+                             "reserve for VMAllocators in case of OOM.",
+                             GetJSTjitDataAllocatorsReserve,
+                             NULL)
+#endif  // JS_TRACER
+
 static PRInt64
-GetJSStringMemoryInUse(void *data)
+GetJSStringData(void *data)
 {
     JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
     return rt->stringMemoryUsed;
 }
 
-NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringMemory,
-                             "js/string-data",
-                             "Memory in use for string data",
-                             GetJSStringMemoryInUse,
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSStringData,
+                             "heap-used/js/string-data",
+                             "Memory allocated for JavaScript string data.",
+                             GetJSStringData,
                              NULL)
 
 XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect)
  : mXPConnect(aXPConnect),
    mJSRuntime(nsnull),
    mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_SIZE)),
    mWrappedJSClassMap(IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_SIZE)),
    mIID2NativeInterfaceMap(IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_SIZE)),
@@ -1377,19 +1474,23 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
         mWatchdogWakeup = JS_NEW_CONDVAR(mJSRuntime->gcLock);
         if (!mWatchdogWakeup)
             NS_RUNTIMEABORT("JS_NEW_CONDVAR failed.");
 
         mJSRuntime->setActivityCallback(ActivityCallback, this);
 
         mJSRuntime->setCustomGCChunkAllocator(&gXPCJSChunkAllocator);
 
-        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSRuntimeGCChunks));
-        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringMemory));
-        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMethodJitCode));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSStringData));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitCode));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSMjitData));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitCode));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsMain));
+        NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSTjitDataAllocatorsReserve));
     }
 
     if(!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,
                           sizeof(ObjectHolder), 512))
         mJSHolders.ops = nsnull;
 
     mCompartmentMap.Init();
     mMTCompartmentMap.Init();
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -1641,24 +1641,25 @@ NS_NewPresShell(nsIPresShell** aInstance
 
   NS_ADDREF(*aInstancePtrResult);
   return NS_OK;
 }
 
 nsTHashtable<PresShell::PresShellPtrKey> *nsIPresShell::sLiveShells = 0;
 
 NS_MEMORY_REPORTER_IMPLEMENT(LayoutPresShell,
-                             "layout/all",
-                             "Memory in use by layout PresShell, PresContext, and other related areas.",
+                             "heap-used/layout/all",
+                             "Memory used by layout PresShell, PresContext, "
+                             "and other related areas.",
                              PresShell::SizeOfLayoutMemoryReporter,
                              nsnull)
 
 NS_MEMORY_REPORTER_IMPLEMENT(LayoutBidi,
-                             "layout/bidi",
-                             "Memory in use by layout Bidi processor.",
+                             "heap-used/layout/bidi",
+                             "Memory used by layout Bidi processor.",
                              PresShell::SizeOfBidiMemoryReporter,
                              nsnull)
 
 PresShell::PresShell()
 {
   mSelection = nsnull;
 #ifdef MOZ_REFLOW_PERF
   mReflowCountMgr = new ReflowCountMgr();
--- a/modules/libpr0n/src/imgLoader.cpp
+++ b/modules/libpr0n/src/imgLoader.cpp
@@ -155,53 +155,53 @@ public:
     : mType(aType)
   { }
 
   NS_DECL_ISUPPORTS
 
   NS_IMETHOD GetPath(char **memoryPath)
   {
     if (mType == ChromeUsedRaw) {
-      *memoryPath = strdup("images/chrome/used/raw");
+      *memoryPath = strdup("heap-used/images/chrome/used/raw");
     } else if (mType == ChromeUsedUncompressed) {
-      *memoryPath = strdup("images/chrome/used/uncompressed");
+      *memoryPath = strdup("heap-used/images/chrome/used/uncompressed");
     } else if (mType == ChromeUnusedRaw) {
-      *memoryPath = strdup("images/chrome/unused/raw");
+      *memoryPath = strdup("heap-used/images/chrome/unused/raw");
     } else if (mType == ChromeUnusedUncompressed) {
-      *memoryPath = strdup("images/chrome/unused/uncompressed");
+      *memoryPath = strdup("heap-used/images/chrome/unused/uncompressed");
     } else if (mType == ContentUsedRaw) {
-      *memoryPath = strdup("images/content/used/raw");
+      *memoryPath = strdup("heap-used/images/content/used/raw");
     } else if (mType == ContentUsedUncompressed) {
-      *memoryPath = strdup("images/content/used/uncompressed");
+      *memoryPath = strdup("heap-used/images/content/used/uncompressed");
     } else if (mType == ContentUnusedRaw) {
-      *memoryPath = strdup("images/content/unused/raw");
+      *memoryPath = strdup("heap-used/images/content/unused/raw");
     } else if (mType == ContentUnusedUncompressed) {
-      *memoryPath = strdup("images/content/unused/uncompressed");
+      *memoryPath = strdup("heap-used/images/content/unused/uncompressed");
     }
     return NS_OK;
   }
 
   NS_IMETHOD GetDescription(char **desc)
   {
     if (mType == ChromeUsedRaw) {
-      *desc = strdup("Memory used by in-use chrome images, compressed data");
+      *desc = strdup("Memory used by in-use chrome images (compressed data).");
     } else if (mType == ChromeUsedUncompressed) {
-      *desc = strdup("Memory used by in-use chrome images, uncompressed data");
+      *desc = strdup("Memory used by in-use chrome images (uncompressed data).");
     } else if (mType == ChromeUnusedRaw) {
-      *desc = strdup("Memory used by not in-use chrome images, compressed data");
+      *desc = strdup("Memory used by not in-use chrome images (compressed data).");
     } else if (mType == ChromeUnusedUncompressed) {
-      *desc = strdup("Memory used by not in-use chrome images, uncompressed data");
+      *desc = strdup("Memory used by not in-use chrome images (uncompressed data).");
     } else if (mType == ContentUsedRaw) {
-      *desc = strdup("Memory used by in-use content images, compressed data");
+      *desc = strdup("Memory used by in-use content images (compressed data).");
     } else if (mType == ContentUsedUncompressed) {
-      *desc = strdup("Memory used by in-use content images, uncompressed data");
+      *desc = strdup("Memory used by in-use content images (uncompressed data).");
     } else if (mType == ContentUnusedRaw) {
-      *desc = strdup("Memory used by not in-use content images, compressed data");
+      *desc = strdup("Memory used by not in-use content images (compressed data).");
     } else if (mType == ContentUnusedUncompressed) {
-      *desc = strdup("Memory used by not in-use content images, uncompressed data");
+      *desc = strdup("Memory used by not in-use content images (uncompressed data).");
     }
     return NS_OK;
   }
 
   struct EnumArg {
     EnumArg(ReporterType aType)
       : rtype(aType), value(0)
     { }
--- a/storage/src/mozStorageConnection.cpp
+++ b/storage/src/mozStorageConnection.cpp
@@ -348,49 +348,49 @@ public:
   {
   }
 
 
   NS_IMETHOD GetPath(char **memoryPath)
   {
     nsCString path;
 
-    path.AppendLiteral("storage/");
+    path.AppendLiteral("heap-used/storage/");
     path.Append(mDBConn.getFilename());
 
     if (mType == LookAside_Used) {
-      path.AppendLiteral("/LookAside_Used");
+      path.AppendLiteral("/lookaside-used");
     }
     else if (mType == Cache_Used) {
-      path.AppendLiteral("/Cache_Used");
+      path.AppendLiteral("/cache-used");
     }
     else if (mType == Schema_Used) {
-      path.AppendLiteral("/Schema_Used");
+      path.AppendLiteral("/schema-used");
     }
     else if (mType == Stmt_Used) {
-      path.AppendLiteral("/Stmt_Used");
+      path.AppendLiteral("/stmt-used");
     }
 
     *memoryPath = ::ToNewCString(path);
     return NS_OK;
   }
 
   NS_IMETHOD GetDescription(char **desc)
   {
     if (mType == LookAside_Used) {
-      *desc = ::strdup("Number of lookaside memory slots currently checked out");
+      *desc = ::strdup("Number of lookaside memory slots currently checked out.");
     }
     else if (mType == Cache_Used) {
-      *desc = ::strdup("Approximate number of bytes of heap memory used by all pager caches");
+      *desc = ::strdup("Memory (approximate) used by all pager caches.");
     }
     else if (mType == Schema_Used) {
-      *desc = ::strdup("Approximate number of bytes of heap memory used to store the schema for all databases associated with the connection");
+      *desc = ::strdup("Memory (approximate) used to store the schema for all databases associated with the connection");
     }
     else if (mType == Stmt_Used) {
-      *desc = ::strdup("Approximate number of bytes of heap and lookaside memory used by all prepared statements");
+      *desc = ::strdup("Memory (approximate) used by all prepared statements");
     }
     return NS_OK;
   }
 
   NS_IMETHOD GetMemoryUsed(PRInt64 *memoryUsed)
   {
     int type = 0;
     if (mType == LookAside_Used) {
@@ -571,29 +571,36 @@ Connection::initialize(nsIFile *aDatabas
     case 1:
     default:
       (void)ExecuteSimpleSQL(NS_LITERAL_CSTRING(
           "PRAGMA synchronous = NORMAL;"));
       break;
   }
 
   nsRefPtr<nsIMemoryReporter> reporter;
+#if 0
+  // FIXME: Bug 649867 explains why this is disabled.
   reporter =
     new StorageMemoryReporter(*this, StorageMemoryReporter::LookAside_Used);
   mMemoryReporters.AppendElement(reporter);
+#endif
 
+  // FIXME: These reporters overlap with storage/sqlite/pagecache and
+  // storage/sqlite/other, and therefore double-count some memory.  See bug
+  // 653630 for details.
   reporter =
     new StorageMemoryReporter(*this, StorageMemoryReporter::Cache_Used);
   mMemoryReporters.AppendElement(reporter);
 
   reporter =
     new StorageMemoryReporter(*this, StorageMemoryReporter::Schema_Used);
   mMemoryReporters.AppendElement(reporter);
 
-  reporter = new StorageMemoryReporter(*this, StorageMemoryReporter::Stmt_Used);
+  reporter =
+    new StorageMemoryReporter(*this, StorageMemoryReporter::Stmt_Used);
   mMemoryReporters.AppendElement(reporter);
 
   for (PRUint32 i = 0; i < mMemoryReporters.Length(); i++) {
     (void)::NS_RegisterMemoryReporter(mMemoryReporters[i]);
   }
 
   return NS_OK;
 }
--- a/storage/src/mozStorageService.cpp
+++ b/storage/src/mozStorageService.cpp
@@ -146,24 +146,30 @@ GetStorageSQLiteOtherMemoryUsed(void *)
 {
   int pageCacheCurrent, pageCacheHigh;
   int rc = ::sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &pageCacheCurrent,
                             &pageCacheHigh, 0);
   return rc == SQLITE_OK ? ::sqlite3_memory_used() - pageCacheCurrent : 0;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLitePageCacheMemoryUsed,
-                             "storage/sqlite/pagecache",
-                             "Memory in use by SQLite for the page cache",
+                             "heap-used/storage/sqlite/pagecache",
+                             "Memory used by SQLite for the page cache. "
+                             "This overlaps with the per-connection cache-used "
+                             "figure, thus over-counting some bytes.  Bug "
+                             "653630 has the details.",
                              GetStorageSQLitePageCacheMemoryUsed,
                              nsnull)
 
 NS_MEMORY_REPORTER_IMPLEMENT(StorageSQLiteOtherMemoryUsed,
-                             "storage/sqlite/other",
-                             "Memory in use by SQLite for other various reasons",
+                             "heap-used/storage/sqlite/other",
+                             "Memory used by SQLite for other various reasons."
+                             "This overlaps with the per-connection stmt-used "
+                             "and schema-used figures, thus over-counting some "
+                             "bytes.  Bug 653630 has the details.",
                              GetStorageSQLiteOtherMemoryUsed,
                              nsnull)
 
 ////////////////////////////////////////////////////////////////////////////////
 //// Helpers
 
 class ServiceMainThreadInitializer : public nsRunnable
 {
--- a/toolkit/components/aboutmemory/Makefile.in
+++ b/toolkit/components/aboutmemory/Makefile.in
@@ -38,9 +38,13 @@
 
 DEPTH		= ../../..
 topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
+ifdef ENABLE_TESTS
+DIRS += tests
+endif
+
 include $(topsrcdir)/config/rules.mk
--- a/toolkit/components/aboutmemory/content/aboutMemory.css
+++ b/toolkit/components/aboutmemory/content/aboutMemory.css
@@ -8,73 +8,51 @@
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is about:memory.
  *
- * The Initial Developer of the Original Code is
- *   Mozilla Corporation
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir@pobox.com>
+ *   Nicholas Nethercote <nnethercote@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-html {
-  background: -moz-Dialog;
-  color: -moz-DialogText;
+.mrValue {
+  font-weight: bold;
+  color: #400;
 }
 
-body {
-  margin: 0;
-  padding: 0 1em;
-  font: message-box;
-}
-
-h1 {
-    font-weight: bold;
-    font-size: x-large;
-}
-
-label {
-    -moz-user-select: text;
+.mrPerc {
 }
 
-h2 {
-    margin-top: 0;
-    font-weight: bold;
-    font-size: large;
+.mrName {
+  font-style: italic;
+  color: #004;
 }
 
-.memBox {
-    max-width: 30em;
-    background: -moz-Field;
-    color: -moz-FieldText;
-    margin-top: 25px;
-    padding: 5px;
-    border: 1px solid -moz-FieldText;
-    border-radius: 10px 10px;
+.treeLine {
+  color: #888;
 }
 
-.memValue {
-    text-align: right;
+.option {
+  font-size: 80%;
+  -moz-user-select: none;  /* no need to include this when cutting+pasting */
 }
-
-.memOverview table {
-    font-size: 120%;
-}
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -9,207 +9,591 @@
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is about:memory
  *
- * The Initial Developer of the Original Code is
- * the Mozilla Foundation.
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Vladimir Vukicevic <vladimir@pobox.com>
+ *   Nicholas Nethercote <nnethercote@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+"use strict";
+
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
-var gMemReporters = { };
+// Must use .href here instead of .search because "about:memory" is a
+// non-standard URL.
+var gVerbose = (location.href.split(/[\?,]/).indexOf("verbose") !== -1);
 
-function $(n) {
-    return document.getElementById(n);
+function onLoad()
+{
+  var os = Cc["@mozilla.org/observer-service;1"].
+      getService(Ci.nsIObserverService);
+  os.notifyObservers(null, "child-memory-reporter-request", null);
+
+  os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
+
+  update();
 }
 
-function makeTableCell(content, c) {
-    var td = document.createElement("td");
-    if (typeof content == "string")
-      content = document.createTextNode(content);
-    td.appendChild(content);
-    if (c)
-        td.setAttribute("class", c);
-
-    return td;
+function onUnload()
+{
+  var os = Cc["@mozilla.org/observer-service;1"].
+      getService(Ci.nsIObserverService);
+  os.removeObserver(ChildMemoryListener, "child-memory-reporter-update");
 }
 
-function makeAbbrNode(str, title) {
-    var abbr = document.createElement("abbr");
-    var text = document.createTextNode(str);
-    abbr.appendChild(text);
-    abbr.setAttribute("title", title);
+function ChildMemoryListener(aSubject, aTopic, aData)
+{
+  update();
+}
 
-    return abbr;
+function $(n)
+{
+  return document.getElementById(n);
 }
 
-function makeTableRow() {
-    var row = document.createElement("tr");
+/**
+ * Top-level function that does the work of generating the page.
+ */
+function update()
+{
+  // First, clear the page contents.  Necessary because update() might be
+  // called more than once due to ChildMemoryListener.
+  var content = $("content");
+  content.parentNode.replaceChild(content.cloneNode(false), content);
+  content = $("content");
 
-    for (var i = 0; i < arguments.length; ++i) {
-        var arg = arguments[i];
-        if (typeof(arg) == "string") {
-            row.appendChild(makeTableCell(arg));
-        } else if (arg.__proto__ == Array.prototype) {
-            row.appendChild(makeTableCell(makeAbbrNode(arg[0], arg[1])));
-        } else {
-            row.appendChild(arg);
-        }
-    }
-
-    return row;
-}
-
-function setTextContent(node, s) {
-    while (node.lastChild)
-        node.removeChild(node.lastChild);
-
-    node.appendChild(document.createTextNode(s));
-}
+  var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+      getService(Ci.nsIMemoryReporterManager);
 
-function formatNumber(n) {
-    var s = "";
-    var neg = false;
-    if (n < 0) {
-        neg = true;
-        n = -n;
+  // Process each memory reporter:
+  // - Make a copy of it into a sub-table indexed by its process.  Each copy
+  //   looks like this:
+  //
+  //     interface Tmr {
+  //       _tpath: string;
+  //       _description: string;
+  //       _memoryUsed: number;
+  //     }
+  //
+  // - The .path property is renamed ._tpath ("truncated path") in the copy
+  //   because the process name and ':' (if present) are removed.
+  // - Note that copying mr.memoryUsed (which calls a C++ function under the
+  //   IDL covers) to tmr._memoryUsed 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.
+  var tmrTable = {};
+  var e = mgr.enumerateReporters();
+  while (e.hasMoreElements()) {
+    var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    var process;
+    var tmr = {};
+    var i = mr.path.indexOf(':');
+    if (i === -1) {
+      process = "Main";
+      tmr._tpath = mr.path;
+    } else {
+      process = mr.path.slice(0, i);
+      tmr._tpath = mr.path.slice(i + 1);
     }
+    tmr._description = mr.description;
+    tmr._memoryUsed  = mr.memoryUsed;
 
-    do {
-        var k = n % 1000;
-        if (n > 999) {
-            if (k > 99)
-                s = k + s;
-            else if (k > 9)
-                s = "0" + k + s;
-            else
-                s = "00" + k + s;
-        } else {
-            s = k + s;
-        }
+    if (!tmrTable[process]) {
+      tmrTable[process] = {};
+    }
+    var tmrs = tmrTable[process];
+    if (tmrs[tmr._tpath]) {
+      // Already an entry;  must be a duplicated reporter.  This can
+      // happen legitimately.  Sum the sizes.
+      tmrs[tmr._tpath]._memoryUsed += tmr._memoryUsed;
+    } else {
+      tmrs[tmr._tpath] = tmr;
+    }
+  }
 
-        n = Math.floor(n / 1000);
-        if (n > 0)
-            s = "," + s;
-    } while (n > 0);
+  // Generate output for one process at a time.  Always start with the
+  // Main process.
+  var text = genProcessText("Main", tmrTable["Main"]);
+  for (var process in tmrTable) {
+    if (process !== "Main") {
+      text += genProcessText(process, tmrTable[process]);
+    }
+  }
 
-    return s;
+  // Generate verbosity option link at the bottom.
+  text += gVerbose
+        ? "<span class='option'><a href='about:memory'>Less verbose</a></span>"
+        : "<span class='option'><a href='about:memory?verbose'>More verbose</a></span>";
+
+  var div = document.createElement("div");
+  div.innerHTML = text;
+  content.appendChild(div);
 }
 
 /**
- * Updates the content of the document with the most current memory information.
+ * Generates the text for a single process.
+ *
+ * @param aProcess
+ *        The name of the process
+ * @param aTmrs
+ *        Table of Tmrs for this process
+ * @return The generated text
  */
-function updateMemoryStatus()
+function genProcessText(aProcess, aTmrs)
 {
-    // if we have the standard reporters for mapped/allocated, put
-    // those at the top
-    if ("malloc/mapped" in gMemReporters &&
-        "malloc/allocated" in gMemReporters)
+  /**
+   * From a list of memory reporters, builds a tree that mirrors the tree
+   * structure that will be shown as output.
+   *
+   * @param aTreeName
+   *        The name of the tree;  either "mapped" or "heap-used"
+   * @param aTreeRootTpath
+   *        The tpath of the top node in the tree
+   * @param aTreeRootDesc
+   *        The description of the top node in the tree
+   * @param aOtherDescTail
+   *        Extra description for the end of the "aTreeName/other" entry
+   * @param aOmitThresholdPerc
+   *        The threshold percentage;  entries that account for less than
+   *        this fraction are aggregated
+   * @return The built tree.  The tree nodes have this structure:
+   *         interface Node {
+   *           _name: string;
+   *           _description: string;
+   *           _memoryUsed: number;
+   *           _kids: [Node];
+   *         }
+   */
+  function buildTree(aTreeName, aTreeRootTpath, aTreeRootDesc, aOtherDescTail,
+                     aOmitThresholdPerc)
+  {
+    function findKid(aName, aKids)
     {
-        // committed is the total amount of memory that we've touched, that is that we have
-        // some kind of backing store for
-        setTextContent($("memMappedValue"),
-                       formatNumber(gMemReporters["malloc/mapped"][0].memoryUsed));
+      for (var i = 0; i < aKids.length; i++) {
+        if (aKids[i]._name === aName) {
+          return aKids[i];
+        }
+      }
+      return undefined;
+    }
 
-        // allocated is the amount of committed memory that we're actively using (i.e., that isn't free)
-        setTextContent($("memInUseValue"),
-                       formatNumber(gMemReporters["malloc/allocated"][0].memoryUsed));
-    } else {
-        $("memOverview").style.display = "none";
+    // We want to process all reporters that begin with 'aTreeName'.
+    // First we build the tree but only filling in '_name' and '_kids'.
+    var t = { _name:aTreeName, _kids:[] };
+    for (var _tpath in aTmrs) {
+      var tmr = aTmrs[_tpath];
+      if (tmr._tpath.slice(0, aTreeName.length + 1) === aTreeName + "/") {
+        var names = tmr._tpath.slice(aTreeName.length + 1).split('/');
+        var u = t;
+        for (var i = 0; i < names.length; i++) {
+          var name = names[i];
+          var uMatch = findKid(name, u._kids);
+          if (uMatch) {
+            u = uMatch;
+          } else {
+            var v = { _name:name, _kids:[] };
+            u._kids.push(v);
+            u = v;
+          }
+        }
+      }
     }
 
-    var mo = $("memOtherRows");
-    while (mo.lastChild)
-        mo.removeChild(mo.lastChild);
-
-    var otherCount = 0;
+    // Next, fill in '_description' and '_memoryUsed' for each node.  For
+    // interior nodes, '_memoryUsed' is computed by summing child nodes.
+    function fillInTree(aT, aPretpath)
+    {
+      var tpath = aPretpath ? aPretpath + '/' + aT._name : aT._name;
+      if (aT._kids.length === 0) {
+        aT._memoryUsed = getBytes(aTmrs, tpath);
+        aT._description = getDescription(aTmrs, tpath);
+      } else {
+        var bytes = 0;
+        for (var i = 0; i < aT._kids.length; i++) {
+          // Allow for -1 (ie. "unknown"), treat it like 0.
+          var b = fillInTree(aT._kids[i], tpath);
+          bytes += (b === -1 ? 0 : b);
+        }
+        aT._memoryUsed = bytes;
+        aT._description = "The sum of all entries below " + tpath + ".";
+      }
+      return aT._memoryUsed;
+    }
+    fillInTree(t, "");
 
-    for each (var reporters in gMemReporters) {
-        // There may be more than one reporter for each path.  We take the sum
-        // of them all for each description.
-        var total = 0;
-        reporters.forEach(function(reporter) {
-          total += reporter.memoryUsed;
-        });
+    // Add the "aTreeName/other" node, which is derived from existing
+    // nodes, then update the root node accordingly.  (But don't do this
+    // if the root node byte count is -1, ie. unknown).
+    var nonOtherBytes = t._memoryUsed;
+    var treeBytes = getBytes(aTmrs, aTreeRootTpath);
+    if (treeBytes !== -1) {
+      var otherBytes = treeBytes - nonOtherBytes;
+      var other = {
+        _name:"other",
+        _description:"All unclassified " + aTreeName + " memory." +
+                     aOtherDescTail,
+        _memoryUsed:otherBytes,
+        _kids:[]
+      };
+      t._kids.push(other);
+    }
+    t._memoryUsed = treeBytes;
+    t._description = aTreeRootDesc;
 
-        var row = makeTableRow([reporters[0].path, reporters[0].description],
-                               makeTableCell(formatNumber(total), "memValue"));
-
-        mo.appendChild(row);
-
-        otherCount++;
+    function shouldOmit(aBytes)
+    {
+      return !gVerbose &&
+             treeBytes !== -1 &&
+             (100 * aBytes / treeBytes) < aOmitThresholdPerc;
     }
 
-    if (otherCount == 0) {
-        var row = makeTableRow("No other information available.");
-        mo.appendChild(row);
+    /**
+     * Sort all kid nodes from largest to smallest and aggregate
+     * insignificant nodes.
+     *
+     * @param aT
+     *        The tree
+     */
+    function filterTree(aT)
+    {
+      var cmpTmrs = function(a, b) { return b._memoryUsed - a._memoryUsed };
+      aT._kids.sort(cmpTmrs);
+
+      for (var i = 0; i < aT._kids.length; i++) {
+        if (shouldOmit(aT._kids[i]._memoryUsed)) {
+          // This sub-tree is below the significance threshold
+          // Remove it and all remaining (smaller) sub-trees, and
+          // replace them with a single aggregate node.
+          var i0 = i;
+          var aggBytes = 0;
+          var aggNames = [];
+          for ( ; i < aT._kids.length; i++) {
+            aggBytes += aT._kids[i]._memoryUsed;
+            aggNames.push(aT._kids[i]._name);
+          }
+          aT._kids.splice(i0);
+          var n = i - i0;
+          var tmrSub = {
+            _name: "(" + n + " omitted)",
+            _description: "Omitted sub-trees: " + aggNames.join(", ") + ".",
+            _memoryUsed: aggBytes,
+            _kids:[]
+          };
+          aT._kids[i0] = tmrSub;
+          break;
+        }
+        filterTree(aT._kids[i]);
+      }
     }
+    filterTree(t);
+
+    return t;
+  }
+
+  var mappedOtherDescTail =
+      " This includes code and data segments, and thread stacks."
+  var mappedRootDesc = getDescription(aTmrs, "mapped");
+  // The threshold used here is much lower than the one for the heap-used
+  // tree, because the "mapped" total size is so much bigger relative to
+  // the interesting entries in the "mapped" tree.
+  var mappedTree = buildTree("mapped", "mapped", mappedRootDesc,
+                             mappedOtherDescTail, 0.01);
+
+  var heapUsedOtherDescTail = "";
+  var heapUsedRootDesc = "See mapped/heap/used above.";
+  var heapUsedTree = buildTree("heap-used", "mapped/heap/used",
+                               heapUsedRootDesc, heapUsedOtherDescTail, 0.1);
+
+  // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
+  var text = "";
+  text += "<h1>" + aProcess + " Process</h1>\n\n";
+  text += genTreeText(mappedTree, "Mapped Memory");
+  text += genTreeText(heapUsedTree, "Used Heap Memory");
+  text += genOtherText(aTmrs);
+  text += "<hr></hr>";
+  return text;
 }
 
 /**
- * Updates gMemReporters to contain all the known memory reporters.
+ * Converts a byte count to an appropriate string representation.
+ *
+ * @param aBytes
+ *        The byte count
+ * @return The string representation
  */
-function updateMemoryReporters()
+function formatBytes(aBytes)
 {
-    gMemReporters = [];
+  var unit = gVerbose ? "B" : "MB";
+
+  if (aBytes === -1) {
+    return "??? " + unit;
+  }
 
-    var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
-              getService(Ci.nsIMemoryReporterManager);
+  function formatInt(aN)
+  {
+    var neg = false;
+    if (aN < 0) {
+      neg = true;
+      aN = -aN;
+    }
+    var s = "";
+    while (true) {
+      var k = aN % 1000;
+      aN = Math.floor(aN / 1000);
+      if (aN > 0) {
+        if (k < 10) {
+          s = ",00" + k + s;
+        } else if (k < 100) {
+          s = ",0" + k + s;
+        } else {
+          s = "," + k + s;
+        }
+      } else {
+        s = k + s;
+        break;
+      }
+    }
+    return neg ? "-" + s : s;
+  }
+
+  var s;
+  if (gVerbose) {
+    s = formatInt(aBytes) + " " + unit;
+  } else {
+    var mbytes = (aBytes / (1024 * 1024)).toFixed(2);
+    var a = String(mbytes).split(".");
+    s = formatInt(a[0]) + "." + a[1] + " " + unit;
+  }
+  return s;
+}
 
-    var e = mgr.enumerateReporters();
-    while (e.hasMoreElements()) {
-        var reporter = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-        if (!gMemReporters[reporter.path]) {
-          gMemReporters[reporter.path] = [];
-        }
-        gMemReporters[reporter.path].push(reporter);
-    }
+/**
+ * Right-justifies a string in a field of a given width, padding as necessary
+ *
+ * @param aS
+ *        The string
+ * @param aN
+ *        The field width
+ * @param aC
+ *        The char used to pad
+ * @return The string representation
+ */
+function pad(aS, aN, aC)
+{
+  var padding = "";
+  var n2 = aN - aS.length;
+  for (var i = 0; i < n2; i++) {
+    padding += aC;
+  }
+  return padding + aS;
+}
+
+/**
+ * Gets the byte count for a particular memory reporter.
+ *
+ * @param aTmrs
+ *        Table of Tmrs for this process
+ * @param aTpath
+ *        The tpath of the memory reporter
+ * @return The byte count
+ */
+function getBytes(aTmrs, aTpath)
+{
+  var tmr = aTmrs[aTpath];
+  if (tmr) {
+    var bytes = tmr._memoryUsed;
+    tmr.done = true;
+    return bytes;
+  }
+  // Nb: this should never occur;  "mapped" and "mapped/heap/used" should
+  // always be registered, and all other tpaths have been extracted from
+  // aTmrs and so the lookup will succeed.  Return an obviously wrong
+  // number that will likely be noticed.
+  return -2 * 1024 * 1024;
+}
+
+/**
+ * Gets the description for a particular memory reporter.
+ *
+ * @param aTmrs
+ *        Table of Tmrs for this process
+ * @param aTpath
+ *        The tpath of the memory reporter
+ * @return The description
+ */
+function getDescription(aTmrs, aTpath)
+{
+  var r = aTmrs[aTpath];
+  return r ? r._description : "???";
+}
+
+function genMrValueText(aValue)
+{
+  return "<span class='mrValue'>" + aValue + "</span>";
+}
+
+function genMrNameText(aDesc, aName)
+{
+  return "-- <span class='mrName' title=\"" + aDesc + "\">" +
+         aName + "</span>\n";
 }
 
-function ChildMemoryListener(subject, topic, data) {
-  updateMemoryReporters();
-  updateMemoryStatus();
+/**
+ * Generates the text for a particular tree, including its heading.
+ *
+ * @param aT
+ *        The tree
+ * @param aTreeName
+ *        The tree's name
+ * @return The generated text
+ */
+function genTreeText(aT, aTreeName)
+{
+  var treeBytes = aT._memoryUsed;
+  var treeBytesLength = formatBytes(treeBytes).length;
+
+  /**
+   * Generates the text for a particular tree, without a heading.
+   *
+   * @param aT
+   *        The tree
+   * @param aIndentGuide
+   *        Records what indentation is required for this tree.  It has one
+   *        entry per level of indentation.  For each entry, ._isLastKid
+   *        records whether the node in question is the last child, and
+   *        ._depth records how many chars of indentation are required.
+   * @param aParentBytesLength
+   *        The length of the formatted byte count of the top node in the tree
+   * @return The generated text
+   */
+  function genTreeText2(aT, aIndentGuide, aParentBytesLength)
+  {
+    function repeatStr(aC, aN)
+    {
+      var s = "";
+      for (var i = 0; i < aN; i++) {
+        s += aC;
+      }
+      return s;
+    }
+
+    // Generate the indent.  There's a subset of the Unicode "light"
+    // box-drawing chars that are widely implemented in terminals, and
+    // this code sticks to that subset to maximize the chance that
+    // cutting and pasting about:memory output to a terminal will work
+    // correctly:
+    const kHorizontal       = "\u2500",
+          kVertical         = "\u2502",
+          kUpAndRight       = "\u2514",
+          kVerticalAndRight = "\u251c";
+    var indent = "<span class='treeLine'>";
+    if (aIndentGuide.length > 0) {
+      for (var i = 0; i < aIndentGuide.length - 1; i++) {
+        indent += aIndentGuide[i]._isLastKid ? " " : kVertical;
+        indent += repeatStr(" ", aIndentGuide[i]._depth - 1);
+      }
+      indent += aIndentGuide[i]._isLastKid ? kUpAndRight : kVerticalAndRight;
+      indent += repeatStr(kHorizontal, aIndentGuide[i]._depth - 1);
+    }
+
+    // Indent more if this entry is narrower than its parent, and update
+    // aIndentGuide accordingly.
+    var tMemoryUsedStr = formatBytes(aT._memoryUsed);
+    var tBytesLength = tMemoryUsedStr.length;
+    var extraIndentLength = Math.max(aParentBytesLength - tBytesLength, 0);
+    if (extraIndentLength > 0) {
+      for (var i = 0; i < extraIndentLength; i++) {
+        indent += kHorizontal;
+      }
+      aIndentGuide[aIndentGuide.length - 1]._depth += extraIndentLength;
+    }
+    indent += "</span>";
+
+    // Generate the percentage.
+    var perc = "";
+    if (treeBytes !== -1) {
+      if (aT._memoryUsed === treeBytes) {
+        perc = "100.0";
+      } else {
+        perc = (100 * aT._memoryUsed / treeBytes).toFixed(2);
+        perc = pad(perc, 5, '0');
+      }
+      perc = "<span class='mrPerc'>(" + perc + "%)</span> ";
+    }
+
+    var text = indent + genMrValueText(tMemoryUsedStr) + " " + perc +
+               genMrNameText(aT._description, aT._name);
+
+    for (var i = 0; i < aT._kids.length; i++) {
+      // 3 is the standard depth, the callee adjusts it if necessary.
+      aIndentGuide.push({ _isLastKid:(i === aT._kids.length - 1), _depth:3 });
+      text += genTreeText2(aT._kids[i], aIndentGuide, tBytesLength);
+      aIndentGuide.pop();
+    }
+    return text;
+  }
+
+  var text = genTreeText2(aT, [], treeBytesLength);
+  // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
+  return "<h2>" + aTreeName + "</h2>\n<pre>" + text + "</pre>\n";
 }
 
-
-function doLoad()
+/**
+ * Generates the text for the "Other Measurements" section.
+ *
+ * @param aTmrs
+ *        Table of Tmrs for this process
+ * @return The generated text
+ */
+function genOtherText(aTmrs)
 {
-    var os = Components.classes["@mozilla.org/observer-service;1"].
-        getService(Components.interfaces.nsIObserverService);
-    os.notifyObservers(null, "child-memory-reporter-request", null);
+  // Get the biggest not-yet-printed value, to determine the field width
+  // for all these entries.  These should all be "other" values, assuming
+  // all paths are well-formed.
+  var maxBytes = 0;
+  for (var tpath in aTmrs) {
+    var tmr = aTmrs[tpath];
+    if (!tmr.done && tmr._memoryUsed > maxBytes) {
+      maxBytes = tmr._memoryUsed;
+    }
+  }
 
-    os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
+  // Generate text for the not-yet-yet-printed values.
+  var maxBytesLength = formatBytes(maxBytes).length;
+  var text = "";
+  for (var tpath in aTmrs) {
+    var tmr = aTmrs[tpath];
+    if (!tmr.done) {
+      text += genMrValueText(
+                pad(formatBytes(tmr._memoryUsed), maxBytesLength, ' ')) + " ";
+      text += genMrNameText(tmr._description, tmr._tpath);
+    }
+  }
 
-    updateMemoryReporters();
-    updateMemoryStatus();
+  // Nb: the newlines give nice spacing if we cut+paste into a text buffer.
+  return "<h2>Other Measurements</h2>\n<pre>" + text + "</pre>\n";
 }
 
-function doUnload()
-{
-    var os = Components.classes["@mozilla.org/observer-service;1"].
-        getService(Components.interfaces.nsIObserverService);
-    os.removeObserver(ChildMemoryListener, "child-memory-reporter-update");
-    
-}
--- a/toolkit/components/aboutmemory/content/aboutMemory.xhtml
+++ b/toolkit/components/aboutmemory/content/aboutMemory.xhtml
@@ -16,16 +16,17 @@
    - The Original Code is about:memory.
    -
    - The Initial Developer of the Original Code is the Mozilla Foundation.
    - Portions created by the Initial Developer are Copyright (C) 2009
    - the Initial Developer. All Rights Reserved.
    -
    - Contributor(s):
    -   Vladimir Vukicevic <vladimir@pobox.com>
+   -   Nicholas Nethercote <nnethercote@mozilla.com>
    -
    - Alternatively, the contents of this file may be used under the terms of
    - either the GNU General Public License Version 2 or later (the "GPL"), or
    - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
    - in which case the provisions of the GPL or the LGPL are applicable instead
    - of those above. If you wish to allow use of your version of this file only
    - under the terms of either the GPL or the LGPL, and not to allow others to
    - use your version of this file under the terms of the MPL, indicate your
@@ -34,50 +35,17 @@
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>about:memory</title>
-
-    <link rel="stylesheet" href="chrome://global/skin/aboutMemory.css"
-          type="text/css"/>
-
-    <script type="text/javascript"
-            src="chrome://global/content/aboutMemory.js"/>
+    <link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>
+    <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
+    <script type="text/javascript" src="chrome://global/content/aboutMemory.js"/>
   </head>
 
-  <body onload="doLoad()" onunload="doUnload()">
-    <h1>Memory Usage</h1>
-
-    <div id="memOverview" class="memOverview memBox">
-      <h2>Overview</h2>
-      <table border="0">
-	<tbody>
-	  <tr>
-            <td width="100%">Memory mapped:</td>
-            <td id="memMappedValue" class="memValue"></td>
-          </tr>
-	  <tr>
-            <td>Memory in use:</td>
-            <td id="memInUseValue" class="memValue"></td>
-          </tr>
-	</tbody>
-      </table>
-    </div>
-
-    <div id="memOther" class="memOther memBox">
-      <h2>Other Information</h2>
-      <table border="0">
-	<thead>
-	  <tr>
-            <th style="text-align: left" width="100%">Description</th>
-            <th>Value</th>
-          </tr>
-	</thead>
-	<tbody id="memOtherRows">
-	</tbody>
-      </table>
-    </div>
-  </body>
+  <!-- No newline before the div element!  This avoids extraneous spaces when
+       pasting the entire output after selecting it with Ctrl-a. -->
+  <body onload="onLoad()" onunload="onUnload()"><div id="content"></div></body>
 </html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutmemory/tests/Makefile.in
@@ -0,0 +1,51 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla.org.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#     Boris Zbarsky <bzbarsky@mit.edu>  (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir	= toolkit/components/aboutmemory/tests
+
+include $(DEPTH)/config/autoconf.mk
+
+DIRS = \
+	chrome \
+	$(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutmemory/tests/chrome/Makefile.in
@@ -0,0 +1,53 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla.org.
+# Portions created by the Initial Developer are Copyright (C) 2005
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = toolkit/components/aboutmemory/tests/chrome
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_CHROME_FILES	= \
+		test_aboutmemory.xul \
+		$(NULL)
+
+libs:: $(_CHROME_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
+
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutmemory/tests/chrome/test_aboutmemory.xul
@@ -0,0 +1,267 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="about:memory"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/MochiKit/packed.js"/>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml"></body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+  var mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+            getService(Ci.nsIMemoryReporterManager);
+
+  // Remove all the real reporters;  save them to restore at the end.
+  var e = mgr.enumerateReporters(); 
+  var realReporters = [];
+  while (e.hasMoreElements()) {
+    var mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    mgr.unregisterReporter(mr);
+    realReporters.push(mr);
+  }
+
+  // Setup various fake-but-deterministic reporters.
+  const KB = 1024;
+  const MB = KB * KB;
+  fakeReporters = [
+    { path: "mapped",               memoryUsed: 1000 * MB },
+    { path: "mapped/heap/used",     memoryUsed:  500 * MB },
+    { path: "mapped/heap/unused",   memoryUsed:  100 * MB },
+    { path: "mapped/a",             memoryUsed:  222 * MB },
+    { path: "heap-used/a",          memoryUsed:   99 * MB },
+    { path: "heap-used/b/a",        memoryUsed:   80 * MB },
+    { path: "heap-used/b/b",        memoryUsed:   75 * MB },
+    { path: "heap-used/b/c/a",      memoryUsed:   44 * MB },
+    { path: "heap-used/b/c/b",      memoryUsed:   33 * MB }, // aggregated
+    { path: "heap-used/c",          memoryUsed:  123 * MB },
+    { path: "heap-used/d",          memoryUsed:  499 * KB }, // aggregated
+    { path: "heap-used/e",          memoryUsed:  100 * KB }, // aggregated
+    { path: "heap-used/f/g/h/i",    memoryUsed:   20 * MB },
+    { path: "other1",               memoryUsed:  111 * MB },
+    { path: "other2",               memoryUsed:  222 * MB },
+
+    { path: "2nd:mapped",           memoryUsed: 1000 * MB },
+    { path: "2nd:mapped/heap/used", memoryUsed:  500 * MB },
+    { path: "2nd:mapped/a/b/c",     memoryUsed:  499 * MB },
+    { path: "2nd:heap-used/a",      memoryUsed:  400 * MB },
+    { path: "2nd:other1",           memoryUsed:  777 * MB },
+
+    // -1 means "don't know";  this should be handled gracefully for
+    // "mapped" and "mapped/heap/used".
+    { path: "3rd:mapped",           memoryUsed: -1        },
+    { path: "3rd:mapped/heap/used", memoryUsed: -1        },
+    { path: "3rd:mapped/a/b",       memoryUsed:  333 * MB },
+    { path: "3rd:heap-used/a/b",    memoryUsed:  444 * MB },
+    { path: "3rd:other1",           memoryUsed:  555 * MB }
+  ];
+  for (var i = 0; i < fakeReporters.length; i++) {
+    mgr.registerReporter(fakeReporters[i]);
+  }
+  ]]>
+  </script>
+
+  <iframe id="amFrame"  src="about:memory"></iframe>
+  <iframe id="amvFrame" src="about:memory?verbose"></iframe>
+
+  <script type="application/javascript">
+  <![CDATA[
+  var amExpectedText =
+"\
+Main Process\n\
+\n\
+Mapped Memory\n\
+1,000.00 MB (100.0%) -- mapped\n\
+├────600.00 MB (60.00%) -- heap\n\
+│    ├──500.00 MB (50.00%) -- used\n\
+│    └──100.00 MB (10.00%) -- unused\n\
+├────222.00 MB (22.20%) -- a\n\
+└────178.00 MB (17.80%) -- other\n\
+\n\
+Used Heap Memory\n\
+500.00 MB (100.0%) -- heap-used\n\
+├──232.00 MB (46.40%) -- b\n\
+│  ├───80.00 MB (16.00%) -- a\n\
+│  ├───77.00 MB (15.40%) -- c\n\
+│  │   ├──44.00 MB (08.80%) -- a\n\
+│  │   └──33.00 MB (06.60%) -- b\n\
+│  └───75.00 MB (15.00%) -- b\n\
+├──123.00 MB (24.60%) -- c\n\
+├───99.00 MB (19.80%) -- a\n\
+├───25.42 MB (05.08%) -- other\n\
+├───20.00 MB (04.00%) -- f\n\
+│   └──20.00 MB (04.00%) -- g\n\
+│      └──20.00 MB (04.00%) -- h\n\
+│         └──20.00 MB (04.00%) -- i\n\
+└────0.58 MB (00.12%) -- (2 omitted)\n\
+\n\
+Other Measurements\n\
+111.00 MB -- other1\n\
+222.00 MB -- other2\n\
+\n\
+2nd Process\n\
+\n\
+Mapped Memory\n\
+1,000.00 MB (100.0%) -- mapped\n\
+├────500.00 MB (50.00%) -- heap\n\
+│    └──500.00 MB (50.00%) -- used\n\
+├────499.00 MB (49.90%) -- a\n\
+│    └──499.00 MB (49.90%) -- b\n\
+│       └──499.00 MB (49.90%) -- c\n\
+└──────1.00 MB (00.10%) -- other\n\
+\n\
+Used Heap Memory\n\
+500.00 MB (100.0%) -- heap-used\n\
+├──400.00 MB (80.00%) -- a\n\
+└──100.00 MB (20.00%) -- other\n\
+\n\
+Other Measurements\n\
+777.00 MB -- other1\n\
+\n\
+3rd Process\n\
+\n\
+Mapped Memory\n\
+??? MB -- mapped\n\
+├──333.00 MB -- a\n\
+│  └──333.00 MB -- b\n\
+└──0.00 MB -- heap\n\
+   └───??? MB -- used\n\
+\n\
+Used Heap Memory\n\
+??? MB -- heap-used\n\
+└──444.00 MB -- a\n\
+   └──444.00 MB -- b\n\
+\n\
+Other Measurements\n\
+555.00 MB -- other1\n\
+\n\
+";
+
+  var amvExpectedText =
+"\
+Main Process\n\
+\n\
+Mapped Memory\n\
+1,048,576,000 B (100.0%) -- mapped\n\
+├────629,145,600 B (60.00%) -- heap\n\
+│    ├──524,288,000 B (50.00%) -- used\n\
+│    └──104,857,600 B (10.00%) -- unused\n\
+├────232,783,872 B (22.20%) -- a\n\
+└────186,646,528 B (17.80%) -- other\n\
+\n\
+Used Heap Memory\n\
+524,288,000 B (100.0%) -- heap-used\n\
+├──243,269,632 B (46.40%) -- b\n\
+│  ├───83,886,080 B (16.00%) -- a\n\
+│  ├───80,740,352 B (15.40%) -- c\n\
+│  │   ├──46,137,344 B (08.80%) -- a\n\
+│  │   └──34,603,008 B (06.60%) -- b\n\
+│  └───78,643,200 B (15.00%) -- b\n\
+├──128,974,848 B (24.60%) -- c\n\
+├──103,809,024 B (19.80%) -- a\n\
+├───26,649,600 B (05.08%) -- other\n\
+├───20,971,520 B (04.00%) -- f\n\
+│   └──20,971,520 B (04.00%) -- g\n\
+│      └──20,971,520 B (04.00%) -- h\n\
+│         └──20,971,520 B (04.00%) -- i\n\
+├──────510,976 B (00.10%) -- d\n\
+└──────102,400 B (00.02%) -- e\n\
+\n\
+Other Measurements\n\
+116,391,936 B -- other1\n\
+232,783,872 B -- other2\n\
+\n\
+2nd Process\n\
+\n\
+Mapped Memory\n\
+1,048,576,000 B (100.0%) -- mapped\n\
+├────524,288,000 B (50.00%) -- heap\n\
+│    └──524,288,000 B (50.00%) -- used\n\
+├────523,239,424 B (49.90%) -- a\n\
+│    └──523,239,424 B (49.90%) -- b\n\
+│       └──523,239,424 B (49.90%) -- c\n\
+└──────1,048,576 B (00.10%) -- other\n\
+\n\
+Used Heap Memory\n\
+524,288,000 B (100.0%) -- heap-used\n\
+├──419,430,400 B (80.00%) -- a\n\
+└──104,857,600 B (20.00%) -- other\n\
+\n\
+Other Measurements\n\
+814,743,552 B -- other1\n\
+\n\
+3rd Process\n\
+\n\
+Mapped Memory\n\
+??? B -- mapped\n\
+├──349,175,808 B -- a\n\
+│  └──349,175,808 B -- b\n\
+└────0 B -- heap\n\
+     └──??? B -- used\n\
+\n\
+Used Heap Memory\n\
+??? B -- heap-used\n\
+└──465,567,744 B -- a\n\
+   └──465,567,744 B -- b\n\
+\n\
+Other Measurements\n\
+581,959,680 B -- other1\n\
+\n\
+"
+
+  function finish()
+  {
+    // Unregister fake reporters, re-register the real 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 < realReporters.length; i++) {
+      mgr.registerReporter(realReporters[i]);
+    }
+    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.
+  function test(aFrame, aExpectedText, aNext) {
+    document.querySelector("#" + aFrame).focus();
+    SimpleTest.waitForClipboard(aExpectedText,
+      function() {
+        synthesizeKey("A", {accelKey: true});
+        synthesizeKey("C", {accelKey: true});
+      },
+      aNext,
+      function() {
+        ok(false, "pasted text doesn't match for " + aFrame);
+        finish();
+      }
+    );
+  }
+
+  addLoadEvent(function() {
+    test(
+      "amFrame",
+      amExpectedText,
+      function() {
+        test(
+          "amvFrame",
+          amvExpectedText,
+          function() {
+            finish()
+          }
+        )
+      }
+    );
+  });
+  SimpleTest.waitForExplicitFinish();
+  ]]>
+  </script>
+</window>
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -39,19 +39,49 @@
 #include "nsISupports.idl"
 
 interface nsISimpleEnumerator;
 
 [scriptable, uuid(d298b942-3e66-4cd3-9ff5-46abc69147a7)]
 interface nsIMemoryReporter : nsISupports
 {
   /*
-   * The path that this memory usage should be reported under.
+   * The path that this memory usage should be reported under.  Paths can
+   * begin with a process name plus a colon, eg "Content:", but this is not
+   * necessary for the main process.  After the process name, paths are
+   * '/'-delimited, eg. "a/b/c".  There are three categories of paths.
+   *
+   * - Paths starting with "mapped" represent non-overlapping regions of mapped
+   *   memory.  Each one can be viewed as representing a path in a tree from
+   *   the root node ("mapped") to a leaf node.  The one exception is the
+   *   special "mapped" path which represents the root of that tree.  So, for
+   *   example, "mapped/heap/used", "mapped/heap/unused",
+   *   "mapped/js/mjit-code", mapped/js/tjit-code", plus the special "mapped"
+   *   path, define this tree:
    *
-   * Normally "/"-delimited for organization.
+   *     mapped
+   *     |--heap
+   *     |  |--used
+   *     |  \--unused
+   *     \--js
+   *        |--mjit-code
+   *        \--tjit-code
+   *
+   *   "mapped/heap" would not be a valid path in this example because "heap"
+   *   is not a leaf node.
+   *
+   * - Paths starting with "heap-used" represent non-overlapping regions of
+   *   used heap memory.  The "mapped" rules above apply equally here.  The
+   *   exceptional path for this tree is "mapped/heap/used" (and so this tree
+   *   is a sub-tree of the "mapped" tree).  When shown in about:memory, this
+   *   tree's root node is given the synonym "heap-used".
+   *
+   * - All other paths represent cross-cuttings memory regions, ie. ones that
+   *   may overlap arbitrarily with regions in the "mapped" and "heap-used"
+   *   trees.
    */
   readonly attribute string path;
 
   /*
    * A human-readable description of this memory usage report
    */
   readonly attribute string description;
 
--- a/xpcom/base/nsMemoryReporterManager.cpp
+++ b/xpcom/base/nsMemoryReporterManager.cpp
@@ -36,16 +36,145 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsCOMPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsMemoryReporterManager.h"
 #include "nsArrayEnumerator.h"
 
+#if defined(XP_LINUX)
+
+#include <unistd.h>
+static PRInt64 GetProcSelfStatmField(int n)
+{
+    // There are more than two fields, but we're only interested in the first
+    // two.
+    static const int MAX_FIELD = 2;
+    size_t fields[MAX_FIELD];
+    NS_ASSERTION(n < MAX_FIELD, "bad field number");
+    FILE *f = fopen("/proc/self/statm", "r");
+    if (f) {
+        int nread = fscanf(f, "%lu %lu", &fields[0], &fields[1]);
+        fclose(f);
+        return (PRInt64) ((nread == MAX_FIELD) ? fields[n]*getpagesize() : -1);
+    }
+    return (PRInt64) -1;
+}
+
+static PRInt64 GetMapped(void *)
+{
+    return GetProcSelfStatmField(0);
+}
+
+static PRInt64 GetResident(void *)
+{
+    return GetProcSelfStatmField(1);
+}
+
+#elif defined(XP_MACOSX)
+
+#include <mach/mach_init.h>
+#include <mach/task.h>
+
+static bool GetTaskBasicInfo(struct task_basic_info *ti)
+{
+    mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
+    kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO,
+                                 (task_info_t)ti, &count);
+    return kr == KERN_SUCCESS;
+}
+
+// Getting a sensible "mapped" number on Mac is difficult.  The following is
+// easy and (I think) corresponds to the VSIZE figure reported by 'top' and
+// 'ps', but that includes shared memory and so is always absurdly high.  This
+// doesn't really matter as the "mapped" figure is never that useful.
+static PRInt64 GetMapped(void *)
+{
+    task_basic_info ti;
+    return (PRInt64) (GetTaskBasicInfo(&ti) ? ti.virtual_size : -1);
+}
+
+static PRInt64 GetResident(void *)
+{
+    task_basic_info ti;
+    return (PRInt64) (GetTaskBasicInfo(&ti) ? ti.resident_size : -1);
+}
+
+#elif defined(XP_WIN)
+
+#include <windows.h>
+#include <psapi.h>
+
+static PRInt64 GetMapped(void *)
+{
+#if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN
+  PROCESS_MEMORY_COUNTERS_EX pmcex;
+  pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
+
+  if (!GetProcessMemoryInfo(GetCurrentProcess(),
+                            (PPROCESS_MEMORY_COUNTERS) &pmcex, sizeof(pmcex)))
+      return (PRInt64) -1;
+
+  return pmcex.PrivateUsage;
+#else
+  return (PRInt64) -1;
+#endif
+}
+
+static PRInt64 GetResident(void *)
+{
+  PROCESS_MEMORY_COUNTERS pmc;
+  pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
+
+  if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
+      return (PRInt64) -1;
+
+  return pmc.WorkingSetSize;
+}
+
+#else
+
+static PRInt64 GetMapped(void *)
+{
+    return (PRInt64) -1;
+}
+
+static PRInt64 GetResident(void *)
+{
+    return (PRInt64) -1;
+}
+
+#endif
+
+// aboutMemory.js requires that this reporter always be registered, even if the
+// byte count returned is always -1.
+NS_MEMORY_REPORTER_IMPLEMENT(Mapped,
+    "mapped",
+    "Memory mapped by the process. Note that 'resident' is a better measure "
+    "of memory resources used by the process. "
+    "On Windows (XP SP2 or later only) this is the private usage and does not "
+    "include memory shared with other processes. "
+    "On Mac and Linux this is the vsize figure as reported by 'top' or 'ps' "
+    "and includes memory shared with other processes;  on Mac the amount of "
+    "shared memory can be very high and so this figure is of limited use.",
+    GetMapped,
+    NULL)
+
+NS_MEMORY_REPORTER_IMPLEMENT(Resident,
+    "resident",
+    "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.",
+    GetResident,
+    NULL)
+
 /**
  ** 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).
  **/
 
 #if defined(MOZ_MEMORY)
 #  if defined(XP_WIN) || defined(SOLARIS) || defined(ANDROID)
@@ -61,194 +190,165 @@
 extern "C" {
 extern void jemalloc_stats(jemalloc_stats_t* stats)
   NS_VISIBILITY_DEFAULT __attribute__((weak));
 }
 #  endif  // XP_LINUX
 #endif  // MOZ_MEMORY
 
 #if HAVE_JEMALLOC_STATS
-#  define HAVE_MALLOC_REPORTERS 1
 
-PRInt64 getMallocMapped(void *) {
-    jemalloc_stats_t stats;
-    jemalloc_stats(&stats);
-    return (PRInt64) stats.mapped;
-}
-
-PRInt64 getMallocAllocated(void *) {
+static PRInt64 GetMappedHeapUsed(void *)
+{
     jemalloc_stats_t stats;
     jemalloc_stats(&stats);
     return (PRInt64) stats.allocated;
 }
 
-PRInt64 getMallocCommitted(void *) {
+static PRInt64 GetMappedHeapUnused(void *)
+{
+    jemalloc_stats_t stats;
+    jemalloc_stats(&stats);
+    return (PRInt64) (stats.mapped - stats.allocated);
+}
+
+static PRInt64 GetHeapCommitted(void *)
+{
     jemalloc_stats_t stats;
     jemalloc_stats(&stats);
     return (PRInt64) stats.committed;
 }
 
-PRInt64 getMallocDirty(void *) {
+static PRInt64 GetHeapDirty(void *)
+{
     jemalloc_stats_t stats;
     jemalloc_stats(&stats);
     return (PRInt64) stats.dirty;
 }
 
+NS_MEMORY_REPORTER_IMPLEMENT(HeapCommitted,
+                             "heap-committed",
+                             "Memory mapped by the heap allocator that is "
+                             "committed, i.e. in physical memory or paged to "
+                             "disk.",
+                             GetHeapCommitted,
+                             NULL)
+
+NS_MEMORY_REPORTER_IMPLEMENT(HeapDirty,
+                             "heap-dirty",
+                             "Memory mapped by the heap allocator that is "
+                             "committed but unused.",
+                             GetHeapDirty,
+                             NULL)
+
 #elif defined(XP_MACOSX) && !defined(MOZ_MEMORY)
-#define HAVE_MALLOC_REPORTERS 1
 #include <malloc/malloc.h>
 
-static PRInt64 getMallocAllocated(void *) {
+static PRInt64 GetMappedHeapUsed(void *)
+{
     struct mstats stats = mstats();
     return (PRInt64) stats.bytes_used;
 }
 
-static PRInt64 getMallocMapped(void *) {
+static PRInt64 GetMappedHeapUnused(void *)
+{
     struct mstats stats = mstats();
-    return (PRInt64) stats.bytes_total;
+    return (PRInt64) (stats.bytes_total - stats.bytes_used);
 }
 
-static PRInt64 getMallocDefaultCommitted(void *) {
+static PRInt64 GetHeapZone0Committed(void *)
+{
     malloc_statistics_t stats;
     malloc_zone_statistics(malloc_default_zone(), &stats);
     return stats.size_in_use;
 }
 
-static PRInt64 getMallocDefaultAllocated(void *) {
+static PRInt64 GetHeapZone0Used(void *)
+{
     malloc_statistics_t stats;
     malloc_zone_statistics(malloc_default_zone(), &stats);
     return stats.size_allocated;
 }
 
-#endif
-
-
-#ifdef HAVE_MALLOC_REPORTERS
-NS_MEMORY_REPORTER_IMPLEMENT(MallocAllocated,
-                             "malloc/allocated",
-                             "Malloc bytes allocated (in use by application)",
-                             getMallocAllocated,
-                             NULL)
-
-NS_MEMORY_REPORTER_IMPLEMENT(MallocMapped,
-                             "malloc/mapped",
-                             "Malloc bytes mapped (not necessarily committed)",
-                             getMallocMapped,
+NS_MEMORY_REPORTER_IMPLEMENT(HeapZone0Committed,
+                             "heap-zone0-committed",
+                             "Memory mapped by the heap allocator that is "
+                             "committed in the default zone.",
+                             GetHeapZone0Committed,
                              NULL)
 
-#if defined(HAVE_JEMALLOC_STATS)
-NS_MEMORY_REPORTER_IMPLEMENT(MallocCommitted,
-                             "malloc/committed",
-                             "Malloc bytes committed (readable/writable)",
-                             getMallocCommitted,
-                             NULL)
-
-NS_MEMORY_REPORTER_IMPLEMENT(MallocDirty,
-                             "malloc/dirty",
-                             "Malloc bytes dirty (committed unused pages)",
-                             getMallocDirty,
+NS_MEMORY_REPORTER_IMPLEMENT(HeapZone0Used,
+                             "heap-zone0-used",
+                             "Memory mapped by the heap allocator in the "
+                             "default zone that is available for use by the "
+                             "application.",
+                             GetHeapZone0Used,
                              NULL)
-#elif defined(XP_MACOSX) && !defined(MOZ_MEMORY)
-NS_MEMORY_REPORTER_IMPLEMENT(MallocDefaultCommitted,
-                             "malloc/zone0/committed",
-                             "Malloc bytes committed (r/w) in default zone",
-                             getMallocDefaultCommitted,
-                             NULL)
+#else
 
-NS_MEMORY_REPORTER_IMPLEMENT(MallocDefaultAllocated,
-                             "malloc/zone0/allocated",
-                             "Malloc bytes allocated (in use) in default zone",
-                             getMallocDefaultAllocated,
-                             NULL)
-#endif
+static PRInt64 GetMappedHeapUsed(void *)
+{
+    return (PRInt64) -1;
+}
+
+static PRInt64 GetMappedHeapUnused(void *)
+{
+    return (PRInt64) -1;
+}
 
 #endif
 
-#ifdef XP_WIN
-#include <windows.h>
-#include <psapi.h>
-
-static PRInt64 GetWin32PrivateBytes(void *) {
-#if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN
-  PROCESS_MEMORY_COUNTERS_EX pmcex;
-  pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX);
-
-  if (!GetProcessMemoryInfo(GetCurrentProcess(),
-                            (PPROCESS_MEMORY_COUNTERS) &pmcex,
-                            sizeof(PROCESS_MEMORY_COUNTERS_EX)))
-    return 0;
-
-  return pmcex.PrivateUsage;
-#else
-  return 0;
-#endif
-}
+// aboutMemory.js requires that this reporter always be registered, even if the
+// byte count returned is always -1.
+NS_MEMORY_REPORTER_IMPLEMENT(MappedHeapUsed,
+    "mapped/heap/used",
+    "Memory mapped by the heap allocator that is available for use by the "
+    "application.  This may exceed the amount of memory requested by the "
+    "application due to the allocator rounding up request sizes.  "
+    "(The exact amount requested is not measured.) "
+    "This is usually the best figure for developers to focus on when trying "
+    "to reduce memory consumption.",
+    GetMappedHeapUsed,
+    NULL)
 
-static PRInt64 GetWin32WorkingSetSize(void *) {
-  PROCESS_MEMORY_COUNTERS pmc;
-  pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS);
-
-  if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
-      return 0;
-
-  return pmc.WorkingSetSize;
-}
-
-NS_MEMORY_REPORTER_IMPLEMENT(Win32WorkingSetSize,
-                             "win32/workingset",
-                             "Win32 working set size",
-                             GetWin32WorkingSetSize,
-                             nsnull);
-
-NS_MEMORY_REPORTER_IMPLEMENT(Win32PrivateBytes,
-                             "win32/privatebytes",
-                             "Win32 private bytes (cannot be shared with other processes).  (Available only on Windows XP SP2 or later.)",
-                             GetWin32PrivateBytes,
-                             nsnull);
-#endif
+NS_MEMORY_REPORTER_IMPLEMENT(MappedHeapUnused,
+    "mapped/heap/unused",
+    "Memory mapped by the heap allocator and not available for use by the "
+    "application.  This can grow large if the heap allocator is holding onto "
+    "memory that the application has freed.",
+    GetMappedHeapUnused,
+    NULL)
 
 /**
  ** nsMemoryReporterManager implementation
  **/
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsMemoryReporterManager, nsIMemoryReporterManager)
 
 NS_IMETHODIMP
 nsMemoryReporterManager::Init()
 {
 #if HAVE_JEMALLOC_STATS && defined(XP_LINUX)
     if (!jemalloc_stats)
         return NS_ERROR_FAILURE;
 #endif
-    /*
-     * Register our core reporters
-     */
+
 #define REGISTER(_x)  RegisterReporter(new NS_MEMORY_REPORTER_NAME(_x))
 
-    /*
-     * Register our core jemalloc/malloc reporters
-     */
-#ifdef HAVE_MALLOC_REPORTERS
-    REGISTER(MallocAllocated);
-    REGISTER(MallocMapped);
+    REGISTER(Mapped);
+    REGISTER(MappedHeapUsed);
+    REGISTER(MappedHeapUnused);
+    REGISTER(Resident);
 
 #if defined(HAVE_JEMALLOC_STATS)
-    REGISTER(MallocCommitted);
-    REGISTER(MallocDirty);
+    REGISTER(HeapCommitted);
+    REGISTER(HeapDirty);
 #elif defined(XP_MACOSX) && !defined(MOZ_MEMORY)
-    REGISTER(MallocDefaultCommitted);
-    REGISTER(MallocDefaultAllocated);
-#endif
-#endif
-
-#ifdef XP_WIN
-#if MOZ_WINSDK_TARGETVER >= MOZ_NTDDI_LONGHORN
-    REGISTER(Win32PrivateBytes);
-#endif
-    REGISTER(Win32WorkingSetSize);
+    REGISTER(HeapZone0Committed);
+    REGISTER(HeapZone0Used);
 #endif
 
     return NS_OK;
 }
 
 nsMemoryReporterManager::nsMemoryReporterManager()
   : mMutex("nsMemoryReporterManager::mMutex")
 {
@@ -257,78 +357,78 @@ nsMemoryReporterManager::nsMemoryReporte
 nsMemoryReporterManager::~nsMemoryReporterManager()
 {
 }
 
 NS_IMETHODIMP
 nsMemoryReporterManager::EnumerateReporters(nsISimpleEnumerator **result)
 {
     nsresult rv;
-    mozilla::MutexAutoLock autoLock(mMutex); 
+    mozilla::MutexAutoLock autoLock(mMutex);
     rv = NS_NewArrayEnumerator(result, mReporters);
     return rv;
 }
 
 NS_IMETHODIMP
 nsMemoryReporterManager::RegisterReporter(nsIMemoryReporter *reporter)
 {
-    mozilla::MutexAutoLock autoLock(mMutex); 
+    mozilla::MutexAutoLock autoLock(mMutex);
     if (mReporters.IndexOf(reporter) != -1)
         return NS_ERROR_FAILURE;
 
     mReporters.AppendObject(reporter);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMemoryReporterManager::UnregisterReporter(nsIMemoryReporter *reporter)
 {
-    mozilla::MutexAutoLock autoLock(mMutex); 
+    mozilla::MutexAutoLock autoLock(mMutex);
     if (!mReporters.RemoveObject(reporter))
         return NS_ERROR_FAILURE;
 
     return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS1(nsMemoryReporter, nsIMemoryReporter)
 
 nsMemoryReporter::nsMemoryReporter(nsCString& prefix,
                                    nsCString& path,
                                    nsCString& desc,
                                    PRInt64 memoryUsed)
 : mDesc(desc)
-, mMemoryUsed(memoryUsed) 
+, mMemoryUsed(memoryUsed)
 {
   if (!prefix.IsEmpty()) {
-    mPath.Append(prefix);
-    mPath.Append(NS_LITERAL_CSTRING(" - "));
+      mPath.Append(prefix);
+      mPath.Append(NS_LITERAL_CSTRING(":"));
   }
   mPath.Append(path);
 }
 
 nsMemoryReporter::~nsMemoryReporter()
 {
 }
 
 NS_IMETHODIMP nsMemoryReporter::GetPath(char **aPath)
 {
-  *aPath = strdup(mPath.get());
-  return NS_OK;
+    *aPath = strdup(mPath.get());
+    return NS_OK;
 }
 
 NS_IMETHODIMP nsMemoryReporter::GetDescription(char **aDescription)
 {
-  *aDescription = strdup(mDesc.get());
-  return NS_OK;
+    *aDescription = strdup(mDesc.get());
+    return NS_OK;
 }
 
 NS_IMETHODIMP nsMemoryReporter::GetMemoryUsed(PRInt64 *aMemoryUsed)
 {
-  *aMemoryUsed = mMemoryUsed;
-  return NS_OK;
+    *aMemoryUsed = mMemoryUsed;
+    return NS_OK;
 }
 
 
 NS_COM nsresult
 NS_RegisterMemoryReporter (nsIMemoryReporter *reporter)
 {
     nsCOMPtr<nsIMemoryReporterManager> mgr = do_GetService("@mozilla.org/memory-reporter-manager;1");
     if (mgr == nsnull)