Bug 702300 (part 8) - Add about:compartments. r=jlebar, ehsan.
authorNicholas Nethercote <nnethercote@mozilla.com>
Thu, 16 Feb 2012 22:10:39 -0800
changeset 87264 17927137a6993f35d7d97a19997112214d191ed2
parent 87263 4edde0ad19d888dcc68420bf6531ca8a8fc7ed12
child 87265 ec21e7e02464f60cabd31bdedb126fd9962dd4a1
push id22103
push userbmo@edmorley.co.uk
push dateTue, 21 Feb 2012 12:01:45 +0000
treeherdermozilla-central@4038ffaa5d82 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar, ehsan
bugs702300
milestone13.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 702300 (part 8) - Add about:compartments. r=jlebar, ehsan.
docshell/base/nsAboutRedirector.cpp
docshell/build/nsDocShellModule.cpp
js/xpconnect/src/XPCJSRuntime.cpp
toolkit/components/aboutmemory/content/aboutMemory.css
toolkit/components/aboutmemory/content/aboutMemory.js
toolkit/components/aboutmemory/content/aboutMemory.xhtml
toolkit/components/aboutmemory/tests/Makefile.in
toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
toolkit/components/aboutmemory/tests/test_aboutmemory.xul
xpcom/base/nsIMemoryReporter.idl
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -81,16 +81,19 @@ static RedirEntry kRedirMap[] = {
     { "buildconfig", "chrome://global/content/buildconfig.html",
       nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT },
     { "license", "chrome://global/content/license.html",
       nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT },
     { "neterror", "chrome://global/content/netError.xhtml",
       nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
       nsIAboutModule::ALLOW_SCRIPT |
       nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+    // aboutMemory.xhtml implements about:compartments
+    { "compartments", "chrome://global/content/aboutMemory.xhtml",
+      nsIAboutModule::ALLOW_SCRIPT },
     { "memory", "chrome://global/content/aboutMemory.xhtml",
       nsIAboutModule::ALLOW_SCRIPT },
     { "addons", "chrome://mozapps/content/extensions/extensions.xul",
       nsIAboutModule::ALLOW_SCRIPT },
     { "newaddon", "chrome://mozapps/content/extensions/newaddon.xul",
       nsIAboutModule::ALLOW_SCRIPT |
       nsIAboutModule::HIDE_FROM_ABOUTABOUT },
     { "support", "chrome://global/content/aboutSupport.xhtml",
--- a/docshell/build/nsDocShellModule.cpp
+++ b/docshell/build/nsDocShellModule.cpp
@@ -205,16 +205,17 @@ const mozilla::Module::ContractIDEntry k
 #endif
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "credits", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "plugins", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "mozilla", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "logo", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "buildconfig", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "license", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "neterror", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
+  { NS_ABOUT_MODULE_CONTRACTID_PREFIX "compartments", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "memory", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "addons", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newaddon", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_ABOUT_MODULE_CONTRACTID_PREFIX "support", &kNS_ABOUT_REDIRECTOR_MODULE_CID },
   { NS_URI_LOADER_CONTRACTID, &kNS_URI_LOADER_CID },
   { NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &kNS_DOCUMENTLOADER_SERVICE_CID },
   { NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &kNS_EXTERNALHELPERAPPSERVICE_CID },
   { NS_EXTERNALPROTOCOLSERVICE_CONTRACTID, &kNS_EXTERNALHELPERAPPSERVICE_CID },
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2079,16 +2079,17 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* 
             NS_RUNTIMEABORT("PR_NewCondVar failed.");
 
         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;
 
     mCompartmentMap.Init();
 
--- a/toolkit/components/aboutmemory/content/aboutMemory.css
+++ b/toolkit/components/aboutmemory/content/aboutMemory.css
@@ -30,27 +30,29 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+/* This file is used for both about:memory and about:compartments. */
+
 body.verbose {
   /* override setting in about.css */
   max-width: 100% !important;
 }
 
-body.non-verbose pre.tree {
+body.non-verbose pre.entries {
   overflow-x: hidden;
   text-overflow: ellipsis;
 }
 
-.sectionHeader {
+h2 {
   background: #ddd;
   padding-left: .1em;
 }
 
 .accuracyWarning {
   color: #f00;
 }
 
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -31,110 +31,203 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+// This file is used for both about:memory and about:compartments.
+
 "use strict";
 
+//---------------------------------------------------------------------------
+// Code shared by about:memory and about:compartments
+//---------------------------------------------------------------------------
+
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
-let gAddedObserver = false;
-
 const KIND_NONHEAP           = Ci.nsIMemoryReporter.KIND_NONHEAP;
 const KIND_HEAP              = Ci.nsIMemoryReporter.KIND_HEAP;
 const KIND_OTHER             = Ci.nsIMemoryReporter.KIND_OTHER;
 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.
+const gVerbose = location.href === "about:memory?verbose" ||
+                 location.href === "about:compartments?verbose";
+
+let gChildMemoryListener = undefined;
+
+//---------------------------------------------------------------------------
+
 // Forward slashes in URLs in paths are represented with backslashes to avoid
 // being mistaken for path separators.  Paths/names/descriptions where this
 // hasn't been undone are prefixed with "unsafe"; the rest are prefixed with
 // "safe".
-function makeSafe(aUnsafeStr)
+function flipBackslashes(aUnsafeStr)
 {
   return aUnsafeStr.replace(/\\/g, '/');
 }
 
 function assert(aCond, aMsg)
 {
   if (!aCond) {
     throw("assertion failed: " + aMsg);
   }
 }
 
 function debug(x)
 {
-  let content = document.getElementById("content");
-  appendElementWithText(content, "div", "legend", JSON.stringify(x));
+  appendElementWithText(document.body, "div", "legend", JSON.stringify(x));
 }
 
 //---------------------------------------------------------------------------
 
-function onLoad()
+function addChildObserversAndUpdate(aUpdateFn)
 {
   let os = Cc["@mozilla.org/observer-service;1"].
       getService(Ci.nsIObserverService);
   os.notifyObservers(null, "child-memory-reporter-request", null);
 
-  os.addObserver(ChildMemoryListener, "child-memory-reporter-update", false);
-  gAddedObserver = true;
+  gChildMemoryListener = aUpdateFn;
+  os.addObserver(gChildMemoryListener, "child-memory-reporter-update", false);
+ 
+  gChildMemoryListener();
+}
 
-  update();
+function onLoad()
+{
+  if (location.href.indexOf("about:memory") === 0) {
+    document.title = "about:memory";
+    onLoadAboutMemory();
+  } else if (location.href.indexOf("about:compartment") === 0) {
+    document.title = "about:compartments";
+    onLoadAboutCompartments();
+  } else {
+    assert(false, "Unknown location");
+  }
 }
 
 function onUnload()
 {
   // We need to check if the observer has been added before removing; in some
   // circumstances (eg. reloading the page quickly) it might not have because
-  // onLoad might not fire.
-  if (gAddedObserver) {
+  // onLoadAbout{Memory,Compartments} might not fire.
+  if (gChildMemoryListener) {
     let os = Cc["@mozilla.org/observer-service;1"].
         getService(Ci.nsIObserverService);
-    os.removeObserver(ChildMemoryListener, "child-memory-reporter-update");
+    os.removeObserver(gChildMemoryListener, "child-memory-reporter-update");
   }
 }
 
 // For maximum effect, this returns to the event loop between each
 // notification.  See bug 610166 comment 12 for an explanation.
 // Ideally a single notification would be enough.
-function sendHeapMinNotifications()
+function minimizeMemoryUsage3x(fAfter)
 {
+  let i = 0;
+
   function runSoon(f)
   {
     let tm = Cc["@mozilla.org/thread-manager;1"]
               .getService(Ci.nsIThreadManager);
 
     tm.mainThread.dispatch({ run: f }, Ci.nsIThread.DISPATCH_NORMAL);
   }
 
   function sendHeapMinNotificationsInner()
   {
     let os = Cc["@mozilla.org/observer-service;1"]
              .getService(Ci.nsIObserverService);
     os.notifyObservers(null, "memory-pressure", "heap-minimize");
 
-    if (++j < 3)
+    if (++i < 3)
       runSoon(sendHeapMinNotificationsInner);
     else
-      runSoon(update);
+      runSoon(fAfter);
   }
 
-  let j = 0;
   sendHeapMinNotificationsInner();
 }
 
 //---------------------------------------------------------------------------
+ 
+/**
+ * Iterates over each reporter and multi-reporter.
+ *
+ * @param aMgr
+ *        The memory reporter manager.
+ * @param aIgnoreSingle
+ *        Function that indicates if we should skip a single reporter, based
+ *        on its path.
+ * @param aIgnoreMulti
+ *        Function that indicates if we should skip a multi-reporter, based on
+ *        its name.
+ * @param aHandleReport
+ *        The function that's called for each report.
+ */
+function processMemoryReporters(aMgr, aIgnoreSingle, aIgnoreMulti,
+                                aHandleReport)
+{
+  // Process each memory reporter with aHandleReport.
+  //
+  // - Note that copying rOrig.amount (which calls a C++ function under the
+  //   IDL covers) to r._amount for every reporter now means that the
+  //   results as consistent as possible -- measurements are made all at
+  //   once before most of the memory required to generate this page is
+  //   allocated.
+  //
+  // - After this point we never use the original memory report again.
+
+  let e = aMgr.enumerateReporters();
+  while (e.hasMoreElements()) {
+    let rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    let unsafePath = rOrig.path;
+    try {
+      if (!aIgnoreSingle(unsafePath)) {
+        aHandleReport(rOrig.process, unsafePath, rOrig.kind, rOrig.units,
+                      rOrig.amount, rOrig.description);
+      }
+    }
+    catch (e) {
+      debug("Bad memory reporter " + unsafePath + ": " + e);
+    }
+  }
+  let e = aMgr.enumerateMultiReporters();
+  while (e.hasMoreElements()) {
+    let mrOrig = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    let name = mrOrig.name;
+    try {
+      if (!aIgnoreMulti(name)) {
+        mrOrig.collectReports(aHandleReport, null);
+      }
+    }
+    catch (e) {
+      debug("Bad memory multi-reporter " + name + ": " + e);
+    }
+  }
+}
+
+//---------------------------------------------------------------------------
+
+function clearBody()
+{
+  let oldBody = document.body;
+  let body = oldBody.cloneNode(false);
+  oldBody.parentNode.replaceChild(body, oldBody);
+  body.classList.add(gVerbose ? 'verbose' : 'non-verbose');
+  return body;
+}
 
 function appendTextNode(aP, aText)
 {
   let e = document.createTextNode(aText);
   aP.appendChild(e);
   return e;
 }
 
@@ -151,18 +244,18 @@ function appendElement(aP, aTagName, aCl
 function appendElementWithText(aP, aTagName, aClassName, aText)
 {
   let e = appendElement(aP, aTagName, aClassName);
   appendTextNode(e, aText);
   return e;
 }
 
 //---------------------------------------------------------------------------
-
-const gVerbose = location.href === "about:memory?verbose";
+// Code specific to about:memory
+//---------------------------------------------------------------------------
 
 const kUnknown = -1;    // used for an unknown _amount
 
 const kTreeUnsafeDescriptions = {
   'explicit' :
 "This tree covers explicit memory allocations by the application, both at the \
 operating system level (via calls to functions such as VirtualAlloc, \
 vm_allocate, and mmap), and at the heap allocation level (via functions such \
@@ -220,120 +313,119 @@ const kTreeNames = {
   'other':    'Other Measurements'
 };
 
 const kMapTreePaths =
   ['smaps/resident', 'smaps/pss', 'smaps/vsize', 'smaps/swap'];
 
 //---------------------------------------------------------------------------
 
-function ChildMemoryListener(aSubject, aTopic, aData)
+function onLoadAboutMemory()
 {
-  update();
+  addChildObserversAndUpdate(updateAboutMemory);
 }
 
 function doGlobalGC()
 {
   Cu.forceGC();
   let os = Cc["@mozilla.org/observer-service;1"]
             .getService(Ci.nsIObserverService);
   os.notifyObservers(null, "child-gc-request", null);
-  update();
+  updateAboutMemory();
 }
 
 function doCC()
 {
   window.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils)
         .cycleCollect();
   let os = Cc["@mozilla.org/observer-service;1"]
             .getService(Ci.nsIObserverService);
   os.notifyObservers(null, "child-cc-request", null);
-  update();
+  updateAboutMemory();
 }
 
 //---------------------------------------------------------------------------
 
 /**
  * Top-level function that does the work of generating the page.
  */
-function update()
+function updateAboutMemory()
 {
-  // First, clear the page contents.  Necessary because update() might be
-  // called more than once due to ChildMemoryListener.
-  let oldContent = document.getElementById("content");
-  let content = oldContent.cloneNode(false);
-  oldContent.parentNode.replaceChild(content, oldContent);
-  content.classList.add(gVerbose ? 'verbose' : 'non-verbose');
+  // First, clear the page contents.  Necessary because updateAboutMemory()
+  // 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);
 
   // Generate output for one process at a time.  Always start with the
   // Main process.
   let reportsByProcess = getReportsByProcess(mgr);
   let hasMozMallocUsableSize = mgr.hasMozMallocUsableSize;
-  appendProcessElements(content, "Main", reportsByProcess["Main"],
-                        hasMozMallocUsableSize);
+  appendProcessReportsElements(body, "Main", reportsByProcess["Main"],
+                               hasMozMallocUsableSize);
   for (let process in reportsByProcess) {
     if (process !== "Main") {
-      appendProcessElements(content, process, reportsByProcess[process],
-                            hasMozMallocUsableSize);
+      appendProcessReportsElements(body, process, reportsByProcess[process],
+                                   hasMozMallocUsableSize);
     }
   }
 
-  appendElement(content, "hr");
+  appendElement(body, "hr");
 
   // Memory-related actions.
   const UpDesc = "Re-measure.";
   const GCDesc = "Do a global garbage collection.";
   const CCDesc = "Do a cycle collection.";
   const MPDesc = "Send three \"heap-minimize\" notifications in a " +
                  "row.  Each notification triggers a global garbage " +
                  "collection followed by a cycle collection, and causes the " +
                  "process to reduce memory usage in other ways, e.g. by " +
                  "flushing various caches.";
 
   function appendButton(aTitle, aOnClick, aText, aId)
   {
-    let b = appendElementWithText(content, "button", "", aText);
+    let b = appendElementWithText(body, "button", "", aText);
     b.title = aTitle;
     b.onclick = aOnClick
     if (aId) {
       b.id = aId;
     }
   }
 
   // The "Update" button has an id so it can be clicked in a test.
-  appendButton(UpDesc, update,                   "Update", "updateButton");
-  appendButton(GCDesc, doGlobalGC,               "GC");
-  appendButton(CCDesc, doCC,                     "CC");
-  appendButton(MPDesc, sendHeapMinNotifications, "Minimize memory usage");
+  appendButton(UpDesc, updateAboutMemory, "Update", "updateButton");
+  appendButton(GCDesc, doGlobalGC,        "GC");
+  appendButton(CCDesc, doCC,              "CC");
+  appendButton(MPDesc, function() { minimizeMemoryUsage3x(updateAboutMemory); },
+                                          "Minimize memory usage");
 
-  let div1 = appendElement(content, "div");
+  let div1 = appendElement(body, "div");
   if (gVerbose) {
     let a = appendElementWithText(div1, "a", "option", "Less verbose");
     a.href = "about:memory";
   } else {
     let a = appendElementWithText(div1, "a", "option", "More verbose");
     a.href = "about:memory?verbose";
   }
 
-  let div2 = appendElement(content, "div");
+  let div2 = appendElement(body, "div");
   let a = appendElementWithText(div2, "a", "option",
                                 "Troubleshooting information");
   a.href = "about:support";
 
   let legendText1 = "Click on a non-leaf node in a tree to expand ('++') " +
                     "or collapse ('--') its children.";
   let legendText2 = "Hover the pointer over the name of a memory report " +
                     "to see a description of what it measures.";
 
-  appendElementWithText(content, "div", "legend", legendText1);
-  appendElementWithText(content, "div", "legend", legendText2);
+  appendElementWithText(body, "div", "legend", legendText1);
+  appendElementWithText(body, "div", "legend", legendText2);
 }
 
 //---------------------------------------------------------------------------
 
 function Report(aUnsafePath, aKind, aUnits, aAmount, aUnsafeDesc)
 {
   this._unsafePath  = aUnsafePath;
   this._kind        = aKind;
@@ -363,26 +455,33 @@ Report.prototype = {
     // called "explicit" which is not part of the "explicit" tree.
     return this._unsafePath.indexOf(aTreeName) === 0 &&
            this._unsafePath.charAt(aTreeName.length) === '/';
   }
 };
 
 function getReportsByProcess(aMgr)
 {
-  // Process each memory reporter:
-  // - Put a copy of its report(s) into a sub-table indexed by its process.
-  //   Each copy is a Report object.  After this point we never use the
-  //   original memory reporter again.
-  //
-  // - Note that copying rOrig.amount (which calls a C++ function under the
-  //   IDL covers) to r._amount for every report now means that the
-  //   results as consistent as possible -- measurements are made all at
-  //   once before most of the memory required to generate this page is
-  //   allocated.
+  // 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.)
+
+  function ignoreSingle(aPath) 
+  {
+    return (aPath.indexOf("smaps/") === 0 && !gVerbose) ||
+           (aPath.indexOf("compartments/") === 0)
+  }
+
+  function ignoreMulti(aName)
+  {
+    return ((aName === "smaps" && !gVerbose) ||
+            (aName === "compartments"));
+  }
+
   let reportsByProcess = {};
 
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aUnsafeDesc)
   {
     let process = aProcess === "" ? "Main" : aProcess;
     let r = new Report(aUnsafePath, aKind, aUnits, aAmount, aUnsafeDesc);
     if (!reportsByProcess[process]) {
@@ -394,44 +493,17 @@ function getReportsByProcess(aMgr)
       // Already an entry;  must be a duplicated report.  This can happen
       // legitimately.  Merge them.
       rOld.merge(r);
     } else {
       reports[r._unsafePath] = r;
     }
   }
 
-  // Process vanilla reporters first, then multi-reporters.
-  let e = aMgr.enumerateReporters();
-  while (e.hasMoreElements()) {
-    let rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-    try {
-      handleReport(rOrig.process, rOrig.path, rOrig.kind, rOrig.units,
-                   rOrig.amount, rOrig.description);
-    }
-    catch(e) {
-      debug("An error occurred when collecting results from the memory reporter " +
-            rOrig.path + ": " + e);
-    }
-  }
-  let e = aMgr.enumerateMultiReporters();
-  while (e.hasMoreElements()) {
-    let mrOrig = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
-    // Ignore the "smaps" reports in non-verbose mode.
-    if (!gVerbose && mrOrig.name === "smaps") {
-      continue;
-    }
-
-    try {
-      mrOrig.collectReports(handleReport, null);
-    }
-    catch(e) {
-      debug("An error occurred when collecting a multi-reporter's results: " + e);
-    }
-  }
+  processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
 
   return reportsByProcess;
 }
 
 //---------------------------------------------------------------------------
 
 // There are two kinds of TreeNode.
 // - Leaf TreeNodes correspond to Reports and have more properties.
@@ -776,42 +848,43 @@ function appendWarningElements(aP, aHasK
 
     let ul = appendElement(div, "ul");
     for (let i = 0;
          i < gUnsafePathsWithInvalidValuesForThisProcess.length;
          i++)
     {
       appendTextNode(ul, " ");
       appendElementWithText(ul, "li", "", 
-        makeSafe(gUnsafePathsWithInvalidValuesForThisProcess[i]));
+        flipBackslashes(gUnsafePathsWithInvalidValuesForThisProcess[i]));
       appendTextNode(ul, "\n");
     }
 
     appendElementWithText(div, "p", "",
       "This indicates a defect in one or more memory reporters.  The " +
       "invalid values are highlighted.");
     appendTextNode(div, "\n\n");  
     gUnsafePathsWithInvalidValuesForThisProcess = [];  // reset for the next process
   }
 }
 
 /**
- * Appends the elements for a single process.
+ * Appends the elements for a single process's Reports.
  *
  * @param aP
  *        The parent DOM node.
  * @param aProcess
  *        The name of the process.
  * @param aReports
  *        Table of Reports for this process, indexed by _unsafePath.
  * @param aHasMozMallocUsableSize
  *        Boolean indicating if moz_malloc_usable_size works.
  * @return The generated text.
  */
-function appendProcessElements(aP, aProcess, aReports, aHasMozMallocUsableSize)
+function appendProcessReportsElements(aP, aProcess, aReports,
+                                      aHasMozMallocUsableSize)
 {
   appendElementWithText(aP, "h1", "", aProcess + " Process");
   appendTextNode(aP, "\n\n");   // gives nice spacing when we cut and paste
 
   // We'll fill this in later.
   let warningsDiv = appendElement(aP, "div", "accuracyWarning");
 
   let explicitTree = buildTree(aReports, 'explicit');
@@ -1012,18 +1085,18 @@ function appendMrNameSpan(aP, aKind, aKi
   } else if (aKidsState === kShowKids) {
     appendElementWithText(aP, "span", "mrSep hidden", " ++ ");
     appendElementWithText(aP, "span", "mrSep",        " -- ");
   } else {
     assert(false, "bad aKidsState");
   }
 
   let nameSpan = appendElementWithText(aP, "span", "mrName",
-                                       makeSafe(aUnsafeName));
-  nameSpan.title = kindToString(aKind) + makeSafe(aUnsafeDesc);
+                                       flipBackslashes(aUnsafeName));
+  nameSpan.title = kindToString(aKind) + flipBackslashes(aUnsafeDesc);
 
   if (aIsUnknown) {
     let noteSpan = appendElementWithText(aP, "span", "mrNote", " [*]");
     noteSpan.title =
       "Warning: this memory reporter was unable to compute a useful value. ";
   }
   if (aIsInvalid) {
     let noteSpan = appendElementWithText(aP, "span", "mrNote", " [?!]");
@@ -1097,20 +1170,20 @@ function expandPathToThisElement(aElemen
   } else if (aElement.classList.contains("hasKids")) {
     // Unhide the '--' separator and hide the '++' separator.
     let  plusSpan = aElement.childNodes[2];
     let minusSpan = aElement.childNodes[3];
     assertClassListContains(plusSpan,  "mrSep");
     assertClassListContains(minusSpan, "mrSep");
     plusSpan.classList.add("hidden");
     minusSpan.classList.remove("hidden");
-    expandPathToThisElement(aElement.parentNode);       // kids or pre.tree
+    expandPathToThisElement(aElement.parentNode);       // kids or pre.entries
 
   } else {
-    assertClassListContains(aElement, "tree");
+    assertClassListContains(aElement, "entries");
   }
 }
 
 /**
  * Appends the elements for the tree, including its heading.
  *
  * @param aPOuter
  *        The parent DOM node.
@@ -1192,17 +1265,17 @@ function appendTreeElements(aPOuter, aT,
     // be collapsed if the node is clicked on.
     let d;
     let hasKids = aT._kids.length > 0;
     let kidsState;
     let showSubtrees;
     if (hasKids) {
       // Determine if we should show the sub-tree below this entry;  this
       // involves reinstating any previous toggling of the sub-tree.
-      let safeTreeId = makeSafe(aProcess + ":" + unsafePath);
+      let safeTreeId = flipBackslashes(aProcess + ":" + unsafePath);
       showSubtrees = !aT._hideKids;
       if (gTogglesBySafeTreeId[safeTreeId]) {
         showSubtrees = !showSubtrees;
       }
       d = appendElement(aP, "span", "hasKids");
       d.id = safeTreeId;
       d.onclick = toggle;
       kidsState = showSubtrees ? kShowKids : kHideKids;
@@ -1210,18 +1283,18 @@ function appendTreeElements(aPOuter, aT,
       assert(!aT._hideKids, "leaf node with _hideKids set")
       kidsState = kNoKids;
       d = aP;
     }
 
     appendMrValueSpan(d, tString, tIsInvalid);
     appendElementWithText(d, "span", "mrPerc", percText);
 
-    // We don't want to show '(nonheap)' on a tree like 'map/vsize', since the
-    // whole tree is non-heap.
+    // We don't want to show '(nonheap)' on a tree like 'smaps/vsize', since
+    // the whole tree is non-heap.
     let kind = isExplicitTree ? aT._kind : undefined;
     appendMrNameSpan(d, kind, kidsState, aT._unsafeDescription, aT._unsafeName,
                      aT._isUnknown, tIsInvalid, aT._nMerged);
     appendTextNode(d, "\n");
 
     // In non-verbose mode, invalid nodes can be hidden in collapsed sub-trees.
     // But it's good to always see them, so force this.
     if (!gVerbose && tIsInvalid) {
@@ -1254,17 +1327,17 @@ function appendTreeElements(aPOuter, aT,
                             baseIndentText, tString.length);
         aIndentGuide.pop();
       }
     }
   }
 
   appendSectionHeader(aPOuter, kTreeNames[aT._unsafeName]);
  
-  let pre = appendElement(aPOuter, "pre", "tree");
+  let pre = appendElement(aPOuter, "pre", "entries");
   appendTreeElements2(pre, /* prePath = */"", aT, [], "", rootStringLength);
   appendTextNode(aPOuter, "\n");  // gives nice spacing when we cut and paste
 }
 
 //---------------------------------------------------------------------------
 
 function OtherReport(aUnsafePath, aUnits, aAmount, aUnsafeDesc, aNMerged)
 {
@@ -1323,28 +1396,28 @@ OtherReport.compare = function(a, b) {
  * @param aProcess
  *        The process these Reports correspond to.
  * @return The generated text.
  */
 function appendOtherElements(aP, aReportsByProcess)
 {
   appendSectionHeader(aP, kTreeNames['other']);
 
-  let pre = appendElement(aP, "pre", "tree");
+  let pre = appendElement(aP, "pre", "entries");
 
   // Generate an array of Report-like elements, stripping out all the
   // Reports that have already been handled.  Also find the width of the
   // widest element, so we can format things nicely.
   let maxStringLength = 0;
   let otherReports = [];
   for (let unsafePath in aReportsByProcess) {
     let r = aReportsByProcess[unsafePath];
     if (!r._done) {
       assert(r._kind === KIND_OTHER,
-             "_kind !== KIND_OTHER for " + makeSafe(r._unsafePath));
+             "_kind !== KIND_OTHER for " + flipBackslashes(r._unsafePath));
       assert(r._nMerged === undefined);  // we don't allow dup'd OTHER Reports
       let o = new OtherReport(r._unsafePath, r._units, r._amount,
                               r._unsafeDescription);
       otherReports.push(o);
       if (o._asString.length > maxStringLength) {
         maxStringLength = o._asString.length;
       }
     }
@@ -1365,12 +1438,202 @@ function appendOtherElements(aP, aReport
     appendTextNode(pre, "\n");
   }
 
   appendTextNode(aP, "\n");  // gives nice spacing when we cut and paste
 }
 
 function appendSectionHeader(aP, aText)
 {
-  appendElementWithText(aP, "h2", "sectionHeader", aText);
+  appendElementWithText(aP, "h2", "", aText);
   appendTextNode(aP, "\n");
 }
 
+//-----------------------------------------------------------------------------
+// Code specific to about:compartments
+//-----------------------------------------------------------------------------
+
+function onLoadAboutCompartments()
+{
+  // Minimize memory usage before generating the page in an attempt to collect
+  // any dead compartments.
+  minimizeMemoryUsage3x(
+    function() { addChildObserversAndUpdate(updateAboutCompartments); });
+}
+
+/**
+ * Top-level function that does the work of generating the page.
+ */
+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);
+
+  // Generate output for one process at a time.  Always start with the
+  // Main process.
+  let compartmentsByProcess = getCompartmentsByProcess(mgr);
+  appendProcessCompartmentsElements(body, "Main",
+                                    compartmentsByProcess["Main"]);
+  for (let process in compartmentsByProcess) {
+    if (process !== "Main") {
+      appendProcessCompartmentsElements(body, process,
+                                        compartmentsByProcess[process]);
+    }
+  }
+
+  appendElement(body, "hr");
+
+  let div1 = appendElement(body, "div");
+  let a;
+  if (gVerbose) {
+    let a = appendElementWithText(div1, "a", "option", "Less verbose");
+    a.href = "about:compartments";
+  } else {
+    let a = appendElementWithText(div1, "a", "option", "More verbose");
+    a.href = "about:compartments?verbose";
+  }
+
+  // Dispatch a "bodygenerated" event to indicate that the DOM has finished
+  // generating.  This is used by tests.
+  let e = document.createEvent("Event");
+  e.initEvent("bodygenerated", false, false);
+  document.dispatchEvent(e);
+}
+
+//---------------------------------------------------------------------------
+
+function Compartment(aUnsafeName, aIsSystemCompartment)
+{
+  this._unsafeName          = aUnsafeName;
+  this._isSystemCompartment = aIsSystemCompartment;
+  // this._nMerged is only defined if > 1
+}
+
+Compartment.prototype = {
+  merge: function(r) {
+    this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
+  }
+};
+
+function getCompartmentsByProcess(aMgr)
+{
+  // Ignore anything that didn't come from the "compartments" multi-reporter.
+  // (Note that some such reports can reach here as single reports if they were
+  // in the child process.)
+
+  function ignoreSingle(aPath) 
+  {
+    return aPath.indexOf("compartments/") !== 0;
+  }
+
+  function ignoreMulti(aName)
+  {
+    return aName !== "compartments";
+  }
+
+  let compartmentsByProcess = {};
+
+  function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount, aDesc)
+  {
+    let process = aProcess === "" ? "Main" : aProcess;
+
+    assert(aKind   === KIND_OTHER, "bad kind");
+    assert(aUnits  === UNITS_COUNT, "bad units");
+    assert(aAmount === 1, "bad amount");
+    assert(aDesc   === "", "bad description");
+
+    let unsafeNames = aUnsafePath.split('/');
+
+    let isSystemCompartment;
+    if (unsafeNames[0] === "compartments" && unsafeNames[1] == "system" &&
+        unsafeNames.length == 3)
+    {
+      isSystemCompartment = true;
+
+    } else if (unsafeNames[0] === "compartments" && unsafeNames[1] == "user" &&
+        unsafeNames.length == 3)
+    {
+      isSystemCompartment = false;
+      // These null principal compartments are user compartments according to
+      // the JS engine, but they look odd being shown with content
+      // compartments, so we put them in the system compartments list.
+      if (unsafeNames[2].indexOf("moz-nullprincipal:{") === 0) {
+        isSystemCompartment = true;
+      }
+
+    } else {
+      assert(false, "bad compartments path: " + aUnsafePath);
+    }
+    let c = new Compartment(unsafeNames[2], isSystemCompartment);
+
+    if (!compartmentsByProcess[process]) {
+      compartmentsByProcess[process] = {};
+    }
+    let compartments = compartmentsByProcess[process];
+    let cOld = compartments[c._unsafeName];
+    if (cOld) {
+      // Already an entry;  must be a duplicated compartment.  This can happen
+      // legitimately.  Merge them.
+      cOld.merge(c);
+    } else {
+      compartments[c._unsafeName] = c;
+    }
+  }
+
+  processMemoryReporters(aMgr, ignoreSingle, ignoreMulti, handleReport);
+
+  return compartmentsByProcess;
+}
+
+//---------------------------------------------------------------------------
+
+function appendProcessCompartmentsElementsHelper(aP, aCompartments, aKindString)
+{
+  appendElementWithText(aP, "h2", "", aKindString + " Compartments\n");
+
+  let compartmentTextArray = [];
+  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);
+    }
+  }
+  compartmentTextArray.sort();
+
+  for (var i = 0; i < compartmentTextArray.length; i++) {
+    appendElementWithText(uPre, "span", "", compartmentTextArray[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.
+ * @return The generated text.
+ */
+function appendProcessCompartmentsElements(aP, aProcess, aCompartments)
+{
+  appendElementWithText(aP, "h1", "", aProcess + " Process");
+  appendTextNode(aP, "\n\n");   // gives nice spacing when we cut and paste
+  
+  appendProcessCompartmentsElementsHelper(aP, aCompartments, "User");
+  appendProcessCompartmentsElementsHelper(aP, aCompartments, "System");
+}
+
--- a/toolkit/components/aboutmemory/content/aboutMemory.xhtml
+++ b/toolkit/components/aboutmemory/content/aboutMemory.xhtml
@@ -32,18 +32,20 @@
    - use your version of this file under the terms of the MPL, indicate your
    - decision by deleting the provisions above and replace them with the notice
    - and other provisions required by the LGPL or the GPL. If you do not delete
    - the provisions above, a recipient may use your version of this file under
    - the terms of any one of the MPL, the GPL or the LGPL.
    -
    - ***** END LICENSE BLOCK ***** -->
 
+<!-- This file is used for both about:memory and about:compartments. -->
+
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
-    <title>about:memory</title>
+    <!-- the <title> is filled in by aboutMemory.js -->
     <link rel="stylesheet" href="chrome://global/skin/aboutMemory.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://global/skin/about.css" type="text/css"/>
     <script type="text/javascript;version=1.8" src="chrome://global/content/aboutMemory.js"/>
   </head>
 
-  <body id="content" onload="onLoad()" onunload="onUnload()"></body>
+  <body onload="onLoad()" onunload="onUnload()"></body>
 </html>
--- a/toolkit/components/aboutmemory/tests/Makefile.in
+++ b/toolkit/components/aboutmemory/tests/Makefile.in
@@ -40,16 +40,17 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = toolkit/components/aboutmemory/tests
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _CHROME_FILES	= \
+		test_aboutcompartments.xul \
 		test_aboutmemory.xul \
 		test_aboutmemory2.xul \
 		test_sqliteMultiReporter.xul \
 		$(NULL)
 
 libs:: $(_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/aboutmemory/tests/test_aboutcompartments.xul
@@ -0,0 +1,230 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window title="about:compartments"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml"></body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript;version=1.8">
+  <![CDATA[
+  const Cc = Components.classes;
+  const Ci = Components.interfaces;
+  let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+            getService(Ci.nsIMemoryReporterManager);
+
+  // Remove all the real reporters and multi-reporters;  save them to
+  // restore at the end.
+  var e = mgr.enumerateReporters();
+  var realReporters = [];
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    mgr.unregisterReporter(r);
+    realReporters.push(r);
+  }
+  e = mgr.enumerateMultiReporters();
+  var realMultiReporters = [];
+  while (e.hasMoreElements()) {
+    var r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mgr.unregisterMultiReporter(r);
+    realMultiReporters.push(r);
+  }
+
+  // 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 BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
+  const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
+
+  function f(aProcess, aPath, aKind, aUnits, aAmount) {
+    return {
+      process:     aProcess,
+      path:        aPath,
+      kind:        aKind,
+      units:       aUnits,
+      description: "",
+      amount:      aAmount 
+    };
+  }
+
+  var fakeReporters = [
+    // 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)
+  ];
+
+  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);
+        }
+        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);
+        }
+        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
+    },
+    // 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);
+        f("smaps/swap/a",       1);
+      },
+      explicitNonHeap: 0
+    }
+  ];
+
+  for (var i = 0; i < fakeReporters.length; i++) {
+    mgr.registerReporter(fakeReporters[i]);
+  }
+  for (var i = 0; i < fakeMultiReporters.length; i++) {
+    mgr.registerMultiReporter(fakeMultiReporters[i]);
+  }
+  ]]>
+  </script>
+
+  <iframe id="acFrame"  height="400" src="about:compartments"></iframe>
+  <iframe id="acvFrame" height="400" src="about:compartments?verbose"></iframe>
+
+  <script type="application/javascript">
+  <![CDATA[
+  var acExpectedText =
+"\
+Main Process\n\
+\n\
+User Compartments\n\
+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\
+2nd Process\n\
+\n\
+User Compartments\n\
+child-user-compartment\n\
+\n\
+System Compartments\n\
+child-system-compartment\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
+    // reporters and multi-reporters, just in case subsequent tests rely on
+    // them.
+    for (var i = 0; i < fakeReporters.length; i++) {
+      mgr.unregisterReporter(fakeReporters[i]);
+    }
+    for (var i = 0; i < fakeMultiReporters.length; i++) {
+      mgr.unregisterMultiReporter(fakeMultiReporters[i]);
+    }
+    for (var i = 0; i < realReporters.length; i++) {
+      mgr.registerReporter(realReporters[i]);
+    }
+    for (var i = 0; i < realMultiReporters.length; i++) {
+      mgr.registerMultiReporter(realMultiReporters[i]);
+    }
+    SimpleTest.finish();
+  }
+
+  // Cut+paste the entire page and check that the cut text matches what we
+  // expect.  This tests the output in general and also that the cutting and
+  // pasting works as expected.
+  function test(aFrameId, aExpected, aNext) {
+    let frame = document.getElementById(aFrameId);
+    let handler = function() {
+      frame.focus();
+      SimpleTest.waitForClipboard(
+        function(aActual) {
+          mostRecentActual = aActual;
+          return aActual === aExpected;
+        },
+        function() {
+          synthesizeKey("A", {accelKey: true});
+          synthesizeKey("C", {accelKey: true});
+        },
+        aNext,
+        function() {
+          ok(false, "pasted text doesn't match for " + aFrameId);
+          dump("******EXPECTED******\n");
+          dump(aExpected);
+          dump("*******ACTUAL*******\n");
+          dump(mostRecentActual);
+          dump("********************\n");
+          finish();
+        }
+      );
+    };
+    // about:compartment dispatches a "bodygenerated" event to the document
+    // when it's finished generating its DOM.  If we don't wait for that we end
+    // up copying an empty page.
+    frame.contentDocument.addEventListener("bodygenerated", handler, false);
+  }
+
+  SimpleTest.waitForFocus(function() {
+    test(
+      "acFrame",
+      acExpectedText,
+      function() {
+        test(
+          "acvFrame",
+          acvExpectedText,
+          function() {
+            finish()
+          }
+        )
+      }
+    );
+  });
+
+  SimpleTest.waitForExplicitFinish();
+  ]]>
+  </script>
+</window>
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
@@ -84,17 +84,20 @@
     f("", "explicit/b/a",       HEAP,     85 * MB),
     f("", "explicit/b/b",       HEAP,     75 * MB),
     f("", "explicit/b/c/a",     HEAP,     70 * MB),
     f("", "explicit/b/c/b",     HEAP,      2 * MB), // omitted
     f("", "explicit/g/a",       HEAP,      6 * MB),
     f("", "explicit/g/b",       HEAP,      5 * MB),
     f("", "explicit/g/other",   HEAP,      4 * MB),
     f("", "other1",             OTHER,   111 * MB),
