зеркало из 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 += [
|
EXTRA_JS_MODULES.devtools.timeline += [
|
||||||
'panel.js',
|
'panel.js',
|
||||||
'widgets/global.js',
|
'widgets/global.js',
|
||||||
|
'widgets/marker-details.js',
|
||||||
'widgets/markers-overview.js',
|
'widgets/markers-overview.js',
|
||||||
'widgets/memory-overview.js',
|
'widgets/memory-overview.js',
|
||||||
'widgets/waterfall.js'
|
'widgets/waterfall.js'
|
||||||
|
|
|
@ -15,3 +15,4 @@ support-files =
|
||||||
[browser_timeline_waterfall-background.js]
|
[browser_timeline_waterfall-background.js]
|
||||||
[browser_timeline_waterfall-generic.js]
|
[browser_timeline_waterfall-generic.js]
|
||||||
[browser_timeline_waterfall-styles.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,
|
is($("#record-button").hasAttribute("checked"), false,
|
||||||
"The record button should be unchecked again.");
|
"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.");
|
"A waterfall view is now displayed.");
|
||||||
|
|
||||||
yield teardown(panel);
|
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>
|
<body>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
var x = 1;
|
||||||
function test() {
|
function test() {
|
||||||
var a = "Hello world!";
|
document.body.style.borderTop = x + "px solid red";
|
||||||
document.body.style.backgroundColor = "rgba(" +
|
x = 1^x;
|
||||||
((Math.random() * 64)|0) + "," +
|
document.body.innerHeight; // flush pending reflows
|
||||||
((Math.random() * 16)|0) + "," +
|
|
||||||
((Math.random() * 16)|0) + ",1)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent this script from being garbage collected.
|
// Prevent this script from being garbage collected.
|
||||||
|
|
|
@ -18,6 +18,8 @@ devtools.lazyRequireGetter(this, "MemoryOverview",
|
||||||
"devtools/timeline/memory-overview", true);
|
"devtools/timeline/memory-overview", true);
|
||||||
devtools.lazyRequireGetter(this, "Waterfall",
|
devtools.lazyRequireGetter(this, "Waterfall",
|
||||||
"devtools/timeline/waterfall", true);
|
"devtools/timeline/waterfall", true);
|
||||||
|
devtools.lazyRequireGetter(this, "MarkerDetails",
|
||||||
|
"devtools/timeline/marker-details", true);
|
||||||
|
|
||||||
devtools.lazyImporter(this, "CanvasGraphUtils",
|
devtools.lazyImporter(this, "CanvasGraphUtils",
|
||||||
"resource:///modules/devtools/Graphs.jsm");
|
"resource:///modules/devtools/Graphs.jsm");
|
||||||
|
@ -250,11 +252,17 @@ let TimelineView = {
|
||||||
initialize: Task.async(function*() {
|
initialize: Task.async(function*() {
|
||||||
this.markersOverview = new MarkersOverview($("#markers-overview"));
|
this.markersOverview = new MarkersOverview($("#markers-overview"));
|
||||||
this.waterfall = new Waterfall($("#timeline-waterfall"));
|
this.waterfall = new Waterfall($("#timeline-waterfall"));
|
||||||
|
this.markerDetails = new MarkerDetails($("#timeline-waterfall-details"));
|
||||||
|
|
||||||
this._onSelecting = this._onSelecting.bind(this);
|
this._onSelecting = this._onSelecting.bind(this);
|
||||||
this._onRefresh = this._onRefresh.bind(this);
|
this._onRefresh = this._onRefresh.bind(this);
|
||||||
this.markersOverview.on("selecting", this._onSelecting);
|
this.markersOverview.on("selecting", this._onSelecting);
|
||||||
this.markersOverview.on("refresh", this._onRefresh);
|
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.markersOverview.ready();
|
||||||
yield this.waterfall.recalculateBounds();
|
yield this.waterfall.recalculateBounds();
|
||||||
|
@ -264,6 +272,9 @@ let TimelineView = {
|
||||||
* Destruction function, called when the tool is closed.
|
* Destruction function, called when the tool is closed.
|
||||||
*/
|
*/
|
||||||
destroy: function() {
|
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("selecting", this._onSelecting);
|
||||||
this.markersOverview.off("refresh", this._onRefresh);
|
this.markersOverview.off("refresh", this._onRefresh);
|
||||||
this.markersOverview.destroy();
|
this.markersOverview.destroy();
|
||||||
|
@ -300,6 +311,18 @@ let TimelineView = {
|
||||||
this.memoryOverview = null;
|
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
|
* Signals that a recording session has started and triggers the appropriate
|
||||||
* changes in the UI.
|
* changes in the UI.
|
||||||
|
@ -328,7 +351,7 @@ let TimelineView = {
|
||||||
handleRecordingEnded: function() {
|
handleRecordingEnded: function() {
|
||||||
$("#record-button").removeAttribute("checked");
|
$("#record-button").removeAttribute("checked");
|
||||||
$("#memory-checkbox").removeAttribute("disabled");
|
$("#memory-checkbox").removeAttribute("disabled");
|
||||||
$("#timeline-pane").selectedPanel = $("#timeline-waterfall");
|
$("#timeline-pane").selectedPanel = $("#timeline-waterfall-container");
|
||||||
|
|
||||||
this.markersOverview.selectionEnabled = true;
|
this.markersOverview.selectionEnabled = true;
|
||||||
|
|
||||||
|
@ -346,9 +369,9 @@ let TimelineView = {
|
||||||
let end = start + this.markersOverview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
|
let end = start + this.markersOverview.width * OVERVIEW_INITIAL_SELECTION_RATIO;
|
||||||
this.markersOverview.setSelection({ start, end });
|
this.markersOverview.setSelection({ start, end });
|
||||||
} else {
|
} else {
|
||||||
let timeStart = interval.startTime;
|
let startTime = interval.startTime;
|
||||||
let timeEnd = interval.endTime;
|
let endTime = interval.endTime;
|
||||||
this.waterfall.setData(markers, timeStart, timeStart, timeEnd);
|
this.waterfall.setData(markers, startTime, startTime, endTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.emit(EVENTS.RECORDING_ENDED);
|
window.emit(EVENTS.RECORDING_ENDED);
|
||||||
|
@ -382,6 +405,14 @@ let TimelineView = {
|
||||||
this.waterfall.clearView();
|
this.waterfall.clearView();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.waterfall.resetSelection();
|
||||||
|
this.updateWaterfall();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild the waterfall.
|
||||||
|
*/
|
||||||
|
updateWaterfall: function() {
|
||||||
let selection = this.markersOverview.getSelection();
|
let selection = this.markersOverview.getSelection();
|
||||||
let start = selection.start / this.markersOverview.dataScaleX;
|
let start = selection.start / this.markersOverview.dataScaleX;
|
||||||
let end = selection.end / this.markersOverview.dataScaleX;
|
let end = selection.end / this.markersOverview.dataScaleX;
|
||||||
|
@ -389,9 +420,10 @@ let TimelineView = {
|
||||||
let markers = TimelineController.getMarkers();
|
let markers = TimelineController.getMarkers();
|
||||||
let interval = TimelineController.getInterval();
|
let interval = TimelineController.getInterval();
|
||||||
|
|
||||||
let timeStart = interval.startTime + Math.min(start, end);
|
let startTime = interval.startTime + Math.min(start, end);
|
||||||
let timeEnd = interval.startTime + Math.max(start, end);
|
let endTime = interval.startTime + Math.max(start, end);
|
||||||
this.waterfall.setData(markers, interval.startTime, timeStart, timeEnd);
|
|
||||||
|
this.waterfall.setData(markers, interval.startTime, startTime, endTime);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -399,7 +431,7 @@ let TimelineView = {
|
||||||
*/
|
*/
|
||||||
_onRefresh: function() {
|
_onRefresh: function() {
|
||||||
this.waterfall.recalculateBounds();
|
this.waterfall.recalculateBounds();
|
||||||
this._onSelecting();
|
this.updateWaterfall();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,10 @@
|
||||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
- 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/skin/" type="text/css"?>
|
||||||
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" 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/common.css" type="text/css"?>
|
||||||
<?xml-stylesheet href="chrome://browser/skin/devtools/timeline.css" type="text/css"?>
|
<?xml-stylesheet href="chrome://browser/skin/devtools/timeline.css" type="text/css"?>
|
||||||
|
|
||||||
<!DOCTYPE window [
|
<!DOCTYPE window [
|
||||||
<!ENTITY % timelineDTD SYSTEM "chrome://browser/locale/devtools/timeline.dtd">
|
<!ENTITY % timelineDTD SYSTEM "chrome://browser/locale/devtools/timeline.dtd">
|
||||||
%timelineDTD;
|
%timelineDTD;
|
||||||
|
@ -66,7 +68,11 @@
|
||||||
<label value="&timelineUI.stopNotice2;"/>
|
<label value="&timelineUI.stopNotice2;"/>
|
||||||
</hbox>
|
</hbox>
|
||||||
|
|
||||||
|
<hbox id="timeline-waterfall-container" class="devtools-responsive-container" flex="1">
|
||||||
<vbox id="timeline-waterfall" 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>
|
</deck>
|
||||||
</vbox>
|
</vbox>
|
||||||
</window>
|
</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.
|
* of all the markers in the timeline data.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
const {Ci, Cu} = require("chrome");
|
||||||
|
|
||||||
loader.lazyRequireGetter(this, "L10N",
|
loader.lazyRequireGetter(this, "L10N",
|
||||||
"devtools/timeline/global", true);
|
"devtools/timeline/global", true);
|
||||||
|
@ -19,6 +19,8 @@ loader.lazyImporter(this, "setNamedTimeout",
|
||||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||||
loader.lazyImporter(this, "clearNamedTimeout",
|
loader.lazyImporter(this, "clearNamedTimeout",
|
||||||
"resource:///modules/devtools/ViewHelpers.jsm");
|
"resource:///modules/devtools/ViewHelpers.jsm");
|
||||||
|
loader.lazyRequireGetter(this, "EventEmitter",
|
||||||
|
"devtools/toolkit/event-emitter");
|
||||||
|
|
||||||
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
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_BACKGROUND_TICKS_OPACITY_ADD = 32; // byte
|
||||||
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
|
const WATERFALL_MARKER_BAR_WIDTH_MIN = 5; // px
|
||||||
|
|
||||||
|
const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A detailed waterfall view for the timeline data.
|
* 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.
|
* The parent node holding the waterfall.
|
||||||
*/
|
*/
|
||||||
function Waterfall(parent) {
|
function Waterfall(parent) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
this._parent = parent;
|
this._parent = parent;
|
||||||
this._document = parent.ownerDocument;
|
this._document = parent.ownerDocument;
|
||||||
this._fragment = this._document.createDocumentFragment();
|
this._fragment = this._document.createDocumentFragment();
|
||||||
|
@ -60,6 +65,8 @@ function Waterfall(parent) {
|
||||||
this._listContents.setAttribute("flex", "1");
|
this._listContents.setAttribute("flex", "1");
|
||||||
this._parent.appendChild(this._listContents);
|
this._parent.appendChild(this._listContents);
|
||||||
|
|
||||||
|
this.setupKeys();
|
||||||
|
|
||||||
this._isRTL = this._getRTL();
|
this._isRTL = this._getRTL();
|
||||||
|
|
||||||
// Lazy require is a bit slow, and these are hot objects.
|
// Lazy require is a bit slow, and these are hot objects.
|
||||||
|
@ -67,6 +74,10 @@ function Waterfall(parent) {
|
||||||
this._blueprint = TIMELINE_BLUEPRINT;
|
this._blueprint = TIMELINE_BLUEPRINT;
|
||||||
this._setNamedTimeout = setNamedTimeout;
|
this._setNamedTimeout = setNamedTimeout;
|
||||||
this._clearNamedTimeout = clearNamedTimeout;
|
this._clearNamedTimeout = clearNamedTimeout;
|
||||||
|
|
||||||
|
// Selected row index. By default, we want the first
|
||||||
|
// row to be selected.
|
||||||
|
this._selectedRowIdx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Waterfall.prototype = {
|
Waterfall.prototype = {
|
||||||
|
@ -77,20 +88,55 @@ Waterfall.prototype = {
|
||||||
* A list of markers received from the controller.
|
* A list of markers received from the controller.
|
||||||
* @param number timeEpoch
|
* @param number timeEpoch
|
||||||
* The absolute time (in milliseconds) when the recording started.
|
* The absolute time (in milliseconds) when the recording started.
|
||||||
* @param number timeStart
|
* @param number startTime
|
||||||
* The time (in milliseconds) to start drawing from.
|
* The time (in milliseconds) to start drawing from.
|
||||||
* @param number timeEnd
|
* @param number endTime
|
||||||
* The time (in milliseconds) to end drawing at.
|
* The time (in milliseconds) to end drawing at.
|
||||||
*/
|
*/
|
||||||
setData: function(markers, timeEpoch, timeStart, timeEnd) {
|
setData: function(markers, timeEpoch, startTime, endTime) {
|
||||||
this.clearView();
|
this.clearView();
|
||||||
|
this._markers = markers;
|
||||||
|
|
||||||
let dataScale = this._waterfallWidth / (timeEnd - timeStart);
|
let dataScale = this._waterfallWidth / (endTime - startTime);
|
||||||
this._drawWaterfallBackground(dataScale);
|
this._drawWaterfallBackground(dataScale);
|
||||||
|
|
||||||
// Label the header as if the first possible marker was at T=0.
|
// Label the header as if the first possible marker was at T=0.
|
||||||
this._buildHeader(this._headerContents, timeStart - timeEpoch, dataScale);
|
this._buildHeader(this._headerContents, startTime - timeEpoch, dataScale);
|
||||||
this._buildMarkers(this._listContents, markers, timeStart, timeEnd, 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
|
* @param nsIDOMNode parent
|
||||||
* The parent node holding the header.
|
* The parent node holding the header.
|
||||||
* @param number timeStart
|
* @param number startTime
|
||||||
* @see Waterfall.prototype.setData
|
* @see Waterfall.prototype.setData
|
||||||
* @param number dataScale
|
* @param number dataScale
|
||||||
* The time scale of the data source.
|
* The time scale of the data source.
|
||||||
*/
|
*/
|
||||||
_buildHeader: function(parent, timeStart, dataScale) {
|
_buildHeader: function(parent, startTime, dataScale) {
|
||||||
let container = this._document.createElement("hbox");
|
let container = this._document.createElement("hbox");
|
||||||
container.className = "waterfall-header-container";
|
container.className = "waterfall-header-container";
|
||||||
container.setAttribute("flex", "1");
|
container.setAttribute("flex", "1");
|
||||||
|
@ -159,7 +205,7 @@ Waterfall.prototype = {
|
||||||
|
|
||||||
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
|
for (let x = 0; x < this._waterfallWidth; x += tickInterval) {
|
||||||
let start = x + direction * WATERFALL_HEADER_TEXT_PADDING;
|
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 label = this._l10n.getFormatStr("timeline.tick", time);
|
||||||
|
|
||||||
let node = this._document.createElement("label");
|
let node = this._document.createElement("label");
|
||||||
|
@ -177,23 +223,25 @@ Waterfall.prototype = {
|
||||||
*
|
*
|
||||||
* @param nsIDOMNode parent
|
* @param nsIDOMNode parent
|
||||||
* The parent node holding the markers.
|
* The parent node holding the markers.
|
||||||
* @param number timeStart
|
* @param number startTime
|
||||||
* @see Waterfall.prototype.setData
|
* @see Waterfall.prototype.setData
|
||||||
* @param number dataScale
|
* @param number dataScale
|
||||||
* The time scale of the data source.
|
* The time scale of the data source.
|
||||||
*/
|
*/
|
||||||
_buildMarkers: function(parent, markers, timeStart, timeEnd, dataScale) {
|
_buildMarkers: function(parent, markers, startTime, endTime, dataScale) {
|
||||||
let processed = 0;
|
let rowsCount = 0;
|
||||||
|
let markerIdx = -1;
|
||||||
|
|
||||||
for (let marker of markers) {
|
for (let marker of markers) {
|
||||||
if (!isMarkerInRange(marker, timeStart, timeEnd)) {
|
markerIdx++;
|
||||||
|
if (!isMarkerInRange(marker, startTime, endTime)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Only build and display a finite number of markers initially, to
|
// Only build and display a finite number of markers initially, to
|
||||||
// preserve a snappy UI. After a certain delay, continue building the
|
// preserve a snappy UI. After a certain delay, continue building the
|
||||||
// outstanding markers while there's (hopefully) no user interaction.
|
// outstanding markers while there's (hopefully) no user interaction.
|
||||||
let arguments_ = [this._fragment, marker, timeStart, dataScale];
|
let arguments_ = [this._fragment, marker, startTime, dataScale, markerIdx, rowsCount];
|
||||||
if (processed++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
if (rowsCount++ < WATERFALL_IMMEDIATE_DRAW_MARKERS_COUNT) {
|
||||||
this._buildMarker.apply(this, arguments_);
|
this._buildMarker.apply(this, arguments_);
|
||||||
} else {
|
} else {
|
||||||
this._outstandingMarkers.push(arguments_);
|
this._outstandingMarkers.push(arguments_);
|
||||||
|
@ -228,6 +276,7 @@ Waterfall.prototype = {
|
||||||
}
|
}
|
||||||
this._outstandingMarkers.length = 0;
|
this._outstandingMarkers.length = 0;
|
||||||
parent.appendChild(this._fragment);
|
parent.appendChild(this._fragment);
|
||||||
|
this.selectRow(this._selectedRowIdx);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,18 +286,24 @@ Waterfall.prototype = {
|
||||||
* The parent node holding the marker.
|
* The parent node holding the marker.
|
||||||
* @param object marker
|
* @param object marker
|
||||||
* The { name, start, end } marker in the data source.
|
* The { name, start, end } marker in the data source.
|
||||||
* @param timeStart
|
* @param startTime
|
||||||
* @see Waterfall.prototype.setData
|
* @see Waterfall.prototype.setData
|
||||||
* @param number dataScale
|
* @param number dataScale
|
||||||
* @see Waterfall.prototype._buildMarkers
|
* @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");
|
let container = this._document.createElement("hbox");
|
||||||
|
container.setAttribute("markerIdx", markerIdx);
|
||||||
container.className = "waterfall-marker-container";
|
container.className = "waterfall-marker-container";
|
||||||
|
|
||||||
if (marker) {
|
if (marker) {
|
||||||
this._buildMarkerSidebar(container, marker);
|
this._buildMarkerSidebar(container, marker);
|
||||||
this._buildMarkerWaterfall(container, marker, timeStart, dataScale);
|
this._buildMarkerWaterfall(container, marker, startTime, dataScale, markerIdx);
|
||||||
|
container.onclick = () => this.selectRow(rowIdx);
|
||||||
} else {
|
} else {
|
||||||
this._buildMarkerSpacer(container);
|
this._buildMarkerSpacer(container);
|
||||||
container.setAttribute("flex", "1");
|
container.setAttribute("flex", "1");
|
||||||
|
@ -258,6 +313,83 @@ Waterfall.prototype = {
|
||||||
parent.appendChild(container);
|
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.
|
* Creates the sidebar part of a marker in this view.
|
||||||
*
|
*
|
||||||
|
@ -308,12 +440,12 @@ Waterfall.prototype = {
|
||||||
* The container node representing the marker.
|
* The container node representing the marker.
|
||||||
* @param object marker
|
* @param object marker
|
||||||
* @see Waterfall.prototype._buildMarker
|
* @see Waterfall.prototype._buildMarker
|
||||||
* @param timeStart
|
* @param startTime
|
||||||
* @see Waterfall.prototype.setData
|
* @see Waterfall.prototype.setData
|
||||||
* @param number dataScale
|
* @param number dataScale
|
||||||
* @see Waterfall.prototype._buildMarkers
|
* @see Waterfall.prototype._buildMarkers
|
||||||
*/
|
*/
|
||||||
_buildMarkerWaterfall: function(container, marker, timeStart, dataScale) {
|
_buildMarkerWaterfall: function(container, marker, startTime, dataScale) {
|
||||||
let blueprint = this._blueprint[marker.name];
|
let blueprint = this._blueprint[marker.name];
|
||||||
|
|
||||||
let waterfall = this._document.createElement("hbox");
|
let waterfall = this._document.createElement("hbox");
|
||||||
|
@ -321,7 +453,7 @@ Waterfall.prototype = {
|
||||||
waterfall.setAttribute("align", "center");
|
waterfall.setAttribute("align", "center");
|
||||||
waterfall.setAttribute("flex", "1");
|
waterfall.setAttribute("flex", "1");
|
||||||
|
|
||||||
let start = (marker.start - timeStart) * dataScale;
|
let start = (marker.start - startTime) * dataScale;
|
||||||
let width = (marker.end - marker.start) * dataScale;
|
let width = (marker.end - marker.start) * dataScale;
|
||||||
let offset = this._isRTL ? this._waterfallWidth : 0;
|
let offset = this._isRTL ? this._waterfallWidth : 0;
|
||||||
|
|
||||||
|
@ -329,6 +461,8 @@ Waterfall.prototype = {
|
||||||
bar.className = "waterfall-marker-bar";
|
bar.className = "waterfall-marker-bar";
|
||||||
bar.style.backgroundColor = blueprint.fill;
|
bar.style.backgroundColor = blueprint.fill;
|
||||||
bar.style.borderColor = blueprint.stroke;
|
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.style.transform = "translateX(" + (start - offset) + "px)";
|
||||||
bar.setAttribute("type", marker.name);
|
bar.setAttribute("type", marker.name);
|
||||||
bar.setAttribute("width", Math.max(width, WATERFALL_MARKER_BAR_WIDTH_MIN));
|
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
|
# %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)
|
# with the details. For examples: Paint (200x100), or console.time (FOO)
|
||||||
timeline.markerDetailFormat=%1$S (%2$S)
|
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;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.waterfall-header-contents {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.waterfall-background-ticks {
|
.waterfall-background-ticks {
|
||||||
/* Background created on a <canvas> in js. */
|
/* Background created on a <canvas> in js. */
|
||||||
/* @see browser/devtools/timeline/widgets/waterfall.js */
|
/* @see browser/devtools/timeline/widgets/waterfall.js */
|
||||||
|
@ -153,3 +157,42 @@
|
||||||
border-radius: 1px;
|
border-radius: 1px;
|
||||||
transform-origin: left center;
|
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;
|
||||||
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче