Bug 856917 (part 1) - Improve about:memory's functional UI. r=kats.

This commit is contained in:
Nicholas Nethercote 2013-04-07 21:37:19 -07:00
parent fd709d6e6f
commit a0eca79ac3
8 changed files with 361 additions and 252 deletions

View File

@ -2,7 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* This file is used for both about:memory and about:compartments. */
/*
* This file is used for both about:memory and about:compartments.
*
* The version used for desktop is located at
* toolkit/components/aboutmemory/content/aboutMemory.css.
* Mobile-specific stuff is at the bottom of this file.
*/
html {
background: -moz-Dialog;
@ -16,20 +22,41 @@ body {
margin: auto;
}
div.section, div.footer {
margin: 2em 0;
box-sizing: border-box;
-moz-box-sizing: border-box;
div.ancillary {
margin: 0.5em 0;
-moz-user-select: none;
}
div.section {
padding: 3em;
padding: 2em;
margin: 1em 0em;
border: 1px solid ThreeDShadow;
border-radius: 10px;
background: -moz-Field;
}
body.non-verbose pre.entries {
div.opsRow {
padding: 0.5em;
margin-right: 0.5em;
margin-top: 0.5em;
border: 1px solid ThreeDShadow;
border-radius: 10px;
background: -moz-Field;
display: inline-block;
}
div.opsRowLabel {
display: block;
margin-bottom: 0.2em;
font-weight: bold;
}
.opsRowLabel label {
margin-left: 1em;
font-weight: normal;
}
div.non-verbose pre.entries {
overflow-x: hidden;
text-overflow: ellipsis;
}
@ -44,14 +71,8 @@ h2 {
padding-left: .1em;
}
/* buttons are different sizes and overlapping without this */
button {
margin: 1%;
padding: 2%;
}
.accuracyWarning {
color: #f00;
color: #d22;
}
.badInputWarning {
@ -85,6 +106,14 @@ button {
cursor: pointer;
}
.hasKids:hover {
text-decoration: underline;
}
.noselect {
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.option {
font-size: 80%;
-moz-user-select: none; /* no need to include this when cutting+pasting */
@ -95,10 +124,6 @@ button {
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.hiddenOnMobile {
display: none;
}
.debug {
font-size: 80%;
}
@ -112,3 +137,15 @@ button {
background-color: #f00;
}
/* Mobile-specific parts go here. */
/* buttons are different sizes and overlapping without this */
button {
margin: 1%;
padding: 2%;
}
.hiddenOnMobile {
display: none;
}

View File

@ -5,11 +5,9 @@
/*
* This file is used for both about:memory and about:compartments.
*
* Portions of this file are based on
* toolkit/themes/windows/global/about.css.
*
* A version used for mobile is located at
* The version used for mobile is located at
* mobile/android/themes/core/aboutMemory.css.
* Desktop-specific stuff is at the bottom of this file.
*/
html {
@ -24,20 +22,41 @@ body {
margin: auto;
}
div.section, div.footer {
margin: 2em 0;
box-sizing: border-box;
-moz-box-sizing: border-box;
div.ancillary {
margin: 0.5em 0;
-moz-user-select: none;
}
div.section {
padding: 3em;
padding: 2em;
margin: 1em 0em;
border: 1px solid ThreeDShadow;
border-radius: 10px;
background: -moz-Field;
}
body.non-verbose pre.entries {
div.opsRow {
padding: 0.5em;
margin-right: 0.5em;
margin-top: 0.5em;
border: 1px solid ThreeDShadow;
border-radius: 10px;
background: -moz-Field;
display: inline-block;
}
div.opsRowLabel {
display: block;
margin-bottom: 0.2em;
font-weight: bold;
}
.opsRowLabel label {
margin-left: 1em;
font-weight: normal;
}
div.non-verbose pre.entries {
overflow-x: hidden;
text-overflow: ellipsis;
}
@ -91,6 +110,10 @@ h2 {
text-decoration: underline;
}
.noselect {
-moz-user-select: none; /* no need to include this when cutting+pasting */
}
.option {
font-size: 80%;
-moz-user-select: none; /* no need to include this when cutting+pasting */
@ -114,3 +137,9 @@ h2 {
background-color: #f00;
}
/* Desktop-specific parts go here. */
.hasKids:hover {
text-decoration: underline;
}

View File

@ -6,21 +6,18 @@
// This file is used for both about:memory and about:compartments.
// about:memory will by default show information about the browser's current
// memory usage, but you can direct it to load information from a file by
// providing a file= query string. For example,
// You can direct about:memory to immediately load memory reports from a file
// by providing a file= query string. For example,
//
// about:memory?file=/foo/bar
// about:memory?verbose&file=/foo/bar%26baz
// about:memory?file=/home/username/reports.json.gz
//
// The order of "verbose" and "file=" isn't significant, and neither "verbose"
// nor "file=" is case-sensitive. We'll URI-unescape the contents of the
// "file=" is not case-sensitive. We'll URI-unescape the contents of the
// "file=" argument, and obviously the filename is case-sensitive iff you're on
// a case-sensitive filesystem. If you specify more than one "file=" argument,
// only the first one is used.
//
// about:compartments doesn't support the "verbose" or "file=" parameters and
// will ignore them if they're provided.
// about:compartments doesn't support "file=" parameters and will ignore them
// if they're provided.
"use strict";
@ -63,7 +60,6 @@ let gUnnamedProcessStr = "Main Process";
// 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.
let gVerbose = false;
let gIsDiff = false;
{
let split = document.location.href.split('?');
@ -72,9 +68,6 @@ let gIsDiff = false;
if (split.length === 2) {
let searchSplit = split[1].split('&');
for (let i = 0; i < searchSplit.length; i++) {
if (searchSplit[i].toLowerCase() === 'verbose') {
gVerbose = true;
}
if (searchSplit[i].toLowerCase() === 'diff') {
gIsDiff = true;
}
@ -121,9 +114,11 @@ function handleException(ex)
{
let str = ex.toString();
if (str.startsWith(gAssertionFailureMsgPrefix)) {
throw ex; // Argh, assertion failure within this file! Give up.
// Argh, assertion failure within this file! Give up.
throw ex;
} else {
badInput(ex); // File or memory reporter problem. Print a message.
// File or memory reporter problem. Print a message.
updateMainAndFooter(ex.toString(), HIDE_FOOTER, "badInputWarning");
}
}
@ -141,12 +136,6 @@ function debug(x)
appendElementWithText(section, "div", "debug", JSON.stringify(x));
}
function badInput(x)
{
let section = appendElement(document.body, 'div', 'section');
appendElementWithText(section, "div", "badInputWarning", x);
}
//---------------------------------------------------------------------------
function addChildObserversAndUpdate(aUpdateFn)
@ -255,13 +244,51 @@ function processMemoryReportsFromFile(aReports, aIgnoreSingle, aHandleReport)
//---------------------------------------------------------------------------
function clearBody()
// The <div> holding everything but the header and footer (if they're present).
// It's what is updated each time the page changes.
let gMain;
// The <div> holding the footer. Is undefined in about:compartments.
let gFooter;
// The "verbose" checkbox.
let gVerbose;
// Values for the second argument to updateMainAndFooter.
let HIDE_FOOTER = 0;
let SHOW_FOOTER = 1;
let IGNORE_FOOTER = 2;
function updateMainAndFooter(aMsg, aFooterAction, aClassName)
{
let oldBody = document.body;
let body = oldBody.cloneNode(false);
oldBody.parentNode.replaceChild(body, oldBody);
body.classList.add(gVerbose ? 'verbose' : 'non-verbose');
return body
// Clear gMain by replacing it with an empty node.
let tmp = gMain.cloneNode(false);
gMain.parentNode.replaceChild(tmp, gMain);
gMain = tmp;
gMain.classList.remove('hidden');
gMain.classList.remove('verbose');
gMain.classList.remove('non-verbose');
if (gVerbose) {
gMain.classList.add(gVerbose.checked ? 'verbose' : 'non-verbose');
}
if (aMsg) {
let className = "section"
if (aClassName) {
className = className + " " + aClassName;
}
appendElementWithText(gMain, 'div', className, aMsg);
}
if (gFooter !== undefined) {
switch (aFooterAction) {
case HIDE_FOOTER: gFooter.classList.add('hidden'); break;
case SHOW_FOOTER: gFooter.classList.remove('hidden'); break;
case IGNORE_FOOTER: break;
default: assertInput(false, "bad footer action in updateMainAndFooter");
}
}
}
function appendTextNode(aP, aText)
@ -375,8 +402,101 @@ function isSmapsPath(aUnsafePath)
//---------------------------------------------------------------------------
function appendButton(aP, aTitle, aOnClick, aText, aId)
{
let b = appendElementWithText(aP, "button", "", aText);
b.title = aTitle;
b.onclick = aOnClick;
if (aId) {
b.id = aId;
}
return b;
}
function onLoadAboutMemory()
{
// Generate the header.
let header = appendElement(document.body, "div", "ancillary");
// A hidden file input element that can be invoked when necessary.
let filePickerInput = appendElementWithText(header, "input", "hidden", "");
filePickerInput.type = "file";
filePickerInput.id = "filePickerInput"; // used in testing
filePickerInput.addEventListener("change", function() {
let file = this.files[0];
let filename = file.mozFullPath;
updateAboutMemoryFromFile(filename);
});
const CuDesc = "Measure current memory reports and show.";
const LdDesc = "Load memory reports from file and show.";
const RdDesc = "Read memory reports from the clipboard and show.";
const SvDesc = "Save memory reports to file.";
const GCDesc = "Do a global garbage collection.";
const CCDesc = "Do a cycle collection.";
const MMDesc = "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.";
let ops = appendElement(header, "div", "");
let row1 = appendElement(ops, "div", "opsRow");
let labelDiv =
appendElementWithText(row1, "div", "opsRowLabel", "Show memory reports");
let label = appendElementWithText(labelDiv, "label", "");
gVerbose = appendElement(label, "input", "");
gVerbose.type = "checkbox";
gVerbose.id = "verbose"; // used for testing
appendTextNode(label, "verbose");
const kEllipsis = "\u2026";
// The "measureButton" id is used for testing.
appendButton(row1, CuDesc, doMeasure, "Measure", "measureButton");
appendButton(row1, LdDesc, () => filePickerInput.click(), "Load" + kEllipsis);
appendButton(row1, RdDesc, updateAboutMemoryFromClipboard,
"Read from clipboard");
let row2 = appendElement(ops, "div", "opsRow");
appendElementWithText(row2, "div", "opsRowLabel", "Save memory reports");
appendButton(row2, SvDesc, saveReportsToFile, "Measure and save" + kEllipsis);
let row3 = appendElement(ops, "div", "opsRow");
appendElementWithText(row3, "div", "opsRowLabel", "Free memory");
appendButton(row3, GCDesc, doGC, "GC");
appendButton(row3, CCDesc, doCC, "CC");
appendButton(row3, MMDesc, doMMU, "Minimize memory usage");
// Generate the main div, where content ("section" divs) will go. It's
// hidden at first.
gMain = appendElement(document.body, 'div', '');
// Generate the footer. It's hidden at first.
gFooter = appendElement(document.body, 'div', 'ancillary hidden');
let a = appendElementWithText(gFooter, "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(gFooter, "div", "legend", legendText1);
appendElementWithText(gFooter, "div", "legend hiddenOnMobile", legendText2);
// Check location.href to see if we're loading from a file.
let search = location.href.split('?')[1];
if (search) {
@ -389,17 +509,15 @@ function onLoadAboutMemory()
}
}
}
addChildObserversAndUpdate(updateAboutMemory);
}
function doGlobalGC()
function doGC()
{
Cu.forceGC();
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.notifyObservers(null, "child-gc-request", null);
updateAboutMemory();
updateMainAndFooter("Garbage collection completed", HIDE_FOOTER);
}
function doCC()
@ -410,7 +528,18 @@ function doCC()
let os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
os.notifyObservers(null, "child-cc-request", null);
updateAboutMemory();
updateMainAndFooter("Cycle collection completed", HIDE_FOOTER);
}
function doMMU()
{
gMgr.minimizeMemoryUsage(
() => updateMainAndFooter("Memory minimization completed", HIDE_FOOTER));
}
function doMeasure()
{
addChildObserversAndUpdate(updateAboutMemoryFromReporters);
}
//---------------------------------------------------------------------------
@ -419,44 +548,29 @@ function doCC()
* Top-level function that does the work of generating the page from the memory
* reporters.
*/
function updateAboutMemory()
function updateAboutMemoryFromReporters()
{
// 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();
// First, clear the contents of main. Necessary because
// updateAboutMemoryFromReporters() might be called more than once due to the
// "child-memory-reporter-update" observer.
updateMainAndFooter("", SHOW_FOOTER);
try {
// Process the reports from the memory reporters.
let process = function(aIgnoreSingle, aIgnoreMulti, aHandleReport) {
processMemoryReporters(aIgnoreSingle, aIgnoreMulti, aHandleReport);
}
appendAboutMemoryMain(body, process, gMgr.hasMozMallocUsableSize,
appendAboutMemoryMain(process, gMgr.hasMozMallocUsableSize,
/* forceShowSmaps = */ false);
} catch (ex) {
handleException(ex);
} finally {
appendAboutMemoryFooter(body);
}
}
// Increment this if the JSON format changes.
var gCurrentFileFormatVersion = 1;
/**
* Handle an update exception that occurs while updating the page.
*
* @param aEx
* The exception.
*/
function clearBodyAndHandleException(aEx) {
let body = clearBody();
handleException(aEx);
appendAboutMemoryFooter(body);
}
/**
* Populate about:memory using the data in the given JSON string.
*
@ -466,8 +580,6 @@ function clearBodyAndHandleException(aEx) {
*/
function updateAboutMemoryFromJSONString(aJSONString)
{
let body = clearBody();
try {
let json = JSON.parse(aJSONString);
assertInput(json.version === gCurrentFileFormatVersion,
@ -480,18 +592,16 @@ function updateAboutMemoryFromJSONString(aJSONString)
processMemoryReportsFromFile(json.reports, aIgnoreSingle,
aHandleReport);
}
appendAboutMemoryMain(body, process, json.hasMozMallocUsableSize,
appendAboutMemoryMain(process, json.hasMozMallocUsableSize,
/* forceShowSmaps = */ true);
} catch (ex) {
handleException(ex);
} finally {
appendAboutMemoryFooter(body);
}
}
/**
* Like updateAboutMemory(), but gets its data from a file instead of the
* memory reporters.
* Like updateAboutMemoryFromReporters(), but gets its data from a file instead
* of the memory reporters.
*
* @param aFilename
* The name of the file being read from.
@ -501,11 +611,14 @@ function updateAboutMemoryFromJSONString(aJSONString)
*/
function updateAboutMemoryFromFile(aFilename)
{
updateMainAndFooter("Loading...", HIDE_FOOTER);
try {
let reader = new FileReader();
reader.onerror = () => { throw "FileReader.onerror"; };
reader.onabort = () => { throw "FileReader.onabort"; };
reader.onload = (aEvent) => {
updateMainAndFooter("", SHOW_FOOTER); // Clear "Loading..." from above.
updateAboutMemoryFromJSONString(aEvent.target.result);
};
@ -531,7 +644,7 @@ function updateAboutMemoryFromFile(aFilename)
}
reader.readAsText(new Blob(this.data));
} catch (ex) {
clearBodyAndHandleException(ex);
handleException(ex);
}
}
}, null);
@ -541,7 +654,7 @@ function updateAboutMemoryFromFile(aFilename)
fileChan.asyncOpen(converter, null);
} catch (ex) {
clearBodyAndHandleException(ex);
handleException(ex);
}
}
@ -573,7 +686,7 @@ function updateAboutMemoryFromClipboard()
updateAboutMemoryFromJSONString(cbString);
} catch (ex) {
clearBodyAndHandleException(ex);
handleException(ex);
}
}
@ -581,8 +694,6 @@ function updateAboutMemoryFromClipboard()
* Processes reports (whether from reporters or from a file) and append the
* main part of the page.
*
* @param aBody
* The DOM body element.
* @param aProcess
* Function that extracts the memory reports from the reporters or from
* file.
@ -592,7 +703,7 @@ function updateAboutMemoryFromClipboard()
* True if we should show the smaps memory reporters even if we're not
* in verbose mode.
*/
function appendAboutMemoryMain(aBody, aProcess, aHasMozMallocUsableSize,
function appendAboutMemoryMain(aProcess, aHasMozMallocUsableSize,
aForceShowSmaps)
{
let treesByProcess = {}, degeneratesByProcess = {}, heapTotalByProcess = {};
@ -641,7 +752,7 @@ function appendAboutMemoryMain(aBody, aProcess, aHasMozMallocUsableSize,
// Generate output for each process.
for (let i = 0; i < processes.length; i++) {
let process = processes[i];
let section = appendElement(aBody, 'div', 'section');
let section = appendElement(gMain, 'div', 'section');
appendProcessAboutMemoryElements(section, process,
treesByProcess[process],
@ -651,90 +762,6 @@ function appendAboutMemoryMain(aBody, aProcess, aHasMozMallocUsableSize,
}
}
/**
* Appends the page footer.
*
* @param aBody
* The DOM body element.
*/
function appendAboutMemoryFooter(aBody)
{
let section = appendElement(aBody, 'div', 'footer');
// 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.";
const RdDesc = "Read memory report data from a file.";
const CbDesc = "Read memory report data from the clipboard.";
const WrDesc = "Write memory report data to a file.";
function appendButton(aP, aTitle, aOnClick, aText, aId)
{
let b = appendElementWithText(aP, "button", "", aText);
b.title = aTitle;
b.onclick = aOnClick
if (aId) {
b.id = aId;
}
}
let div1 = appendElement(section, "div");
// The "Update" button has an id so it can be clicked in a test.
appendButton(div1, UpDesc, updateAboutMemory, "Update", "updateButton");
appendButton(div1, GCDesc, doGlobalGC, "GC");
appendButton(div1, CCDesc, doCC, "CC");
appendButton(div1, MPDesc,
function() { gMgr.minimizeMemoryUsage(updateAboutMemory); },
"Minimize memory usage");
// The standard file input element is ugly. So we hide it, and add a button
// that when clicked invokes the input element.
let input = appendElementWithText(div1, "input", "hidden", "input text");
input.type = "file";
input.id = "fileInput"; // has an id so it can be invoked by a test
input.addEventListener("change", function() {
let file = this.files[0];
updateAboutMemoryFromFile(file.mozFullPath);
});
appendButton(div1, RdDesc, function() { input.click() },
"Read reports from a file", "readReportsFromFileButton");
appendButton(div1, CbDesc, updateAboutMemoryFromClipboard,
"Read reports from clipboard", "readReportsFromClipboardButton");
appendButton(div1, WrDesc, writeReportsToFile,
"Write reports to a file", "writeReportsToAFileButton");
let div2 = appendElement(section, "div");
if (gVerbose) {
let a = appendElementWithText(div2, "a", "option", "Less verbose");
a.href = "about:memory";
} else {
let a = appendElementWithText(div2, "a", "option", "More verbose");
a.href = "about:memory?verbose";
}
let div3 = appendElement(section, "div");
let a = appendElementWithText(div3, "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(section, "div", "legend", legendText1);
appendElementWithText(section, "div", "legend hiddenOnMobile", legendText2);
}
//---------------------------------------------------------------------------
// This regexp matches sentences and sentence fragments, i.e. strings that
@ -784,7 +811,7 @@ function getTreesByProcess(aProcessMemoryReports, aTreesByProcess,
function ignoreSingle(aUnsafePath)
{
return (isSmapsPath(aUnsafePath) && !gVerbose && !aForceShowSmaps) ||
return (isSmapsPath(aUnsafePath) && !gVerbose.checked && !aForceShowSmaps) ||
aUnsafePath.startsWith("compartments/") ||
aUnsafePath.startsWith("ghost-windows/") ||
aUnsafePath == "resident-fast";
@ -792,7 +819,7 @@ function getTreesByProcess(aProcessMemoryReports, aTreesByProcess,
function ignoreMulti(aMRName)
{
return (aMRName === "smaps" && !gVerbose && !aForceShowSmaps) ||
return (aMRName === "smaps" && !gVerbose.checked && !aForceShowSmaps) ||
aMRName === "compartments" ||
aMRName === "ghost-windows";
}
@ -801,7 +828,8 @@ function getTreesByProcess(aProcessMemoryReports, aTreesByProcess,
aDescription)
{
if (isExplicitPath(aUnsafePath)) {
assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP, "bad explicit kind");
assertInput(aKind === KIND_HEAP || aKind === KIND_NONHEAP,
"bad explicit kind");
assertInput(aUnits === UNITS_BYTES, "bad explicit units");
assertInput(gSentenceRegExp.test(aDescription),
"non-sentence explicit description");
@ -1053,7 +1081,7 @@ function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
function isInsignificant(aT)
{
return !gVerbose &&
return !gVerbose.checked &&
(100 * aT._amount / aTotalBytes) < kSignificanceThresholdPerc;
}
@ -1251,7 +1279,7 @@ function appendProcessAboutMemoryElements(aP, aProcess, aTrees, aDegenerates,
}
otherDegenerates.sort(TreeNode.compareUnsafeNames);
// Now generate the elements, putting non-degenerate trees first.
// Now generate the elements, putting non-degenerate trees first.
let pre = appendSectionHeader(aP, kSectionNames['other']);
for (let i = 0; i < otherTrees.length; i++) {
let t = otherTrees[i];
@ -1343,10 +1371,10 @@ function formatInt(aN, aExtra)
*/
function formatBytes(aBytes)
{
let unit = gVerbose ? " B" : " MB";
let unit = gVerbose.checked ? " B" : " MB";
let s;
if (gVerbose) {
if (gVerbose.checked) {
s = formatInt(aBytes, unit);
} else {
let mbytes = (aBytes / (1024 * 1024)).toFixed(2);
@ -1631,7 +1659,7 @@ function appendTreeElements(aP, aRoot, aProcess, aPadText)
// 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) {
if (!gVerbose.checked && tIsInvalid) {
expandPathToThisElement(d);
}
@ -1674,7 +1702,7 @@ function appendSectionHeader(aP, aText)
//---------------------------------------------------------------------------
function writeReportsToFile()
function saveReportsToFile()
{
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
fp.init(window, "Save Memory Reports", Ci.nsIFilePicker.modeSave);
@ -1692,6 +1720,8 @@ function writeReportsToFile()
.getService(Ci.nsIMemoryInfoDumper);
dumper.dumpMemoryReportsToNamedFile(fp.file.path);
updateMainAndFooter("Saved reports to " + fp.file.path, HIDE_FOOTER);
}
};
fp.open(fpCallback);
@ -1703,6 +1733,10 @@ function writeReportsToFile()
function onLoadAboutCompartments()
{
// Generate the main div, where content will go. about:compartments doesn't
// have a header or footer.
gMain = appendElement(document.body, 'div', 'section');
// First generate the page, then minimize memory usage to collect any dead
// compartments, then update the page. The first generation step may sound
// unnecessary, but it avoids a short delay in showing content when the page
@ -1718,37 +1752,44 @@ function onLoadAboutCompartments()
*/
function updateAboutCompartments()
{
// First, clear the page contents. Necessary because
// updateAboutCompartments() might be called more than once due to the
// First, clear the contents of main. Necessary because
// updateAboutMemoryFromReporters() might be called more than once due to the
// "child-memory-reporter-update" observer.
let body = clearBody();
updateMainAndFooter("", IGNORE_FOOTER);
let compartmentsByProcess = getCompartmentsByProcess();
let ghostWindowsByProcess = getGhostWindowsByProcess();
try {
let compartmentsByProcess = getCompartmentsByProcess();
let ghostWindowsByProcess = getGhostWindowsByProcess();
function handleProcess(aProcess) {
let section = appendElement(body, 'div', 'section');
appendProcessAboutCompartmentsElements(section, aProcess,
compartmentsByProcess[aProcess],
ghostWindowsByProcess[aProcess]);
}
// Sort our list of processes.
let processes = Object.keys(compartmentsByProcess);
processes.sort(function(aProcessA, aProcessB) {
assert(aProcessA != aProcessB,
"Elements of Object.keys() should be unique, but " +
"saw duplicate '" + aProcessA + "' elem.");
// Generate output for one process at a time. Always start with the
// Main process.
handleProcess(gUnnamedProcessStr);
for (let process in compartmentsByProcess) {
if (process !== gUnnamedProcessStr) {
handleProcess(process);
// Always put the main process first.
if (aProcessA == gUnnamedProcessStr) {
return -1;
}
if (aProcessB == gUnnamedProcessStr) {
return 1;
}
// Otherwise the order doesn't matter.
return 0;
});
// Generate output for each process.
for (let i = 0; i < processes.length; i++) {
let process = processes[i];
appendProcessAboutCompartmentsElements(gMain, process,
compartmentsByProcess[process],
ghostWindowsByProcess[process]);
}
}
let section = appendElement(body, 'div', 'footer');
if (gVerbose) {
let a = appendElementWithText(section, "a", "option", "Less verbose");
a.href = "about:compartments";
} else {
let a = appendElementWithText(section, "a", "option", "More verbose");
a.href = "about:compartments?verbose";
} catch (ex) {
handleException(ex);
}
}

View File

@ -140,9 +140,8 @@
]]>
</script>
<iframe id="acFrame" height="400" src="about:compartments"></iframe>
<!-- vary the capitalization to make sure that works -->
<iframe id="acvFrame" height="400" src="abouT:compartMENTS?veRBOse"></iframe>
<iframe id="acFrame" height="400" src="abouT:compartMENTS"></iframe>
<script type="application/javascript">
<![CDATA[
@ -179,9 +178,6 @@ 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
@ -240,13 +236,7 @@ Ghost Windows\n\
"acFrame",
acExpectedText,
function() {
test(
"acvFrame",
acvExpectedText,
function() {
finish()
}
)
finish()
}
);
});

View File

@ -250,9 +250,9 @@
]]>
</script>
<iframe id="amFrame" height="400" src="about:memory"></iframe>
<iframe id="amFrame" height="300" src="about:memory"></iframe>
<!-- vary the capitalization to make sure that works -->
<iframe id="amvFrame" height="400" src="About:Memory?verbose"></iframe>
<iframe id="amvFrame" height="300" src="About:Memory"></iframe>
<script type="application/javascript">
<![CDATA[
@ -609,11 +609,20 @@ Other Measurements\n\
// 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) {
function test(aFrameId, aVerbose, aExpected, aNext) {
SimpleTest.executeSoon(function() {
ok(document.title === "about:memory", "document.title is correct");
let mostRecentActual;
document.getElementById(aFrameId).focus();
let frame = document.getElementById(aFrameId);
frame.focus();
// Set the verbose checkbox value and click the go button.
let doc = frame.contentWindow.document;
let measureButton = doc.getElementById("measureButton");
let verbose = doc.getElementById("verbose");
verbose.checked = aVerbose;
measureButton.click();
SimpleTest.waitForClipboard(
function(aActual) {
mostRecentActual = aActual;
@ -640,10 +649,12 @@ Other Measurements\n\
SimpleTest.waitForFocus(function() {
test(
"amFrame",
/* verbose = */ false,
amExpectedText,
function() {
test(
"amvFrame",
/* verbose = */ true,
amvExpectedText,
function() {
finish()

View File

@ -110,21 +110,23 @@
// Click on the identified element, then cut+paste the entire page and
// check that the cut text matches what we expect.
function test(aId, aExpected, aNext) {
function test(aId, aSwap, aExpected, aNext) {
let win = document.getElementById("amFrame").contentWindow;
if (aId) {
let node = win.document.getElementById(aId);
// Yuk: clicking a button is easy; but for tree entries we need to
// click on a child of the span identified via |id|.
// Also, we swap the paths of hiReport/hi2Report and
// jkReport/jk2Report just before updating, to test what happens when
// significant nodes become insignificant and vice versa.
if (node.nodeName === "button") {
hiReport.path = "explicit/j/k";
hi2Report.path = "explicit/j/k2";
jkReport.path = "explicit/h/i";
jk2Report.path = "explicit/h/i2";
if (aSwap) {
// We swap the paths of hiReport/hi2Report and jkReport/jk2Report
// just before updating, to test what happens when significant nodes
// become insignificant and vice versa.
hiReport.path = "explicit/j/k";
hi2Report.path = "explicit/j/k2";
jkReport.path = "explicit/h/i";
jk2Report.path = "explicit/h/i2";
}
node.click();
} else {
node.childNodes[0].click();
@ -161,7 +163,7 @@
function chain(aIds) {
let x = aIds.shift();
if (x) {
return function() { test(x.id, x.expected, chain(aIds)); }
return function() { test(x.id, x.swap, x.expected, chain(aIds)); }
} else {
return function() { finish(); };
}
@ -417,16 +419,16 @@ Other Measurements\n\
// upon update (and should remain expanded)
//
let idsToClick = [
{ id: "", expected: startExpected },
{ id: "Main Process:explicit/a/c", expected: acCollapsedExpected },
{ id: "Main Process:explicit/a/l", expected: alExpandedExpected },
{ id: "Main Process:explicit/a", expected: aCollapsedExpected },
{ id: "Main Process:explicit/h", expected: hCollapsedExpected },
{ id: "Main Process:explicit/j", expected: jExpandedExpected },
{ id: "updateButton", expected: updatedExpected },
{ id: "Main Process:explicit/a", expected: aExpandedExpected },
{ id: "Main Process:explicit/a/c", expected: acExpandedExpected },
{ id: "Main Process:explicit/a/l", expected: alCollapsedExpected }
{ id: "measureButton", swap: 0, expected: startExpected },
{ id: "Main Process:explicit/a/c", swap: 0, expected: acCollapsedExpected },
{ id: "Main Process:explicit/a/l", swap: 0, expected: alExpandedExpected },
{ id: "Main Process:explicit/a", swap: 0, expected: aCollapsedExpected },
{ id: "Main Process:explicit/h", swap: 0, expected: hCollapsedExpected },
{ id: "Main Process:explicit/j", swap: 0, expected: jExpandedExpected },
{ id: "measureButton", swap: 1, expected: updatedExpected },
{ id: "Main Process:explicit/a", swap: 0, expected: aExpandedExpected },
{ id: "Main Process:explicit/a/c", swap: 0, expected: acExpandedExpected },
{ id: "Main Process:explicit/a/l", swap: 0, expected: alCollapsedExpected }
];
SimpleTest.waitForFocus(chain(idsToClick));

View File

@ -122,7 +122,7 @@
/* dumpChildProcesses = */ false);
}
let input = frame.contentWindow.document.getElementById("fileInput");
let input = frame.contentWindow.document.getElementById("filePickerInput");
input.value = file.path; // this works because it's a chrome test
var e = document.createEvent('Event');
@ -199,8 +199,7 @@ Other Measurements\n\
// This is the output for a malformed data file.
let expectedBad =
"\
Invalid memory report(s): missing 'hasMozMallocUsableSize' property\n\
";
Invalid memory report(s): missing 'hasMozMallocUsableSize' property";
let frames = [
// This loads a pre-existing file that is valid.

View File

@ -7,7 +7,7 @@
//---------------------------------------------------------------------------
// This script diffs two memory report dumps produced by about:memory. Run it
// using a JS shell. View the result in about:memory using its diff mode, e.g.
// visit "about:memory?diff" or "about:memory?verbose&diff".
// visit "about:memory?diff" or "about:memory?diff".
//
// A simple test can be performed by running:
//