Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / FlameChart.js
1 /**
2  * Copyright (C) 2013 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
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
13  * distribution.
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.
17  *
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.
29  */
30
31 /**
32  * @constructor
33  * @extends {WebInspector.View}
34  * @param {!WebInspector.FlameChartDataProvider} dataProvider
35  */
36 WebInspector.FlameChart = function(dataProvider)
37 {
38     WebInspector.View.call(this);
39     this.registerRequiredCSS("flameChart.css");
40     this.element.id = "cpu-flame-chart";
41
42     this._overviewPane = new WebInspector.FlameChart.OverviewPane(dataProvider);
43     this._overviewPane.show(this.element);
44
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);
49 }
50
51 WebInspector.FlameChart.DividersBarHeight = 20;
52
53 WebInspector.FlameChart.prototype = {
54     /**
55      * @param {!WebInspector.Event} event
56      */
57     _onWindowChanged: function(event)
58     {
59         this._mainPane.changeWindow(this._overviewPane._overviewGrid.windowLeft(), this._overviewPane._overviewGrid.windowRight());
60     },
61
62     /**
63      * @param {!number} timeLeft
64      * @param {!number} timeRight
65      */
66     selectRange: function(timeLeft, timeRight)
67     {
68         this._overviewPane._selectRange(timeLeft, timeRight);
69     },
70
71     /**
72      * @param {!WebInspector.Event} event
73      */
74     _onEntrySelected: function(event)
75     {
76         this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data);
77     },
78
79     update: function()
80     {
81         this._overviewPane.update();
82         this._mainPane.update();
83     },
84
85     __proto__: WebInspector.View.prototype
86 };
87
88 /**
89  * @interface
90  */
91 WebInspector.FlameChartDataProvider = function()
92 {
93 }
94
95 /** @typedef {!{
96         maxStackDepth: number,
97         totalTime: 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>
105     }}
106  */
107 WebInspector.FlameChart.TimelineData;
108
109 WebInspector.FlameChartDataProvider.prototype = {
110     /**
111      * @return {?WebInspector.FlameChart.TimelineData}
112      */
113     timelineData: function() { },
114
115     /**
116      * @return {!WebInspector.FlameChart.ColorGenerator}
117      */
118     colorGenerator: function() { },
119
120     /**
121      * @param {number} entryIndex
122      * @return {?Array.<!{title: string, text: string}>}
123      */
124     prepareHighlightedEntryInfo: function(entryIndex) { },
125
126     /**
127      * @param {number} entryIndex
128      * @return {boolean}
129      */
130     canJumpToEntry: function(entryIndex) { },
131
132     /**
133      * @param {number} entryIndex
134      * @return {?Object}
135      */
136     entryData: function(entryIndex) { }
137 }
138
139 /**
140  * @constructor
141  * @implements {WebInspector.TimelineGrid.Calculator}
142  */
143 WebInspector.FlameChart.Calculator = function()
144 {
145 }
146
147 WebInspector.FlameChart.Calculator.prototype = {
148     /**
149      * @param {!WebInspector.FlameChart.MainPane} mainPane
150      */
151     _updateBoundaries: function(mainPane)
152     {
153         function log10(x)
154         {
155             return Math.log(x) / Math.LN10;
156         }
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();
164     },
165
166     /**
167      * @param {number} time
168      * @return {number}
169      */
170     computePosition: function(time)
171     {
172         return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft;
173     },
174
175     /**
176      * @param {number} value
177      * @param {boolean=} hires
178      * @return {string}
179      */
180     formatTime: function(value, hires)
181     {
182         var format = "%." + this._decimalDigits + "f\u2009ms";
183         return WebInspector.UIString(format, value + this._minimumBoundaries);
184     },
185
186     /**
187      * @return {number}
188      */
189     maximumBoundary: function()
190     {
191         return this._maximumBoundaries;
192     },
193
194     /**
195      * @return {number}
196      */
197     minimumBoundary: function()
198     {
199         return this._minimumBoundaries;
200     },
201
202     /**
203      * @return {number}
204      */
205     zeroTime: function()
206     {
207         return 0;
208     },
209
210     /**
211      * @return {number}
212      */
213     boundarySpan: function()
214     {
215         return this._maximumBoundaries - this._minimumBoundaries;
216     }
217 }
218
219 /**
220  * @constructor
221  * @implements {WebInspector.TimelineGrid.Calculator}
222  */
223 WebInspector.FlameChart.OverviewCalculator = function()
224 {
225 }
226
227 WebInspector.FlameChart.OverviewCalculator.prototype = {
228     /**
229      * @param {!WebInspector.FlameChart.OverviewPane} overviewPane
230      */
231     _updateBoundaries: function(overviewPane)
232     {
233         this._minimumBoundaries = 0;
234         var totalTime = overviewPane._timelineData().totalTime;
235         this._maximumBoundaries = totalTime;
236         this._xScaleFactor = overviewPane._overviewCanvas.width / totalTime;
237     },
238
239     /**
240      * @param {number} time
241      * @return {number}
242      */
243     computePosition: function(time)
244     {
245         return (time - this._minimumBoundaries) * this._xScaleFactor;
246     },
247
248     /**
249      * @param {number} value
250      * @param {boolean=} hires
251      * @return {string}
252      */
253     formatTime: function(value, hires)
254     {
255         return Number.secondsToString((value + this._minimumBoundaries) / 1000, hires);
256     },
257
258     /**
259      * @return {number}
260      */
261     maximumBoundary: function()
262     {
263         return this._maximumBoundaries;
264     },
265
266     /**
267      * @return {number}
268      */
269     minimumBoundary: function()
270     {
271         return this._minimumBoundaries;
272     },
273
274     /**
275      * @return {number}
276      */
277     zeroTime: function()
278     {
279         return this._minimumBoundaries;
280     },
281
282     /**
283      * @return {number}
284      */
285     boundarySpan: function()
286     {
287         return this._maximumBoundaries - this._minimumBoundaries;
288     }
289 }
290
291 WebInspector.FlameChart.Events = {
292     EntrySelected: "EntrySelected"
293 }
294
295 /**
296  * @constructor
297  */
298 WebInspector.FlameChart.ColorGenerator = function()
299 {
300     this._colorPairs = {};
301     this._colorIndexes = [];
302     this._currentColorIndex = 0;
303 }
304
305 WebInspector.FlameChart.ColorGenerator.prototype = {
306     /**
307      * @param {string} id
308      * @param {string|!CanvasGradient} highlighted
309      * @param {string|!CanvasGradient} normal
310      */
311     setColorPairForID: function(id, highlighted, normal)
312     {
313         var colorPair = {index: this._currentColorIndex++, highlighted: highlighted, normal: normal};
314         this._colorPairs[id] = colorPair;
315         this._colorIndexes[colorPair.index] = colorPair;
316     },
317
318     /**
319      * @param {!string} id
320      * @param {number=} sat
321      * @return {!Object}
322      */
323     colorPairForID: function(id, sat)
324     {
325         if (typeof sat !== "number")
326             sat = 100;
327         var colorPairs = this._colorPairs;
328         var colorPair = colorPairs[id];
329         if (!colorPair) {
330             colorPairs[id] = colorPair = this._createPair(this._currentColorIndex++, sat);
331             this._colorIndexes[colorPair.index] = colorPair;
332         }
333         return colorPair;
334     },
335
336     /**
337      * @param {!number} index
338      */
339     _colorPairForIndex: function(index)
340     {
341         return this._colorIndexes[index];
342     },
343
344     /**
345      * @param {!number} index
346      * @param {!number} sat
347      */
348     _createPair: function(index, sat)
349     {
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)"};
352     }
353 }
354
355 /**
356  * @interface
357  */
358 WebInspector.FlameChart.OverviewPaneInterface = function()
359 {
360 }
361
362 WebInspector.FlameChart.OverviewPaneInterface.prototype = {
363     /**
364      * @param {number} zoom
365      * @param {number} referencePoint
366      */
367     zoom: function(zoom, referencePoint) { },
368
369     /**
370      * @param {number} windowLeft
371      * @param {number} windowRight
372      */
373     setWindow: function(windowLeft, windowRight) { },
374 }
375
376 /**
377  * @constructor
378  * @extends {WebInspector.View}
379  * @implements {WebInspector.FlameChart.OverviewPaneInterface}
380  * @param {!WebInspector.FlameChartDataProvider} dataProvider
381  */
382 WebInspector.FlameChart.OverviewPane = function(dataProvider)
383 {
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;
393 }
394
395 WebInspector.FlameChart.OverviewPane.prototype = {
396     /**
397      * @param {number} zoom
398      * @param {number} referencePoint
399      */
400     zoom: function(zoom, referencePoint)
401     {
402         this._overviewGrid.zoom(zoom, referencePoint);
403     },
404
405     /**
406      * @param {number} windowLeft
407      * @param {number} windowRight
408      */
409     setWindow: function(windowLeft, windowRight)
410     {
411         this._overviewGrid.setWindow(windowLeft, windowRight);
412     },
413
414     /**
415      * @param {!number} timeLeft
416      * @param {!number} timeRight
417      */
418     _selectRange: function(timeLeft, timeRight)
419     {
420         var timelineData = this._timelineData();
421         if (!timelineData)
422             return;
423         this._overviewGrid.setWindow(timeLeft / timelineData.totalTime, timeRight / timelineData.totalTime);
424     },
425
426     /**
427      * @return {?WebInspector.FlameChart.TimelineData}
428      */
429     _timelineData: function()
430     {
431         return this._dataProvider.timelineData();
432     },
433
434     onResize: function()
435     {
436         this._scheduleUpdate();
437     },
438
439     _scheduleUpdate: function()
440     {
441         if (this._updateTimerId)
442             return;
443         this._updateTimerId = setTimeout(this.update.bind(this), 10);
444     },
445
446     update: function()
447     {
448         this._updateTimerId = 0;
449         var timelineData = this._timelineData();
450         if (!timelineData)
451             return;
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(
456             timelineData,
457             this._overviewCanvas.getContext("2d"),
458             this._overviewContainer.clientWidth,
459             this._overviewContainer.clientHeight - WebInspector.FlameChart.DividersBarHeight
460         );
461     },
462
463     /**
464      * @param {!number} width
465      * @param {!number} height
466      */
467     _resetCanvas: function(width, height)
468     {
469         var ratio = window.devicePixelRatio;
470         this._overviewCanvas.width = width * ratio;
471         this._overviewCanvas.height = height * ratio;
472     },
473
474     __proto__: WebInspector.View.prototype
475 }
476
477 /**
478  * @param {!WebInspector.FlameChart.TimelineData} timelineData
479  * @param {!number} width
480  */
481 WebInspector.FlameChart.OverviewPane.calculateDrawData = function(timelineData, width)
482 {
483     var entryOffsets = timelineData.entryOffsets;
484     var entryTotalTimes = timelineData.entryTotalTimes;
485     var entryLevels = timelineData.entryLevels;
486     var length = entryOffsets.length;
487
488     var drawData = new Uint8Array(width);
489     var scaleFactor = width / timelineData.totalTime;
490
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);
496     }
497     return drawData;
498 }
499
500 /**
501  * @param {!WebInspector.FlameChart.TimelineData} timelineData
502  * @param {!Object} context
503  * @param {!number} width
504  * @param {!number} height
505  */
506 WebInspector.FlameChart.OverviewPane.drawOverviewCanvas = function(timelineData, context, width, height)
507 {
508     var drawData = WebInspector.FlameChart.OverviewPane.calculateDrawData(timelineData, width);
509     if (!drawData)
510         return;
511
512     var ratio = window.devicePixelRatio;
513     var canvasWidth = width * ratio;
514     var canvasHeight = height * ratio;
515
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);
522     if (drawData)
523       context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1));
524     var value;
525     for (var x = 0; x < width; ++x) {
526         value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1);
527         context.lineTo(x * ratio, value);
528     }
529     context.lineTo(canvasWidth + 1, value);
530     context.lineTo(canvasWidth + 1, canvasHeight - 1);
531     context.fill();
532     context.stroke();
533     context.closePath();
534 }
535
536 /**
537  * @constructor
538  * @extends {WebInspector.View}
539  * @param {!WebInspector.FlameChartDataProvider} dataProvider
540  * @param {?WebInspector.FlameChart.OverviewPaneInterface} overviewPane
541  * @param {boolean} isTopDown
542  */
543 WebInspector.FlameChart.MainPane = function(dataProvider, overviewPane, isTopDown)
544 {
545     WebInspector.View.call(this);
546     this.element.classList.add("flame-chart-main-pane");
547     this._overviewPane = overviewPane;
548     this._isTopDown = isTopDown;
549
550     this._timelineGrid = new WebInspector.TimelineGrid();
551     this.element.appendChild(this._timelineGrid.element);
552     this._calculator = new WebInspector.FlameChart.Calculator();
553
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);
559
560     this._entryInfo = this.element.createChild("div", "profile-entry-info");
561
562     this._dataProvider = dataProvider;
563
564     this._windowLeft = 0.0;
565     this._windowRight = 1.0;
566     this._windowWidth = 1.0;
567     this._barHeight = 15;
568     this._minWidth = 1;
569     this._paddingLeft = 15;
570     this._highlightedEntryIndex = -1;
571 }
572
573 WebInspector.FlameChart.MainPane.prototype = {
574     /**
575      * @return {?WebInspector.FlameChart.TimelineData}
576      */
577     _timelineData: function()
578     {
579         return this._dataProvider.timelineData();
580     },
581
582     /**
583      * @param {!number} windowLeft
584      * @param {!number} windowRight
585      */
586     changeWindow: function(windowLeft, windowRight)
587     {
588         this._windowLeft = windowLeft;
589         this._windowRight = windowRight;
590         this._windowWidth = this._windowRight - this._windowLeft;
591
592         this._scheduleUpdate();
593     },
594
595     /**
596      * @param {!MouseEvent} event
597      */
598     _startCanvasDragging: function(event)
599     {
600         if (!this._timelineData())
601             return false;
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 = "";
608
609         return true;
610     },
611
612     /**
613      * @param {!MouseEvent} event
614      */
615     _canvasDragging: function(event)
616     {
617         var pixelShift = this._dragStartPoint - event.pageX;
618         var windowShift = pixelShift / this._totalPixels;
619
620         var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift);
621         if (windowLeft === this._windowLeft)
622             return;
623         windowShift = windowLeft - this._dragStartWindowLeft;
624
625         var windowRight = Math.min(1, this._dragStartWindowRight + windowShift);
626         if (windowRight === this._windowRight)
627             return;
628         windowShift = windowRight - this._dragStartWindowRight;
629         if (this._overviewPane)
630             this._overviewPane.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift);
631         this._wasDragged = true;
632     },
633
634     _endCanvasDragging: function()
635     {
636         this._isDragging = false;
637     },
638
639     /**
640      * @param {?MouseEvent} event
641      */
642     _onMouseMove: function(event)
643     {
644         if (this._isDragging)
645             return;
646
647         var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY);
648
649         if (this._highlightedEntryIndex === entryIndex)
650             return;
651
652         if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex))
653             this._canvas.style.cursor = "default";
654         else
655             this._canvas.style.cursor = "pointer";
656
657         this._highlightedEntryIndex = entryIndex;
658         this._scheduleUpdate();
659     },
660
661     _onClick: function()
662     {
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)
667             return;
668         if (this._highlightedEntryIndex === -1)
669             return;
670         var data = this._dataProvider.entryData(this._highlightedEntryIndex);
671         if (data)
672             this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, data);
673     },
674
675     /**
676      * @param {?MouseEvent} e
677      */
678     _onMouseWheel: function(e)
679     {
680         if (!this._overviewPane)
681             return;
682         if (e.wheelDeltaY) {
683             const zoomFactor = 1.1;
684             const mouseWheelZoomSpeed = 1 / 120;
685
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);
689         } else {
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);
692         }
693     },
694
695     /**
696      * @param {!number} x
697      * @param {!number} y
698      */
699     _coordinatesToEntryIndex: function(x, y)
700     {
701         var timelineData = this._timelineData();
702         if (!timelineData)
703             return -1;
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);
706
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])
713                 return -1;
714             if (cursorTime < (entryOffsets[i] + entryTotalTimes[i])
715                 && cursorLevel === entryLevels[i])
716                 return i;
717         }
718         return -1;
719     },
720
721     /**
722      * @param {!number} height
723      * @param {!number} width
724      */
725     draw: function(width, height)
726     {
727         var timelineData = this._timelineData();
728         if (!timelineData)
729             return;
730
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";
736
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;
750
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;
758
759         var marksField = [];
760         for (var i = 0; i < timelineData.maxStackDepth; ++i)
761             marksField.push(new Uint16Array(width));
762
763         var barHeight = this._isTopDown ? -this._barHeight : this._barHeight;
764         var barX = 0;
765         var barWidth = 0;
766         var barRight = 0;
767         var barLevel = 0;
768         var bHeight = this._isTopDown ? WebInspector.FlameChart.DividersBarHeight : height - this._barHeight;
769         context.strokeStyle = "black";
770         var colorPair;
771         var entryIndex = 0;
772         var entryOffset = 0;
773         for (var colorIndex = 0; colorIndex < colorEntryIndexes.length; ++colorIndex) {
774             colorPair = colorGenerator._colorPairForIndex(colorIndex);
775             context.fillStyle = colorPair.normal;
776             var indexes = colorEntryIndexes[colorIndex];
777             if (!indexes)
778                 continue;
779             context.beginPath();
780             for (var i = 0; i < indexes.length; ++i) {
781                 entryIndex = indexes[i];
782                 entryOffset = entryOffsets[entryIndex];
783                 if (entryOffset > timeWindowRight)
784                     break;
785                 barX = Math.ceil(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
786                 if (barX >= width)
787                     continue;
788                 barRight = Math.floor((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
789                 if (barRight < 0)
790                     continue;
791                 barWidth = (barRight - barX) || minWidth;
792                 barLevel = entryLevels[entryIndex];
793                 var marksRow = marksField[barLevel];
794                 if (barWidth <= marksRow[barX])
795                     continue;
796                 marksRow[barX] = barWidth;
797                 if (entryIndex === this._highlightedEntryIndex) {
798                     context.fill();
799                     context.beginPath();
800                     context.fillStyle = colorPair.highlighted;
801                 }
802                 context.rect(barX, bHeight - barLevel * barHeight, barWidth, this._barHeight);
803                 if (entryIndex === this._highlightedEntryIndex) {
804                     context.fill();
805                     context.beginPath();
806                     context.fillStyle = colorPair.normal;
807                 }
808                 if (barWidth > minTextWidth)
809                     titleIndexes[lastTitleIndex++] = entryIndex;
810             }
811             context.fill();
812         }
813
814         var font = (this._barHeight - 4) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
815         var boldFont = "bold " + font;
816         var isBoldFontSelected = false;
817         context.font = font;
818         context.textBaseline = "alphabetic";
819         context.fillStyle = "#333";
820         this._dotsWidth = context.measureText("\u2026").width;
821
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]) {
827                     context.font = font;
828                     isBoldFontSelected = false;
829                 }
830             } else {
831                 if (entryDeoptFlags[entryIndex]) {
832                     context.font = boldFont;
833                     isBoldFontSelected = true;
834                 }
835             }
836
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);
844             if (title)
845                 context.fillText(title, xText + textPaddingLeft, textBaseHeight - entryLevels[entryIndex] * barHeight);
846         }
847
848         this._entryInfo.removeChildren();
849         if (!this._isDragging) {
850             var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this._highlightedEntryIndex);
851             if (entryInfo)
852                 this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
853         }
854     },
855
856     _buildEntryInfo: function(entryInfo)
857     {
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;
867         }
868         return infoTable;
869     },
870
871     _prepareText: function(context, title, maxSize)
872     {
873         if (maxSize < this._dotsWidth)
874             return null;
875         var titleWidth = context.measureText(title).width;
876         if (maxSize > titleWidth)
877             return title;
878         maxSize -= this._dotsWidth;
879         var dotRegExp=/[\.\$]/g;
880         var match = dotRegExp.exec(title);
881         if (!match) {
882             var visiblePartSize = maxSize / titleWidth;
883             var newTextLength = Math.floor(title.length * visiblePartSize) + 1;
884             var minTextLength = 4;
885             if (newTextLength < minTextLength)
886                 return null;
887             var substring;
888             do {
889                 --newTextLength;
890                 substring = title.substring(0, newTextLength);
891             } while (context.measureText(substring).width > maxSize);
892             return title.substring(0, newTextLength) + "\u2026";
893         }
894         while (match) {
895             var substring = title.substring(match.index + 1);
896             var width = context.measureText(substring).width;
897             if (maxSize > width)
898                 return "\u2026" + substring;
899             match = dotRegExp.exec(title);
900         }
901         var i = 0;
902         do {
903             ++i;
904         } while (context.measureText(title.substring(0, i)).width < maxSize);
905         return title.substring(0, i - 1) + "\u2026";
906     },
907
908     _updateBoundaries: function()
909     {
910         this._totalTime = this._timelineData().totalTime;
911         this._timeWindowLeft = this._windowLeft * this._totalTime;
912         this._timeWindowRight = this._windowRight * this._totalTime;
913
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);
918
919         this._timeToPixel = this._totalPixels / this._totalTime;
920         this._pixelToTime = this._totalTime / this._totalPixels;
921         this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
922     },
923
924     onResize: function()
925     {
926         this._scheduleUpdate();
927     },
928
929     _scheduleUpdate: function()
930     {
931         if (this._updateTimerId)
932             return;
933         this._updateTimerId = setTimeout(this.update.bind(this), 10);
934     },
935
936     update: function()
937     {
938         this._updateTimerId = 0;
939         if (!this._timelineData()) {
940             this._timelineGrid.hideDividers();
941             return;
942         }
943         this._updateBoundaries();
944         if (this._timelineData().entryLevels.length)
945             this._timelineGrid.showDividers();
946         else
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);
952     },
953
954     __proto__: WebInspector.View.prototype
955 }