mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-31 21:21:08 +00:00
Bug 1069421 - Add a memory graph to the timeline, r=pbrosset,paul
--HG-- rename : browser/devtools/shared/test/browser_graphs-09.js => browser/devtools/shared/test/browser_graphs-09a.js rename : browser/devtools/timeline/widgets/overview.js => browser/devtools/timeline/widgets/markers-overview.js
This commit is contained in:
parent
dfb185fe5d
commit
f9702f8d40
@ -24,7 +24,9 @@ support-files =
|
||||
[browser_graphs-07a.js]
|
||||
[browser_graphs-07b.js]
|
||||
[browser_graphs-08.js]
|
||||
[browser_graphs-09.js]
|
||||
[browser_graphs-09a.js]
|
||||
[browser_graphs-09b.js]
|
||||
[browser_graphs-09c.js]
|
||||
[browser_graphs-10a.js]
|
||||
[browser_graphs-10b.js]
|
||||
[browser_graphs-11a.js]
|
||||
|
@ -27,11 +27,27 @@ function* performTest() {
|
||||
}
|
||||
|
||||
function* testGraph(graph) {
|
||||
info("Should be able to set the grpah data before waiting for the ready event.");
|
||||
info("Should be able to set the graph data before waiting for the ready event.");
|
||||
|
||||
yield graph.setDataWhenReady(TEST_DATA);
|
||||
ok(graph.hasData(), "Data was set successfully.");
|
||||
|
||||
is(graph._gutter.hidden, false,
|
||||
"The gutter should not be hidden because the tooltips have arrows.");
|
||||
is(graph._maxTooltip.hidden, false,
|
||||
"The max tooltip should not be hidden.");
|
||||
is(graph._avgTooltip.hidden, false,
|
||||
"The avg tooltip should not be hidden.");
|
||||
is(graph._minTooltip.hidden, false,
|
||||
"The min tooltip should not be hidden.");
|
||||
|
||||
is(graph._maxTooltip.getAttribute("with-arrows"), "true",
|
||||
"The maximum tooltip has the correct 'with-arrows' attribute.");
|
||||
is(graph._avgTooltip.getAttribute("with-arrows"), "true",
|
||||
"The average tooltip has the correct 'with-arrows' attribute.");
|
||||
is(graph._minTooltip.getAttribute("with-arrows"), "true",
|
||||
"The minimum tooltip has the correct 'with-arrows' attribute.");
|
||||
|
||||
is(graph._maxTooltip.querySelector("[text=info]").textContent, "max",
|
||||
"The maximum tooltip displays the correct info.");
|
||||
is(graph._avgTooltip.querySelector("[text=info]").textContent, "avg",
|
||||
@ -41,7 +57,7 @@ function* testGraph(graph) {
|
||||
|
||||
is(graph._maxTooltip.querySelector("[text=value]").textContent, "60",
|
||||
"The maximum tooltip displays the correct value.");
|
||||
is(graph._avgTooltip.querySelector("[text=value]").textContent, "41",
|
||||
is(graph._avgTooltip.querySelector("[text=value]").textContent, "41.71",
|
||||
"The average tooltip displays the correct value.");
|
||||
is(graph._minTooltip.querySelector("[text=value]").textContent, "10",
|
||||
"The minimum tooltip displays the correct value.");
|
63
browser/devtools/shared/test/browser_graphs-09b.js
Normal file
63
browser/devtools/shared/test/browser_graphs-09b.js
Normal file
@ -0,0 +1,63 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that line graphs properly use the tooltips configuration properties.
|
||||
|
||||
const TEST_DATA = [{ delta: 112, value: 48 }, { delta: 213, value: 59 }, { delta: 313, value: 60 }, { delta: 413, value: 59 }, { delta: 530, value: 59 }, { delta: 646, value: 58 }, { delta: 747, value: 60 }, { delta: 863, value: 48 }, { delta: 980, value: 37 }, { delta: 1097, value: 30 }, { delta: 1213, value: 29 }, { delta: 1330, value: 23 }, { delta: 1430, value: 10 }, { delta: 1534, value: 17 }, { delta: 1645, value: 20 }, { delta: 1746, value: 22 }, { delta: 1846, value: 39 }, { delta: 1963, value: 26 }, { delta: 2080, value: 27 }, { delta: 2197, value: 35 }, { delta: 2312, value: 47 }, { delta: 2412, value: 53 }, { delta: 2514, value: 60 }, { delta: 2630, value: 37 }, { delta: 2730, value: 36 }, { delta: 2830, value: 37 }, { delta: 2946, value: 36 }, { delta: 3046, value: 40 }, { delta: 3163, value: 47 }, { delta: 3280, value: 41 }, { delta: 3380, value: 35 }, { delta: 3480, value: 27 }, { delta: 3580, value: 39 }, { delta: 3680, value: 42 }, { delta: 3780, value: 49 }, { delta: 3880, value: 55 }, { delta: 3980, value: 60 }, { delta: 4080, value: 60 }, { delta: 4180, value: 60 }];
|
||||
let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
|
||||
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
let graph = new LineGraphWidget(doc.body, "fps");
|
||||
graph.withTooltipArrows = false;
|
||||
graph.withFixedTooltipPositions = true;
|
||||
|
||||
yield testGraph(graph);
|
||||
|
||||
graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function* testGraph(graph) {
|
||||
yield graph.setDataWhenReady(TEST_DATA);
|
||||
|
||||
is(graph._gutter.hidden, true,
|
||||
"The gutter should be hidden because the tooltips don't have arrows.");
|
||||
is(graph._maxTooltip.hidden, false,
|
||||
"The max tooltip should not be hidden.");
|
||||
is(graph._avgTooltip.hidden, false,
|
||||
"The avg tooltip should not be hidden.");
|
||||
is(graph._minTooltip.hidden, false,
|
||||
"The min tooltip should not be hidden.");
|
||||
|
||||
is(graph._maxTooltip.getAttribute("with-arrows"), "false",
|
||||
"The maximum tooltip has the correct 'with-arrows' attribute.");
|
||||
is(graph._avgTooltip.getAttribute("with-arrows"), "false",
|
||||
"The average tooltip has the correct 'with-arrows' attribute.");
|
||||
is(graph._minTooltip.getAttribute("with-arrows"), "false",
|
||||
"The minimum tooltip has the correct 'with-arrows' attribute.");
|
||||
|
||||
is(parseInt(graph._maxTooltip.style.top), 8,
|
||||
"The maximum tooltip is positioned correctly.");
|
||||
is(parseInt(graph._avgTooltip.style.top), 8,
|
||||
"The average tooltip is positioned correctly.");
|
||||
is(parseInt(graph._minTooltip.style.top), 142,
|
||||
"The minimum tooltip is positioned correctly.");
|
||||
|
||||
is(parseInt(graph._maxGutterLine.style.top), 22,
|
||||
"The maximum gutter line is positioned correctly.");
|
||||
is(parseInt(graph._avgGutterLine.style.top), 61,
|
||||
"The average gutter line is positioned correctly.");
|
||||
is(parseInt(graph._minGutterLine.style.top), 128,
|
||||
"The minimum gutter line is positioned correctly.");
|
||||
}
|
40
browser/devtools/shared/test/browser_graphs-09c.js
Normal file
40
browser/devtools/shared/test/browser_graphs-09c.js
Normal file
@ -0,0 +1,40 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Tests that line graphs hide the tooltips when there's no data available.
|
||||
|
||||
const TEST_DATA = [];
|
||||
let {LineGraphWidget} = Cu.import("resource:///modules/devtools/Graphs.jsm", {});
|
||||
let {DOMHelpers} = Cu.import("resource:///modules/devtools/DOMHelpers.jsm", {});
|
||||
let {Promise} = devtools.require("resource://gre/modules/Promise.jsm");
|
||||
let {Hosts} = devtools.require("devtools/framework/toolbox-hosts");
|
||||
|
||||
let test = Task.async(function*() {
|
||||
yield promiseTab("about:blank");
|
||||
yield performTest();
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
|
||||
function* performTest() {
|
||||
let [host, win, doc] = yield createHost();
|
||||
let graph = new LineGraphWidget(doc.body, "fps");
|
||||
|
||||
yield testGraph(graph);
|
||||
|
||||
graph.destroy();
|
||||
host.destroy();
|
||||
}
|
||||
|
||||
function* testGraph(graph) {
|
||||
yield graph.setDataWhenReady(TEST_DATA);
|
||||
|
||||
is(graph._gutter.hidden, false,
|
||||
"The gutter should not be hidden.");
|
||||
is(graph._maxTooltip.hidden, true,
|
||||
"The max tooltip should be hidden.");
|
||||
is(graph._avgTooltip.hidden, true,
|
||||
"The avg tooltip should be hidden.");
|
||||
is(graph._minTooltip.hidden, true,
|
||||
"The min tooltip should be hidden.");
|
||||
}
|
@ -19,6 +19,7 @@ this.EXPORTED_SYMBOLS = [
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
const GRAPH_SRC = "chrome://browser/content/devtools/graphs-frame.xhtml";
|
||||
const L10N = new ViewHelpers.L10N();
|
||||
|
||||
// Generic constants.
|
||||
|
||||
@ -44,8 +45,9 @@ const GRAPH_STRIPE_PATTERN_LINE_SPACING = 4; // px
|
||||
|
||||
const LINE_GRAPH_DAMPEN_VALUES = 0.85;
|
||||
const LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS = 400; // 20 px
|
||||
const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 10; // px
|
||||
const LINE_GRAPH_TOOLTIP_SAFE_BOUNDS = 8; // px
|
||||
|
||||
const LINE_GRAPH_BACKGROUND_COLOR = "#0088cc";
|
||||
const LINE_GRAPH_STROKE_WIDTH = 1; // px
|
||||
const LINE_GRAPH_STROKE_COLOR = "rgba(255,255,255,0.9)";
|
||||
const LINE_GRAPH_HELPER_LINES_DASH = [5]; // px
|
||||
@ -487,7 +489,8 @@ AbstractCanvasGraph.prototype = {
|
||||
* @return boolean
|
||||
*/
|
||||
hasSelection: function() {
|
||||
return this._selection.start != null && this._selection.end != null;
|
||||
return this._selection &&
|
||||
this._selection.start != null && this._selection.end != null;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -496,7 +499,8 @@ AbstractCanvasGraph.prototype = {
|
||||
* @return boolean
|
||||
*/
|
||||
hasSelectionInProgress: function() {
|
||||
return this._selection.start != null && this._selection.end == null;
|
||||
return this._selection &&
|
||||
this._selection.start != null && this._selection.end == null;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -552,7 +556,7 @@ AbstractCanvasGraph.prototype = {
|
||||
* @return boolean
|
||||
*/
|
||||
hasCursor: function() {
|
||||
return this._cursor.x != null;
|
||||
return this._cursor && this._cursor.x != null;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1176,6 +1180,14 @@ this.LineGraphWidget = function(parent, metric, ...args) {
|
||||
}
|
||||
|
||||
LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
backgroundColor: LINE_GRAPH_BACKGROUND_COLOR,
|
||||
backgroundGradientStart: LINE_GRAPH_BACKGROUND_GRADIENT_START,
|
||||
backgroundGradientEnd: LINE_GRAPH_BACKGROUND_GRADIENT_END,
|
||||
strokeColor: LINE_GRAPH_STROKE_COLOR,
|
||||
strokeWidth: LINE_GRAPH_STROKE_WIDTH,
|
||||
maximumLineColor: LINE_GRAPH_MAXIMUM_LINE_COLOR,
|
||||
averageLineColor: LINE_GRAPH_AVERAGE_LINE_COLOR,
|
||||
minimumLineColor: LINE_GRAPH_MINIMUM_LINE_COLOR,
|
||||
clipheadLineColor: LINE_GRAPH_CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: LINE_GRAPH_SELECTION_LINE_COLOR,
|
||||
selectionBackgroundColor: LINE_GRAPH_SELECTION_BACKGROUND_COLOR,
|
||||
@ -1188,12 +1200,29 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
*/
|
||||
dataOffsetX: 0,
|
||||
|
||||
/**
|
||||
* The scalar used to multiply the graph values to leave some headroom
|
||||
* on the top.
|
||||
*/
|
||||
dampenValuesFactor: LINE_GRAPH_DAMPEN_VALUES,
|
||||
|
||||
/**
|
||||
* Points that are too close too each other in the graph will not be rendered.
|
||||
* This scalar specifies the required minimum squared distance between points.
|
||||
*/
|
||||
minDistanceBetweenPoints: LINE_GRAPH_MIN_SQUARED_DISTANCE_BETWEEN_POINTS,
|
||||
|
||||
/**
|
||||
* Specifies if min/max/avg tooltips have arrow handlers on their sides.
|
||||
*/
|
||||
withTooltipArrows: true,
|
||||
|
||||
/**
|
||||
* Specifies if min/max/avg tooltips are positioned based on the actual
|
||||
* values, or just placed next to the graph corners.
|
||||
*/
|
||||
withFixedTooltipPositions: false,
|
||||
|
||||
/**
|
||||
* Renders the graph's data source.
|
||||
* @see AbstractCanvasGraph.prototype.buildGraphImage
|
||||
@ -1204,8 +1233,8 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
let height = this._height;
|
||||
|
||||
let totalTicks = this._data.length;
|
||||
let firstTick = this._data[0].delta;
|
||||
let lastTick = this._data[totalTicks - 1].delta;
|
||||
let firstTick = totalTicks ? this._data[0].delta : 0;
|
||||
let lastTick = totalTicks ? this._data[totalTicks - 1].delta : 0;
|
||||
let maxValue = Number.MIN_SAFE_INTEGER;
|
||||
let minValue = Number.MAX_SAFE_INTEGER;
|
||||
let sumValues = 0;
|
||||
@ -1217,7 +1246,7 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
}
|
||||
|
||||
let dataScaleX = this.dataScaleX = width / (lastTick - this.dataOffsetX);
|
||||
let dataScaleY = this.dataScaleY = height / maxValue * LINE_GRAPH_DAMPEN_VALUES;
|
||||
let dataScaleY = this.dataScaleY = height / maxValue * this.dampenValuesFactor;
|
||||
|
||||
/**
|
||||
* Calculates the squared distance between two 2D points.
|
||||
@ -1230,12 +1259,15 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
|
||||
// Draw the graph.
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
let gradient = ctx.createLinearGradient(0, height / 2, 0, height);
|
||||
gradient.addColorStop(0, LINE_GRAPH_BACKGROUND_GRADIENT_START);
|
||||
gradient.addColorStop(1, LINE_GRAPH_BACKGROUND_GRADIENT_END);
|
||||
gradient.addColorStop(0, this.backgroundGradientStart);
|
||||
gradient.addColorStop(1, this.backgroundGradientEnd);
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.strokeStyle = LINE_GRAPH_STROKE_COLOR;
|
||||
ctx.lineWidth = LINE_GRAPH_STROKE_WIDTH * this._pixelRatio;
|
||||
ctx.strokeStyle = this.strokeColor;
|
||||
ctx.lineWidth = this.strokeWidth * this._pixelRatio;
|
||||
ctx.beginPath();
|
||||
|
||||
let prevX = 0;
|
||||
@ -1268,43 +1300,46 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
|
||||
// Draw the maximum value horizontal line.
|
||||
|
||||
ctx.strokeStyle = LINE_GRAPH_MAXIMUM_LINE_COLOR;
|
||||
ctx.strokeStyle = this.maximumLineColor;
|
||||
ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let maximumY = height - maxValue * dataScaleY - ctx.lineWidth;
|
||||
let maximumY = height - maxValue * dataScaleY;
|
||||
ctx.moveTo(0, maximumY);
|
||||
ctx.lineTo(width, maximumY);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw the average value horizontal line.
|
||||
|
||||
ctx.strokeStyle = LINE_GRAPH_AVERAGE_LINE_COLOR;
|
||||
ctx.strokeStyle = this.averageLineColor;
|
||||
ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let avgValue = sumValues / totalTicks;
|
||||
let averageY = height - avgValue * dataScaleY - ctx.lineWidth;
|
||||
let avgValue = totalTicks ? sumValues / totalTicks : 0;
|
||||
let averageY = height - avgValue * dataScaleY;
|
||||
ctx.moveTo(0, averageY);
|
||||
ctx.lineTo(width, averageY);
|
||||
ctx.stroke();
|
||||
|
||||
// Draw the minimum value horizontal line.
|
||||
|
||||
ctx.strokeStyle = LINE_GRAPH_MINIMUM_LINE_COLOR;
|
||||
ctx.strokeStyle = this.minimumLineColor;
|
||||
ctx.lineWidth = LINE_GRAPH_HELPER_LINES_WIDTH;
|
||||
ctx.setLineDash(LINE_GRAPH_HELPER_LINES_DASH);
|
||||
ctx.beginPath();
|
||||
let minimumY = height - minValue * dataScaleY - ctx.lineWidth;
|
||||
let minimumY = height - minValue * dataScaleY;
|
||||
ctx.moveTo(0, minimumY);
|
||||
ctx.lineTo(width, minimumY);
|
||||
ctx.stroke();
|
||||
|
||||
// Update the tooltips text and gutter lines.
|
||||
|
||||
this._maxTooltip.querySelector("[text=value]").textContent = maxValue|0;
|
||||
this._avgTooltip.querySelector("[text=value]").textContent = avgValue|0;
|
||||
this._minTooltip.querySelector("[text=value]").textContent = minValue|0;
|
||||
this._maxTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(maxValue, 2);
|
||||
this._avgTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(avgValue, 2);
|
||||
this._minTooltip.querySelector("[text=value]").textContent =
|
||||
L10N.numberWithDecimals(minValue, 2);
|
||||
|
||||
/**
|
||||
* Constrains a value to a range.
|
||||
@ -1316,19 +1351,32 @@ LineGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
}
|
||||
|
||||
let bottom = height / this._pixelRatio;
|
||||
let maxPosY = map(maxValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
|
||||
let avgPosY = map(avgValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
|
||||
let minPosY = map(minValue * LINE_GRAPH_DAMPEN_VALUES, 0, maxValue, bottom, 0);
|
||||
let maxPosY = map(maxValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
let avgPosY = map(avgValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
let minPosY = map(minValue * this.dampenValuesFactor, 0, maxValue, bottom, 0);
|
||||
|
||||
let safeTop = LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
|
||||
let safeBottom = bottom - LINE_GRAPH_TOOLTIP_SAFE_BOUNDS;
|
||||
|
||||
this._maxTooltip.style.top = clamp(maxPosY, safeTop, safeBottom) + "px";
|
||||
this._avgTooltip.style.top = clamp(avgPosY, safeTop, safeBottom) + "px";
|
||||
this._minTooltip.style.top = clamp(minPosY, safeTop, safeBottom) + "px";
|
||||
this._maxGutterLine.style.top = clamp(maxPosY, safeTop, safeBottom) + "px";
|
||||
this._avgGutterLine.style.top = clamp(avgPosY, safeTop, safeBottom) + "px";
|
||||
this._minGutterLine.style.top = clamp(minPosY, safeTop, safeBottom) + "px";
|
||||
this._maxTooltip.style.top = (this.withFixedTooltipPositions
|
||||
? safeTop : clamp(maxPosY, safeTop, safeBottom)) + "px";
|
||||
this._avgTooltip.style.top = (this.withFixedTooltipPositions
|
||||
? safeTop : clamp(avgPosY, safeTop, safeBottom)) + "px";
|
||||
this._minTooltip.style.top = (this.withFixedTooltipPositions
|
||||
? safeBottom : clamp(minPosY, safeTop, safeBottom)) + "px";
|
||||
|
||||
this._maxGutterLine.style.top = maxPosY + "px";
|
||||
this._avgGutterLine.style.top = avgPosY + "px";
|
||||
this._minGutterLine.style.top = minPosY + "px";
|
||||
|
||||
this._maxTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
this._avgTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
this._minTooltip.setAttribute("with-arrows", this.withTooltipArrows);
|
||||
|
||||
this._gutter.hidden = !this.withTooltipArrows;
|
||||
this._maxTooltip.hidden = !totalTicks;
|
||||
this._avgTooltip.hidden = !totalTicks;
|
||||
this._minTooltip.hidden = !totalTicks;
|
||||
|
||||
return canvas;
|
||||
},
|
||||
@ -1466,6 +1514,12 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
*/
|
||||
dataOffsetX: 0,
|
||||
|
||||
/**
|
||||
* The scalar used to multiply the graph values to leave some headroom
|
||||
* on the top.
|
||||
*/
|
||||
dampenValuesFactor: BAR_GRAPH_DAMPEN_VALUES,
|
||||
|
||||
/**
|
||||
* Bars that are too close too each other in the graph will be combined.
|
||||
* This scalar specifies the required minimum width of each bar.
|
||||
@ -1520,7 +1574,7 @@ BarGraphWidget.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
data: this._data,
|
||||
dataScaleX: dataScaleX,
|
||||
minBarsWidth: minBarsWidth
|
||||
}) * BAR_GRAPH_DAMPEN_VALUES;
|
||||
}) * this.dampenValuesFactor;
|
||||
|
||||
// Draw the graph.
|
||||
|
||||
@ -1923,6 +1977,13 @@ this.CanvasGraphUtils = {
|
||||
if (!graph1 || !graph2) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (graph1.hasSelection()) {
|
||||
graph2.setSelection(graph1.getSelection());
|
||||
} else {
|
||||
graph2.dropSelection();
|
||||
}
|
||||
|
||||
graph1.on("selecting", () => {
|
||||
graph2.setSelection(graph1.getSelection());
|
||||
});
|
||||
|
@ -6,7 +6,8 @@
|
||||
EXTRA_JS_MODULES.devtools.timeline += [
|
||||
'panel.js',
|
||||
'widgets/global.js',
|
||||
'widgets/overview.js',
|
||||
'widgets/markers-overview.js',
|
||||
'widgets/memory-overview.js',
|
||||
'widgets/waterfall.js'
|
||||
]
|
||||
|
||||
|
@ -10,6 +10,7 @@ support-files =
|
||||
[browser_timeline_overview-initial-selection-02.js]
|
||||
[browser_timeline_overview-update.js]
|
||||
[browser_timeline_panels.js]
|
||||
[browser_timeline_recording-without-memory.js]
|
||||
[browser_timeline_recording.js]
|
||||
[browser_timeline_waterfall-background.js]
|
||||
[browser_timeline_waterfall-generic.js]
|
||||
|
@ -8,9 +8,12 @@
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
|
||||
let { EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
|
||||
|
||||
$("#memory-checkbox").checked = true;
|
||||
yield TimelineController.updateMemoryRecording();
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
@ -21,19 +24,22 @@ let test = Task.async(function*() {
|
||||
"The overview graph was updated a bunch of times.");
|
||||
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
|
||||
"There are some markers available.");
|
||||
ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
|
||||
"There are some memory measurements available now.");
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
let interval = TimelineController.getInterval();
|
||||
let markers = TimelineController.getMarkers();
|
||||
let selection = TimelineView.overview.getSelection();
|
||||
let selection = TimelineView.markersOverview.getSelection();
|
||||
|
||||
is((selection.start) | 0,
|
||||
((markers[0].start - markers.startTime) * TimelineView.overview.dataScaleX) | 0,
|
||||
((markers[0].start - interval.startTime) * TimelineView.markersOverview.dataScaleX) | 0,
|
||||
"The initial selection start is correct.");
|
||||
|
||||
is((selection.end - selection.start) | 0,
|
||||
(selectionRatio * TimelineView.overview.width) | 0,
|
||||
(selectionRatio * TimelineView.markersOverview.width) | 0,
|
||||
"The initial selection end is correct.");
|
||||
|
||||
yield teardown(panel);
|
||||
|
@ -8,9 +8,12 @@
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
|
||||
let { EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
let { OVERVIEW_INITIAL_SELECTION_RATIO: selectionRatio } = panel.panelWin;
|
||||
|
||||
$("#memory-checkbox").checked = true;
|
||||
yield TimelineController.updateMemoryRecording();
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
@ -18,10 +21,13 @@ let test = Task.async(function*() {
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
let markers = TimelineController.getMarkers();
|
||||
let selection = TimelineView.overview.getSelection();
|
||||
let memory = TimelineController.getMemory();
|
||||
let selection = TimelineView.markersOverview.getSelection();
|
||||
|
||||
is(markers.length, 0,
|
||||
"There are no markers available.");
|
||||
is(memory.length, 0,
|
||||
"There are no memory measurements available.");
|
||||
is(selection.start, null,
|
||||
"The initial selection start is correct.");
|
||||
is(selection.end, null,
|
||||
|
@ -2,46 +2,72 @@
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the overview graph is continuously updated.
|
||||
* Tests if the markers and memory overviews are continuously updated.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel } = yield initTimelinePanel("about:blank");
|
||||
let { EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
|
||||
$("#memory-checkbox").checked = true;
|
||||
yield TimelineController.updateMemoryRecording();
|
||||
|
||||
yield DevToolsUtils.waitForTime(1000);
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
ok("selectionEnabled" in TimelineView.overview,
|
||||
"The selection should not be enabled for the overview graph (1).");
|
||||
is(TimelineView.overview.selectionEnabled, false,
|
||||
"The selection should not be enabled for the overview graph (2).");
|
||||
is(TimelineView.overview.hasSelection(), false,
|
||||
"The overview graph shouldn't have a selection before recording.");
|
||||
ok("selectionEnabled" in TimelineView.markersOverview,
|
||||
"The selection should not be enabled for the markers overview (1).");
|
||||
is(TimelineView.markersOverview.selectionEnabled, false,
|
||||
"The selection should not be enabled for the markers overview (2).");
|
||||
is(TimelineView.markersOverview.hasSelection(), false,
|
||||
"The markers overview shouldn't have a selection before recording.");
|
||||
|
||||
ok("selectionEnabled" in TimelineView.memoryOverview,
|
||||
"The selection should not be enabled for the memory overview (1).");
|
||||
is(TimelineView.memoryOverview.selectionEnabled, false,
|
||||
"The selection should not be enabled for the memory overview (2).");
|
||||
is(TimelineView.memoryOverview.hasSelection(), false,
|
||||
"The memory overview shouldn't have a selection before recording.");
|
||||
|
||||
let updated = 0;
|
||||
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
|
||||
|
||||
ok((yield waitUntil(() => updated > 10)),
|
||||
"The overview graph was updated a bunch of times.");
|
||||
"The overviews were updated a bunch of times.");
|
||||
ok((yield waitUntil(() => TimelineController.getMemory().length > 10)),
|
||||
"There are some memory measurements available now.");
|
||||
|
||||
ok("selectionEnabled" in TimelineView.overview,
|
||||
"The selection should still not be enabled for the overview graph (3).");
|
||||
is(TimelineView.overview.selectionEnabled, false,
|
||||
"The selection should still not be enabled for the overview graph (4).");
|
||||
is(TimelineView.overview.hasSelection(), false,
|
||||
"The overview graph should not have a selection while recording.");
|
||||
ok("selectionEnabled" in TimelineView.markersOverview,
|
||||
"The selection should still not be enabled for the markers overview (3).");
|
||||
is(TimelineView.markersOverview.selectionEnabled, false,
|
||||
"The selection should still not be enabled for the markers overview (4).");
|
||||
is(TimelineView.markersOverview.hasSelection(), false,
|
||||
"The markers overview should not have a selection while recording.");
|
||||
|
||||
ok("selectionEnabled" in TimelineView.memoryOverview,
|
||||
"The selection should still not be enabled for the memory overview (3).");
|
||||
is(TimelineView.memoryOverview.selectionEnabled, false,
|
||||
"The selection should still not be enabled for the memory overview (4).");
|
||||
is(TimelineView.memoryOverview.hasSelection(), false,
|
||||
"The memory overview should not have a selection while recording.");
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
is(TimelineController.getMarkers().length, 0,
|
||||
"There are no markers available.");
|
||||
is(TimelineView.overview.selectionEnabled, true,
|
||||
"The selection should now be enabled for the overview graph.");
|
||||
is(TimelineView.overview.hasSelection(), false,
|
||||
"The overview graph should not have a selection after recording.");
|
||||
isnot(TimelineController.getMemory().length, 0,
|
||||
"There are some memory measurements available.");
|
||||
|
||||
is(TimelineView.markersOverview.selectionEnabled, true,
|
||||
"The selection should now be enabled for the markers overview.");
|
||||
is(TimelineView.markersOverview.hasSelection(), false,
|
||||
"The markers overview should not have a selection after recording.");
|
||||
|
||||
is(TimelineView.memoryOverview.selectionEnabled, true,
|
||||
"The selection should now be enabled for the memory overview.");
|
||||
is(TimelineView.memoryOverview.hasSelection(), false,
|
||||
"The memory overview should not have a selection after recording.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
|
@ -0,0 +1,36 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if the timeline actor isn't unnecessarily asked to record memory.
|
||||
*/
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
|
||||
let { $, EVENTS, TimelineView, TimelineController } = panel.panelWin;
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
let updated = 0;
|
||||
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
|
||||
|
||||
ok((yield waitUntil(() => updated > 10)),
|
||||
"The overview graph was updated a bunch of times.");
|
||||
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
|
||||
"There are some markers available.");
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
let markers = TimelineController.getMarkers();
|
||||
let memory = TimelineController.getMemory();
|
||||
|
||||
isnot(markers.length, 0,
|
||||
"There are some markers available.");
|
||||
is(memory.length, 0,
|
||||
"There are no memory measurements available.");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
});
|
@ -7,7 +7,10 @@
|
||||
|
||||
let test = Task.async(function*() {
|
||||
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
|
||||
let { gFront, TimelineController } = panel.panelWin;
|
||||
let { $, gFront, TimelineController } = panel.panelWin;
|
||||
|
||||
$("#memory-checkbox").checked = true;
|
||||
yield TimelineController.updateMemoryRecording();
|
||||
|
||||
is((yield gFront.isRecording()), false,
|
||||
"The timeline actor should not be recording when the tool starts.");
|
||||
@ -20,13 +23,16 @@ let test = Task.async(function*() {
|
||||
"The timeline actor should be recording now.");
|
||||
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
|
||||
"There are some markers available now.");
|
||||
ok((yield waitUntil(() => TimelineController.getMemory().length > 0)),
|
||||
"There are some memory measurements available now.");
|
||||
|
||||
ok("startTime" in TimelineController.getMarkers(),
|
||||
"A `startTime` field was set on the markers array.");
|
||||
ok("endTime" in TimelineController.getMarkers(),
|
||||
"An `endTime` field was set on the markers array.");
|
||||
ok(TimelineController.getMarkers().endTime >
|
||||
TimelineController.getMarkers().startTime,
|
||||
ok("startTime" in TimelineController.getInterval(),
|
||||
"A `startTime` field was set on the recording data.");
|
||||
ok("endTime" in TimelineController.getInterval(),
|
||||
"An `endTime` field was set on the recording data.");
|
||||
|
||||
ok(TimelineController.getInterval().endTime >
|
||||
TimelineController.getInterval().startTime,
|
||||
"Some time has passed since the recording started.");
|
||||
|
||||
yield teardown(panel);
|
||||
|
@ -17,7 +17,7 @@ let test = Task.async(function*() {
|
||||
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
|
||||
|
||||
ok((yield waitUntil(() => updated > 0)),
|
||||
"The overview graph was updated a bunch of times.");
|
||||
"The overview graphs were updated a bunch of times.");
|
||||
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
|
||||
"There are some markers available.");
|
||||
|
||||
|
@ -16,7 +16,7 @@ let test = Task.async(function*() {
|
||||
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
|
||||
|
||||
ok((yield waitUntil(() => updated > 0)),
|
||||
"The overview graph was updated a bunch of times.");
|
||||
"The overview graphs were updated a bunch of times.");
|
||||
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
|
||||
"There are some markers available.");
|
||||
|
||||
@ -25,42 +25,42 @@ let test = Task.async(function*() {
|
||||
|
||||
// Test the header container.
|
||||
|
||||
ok($(".timeline-header-container"),
|
||||
ok($(".waterfall-header-container"),
|
||||
"A header container should have been created.");
|
||||
|
||||
// Test the header sidebar (left).
|
||||
|
||||
ok($(".timeline-header-sidebar"),
|
||||
ok($(".waterfall-header-container > .waterfall-sidebar"),
|
||||
"A header sidebar node should have been created.");
|
||||
ok($(".timeline-header-sidebar > .timeline-header-name"),
|
||||
ok($(".waterfall-header-container > .waterfall-sidebar > .waterfall-header-name"),
|
||||
"A header name label should have been created inside the sidebar.");
|
||||
|
||||
// Test the header ticks (right).
|
||||
|
||||
ok($(".timeline-header-ticks"),
|
||||
ok($(".waterfall-header-ticks"),
|
||||
"A header ticks node should have been created.");
|
||||
ok($$(".timeline-header-ticks > .timeline-header-tick").length > 0,
|
||||
ok($$(".waterfall-header-ticks > .waterfall-header-tick").length > 0,
|
||||
"Some header tick labels should have been created inside the tick node.");
|
||||
|
||||
// Test the markers container.
|
||||
|
||||
ok($(".timeline-marker-container"),
|
||||
ok($(".waterfall-marker-container"),
|
||||
"A marker container should have been created.");
|
||||
|
||||
// Test the markers sidebar (left).
|
||||
|
||||
ok($$(".timeline-marker-sidebar").length,
|
||||
ok($$(".waterfall-marker-container > .waterfall-sidebar").length,
|
||||
"Some marker sidebar nodes should have been created.");
|
||||
ok($$(".timeline-marker-sidebar:not(spacer) > .timeline-marker-bullet").length,
|
||||
ok($$(".waterfall-marker-container > .waterfall-sidebar:not(spacer) > .waterfall-marker-bullet").length,
|
||||
"Some marker color bullets should have been created inside the sidebar.");
|
||||
ok($$(".timeline-marker-sidebar:not(spacer) > .timeline-marker-name").length,
|
||||
ok($$(".waterfall-marker-container > .waterfall-sidebar:not(spacer) > .waterfall-marker-name").length,
|
||||
"Some marker name labels should have been created inside the sidebar.");
|
||||
|
||||
// Test the markers waterfall (right).
|
||||
|
||||
ok($$(".timeline-marker-waterfall").length,
|
||||
ok($$(".waterfall-marker-item").length,
|
||||
"Some marker waterfall nodes should have been created.");
|
||||
ok($$(".timeline-marker-waterfall:not(spacer) > .timeline-marker-bar").length,
|
||||
ok($$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar").length,
|
||||
"Some marker color bars should have been created inside the waterfall.");
|
||||
|
||||
yield teardown(panel);
|
||||
|
@ -27,7 +27,7 @@ let test = Task.async(function*() {
|
||||
panel.panelWin.on(EVENTS.OVERVIEW_UPDATED, () => updated++);
|
||||
|
||||
ok((yield waitUntil(() => updated > 0)),
|
||||
"The overview graph was updated a bunch of times.");
|
||||
"The overview graphs were updated a bunch of times.");
|
||||
ok((yield waitUntil(() => TimelineController.getMarkers().length > 0)),
|
||||
"There are some markers available.");
|
||||
|
||||
|
@ -12,11 +12,16 @@ devtools.lazyRequireGetter(this, "promise");
|
||||
devtools.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
devtools.lazyRequireGetter(this, "Overview",
|
||||
"devtools/timeline/overview", true);
|
||||
devtools.lazyRequireGetter(this, "MarkersOverview",
|
||||
"devtools/timeline/markers-overview", true);
|
||||
devtools.lazyRequireGetter(this, "MemoryOverview",
|
||||
"devtools/timeline/memory-overview", true);
|
||||
devtools.lazyRequireGetter(this, "Waterfall",
|
||||
"devtools/timeline/waterfall", true);
|
||||
|
||||
devtools.lazyImporter(this, "CanvasGraphUtils",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
|
||||
devtools.lazyImporter(this, "PluralForm",
|
||||
"resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
@ -29,7 +34,7 @@ const EVENTS = {
|
||||
RECORDING_STARTED: "Timeline:RecordingStarted",
|
||||
RECORDING_ENDED: "Timeline:RecordingEnded",
|
||||
|
||||
// When the overview graph is populated with new markers.
|
||||
// When the overview graphs are populated with new markers.
|
||||
OVERVIEW_UPDATED: "Timeline:OverviewUpdated",
|
||||
|
||||
// When the waterfall view is populated with new markers.
|
||||
@ -63,9 +68,13 @@ let shutdownTimeline = Task.async(function*() {
|
||||
*/
|
||||
let TimelineController = {
|
||||
/**
|
||||
* Permanent storage for the markers streamed by the backend.
|
||||
* Permanent storage for the markers and the memory measurements streamed by
|
||||
* the backend, along with the start and end timestamps.
|
||||
*/
|
||||
_starTime: 0,
|
||||
_endTime: 0,
|
||||
_markers: [],
|
||||
_memory: [],
|
||||
|
||||
/**
|
||||
* Initialization function, called when the tool is started.
|
||||
@ -73,7 +82,9 @@ let TimelineController = {
|
||||
initialize: function() {
|
||||
this._onRecordingTick = this._onRecordingTick.bind(this);
|
||||
this._onMarkers = this._onMarkers.bind(this);
|
||||
this._onMemory = this._onMemory.bind(this);
|
||||
gFront.on("markers", this._onMarkers);
|
||||
gFront.on("memory", this._onMemory);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -81,16 +92,44 @@ let TimelineController = {
|
||||
*/
|
||||
destroy: function() {
|
||||
gFront.off("markers", this._onMarkers);
|
||||
gFront.off("memory", this._onMemory);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the { stat, end } time interval for this recording.
|
||||
* @return object
|
||||
*/
|
||||
getInterval: function() {
|
||||
return { startTime: this._startTime, endTime: this._endTime };
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the accumulated markers in this recording.
|
||||
* @return array.
|
||||
* @return array
|
||||
*/
|
||||
getMarkers: function() {
|
||||
return this._markers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the accumulated memory measurements in this recording.
|
||||
* @return array
|
||||
*/
|
||||
getMemory: function() {
|
||||
return this._memory;
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the views to show or hide the memory recording data.
|
||||
*/
|
||||
updateMemoryRecording: Task.async(function*() {
|
||||
if ($("#memory-checkbox").checked) {
|
||||
yield TimelineView.showMemoryOverview();
|
||||
} else {
|
||||
yield TimelineView.hideMemoryOverview();
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts/stops the timeline recording and streaming.
|
||||
*/
|
||||
@ -108,17 +147,20 @@ let TimelineController = {
|
||||
*/
|
||||
_startRecording: function*() {
|
||||
TimelineView.handleRecordingStarted();
|
||||
let startTime = yield gFront.start();
|
||||
|
||||
let withMemory = $("#memory-checkbox").checked;
|
||||
let startTime = yield gFront.start({ withMemory });
|
||||
|
||||
// Times must come from the actor in order to be self-consistent.
|
||||
// However, we also want to update the view with the elapsed time
|
||||
// even when the actor is not generating data. To do this we get
|
||||
// the local time and use it to compute a reasonable elapsed time.
|
||||
// See _onRecordingTick.
|
||||
this._localStartTime = performance.now();
|
||||
|
||||
this._startTime = startTime;
|
||||
this._endTime = startTime;
|
||||
this._markers = [];
|
||||
this._markers.startTime = startTime;
|
||||
this._markers.endTime = startTime;
|
||||
this._memory = [];
|
||||
this._updateId = setInterval(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
|
||||
},
|
||||
|
||||
@ -131,7 +173,7 @@ let TimelineController = {
|
||||
// Sorting markers is only important when displayed in the waterfall.
|
||||
this._markers = this._markers.sort((a,b) => (a.start > b.start));
|
||||
|
||||
TimelineView.handleMarkersUpdate(this._markers);
|
||||
TimelineView.handleRecordingUpdate();
|
||||
TimelineView.handleRecordingEnded();
|
||||
yield gFront.stop();
|
||||
},
|
||||
@ -143,6 +185,7 @@ let TimelineController = {
|
||||
_stopRecordingAndDiscardData: function*() {
|
||||
yield this._stopRecording();
|
||||
this._markers.length = 0;
|
||||
this._memory.length = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -156,22 +199,34 @@ let TimelineController = {
|
||||
*/
|
||||
_onMarkers: function(markers, endTime) {
|
||||
Array.prototype.push.apply(this._markers, markers);
|
||||
this._markers.endTime = endTime;
|
||||
this._endTime = endTime;
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback handling the "memory" event on the timeline front.
|
||||
*
|
||||
* @param number delta
|
||||
* The number of milliseconds elapsed since epoch.
|
||||
* @param object measurement
|
||||
* A detailed breakdown of the current memory usage.
|
||||
*/
|
||||
_onMemory: function(delta, measurement) {
|
||||
this._memory.push({ delta, value: measurement.total / 1024 / 1024 });
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback invoked at a fixed interval while recording.
|
||||
* Updates the markers store with the current time and the timeline overview.
|
||||
* Updates the current time and the timeline overview.
|
||||
*/
|
||||
_onRecordingTick: function() {
|
||||
// Compute an approximate ending time for the view. This is
|
||||
// needed to ensure that the view updates even when new data is
|
||||
// not being generated.
|
||||
let fakeTime = this._markers.startTime + (performance.now() - this._localStartTime);
|
||||
if (fakeTime > this._markers.endTime) {
|
||||
this._markers.endTime = fakeTime;
|
||||
let fakeTime = this._startTime + (performance.now() - this._localStartTime);
|
||||
if (fakeTime > this._endTime) {
|
||||
this._endTime = fakeTime;
|
||||
}
|
||||
TimelineView.handleMarkersUpdate(this._markers);
|
||||
TimelineView.handleRecordingUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
@ -183,15 +238,15 @@ let TimelineView = {
|
||||
* Initialization function, called when the tool is started.
|
||||
*/
|
||||
initialize: Task.async(function*() {
|
||||
this.overview = new Overview($("#timeline-overview"));
|
||||
this.markersOverview = new MarkersOverview($("#markers-overview"));
|
||||
this.waterfall = new Waterfall($("#timeline-waterfall"));
|
||||
|
||||
this._onSelecting = this._onSelecting.bind(this);
|
||||
this._onRefresh = this._onRefresh.bind(this);
|
||||
this.overview.on("selecting", this._onSelecting);
|
||||
this.overview.on("refresh", this._onRefresh);
|
||||
this.markersOverview.on("selecting", this._onSelecting);
|
||||
this.markersOverview.on("refresh", this._onRefresh);
|
||||
|
||||
yield this.overview.ready();
|
||||
yield this.markersOverview.ready();
|
||||
yield this.waterfall.recalculateBounds();
|
||||
}),
|
||||
|
||||
@ -199,9 +254,40 @@ let TimelineView = {
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
this.overview.off("selecting", this._onSelecting);
|
||||
this.overview.off("refresh", this._onRefresh);
|
||||
this.overview.destroy();
|
||||
this.markersOverview.off("selecting", this._onSelecting);
|
||||
this.markersOverview.off("refresh", this._onRefresh);
|
||||
this.markersOverview.destroy();
|
||||
|
||||
// The memory overview graph is not always available.
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.destroy();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Shows the memory overview graph.
|
||||
*/
|
||||
showMemoryOverview: Task.async(function*() {
|
||||
this.memoryOverview = new MemoryOverview($("#memory-overview"));
|
||||
yield this.memoryOverview.ready();
|
||||
|
||||
let interval = TimelineController.getInterval();
|
||||
let memory = TimelineController.getMemory();
|
||||
this.memoryOverview.setData({ interval, memory });
|
||||
|
||||
CanvasGraphUtils.linkAnimation(this.markersOverview, this.memoryOverview);
|
||||
CanvasGraphUtils.linkSelection(this.markersOverview, this.memoryOverview);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Hides the memory overview graph.
|
||||
*/
|
||||
hideMemoryOverview: function() {
|
||||
if (!this.memoryOverview) {
|
||||
return;
|
||||
}
|
||||
this.memoryOverview.destroy();
|
||||
this.memoryOverview = null;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -210,11 +296,16 @@ let TimelineView = {
|
||||
*/
|
||||
handleRecordingStarted: function() {
|
||||
$("#record-button").setAttribute("checked", "true");
|
||||
$("#memory-checkbox").setAttribute("disabled", "true");
|
||||
$("#timeline-pane").selectedPanel = $("#recording-notice");
|
||||
|
||||
this.overview.selectionEnabled = false;
|
||||
this.overview.dropSelection();
|
||||
this.overview.setData([]);
|
||||
this.markersOverview.clearView();
|
||||
|
||||
// The memory overview graph is not always available.
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.clearView();
|
||||
}
|
||||
|
||||
this.waterfall.clearView();
|
||||
|
||||
window.emit(EVENTS.RECORDING_STARTED);
|
||||
@ -226,18 +317,28 @@ let TimelineView = {
|
||||
*/
|
||||
handleRecordingEnded: function() {
|
||||
$("#record-button").removeAttribute("checked");
|
||||
$("#memory-checkbox").removeAttribute("disabled");
|
||||
$("#timeline-pane").selectedPanel = $("#timeline-waterfall");
|
||||
|
||||
this.overview.selectionEnabled = true;
|
||||
this.markersOverview.selectionEnabled = true;
|
||||
|
||||
// The memory overview graph is not always available.
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.selectionEnabled = true;
|
||||
}
|
||||
|
||||
let interval = TimelineController.getInterval();
|
||||
let markers = TimelineController.getMarkers();
|
||||
let memory = TimelineController.getMemory();
|
||||
|
||||
if (markers.length) {
|
||||
let start = (markers[0].start - markers.startTime) * this.overview.dataScaleX;
|
||||
let end = start + this.overview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
|
||||
this.overview.setSelection({ start, end });
|
||||
let start = (markers[0].start - interval.startTime) * this.markersOverview.dataScaleX;
|
||||
let end = start + this.markersOverview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
|
||||
this.markersOverview.setSelection({ start, end });
|
||||
} else {
|
||||
let duration = markers.endTime - markers.startTime;
|
||||
this.waterfall.setData(markers, markers.startTime, markers.endTime);
|
||||
let timeStart = interval.startTime;
|
||||
let timeEnd = interval.endTime;
|
||||
this.waterfall.setData(markers, timeStart, timeStart, timeEnd);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.RECORDING_ENDED);
|
||||
@ -246,12 +347,19 @@ let TimelineView = {
|
||||
/**
|
||||
* Signals that a new set of markers was made available by the controller,
|
||||
* or that the overview graph needs to be updated.
|
||||
*
|
||||
* @param array markers
|
||||
* A list of new markers collected since the recording has started.
|
||||
*/
|
||||
handleMarkersUpdate: function(markers) {
|
||||
this.overview.setData(markers);
|
||||
handleRecordingUpdate: function() {
|
||||
let interval = TimelineController.getInterval();
|
||||
let markers = TimelineController.getMarkers();
|
||||
let memory = TimelineController.getMemory();
|
||||
|
||||
this.markersOverview.setData({ interval, markers });
|
||||
|
||||
// The memory overview graph is not always available.
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.setData({ interval, memory });
|
||||
}
|
||||
|
||||
window.emit(EVENTS.OVERVIEW_UPDATED);
|
||||
},
|
||||
|
||||
@ -259,19 +367,21 @@ let TimelineView = {
|
||||
* Callback handling the "selecting" event on the timeline overview.
|
||||
*/
|
||||
_onSelecting: function() {
|
||||
if (!this.overview.hasSelection() &&
|
||||
!this.overview.hasSelectionInProgress()) {
|
||||
if (!this.markersOverview.hasSelection() &&
|
||||
!this.markersOverview.hasSelectionInProgress()) {
|
||||
this.waterfall.clearView();
|
||||
return;
|
||||
}
|
||||
let selection = this.overview.getSelection();
|
||||
let start = selection.start / this.overview.dataScaleX;
|
||||
let end = selection.end / this.overview.dataScaleX;
|
||||
let selection = this.markersOverview.getSelection();
|
||||
let start = selection.start / this.markersOverview.dataScaleX;
|
||||
let end = selection.end / this.markersOverview.dataScaleX;
|
||||
|
||||
let markers = TimelineController.getMarkers();
|
||||
let timeStart = markers.startTime + Math.min(start, end);
|
||||
let timeEnd = markers.startTime + Math.max(start, end);
|
||||
this.waterfall.setData(markers, timeStart, timeEnd);
|
||||
let interval = TimelineController.getInterval();
|
||||
|
||||
let timeStart = interval.startTime + Math.min(start, end);
|
||||
let timeEnd = interval.startTime + Math.max(start, end);
|
||||
this.waterfall.setData(markers, interval.startTime, timeStart, timeEnd);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -25,13 +25,17 @@
|
||||
class="devtools-toolbarbutton"
|
||||
oncommand="TimelineController.toggleRecording()"
|
||||
tooltiptext="&timelineUI.recordButton.tooltip;"/>
|
||||
<spacer flex="1"/>
|
||||
<checkbox id="memory-checkbox"
|
||||
label="&timelineUI.memoryCheckbox.label;"
|
||||
oncommand="TimelineController.updateMemoryRecording()"
|
||||
tooltiptext="&timelineUI.memoryCheckbox.tooltip;"/>
|
||||
<label id="record-label"
|
||||
value="&timelineUI.recordLabel;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<vbox id="timeline-overview"/>
|
||||
<vbox id="markers-overview"/>
|
||||
<vbox id="memory-overview"/>
|
||||
|
||||
<deck id="timeline-pane"
|
||||
flex="1">
|
||||
|
@ -4,9 +4,9 @@
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "overview" graph, which is a minimap of all the
|
||||
* timeline data. Regions inside it may be selected, determining which markers
|
||||
* are visible in the "waterfall".
|
||||
* This file contains the "markers overview" graph, which is a minimap of all
|
||||
* the timeline data. Regions inside it may be selected, determining which
|
||||
* markers are visible in the "waterfall".
|
||||
*/
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
@ -21,8 +21,8 @@ loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const OVERVIEW_HEADER_HEIGHT = 20; // px
|
||||
const OVERVIEW_BODY_HEIGHT = 50; // px
|
||||
const OVERVIEW_HEADER_HEIGHT = 14; // px
|
||||
const OVERVIEW_BODY_HEIGHT = 55; // 11px * 5 groups
|
||||
|
||||
const OVERVIEW_BACKGROUND_COLOR = "#fff";
|
||||
const OVERVIEW_CLIPHEAD_LINE_COLOR = "#666";
|
||||
@ -33,36 +33,36 @@ const OVERVIEW_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
|
||||
const OVERVIEW_HEADER_TICKS_MULTIPLE = 100; // ms
|
||||
const OVERVIEW_HEADER_TICKS_SPACING_MIN = 75; // px
|
||||
const OVERVIEW_HEADER_SAFE_BOUNDS = 50; // px
|
||||
const OVERVIEW_HEADER_BACKGROUND = "#ebeced";
|
||||
const OVERVIEW_HEADER_BACKGROUND = "#fff";
|
||||
const OVERVIEW_HEADER_TEXT_COLOR = "#18191a";
|
||||
const OVERVIEW_HEADER_TEXT_FONT_SIZE = 9; // px
|
||||
const OVERVIEW_HEADER_TEXT_FONT_FAMILY = "sans-serif";
|
||||
const OVERVIEW_HEADER_TEXT_PADDING = 6; // px
|
||||
const OVERVIEW_TIMELINE_STROKES = "#aaa";
|
||||
const OVERVIEW_HEADER_TEXT_PADDING_LEFT = 6; // px
|
||||
const OVERVIEW_HEADER_TEXT_PADDING_TOP = 1; // px
|
||||
const OVERVIEW_TIMELINE_STROKES = "#ccc";
|
||||
const OVERVIEW_MARKERS_COLOR_STOPS = [0, 0.1, 0.75, 1];
|
||||
const OVERVIEW_MARKER_DURATION_MIN = 4; // ms
|
||||
const OVERVIEW_GROUP_VERTICAL_PADDING = 6; // px
|
||||
const OVERVIEW_GROUP_VERTICAL_PADDING = 5; // px
|
||||
const OVERVIEW_GROUP_ALTERNATING_BACKGROUND = "rgba(0,0,0,0.05)";
|
||||
|
||||
/**
|
||||
* An overview for the timeline data.
|
||||
* An overview for the markers data.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function Overview(parent, ...args) {
|
||||
AbstractCanvasGraph.apply(this, [parent, "timeline-overview", ...args]);
|
||||
function MarkersOverview(parent, ...args) {
|
||||
AbstractCanvasGraph.apply(this, [parent, "markers-overview", ...args]);
|
||||
this.once("ready", () => {
|
||||
// Set the list of names, properties and colors used to paint this overview.
|
||||
this.setBlueprint(TIMELINE_BLUEPRINT);
|
||||
|
||||
var preview = [];
|
||||
preview.startTime = 0;
|
||||
preview.endTime = 1000;
|
||||
this.setData(preview);
|
||||
// Populate this overview with some dummy initial data.
|
||||
this.setData({ interval: { startTime: 0, endTime: 1000 }, markers: [] });
|
||||
});
|
||||
}
|
||||
|
||||
Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
MarkersOverview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
fixedHeight: OVERVIEW_HEADER_HEIGHT + OVERVIEW_BODY_HEIGHT,
|
||||
clipheadLineColor: OVERVIEW_CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: OVERVIEW_SELECTION_LINE_COLOR,
|
||||
@ -83,11 +83,23 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData({ interval: { startTime: 0, endTime: 0 }, markers: [] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Renders the graph's data source.
|
||||
* @see AbstractCanvasGraph.prototype.buildGraphImage
|
||||
*/
|
||||
buildGraphImage: function() {
|
||||
let { interval, markers } = this._data;
|
||||
let { startTime, endTime } = interval;
|
||||
|
||||
let { canvas, ctx } = this._getNamedCanvas("overview-data");
|
||||
let canvasWidth = this._width;
|
||||
let canvasHeight = this._height;
|
||||
@ -97,7 +109,7 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
// Group markers into separate paint batches. This is necessary to
|
||||
// draw all markers sharing the same style at once.
|
||||
|
||||
for (let marker of this._data) {
|
||||
for (let marker of markers) {
|
||||
this._paintBatches.get(marker.name).batch.push(marker);
|
||||
}
|
||||
|
||||
@ -108,7 +120,7 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
let groupHeight = OVERVIEW_BODY_HEIGHT * this._pixelRatio / totalGroups;
|
||||
let groupPadding = OVERVIEW_GROUP_VERTICAL_PADDING * this._pixelRatio;
|
||||
|
||||
let totalTime = (this._data.endTime - this._data.startTime) || 0;
|
||||
let totalTime = (endTime - startTime) || 0;
|
||||
let dataScale = this.dataScaleX = availableWidth / totalTime;
|
||||
|
||||
// Draw the header and overview background.
|
||||
@ -124,7 +136,7 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
ctx.fillStyle = OVERVIEW_GROUP_ALTERNATING_BACKGROUND;
|
||||
ctx.beginPath();
|
||||
|
||||
for (let i = 1; i < totalGroups; i += 2) {
|
||||
for (let i = 0; i < totalGroups; i += 2) {
|
||||
let top = headerHeight + i * groupHeight;
|
||||
ctx.rect(0, top, canvasWidth, groupHeight);
|
||||
}
|
||||
@ -133,24 +145,26 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
|
||||
// Draw the timeline header ticks.
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
let fontSize = OVERVIEW_HEADER_TEXT_FONT_SIZE * this._pixelRatio;
|
||||
let fontFamily = OVERVIEW_HEADER_TEXT_FONT_FAMILY;
|
||||
let textPaddingLeft = OVERVIEW_HEADER_TEXT_PADDING_LEFT * this._pixelRatio;
|
||||
let textPaddingTop = OVERVIEW_HEADER_TEXT_PADDING_TOP * this._pixelRatio;
|
||||
let tickInterval = this._findOptimalTickInterval(dataScale);
|
||||
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = fontSize + "px " + fontFamily;
|
||||
ctx.fillStyle = OVERVIEW_HEADER_TEXT_COLOR;
|
||||
ctx.strokeStyle = OVERVIEW_TIMELINE_STROKES;
|
||||
ctx.beginPath();
|
||||
|
||||
let tickInterval = this._findOptimalTickInterval(dataScale);
|
||||
let headerTextPadding = OVERVIEW_HEADER_TEXT_PADDING * this._pixelRatio;
|
||||
|
||||
for (let x = 0; x < availableWidth; x += tickInterval) {
|
||||
let left = x + headerTextPadding;
|
||||
let lineLeft = x;
|
||||
let textLeft = lineLeft + textPaddingLeft;
|
||||
let time = Math.round(x / dataScale);
|
||||
let label = L10N.getFormatStr("timeline.tick", time);
|
||||
ctx.fillText(label, left, headerHeight / 2 + 1);
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, canvasHeight);
|
||||
ctx.fillText(label, textLeft, headerHeight / 2 + textPaddingTop);
|
||||
ctx.moveTo(lineLeft, 0);
|
||||
ctx.lineTo(lineLeft, canvasHeight);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
@ -170,8 +184,8 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
ctx.beginPath();
|
||||
|
||||
for (let { start, end } of batch) {
|
||||
start -= this._data.startTime;
|
||||
end -= this._data.startTime;
|
||||
start -= interval.startTime;
|
||||
end -= interval.startTime;
|
||||
|
||||
let left = start * dataScale;
|
||||
let duration = Math.max(end - start, OVERVIEW_MARKER_DURATION_MIN);
|
||||
@ -208,4 +222,4 @@ Overview.prototype = Heritage.extend(AbstractCanvasGraph.prototype, {
|
||||
}
|
||||
});
|
||||
|
||||
exports.Overview = Overview;
|
||||
exports.MarkersOverview = MarkersOverview;
|
88
browser/devtools/timeline/widgets/memory-overview.js
Normal file
88
browser/devtools/timeline/widgets/memory-overview.js
Normal file
@ -0,0 +1,88 @@
|
||||
/* 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/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the "memory overview" graph, a simple representation of
|
||||
* of all the memory measurements taken while streaming the timeline data.
|
||||
*/
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
Cu.import("resource:///modules/devtools/Graphs.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
|
||||
loader.lazyRequireGetter(this, "L10N",
|
||||
"devtools/timeline/global", true);
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const OVERVIEW_HEIGHT = 30; // px
|
||||
|
||||
const OVERVIEW_BACKGROUND_COLOR = "#fff";
|
||||
const OVERVIEW_BACKGROUND_GRADIENT_START = "rgba(0,136,204,0.1)";
|
||||
const OVERVIEW_BACKGROUND_GRADIENT_END = "rgba(0,136,204,0.0)";
|
||||
const OVERVIEW_STROKE_WIDTH = 1; // px
|
||||
const OVERVIEW_STROKE_COLOR = "rgba(0,136,204,1)";
|
||||
const OVERVIEW_CLIPHEAD_LINE_COLOR = "#666";
|
||||
const OVERVIEW_SELECTION_LINE_COLOR = "#555";
|
||||
const OVERVIEW_MAXIMUM_LINE_COLOR = "rgba(0,136,204,0.4)";
|
||||
const OVERVIEW_AVERAGE_LINE_COLOR = "rgba(0,136,204,0.7)";
|
||||
const OVERVIEW_MINIMUM_LINE_COLOR = "rgba(0,136,204,0.9)";
|
||||
|
||||
const OVERVIEW_SELECTION_BACKGROUND_COLOR = "rgba(76,158,217,0.25)";
|
||||
const OVERVIEW_SELECTION_STRIPES_COLOR = "rgba(255,255,255,0.1)";
|
||||
|
||||
/**
|
||||
* An overview for the memory data.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function MemoryOverview(parent) {
|
||||
LineGraphWidget.call(this, parent, L10N.getStr("graphs.memory"));
|
||||
|
||||
this.once("ready", () => {
|
||||
// Populate this overview with some dummy initial data.
|
||||
this.setData({ interval: { startTime: 0, endTime: 1000 }, memory: [] });
|
||||
});
|
||||
}
|
||||
|
||||
MemoryOverview.prototype = Heritage.extend(LineGraphWidget.prototype, {
|
||||
dampenValuesFactor: 0.95,
|
||||
fixedHeight: OVERVIEW_HEIGHT,
|
||||
backgroundColor: OVERVIEW_BACKGROUND_COLOR,
|
||||
backgroundGradientStart: OVERVIEW_BACKGROUND_GRADIENT_START,
|
||||
backgroundGradientEnd: OVERVIEW_BACKGROUND_GRADIENT_END,
|
||||
strokeColor: OVERVIEW_STROKE_COLOR,
|
||||
strokeWidth: OVERVIEW_STROKE_WIDTH,
|
||||
maximumLineColor: OVERVIEW_MAXIMUM_LINE_COLOR,
|
||||
averageLineColor: OVERVIEW_AVERAGE_LINE_COLOR,
|
||||
minimumLineColor: OVERVIEW_MINIMUM_LINE_COLOR,
|
||||
clipheadLineColor: OVERVIEW_CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: OVERVIEW_SELECTION_LINE_COLOR,
|
||||
selectionBackgroundColor: OVERVIEW_SELECTION_BACKGROUND_COLOR,
|
||||
selectionStripesColor: OVERVIEW_SELECTION_STRIPES_COLOR,
|
||||
withTooltipArrows: false,
|
||||
withFixedTooltipPositions: true,
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData({ interval: { startTime: 0, endTime: 0 }, memory: [] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the data source for this graph.
|
||||
*/
|
||||
setData: function({ interval, memory }) {
|
||||
this.dataOffsetX = interval.startTime;
|
||||
LineGraphWidget.prototype.setData.call(this, memory);
|
||||
}
|
||||
});
|
||||
|
||||
exports.MemoryOverview = MemoryOverview;
|
@ -22,15 +22,14 @@ loader.lazyImporter(this, "clearNamedTimeout",
|
||||
|
||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||
|
||||
const TIMELINE_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
|
||||
const TIMELINE_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
|
||||
const WATERFALL_SIDEBAR_WIDTH = 150; // px
|
||||
|
||||
const TIMELINE_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const TIMELINE_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const TIMELINE_HEADER_TEXT_PADDING = 3; // px
|
||||
const WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT = 30;
|
||||
const WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY = 75; // ms
|
||||
|
||||
const TIMELINE_MARKER_SIDEBAR_WIDTH = 150; // px
|
||||
const TIMELINE_MARKER_BAR_WIDTH_MIN = 5; // px
|
||||
const WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_HEADER_TICKS_SPACING_MIN = 50; // px
|
||||
const WATERFALL_HEADER_TEXT_PADDING = 3; // px
|
||||
|
||||
const WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
|
||||
const WATERFALL_BACKGROUND_TICKS_SCALES = 3;
|
||||
@ -38,6 +37,7 @@ const WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
|
||||
const WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
|
||||
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
|
||||
|
||||
/**
|
||||
* A detailed waterfall view for the timeline data.
|
||||
@ -52,11 +52,11 @@ function Waterfall(parent) {
|
||||
this._outstandingMarkers = [];
|
||||
|
||||
this._headerContents = this._document.createElement("hbox");
|
||||
this._headerContents.className = "timeline-header-contents";
|
||||
this._headerContents.className = "waterfall-header-contents";
|
||||
this._parent.appendChild(this._headerContents);
|
||||
|
||||
this._listContents = this._document.createElement("vbox");
|
||||
this._listContents.className = "timeline-list-contents";
|
||||
this._listContents.className = "waterfall-list-contents";
|
||||
this._listContents.setAttribute("flex", "1");
|
||||
this._parent.appendChild(this._listContents);
|
||||
|
||||
@ -75,18 +75,21 @@ Waterfall.prototype = {
|
||||
*
|
||||
* @param array markers
|
||||
* A list of markers received from the controller.
|
||||
* @param number timeEpoch
|
||||
* The absolute time (in milliseconds) when the recording started.
|
||||
* @param number timeStart
|
||||
* The time (in milliseconds) to start drawing from.
|
||||
* @param number timeEnd
|
||||
* The time (in milliseconds) to end drawing at.
|
||||
*/
|
||||
setData: function(markers, timeStart, timeEnd) {
|
||||
setData: function(markers, timeEpoch, timeStart, timeEnd) {
|
||||
this.clearView();
|
||||
|
||||
let dataScale = this._waterfallWidth / (timeEnd - timeStart);
|
||||
this._drawWaterfallBackground(dataScale);
|
||||
|
||||
// Label the header as if the first possible marker was at T=0.
|
||||
this._buildHeader(this._headerContents, timeStart - markers.startTime, dataScale);
|
||||
this._buildHeader(this._headerContents, timeStart - timeEpoch, dataScale);
|
||||
this._buildMarkers(this._listContents, markers, timeStart, timeEnd, dataScale);
|
||||
},
|
||||
|
||||
@ -111,7 +114,7 @@ Waterfall.prototype = {
|
||||
*/
|
||||
recalculateBounds: function() {
|
||||
let bounds = this._parent.getBoundingClientRect();
|
||||
this._waterfallWidth = bounds.width - TIMELINE_MARKER_SIDEBAR_WIDTH;
|
||||
this._waterfallWidth = bounds.width - WATERFALL_SIDEBAR_WIDTH;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -126,22 +129,22 @@ Waterfall.prototype = {
|
||||
*/
|
||||
_buildHeader: function(parent, timeStart, dataScale) {
|
||||
let container = this._document.createElement("hbox");
|
||||
container.className = "timeline-header-container";
|
||||
container.className = "waterfall-header-container";
|
||||
container.setAttribute("flex", "1");
|
||||
|
||||
let sidebar = this._document.createElement("hbox");
|
||||
sidebar.className = "timeline-header-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", TIMELINE_MARKER_SIDEBAR_WIDTH);
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
container.appendChild(sidebar);
|
||||
|
||||
let name = this._document.createElement("label");
|
||||
name.className = "plain timeline-header-name";
|
||||
name.className = "plain waterfall-header-name";
|
||||
name.setAttribute("value", this._l10n.getStr("timeline.records"));
|
||||
sidebar.appendChild(name);
|
||||
|
||||
let ticks = this._document.createElement("hbox");
|
||||
ticks.className = "timeline-header-ticks";
|
||||
ticks.className = "waterfall-header-ticks waterfall-background-ticks";
|
||||
ticks.setAttribute("align", "center");
|
||||
ticks.setAttribute("flex", "1");
|
||||
container.appendChild(ticks);
|
||||
@ -149,18 +152,18 @@ Waterfall.prototype = {
|
||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||
let direction = this._isRTL ? -1 : 1;
|
||||
let tickInterval = this._findOptimalTickInterval({
|
||||
ticksMultiple: TIMELINE_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: TIMELINE_HEADER_TICKS_SPACING_MIN,
|
||||
ticksMultiple: WATERFALL_HEADER_TICKS_MULTIPLE,
|
||||
ticksSpacingMin: WATERFALL_HEADER_TICKS_SPACING_MIN,
|
||||
dataScale: dataScale
|
||||
});
|
||||
|
||||
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
|
||||
let start = x + direction * TIMELINE_HEADER_TEXT_PADDING;
|
||||
let start = x + direction * WATERFALL_HEADER_TEXT_PADDING;
|
||||
let time = Math.round(timeStart + x / dataScale);
|
||||
let label = this._l10n.getFormatStr("timeline.tick", time);
|
||||
|
||||
let node = this._document.createElement("label");
|
||||
node.className = "plain timeline-header-tick";
|
||||
node.className = "plain waterfall-header-tick";
|
||||
node.style.transform = "translateX(" + (start - offset) + "px)";
|
||||
node.setAttribute("value", label);
|
||||
ticks.appendChild(node);
|
||||
@ -190,7 +193,7 @@ Waterfall.prototype = {
|
||||
// preserve a snappy UI. After a certain delay, continue building the
|
||||
// outstanding markers while there's (hopefully) no user interaction.
|
||||
let arguments_ = [this._fragment, marker, timeStart, dataScale];
|
||||
if (processed++ < TIMELINE_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
||||
if (processed++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
||||
this._buildMarker.apply(this, arguments_);
|
||||
} else {
|
||||
this._outstandingMarkers.push(arguments_);
|
||||
@ -205,7 +208,7 @@ Waterfall.prototype = {
|
||||
// Otherwise prepare flushing the outstanding markers after a small delay.
|
||||
else {
|
||||
this._setNamedTimeout("flush-outstanding-markers",
|
||||
TIMELINE_FLUSH_OUTSTANDING_MARKERS_DELAY,
|
||||
WATERFALL_FLUSH_OUTSTANDING_MARKERS_DELAY,
|
||||
() => this._buildOutstandingMarkers(parent));
|
||||
}
|
||||
|
||||
@ -241,7 +244,7 @@ Waterfall.prototype = {
|
||||
*/
|
||||
_buildMarker: function(parent, marker, timeStart, dataScale) {
|
||||
let container = this._document.createElement("hbox");
|
||||
container.className = "timeline-marker-container";
|
||||
container.className = "waterfall-marker-container";
|
||||
|
||||
if (marker) {
|
||||
this._buildMarkerSidebar(container, marker);
|
||||
@ -267,12 +270,12 @@ Waterfall.prototype = {
|
||||
let blueprint = this._blueprint[marker.name];
|
||||
|
||||
let sidebar = this._document.createElement("hbox");
|
||||
sidebar.className = "timeline-marker-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", TIMELINE_MARKER_SIDEBAR_WIDTH);
|
||||
sidebar.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebar.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
sidebar.setAttribute("align", "center");
|
||||
|
||||
let bullet = this._document.createElement("hbox");
|
||||
bullet.className = "timeline-marker-bullet";
|
||||
bullet.className = "waterfall-marker-bullet";
|
||||
bullet.style.backgroundColor = blueprint.fill;
|
||||
bullet.style.borderColor = blueprint.stroke;
|
||||
bullet.setAttribute("type", marker.name);
|
||||
@ -281,7 +284,7 @@ Waterfall.prototype = {
|
||||
let name = this._document.createElement("label");
|
||||
name.setAttribute("crop", "end");
|
||||
name.setAttribute("flex", "1");
|
||||
name.className = "plain timeline-marker-name";
|
||||
name.className = "plain waterfall-marker-name";
|
||||
|
||||
let label;
|
||||
if (marker.detail && marker.detail.causeName) {
|
||||
@ -314,7 +317,8 @@ Waterfall.prototype = {
|
||||
let blueprint = this._blueprint[marker.name];
|
||||
|
||||
let waterfall = this._document.createElement("hbox");
|
||||
waterfall.className = "timeline-marker-waterfall";
|
||||
waterfall.className = "waterfall-marker-item waterfall-background-ticks";
|
||||
waterfall.setAttribute("align", "center");
|
||||
waterfall.setAttribute("flex", "1");
|
||||
|
||||
let start = (marker.start - timeStart) * dataScale;
|
||||
@ -322,12 +326,12 @@ Waterfall.prototype = {
|
||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||
|
||||
let bar = this._document.createElement("hbox");
|
||||
bar.className = "timeline-marker-bar";
|
||||
bar.className = "waterfall-marker-bar";
|
||||
bar.style.backgroundColor = blueprint.fill;
|
||||
bar.style.borderColor = blueprint.stroke;
|
||||
bar.style.transform = "translateX(" + (start - offset) + "px)";
|
||||
bar.setAttribute("type", marker.name);
|
||||
bar.setAttribute("width", Math.max(width, TIMELINE_MARKER_BAR_WIDTH_MIN));
|
||||
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));
|
||||
waterfall.appendChild(bar);
|
||||
|
||||
container.appendChild(waterfall);
|
||||
@ -341,11 +345,11 @@ Waterfall.prototype = {
|
||||
*/
|
||||
_buildMarkerSpacer: function(container) {
|
||||
let sidebarSpacer = this._document.createElement("spacer");
|
||||
sidebarSpacer.className = "timeline-marker-sidebar theme-sidebar";
|
||||
sidebarSpacer.setAttribute("width", TIMELINE_MARKER_SIDEBAR_WIDTH);
|
||||
sidebarSpacer.className = "waterfall-sidebar theme-sidebar";
|
||||
sidebarSpacer.setAttribute("width", WATERFALL_SIDEBAR_WIDTH);
|
||||
|
||||
let waterfallSpacer = this._document.createElement("spacer");
|
||||
waterfallSpacer.className = "timeline-marker-waterfall";
|
||||
waterfallSpacer.className = "waterfall-marker-item waterfall-background-ticks";
|
||||
waterfallSpacer.setAttribute("flex", "1");
|
||||
|
||||
container.appendChild(sidebarSpacer);
|
||||
|
@ -15,10 +15,19 @@
|
||||
- on a button that starts a new recording. -->
|
||||
<!ENTITY timelineUI.recordButton.tooltip "Record timeline operations">
|
||||
|
||||
<!-- LOCALIZATION NOTE (timelineUI.recordButton): This string is displayed
|
||||
<!-- LOCALIZATION NOTE (timelineUI.recordLabel): This string is displayed
|
||||
- as a label to signal that a recording is in progress. -->
|
||||
<!ENTITY timelineUI.recordLabel "Recording…">
|
||||
|
||||
<!-- LOCALIZATION NOTE (timelineUI.timelineUI.memoryCheckbox.label): This string
|
||||
- is displayed next to a checkbox determining whether or not memory
|
||||
- measurements are enabled. -->
|
||||
<!ENTITY timelineUI.memoryCheckbox.label "Memory">
|
||||
|
||||
<!-- LOCALIZATION NOTE (timelineUI.timelineUI.memoryCheckbox.tooltip): This string
|
||||
- is displayed next to the memory checkbox -->
|
||||
<!ENTITY timelineUI.memoryCheckbox.tooltip "Enable memory measurements">
|
||||
|
||||
<!-- LOCALIZATION NOTE (timelineUI.emptyNotice1/2): This is the label shown
|
||||
- in the timeline view when empty. -->
|
||||
<!ENTITY timelineUI.emptyNotice1 "Click on the">
|
||||
|
@ -41,6 +41,12 @@ timeline.label.paint=Paint
|
||||
timeline.label.domevent=DOM Event
|
||||
timeline.label.consoleTime=Console
|
||||
|
||||
# LOCALIZATION NOTE (graphs.memory):
|
||||
# This string is displayed in the memory graph of the Performance tool,
|
||||
# as the unit used to memory consumption. This label should be kept
|
||||
# AS SHORT AS POSSIBLE so it doesn't obstruct important parts of the graph.
|
||||
graphs.memory=MB
|
||||
|
||||
# LOCALIZATION NOTE (timeline.markerDetailFormat):
|
||||
# Some timeline markers come with details, like a size, a name, a js function.
|
||||
# %1$S is replaced with one of the above label (timeline.label.*) and %2$S
|
||||
|
@ -48,23 +48,22 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-dark #timeline-overview {
|
||||
border-bottom: 1px solid #000;
|
||||
.theme-dark #timeline-pane {
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
|
||||
.theme-light #timeline-overview {
|
||||
border-bottom: 1px solid #aaa;
|
||||
.theme-light #timeline-pane {
|
||||
border-top: 1px solid #aaa;
|
||||
}
|
||||
|
||||
.timeline-list-contents {
|
||||
.waterfall-list-contents {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.timeline-header-ticks,
|
||||
.timeline-marker-waterfall {
|
||||
.waterfall-background-ticks {
|
||||
/* Background created on a <canvas> in js. */
|
||||
/* @see browser/devtools/timeline/widgets/waterfall.js */
|
||||
background-image: -moz-element(#waterfall-background);
|
||||
@ -72,76 +71,69 @@
|
||||
background-position: -1px center;
|
||||
}
|
||||
|
||||
.timeline-marker-waterfall {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.timeline-marker-container[is-spacer] {
|
||||
.waterfall-marker-container[is-spacer] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.theme-dark .timeline-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
.theme-dark .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
background-color: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.theme-light .timeline-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
.theme-light .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
background-color: rgba(128,128,128,0.03);
|
||||
}
|
||||
|
||||
.theme-dark .timeline-marker-container:hover {
|
||||
.theme-dark .waterfall-marker-container:hover {
|
||||
background-color: rgba(255,255,255,0.1) !important;
|
||||
}
|
||||
|
||||
.theme-light .timeline-marker-container:hover {
|
||||
.theme-light .waterfall-marker-container:hover {
|
||||
background-color: rgba(128,128,128,0.1) !important;
|
||||
}
|
||||
|
||||
.timeline-header-sidebar,
|
||||
.timeline-marker-sidebar {
|
||||
.waterfall-marker-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.waterfall-sidebar {
|
||||
-moz-border-end: 1px solid;
|
||||
}
|
||||
|
||||
.theme-dark .timeline-header-sidebar,
|
||||
.theme-dark .timeline-marker-sidebar {
|
||||
.theme-dark .waterfall-sidebar {
|
||||
-moz-border-end-color: #000;
|
||||
}
|
||||
|
||||
.theme-light .timeline-header-sidebar,
|
||||
.theme-light .timeline-marker-sidebar {
|
||||
.theme-light .waterfall-sidebar {
|
||||
-moz-border-end-color: #aaa;
|
||||
}
|
||||
|
||||
.timeline-header-sidebar {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.timeline-marker-sidebar {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.timeline-marker-container:hover > .timeline-marker-sidebar {
|
||||
.waterfall-marker-container:hover > .waterfall-sidebar {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.timeline-header-tick {
|
||||
.waterfall-header-name {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.waterfall-header-tick {
|
||||
width: 100px;
|
||||
font-size: 9px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.theme-dark .timeline-header-tick {
|
||||
.theme-dark .waterfall-header-tick {
|
||||
color: #a9bacb;
|
||||
}
|
||||
|
||||
.theme-light .timeline-header-tick {
|
||||
.theme-light .waterfall-header-tick {
|
||||
color: #292e33;
|
||||
}
|
||||
|
||||
.timeline-header-tick:not(:first-child) {
|
||||
.waterfall-header-tick:not(:first-child) {
|
||||
-moz-margin-start: -100px !important; /* Don't affect layout. */
|
||||
}
|
||||
|
||||
.timeline-marker-bullet {
|
||||
.waterfall-marker-bullet {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
-moz-margin-start: 8px;
|
||||
@ -150,9 +142,13 @@
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.timeline-marker-bar {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
.waterfall-marker-name {
|
||||
font-size: 95%;
|
||||
padding-bottom: 1px !important;
|
||||
}
|
||||
|
||||
.waterfall-marker-bar {
|
||||
height: 9px;
|
||||
border: 1px solid;
|
||||
border-radius: 1px;
|
||||
transform-origin: left center;
|
||||
|
@ -938,10 +938,6 @@
|
||||
|
||||
/* Line graph widget */
|
||||
|
||||
.line-graph-widget-canvas {
|
||||
background: #0088cc;
|
||||
}
|
||||
|
||||
.line-graph-widget-gutter {
|
||||
position: absolute;
|
||||
background: rgba(255,255,255,0.75);
|
||||
@ -957,7 +953,6 @@
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
border-top: 1px solid;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.line-graph-widget-gutter-line[type=maximum] {
|
||||
@ -975,7 +970,6 @@
|
||||
.line-graph-widget-tooltip {
|
||||
position: absolute;
|
||||
background: rgba(255,255,255,0.75);
|
||||
box-shadow: 0 2px 1px rgba(0,0,0,0.1);
|
||||
border-radius: 2px;
|
||||
line-height: 15px;
|
||||
-moz-padding-start: 6px;
|
||||
@ -986,7 +980,7 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip::before {
|
||||
.line-graph-widget-tooltip[with-arrows=true]::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-top: 3px solid transparent;
|
||||
@ -994,26 +988,38 @@
|
||||
top: calc(50% - 3px);
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[arrow=start]::before {
|
||||
.line-graph-widget-tooltip[arrow=start][with-arrows=true]::before {
|
||||
-moz-border-end: 3px solid rgba(255,255,255,0.75);
|
||||
left: -3px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[arrow=end]::before {
|
||||
.line-graph-widget-tooltip[arrow=end][with-arrows=true]::before {
|
||||
-moz-border-start: 3px solid rgba(255,255,255,0.75);
|
||||
right: -3px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[type=maximum] {
|
||||
left: calc(10px + 6px);
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[type=minimum] {
|
||||
left: calc(10px + 6px);
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[type=average] {
|
||||
right: 6px;
|
||||
right: -1px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[type=maximum][with-arrows=true] {
|
||||
left: 14px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[type=minimum][with-arrows=true] {
|
||||
left: 14px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip[type=average][with-arrows=true] {
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.line-graph-widget-tooltip > [text=info] {
|
||||
|
Loading…
Reference in New Issue
Block a user