Bug 687724 (part 3) - Report JS memory consumption for compartments that are associated with |window| objects under "window-objects". r=bholley,jlebar,luke.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 05 Jul 2012 21:12:37 -0700
changeset 98491 e586043f5b58
parent 98490 7919de7e468c
child 98492 0158f2d0b32d
push id11506
push usernnethercote@mozilla.com
push dateFri, 06 Jul 2012 05:52:14 +0000
treeherdermozilla-inbound@0158f2d0b32d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, jlebar, luke
bugs687724
milestone16.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 687724 (part 3) - Report JS memory consumption for compartments that are associated with |window| objects under "window-objects". r=bholley,jlebar,luke.
dom/base/nsWindowMemoryReporter.cpp
dom/workers/WorkerPrivate.cpp
js/public/MemoryMetrics.h
js/src/MemoryMetrics.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/xpconnect/src/Makefile.in
js/xpconnect/src/XPCJSMemoryReporter.h
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcpublic.h
toolkit/components/aboutmemory/tests/test_memoryReporters.xul
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -5,16 +5,17 @@
 
 #include "nsWindowMemoryReporter.h"
 #include "nsGlobalWindow.h"
 #include "nsIEffectiveTLDService.h"
 #include "mozilla/Services.h"
 #include "mozilla/Preferences.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
+#include "XPCJSMemoryReporter.h"
 
 using namespace mozilla;
 
 nsWindowMemoryReporter::nsWindowMemoryReporter()
   : mCheckForGhostWindowsCallbackPending(false)
 {
   mDetachedWindows.Init();
 }
@@ -96,20 +97,24 @@ AppendWindowURI(nsGlobalWindow *aWindow,
     // If we're unable to find a URI, we're dealing with a chrome window with
     // no document in it (or somesuch), so we call this a "system window".
     aStr += NS_LITERAL_CSTRING("[system]");
   }
 }
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(DOMStyleMallocSizeOf, "windows")
 
+// The key is the window ID.
+typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths;
+
 static nsresult
 CollectWindowReports(nsGlobalWindow *aWindow,
                      nsWindowSizes *aWindowTotalSizes,
                      nsTHashtable<nsUint64HashKey> *aGhostWindowIDs,
+                     WindowPaths *aWindowPaths,
                      nsIMemoryMultiReporterCallback *aCb,
                      nsISupports *aClosure)
 {
   nsCAutoString windowPath("explicit/window-objects/");
 
   // Avoid calling aWindow->GetTop() if there's no outer window.  It will work
   // just fine, but will spew a lot of warnings.
   nsGlobalWindow *top = NULL;
@@ -135,16 +140,19 @@ CollectWindowReports(nsGlobalWindow *aWi
       windowPath += NS_LITERAL_CSTRING("top(none)/detached/");
     }
   }
 
   windowPath += NS_LITERAL_CSTRING("window(");
   AppendWindowURI(aWindow, windowPath);
   windowPath += NS_LITERAL_CSTRING(")");
 
