Bug 828052 - Update Cleopatra and fix the issue with gJavaScriptOnly flag not working; r=past

This commit is contained in:
Anton Kovalyov 2013-01-16 11:44:35 -08:00
parent 23f70e6cd3
commit e15b9dcc56
5 changed files with 1022 additions and 367 deletions

View File

@ -277,7 +277,7 @@ ProfilerPanel.prototype = {
* If the instance is already loaded, onLoad will be * If the instance is already loaded, onLoad will be
* called synchronously. * called synchronously.
*/ */
switchToProfile: function PP_switchToProfile(profile, onLoad) { switchToProfile: function PP_switchToProfile(profile, onLoad=function() {}) {
let doc = this.document; let doc = this.document;
if (this.activeProfile) { if (this.activeProfile) {

View File

@ -47,14 +47,19 @@ function onParentMessage(event) {
var stop = document.getElementById("stopWrapper"); var stop = document.getElementById("stopWrapper");
var msg = JSON.parse(event.data); var msg = JSON.parse(event.data);
if (msg.task === "onStarted") { switch (msg.task) {
start.style.display = "none"; case "onStarted":
start.querySelector("button").removeAttribute("disabled"); start.style.display = "none";
stop.style.display = "inline"; start.querySelector("button").removeAttribute("disabled");
} else if (msg.task === "onStopped") { stop.style.display = "inline";
stop.style.display = "none"; break;
stop.querySelector("button").removeAttribute("disabled"); case "onStopped":
start.style.display = "inline"; stop.style.display = "none";
stop.querySelector("button").removeAttribute("disabled");
start.style.display = "inline";
break;
case "receiveProfileData":
loadProfile(JSON.stringify(msg.rawProfile));
} }
} }
@ -66,7 +71,9 @@ window.addEventListener("message", onParentMessage);
*/ */
function initUI() { function initUI() {
gLightMode = true; gLightMode = true;
gJavaScriptOnly = true;
gFileList = { profileParsingFinished: function () {} };
gInfoBar = { display: function () {} };
var container = document.createElement("div"); var container = document.createElement("div");
container.id = "ui"; container.id = "ui";
@ -101,4 +108,104 @@ function initUI() {
controlPane.querySelector("#stopWrapper > span.btn").appendChild(stopButton); controlPane.querySelector("#stopWrapper > span.btn").appendChild(stopButton);
gMainArea.appendChild(controlPane); gMainArea.appendChild(controlPane);
}
/**
* Modified copy of Cleopatra's enterFinishedProfileUI.
* By overriding the function we don't need to modify ui.js which helps
* with updating from upstream.
*/
function enterFinishedProfileUI() {
var cover = document.createElement("div");
cover.className = "finishedProfilePaneBackgroundCover";
var pane = document.createElement("table");
var rowIndex = 0;
var currRow;
pane.style.width = "100%";
pane.style.height = "100%";
pane.border = "0";
pane.cellPadding = "0";
pane.cellSpacing = "0";
pane.borderCollapse = "collapse";
pane.className = "finishedProfilePane";
gBreadcrumbTrail = new BreadcrumbTrail();
currRow = pane.insertRow(rowIndex++);
currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer());
gHistogramView = new HistogramView();
currRow = pane.insertRow(rowIndex++);
currRow.insertCell(0).appendChild(gHistogramView.getContainer());
if (gMeta && gMeta.videoCapture) {
gVideoPane = new VideoPane(gMeta.videoCapture);
gVideoPane.onTimeChange(videoPaneTimeChange);
currRow = pane.insertRow(rowIndex++);
currRow.insertCell(0).appendChild(gVideoPane.getContainer());
}
var tree = document.createElement("div");
tree.className = "treeContainer";
tree.style.width = "100%";
tree.style.height = "100%";
gTreeManager = new ProfileTreeManager();
gTreeManager.treeView.setColumns([
{ name: "sampleCount", title: "Running time" },
{ name: "selfSampleCount", title: "Self" },
{ name: "resource", title: "" },
]);
currRow = pane.insertRow(rowIndex++);
currRow.style.height = "100%";
var cell = currRow.insertCell(0);
cell.appendChild(tree);
tree.appendChild(gTreeManager.getContainer());
gPluginView = new PluginView();
tree.appendChild(gPluginView.getContainer());
gMainArea.appendChild(cover);
gMainArea.appendChild(pane);
var currentBreadcrumb = gSampleFilters;
gBreadcrumbTrail.add({
title: "Complete Profile",
enterCallback: function () {
gSampleFilters = [];
filtersChanged();
}
});
if (currentBreadcrumb == null || currentBreadcrumb.length == 0) {
gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection);
viewOptionsChanged();
}
for (var i = 0; i < currentBreadcrumb.length; i++) {
var filter = currentBreadcrumb[i];
var forceSelection = null;
if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) {
forceSelection = gRestoreSelection;
}
switch (filter.type) {
case "FocusedFrameSampleFilter":
focusOnSymbol(filter.name, filter.symbolName);
gBreadcrumbTrail.enterLastItem(forceSelection);
case "FocusedCallstackPrefixSampleFilter":
focusOnCallstack(filter.focusedCallstack, filter.name, false);
gBreadcrumbTrail.enterLastItem(forceSelection);
case "FocusedCallstackPostfixSampleFilter":
focusOnCallstack(filter.focusedCallstack, filter.name, true);
gBreadcrumbTrail.enterLastItem(forceSelection);
case "RangeSampleFilter":
gHistogramView.selectRange(filter.start, filter.end);
gBreadcrumbTrail.enterLastItem(forceSelection);
}
}
toggleJavascriptOnly();
} }

View File

@ -239,17 +239,26 @@ function parseRawProfile(requestID, params, rawProfile) {
var symbolicationTable = {}; var symbolicationTable = {};
var symbols = []; var symbols = [];
var symbolIndices = {}; var symbolIndices = {};
var resources = {};
var functions = []; var functions = [];
var functionIndices = {}; var functionIndices = {};
var samples = []; var samples = [];
var meta = {}; var meta = {};
var armIncludePCIndex = {}; var armIncludePCIndex = {};
if (rawProfile == null) {
throw "rawProfile is null";
}
if (typeof rawProfile == "string" && rawProfile[0] == "{") { if (typeof rawProfile == "string" && rawProfile[0] == "{") {
// rawProfile is a JSON string. // rawProfile is a JSON string.
rawProfile = JSON.parse(rawProfile); rawProfile = JSON.parse(rawProfile);
if (rawProfile === null) {
throw "rawProfile couldn't not successfully be parsed using JSON.parse. Make sure that the profile is a valid JSON encoding.";
}
} }
if (rawProfile.profileJSON && !rawProfile.profileJSON.meta && rawProfile.meta) { if (rawProfile.profileJSON && !rawProfile.profileJSON.meta && rawProfile.meta) {
rawProfile.profileJSON.meta = rawProfile.meta; rawProfile.profileJSON.meta = rawProfile.meta;
} }
@ -271,149 +280,217 @@ function parseRawProfile(requestID, params, rawProfile) {
parseProfileString(rawProfile); parseProfileString(rawProfile);
} }
if (params.profileId) {
meta.profileId = params.profileId;
}
function cleanFunctionName(functionName) { function cleanFunctionName(functionName) {
var ignoredPrefix = "non-virtual thunk to "; var ignoredPrefix = "non-virtual thunk to ";
if (functionName.substr(0, ignoredPrefix.length) == ignoredPrefix) if (functionName.startsWith(ignoredPrefix))
return functionName.substr(ignoredPrefix.length); return functionName.substr(ignoredPrefix.length);
return functionName; return functionName;
} }
function resourceNameForAddon(addonID) { function resourceNameForAddon(addon) {
for (var i in meta.addons) { if (!addon)
var addon = meta.addons[i]; return "";
if (addon.id.toLowerCase() == addonID.toLowerCase()) {
var iconHTML = "";
if (addon.iconURL)
iconHTML = "<img src=\"" + addon.iconURL + "\" style='width:12px; height:12px;'> "
return iconHTML + " " + (/@jetpack$/.exec(addonID) ? "Jetpack: " : "") + addon.name;
}
}
return "";
}
function parseResourceName(url) {
if (!url) {
return "No URL";
}
if (url.startsWith("resource:///")) {
// Take the last URL from a chained list of URLs.
var urls = url.split(" -> ");
url = urls[urls.length - 1];
}
// TODO Fix me, this certainly doesn't handle all URLs formats
var match = /^.*:\/\/(.*?)\/.*$/.exec(url);
if (!match)
return url;
var host = match[1];
if (meta && meta.addons) {
if (url.startsWith("resource:") && endsWith(host, "-at-jetpack")) {
// Assume this is a jetpack url
var jetpackID = host.substring(0, host.length - 11) + "@jetpack";
var resName = resourceNameForAddon(jetpackID);
if (resName)
return resName;
}
if (url.startsWith("file:///") && url.indexOf("/extensions/") != -1) {
var unpackedAddonNameMatch = /\/extensions\/(.*?)\//.exec(url);
if (unpackedAddonNameMatch) {
var resName = resourceNameForAddon(decodeURIComponent(unpackedAddonNameMatch[1]));
if (resName)
return resName;
}
}
if (url.startsWith("jar:file:///") && url.indexOf("/extensions/") != -1) {
var packedAddonNameMatch = /\/extensions\/(.*?).xpi/.exec(url);
if (packedAddonNameMatch) {
var resName = resourceNameForAddon(decodeURIComponent(packedAddonNameMatch[1]));
if (resName)
return resName;
}
}
}
var iconHTML = ""; var iconHTML = "";
if (url.indexOf("http://") == 0) { if (addon.iconURL)
iconHTML = "<img src=\"http://" + host + "/favicon.ico\" style='width:12px; height:12px;'> "; iconHTML = "<img src=\"" + addon.iconURL + "\" style='width:12px; height:12px;'> "
} else if (url.indexOf("https://") == 0) { return iconHTML + " " + (/@jetpack$/.exec(addon.id) ? "Jetpack: " : "") + addon.name;
iconHTML = "<img src=\"https://" + host + "/favicon.ico\" style='width:12px; height:12px;'> "; }
function addonWithID(addonID) {
return firstMatch(meta.addons, function addonHasID(addon) {
return addon.id.toLowerCase() == addonID.toLowerCase();
})
}
function resourceNameForAddonWithID(addonID) {
return resourceNameForAddon(addonWithID(addonID));
}
function findAddonForChromeURIHost(host) {
return firstMatch(meta.addons, function addonUsesChromeURIHost(addon) {
return addon.chromeURIHosts && addon.chromeURIHosts.indexOf(host) != -1;
});
}
function ensureResource(name, resourceDescription) {
if (!(name in resources)) {
resources[name] = resourceDescription;
} }
return iconHTML + host; return name;
}
function resourceNameFromLibrary(library) {
return ensureResource("lib_" + library, {
type: "library",
name: library
});
}
function getAddonForScriptURI(url, host) {
if (!meta || !meta.addons)
return null;
if (url.startsWith("resource:") && endsWith(host, "-at-jetpack")) {
// Assume this is a jetpack url
var jetpackID = host.substring(0, host.length - 11) + "@jetpack";
return addonWithID(jetpackID);
}
if (url.startsWith("file:///") && url.indexOf("/extensions/") != -1) {
var unpackedAddonNameMatch = /\/extensions\/(.*?)\//.exec(url);
if (unpackedAddonNameMatch)
return addonWithID(decodeURIComponent(unpackedAddonNameMatch[1]));
return null;
}
if (url.startsWith("jar:file:///") && url.indexOf("/extensions/") != -1) {
var packedAddonNameMatch = /\/extensions\/(.*?).xpi/.exec(url);
if (packedAddonNameMatch)
return addonWithID(decodeURIComponent(packedAddonNameMatch[1]));
return null;
}
if (url.startsWith("chrome://")) {
var chromeURIMatch = /chrome\:\/\/(.*?)\//.exec(url);
if (chromeURIMatch)
return findAddonForChromeURIHost(chromeURIMatch[1]);
return null;
}
return null;
}
function resourceNameFromURI(url) {
if (!url)
return ensureResource("unknown", {type: "unknown", name: "<unknown>"});
var match = /^(.*):\/\/(.*?)\//.exec(url);
if (!match) {
// Can this happen? If so, we should change the regular expression above.
return ensureResource("url_" + url, {type: "url", name: url});
}
var urlRoot = match[0];
var protocol = match[1];
var host = match[2];
var addon = getAddonForScriptURI(url, host);
if (addon) {
return ensureResource("addon_" + addon.id, {
type: "addon",
name: addon.name,
addonID: addon.id,
icon: addon.iconURL
});
}
if (protocol.startsWith("http")) {
return ensureResource("webhost_" + host, {
type: "webhost",
name: host,
icon: urlRoot + "favicon.ico"
});
}
return ensureResource("otherhost_" + host, {
type: "otherhost",
name: host
});
} }
function parseScriptFile(url) { function parseScriptFile(url) {
// TODO Fix me, this certainly doesn't handle all URLs formats var match = /([^\/]*)$/.exec(url);
var match = /^.*\/(.*)\.js$/.exec(url); if (match && match[1])
return match[1];
if (!match) return url;
return url;
return match[1] + ".js";
} }
function parseScriptURI(url) { // JS File information sometimes comes with multiple URIs which are chained
// with " -> ". We only want the last URI in this list.
function getRealScriptURI(url) {
if (url) { if (url) {
var urlTokens = url.split(" "); var urls = url.split(" -> ");
url = urlTokens[urlTokens.length-1]; return urls[urls.length - 1];
} }
return url; return url;
} }
function getFunctionInfo(fullName) { function getFunctionInfo(fullName) {
var isJSFrame = false;
var match = function getCPPFunctionInfo(fullName) {
/^(.*) \(in ([^\)]*)\) (\+ [0-9]+)$/.exec(fullName) || var match =
/^(.*) \(in ([^\)]*)\) (\(.*:.*\))$/.exec(fullName) || /^(.*) \(in ([^\)]*)\) (\+ [0-9]+)$/.exec(fullName) ||
/^(.*) \(in ([^\)]*)\)$/.exec(fullName); /^(.*) \(in ([^\)]*)\) (\(.*:.*\))$/.exec(fullName) ||
// Try to parse a JS frame /^(.*) \(in ([^\)]*)\)$/.exec(fullName);
var scriptLocation = null;
var jsMatch1 = match || if (!match)
/^(.*) \((.*):([0-9]+)\)$/.exec(fullName); return null;
if (!match && jsMatch1) {
scriptLocation = { return {
scriptURI: parseScriptURI(jsMatch1[2]), functionName: cleanFunctionName(match[1]),
lineInformation: jsMatch1[3] libraryName: resourceNameFromLibrary(match[2]),
lineInformation: match[3] || "",
isRoot: false,
isJSFrame: false
}; };
match = [0, jsMatch1[1]+"() @ "+parseScriptFile(jsMatch1[2]) + ":" + jsMatch1[3], parseResourceName(jsMatch1[2]), ""];
isJSFrame = true;
} }
var jsMatch2 = match ||
/^(.*):([0-9]+)$/.exec(fullName); function getJSFunctionInfo(fullName) {
if (!match && jsMatch2) { var jsMatch =
scriptLocation = { /^(.*) \((.*):([0-9]+)\)$/.exec(fullName) ||
scriptURI: parseScriptURI(jsMatch2[1]), /^()(.*):([0-9]+)$/.exec(fullName);
lineInformation: jsMatch2[2]
if (!jsMatch)
return null;
var functionName = jsMatch[1] || "<Anonymous>";
var scriptURI = getRealScriptURI(jsMatch[2]);
var lineNumber = jsMatch[3];
var scriptFile = parseScriptFile(scriptURI);
var resourceName = resourceNameFromURI(scriptURI);
return {
functionName: functionName + "() @ " + scriptFile + ":" + lineNumber,
libraryName: resourceName,
lineInformation: "",
isRoot: false,
isJSFrame: true,
scriptLocation: {
scriptURI: scriptURI,
lineInformation: lineNumber
}
}; };
match = [0, "<Anonymous> @ "+parseScriptFile(jsMatch2[1]) + ":" + jsMatch2[2], parseResourceName(jsMatch2[1]), ""];
isJSFrame = true;
} }
if (!match) {
match = [fullName, fullName]; function getFallbackFunctionInfo(fullName) {
return {
functionName: cleanFunctionName(fullName),
libraryName: "",
lineInformation: "",
isRoot: fullName == "(root)",
isJSFrame: false
};
} }
return {
functionName: cleanFunctionName(match[1]), return getCPPFunctionInfo(fullName) ||
libraryName: match[2] || "", getJSFunctionInfo(fullName) ||
lineInformation: match[3] || "", getFallbackFunctionInfo(fullName);
isJSFrame: isJSFrame,
scriptLocation: scriptLocation
};
} }
function indexForFunction(symbol, functionName, libraryName, isJSFrame, scriptLocation) { function indexForFunction(symbol, info) {
var resolve = functionName+"_LIBNAME_"+libraryName; var resolve = info.functionName + "__" + info.libraryName;
if (resolve in functionIndices) if (resolve in functionIndices)
return functionIndices[resolve]; return functionIndices[resolve];
var newIndex = functions.length; var newIndex = functions.length;
functions[newIndex] = { info.symbol = symbol;
symbol: symbol, functions[newIndex] = info;
functionName: functionName,
libraryName: libraryName,
isJSFrame: isJSFrame,
scriptLocation: scriptLocation
};
functionIndices[resolve] = newIndex; functionIndices[resolve] = newIndex;
return newIndex; return newIndex;
} }
@ -424,8 +501,9 @@ function parseRawProfile(requestID, params, rawProfile) {
return { return {
symbolName: symbol, symbolName: symbol,
functionName: info.functionName, functionName: info.functionName,
functionIndex: indexForFunction(symbol, info.functionName, info.libraryName, info.isJSFrame, info.scriptLocation), functionIndex: indexForFunction(symbol, info),
lineInformation: info.lineInformation, lineInformation: info.lineInformation,
isRoot: info.isRoot,
isJSFrame: info.isJSFrame, isJSFrame: info.isJSFrame,
scriptLocation: info.scriptLocation scriptLocation: info.scriptLocation
}; };
@ -582,6 +660,9 @@ function parseRawProfile(requestID, params, rawProfile) {
if (sample.responsiveness) { if (sample.responsiveness) {
sample.extraInfo["responsiveness"] = sample.responsiveness; sample.extraInfo["responsiveness"] = sample.responsiveness;
} }
if (sample.marker) {
sample.extraInfo["marker"] = sample.marker;
}
if (sample.time) { if (sample.time) {
sample.extraInfo["time"] = sample.time; sample.extraInfo["time"] = sample.time;
} }
@ -600,17 +681,22 @@ function parseRawProfile(requestID, params, rawProfile) {
if (!sample) continue; if (!sample) continue;
// If length == 0 then the sample was filtered when saving the profile // If length == 0 then the sample was filtered when saving the profile
if (sample.frames.length >= 1 && sample.frames[0] != rootIndex) if (sample.frames.length >= 1 && sample.frames[0] != rootIndex)
sample.frames.splice(0, 0, rootIndex) sample.frames.unshift(rootIndex)
} }
} }
} }
progressReporter.finish(); progressReporter.finish();
var profileID = gNextProfileID++; // Don't increment the profile ID now because (1) it's buggy
// and (2) for now there's no point in storing each profile
// here if we're storing them in the local storage.
//var profileID = gNextProfileID++;
var profileID = gNextProfileID;
gProfiles[profileID] = JSON.parse(JSON.stringify({ gProfiles[profileID] = JSON.parse(JSON.stringify({
meta: meta, meta: meta,
symbols: symbols, symbols: symbols,
functions: functions, functions: functions,
resources: resources,
allSamples: samples allSamples: samples
})); }));
clearRegExpLastMatch(); clearRegExpLastMatch();
@ -619,7 +705,8 @@ function parseRawProfile(requestID, params, rawProfile) {
numSamples: samples.length, numSamples: samples.length,
profileID: profileID, profileID: profileID,
symbols: symbols, symbols: symbols,
functions: functions functions: functions,
resources: resources
}); });
} }
@ -688,7 +775,6 @@ function convertToCallTree(samples, isReverse) {
function areSamplesMultiroot(samples) { function areSamplesMultiroot(samples) {
var previousRoot; var previousRoot;
for (var i = 0; i < samples.length; ++i) { for (var i = 0; i < samples.length; ++i) {
if (!samples[i].frames) continue;
if (!previousRoot) { if (!previousRoot) {
previousRoot = samples[i].frames[0]; previousRoot = samples[i].frames[0];
continue; continue;
@ -706,8 +792,6 @@ function convertToCallTree(samples, isReverse) {
return new TreeNode("(empty)", null, 0); return new TreeNode("(empty)", null, 0);
var firstRoot = null; var firstRoot = null;
for (var i = 0; i < samples.length; ++i) { for (var i = 0; i < samples.length; ++i) {
if (!samples[i].frames) continue;
sendError(null, "got root: " + samples[i].frames[0]);
firstRoot = samples[i].frames[0]; firstRoot = samples[i].frames[0];
break; break;
} }
@ -718,9 +802,6 @@ function convertToCallTree(samples, isReverse) {
var treeRoot = new TreeNode((isReverse || multiRoot) ? "(total)" : firstRoot, null, 0); var treeRoot = new TreeNode((isReverse || multiRoot) ? "(total)" : firstRoot, null, 0);
for (var i = 0; i < samples.length; ++i) { for (var i = 0; i < samples.length; ++i) {
var sample = samples[i]; var sample = samples[i];
if (!sample.frames) {
continue;
}
var callstack = sample.frames.slice(0); var callstack = sample.frames.slice(0);
callstack.shift(); callstack.shift();
if (isReverse) if (isReverse)
@ -764,68 +845,72 @@ function filterBySymbol(samples, symbolOrFunctionIndex) {
}); });
} }
function filterByCallstackPrefix(samples, callstack) { function filterByCallstackPrefix(samples, symbols, functions, callstack, appliesToJS, useFunctions) {
return samples.map(function filterSample(origSample) { var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
if (!origSample) return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
} : function isJSSymbolOrRoot(symbolIndex) {
return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
};
return samples.map(function filterSample(sample) {
if (!sample)
return null; return null;
if (origSample.frames.length < callstack.length) if (sample.frames.length < callstack.length)
return null; return null;
var sample = cloneSample(origSample); for (var i = 0, j = 0; j < callstack.length; i++) {
for (var i = 0; i < callstack.length; i++) { if (i >= sample.frames.length)
if (sample.frames[i] != callstack[i])
return null; return null;
if (appliesToJS && !isJSFrameOrRoot(sample.frames[i]))
continue;
if (sample.frames[i] != callstack[j])
return null;
j++;
} }
sample.frames = sample.frames.slice(callstack.length - 1); return makeSample(sample.frames.slice(i - 1), sample.extraInfo);
return sample;
}); });
} }
function filterByCallstackPostfix(samples, callstack) { function filterByCallstackPostfix(samples, symbols, functions, callstack, appliesToJS, useFunctions) {
return samples.map(function filterSample(origSample) { var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
if (!origSample) return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
} : function isJSSymbolOrRoot(symbolIndex) {
return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
};
return samples.map(function filterSample(sample) {
if (!sample)
return null; return null;
if (origSample.frames.length < callstack.length) if (sample.frames.length < callstack.length)
return null; return null;
var sample = cloneSample(origSample); for (var i = 0, j = 0; j < callstack.length; i++) {
for (var i = 0; i < callstack.length; i++) { if (i >= sample.frames.length)
if (sample.frames[sample.frames.length - i - 1] != callstack[i])
return null; return null;
if (appliesToJS && !isJSFrameOrRoot(sample.frames[sample.frames.length - i - 1]))
continue;
if (sample.frames[sample.frames.length - i - 1] != callstack[j])
return null;
j++;
} }
sample.frames = sample.frames.slice(0, sample.frames.length - callstack.length + 1); var newFrames = sample.frames.slice(0, sample.frames.length - i + 1);
return sample; return makeSample(newFrames, sample.extraInfo);
}); });
} }
function chargeNonJSToCallers(samples, symbols, functions, useFunctions) { function chargeNonJSToCallers(samples, symbols, functions, useFunctions) {
function isJSFrame(index, useFunction) { var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
if (useFunctions) { return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
if (!(index in functions)) } : function isJSSymbolOrRoot(symbolIndex) {
return ""; return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
return functions[index].isJSFrame; };
}
if (!(index in symbols))
return "";
return symbols[index].isJSFrame;
}
samples = samples.slice(0); samples = samples.slice(0);
for (var i = 0; i < samples.length; ++i) { for (var i = 0; i < samples.length; ++i) {
var sample = samples[i]; var sample = samples[i];
if (!sample) if (!sample)
continue; continue;
var callstack = sample.frames; var newFrames = sample.frames.filter(isJSFrameOrRoot);
var newFrames = [];
for (var j = 0; j < callstack.length; ++j) {
if (isJSFrame(callstack[j], useFunctions)) {
// Record Javascript frames
newFrames.push(callstack[j]);
}
}
if (!newFrames.length) { if (!newFrames.length) {
newFrames = null; samples[i] = null;
} else { } else {
newFrames.splice(0, 0, "(total)"); samples[i].frames = newFrames;
} }
samples[i].frames = newFrames;
} }
return samples; return samples;
} }
@ -912,26 +997,28 @@ function FocusedFrameSampleFilter(focusedSymbol) {
this._focusedSymbol = focusedSymbol; this._focusedSymbol = focusedSymbol;
} }
FocusedFrameSampleFilter.prototype = { FocusedFrameSampleFilter.prototype = {
filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions) { filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions, useFunctions) {
return filterBySymbol(samples, this._focusedSymbol); return filterBySymbol(samples, this._focusedSymbol);
} }
}; };
function FocusedCallstackPrefixSampleFilter(focusedCallstack) { function FocusedCallstackPrefixSampleFilter(focusedCallstack, appliesToJS) {
this._focusedCallstackPrefix = focusedCallstack; this._focusedCallstackPrefix = focusedCallstack;
this._appliesToJS = appliesToJS;
} }
FocusedCallstackPrefixSampleFilter.prototype = { FocusedCallstackPrefixSampleFilter.prototype = {
filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions) { filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions, useFunctions) {
return filterByCallstackPrefix(samples, this._focusedCallstackPrefix); return filterByCallstackPrefix(samples, symbols, functions, this._focusedCallstackPrefix, this._appliesToJS, useFunctions);
} }
}; };
function FocusedCallstackPostfixSampleFilter(focusedCallstack) { function FocusedCallstackPostfixSampleFilter(focusedCallstack, appliesToJS) {
this._focusedCallstackPostfix = focusedCallstack; this._focusedCallstackPostfix = focusedCallstack;
this._appliesToJS = appliesToJS;
} }
FocusedCallstackPostfixSampleFilter.prototype = { FocusedCallstackPostfixSampleFilter.prototype = {
filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions) { filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions, useFunctions) {
return filterByCallstackPostfix(samples, this._focusedCallstackPostfix); return filterByCallstackPostfix(samples, symbols, functions, this._focusedCallstackPostfix, this._appliesToJS, useFunctions);
} }
}; };
@ -951,9 +1038,9 @@ function unserializeSampleFilters(filters) {
case "FocusedFrameSampleFilter": case "FocusedFrameSampleFilter":
return new FocusedFrameSampleFilter(filter.focusedSymbol); return new FocusedFrameSampleFilter(filter.focusedSymbol);
case "FocusedCallstackPrefixSampleFilter": case "FocusedCallstackPrefixSampleFilter":
return new FocusedCallstackPrefixSampleFilter(filter.focusedCallstack); return new FocusedCallstackPrefixSampleFilter(filter.focusedCallstack, filter.appliesToJS);
case "FocusedCallstackPostfixSampleFilter": case "FocusedCallstackPostfixSampleFilter":
return new FocusedCallstackPostfixSampleFilter(filter.focusedCallstack); return new FocusedCallstackPostfixSampleFilter(filter.focusedCallstack, filter.appliesToJS);
case "RangeSampleFilter": case "RangeSampleFilter":
return new RangeSampleFilter(filter.start, filter.end); return new RangeSampleFilter(filter.start, filter.end);
case "PluginView": case "PluginView":
@ -975,14 +1062,6 @@ function updateFilters(requestID, profileID, filters) {
if (filters.mergeFunctions) { if (filters.mergeFunctions) {
samples = discardLineLevelInformation(samples, symbols, functions); samples = discardLineLevelInformation(samples, symbols, functions);
} }
if (filters.javascriptOnly) {
try {
//samples = filterByName(samples, symbols, functions, "runScript", filters.mergeFunctions);
samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions);
} catch (e) {
dump("Could not filer by javascript: " + e + "\n");
}
}
if (filters.nameFilter) { if (filters.nameFilter) {
try { try {
samples = filterByName(samples, symbols, functions, filters.nameFilter, filters.mergeFunctions); samples = filterByName(samples, symbols, functions, filters.nameFilter, filters.mergeFunctions);
@ -992,16 +1071,19 @@ function updateFilters(requestID, profileID, filters) {
} }
samples = unserializeSampleFilters(filters.sampleFilters).reduce(function (filteredSamples, currentFilter) { samples = unserializeSampleFilters(filters.sampleFilters).reduce(function (filteredSamples, currentFilter) {
if (currentFilter===null) return filteredSamples; if (currentFilter===null) return filteredSamples;
return currentFilter.filter(filteredSamples, symbols, functions); return currentFilter.filter(filteredSamples, symbols, functions, filters.mergeFunctions);
}, samples); }, samples);
if (filters.jankOnly) { if (filters.jankOnly) {
samples = filterByJank(samples, gJankThreshold); samples = filterByJank(samples, gJankThreshold);
} }
if (filters.javascriptOnly) {
samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions);
}
gProfiles[profileID].filterSettings = filters; gProfiles[profileID].filterSettings = filters;
gProfiles[profileID].filteredSamples = samples; gProfiles[profileID].filteredSamples = samples;
sendFinishedInChunks(requestID, samples, 40000, sendFinishedInChunks(requestID, samples, 40000,
function (sample) { return (sample && sample.frames) ? sample.frames.length : 1; }); function (sample) { return sample ? sample.frames.length : 1; });
} }
function updateViewOptions(requestID, profileID, options) { function updateViewOptions(requestID, profileID, options) {
@ -1039,7 +1121,7 @@ function calculateHistogramData(requestID, profileID) {
for (var i = 0; i < data.length; ++i) { for (var i = 0; i < data.length; ++i) {
if (!data[i]) if (!data[i])
continue; continue;
var value = data[i].frames ? data[i].frames.length : 0; var value = data[i].frames.length;
if (maxHeight < value) if (maxHeight < value)
maxHeight = value; maxHeight = value;
} }
@ -1053,7 +1135,7 @@ function calculateHistogramData(requestID, profileID) {
var frameStart = {}; var frameStart = {};
for (var i = 0; i < data.length; i++) { for (var i = 0; i < data.length; i++) {
var step = data[i]; var step = data[i];
if (!step || !step.frames) { if (!step) {
// Add a gap for the sample that was filtered out. // Add a gap for the sample that was filtered out.
nextX += 1 / samplesPerStep; nextX += 1 / samplesPerStep;
continue; continue;
@ -1139,6 +1221,26 @@ var diagnosticList = [
; ;
}, },
}, },
{
image: "cache.png",
title: "Bug 717761 - Main thread can be blocked by IO on the cache thread",
bugNumber: "717761",
check: function(frames, symbols, meta) {
return stepContains('nsCacheEntryDescriptor::GetStoragePolicy', frames, symbols)
;
},
},
{
image: "js.png",
title: "Web Content Shutdown Notification",
check: function(frames, symbols, meta) {
return stepContains('nsAppStartup::Quit', frames, symbols)
&& stepContains('nsDocShell::FirePageHideNotification', frames, symbols)
;
},
},
{ {
image: "js.png", image: "js.png",
title: "Bug 789193 - AMI_startup() takes 200ms on startup", title: "Bug 789193 - AMI_startup() takes 200ms on startup",
@ -1149,6 +1251,44 @@ var diagnosticList = [
; ;
}, },
}, },
{
image: "js.png",
title: "Bug 818296 - [Shutdown] js::NukeCrossCompartmentWrappers takes up 300ms on shutdown",
bugNumber: "818296",
check: function(frames, symbols, meta) {
return stepContains('js::NukeCrossCompartmentWrappers', frames, symbols)
&& (stepContains('WindowDestroyedEvent', frames, symbols) || stepContains('DoShutdown', frames, symbols))
;
},
},
{
image: "js.png",
title: "Bug 818274 - [Shutdown] Telemetry takes ~10ms on shutdown",
bugNumber: "818274",
check: function(frames, symbols, meta) {
return stepContains('TelemetryPing.js', frames, symbols)
;
},
},
{
image: "plugin.png",
title: "Bug 818265 - [Shutdown] Plug-in shutdown takes ~90ms on shutdown",
bugNumber: "818265",
check: function(frames, symbols, meta) {
return stepContains('PluginInstanceParent::Destroy', frames, symbols)
;
},
},
{
image: "snapshot.png",
title: "Bug 720575 - Make thumbnailing faster and/or asynchronous",
bugNumber: "720575",
check: function(frames, symbols, meta) {
return stepContains('Thumbnails_capture()', frames, symbols)
;
},
},
{ {
image: "js.png", image: "js.png",
title: "Bug 789185 - LoginManagerStorage_mozStorage.init() takes 300ms on startup ", title: "Bug 789185 - LoginManagerStorage_mozStorage.init() takes 300ms on startup ",
@ -1238,7 +1378,6 @@ var diagnosticList = [
var ccEvent = findCCEvent(frames, symbols, meta, step); var ccEvent = findCCEvent(frames, symbols, meta, step);
if (ccEvent) { if (ccEvent) {
dump("Found\n");
return true; return true;
} }
return false; return false;
@ -1268,10 +1407,21 @@ var diagnosticList = [
check: function(frames, symbols, meta) { check: function(frames, symbols, meta) {
return stepContainsRegEx(/.*Collect.*Runtime.*Invocation.*/, frames, symbols) return stepContainsRegEx(/.*Collect.*Runtime.*Invocation.*/, frames, symbols)
|| stepContains('GarbageCollectNow', frames, symbols) // Label || stepContains('GarbageCollectNow', frames, symbols) // Label
|| stepContains('JS_GC(', frames, symbols) // Label
|| stepContains('CycleCollect__', frames, symbols) // Label || stepContains('CycleCollect__', frames, symbols) // Label
; ;
}, },
}, },
{
image: "cc.png",
title: "Cycle Collect",
check: function(frames, symbols, meta) {
return stepContains('nsCycleCollector::Collect', frames, symbols)
|| stepContains('CycleCollect__', frames, symbols) // Label
|| stepContains('nsCycleCollectorRunner::Collect', frames, symbols) // Label
;
},
},
{ {
image: "plugin.png", image: "plugin.png",
title: "Sync Plugin Constructor", title: "Sync Plugin Constructor",
@ -1296,6 +1446,7 @@ var diagnosticList = [
check: function(frames, symbols, meta) { check: function(frames, symbols, meta) {
return stepContains('__getdirentries64', frames, symbols) return stepContains('__getdirentries64', frames, symbols)
|| stepContains('__open', frames, symbols) || stepContains('__open', frames, symbols)
|| stepContains('NtFlushBuffersFile', frames, symbols)
|| stepContains('storage:::Statement::ExecuteStep', frames, symbols) || stepContains('storage:::Statement::ExecuteStep', frames, symbols)
|| stepContains('__unlink', frames, symbols) || stepContains('__unlink', frames, symbols)
|| stepContains('fsync', frames, symbols) || stepContains('fsync', frames, symbols)
@ -1371,6 +1522,8 @@ function findGCSlice(frames, symbols, meta, step) {
} }
function stepContains(substring, frames, symbols) { function stepContains(substring, frames, symbols) {
for (var i = 0; frames && i < frames.length; i++) { for (var i = 0; frames && i < frames.length; i++) {
if (!(frames[i] in symbols))
continue;
var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
if (frameSym.indexOf(substring) != -1) { if (frameSym.indexOf(substring) != -1) {
return true; return true;
@ -1380,6 +1533,8 @@ function stepContains(substring, frames, symbols) {
} }
function stepContainsRegEx(regex, frames, symbols) { function stepContainsRegEx(regex, frames, symbols) {
for (var i = 0; frames && i < frames.length; i++) { for (var i = 0; frames && i < frames.length; i++) {
if (!(frames[i] in symbols))
continue;
var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
if (regex.exec(frameSym)) { if (regex.exec(frameSym)) {
return true; return true;
@ -1390,6 +1545,8 @@ function stepContainsRegEx(regex, frames, symbols) {
function symbolSequence(symbolsOrder, frames, symbols) { function symbolSequence(symbolsOrder, frames, symbols) {
var symbolIndex = 0; var symbolIndex = 0;
for (var i = 0; frames && i < frames.length; i++) { for (var i = 0; frames && i < frames.length; i++) {
if (!(frames[i] in symbols))
continue;
var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
var substring = symbolsOrder[symbolIndex]; var substring = symbolsOrder[symbolIndex];
if (frameSym.indexOf(substring) != -1) { if (frameSym.indexOf(substring) != -1) {
@ -1458,14 +1615,13 @@ function calculateDiagnosticItems(requestID, profileID, meta) {
*/ */
data.forEach(function diagnoseStep(step, x) { data.forEach(function diagnoseStep(step, x) {
if (!step) if (step) {
return; var frames = step.frames;
var frames = step.frames; var diagnostic = firstMatch(diagnosticList, function (diagnostic) {
return diagnostic.check(frames, symbols, meta, step);
var diagnostic = firstMatch(diagnosticList, function (diagnostic) { });
return diagnostic.check(frames, symbols, meta, step); }
});
if (!diagnostic) { if (!diagnostic) {
finishPendingDiagnostic(x); finishPendingDiagnostic(x);

View File

@ -20,24 +20,20 @@ RegExp.escape = function(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
} }
var requestAnimationFrame_timeout = null;
var requestAnimationFrame = window.webkitRequestAnimationFrame || var requestAnimationFrame = window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame || window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame || window.oRequestAnimationFrame ||
window.msRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback, element) { function(callback, element) {
window.setTimeout(callback, 1000 / 60); return window.setTimeout(callback, 1000 / 60);
}; };
var cancelAnimationFrame = window.webkitCancelAnimationFrame || var cancelAnimationFrame = window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame || window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame || window.oCancelAnimationFrame ||
window.msCancelAnimationFrame || window.msCancelAnimationFrame ||
function(callback, element) { function(req) {
if (requestAnimationFrame_timeout) { window.clearTimeout(req);
window.clearTimeout(requestAnimationFrame_timeout);
requestAnimationFrame_timeout = null;
}
}; };
function TreeView() { function TreeView() {
@ -65,6 +61,10 @@ function TreeView() {
this._horizontalScrollbox.className = "treeViewHorizontalScrollbox"; this._horizontalScrollbox.className = "treeViewHorizontalScrollbox";
this._verticalScrollbox.appendChild(this._horizontalScrollbox); this._verticalScrollbox.appendChild(this._horizontalScrollbox);
this._styleElement = document.createElement("style");
this._styleElement.setAttribute("type", "text/css");
this._container.appendChild(this._styleElement);
this._contextMenu = document.createElement("menu"); this._contextMenu = document.createElement("menu");
this._contextMenu.setAttribute("type", "context"); this._contextMenu.setAttribute("type", "context");
this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++; this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++;
@ -74,11 +74,17 @@ function TreeView() {
this._busyCover.className = "busyCover"; this._busyCover.className = "busyCover";
this._container.appendChild(this._busyCover); this._container.appendChild(this._busyCover);
this._abortToggleAll = false; this._abortToggleAll = false;
this.initSelection = true;
var self = this; var self = this;
this._container.onkeydown = function (e) { this._container.onkeydown = function (e) {
self._onkeypress(e); self._onkeypress(e);
}; };
this._container.onkeypress = function (e) {
// on key down gives us '8' and mapping shift+8='*' may not be portable.
if (String.fromCharCode(e.charCode) == '*')
self._onkeypress(e);
};
this._container.onclick = function (e) { this._container.onclick = function (e) {
self._onclick(e); self._onclick(e);
}; };
@ -106,9 +112,11 @@ TreeView.prototype = {
dataIsOutdated: function TreeView_dataIsOutdated() { dataIsOutdated: function TreeView_dataIsOutdated() {
this._busyCover.classList.add("busy"); this._busyCover.classList.add("busy");
}, },
display: function TreeView_display(data, filterByName) { display: function TreeView_display(data, resources, filterByName) {
this._busyCover.classList.remove("busy"); this._busyCover.classList.remove("busy");
this._filterByName = filterByName; this._filterByName = filterByName;
this._resources = resources;
this._addResourceIconStyles();
this._filterByNameReg = null; // lazy init this._filterByNameReg = null; // lazy init
if (this._filterByName === "") if (this._filterByName === "")
this._filterByName = null; this._filterByName = null;
@ -126,9 +134,12 @@ TreeView.prototype = {
data: data[0].getData() data: data[0].getData()
}); });
this._processPendingActionsChunk(); this._processPendingActionsChunk();
this._select(this._horizontalScrollbox.firstChild); if (this._initSelection === true) {
this._toggle(this._horizontalScrollbox.firstChild); this._initSelection = false;
this._container.focus(); this._select(this._horizontalScrollbox.firstChild);
this._toggle(this._horizontalScrollbox.firstChild);
}
changeFocus(this._container);
}, },
// Provide a snapshot of the reverse selection to restore with 'invert callback' // Provide a snapshot of the reverse selection to restore with 'invert callback'
getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) { getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) {
@ -142,8 +153,8 @@ TreeView.prototype = {
snapshot.push(curr.name); snapshot.push(curr.name);
//dump(JSON.stringify(curr.name) + "\n"); //dump(JSON.stringify(curr.name) + "\n");
} }
if (curr.children && curr.children.length >= 1) { if (curr.treeChildren && curr.treeChildren.length >= 1) {
curr = curr.children[0].getData(); curr = curr.treeChildren[0].getData();
} else { } else {
break; break;
} }
@ -171,6 +182,7 @@ TreeView.prototype = {
}, },
// Take a selection snapshot and restore the selection // Take a selection snapshot and restore the selection
restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContigious) { restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContigious) {
//console.log("restore selection: " + JSON.stringify(snapshot));
var currNode = this._horizontalScrollbox.firstChild; var currNode = this._horizontalScrollbox.firstChild;
if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") { if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") {
snapshot.shift(); snapshot.shift();
@ -181,7 +193,7 @@ TreeView.prototype = {
this._syncProcessPendingActionProcessing(); this._syncProcessPendingActionProcessing();
for (var i = 0; i < currNode.treeChildren.length; i++) { for (var i = 0; i < currNode.treeChildren.length; i++) {
if (currNode.treeChildren[i].data.name == snapshot[0]) { if (currNode.treeChildren[i].data.name == snapshot[0]) {
//dump("Found: " + currNode.treeChildren[i].data.name + "\n"); //console.log("Found: " + currNode.treeChildren[i].data.name + "\n");
snapshot.shift(); snapshot.shift();
this._toggle(currNode, false, true); this._toggle(currNode, false, true);
currNode = currNode.treeChildren[i]; currNode = currNode.treeChildren[i];
@ -193,11 +205,11 @@ TreeView.prototype = {
var pendingSearch = [currNode.data]; var pendingSearch = [currNode.data];
while (pendingSearch.length > 0) { while (pendingSearch.length > 0) {
var node = pendingSearch.shift(); var node = pendingSearch.shift();
//dump("searching: " + node.name + " for: " + snapshot[0] + "\n"); //console.log("searching: " + node.name + " for: " + snapshot[0] + "\n");
if (!node.children) if (!node.treeChildren)
continue; continue;
for (var i = 0; i < node.children.length; i++) { for (var i = 0; i < node.treeChildren.length; i++) {
var childNode = node.children[i].getData(); var childNode = node.treeChildren[i].getData();
if (childNode.name == snapshot[0]) { if (childNode.name == snapshot[0]) {
//dump("found: " + childNode.name + "\n"); //dump("found: " + childNode.name + "\n");
snapshot.shift(); snapshot.shift();
@ -317,7 +329,13 @@ TreeView.prototype = {
}; };
}, },
_scrollHeightChanged: function TreeView__scrollHeightChanged() { _scrollHeightChanged: function TreeView__scrollHeightChanged() {
this._leftColumnBackground.style.height = this._horizontalScrollbox.getBoundingClientRect().height + 'px'; if (!this._pendingScrollHeightChanged) {
var self = this;
this._pendingScrollHeightChanged = setTimeout(function() {
self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px';
self._pendingScrollHeightChanged = null;
}, 0);
}
}, },
_createTree: function TreeView__createTree(parentElement, parentNode, data) { _createTree: function TreeView__createTree(parentElement, parentNode, data) {
var div = document.createElement("div"); var div = document.createElement("div");
@ -328,27 +346,49 @@ TreeView.prototype = {
var treeLine = document.createElement("div"); var treeLine = document.createElement("div");
treeLine.className = "treeLine"; treeLine.className = "treeLine";
treeLine.innerHTML = this._HTMLForFunction(data); treeLine.innerHTML = this._HTMLForFunction(data);
div.depth = parentNode ? parentNode.depth + 1 : 0;
div.style.marginLeft = div.depth + "em";
// When this item is toggled we will expand its children // When this item is toggled we will expand its children
div.pendingExpand = []; div.pendingExpand = [];
div.treeLine = treeLine; div.treeLine = treeLine;
div.data = data; div.data = data;
// Useful for debugging
//this.uniqueID = this.uniqueID || 0;
//div.id = "Node" + this.uniqueID++;
div.appendChild(treeLine); div.appendChild(treeLine);
div.treeChildren = []; div.treeChildren = [];
div.treeParent = parentNode; div.treeParent = parentNode;
if (hasChildren) { if (hasChildren) {
var parent = document.createElement("div");
parent.className = "treeViewNodeList";
for (var i = 0; i < data.children.length; ++i) { for (var i = 0; i < data.children.length; ++i) {
div.pendingExpand.push({parentElement: parent, parentNode: div, data: data.children[i].getData() }); div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() });
} }
div.appendChild(parent);
} }
if (parentNode) { if (parentNode) {
parentNode.treeChildren.push(div); parentNode.treeChildren.push(div);
} }
parentElement.appendChild(div); if (parentNode != null) {
var nextTo;
if (parentNode.treeChildren.length > 1) {
nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling;
} else {
nextTo = parentNode.nextSibling;
}
parentElement.insertBefore(div, nextTo);
} else {
parentElement.appendChild(div);
}
return div; return div;
}, },
_addResourceIconStyles: function TreeView__addResourceIconStyles() {
var styles = [];
for (var resourceName in this._resources) {
var resource = this._resources[resourceName];
if (resource.icon) {
styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }');
}
}
this._styleElement.textContent = styles.join("\n");
},
_populateContextMenu: function TreeView__populateContextMenu(event) { _populateContextMenu: function TreeView__populateContextMenu(event) {
this._verticalScrollbox.setAttribute("contextmenu", ""); this._verticalScrollbox.setAttribute("contextmenu", "");
@ -385,9 +425,9 @@ TreeView.prototype = {
_contextMenuForFunction: function TreeView__contextMenuForFunction(node) { _contextMenuForFunction: function TreeView__contextMenuForFunction(node) {
// TODO move me outside tree.js // TODO move me outside tree.js
var menu = []; var menu = [];
if (node.library != null && ( if (node.library && (
node.library.toLowerCase() == "xul" || node.library.toLowerCase() == "lib_xul" ||
node.library.toLowerCase() == "xul.dll" node.library.toLowerCase() == "lib_xul.dll"
)) { )) {
menu.push("View Source"); menu.push("View Source");
} }
@ -403,7 +443,8 @@ TreeView.prototype = {
}, },
_HTMLForFunction: function TreeView__HTMLForFunction(node) { _HTMLForFunction: function TreeView__HTMLForFunction(node) {
var nodeName = escapeHTML(node.name); var nodeName = escapeHTML(node.name);
var libName = node.library; var resource = this._resources[node.library] || {};
var libName = escapeHTML(resource.name || "");
if (this._filterByName) { if (this._filterByName) {
if (!this._filterByNameReg) { if (!this._filterByNameReg) {
this._filterByName = RegExp.escape(this._filterByName); this._filterByName = RegExp.escape(this._filterByName);
@ -422,6 +463,7 @@ TreeView.prototype = {
'<span class="sampleCount">' + node.counter + '</span> ' + '<span class="sampleCount">' + node.counter + '</span> ' +
'<span class="samplePercentage">' + samplePercentage + '</span> ' + '<span class="samplePercentage">' + samplePercentage + '</span> ' +
'<span class="selfSampleCount">' + node.selfCounter + '</span> ' + '<span class="selfSampleCount">' + node.selfCounter + '</span> ' +
'<span class="resourceIcon" data-resource="' + node.library + '"></span> ' +
'<span class="functionName">' + nodeName + '</span>' + '<span class="functionName">' + nodeName + '</span>' +
'<span class="libraryName">' + libName + '</span>' + '<span class="libraryName">' + libName + '</span>' +
'<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">'; '<input type="button" value="Focus Callstack" title="Focus Callstack" class="focusCallstackButton" tabindex="-1">';
@ -434,15 +476,26 @@ TreeView.prototype = {
this._schedulePendingActionProcessing(); this._schedulePendingActionProcessing();
} }
}, },
_showChild: function TreeView__showChild(div, isVisible) {
for (var i = 0; i < div.treeChildren.length; i++) {
div.treeChildren[i].style.display = isVisible?"":"none";
if (!isVisible) {
div.treeChildren[i].classList.add("collapsed");
this._showChild(div.treeChildren[i], isVisible);
}
}
},
_toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) {
var currentCollapsedValue = this._isCollapsed(div); var currentCollapsedValue = this._isCollapsed(div);
if (newCollapsedValue === undefined) if (newCollapsedValue === undefined)
newCollapsedValue = !currentCollapsedValue; newCollapsedValue = !currentCollapsedValue;
if (newCollapsedValue) { if (newCollapsedValue) {
div.classList.add("collapsed"); div.classList.add("collapsed");
this._showChild(div, false);
} else { } else {
this._resolveChildren(div, true); this._resolveChildren(div, true);
div.classList.remove("collapsed"); div.classList.remove("collapsed");
this._showChild(div, true);
} }
if (!suppressScrollHeightNotification) if (!suppressScrollHeightNotification)
this._scrollHeightChanged(); this._scrollHeightChanged();
@ -500,6 +553,19 @@ TreeView.prototype = {
return this._getNextSib(div.treeParent); return this._getNextSib(div.treeParent);
return div.treeParent.treeChildren[nodeIndex+1]; return div.treeParent.treeChildren[nodeIndex+1];
}, },
_scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) {
// Schedule this on the animation frame otherwise we may run this more then once per frames
// causing more work then needed.
var self = this;
if (self._pendingAnimationFrame != null) {
return;
}
self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() {
cancelAnimationFrame(self._pendingAnimationFrame);
self._pendingAnimationFrame = null;
self._scrollIntoView(element, maxImportantWidth);
});
},
_scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) { _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) {
// Make sure that element is inside the visible part of our scrollbox by // Make sure that element is inside the visible part of our scrollbox by
// adjusting the scroll positions. If element is wider or // adjusting the scroll positions. If element is wider or
@ -541,9 +607,10 @@ TreeView.prototype = {
li.treeLine.classList.add("selected"); li.treeLine.classList.add("selected");
this._selectedNode = li; this._selectedNode = li;
var functionName = li.treeLine.querySelector(".functionName"); var functionName = li.treeLine.querySelector(".functionName");
this._scrollIntoView(functionName, 400); this._scheduleScrollIntoView(functionName, 400);
this._fireEvent("select", li.data); this._fireEvent("select", li.data);
} }
updateDocumentURL();
}, },
_isCollapsed: function TreeView__isCollapsed(div) { _isCollapsed: function TreeView__isCollapsed(div) {
return div.classList.contains("collapsed"); return div.classList.contains("collapsed");
@ -615,6 +682,7 @@ TreeView.prototype = {
var isCollapsed = this._isCollapsed(selected); var isCollapsed = this._isCollapsed(selected);
if (isCollapsed) { if (isCollapsed) {
this._toggle(selected); this._toggle(selected);
this._syncProcessPendingActionProcessing();
} else { } else {
// Do KEY_DOWN // Do KEY_DOWN
var nextSib = this._getNextSib(selected); var nextSib = this._getNextSib(selected);

File diff suppressed because it is too large Load Diff