| 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;
|
| + }
|
| };
|
|
|