Bug 1226319 - Part 3 - Land JIT Viewer implemented in react. r=fitzgen

This commit is contained in:
Jordan Santell 2016-01-24 17:57:15 -08:00
Родитель f00f8e8a1f
Коммит 5b4a50f7d7
25 изменённых файлов: 910 добавлений и 681 удалений

Просмотреть файл

@ -100,7 +100,6 @@ devtools.jar:
content/performance/views/details-js-flamegraph.js (performance/views/details-js-flamegraph.js)
content/performance/views/details-memory-call-tree.js (performance/views/details-memory-call-tree.js)
content/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
content/performance/views/optimizations-list.js (performance/views/optimizations-list.js)
content/performance/views/recordings.js (performance/views/recordings.js)
content/memory/memory.xhtml (memory/memory.xhtml)
content/memory/initializer.js (memory/initializer.js)
@ -219,6 +218,8 @@ devtools.jar:
skin/splitview.css (themes/splitview.css)
skin/styleeditor.css (themes/styleeditor.css)
skin/webaudioeditor.css (themes/webaudioeditor.css)
skin/components-frame.css (themes/components-frame.css)
skin/jit-optimizations.css (themes/jit-optimizations.css)
skin/images/magnifying-glass.png (themes/images/magnifying-glass.png)
skin/images/magnifying-glass@2x.png (themes/images/magnifying-glass@2x.png)
skin/images/magnifying-glass-light.png (themes/images/magnifying-glass-light.png)

Просмотреть файл

@ -0,0 +1,31 @@
# 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/.
# LOCALIZATION NOTE These strings are used within the JIT tools
# in the Performance Tools which is available from the Web Developer
# sub-menu -> 'Performance' The correct localization of this file might
# be to keep it in English, or another language commonly spoken among
# web developers. You want to make that choice consistent across the
# developer tools. A good criteria is the language in which you'd find the best
# documentation on web development on the web.
# LOCALIZATION NOTE (jit.title):
# This string is displayed in the header of the JIT Optimizations view.
jit.title=JIT Optimizations
# LOCALIZATION NOTE (jit.optimizationFailure):
# This string is displayed in a tooltip when no JIT optimizations were detected.
jit.optimizationFailure=Optimization failed
# LOCALIZATION NOTE (jit.samples):
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This string is displayed for the unit representing the number of times a
# frame is sampled.
# "#1" represents the number of samples
# example: 30 samples
jit.samples=#1 sample;#1 samples
# LOCALIZATION NOTE (jit.empty):
# This string is displayed when there are no JIT optimizations to display.
jit.empty=No JIT optimizations recorded for this frame.

Просмотреть файл

@ -140,10 +140,6 @@
<!ENTITY performanceUI.enableJITOptimizations "Record JIT Optimizations">
<!ENTITY performanceUI.enableJITOptimizations.tooltiptext "Record JIT optimization data sampled in each JavaScript frame.">
<!-- LOCALIZATION NOTE (performanceUI.JITOptimizationsTitle): This string
- is displayed as the title of the JIT Optimizations panel. -->
<!ENTITY performanceUI.JITOptimizationsTitle "JIT Optimizations">
<!-- LOCALIZATION NOTE (performanceUI.console.recordingNoticeStart/recordingNoticeEnd):
- This string is displayed when a recording is selected that started via console.profile.
- Wraps the command used to start, like "Currently recording via console.profile("label")" -->

Просмотреть файл

@ -141,22 +141,6 @@ recordingsList.saveDialogJSONFilter=JSON Files
# This string is displayed as a filter for saving a recording to disk.
recordingsList.saveDialogAllFilter=All Files
# LOCALIZATION NOTE (jit.optimizationFailure):
# This string is displayed in a tooltip when no JIT optimizations were detected.
jit.optimizationFailure=Optimization failed
# LOCALIZATION NOTE (jit.samples):
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# This string is displayed for the unit representing the number of times a
# frame is sampled.
# "#1" represents the number of samples
# example: 30 samples
jit.samples=#1 sample;#1 samples
# LOCALIZATION NOTE (jit.empty):
# This string is displayed when there are no JIT optimizations to display.
jit.empty=No JIT optimizations recorded for this frame.
# LOCALIZATION NOTE (timeline.tick):
# This string is displayed in the timeline overview, for delimiting ticks
# by time, in milliseconds.

Просмотреть файл

@ -14,6 +14,7 @@
<link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/widgets.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/memory.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/components-frame.css" type="text/css"/>
<script type="application/javascript;version=1.8"
src="chrome://devtools/content/shared/theme-switching.js"/>

Просмотреть файл

@ -0,0 +1,9 @@
# vim: set filetype=python:
# 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/.
DevToolsModules(
'optimizations-item.js',
'optimizations.js',
)

Просмотреть файл

