Bug 1074106 - [timeline] marker sidebar. r=vporof

This commit is contained in:
Paul Rouget 2014-11-24 05:55:25 +01:00
parent f5cb282c1f
commit 2478fc4e36
11 changed files with 508 additions and 37 deletions

View File

@ -6,6 +6,7 @@
EXTRA_JS_MODULES.devtools.timeline += [
'panel.js',
'widgets/global.js',
'widgets/marker-details.js',
'widgets/markers-overview.js',
'widgets/memory-overview.js',
'widgets/waterfall.js'

View File

@ -15,3 +15,4 @@ support-files =
[browser_timeline_waterfall-background.js]
[browser_timeline_waterfall-generic.js]
[browser_timeline_waterfall-styles.js]
[browser_timeline_waterfall-sidebar.js]

View File

@ -34,7 +34,7 @@ let test = Task.async(function*() {
is($("#record-button").hasAttribute("checked"), false,
"The record button should be unchecked again.");
is($("#timeline-pane").selectedPanel, $("#timeline-waterfall"),
is($("#timeline-pane").selectedPanel, $("#timeline-waterfall-container"),
"A waterfall view is now displayed.");
yield teardown(panel);

View File

@ -0,0 +1,59 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if the sidebar is properly updated when a marker is selected.
*/
let test = Task.async(function*() {
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
let { $, $$, EVENTS, TimelineController, TimelineView } = panel.panelWin;
let { L10N } = devtools.require("devtools/timeline/global");
yield TimelineController.toggleRecording();
ok(true, "Recording has started.");
yield waitUntil(() => {
// Wait until we get 3 different markers.
let markers = TimelineController.getMarkers();
return markers.some(m => m.name == "Styles") &&
markers.some(m => m.name == "Reflow") &&
markers.some(m => m.name == "Paint");
});
yield TimelineController.toggleRecording();
ok(true, "Recording has ended.");
// Select everything
TimelineView.markersOverview.setSelection({ start: 0, end: TimelineView.markersOverview.width })
let bars = $$(".waterfall-marker-item:not(spacer) > .waterfall-marker-bar");
let markers = TimelineController.getMarkers();
ok(bars.length > 2, "got at least 3 markers");
let sidebar = $("#timeline-waterfall-details");
for (let i = 0; i < bars.length; i++) {
let bar = bars[i];
bar.click();
let m = markers[i];
is($("#timeline-waterfall-details .marker-details-type").getAttribute("value"), m.name,
"sidebar title matches markers name");
let printedStartTime = $(".marker-details-start .marker-details-labelvalue").getAttribute("value");
let printedEndTime = $(".marker-details-end .marker-details-labelvalue").getAttribute("value");
let printedDuration= $(".marker-details-duration .marker-details-labelvalue").getAttribute("value");
let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
// Values are rounded. We don't use a strict equality.
is(toMs(m.start), printedStartTime, "sidebar start time is valid");
is(toMs(m.end), printedEndTime, "sidebar end time is valid");
is(toMs(m.end - m.start), printedDuration, "sidebar duration is valid");
}
yield teardown(panel);
finish();
});

View File

@ -10,12 +10,11 @@
<body>
<script type="text/javascript">
var x = 1;
function test() {
var a = "Hello world!";
document.body.style.backgroundColor = "rgba(" +
((Math.random() * 64)|0) + "," +
((Math.random() * 16)|0) + "," +
((Math.random() * 16)|0) + ",1)";
document.body.style.borderTop = x + "px solid red";
x = 1^x;
document.body.innerHeight; // flush pending reflows
}
// Prevent this script from being garbage collected.

View File

@ -18,6 +18,8 @@ devtools.lazyRequireGetter(this, "MemoryOverview",
"devtools/timeline/memory-overview", true);
devtools.lazyRequireGetter(this, "Waterfall",
"devtools/timeline/waterfall", true);
devtools.lazyRequireGetter(this, "MarkerDetails",
"devtools/timeline/marker-details", true);
devtools.lazyImporter(this, "CanvasGraphUtils",
"resource:///modules/devtools/Graphs.jsm");
@ -250,11 +252,17 @@ let TimelineView = {
initialize: Task.async(function*() {
this.markersOverview = new MarkersOverview($("#markers-overview"));
this.waterfall = new Waterfall($("#timeline-waterfall"));
this.markerDetails = new MarkerDetails($("#timeline-waterfall-details"));
this._onSelecting = this._onSelecting.bind(this);
this._onRefresh = this._onRefresh.bind(this);
this.markersOverview.on("selecting", this._onSelecting);
this.markersOverview.on("refresh", this._onRefresh);
this.markerDetails.on("resize", this._onRefresh);
this._onMarkerSelected = this._onMarkerSelected.bind(this);
this.waterfall.on("selected", this._onMarkerSelected);
this.waterfall.on("unselected", this._onMarkerSelected);
yield this.markersOverview.ready();
yield this.waterfall.recalculateBounds();
@ -264,6 +272,9 @@ let TimelineView = {
* Destruction function, called when the tool is closed.
*/
destroy: function() {
this.markerDetails.off("resize", this._onRefresh);
this.waterfall.off("selected", this._onMarkerSelected);
this.waterfall.off("unselected", this._onMarkerSelected);
this.markersOverview.off("selecting", this._onSelecting);
this.markersOverview.off("refresh", this._onRefresh);
this.markersOverview.destroy();
@ -300,6 +311,18 @@ let TimelineView = {
this.memoryOverview = null;
},
/**
* A marker has been selected in the waterfall.
*/
_onMarkerSelected: function(event, marker) {
if (event == "selected") {
this.markerDetails.render(marker);
}
if (event == "unselected") {
this.markerDetails.empty();
}
},
/**
* Signals that a recording session has started and triggers the appropriate
* changes in the UI.
@ -328,7 +351,7 @@ let TimelineView = {
handleRecordingEnded: function() {
$("#record-button").removeAttribute("checked");
$("#memory-checkbox").removeAttribute("disabled");
$("#timeline-pane").selectedPanel = $("#timeline-waterfall");
$("#timeline-pane").selectedPanel = $("#timeline-waterfall-container");
this.markersOverview.selectionEnabled = true;
@ -346,9 +369,9 @@ let TimelineView = {
let end = start + this.markersOverview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
this.markersOverview.setSelection({ start, end });
} else {
let timeStart = interval.startTime;
let timeEnd = interval.endTime;
this.waterfall.setData(markers, timeStart, timeStart, timeEnd);
let startTime = interval.startTime;
let endTime = interval.endTime;
this.waterfall.setData(markers, startTime, startTime, endTime);
}
window.emit(EVENTS.RECORDING_ENDED);
@ -382,6 +405,14 @@ let TimelineView = {
this.waterfall.clearView();
return;
}
this.waterfall.resetSelection();
this.updateWaterfall();
},
/**
* Rebuild the waterfall.
*/
updateWaterfall: function() {
let selection = this.markersOverview.getSelection();
let start = selection.start / this.markersOverview.dataScaleX;
let end = selection.end / this.markersOverview.dataScaleX;
@ -389,9 +420,10 @@ let TimelineView = {
let markers = TimelineController.getMarkers();
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);
let startTime = interval.startTime + Math.min(start, end);
let endTime = interval.startTime + Math.max(start, end);
this.waterfall.setData(markers, interval.startTime, startTime, endTime);
},
/**
@ -399,7 +431,7 @@ let TimelineView = {
*/
_onRefresh: function() {
this.waterfall.recalculateBounds();
this._onSelecting();
this.updateWaterfall();
}
};

View File

@ -4,8 +4,10 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/timeline.css" type="text/css"?>
<!DOCTYPE window [
<!ENTITY % timelineDTD SYSTEM "chrome://browser/locale/devtools/timeline.dtd">
%timelineDTD;
@ -66,7 +68,11 @@
<label value="&timelineUI.stopNotice2;"/>
</hbox>
<vbox id="timeline-waterfall" flex="1"/>
<hbox id="timeline-waterfall-container" class="devtools-responsive-container" flex="1">
<vbox id="timeline-waterfall" flex="1"/>
<splitter class="devtools-side-splitter"/>
<vbox id="timeline-waterfall-details" class="theme-sidebar" width="150" height="150"/>
</hbox>
</deck>
</vbox>
</window>

View File

@ -0,0 +1,184 @@
/* 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";
let { Ci } = require("chrome");
/**
* This file contains the rendering code for the marker sidebar.
*/
loader.lazyRequireGetter(this, "L10N",
"devtools/timeline/global", true);
loader.lazyRequireGetter(this, "TIMELINE_BLUEPRINT",
"devtools/timeline/global", true);
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
/**
* A detailed view for one single marker.
*
* @param nsIDOMNode parent
* The parent node holding the view.
*/
function MarkerDetails(parent) {
EventEmitter.decorate(this);
this._document = parent.ownerDocument;
this._parent = parent;
this._splitter = this._document.querySelector("#timeline-waterfall-container > splitter");
this._splitter.addEventListener("mouseup", () => this.emit("resize"));
}
MarkerDetails.prototype = {
destroy: function() {
this.empty();
this._parent = null;
},
/**
* Clears the view.
*/
empty: function() {
this._parent.innerHTML = "";
},
/**
* Builds the label representing marker's type.
*
* @param string type
* Could be "Paint", "Reflow", "Styles", ...
* See TIMELINE_BLUEPRINT in widgets/global.js
*/
buildMarkerTypeLabel: function(type) {
let blueprint = TIMELINE_BLUEPRINT[type];
let hbox = this._document.createElement("hbox");
hbox.setAttribute("align", "center");
let bullet = this._document.createElement("hbox");
bullet.className = "marker-details-bullet";
bullet.style.backgroundColor = blueprint.fill;
bullet.style.borderColor = blueprint.stroke;
let label = this._document.createElement("label");
label.className = "marker-details-type";
label.setAttribute("value", blueprint.label);
hbox.appendChild(bullet);
hbox.appendChild(label);
return hbox;
},
/**
* Builds labels for name:value pairs. Like "Start: 100ms",
* "Duration: 200ms", ...
*
* @param string l10nName
* String identifier for label's name.
* @param string value
* Label's value.
*/
buildNameValueLabel: function(l10nName, value) {
let hbox = this._document.createElement("hbox");
let labelName = this._document.createElement("label");
let labelValue = this._document.createElement("label");
labelName.className = "marker-details-labelname";
labelValue.className = "marker-details-labelvalue";
labelName.setAttribute("value", L10N.getStr(l10nName));
labelValue.setAttribute("value", value);
hbox.appendChild(labelName);
hbox.appendChild(labelValue);
return hbox;
},
/**
* Populates view with marker's details.
*
* @param object marker
* The marker to display.
*/
render: function(marker) {
this.empty();
// UI for any marker
let title = this.buildMarkerTypeLabel(marker.name);
let toMs = ms => L10N.getFormatStrWithNumbers("timeline.tick", ms);
let start = this.buildNameValueLabel("timeline.markerDetail.start", toMs(marker.start));
let end = this.buildNameValueLabel("timeline.markerDetail.end", toMs(marker.end));
let duration = this.buildNameValueLabel("timeline.markerDetail.duration", toMs(marker.end - marker.start));
start.classList.add("marker-details-start");
end.classList.add("marker-details-end");
duration.classList.add("marker-details-duration");
this._parent.appendChild(title);
this._parent.appendChild(start);
this._parent.appendChild(end);
this._parent.appendChild(duration);
// UI for specific markers
switch (marker.name) {
case "ConsoleTime":
this.renderConsoleTimeMarker(this._parent, marker);
break;
case "DOMEvent":
this.renderDOMEventMarker(this._parent, marker);
break;
default:
}
},
/**
* Render details of a console marker (console.time).
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderConsoleTimeMarker: function(parent, marker) {
if ("causeName" in marker) {
let timerName = this.buildNameValueLabel("timeline.markerDetail.consoleTimerName", marker.causeName);
this._parent.appendChild(timerName);
}
},
/**
* Render details of a DOM Event marker.
*
* @param nsIDOMNode parent
* The parent node holding the view.
* @param object marker
* The marker to display.
*/
renderDOMEventMarker: function(parent, marker) {
if ("type" in marker) {
let type = this.buildNameValueLabel("timeline.markerDetail.DOMEventType", marker.type);
this._parent.appendChild(type);
}
if ("eventPhase" in marker) {
let phaseL10NProp;
if (marker.eventPhase == Ci.nsIDOMEvent.AT_TARGET) {
phaseL10NProp = "timeline.markerDetail.DOMEventTargetPhase";
}
if (marker.eventPhase == Ci.nsIDOMEvent.CAPTURING_PHASE) {
phaseL10NProp = "timeline.markerDetail.DOMEventCapturingPhase";
}
if (marker.eventPhase == Ci.nsIDOMEvent.BUBBLING_PHASE) {
phaseL10NProp = "timeline.markerDetail.DOMEventBubblingPhase";
}
let phase = this.buildNameValueLabel("timeline.markerDetail.DOMEventPhase", L10N.getStr(phaseL10NProp));
this._parent.appendChild(phase);
}
},
}
exports.MarkerDetails = MarkerDetails;

View File

@ -8,7 +8,7 @@
* of all the markers in the timeline data.
*/
const {Cc, Ci, Cu, Cr} = require("chrome");
const {Ci, Cu} = require("chrome");
loader.lazyRequireGetter(this, "L10N",
"devtools/timeline/global", true);
@ -19,6 +19,8 @@ loader.lazyImporter(this, "setNamedTimeout",
"resource:///modules/devtools/ViewHelpers.jsm");
loader.lazyImporter(this, "clearNamedTimeout",
"resource:///modules/devtools/ViewHelpers.jsm");
loader.lazyRequireGetter(this, "EventEmitter",
"devtools/toolkit/event-emitter");
const HTML_NS = "http://www.w3.org/1999/xhtml";
@ -39,6 +41,8 @@ const WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32; // byte
const WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
/**
* A detailed waterfall view for the timeline data.
*
@ -46,6 +50,7 @@ const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
* The parent node holding the waterfall.
*/
function Waterfall(parent) {
EventEmitter.decorate(this);
this._parent = parent;
this._document = parent.ownerDocument;
this._fragment = this._document.createDocumentFragment();
@ -60,6 +65,8 @@ function Waterfall(parent) {
this._listContents.setAttribute("flex", "1");
this._parent.appendChild(this._listContents);
this.setupKeys();
this._isRTL = this._getRTL();
// Lazy require is a bit slow, and these are hot objects.
@ -67,6 +74,10 @@ function Waterfall(parent) {
this._blueprint = TIMELINE_BLUEPRINT;
this._setNamedTimeout = setNamedTimeout;
this._clearNamedTimeout = clearNamedTimeout;
// Selected row index. By default, we want the first
// row to be selected.
this._selectedRowIdx = 0;
}
Waterfall.prototype = {
@ -77,20 +88,55 @@ Waterfall.prototype = {
* A list of markers received from the controller.
* @param number timeEpoch
* The absolute time (in milliseconds) when the recording started.
* @param number timeStart
* @param number startTime
* The time (in milliseconds) to start drawing from.
* @param number timeEnd
* @param number endTime
* The time (in milliseconds) to end drawing at.
*/
setData: function(markers, timeEpoch, timeStart, timeEnd) {
setData: function(markers, timeEpoch, startTime, endTime) {
this.clearView();
this._markers = markers;
let dataScale = this._waterfallWidth / (timeEnd - timeStart);
let dataScale = this._waterfallWidth / (endTime - startTime);
this._drawWaterfallBackground(dataScale);
// Label the header as if the first possible marker was at T=0.
this._buildHeader(this._headerContents, timeStart - timeEpoch, dataScale);
this._buildMarkers(this._listContents, markers, timeStart, timeEnd, dataScale);
this._buildHeader(this._headerContents, startTime - timeEpoch, dataScale);
this._buildMarkers(this._listContents, markers, startTime, endTime, dataScale);
this.selectRow(this._selectedRowIdx);
},
/**
* Keybindings.
*/
setupKeys: function() {
let pane = this._document.querySelector("#timeline-pane");
pane.parentNode.parentNode.addEventListener("keydown", e => {
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) {
e.preventDefault();
this.selectNearestRow(this._selectedRowIdx - 1);
}
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
e.preventDefault();
this.selectNearestRow(this._selectedRowIdx + 1);
}
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME) {
e.preventDefault();
this.selectNearestRow(0);
}
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END) {
e.preventDefault();
this.selectNearestRow(this._listContents.children.length);
}
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
e.preventDefault();
this.selectNearestRow(this._selectedRowIdx - WATERFALL_ROWCOUNT_ONPAGEUPDOWN);
}
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
e.preventDefault();
this.selectNearestRow(this._selectedRowIdx + WATERFALL_ROWCOUNT_ONPAGEUPDOWN);
}
}, true);
},
/**
@ -122,12 +168,12 @@ Waterfall.prototype = {
*
* @param nsIDOMNode parent
* The parent node holding the header.
* @param number timeStart
* @param number startTime
* @see Waterfall.prototype.setData
* @param number dataScale
* The time scale of the data source.
*/
_buildHeader: function(parent, timeStart, dataScale) {
_buildHeader: function(parent, startTime, dataScale) {
let container = this._document.createElement("hbox");
container.className = "waterfall-header-container";
container.setAttribute("flex", "1");
@ -159,7 +205,7 @@ Waterfall.prototype = {
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
let start = x + direction * WATERFALL_HEADER_TEXT_PADDING;
let time = Math.round(timeStart + x / dataScale);
let time = Math.round(startTime + x / dataScale);
let label = this._l10n.getFormatStr("timeline.tick", time);
let node = this._document.createElement("label");
@ -177,23 +223,25 @@ Waterfall.prototype = {
*
* @param nsIDOMNode parent
* The parent node holding the markers.
* @param number timeStart
* @param number startTime
* @see Waterfall.prototype.setData
* @param number dataScale
* The time scale of the data source.
*/
_buildMarkers: function(parent, markers, timeStart, timeEnd, dataScale) {
let processed = 0;
_buildMarkers: function(parent, markers, startTime, endTime, dataScale) {
let rowsCount = 0;
let markerIdx = -1;
for (let marker of markers) {
if (!isMarkerInRange(marker, timeStart, timeEnd)) {
markerIdx++;
if (!isMarkerInRange(marker, startTime, endTime)) {
continue;
}
// Only build and display a finite number of markers initially, to
// 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++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
let arguments_ = [this._fragment, marker, startTime, dataScale, markerIdx, rowsCount];
if (rowsCount++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
this._buildMarker.apply(this, arguments_);
} else {
this._outstandingMarkers.push(arguments_);
@ -228,6 +276,7 @@ Waterfall.prototype = {
}
this._outstandingMarkers.length = 0;
parent.appendChild(this._fragment);
this.selectRow(this._selectedRowIdx);
},
/**
@ -237,18 +286,24 @@ Waterfall.prototype = {
* The parent node holding the marker.
* @param object marker
* The { name, start, end } marker in the data source.
* @param timeStart
* @param startTime
* @see Waterfall.prototype.setData
* @param number dataScale
* @see Waterfall.prototype._buildMarkers
* @param number markerIdx
* Index of the marker in this._markers
* @param number rowIdx
* Index of current row
*/
_buildMarker: function(parent, marker, timeStart, dataScale) {
_buildMarker: function(parent, marker, startTime, dataScale, markerIdx, rowIdx) {
let container = this._document.createElement("hbox");
container.setAttribute("markerIdx", markerIdx);
container.className = "waterfall-marker-container";
if (marker) {
this._buildMarkerSidebar(container, marker);
this._buildMarkerWaterfall(container, marker, timeStart, dataScale);
this._buildMarkerWaterfall(container, marker, startTime, dataScale, markerIdx);
container.onclick = () => this.selectRow(rowIdx);
} else {
this._buildMarkerSpacer(container);
container.setAttribute("flex", "1");
@ -258,6 +313,83 @@ Waterfall.prototype = {
parent.appendChild(container);
},
/**
* Select first row.
*/
resetSelection: function() {
this.selectRow(0);
},
/**
* Select a marker in the waterfall.
*
* @param number idx
* Index of the row to select. -1 clears the selection.
*/
selectRow: function(idx) {
// Unselect
let prev = this._listContents.children[this._selectedRowIdx];
if (prev) {
prev.classList.remove("selected");
}
this._selectedRowIdx = idx;
let row = this._listContents.children[idx];
if (row && !row.hasAttribute("is-spacer")) {
row.focus();
row.classList.add("selected");
let markerIdx = row.getAttribute("markerIdx");
this.emit("selected", this._markers[markerIdx]);
this.ensureRowIsVisible(row);
} else {
this.emit("unselected");
}
},
/**
* Find a valid row to select.
*
* @param number idx
* Index of the row to select.
*/
selectNearestRow: function(idx) {
if (this._listContents.children.length == 0) {
return;
}
idx = Math.max(idx, 0);
idx = Math.min(idx, this._listContents.children.length - 1);
let row = this._listContents.children[idx];
if (row && row.hasAttribute("is-spacer")) {
if (idx > 0) {
return this.selectNearestRow(idx - 1);
} else {
return;
}
}
this.selectRow(idx);
},
/**
* Scroll waterfall to ensure row is in the viewport.
*
* @param number idx
* Index of the row to select.
*/
ensureRowIsVisible: function(row) {
let parent = row.parentNode;
let parentRect = parent.getBoundingClientRect();
let rowRect = row.getBoundingClientRect();
let yDelta = rowRect.top - parentRect.top;
if (yDelta < 0) {
parent.scrollTop += yDelta;
}
yDelta = parentRect.bottom - rowRect.bottom;
if (yDelta < 0) {
parent.scrollTop -= yDelta;
}
},
/**
* Creates the sidebar part of a marker in this view.
*
@ -308,12 +440,12 @@ Waterfall.prototype = {
* The container node representing the marker.
* @param object marker
* @see Waterfall.prototype._buildMarker
* @param timeStart
* @param startTime
* @see Waterfall.prototype.setData
* @param number dataScale
* @see Waterfall.prototype._buildMarkers
*/
_buildMarkerWaterfall: function(container, marker, timeStart, dataScale) {
_buildMarkerWaterfall: function(container, marker, startTime, dataScale) {
let blueprint = this._blueprint[marker.name];
let waterfall = this._document.createElement("hbox");
@ -321,7 +453,7 @@ Waterfall.prototype = {
waterfall.setAttribute("align", "center");
waterfall.setAttribute("flex", "1");
let start = (marker.start - timeStart) * dataScale;
let start = (marker.start - startTime) * dataScale;
let width = (marker.end - marker.start) * dataScale;
let offset = this._isRTL ? this._waterfallWidth : 0;
@ -329,6 +461,8 @@ Waterfall.prototype = {
bar.className = "waterfall-marker-bar";
bar.style.backgroundColor = blueprint.fill;
bar.style.borderColor = blueprint.stroke;
// Save border color. It will change when marker is selected.
bar.setAttribute("borderColor", blueprint.stroke);
bar.style.transform = "translateX(" + (start - offset) + "px)";
bar.setAttribute("type", marker.name);
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));

View File

@ -53,3 +53,15 @@ graphs.memory=MB
# %1$S is replaced with one of the above label (timeline.label.*) and %2$S
# with the details. For examples: Paint (200x100), or console.time (FOO)
timeline.markerDetailFormat=%1$S (%2$S)
# LOCALIZATION NOTE (time.markerDetail.*):
# Strings used in the waterfall sidebar.
timeline.markerDetail.start=Start:
timeline.markerDetail.end=End:
timeline.markerDetail.duration=Duration:
timeline.markerDetail.consoleTimerName=Timer Name:
timeline.markerDetail.DOMEventType=Event Type:
timeline.markerDetail.DOMEventPhase=Phase:
timeline.markerDetail.DOMEventTargetPhase=Target
timeline.markerDetail.DOMEventCapturingPhase=Capture
timeline.markerDetail.DOMEventBubblingPhase=Bubbling

View File

@ -63,6 +63,10 @@
overflow-y: auto;
}
.waterfall-header-contents {
overflow-x: hidden;
}
.waterfall-background-ticks {
/* Background created on a <canvas> in js. */
/* @see browser/devtools/timeline/widgets/waterfall.js */
@ -153,3 +157,42 @@
border-radius: 1px;
transform-origin: left center;
}
.theme-light .waterfall-marker-container.selected > .waterfall-sidebar,
.theme-light .waterfall-marker-container.selected > .waterfall-marker-item {
background-color: #4c9ed9; /* Select Highlight Blue */
color: #f5f7fa; /* Light foreground text */
}
.theme-dark .waterfall-marker-container.selected > .waterfall-sidebar,
.theme-dark .waterfall-marker-container.selected > .waterfall-marker-item {
background-color: #1d4f73; /* Select Highlight Blue */
color: #f5f7fa; /* Light foreground text */
}
.waterfall-marker-container.selected .waterfall-marker-bullet,
.waterfall-marker-container.selected .waterfall-marker-bar {
border-color: initial!important;
}
#timeline-waterfall-details {
padding-top: 28px;
overflow: auto;
}
.marker-details-bullet {
width: 8px;
height: 8px;
margin: 0 8px;
border: 1px solid;
border-radius: 1px;
}
.marker-details-type {
font-size: 1.2em;
font-weight: bold;
}
.marker-details-duration {
font-weight: bold;
}