-    f2("", "other4",            OTHER,   COUNT_CUMULATIVE, 888)
+    f2("", "other4",            OTHER,   COUNT_CUMULATIVE, 888),
+    // These compartments ones shouldn't be displayed.
+    f("", "compartments/user/foo",   OTHER, COUNT, 1),
+    f("", "compartments/system/foo", OTHER, COUNT, 1)
   ];
   let fakeMultiReporters = [
     { name: "fake1",
       collectReports: function(aCbObj, aClosure) {
         function f(aP, aK, aU, aA) {
           aCbObj.callback("", aP, aK, aU, aA, "(desc)", aClosure);
         }
         f("explicit/c/d",     NONHEAP, BYTES,  13 * MB),
@@ -130,16 +133,26 @@
         f("smaps/swap/a",       1);
         f("smaps/swap/a",       2);
         f("smaps/vsize/a",      19);
         f("smaps/swap/b/c",     10);
         f("smaps/resident/a",   42);
         f("smaps/pss/a",        43);
       },
       explicitNonHeap: 0
+    },
+    { name: "compartments",
+      collectReports: function(aCbObj, aClosure) {
+        function f(aP) {
+          aCbObj.callback("", aP, OTHER, COUNT, 1, "", aClosure);
+        }
+        f("compartments/user/bar");
+        f("compartments/system/bar");
+      },
+      explicitNonHeap: 0
     }
   ];
   for (let i = 0; i < fakeReporters.length; i++) {
     mgr.registerReporter(fakeReporters[i]);
   }
   for (let i = 0; i < fakeMultiReporters.length; i++) {
     mgr.registerMultiReporter(fakeMultiReporters[i]);
   }
