Home | History | Annotate | Download | only in timeline
      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 }
    981