Bug 738624 - Add ghost windows to about:compartments. r=njn
authorJustin Lebar <justin.lebar@gmail.com>
Sat, 31 Mar 2012 15:39:31 -0700
changeset 90811 bd73daadcfe99bf35df61d03ec0d997511520b66
parent 90810 11033861846565439c317767b72906743878e6cf
child 90812 9e4d09efa335ed3150ed8ac0a4b15a7b5875c2a7
push id22393
push usermbrubeck@mozilla.com
push dateMon, 02 Apr 2012 17:52:25 +0000
treeherdermozilla-central@95df15895e02 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs738624
milestone14.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 738624 - Add ghost windows to about:compartments. r=njn
dom/base/nsWindowMemoryReporter.cpp
dom/base/nsWindowMemoryReporter.h
extensions/universalchardet/tests/bug631751be_text.html
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/tests/mochitest/test_bug462428.html
mobile/android/components/UpdatePrompt.js
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
toolkit/mozapps/update/updater/progressui_null.cpp
xpcom/base/nsIMemoryReporter.idl
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -53,26 +53,33 @@ nsWindowMemoryReporter::nsWindowMemoryRe
 
 NS_IMPL_ISUPPORTS3(nsWindowMemoryReporter, nsIMemoryMultiReporter, nsIObserver,
                    nsSupportsWeakReference)
 
 /* static */
 void
 nsWindowMemoryReporter::Init()
 {
-  // The memory reporter manager is going to own this object.
-  nsWindowMemoryReporter *reporter = new nsWindowMemoryReporter();
-  NS_RegisterMemoryMultiReporter(reporter);
+  // The memory reporter manager will own this object.
+  nsWindowMemoryReporter *windowReporter = new nsWindowMemoryReporter();
+  NS_RegisterMemoryMultiReporter(windowReporter);
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     // DOM_WINDOW_DESTROYED_TOPIC announces what we call window "detachment",
     // when a window's docshell is set to NULL.
-    os->AddObserver(reporter, DOM_WINDOW_DESTROYED_TOPIC, /* weakRef = */ true);
+    os->AddObserver(windowReporter, DOM_WINDOW_DESTROYED_TOPIC,
+                    /* weakRef = */ true);
+    os->AddObserver(windowReporter, "after-minimize-memory-usage",
+                    /* weakRef = */ true);
   }
+
+  nsGhostWindowMemoryReporter *ghostReporter =
+    new nsGhostWindowMemoryReporter(windowReporter);
+  NS_RegisterMemoryMultiReporter(ghostReporter);
 }
 
 static already_AddRefed<nsIURI>
 GetWindowURI(nsIDOMWindow *aWindow)
 {
   nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(aWindow);
   NS_ENSURE_TRUE(pWindow, NULL);
 
@@ -288,45 +295,84 @@ nsWindowMemoryReporter::CollectReports(n
 NS_IMETHODIMP
 nsWindowMemoryReporter::GetExplicitNonHeap(PRInt64* aAmount)
 {
   // This reporter only measures heap memory.
   *aAmount = 0;
   return NS_OK;
 }
 
+PRUint32
+nsWindowMemoryReporter::GetGhostTimeout()
+{
+  return Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
+}
+
 NS_IMETHODIMP
 nsWindowMemoryReporter::Observe(nsISupports *aSubject, const char *aTopic,
                                 const PRUnichar *aData)
 {
-  // A window was detached.  Insert it into mDetachedWindows and run
-  // CheckForGhostWindows sometime soon.
+  if (!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC)) {
+    ObserveDOMWindowDetached(aSubject);
+  } else if (!strcmp(aTopic, "after-minimize-memory-usage")) {
+    ObserveAfterMinimizeMemoryUsage();
+  } else {
+    MOZ_ASSERT(false);
+  }
 
-  MOZ_ASSERT(!strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC));
+  return NS_OK;
+}
 
 void
-nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports *aWindow)
+nsWindowMemoryReporter::ObserveDOMWindowDetached(nsISupports* aWindow)
 {
   nsWeakPtr weakWindow = do_GetWeakReference(aWindow);
   if (!weakWindow) {
     NS_WARNING("Couldn't take weak reference to a window?");
     return;
   }
 
   mDetachedWindows.Put(weakWindow, TimeStamp());
 
   if (!mCheckForGhostWindowsCallbackPending) {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this,
                            &nsWindowMemoryReporter::CheckForGhostWindowsCallback);
     NS_DispatchToCurrentThread(runnable);
     mCheckForGhostWindowsCallbackPending = true;
   }
