mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-10-05 04:55:33 +00:00
397 lines
12 KiB
JavaScript
397 lines
12 KiB
JavaScript
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|||
|
* 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/. */
|
|||
|
/* import-globals-from ../performance-controller.js */
|
|||
|
/* import-globals-from ../performance-view.js */
|
|||
|
/* globals document */
|
|||
|
"use strict";
|
|||
|
|
|||
|
const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
|
|||
|
const OPTIMIZATION_FAILURE = L10N.getStr("jit.optimizationFailure");
|
|||
|
const JIT_SAMPLES = L10N.getStr("jit.samples");
|
|||
|
const JIT_EMPTY_TEXT = L10N.getStr("jit.empty");
|
|||
|
const PROPNAME_MAX_LENGTH = 4;
|
|||
|
|
|||
|
/**
|
|||
|
* View for rendering a list of all optmizations found in a frame.
|
|||
|
* The terminology and types used here can be referenced:
|
|||
|
* @see devtools/client/performance/modules/logic/jit.js
|
|||
|
*/
|
|||
|
|
|||
|
var OptimizationsListView = {
|
|||
|
|
|||
|
_currentFrame: null,
|
|||
|
|
|||
|
/**
|
|||
|
* Initialization function called when the tool starts up.
|
|||
|
*/
|
|||
|
initialize: function () {
|
|||
|
this.reset = this.reset.bind(this);
|
|||
|
this._onThemeChanged = this._onThemeChanged.bind(this);
|
|||
|
|
|||
|
this.el = $("#jit-optimizations-view");
|
|||
|
this.$headerName = $("#jit-optimizations-header .header-function-name");
|
|||
|
this.$headerFile = $("#jit-optimizations-header .header-file");
|
|||
|
this.$headerLine = $("#jit-optimizations-header .header-line");
|
|||
|
|
|||
|
this.tree = new TreeWidget($("#jit-optimizations-raw-view"), {
|
|||
|
sorted: false,
|
|||
|
emptyText: JIT_EMPTY_TEXT
|
|||
|
});
|
|||
|
this.graph = new OptimizationsGraph($("#optimizations-graph"));
|
|||
|
this.graph.setTheme(PerformanceController.getTheme());
|
|||
|
|
|||
|
// Start the tree by resetting.
|
|||
|
this.reset();
|
|||
|
|
|||
|
PerformanceController.on(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Destruction function called when the tool cleans up.
|
|||
|
*/
|
|||
|
destroy: function () {
|
|||
|
PerformanceController.off(EVENTS.THEME_CHANGED, this._onThemeChanged);
|
|||
|
this.tree = null;
|
|||
|
this.$headerName = this.$headerFile = this.$headerLine = this.el = null;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Takes a FrameNode, with corresponding optimization data to be displayed
|
|||
|
* in the view.
|
|||
|
*
|
|||
|
* @param {FrameNode} frameNode
|
|||
|
*/
|
|||
|
setCurrentFrame: function (threadNode, frameNode) {
|
|||
|
if (threadNode !== this.getCurrentThread()) {
|
|||
|
this._currentThread = threadNode;
|
|||
|
}
|
|||
|
if (frameNode !== this.getCurrentFrame()) {
|
|||
|
this._currentFrame = frameNode;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the current frame node for this view.
|
|||
|
*
|
|||
|
* @return {?FrameNode}
|
|||
|
*/
|
|||
|
getCurrentFrame: function () {
|
|||
|
return this._currentFrame;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Returns the current thread node for this view.
|
|||
|
*
|
|||
|
* @return {?ThreadNode}
|
|||
|
*/
|
|||
|
getCurrentThread: function () {
|
|||
|
return this._currentThread;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Clears out data in the tree, sets to an empty state,
|
|||
|
* and removes current frame.
|
|||
|
*/
|
|||
|
reset: function () {
|
|||
|
this.setCurrentFrame(null, null);
|
|||
|
this.clear();
|
|||
|
this.el.classList.add("empty");
|
|||
|
this.emit(EVENTS.OPTIMIZATIONS_RESET);
|
|||
|
this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Clears out data in the tree.
|
|||
|
*/
|
|||
|
clear: function () {
|
|||
|
this.tree.clear();
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Takes a JITOptimizations object and builds a view containing all attempted
|
|||
|
* optimizations for this frame. This view is very verbose and meant for those
|
|||
|
* who understand JIT compilers.
|
|||
|
*/
|
|||
|
render: function () {
|
|||
|
let frameNode = this.getCurrentFrame();
|
|||
|
|
|||
|
if (!frameNode) {
|
|||
|
this.reset();
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
let view = this.tree;
|
|||
|
|
|||
|
// Set header information, even if the frame node
|
|||
|
// does not have any optimization data
|
|||
|
let frameData = frameNode.getInfo();
|
|||
|
this._setHeaders(frameData);
|
|||
|
this.clear();
|
|||
|
|
|||
|
// If this frame node does not have optimizations, or if its a meta node in the
|
|||
|
// case of only showing content, reset the view.
|
|||
|
if (!frameNode.hasOptimizations() || frameNode.isMetaCategory) {
|
|||
|
this.reset();
|
|||
|
return;
|
|||
|
}
|
|||
|
this.el.classList.remove("empty");
|
|||
|
|
|||
|
// An array of sorted OptimizationSites.
|
|||
|
let sites = frameNode.getOptimizations().optimizationSites;
|
|||
|
|
|||
|
for (let site of sites) {
|
|||
|
this._renderSite(view, site, frameData);
|
|||
|
}
|
|||
|
|
|||
|
this._renderTierGraph();
|
|||
|
|
|||
|
this.emit(EVENTS.OPTIMIZATIONS_RENDERED, this.getCurrentFrame());
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Renders the optimization tier graph over time.
|
|||
|
*/
|
|||
|
_renderTierGraph: function () {
|
|||
|
this.graph.render(this.getCurrentThread(), this.getCurrentFrame());
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Creates an entry in the tree widget for an optimization site.
|
|||
|
*/
|
|||
|
_renderSite: function (view, site, frameData) {
|
|||
|
let { id, samples, data } = site;
|
|||
|
let { types, attempts } = data;
|
|||
|
let siteNode = this._createSiteNode(frameData, site);
|
|||
|
|
|||
|
// Cast `id` to a string so TreeWidget doesn't think it does not exist
|
|||
|
id = id + "";
|
|||
|
|
|||
|
view.add([{ id: id, node: siteNode }]);
|
|||
|
|
|||
|
// Add types -- Ion types are the parent, with
|
|||
|
// the observed types as children.
|
|||
|
view.add([id, { id: `${id}-types`, label: `Types (${types.length})` }]);
|
|||
|
this._renderIonType(view, site);
|
|||
|
|
|||
|
// Add attempts
|
|||
|
view.add([id, { id: `${id}-attempts`, label: `Attempts (${attempts.length})` }]);
|
|||
|
for (let i = attempts.length - 1; i >= 0; i--) {
|
|||
|
let node = this._createAttemptNode(attempts[i]);
|
|||
|
view.add([id, `${id}-attempts`, { node }]);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Renders all Ion types from an optimization site, with its children
|
|||
|
* ObservedTypes.
|
|||
|
*/
|
|||
|
_renderIonType: function (view, site) {
|
|||
|
let { id, data: { types }} = site;
|
|||
|
// Cast `id` to a string so TreeWidget doesn't think it does not exist
|
|||
|
id = id + "";
|
|||
|
for (let i = 0; i < types.length; i++) {
|
|||
|
let ionType = types[i];
|
|||
|
|
|||
|
let ionNode = this._createIonNode(ionType);
|
|||
|
view.add([id, `${id}-types`, { id: `${id}-types-${i}`, node: ionNode }]);
|
|||
|
for (let observedType of (ionType.typeset || [])) {
|
|||
|
let node = this._createObservedTypeNode(observedType);
|
|||
|
view.add([id, `${id}-types`, `${id}-types-${i}`, { node }]);
|
|||
|
}
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Creates an element for insertion in the raw view for an OptimizationSite.
|
|||
|
*/
|
|||
|
_createSiteNode: function (frameData, site) {
|
|||
|
let node = document.createElement("span");
|
|||
|
let desc = document.createElement("span");
|
|||
|
let line = document.createElement("span");
|
|||
|
let column = document.createElement("span");
|
|||
|
let urlNode = this._createDebuggerLinkNode(frameData.url, site.data.line);
|
|||
|
|
|||
|
let attempts = site.getAttempts();
|
|||
|
let lastStrategy = attempts[attempts.length - 1].strategy;
|
|||
|
|
|||
|
let propString = "";
|
|||
|
if (site.data.propertyName) {
|
|||
|
if (site.data.propertyName.length > PROPNAME_MAX_LENGTH) {
|
|||
|
propString = ` (.${site.data.propertyName.substr(0, PROPNAME_MAX_LENGTH)}…)`;
|
|||
|
desc.setAttribute("tooltiptext", site.data.propertyName);
|
|||
|
} else {
|
|||
|
propString = ` (.${site.data.propertyName})`;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!site.hasSuccessfulOutcome()) {
|
|||
|
let icon = document.createElement("span");
|
|||
|
icon.setAttribute("tooltiptext", OPTIMIZATION_FAILURE);
|
|||
|
icon.setAttribute("severity", "warning");
|
|||
|
icon.className = "opt-icon";
|
|||
|
node.appendChild(icon);
|
|||
|
}
|
|||
|
|
|||
|
let sampleString = PluralForm.get(site.samples, JIT_SAMPLES).replace("#1", site.samples);
|
|||
|
desc.textContent = `${lastStrategy}${propString} – (${sampleString})`;
|
|||
|
line.textContent = site.data.line;
|
|||
|
line.className = "opt-line";
|
|||
|
column.textContent = site.data.column;
|
|||
|
column.className = "opt-line";
|
|||
|
node.appendChild(desc);
|
|||
|
node.appendChild(urlNode);
|
|||
|
node.appendChild(line);
|
|||
|
node.appendChild(column);
|
|||
|
|
|||
|
return node;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Creates an element for insertion in the raw view for an IonType.
|
|||
|
*
|
|||
|
* @see devtools/client/performance/modules/logic/jit.js
|
|||
|
* @param {IonType} ionType
|
|||
|
* @return {Element}
|
|||
|
*/
|
|||
|
_createIonNode: function (ionType) {
|
|||
|
let node = document.createElement("span");
|
|||
|
node.textContent = `${ionType.site} : ${ionType.mirType}`;
|
|||
|
node.className = "opt-ion-type";
|
|||
|
return node;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Creates an element for insertion in the raw view for an ObservedType.
|
|||
|
*
|
|||
|
* @see devtools/client/performance/modules/logic/jit.js
|
|||
|
* @param {ObservedType} type
|
|||
|
* @return {Element}
|
|||
|
*/
|
|||
|
_createObservedTypeNode: function (type) {
|
|||
|
let node = document.createElement("span");
|
|||
|
let typeNode = document.createElement("span");
|
|||
|
|
|||
|
typeNode.textContent = `${type.keyedBy}` + (type.name ? ` → ${type.name}` : "");
|
|||
|
typeNode.className = "opt-type";
|
|||
|
node.appendChild(typeNode);
|
|||
|
|
|||
|
// If we have a type and a location, try to make a
|
|||
|
// link to the debugger
|
|||
|
if (type.location && type.line) {
|
|||
|
let urlNode = this._createDebuggerLinkNode(type.location, type.line);
|
|||
|
node.appendChild(urlNode);
|
|||
|
}
|
|||
|
// Otherwise if we just have a location, it could just
|
|||
|
// be a memory location
|
|||
|
else if (type.location) {
|
|||
|
let locNode = document.createElement("span");
|
|||
|
locNode.textContent = `@${type.location}`;
|
|||
|
locNode.className = "opt-url";
|
|||
|
node.appendChild(locNode);
|
|||
|
}
|
|||
|
|
|||
|
if (type.line) {
|
|||
|
let line = document.createElement("span");
|
|||
|
line.textContent = type.line;
|
|||
|
line.className = "opt-line";
|
|||
|
node.appendChild(line);
|
|||
|
}
|
|||
|
return node;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Creates an element for insertion in the raw view for an OptimizationAttempt.
|
|||
|
*
|
|||
|
* @see devtools/client/performance/modules/logic/jit.js
|
|||
|
* @param {OptimizationAttempt} attempt
|
|||
|
* @return {Element}
|
|||
|
*/
|
|||
|
_createAttemptNode: function (attempt) {
|
|||
|
let node = document.createElement("span");
|
|||
|
let strategyNode = document.createElement("span");
|
|||
|
let outcomeNode = document.createElement("span");
|
|||
|
|
|||
|
strategyNode.textContent = attempt.strategy;
|
|||
|
strategyNode.className = "opt-strategy";
|
|||
|
outcomeNode.textContent = attempt.outcome;
|
|||
|
outcomeNode.className = "opt-outcome";
|
|||
|
outcomeNode.setAttribute("outcome",
|
|||
|
JITOptimizations.isSuccessfulOutcome(attempt.outcome) ? "success" : "failure");
|
|||
|
|
|||
|
node.appendChild(strategyNode);
|
|||
|
node.appendChild(outcomeNode);
|
|||
|
node.className = "opt-attempt";
|
|||
|
return node;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Creates a new element, linking it up to the debugger upon clicking.
|
|||
|
* Can also optionally pass in an element to modify it rather than
|
|||
|
* creating a new one.
|
|||
|
*
|
|||
|
* @param {String} url
|
|||
|
* @param {Number} line
|
|||
|
* @param {?Element} el
|
|||
|
* @return {Element}
|
|||
|
*/
|
|||
|
_createDebuggerLinkNode: function (url, line, el) {
|
|||
|
let node = el || document.createElement("span");
|
|||
|
node.className = "opt-url";
|
|||
|
let fileName;
|
|||
|
|
|||
|
if (this._isLinkableURL(url)) {
|
|||
|
fileName = url.slice(url.lastIndexOf("/") + 1);
|
|||
|
node.classList.add("debugger-link");
|
|||
|
node.setAttribute("tooltiptext", URL_LABEL_TOOLTIP + " → " + url);
|
|||
|
node.addEventListener("click", () => gToolbox.viewSourceInDebugger(url, line));
|
|||
|
}
|
|||
|
fileName = fileName || url || "";
|
|||
|
node.textContent = fileName ? `@${fileName}` : "";
|
|||
|
return node;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Updates the headers with the current frame's data.
|
|||
|
*/
|
|||
|
_setHeaders: function (frameData) {
|
|||
|
let isMeta = frameData.isMetaCategory;
|
|||
|
let name = isMeta ? frameData.categoryData.label : frameData.functionName;
|
|||
|
let url = isMeta ? "" : frameData.url;
|
|||
|
let line = isMeta ? "" : frameData.line;
|
|||
|
|
|||
|
this.$headerName.textContent = name;
|
|||
|
this.$headerLine.textContent = line;
|
|||
|
this._createDebuggerLinkNode(url, line, this.$headerFile);
|
|||
|
|
|||
|
this.$headerLine.hidden = isMeta;
|
|||
|
this.$headerFile.hidden = isMeta;
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Takes a string and returns a boolean indicating whether or not
|
|||
|
* this is a valid url for linking to the debugger.
|
|||
|
*
|
|||
|
* @param {String} url
|
|||
|
* @return {Boolean}
|
|||
|
*/
|
|||
|
_isLinkableURL: function (url) {
|
|||
|
return url && url.indexOf &&
|
|||
|
(url.indexOf("http") === 0 ||
|
|||
|
url.indexOf("resource://") === 0 ||
|
|||
|
url.indexOf("file://") === 0);
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Called when `devtools.theme` changes.
|
|||
|
*/
|
|||
|
_onThemeChanged: function (_, theme) {
|
|||
|
this.graph.setTheme(theme);
|
|||
|
this.graph.refresh({ force: true });
|
|||
|
},
|
|||
|
|
|||
|
toString: () => "[object OptimizationsListView]"
|
|||
|
};
|
|||
|
|
|||
|
EventEmitter.decorate(OptimizationsListView);
|