Bug 704623 - Track memory used by orphan DOM nodes. code=mccr8,njn. r=njn,mccr8.
authorAndrew McCreight <amccreight@mozilla.com>
Fri, 20 Apr 2012 14:01:33 +1000
changeset 103722 57ee1a91e771071a78a654a3cfe24a14e3ce04fc
parent 103721 89945c6ca23233c14f6167498b8577dc658174e7
child 103723 ee74ca523ff236d071564f186588175f8f6e8722
push idunknown
push userunknown
push dateunknown
reviewersnjn, mccr8
bugs704623
milestone16.0a1
Bug 704623 - Track memory used by orphan DOM nodes. code=mccr8,njn. r=njn,mccr8.
dom/workers/WorkerPrivate.cpp
js/public/MemoryMetrics.h
js/src/MemoryMetrics.cpp
js/xpconnect/src/XPCJSRuntime.cpp
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -122,36 +122,50 @@ SwapToISupportsArray(SmartPtr<T>& aSrc,
     static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw);
   dest->swap(rawSupports);
 }
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsWorkerMallocSizeOf, "js-worker")
 
 struct WorkerJSRuntimeStats : public JS::RuntimeStats
 {
-  WorkerJSRuntimeStats()
-   : JS::RuntimeStats(JsWorkerMallocSizeOf) { }
+  WorkerJSRuntimeStats(nsACString &aRtPath)
+   : JS::RuntimeStats(JsWorkerMallocSizeOf), mRtPath(aRtPath) { }
+
+  ~WorkerJSRuntimeStats() {
+    for (size_t i = 0; i != compartmentStatsVector.length(); i++) {
+      free(compartmentStatsVector[i].extra1);
+      // no need to free |extra2|, because it's a static string
+    }
+  }
 
   virtual void initExtraCompartmentStats(JSCompartment *c,
                                          JS::CompartmentStats *cstats) MOZ_OVERRIDE
   {
     MOZ_ASSERT(!cstats->extra1);
     MOZ_ASSERT(!cstats->extra2);
     
     // 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");
+    // This is the |cJSPathPrefix|.  Each worker has exactly two compartments:
+    // one for atoms, and one for everything else.
+    nsCString cJSPathPrefix(mRtPath);
+    cJSPathPrefix += js::IsAtomsCompartment(c)
+                   ? NS_LITERAL_CSTRING("compartment(web-worker-atoms)/")
+                   : NS_LITERAL_CSTRING("compartment(web-worker)/");
+    cstats->extra1 = strdup(cJSPathPrefix.get());
+
+    // This is the |cDOMPathPrefix|, which should never be used when reporting
+    // with workers (hence the "?!").
+    cstats->extra2 = (void *)"explicit/workers/?!/";
   }
+
+private:
+  nsCString mRtPath;
 };
   
 class WorkerMemoryReporter MOZ_FINAL : public nsIMemoryMultiReporter
 {
   WorkerPrivate* mWorkerPrivate;
   nsCString mAddressString;
   nsCString mRtPath;
 
@@ -227,17 +241,17 @@ public:
   }
 
   NS_IMETHOD
   CollectReports(nsIMemoryMultiReporterCallback* aCallback,
                  nsISupports* aClosure)
   {
     AssertIsOnMainThread();
 
-    WorkerJSRuntimeStats rtStats;
+    WorkerJSRuntimeStats rtStats(mRtPath);
     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, mRtPath,
@@ -1576,17 +1590,17 @@ public:
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     JSRuntime *rt = JS_GetRuntime(aCx);
     if (mIsQuick) {
       *static_cast<int64_t*>(mData) = JS::GetExplicitNonHeapForRuntime(rt, JsWorkerMallocSizeOf);
       *mSucceeded = true;
     } else {
-      *mSucceeded = JS::CollectRuntimeStats(rt, static_cast<JS::RuntimeStats*>(mData));
+      *mSucceeded = JS::CollectRuntimeStats(rt, static_cast<JS::RuntimeStats*>(mData), nsnull);
     }
 
     {
       MutexAutoLock lock(mMutex);
       mDone = true;
       mCondVar.Notify();
     }
 
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -104,16 +104,17 @@ struct CompartmentStats
     size_t gcHeapTypeObjects;
 #if JS_HAS_XML_SUPPORT
     size_t gcHeapXML;
 #endif
 
     size_t objectSlots;
     size_t objectElements;
     size_t objectMisc;
+    size_t objectPrivate;
     size_t stringChars;
     size_t shapesExtraTreeTables;
     size_t shapesExtraDictTables;
     size_t shapesExtraTreeShapeKids;
     size_t shapesCompartmentTables;
     size_t scriptData;
     size_t mjitData;
     size_t crossCompartmentWrappers;
@@ -137,16 +138,17 @@ struct CompartmentStats
         ADD(gcHeapTypeObjects);
     #if JS_HAS_XML_SUPPORT
         ADD(gcHeapXML);
     #endif
 
         ADD(objectSlots);
         ADD(objectElements);
         ADD(objectMisc);
+        ADD(objectPrivate);
         ADD(stringChars);
         ADD(shapesExtraTreeTables);
         ADD(shapesExtraDictTables);
         ADD(shapesExtraTreeShapeKids);
         ADD(shapesCompartmentTables);
         ADD(scriptData);
         ADD(mjitData);
         ADD(crossCompartmentWrappers);
@@ -216,18 +218,26 @@ struct RuntimeStats
 
     JSMallocSizeOfFun mallocSizeOf;
 
     virtual void initExtraCompartmentStats(JSCompartment *c, CompartmentStats *cstats) = 0;
 };
 
 #ifdef JS_THREADSAFE
 