+}
 
-  return NS_OK;
+static PLDHashOperator
+BackdateTimeStampsEnumerator(nsISupports *aKey, TimeStamp &aTimeStamp,
+                             void* aClosure)
+{
+  TimeStamp *minTimeStamp = static_cast<TimeStamp*>(aClosure);
+  
+  if (!aTimeStamp.IsNull() && aTimeStamp > *minTimeStamp) {
+    aTimeStamp = *minTimeStamp;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+void
+nsWindowMemoryReporter::ObserveAfterMinimizeMemoryUsage()
+{
+  // Someone claims they've done enough GC/CCs so that all eligible windows
+  // have been free'd.  So we deem that any windows which satisfy ghost
+  // criteria (1) and (2) now satisfy criterion (3) as well.
+  //
+  // To effect this change, we'll backdate some of our timestamps.
+
+  TimeStamp minTimeStamp = TimeStamp::Now() -
+                           TimeDuration::FromSeconds(GetGhostTimeout());
+
+  mDetachedWindows.Enumerate(BackdateTimeStampsEnumerator,
+                             &minTimeStamp);
 }
 
 void
 nsWindowMemoryReporter::CheckForGhostWindowsCallback()
 {
   mCheckForGhostWindowsCallbackPending = false;
   CheckForGhostWindows();
 }
@@ -458,19 +504,108 @@ nsWindowMemoryReporter::CheckForGhostWin
   nonDetachedWindowDomains.Init();
 
   // Populate nonDetachedWindowDomains.
   GetNonDetachedWindowDomainsEnumeratorData nonDetachedEnumData =
     { &nonDetachedWindowDomains, tldService };
   windowsById->EnumerateRead(GetNonDetachedWindowDomainsEnumerator,
                              &nonDetachedEnumData);
 
-  PRUint32 ghostTimeout =
-    Preferences::GetUint("memory.ghost_window_timeout_seconds", 60);
-
   // Update mDetachedWindows and write the ghost window IDs into aOutGhostIDs,
   // if it's not null.
   CheckForGhostWindowsEnumeratorData ghostEnumData =
     { &nonDetachedWindowDomains, aOutGhostIDs, tldService,
-      ghostTimeout, TimeStamp::Now() };
+      GetGhostTimeout(), TimeStamp::Now() };
   mDetachedWindows.Enumerate(CheckForGhostWindowsEnumerator,
                              &ghostEnumData);
 }
