tenfourfox/devtools/client/performance/views/details-waterfall.js
Cameron Kaiser c9b2922b70 hello FPR
2017-04-19 00:56:45 -07:00

242 lines
7.2 KiB
JavaScript

/* 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/. */
/* import-globals-from ../performance-controller.js */
/* import-globals-from ../performance-view.js */
/* globals window */
"use strict";
const WATERFALL_RESIZE_EVENTS_DRAIN = 100; // ms
const MARKER_DETAILS_WIDTH = 200;
/**
* Waterfall view containing the timeline markers, controlled by DetailsView.
*/
var WaterfallView = Heritage.extend(DetailsSubview, {
// Smallest unit of time between two markers. Larger by 10x^3 than Number.EPSILON.
MARKER_EPSILON: 0.000000000001,
observedPrefs: [
"hidden-markers"
],
rerenderPrefs: [
"hidden-markers"
],
rangeChangeDebounceTime: 75, // ms
/**
* Sets up the view with event binding.
*/
initialize: function () {
DetailsSubview.initialize.call(this);
this._cache = new WeakMap();
this._onMarkerSelected = this._onMarkerSelected.bind(this);
this._onResize = this._onResize.bind(this);
this._onViewSource = this._onViewSource.bind(this);
this._onShowAllocations = this._onShowAllocations.bind(this);
this._hiddenMarkers = PerformanceController.getPref("hidden-markers");
this.headerContainer = $("#waterfall-header");
this.breakdownContainer = $("#waterfall-breakdown");
this.detailsContainer = $("#waterfall-details");
this.detailsSplitter = $("#waterfall-view > splitter");
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
this.details.hidden = true;
this.details.on("resize", this._onResize);
this.details.on("view-source", this._onViewSource);
this.details.on("show-allocations", this._onShowAllocations);
window.addEventListener("resize", this._onResize);
// TODO bug 1167093 save the previously set width, and ensure minimum width
this.details.width = MARKER_DETAILS_WIDTH;
},
/**
* Unbinds events.
*/
destroy: function () {
DetailsSubview.destroy.call(this);
this._cache = null;
this.details.off("resize", this._onResize);
this.details.off("view-source", this._onViewSource);
this.details.off("show-allocations", this._onShowAllocations);
window.removeEventListener("resize", this._onResize);
},
/**
* Method for handling all the set up for rendering a new waterfall.
*
* @param object interval [optional]
* The { startTime, endTime }, in milliseconds.
*/
render: function(interval={}) {
let recording = PerformanceController.getCurrentRecording();
let startTime = interval.startTime || 0;
let endTime = interval.endTime || recording.getDuration();
let markers = recording.getMarkers();
let rootMarkerNode = this._prepareWaterfallTree(markers);
this._populateWaterfallTree(rootMarkerNode, { startTime, endTime });
this.emit(EVENTS.WATERFALL_RENDERED);
},
/**
* Called when a marker is selected in the waterfall view,
* updating the markers detail view.
*/
_onMarkerSelected: function (event, marker) {
let recording = PerformanceController.getCurrentRecording();
let frames = recording.getFrames();
let allocations = recording.getConfiguration().withAllocations;
if (event === "selected") {
this.details.render({ marker, frames, allocations });
this.details.hidden = false;
this._lastSelected = marker;
}
if (event === "unselected") {
this.details.empty();
}
},
/**
* Called when the marker details view is resized.
*/
_onResize: function () {
setNamedTimeout("waterfall-resize", WATERFALL_RESIZE_EVENTS_DRAIN, () => {
this._markersRoot.recalculateBounds();
this.render(OverviewView.getTimeInterval());
});
},
/**
* Called whenever an observed pref is changed.
*/
_onObservedPrefChange: function(_, prefName) {
this._hiddenMarkers = PerformanceController.getPref("hidden-markers");
// Clear the cache as we'll need to recompute the collapsed
// marker model
this._cache = new WeakMap();
},
/**
* Called when MarkerDetails view emits an event to view source.
*/
_onViewSource: function (_, data) {
gToolbox.viewSourceInDebugger(data.url, data.line);
},
/**
* Called when MarkerDetails view emits an event to snap to allocations.
*/
_onShowAllocations: function (_, data) {
let { endTime } = data;
let startTime = 0;
let recording = PerformanceController.getCurrentRecording();
let markers = recording.getMarkers();
let lastGCMarkerFromPreviousCycle = null;
let lastGCMarker = null;
// Iterate over markers looking for the most recent GC marker
// from the cycle before the marker's whose allocations we're interested in.
for (let marker of markers) {
// We found the marker whose allocations we're tracking; abort
if (marker.start === endTime) {
break;
}
if (marker.name === "GarbageCollection") {
if (lastGCMarker && lastGCMarker.cycle !== marker.cycle) {
lastGCMarkerFromPreviousCycle = lastGCMarker;
}
lastGCMarker = marker;
}
}
if (lastGCMarkerFromPreviousCycle) {
startTime = lastGCMarkerFromPreviousCycle.end;
}
// Adjust times so we don't include the range of these markers themselves.
endTime -= this.MARKER_EPSILON;
startTime += startTime !== 0 ? this.MARKER_EPSILON : 0;
OverviewView.setTimeInterval({ startTime, endTime });
DetailsView.selectView("memory-calltree");
},
/**
* Called when the recording is stopped and prepares data to
* populate the waterfall tree.
*/
_prepareWaterfallTree: function(markers) {
let cached = this._cache.get(markers);
if (cached) {
return cached;
}
let rootMarkerNode = WaterfallUtils.createParentNode({ name: "(root)" });
WaterfallUtils.collapseMarkersIntoNode({
rootNode: rootMarkerNode,
markersList: markers,
filter: this._hiddenMarkers
});
this._cache.set(markers, rootMarkerNode);
return rootMarkerNode;
},
/**
* Renders the waterfall tree.
*/
_populateWaterfallTree: function(rootMarkerNode, interval) {
let root = new MarkerView({
marker: rootMarkerNode,
// The root node is irrelevant in a waterfall tree.
hidden: true,
// The waterfall tree should not expand by default.
autoExpandDepth: 0
});
let header = new WaterfallHeader(root);
this._markersRoot = root;
this._waterfallHeader = header;
root.filter = this._hiddenMarkers;
root.interval = interval;
root.on("selected", this._onMarkerSelected);
root.on("unselected", this._onMarkerSelected);
this.breakdownContainer.innerHTML = "";
root.attachTo(this.breakdownContainer);
this.headerContainer.innerHTML = "";
header.attachTo(this.headerContainer);
// If an item was previously selected in this view, attempt to
// re-select it by traversing the newly created tree.
if (this._lastSelected) {
let item = root.find(i => i.marker === this._lastSelected);
if (item) {
item.focus();
}
}
},
toString: () => "[object WaterfallView]"
});
EventEmitter.decorate(WaterfallView);