Bug 1126882 - Detail views in the new performance tool should share a base class to avoid code duplication, r=jsantell

This commit is contained in:
Victor Porof 2015-01-29 08:24:04 -05:00
parent d4ea5b902a
commit 470f708065
6 changed files with 132 additions and 180 deletions

View File

@ -98,6 +98,7 @@ browser.jar:
content/browser/devtools/performance/views/overview.js (performance/views/overview.js)
content/browser/devtools/performance/views/toolbar.js (performance/views/toolbar.js)
content/browser/devtools/performance/views/details.js (performance/views/details.js)
content/browser/devtools/performance/views/details-subview.js (performance/views/details-abstract-subview.js)
content/browser/devtools/performance/views/details-call-tree.js (performance/views/details-call-tree.js)
content/browser/devtools/performance/views/details-waterfall.js (performance/views/details-waterfall.js)
content/browser/devtools/performance/views/details-flamegraph.js (performance/views/details-flamegraph.js)

View File

@ -19,6 +19,7 @@
<script type="application/javascript" src="performance/recording-model.js"/>
<script type="application/javascript" src="performance/views/overview.js"/>
<script type="application/javascript" src="performance/views/toolbar.js"/>
<script type="application/javascript" src="performance/views/details-subview.js"/>
<script type="application/javascript" src="performance/views/details-call-tree.js"/>
<script type="application/javascript" src="performance/views/details-waterfall.js"/>
<script type="application/javascript" src="performance/views/details-flamegraph.js"/>

View File

@ -0,0 +1,86 @@
/* 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";
/**
* A base class from which all detail views inherit.
*/
let DetailsSubview = {
/**
* Sets up the view with event binding.
*/
initialize: function () {
this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
this._onOverviewRangeChange = this._onOverviewRangeChange.bind(this);
this._onDetailsViewSelected = this._onDetailsViewSelected.bind(this);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onOverviewRangeChange);
DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
/**
* Unbinds events.
*/
destroy: function () {
clearNamedTimeout("range-change-debounce");
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onOverviewRangeChange);
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onOverviewRangeChange);
DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
/**
* Amount of time (in milliseconds) to wait until this view gets updated,
* when the range is changed in the overview.
*/
rangeChangeDebounceTime: 0,
/**
* Flag specifying if this view should be updated when selected. This will
* be set to true, for example, when the range changes in the overview and
* this view is not currently visible.
*/
shouldUpdateWhenShown: false,
/**
* Called when recording stops or is selected.
*/
_onRecordingStoppedOrSelected: function(_, recording) {
if (!recording.isRecording()) {
this.render();
}
},
/**
* Fired when a range is selected or cleared in the OverviewView.
*/
_onOverviewRangeChange: function (_, interval) {
if (DetailsView.isViewSelected(this)) {
let debounced = () => this.render(interval);
setNamedTimeout("range-change-debounce", this.rangeChangeDebounceTime, debounced);
} else {
this.shouldUpdateWhenShown = true;
}
},
/**
* Fired when a view is selected in the DetailsView.
*/
_onDetailsViewSelected: function() {
if (DetailsView.isViewSelected(this) && this.shouldUpdateWhenShown) {
this.render(OverviewView.getTimeInterval());
this.shouldUpdateWhenShown = false;
}
}
};
/**
* Convenient way of emitting events from the view.
*/
EventEmitter.decorate(DetailsSubview);

View File

@ -3,42 +3,31 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const CALLTREE_UPDATE_DEBOUNCE = 50; // ms
/**
* CallTree view containing profiler call tree, controlled by DetailsView.
*/
let CallTreeView = {
let CallTreeView = Heritage.extend(DetailsSubview, {
rangeChangeDebounceTime: 50, // ms
/**
* Sets up the view with event binding.
*/
initialize: function () {
this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
this._onRangeChange = this._onRangeChange.bind(this);
this._onDetailsViewSelected = this._onDetailsViewSelected.bind(this);
DetailsSubview.initialize.call(this);
this._onPrefChanged = this._onPrefChanged.bind(this);
this._onLink = this._onLink.bind(this);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
/**
* Unbinds events.
*/
destroy: function () {
clearNamedTimeout("calltree-update");
DetailsSubview.destroy.call(this);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
/**
@ -57,38 +46,6 @@ let CallTreeView = {
this.emit(EVENTS.CALL_TREE_RENDERED);
},
/**
* Called when recording is stopped or has been selected.
*/
_onRecordingStoppedOrSelected: function (_, recording) {
if (!recording.isRecording()) {
this.render();
}
},
/**
* Fired when a range is selected or cleared in the OverviewView.
*/
_onRangeChange: function (_, interval) {
if (DetailsView.isViewSelected(this)) {
let debounced = () => this.render(interval);
setNamedTimeout("calltree-update", CALLTREE_UPDATE_DEBOUNCE, debounced);
} else {
this._dirty = true;
this._interval = interval;
}
},
/**
* Fired when a view is selected in the DetailsView.
*/
_onDetailsViewSelected: function() {
if (DetailsView.isViewSelected(this) && this._dirty) {
this.render(this._interval);
this._dirty = false;
}
},
/**
* Fired on the "link" event for the call tree in this container.
*/
@ -155,12 +112,7 @@ let CallTreeView = {
this.render(OverviewView.getTimeInterval());
}
}
};
/**
* Convenient way of emitting events from the view.
*/
EventEmitter.decorate(CallTreeView);
});
/**
* Opens/selects the debugger in this toolbox and jumps to the specified

View File

@ -7,40 +7,29 @@
* FlameGraph view containing a pyramid-like visualization of a profile,
* controlled by DetailsView.
*/
let FlameGraphView = {
let FlameGraphView = Heritage.extend(DetailsSubview, {
/**
* Sets up the view with event binding.
*/
initialize: Task.async(function* () {
this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
this._onRangeChange = this._onRangeChange.bind(this);
this._onRangeChangeInGraph = this._onRangeChangeInGraph.bind(this);
this._onDetailsViewSelected = this._onDetailsViewSelected.bind(this);
DetailsSubview.initialize.call(this);
this.graph = new FlameGraph($("#flamegraph-view"));
this.graph.timelineTickUnits = L10N.getStr("graphs.ms");
yield this.graph.ready();
this.graph.on("selecting", this._onRangeChangeInGraph);
this._onRangeChangeInGraph = this._onRangeChangeInGraph.bind(this);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
this.graph.on("selecting", this._onRangeChangeInGraph);
}),
/**
* Unbinds events.
*/
destroy: function () {
this.graph.off("selecting", this._onRangeChangeInGraph);
DetailsSubview.destroy.call(this);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
this.graph.off("selecting", this._onRangeChangeInGraph);
},
/**
@ -51,42 +40,28 @@ let FlameGraphView = {
*/
render: function (interval={}) {
let recording = PerformanceController.getCurrentRecording();
let startTime = interval.startTime || 0;
let endTime = interval.endTime || recording.getDuration();
this.graph.setViewRange({ startTime, endTime });
this.emit(EVENTS.FLAMEGRAPH_RENDERED);
},
/**
* Called when recording is stopped or selected.
*/
_onRecordingStoppedOrSelected: function (_, recording) {
if (recording.isRecording()) {
return;
}
let duration = recording.getDuration();
let profile = recording.getProfile();
let samples = profile.threads[0].samples;
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
flattenRecursion: Prefs.flattenTreeRecursion,
filterFrames: !Prefs.showPlatformData && FrameNode.isContent,
showIdleBlocks: Prefs.showIdleBlocks && L10N.getStr("table.idle")
});
let startTime = 0;
let endTime = recording.getDuration();
this.graph.setData({ data, bounds: { startTime, endTime } });
this.render();
},
/**
* Fired when a range is selected or cleared in the OverviewView.
*/
_onRangeChange: function (_, interval) {
if (DetailsView.isViewSelected(this)) {
this.render(interval);
} else {
this._dirty = true;
this._interval = interval;
}
this.graph.setData({ data,
bounds: {
startTime: 0,
endTime: duration
},
visible: {
startTime: interval.startTime || 0,
endTime: interval.endTime || duration
}
});
this.emit(EVENTS.FLAMEGRAPH_RENDERED);
},
/**
@ -95,20 +70,5 @@ let FlameGraphView = {
_onRangeChangeInGraph: function () {
let interval = this.graph.getViewRange();
OverviewView.setTimeInterval(interval, { stopPropagation: true });
},
/**
* Fired when a view is selected in the DetailsView.
*/
_onDetailsViewSelected: function() {
if (DetailsView.isViewSelected(this) && this._dirty) {
this.render(this._interval);
this._dirty = false;
}
}
};
/**
* Convenient way of emitting events from the view.
*/
EventEmitter.decorate(FlameGraphView);
});