+
+NS_IMPL_ISUPPORTS1(nsWindowMemoryReporter::nsGhostWindowMemoryReporter,
+                   nsIMemoryMultiReporter)
+
+nsWindowMemoryReporter::
+nsGhostWindowMemoryReporter::nsGhostWindowMemoryReporter(
+  nsWindowMemoryReporter* aWindowReporter)
+  : mWindowReporter(aWindowReporter)
+{
+}
+
+NS_IMETHODIMP
+nsWindowMemoryReporter::
+nsGhostWindowMemoryReporter::GetName(nsACString& aName)
+{
+  aName.AssignLiteral("ghost-windows");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowMemoryReporter::
+nsGhostWindowMemoryReporter::GetExplicitNonHeap(PRInt64* aOut)
+{
+  *aOut = 0;
+  return NS_OK;
+}
+
+struct ReportGhostWindowsEnumeratorData
+{
+  nsIMemoryMultiReporterCallback* callback;
+  nsISupports* closure;
+  nsresult rv;
+};
+
+static PLDHashOperator
+ReportGhostWindowsEnumerator(nsUint64HashKey* aIDHashKey, void* aClosure)
+{
+  ReportGhostWindowsEnumeratorData *data =
+    static_cast<ReportGhostWindowsEnumeratorData*>(aClosure);
+
+  nsGlobalWindow::WindowByIdTable* windowsById =
+    nsGlobalWindow::GetWindowsTable();
+  if (!windowsById) {
+    NS_WARNING("Couldn't get window-by-id hashtable?");
+    return PL_DHASH_NEXT;
+  }
+
+  nsGlobalWindow* window = windowsById->Get(aIDHashKey->GetKey());
+  if (!window) {
+    NS_WARNING("Could not look up window?");
+    return PL_DHASH_NEXT;
+  }
+
+  nsCAutoString path;
+  path.AppendLiteral("ghost-windows/");
+  AppendWindowURI(window, path);
+
+  nsresult rv = data->callback->Callback(
+    /* process = */ EmptyCString(),
+    path,
+    nsIMemoryReporter::KIND_SUMMARY,
+    nsIMemoryReporter::UNITS_COUNT,
+    /* amount = */ 1,
+    /* desc = */ EmptyCString(),
+    data->closure);
+
+  if (NS_FAILED(rv) && NS_SUCCEEDED(data->rv)) {
+    data->rv = rv;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+NS_IMETHODIMP
+nsWindowMemoryReporter::
+nsGhostWindowMemoryReporter::CollectReports(nsIMemoryMultiReporterCallback* aCb,
+                                           nsISupports* aClosure)
+{
+  // Get the IDs of all the ghost windows in existance.
+  nsTHashtable<nsUint64HashKey> ghostWindows;
+  ghostWindows.Init();
+  mWindowReporter->CheckForGhostWindows(&ghostWindows);
+
+  ReportGhostWindowsEnumeratorData reportGhostWindowsEnumData =
+    { aCb, aClosure, NS_OK };
+
+  // Call aCb->Callback() for each ghost window.
+  ghostWindows.EnumerateEntries(ReportGhostWindowsEnumerator,
+                                &reportGhostWindowsEnumData);
+
+  return reportGhostWindowsEnumData.rv;
+}
--- a/dom/base/nsWindowMemoryReporter.h
+++ b/dom/base/nsWindowMemoryReporter.h
@@ -37,16 +37,17 @@
 
 #ifndef nsWindowMemoryReporter_h__
 #define nsWindowMemoryReporter_h__
 
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "nsDataHashtable.h"
 #include "nsWeakReference.h"
+#include "nsAutoPtr.h"
 #include "mozilla/TimeStamp.h"
 
 // This should be used for any nsINode sub-class that has fields of its own
 // that it needs to measure;  any sub-class that doesn't use it will inherit
 // SizeOfExcludingThis from its super-class.  SizeOfIncludingThis() need not be
 // defined, it is inherited from nsINode.
 #define NS_DECL_SIZEOF_EXCLUDING_THIS \
   virtual size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const;
@@ -134,16 +135,33 @@ class nsWindowMemoryReporter: public nsI
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMEMORYMULTIREPORTER
   NS_DECL_NSIOBSERVER
 
   static void Init();
 
 private:
+  /**
+   * nsGhostWindowMemoryReporter generates the "ghost-windows" memory report.
+   * If you're only interested in the list of ghost windows, running this
+   * report is faster than running nsWindowMemoryReporter.
+   */
+  class nsGhostWindowMemoryReporter: public nsIMemoryMultiReporter
+  {
+  public:
+    nsGhostWindowMemoryReporter(nsWindowMemoryReporter* aWindowReporter);
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIMEMORYMULTIREPORTER
+
+  private:
+    nsRefPtr<nsWindowMemoryReporter> mWindowReporter;
+  };
+
   // Protect ctor, use Init() instead.
   nsWindowMemoryReporter();
 
   /**
    * Get the number of seconds for which a window must satisfy ghost criteria
    * (1) and (2) before we deem that it satisfies criterion (3).
    */
   PRUint32 GetGhostTimeout();
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1698,17 +1698,17 @@ class JSCompartmentsMultiReporter : publ
         Paths paths; 
         JS_IterateCompartments(nsXPConnect::GetRuntimeInstance()->GetJSRuntime(),
                                &paths, CompartmentCallback);
  
         // Report.
         for (size_t i = 0; i < paths.length(); i++)
             // These ones don't need a description, hence the "".
             REPORT(nsCString(paths[i]),
-                   nsIMemoryReporter::KIND_OTHER,
+                   nsIMemoryReporter::KIND_SUMMARY,
                    nsIMemoryReporter::UNITS_COUNT,
                    1, "");
 
         return NS_OK;
     }
 
     NS_IMETHOD
     GetExplicitNonHeap(PRInt64 *n)
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -46,16 +46,17 @@
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const KIND_NONHEAP           = Ci.nsIMemoryReporter.KIND_NONHEAP;
 const KIND_HEAP              = Ci.nsIMemoryReporter.KIND_HEAP;
 const KIND_OTHER             = Ci.nsIMemoryReporter.KIND_OTHER;
+const KIND_SUMMARY           = Ci.nsIMemoryReporter.KIND_SUMMARY;
 const UNITS_BYTES            = Ci.nsIMemoryReporter.UNITS_BYTES;
 const UNITS_COUNT            = Ci.nsIMemoryReporter.UNITS_COUNT;
 const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
 const UNITS_PERCENTAGE       = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
 
 // Because about:memory and about:compartments are non-standard URLs,
 // location.search is undefined, so we have to use location.href here.
 // The toLowerCase() calls ensure that addresses like "ABOUT:MEMORY" work.
@@ -158,20 +159,22 @@ function minimizeMemoryUsage3x(fAfter)
   }
 
   function sendHeapMinNotificationsInner()
   {
     let os = Cc["@mozilla.org/observer-service;1"]
              .getService(Ci.nsIObserverService);
     os.notifyObservers(null, "memory-pressure", "heap-minimize");
 
-    if (++i < 3)
+    if (++i < 3) {
       runSoon(sendHeapMinNotificationsInner);
-    else
+    } else {
+      os.notifyObservers(null, "after-minimize-memory-usage", "about:memory");
       runSoon(fAfter);
+    }
   }
 
   sendHeapMinNotificationsInner();
 }
 
 //---------------------------------------------------------------------------
  
 /**
@@ -270,21 +273,20 @@ function checkReport(aUnsafePath, aKind,
     assert(aDescription.match(gSentenceRegExp),
            "non-sentence explicit description");
 
   } else if (aUnsafePath.startsWith("smaps/")) {
     assert(aKind === KIND_NONHEAP, "bad smaps kind");
     assert(aUnits === UNITS_BYTES, "bad smaps units");
     assert(aDescription !== "", "empty smaps description");
 
-  } else if (aUnsafePath.startsWith("compartments/")) {
-    assert(aKind === KIND_OTHER, "bad compartments kind");
-    assert(aUnits === UNITS_COUNT, "bad compartments units");
-    assert(aAmount === 1, "bad amount");
-    assert(aDescription === "", "bad description");
+  } else if (aKind === KIND_SUMMARY) {
+    assert(!aUnsafePath.startsWith("explicit/") &&
+           !aUnsafePath.startsWith("smaps/"),
+           "bad SUMMARY path");
 
   } else {
     assert(aUnsafePath.indexOf("/") === -1, "'other' path contains '/'");
     assert(aKind === KIND_OTHER, "bad other kind: " + aUnsafePath);
     assert(aDescription.match(gSentenceRegExp),
            "non-sentence other description");
   }
 }
@@ -532,30 +534,32 @@ Report.prototype = {
     return this._unsafePath.startsWith(aTreeName) &&
            this._unsafePath.charAt(aTreeName.length) === '/';
   }
 };
 
 function getReportsByProcess(aMgr)
 {
   // Ignore the "smaps" multi-reporter in non-verbose mode, and the
-  // "compartments" multi-reporter all the time.  (Note that reports from these
-  // multi-reporters can reach here as single reports if they were in the child
-  // process.)
+  // "compartments" and "ghost-windows" multi-reporters all the time.  (Note
+  // that reports from these multi-reporters can reach here as single reports
+  // if they were in the child process.)
 
   function ignoreSingle(aPath) 
   {
     return (aPath.startsWith("smaps/") && !gVerbose) ||
-           (aPath.startsWith("compartments/"))
+           aPath.startsWith("compartments/") ||
+           aPath.startsWith("ghost-windows/");
   }
 
   function ignoreMulti(aName)
   {
-    return ((aName === "smaps" && !gVerbose) ||
-            (aName === "compartments"));
+    return (aName === "smaps" && !gVerbose) ||
+           aName === "compartments" ||
+           aName === "ghost-windows";
   }
 
   let reportsByProcess = {};
 
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aDescription)
   {
     let process = aProcess === "" ? "Main" : aProcess;
@@ -1547,25 +1551,31 @@ function updateAboutCompartments()
   // First, clear the page contents.  Necessary because
   // updateAboutCompartments() might be called more than once due to the
   // "child-memory-reporter-update" observer.
   let body = clearBody();
 
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
       getService(Ci.nsIMemoryReporterManager);
 
+  let compartmentsByProcess = getCompartmentsByProcess(mgr);
+  let ghostWindowsByProcess = getGhostWindowsByProcess(mgr);
+
+  function handleProcess(aProcess) {
+    appendProcessAboutCompartmentsElements(body, aProcess,
+                                           compartmentsByProcess[aProcess],
+                                           ghostWindowsByProcess[aProcess]);
+  }
+
   // Generate output for one process at a time.  Always start with the
   // Main process.
-  let compartmentsByProcess = getCompartmentsByProcess(mgr);
-  appendProcessCompartmentsElements(body, "Main",
-                                    compartmentsByProcess["Main"]);
+  handleProcess('Main');
   for (let process in compartmentsByProcess) {
     if (process !== "Main") {
-      appendProcessCompartmentsElements(body, process,
-                                        compartmentsByProcess[process]);
+      handleProcess(process);
     }
   }
 
   appendElement(body, "hr");
 
   let div1 = appendElement(body, "div");
   let a;
   if (gVerbose) {
@@ -1651,57 +1661,132 @@ function getCompartmentsByProcess(aMgr)
     }
   }
 
   processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
 
   return compartmentsByProcess;
 }
 
+function GhostWindow(aUnsafeURL)
+{
+  // Call it _unsafeName rather than _unsafeURL for symmetry with the
+  // Compartment object.
+  this._unsafeName = aUnsafeURL;
+
+  // this._nMerged is only defined if > 1
+}
+
+GhostWindow.prototype = {
+  merge: function(r) {
+    this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
+  }
+};
+
+function getGhostWindowsByProcess(aMgr)
+{
+  function ignoreSingle(aPath) 
+  {
+    return !aPath.startsWith('ghost-windows/')
+  }
+
+  function ignoreMulti(aName)
+  {
+    return aName !== "ghost-windows";
+  }
+
+  let ghostWindowsByProcess = {};
+
+  function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
+                        aDescription)
+  {
+    let unsafeSplit = aUnsafePath.split('/');
+    assert(unsafeSplit[0] == 'ghost-windows/',
+           'Unexpected path in getGhostWindowsByProcess: ' + aUnsafePath);
+
+    let unsafeURL = unsafeSplit[1];
+    let ghostWindow = new GhostWindow(unsafeURL);
+
+    let process = aProcess === "" ? "Main" : aProcess;
+    if (!ghostWindowsByProcess[process]) {
+      ghostWindowsByProcess[process] = {};
+    }
+
+    if (ghostWindowsByProcess[process][unsafeURL]) {
+      ghostWindowsByProcess[process][unsafeURL].merge(ghostWindow);
+    }
+    else {
+      ghostWindowsByProcess[process][unsafeURL] = ghostWindow;
+    }
+  }
+
+  processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
+
+  return ghostWindowsByProcess;
+}
+
 //---------------------------------------------------------------------------
 
-function appendProcessCompartmentsElementsHelper(aP, aCompartments, aKindString)
+function appendProcessAboutCompartmentsElementsHelper(aP, aEntries, aKindString)
 {
-  appendElementWithText(aP, "h2", "", aKindString + " Compartments\n");
+  // aEntries might be null or undefined, e.g. if there are no ghost windows
+  // for this process.
+  aEntries = aEntries ? aEntries : {};
 
-  let compartmentTextArray = [];
+  appendElementWithText(aP, "h2", "", aKindString + "\n");
+
   let uPre = appendElement(aP, "pre", "entries");
-  for (let name in aCompartments) {
-    let c = aCompartments[name];
-    let isSystemKind = aKindString === "System";
-    if (c._isSystemCompartment === isSystemKind) {
-      let text = flipBackslashes(c._unsafeName);
-      if (c._nMerged) {
-        text += " [" + c._nMerged + "]";
-      }
-      text += "\n";
-      compartmentTextArray.push(text);
+
+  let lines = [];
+  for (let name in aEntries) {
+    let e = aEntries[name];
+    let line = flipBackslashes(e._unsafeName);
+    if (e._nMerged) {
+      line += ' [' + e._nMerged + ']';
     }
+    line += '\n';
+    lines.push(line);
   }
-  compartmentTextArray.sort();
+  lines.sort();
 
-  for (let i = 0; i < compartmentTextArray.length; i++) {
-    appendElementWithText(uPre, "span", "", compartmentTextArray[i]);
+  for (let i = 0; i < lines.length; i++) {
+    appendElementWithText(uPre, "span", "", lines[i]);
   }
 
   appendTextNode(aP, "\n");   // gives nice spacing when we cut and paste
 }
 
 /**
  * Appends the elements for a single process.
  *
  * @param aP
  *        The parent DOM node.
  * @param aProcess
  *        The name of the process.
  * @param aCompartments
  *        Table of Compartments for this process, indexed by _unsafeName.
+ * @param aGhostWindows
+ *        Array of window URLs of ghost windows.
+ *
  * @return The generated text.
  */
-function appendProcessCompartmentsElements(aP, aProcess, aCompartments)
+function appendProcessAboutCompartmentsElements(aP, aProcess, aCompartments, aGhostWindows)
 {
   appendElementWithText(aP, "h1", "", aProcess + " Process");
   appendTextNode(aP, "\n\n");   // gives nice spacing when we cut and paste
+
+  let userCompartments = {};
+  let systemCompartments = {};
+  for (let name in aCompartments) {
+    let c = aCompartments[name];
+    if (c._isSystemCompartment) {
+      systemCompartments[name] = c;
+    }
+    else {
+      userCompartments[name] = c;
+    }
+  }
   
-  appendProcessCompartmentsElementsHelper(aP, aCompartments, "User");
-  appendProcessCompartmentsElementsHelper(aP, aCompartments, "System");
+  appendProcessAboutCompartmentsElementsHelper(aP, userCompartments, "User Compartments");
+  appendProcessAboutCompartmentsElementsHelper(aP, systemCompartments, "System Compartments");
+  appendProcessAboutCompartmentsElementsHelper(aP, aGhostWindows, "Ghost Windows");
 }
 
--- a/toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
@@ -42,16 +42,17 @@
   }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
   const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
+  const SUMMARY = Ci.nsIMemoryReporter.KIND_SUMMARY;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
 
   function f(aProcess, aPath, aKind, aUnits, aAmount) {
     return {
       process:     aProcess,
       path:        aPath,
@@ -66,18 +67,18 @@
     // These should be ignored.
     f("", "explicit/a",         HEAP,    BYTES, 222 * MB),
     f("", "explicit/b/a",       HEAP,    BYTES,  85 * MB),
     f("", "explicit/b/b",       NONHEAP, BYTES,  85 * MB),
     f("", "other1",             OTHER,   BYTES, 111 * MB),
     f("", "other2",             OTHER,   COUNT, 888),
 
     f("2nd", "explicit/c",      HEAP,    BYTES, 333 * MB),
-    f("2nd", "compartments/user/child-user-compartment",     OTHER, COUNT, 1),
-    f("2nd", "compartments/system/child-system-compartment", OTHER, COUNT, 1)
+    f("2nd", "compartments/user/child-user-compartment",     SUMMARY, COUNT, 1),
+    f("2nd", "compartments/system/child-system-compartment", SUMMARY, COUNT, 1)
   ];
 
   var fakeMultiReporters = [
     // These shouldn't show up.
     { name: "fake",
       collectReports: function(aCbObj, aClosure) {
         function f(aP, aK, aU, aA) {
           aCbObj.callback("", aP, aK, aU, aA, "Desc.", aClosure);
@@ -85,32 +86,42 @@
         f("explicit/a/d",     HEAP,    BYTES,  13 * MB);
         f("explicit/b/c",     NONHEAP, BYTES,  10 * MB);
        },
        explicitNonHeap: 10*MB
     },
     { name: "compartments",
       collectReports: function(aCbObj, aClosure) {
         function f(aP) {
-          aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
+          aCbObj.callback("", aP, SUMMARY, COUNT, 1, "", aClosure);
         }
         f("compartments/user/http:\\\\foo.com\\");
         f("compartments/user/https:\\\\bar.com\\bar?baz");
         f("compartments/user/https:\\\\very-long-url.com\\very-long\\oh-so-long\\really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789");
         // This moz-nullprincipal one is shown under "System Compartments" even
         // though its path indicates it's a user compartment.
         f("compartments/user/moz-nullprincipal:{7ddefdaf-34f1-473f-9b03-50a4568ccb06}");
         // This should show up once with a "[3]" suffix
         f("compartments/system/[System Principal]");
         f("compartments/system/[System Principal]");
         f("compartments/system/[System Principal]");
         f("compartments/system/atoms");
       },
       explicitNonHeap: 0
     },
+    { name: "ghost-windows",
+      collectReports: function(aCbObj, aClosure) {
+        function f(aP) {
+          aCbObj.callback("", aP, SUMMARY, COUNT, 1, "", aClosure);
+        }
+        f("ghost-windows/https:\\\\very-long-url.com\\very-long\\oh-so-long\\really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789");
+        f("ghost-windows/http:\\\\foobar.com\\foo?bar#baz");
+      },
+      explicitNonHeap: 0
+    },
     // These shouldn't show up.
     { name: "smaps",
        collectReports: function(aCbObj, aClosure) {
         // The amounts are given in pages, so multiply here by 4kb.
         function f(aP, aA) {
           aCbObj.callback("", aP, NONHEAP, BYTES, aA * 4 * KB, "Desc.", aClosure);
         }
         f("smaps/vsize/a",     24);
@@ -144,24 +155,30 @@ http://foo.com/\n\
 https://bar.com/bar?baz\n\
 https://very-long-url.com/very-long/oh-so-long/really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789\n\
 \n\
 System Compartments\n\
 [System Principal] [3]\n\
 atoms\n\
 moz-nullprincipal:{7ddefdaf-34f1-473f-9b03-50a4568ccb06}\n\
 \n\
+Ghost Windows\n\
+http://foobar.com/foo?bar#baz\n\
+https://very-long-url.com/very-long/oh-so-long/really-quite-long.html?a=2&b=3&c=4&d=5&e=abcdefghijklmnopqrstuvwxyz&f=123456789123456789123456789\n\
+\n\
 2nd Process\n\
 \n\
 User Compartments\n\
 child-user-compartment\n\
 \n\
 System Compartments\n\
 child-system-compartment\n\
 \n\
+Ghost Windows\n\
+\n\
 ";
 
   // Verbose mode output is the same when you cut and paste.
   var acvExpectedText = acExpectedText;
 
   function finish()
   {
     // Unregister fake reporters and multi-reporters, re-register the real
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -112,20 +112,18 @@ interface nsIMemoryReporter : nsISupport
    * - Paths starting with "smaps/" represent regions of virtual memory that the
    *   process has mapped.  The rest of the path describes the type of
    *   measurement; for instance, the reporter "smaps/rss/[stack]" might report
    *   how much of the process's stack is currently in physical memory.
    *
    *   Reporters in this category must have kind NONHEAP, units BYTES, and
    *   a non-empty description.
    *
-   * - Paths starting with "compartments/" represent the names of JS
-   *   compartments.  Reporters in this category must paths of the form
-   *   "compartments/user/<name>" or "compartments/system/<name>", amount 1,
-   *   kind OTHER, units COUNT, and an empty description.
+   * - Reporters with kind SUMMARY may have any path which doesn't start with
+   *   "explicit/" or "smaps/".
    *
    * - All other paths represent cross-cutting values and may overlap with any
    *   other reporter.  Reporters in this category must have paths that do not
    *   contain '/' separators, kind OTHER, and a description that is a
    *   sentence.
    */
   readonly attribute AUTF8String path;
 
@@ -141,20 +139,32 @@ interface nsIMemoryReporter : nsISupport
    *    live on the heap.  Such memory is commonly allocated by calling one of
    *    the OS's memory-mapping functions (e.g. mmap, VirtualAlloc, or
    *    vm_allocate).  Reporters in this category must have units UNITS_BYTES
    *    and must have a path starting with "explicit/" or "smaps/".
    *
    *  - OTHER: reporters which don't fit into either of these categories. Such
    *    reporters must have a path that does not start with "explicit/" or
    *    "smaps/" and may have any units.
+   *
+   *  - SUMMARY: reporters which report data that's available in a more
+   *    detailed form via other reporters.  These reporters are sometimes
+   *    useful for efficiency purposes -- for example, a KIND_SUMMARY reporter
+   *    might list all the JS compartments without the overhead of the full JS
+   *    memory reporter, which walks the JS heap.
+   *
+   *    Unlike other reporters, SUMMARY reporters may have empty descriptions.
+   *
+   *    SUMMARY reporters must not have a path starting with "explicit/" or
+   *    "smaps/".
    */
   const PRInt32 KIND_NONHEAP = 0;
   const PRInt32 KIND_HEAP    = 1;
   const PRInt32 KIND_OTHER   = 2;
+  const PRInt32 KIND_SUMMARY = 3;
 
   /*
    * KIND_MAPPED is a deprecated synonym for KIND_NONHEAP.  We keep it around
    * to as not to break extensions which might use this interface, but we will
    * remove it eventually.
    */
   const PRInt32 KIND_MAPPED  = 0;