+  // Remember the path for later.
+  aWindowPaths->Put(aWindow->WindowID(), windowPath);
+
 #define REPORT(_pathTail, _amount, _desc)                                     \
   do {                                                                        \
     if (_amount > 0) {                                                        \
         nsCAutoString path(windowPath);                                       \
         path += _pathTail;                                                    \
         nsresult rv;                                                          \
         rv = aCb->Callback(EmptyCString(), path, nsIMemoryReporter::KIND_HEAP,\
                       nsIMemoryReporter::UNITS_BYTES, _amount,                \
@@ -289,24 +297,33 @@ nsWindowMemoryReporter::CollectReports(n
   nsTHashtable<nsUint64HashKey> ghostWindows;
   ghostWindows.Init();
   CheckForGhostWindows(&ghostWindows);
 
   nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(
     NS_EFFECTIVETLDSERVICE_CONTRACTID);
   NS_ENSURE_STATE(tldService);
 
+  WindowPaths windowPaths;
+  windowPaths.Init();
+
   // Collect window memory usage.
   nsWindowSizes windowTotalSizes(NULL);
   for (PRUint32 i = 0; i < windows.Length(); i++) {
     nsresult rv = CollectWindowReports(windows[i], &windowTotalSizes,
-                                       &ghostWindows, aCb, aClosure);
+                                       &ghostWindows, &windowPaths,
+                                       aCb, aClosure);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
+  // Report JS memory usage.  We do this from here because the JS memory
+  // multi-reporter needs to be passed |windowPaths|.
+  nsresult rv = xpc::JSMemoryMultiReporter::CollectReports(&windowPaths, aCb, aClosure);
+  NS_ENSURE_SUCCESS(rv, rv);
+
 #define REPORT(_path, _amount, _desc)                                         \
   do {                                                                        \
     nsresult rv;                                                              \
     rv = aCb->Callback(EmptyCString(), NS_LITERAL_CSTRING(_path),             \
                        nsIMemoryReporter::KIND_OTHER,                         \
                        nsIMemoryReporter::UNITS_BYTES, _amount,               \
                        NS_LITERAL_CSTRING(_desc), aClosure);                  \
     NS_ENSURE_SUCCESS(rv, rv);                                                \
@@ -387,19 +404,19 @@ nsWindowMemoryReporter::CollectReports(n
 #undef REPORT
     
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWindowMemoryReporter::GetExplicitNonHeap(PRInt64* aAmount)
 {
-  // This reporter only measures heap memory.
-  *aAmount = 0;
-  return NS_OK;
+  // This reporter only measures heap memory, so we don't need to report any
+  // bytes for it.  However, the JS multi-reporter needs to be invoked.
+  return xpc::JSMemoryMultiReporter::GetExplicitNonHeap(aAmount);
 }
 
 PRUint32
 nsWindowMemoryReporter::GetGhostTimeout()
 {
   return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
 }
 
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -128,29 +128,37 @@ NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsW
 struct WorkerJSRuntimeStats : public JS::RuntimeStats
 {
   WorkerJSRuntimeStats()
    : JS::RuntimeStats(JsWorkerMallocSizeOf) { }
 
   virtual void initExtraCompartmentStats(JSCompartment *c,
                                          JS::CompartmentStats *cstats) MOZ_OVERRIDE
   {
-    MOZ_ASSERT(!cstats->extra);
+    MOZ_ASSERT(!cstats->extra1);
+    MOZ_ASSERT(!cstats->extra2);
     
-    // ReportJSRuntimeExplicitTreeStats expects that cstats->extra is a char pointer
-    const char *name = js::IsAtomsCompartment(c) ? "Web Worker Atoms" : "Web Worker";
-    cstats->extra = const_cast<char *>(name);
+    // ReportJSRuntimeExplicitTreeStats expects that cstats->{extra1,extra2}
+    // are char pointers.
+
+    // This is the |cPathPrefix|.  Using NULL here means that we'll end up
+    // using WorkerMemoryReporter::mRtPath as the path prefix for each
+    // compartment.  See xpc::ReportJSRuntimeExplicitTreeStats().
+    cstats->extra1 = NULL;
+
+    // This is the |cName|.
+    cstats->extra2 = (void *)(js::IsAtomsCompartment(c) ? "Web Worker Atoms" : "Web Worker");
   }
 };
   
 class WorkerMemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
   WorkerPrivate* mWorkerPrivate;
   nsCString mAddressString;
-  nsCString mPathPrefix;
+  nsCString mRtPath;
 
 public:
   NS_DECL_ISUPPORTS
 
   WorkerMemoryReporter(WorkerPrivate* aWorkerPrivate)
   : mWorkerPrivate(aWorkerPrivate)
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
@@ -170,20 +178,20 @@ public:
         mAddressString.Assign(address, addressSize);
       }
       else {
         NS_WARNING("JS_snprintf failed!");
         mAddressString.AssignLiteral("<unknown address>");
       }
     }
 
-    mPathPrefix = NS_LITERAL_CSTRING("explicit/dom/workers(") +
-                  escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
-                  escapedURL + NS_LITERAL_CSTRING(", ") + mAddressString +
-                  NS_LITERAL_CSTRING(")/");
+    mRtPath = NS_LITERAL_CSTRING("explicit/workers/workers(") +
+              escapedDomain + NS_LITERAL_CSTRING(")/worker(") +
+              escapedURL + NS_LITERAL_CSTRING(", ") + mAddressString +
+              NS_LITERAL_CSTRING(")/");
   }
 
   nsresult
   CollectForRuntime(bool aIsQuick, void* aData)
   {
     AssertIsOnMainThread();
 
     if (mWorkerPrivate) {
@@ -227,17 +235,17 @@ public:
     WorkerJSRuntimeStats rtStats;
     nsresult rv = CollectForRuntime(/* isQuick = */false, &rtStats);
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     // Always report, even if we're disabled, so that we at least get an entry
     // in about::memory.
-    return xpc::ReportJSRuntimeExplicitTreeStats(rtStats, mPathPrefix,
+    return xpc::ReportJSRuntimeExplicitTreeStats(rtStats, mRtPath,
                                                  aCallback, aClosure);
   }
 
   NS_IMETHOD
   GetExplicitNonHeap(PRInt64 *aAmount)
   {
     AssertIsOnMainThread();
 
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -80,17 +80,19 @@ struct RuntimeSizes
 };
 
 struct CompartmentStats
 {
     CompartmentStats() {
         memset(this, 0, sizeof(*this));
     }
 
-    void   *extra;
+    // These fields can be used by embedders.
+    void   *extra1;
+    void   *extra2;
 
     // If you add a new number, remember to update add() and maybe
     // gcHeapThingsSize()!
     size_t gcHeapArenaAdmin;
     size_t gcHeapUnusedGcThings;
 
     size_t gcHeapObjectsNonFunction;
     size_t gcHeapObjectsFunction;
--- a/js/src/MemoryMetrics.cpp
+++ b/js/src/MemoryMetrics.cpp
@@ -191,17 +191,17 @@ CollectRuntimeStats(JSRuntime *rt, Runti
 
     // This just computes rtStats->gcHeapDecommittedArenas.
     IterateChunks(rt, rtStats, StatsChunkCallback);
 
     // Take the per-compartment measurements.
     IterateCompartmentsArenasCells(rt, rtStats, StatsCompartmentCallback,
                                    StatsArenaCallback, StatsCellCallback);
 
-    // Take the "explcit/js/runtime/" measurements.
+    // Take the "explicit/js/runtime/" measurements.
     rt->sizeOfIncludingThis(rtStats->mallocSizeOf, &rtStats->runtime);
 
     rtStats->gcHeapGcThings = 0;
     for (size_t i = 0; i < rtStats->compartmentStatsVector.length(); i++) {
         CompartmentStats &cStats = rtStats->compartmentStatsVector[i];
 
         rtStats->totals.add(cStats);
         rtStats->gcHeapGcThings += cStats.gcHeapThingsSize();
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -225,16 +225,28 @@ AssertHeapIsIdle(JSRuntime *rt)
 
 static void
 AssertHeapIsIdle(JSContext *cx)
 {
     AssertHeapIsIdle(cx->runtime);
 }
 
 static void
+AssertHeapIsIdleOrIterating(JSRuntime *rt)
+{
+    JS_ASSERT(rt->heapState != JSRuntime::Collecting);
+}
+
+static void
+AssertHeapIsIdleOrIterating(JSContext *cx)
+{
+    AssertHeapIsIdleOrIterating(cx->runtime);
+}
+
+static void
 AssertHeapIsIdleOrStringIsFlat(JSContext *cx, JSString *str)
 {
     /*
      * We allow some functions to be called during a GC as long as the argument
      * is a flat string, since that will not cause allocation.
      */
     JS_ASSERT_IF(cx->runtime->isHeapBusy(), str->isFlat());
 }
@@ -2163,16 +2175,24 @@ JS_PUBLIC_API(JSObject *)
 JS_GetGlobalForObject(JSContext *cx, JSObject *obj)
 {
     AssertHeapIsIdle(cx);
     assertSameCompartment(cx, obj);
     return &obj->global();
 }
 
 JS_PUBLIC_API(JSObject *)
+JS_GetGlobalForCompartmentOrNull(JSContext *cx, JSCompartment *c)
+{
+    AssertHeapIsIdleOrIterating(cx);
+    assertSameCompartment(cx, c);
+    return c->maybeGlobal();
+}
+
+JS_PUBLIC_API(JSObject *)
 JS_GetGlobalForScopeChain(JSContext *cx)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
     return GetGlobalForScopeChain(cx);
 }
 
 JS_PUBLIC_API(jsval)
@@ -5561,25 +5581,25 @@ JS_IsRunning(JSContext *cx)
     while (fp && fp->isDummyFrame())
         fp = fp->prev();
     return fp != NULL;
 }
 
 JS_PUBLIC_API(JSBool)
 JS_SaveFrameChain(JSContext *cx)
 {
-    AssertHeapIsIdle(cx);
+    AssertHeapIsIdleOrIterating(cx);
     CHECK_REQUEST(cx);
     return cx->stack.saveFrameChain();
 }
 
 JS_PUBLIC_API(void)
 JS_RestoreFrameChain(JSContext *cx)
 {
-    AssertHeapIsIdle(cx);
+    AssertHeapIsIdleOrIterating(cx);
     CHECK_REQUEST(cx);
     cx->stack.restoreFrameChain();
 }
 
 #ifdef MOZ_TRACE_JSCALLS
 JS_PUBLIC_API(void)
 JS_SetFunctionCallback(JSContext *cx, JSFunctionCallback fcb)
 {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3048,16 +3048,23 @@ JS_GetFunctionPrototype(JSContext *cx, J
  * which |forObj| was created.
  */
 extern JS_PUBLIC_API(JSObject *)
 JS_GetObjectPrototype(JSContext *cx, JSObject *forObj);
 
 extern JS_PUBLIC_API(JSObject *)
 JS_GetGlobalForObject(JSContext *cx, JSObject *obj);
 
+/*
+ * May return NULL, if |c| never had a global (e.g. the atoms compartment), or
+ * if |c|'s global has been collected.
+ */
+extern JS_PUBLIC_API(JSObject *)
+JS_GetGlobalForCompartmentOrNull(JSContext *cx, JSCompartment *c);
+
 extern JS_PUBLIC_API(JSObject *)
 JS_GetGlobalForScopeChain(JSContext *cx);
 
 extern JS_PUBLIC_API(JSObject *)
 JS_GetScriptedGlobal(JSContext *cx);
 
 /*
  * Initialize the 'Reflect' object on a global object.
--- a/js/xpconnect/src/Makefile.in
+++ b/js/xpconnect/src/Makefile.in
@@ -14,16 +14,17 @@ MODULE		= xpconnect
 
 LIBRARY_NAME    = xpconnect_s
 FORCE_STATIC_LIB = 1
 LIBXUL_LIBRARY = 1
 EXPORTS = \
 		qsObjectHelper.h \
 		xpcObjectHelper.h \
 		xpcpublic.h \
+		XPCJSMemoryReporter.h \
 		dombindings.h \
 		dombindings_gen.h
 
 CPPSRCS		= \
 		nsScriptError.cpp \
 		nsXPConnect.cpp \
 		XPCCallContext.cpp \
 		XPCComponents.cpp \
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/src/XPCJSMemoryReporter.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XPCJSMemoryReporter_h
+#define XPCJSMemoryReporter_h
+
+class nsISupports;
+class nsIMemoryMultiReporterCallback;
+
+namespace xpc {
+
+// The key is the window ID.
+typedef nsDataHashtable<nsUint64HashKey, nsCString> WindowPaths;
+
+// This is very nearly an instance of nsIMemoryMultiReporter, but it's not,
+// because it's invoked by nsWindowMemoryReporter in order to get |windowPaths|
+// in CollectReports.
+class JSMemoryMultiReporter
+{
+public:
+    static nsresult CollectReports(WindowPaths *windowPaths, nsIMemoryMultiReporterCallback *cb,
+                                   nsISupports *closure);
+
+    static nsresult GetExplicitNonHeap(PRInt64 *n);
+};
+
+}
+
+#endif
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -5,20 +5,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Per JSRuntime object */
 
 #include "mozilla/Util.h"
 
 #include "xpcprivate.h"
 #include "xpcpublic.h"
+#include "XPCJSMemoryReporter.h"
 #include "WrapperFactory.h"
 #include "dom_quickstubs.h"
 
 #include "nsIMemoryReporter.h"
+#include "nsPIDOMWindow.h"
 #include "nsPrintfCString.h"
 #include "mozilla/FunctionTimer.h"
 #include "prsystem.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 
 #include "nsContentUtils.h"
 #include "nsCCUncollectableMarker.h"
@@ -1281,16 +1283,18 @@ static const size_t SUNDRIES_THRESHOLD =
         nsresult rv;                                                          \
         rv = cb->Callback(EmptyCString(), _path, _kind,                       \
                           nsIMemoryReporter::UNITS_BYTES, amount,             \
                           NS_LITERAL_CSTRING(_desc), closure);                \
         NS_ENSURE_SUCCESS(rv, rv);                                            \
         rtTotal += amount;                                                    \
     } while (0)
 
+NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsMallocSizeOf, "js")
+
 namespace xpc {
 
 static nsresult
 ReportCompartmentStats(const JS::CompartmentStats &cStats,
                        const nsACString &pathPrefix,
                        nsIMemoryMultiReporterCallback *cb,
                        nsISupports *closure, size_t *gcTotalOut = NULL)
 {
@@ -1467,142 +1471,150 @@ ReportCompartmentStats(const JS::Compart
         *gcTotalOut += gcTotal;
     }
 
     return NS_OK;
 }
 
 nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
-                                 const nsACString &pathPrefix,
+                                 const nsACString &rtPath,
                                  nsIMemoryMultiReporterCallback *cb,
                                  nsISupports *closure, size_t *rtTotalOut)
 {
     nsresult rv;
 
     // Report each compartment's numbers.
 
     size_t gcTotal = 0;
     for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) {
         JS::CompartmentStats cStats = rtStats.compartmentStatsVector[i];
-        const char *name = static_cast<char *>(cStats.extra);
-        nsCString pathPrefix2 = pathPrefix + NS_LITERAL_CSTRING("compartment(") +
-                                nsDependentCString(name) + NS_LITERAL_CSTRING(")/");
+        const char *cPathPrefix = static_cast<char *>(cStats.extra1);
+        const char *cName       = static_cast<char *>(cStats.extra2);
 
-        rv = ReportCompartmentStats(cStats, pathPrefix2, cb, closure, &gcTotal);
+        // If cPathPrefix is NULL, cPath is "<rtPath>compartment(<cName>)/"
+        //              otherwise, cPath is "<cPathPrefix>compartment(<cName>)/"
+        nsCString cPath;
+        if (cPathPrefix) {
+            cPath.Assign(nsDependentCString(cPathPrefix));
+        } else {
+            cPath.Assign(rtPath);
+        }
+        cPath += NS_LITERAL_CSTRING("compartment(") +
+                 nsDependentCString(cName) + NS_LITERAL_CSTRING(")/");
+
+        rv = ReportCompartmentStats(cStats, cPath, cb, closure, &gcTotal);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Report the rtStats.runtime numbers under "runtime/", and compute their
     // total for later.
 
     size_t rtTotal = 0;
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/runtime-object"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/runtime-object"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.object,
                   "Memory used by the JSRuntime object.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/atoms-table"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/atoms-table"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.atomsTable,
                   "Memory used by the atoms table.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/contexts"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/contexts"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.contexts,
                   "Memory used by JSContext objects and certain structures "
                   "hanging off them.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/dtoa"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/dtoa"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.dtoa,
                   "Memory used by DtoaState, which is used for converting "
                   "strings to numbers and vice versa.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/temporary"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/temporary"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.temporary,
                   "Memory held transiently in JSRuntime and used during "
                   "compilation.  It mostly holds parse nodes.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/mjit-code"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/mjit-code"),
                   nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.mjitCode,
                   "Memory used by the method JIT to hold the runtime's "
                   "generated code.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/regexp-code"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/regexp-code"),
                   nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.regexpCode,
                   "Memory used by the regexp JIT to hold generated code.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/unused-code-memory"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/unused-code-memory"),
                   nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.unusedCodeMemory,
                   "Memory allocated by the method and/or regexp JIT to hold the "
                   "runtime's code, but which is currently unused.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/stack-committed"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/stack-committed"),
                   nsIMemoryReporter::KIND_NONHEAP, rtStats.runtime.stackCommitted,
                   "Memory used for the JS call stack.  This is the committed "
                   "portion of the stack; the uncommitted portion is not "
                   "measured because it hardly costs anything.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/gc-marker"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc-marker"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.gcMarker,
                   "Memory used for the GC mark stack and gray roots.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/math-cache"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.mathCache,
                   "Memory used for the math cache.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/script-filenames"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-filenames"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.scriptFilenames,
                   "Memory used for the table holding script filenames.");
 
-    RREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("runtime/compartment-objects"),
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/compartment-objects"),
                   nsIMemoryReporter::KIND_HEAP, rtStats.runtime.compartmentObjects,
                   "Memory used for JSCompartment objects.  These are fairly "
                   "small and all the same size, so they're not worth reporting "
                   "on a per-compartment basis.");
 
     if (rtTotalOut) {
         *rtTotalOut = rtTotal;
     }
 
     // Report GC numbers that don't belong to a compartment.
 
-    REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-arenas"),
+    REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-arenas"),
                     rtStats.gcHeapUnusedArenas,
                     "Memory on the garbage-collected JavaScript heap taken by "
                     "empty arenas within non-empty chunks.");
 
-    REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-chunks"),
+    REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-chunks"),
                     rtStats.gcHeapUnusedChunks,
                     "Memory on the garbage-collected JavaScript heap taken by "
                     "empty chunks, which will soon be released unless claimed "
                     "for new allocations.");
 
-    REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/decommitted-arenas"),
+    REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/decommitted-arenas"),
                     rtStats.gcHeapDecommittedArenas,
                     "Memory on the garbage-collected JavaScript heap, "
                     "in arenas in non-empty chunks, that is returned to the OS. "
                     "This means it takes up address space but no physical "
                     "memory or swap space.");
 
-    REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/chunk-admin"),
+    REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/chunk-admin"),
                     rtStats.gcHeapChunkAdmin,
                     "Memory on the garbage-collected JavaScript heap, within "
                     "chunks, that is used to hold internal bookkeeping "
                     "information.");
 
     // gcTotal is the sum of everything we've reported for the GC heap.  It
     // should equal rtStats.gcHeapChunkTotal.
     JS_ASSERT(gcTotal == rtStats.gcHeapChunkTotal);
 
     return NS_OK;
 }
 
 } // namespace xpc
 
-NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsMallocSizeOf, "js")
-
 class JSCompartmentsMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
   public:
     NS_DECL_ISUPPORTS
 
     NS_IMETHOD GetName(nsACString &name)
     {
         name.AssignLiteral("compartments");
@@ -1656,127 +1668,158 @@ class JSCompartmentsMultiReporter MOZ_FI
         return NS_OK;
     }
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(JSCompartmentsMultiReporter
                               , nsIMemoryMultiReporter
                               )
 
-struct XPCJSRuntimeStats : public JS::RuntimeStats {
-    XPCJSRuntimeStats()
-      : JS::RuntimeStats(JsMallocSizeOf) { }
+namespace xpc {
+
+class XPCJSRuntimeStats : public JS::RuntimeStats
+{
+    JSContext   *mCx;
+    WindowPaths *mWindowPaths;
+
+  public:
+    XPCJSRuntimeStats(WindowPaths *windowPaths)
+      : JS::RuntimeStats(JsMallocSizeOf), mCx(NULL), mWindowPaths(windowPaths)
+    { }
+
+    bool init(XPCJSRuntime *xpcrt) {
+        mCx = JS_NewContext(xpcrt->GetJSRuntime(), 0);
+        return !!mCx;
+    }
     
     ~XPCJSRuntimeStats() {
-        for (size_t i = 0; i != compartmentStatsVector.length(); ++i)
-            free(compartmentStatsVector[i].extra);
+        JS_DestroyContextNoGC(mCx);
+
+        for (size_t i = 0; i != compartmentStatsVector.length(); ++i) {
+            free(compartmentStatsVector[i].extra1);
+            free(compartmentStatsVector[i].extra2);
+        }
     }
 
     virtual void initExtraCompartmentStats(JSCompartment *c,
                                            JS::CompartmentStats *cstats) MOZ_OVERRIDE {
-        nsCAutoString name;
-        GetCompartmentName(c, name);
-        cstats->extra = strdup(name.get());
+        nsCAutoString cName, cPathPrefix;
+        GetCompartmentName(c, cName);
+
+        // Get the compartment's global.
+        if (JSObject *global = JS_GetGlobalForCompartmentOrNull(mCx, c)) {
+            nsISupports *native = nsXPConnect::GetXPConnect()->GetNativeOfWrapper(mCx, global);
+            if (nsCOMPtr<nsPIDOMWindow> piwindow = do_QueryInterface(native)) {
+                // The global is a |window| object.  Use the path prefix that
+                // we should have already created for it.
+                if (mWindowPaths->Get(piwindow->WindowID(), &cPathPrefix)) {
+                    cPathPrefix.AppendLiteral("/js/");
+                } else {
+                    cPathPrefix.AssignLiteral("explicit/js-non-window/compartments/unknown-window-global/");
+                }
+            } else {
+                cPathPrefix.AssignLiteral("explicit/js-non-window/compartments/non-window-global/");
+            }
+        } else {
+            cPathPrefix.AssignLiteral("explicit/js-non-window/compartments/no-global/");
+        }
+
+        cstats->extra1 = strdup(cPathPrefix.get());
+        cstats->extra2 = strdup(cName.get());
     }
 };
     
-class JSMemoryMultiReporter MOZ_FINAL : public nsIMemoryMultiReporter
-{
-public:
-    NS_DECL_ISUPPORTS
-
-    NS_IMETHOD GetName(nsACString &name)
-    {
-        name.AssignLiteral("js");
-        return NS_OK;
-    }
-
-    NS_IMETHOD CollectReports(nsIMemoryMultiReporterCallback *cb,
-                              nsISupports *closure)
+    nsresult
+    JSMemoryMultiReporter::CollectReports(WindowPaths *windowPaths,
+                                          nsIMemoryMultiReporterCallback *cb,
+                                          nsISupports *closure)
     {
         XPCJSRuntime *xpcrt = nsXPConnect::GetRuntimeInstance();
 
         // In the first step we get all the stats and stash them in a local
         // data structure.  In the second step we pass all the stashed stats to
         // the callback.  Separating these steps is important because the
         // callback may be a JS function, and executing JS while getting these
         // stats seems like a bad idea.
-        XPCJSRuntimeStats rtStats;
+
+        XPCJSRuntimeStats rtStats(windowPaths);
+        if (!rtStats.init(xpcrt))
+            return NS_ERROR_FAILURE;
+
         if (!JS::CollectRuntimeStats(xpcrt->GetJSRuntime(), &rtStats))
             return NS_ERROR_FAILURE;
 
         size_t xpconnect =
             xpcrt->SizeOfIncludingThis(JsMallocSizeOf) +
             XPCWrappedNativeScope::SizeOfAllScopesIncludingThis(JsMallocSizeOf);
 
-        NS_NAMED_LITERAL_CSTRING(explicitJs, "explicit/js/");
-
         // This is the second step (see above).  First we report stuff in the
         // "explicit" tree, then we report other stuff.
 
+        nsresult rv;
         size_t rtTotal = 0;
-        nsresult rv =
-            xpc::ReportJSRuntimeExplicitTreeStats(rtStats, explicitJs, cb,
-                                                  closure, &rtTotal);
+        rv = xpc::ReportJSRuntimeExplicitTreeStats(rtStats,
+                                                   NS_LITERAL_CSTRING("explicit/js-non-window/"),
+                                                   cb, closure, &rtTotal);
         NS_ENSURE_SUCCESS(rv, rv);
 
         // Report the sums of the compartment numbers.
         rv = ReportCompartmentStats(rtStats.totals,
                                     NS_LITERAL_CSTRING("js-main-runtime/compartments/"),
                                     cb, closure);
         NS_ENSURE_SUCCESS(rv, rv);
 
         // Report the sum of the runtime/ numbers.
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/runtime"),
                      nsIMemoryReporter::KIND_OTHER, rtTotal,
-                     "The sum of all measurements under 'explicit/js/runtime/'.");
+                     "The sum of all measurements under 'explicit/js-non-window/runtime/'.");
 
         // Report the numbers for memory outside of compartments.
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/decommitted-arenas"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapDecommittedArenas,
-                     "The same as 'explicit/js/gc-heap/decommitted-arenas'.");
+                     "The same as 'explicit/js-non-window/gc-heap/decommitted-arenas'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapUnusedChunks,
-                     "The same as 'explicit/js/gc-heap/unused-chunks'.");
+                     "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-arenas"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapUnusedArenas,
-                     "The same as 'explicit/js/gc-heap/unused-arenas'.");
+                     "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/chunk-admin"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapChunkAdmin,
-                     "The same as 'explicit/js/gc-heap/chunk-admin'.");
+                     "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
 
         // Report a breakdown of the committed GC space.
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/chunks"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapUnusedChunks,
-                     "The same as 'explicit/js/gc-heap/unused-chunks'.");
+                     "The same as 'explicit/js-non-window/gc-heap/unused-chunks'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/arenas"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapUnusedArenas,
-                     "The same as 'explicit/js/gc-heap/unused-arenas'.");
+                     "The same as 'explicit/js-non-window/gc-heap/unused-arenas'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.totals.gcHeapUnusedGcThings,
                      "The same as 'js-main-runtime/compartments/gc-heap/unused-gc-things'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.gcHeapChunkAdmin,