@ -0,0 +1,145 @@
/* 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/. */
const { Cu } = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const STRINGS_URI = "chrome://devtools/locale/jit-optimizations.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const { DOM: dom, PropTypes, createClass, createFactory } = require("devtools/client/shared/vendor/react");
const Frame = createFactory(require("devtools/client/shared/components/frame"));
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;
// If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
// in `devtools/client/themes/jit-optimizations.css`
const TREE_ROW_HEIGHT = 14;
const OPTIMIZATION_ITEM_TYPES = ["site", "attempts", "types", "attempt", "type", "observedtype"];
const OptimizationsItem = module.exports = createClass({
displayName: "OptimizationsItem",
propTypes: {
onViewSourceInDebugger: PropTypes.func.isRequired,
frameData: PropTypes.object.isRequired,
type: PropTypes.oneOf(OPTIMIZATION_ITEM_TYPES).isRequired,
},
render() {
let {
item,
depth,
arrow,
focused,
type,
frameData,
onViewSourceInDebugger,
} = this.props;
let content;
switch (type) {
case "site": content = this._renderSite(this.props); break;
case "attempts": content = this._renderAttempts(this.props); break;
case "types": content = this._renderTypes(this.props); break;
case "attempt": content = this._renderAttempt(this.props); break;
case "type": content = this._renderType(this.props); break;
case "observedtype": content = this._renderObservedType(this.props); break;
};
return dom.div(
{
className: `optimization-tree-item optimization-tree-item-${type}`,
style: { marginLeft: depth * TREE_ROW_HEIGHT }
},
arrow,
content
);
},
_renderSite({ item: site, onViewSourceInDebugger, frameData }) {
let attempts = site.data.attempts;
let lastStrategy = attempts[attempts.length - 1].strategy;
let propString = "";
let propertyName = site.data.propertyName;
// Display property name if it exists
if (propertyName) {
if (propertyName.length > PROPNAME_MAX_LENGTH) {
propString = ` (.${propertyName.substr(0, PROPNAME_MAX_LENGTH)}…)`;
} else {
propString = ` (.${propertyName})`;
}
}
let sampleString = PluralForm.get(site.samples, JIT_SAMPLES).replace("#1", site.samples);
let text = `${lastStrategy}${propString} – (${sampleString})`;
let frame = Frame({
onClick: () => onViewSourceInDebugger(frameData.url, site.data.line),
frame: {
source: frameData.url,
line: site.data.line,
column: site.data.column,
}
})
let children = [text, frame];
if (!site.hasSuccessfulOutcome()) {
children.unshift(dom.span({ className: "opt-icon warning" }));
}
return dom.span({ className: "optimization-site" }, ...children);
},
_renderAttempts({ item: attempts }) {
return dom.span({ className: "optimization-attempts" },
`Attempts (${attempts.length})`
);
},
_renderTypes({ item: types }) {
return dom.span({ className: "optimization-types" },
`Types (${types.length})`
);
},
_renderAttempt({ item: attempt }) {
let success = JITOptimizations.isSuccessfulOutcome(attempt.outcome);
let { strategy, outcome } = attempt;
return dom.span({ className: "optimization-attempt" },
dom.span({ className: "optimization-strategy" }, strategy),
" → ",
dom.span({ className: `optimization-outcome ${success ? "success" : "failure"}` }, outcome)
);
},
_renderType({ item: type }) {
return dom.span({ className: "optimization-ion-type" }, `${type.site}:${type.mirType}`);
},
_renderObservedType({ onViewSourceInDebugger, item: type }) {
let children = [
`${type.keyedBy}${type.name ? `${type.name}` : ""}`
];
// If we have a line and location, make a link to the debugger
if (type.location && type.line) {
children.push(
Frame({
onClick: () => onViewSourceInDebugger(type.location, type.line),
frame: {
source: type.location,
line: type.line,
column: type.column,
}
})
);
}
// Otherwise if we just have a location, it's probably just a memory location.
else if (type.location) {
children.push(`@${type.location}`);
}
return dom.span({ className: "optimization-observed-type" }, ...children);
},
});

Просмотреть файл

@ -0,0 +1,183 @@
/* 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/. */
const { Cu } = require("chrome");
Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
const STRINGS_URI = "chrome://devtools/locale/jit-optimizations.properties";
const L10N = new ViewHelpers.L10N(STRINGS_URI);
const { assert } = require("devtools/shared/DevToolsUtils");
const { DOM: dom, createClass, createFactory, PropTypes } = require("devtools/client/shared/vendor/react");
const Tree = createFactory(require("../../shared/components/tree"));
const OptimizationsItem = createFactory(require("./optimizations-item"));
const FrameView = createFactory(require("../../shared/components/frame"));
const onClickTooltipString = frame =>
L10N.getFormatStr("viewsourceindebugger",`${frame.source}:${frame.line}:${frame.column}`);
const JIT_TITLE = L10N.getStr("jit.title");
// If TREE_ROW_HEIGHT changes, be sure to change `var(--jit-tree-row-height)`
// in `devtools/client/themes/jit-optimizations.css`
const TREE_ROW_HEIGHT = 14;
const Optimizations = module.exports = createClass({
displayName: "Optimizations",
propTypes: {
onViewSourceInDebugger: PropTypes.func.isRequired,
frameData: PropTypes.object.isRequired,
optimizationSites: PropTypes.array.isRequired,
},
getInitialState() {
return {
expanded: new Set()
};
},
getDefaultProps() {
return {};
},
render() {
let header = this._createHeader(this.props);
let tree = this._createTree(this.props);
return dom.div({}, header, tree);
},
/**
* Frame data generated from `frameNode.getInfo()`, or an empty
* object, as well as a handler for clicking on the frame component.
*
* @param {?Object} .frameData
* @param {Function} .onViewSourceInDebugger
* @return {ReactElement}
*/
_createHeader: function ({ frameData, onViewSourceInDebugger }) {
let { isMetaCategory, url, line } = frameData;
let name = isMetaCategory ? frameData.categoryData.label :
frameData.functionName || "";
// Simulate `SavedFrame`s interface
let frame = { source: url, line: +line, functionDisplayName: name };
// Neither Meta Category nodes, or the lack of a selected frame node,
// renders out a frame source, like "file.js:123"; so just use
// an empty span.
let frameComponent;
if (isMetaCategory || !name) {
frameComponent = dom.span();
} else {
frameComponent = FrameView({
frame,
onClick: () => onViewSourceInDebugger(frame),
});
}
return dom.div({ className: "optimization-header" },
dom.span({ className: "header-title" }, JIT_TITLE),
dom.span({ className: "header-function-name" }, name),
frameComponent
);
},
_createTree(props) {
let { frameData, onViewSourceInDebugger, optimizationSites: sites } = this.props;
let getSite = id => sites.find(site => site.id === id);
let getIonTypeForObserved = type =>
getSite(type.id).data.types.find(iontype => (iontype.typeset || []).indexOf(type) !== -1);
let isSite = site => getSite(site.id) === site;
let isAttempts = attempts => getSite(attempts.id).data.attempts === attempts;
let isAttempt = attempt => getSite(attempt.id).data.attempts.indexOf(attempt) !== -1;
let isTypes = types => getSite(types.id).data.types === types;
let isType = type => getSite(type.id).data.types.indexOf(type) !== -1;
let isObservedType = type => getIonTypeForObserved(type);
let getRowType = node => {
return isSite(node) ? "site" :
isAttempts(node) ? "attempts" :
isTypes(node) ? "types" :
isAttempt(node) ? "attempt" :
isType(node) ? "type":
isObservedType(node) ? "observedtype": null;
};
// Creates a unique key for each node in the
// optimizations data
let getKey = node => {
let site = getSite(node.id);
if (isSite(node)) {
return node.id;
} else if (isAttempts(node)) {
return `${node.id}-A`;
} else if (isTypes(node)) {
return `${node.id}-T`;
} else if (isType(node)) {
return `${node.id}-T-${site.data.types.indexOf(node)}`;
} else if (isAttempt(node)) {
return `${node.id}-A-${site.data.attempts.indexOf(node)}`;
} else if (isObservedType(node)) {
let iontype = getIonTypeForObserved(node);
return `${getKey(iontype)}-O-${iontype.typeset.indexOf(node)}`;
}
};
return Tree({
autoExpandDepth: 0,
getParent: node => {
let site = getSite(node.id);
let parent;
if (isAttempts(node) || isTypes(node)) {
parent = site;
} else if (isType(node)) {
parent = site.data.types;
} else if (isAttempt(node)) {
parent = site.data.attempts;
} else if (isObservedType(node)) {
parent = getIonTypeForObserved(node);
}
assert(parent, "Could not find a parent for optimization data node");
return parent;
},
getChildren: node => {
if (isSite(node)) {
return [node.data.types, node.data.attempts];
} else if (isAttempts(node) || isTypes(node)) {
return node;
} else if (isType(node)) {
return node.typeset || [];
} else {
return [];
}
},
isExpanded: node => this.state.expanded.has(node),
onExpand: node => this.setState(state => {
let expanded = new Set(state.expanded);
expanded.add(node);
return { expanded };
}),
onCollapse: node => this.setState(state => {
let expanded = new Set(state.expanded);
expanded.delete(node);
return { expanded };
}),
onFocus: function () {},
getKey,
getRoots: () => sites || [],
itemHeight: TREE_ROW_HEIGHT,
renderItem: (item, depth, focused, arrow, expanded) =>
new OptimizationsItem({
onViewSourceInDebugger,
item,
depth,
focused,
arrow,
expanded,
type: getRowType(item),
frameData,
}),
});
}
});