@@ -160,16 +173,23 @@
     f("2nd", "explicit/flip\\the\\backslashes",
                                 HEAP,    200 * MB),
     f("2nd", "explicit/compartment(compartment-url)",
                                 HEAP,    200 * MB),
     // The escaping of compartment names must prevent this script from running.
     f("2nd", "danger<script>window.alert(1)</script>",
                                 OTHER,   666 * MB),
     f("2nd", "other1",          OTHER,   111 * MB),
+    // Even though the "smaps" reporter is a multi-reporter, if its in a
+    // child process it'll be passed to the main process as single reports.
+    // The fact that we skip the "smaps" multi-reporter in the main
+    // process won't cause these to be skipped;  the fall-back skipping will
+    // be hit instead.
+    f("2nd", "smaps/vsize/e",   NONHEAP, 24*4*KB),
+    f("2nd", "smaps/vsize/f",   NONHEAP, 24*4*KB),
 
     // kUnknown should be handled gracefully for "heap-allocated", non-leaf
     // reporters, leaf-reporters, "other" reporters, and duplicated reporters.
     f("3rd", "heap-allocated",  OTHER,   kUnknown),
     f("3rd", "explicit/a/b",    HEAP,    333 * MB),
     f("3rd", "explicit/a/c",    HEAP,    444 * MB),
     f("3rd", "explicit/a/c",    HEAP,    kUnknown), // dup: merge
     f("3rd", "explicit/a/d",    HEAP,    kUnknown),