-                     "The same as 'explicit/js/gc-heap/chunk-admin'.");
+                     "The same as 'explicit/js-non-window/gc-heap/chunk-admin'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/arena-admin"),
                      nsIMemoryReporter::KIND_OTHER,
                      rtStats.totals.gcHeapArenaAdmin,
                      "The same as 'js-main-runtime/compartments/gc-heap/arena-admin'.");
 
         REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things"),
                      nsIMemoryReporter::KIND_OTHER,
@@ -1788,28 +1831,25 @@ public:
 
         REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect"),
                      nsIMemoryReporter::KIND_HEAP, xpconnect,
                      "Memory used by XPConnect.");
 
         return NS_OK;
     }
 
-    NS_IMETHOD
-    GetExplicitNonHeap(PRInt64 *n)
+    nsresult
+    JSMemoryMultiReporter::GetExplicitNonHeap(PRInt64 *n)
     {
         JSRuntime *rt = nsXPConnect::GetRuntimeInstance()->GetJSRuntime();
         *reinterpret_cast<int64_t*>(n) = JS::GetExplicitNonHeapForRuntime(rt, JsMallocSizeOf);
         return NS_OK;
     }
-};
 
-NS_IMPL_THREADSAFE_ISUPPORTS1(JSMemoryMultiReporter
-                              , nsIMemoryMultiReporter
-                              )
+} // namespace xpc
 
 #ifdef MOZ_CRASHREPORTER
 static JSBool
 DiagnosticMemoryCallback(void *ptr, size_t size)
 {
     return CrashReporter::RegisterAppMemory(ptr, size) == NS_OK;
 }
 #endif
