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 68931 1f0635e935d9a56880ea2f9ad4e3afaa1c8437c3
parent 68930 dcdb0421464db15fbeae3f884fffbf2759ddaf96
child 68932 eb15fab87e7e7b2bdfcbf19a8648543be3f892ab
push id19803
push userCallek@gmail.com
push dateWed, 04 May 2011 00:34:10 +0000
treeherdermozilla-central@1f0635e935d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvlad, sdwilsh, dvander, gavin, ehsan, edwsmith, benjamin
bugs633653
milestone6.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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)