Bug 698930 (part 3) - Clearly identify in aboutMemory.js which user-controlled strings have been sanitized. r=jlebar.
authorNicholas Nethercote <nnethercote@mozilla.com>
Sun, 29 Jan 2012 14:05:12 -0800
changeset 86939 50080b09ae0487b75c1111211d2ae76e532df296
parent 86938 f989793c20bd316d32bb16f4dfbd8f4b1d567d47
child 86940 3bd7584753ae7d9063159353ff9bdd483f33f5bb
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlebar
bugs698930
milestone12.0a1
Bug 698930 (part 3) - Clearly identify in aboutMemory.js which user-controlled strings have been sanitized. r=jlebar.
toolkit/components/aboutmemory/content/aboutMemory.js
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -53,17 +53,40 @@ const KIND_HEAP    = Ci.nsIMemoryReporte
 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;
 
 const kUnknown = -1;    // used for _amount if a memory reporter failed
 
-const kTreeDescriptions = {
+// Paths, names and descriptions all need to be sanitized before being
+// displayed, because the user has some control over them via names of
+// compartments, windows, etc.  Also, forward slashes in URLs in paths are
+// represented with backslashes to avoid being mistaken for path separators, so
+// we need to undo that as well.
+
+function escapeAll(aStr)
+{
+  return aStr.replace(/\&/g, '&amp;').replace(/'/g, '&#39;').
+              replace(/\</g, '&lt;').replace(/>/g, '&gt;').
+              replace(/\"/g, '&quot;');
+}
+
+function flipBackslashes(aStr)
+{
+  return aStr.replace(/\\/g, '/');
+}
+
+function makeSafe(aUnsafeStr)
+{
+  return escapeAll(flipBackslashes(aUnsafeStr));
+}
+
+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 as malloc, calloc, realloc, memalign, operator " +
     "new, and operator new[]).  It excludes memory that is mapped implicitly " +
     "such as code and data segments, and thread stacks.  It also excludes " +
     "heap memory that has been freed by the application but is still being " +
@@ -194,23 +217,23 @@ function sendHeapMinNotifications()
     else
       runSoon(update);
   }
 
   var j = 0;
   sendHeapMinNotificationsInner();
 }
 
-function Reporter(aPath, aKind, aUnits, aAmount, aDescription)
+function Reporter(aUnsafePath, aKind, aUnits, aAmount, aUnsafeDesc)
 {
-  this._path        = aPath;
+  this._unsafePath  = aUnsafePath;
   this._kind        = aKind;
   this._units       = aUnits;
   this._amount      = aAmount;
-  this._description = aDescription;
+  this._unsafeDescription = aUnsafeDesc;
   // this._nMerged is only defined if > 1
   // this._done is defined when getBytes is called
 }
 
 Reporter.prototype = {
   // Sum the values (accounting for possible kUnknown amounts), and mark |this|
   // as a dup.  We mark dups because it's useful to know when a reporter is
   // duplicated;  it might be worth investigating and splitting up to have
@@ -223,17 +246,17 @@ Reporter.prototype = {
     }
     this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
   },
 
   treeNameMatches: function(aTreeName) {
     // Nb: the '/' must be present, because we have a KIND_OTHER reporter
     // called "explicit" which is not part of the "explicit" tree.
     aTreeName += "/";
-    return this._path.slice(0, aTreeName.length) === aTreeName;
+    return this._unsafePath.slice(0, aTreeName.length) === aTreeName;
   }
 };
 
 function getReportersByProcess(aMgr)
 {
   // Process each memory reporter:
   // - Make a copy of it into a sub-table indexed by its process.  Each copy
   //   is a Reporter object.  After this point we never use the original memory
@@ -241,31 +264,32 @@ function getReportersByProcess(aMgr)
   //
   // - 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.
   var reportersByProcess = {};
 
-  function addReporter(aProcess, aPath, aKind, aUnits, aAmount, aDescription)
+  function addReporter(aProcess, aUnsafePath, aKind, aUnits, aAmount,
+                       aUnsafeDesc)
   {
     var process = aProcess === "" ? "Main" : aProcess;
-    var r = new Reporter(aPath, aKind, aUnits, aAmount, aDescription);
+    var r = new Reporter(aUnsafePath, aKind, aUnits, aAmount, aUnsafeDesc);
     if (!reportersByProcess[process]) {
       reportersByProcess[process] = {};
     }
     var reporters = reportersByProcess[process];
-    var reporter = reporters[r._path];
+    var reporter = reporters[r._unsafePath];
     if (reporter) {
       // Already an entry;  must be a duplicated reporter.  This can happen
       // legitimately.  Merge them.
       reporter.merge(r);
     } else {
-      reporters[r._path] = r;
+      reporters[r._unsafePath] = r;
     }
   }
 
   // Process vanilla reporters first, then multi-reporters.
   var e = aMgr.enumerateReporters();
   while (e.hasMoreElements()) {
     var rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     try {
@@ -366,38 +390,38 @@ function update()
   div.innerHTML = text;
   content.appendChild(div);
 }
 
 // There are two kinds of TreeNode.
 // - Leaf TreeNodes correspond to Reporters and have more properties.
 // - Non-leaf TreeNodes are just scaffolding nodes for the tree;  their values
 //   are derived from their children.
-function TreeNode(aName)
+function TreeNode(aUnsafeName)
 {
   // Nb: _units is not needed, it's always UNITS_BYTES.
-  this._name = aName;
+  this._unsafeName = aUnsafeName;
   this._kids = [];
   // All TreeNodes have these properties added later:
   // - _amount (which is never |kUnknown|)
-  // - _description
+  // - _unsafeDescription
   //
   // Leaf TreeNodes have these properties added later:
   // - _kind
   // - _nMerged (if > 1)
   // - _isUnknown (only defined if true)
   //
   // Non-leaf TreeNodes have these properties added later:
   // - _hideKids (only defined if true)
 }
 
 TreeNode.prototype = {
-  findKid: function(aName) {
+  findKid: function(aUnsafeName) {
     for (var i = 0; i < this._kids.length; i++) {
-      if (this._kids[i]._name === aName) {
+      if (this._kids[i]._unsafeName === aUnsafeName) {
         return this._kids[i];
       }
     }
     return undefined;
   },
 
   toString: function() {
     return formatBytes(this._amount);
@@ -408,59 +432,59 @@ TreeNode.compare = function(a, b) {
   return b._amount - a._amount;
 };
 
 /**
  * From a list of memory reporters, builds a tree that mirrors the tree
  * structure that will be shown as output.
  *
  * @param aReporters
- *        The table of Reporters, indexed by path.
+ *        The table of Reporters, indexed by _unsafePath.
  * @param aTreeName
  *        The name of the tree being built.
  * @return The built tree.
  */
 function buildTree(aReporters, aTreeName)
 {
   // We want to process all reporters that begin with |aTreeName|.  First we
   // build the tree but only fill the properties that we can with a top-down
   // traversal.
 
   // There should always be at least one matching reporter when |aTreeName| is
   // "explicit".  But there may be zero for "map" trees;  if that happens,
   // bail.
   var foundReporter = false;
-  for (var path in aReporters) {
-    if (aReporters[path].treeNameMatches(aTreeName)) {
+  for (var unsafePath in aReporters) {
+    if (aReporters[unsafePath].treeNameMatches(aTreeName)) {
       foundReporter = true;
       break;
     }
   }
   if (!foundReporter) {
     assert(aTreeName !== 'explicit');
     return null;
   }
 
   var t = new TreeNode("falseRoot");
-  for (var path in aReporters) {
-    // Add any missing nodes in the tree implied by the path.
-    var r = aReporters[path];
+  for (var unsafePath in aReporters) {
+    // Add any missing nodes in the tree implied by the unsafePath.
+    var r = aReporters[unsafePath];
     if (r.treeNameMatches(aTreeName)) {
       assert(r._kind === KIND_HEAP || r._kind === KIND_NONHEAP,
              "reporters in the tree must have KIND_HEAP or KIND_NONHEAP");
       assert(r._units === UNITS_BYTES, "r._units === UNITS_BYTES");
-      var names = r._path.split('/');
+      var unsafeNames = r._unsafePath.split('/');
       var u = t;
-      for (var i = 0; i < names.length; i++) {
-        var name = names[i];
-        var uMatch = u.findKid(name);
+      for (var i = 0; i < unsafeNames.length; i++) {
+        var unsafeName = unsafeNames[i];
+        var uMatch = u.findKid(unsafeName);
         if (uMatch) {
           u = uMatch;
         } else {
-          var v = new TreeNode(name);
+          var v = new TreeNode(unsafeName);
           u._kids.push(v);
           u = v;
         }
       }
       // Fill in extra details from the Reporter.
       u._kind = r._kind;
       if (r._nMerged) {
         u._nMerged = r._nMerged;
@@ -469,40 +493,42 @@ function buildTree(aReporters, aTreeName
   }
 
   // Using falseRoot makes the above code simpler.  Now discard it, leaving
   // aTreeName at the root.
   t = t._kids[0];
 
   // Next, fill in the remaining properties bottom-up.
   // Note that this function never returns kUnknown.
-  function fillInTree(aT, aPrepath)
+  function fillInTree(aT, aUnsafePrePath)
   {
-    var path = aPrepath ? aPrepath + '/' + aT._name : aT._name;
+    var unsafePath =
+      aUnsafePrePath ? aUnsafePrePath + '/' + aT._unsafeName : aT._unsafeName; 
     if (aT._kids.length === 0) {
       // Leaf node.  Must have a reporter.
       assert(aT._kind !== undefined, "aT._kind is undefined for leaf node");
-      aT._description = getDescription(aReporters, path);
-      var amount = getBytes(aReporters, path);
+      aT._unsafeDescription = getUnsafeDescription(aReporters, unsafePath);
+      var amount = getBytes(aReporters, unsafePath);
       if (amount !== kUnknown) {
         aT._amount = amount;
       } else {
         aT._amount = 0;
         aT._isUnknown = true;
       }
     } else {
       // Non-leaf node.  Derive its size and description entirely from its
       // children.
       assert(aT._kind === undefined, "aT._kind is defined for non-leaf node");
       var childrenBytes = 0;
       for (var i = 0; i < aT._kids.length; i++) {
-        childrenBytes += fillInTree(aT._kids[i], path);
+        childrenBytes += fillInTree(aT._kids[i], unsafePath);
       }
       aT._amount = childrenBytes;
-      aT._description = "The sum of all entries below '" + aT._name + "'.";
+      aT._unsafeDescription =
+        "The sum of all entries below '" + aT._unsafeName + "'.";
     }
     assert(aT._amount !== kUnknown, "aT._amount !== kUnknown");
     return aT._amount;
   }
 
   fillInTree(t, "");
 
   // Reduce the depth of the tree by the number of occurrences of '/' in
@@ -510,48 +536,48 @@ function buildTree(aReporters, aTreeName
   var slashCount = 0;
   for (var i = 0; i < aTreeName.length; i++) {
     if (aTreeName[i] == '/') {
       assert(t._kids.length == 1, "Not expecting multiple kids here.");
       t = t._kids[0];
     }
   }
 
-  // Set the description on the root node.
-  t._description = kTreeDescriptions[t._name];
+  // Set the (unsafe) description on the root node.
+  t._unsafeDescription = kTreeUnsafeDescriptions[t._unsafeName];
 
   return t;
 }
 
 /**
  * Ignore all the memory reporters that belong to a tree;  this involves
  * explicitly marking them as done.
  *
  * @param aReporters
- *        The table of Reporters, indexed by path.
+ *        The table of Reporters, indexed by _unsafePath.
  * @param aTreeName
  *        The name of the tree being built.
  */
 function ignoreTree(aReporters, aTreeName)
 {
-  for (var path in aReporters) {
-    var r = aReporters[path];
+  for (var unsafePath in aReporters) {
+    var r = aReporters[unsafePath];
     if (r.treeNameMatches(aTreeName)) {
-      var dummy = getBytes(aReporters, path);
+      var dummy = getBytes(aReporters, unsafePath);
     }
   }
 }
 
 /**
  * Do some work which only makes sense for the 'explicit' tree.
  *
  * @param aT
  *        The tree.
  * @param aReporters
- *        Table of Reporters for this process, indexed by _path.
+ *        Table of Reporters for this process, indexed by _unsafePath.
  * @return A boolean indicating if "heap-allocated" is known for the process.
  */
 function fixUpExplicitTree(aT, aReporters)
 {
   // Determine how many bytes are reported by heap reporters.
   var s = "";
   function getKnownHeapUsedBytes(aT)
   {
@@ -578,21 +604,20 @@ function fixUpExplicitTree(aT, aReporter
     heapUnclassifiedT._amount =
       heapAllocatedBytes - getKnownHeapUsedBytes(aT);
   } else {
     heapUnclassifiedT._amount = 0;
     heapUnclassifiedT._isUnknown = true;
   }
   // This kindToString() ensures the "(Heap)" prefix is set without having to
   // set the _kind property, which would mean that there is a corresponding
-  // Reporter for this TreeNode (which isn't true).
-  heapUnclassifiedT._description =
-      kindToString(KIND_HEAP) +
+  // Reporter for this TreeNode (which isn't true)
+  heapUnclassifiedT._unsafeDescription = kindToString(KIND_HEAP) +
       "Memory not classified by a more specific reporter. This includes " +
-      "slop bytes due to internal fragmentation in the heap allocator "
+      "slop bytes due to internal fragmentation in the heap allocator " +
       "(caused when the allocator rounds up request sizes).";
 
   aT._kids.push(heapUnclassifiedT);
   aT._amount += heapUnclassifiedT._amount;
 
   return hasKnownHeapAllocated;
 }
 
@@ -644,17 +669,17 @@ function sortTreeAndInsertAggregateNodes
       var aggT = new TreeNode("(" + nAgg + " tiny)");
       var aggBytes = 0;
       for ( ; i < aT._kids.length; i++) {
         aggBytes += aT._kids[i]._amount;
         aggT._kids.push(aT._kids[i]);
       }
       aggT._hideKids = true;
       aggT._amount = aggBytes;
-      aggT._description =
+      aggT._unsafeDescription =
         nAgg + " sub-trees that are below the " + kSignificanceThresholdPerc +
         "% significance threshold.";
       aT._kids.splice(i0, nAgg, aggT);
       aT._kids.sort(TreeNode.compare);
 
       // Process the moved children.
       for (i = 0; i < aggT._kids.length; i++) {
         sortTreeAndInsertAggregateNodes(aTotalBytes, aggT._kids[i]);
@@ -667,19 +692,19 @@ function sortTreeAndInsertAggregateNodes
 
   // The first n-1 children were significant.  Don't consider if the last child
   // is significant;  there's no point creating an aggregate node that only has
   // one child.  Just process it.
   sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
 }
 
 // Global variable indicating if we've seen any invalid values for this
-// process;  it holds the paths of any such reporters.  Reset for each new
-// process.
-var gProcessInvalidValues = [];
+// process;  it holds the unsafePaths of any such reporters.  It is reset for
+// each new process.
+var gUnsafePathsWithInvalidValuesForThisProcess = [];
 
 function genWarningText(aHasKnownHeapAllocated, aHasMozMallocUsableSize)
 {
   var warningText = "";
 
   if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
     warningText =
       "<p class='accuracyWarning'>WARNING: the 'heap-allocated' memory " +
@@ -699,44 +724,50 @@ function genWarningText(aHasKnownHeapAll
     warningText =
       "<p class='accuracyWarning'>WARNING: the moz_malloc_usable_size() " +
       "function does not work for this platform and/or configuration. " +
       "This means that much of the heap-allocated memory is not measured " +
       "by individual memory reporters and so will fall under " +
       "'heap-unclassified'.</p>\n\n";
   }
 
-  if (gProcessInvalidValues.length > 0) {
+  if (gUnsafePathsWithInvalidValuesForThisProcess.length > 0) {
     warningText +=
       "<div class='accuracyWarning'>" +
       "<p>WARNING: the following values are negative or unreasonably " +
       "large.</p>\n" +
       "<ul>";
-    for (var i = 0; i < gProcessInvalidValues.length; i++) {
-      warningText += " <li>" + prepName(gProcessInvalidValues[i]) + "</li>\n";
+    for (var i = 0;
+         i < gUnsafePathsWithInvalidValuesForThisProcess.length;
+         i++)
+    {
+      warningText +=
+        " <li>" +
+        makeSafe(gUnsafePathsWithInvalidValuesForThisProcess[i]) +
+        "</li>\n";
     }
     warningText +=
       "</ul>" +
       "<p>This indicates a defect in one or more memory reporters.  The " +
       "invalid values are highlighted, but you may need to expand one " +
       "or more sub-trees to see them.</p>\n\n" +
       "</div>";
-    gProcessInvalidValues = [];  // reset for the next process
+    gUnsafePathsWithInvalidValuesForThisProcess = [];  // reset for the next process
   }
 
   return warningText;
 }
 
 /**
  * Generates the text for a single process.
  *
  * @param aProcess
  *        The name of the process.
  * @param aReporters
- *        Table of Reporters for this process, indexed by _path.
+ *        Table of Reporters for this process, indexed by _unsafePath.
  * @param aHasMozMallocUsableSize
  *        Boolean indicating if moz_malloc_usable_size works.
  * @return The generated text.
  */
 function genProcessText(aProcess, aReporters, aHasMozMallocUsableSize)
 {
   var explicitTree = buildTree(aReporters, 'explicit');
   var hasKnownHeapAllocated = fixUpExplicitTree(explicitTree, aReporters);
@@ -744,17 +775,18 @@ function genProcessText(aProcess, aRepor
   var explicitText = genTreeText(explicitTree, aProcess);
 
   // We only show these breakdown trees in verbose mode.
   var mapTreeText = "";
   kMapTreePaths.forEach(function(t) {
     if (gVerbose) {
       var tree = buildTree(aReporters, t);
 
-      // |tree| will be null if we don't have any reporters for the given path.
+      // |tree| will be null if we don't have any reporters for the given
+      // unsafePath.
       if (tree) {
         sortTreeAndInsertAggregateNodes(tree._amount, tree);
         tree._hideKids = true;   // map trees are always initially collapsed
         mapTreeText += genTreeText(tree, aProcess);
       }
     } else {
       ignoreTree(aReporters, t);
     }
@@ -883,47 +915,47 @@ function pad(aS, aN, aC)
   return padding + aS;
 }
 
 /**
  * Gets the byte count for a particular Reporter and sets its _done
  * property.
  *
  * @param aReporters
- *        Table of Reporters for this process, indexed by _path.
- * @param aPath
- *        The path of the R.
+ *        Table of Reporters for this process, indexed by _unsafePath.
+ * @param aUnsafePath
+ *        The unsafePath of the R.
  * @param aDoNotMark
  *        If true, the _done property is not set.
  * @return The byte count.
  */
-function getBytes(aReporters, aPath, aDoNotMark)
+function getBytes(aReporters, aUnsafePath, aDoNotMark)
 {
-  var r = aReporters[aPath];
-  assert(r, "getBytes: no such Reporter: " + aPath);
+  var r = aReporters[aUnsafePath];
+  assert(r, "getBytes: no such Reporter: " + makeSafe(aUnsafePath));
   if (!aDoNotMark) {
     r._done = true;
   }
   return r._amount;
 }
 
 /**
- * Gets the description for a particular Reporter.
+ * Gets the (unsafe) description for a particular Reporter.
  *
  * @param aReporters
- *        Table of Reporters for this process, indexed by _path.
- * @param aPath
- *        The path of the Reporter.
+ *        Table of Reporters for this process, indexed by _unsafePath.
+ * @param aUnsafePath
+ *        The unsafePath of the Reporter.
  * @return The description.
  */
-function getDescription(aReporters, aPath)
+function getUnsafeDescription(aReporters, aUnsafePath)
 {
-  var r = aReporters[aPath];
-  assert(r, "getDescription: no such Reporter: " + aPath);
-  return r._description;
+  var r = aReporters[aUnsafePath];
+  assert(r, "getUnsafeDescription: no such Reporter: " + makeSafe(aUnsafePath));
+  return r._unsafeDescription;
 }
 
 // There's a subset of the Unicode "light" box-drawing chars that are widely
 // implemented in terminals, and this code sticks to that subset to maximize
 // the chance that cutting and pasting about:memory output to a terminal will
 // work correctly:
 const kHorizontal       = "\u2500",
       kVertical         = "\u2502",
@@ -943,60 +975,34 @@ function kindToString(aKind)
    case KIND_NONHEAP: return "(Non-heap) ";
    case KIND_HEAP:    return "(Heap) ";
    case KIND_OTHER:
    case undefined:    return "";
    default:           assert(false, "bad kind in kindToString");
   }
 }
 
-// For user-controlled strings.
-function escapeAll(aStr)
-{
-  return aStr.replace(/\&/g, '&amp;').replace(/'/g, '&#39;').
-              replace(/\</g, '&lt;').replace(/>/g, '&gt;').
-              replace(/\"/g, '&quot;');
-}
-
-// Compartment reporter names are URLs and so can include forward slashes.  But
-// forward slash is the memory reporter path separator.  So the memory
-// reporters change them to backslashes.  Undo that here.
-function flipBackslashes(aStr)
-{
-  return aStr.replace(/\\/g, '/');
-}
-
-function prepName(aStr)
-{
-  return escapeAll(flipBackslashes(aStr));
-}
-
-function prepDesc(aStr)
-{
-  return escapeAll(flipBackslashes(aStr));
-}
-
-function genMrNameText(aKind, aShowSubtrees, aHasKids, aDesc, aName,
-                       aIsUnknown, aIsInvalid, aNMerged)
+function genMrNameText(aKind, aShowSubtrees, aHasKids, aUnsafeDesc,
+                       aUnsafeName, aIsUnknown, aIsInvalid, aNMerged)
 {
   var text = "";
   if (aHasKids) {
     if (aShowSubtrees) {
       text += "<span class='mrSep hidden'> ++ </span>";
       text += "<span class='mrSep'> -- </span>";
     } else {
       text += "<span class='mrSep'> ++ </span>";
       text += "<span class='mrSep hidden'> -- </span>";
     }
   } else {
     text += "<span class='mrSep'> " + kHorizontal + kHorizontal + " </span>";
   }
   text += "<span class='mrName' title='" +
-          kindToString(aKind) + prepDesc(aDesc) + "'>" +
-          prepName(aName) + "</span>";
+          kindToString(aKind) + makeSafe(aUnsafeDesc) + "'>" +
+          makeSafe(aUnsafeName) + "</span>";
   if (aIsUnknown) {
     const problemDesc =
       "Warning: this memory reporter was unable to compute a useful value. ";
     text += "<span class='mrNote' title=\"" + problemDesc + "\"> [*]</span>";
   }
   if (aIsInvalid) {
     const invalidDesc =
       "Warning: this value is invalid and indicates a bug in one or more " +
@@ -1007,23 +1013,23 @@ function genMrNameText(aKind, aShowSubtr
     const dupDesc = "This value is the sum of " + aNMerged +
                     " memory reporters that all have the same path.";
     text += "<span class='mrNote' title=\"" + dupDesc + "\"> [" +
             aNMerged + "]</span>";
   }
   return text + '\n';
 }
 
-// This is used to record which sub-trees have been toggled, so the
-// collapsed/expanded state can be replicated when the page is regenerated.
-// It can end up holding IDs of nodes that no longer exist, e.g. for
-// compartments that have been closed.  This doesn't seem like a big deal,
+// This is used to record the (safe) IDs of which sub-trees have been toggled,
+// so the collapsed/expanded state can be replicated when the page is
+// regenerated.  It can end up holding IDs of nodes that no longer exist, e.g.
+// for compartments that have been closed.  This doesn't seem like a big deal,
 // because the number is limited by the number of entries the user has changed
 // from their original state.
-var gToggles = {};
+var gTogglesBySafeTreeId = {};
 
 function toggle(aEvent)
 {
   // This relies on each line being a span that contains at least five spans:
   // mrValue, mrPerc, mrSep ('++'), mrSep ('--'), mrName, and then zero or more
   // mrNotes.  All whitespace must be within one of these spans for this
   // function to find the right nodes.  And the span containing the children of
   // this line must immediately follow.  Assertions check this.
@@ -1047,72 +1053,72 @@ function toggle(aEvent)
   minusSpan.classList.toggle("hidden");
 
   // Toggle visibility of the span containing this node's children.
   var subTreeSpan = outerSpan.nextSibling;
   assertClassName(subTreeSpan, "kids");
   subTreeSpan.classList.toggle("hidden");
 
   // Record/unrecord that this sub-tree was toggled.
-  var treeId = outerSpan.id;
-  if (gToggles[treeId]) {
-    delete gToggles[treeId];
+  var safeTreeId = outerSpan.id;
+  if (gTogglesBySafeTreeId[safeTreeId]) {
+    delete gTogglesBySafeTreeId[safeTreeId];
   } else {
-    gToggles[treeId] = true;
+    gTogglesBySafeTreeId[safeTreeId] = true;
   }
 }
 
 /**
  * Generates the text for the tree, including its heading.
  *
  * @param aT
  *        The tree.
  * @param aProcess
  *        The process the tree corresponds to.
  * @return The generated text.
  */
 function genTreeText(aT, aProcess)
 {
   var treeBytes = aT._amount;
   var rootStringLength = aT.toString().length;
-  var isExplicitTree = aT._name == 'explicit';
+  var isExplicitTree = aT._unsafeName == 'explicit';
 
   /**
    * Generates the text for a particular tree, without a heading.
    *
-   * @param aPrePath
-   *        The partial path leading up to this node.
+   * @param aUnsafePrePath
+   *        The partial unsafePath leading up to this node.
    * @param aT
    *        The tree.
    * @param aIndentGuide
    *        Records what indentation is required for this tree.  It has one
    *        entry per level of indentation.  For each entry, ._isLastKid
    *        records whether the node in question is the last child, and
    *        ._depth records how many chars of indentation are required.
    * @param aParentStringLength
    *        The length of the formatted byte count of the top node in the tree.
    * @return The generated text.
    */
-  function genTreeText2(aPrePath, aT, aIndentGuide, aParentStringLength)
+  function genTreeText2(aUnsafePrePath, aT, aIndentGuide, aParentStringLength)
   {
     function repeatStr(aC, aN)
     {
       var s = "";
       for (var i = 0; i < aN; i++) {
         s += aC;
       }
       return s;
     }
 
     // Determine if we should show the sub-tree below this entry;  this
     // involves reinstating any previous toggling of the sub-tree.
-    var path = aPrePath + aT._name;
-    var treeId = aProcess + ":" + escapeAll(path);
+    var unsafePath = aUnsafePrePath + aT._unsafeName;
+    var safeTreeId = escapeAll(aProcess + ":" + unsafePath);
     var showSubtrees = !aT._hideKids;
-    if (gToggles[treeId]) {
+    if (gTogglesBySafeTreeId[safeTreeId]) {
       showSubtrees = !showSubtrees;
     }
 
     // Generate the indent.
     var indent = "<span class='treeLine'>";
     if (aIndentGuide.length > 0) {
       for (var i = 0; i < aIndentGuide.length - 1; i++) {
         indent += aIndentGuide[i]._isLastKid ? " " : kVertical;
@@ -1138,17 +1144,17 @@ function genTreeText(aT, aProcess)
     var percText = "";
     var tIsInvalid = false;
     if (aT._amount === treeBytes) {
       percText = "100.0";
     } else {
       var perc = (100 * aT._amount / treeBytes);
       if (!(0 <= perc && perc <= 100)) {
         tIsInvalid = true;
-        gProcessInvalidValues.push(path);
+        gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath);
       }
       percText = (100 * aT._amount / treeBytes).toFixed(2);
       percText = pad(percText, 5, '0');
     }
     percText = tIsInvalid ?
                "<span class='mrPerc invalid'> (" + percText + "%)</span>" :
                "<span class='mrPerc'> ("         + percText + "%)</span>";
 
@@ -1159,57 +1165,57 @@ function genTreeText(aT, aProcess)
     // For non-leaf nodes, the entire sub-tree is put within a span so it can
     // be collapsed if the node is clicked on.
     var hasKids = aT._kids.length > 0;
     if (!hasKids) {
       assert(!aT._hideKids, "leaf node with _hideKids set")
     }
     var text = indent;
     if (hasKids) {
-      text +=
-        "<span onclick='toggle(event)' class='hasKids' id='" + treeId + "'>";
+      text += "<span onclick='toggle(event)' class='hasKids' id='" +
+              safeTreeId + "'>";
     }
     text += genMrValueText(tString, tIsInvalid) + percText;
-    text += genMrNameText(kind, showSubtrees, hasKids, aT._description,
-                          aT._name, aT._isUnknown, tIsInvalid, aT._nMerged);
+    text += genMrNameText(kind, showSubtrees, hasKids, aT._unsafeDescription,
+                          aT._unsafeName, aT._isUnknown, tIsInvalid,
+                          aT._nMerged);
     if (hasKids) {
       var hiddenText = showSubtrees ? "" : " hidden";
       // The 'kids' class is just used for sanity checking in toggle().
       text += "</span><span class='kids" + hiddenText + "'>";
     }
 
     for (var i = 0; i < aT._kids.length; i++) {
       // 3 is the standard depth, the callee adjusts it if necessary.
       aIndentGuide.push({ _isLastKid: (i === aT._kids.length - 1), _depth: 3 });
-      text += genTreeText2(path + "/", aT._kids[i], aIndentGuide,
+      text += genTreeText2(unsafePath + "/", aT._kids[i], aIndentGuide,
                            tString.length);
       aIndentGuide.pop();
     }
     text += hasKids ? "</span>" : "";
     return text;
   }
 
   var text = genTreeText2(/* prePath = */"", aT, [], rootStringLength);
 
-  return genSectionMarkup(aT._name, text);
+  return genSectionMarkup(aT._unsafeName, text);
 }
 
-function OtherReporter(aPath, aUnits, aAmount, aDescription,
-                       aNMerged)
+function OtherReporter(aUnsafePath, aUnits, aAmount, aUnsafeDesc, aNMerged)
 {
   // Nb: _kind is not needed, it's always KIND_OTHER.
-  this._path        = aPath;
-  this._units       = aUnits;
+  this._unsafePath = aUnsafePath;
+  this._units    = aUnits;
   if (aAmount === kUnknown) {
     this._amount     = 0;
     this._isUnknown = true;
   } else {
     this._amount = aAmount;
   }
-  this._description = aDescription;
+  this._unsafeDescription = aUnsafeDesc;
   this._asString = this.toString();
 }
 
 OtherReporter.prototype = {
   toString: function() {
     switch (this._units) {
       case UNITS_BYTES:            return formatBytes(this._amount);
       case UNITS_COUNT:
@@ -1230,63 +1236,65 @@ OtherReporter.prototype = {
                                            !(0 <= n && n <= 10000));
       default:
         assert(false, "bad units in OtherReporter.isInvalid");
     }
   }
 };
 
 OtherReporter.compare = function(a, b) {
-  return a._path < b._path ? -1 :
-         a._path > b._path ?  1 :
+  return a._unsafePath < b._unsafePath ? -1 :
+         a._unsafePath > b._unsafePath ?  1 :
          0;
 };
 
 /**
  * Generates the text for the "Other Measurements" section.
  *
  * @param aReportersByProcess
- *        Table of Reporters for this process, indexed by _path.
+ *        Table of Reporters for this process, indexed by _unsafePath.
  * @param aProcess
  *        The process these reporters correspond to.
  * @return The generated text.
  */
 function genOtherText(aReportersByProcess, aProcess)
 {
   // Generate an array of Reporter-like elements, stripping out all the
   // Reporters that have already been handled.  Also find the width of the
   // widest element, so we can format things nicely.
   var maxStringLength = 0;
   var otherReporters = [];
-  for (var path in aReportersByProcess) {
-    var r = aReportersByProcess[path];
+  for (var unsafePath in aReportersByProcess) {
+    var r = aReportersByProcess[unsafePath];
     if (!r._done) {
-      assert(r._kind === KIND_OTHER, "_kind !== KIND_OTHER for " + r._path);
+      assert(r._kind === KIND_OTHER,
+             "_kind !== KIND_OTHER for " + makeSafe(r._unsafePath));
       assert(r._nMerged === undefined);  // we don't allow dup'd OTHER reporters
-      var o = new OtherReporter(r._path, r._units, r._amount, r._description);
+      var o = new OtherReporter(r._unsafePath, r._units, r._amount,
+                                r._unsafeDescription);
       otherReporters.push(o);
       if (o._asString.length > maxStringLength) {
         maxStringLength = o._asString.length;
       }
     }
   }
   otherReporters.sort(OtherReporter.compare);
 
   // Generate text for the not-yet-printed values.
   var text = "";
   for (var i = 0; i < otherReporters.length; i++) {
     var o = otherReporters[i];
     var oIsInvalid = o.isInvalid();
     if (oIsInvalid) {
-      gProcessInvalidValues.push(o._path);
+      gUnsafePathsWithInvalidValuesForThisProcess.push(o._unsafePath);
     }
     text += genMrValueText(pad(o._asString, maxStringLength, ' '), oIsInvalid);
     text += genMrNameText(KIND_OTHER, /* showSubtrees = */true,
-                          /* hasKids = */false, o._description, o._path,
-                          o._isUnknown, oIsInvalid);
+                          /* hasKids = */false, o._unsafeDescription,
+                          o._unsafePath, o._isUnknown, oIsInvalid);
   }
 
   return genSectionMarkup('other', text);
 }
 
 function genSectionMarkup(aName, aText)
 {
   return "<h2 class='sectionHeader'>" + kTreeNames[aName] + "</h2>\n" +