@@ -206,40 +226,20 @@
     f("5th", "explicit/a/pos",   HEAP,     40 * KB),
     f("5th", "explicit/a/neg1",  NONHEAP, -20 * KB),
     f("5th", "explicit/a/neg2",  NONHEAP, -10 * KB),
     f("5th", "explicit/b/c/d/e", NONHEAP,  20 * KB),
     f("5th", "explicit/b/c/d/f", NONHEAP, -60 * KB),
     f("5th", "explicit/b/c/g/h", NONHEAP,  10 * KB),
     f("5th", "explicit/b/c/i/j", NONHEAP,   5 * KB)
   ];
-  let fakeMultiReporters2 = [
-    // Because this multi-reporter is in a child process, the fact that we
-    // skip the "smaps" multi-reporter in the parent process won't cause
-    // these to be skipped;  the fall-back skipping will be hit instead.
-    { name: "smaps",
-      collectReports: function(aCbObj, aClosure) {
-        // The amounts are given in pages, so multiply here by 4kb.
-        function f(aP, aA) {
-          aCbObj.callback("2nd", aP, NONHEAP, BYTES, aA * 4 * KB, "(desc)", aClosure);
-        }
-        f("smaps/vsize/a", 24);
-        f("smaps/vsize/b", 24);
-      },
-      explicitNonHeap: 0
-    }
-  ];
   for (let i = 0; i < fakeReporters2.length; i++) {
     mgr.registerReporter(fakeReporters2[i]);
   }