Просмотреть файл

@ -52,7 +52,7 @@ const SUCCESSFUL_OUTCOMES = [
* a "getter" optimization, `a[b]`, has site `a` (the "Receiver") and `b` (the "Index").
*
* Generally the more ObservedTypes, the more deoptimized this OptimizationSite is.
* There could be no ObservedTypes, in which case `types` is undefined.
* There could be no ObservedTypes, in which case `typeset` is undefined.
*
* @type {?Array<ObservedType>} typeset
* @type {string} site
@ -184,23 +184,33 @@ const JITOptimizations = function (rawSites, stringTable) {
let data = site.data;
let STRATEGY_SLOT = data.attempts.schema.strategy;
let OUTCOME_SLOT = data.attempts.schema.outcome;
let attempts = data.attempts.data.map((a) => {
return {
id: site.id,
strategy: stringTable[a[STRATEGY_SLOT]],
outcome: stringTable[a[OUTCOME_SLOT]]
}
});
let types = data.types.map((t) => {
let typeset = maybeTypeset(t.typeset, stringTable);
if (typeset) {
typeset.forEach(t => t.id = site.id);
}
return {
id: site.id,
typeset,
site: stringTable[t.site],
mirType: stringTable[t.mirType]
};
});
// Add IDs to to all children objects, so we can correllate sites when
// just looking at a specific type, attempt, etc..
attempts.id = types.id = site.id;
site.data = {
attempts: data.attempts.data.map((a) => {
return {
strategy: stringTable[a[STRATEGY_SLOT]],
outcome: stringTable[a[OUTCOME_SLOT]]
}
}),
types: data.types.map((t) => {
return {
typeset: maybeTypeset(t.typeset, stringTable),
site: stringTable[t.site],
mirType: stringTable[t.mirType]
};
}),
attempts,
types,
propertyName: maybeString(stringTable, data.propertyName),
line: data.line,
column: data.column

Просмотреть файл

@ -4,6 +4,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DIRS += [
'components',
'legacy',
'modules',
]

Просмотреть файл

@ -18,6 +18,9 @@ Object.defineProperty(this, "EVENTS", {
writable: false
});
var React = require("devtools/client/shared/vendor/react");
var ReactDOM = require("devtools/client/shared/vendor/react-dom");
var Optimizations = React.createFactory(require("devtools/client/performance/components/optimizations"));
var Services = require("Services");
var promise = require("promise");
var EventEmitter = require("devtools/shared/event-emitter");

Просмотреть файл

@ -7,6 +7,8 @@
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/performance.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/jit-optimizations.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/components-frame.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % performanceDTD SYSTEM "chrome://devtools/locale/performance.dtd">
%performanceDTD;
@ -26,7 +28,6 @@
<script type="application/javascript" src="views/details-memory-flamegraph.js"/>
<script type="application/javascript" src="views/details.js"/>
<script type="application/javascript" src="views/recordings.js"/>
<script type="application/javascript" src="views/optimizations-list.js"/>
<popupset id="performance-options-popupset">
<menupopup id="performance-filter-menupopup"/>
@ -318,17 +319,7 @@
<splitter class="devtools-side-splitter"/>
<!-- Optimizations Panel -->
<vbox id="jit-optimizations-view"
class="hidden">
<toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
<hbox id="jit-optimizations-header">
<span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
<span class="header-function-name" />
<span class="header-file opt-url debugger-link" />
<span class="header-line opt-line" />
</hbox>
</toolbar>
<hbox id="optimizations-graph"></hbox>
<vbox id="jit-optimizations-raw-view"></vbox>
class="hidden">
</vbox>
</hbox>

Просмотреть файл

@ -43,7 +43,9 @@ skip-if = true # Bug 1161817
[browser_perf-events-calltree.js]
[browser_perf-highlighted.js]
[browser_perf-jit-view-01.js]
skip-if = true # Bug 1176056
[browser_perf-jit-view-02.js]
skip-if = true # Bug 1176056
[browser_perf-legacy-front-01.js]
[browser_perf-legacy-front-02.js]
[browser_perf-legacy-front-03.js]

Просмотреть файл

@ -11,7 +11,7 @@ var { CATEGORY_MASK } = require("devtools/client/performance/modules/global");
function* spawnTest() {
let { panel } = yield initPerformance(SIMPLE_URL);
let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
let { OverviewView, DetailsView, JsCallTreeView, RecordingsView } = panel.panelWin;
let profilerData = { threads: [gThread] };

Просмотреть файл

@ -29,14 +29,15 @@ var JsCallTreeView = Heritage.extend(DetailsSubview, {
this.container = $("#js-calltree-view .call-tree-cells-container");
OptimizationsListView.initialize();
this.optimizationsElement = $("#jit-optimizations-view");
},
/**
* Unbinds events.
*/
destroy: function () {
OptimizationsListView.destroy();
ReactDOM.unmountComponentAtNode(this.optimizationsElement);
this.optimizationsElement = null;
this.container = null;
this.threadNode = null;
DetailsSubview.destroy.call(this);
@ -67,25 +68,48 @@ var JsCallTreeView = Heritage.extend(DetailsSubview, {
} else {
this.hideOptimizations();
}
OptimizationsListView.reset();
this.emit(EVENTS.JS_CALL_TREE_RENDERED);
},
showOptimizations: function () {
$("#jit-optimizations-view").classList.remove("hidden");
this.optimizationsElement.classList.remove("hidden");
},
hideOptimizations: function () {
$("#jit-optimizations-view").classList.add("hidden");
this.optimizationsElement.classList.add("hidden");
},
_onFocus: function (_, treeItem) {
if (PerformanceController.getCurrentRecording().getConfiguration().withJITOptimizations) {
OptimizationsListView.setCurrentFrame(this.threadNode, treeItem.frame);
OptimizationsListView.render();
let recording = PerformanceController.getCurrentRecording();
let frameNode = treeItem.frame;
if (!frameNode) {
console.warn("No frame found!");
return;
}
let frameData = frameNode.getInfo();
let optimizationSites = frameNode.hasOptimizations()
? frameNode.getOptimizations().optimizationSites
: [];
let optimizations = Optimizations({
frameData,
optimizationSites,
onViewSourceInDebugger: (url, line) => {
gToolbox.viewSourceInDebugger(url, line).then(success => {
if (success) {
this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
} else {
this.emit(EVENTS.SOURCE_NOT_FOUND_IN_JS_DEBUGGER);
}
});
}
});
ReactDOM.render(optimizations, this.optimizationsElement);
this.emit("focus", treeItem);
},

Просмотреть файл

@ -1,396 +0,0 @@
/* 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);

Просмотреть файл

@ -26,7 +26,7 @@ const Frame = module.exports = createClass({
functionDisplayName: PropTypes.string,
source: PropTypes.string.isRequired,
line: PropTypes.number.isRequired,
column: PropTypes.number.isRequired,
column: PropTypes.number,
}).isRequired,
// Clicking on the frame link -- probably should link to the debugger.
onClick: PropTypes.func.isRequired,
@ -46,7 +46,7 @@ const Frame = module.exports = createClass({
tooltip += `:${frame.column}`;
}
let sourceString = `${frame.source}:${frame.line}`;
let sourceString = `${long}:${frame.line}`;
if (frame.column) {
sourceString += `:${frame.column}`;
}

Просмотреть файл

@ -2,6 +2,8 @@
support-files =
head.js
[test_frame_01.html]
[test_frame_02.html]
[test_tree_01.html]
[test_tree_02.html]
[test_tree_03.html]

Просмотреть файл

@ -23,6 +23,59 @@ var { require: browserRequire } = BrowserLoader("resource://devtools/client/shar
var EXAMPLE_URL = "http://example.com/browser/browser/devtools/shared/test/";
function forceRender(comp) {
return setState(comp, {})
.then(() => setState(comp, {}));
}
// All tests are asynchronous.
SimpleTest.waitForExplicitFinish();
function onNextAnimationFrame(fn) {
return () =>
requestAnimationFrame(() =>
requestAnimationFrame(fn));
}
function setState(component, newState) {
var deferred = promise.defer();
component.setState(newState, onNextAnimationFrame(deferred.resolve));
return deferred.promise;
}
function setProps(component, newState) {
var deferred = promise.defer();
component.setProps(newState, onNextAnimationFrame(deferred.resolve));
return deferred.promise;
}
function dumpn(msg) {
dump(`SHARED-COMPONENTS-TEST: ${msg}\n`);
}
/**
* Tree
*/
var TEST_TREE_INTERFACE = {
getParent: x => TEST_TREE.parent[x],
getChildren: x => TEST_TREE.children[x],
renderItem: (x, depth, focused, arrow) => "-".repeat(depth) + x + ":" + focused + "\n",
getRoots: () => ["A", "M"],
getKey: x => "key-" + x,
itemHeight: 1,
onExpand: x => TEST_TREE.expanded.add(x),
onCollapse: x => TEST_TREE.expanded.delete(x),
isExpanded: x => TEST_TREE.expanded.has(x),
};
function isRenderedTree(actual, expectedDescription, msg) {
const expected = expectedDescription.map(x => x + "\n").join("");
dumpn(`Expected tree:\n${expected}`);
dumpn(`Actual tree:\n${actual}`);
is(actual, expected, msg);
}
// Encoding of the following tree/forest:
//
// A
@ -78,51 +131,26 @@ var TEST_TREE = {
expanded: new Set(),
};
var TEST_TREE_INTERFACE = {
getParent: x => TEST_TREE.parent[x],
getChildren: x => TEST_TREE.children[x],
renderItem: (x, depth, focused, arrow) => "-".repeat(depth) + x + ":" + focused + "\n",
getRoots: () => ["A", "M"],
getKey: x => "key-" + x,
itemHeight: 1,
onExpand: x => TEST_TREE.expanded.add(x),
onCollapse: x => TEST_TREE.expanded.delete(x),
isExpanded: x => TEST_TREE.expanded.has(x),
};
function forceRender(tree) {
return setState(tree, {})
.then(() => setState(tree, {}));
/**
* Frame
*/
function checkFrameString (component, file, line, column) {
let el = component.getDOMNode();
is(el.querySelector(".frame-link-filename").textContent, file);
is(+el.querySelector(".frame-link-line").textContent, +line);
if (column != null) {
is(+el.querySelector(".frame-link-column").textContent, +column);
is(el.querySelectorAll(".frame-link-colon").length, 2);
} else {
is(el.querySelector(".frame-link-column"), null,
"Should not render column when none specified");
is(el.querySelectorAll(".frame-link-colon").length, 1,
"Should only render one colon when no column specified");
}
}
// All tests are asynchronous.
SimpleTest.waitForExplicitFinish();
function onNextAnimationFrame(fn) {
return () =>
requestAnimationFrame(() =>
requestAnimationFrame(fn));
}
function setState(component, newState) {
var deferred = promise.defer();
component.setState(newState, onNextAnimationFrame(deferred.resolve));
return deferred.promise;
}
function setProps(component, newState) {
var deferred = promise.defer();
component.setProps(newState, onNextAnimationFrame(deferred.resolve));
return deferred.promise;
}
function dumpn(msg) {
dump(`MEMORY-TEST: ${msg}\n`);
}
function isRenderedTree(actual, expectedDescription, msg) {
const expected = expectedDescription.map(x => x + "\n").join("");
dumpn(`Expected tree:\n${expected}`);
dumpn(`Actual tree:\n${actual}`);
is(actual, expected, msg);
function checkFrameTooltips (component, mainTooltip, linkTooltip) {
let el = component.getDOMNode();
is(el.getAttribute("title"), mainTooltip);
is(el.querySelector("a.frame-link-filename").getAttribute("title"), linkTooltip);
}

Просмотреть файл

@ -0,0 +1,89 @@
<!DOCTYPE HTML>
<html>
<!--
Test the formatting of the file name, line and columns are correct in frame components,
with optional columns, unknown and non-URL sources.
-->
<head>
<meta charset="utf-8">
<title>Frame component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let Frame = React.createFactory(browserRequire("devtools/client/shared/components/frame"));
ok(Frame, "Should get Frame");
let frame;
// Check when there's a column
frame = ReactDOM.render(Frame({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
column: 10,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameString(frame, "mahscripts.js", 55, 10);
// Check when there's no column
frame = ReactDOM.render(Frame({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameString(frame, "mahscripts.js", 55);
// Check when column === 0
frame = ReactDOM.render(Frame({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
column: 0,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameString(frame, "mahscripts.js", 55, 0);
// Check when there's no parseable URL source
frame = ReactDOM.render(Frame({
frame: {
source: "self-hosted",
line: 1,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameString(frame, "self-hosted",1);
// Check when there's no source
frame = ReactDOM.render(Frame({
frame: {
line: 1,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameString(frame, "(unknown)",1);
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,85 @@
<!DOCTYPE HTML>
<html>
<!--
Test the formatting of the tooltips in the frame component.
-->
<head>
<meta charset="utf-8">
<title>Frame component test</title>
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
</head>
<body>
<pre id="test">
<script src="head.js" type="application/javascript;version=1.8"></script>
<script type="application/javascript;version=1.8">
window.onload = Task.async(function* () {
try {
let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
let React = browserRequire("devtools/client/shared/vendor/react");
let Frame = React.createFactory(browserRequire("devtools/client/shared/components/frame"));
ok(Frame, "Should get Frame");
let frame;
// Check when there's a column
frame = ReactDOM.render(Frame({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
column: 10,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameTooltips(frame,
"http://myfile.com/mahscripts.js:55:10",
"View source in Debugger → http://myfile.com/mahscripts.js:55:10");
// Check when there's no column
frame = ReactDOM.render(Frame({
frame: {
source: "http://myfile.com/mahscripts.js",
line: 55,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameTooltips(frame,
"http://myfile.com/mahscripts.js:55",
"View source in Debugger → http://myfile.com/mahscripts.js:55");
// Check when there's no parseable URL source
frame = ReactDOM.render(Frame({
frame: {
source: "self-hosted",
line: 1,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameTooltips(frame,
"self-hosted:1",
"View source in Debugger → self-hosted:1");
// Check when there's no source
frame = ReactDOM.render(Frame({
frame: {
line: 1,
},
onClick: ()=>{},
}), window.document.body);
yield forceRender(frame);
checkFrameTooltips(frame,
"(unknown):1",
"View source in Debugger → (unknown):1");
} catch(e) {
ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
} finally {
SimpleTest.finish();
}
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,46 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/**
* Frame Component
* Styles for React component at `devtools/client/shared/components/frame.js`
*/
.frame-link {
margin-left: 7px;
}
.focused .frame-link-filename,
.focused .frame-link-column,
.focused .frame-link-line,
.focused .frame-link-host,
.focused .frame-link-colon {
color: var(--theme-selection-color);
}
.frame-link .frame-link-filename {
color: var(--theme-highlight-blue);
cursor: pointer;
}
.frame-link .frame-link-filename:hover {
text-decoration: underline;
}
.frame-link .frame-link-host {
margin-inline-start: 5px;
font-size: 90%;
color: var(--theme-content-color2);
}
.frame-link .frame-link-function-display-name {
margin-inline-end: 5px;
}
.frame-link .frame-link-column,
.frame-link .frame-link-line,
.frame-link .frame-link-colon {
color: var(--theme-highlight-orange);
}

Просмотреть файл

@ -0,0 +1,143 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
/**
* JIT View
*/
#jit-optimizations-view {
width: 350px;
overflow-x: auto;
min-width: 200px;
white-space: nowrap;
--jit-tree-row-height: 14;
--jit-tree-header-height: 16;
}
#jit-optimizations-view > div {
flex: 1;
}
#jit-optimizations-view div {
display: block;
}
.tree {
/**
* Flexing to fill out remaining vertical space.
*/
flex: 1;
overflow-y: auto;
height: 100%;
background-color: var(--theme-body-background);
}
.optimization-header {
height: var(--jit-tree-header-height);
padding: 2px 5px;
background-color: var(--theme-tab-toolbar-background);
}
#jit-optimizations-view .header-title {
font-weight: bold;
padding-right: 7px;
}
.tree-node {
height: var(--jit-tree-row-height);
clear: both;
}
.tree-node button {
display: none;
}
#jit-optimizations-view .optimization-tree-item {
display: flex;
}
#jit-optimizations-view .arrow,
#jit-optimizations-view .optimization-site,
#jit-optimizations-view .optimization-attempts,
#jit-optimizations-view .optimization-attempt,
#jit-optimizations-view .optimization-types,
#jit-optimizations-view .optimization-ion-type,
#jit-optimizations-view .optimization-observed-type {
float: left;
}
#jit-optimizations-view .optimization-outcome.success {
color: var(--theme-highlight-green);
}
#jit-optimizations-view .optimization-outcome.failure {
color: var(--theme-highlight-red);
}
.opt-icon::before {
content: "";
background-image: url(chrome://devtools/skin/images/webconsole.svg);
background-repeat: no-repeat;
background-size: 72px 60px;
/* show grey "i" bubble by default */
background-position: -36px -36px;
width: 10px;
height: 10px;
display: inline-block;
max-height: 12px;
}
#jit-optimizations-view .opt-icon {
float: left;
}
#jit-optimizations-view .opt-icon::before {
margin: 1px 6px 0 0;
}
.theme-light .opt-icon::before {
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
}
.opt-icon.warning::before {
background-position: -24px -24px;
}
/* Frame Component */
.focused .frame-link-filename,
.focused .frame-link-column,
.focused .frame-link-line,
.focused .frame-link-host,
.focused .frame-link-colon {
color: var(--theme-selection-color);
}
.frame-link {
margin-left: 7px;
}
.frame-link-filename {
color: var(--theme-highlight-blue);
cursor: pointer;
}
.frame-link-filename:hover {
text-decoration: underline;
}
.frame-link-column,
.frame-link-line,
.frame-link-colon {
color: var(--theme-highlight-orange);
}
.frame-link-host {
margin-inline-start: 5px;
font-size: 90%;
color: var(--theme-content-color2);
}
.frame-link-function-display-name {
margin-inline-end: 5px;
}

Просмотреть файл

@ -465,39 +465,6 @@ html, body, #app, #memory-tool {
margin-right: .5em;
}
.focused .frame-link-filename,
.focused .frame-link-column,
.focused .frame-link-line,
.focused .frame-link-host,
.focused .frame-link-colon {
color: var(--theme-selection-color);
}
.frame-link-filename {
color: var(--theme-highlight-blue);
cursor: pointer;
}
.frame-link-filename:hover {
text-decoration: underline;
}
.frame-link-column,
.frame-link-line,
.frame-link-colon {
color: var(--theme-highlight-orange);
}
.frame-link-host {
margin-inline-start: 5px;
font-size: 90%;
color: var(--theme-content-color2);
}
.frame-link-function-display-name {
margin-inline-end: 5px;
}
.no-allocation-stacks {
border-color: var(--theme-splitter-color);
border-style: solid;

Просмотреть файл

@ -272,6 +272,8 @@
.call-tree-item .call-tree-cell,
.call-tree-item .call-tree-cell[type=function] description {
-moz-user-select: text;
/* so that optimizations view doesn't break the lines in call tree */
white-space: nowrap;
}
.call-tree-item .call-tree-cell::-moz-selection,
@ -609,153 +611,6 @@ menuitem.marker-color-graphs-grey:before,
border-color: var(--theme-graphs-grey);
}
/**
* JIT View
*/
#jit-optimizations-view {
width: 350px;
overflow-x: hidden;
overflow-y: auto;
min-width: 200px;
}
#optimizations-graph {
height: 30px;
}
#jit-optimizations-view.empty #optimizations-graph {
display: none !important;
}
/* override default styles for tree widget */
#jit-optimizations-view .tree-widget-empty-text {
font-size: inherit;
padding: 0px;
margin: 8px;
}
#jit-optimizations-view:not(.empty) .tree-widget-empty-text {
display: none;
}
#jit-optimizations-toolbar {
height: 18px;
min-height: 0px; /* override .devtools-toolbar min-height */
}
.jit-optimizations-title {
margin: 0px 4px;
font-weight: 600;
}
#jit-optimizations-raw-view {
font-size: 90%;
}
/* override default .tree-widget-item line-height */
#jit-optimizations-raw-view .tree-widget-item {
line-height: 20px !important;
display: block;
overflow: hidden;
}
#jit-optimizations-raw-view .tree-widget-item[level="1"] {
font-weight: 600;
}
#jit-optimizations-view .opt-outcome::before {
content: "→";
margin: 4px 0px;
color: var(--theme-body-color);
}
#jit-optimizations-view .theme-selected .opt-outcome::before {
color: var(--theme-selection-color);
}
#jit-optimizations-view .tree-widget-item:not(.theme-selected) .opt-outcome[outcome=success] {
color: var(--theme-highlight-green);
}
#jit-optimizations-view .tree-widget-item:not(.theme-selected) .opt-outcome[outcome=failure] {
color: var(--theme-highlight-red);
}
#jit-optimizations-view .tree-widget-container {
-moz-margin-end: 0px;
}
#jit-optimizations-view .tree-widget-container > li,
#jit-optimizations-view .tree-widget-children > li {
overflow: hidden;
}
.opt-line::before {
content: ":";
color: var(--theme-highlight-orange);
}
.theme-selected .opt-line::before {
color: var(--theme-selection-color);
}
.opt-line.header-line::before {
color: var(--theme-body-color);
}
#jit-optimizations-view.empty .opt-line.header-line::before {
display: none;
}
.opt-url {
-moz-margin-start: 4px !important;
}
.opt-url:hover {
text-decoration: underline;
}
.opt-url.debugger-link {
cursor: pointer;
}
.opt-icon::before {
content: "";
background-image: url(chrome://devtools/skin/images/webconsole.svg);
background-repeat: no-repeat;
background-size: 72px 60px;
/* show grey "i" bubble by default */
background-position: -36px -36px;
width: 12px;
height: 12px;
display: inline-block;
max-height: 12px;
}
#jit-optimizations-view .opt-icon::before {
margin: 5px 6px 0 0;
}
description.opt-icon {
margin: 0px 0px 0px 0px;
}
description.opt-icon::before {
margin: 1px 4px 0px 0px;
}
.theme-light .opt-icon::before {
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
}
.opt-icon[severity=warning]::before {
background-position: -24px -24px;
}
ul.frames-list {
list-style-type: none;
padding: 0px;
margin: 0px;
}
ul.frames-list li {
cursor: pointer;
}
ul.frames-list li.selected {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}
/**
* Configurable Options
*
@ -787,3 +642,32 @@ menuitem.experimental-option::before {
#performance-options-menupopup:not(.experimental-enabled) .experimental-option::before {
display: none;
}
.opt-icon::before {
content: "";
background-image: url(chrome://devtools/skin/images/webconsole.svg);
background-repeat: no-repeat;
background-size: 72px 60px;
/* show grey "i" bubble by default */
background-position: -36px -36px;
width: 10px;
height: 10px;
display: inline-block;
max-height: 12px;
}
.theme-light .opt-icon::before {
background-image: url(chrome://devtools/skin/images/webconsole.svg#light-icons);
}
.opt-icon.warning::before {
background-position: -24px -24px;
}
/* for call tree */
description.opt-icon {
margin: 0px 0px 0px 0px;
}
description.opt-icon::before {
margin: 1px 4px 0px 0px;
}