@@ -1951,17 +1991,16 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
     JS_EnumerateDiagnosticMemoryRegions(DiagnosticMemoryCallback);
 #endif
     JS_SetAccumulateTelemetryCallback(mJSRuntime, AccumulateTelemetryCallback);
     js::SetActivityCallback(mJSRuntime, ActivityCallback, this);
         
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSGCHeap));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSSystemCompartmentCount));
     NS_RegisterMemoryReporter(new NS_MEMORY_REPORTER_NAME(XPConnectJSUserCompartmentCount));
-    NS_RegisterMemoryMultiReporter(new JSMemoryMultiReporter);
     NS_RegisterMemoryMultiReporter(new JSCompartmentsMultiReporter);
 
     if (!JS_DHashTableInit(&mJSHolders, JS_DHashGetStubOps(), nsnull,
                            sizeof(ObjectHolder), 512))
         mJSHolders.ops = nsnull;
 
     mCompartmentSet.init();
 
--- a/js/xpconnect/src/xpcpublic.h
+++ b/js/xpconnect/src/xpcpublic.h
@@ -261,17 +261,17 @@ void SetLocationForGlobal(JSObject *glob
 bool
 DOM_DefineQuickStubs(JSContext *cx, JSObject *proto, PRUint32 flags,
                      PRUint32 interfaceCount, const nsIID **interfaceArray);
 
 // This reports all the stats in |rtStats| that belong in the "explicit" tree,
 // (which isn't all of them).
 nsresult
 ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats &rtStats,
-                                 const nsACString &pathPrefix,
+                                 const nsACString &rtPath,
                                  nsIMemoryMultiReporterCallback *cb,
                                  nsISupports *closure, size_t *rtTotal = NULL);
 
 /**
  * Convert a jsval to PRInt64. Return true on success.
  */
 inline bool
 ValueToInt64(JSContext *cx, JS::Value v, int64_t *result)
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -38,19 +38,19 @@
 
   let explicitAmounts = [];
   let vsizeAmounts = [];
   let residentAmounts = [];
   let jsGcHeapAmounts = [];
   let heapAllocatedAmounts = [];
   let storageSqliteAmounts = [];
 
