Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(744)

Unified Diff: third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js
diff --git a/third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js b/third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js
index 16fc2816b5f7d8ef1d0fd36ab011b1c2252685a7..9294f25eb371a0cfe3121f0e5648271594540efa 100644
--- a/third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js
+++ b/third_party/WebKit/Source/devtools/front_end/ui_lazy/FlameChart.js
@@ -27,38 +27,38 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-
/**
* @interface
*/
-WebInspector.FlameChartDelegate = function() { };
+WebInspector.FlameChartDelegate = function() {};
WebInspector.FlameChartDelegate.prototype = {
- /**
- * @param {number} startTime
- * @param {number} endTime
- */
- requestWindowTimes: function(startTime, endTime) { },
-
- /**
- * @param {number} startTime
- * @param {number} endTime
- */
- updateRangeSelection: function(startTime, endTime) { },
+ /**
+ * @param {number} startTime
+ * @param {number} endTime
+ */
+ requestWindowTimes: function(startTime, endTime) {},
+
+ /**
+ * @param {number} startTime
+ * @param {number} endTime
+ */
+ updateRangeSelection: function(startTime, endTime) {},
};
/**
- * @constructor
- * @extends {WebInspector.ChartViewport}
- * @param {!WebInspector.FlameChartDataProvider} dataProvider
- * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
- * @param {!WebInspector.Setting=} groupExpansionSetting
+ * @unrestricted
*/
-WebInspector.FlameChart = function(dataProvider, flameChartDelegate, groupExpansionSetting)
-{
- WebInspector.ChartViewport.call(this);
- this.registerRequiredCSS("ui_lazy/flameChart.css");
- this.contentElement.classList.add("flame-chart-main-pane");
+WebInspector.FlameChart = class extends WebInspector.ChartViewport {
+ /**
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider
+ * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
+ * @param {!WebInspector.Setting=} groupExpansionSetting
+ */
+ constructor(dataProvider, flameChartDelegate, groupExpansionSetting) {
+ super();
+ this.registerRequiredCSS('ui_lazy/flameChart.css');
+ this.contentElement.classList.add('flame-chart-main-pane');
this._flameChartDelegate = flameChartDelegate;
this._groupExpansionSetting = groupExpansionSetting;
this._groupExpansionState = groupExpansionSetting && groupExpansionSetting.get() || {};
@@ -66,18 +66,18 @@ WebInspector.FlameChart = function(dataProvider, flameChartDelegate, groupExpans
this._dataProvider = dataProvider;
this._calculator = new WebInspector.FlameChart.Calculator(dataProvider);
- this._canvas = /** @type {!HTMLCanvasElement} */ (this.viewportElement.createChild("canvas"));
+ this._canvas = /** @type {!HTMLCanvasElement} */ (this.viewportElement.createChild('canvas'));
this._canvas.tabIndex = 1;
this.setDefaultFocusedElement(this._canvas);
- this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this), false);
- this._canvas.addEventListener("mouseout", this._onMouseOut.bind(this), false);
- this._canvas.addEventListener("click", this._onClick.bind(this), false);
- this._canvas.addEventListener("keydown", this._onKeyDown.bind(this), false);
+ this._canvas.addEventListener('mousemove', this._onMouseMove.bind(this), false);
+ this._canvas.addEventListener('mouseout', this._onMouseOut.bind(this), false);
+ this._canvas.addEventListener('click', this._onClick.bind(this), false);
+ this._canvas.addEventListener('keydown', this._onKeyDown.bind(this), false);
- this._entryInfo = this.viewportElement.createChild("div", "flame-chart-entry-info");
- this._markerHighlighElement = this.viewportElement.createChild("div", "flame-chart-marker-highlight-element");
- this._highlightElement = this.viewportElement.createChild("div", "flame-chart-highlight-element");
- this._selectedElement = this.viewportElement.createChild("div", "flame-chart-selected-element");
+ this._entryInfo = this.viewportElement.createChild('div', 'flame-chart-entry-info');
+ this._markerHighlighElement = this.viewportElement.createChild('div', 'flame-chart-marker-highlight-element');
+ this._highlightElement = this.viewportElement.createChild('div', 'flame-chart-highlight-element');
+ this._selectedElement = this.viewportElement.createChild('div', 'flame-chart-selected-element');
this._windowLeft = 0.0;
this._windowRight = 1.0;
@@ -109,6 +109,1161 @@ WebInspector.FlameChart = function(dataProvider, flameChartDelegate, groupExpans
this._textWidth = new Map();
this._lastMouseOffsetX = 0;
+ }
+
+ /**
+ * @override
+ */
+ willHide() {
+ this.hideHighlight();
+ }
+
+ /**
+ * @param {number} entryIndex
+ */
+ highlightEntry(entryIndex) {
+ if (this._highlightedEntryIndex === entryIndex)
+ return;
+ this._highlightedEntryIndex = entryIndex;
+ this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
+ }
+
+ hideHighlight() {
+ this._entryInfo.removeChildren();
+ this._highlightedEntryIndex = -1;
+ this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
+ }
+
+ _resetCanvas() {
+ var ratio = window.devicePixelRatio;
+ this._canvas.width = this._offsetWidth * ratio;
+ this._canvas.height = this._offsetHeight * ratio;
+ this._canvas.style.width = this._offsetWidth + 'px';
+ this._canvas.style.height = this._offsetHeight + 'px';
+ }
+
+ /**
+ * @return {?WebInspector.FlameChart.TimelineData}
+ */
+ _timelineData() {
+ if (!this._dataProvider)
+ return null;
+ var timelineData = this._dataProvider.timelineData();
+ if (timelineData !== this._rawTimelineData || timelineData.entryStartTimes.length !== this._rawTimelineDataLength)
+ this._processTimelineData(timelineData);
+ return this._rawTimelineData;
+ }
+
+ /**
+ * @param {number} entryIndex
+ */
+ _revealEntry(entryIndex) {
+ var timelineData = this._timelineData();
+ if (!timelineData)
+ return;
+ // Think in terms of not where we are, but where we'll be after animation (if present)
+ var timeLeft = this._cancelWindowTimesAnimation ? this._pendingAnimationTimeLeft : this._timeWindowLeft;
+ var timeRight = this._cancelWindowTimesAnimation ? this._pendingAnimationTimeRight : this._timeWindowRight;
+ var entryStartTime = timelineData.entryStartTimes[entryIndex];
+ var entryTotalTime = timelineData.entryTotalTimes[entryIndex];
+ var entryEndTime = entryStartTime + entryTotalTime;
+ var minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft);
+
+ var y = this._levelToHeight(timelineData.entryLevels[entryIndex]);
+ this.setScrollOffset(y, this._barHeight);
+
+ if (timeLeft > entryEndTime) {
+ var delta = timeLeft - entryEndTime + minEntryTimeWindow;
+ this._flameChartDelegate.requestWindowTimes(timeLeft - delta, timeRight - delta);
+ } else if (timeRight < entryStartTime) {
+ var delta = entryStartTime - timeRight + minEntryTimeWindow;
+ this._flameChartDelegate.requestWindowTimes(timeLeft + delta, timeRight + delta);
+ }
+ }
+
+ /**
+ * @override
+ * @param {number} startTime
+ * @param {number} endTime
+ */
+ setWindowTimes(startTime, endTime) {
+ super.setWindowTimes(startTime, endTime);
+ this._updateHighlight();
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onMouseMove(event) {
+ this._lastMouseOffsetX = event.offsetX;
+ this._lastMouseOffsetY = event.offsetY;
+ if (!this._enabled())
+ return;
+ if (this.isDragging())
+ return;
+ if (this._coordinatesToGroupIndex(event.offsetX, event.offsetY) >= 0) {
+ this.hideHighlight();
+ this.viewportElement.style.cursor = 'pointer';
+ return;
+ }
+ this._updateHighlight();
+ }
+
+ _updateHighlight() {
+ var inDividersBar = this._lastMouseOffsetY < WebInspector.FlameChart.DividersBarHeight;
+ this._highlightedMarkerIndex = inDividersBar ? this._markerIndexAtPosition(this._lastMouseOffsetX) : -1;
+ this._updateMarkerHighlight();
+
+ var entryIndex = this._coordinatesToEntryIndex(this._lastMouseOffsetX, this._lastMouseOffsetY);
+ if (entryIndex === -1) {
+ this.hideHighlight();
+ return;
+ }
+ if (this.isDragging())
+ return;
+ this._updatePopover(entryIndex);
+ this.viewportElement.style.cursor = this._dataProvider.canJumpToEntry(entryIndex) ? 'pointer' : 'default';
+ this.highlightEntry(entryIndex);
+ }
+
+ _onMouseOut() {
+ this._lastMouseOffsetX = -1;
+ this._lastMouseOffsetY = -1;
+ this.hideHighlight();
+ }
+
+ /**
+ * @param {number} entryIndex
+ */
+ _updatePopover(entryIndex) {
+ if (entryIndex === this._highlightedEntryIndex) {
+ this._updatePopoverOffset();
+ return;
+ }
+ this._entryInfo.removeChildren();
+ var popoverElement = this._dataProvider.prepareHighlightedEntryInfo(entryIndex);
+ if (popoverElement) {
+ this._entryInfo.appendChild(popoverElement);
+ this._updatePopoverOffset();
+ }
+ }
+
+ _updatePopoverOffset() {
+ var mouseX = this._lastMouseOffsetX;
+ var mouseY = this._lastMouseOffsetY;
+ var parentWidth = this._entryInfo.parentElement.clientWidth;
+ var parentHeight = this._entryInfo.parentElement.clientHeight;
+ var infoWidth = this._entryInfo.clientWidth;
+ var infoHeight = this._entryInfo.clientHeight;
+ var /** @const */ offsetX = 10;
+ var /** @const */ offsetY = 6;
+ var x;
+ var y;
+ for (var quadrant = 0; quadrant < 4; ++quadrant) {
+ var dx = quadrant & 2 ? -offsetX - infoWidth : offsetX;
+ var dy = quadrant & 1 ? -offsetY - infoHeight : offsetY;
+ x = Number.constrain(mouseX + dx, 0, parentWidth - infoWidth);
+ y = Number.constrain(mouseY + dy, 0, parentHeight - infoHeight);
+ if (x >= mouseX || mouseX >= x + infoWidth || y >= mouseY || mouseY >= y + infoHeight)
+ break;
+ }
+ this._entryInfo.style.left = x + 'px';
+ this._entryInfo.style.top = y + 'px';
+ }
+
+ /**
+ * @param {!Event} event
+ */
+ _onClick(event) {
+ this.focus();
+ // onClick comes after dragStart and dragEnd events.
+ // So if there was drag (mouse move) in the middle of that events
+ // we skip the click. Otherwise we jump to the sources.
+ const clickThreshold = 5;
+ if (this.maxDragOffset() > clickThreshold)
+ return;
+ var groupIndex = this._coordinatesToGroupIndex(event.offsetX, event.offsetY);
+ if (groupIndex >= 0) {
+ this._toggleGroupVisibility(groupIndex);
+ return;
+ }
+ this.hideRangeSelection();
+ this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, this._highlightedEntryIndex);
+ }
+
+ /**
+ * @param {number} groupIndex
+ */
+ _toggleGroupVisibility(groupIndex) {
+ if (!this._isGroupCollapsible(groupIndex))
+ return;
+ var groups = this._rawTimelineData.groups;
+ var group = groups[groupIndex];
+ group.expanded = !group.expanded;
+ this._groupExpansionState[group.name] = group.expanded;
+ if (this._groupExpansionSetting)
+ this._groupExpansionSetting.set(this._groupExpansionState);
+ this._updateLevelPositions();
+
+ this._updateHighlight();
+ if (!group.expanded) {
+ var timelineData = this._timelineData();
+ var level = timelineData.entryLevels[this._selectedEntryIndex];
+ if (this._selectedEntryIndex >= 0 && level >= group.startLevel &&
+ (groupIndex === groups.length || groups[groupIndex + 1].startLevel > level))
+ this._selectedEntryIndex = -1;
+ }
+
+ this._updateHeight();
+ this._resetCanvas();
+ this._draw(this._offsetWidth, this._offsetHeight);
+ }
+
+ /**
+ * @param {!Event} e
+ */
+ _onKeyDown(e) {
+ this._handleSelectionNavigation(e);
+ }
+
+ /**
+ * @param {!Event} e
+ */
+ _handleSelectionNavigation(e) {
+ if (!WebInspector.KeyboardShortcut.hasNoModifiers(e))
+ return;
+ if (this._selectedEntryIndex === -1)
+ return;
+ var timelineData = this._timelineData();
+ if (!timelineData)
+ return;
+
+ /**
+ * @param {number} time
+ * @param {number} entryIndex
+ * @return {number}
+ */
+ function timeComparator(time, entryIndex) {
+ return time - timelineData.entryStartTimes[entryIndex];
+ }
+
+ /**
+ * @param {number} entry1
+ * @param {number} entry2
+ * @return {boolean}
+ */
+ function entriesIntersect(entry1, entry2) {
+ var start1 = timelineData.entryStartTimes[entry1];
+ var start2 = timelineData.entryStartTimes[entry2];
+ var end1 = start1 + timelineData.entryTotalTimes[entry1];
+ var end2 = start2 + timelineData.entryTotalTimes[entry2];
+ return start1 < end2 && start2 < end1;
+ }
+
+ var keys = WebInspector.KeyboardShortcut.Keys;
+ if (e.keyCode === keys.Left.code || e.keyCode === keys.Right.code) {
+ var level = timelineData.entryLevels[this._selectedEntryIndex];
+ var levelIndexes = this._timelineLevels[level];
+ var indexOnLevel = levelIndexes.lowerBound(this._selectedEntryIndex);
+ indexOnLevel += e.keyCode === keys.Left.code ? -1 : 1;
+ e.consume(true);
+ if (indexOnLevel >= 0 && indexOnLevel < levelIndexes.length)
+ this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, levelIndexes[indexOnLevel]);
+ return;
+ }
+ if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) {
+ e.consume(true);
+ var level = timelineData.entryLevels[this._selectedEntryIndex];
+ level += e.keyCode === keys.Up.code ? -1 : 1;
+ if (level < 0 || level >= this._timelineLevels.length)
+ return;
+ var entryTime = timelineData.entryStartTimes[this._selectedEntryIndex] +
+ timelineData.entryTotalTimes[this._selectedEntryIndex] / 2;
+ var levelIndexes = this._timelineLevels[level];
+ var indexOnLevel = levelIndexes.upperBound(entryTime, timeComparator) - 1;
+ if (!entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel])) {
+ ++indexOnLevel;
+ if (indexOnLevel >= levelIndexes.length ||
+ !entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel]))
+ return;
+ }
+ this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, levelIndexes[indexOnLevel]);
+ }
+ }
+
+ /**
+ * @param {number} x
+ * @return {number}
+ */
+ _cursorTime(x) {
+ return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime + this._minimumBoundary;
+ }
+
+ /**
+ * @param {number} x
+ * @param {number} y
+ * @return {number}
+ */
+ _coordinatesToEntryIndex(x, y) {
+ if (x < 0 || y < 0)
+ return -1;
+ y += this.scrollOffset();
+ var timelineData = this._timelineData();
+ if (!timelineData)
+ return -1;
+ var cursorTime = this._cursorTime(x);
+ var cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1;
+ if (cursorLevel < 0 || !this._visibleLevels[cursorLevel])
+ return -1;
+ var offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel];
+ if (offsetFromLevel > this._barHeight)
+ return -1;
+ var entryStartTimes = timelineData.entryStartTimes;
+ var entryTotalTimes = timelineData.entryTotalTimes;
+ var entryIndexes = this._timelineLevels[cursorLevel];
+ if (!entryIndexes || !entryIndexes.length)
+ return -1;
+
+ /**
+ * @param {number} time
+ * @param {number} entryIndex
+ * @return {number}
+ */
+ function comparator(time, entryIndex) {
+ return time - entryStartTimes[entryIndex];
+ }
+ var indexOnLevel = Math.max(entryIndexes.upperBound(cursorTime, comparator) - 1, 0);
+
+ /**
+ * @this {WebInspector.FlameChart}
+ * @param {number} entryIndex
+ * @return {boolean}
+ */
+ function checkEntryHit(entryIndex) {
+ if (entryIndex === undefined)
+ return false;
+ var startTime = entryStartTimes[entryIndex];
+ var duration = entryTotalTimes[entryIndex];
+ if (isNaN(duration)) {
+ var dx = (startTime - cursorTime) / this._pixelToTime;
+ var dy = this._barHeight / 2 - offsetFromLevel;
+ return dx * dx + dy * dy < this._markerRadius * this._markerRadius;
+ }
+ var endTime = startTime + duration;
+ var barThreshold = 3 * this._pixelToTime;
+ return startTime - barThreshold < cursorTime && cursorTime < endTime + barThreshold;
+ }
+
+ var entryIndex = entryIndexes[indexOnLevel];
+ if (checkEntryHit.call(this, entryIndex))
+ return entryIndex;
+ entryIndex = entryIndexes[indexOnLevel + 1];
+ if (checkEntryHit.call(this, entryIndex))
+ return entryIndex;
+ return -1;
+ }
+
+ /**
+ * @param {number} x
+ * @param {number} y
+ * @return {number}
+ */
+ _coordinatesToGroupIndex(x, y) {
+ if (x < 0 || y < 0)
+ return -1;
+ y += this.scrollOffset();
+ var groups = this._rawTimelineData.groups || [];
+ var group = this._groupOffsets.upperBound(y) - 1;
+
+ if (group < 0 || group >= groups.length || y - this._groupOffsets[group] >= groups[group].style.height)
+ return -1;
+ var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
+ context.save();
+ context.font = groups[group].style.font;
+ var right = this._headerLeftPadding + this._labelWidthForGroup(context, groups[group]);
+ context.restore();
+ if (x > right)
+ return -1;
+
+ return group;
+ }
+
+ /**
+ * @param {number} x
+ * @return {number}
+ */
+ _markerIndexAtPosition(x) {
+ var markers = this._timelineData().markers;
+ if (!markers)
+ return -1;
+ var accurracyOffsetPx = 1;
+ var time = this._cursorTime(x);
+ var leftTime = this._cursorTime(x - accurracyOffsetPx);
+ var rightTime = this._cursorTime(x + accurracyOffsetPx);
+
+ var left = this._markerIndexBeforeTime(leftTime);
+ var markerIndex = -1;
+ var distance = Infinity;
+ for (var i = left; i < markers.length && markers[i].startTime() < rightTime; i++) {
+ var nextDistance = Math.abs(markers[i].startTime() - time);
+ if (nextDistance < distance) {
+ markerIndex = i;
+ distance = nextDistance;
+ }
+ }
+ return markerIndex;
+ }
+
+ /**
+ * @param {number} time
+ * @return {number}
+ */
+ _markerIndexBeforeTime(time) {
+ return this._timelineData().markers.lowerBound(
+ time, (markerTimestamp, marker) => markerTimestamp - marker.startTime());
+ }
+
+ /**
+ * @param {number} height
+ * @param {number} width
+ */
+ _draw(width, height) {
+ var timelineData = this._timelineData();
+ if (!timelineData)
+ return;
+
+ var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
+ context.save();
+ var ratio = window.devicePixelRatio;
+ var top = this.scrollOffset();
+ context.scale(ratio, ratio);
+ context.translate(0, -top);
+ var defaultFont = '11px ' + WebInspector.fontFamily();
+ context.font = defaultFont;
+
+ var timeWindowRight = this._timeWindowRight;
+ var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeToPixel;
+ var entryTotalTimes = timelineData.entryTotalTimes;
+ var entryStartTimes = timelineData.entryStartTimes;
+ var entryLevels = timelineData.entryLevels;
+
+ var titleIndices = [];
+ var markerIndices = [];
+ var textPadding = this._dataProvider.textPadding();
+ var minTextWidth = 2 * textPadding + this._measureWidth(context, '\u2026');
+ var barHeight = this._barHeight;
+ var minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(top) - 1, 0);
+
+ /** @type {!Map<string, !Array<number>>} */
+ var colorBuckets = new Map();
+ for (var level = minVisibleBarLevel; level < this._dataProvider.maxStackDepth(); ++level) {
+ if (this._levelToHeight(level) > top + height)
+ break;
+ if (!this._visibleLevels[level])
+ continue;
+
+ // Entries are ordered by start time within a level, so find the last visible entry.
+ var levelIndexes = this._timelineLevels[level];
+ var rightIndexOnLevel =
+ levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
+ var lastDrawOffset = Infinity;
+ for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
+ var entryIndex = levelIndexes[entryIndexOnLevel];
+ var entryStartTime = entryStartTimes[entryIndex];
+ var entryOffsetRight = entryStartTime + (entryTotalTimes[entryIndex] || 0);
+ if (entryOffsetRight <= timeWindowLeft)
+ break;
+
+ var barX = this._timeToPositionClipped(entryStartTime);
+ // Check if the entry entirely fits into an already drawn pixel, we can just skip drawing it.
+ if (barX >= lastDrawOffset)
+ continue;
+ lastDrawOffset = barX;
+
+ var color = this._dataProvider.entryColor(entryIndex);
+ var bucket = colorBuckets.get(color);
+ if (!bucket) {
+ bucket = [];
+ colorBuckets.set(color, bucket);
+ }
+ bucket.push(entryIndex);
+ }
+ }
+
+ var colors = colorBuckets.keysArray();
+ // We don't use for-of here because it's slow.
+ for (var c = 0; c < colors.length; ++c) {
+ var color = colors[c];
+ var indexes = colorBuckets.get(color);
+ context.beginPath();
+ context.fillStyle = color;
+ for (var i = 0; i < indexes.length; ++i) {
+ var entryIndex = indexes[i];
+ var entryStartTime = entryStartTimes[entryIndex];
+ var barX = this._timeToPositionClipped(entryStartTime);
+ var duration = entryTotalTimes[entryIndex];
+ var barLevel = entryLevels[entryIndex];
+ var barY = this._levelToHeight(barLevel);
+ if (isNaN(duration)) {
+ context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
+ context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
+ markerIndices.push(entryIndex);
+ continue;
+ }
+ var barRight = this._timeToPositionClipped(entryStartTime + duration);
+ var barWidth = Math.max(barRight - barX, 1);
+ context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
+ if (barWidth > minTextWidth || this._dataProvider.forceDecoration(entryIndex))
+ titleIndices.push(entryIndex);
+ }
+ context.fill();
+ }
+
+ context.strokeStyle = 'rgba(0, 0, 0, 0.2)';
+ context.beginPath();
+ for (var m = 0; m < markerIndices.length; ++m) {
+ var entryIndex = markerIndices[m];
+ var entryStartTime = entryStartTimes[entryIndex];
+ var barX = this._timeToPositionClipped(entryStartTime);
+ var barLevel = entryLevels[entryIndex];
+ var barY = this._levelToHeight(barLevel);
+ context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
+ context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
+ }
+ context.stroke();
+
+ context.textBaseline = 'alphabetic';
+ var textBaseHeight = this._barHeight - this._dataProvider.textBaseline();
+
+ for (var i = 0; i < titleIndices.length; ++i) {
+ var entryIndex = titleIndices[i];
+ var entryStartTime = entryStartTimes[entryIndex];
+ var barX = this._timeToPositionClipped(entryStartTime);
+ var barRight = Math.min(this._timeToPositionClipped(entryStartTime + entryTotalTimes[entryIndex]), width) + 1;
+ var barWidth = barRight - barX;
+ var barLevel = entryLevels[entryIndex];
+ var barY = this._levelToHeight(barLevel);
+ var text = this._dataProvider.entryTitle(entryIndex);
+ if (text && text.length) {
+ context.font = this._dataProvider.entryFont(entryIndex) || defaultFont;
+ text = this._prepareText(context, text, barWidth - 2 * textPadding);
+ }
+ var unclippedBarX = this._timeToPosition(entryStartTime);
+ if (this._dataProvider.decorateEntry(
+ entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, this._timeToPixel))
+ continue;
+ if (!text || !text.length)
+ continue;
+ context.fillStyle = this._dataProvider.textColor(entryIndex);
+ context.fillText(text, barX + textPadding, barY + textBaseHeight);
+ }
+
+ this._drawFlowEvents(context, width, height);
+
+ context.restore();
+
+ WebInspector.TimelineGrid.drawCanvasGrid(context, this._calculator, 3);
+ this._drawMarkers();
+ this._drawGroupHeaders(width, height);
+
+ this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
+ this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
+ this._updateMarkerHighlight();
+ }
+
+ /**
+ * @param {number} width
+ * @param {number} height
+ */
+ _drawGroupHeaders(width, height) {
+ var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
+ var top = this.scrollOffset();
+ var ratio = window.devicePixelRatio;
+ var barHeight = this._barHeight;
+ var textBaseHeight = barHeight - this._dataProvider.textBaseline();
+ var groups = this._rawTimelineData.groups || [];
+ if (!groups.length)
+ return;
+
+ var groupOffsets = this._groupOffsets;
+ var lastGroupOffset = Array.prototype.peekLast.call(groupOffsets);
+ var colorUsage = WebInspector.ThemeSupport.ColorUsage;
+
+ context.save();
+ context.scale(ratio, ratio);
+ context.translate(0, -top);
+
+ context.fillStyle = WebInspector.themeSupport.patchColor('#eee', colorUsage.Background);
+ forEachGroup.call(this, (offset, index, group) => {
+ var paddingHeight = group.style.padding;
+ if (paddingHeight < 5)
+ return;
+ context.fillRect(0, offset - paddingHeight + 2, width, paddingHeight - 4);
+ });
+ if (groups.length && lastGroupOffset < top + height)
+ context.fillRect(0, lastGroupOffset + 2, width, top + height - lastGroupOffset);
+
+ context.strokeStyle = WebInspector.themeSupport.patchColor('#bbb', colorUsage.Background);
+ context.beginPath();
+ forEachGroup.call(this, (offset, index, group, isFirst) => {
+ if (isFirst || group.style.padding < 4)
+ return;
+ hLine(offset - 2.5);
+ });
+ hLine(lastGroupOffset + 0.5);
+ context.stroke();
+
+ forEachGroup.call(this, (offset, index, group) => {
+ if (group.style.useFirstLineForOverview)
+ return;
+ if (!this._isGroupCollapsible(index) || group.expanded) {
+ if (!group.style.shareHeaderLine) {
+ context.fillStyle = group.style.backgroundColor;
+ context.fillRect(0, offset, width, group.style.height);
+ }
+ return;
+ }
+ var nextGroup = index + 1;
+ while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel)
+ nextGroup++;
+ var endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
+ this._drawCollapsedOverviewForGroup(offset + 1, group.startLevel, endLevel);
+ });
+
+ context.save();
+ forEachGroup.call(this, (offset, index, group) => {
+ context.font = group.style.font;
+ if (this._isGroupCollapsible(index) && !group.expanded || group.style.shareHeaderLine) {
+ var width = this._labelWidthForGroup(context, group);
+ context.fillStyle = WebInspector.Color.parse(group.style.backgroundColor).setAlpha(0.7).asString(null);
+ context.fillRect(
+ this._headerLeftPadding - this._headerLabelXPadding, offset + this._headerLabelYPadding, width,
+ barHeight - 2 * this._headerLabelYPadding);
+ }
+ context.fillStyle = group.style.color;
+ context.fillText(
+ group.name, Math.floor(this._expansionArrowIndent * (group.style.nestingLevel + 1) + this._arrowSide),
+ offset + textBaseHeight);
+ });
+ context.restore();
+
+ context.fillStyle = WebInspector.themeSupport.patchColor('#6e6e6e', colorUsage.Foreground);
+ context.beginPath();
+ forEachGroup.call(this, (offset, index, group) => {
+ if (this._isGroupCollapsible(index))
+ drawExpansionArrow.call(
+ this, this._expansionArrowIndent * (group.style.nestingLevel + 1),
+ offset + textBaseHeight - this._arrowSide / 2, !!group.expanded);
+ });
+ context.fill();
+
+ context.strokeStyle = WebInspector.themeSupport.patchColor('#ddd', colorUsage.Background);
+ context.beginPath();
+ context.stroke();
+
+ context.restore();
+
+ /**
+ * @param {number} y
+ */
+ function hLine(y) {
+ context.moveTo(0, y);
+ context.lineTo(width, y);
+ }
+
+ /**
+ * @param {number} x
+ * @param {number} y
+ * @param {boolean} expanded
+ * @this {WebInspector.FlameChart}
+ */
+ function drawExpansionArrow(x, y, expanded) {
+ var arrowHeight = this._arrowSide * Math.sqrt(3) / 2;
+ var arrowCenterOffset = Math.round(arrowHeight / 2);
+ context.save();
+ context.translate(x, y);
+ context.rotate(expanded ? Math.PI / 2 : 0);
+ context.moveTo(-arrowCenterOffset, -this._arrowSide / 2);
+ context.lineTo(-arrowCenterOffset, this._arrowSide / 2);
+ context.lineTo(arrowHeight - arrowCenterOffset, 0);
+ context.restore();
+ }
+
+ /**
+ * @param {function(number, number, !WebInspector.FlameChart.Group, boolean)} callback
+ * @this {WebInspector.FlameChart}
+ */
+ function forEachGroup(callback) {
+ /** @type !Array<{nestingLevel: number, visible: boolean}> */
+ var groupStack = [{nestingLevel: -1, visible: true}];
+ for (var i = 0; i < groups.length; ++i) {
+ var groupTop = groupOffsets[i];
+ var group = groups[i];
+ if (groupTop - group.style.padding > top + height)
+ break;
+ var firstGroup = true;
+ while (groupStack.peekLast().nestingLevel >= group.style.nestingLevel) {
+ groupStack.pop();
+ firstGroup = false;
+ }
+ var parentGroupVisible = groupStack.peekLast().visible;
+ var thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible(i) || group.expanded);
+ groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGroupVisible});
+ if (!parentGroupVisible || groupTop + group.style.height < top)
+ continue;
+ callback(groupTop, i, group, firstGroup);
+ }
+ }
+ }
+
+ /**
+ * @param {!CanvasRenderingContext2D} context
+ * @param {!WebInspector.FlameChart.Group} group
+ * @return {number}
+ */
+ _labelWidthForGroup(context, group) {
+ return this._measureWidth(context, group.name) + this._expansionArrowIndent * (group.style.nestingLevel + 1) +
+ 2 * this._headerLabelXPadding;
+ }
+
+ /**
+ * @param {number} y
+ * @param {number} startLevel
+ * @param {number} endLevel
+ */
+ _drawCollapsedOverviewForGroup(y, startLevel, endLevel) {
+ var range = new WebInspector.SegmentedRange(mergeCallback);
+ var timeWindowRight = this._timeWindowRight;
+ var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeToPixel;
+ var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
+ var barHeight = this._barHeight - 2;
+ var entryStartTimes = this._rawTimelineData.entryStartTimes;
+ var entryTotalTimes = this._rawTimelineData.entryTotalTimes;
+
+ for (var level = startLevel; level < endLevel; ++level) {
+ var levelIndexes = this._timelineLevels[level];
+ var rightIndexOnLevel =
+ levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
+ var lastDrawOffset = Infinity;
+
+ for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
+ var entryIndex = levelIndexes[entryIndexOnLevel];
+ var entryStartTime = entryStartTimes[entryIndex];
+ var startPosition = this._timeToPositionClipped(entryStartTime);
+ var entryEndTime = entryStartTime + entryTotalTimes[entryIndex];
+ if (isNaN(entryEndTime) || startPosition >= lastDrawOffset)
+ continue;
+ if (entryEndTime <= timeWindowLeft)
+ break;
+ lastDrawOffset = startPosition;
+ var color = this._dataProvider.entryColor(entryIndex);
+ range.append(new WebInspector.Segment(startPosition, this._timeToPositionClipped(entryEndTime), color));
+ }
+ }
+
+ var segments = range.segments().slice().sort((a, b) => a.data.localeCompare(b.data));
+ var lastColor;
+ context.beginPath();
+ for (var i = 0; i < segments.length; ++i) {
+ var segment = segments[i];
+ if (lastColor !== segments[i].data) {
+ context.fill();
+ context.beginPath();
+ lastColor = segments[i].data;
+ context.fillStyle = lastColor;
+ }
+ context.rect(segment.begin, y, segment.end - segment.begin, barHeight);
+ }
+ context.fill();
+
+ /**
+ * @param {!WebInspector.Segment} a
+ * @param {!WebInspector.Segment} b
+ * @return {?WebInspector.Segment}
+ */
+ function mergeCallback(a, b) {
+ return a.data === b.data && a.end + 0.4 > b.end ? a : null;
+ }
+ }
+
+ /**
+ * @param {!CanvasRenderingContext2D} context
+ * @param {number} height
+ * @param {number} width
+ */
+ _drawFlowEvents(context, width, height) {
+ var timelineData = this._timelineData();
+ var timeWindowRight = this._timeWindowRight;
+ var timeWindowLeft = this._timeWindowLeft;
+ var flowStartTimes = timelineData.flowStartTimes;
+ var flowEndTimes = timelineData.flowEndTimes;
+ var flowStartLevels = timelineData.flowStartLevels;
+ var flowEndLevels = timelineData.flowEndLevels;
+ var flowCount = flowStartTimes.length;
+ var endIndex = flowStartTimes.lowerBound(timeWindowRight);
+
+ var color = [];
+ var fadeColorsCount = 8;
+ for (var i = 0; i <= fadeColorsCount; ++i)
+ color[i] = 'rgba(128, 0, 0, ' + i / fadeColorsCount + ')';
+ var fadeColorsRange = color.length;
+ var minimumFlowDistancePx = 15;
+ var flowArcHeight = 4 * this._barHeight;
+ var colorIndex = 0;
+ context.lineWidth = 0.5;
+ for (var i = 0; i < endIndex; ++i) {
+ if (flowEndTimes[i] < timeWindowLeft)
+ continue;
+ var startX = this._timeToPosition(flowStartTimes[i]);
+ var endX = this._timeToPosition(flowEndTimes[i]);
+ if (endX - startX < minimumFlowDistancePx)
+ continue;
+ if (startX < -minimumFlowDistancePx && endX > width + minimumFlowDistancePx)
+ continue;
+ // Assign a trasparent color if the flow is small enough or if the previous color was a transparent color.
+ if (endX - startX < minimumFlowDistancePx + fadeColorsRange || colorIndex !== color.length - 1) {
+ colorIndex = Math.min(fadeColorsRange - 1, Math.floor(endX - startX - minimumFlowDistancePx));
+ context.strokeStyle = color[colorIndex];
+ }
+ var startY = this._levelToHeight(flowStartLevels[i]) + this._barHeight;
+ var endY = this._levelToHeight(flowEndLevels[i]);
+ context.beginPath();
+ context.moveTo(startX, startY);
+ var arcHeight = Math.max(Math.sqrt(Math.abs(startY - endY)), flowArcHeight) + 5;
+ context.bezierCurveTo(startX, startY + arcHeight, endX, endY + arcHeight, endX, endY + this._barHeight);
+ context.stroke();
+ }
+ }
+
+ _drawMarkers() {
+ var markers = this._timelineData().markers;
+ var left = this._markerIndexBeforeTime(this._calculator.minimumBoundary());
+ var rightBoundary = this._calculator.maximumBoundary();
+
+ var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext('2d'));
+ context.save();
+ var ratio = window.devicePixelRatio;
+ context.scale(ratio, ratio);
+ var height = WebInspector.FlameChart.DividersBarHeight - 1;
+ for (var i = left; i < markers.length; i++) {
+ var timestamp = markers[i].startTime();
+ if (timestamp > rightBoundary)
+ break;
+ markers[i].draw(context, this._calculator.computePosition(timestamp), height, this._timeToPixel);
+ }
+ context.restore();
+ }
+
+ _updateMarkerHighlight() {
+ var element = this._markerHighlighElement;
+ if (element.parentElement)
+ element.remove();
+ var markerIndex = this._highlightedMarkerIndex;
+ if (markerIndex === -1)
+ return;
+ var marker = this._timelineData().markers[markerIndex];
+ var barX = this._timeToPositionClipped(marker.startTime());
+ element.title = marker.title();
+ var style = element.style;
+ style.left = barX + 'px';
+ style.backgroundColor = marker.color();
+ this.viewportElement.appendChild(element);
+ }
+
+ /**
+ * @param {?WebInspector.FlameChart.TimelineData} timelineData
+ */
+ _processTimelineData(timelineData) {
+ if (!timelineData) {
+ this._timelineLevels = null;
+ this._visibleLevelOffsets = null;
+ this._visibleLevels = null;
+ this._groupOffsets = null;
+ this._rawTimelineData = null;
+ this._rawTimelineDataLength = 0;
+ return;
+ }
+
+ this._rawTimelineData = timelineData;
+ this._rawTimelineDataLength = timelineData.entryStartTimes.length;
+
+ var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
+ for (var i = 0; i < timelineData.entryLevels.length; ++i)
+ ++entryCounters[timelineData.entryLevels[i]];
+ var levelIndexes = new Array(entryCounters.length);
+ for (var i = 0; i < levelIndexes.length; ++i) {
+ levelIndexes[i] = new Uint32Array(entryCounters[i]);
+ entryCounters[i] = 0;
+ }
+ for (var i = 0; i < timelineData.entryLevels.length; ++i) {
+ var level = timelineData.entryLevels[i];
+ levelIndexes[level][entryCounters[level]++] = i;
+ }
+ this._timelineLevels = levelIndexes;
+ var groups = this._rawTimelineData.groups || [];
+ for (var i = 0; i < groups.length; ++i) {
+ var expanded = this._groupExpansionState[groups[i].name];
+ if (expanded !== undefined)
+ groups[i].expanded = expanded;
+ }
+ this._updateLevelPositions();
+ this._updateHeight();
+ }
+
+ _updateLevelPositions() {
+ var levelCount = this._dataProvider.maxStackDepth();
+ var groups = this._rawTimelineData.groups || [];
+ this._visibleLevelOffsets = new Uint32Array(levelCount + 1);
+ this._visibleLevels = new Uint16Array(levelCount);
+ this._groupOffsets = new Uint32Array(groups.length + 1);
+
+ var groupIndex = -1;
+ var currentOffset = WebInspector.FlameChart.DividersBarHeight;
+ var visible = true;
+ /** @type !Array<{nestingLevel: number, visible: boolean}> */
+ var groupStack = [{nestingLevel: -1, visible: true}];
+ for (var level = 0; level < levelCount; ++level) {
+ while (groupIndex < groups.length - 1 && level === groups[groupIndex + 1].startLevel) {
+ ++groupIndex;
+ var style = groups[groupIndex].style;
+ var nextLevel = true;
+ while (groupStack.peekLast().nestingLevel >= style.nestingLevel) {
+ groupStack.pop();
+ nextLevel = false;
+ }
+ var thisGroupIsVisible =
+ groupIndex >= 0 && this._isGroupCollapsible(groupIndex) ? groups[groupIndex].expanded : true;
+ var parentGroupIsVisible = groupStack.peekLast().visible;
+ visible = thisGroupIsVisible && parentGroupIsVisible;
+ groupStack.push({nestingLevel: style.nestingLevel, visible: visible});
+ if (parentGroupIsVisible)
+ currentOffset += nextLevel ? 0 : style.padding;
+ this._groupOffsets[groupIndex] = currentOffset;
+ if (parentGroupIsVisible && !style.shareHeaderLine)
+ currentOffset += style.height;
+ }
+ var isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex].startLevel;
+ var thisLevelIsVisible = visible || isFirstOnLevel && groups[groupIndex].style.useFirstLineForOverview;
+ this._visibleLevels[level] = thisLevelIsVisible;
+ this._visibleLevelOffsets[level] = currentOffset;
+ if (thisLevelIsVisible || (parentGroupIsVisible && style.shareHeaderLine && isFirstOnLevel))
+ currentOffset += this._barHeight;
+ }
+ if (groupIndex >= 0)
+ this._groupOffsets[groupIndex + 1] = currentOffset;
+ this._visibleLevelOffsets[level] = currentOffset;
+ }
+
+ /**
+ * @param {number} index
+ */
+ _isGroupCollapsible(index) {
+ var groups = this._rawTimelineData.groups || [];
+ var style = groups[index].style;
+ if (!style.shareHeaderLine || !style.collapsible)
+ return !!style.collapsible;
+ var isLastGroup = index + 1 >= groups.length;
+ if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nestingLevel)
+ return true;
+ var nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : groups[index + 1].startLevel;
+ // For groups that only have one line and share header line, pretend these are not collapsible.
+ return nextGroupLevel !== groups[index].startLevel + 1;
+ }
+
+ /**
+ * @param {number} entryIndex
+ */
+ setSelectedEntry(entryIndex) {
+ if (entryIndex === -1 && !this.isDragging())
+ this.hideRangeSelection();
+ if (this._selectedEntryIndex === entryIndex)
+ return;
+ this._selectedEntryIndex = entryIndex;
+ this._revealEntry(entryIndex);
+ this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
+ }
+
+ /**
+ * @param {!Element} element
+ * @param {number} entryIndex
+ */
+ _updateElementPosition(element, entryIndex) {
+ const elementMinWidthPx = 2;
+ if (element.parentElement)
+ element.remove();
+ if (entryIndex === -1)
+ return;
+ var timelineData = this._timelineData();
+ var startTime = timelineData.entryStartTimes[entryIndex];
+ var endTime = startTime + (timelineData.entryTotalTimes[entryIndex] || 0);
+ var barX = this._timeToPositionClipped(startTime);
+ var barRight = this._timeToPositionClipped(endTime);
+ if (barRight === 0 || barX === this._offsetWidth)
+ return;
+ var barWidth = barRight - barX;
+ var barCenter = barX + barWidth / 2;
+ barWidth = Math.max(barWidth, elementMinWidthPx);
+ barX = barCenter - barWidth / 2;
+ var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - this.scrollOffset();
+ var style = element.style;
+ style.left = barX + 'px';
+ style.top = barY + 'px';
+ style.width = barWidth + 'px';
+ style.height = this._barHeight - 1 + 'px';
+ this.viewportElement.appendChild(element);
+ }
+
+ /**
+ * @param {number} time
+ * @return {number}
+ */
+ _timeToPositionClipped(time) {
+ return Number.constrain(this._timeToPosition(time), 0, this._offsetWidth);
+ }
+
+ /**
+ * @param {number} time
+ * @return {number}
+ */
+ _timeToPosition(time) {
+ return Math.floor((time - this._minimumBoundary) * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
+ }
+
+ /**
+ * @param {number} level
+ * @return {number}
+ */
+ _levelToHeight(level) {
+ return this._visibleLevelOffsets[level];
+ }
+
+ /**
+ * @param {!CanvasRenderingContext2D} context
+ * @param {string} text
+ * @param {number} maxWidth
+ * @return {string}
+ */
+ _prepareText(context, text, maxWidth) {
+ var /** @const */ maxLength = 200;
+ if (maxWidth <= 10)
+ return '';
+ if (text.length > maxLength)
+ text = text.trimMiddle(maxLength);
+ var textWidth = this._measureWidth(context, text);
+ if (textWidth <= maxWidth)
+ return text;
+
+ var l = 0;
+ var r = text.length;
+ var lv = 0;
+ var rv = textWidth;
+ while (l < r && lv !== rv && lv !== maxWidth) {
+ var m = Math.ceil(l + (r - l) * (maxWidth - lv) / (rv - lv));
+ var mv = this._measureWidth(context, text.trimMiddle(m));
+ if (mv <= maxWidth) {
+ l = m;
+ lv = mv;
+ } else {
+ r = m - 1;
+ rv = mv;
+ }
+ }
+ text = text.trimMiddle(l);
+ return text !== '\u2026' ? text : '';
+ }
+
+ /**
+ * @param {!CanvasRenderingContext2D} context
+ * @param {string} text
+ * @return {number}
+ */
+ _measureWidth(context, text) {
+ var /** @const */ maxCacheableLength = 200;
+ if (text.length > maxCacheableLength)
+ return context.measureText(text).width;
+
+ var font = context.font;
+ var textWidths = this._textWidth.get(font);
+ if (!textWidths) {
+ textWidths = new Map();
+ this._textWidth.set(font, textWidths);
+ }
+ var width = textWidths.get(text);
+ if (!width) {
+ width = context.measureText(text).width;
+ textWidths.set(text, width);
+ }
+ return width;
+ }
+
+ _updateBoundaries() {
+ this._totalTime = this._dataProvider.totalTime();
+ this._minimumBoundary = this._dataProvider.minimumBoundary();
+
+ var windowWidth = 1;
+ if (this._timeWindowRight !== Infinity) {
+ this._windowLeft = (this._timeWindowLeft - this._minimumBoundary) / this._totalTime;
+ this._windowRight = (this._timeWindowRight - this._minimumBoundary) / this._totalTime;
+ windowWidth = this._windowRight - this._windowLeft;
+ } else if (this._timeWindowLeft === Infinity) {
+ this._windowLeft = Infinity;
+ this._windowRight = Infinity;
+ } else {
+ this._windowLeft = 0;
+ this._windowRight = 1;
+ }
+
+ var totalPixels = Math.floor((this._offsetWidth - this._paddingLeft) / windowWidth);
+ this._pixelWindowLeft = Math.floor(totalPixels * this._windowLeft);
+
+ this._timeToPixel = totalPixels / this._totalTime;
+ this._pixelToTime = this._totalTime / totalPixels;
+ }
+
+ _updateHeight() {
+ var height = this._levelToHeight(this._dataProvider.maxStackDepth());
+ this.setContentHeight(height);
+ }
+
+ /**
+ * @override
+ */
+ onResize() {
+ super.onResize();
+ this.scheduleUpdate();
+ }
+
+ /**
+ * @override
+ */
+ update() {
+ if (!this._timelineData())
+ return;
+ this._resetCanvas();
+ this._updateHeight();
+ this._updateBoundaries();
+ this._calculator._updateBoundaries(this);
+ this._draw(this._offsetWidth, this._offsetHeight);
+ if (!this.isDragging())
+ this._updateHighlight();
+ }
+
+ /**
+ * @override
+ */
+ reset() {
+ super.reset();
+ this._highlightedMarkerIndex = -1;
+ this._highlightedEntryIndex = -1;
+ this._selectedEntryIndex = -1;
+ /** @type {!Map<string,!Map<string,number>>} */
+ this._textWidth = new Map();
+ this.update();
+ }
+
+ _enabled() {
+ return this._rawTimelineDataLength !== 0;
+ }
};
WebInspector.FlameChart.DividersBarHeight = 18;
@@ -118,9 +1273,7 @@ WebInspector.FlameChart.MinimalTimeWindowMs = 0.5;
/**
* @interface
*/
-WebInspector.FlameChartDataProvider = function()
-{
-};
+WebInspector.FlameChartDataProvider = function() {};
/**
* @typedef {!{name: string, startLevel: number, expanded: (boolean|undefined), style: !WebInspector.FlameChart.GroupStyle}}
@@ -143,14 +1296,16 @@ WebInspector.FlameChart.Group;
WebInspector.FlameChart.GroupStyle;
/**
- * @constructor
- * @param {!Array<number>|!Uint16Array} entryLevels
- * @param {!Array<number>|!Float32Array} entryTotalTimes
- * @param {!Array<number>|!Float64Array} entryStartTimes
- * @param {?Array<!WebInspector.FlameChart.Group>} groups
+ * @unrestricted
*/
-WebInspector.FlameChart.TimelineData = function(entryLevels, entryTotalTimes, entryStartTimes, groups)
-{
+WebInspector.FlameChart.TimelineData = class {
+ /**
+ * @param {!Array<number>|!Uint16Array} entryLevels
+ * @param {!Array<number>|!Float32Array} entryTotalTimes
+ * @param {!Array<number>|!Float64Array} entryStartTimes
+ * @param {?Array<!WebInspector.FlameChart.Group>} groups
+ */
+ constructor(entryLevels, entryTotalTimes, entryStartTimes, groups) {
this.entryLevels = entryLevels;
this.entryTotalTimes = entryTotalTimes;
this.entryStartTimes = entryStartTimes;
@@ -161,1505 +1316,298 @@ WebInspector.FlameChart.TimelineData = function(entryLevels, entryTotalTimes, en
this.flowStartLevels = [];
this.flowEndTimes = [];
this.flowEndLevels = [];
+ }
};
WebInspector.FlameChartDataProvider.prototype = {
- /**
- * @return {number}
- */
- barHeight: function() { },
-
- /**
- * @return {number}
- */
- minimumBoundary: function() { },
-
- /**
- * @return {number}
- */
- totalTime: function() { },
-
- /**
- * @param {number} value
- * @param {number=} precision
- * @return {string}
- */
- formatValue: function(value, precision) { },
-
- /**
- * @return {number}
- */
- maxStackDepth: function() { },
-
- /**
- * @return {?WebInspector.FlameChart.TimelineData}
- */
- timelineData: function() { },
-
- /**
- * @param {number} entryIndex
- * @return {?Element}
- */
- prepareHighlightedEntryInfo: function(entryIndex) { },
-
- /**
- * @param {number} entryIndex
- * @return {boolean}
- */
- canJumpToEntry: function(entryIndex) { },
-
- /**
- * @param {number} entryIndex
- * @return {?string}
- */
- entryTitle: function(entryIndex) { },
-
- /**
- * @param {number} entryIndex
- * @return {?string}
- */
- entryFont: function(entryIndex) { },
-
- /**
- * @param {number} entryIndex
- * @return {string}
- */
- entryColor: function(entryIndex) { },
-
- /**
- * @param {number} entryIndex
- * @param {!CanvasRenderingContext2D} context
- * @param {?string} text
- * @param {number} barX
- * @param {number} barY
- * @param {number} barWidth
- * @param {number} barHeight
- * @param {number} unclippedBarX
- * @param {number} timeToPixels
- * @return {boolean}
- */
- decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, timeToPixels) { },
-
- /**
- * @param {number} entryIndex
- * @return {boolean}
- */
- forceDecoration: function(entryIndex) { },
-
- /**
- * @param {number} entryIndex
- * @return {string}
- */
- textColor: function(entryIndex) { },
-
- /**
- * @return {number}
- */
- textBaseline: function() { },
-
- /**
- * @return {number}
- */
- textPadding: function() { },
-
- /**
- * @return {number}
- */
- paddingLeft: function() { },
+ /**
+ * @return {number}
+ */
+ barHeight: function() {},
+
+ /**
+ * @return {number}
+ */
+ minimumBoundary: function() {},
+
+ /**
+ * @return {number}
+ */
+ totalTime: function() {},
+
+ /**
+ * @param {number} value
+ * @param {number=} precision
+ * @return {string}
+ */
+ formatValue: function(value, precision) {},
+
+ /**
+ * @return {number}
+ */
+ maxStackDepth: function() {},
+
+ /**
+ * @return {?WebInspector.FlameChart.TimelineData}
+ */
+ timelineData: function() {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {?Element}
+ */
+ prepareHighlightedEntryInfo: function(entryIndex) {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {boolean}
+ */
+ canJumpToEntry: function(entryIndex) {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {?string}
+ */
+ entryTitle: function(entryIndex) {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {?string}
+ */
+ entryFont: function(entryIndex) {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {string}
+ */
+ entryColor: function(entryIndex) {},
+
+ /**
+ * @param {number} entryIndex
+ * @param {!CanvasRenderingContext2D} context
+ * @param {?string} text
+ * @param {number} barX
+ * @param {number} barY
+ * @param {number} barWidth
+ * @param {number} barHeight
+ * @param {number} unclippedBarX
+ * @param {number} timeToPixels
+ * @return {boolean}
+ */
+ decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, timeToPixels) {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {boolean}
+ */
+ forceDecoration: function(entryIndex) {},
+
+ /**
+ * @param {number} entryIndex
+ * @return {string}
+ */
+ textColor: function(entryIndex) {},
+
+ /**
+ * @return {number}
+ */
+ textBaseline: function() {},
+
+ /**
+ * @return {number}
+ */
+ textPadding: function() {},
+
+ /**
+ * @return {number}
+ */
+ paddingLeft: function() {},
};
/**
* @interface
*/
-WebInspector.FlameChartMarker = function()
-{
-};
+WebInspector.FlameChartMarker = function() {};
WebInspector.FlameChartMarker.prototype = {
- /**
- * @return {number}
- */
- startTime: function() { },
-
- /**
- * @return {string}
- */
- color: function() { },
-
- /**
- * @return {string}
- */
- title: function() { },
-
- /**
- * @param {!CanvasRenderingContext2D} context
- * @param {number} x
- * @param {number} height
- * @param {number} pixelsPerMillisecond
- */
- draw: function(context, x, height, pixelsPerMillisecond) { },
+ /**
+ * @return {number}
+ */
+ startTime: function() {},
+
+ /**
+ * @return {string}
+ */
+ color: function() {},
+
+ /**
+ * @return {string}
+ */
+ title: function() {},
+
+ /**
+ * @param {!CanvasRenderingContext2D} context
+ * @param {number} x
+ * @param {number} height
+ * @param {number} pixelsPerMillisecond
+ */
+ draw: function(context, x, height, pixelsPerMillisecond) {},
};
/** @enum {symbol} */
WebInspector.FlameChart.Events = {
- EntrySelected: Symbol("EntrySelected")
+ EntrySelected: Symbol('EntrySelected')
};
-
/**
- * @constructor
- * @param {!{min: number, max: number}|number=} hueSpace
- * @param {!{min: number, max: number, count: (number|undefined)}|number=} satSpace
- * @param {!{min: number, max: number, count: (number|undefined)}|number=} lightnessSpace
- * @param {!{min: number, max: number, count: (number|undefined)}|number=} alphaSpace
+ * @unrestricted
*/
-WebInspector.FlameChart.ColorGenerator = function(hueSpace, satSpace, lightnessSpace, alphaSpace)
-{
- this._hueSpace = hueSpace || { min: 0, max: 360 };
+WebInspector.FlameChart.ColorGenerator = class {
+ /**
+ * @param {!{min: number, max: number}|number=} hueSpace
+ * @param {!{min: number, max: number, count: (number|undefined)}|number=} satSpace
+ * @param {!{min: number, max: number, count: (number|undefined)}|number=} lightnessSpace
+ * @param {!{min: number, max: number, count: (number|undefined)}|number=} alphaSpace
+ */
+ constructor(hueSpace, satSpace, lightnessSpace, alphaSpace) {
+ this._hueSpace = hueSpace || {min: 0, max: 360};
this._satSpace = satSpace || 67;
this._lightnessSpace = lightnessSpace || 80;
this._alphaSpace = alphaSpace || 1;
/** @type {!Map<string, string>} */
this._colors = new Map();
-};
-
-WebInspector.FlameChart.ColorGenerator.prototype = {
- /**
- * @param {string} id
- * @param {string} color
- */
- setColorForID: function(id, color)
- {
- this._colors.set(id, color);
- },
-
- /**
- * @param {string} id
- * @return {string}
- */
- colorForID: function(id)
- {
- var color = this._colors.get(id);
- if (!color) {
- color = this._generateColorForID(id);
- this._colors.set(id, color);
- }
- return color;
- },
-
- /**
- * @param {string} id
- * @return {string}
- */
- _generateColorForID: function(id)
- {
- var hash = String.hashCode(id);
- var h = this._indexToValueInSpace(hash, this._hueSpace);
- var s = this._indexToValueInSpace(hash >> 8, this._satSpace);
- var l = this._indexToValueInSpace(hash >> 16, this._lightnessSpace);
- var a = this._indexToValueInSpace(hash >> 24, this._alphaSpace);
- return "hsla(" + h + ", " + s + "%, " + l + "%, " + a + ")";
- },
-
- /**
- * @param {number} index
- * @param {!{min: number, max: number, count: (number|undefined)}|number} space
- * @return {number}
- */
- _indexToValueInSpace: function(index, space)
- {
- if (typeof space === "number")
- return space;
- var count = space.count || space.max - space.min;
- index %= count;
- return space.min + Math.floor(index / (count - 1) * (space.max - space.min));
+ }
+
+ /**
+ * @param {string} id
+ * @param {string} color
+ */
+ setColorForID(id, color) {
+ this._colors.set(id, color);
+ }
+
+ /**
+ * @param {string} id
+ * @return {string}
+ */
+ colorForID(id) {
+ var color = this._colors.get(id);
+ if (!color) {
+ color = this._generateColorForID(id);
+ this._colors.set(id, color);
}
+ return color;
+ }
+
+ /**
+ * @param {string} id
+ * @return {string}
+ */
+ _generateColorForID(id) {
+ var hash = String.hashCode(id);
+ var h = this._indexToValueInSpace(hash, this._hueSpace);
+ var s = this._indexToValueInSpace(hash >> 8, this._satSpace);
+ var l = this._indexToValueInSpace(hash >> 16, this._lightnessSpace);
+ var a = this._indexToValueInSpace(hash >> 24, this._alphaSpace);
+ return 'hsla(' + h + ', ' + s + '%, ' + l + '%, ' + a + ')';
+ }
+
+ /**
+ * @param {number} index
+ * @param {!{min: number, max: number, count: (number|undefined)}|number} space
+ * @return {number}
+ */
+ _indexToValueInSpace(index, space) {
+ if (typeof space === 'number')
+ return space;
+ var count = space.count || space.max - space.min;
+ index %= count;
+ return space.min + Math.floor(index / (count - 1) * (space.max - space.min));
+ }
};
-
/**
- * @constructor
* @implements {WebInspector.TimelineGrid.Calculator}
- * @param {!WebInspector.FlameChartDataProvider} dataProvider
+ * @unrestricted
*/
-WebInspector.FlameChart.Calculator = function(dataProvider)
-{
+WebInspector.FlameChart.Calculator = class {
+ /**
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider
+ */
+ constructor(dataProvider) {
this._dataProvider = dataProvider;
this._paddingLeft = 0;
-};
-
-WebInspector.FlameChart.Calculator.prototype = {
- /**
- * @override
- * @return {number}
- */
- paddingLeft: function()
- {
- return this._paddingLeft;
- },
-
- /**
- * @param {!WebInspector.FlameChart} mainPane
- */
- _updateBoundaries: function(mainPane)
- {
- this._totalTime = mainPane._dataProvider.totalTime();
- this._zeroTime = mainPane._dataProvider.minimumBoundary();
- this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._totalTime;
- this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this._totalTime;
- this._paddingLeft = mainPane._paddingLeft;
- this._width = mainPane._offsetWidth - this._paddingLeft;
- this._timeToPixel = this._width / this.boundarySpan();
- },
-
- /**
- * @override
- * @param {number} time
- * @return {number}
- */
- computePosition: function(time)
- {
- return Math.round((time - this._minimumBoundaries) * this._timeToPixel + this._paddingLeft);
- },
-
- /**
- * @override
- * @param {number} value
- * @param {number=} precision
- * @return {string}
- */
- formatValue: function(value, precision)
- {
- return this._dataProvider.formatValue(value - this._zeroTime, precision);
- },
-
- /**
- * @override
- * @return {number}
- */
- maximumBoundary: function()
- {
- return this._maximumBoundaries;
- },
-
- /**
- * @override
- * @return {number}
- */
- minimumBoundary: function()
- {
- return this._minimumBoundaries;
- },
-
- /**
- * @override
- * @return {number}
- */
- zeroTime: function()
- {
- return this._zeroTime;
- },
-
- /**
- * @override
- * @return {number}
- */
- boundarySpan: function()
- {
- return this._maximumBoundaries - this._minimumBoundaries;
- }
-};
-
-WebInspector.FlameChart.prototype = {
- /**
- * @override
- */
- willHide: function()
- {
- this.hideHighlight();
- },
-
- /**
- * @param {number} entryIndex
- */
- highlightEntry: function(entryIndex)
- {
- if (this._highlightedEntryIndex === entryIndex)
- return;
- this._highlightedEntryIndex = entryIndex;
- this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
- },
-
- hideHighlight: function()
- {
- this._entryInfo.removeChildren();
- this._highlightedEntryIndex = -1;
- this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
- },
-
- _resetCanvas: function()
- {
- var ratio = window.devicePixelRatio;
- this._canvas.width = this._offsetWidth * ratio;
- this._canvas.height = this._offsetHeight * ratio;
- this._canvas.style.width = this._offsetWidth + "px";
- this._canvas.style.height = this._offsetHeight + "px";
- },
-
- /**
- * @return {?WebInspector.FlameChart.TimelineData}
- */
- _timelineData: function()
- {
- if (!this._dataProvider)
- return null;
- var timelineData = this._dataProvider.timelineData();
- if (timelineData !== this._rawTimelineData || timelineData.entryStartTimes.length !== this._rawTimelineDataLength)
- this._processTimelineData(timelineData);
- return this._rawTimelineData;
- },
-
- /**
- * @param {number} entryIndex
- */
- _revealEntry: function(entryIndex)
- {
- var timelineData = this._timelineData();
- if (!timelineData)
- return;
- // Think in terms of not where we are, but where we'll be after animation (if present)
- var timeLeft = this._cancelWindowTimesAnimation ? this._pendingAnimationTimeLeft : this._timeWindowLeft;
- var timeRight = this._cancelWindowTimesAnimation ? this._pendingAnimationTimeRight : this._timeWindowRight;
- var entryStartTime = timelineData.entryStartTimes[entryIndex];
- var entryTotalTime = timelineData.entryTotalTimes[entryIndex];
- var entryEndTime = entryStartTime + entryTotalTime;
- var minEntryTimeWindow = Math.min(entryTotalTime, timeRight - timeLeft);
-
- var y = this._levelToHeight(timelineData.entryLevels[entryIndex]);
- this.setScrollOffset(y, this._barHeight);
-
- if (timeLeft > entryEndTime) {
- var delta = timeLeft - entryEndTime + minEntryTimeWindow;
- this._flameChartDelegate.requestWindowTimes(timeLeft - delta, timeRight - delta);
- } else if (timeRight < entryStartTime) {
- var delta = entryStartTime - timeRight + minEntryTimeWindow;
- this._flameChartDelegate.requestWindowTimes(timeLeft + delta, timeRight + delta);
- }
- },
-
- /**
- * @override
- * @param {number} startTime
- * @param {number} endTime
- */
- setWindowTimes: function(startTime, endTime)
- {
- WebInspector.FlameChart.prototype.__proto__.setWindowTimes.call(this, startTime, endTime);
- this._updateHighlight();
- },
-
- /**
- * @param {!Event} event
- */
- _onMouseMove: function(event)
- {
- this._lastMouseOffsetX = event.offsetX;
- this._lastMouseOffsetY = event.offsetY;
- if (!this._enabled())
- return;
- if (this.isDragging())
- return;
- if (this._coordinatesToGroupIndex(event.offsetX, event.offsetY) >= 0) {
- this.hideHighlight();
- this.viewportElement.style.cursor = "pointer";
- return;
- }
- this._updateHighlight();
- },
-
- _updateHighlight: function()
- {
- var inDividersBar = this._lastMouseOffsetY < WebInspector.FlameChart.DividersBarHeight;
- this._highlightedMarkerIndex = inDividersBar ? this._markerIndexAtPosition(this._lastMouseOffsetX) : -1;
- this._updateMarkerHighlight();
-
- var entryIndex = this._coordinatesToEntryIndex(this._lastMouseOffsetX, this._lastMouseOffsetY);
- if (entryIndex === -1) {
- this.hideHighlight();
- return;
- }
- if (this.isDragging())
- return;
- this._updatePopover(entryIndex);
- this.viewportElement.style.cursor = this._dataProvider.canJumpToEntry(entryIndex) ? "pointer" : "default";
- this.highlightEntry(entryIndex);
- },
-
- _onMouseOut: function()
- {
- this._lastMouseOffsetX = -1;
- this._lastMouseOffsetY = -1;
- this.hideHighlight();
- },
-
- /**
- * @param {number} entryIndex
- */
- _updatePopover: function(entryIndex)
- {
- if (entryIndex === this._highlightedEntryIndex) {
- this._updatePopoverOffset();
- return;
- }
- this._entryInfo.removeChildren();
- var popoverElement = this._dataProvider.prepareHighlightedEntryInfo(entryIndex);
- if (popoverElement) {
- this._entryInfo.appendChild(popoverElement);
- this._updatePopoverOffset();
- }
- },
-
- _updatePopoverOffset: function()
- {
- var mouseX = this._lastMouseOffsetX;
- var mouseY = this._lastMouseOffsetY;
- var parentWidth = this._entryInfo.parentElement.clientWidth;
- var parentHeight = this._entryInfo.parentElement.clientHeight;
- var infoWidth = this._entryInfo.clientWidth;
- var infoHeight = this._entryInfo.clientHeight;
- var /** @const */ offsetX = 10;
- var /** @const */ offsetY = 6;
- var x;
- var y;
- for (var quadrant = 0; quadrant < 4; ++quadrant) {
- var dx = quadrant & 2 ? -offsetX - infoWidth : offsetX;
- var dy = quadrant & 1 ? -offsetY - infoHeight : offsetY;
- x = Number.constrain(mouseX + dx, 0, parentWidth - infoWidth);
- y = Number.constrain(mouseY + dy, 0, parentHeight - infoHeight);
- if (x >= mouseX || mouseX >= x + infoWidth || y >= mouseY || mouseY >= y + infoHeight)
- break;
- }
- this._entryInfo.style.left = x + "px";
- this._entryInfo.style.top = y + "px";
- },
-
- /**
- * @param {!Event} event
- */
- _onClick: function(event)
- {
- this.focus();
- // onClick comes after dragStart and dragEnd events.
- // So if there was drag (mouse move) in the middle of that events
- // we skip the click. Otherwise we jump to the sources.
- const clickThreshold = 5;
- if (this.maxDragOffset() > clickThreshold)
- return;
- var groupIndex = this._coordinatesToGroupIndex(event.offsetX, event.offsetY);
- if (groupIndex >= 0) {
- this._toggleGroupVisibility(groupIndex);
- return;
- }
- this.hideRangeSelection();
- this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, this._highlightedEntryIndex);
- },
-
- /**
- * @param {number} groupIndex
- */
- _toggleGroupVisibility: function(groupIndex)
- {
- if (!this._isGroupCollapsible(groupIndex))
- return;
- var groups = this._rawTimelineData.groups;
- var group = groups[groupIndex];
- group.expanded = !group.expanded;
- this._groupExpansionState[group.name] = group.expanded;
- if (this._groupExpansionSetting)
- this._groupExpansionSetting.set(this._groupExpansionState);
- this._updateLevelPositions();
-
- this._updateHighlight();
- if (!group.expanded) {
- var timelineData = this._timelineData();
- var level = timelineData.entryLevels[this._selectedEntryIndex];
- if (this._selectedEntryIndex >= 0 && level >= group.startLevel && (groupIndex === groups.length || groups[groupIndex + 1].startLevel > level))
- this._selectedEntryIndex = -1;
- }
-
- this._updateHeight();
- this._resetCanvas();
- this._draw(this._offsetWidth, this._offsetHeight);
- },
-
- /**
- * @param {!Event} e
- */
- _onKeyDown: function(e)
- {
- this._handleSelectionNavigation(e);
- },
-
- /**
- * @param {!Event} e
- */
- _handleSelectionNavigation: function(e)
- {
- if (!WebInspector.KeyboardShortcut.hasNoModifiers(e))
- return;
- if (this._selectedEntryIndex === -1)
- return;
- var timelineData = this._timelineData();
- if (!timelineData)
- return;
-
- /**
- * @param {number} time
- * @param {number} entryIndex
- * @return {number}
- */
- function timeComparator(time, entryIndex)
- {
- return time - timelineData.entryStartTimes[entryIndex];
- }
-
- /**
- * @param {number} entry1
- * @param {number} entry2
- * @return {boolean}
- */
- function entriesIntersect(entry1, entry2)
- {
- var start1 = timelineData.entryStartTimes[entry1];
- var start2 = timelineData.entryStartTimes[entry2];
- var end1 = start1 + timelineData.entryTotalTimes[entry1];
- var end2 = start2 + timelineData.entryTotalTimes[entry2];
- return start1 < end2 && start2 < end1;
- }
-
- var keys = WebInspector.KeyboardShortcut.Keys;
- if (e.keyCode === keys.Left.code || e.keyCode === keys.Right.code) {
- var level = timelineData.entryLevels[this._selectedEntryIndex];
- var levelIndexes = this._timelineLevels[level];
- var indexOnLevel = levelIndexes.lowerBound(this._selectedEntryIndex);
- indexOnLevel += e.keyCode === keys.Left.code ? -1 : 1;
- e.consume(true);
- if (indexOnLevel >= 0 && indexOnLevel < levelIndexes.length)
- this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, levelIndexes[indexOnLevel]);
- return;
- }
- if (e.keyCode === keys.Up.code || e.keyCode === keys.Down.code) {
- e.consume(true);
- var level = timelineData.entryLevels[this._selectedEntryIndex];
- level += e.keyCode === keys.Up.code ? -1 : 1;
- if (level < 0 || level >= this._timelineLevels.length)
- return;
- var entryTime = timelineData.entryStartTimes[this._selectedEntryIndex] + timelineData.entryTotalTimes[this._selectedEntryIndex] / 2;
- var levelIndexes = this._timelineLevels[level];
- var indexOnLevel = levelIndexes.upperBound(entryTime, timeComparator) - 1;
- if (!entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel])) {
- ++indexOnLevel;
- if (indexOnLevel >= levelIndexes.length || !entriesIntersect(this._selectedEntryIndex, levelIndexes[indexOnLevel]))
- return;
- }
- this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, levelIndexes[indexOnLevel]);
- }
- },
-
- /**
- * @param {number} x
- * @return {number}
- */
- _cursorTime: function(x)
- {
- return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime + this._minimumBoundary;
- },
-
- /**
- * @param {number} x
- * @param {number} y
- * @return {number}
- */
- _coordinatesToEntryIndex: function(x, y)
- {
- if (x < 0 || y < 0)
- return -1;
- y += this.scrollOffset();
- var timelineData = this._timelineData();
- if (!timelineData)
- return -1;
- var cursorTime = this._cursorTime(x);
- var cursorLevel = this._visibleLevelOffsets.upperBound(y) - 1;
- if (cursorLevel < 0 || !this._visibleLevels[cursorLevel])
- return -1;
- var offsetFromLevel = y - this._visibleLevelOffsets[cursorLevel];
- if (offsetFromLevel > this._barHeight)
- return -1;
- var entryStartTimes = timelineData.entryStartTimes;
- var entryTotalTimes = timelineData.entryTotalTimes;
- var entryIndexes = this._timelineLevels[cursorLevel];
- if (!entryIndexes || !entryIndexes.length)
- return -1;
-
- /**
- * @param {number} time
- * @param {number} entryIndex
- * @return {number}
- */
- function comparator(time, entryIndex)
- {
- return time - entryStartTimes[entryIndex];
- }
- var indexOnLevel = Math.max(entryIndexes.upperBound(cursorTime, comparator) - 1, 0);
-
- /**
- * @this {WebInspector.FlameChart}
- * @param {number} entryIndex
- * @return {boolean}
- */
- function checkEntryHit(entryIndex)
- {
- if (entryIndex === undefined)
- return false;
- var startTime = entryStartTimes[entryIndex];
- var duration = entryTotalTimes[entryIndex];
- if (isNaN(duration)) {
- var dx = (startTime - cursorTime) / this._pixelToTime;
- var dy = this._barHeight / 2 - offsetFromLevel;
- return dx * dx + dy * dy < this._markerRadius * this._markerRadius;
- }
- var endTime = startTime + duration;
- var barThreshold = 3 * this._pixelToTime;
- return startTime - barThreshold < cursorTime && cursorTime < endTime + barThreshold;
- }
-
- var entryIndex = entryIndexes[indexOnLevel];
- if (checkEntryHit.call(this, entryIndex))
- return entryIndex;
- entryIndex = entryIndexes[indexOnLevel + 1];
- if (checkEntryHit.call(this, entryIndex))
- return entryIndex;
- return -1;
- },
-
- /**
- * @param {number} x
- * @param {number} y
- * @return {number}
- */
- _coordinatesToGroupIndex: function(x, y)
- {
- if (x < 0 || y < 0)
- return -1;
- y += this.scrollOffset();
- var groups = this._rawTimelineData.groups || [];
- var group = this._groupOffsets.upperBound(y) - 1;
-
- if (group < 0 || group >= groups.length || y - this._groupOffsets[group] >= groups[group].style.height)
- return -1;
- var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
- context.save();
- context.font = groups[group].style.font;
- var right = this._headerLeftPadding + this._labelWidthForGroup(context, groups[group]);
- context.restore();
- if (x > right)
- return -1;
-
- return group;
- },
-
- /**
- * @param {number} x
- * @return {number}
- */
- _markerIndexAtPosition: function(x)
- {
- var markers = this._timelineData().markers;
- if (!markers)
- return -1;
- var accurracyOffsetPx = 1;
- var time = this._cursorTime(x);
- var leftTime = this._cursorTime(x - accurracyOffsetPx);
- var rightTime = this._cursorTime(x + accurracyOffsetPx);
-
- var left = this._markerIndexBeforeTime(leftTime);
- var markerIndex = -1;
- var distance = Infinity;
- for (var i = left; i < markers.length && markers[i].startTime() < rightTime; i++) {
- var nextDistance = Math.abs(markers[i].startTime() - time);
- if (nextDistance < distance) {
- markerIndex = i;
- distance = nextDistance;
- }
- }
- return markerIndex;
- },
-
- /**
- * @param {number} time
- * @return {number}
- */
- _markerIndexBeforeTime: function(time)
- {
- return this._timelineData().markers.lowerBound(time, (markerTimestamp, marker) => markerTimestamp - marker.startTime());
- },
-
- /**
- * @param {number} height
- * @param {number} width
- */
- _draw: function(width, height)
- {
- var timelineData = this._timelineData();
- if (!timelineData)
- return;
-
- var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
- context.save();
- var ratio = window.devicePixelRatio;
- var top = this.scrollOffset();
- context.scale(ratio, ratio);
- context.translate(0, -top);
- var defaultFont = "11px " + WebInspector.fontFamily();
- context.font = defaultFont;
-
- var timeWindowRight = this._timeWindowRight;
- var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeToPixel;
- var entryTotalTimes = timelineData.entryTotalTimes;
- var entryStartTimes = timelineData.entryStartTimes;
- var entryLevels = timelineData.entryLevels;
-
- var titleIndices = [];
- var markerIndices = [];
- var textPadding = this._dataProvider.textPadding();
- var minTextWidth = 2 * textPadding + this._measureWidth(context, "\u2026");
- var barHeight = this._barHeight;
- var minVisibleBarLevel = Math.max(this._visibleLevelOffsets.upperBound(top) - 1, 0);
-
- /** @type {!Map<string, !Array<number>>} */
- var colorBuckets = new Map();
- for (var level = minVisibleBarLevel; level < this._dataProvider.maxStackDepth(); ++level) {
- if (this._levelToHeight(level) > top + height)
- break;
- if (!this._visibleLevels[level])
- continue;
-
- // Entries are ordered by start time within a level, so find the last visible entry.
- var levelIndexes = this._timelineLevels[level];
- var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
- var lastDrawOffset = Infinity;
- for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
- var entryIndex = levelIndexes[entryIndexOnLevel];
- var entryStartTime = entryStartTimes[entryIndex];
- var entryOffsetRight = entryStartTime + (entryTotalTimes[entryIndex] || 0);
- if (entryOffsetRight <= timeWindowLeft)
- break;
-
- var barX = this._timeToPositionClipped(entryStartTime);
- // Check if the entry entirely fits into an already drawn pixel, we can just skip drawing it.
- if (barX >= lastDrawOffset)
- continue;
- lastDrawOffset = barX;
-
- var color = this._dataProvider.entryColor(entryIndex);
- var bucket = colorBuckets.get(color);
- if (!bucket) {
- bucket = [];
- colorBuckets.set(color, bucket);
- }
- bucket.push(entryIndex);
- }
- }
-
- var colors = colorBuckets.keysArray();
- // We don't use for-of here because it's slow.
- for (var c = 0; c < colors.length; ++c) {
- var color = colors[c];
- var indexes = colorBuckets.get(color);
- context.beginPath();
- context.fillStyle = color;
- for (var i = 0; i < indexes.length; ++i) {
- var entryIndex = indexes[i];
- var entryStartTime = entryStartTimes[entryIndex];
- var barX = this._timeToPositionClipped(entryStartTime);
- var duration = entryTotalTimes[entryIndex];
- var barLevel = entryLevels[entryIndex];
- var barY = this._levelToHeight(barLevel);
- if (isNaN(duration)) {
- context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
- context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
- markerIndices.push(entryIndex);
- continue;
- }
- var barRight = this._timeToPositionClipped(entryStartTime + duration);
- var barWidth = Math.max(barRight - barX, 1);
- context.rect(barX, barY, barWidth - 0.4, barHeight - 1);
- if (barWidth > minTextWidth || this._dataProvider.forceDecoration(entryIndex))
- titleIndices.push(entryIndex);
- }
- context.fill();
- }
-
- context.strokeStyle = "rgba(0, 0, 0, 0.2)";
- context.beginPath();
- for (var m = 0; m < markerIndices.length; ++m) {
- var entryIndex = markerIndices[m];
- var entryStartTime = entryStartTimes[entryIndex];
- var barX = this._timeToPositionClipped(entryStartTime);
- var barLevel = entryLevels[entryIndex];
- var barY = this._levelToHeight(barLevel);
- context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
- context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
- }
- context.stroke();
-
- context.textBaseline = "alphabetic";
- var textBaseHeight = this._barHeight - this._dataProvider.textBaseline();
-
- for (var i = 0; i < titleIndices.length; ++i) {
- var entryIndex = titleIndices[i];
- var entryStartTime = entryStartTimes[entryIndex];
- var barX = this._timeToPositionClipped(entryStartTime);
- var barRight = Math.min(this._timeToPositionClipped(entryStartTime + entryTotalTimes[entryIndex]), width) + 1;
- var barWidth = barRight - barX;
- var barLevel = entryLevels[entryIndex];
- var barY = this._levelToHeight(barLevel);
- var text = this._dataProvider.entryTitle(entryIndex);
- if (text && text.length) {
- context.font = this._dataProvider.entryFont(entryIndex) || defaultFont;
- text = this._prepareText(context, text, barWidth - 2 * textPadding);
- }
- var unclippedBarX = this._timeToPosition(entryStartTime);
- if (this._dataProvider.decorateEntry(entryIndex, context, text, barX, barY, barWidth, barHeight, unclippedBarX, this._timeToPixel))
- continue;
- if (!text || !text.length)
- continue;
- context.fillStyle = this._dataProvider.textColor(entryIndex);
- context.fillText(text, barX + textPadding, barY + textBaseHeight);
- }
-
- this._drawFlowEvents(context, width, height);
-
- context.restore();
-
- WebInspector.TimelineGrid.drawCanvasGrid(context, this._calculator, 3);
- this._drawMarkers();
- this._drawGroupHeaders(width, height);
-
- this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
- this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
- this._updateMarkerHighlight();
- },
-
- /**
- * @param {number} width
- * @param {number} height
- */
- _drawGroupHeaders: function(width, height)
- {
- var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
- var top = this.scrollOffset();
- var ratio = window.devicePixelRatio;
- var barHeight = this._barHeight;
- var textBaseHeight = barHeight - this._dataProvider.textBaseline();
- var groups = this._rawTimelineData.groups || [];
- if (!groups.length)
- return;
-
- var groupOffsets = this._groupOffsets;
- var lastGroupOffset = Array.prototype.peekLast.call(groupOffsets);
- var colorUsage = WebInspector.ThemeSupport.ColorUsage;
-
- context.save();
- context.scale(ratio, ratio);
- context.translate(0, -top);
-
- context.fillStyle = WebInspector.themeSupport.patchColor("#eee", colorUsage.Background);
- forEachGroup.call(this, (offset, index, group) => {
- var paddingHeight = group.style.padding;
- if (paddingHeight < 5)
- return;
- context.fillRect(0, offset - paddingHeight + 2, width, paddingHeight - 4);
- });
- if (groups.length && lastGroupOffset < top + height)
- context.fillRect(0, lastGroupOffset + 2, width, top + height - lastGroupOffset);
-
- context.strokeStyle = WebInspector.themeSupport.patchColor("#bbb", colorUsage.Background);
- context.beginPath();
- forEachGroup.call(this, (offset, index, group, isFirst) => {
- if (isFirst || group.style.padding < 4)
- return;
- hLine(offset - 2.5);
- });
- hLine(lastGroupOffset + 0.5);
- context.stroke();
-
- forEachGroup.call(this, (offset, index, group) => {
- if (group.style.useFirstLineForOverview)
- return;
- if (!this._isGroupCollapsible(index) || group.expanded) {
- if (!group.style.shareHeaderLine) {
- context.fillStyle = group.style.backgroundColor;
- context.fillRect(0, offset, width, group.style.height);
- }
- return;
- }
- var nextGroup = index + 1;
- while (nextGroup < groups.length && groups[nextGroup].style.nestingLevel > group.style.nestingLevel)
- nextGroup++;
- var endLevel = nextGroup < groups.length ? groups[nextGroup].startLevel : this._dataProvider.maxStackDepth();
- this._drawCollapsedOverviewForGroup(offset + 1, group.startLevel, endLevel);
- });
-
- context.save();
- forEachGroup.call(this, (offset, index, group) => {
- context.font = group.style.font;
- if (this._isGroupCollapsible(index) && !group.expanded || group.style.shareHeaderLine) {
- var width = this._labelWidthForGroup(context, group);
- context.fillStyle = WebInspector.Color.parse(group.style.backgroundColor).setAlpha(0.7).asString(null);
- context.fillRect(this._headerLeftPadding - this._headerLabelXPadding, offset + this._headerLabelYPadding, width, barHeight - 2 * this._headerLabelYPadding);
- }
- context.fillStyle = group.style.color;
- context.fillText(group.name, Math.floor(this._expansionArrowIndent * (group.style.nestingLevel + 1) + this._arrowSide), offset + textBaseHeight);
- });
- context.restore();
-
- context.fillStyle = WebInspector.themeSupport.patchColor("#6e6e6e", colorUsage.Foreground);
- context.beginPath();
- forEachGroup.call(this, (offset, index, group) => {
- if (this._isGroupCollapsible(index))
- drawExpansionArrow.call(this, this._expansionArrowIndent * (group.style.nestingLevel + 1), offset + textBaseHeight - this._arrowSide / 2, !!group.expanded);
- });
- context.fill();
-
- context.strokeStyle = WebInspector.themeSupport.patchColor("#ddd", colorUsage.Background);
- context.beginPath();
- context.stroke();
-
- context.restore();
-
- /**
- * @param {number} y
- */
- function hLine(y)
- {
- context.moveTo(0, y);
- context.lineTo(width, y);
- }
-
- /**
- * @param {number} x
- * @param {number} y
- * @param {boolean} expanded
- * @this {WebInspector.FlameChart}
- */
- function drawExpansionArrow(x, y, expanded)
- {
- var arrowHeight = this._arrowSide * Math.sqrt(3) / 2;
- var arrowCenterOffset = Math.round(arrowHeight / 2);
- context.save();
- context.translate(x, y);
- context.rotate(expanded ? Math.PI / 2 : 0);
- context.moveTo(-arrowCenterOffset, -this._arrowSide / 2);
- context.lineTo(-arrowCenterOffset, this._arrowSide / 2);
- context.lineTo(arrowHeight - arrowCenterOffset, 0);
- context.restore();
- }
-
- /**
- * @param {function(number, number, !WebInspector.FlameChart.Group, boolean)} callback
- * @this {WebInspector.FlameChart}
- */
- function forEachGroup(callback)
- {
- /** @type !Array<{nestingLevel: number, visible: boolean}> */
- var groupStack = [{nestingLevel: -1, visible: true}];
- for (var i = 0; i < groups.length; ++i) {
- var groupTop = groupOffsets[i];
- var group = groups[i];
- if (groupTop - group.style.padding > top + height)
- break;
- var firstGroup = true;
- while (groupStack.peekLast().nestingLevel >= group.style.nestingLevel) {
- groupStack.pop();
- firstGroup = false;
- }
- var parentGroupVisible = groupStack.peekLast().visible;
- var thisGroupVisible = parentGroupVisible && (!this._isGroupCollapsible(i) || group.expanded);
- groupStack.push({nestingLevel: group.style.nestingLevel, visible: thisGroupVisible});
- if (!parentGroupVisible || groupTop + group.style.height < top)
- continue;
- callback(groupTop, i, group, firstGroup);
- }
- }
- },
-
- /**
- * @param {!CanvasRenderingContext2D} context
- * @param {!WebInspector.FlameChart.Group} group
- * @return {number}
- */
- _labelWidthForGroup: function(context, group)
- {
- return this._measureWidth(context, group.name) + this._expansionArrowIndent * (group.style.nestingLevel + 1) + 2 * this._headerLabelXPadding;
- },
-
- /**
- * @param {number} y
- * @param {number} startLevel
- * @param {number} endLevel
- */
- _drawCollapsedOverviewForGroup: function(y, startLevel, endLevel)
- {
- var range = new WebInspector.SegmentedRange(mergeCallback);
- var timeWindowRight = this._timeWindowRight;
- var timeWindowLeft = this._timeWindowLeft - this._paddingLeft / this._timeToPixel;
- var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
- var barHeight = this._barHeight - 2;
- var entryStartTimes = this._rawTimelineData.entryStartTimes;
- var entryTotalTimes = this._rawTimelineData.entryTotalTimes;
-
- for (var level = startLevel; level < endLevel; ++level) {
- var levelIndexes = this._timelineLevels[level];
- var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, (time, entryIndex) => time - entryStartTimes[entryIndex]) - 1;
- var lastDrawOffset = Infinity;
-
- for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
- var entryIndex = levelIndexes[entryIndexOnLevel];
- var entryStartTime = entryStartTimes[entryIndex];
- var startPosition = this._timeToPositionClipped(entryStartTime);
- var entryEndTime = entryStartTime + entryTotalTimes[entryIndex];
- if (isNaN(entryEndTime) || startPosition >= lastDrawOffset)
- continue;
- if (entryEndTime <= timeWindowLeft)
- break;
- lastDrawOffset = startPosition;
- var color = this._dataProvider.entryColor(entryIndex);
- range.append(new WebInspector.Segment(startPosition, this._timeToPositionClipped(entryEndTime), color));
- }
- }
-
- var segments = range.segments().slice().sort((a, b) => a.data.localeCompare(b.data));
- var lastColor;
- context.beginPath();
- for (var i = 0; i < segments.length; ++i) {
- var segment = segments[i];
- if (lastColor !== segments[i].data) {
- context.fill();
- context.beginPath();
- lastColor = segments[i].data;
- context.fillStyle = lastColor;
- }
- context.rect(segment.begin, y, segment.end - segment.begin, barHeight);
- }
- context.fill();
-
- /**
- * @param {!WebInspector.Segment} a
- * @param {!WebInspector.Segment} b
- * @return {?WebInspector.Segment}
- */
- function mergeCallback(a, b)
- {
- return a.data === b.data && a.end + 0.4 > b.end ? a : null;
- }
- },
-
- /**
- * @param {!CanvasRenderingContext2D} context
- * @param {number} height
- * @param {number} width
- */
- _drawFlowEvents: function(context, width, height)
- {
- var timelineData = this._timelineData();
- var timeWindowRight = this._timeWindowRight;
- var timeWindowLeft = this._timeWindowLeft;
- var flowStartTimes = timelineData.flowStartTimes;
- var flowEndTimes = timelineData.flowEndTimes;
- var flowStartLevels = timelineData.flowStartLevels;
- var flowEndLevels = timelineData.flowEndLevels;
- var flowCount = flowStartTimes.length;
- var endIndex = flowStartTimes.lowerBound(timeWindowRight);
-
- var color = [];
- var fadeColorsCount = 8;
- for (var i = 0; i <= fadeColorsCount; ++i)
- color[i] = "rgba(128, 0, 0, " + i / fadeColorsCount + ")";
- var fadeColorsRange = color.length;
- var minimumFlowDistancePx = 15;
- var flowArcHeight = 4 * this._barHeight;
- var colorIndex = 0;
- context.lineWidth = 0.5;
- for (var i = 0; i < endIndex; ++i) {
- if (flowEndTimes[i] < timeWindowLeft)
- continue;
- var startX = this._timeToPosition(flowStartTimes[i]);
- var endX = this._timeToPosition(flowEndTimes[i]);
- if (endX - startX < minimumFlowDistancePx)
- continue;
- if (startX < -minimumFlowDistancePx && endX > width + minimumFlowDistancePx)
- continue;
- // Assign a trasparent color if the flow is small enough or if the previous color was a transparent color.
- if (endX - startX < minimumFlowDistancePx + fadeColorsRange || colorIndex !== color.length - 1) {
- colorIndex = Math.min(fadeColorsRange - 1, Math.floor(endX - startX - minimumFlowDistancePx));
- context.strokeStyle = color[colorIndex];
- }
- var startY = this._levelToHeight(flowStartLevels[i]) + this._barHeight;
- var endY = this._levelToHeight(flowEndLevels[i]);
- context.beginPath();
- context.moveTo(startX, startY);
- var arcHeight = Math.max(Math.sqrt(Math.abs(startY - endY)), flowArcHeight) + 5;
- context.bezierCurveTo(startX, startY + arcHeight,
- endX, endY + arcHeight,
- endX, endY + this._barHeight);
- context.stroke();
- }
- },
-
- _drawMarkers: function()
- {
- var markers = this._timelineData().markers;
- var left = this._markerIndexBeforeTime(this._calculator.minimumBoundary());
- var rightBoundary = this._calculator.maximumBoundary();
-
- var context = /** @type {!CanvasRenderingContext2D} */ (this._canvas.getContext("2d"));
- context.save();
- var ratio = window.devicePixelRatio;
- context.scale(ratio, ratio);
- var height = WebInspector.FlameChart.DividersBarHeight - 1;
- for (var i = left; i < markers.length; i++) {
- var timestamp = markers[i].startTime();
- if (timestamp > rightBoundary)
- break;
- markers[i].draw(context, this._calculator.computePosition(timestamp), height, this._timeToPixel);
- }
- context.restore();
- },
-
- _updateMarkerHighlight: function()
- {
- var element = this._markerHighlighElement;
- if (element.parentElement)
- element.remove();
- var markerIndex = this._highlightedMarkerIndex;
- if (markerIndex === -1)
- return;
- var marker = this._timelineData().markers[markerIndex];
- var barX = this._timeToPositionClipped(marker.startTime());
- element.title = marker.title();
- var style = element.style;
- style.left = barX + "px";
- style.backgroundColor = marker.color();
- this.viewportElement.appendChild(element);
- },
-
- /**
- * @param {?WebInspector.FlameChart.TimelineData} timelineData
- */
- _processTimelineData: function(timelineData)
- {
- if (!timelineData) {
- this._timelineLevels = null;
- this._visibleLevelOffsets = null;
- this._visibleLevels = null;
- this._groupOffsets = null;
- this._rawTimelineData = null;
- this._rawTimelineDataLength = 0;
- return;
- }
-
- this._rawTimelineData = timelineData;
- this._rawTimelineDataLength = timelineData.entryStartTimes.length;
-
- var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
- for (var i = 0; i < timelineData.entryLevels.length; ++i)
- ++entryCounters[timelineData.entryLevels[i]];
- var levelIndexes = new Array(entryCounters.length);
- for (var i = 0; i < levelIndexes.length; ++i) {
- levelIndexes[i] = new Uint32Array(entryCounters[i]);
- entryCounters[i] = 0;
- }
- for (var i = 0; i < timelineData.entryLevels.length; ++i) {
- var level = timelineData.entryLevels[i];
- levelIndexes[level][entryCounters[level]++] = i;
- }
- this._timelineLevels = levelIndexes;
- var groups = this._rawTimelineData.groups || [];
- for (var i = 0; i < groups.length; ++i) {
- var expanded = this._groupExpansionState[groups[i].name];
- if (expanded !== undefined)
- groups[i].expanded = expanded;
- }
- this._updateLevelPositions();
- this._updateHeight();
- },
-
- _updateLevelPositions: function()
- {
- var levelCount = this._dataProvider.maxStackDepth();
- var groups = this._rawTimelineData.groups || [];
- this._visibleLevelOffsets = new Uint32Array(levelCount + 1);
- this._visibleLevels = new Uint16Array(levelCount);
- this._groupOffsets = new Uint32Array(groups.length + 1);
-
- var groupIndex = -1;
- var currentOffset = WebInspector.FlameChart.DividersBarHeight;
- var visible = true;
- /** @type !Array<{nestingLevel: number, visible: boolean}> */
- var groupStack = [{nestingLevel: -1, visible: true}];
- for (var level = 0; level < levelCount; ++level) {
- while (groupIndex < groups.length - 1 && level === groups[groupIndex + 1].startLevel) {
- ++groupIndex;
- var style = groups[groupIndex].style;
- var nextLevel = true;
- while (groupStack.peekLast().nestingLevel >= style.nestingLevel) {
- groupStack.pop();
- nextLevel = false;
- }
- var thisGroupIsVisible = groupIndex >= 0 && this._isGroupCollapsible(groupIndex) ? groups[groupIndex].expanded : true;
- var parentGroupIsVisible = groupStack.peekLast().visible;
- visible = thisGroupIsVisible && parentGroupIsVisible;
- groupStack.push({nestingLevel: style.nestingLevel, visible: visible});
- if (parentGroupIsVisible)
- currentOffset += nextLevel ? 0 : style.padding;
- this._groupOffsets[groupIndex] = currentOffset;
- if (parentGroupIsVisible && !style.shareHeaderLine)
- currentOffset += style.height;
- }
- var isFirstOnLevel = groupIndex >= 0 && level === groups[groupIndex].startLevel;
- var thisLevelIsVisible = visible || isFirstOnLevel && groups[groupIndex].style.useFirstLineForOverview;
- this._visibleLevels[level] = thisLevelIsVisible;
- this._visibleLevelOffsets[level] = currentOffset;
- if (thisLevelIsVisible || (parentGroupIsVisible && style.shareHeaderLine && isFirstOnLevel))
- currentOffset += this._barHeight;
- }
- if (groupIndex >= 0)
- this._groupOffsets[groupIndex + 1] = currentOffset;
- this._visibleLevelOffsets[level] = currentOffset;
- },
-
- /**
- * @param {number} index
- */
- _isGroupCollapsible: function(index)
- {
- var groups = this._rawTimelineData.groups || [];
- var style = groups[index].style;
- if (!style.shareHeaderLine || !style.collapsible)
- return !!style.collapsible;
- var isLastGroup = index + 1 >= groups.length;
- if (!isLastGroup && groups[index + 1].style.nestingLevel > style.nestingLevel)
- return true;
- var nextGroupLevel = isLastGroup ? this._dataProvider.maxStackDepth() : groups[index + 1].startLevel;
- // For groups that only have one line and share header line, pretend these are not collapsible.
- return nextGroupLevel !== groups[index].startLevel + 1;
- },
-
- /**
- * @param {number} entryIndex
- */
- setSelectedEntry: function(entryIndex)
- {
- if (entryIndex === -1 && !this.isDragging())
- this.hideRangeSelection();
- if (this._selectedEntryIndex === entryIndex)
- return;
- this._selectedEntryIndex = entryIndex;
- this._revealEntry(entryIndex);
- this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
- },
-
- /**
- * @param {!Element} element
- * @param {number} entryIndex
- */
- _updateElementPosition: function(element, entryIndex)
- {
- const elementMinWidthPx = 2;
- if (element.parentElement)
- element.remove();
- if (entryIndex === -1)
- return;
- var timelineData = this._timelineData();
- var startTime = timelineData.entryStartTimes[entryIndex];
- var endTime = startTime + (timelineData.entryTotalTimes[entryIndex] || 0);
- var barX = this._timeToPositionClipped(startTime);
- var barRight = this._timeToPositionClipped(endTime);
- if (barRight === 0 || barX === this._offsetWidth)
- return;
- var barWidth = barRight - barX;
- var barCenter = barX + barWidth / 2;
- barWidth = Math.max(barWidth, elementMinWidthPx);
- barX = barCenter - barWidth / 2;
- var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - this.scrollOffset();
- var style = element.style;
- style.left = barX + "px";
- style.top = barY + "px";
- style.width = barWidth + "px";
- style.height = this._barHeight - 1 + "px";
- this.viewportElement.appendChild(element);
- },
-
- /**
- * @param {number} time
- * @return {number}
- */
- _timeToPositionClipped: function(time)
- {
- return Number.constrain(this._timeToPosition(time), 0, this._offsetWidth);
- },
-
- /**
- * @param {number} time
- * @return {number}
- */
- _timeToPosition: function(time)
- {
- return Math.floor((time - this._minimumBoundary) * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
- },
-
- /**
- * @param {number} level
- * @return {number}
- */
- _levelToHeight: function(level)
- {
- return this._visibleLevelOffsets[level];
- },
-
- /**
- * @param {!CanvasRenderingContext2D} context
- * @param {string} text
- * @param {number} maxWidth
- * @return {string}
- */
- _prepareText: function(context, text, maxWidth)
- {
- var /** @const */ maxLength = 200;
- if (maxWidth <= 10)
- return "";
- if (text.length > maxLength)
- text = text.trimMiddle(maxLength);
- var textWidth = this._measureWidth(context, text);
- if (textWidth <= maxWidth)
- return text;
-
- var l = 0;
- var r = text.length;
- var lv = 0;
- var rv = textWidth;
- while (l < r && lv !== rv && lv !== maxWidth) {
- var m = Math.ceil(l + (r - l) * (maxWidth - lv) / (rv - lv));
- var mv = this._measureWidth(context, text.trimMiddle(m));
- if (mv <= maxWidth) {
- l = m;
- lv = mv;
- } else {
- r = m - 1;
- rv = mv;
- }
- }
- text = text.trimMiddle(l);
- return text !== "\u2026" ? text : "";
- },
-
- /**
- * @param {!CanvasRenderingContext2D} context
- * @param {string} text
- * @return {number}
- */
- _measureWidth: function(context, text)
- {
- var /** @const */ maxCacheableLength = 200;
- if (text.length > maxCacheableLength)
- return context.measureText(text).width;
-
- var font = context.font;
- var textWidths = this._textWidth.get(font);
- if (!textWidths) {
- textWidths = new Map();
- this._textWidth.set(font, textWidths);
- }
- var width = textWidths.get(text);
- if (!width) {
- width = context.measureText(text).width;
- textWidths.set(text, width);
- }
- return width;
- },
-
- _updateBoundaries: function()
- {
- this._totalTime = this._dataProvider.totalTime();
- this._minimumBoundary = this._dataProvider.minimumBoundary();
-
- var windowWidth = 1;
- if (this._timeWindowRight !== Infinity) {
- this._windowLeft = (this._timeWindowLeft - this._minimumBoundary) / this._totalTime;
- this._windowRight = (this._timeWindowRight - this._minimumBoundary) / this._totalTime;
- windowWidth = this._windowRight - this._windowLeft;
- } else if (this._timeWindowLeft === Infinity) {
- this._windowLeft = Infinity;
- this._windowRight = Infinity;
- } else {
- this._windowLeft = 0;
- this._windowRight = 1;
- }
-
- var totalPixels = Math.floor((this._offsetWidth - this._paddingLeft) / windowWidth);
- this._pixelWindowLeft = Math.floor(totalPixels * this._windowLeft);
-
- this._timeToPixel = totalPixels / this._totalTime;
- this._pixelToTime = this._totalTime / totalPixels;
- },
-
- _updateHeight: function()
- {
- var height = this._levelToHeight(this._dataProvider.maxStackDepth());
- this.setContentHeight(height);
- },
-
- /**
- * @override
- */
- onResize: function()
- {
- WebInspector.FlameChart.prototype.__proto__.onResize.call(this);
- this.scheduleUpdate();
- },
-
- /**
- * @override
- */
- update: function()
- {
- if (!this._timelineData())
- return;
- this._resetCanvas();
- this._updateHeight();
- this._updateBoundaries();
- this._calculator._updateBoundaries(this);
- this._draw(this._offsetWidth, this._offsetHeight);
- if (!this.isDragging())
- this._updateHighlight();
- },
-
- reset: function()
- {
- WebInspector.FlameChart.prototype.__proto__.reset.call(this);
- this._highlightedMarkerIndex = -1;
- this._highlightedEntryIndex = -1;
- this._selectedEntryIndex = -1;
- /** @type {!Map<string,!Map<string,number>>} */
- this._textWidth = new Map();
- this.update();
- },
-
- _enabled: function()
- {
- return this._rawTimelineDataLength !== 0;
- },
-
- __proto__: WebInspector.ChartViewport.prototype
+ }
+
+ /**
+ * @override
+ * @return {number}
+ */
+ paddingLeft() {
+ return this._paddingLeft;
+ }
+
+ /**
+ * @param {!WebInspector.FlameChart} mainPane
+ */
+ _updateBoundaries(mainPane) {
+ this._totalTime = mainPane._dataProvider.totalTime();
+ this._zeroTime = mainPane._dataProvider.minimumBoundary();
+ this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._totalTime;
+ this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this._totalTime;
+ this._paddingLeft = mainPane._paddingLeft;
+ this._width = mainPane._offsetWidth - this._paddingLeft;
+ this._timeToPixel = this._width / this.boundarySpan();
+ }
+
+ /**
+ * @override
+ * @param {number} time
+ * @return {number}
+ */
+ computePosition(time) {
+ return Math.round((time - this._minimumBoundaries) * this._timeToPixel + this._paddingLeft);
+ }
+
+ /**
+ * @override
+ * @param {number} value
+ * @param {number=} precision
+ * @return {string}
+ */
+ formatValue(value, precision) {
+ return this._dataProvider.formatValue(value - this._zeroTime, precision);
+ }
+
+ /**
+ * @override
+ * @return {number}
+ */
+ maximumBoundary() {
+ return this._maximumBoundaries;
+ }
+
+ /**
+ * @override
+ * @return {number}
+ */
+ minimumBoundary() {
+ return this._minimumBoundaries;
+ }
+
+ /**
+ * @override
+ * @return {number}
+ */
+ zeroTime() {
+ return this._zeroTime;
+ }
+
+ /**
+ * @override
+ * @return {number}
+ */
+ boundarySpan() {
+ return this._maximumBoundaries - this._minimumBoundaries;
+ }
};

Powered by Google App Engine
This is Rietveld 408576698