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.
33 * @extends {WebInspector.View}
34 * @param {!WebInspector.FlameChartDataProvider} dataProvider
36 WebInspector.FlameChart = function(dataProvider)
38 WebInspector.View.call(this);
39 this.registerRequiredCSS("flameChart.css");
40 this.element.id = "cpu-flame-chart";
42 this._overviewPane = new WebInspector.FlameChart.OverviewPane(dataProvider);
43 this._overviewPane.show(this.element);
45 this._mainPane = new WebInspector.FlameChart.MainPane(dataProvider, this._overviewPane, false);
46 this._mainPane.show(this.element);
47 this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
48 this._overviewPane._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
51 WebInspector.FlameChart.DividersBarHeight = 20;
53 WebInspector.FlameChart.prototype = {
55 * @param {!WebInspector.Event} event
57 _onWindowChanged: function(event)
59 this._mainPane.changeWindow(this._overviewPane._overviewGrid.windowLeft(), this._overviewPane._overviewGrid.windowRight());
63 * @param {!number} timeLeft
64 * @param {!number} timeRight
66 selectRange: function(timeLeft, timeRight)
68 this._overviewPane._selectRange(timeLeft, timeRight);
72 * @param {!WebInspector.Event} event
74 _onEntrySelected: function(event)
76 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data);
81 this._overviewPane.update();
82 this._mainPane.update();
85 __proto__: WebInspector.View.prototype
91 WebInspector.FlameChartDataProvider = function()
96 maxStackDepth: number,
98 entryLevels: !Array.<number>,
99 entryTotalTimes: !Array.<number>,
100 entrySelfTimes: !Array.<number>,
101 entryOffsets: !Array.<number>,
102 colorEntryIndexes: !Array.<number>,
103 entryTitles: !Array.<string>,
104 entryDeoptFlags: !Array.<number>
107 WebInspector.FlameChart.TimelineData;
109 WebInspector.FlameChartDataProvider.prototype = {
111 * @return {?WebInspector.FlameChart.TimelineData}
113 timelineData: function() { },
116 * @return {!WebInspector.FlameChart.ColorGenerator}
118 colorGenerator: function() { },
121 * @param {number} entryIndex
122 * @return {?Array.<!{title: string, text: string}>}
124 prepareHighlightedEntryInfo: function(entryIndex) { },
127 * @param {number} entryIndex
130 canJumpToEntry: function(entryIndex) { },
133 * @param {number} entryIndex
136 entryData: function(entryIndex) { }
141 * @implements {WebInspector.TimelineGrid.Calculator}
143 WebInspector.FlameChart.Calculator = function()
147 WebInspector.FlameChart.Calculator.prototype = {
149 * @param {!WebInspector.FlameChart.MainPane} mainPane
151 _updateBoundaries: function(mainPane)
155 return Math.log(x) / Math.LN10;
157 this._decimalDigits = Math.max(0, -Math.floor(log10(mainPane._timelineGrid.gridSliceTime * 1.01)));
158 var totalTime = mainPane._timelineData().totalTime;
159 this._minimumBoundaries = mainPane._windowLeft * totalTime;
160 this._maximumBoundaries = mainPane._windowRight * totalTime;
161 this.paddingLeft = mainPane._paddingLeft;
162 this._width = mainPane._canvas.width - this.paddingLeft;
163 this._timeToPixel = this._width / this.boundarySpan();
167 * @param {number} time
170 computePosition: function(time)
172 return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft;
176 * @param {number} value
177 * @param {boolean=} hires
180 formatTime: function(value, hires)
182 var format = "%." + this._decimalDigits + "f\u2009ms";
183 return WebInspector.UIString(format, value + this._minimumBoundaries);
189 maximumBoundary: function()
191 return this._maximumBoundaries;
197 minimumBoundary: function()
199 return this._minimumBoundaries;
213 boundarySpan: function()
215 return this._maximumBoundaries - this._minimumBoundaries;
221 * @implements {WebInspector.TimelineGrid.Calculator}
223 WebInspector.FlameChart.OverviewCalculator = function()
227 WebInspector.FlameChart.OverviewCalculator.prototype = {
229 * @param {!WebInspector.FlameChart.OverviewPane} overviewPane
231 _updateBoundaries: function(overviewPane)
233 this._minimumBoundaries = 0;
234 var totalTime = overviewPane._timelineData().totalTime;
235 this._maximumBoundaries = totalTime;
236 this._xScaleFactor = overviewPane._overviewCanvas.width / totalTime;
240 * @param {number} time
243 computePosition: function(time)
245 return (time - this._minimumBoundaries) * this._xScaleFactor;
249 * @param {number} value
250 * @param {boolean=} hires
253 formatTime: function(value, hires)
255 return Number.secondsToString((value + this._minimumBoundaries) / 1000, hires);
261 maximumBoundary: function()
263 return this._maximumBoundaries;
269 minimumBoundary: function()
271 return this._minimumBoundaries;
279 return this._minimumBoundaries;
285 boundarySpan: function()
287 return this._maximumBoundaries - this._minimumBoundaries;
291 WebInspector.FlameChart.Events = {
292 EntrySelected: "EntrySelected"
298 WebInspector.FlameChart.ColorGenerator = function()
300 this._colorPairs = {};
301 this._colorIndexes = [];
302 this._currentColorIndex = 0;
305 WebInspector.FlameChart.ColorGenerator.prototype = {
308 * @param {string|!CanvasGradient} highlighted
309 * @param {string|!CanvasGradient} normal
311 setColorPairForID: function(id, highlighted, normal)
313 var colorPair = {index: this._currentColorIndex++, highlighted: highlighted, normal: normal};
314 this._colorPairs[id] = colorPair;
315 this._colorIndexes[colorPair.index] = colorPair;
319 * @param {!string} id
320 * @param {number=} sat
323 colorPairForID: function(id, sat)
325 if (typeof sat !== "number")
327 var colorPairs = this._colorPairs;
328 var colorPair = colorPairs[id];
330 colorPairs[id] = colorPair = this._createPair(this._currentColorIndex++, sat);
331 this._colorIndexes[colorPair.index] = colorPair;
337 * @param {!number} index
339 _colorPairForIndex: function(index)
341 return this._colorIndexes[index];
345 * @param {!number} index
346 * @param {!number} sat
348 _createPair: function(index, sat)
350 var hue = (index * 7 + 12 * (index % 2)) % 360;
351 return {index: index, highlighted: "hsla(" + hue + ", " + sat + "%, 33%, 0.7)", normal: "hsla(" + hue + ", " + sat + "%, 66%, 0.7)"};
358 WebInspector.FlameChart.OverviewPaneInterface = function()
362 WebInspector.FlameChart.OverviewPaneInterface.prototype = {
364 * @param {number} zoom
365 * @param {number} referencePoint
367 zoom: function(zoom, referencePoint) { },
370 * @param {number} windowLeft
371 * @param {number} windowRight
373 setWindow: function(windowLeft, windowRight) { },
378 * @extends {WebInspector.View}
379 * @implements {WebInspector.FlameChart.OverviewPaneInterface}
380 * @param {!WebInspector.FlameChartDataProvider} dataProvider
382 WebInspector.FlameChart.OverviewPane = function(dataProvider)
384 WebInspector.View.call(this);
385 this.element.classList.add("flame-chart-overview-pane");
386 this._overviewContainer = this.element.createChild("div", "overview-container");
387 this._overviewGrid = new WebInspector.OverviewGrid("flame-chart");
388 this._overviewGrid.element.classList.add("fill");
389 this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas");
390 this._overviewContainer.appendChild(this._overviewGrid.element);
391 this._overviewCalculator = new WebInspector.FlameChart.OverviewCalculator();
392 this._dataProvider = dataProvider;
395 WebInspector.FlameChart.OverviewPane.prototype = {
397 * @param {number} zoom
398 * @param {number} referencePoint
400 zoom: function(zoom, referencePoint)
402 this._overviewGrid.zoom(zoom, referencePoint);
406 * @param {number} windowLeft
407 * @param {number} windowRight
409 setWindow: function(windowLeft, windowRight)
411 this._overviewGrid.setWindow(windowLeft, windowRight);
415 * @param {!number} timeLeft
416 * @param {!number} timeRight
418 _selectRange: function(timeLeft, timeRight)
420 var timelineData = this._timelineData();
423 this._overviewGrid.setWindow(timeLeft / timelineData.totalTime, timeRight / timelineData.totalTime);
427 * @return {?WebInspector.FlameChart.TimelineData}
429 _timelineData: function()
431 return this._dataProvider.timelineData();
436 this._scheduleUpdate();
439 _scheduleUpdate: function()
441 if (this._updateTimerId)
443 this._updateTimerId = setTimeout(this.update.bind(this), 10);
448 this._updateTimerId = 0;
449 var timelineData = this._timelineData();
452 this._resetCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight);
453 this._overviewCalculator._updateBoundaries(this);
454 this._overviewGrid.updateDividers(this._overviewCalculator);
455 WebInspector.FlameChart.OverviewPane.drawOverviewCanvas(
457 this._overviewCanvas.getContext("2d"),
458 this._overviewContainer.clientWidth,
459 this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight
464 * @param {!number} width
465 * @param {!number} height
467 _resetCanvas: function(width, height)
469 var ratio = window.devicePixelRatio;
470 this._overviewCanvas.width = width * ratio;
471 this._overviewCanvas.height = height * ratio;
474 __proto__: WebInspector.View.prototype
478 * @param {!WebInspector.FlameChart.TimelineData} timelineData
479 * @param {!number} width
481 WebInspector.FlameChart.OverviewPane.calculateDrawData = function(timelineData, width)
483 var entryOffsets = timelineData.entryOffsets;
484 var entryTotalTimes = timelineData.entryTotalTimes;
485 var entryLevels = timelineData.entryLevels;
486 var length = entryOffsets.length;
488 var drawData = new Uint8Array(width);
489 var scaleFactor = width / timelineData.totalTime;
491 for (var entryIndex = 0; entryIndex < length; ++entryIndex) {
492 var start = Math.floor(entryOffsets[entryIndex] * scaleFactor);
493 var finish = Math.floor((entryOffsets[entryIndex] + entryTotalTimes[entryIndex]) * scaleFactor);
494 for (var x = start; x <= finish; ++x)
495 drawData[x] = Math.max(drawData[x], entryLevels[entryIndex] + 1);
501 * @param {!WebInspector.FlameChart.TimelineData} timelineData
502 * @param {!Object} context
503 * @param {!number} width
504 * @param {!number} height
506 WebInspector.FlameChart.OverviewPane.drawOverviewCanvas = function(timelineData, context, width, height)
508 var drawData = WebInspector.FlameChart.OverviewPane.calculateDrawData(timelineData, width);
512 var ratio = window.devicePixelRatio;
513 var canvasWidth = width * ratio;
514 var canvasHeight = height * ratio;
516 var yScaleFactor = canvasHeight / (timelineData.maxStackDepth * 1.1);
517 context.lineWidth = 1;
518 context.translate(0.5, 0.5);
519 context.strokeStyle = "rgba(20,0,0,0.4)";
520 context.fillStyle = "rgba(214,225,254,0.8)";
521 context.moveTo(-1, canvasHeight - 1);
523 context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1));
525 for (var x = 0; x < width; ++x) {
526 value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1);
527 context.lineTo(x * ratio, value);
529 context.lineTo(canvasWidth + 1, value);
530 context.lineTo(canvasWidth + 1, canvasHeight - 1);
538 * @extends {WebInspector.View}
539 * @param {!WebInspector.FlameChartDataProvider} dataProvider
540 * @param {?WebInspector.FlameChart.OverviewPaneInterface} overviewPane
541 * @param {boolean} isTopDown
543 WebInspector.FlameChart.MainPane = function(dataProvider, overviewPane, isTopDown)
545 WebInspector.View.call(this);
546 this.element.classList.add("flame-chart-main-pane");
547 this._overviewPane = overviewPane;
548 this._isTopDown = isTopDown;
550 this._timelineGrid = new WebInspector.TimelineGrid();
551 this.element.appendChild(this._timelineGrid.element);
552 this._calculator = new WebInspector.FlameChart.Calculator();
554 this._canvas = this.element.createChild("canvas");
555 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this));
556 this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
557 this._canvas.addEventListener("click", this._onClick.bind(this), false);
558 WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "move", null);
560 this._entryInfo = this.element.createChild("div", "profile-entry-info");
562 this._dataProvider = dataProvider;
564 this._windowLeft = 0.0;
565 this._windowRight = 1.0;
566 this._windowWidth = 1.0;
567 this._barHeight = 15;
569 this._paddingLeft = 15;
570 this._highlightedEntryIndex = -1;
573 WebInspector.FlameChart.MainPane.prototype = {
575 * @return {?WebInspector.FlameChart.TimelineData}
577 _timelineData: function()
579 return this._dataProvider.timelineData();
583 * @param {!number} windowLeft
584 * @param {!number} windowRight
586 changeWindow: function(windowLeft, windowRight)
588 this._windowLeft = windowLeft;
589 this._windowRight = windowRight;
590 this._windowWidth = this._windowRight - this._windowLeft;
592 this._scheduleUpdate();
596 * @param {!MouseEvent} event
598 _startCanvasDragging: function(event)
600 if (!this._timelineData())
602 this._isDragging = true;
603 this._wasDragged = false;
604 this._dragStartPoint = event.pageX;
605 this._dragStartWindowLeft = this._windowLeft;
606 this._dragStartWindowRight = this._windowRight;
607 this._canvas.style.cursor = "";
613 * @param {!MouseEvent} event
615 _canvasDragging: function(event)
617 var pixelShift = this._dragStartPoint - event.pageX;
618 var windowShift = pixelShift / this._totalPixels;
620 var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift);
621 if (windowLeft === this._windowLeft)
623 windowShift = windowLeft - this._dragStartWindowLeft;
625 var windowRight = Math.min(1, this._dragStartWindowRight + windowShift);
626 if (windowRight === this._windowRight)
628 windowShift = windowRight - this._dragStartWindowRight;
629 if (this._overviewPane)
630 this._overviewPane.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift);
631 this._wasDragged = true;
634 _endCanvasDragging: function()
636 this._isDragging = false;
640 * @param {?MouseEvent} event
642 _onMouseMove: function(event)
644 if (this._isDragging)
647 var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY);
649 if (this._highlightedEntryIndex === entryIndex)
652 if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex))
653 this._canvas.style.cursor = "default";
655 this._canvas.style.cursor = "pointer";
657 this._highlightedEntryIndex = entryIndex;
658 this._scheduleUpdate();
663 // onClick comes after dragStart and dragEnd events.
664 // So if there was drag (mouse move) in the middle of that events
665 // we skip the click. Otherwise we jump to the sources.
666 if (this._wasDragged)
668 if (this._highlightedEntryIndex === -1)
670 var data = this._dataProvider.entryData(this._highlightedEntryIndex);
672 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, data);
676 * @param {?MouseEvent} e
678 _onMouseWheel: function(e)
680 if (!this._overviewPane)
683 const zoomFactor = 1.1;
684 const mouseWheelZoomSpeed = 1 / 120;
686 var zoom = Math.pow(zoomFactor, -e.wheelDeltaY * mouseWheelZoomSpeed);
687 var referencePoint = (this._pixelWindowLeft + e.offsetX - this._paddingLeft) / this._totalPixels;
688 this._overviewPane.zoom(zoom, referencePoint);
690 var shift = Number.constrain(-1 * this._windowWidth / 4 * e.wheelDeltaX / 120, -this._windowLeft, 1 - this._windowRight);
691 this._overviewPane.setWindow(this._windowLeft + shift, this._windowRight + shift);
699 _coordinatesToEntryIndex: function(x, y)
701 var timelineData = this._timelineData();
704 var cursorTime = (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime;
705 var cursorLevel = this._isTopDown ? Math.floor(y / this._barHeight - 1) : Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight);
707 var entryOffsets = timelineData.entryOffsets;
708 var entryTotalTimes = timelineData.entryTotalTimes;
709 var entryLevels = timelineData.entryLevels;
710 var length = entryOffsets.length;
711 for (var i = 0; i < length; ++i) {
712 if (cursorTime < entryOffsets[i])
714 if (cursorTime < (entryOffsets[i] + entryTotalTimes[i])
715 && cursorLevel === entryLevels[i])
722 * @param {!number} height
723 * @param {!number} width
725 draw: function(width, height)
727 var timelineData = this._timelineData();
731 var ratio = window.devicePixelRatio;
732 this._canvas.width = width * ratio;
733 this._canvas.height = height * ratio;
734 this._canvas.style.width = width + "px";
735 this._canvas.style.height = height + "px";
737 var context = this._canvas.getContext("2d");
738 context.scale(ratio, ratio);
739 var timeWindowRight = this._timeWindowRight;
740 var timeToPixel = this._timeToPixel;
741 var pixelWindowLeft = this._pixelWindowLeft;
742 var paddingLeft = this._paddingLeft;
743 var minWidth = this._minWidth;
744 var entryTotalTimes = timelineData.entryTotalTimes;
745 var entryOffsets = timelineData.entryOffsets;
746 var entryLevels = timelineData.entryLevels;
747 var colorEntryIndexes = timelineData.colorEntryIndexes;
748 var entryTitles = timelineData.entryTitles;
749 var entryDeoptFlags = timelineData.entryDeoptFlags;
751 var colorGenerator = this._dataProvider.colorGenerator();
752 var titleIndexes = new Uint32Array(timelineData.entryTotalTimes);
753 var lastTitleIndex = 0;
754 var dotsWidth = context.measureText("\u2026").width;
755 var textPaddingLeft = 2;
756 this._minTextWidth = context.measureText("\u2026").width + textPaddingLeft;
757 var minTextWidth = this._minTextWidth;
760 for (var i = 0; i < timelineData.maxStackDepth; ++i)
761 marksField.push(new Uint16Array(width));
763 var barHeight = this._isTopDown ? -this._barHeight : this._barHeight;
768 var bHeight = this._isTopDown ? WebInspector.FlameChart.DividersBarHeight : height - this._barHeight;
769 context.strokeStyle = "black";
773 for (var colorIndex = 0; colorIndex < colorEntryIndexes.length; ++colorIndex) {
774 colorPair = colorGenerator._colorPairForIndex(colorIndex);
775 context.fillStyle = colorPair.normal;
776 var indexes = colorEntryIndexes[colorIndex];
780 for (var i = 0; i < indexes.length; ++i) {
781 entryIndex = indexes[i];
782 entryOffset = entryOffsets[entryIndex];
783 if (entryOffset > timeWindowRight)
785 barX = Math.ceil(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
788 barRight = Math.floor((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
791 barWidth = (barRight - barX) || minWidth;
792 barLevel = entryLevels[entryIndex];
793 var marksRow = marksField[barLevel];
794 if (barWidth <= marksRow[barX])
796 marksRow[barX] = barWidth;
797 if (entryIndex === this._highlightedEntryIndex) {
800 context.fillStyle = colorPair.highlighted;
802 context.rect(barX, bHeight - barLevel * barHeight, barWidth, this._barHeight);
803 if (entryIndex === this._highlightedEntryIndex) {
806 context.fillStyle = colorPair.normal;
808 if (barWidth > minTextWidth)
809 titleIndexes[lastTitleIndex++] = entryIndex;
814 var font = (this._barHeight - 4) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
815 var boldFont = "bold " + font;
816 var isBoldFontSelected = false;
818 context.textBaseline = "alphabetic";
819 context.fillStyle = "#333";
820 this._dotsWidth = context.measureText("\u2026").width;
822 var textBaseHeight = bHeight + this._barHeight - 4;
823 for (var i = 0; i < lastTitleIndex; ++i) {
824 entryIndex = titleIndexes[i];
825 if (isBoldFontSelected) {
826 if (!entryDeoptFlags[entryIndex]) {
828 isBoldFontSelected = false;
831 if (entryDeoptFlags[entryIndex]) {
832 context.font = boldFont;
833 isBoldFontSelected = true;
837 entryOffset = entryOffsets[entryIndex];
838 barX = Math.floor(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
839 barRight = Math.ceil((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
840 barWidth = (barRight - barX) || minWidth;
841 var xText = Math.max(0, barX);
842 var widthText = barWidth - textPaddingLeft + barX - xText;
843 var title = this._prepareText(context, entryTitles[entryIndex], widthText);
845 context.fillText(title, xText + textPaddingLeft, textBaseHeight - entryLevels[entryIndex] * barHeight);
848 this._entryInfo.removeChildren();
849 if (!this._isDragging) {
850 var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this._highlightedEntryIndex);
852 this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
856 _buildEntryInfo: function(entryInfo)
858 var infoTable = document.createElement("table");
859 infoTable.className = "info-table";
860 for (var i = 0; i < entryInfo.length; ++i) {
861 var row = infoTable.createChild("tr");
862 var titleCell = row.createChild("td");
863 titleCell.textContent = entryInfo[i].title;
864 titleCell.className = "title";
865 var textCell = row.createChild("td");
866 textCell.textContent = entryInfo[i].text;
871 _prepareText: function(context, title, maxSize)
873 if (maxSize < this._dotsWidth)
875 var titleWidth = context.measureText(title).width;
876 if (maxSize > titleWidth)
878 maxSize -= this._dotsWidth;
879 var dotRegExp=/[\.\$]/g;
880 var match = dotRegExp.exec(title);
882 var visiblePartSize = maxSize / titleWidth;
883 var newTextLength = Math.floor(title.length * visiblePartSize) + 1;
884 var minTextLength = 4;
885 if (newTextLength < minTextLength)
890 substring = title.substring(0, newTextLength);
891 } while (context.measureText(substring).width > maxSize);
892 return title.substring(0, newTextLength) + "\u2026";
895 var substring = title.substring(match.index + 1);
896 var width = context.measureText(substring).width;
898 return "\u2026" + substring;
899 match = dotRegExp.exec(title);
904 } while (context.measureText(title.substring(0, i)).width < maxSize);
905 return title.substring(0, i - 1) + "\u2026";
908 _updateBoundaries: function()
910 this._totalTime = this._timelineData().totalTime;
911 this._timeWindowLeft = this._windowLeft * this._totalTime;
912 this._timeWindowRight = this._windowRight * this._totalTime;
914 this._pixelWindowWidth = this.element.clientWidth - this._paddingLeft;
915 this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth);
916 this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft);
917 this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight);
919 this._timeToPixel = this._totalPixels / this._totalTime;
920 this._pixelToTime = this._totalTime / this._totalPixels;
921 this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
926 this._scheduleUpdate();
929 _scheduleUpdate: function()
931 if (this._updateTimerId)
933 this._updateTimerId = setTimeout(this.update.bind(this), 10);
938 this._updateTimerId = 0;
939 if (!this._timelineData()) {
940 this._timelineGrid.hideDividers();
943 this._updateBoundaries();
944 if (this._timelineData().entryLevels.length)
945 this._timelineGrid.showDividers();
947 this._timelineGrid.hideDividers();
948 this.draw(this.element.clientWidth, this.element.clientHeight);
949 this._calculator._updateBoundaries(this);
950 this._timelineGrid.element.style.width = this.element.clientWidth;
951 this._timelineGrid.updateDividers(this._calculator);
954 __proto__: WebInspector.View.prototype