97f697f505fc482d2340a980bbaa0057d7d23a90
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TimelineFlameChart.js
1 /*
2  * Copyright (C) 2014 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  * @implements {WebInspector.FlameChartDataProvider}
34  * @implements {WebInspector.TimelineFlameChart.SelectionProvider}
35  * @param {!WebInspector.TimelineModelImpl} model
36  * @param {!WebInspector.TimelineFrameModelBase} frameModel
37  */
38 WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
39 {
40     WebInspector.FlameChartDataProvider.call(this);
41     this._model = model;
42     this._frameModel = frameModel;
43     this._font = "12px " + WebInspector.fontFamily();
44     this._linkifier = new WebInspector.Linkifier();
45 }
46
47 WebInspector.TimelineFlameChartDataProvider.prototype = {
48     /**
49      * @return {number}
50      */
51     barHeight: function()
52     {
53         return 20;
54     },
55
56     /**
57      * @return {number}
58      */
59     textBaseline: function()
60     {
61         return 6;
62     },
63
64     /**
65      * @return {number}
66      */
67     textPadding: function()
68     {
69         return 5;
70     },
71
72     /**
73      * @param {number} entryIndex
74      * @return {string}
75      */
76     entryFont: function(entryIndex)
77     {
78         return this._font;
79     },
80
81     /**
82      * @param {number} entryIndex
83      * @return {?string}
84      */
85     entryTitle: function(entryIndex)
86     {
87         var record = this._records[entryIndex];
88         if (record === this._cpuThreadRecord)
89             return WebInspector.UIString("CPU");
90         else if (record === this._gpuThreadRecord)
91             return WebInspector.UIString("GPU");
92         var details = WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile());
93         var title = WebInspector.TimelineUIUtilsImpl.recordTitle(record);
94         return details ? WebInspector.UIString("%s (%s)", title, details.textContent) : title;
95     },
96
97     /**
98      * @param {number} startTime
99      * @param {number} endTime
100      * @return {?Array.<number>}
101      */
102     dividerOffsets: function(startTime, endTime)
103     {
104         // While we have tracing and timeline flame chart on screen at a time,
105         // we don't want to render frame-based grid.
106         return null;
107     },
108
109     reset: function()
110     {
111         this._timelineData = null;
112     },
113
114     /**
115      * @return {!WebInspector.FlameChart.TimelineData}
116      */
117     timelineData: function()
118     {
119         if (this._timelineData)
120             return this._timelineData;
121
122         this._linkifier.reset();
123
124         /**
125          * @type {?WebInspector.FlameChart.TimelineData}
126          */
127         this._timelineData = {
128             entryLevels: [],
129             entryTotalTimes: [],
130             entryStartTimes: []
131         };
132
133         this._records = [];
134         this._entryThreadDepths = {};
135         this._minimumBoundary = this._model.minimumRecordTime();
136
137         var cpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
138         this._cpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (cpuThreadRecordPayload), null);
139         this._pushRecord(this._cpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary()));
140
141         this._gpuThreadRecord = null;
142
143         var records = this._model.records();
144         for (var i = 0; i < records.length; ++i) {
145             var record = records[i];
146             var thread = record.thread();
147             if (thread === "gpu")
148                 continue;
149             if (!thread) {
150                 for (var j = 0; j < record.children().length; ++j)
151                     this._appendRecord(record.children()[j], 1);
152             } else {
153                 var visible = this._appendRecord(records[i], 1);
154                 if (visible && !this._gpuThreadRecord) {
155                     var gpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
156                     this._gpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (gpuThreadRecordPayload), null);
157                     this._pushRecord(this._gpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary()));
158                 }
159             }
160         }
161
162         var cpuStackDepth = Math.max(4, this._entryThreadDepths[undefined]);
163         delete this._entryThreadDepths[undefined];
164         this._maxStackDepth = cpuStackDepth;
165
166         if (this._gpuThreadRecord) {
167             // We have multiple threads, update levels.
168             var threadBaselines = {};
169             var threadBaseline = cpuStackDepth + 2;
170
171             for (var thread in this._entryThreadDepths) {
172                 threadBaselines[thread] = threadBaseline;
173                 threadBaseline += this._entryThreadDepths[thread];
174             }
175             this._maxStackDepth = threadBaseline;
176
177             for (var i = 0; i < this._records.length; ++i) {
178                 var record = this._records[i];
179                 var level = this._timelineData.entryLevels[i];
180                 if (record === this._cpuThreadRecord)
181                     level = 0;
182                 else if (record === this._gpuThreadRecord)
183                     level = cpuStackDepth + 2;
184                 else if (record.thread())
185                     level += threadBaselines[record.thread()];
186                 this._timelineData.entryLevels[i] = level;
187             }
188         }
189
190         return this._timelineData;
191     },
192
193     /**
194      * @return {number}
195      */
196     minimumBoundary: function()
197     {
198         return this._minimumBoundary;
199     },
200
201     /**
202      * @return {number}
203      */
204     totalTime: function()
205     {
206         return Math.max(1000, this._model.maximumRecordTime() - this._model.minimumRecordTime());
207     },
208
209     /**
210      * @return {number}
211      */
212     maxStackDepth: function()
213     {
214         return this._maxStackDepth;
215     },
216
217     /**
218      * @param {!WebInspector.TimelineModel.Record} record
219      * @param {number} level
220      * @return {boolean}
221      */
222     _appendRecord: function(record, level)
223     {
224         var result = false;
225         if (!this._model.isVisible(record)) {
226             for (var i = 0; i < record.children().length; ++i)
227                 result = this._appendRecord(record.children()[i], level) || result;
228             return result;
229         }
230
231         this._pushRecord(record, level, record.startTime(), record.endTime());
232         for (var i = 0; i < record.children().length; ++i)
233             this._appendRecord(record.children()[i], level + 1);
234         return true;
235     },
236
237     /**
238      * @param {!WebInspector.TimelineModel.Record} record
239      * @param {number} level
240      * @param {number} startTime
241      * @param {number} endTime
242      * @return {number}
243      */
244     _pushRecord: function(record, level, startTime, endTime)
245     {
246         var index = this._records.length;
247         this._records.push(record);
248         this._timelineData.entryStartTimes[index] = startTime;
249         this._timelineData.entryLevels[index] = level;
250         this._timelineData.entryTotalTimes[index] = endTime - startTime;
251         this._entryThreadDepths[record.thread()] = Math.max(level, this._entryThreadDepths[record.thread()] || 0);
252         return index;
253     },
254
255     /**
256      * @param {number} entryIndex
257      * @return {?Array.<!{title: string, text: string}>}
258      */
259     prepareHighlightedEntryInfo: function(entryIndex)
260     {
261         return null;
262     },
263
264     /**
265      * @param {number} entryIndex
266      * @return {boolean}
267      */
268     canJumpToEntry: function(entryIndex)
269     {
270         return false;
271     },
272
273     /**
274      * @param {number} entryIndex
275      * @return {string}
276      */
277     entryColor: function(entryIndex)
278     {
279         var record = this._records[entryIndex];
280         if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
281             return "#555";
282
283         if (record.type() === WebInspector.TimelineModel.RecordType.JSFrame)
284             return WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator().colorForID(record.data()["functionName"]);
285
286         return record.category().fillColorStop1;
287     },
288
289
290     /**
291      * @param {number} entryIndex
292      * @param {!CanvasRenderingContext2D} context
293      * @param {?string} text
294      * @param {number} barX
295      * @param {number} barY
296      * @param {number} barWidth
297      * @param {number} barHeight
298      * @param {function(number):number} offsetToPosition
299      * @return {boolean}
300      */
301     decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
302     {
303         if (barWidth < 5)
304             return false;
305
306         var record = this._records[entryIndex];
307         var timelineData = this._timelineData;
308
309         var category = record.category();
310         // Paint text using white color on dark background.
311         if (text) {
312             context.save();
313             context.fillStyle = "white";
314             context.shadowColor = "rgba(0, 0, 0, 0.1)";
315             context.shadowOffsetX = 1;
316             context.shadowOffsetY = 1;
317             context.font = this._font;
318             context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
319             context.restore();
320         }
321
322         if (record.children().length) {
323             var entryStartTime = timelineData.entryStartTimes[entryIndex];
324             var barSelf = offsetToPosition(entryStartTime + record.selfTime())
325
326             context.beginPath();
327             context.fillStyle = category.backgroundColor;
328             context.rect(barSelf, barY, barX + barWidth - barSelf, barHeight);
329             context.fill();
330
331             // Paint text using dark color on light background.
332             if (text) {
333                 context.save();
334                 context.clip();
335                 context.fillStyle = category.borderColor;
336                 context.shadowColor = "rgba(0, 0, 0, 0.1)";
337                 context.shadowOffsetX = 1;
338                 context.shadowOffsetY = 1;
339                 context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
340                 context.restore();
341             }
342         }
343
344         if (record.warnings()) {
345             context.save();
346
347             context.rect(barX, barY, barWidth, this.barHeight());
348             context.clip();
349
350             context.beginPath();
351             context.fillStyle = record.warnings() ? "red" : "rgba(255, 0, 0, 0.5)";
352             context.moveTo(barX + barWidth - 15, barY + 1);
353             context.lineTo(barX + barWidth - 1, barY + 1);
354             context.lineTo(barX + barWidth - 1, barY + 15);
355             context.fill();
356
357             context.restore();
358         }
359
360         return true;
361     },
362
363     /**
364      * @param {number} entryIndex
365      * @return {boolean}
366      */
367     forceDecoration: function(entryIndex)
368     {
369         var record = this._records[entryIndex];
370         return !!record.warnings();
371     },
372
373     /**
374      * @param {number} entryIndex
375      * @return {?{startTime: number, endTime: number}}
376      */
377     highlightTimeRange: function(entryIndex)
378     {
379         var record = this._records[entryIndex];
380         if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
381             return null;
382         return {
383             startTime: record.startTime(),
384             endTime: record.endTime()
385         };
386     },
387
388     /**
389      * @return {number}
390      */
391     paddingLeft: function()
392     {
393         return 0;
394     },
395
396     /**
397      * @param {number} entryIndex
398      * @return {string}
399      */
400     textColor: function(entryIndex)
401     {
402         return "white";
403     },
404
405     /**
406      * @param {number} entryIndex
407      * @return {?WebInspector.TimelineSelection}
408      */
409     createSelection: function(entryIndex)
410     {
411         var record = this._records[entryIndex];
412         if (record instanceof WebInspector.TimelineModel.RecordImpl) {
413             this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex);
414             return this._lastSelection.timelineSelection;
415         }
416         return null;
417     },
418
419     /**
420      * @param {?WebInspector.TimelineSelection} selection
421      * @return {number}
422      */
423     entryIndexForSelection: function(selection)
424     {
425         if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.Record)
426             return -1;
427         var record = /** @type{!WebInspector.TimelineModel.Record} */ (selection.object());
428         if (this._lastSelection && this._lastSelection.timelineSelection.object() === record)
429             return this._lastSelection.entryIndex;
430         var entryRecords = this._records;
431         for (var entryIndex = 0; entryIndex < entryRecords.length; ++entryIndex) {
432             if (entryRecords[entryIndex] === record) {
433                 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex);
434                 return entryIndex;
435             }
436         }
437         return -1;
438     }
439 }
440
441 /**
442  * @constructor
443  * @implements {WebInspector.FlameChartDataProvider}
444  * @implements {WebInspector.TimelineFlameChart.SelectionProvider}
445  * @param {!WebInspector.TracingTimelineModel} model
446  * @param {!WebInspector.TimelineFrameModelBase} frameModel
447  * @param {!WebInspector.Target} target
448  */
449 WebInspector.TracingBasedTimelineFlameChartDataProvider = function(model, frameModel, target)
450 {
451     WebInspector.FlameChartDataProvider.call(this);
452     this._model = model;
453     this._frameModel = frameModel;
454     this._target = target;
455     this._font = "12px " + WebInspector.fontFamily();
456     this._linkifier = new WebInspector.Linkifier();
457     this._palette = new WebInspector.TraceViewPalette();
458     this._entryIndexToTitle = {};
459 }
460
461 WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
462     /**
463      * @return {number}
464      */
465     barHeight: function()
466     {
467         return 20;
468     },
469
470     /**
471      * @return {number}
472      */
473     textBaseline: function()
474     {
475         return 6;
476     },
477
478     /**
479      * @return {number}
480      */
481     textPadding: function()
482     {
483         return 5;
484     },
485
486     /**
487      * @param {number} entryIndex
488      * @return {string}
489      */
490     entryFont: function(entryIndex)
491     {
492         return this._font;
493     },
494
495     /**
496      * @param {number} entryIndex
497      * @return {?string}
498      */
499     entryTitle: function(entryIndex)
500     {
501         var event = this._entryEvents[entryIndex];
502         if (event) {
503             var name = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name).title;
504             // TODO(yurys): support event dividers
505             var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier, false, this._target);
506             return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
507         }
508         var title = this._entryIndexToTitle[entryIndex];
509         if (!title) {
510             title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
511             console.error(title);
512         }
513         return title;
514     },
515
516     /**
517      * @param {number} startTime
518      * @param {number} endTime
519      * @return {?Array.<number>}
520      */
521     dividerOffsets: function(startTime, endTime)
522     {
523         return null;
524     },
525
526     reset: function()
527     {
528         this._timelineData = null;
529         /** @type {!Array.<!WebInspector.TracingModel.Event>} */
530         this._entryEvents = [];
531         this._entryIndexToTitle = {};
532     },
533
534     /**
535      * @return {!WebInspector.FlameChart.TimelineData}
536      */
537     timelineData: function()
538     {
539         if (this._timelineData)
540             return this._timelineData;
541
542         /**
543          * @type {?WebInspector.FlameChart.TimelineData}
544          */
545         this._timelineData = {
546             entryLevels: [],
547             entryTotalTimes: [],
548             entryStartTimes: []
549         };
550
551         this._currentLevel = 0;
552         this._minimumBoundary = this._model.minimumRecordTime();
553         this._timeSpan = Math.max(this._model.maximumRecordTime() - this._minimumBoundary, 1000);
554         this._appendHeaderRecord("CPU");
555         var events = this._model.mainThreadEvents();
556         var maxStackDepth = 0;
557         for (var eventIndex = 0; eventIndex < events.length; ++eventIndex) {
558             var event = events[eventIndex];
559             var category = event.category;
560             if (category !== "disabled-by-default-devtools.timeline" && category !== "devtools")
561                 continue;
562             if (event.duration || event.phase === WebInspector.TracingModel.Phase.Instant) {
563                 this._appendEvent(event);
564                 if (maxStackDepth < event.level)
565                     maxStackDepth = event.level;
566             }
567         }
568         this._currentLevel += maxStackDepth + 1;
569
570         this._appendHeaderRecord("GPU");
571         return this._timelineData;
572     },
573
574     /**
575      * @return {number}
576      */
577     minimumBoundary: function()
578     {
579         return this._minimumBoundary;
580     },
581
582     /**
583      * @return {number}
584      */
585     totalTime: function()
586     {
587         return this._timeSpan;
588     },
589
590     /**
591      * @return {number}
592      */
593     maxStackDepth: function()
594     {
595         return this._currentLevel;
596     },
597
598     /**
599      * @param {number} entryIndex
600      * @return {?Array.<!{title: string, text: string}>}
601      */
602     prepareHighlightedEntryInfo: function(entryIndex)
603     {
604         return null;
605     },
606
607     /**
608      * @param {number} entryIndex
609      * @return {boolean}
610      */
611     canJumpToEntry: function(entryIndex)
612     {
613         return false;
614     },
615
616     /**
617      * @param {number} entryIndex
618      * @return {string}
619      */
620     entryColor: function(entryIndex)
621     {
622         var event = this._entryEvents[entryIndex];
623         if (!event)
624             return "#555";
625         var style = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name);
626         return style.category.fillColorStop1;
627     },
628
629     /**
630      * @param {number} entryIndex
631      * @param {!CanvasRenderingContext2D} context
632      * @param {?string} text
633      * @param {number} barX
634      * @param {number} barY
635      * @param {number} barWidth
636      * @param {number} barHeight
637      * @param {function(number):number} offsetToPosition
638      * @return {boolean}
639      */
640     decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
641     {
642         if (barWidth < 5)
643             return false;
644
645         var timelineData = this._timelineData;
646
647         // Paint text using white color on dark background.
648         if (text) {
649             context.save();
650             context.fillStyle = "white";
651             context.shadowColor = "rgba(0, 0, 0, 0.1)";
652             context.shadowOffsetX = 1;
653             context.shadowOffsetY = 1;
654             context.font = this._font;
655             context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
656             context.restore();
657         }
658
659         var event = this._entryEvents[entryIndex];
660         if (event && event.warning) {
661             context.save();
662
663             context.rect(barX, barY, barWidth, this.barHeight());
664             context.clip();
665
666             context.beginPath();
667             context.fillStyle = "red";
668             context.moveTo(barX + barWidth - 15, barY + 1);
669             context.lineTo(barX + barWidth - 1, barY + 1);
670             context.lineTo(barX + barWidth - 1, barY + 15);
671             context.fill();
672
673             context.restore();
674         }
675
676         return true;
677     },
678
679     /**
680      * @param {number} entryIndex
681      * @return {boolean}
682      */
683     forceDecoration: function(entryIndex)
684     {
685         var event = this._entryEvents[entryIndex];
686         if (!event)
687             return false;
688         return !!event.warning;
689     },
690
691    /**
692      * @param {number} entryIndex
693      * @return {?{startTime: number, endTime: number}}
694      */
695     highlightTimeRange: function(entryIndex)
696     {
697         var event = this._entryEvents[entryIndex];
698         if (!event)
699             return null;
700         return {
701             startTime: event.startTime,
702             endTime: event.endTime
703         }
704     },
705
706     /**
707      * @return {number}
708      */
709     paddingLeft: function()
710     {
711         return 0;
712     },
713
714     /**
715      * @param {number} entryIndex
716      * @return {string}
717      */
718     textColor: function(entryIndex)
719     {
720         return "white";
721     },
722
723     /**
724      * @param {string} title
725      */
726     _appendHeaderRecord: function(title)
727     {
728         var index = this._entryEvents.length;
729         this._entryIndexToTitle[index] = title;
730         this._entryEvents.push(null);
731         this._timelineData.entryLevels[index] = this._currentLevel++;
732         this._timelineData.entryTotalTimes[index] = this._timeSpan;
733         this._timelineData.entryStartTimes[index] = this._minimumBoundary;
734     },
735
736     /**
737      * @param {!WebInspector.TracingModel.Event} event
738      */
739     _appendEvent: function(event)
740     {
741         var index = this._entryEvents.length;
742         this._entryEvents.push(event);
743         this._timelineData.entryLevels[index] = this._currentLevel + event.level;
744         this._timelineData.entryTotalTimes[index] = event.duration || 1;
745         this._timelineData.entryStartTimes[index] = event.startTime;
746     },
747
748     /**
749      * @param {number} entryIndex
750      * @return {?WebInspector.TimelineSelection}
751      */
752     createSelection: function(entryIndex)
753     {
754         var event = this._entryEvents[entryIndex];
755         if (!event)
756             return null;
757         this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
758         return this._lastSelection.timelineSelection;
759     },
760
761     /**
762      * @param {?WebInspector.TimelineSelection} selection
763      * @return {number}
764      */
765     entryIndexForSelection: function(selection)
766     {
767         if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.TraceEvent)
768             return -1;
769         var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
770         if (this._lastSelection && this._lastSelection.timelineSelection.object() === event)
771             return this._lastSelection.entryIndex;
772         var entryEvents = this._entryEvents;
773         for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
774             if (entryEvents[entryIndex] === event) {
775                 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
776                 return entryIndex;
777             }
778         }
779         return -1;
780     }
781 }
782
783 /**
784  * @return {!WebInspector.FlameChart.ColorGenerator}
785  */
786 WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator = function()
787 {
788     if (!WebInspector.TimelineFlameChartDataProvider._jsFrameColorGenerator) {
789         var hueSpace = { min: 30, max: 55, count: 5 };
790         var satSpace = { min: 70, max: 100, count: 6 };
791         var colorGenerator = new WebInspector.FlameChart.ColorGenerator(hueSpace, satSpace, 50);
792         colorGenerator.setColorForID("(idle)", "hsl(0, 0%, 60%)");
793         colorGenerator.setColorForID("(program)", "hsl(0, 0%, 60%)");
794         colorGenerator.setColorForID("(garbage collector)", "hsl(0, 0%, 60%)");
795         WebInspector.TimelineFlameChartDataProvider._jsFrameColorGenerator = colorGenerator;
796     }
797     return WebInspector.TimelineFlameChartDataProvider._jsFrameColorGenerator;
798 }
799
800 /**
801  * @constructor
802  * @extends {WebInspector.VBox}
803  * @implements {WebInspector.TimelineModeView}
804  * @implements {WebInspector.FlameChartDelegate}
805  * @param {!WebInspector.TimelineModeViewDelegate} delegate
806  * @param {!WebInspector.TimelineModel} model
807  * @param {?WebInspector.TracingTimelineModel} tracingModel
808  * @param {!WebInspector.TimelineFrameModelBase} frameModel
809  */
810 WebInspector.TimelineFlameChart = function(delegate, model, tracingModel, frameModel)
811 {
812     WebInspector.VBox.call(this);
813     this.element.classList.add("timeline-flamechart");
814     this.registerRequiredCSS("flameChart.css");
815     this._delegate = delegate;
816     this._model = model;
817     this._dataProvider = tracingModel
818         ? new WebInspector.TracingBasedTimelineFlameChartDataProvider(tracingModel, frameModel, model.target())
819         : new WebInspector.TimelineFlameChartDataProvider(/** @type {!WebInspector.TimelineModelImpl} */(model), frameModel);
820     this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
821     this._mainView.show(this.element);
822     this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
823     this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
824 }
825
826 WebInspector.TimelineFlameChart.prototype = {
827     dispose: function()
828     {
829         this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
830         this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this);
831     },
832
833     /**
834      * @param {number} windowStartTime
835      * @param {number} windowEndTime
836      */
837     requestWindowTimes: function(windowStartTime, windowEndTime)
838     {
839         this._delegate.requestWindowTimes(windowStartTime, windowEndTime);
840     },
841
842     /**
843      * @param {?RegExp} textFilter
844      */
845     refreshRecords: function(textFilter)
846     {
847         this._dataProvider.reset();
848         this._mainView._scheduleUpdate();
849     },
850
851     wasShown: function()
852     {
853         this._mainView._scheduleUpdate();
854     },
855
856
857     /**
858      * @return {!WebInspector.View}
859      */
860     view: function()
861     {
862         return this;
863     },
864
865     reset: function()
866     {
867         this._automaticallySizeWindow = true;
868         this._dataProvider.reset();
869         this._mainView.reset();
870         this._mainView.setWindowTimes(0, Infinity);
871     },
872
873     _onRecordingStarted: function()
874     {
875         this._automaticallySizeWindow = true;
876         this._mainView.reset();
877     },
878
879     /**
880      * @param {!WebInspector.TimelineModel.Record} record
881      */
882     addRecord: function(record)
883     {
884         this._dataProvider.reset();
885         if (this._automaticallySizeWindow) {
886             var minimumRecordTime = this._model.minimumRecordTime();
887             if (record.startTime() > (minimumRecordTime + 1000)) {
888                 this._automaticallySizeWindow = false;
889                 this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
890             }
891             this._mainView._scheduleUpdate();
892         } else {
893             if (!this._pendingUpdateTimer)
894                 this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
895         }
896     },
897
898     _updateOnAddRecord: function()
899     {
900         delete this._pendingUpdateTimer;
901         this._mainView._scheduleUpdate();
902     },
903
904     /**
905      * @param {number} startTime
906      * @param {number} endTime
907      */
908     setWindowTimes: function(startTime, endTime)
909     {
910         this._mainView.setWindowTimes(startTime, endTime);
911         this._delegate.select(null);
912     },
913
914     /**
915      * @param {number} width
916      */
917     setSidebarSize: function(width)
918     {
919     },
920
921     /**
922      * @param {?WebInspector.TimelineModel.Record} record
923      * @param {string=} regex
924      * @param {boolean=} selectRecord
925      */
926     highlightSearchResult: function(record, regex, selectRecord)
927     {
928     },
929
930     /**
931      * @param {?WebInspector.TimelineSelection} selection
932      */
933     setSelection: function(selection)
934     {
935         var index = this._dataProvider.entryIndexForSelection(selection);
936         this._mainView.setSelectedEntry(index);
937     },
938
939     /**
940      * @param {!WebInspector.Event} event
941      */
942     _onEntrySelected: function(event)
943     {
944         var entryIndex = /** @type{number} */ (event.data);
945         var timelineSelection = this._dataProvider.createSelection(entryIndex);
946         if (timelineSelection)
947             this._delegate.select(timelineSelection);
948     },
949
950     __proto__: WebInspector.VBox.prototype
951 }
952
953 /**
954   * @constructor
955   * @param {!WebInspector.TimelineSelection} selection
956   * @param {number} entryIndex
957   */
958 WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex)
959 {
960     this.timelineSelection = selection;
961     this.entryIndex = entryIndex;
962 }
963
964 /**
965   * @interface
966   */
967 WebInspector.TimelineFlameChart.SelectionProvider = function() { }
968
969 WebInspector.TimelineFlameChart.SelectionProvider.prototype = {
970     /**
971      * @param {number} entryIndex
972      * @return {?WebInspector.TimelineSelection}
973      */
974     createSelection: function(entryIndex) { },
975     /**
976      * @param {?WebInspector.TimelineSelection} selection
977      * @return {number}
978      */
979     entryIndexForSelection: function(selection) { }
980 }