-  for (let i = 0; i < fakeMultiReporters2.length; i++) {
-    mgr.registerMultiReporter(fakeMultiReporters2[i]);
-  }
   fakeReporters = fakeReporters.concat(fakeReporters2);
-  fakeMultiReporters = fakeMultiReporters.concat(fakeMultiReporters2);
   ]]>
   </script>
 
   <iframe id="amFrame"  height="400" src="about:memory"></iframe>
   <iframe id="amvFrame" height="400" src="about:memory?verbose"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
@@ -560,32 +560,32 @@ 104,857,600 B ── heap-allocated\n\
       mgr.registerMultiReporter(realMultiReporters[i]);
     }
     SimpleTest.finish();
   }
 
   // Cut+paste the entire page and check that the cut text matches what we
   // expect.  This tests the output in general and also that the cutting and
   // pasting works as expected.
-  function test(aFrame, aExpected, aNext) {
+  function test(aFrameId, aExpected, aNext) {
     SimpleTest.executeSoon(function() {
       let mostRecentActual;
-      document.getElementById(aFrame).focus();
+      document.getElementById(aFrameId).focus();
       SimpleTest.waitForClipboard(
         function(aActual) {
           mostRecentActual = aActual;
           return aActual === aExpected;
         },
         function() {
           synthesizeKey("A", {accelKey: true});
           synthesizeKey("C", {accelKey: true});
         },
         aNext,
         function() {
-          ok(false, "pasted text doesn't match for " + aFrame);
+          ok(false, "pasted text doesn't match for " + aFrameId);
           dump("******EXPECTED******\n");
           dump(aExpected);
           dump("*******ACTUAL*******\n");
           dump(mostRecentActual);
           dump("********************\n");
           finish();
         }
       );
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -67,19 +67,25 @@ interface nsIMemoryReporter : nsISupport
    * (This is true even for reporters in a child process.)  When a reporter
    * from a child process is copied into the main process, the copy has its
    * 'process' field set appropriately.
    */
   readonly attribute ACString process;
 
   /*
    * The path that this memory usage should be reported under.  Paths are
-   * '/'-delimited, eg. "a/b/c".  There are three categories of paths.
+   * '/'-delimited, eg. "a/b/c".  If you want to include a '/' not as a path
+   * separator, e.g. because the path contains a URL, you need to convert
+   * each '/' in the URL to a '\'.  Consumers of the path will undo this
+   * change.  Any other '\' character in a path will also be changed.  This
+   * is clumsy but hasn't caused any problems so far.
    *
-   * - Paths starting with "explicit" represent regions of memory that have
+   * There are several categories of paths.
+   *
+   * - Paths starting with "explicit/" represent regions of memory that have
    *   been explicitly allocated with an OS-level allocation (eg.
    *   mmap/VirtualAlloc/vm_allocate) or a heap-level allocation (eg.
    *   malloc/calloc/operator new).
    *
    *   Each reporter can be viewed as representing a leaf node in a tree
    *   rooted at "explicit".  Internal nodes of the tree don't have
    *   reporters.  So, for example, the reporters "explicit/a/b",
    *   "explicit/a/c", "explicit/d/e", and "explicit/d/f" define this tree:
@@ -94,46 +100,51 @@ interface nsIMemoryReporter : nsISupport
    *
    *   Nodes marked with a [*] have a reporter.  Notice that the internal
    *   nodes are implicitly defined by the paths.
    *
    *   A node's children divide their parent's memory into disjoint pieces.
    *   So in the example above, |a| may not count any allocations counted by
    *   |d|, and vice versa.
    *
-   * - Paths starting with "map" represent regions of virtual memory that the
-   *   process has mapped.  The reporter immediately beneath "map" describes
-   *   the type of measurement; for instance, the reporter "map/rss/[stack]"
-   *   might report how much of the process's stack is currently in physical
-   *   memory.
+   * - 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 and units BYTES.
    *
+   * - 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.
+   *
    * - All other paths represent cross-cutting values and may overlap with any
-   *   other reporter.
+   *   other reporter.  Reporters in this category must have paths that do not
+   *   contain '/' separators, and kind OTHER.
    */
   readonly attribute AUTF8String path;
 
   /*
    * There are three categories of memory reporters:
    *
    *  - HEAP: memory allocated by the heap allocator, e.g. by calling malloc,
    *    calloc, realloc, memalign, operator new, or operator new[].  Reporters
    *    in this category must have units UNITS_BYTES and must have a path
-   *    starting with "explicit".
+   *    starting with "explicit/".
    *
    *  - NONHEAP: memory which the program explicitly allocated, but does not
    *    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 "map".
+   *    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 "map"
-   *    and may have any units.
+   *    reporters must have a path that does not start with "explicit/" or
+   *    "smaps/" and may have any units.
    */
   const PRInt32 KIND_NONHEAP = 0;
   const PRInt32 KIND_HEAP    = 1;
   const PRInt32 KIND_OTHER   = 2;
 
   /*
    * 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