-  let areJsCompartmentsPresent = false;
+  let areJsNonWindowCompartmentsPresent = false;
+  let areWindowObjectsJsCompartmentsPresent = false;
   let isSandboxLocationShown = false;
-  let areWindowObjectsPresent = false;
   let isPlacesPresent = false;
   let isImagesPresent = false;
   let isXptiWorkingSetPresent = false;
   let isAtomTablePresent = false;
 
   let mySandbox = Components.utils.Sandbox(document.nodePrincipal,
                     { sandboxName: "this-is-a-sandbox-name" });
 
@@ -66,20 +66,20 @@
     } else if (aPath === "js-main-runtime-gc-heap-committed/used/gc-things") {
       jsGcHeapAmounts.push(aAmount); 
     } else if (aPath === "heap-allocated") {
       heapAllocatedAmounts.push(aAmount);
     } else if (aPath === "storage-sqlite") {
       storageSqliteAmounts.push(aAmount);
 
     // Check the presence of some other notable reporters.
-    } else if (aPath.search(/^explicit\/js\/compartment\(/) >= 0) {
-      areJsCompartmentsPresent = true;
-    } else if (aPath.search(/^explicit\/window-objects\/top\(/) >= 0) {
-      areWindowObjectsPresent = true;
+    } else if (aPath.search(/^explicit\/js-non-window\/.*compartment\(/) >= 0) {
+      areJsNonWindowCompartmentsPresent = true;
+    } else if (aPath.search(/^explicit\/window-objects\/top\(.*\/js\/compartment\(/) >= 0) {
+      areWindowObjectsJsCompartmentsPresent = true;
     } else if (aPath.search(/^explicit\/storage\/sqlite\/places.sqlite/) >= 0) {
       isPlacesPresent = true;
     } else if (aPath.search(/^explicit\/images/) >= 0) {
       isImagesPresent = true;
     } else if (aPath.search(/^explicit\/xpti-working-set$/) >= 0) {
       isXptiWorkingSetPresent = true;
     } else if (aPath.search(/^explicit\/atom-tables$/) >= 0) {
       isAtomTablePresent = true;
@@ -139,20 +139,20 @@
     checkSpecialReport("explicit",       explicitAmounts);
     checkSpecialReport("heap-allocated", heapAllocatedAmounts);
   }
   checkSpecialReport("vsize",          vsizeAmounts);
   checkSpecialReport("resident",       residentAmounts);
   checkSpecialReport("js-main-runtime-gc-heap-committed/used/gc-things", jsGcHeapAmounts);
   checkSpecialReport("storage-sqlite", storageSqliteAmounts);
 
-  ok(areJsCompartmentsPresent,  "js compartments are present");
-  ok(isSandboxLocationShown,    "sandbox locations are present");
-  ok(areWindowObjectsPresent,   "window objects are present");
-  ok(isPlacesPresent,           "places is present");
-  ok(isImagesPresent,           "images is present");
-  ok(isXptiWorkingSetPresent,   "xpti-working-set is present");
-  ok(isAtomTablePresent,        "atom-table is present");
+  ok(areJsNonWindowCompartmentsPresent,     "js-non-window compartments are present");
+  ok(areWindowObjectsJsCompartmentsPresent, "window-objects/.../js compartments are present");
+  ok(isSandboxLocationShown,                "sandbox locations are present");
+  ok(isPlacesPresent,                       "places is present");
+  ok(isImagesPresent,                       "images is present");
+  ok(isXptiWorkingSetPresent,               "xpti-working-set is present");
+  ok(isAtomTablePresent,                    "atom-table is present");
 
   ]]>
   </script>
 </window>