зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1074106 - [timeline] marker sidebar. r=vporof
This commit is contained in:
Родитель
f5cb282c1f
Коммит
2478fc4e36
|
@ -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'
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
});
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче