mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 856917 (part 1) - Improve about:memory's functional UI. r=kats.
This commit is contained in:
parent
fd709d6e6f
commit
a0eca79ac3
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -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()
|
||||
|
@ -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));
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user