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 103300 e586043f5b5816da1b6b7146176bd4f021c43953
parent 103299 7919de7e468cbb10df2838491b3172c0d063fccb
child 103301 0158f2d0b32d487c9474309112343e9264afc80d
push idunknown
push userunknown
push dateunknown
reviewersbholley, jlebar, luke
bugs687724
milestone16.0a1
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>