View File

@ -3,56 +3,45 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const WATERFALL_UPDATE_DEBOUNCE = 10; // ms
/**
* Waterfall view containing the timeline markers, controlled by DetailsView.
*/
let WaterfallView = {
let WaterfallView = Heritage.extend(DetailsSubview, {
rangeChangeDebounceTime: 10, // ms
/**
* Sets up the view with event binding.
*/
initialize: Task.async(function *() {
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
this._onRangeChange = this._onRangeChange.bind(this);
this._onDetailsViewSelected = this._onDetailsViewSelected.bind(this);
this._onMarkerSelected = this._onMarkerSelected.bind(this);
this._onResize = this._onResize.bind(this);
initialize: function () {
DetailsSubview.initialize.call(this);
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#details-pane"), TIMELINE_BLUEPRINT);
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
this._onRecordingStarted = this._onRecordingStarted.bind(this);
this._onMarkerSelected = this._onMarkerSelected.bind(this);
this._onResize = this._onResize.bind(this);
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
this.waterfall.on("selected", this._onMarkerSelected);
this.waterfall.on("unselected", this._onMarkerSelected);
this.details.on("resize", this._onResize);
PerformanceController.on(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
OverviewView.on(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
OverviewView.on(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
DetailsView.on(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
this.waterfall.recalculateBounds();
}),
},
/**
* Unbinds events.
*/
destroy: function () {
clearNamedTimeout("waterfall-update");
DetailsSubview.destroy.call(this);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
this.waterfall.off("selected", this._onMarkerSelected);
this.waterfall.off("unselected", this._onMarkerSelected);
this.details.off("resize", this._onResize);
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
OverviewView.off(EVENTS.OVERVIEW_RANGE_SELECTED, this._onRangeChange);
OverviewView.off(EVENTS.OVERVIEW_RANGE_CLEARED, this._onRangeChange);
DetailsView.off(EVENTS.DETAILS_VIEW_SELECTED, this._onDetailsViewSelected);
},
/**
@ -77,38 +66,6 @@ let WaterfallView = {
this.waterfall.clearView();
},
/**
* Called when recording stops or is selected.
*/
_onRecordingStoppedOrSelected: function (_, recording) {
if (!recording.isRecording()) {
this.render();
}
},
/**
* Fired when a range is selected or cleared in the OverviewView.
*/
_onRangeChange: function (_, interval) {
if (DetailsView.isViewSelected(this)) {
let debounced = () => this.render(interval);
setNamedTimeout("waterfall-update", WATERFALL_UPDATE_DEBOUNCE, debounced);
} else {
this._dirty = true;
this._interval = interval;
}
},
/**
* Fired when a view is selected in the DetailsView.
*/
_onDetailsViewSelected: function() {
if (DetailsView.isViewSelected(this) && this._dirty) {
this.render(this._interval);
this._dirty = false;
}
},
/**
* Called when a marker is selected in the waterfall view,
* updating the markers detail view.
@ -132,9 +89,4 @@ let WaterfallView = {
this.waterfall.recalculateBounds();
this.render();
}
};
/**
* Convenient way of emitting events from the view.
*/
EventEmitter.decorate(WaterfallView);
});