2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 WebInspector.FlameChartDelegate = function() { }
36 WebInspector.FlameChartDelegate.prototype = {
38 * @param {number} startTime
39 * @param {number} endTime
41 requestWindowTimes: function(startTime, endTime) { }
46 * @extends {WebInspector.HBox}
47 * @param {!WebInspector.FlameChartDataProvider} dataProvider
48 * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
49 * @param {boolean} isTopDown
51 WebInspector.FlameChart = function(dataProvider, flameChartDelegate, isTopDown)
53 WebInspector.HBox.call(this);
54 this.element.classList.add("flame-chart-main-pane");
55 this._flameChartDelegate = flameChartDelegate;
56 this._isTopDown = isTopDown;
58 this._calculator = new WebInspector.FlameChart.Calculator();
60 this._canvas = this.element.createChild("canvas");
61 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this));
62 this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
63 this._canvas.addEventListener("click", this._onClick.bind(this), false);
64 WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "move", null);
66 this._vScrollElement = this.element.createChild("div", "flame-chart-v-scroll");
67 this._vScrollContent = this._vScrollElement.createChild("div");
68 this._vScrollElement.addEventListener("scroll", this._scheduleUpdate.bind(this), false);
70 this._entryInfo = this.element.createChild("div", "profile-entry-info");
71 this._highlightElement = this.element.createChild("div", "flame-chart-highlight-element");
72 this._selectedElement = this.element.createChild("div", "flame-chart-selected-element");
74 this._dataProvider = dataProvider;
76 this._windowLeft = 0.0;
77 this._windowRight = 1.0;
78 this._windowWidth = 1.0;
79 this._timeWindowLeft = 0;
80 this._timeWindowRight = Infinity;
81 this._barHeight = dataProvider.barHeight();
82 this._barHeightDelta = this._isTopDown ? -this._barHeight : this._barHeight;
84 this._paddingLeft = this._dataProvider.paddingLeft();
85 this._markerPadding = 2;
86 this._markerRadius = this._barHeight / 2 - this._markerPadding;
87 this._highlightedEntryIndex = -1;
88 this._selectedEntryIndex = -1;
92 WebInspector.FlameChart.DividersBarHeight = 20;
97 WebInspector.FlameChartDataProvider = function()
102 entryLevels: (!Array.<number>|!Uint8Array),
103 entryTotalTimes: (!Array.<number>|!Float32Array),
104 entryOffsets: (!Array.<number>|!Float32Array)
107 WebInspector.FlameChart.TimelineData;
109 WebInspector.FlameChartDataProvider.prototype = {
113 barHeight: function() { },
116 * @param {number} startTime
117 * @param {number} endTime
118 * @return {?Array.<number>}
120 dividerOffsets: function(startTime, endTime) { },
125 zeroTime: function() { },
130 totalTime: function() { },
135 maxStackDepth: function() { },
138 * @return {?WebInspector.FlameChart.TimelineData}
140 timelineData: function() { },
143 * @param {number} entryIndex
144 * @return {?Array.<!{title: string, text: string}>}
146 prepareHighlightedEntryInfo: function(entryIndex) { },
149 * @param {number} entryIndex
152 canJumpToEntry: function(entryIndex) { },
155 * @param {number} entryIndex
158 entryTitle: function(entryIndex) { },
161 * @param {number} entryIndex
164 entryFont: function(entryIndex) { },
167 * @param {number} entryIndex
170 entryColor: function(entryIndex) { },
173 * @param {number} entryIndex
174 * @param {!CanvasRenderingContext2D} context
175 * @param {?string} text
176 * @param {number} barX
177 * @param {number} barY
178 * @param {number} barWidth
179 * @param {number} barHeight
180 * @param {function(number):number} offsetToPosition
183 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition) { },
186 * @param {number} entryIndex
189 forceDecoration: function(entryIndex) { },
192 * @param {number} entryIndex
195 textColor: function(entryIndex) { },
200 textBaseline: function() { },
205 textPadding: function() { },
208 * @return {?{startTimeOffset: number, endTimeOffset: number}}
210 highlightTimeRange: function(entryIndex) { },
215 paddingLeft: function() { },
218 WebInspector.FlameChart.Events = {
219 EntrySelected: "EntrySelected"
225 * @param {!{min: number, max: number, count: number}|number=} hueSpace
226 * @param {!{min: number, max: number, count: number}|number=} satSpace
227 * @param {!{min: number, max: number, count: number}|number=} lightnessSpace
229 WebInspector.FlameChart.ColorGenerator = function(hueSpace, satSpace, lightnessSpace)
231 this._hueSpace = hueSpace || { min: 0, max: 360, count: 20 };
232 this._satSpace = satSpace || 67;
233 this._lightnessSpace = lightnessSpace || 80;
235 this._currentColorIndex = 0;
238 WebInspector.FlameChart.ColorGenerator.prototype = {
241 * @param {string|!CanvasGradient} color
243 setColorForID: function(id, color)
245 this._colors[id] = color;
252 colorForID: function(id)
254 var color = this._colors[id];
256 color = this._createColor(this._currentColorIndex++);
257 this._colors[id] = color;
263 * @param {number} index
266 _createColor: function(index)
268 var h = this._indexToValueInSpace(index, this._hueSpace);
269 var s = this._indexToValueInSpace(index, this._satSpace);
270 var l = this._indexToValueInSpace(index, this._lightnessSpace);
271 return "hsl(" + h + ", " + s + "%, " + l + "%)";
275 * @param {number} index
276 * @param {!{min: number, max: number, count: number}|number} space
279 _indexToValueInSpace: function(index, space)
281 if (typeof space === "number")
283 index %= space.count;
284 return space.min + index / space.count * (space.max - space.min);
291 * @implements {WebInspector.TimelineGrid.Calculator}
293 WebInspector.FlameChart.Calculator = function()
295 this._paddingLeft = 0;
298 WebInspector.FlameChart.Calculator.prototype = {
302 paddingLeft: function()
304 return this._paddingLeft;
308 * @param {!WebInspector.FlameChart} mainPane
310 _updateBoundaries: function(mainPane)
312 this._totalTime = mainPane._dataProvider.totalTime();
313 this._zeroTime = mainPane._dataProvider.zeroTime();
314 this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._totalTime;
315 this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this._totalTime;
316 this._paddingLeft = mainPane._paddingLeft;
317 this._width = mainPane._canvas.width / window.devicePixelRatio - this._paddingLeft;
318 this._timeToPixel = this._width / this.boundarySpan();
322 * @param {number} time
325 computePosition: function(time)
327 return Math.round((time - this._minimumBoundaries) * this._timeToPixel + this._paddingLeft);
331 * @param {number} value
332 * @param {number=} precision
335 formatTime: function(value, precision)
337 return Number.preciseMillisToString(value - this._zeroTime, precision);
343 maximumBoundary: function()
345 return this._maximumBoundaries;
351 minimumBoundary: function()
353 return this._minimumBoundaries;
361 return this._zeroTime;
367 boundarySpan: function()
369 return this._maximumBoundaries - this._minimumBoundaries;
373 WebInspector.FlameChart.prototype = {
374 _resetCanvas: function()
376 var ratio = window.devicePixelRatio;
377 this._canvas.width = this._offsetWidth * ratio;
378 this._canvas.height = this._offsetHeight * ratio;
379 this._canvas.style.width = this._offsetWidth + "px";
380 this._canvas.style.height = this._offsetHeight + "px";
384 * @return {?WebInspector.FlameChart.TimelineData}
386 _timelineData: function()
388 var timelineData = this._dataProvider.timelineData();
389 if (timelineData !== this._rawTimelineData || timelineData.entryOffsets.length !== this._rawTimelineDataLength)
390 this._processTimelineData(timelineData);
391 return this._rawTimelineData;
395 * @param {number} startTime
396 * @param {number} endTime
398 setWindowTimes: function(startTime, endTime)
400 this._timeWindowLeft = startTime;
401 this._timeWindowRight = endTime;
402 this._scheduleUpdate();
406 * @param {!MouseEvent} event
408 _startCanvasDragging: function(event)
410 if (!this._timelineData() || this._timeWindowRight === Infinity)
412 this._isDragging = true;
413 this._maxDragOffset = 0;
414 this._dragStartPointX = event.pageX;
415 this._dragStartPointY = event.pageY;
416 this._dragStartScrollTop = this._vScrollElement.scrollTop;
417 this._dragStartWindowLeft = this._timeWindowLeft;
418 this._dragStartWindowRight = this._timeWindowRight;
419 this._canvas.style.cursor = "";
425 * @param {!MouseEvent} event
427 _canvasDragging: function(event)
429 var pixelShift = this._dragStartPointX - event.pageX;
430 var pixelScroll = this._dragStartPointY - event.pageY;
431 this._vScrollElement.scrollTop = this._dragStartScrollTop + pixelScroll;
432 var windowShift = pixelShift / this._totalPixels;
433 var windowTime = this._windowWidth * this._totalTime;
434 var timeShift = windowTime * pixelShift / this._pixelWindowWidth;
435 timeShift = Number.constrain(
437 this._zeroTime - this._dragStartWindowLeft,
438 this._zeroTime + this._totalTime - this._dragStartWindowRight
440 var windowLeft = this._dragStartWindowLeft + timeShift;
441 var windowRight = this._dragStartWindowRight + timeShift;
442 this._flameChartDelegate.requestWindowTimes(windowLeft, windowRight);
443 this._maxDragOffset = Math.max(this._maxDragOffset, Math.abs(pixelShift));
446 _endCanvasDragging: function()
448 this._isDragging = false;
452 * @param {?MouseEvent} event
454 _onMouseMove: function(event)
456 if (this._isDragging)
458 var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY);
460 if (this._highlightedEntryIndex === entryIndex)
463 if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex))
464 this._canvas.style.cursor = "default";
466 this._canvas.style.cursor = "pointer";
468 this._highlightedEntryIndex = entryIndex;
470 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
471 this._entryInfo.removeChildren();
473 if (this._highlightedEntryIndex === -1)
476 if (!this._isDragging) {
477 var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this._highlightedEntryIndex);
479 this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
485 // onClick comes after dragStart and dragEnd events.
486 // So if there was drag (mouse move) in the middle of that events
487 // we skip the click. Otherwise we jump to the sources.
488 const clickThreshold = 5;
489 if (this._maxDragOffset > clickThreshold)
491 if (this._highlightedEntryIndex === -1)
493 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, this._highlightedEntryIndex);
497 * @param {?MouseEvent} e
499 _onMouseWheel: function(e)
501 var scrollIsThere = this._totalHeight > this._offsetHeight;
502 var windowLeft = this._timeWindowLeft ? this._timeWindowLeft : this._dataProvider.zeroTime();
503 var windowRight = this._timeWindowRight !== Infinity ? this._timeWindowRight : this._dataProvider.zeroTime() + this._dataProvider.totalTime();
505 var panHorizontally = e.wheelDeltaX && !e.shiftKey;
506 var panVertically = scrollIsThere && ((e.wheelDeltaY && !e.shiftKey) || (Math.abs(e.wheelDeltaX) === 120 && !e.shiftKey));
508 this._vScrollElement.scrollTop -= e.wheelDeltaY / 120 * this._offsetHeight / 8;
509 } else if (panHorizontally) {
510 var shift = -e.wheelDeltaX * this._pixelToTime;
511 shift = Number.constrain(shift, this._zeroTime - windowLeft, this._totalTime + this._zeroTime - windowRight);
513 windowRight += shift;
515 const mouseWheelZoomSpeed = 1 / 120;
516 var zoom = Math.pow(1.2, -(e.wheelDeltaY || e.wheelDeltaX) * mouseWheelZoomSpeed) - 1;
517 var cursorTime = this._cursorTime(e.offsetX);
518 windowLeft += (windowLeft - cursorTime) * zoom;
519 windowRight += (windowRight - cursorTime) * zoom;
521 windowLeft = Number.constrain(windowLeft, this._zeroTime, this._totalTime + this._zeroTime);
522 windowRight = Number.constrain(windowRight, this._zeroTime, this._totalTime + this._zeroTime);
523 this._flameChartDelegate.requestWindowTimes(windowLeft, windowRight);
525 // Block swipe gesture.
533 _cursorTime: function(x)
535 return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime + this._zeroTime;
543 _coordinatesToEntryIndex: function(x, y)
545 y += this._scrollTop;
546 var timelineData = this._timelineData();
549 var cursorTimeOffset = this._cursorTime(x) - this._zeroTime;
552 if (this._isTopDown) {
553 cursorLevel = Math.floor((y - WebInspector.FlameChart.DividersBarHeight) / this._barHeight);
554 offsetFromLevel = y - WebInspector.FlameChart.DividersBarHeight - cursorLevel * this._barHeight;
556 cursorLevel = Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight);
557 offsetFromLevel = this._canvas.height / window.devicePixelRatio - cursorLevel * this._barHeight;
559 var entryOffsets = timelineData.entryOffsets;
560 var entryTotalTimes = timelineData.entryTotalTimes;
561 var entryIndexes = this._timelineLevels[cursorLevel];
562 if (!entryIndexes || !entryIndexes.length)
565 function comparator(time, entryIndex)
567 return time - entryOffsets[entryIndex];
569 var indexOnLevel = Math.max(entryIndexes.upperBound(cursorTimeOffset, comparator) - 1, 0);
572 * @this {WebInspector.FlameChart}
573 * @param {number} entryIndex
576 function checkEntryHit(entryIndex)
578 if (entryIndex === undefined)
580 var startTime = entryOffsets[entryIndex];
581 var duration = entryTotalTimes[entryIndex];
582 if (isNaN(duration)) {
583 var dx = (startTime - cursorTimeOffset) / this._pixelToTime;
584 var dy = this._barHeight / 2 - offsetFromLevel;
585 return dx * dx + dy * dy < this._markerRadius * this._markerRadius;
587 var endTime = startTime + duration;
588 var barThreshold = 3 * this._pixelToTime;
589 return startTime - barThreshold < cursorTimeOffset && cursorTimeOffset < endTime + barThreshold;
592 var entryIndex = entryIndexes[indexOnLevel];
593 if (checkEntryHit.call(this, entryIndex))
595 entryIndex = entryIndexes[indexOnLevel + 1];
596 if (checkEntryHit.call(this, entryIndex))
602 * @param {number} height
603 * @param {number} width
605 _draw: function(width, height)
607 var timelineData = this._timelineData();
611 var context = this._canvas.getContext("2d");
613 var ratio = window.devicePixelRatio;
614 context.scale(ratio, ratio);
616 var timeWindowRight = this._timeWindowRight - this._zeroTime;
617 var timeWindowLeft = this._timeWindowLeft - this._zeroTime;
618 var timeToPixel = this._timeToPixel;
619 var pixelWindowLeft = this._pixelWindowLeft;
620 var paddingLeft = this._paddingLeft;
621 var minWidth = this._minWidth;
622 var entryTotalTimes = timelineData.entryTotalTimes;
623 var entryOffsets = timelineData.entryOffsets;
624 var entryLevels = timelineData.entryLevels;
626 var titleIndices = new Uint32Array(timelineData.entryTotalTimes);
627 var nextTitleIndex = 0;
628 var markerIndices = new Uint32Array(timelineData.entryTotalTimes);
629 var nextMarkerIndex = 0;
630 var textPadding = this._dataProvider.textPadding();
631 this._minTextWidth = 2 * textPadding + this._measureWidth(context, "\u2026");
632 var minTextWidth = this._minTextWidth;
634 var barHeight = this._barHeight;
636 var offsetToPosition = this._offsetToPosition.bind(this);
637 var textBaseHeight = this._baseHeight + barHeight - this._dataProvider.textBaseline();
638 var colorBuckets = {};
639 var minVisibleBarLevel = Math.max(Math.floor((this._scrollTop - this._baseHeight) / barHeight), 0);
640 var maxVisibleBarLevel = Math.min(Math.floor((this._scrollTop - this._baseHeight + height) / barHeight), this._dataProvider.maxStackDepth());
642 context.translate(0, -this._scrollTop);
644 function comparator(time, entryIndex)
646 return time - entryOffsets[entryIndex];
649 for (var level = minVisibleBarLevel; level <= maxVisibleBarLevel; ++level) {
650 // Entries are ordered by start time within a level, so find the last visible entry.
651 var levelIndexes = this._timelineLevels[level];
652 var rightIndexOnLevel = levelIndexes.lowerBound(timeWindowRight, comparator) - 1;
653 var lastDrawOffset = Infinity;
654 for (var entryIndexOnLevel = rightIndexOnLevel; entryIndexOnLevel >= 0; --entryIndexOnLevel) {
655 var entryIndex = levelIndexes[entryIndexOnLevel];
656 var entryOffset = entryOffsets[entryIndex];
657 var entryOffsetRight = entryOffset + (isNaN(entryTotalTimes[entryIndex]) ? 0 : entryTotalTimes[entryIndex]);
658 if (entryOffsetRight <= timeWindowLeft)
661 var barX = this._offsetToPosition(entryOffset);
662 if (barX >= lastDrawOffset)
664 var barRight = Math.min(this._offsetToPosition(entryOffsetRight), lastDrawOffset);
665 lastDrawOffset = barX;
667 var color = this._dataProvider.entryColor(entryIndex);
668 var bucket = colorBuckets[color];
671 colorBuckets[color] = bucket;
673 bucket.push(entryIndex);
677 var colors = Object.keys(colorBuckets);
678 // We don't use for-in here because it couldn't be optimized.
679 for (var c = 0; c < colors.length; ++c) {
680 var color = colors[c];
681 context.fillStyle = color;
682 context.strokeStyle = color;
683 var indexes = colorBuckets[color];
685 // First fill the boxes.
687 for (var i = 0; i < indexes.length; ++i) {
688 var entryIndex = indexes[i];
689 var entryOffset = entryOffsets[entryIndex];
690 var barX = this._offsetToPosition(entryOffset);
691 var barRight = this._offsetToPosition(entryOffset + entryTotalTimes[entryIndex]);
692 var barWidth = Math.max(barRight - barX, minWidth);
693 var barLevel = entryLevels[entryIndex];
694 var barY = this._levelToHeight(barLevel);
695 if (isNaN(entryTotalTimes[entryIndex])) {
696 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
697 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
698 markerIndices[nextMarkerIndex++] = entryIndex;
700 context.rect(barX, barY, barWidth, barHeight);
701 if (barWidth > minTextWidth || this._dataProvider.forceDecoration(entryIndex))
702 titleIndices[nextTitleIndex++] = entryIndex;
708 context.strokeStyle = "rgb(0, 0, 0)";
710 for (var m = 0; m < nextMarkerIndex; ++m) {
711 var entryIndex = markerIndices[m];
712 var entryOffset = entryOffsets[entryIndex];
713 var barX = this._offsetToPosition(entryOffset);
714 var barLevel = entryLevels[entryIndex];
715 var barY = this._levelToHeight(barLevel);
716 context.moveTo(barX + this._markerRadius, barY + barHeight / 2);
717 context.arc(barX, barY + barHeight / 2, this._markerRadius, 0, Math.PI * 2);
721 context.textBaseline = "alphabetic";
723 for (var i = 0; i < nextTitleIndex; ++i) {
724 var entryIndex = titleIndices[i];
725 var entryOffset = entryOffsets[entryIndex];
726 var barX = this._offsetToPosition(entryOffset);
727 var barRight = this._offsetToPosition(entryOffset + entryTotalTimes[entryIndex]);
728 var barWidth = Math.max(barRight - barX, minWidth);
729 var barLevel = entryLevels[entryIndex];
730 var barY = this._levelToHeight(barLevel);
731 var text = this._dataProvider.entryTitle(entryIndex);
732 if (text && text.length) {
733 context.font = this._dataProvider.entryFont(entryIndex);
734 text = this._prepareText(context, text, barWidth - 2 * textPadding);
737 if (this._dataProvider.decorateEntry(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition))
739 if (!text || !text.length)
742 context.fillStyle = this._dataProvider.textColor(entryIndex);
743 context.fillText(text, barX + textPadding, textBaseHeight - barLevel * this._barHeightDelta);
747 var offsets = this._dataProvider.dividerOffsets(this._calculator.minimumBoundary(), this._calculator.maximumBoundary());
748 WebInspector.TimelineGrid.drawCanvasGrid(this._canvas, this._calculator, offsets);
750 this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
751 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
755 * @param {?WebInspector.FlameChart.TimelineData} timelineData
757 _processTimelineData: function(timelineData)
760 this._timelineLevels = null;
761 this._rawTimelineData = null;
762 this._rawTimelineDataLength = 0;
766 var entryCounters = new Uint32Array(this._dataProvider.maxStackDepth() + 1);
767 for (var i = 0; i < timelineData.entryLevels.length; ++i)
768 ++entryCounters[timelineData.entryLevels[i]];
769 var levelIndexes = new Array(entryCounters.length);
770 for (var i = 0; i < levelIndexes.length; ++i) {
771 levelIndexes[i] = new Uint32Array(entryCounters[i]);
772 entryCounters[i] = 0;
774 for (var i = 0; i < timelineData.entryLevels.length; ++i) {
775 var level = timelineData.entryLevels[i];
776 levelIndexes[level][entryCounters[level]++] = i;
778 this._timelineLevels = levelIndexes;
779 this._rawTimelineData = timelineData;
780 this._rawTimelineDataLength = timelineData.entryOffsets.length;
784 * @param {number} entryIndex
786 setSelectedEntry: function(entryIndex)
788 this._selectedEntryIndex = entryIndex;
789 this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
792 _updateElementPosition: function(element, entryIndex)
794 if (element.parentElement)
796 if (entryIndex === -1)
798 var timeRange = this._dataProvider.highlightTimeRange(entryIndex);
801 var timelineData = this._timelineData();
802 var barX = this._offsetToPosition(timeRange.startTimeOffset);
803 var barRight = this._offsetToPosition(timeRange.endTimeOffset);
804 if (barRight === 0 || barX === this._canvas.width)
806 var barWidth = Math.max(barRight - barX, this._minWidth);
807 var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - this._scrollTop;
808 var style = element.style;
809 style.left = barX + "px";
810 style.top = barY + "px";
811 style.width = barWidth + "px";
812 style.height = this._barHeight + "px";
813 this.element.appendChild(element);
816 _offsetToPosition: function(offset)
818 var value = Math.floor(offset * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
819 return Math.min(this._canvas.width, Math.max(0, value));
822 _levelToHeight: function(level)
824 return this._baseHeight - level * this._barHeightDelta;
827 _buildEntryInfo: function(entryInfo)
829 var infoTable = document.createElement("table");
830 infoTable.className = "info-table";
831 for (var i = 0; i < entryInfo.length; ++i) {
832 var row = infoTable.createChild("tr");
833 var titleCell = row.createChild("td");
834 titleCell.textContent = entryInfo[i].title;
835 titleCell.className = "title";
836 var textCell = row.createChild("td");
837 textCell.textContent = entryInfo[i].text;
843 * @param {!CanvasRenderingContext2D} context
844 * @param {string} title
845 * @param {number} maxSize
848 _prepareText: function(context, title, maxSize)
850 var titleWidth = this._measureWidth(context, title);
851 if (maxSize >= titleWidth)
855 var r = title.length;
857 var m = (l + r) >> 1;
858 if (this._measureWidth(context, title.trimMiddle(m)) <= maxSize)
863 title = title.trimMiddle(r - 1);
864 return title !== "\u2026" ? title : "";
868 * @param {!CanvasRenderingContext2D} context
869 * @param {string} text
872 _measureWidth: function(context, text)
874 if (text.length > 20)
875 return context.measureText(text).width;
877 var font = context.font;
878 var textWidths = this._textWidth[font];
881 this._textWidth[font] = textWidths;
883 var width = textWidths[text];
885 width = context.measureText(text).width;
886 textWidths[text] = width;
891 _updateBoundaries: function()
893 this._totalTime = this._dataProvider.totalTime();
894 this._zeroTime = this._dataProvider.zeroTime();
896 if (this._timeWindowRight !== Infinity) {
897 this._windowLeft = (this._timeWindowLeft - this._zeroTime) / this._totalTime;
898 this._windowRight = (this._timeWindowRight - this._zeroTime) / this._totalTime;
899 this._windowWidth = this._windowRight - this._windowLeft;
901 this._windowLeft = 0;
902 this._windowRight = 1;
903 this._windowWidth = 1;
906 this._pixelWindowWidth = this._offsetWidth - this._paddingLeft;
907 this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth);
908 this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft);
909 this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight);
911 this._timeToPixel = this._totalPixels / this._totalTime;
912 this._pixelToTime = this._totalTime / this._totalPixels;
913 this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
915 this._baseHeight = this._isTopDown ? WebInspector.FlameChart.DividersBarHeight : this._offsetHeight - this._barHeight;
917 this._totalHeight = this._levelToHeight(this._dataProvider.maxStackDepth() + 1);
918 this._vScrollContent.style.height = this._totalHeight + "px";
919 this._scrollTop = this._vScrollElement.scrollTop;
924 var showScroll = this._totalHeight > this._offsetHeight;
925 this._vScrollElement.classList.toggle("hidden", !showScroll);
926 this._offsetWidth = this.element.offsetWidth - (WebInspector.isMac() ? 0 : this._vScrollElement.offsetWidth);
927 this._offsetHeight = this.element.offsetHeight;
928 this._scheduleUpdate();
931 _scheduleUpdate: function()
933 if (this._updateTimerId)
935 this._updateTimerId = requestAnimationFrame(this.update.bind(this));
940 this._updateTimerId = 0;
941 if (!this._timelineData())
944 this._updateBoundaries();
945 this._calculator._updateBoundaries(this);
946 this._draw(this._offsetWidth, this._offsetHeight);
951 this._highlightedEntryIndex = -1;
952 this._selectedEntryIndex = -1;
953 this._textWidth = {};
957 __proto__: WebInspector.HBox.prototype