• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
38WebInspector.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
47WebInspector.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 */
449WebInspector.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
461WebInspector.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 */
786WebInspector.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 */
810WebInspector.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
826WebInspector.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  */
958WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex)
959{
960    this.timelineSelection = selection;
961    this.entryIndex = entryIndex;
962}
963
964/**
965  * @interface
966  */
967WebInspector.TimelineFlameChart.SelectionProvider = function() { }
968
969WebInspector.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}
981