+class ObjectPrivateVisitor
+{
+public:
+    // Within CollectRuntimeStats, this method is called for each JS object
+    // that has a private slot containing an nsISupports pointer.
+    virtual size_t sizeOfIncludingThis(void *aSupports) = 0;
+};
+
 extern JS_PUBLIC_API(bool)
-CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats);
+CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisitor *opv);
 
 extern JS_PUBLIC_API(int64_t)
 GetExplicitNonHeapForRuntime(JSRuntime *rt, JSMallocSizeOfFun mallocSizeOf);
 
 #endif /* JS_THREADSAFE */
 
 extern JS_PUBLIC_API(size_t)
 SystemCompartmentCount(const JSRuntime *rt);
--- a/js/src/MemoryMetrics.cpp
+++ b/js/src/MemoryMetrics.cpp
@@ -17,16 +17,23 @@
 #include "jsobjinlines.h"
 
 #ifdef JS_THREADSAFE
 
 namespace JS {
 
 using namespace js;
 
+struct IteratorClosure
+{
+  RuntimeStats *rtStats;
+  ObjectPrivateVisitor *opv;
+  IteratorClosure(RuntimeStats *rt, ObjectPrivateVisitor *v) : rtStats(rt), opv(v) {}
+};
+
 size_t
 CompartmentStats::gcHeapThingsSize()
 {
     // These are just the GC-thing measurements.
     size_t n = 0;
     n += gcHeapObjectsNonFunction;
     n += gcHeapObjectsFunction;
     n += gcHeapStrings;
@@ -49,17 +56,17 @@ CompartmentStats::gcHeapThingsSize()
 
     return n;
 }
 
 static void
 StatsCompartmentCallback(JSRuntime *rt, void *data, JSCompartment *compartment)
 {
     // Append a new CompartmentStats to the vector.
-    RuntimeStats *rtStats = static_cast<RuntimeStats *>(data);
+    RuntimeStats *rtStats = static_cast<IteratorClosure *>(data)->rtStats;
 
     // CollectRuntimeStats reserves enough space.
     MOZ_ALWAYS_TRUE(rtStats->compartmentStatsVector.growBy(1));
     CompartmentStats &cStats = rtStats->compartmentStatsVector.back();
     rtStats->initExtraCompartmentStats(compartment, &cStats);
     rtStats->currCompartmentStats = &cStats;
 
     // Get the compartment-level numbers.
@@ -77,17 +84,17 @@ StatsChunkCallback(JSRuntime *rt, void *
         if (chunk->decommittedArenas.get(i))
             rtStats->gcHeapDecommittedArenas += gc::ArenaSize;
 }
 
 static void
 StatsArenaCallback(JSRuntime *rt, void *data, gc::Arena *arena,
                    JSGCTraceKind traceKind, size_t thingSize)
 {
-    RuntimeStats *rtStats = static_cast<RuntimeStats *>(data);
+    RuntimeStats *rtStats = static_cast<IteratorClosure *>(data)->rtStats;
 
     // The admin space includes (a) the header and (b) the padding between the
     // end of the header and the start of the first GC thing.
     size_t allocationSpace = arena->thingsSpan(thingSize);
     rtStats->currCompartmentStats->gcHeapArenaAdmin +=
         gc::ArenaSize - allocationSpace;
 
     // We don't call the callback on unused things.  So we compute the
@@ -96,33 +103,43 @@ StatsArenaCallback(JSRuntime *rt, void *
     // subtracting thingSize for every used cell, in StatsCellCallback().
     rtStats->currCompartmentStats->gcHeapUnusedGcThings += allocationSpace;
 }
 
 static void
 StatsCellCallback(JSRuntime *rt, void *data, void *thing, JSGCTraceKind traceKind,
                   size_t thingSize)
 {
-    RuntimeStats *rtStats = static_cast<RuntimeStats *>(data);
+    IteratorClosure *closure = static_cast<IteratorClosure *>(data);
+    RuntimeStats *rtStats = closure->rtStats;
     CompartmentStats *cStats = rtStats->currCompartmentStats;
     switch (traceKind) {
     case JSTRACE_OBJECT:
     {
         JSObject *obj = static_cast<JSObject *>(thing);
         if (obj->isFunction()) {
             cStats->gcHeapObjectsFunction += thingSize;
         } else {
             cStats->gcHeapObjectsNonFunction += thingSize;
         }
         size_t slotsSize, elementsSize, miscSize;
         obj->sizeOfExcludingThis(rtStats->mallocSizeOf, &slotsSize,
                                  &elementsSize, &miscSize);
         cStats->objectSlots += slotsSize;
         cStats->objectElements += elementsSize;
         cStats->objectMisc += miscSize;
+
+        if (ObjectPrivateVisitor *opv = closure->opv) {
+            js::Class *clazz = js::GetObjectClass(obj);
+            if (clazz->flags & JSCLASS_HAS_PRIVATE &&
+                clazz->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS)
+            {
+                cStats->objectPrivate += opv->sizeOfIncludingThis(GetObjectPrivate(obj));
+            }
+        }
         break;
     }
     case JSTRACE_STRING:
     {
         JSString *str = static_cast<JSString *>(thing);
         cStats->gcHeapStrings += thingSize;
         cStats->stringChars += str->sizeOfExcludingThis(rtStats->mallocSizeOf);
         break;
@@ -173,32 +190,33 @@ StatsCellCallback(JSRuntime *rt, void *d
     }
 #endif
     }
     // Yes, this is a subtraction:  see StatsArenaCallback() for details.
     cStats->gcHeapUnusedGcThings -= thingSize;
 }
 
 JS_PUBLIC_API(bool)
-CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats)
+CollectRuntimeStats(JSRuntime *rt, RuntimeStats *rtStats, ObjectPrivateVisitor *opv)
 {
     if (!rtStats->compartmentStatsVector.reserve(rt->compartments.length()))
         return false;
 
     rtStats->gcHeapChunkTotal =
         size_t(JS_GetGCParameter(rt, JSGC_TOTAL_CHUNKS)) * gc::ChunkSize;
 
     rtStats->gcHeapUnusedChunks =
         size_t(JS_GetGCParameter(rt, JSGC_UNUSED_CHUNKS)) * gc::ChunkSize;
 
     // This just computes rtStats->gcHeapDecommittedArenas.
     IterateChunks(rt, rtStats, StatsChunkCallback);
 
     // Take the per-compartment measurements.
-    IterateCompartmentsArenasCells(rt, rtStats, StatsCompartmentCallback,
+    IteratorClosure closure(rtStats, opv);
+    IterateCompartmentsArenasCells(rt, &closure, StatsCompartmentCallback,
                                    StatsArenaCallback, StatsCellCallback);
 
     // 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];
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1301,183 +1301,192 @@ static const size_t SUNDRIES_THRESHOLD =
     } while (0)
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(JsMallocSizeOf, "js")
 
 namespace xpc {
 
 static nsresult
 ReportCompartmentStats(const JS::CompartmentStats &cStats,
-                       const nsACString &pathPrefix,
+                       const nsACString &cJSPathPrefix,
+                       const nsACString &cDOMPathPrefix,
                        nsIMemoryMultiReporterCallback *cb,
                        nsISupports *closure, size_t *gcTotalOut = NULL)
 {
     size_t gcTotal = 0, gcHeapSundries = 0, otherSundries = 0;
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/arena-admin"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/arena-admin"),
                      cStats.gcHeapArenaAdmin,
                      "Memory on the garbage-collected JavaScript "
                      "heap, within arenas, that is used (a) to hold internal "
                      "bookkeeping information, and (b) to provide padding to "
                      "align GC things.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-gc-things"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/unused-gc-things"),
                      cStats.gcHeapUnusedGcThings,
                      "Memory on the garbage-collected JavaScript "
                      "heap taken by empty GC thing slots within non-empty "
                      "arenas.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/objects/non-function"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/objects/non-function"),
                      cStats.gcHeapObjectsNonFunction,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds non-function objects.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/objects/function"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/objects/function"),
                      cStats.gcHeapObjectsFunction,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds function objects.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/strings"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/strings"),
                      cStats.gcHeapStrings,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds string headers.  String headers contain "
                      "various pieces of information about a string, but do not "
                      "contain (except in the case of very short strings) the "
                      "string characters;  characters in longer strings are "
                      "counted under 'gc-heap/string-chars' instead.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/scripts"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/scripts"),
                      cStats.gcHeapScripts,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds JSScript instances. A JSScript is "
                      "created for each user-defined function in a script. One "
                      "is also created for the top-level code in a script.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/tree"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/tree"),
                      cStats.gcHeapShapesTree,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds shapes that are in a property tree.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/dict"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/dict"),
                      cStats.gcHeapShapesDict,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds shapes that are in dictionary mode.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/base"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/shapes/base"),
                      cStats.gcHeapShapesBase,
                      "Memory on the garbage-collected JavaScript "
                      "heap that collates data common to many shapes.");
 
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/type-objects"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/type-objects"),
                      cStats.gcHeapTypeObjects,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds type inference information.");
 
 #if JS_HAS_XML_SUPPORT
