зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1077458 - Implement marker's waterfall view in new performance tool, with details view toggling between waterfall and call tree views. r=vp
This commit is contained in:
Родитель
db85b3268a
Коммит
0ff15d35ae
|
@ -93,6 +93,7 @@ browser.jar:
|
|||
content/browser/devtools/performance/views/overview.js (performance/views/overview.js)
|
||||
content/browser/devtools/performance/views/details.js (performance/views/details.js)
|
||||
content/browser/devtools/performance/views/call-tree.js (performance/views/call-tree.js)
|
||||
content/browser/devtools/performance/views/waterfall.js (performance/views/waterfall.js)
|
||||
#endif
|
||||
content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
|
||||
content/browser/devtools/commandline.css (commandline/commandline.css)
|
||||
|
|
|
@ -23,6 +23,10 @@ devtools.lazyRequireGetter(this, "L10N",
|
|||
"devtools/profiler/global", true);
|
||||
devtools.lazyImporter(this, "LineGraphWidget",
|
||||
"resource:///modules/devtools/Graphs.jsm");
|
||||
devtools.lazyRequireGetter(this, "Waterfall",
|
||||
"devtools/timeline/waterfall", true);
|
||||
devtools.lazyRequireGetter(this, "MarkerDetails",
|
||||
"devtools/timeline/marker-details", true);
|
||||
devtools.lazyRequireGetter(this, "CallView",
|
||||
"devtools/profiler/tree-view", true);
|
||||
devtools.lazyRequireGetter(this, "ThreadNode",
|
||||
|
@ -47,8 +51,14 @@ const EVENTS = {
|
|||
// Emitted by the OverviewView when a selection range has been removed
|
||||
OVERVIEW_RANGE_CLEARED: "Performance:UI:OverviewRangeCleared",
|
||||
|
||||
// Emitted by the DetailsView when a subview is selected
|
||||
DETAILS_VIEW_SELECTED: "Performance:UI:DetailsViewSelected",
|
||||
|
||||
// Emitted by the CallTreeView when a call tree has been rendered
|
||||
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered"
|
||||
CALL_TREE_RENDERED: "Performance:UI:CallTreeRendered",
|
||||
|
||||
// Emitted by the WaterfallView when it has been rendered
|
||||
WATERFALL_RENDERED: "Performance:UI:WaterfallRendered"
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -112,6 +122,7 @@ let PerformanceController = {
|
|||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
gFront.on("ticks", this._onTimelineData);
|
||||
gFront.on("markers", this._onTimelineData);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -120,6 +131,8 @@ let PerformanceController = {
|
|||
destroy: function() {
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
gFront.off("ticks", this._onTimelineData);
|
||||
gFront.off("markers", this._onTimelineData);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -127,8 +140,12 @@ let PerformanceController = {
|
|||
* when the front is starting to record.
|
||||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
yield gFront.startRecording();
|
||||
this.emit(EVENTS.RECORDING_STARTED);
|
||||
// Save local start time for use with faking the endTime
|
||||
// if not returned from the timeline actor
|
||||
this._localStartTime = performance.now();
|
||||
|
||||
let startTime = this._startTime = yield gFront.startRecording();
|
||||
this.emit(EVENTS.RECORDING_STARTED, startTime);
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -137,6 +154,12 @@ let PerformanceController = {
|
|||
*/
|
||||
stopRecording: Task.async(function *() {
|
||||
let results = yield gFront.stopRecording();
|
||||
// If `endTime` is not yielded from timeline actor (< Fx36),
|
||||
// fake an endTime
|
||||
if (!results.endTime) {
|
||||
this._endTime = results.endTime = this._startTime + (performance.now() - this._localStartTime);
|
||||
}
|
||||
|
||||
this.emit(EVENTS.RECORDING_STOPPED, results);
|
||||
}),
|
||||
|
||||
|
|
|
@ -237,7 +237,12 @@ PerformanceFront.prototype = {
|
|||
// The timeline actor is target-dependent, so just make sure
|
||||
// it's recording.
|
||||
let withMemory = showTimelineMemory();
|
||||
yield this._request("timeline", "start", { withTicks: true, withMemory: withMemory });
|
||||
|
||||
// Return start time from timeline actor
|
||||
let startTime = yield this._request("timeline", "start", { withTicks: true, withMemory: withMemory });
|
||||
this._startTime = startTime;
|
||||
|
||||
return { startTime };
|
||||
}),
|
||||
|
||||
/**
|
||||
|
@ -254,12 +259,13 @@ PerformanceFront.prototype = {
|
|||
filterSamples(profilerData, this._profilingStartTime);
|
||||
offsetSampleTimes(profilerData, this._profilingStartTime);
|
||||
|
||||
yield this._request("timeline", "stop");
|
||||
let endTime = this._endTime = yield this._request("timeline", "stop");
|
||||
|
||||
// Join all the acquired data and return it for outside consumers.
|
||||
return {
|
||||
recordingDuration: profilerData.currentTime - this._profilingStartTime,
|
||||
profilerData: profilerData
|
||||
profilerData: profilerData,
|
||||
endTime: endTime
|
||||
};
|
||||
}),
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
<script type="application/javascript" src="performance/views/overview.js"/>
|
||||
<script type="application/javascript" src="performance/views/details.js"/>
|
||||
<script type="application/javascript" src="performance/views/call-tree.js"/>
|
||||
<script type="application/javascript" src="performance/views/waterfall.js"/>
|
||||
|
||||
<vbox class="theme-body" flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
|
@ -44,10 +45,28 @@
|
|||
<vbox id="time-framerate" flex="1"/>
|
||||
</box>
|
||||
<splitter class="devtools-horizontal-splitter" />
|
||||
<box id="details-pane"
|
||||
<toolbar id="details-toolbar" class="devtools-toolbar">
|
||||
<hbox class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="waterfall"
|
||||
data-view="waterfall" />
|
||||
<toolbarbutton id="select-calltree-view"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="calltree"
|
||||
data-view="calltree" />
|
||||
</hbox>
|
||||
</toolbar>
|
||||
<deck id="details-pane"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<vbox class="call-tree" flex="1">
|
||||
<hbox id="waterfall-view" flex="1">
|
||||
<vbox id="waterfall-graph" flex="1" />
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<vbox id="waterfall-details" class="theme-sidebar" width="150" height="150"/>
|
||||
</hbox>
|
||||
|
||||
<vbox id="calltree-view" class="call-tree" flex="1">
|
||||
<hbox class="call-tree-headers-container">
|
||||
<label class="plain call-tree-header"
|
||||
type="duration"
|
||||
|
@ -76,6 +95,6 @@
|
|||
</hbox>
|
||||
<vbox class="call-tree-cells-container" flex="1"/>
|
||||
</vbox>
|
||||
</box>
|
||||
</deck>
|
||||
</vbox>
|
||||
</window>
|
||||
|
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
# that need to be moved over to performance tool
|
||||
|
||||
[browser_perf-aaa-run-first-leaktest.js]
|
||||
[browser_perf-front.js]
|
||||
[browser_perf-front-basic-timeline-01.js]
|
||||
[browser_perf-front-basic-profiler-01.js]
|
||||
# bug 1077464
|
||||
|
@ -32,5 +33,8 @@ support-files =
|
|||
[browser_perf-overview-render-01.js]
|
||||
[browser_perf-overview-render-02.js]
|
||||
[browser_perf-overview-selection.js]
|
||||
|
||||
[browser_perf-details.js]
|
||||
[browser_perf-details-calltree-render-01.js]
|
||||
[browser_perf-details-calltree-render-02.js]
|
||||
[browser_perf-details-waterfall-render-01.js]
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the waterfall view renders content after recording.
|
||||
*/
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, WaterfallView } = panel.panelWin;
|
||||
|
||||
yield startRecording(panel);
|
||||
|
||||
yield waitUntil(() => WaterfallView._markers.length);
|
||||
|
||||
let rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
|
||||
|
||||
yield stopRecording(panel);
|
||||
|
||||
yield rendered;
|
||||
ok(true, "WaterfallView rendered after recording is stopped.");
|
||||
|
||||
ok(WaterfallView._markers.length, "WaterfallView contains markers");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the details view toggles subviews.
|
||||
*/
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { EVENTS, $, DetailsView, document: doc } = panel.panelWin;
|
||||
|
||||
info("views on startup");
|
||||
checkViews(DetailsView, doc, "waterfall");
|
||||
|
||||
// Select calltree view
|
||||
let viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
|
||||
command($("toolbarbutton[data-view='calltree']"));
|
||||
let [_, viewName] = yield viewChanged;
|
||||
is(viewName, "calltree", "DETAILS_VIEW_SELECTED fired with view name");
|
||||
checkViews(DetailsView, doc, "calltree");
|
||||
|
||||
// Select waterfall view
|
||||
viewChanged = onceSpread(DetailsView, EVENTS.DETAILS_VIEW_SELECTED);
|
||||
command($("toolbarbutton[data-view='waterfall']"));
|
||||
[_, viewName] = yield viewChanged;
|
||||
is(viewName, "waterfall", "DETAILS_VIEW_SELECTED fired with view name");
|
||||
checkViews(DetailsView, doc, "waterfall");
|
||||
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
|
||||
function checkViews (DetailsView, doc, currentView) {
|
||||
for (let viewName in DetailsView.views) {
|
||||
let view = DetailsView.views[viewName].el;
|
||||
let button = doc.querySelector("toolbarbutton[data-view='" + viewName + "']");
|
||||
|
||||
if (viewName === currentView) {
|
||||
ok(!view.getAttribute("hidden"), view + " view displayed");
|
||||
ok(button.getAttribute("checked"), view + " button checked");
|
||||
} else {
|
||||
ok(view.getAttribute("hidden"), view + " view hidden");
|
||||
ok(!button.getAttribute("checked"), view + " button not checked");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Test basic functionality of PerformanceFront, emitting start and endtime values
|
||||
*/
|
||||
|
||||
let WAIT = 1000;
|
||||
|
||||
function spawnTest () {
|
||||
let { target, front } = yield initBackend(SIMPLE_URL);
|
||||
|
||||
let { startTime } = yield front.startRecording();
|
||||
|
||||
ok(typeof startTime === "number", "front.startRecording() emits start time");
|
||||
|
||||
yield busyWait(WAIT);
|
||||
|
||||
let { endTime } = yield front.stopRecording();
|
||||
|
||||
ok(typeof endTime === "number", "front.stopRecording() emits end time");
|
||||
ok(endTime > startTime, "endTime is after startTime");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
||||
}
|
|
@ -211,8 +211,14 @@ function busyWait(time) {
|
|||
while (Date.now() - start < time) { stack = Components.stack; }
|
||||
}
|
||||
|
||||
function idleWait(time) {
|
||||
return DevToolsUtils.waitForTime(time);
|
||||
function command (button) {
|
||||
let ev = button.ownerDocument.createEvent("XULCommandEvent");
|
||||
ev.initCommandEvent("command", true, true, button.ownerDocument.defaultView, 0, false, false, false, false, null);
|
||||
button.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
function click (win, button) {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, button, win);
|
||||
}
|
||||
|
||||
function* startRecording(panel) {
|
||||
|
@ -227,7 +233,7 @@ function* startRecording(panel) {
|
|||
ok(!button.hasAttribute("locked"),
|
||||
"The record button should not be locked yet.");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, button, win);
|
||||
click(win, button);
|
||||
|
||||
yield clicked;
|
||||
|
||||
|
@ -255,7 +261,7 @@ function* stopRecording(panel) {
|
|||
ok(!button.hasAttribute("locked"),
|
||||
"The record button should not be locked yet.");
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" }, button, win);
|
||||
click(win, button);
|
||||
|
||||
yield clicked;
|
||||
|
||||
|
|
|
@ -3,34 +3,69 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const DEFAULT_DETAILS_SUBVIEW = "waterfall";
|
||||
|
||||
/**
|
||||
* Details view containing profiler call tree. Manages
|
||||
* subviews and toggles visibility between them.
|
||||
*/
|
||||
let DetailsView = {
|
||||
/**
|
||||
* Name to index mapping of subviews, used by selecting view.
|
||||
*/
|
||||
viewIndexes: {
|
||||
waterfall: 0,
|
||||
calltree: 1
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the view with event binding, initializes
|
||||
* subviews.
|
||||
*/
|
||||
initialize: function () {
|
||||
this.views = {
|
||||
callTree: CallTreeView
|
||||
};
|
||||
initialize: Task.async(function *() {
|
||||
this.el = $("#details-pane");
|
||||
|
||||
// Initialize subviews
|
||||
return promise.all([
|
||||
CallTreeView.initialize()
|
||||
]);
|
||||
this._onViewToggle = this._onViewToggle.bind(this);
|
||||
|
||||
for (let button of $$("toolbarbutton[data-view]", $("#details-toolbar"))) {
|
||||
button.addEventListener("command", this._onViewToggle);
|
||||
}
|
||||
|
||||
yield CallTreeView.initialize();
|
||||
yield WaterfallView.initialize();
|
||||
this.selectView(DEFAULT_DETAILS_SUBVIEW);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Select one of the DetailView's subviews to be rendered,
|
||||
* hiding the others.
|
||||
*
|
||||
* @params {String} selectedView
|
||||
* Name of the view to be shown.
|
||||
*/
|
||||
selectView: function (selectedView) {
|
||||
this.el.selectedIndex = this.viewIndexes[selectedView];
|
||||
this.emit(EVENTS.DETAILS_VIEW_SELECTED, selectedView);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when a view button is clicked.
|
||||
*/
|
||||
_onViewToggle: function (e) {
|
||||
this.selectView(e.target.getAttribute("data-view"));
|
||||
},
|
||||
|
||||
/**
|
||||
* Unbinds events, destroys subviews.
|
||||
*/
|
||||
destroy: function () {
|
||||
return promise.all([
|
||||
CallTreeView.destroy()
|
||||
]);
|
||||
destroy: Task.async(function *() {
|
||||
for (let button of $$("toolbarbutton[data-view]", $("#details-toolbar"))) {
|
||||
button.removeEventListener("command", this._onViewToggle);
|
||||
}
|
||||
|
||||
yield CallTreeView.destroy();
|
||||
yield WaterfallView.destroy();
|
||||
})
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
/* 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";
|
||||
|
||||
/**
|
||||
* Waterfall view controlled by DetailsView.
|
||||
*/
|
||||
let WaterfallView = {
|
||||
_startTime: 0,
|
||||
_endTime: 0,
|
||||
_markers: [],
|
||||
|
||||
/**
|
||||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: Task.async(function *() {
|
||||
this.el = $("#waterfall-view");
|
||||
this._stop = this._stop.bind(this);
|
||||
this._start = this._start.bind(this);
|
||||
this._onTimelineData = this._onTimelineData.bind(this);
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
this._onResize = this._onResize.bind(this);
|
||||
|
||||
this.graph = new Waterfall($("#waterfall-graph"), $("#details-pane"));
|
||||
this.markerDetails = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
|
||||
this.graph.on("selected", this._onMarkerSelected);
|
||||
this.graph.on("unselected", this._onMarkerSelected);
|
||||
this.markerDetails.on("resize", this._onResize);
|
||||
|
||||
PerformanceController.on(EVENTS.RECORDING_STARTED, this._start);
|
||||
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
PerformanceController.on(EVENTS.TIMELINE_DATA, this._onTimelineData);
|
||||
yield this.graph.recalculateBounds();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Unbinds events.
|
||||
*/
|
||||
destroy: function () {
|
||||
this.graph.off("selected", this._onMarkerSelected);
|
||||
this.graph.off("unselected", this._onMarkerSelected);
|
||||
this.markerDetails.off("resize", this._onResize);
|
||||
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._start);
|
||||
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
|
||||
PerformanceController.off(EVENTS.TIMELINE_DATA, this._onTimelineData);
|
||||
},
|
||||
|
||||
render: Task.async(function *() {
|
||||
yield this.graph.recalculateBounds();
|
||||
this.graph.setData(this._markers, this._startTime, this._startTime, this._endTime);
|
||||
this.emit(EVENTS.WATERFALL_RENDERED);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Event handlers
|
||||
*/
|
||||
|
||||
/**
|
||||
* Called when recording starts.
|
||||
*/
|
||||
_start: function (_, { startTime }) {
|
||||
this._startTime = startTime;
|
||||
this._endTime = startTime;
|
||||
this.graph.clearView();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when recording stops.
|
||||
*/
|
||||
_stop: Task.async(function *(_, { endTime }) {
|
||||
this._endTime = endTime;
|
||||
this._markers = this._markers.sort((a,b) => (a.start > b.start));
|
||||
this.render();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called when a marker is selected in the waterfall view,
|
||||
* updating the markers detail view.
|
||||
*/
|
||||
_onMarkerSelected: function (event, marker) {
|
||||
if (event === "selected") {
|
||||
this.markerDetails.render(marker);
|
||||
}
|
||||
if (event === "unselected") {
|
||||
this.markerDetails.empty();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the marker details view is resized.
|
||||
*/
|
||||
_onResize: function () {
|
||||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the TimelineFront has new data for
|
||||
* framerate, markers or memory, and stores the data
|
||||
* to be plotted subsequently.
|
||||
*/
|
||||
_onTimelineData: function (_, eventName, ...data) {
|
||||
if (eventName === "markers") {
|
||||
let [markers, endTime] = data;
|
||||
Array.prototype.push.apply(this._markers, markers);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Convenient way of emitting events from the view.
|
||||
*/
|
||||
EventEmitter.decorate(WaterfallView);
|
|
@ -251,8 +251,8 @@ 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.waterfall = new Waterfall($("#timeline-waterfall"), $("#timeline-pane"));
|
||||
this.markerDetails = new MarkerDetails($("#timeline-waterfall-details"), $("#timeline-waterfall-container > splitter"));
|
||||
|
||||
this._onSelecting = this._onSelecting.bind(this);
|
||||
this._onRefresh = this._onRefresh.bind(this);
|
||||
|
@ -273,8 +273,10 @@ let TimelineView = {
|
|||
*/
|
||||
destroy: function() {
|
||||
this.markerDetails.off("resize", this._onRefresh);
|
||||
this.markerDetails.destroy();
|
||||
this.waterfall.off("selected", this._onMarkerSelected);
|
||||
this.waterfall.off("unselected", this._onMarkerSelected);
|
||||
this.waterfall.destroy();
|
||||
this.markersOverview.off("selecting", this._onSelecting);
|
||||
this.markersOverview.off("refresh", this._onRefresh);
|
||||
this.markersOverview.destroy();
|
||||
|
|
|
@ -22,12 +22,14 @@ loader.lazyRequireGetter(this, "EventEmitter",
|
|||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the view.
|
||||
* @param nsIDOMNode splitter
|
||||
* The splitter node that the resize event is bound to.
|
||||
*/
|
||||
function MarkerDetails(parent) {
|
||||
function MarkerDetails(parent, splitter) {
|
||||
EventEmitter.decorate(this);
|
||||
this._document = parent.ownerDocument;
|
||||
this._parent = parent;
|
||||
this._splitter = this._document.querySelector("#timeline-waterfall-container > splitter");
|
||||
this._splitter = splitter;
|
||||
this._splitter.addEventListener("mouseup", () => this.emit("resize"));
|
||||
}
|
||||
|
||||
|
@ -35,6 +37,7 @@ MarkerDetails.prototype = {
|
|||
destroy: function() {
|
||||
this.empty();
|
||||
this._parent = null;
|
||||
this._splitter = null;
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -48,11 +48,14 @@ const WATERFALL_ROWCOUNT_ONPAGEUPDOWN = 10;
|
|||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the waterfall.
|
||||
* @param nsIDOMNode container
|
||||
* The container node that key events should be bound to.
|
||||
*/
|
||||
function Waterfall(parent) {
|
||||
function Waterfall(parent, container) {
|
||||
EventEmitter.decorate(this);
|
||||
this._parent = parent;
|
||||
this._document = parent.ownerDocument;
|
||||
this._container = container;
|
||||
this._fragment = this._document.createDocumentFragment();
|
||||
this._outstandingMarkers = [];
|
||||
|
||||
|
@ -78,9 +81,15 @@ function Waterfall(parent) {
|
|||
// Selected row index. By default, we want the first
|
||||
// row to be selected.
|
||||
this._selectedRowIdx = 0;
|
||||
|
||||
// Default rowCount
|
||||
this.rowCount = WATERFALL_ROWCOUNT_ONPAGEUPDOWN;
|
||||
}
|
||||
|
||||
Waterfall.prototype = {
|
||||
destroy: function() {
|
||||
this._parent = this._document = this._container = null;
|
||||
},
|
||||
/**
|
||||
* Populates this view with the provided data source.
|
||||
*
|
||||
|
@ -110,7 +119,7 @@ Waterfall.prototype = {
|
|||
* Keybindings.
|
||||
*/
|
||||
setupKeys: function() {
|
||||
let pane = this._document.querySelector("#timeline-pane");
|
||||
let pane = this._container;
|
||||
pane.parentNode.parentNode.addEventListener("keydown", e => {
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP) {
|
||||
e.preventDefault();
|
||||
|
@ -130,11 +139,11 @@ Waterfall.prototype = {
|
|||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx - WATERFALL_ROWCOUNT_ONPAGEUPDOWN);
|
||||
this.selectNearestRow(this._selectedRowIdx - this.rowCount);
|
||||
}
|
||||
if (e.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
|
||||
e.preventDefault();
|
||||
this.selectNearestRow(this._selectedRowIdx + WATERFALL_ROWCOUNT_ONPAGEUPDOWN);
|
||||
this.selectNearestRow(this._selectedRowIdx + this.rowCount);
|
||||
}
|
||||
}, true);
|
||||
},
|
||||
|
|
|
@ -33,6 +33,17 @@
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Details Panel */
|
||||
|
||||
#select-waterfall-view {
|
||||
list-style-image: url(performance-icons.svg#waterfall);
|
||||
}
|
||||
|
||||
#select-calltree-view {
|
||||
list-style-image: url(performance-icons.svg#call-tree);
|
||||
}
|
||||
|
||||
|
||||
/* Profile call tree */
|
||||
|
||||
.theme-dark .call-tree-headers-container {
|
||||
|
@ -255,3 +266,148 @@
|
|||
transform: scale(0.75);
|
||||
transform-origin: center right;
|
||||
}
|
||||
|
||||
/**
|
||||
* Details Waterfall Styles
|
||||
*/
|
||||
|
||||
.waterfall-list-contents {
|
||||
/* Hack: force hardware acceleration */
|
||||
transform: translateZ(1px);
|
||||
overflow-x: hidden;
|
||||
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 */
|
||||
background-image: -moz-element(#waterfall-background);
|
||||
background-repeat: repeat-y;
|
||||
background-position: -1px center;
|
||||
}
|
||||
|
||||
.waterfall-marker-container[is-spacer] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
background-color: rgba(255,255,255,0.03);
|
||||
}
|
||||
|
||||
.theme-light .waterfall-marker-container:not([is-spacer]):nth-child(2n) {
|
||||
background-color: rgba(128,128,128,0.03);
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-marker-container:hover {
|
||||
background-color: rgba(255,255,255,0.1) !important;
|
||||
}
|
||||
|
||||
.theme-light .waterfall-marker-container:hover {
|
||||
background-color: rgba(128,128,128,0.1) !important;
|
||||
}
|
||||
|
||||
.waterfall-marker-item {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.waterfall-sidebar {
|
||||
-moz-border-end: 1px solid;
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-sidebar {
|
||||
-moz-border-end-color: #000;
|
||||
}
|
||||
|
||||
.theme-light .waterfall-sidebar {
|
||||
-moz-border-end-color: #aaa;
|
||||
}
|
||||
|
||||
.waterfall-marker-container:hover > .waterfall-sidebar {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.waterfall-header-name {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.waterfall-header-tick {
|
||||
width: 100px;
|
||||
font-size: 9px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.theme-dark .waterfall-header-tick {
|
||||
color: #a9bacb;
|
||||
}
|
||||
|
||||
.theme-light .waterfall-header-tick {
|
||||
color: #292e33;
|
||||
}
|
||||
|
||||
.waterfall-header-tick:not(:first-child) {
|
||||
-moz-margin-start: -100px !important; /* Don't affect layout. */
|
||||
}
|
||||
|
||||
.waterfall-marker-bullet {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
-moz-margin-start: 8px;
|
||||
-moz-margin-end: 6px;
|
||||
border: 1px solid;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.waterfall-marker-name {
|
||||
font-size: 95%;
|
||||
padding-bottom: 1px !important;
|
||||
}
|
||||
|
||||
.waterfall-marker-bar {
|
||||
height: 9px;
|
||||
border: 1px solid;
|
||||
border-radius: 1px;
|
||||
transform-origin: left center;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
|
@ -241,7 +241,14 @@ let TimelineActor = exports.TimelineActor = protocol.ActorClass({
|
|||
}
|
||||
|
||||
clearTimeout(this._dataPullTimeout);
|
||||
}, {}),
|
||||
return this.docShells[0].now();
|
||||
}, {
|
||||
response: {
|
||||
// Set as possibly nullable due to the end time possibly being
|
||||
// undefined during destruction
|
||||
value: RetVal("nullable:number")
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* When a new window becomes available in the tabActor, start recording its
|
||||
|
|
Загрузка…
Ссылка в новой задаче