-    CREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/xml"),
+    CREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/xml"),
                      cStats.gcHeapXML,
                      "Memory on the garbage-collected JavaScript "
                      "heap that holds E4X XML objects.");
 #endif
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("objects/slots"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/slots"),
                   cStats.objectSlots,
                   "Memory allocated for the non-fixed object "
                   "slot arrays, which are used to represent object properties. "
                   "Some objects also contain a fixed number of slots which are "
                   "stored on the JavaScript heap; those slots "
                   "are not counted here, but in 'gc-heap/objects' instead.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("objects/elements"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/elements"),
                   cStats.objectElements,
                   "Memory allocated for object element "
                   "arrays, which are used to represent indexed object "
                   "properties.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("objects/misc"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("objects/misc"),
                   cStats.objectMisc,
                   "Memory allocated for various small, miscellaneous "
                   "structures that hang off certain kinds of objects.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("string-chars"),
+    // Note that we use cDOMPathPrefix here.  This is because we measure orphan
+    // DOM nodes in the JS multi-reporter, but we want to report them in a
+    // "dom" sub-tree rather than a "js" sub-tree.
+    CREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"),
+                  cStats.objectPrivate,
+                  "Memory used by orphan DOM nodes that are only reachable "
+                  "from JavaScript objects.");
+
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("string-chars"),
                   cStats.stringChars,
                   "Memory allocated to hold string "
                   "characters.  Sometimes more memory is allocated than "
                   "necessary, to simplify string concatenation.  Each string "
                   "also includes a header which is stored on the "
                   "compartment's JavaScript heap;  that header is not counted "
                   "here, but in 'gc-heap/strings' instead.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-tables"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-tables"),
                   cStats.shapesExtraTreeTables,
                   "Memory allocated for the property tables "
                   "that belong to shapes that are in a property tree.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/dict-tables"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes-extra/dict-tables"),
                   cStats.shapesExtraDictTables,
                   "Memory allocated for the property tables "
                   "that belong to shapes that are in dictionary mode.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-shape-kids"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes-extra/tree-shape-kids"),
                   cStats.shapesExtraTreeShapeKids,
                   "Memory allocated for the kid hashes that "
                   "belong to shapes that are in a property tree.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes-extra/compartment-tables"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("shapes-extra/compartment-tables"),
                   cStats.shapesCompartmentTables,
                   "Memory used by compartment-wide tables storing shape "
                   "information for use during object construction.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("script-data"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("script-data"),
                   cStats.scriptData,
                   "Memory allocated for JSScript bytecode and various "
                   "variable-length tables.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("mjit-data"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("mjit-data"),
                   cStats.mjitData,
                   "Memory used by the method JIT for "
                   "compilation data: JITScripts, native maps, and inline "
                   "cache structs.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrappers"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrappers"),
                   cStats.crossCompartmentWrappers,
                   "Memory used by cross-compartment wrappers.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-inference/script-main"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/script-main"),
                   cStats.typeInferenceSizes.scripts,
                   "Memory used during type inference to store type sets of "
                   "variables and dynamically observed types.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-inference/object-main"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/object-main"),
                   cStats.typeInferenceSizes.objects,
                   "Memory used during type inference to store types and "
                   "possible property types of JS objects.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-inference/tables"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/tables"),
                   cStats.typeInferenceSizes.tables,
                   "Memory used during type inference for compartment-wide "
                   "tables.");
 
-    CREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("analysis-temporary"),
+    CREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("analysis-temporary"),
                   cStats.typeInferenceSizes.temporary,
                   "Memory used during type inference and compilation to hold "
                   "transient analysis information.  Cleared on GC.");
 
     if (gcHeapSundries > 0) {
         // We deliberately don't use CREPORT_GC_BYTES here.
-        REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap/sundries"),
+        REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("gc-heap/sundries"),
                         gcHeapSundries,
                         "The sum of all the gc-heap "
                         "measurements that are too small to be worth showing "
                         "individually.");
     }
 
     if (otherSundries > 0) {
         // We deliberately don't use CREPORT_BYTES here.
-        REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("other-sundries"),
+        REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("other-sundries"),
                      nsIMemoryReporter::KIND_HEAP, otherSundries,
                      "The sum of all the non-gc-heap "
                      "measurements that are too small to be worth showing "
                      "individually.");
     }
 
     if (gcTotalOut) {
         *gcTotalOut += gcTotal;
@@ -1494,31 +1503,20 @@ ReportJSRuntimeExplicitTreeStats(const J
 {
     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 *cPathPrefix = static_cast<char *>(cStats.extra1);
-        const char *cName       = static_cast<char *>(cStats.extra2);
+        nsCString cJSPathPrefix(static_cast<char *>(cStats.extra1));
+        nsCString cDOMPathPrefix(static_cast<char *>(cStats.extra2));
 
-        // 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);
+        rv = ReportCompartmentStats(cStats, cJSPathPrefix, cDOMPathPrefix, 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;
 
@@ -1680,18 +1678,59 @@ class JSCompartmentsMultiReporter MOZ_FI
         return NS_OK;
     }
 };
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(JSCompartmentsMultiReporter
                               , nsIMemoryMultiReporter
                               )
 
+NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(OrphanSizeOf, "orphans")
+
 namespace xpc {
 
+static size_t
+SizeOfTreeIncludingThis(nsINode *tree)
+{       
+    size_t n = tree->SizeOfIncludingThis(OrphanSizeOf);
+    for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree)) {
+        n += child->SizeOfIncludingThis(OrphanSizeOf);
+    }   
+    return n;
+}
+
+class OrphanReporter : public JS::ObjectPrivateVisitor
+{
+public:
+    OrphanReporter()
+    {
+        mAlreadyMeasuredOrphanTrees.Init();
+    }
+
+    virtual size_t sizeOfIncludingThis(void *aSupports)
+    {
+        size_t n = 0;
+        nsCOMPtr<nsINode> node = do_QueryInterface(static_cast<nsISupports*>(aSupports));
+        if (node && !node->IsInDoc()) {
+            // This is an orphan node.  If we haven't already handled the
+            // sub-tree that this node belongs to, measure the sub-tree's size
+            // and then record its root so we don't measure it again.
+            nsCOMPtr<nsINode> orphanTree = node->SubtreeRoot();
+            if (!mAlreadyMeasuredOrphanTrees.Contains(orphanTree)) {
+                n += SizeOfTreeIncludingThis(orphanTree);
+                mAlreadyMeasuredOrphanTrees.PutEntry(orphanTree);
+            }
+        }
+        return n;
+    }
+
+private:
+    nsTHashtable <nsISupportsHashKey> mAlreadyMeasuredOrphanTrees;
+};
+
 class XPCJSRuntimeStats : public JS::RuntimeStats
 {
     JSContext   *mCx;
     WindowPaths *mWindowPaths;
 
   public:
     XPCJSRuntimeStats(WindowPaths *windowPaths)
       : JS::RuntimeStats(JsMallocSizeOf), mCx(NULL), mWindowPaths(windowPaths)
@@ -1708,39 +1747,58 @@ class XPCJSRuntimeStats : public JS::Run
         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 cName, cPathPrefix;
+        nsCAutoString cJSPathPrefix, cDOMPathPrefix;
+        nsCString cName;
         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/");
+                if (mWindowPaths->Get(piwindow->WindowID(), &cJSPathPrefix)) {
+                    cDOMPathPrefix.Assign(cJSPathPrefix);
+                    cDOMPathPrefix.AppendLiteral("/dom/");
+                    cJSPathPrefix.AppendLiteral("/js/");
                 } else {
-                    cPathPrefix.AssignLiteral("explicit/js-non-window/compartments/unknown-window-global/");
+                    cJSPathPrefix.AssignLiteral("explicit/js-non-window/compartments/unknown-window-global/");
+                    cDOMPathPrefix.AssignLiteral("explicit/dom/?!/");
                 }
             } else {
-                cPathPrefix.AssignLiteral("explicit/js-non-window/compartments/non-window-global/");
+                cJSPathPrefix.AssignLiteral("explicit/js-non-window/compartments/non-window-global/");
+                cDOMPathPrefix.AssignLiteral("explicit/dom/?!/");
             }
         } else {
-            cPathPrefix.AssignLiteral("explicit/js-non-window/compartments/no-global/");
+            cJSPathPrefix.AssignLiteral("explicit/js-non-window/compartments/no-global/");
+            cDOMPathPrefix.AssignLiteral("explicit/dom/?!/");
         }
 
-        cstats->extra1 = strdup(cPathPrefix.get());
-        cstats->extra2 = strdup(cName.get());
+        cJSPathPrefix += NS_LITERAL_CSTRING("compartment(") + cName + NS_LITERAL_CSTRING(")/");
+
+        // cJSPathPrefix is used for almost all the compartment-specific
+        // reports.  At this point it has the form
+        // "<something>/compartment/(<cname>)/".
+        //
+        // cDOMPathPrefix is used for DOM orphan nodes, which are counted by
+        // the JS multi-reporter but reported as part of the DOM measurements.
+        // At this point it has the form "<something>/dom/" if this compartment
+        // belongs to an nsGlobalWindow, and "explicit/dom/?!/" otherwise (in
+        // which case it shouldn't be used, because non-nsGlobalWindow
+        // compartments shouldn't have orphan DOM nodes).
+
+        cstats->extra1 = strdup(cJSPathPrefix.get());
+        cstats->extra2 = strdup(cDOMPathPrefix.get());
     }
 };
     
 nsresult
 JSMemoryMultiReporter::CollectReports(WindowPaths *windowPaths,
                                       nsIMemoryMultiReporterCallback *cb,
                                       nsISupports *closure)
 {
@@ -1751,17 +1809,18 @@ JSMemoryMultiReporter::CollectReports(Wi
     // 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(windowPaths);
     if (!rtStats.init(xpcrt))
         return NS_ERROR_FAILURE;
 
-    if (!JS::CollectRuntimeStats(xpcrt->GetJSRuntime(), &rtStats))
+    OrphanReporter orphanReporter;
+    if (!JS::CollectRuntimeStats(xpcrt->GetJSRuntime(), &rtStats, &orphanReporter))
         return NS_ERROR_FAILURE;
 
     size_t xpconnect =
         xpcrt->SizeOfIncludingThis(JsMallocSizeOf) +
         XPCWrappedNativeScope::SizeOfAllScopesIncludingThis(JsMallocSizeOf);
 
     // This is the second step (see above).  First we report stuff in the
     // "explicit" tree, then we report other stuff.
@@ -1771,16 +1830,17 @@ JSMemoryMultiReporter::CollectReports(Wi
     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/"),
+                                NS_LITERAL_CSTRING("window-objects/dom/"),
                                 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-non-window/runtime/'.");