Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / timeline / TimelineFlameChart.js
index 97f697f..90d5dbd 100644 (file)
 /**
  * @constructor
  * @implements {WebInspector.FlameChartDataProvider}
- * @implements {WebInspector.TimelineFlameChart.SelectionProvider}
- * @param {!WebInspector.TimelineModelImpl} model
+ * @param {!WebInspector.TracingTimelineModel} model
  * @param {!WebInspector.TimelineFrameModelBase} frameModel
  */
 WebInspector.TimelineFlameChartDataProvider = function(model, frameModel)
 {
     WebInspector.FlameChartDataProvider.call(this);
+    this.reset();
     this._model = model;
     this._frameModel = frameModel;
     this._font = "12px " + WebInspector.fontFamily();
     this._linkifier = new WebInspector.Linkifier();
+    this._filters = [];
+    this.addFilter(WebInspector.TracingTimelineUIUtils.hiddenEventsFilter());
+    this.addFilter(new WebInspector.TracingTimelineModel.ExclusiveEventNameFilter([WebInspector.TracingTimelineModel.RecordType.Program]));
 }
 
+WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.01;
+WebInspector.TimelineFlameChartDataProvider.JSFrameCoalsceThresholdMs = 1.1;
+
 WebInspector.TimelineFlameChartDataProvider.prototype = {
     /**
      * @return {number}
@@ -84,14 +90,21 @@ WebInspector.TimelineFlameChartDataProvider.prototype = {
      */
     entryTitle: function(entryIndex)
     {
-        var record = this._records[entryIndex];
-        if (record === this._cpuThreadRecord)
-            return WebInspector.UIString("CPU");
-        else if (record === this._gpuThreadRecord)
-            return WebInspector.UIString("GPU");
-        var details = WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile());
-        var title = WebInspector.TimelineUIUtilsImpl.recordTitle(record);
-        return details ? WebInspector.UIString("%s (%s)", title, details.textContent) : title;
+        var event = this._entryEvents[entryIndex];
+        if (event) {
+            var name = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name).title;
+            // TODO(yurys): support event dividers
+            var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier);
+            if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame && details)
+                return details.textContent;
+            return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
+        }
+        var title = this._entryIndexToTitle[entryIndex];
+        if (!title) {
+            title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
+            console.error(title);
+        }
+        return title;
     },
 
     /**
@@ -101,474 +114,202 @@ WebInspector.TimelineFlameChartDataProvider.prototype = {
      */
     dividerOffsets: function(startTime, endTime)
     {
-        // While we have tracing and timeline flame chart on screen at a time,
-        // we don't want to render frame-based grid.
         return null;
     },
 
-    reset: function()
-    {
-        this._timelineData = null;
-    },
-
-    /**
-     * @return {!WebInspector.FlameChart.TimelineData}
-     */
-    timelineData: function()
-    {
-        if (this._timelineData)
-            return this._timelineData;
-
-        this._linkifier.reset();
-
-        /**
-         * @type {?WebInspector.FlameChart.TimelineData}
-         */
-        this._timelineData = {
-            entryLevels: [],
-            entryTotalTimes: [],
-            entryStartTimes: []
-        };
-
-        this._records = [];
-        this._entryThreadDepths = {};
-        this._minimumBoundary = this._model.minimumRecordTime();
-
-        var cpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
-        this._cpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (cpuThreadRecordPayload), null);
-        this._pushRecord(this._cpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary()));
-
-        this._gpuThreadRecord = null;
-
-        var records = this._model.records();
-        for (var i = 0; i < records.length; ++i) {
-            var record = records[i];
-            var thread = record.thread();
-            if (thread === "gpu")
-                continue;
-            if (!thread) {
-                for (var j = 0; j < record.children().length; ++j)
-                    this._appendRecord(record.children()[j], 1);
-            } else {
-                var visible = this._appendRecord(records[i], 1);
-                if (visible && !this._gpuThreadRecord) {
-                    var gpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program };
-                    this._gpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (gpuThreadRecordPayload), null);
-                    this._pushRecord(this._gpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary()));
-                }
-            }
-        }
-
-        var cpuStackDepth = Math.max(4, this._entryThreadDepths[undefined]);
-        delete this._entryThreadDepths[undefined];
-        this._maxStackDepth = cpuStackDepth;
-
-        if (this._gpuThreadRecord) {
-            // We have multiple threads, update levels.
-            var threadBaselines = {};
-            var threadBaseline = cpuStackDepth + 2;
-
-            for (var thread in this._entryThreadDepths) {
-                threadBaselines[thread] = threadBaseline;
-                threadBaseline += this._entryThreadDepths[thread];
-            }
-            this._maxStackDepth = threadBaseline;
-
-            for (var i = 0; i < this._records.length; ++i) {
-                var record = this._records[i];
-                var level = this._timelineData.entryLevels[i];
-                if (record === this._cpuThreadRecord)
-                    level = 0;
-                else if (record === this._gpuThreadRecord)
-                    level = cpuStackDepth + 2;
-                else if (record.thread())
-                    level += threadBaselines[record.thread()];
-                this._timelineData.entryLevels[i] = level;
-            }
-        }
-
-        return this._timelineData;
-    },
-
-    /**
-     * @return {number}
-     */
-    minimumBoundary: function()
-    {
-        return this._minimumBoundary;
-    },
-
-    /**
-     * @return {number}
-     */
-    totalTime: function()
-    {
-        return Math.max(1000, this._model.maximumRecordTime() - this._model.minimumRecordTime());
-    },
-
-    /**
-     * @return {number}
-     */
-    maxStackDepth: function()
-    {
-        return this._maxStackDepth;
-    },
-
     /**
-     * @param {!WebInspector.TimelineModel.Record} record
-     * @param {number} level
-     * @return {boolean}
-     */
-    _appendRecord: function(record, level)
-    {
-        var result = false;
-        if (!this._model.isVisible(record)) {
-            for (var i = 0; i < record.children().length; ++i)
-                result = this._appendRecord(record.children()[i], level) || result;
-            return result;
-        }
-
-        this._pushRecord(record, level, record.startTime(), record.endTime());
-        for (var i = 0; i < record.children().length; ++i)
-            this._appendRecord(record.children()[i], level + 1);
-        return true;
-    },
-
-    /**
-     * @param {!WebInspector.TimelineModel.Record} record
-     * @param {number} level
-     * @param {number} startTime
-     * @param {number} endTime
-     * @return {number}
+     * @override
+     * @param {number} index
+     * @return {string}
      */
-    _pushRecord: function(record, level, startTime, endTime)
+    markerColor: function(index)
     {
-        var index = this._records.length;
-        this._records.push(record);
-        this._timelineData.entryStartTimes[index] = startTime;
-        this._timelineData.entryLevels[index] = level;
-        this._timelineData.entryTotalTimes[index] = endTime - startTime;
-        this._entryThreadDepths[record.thread()] = Math.max(level, this._entryThreadDepths[record.thread()] || 0);
-        return index;
+        var event = this._markerEvents[index];
+        return WebInspector.TracingTimelineUIUtils.markerEventColor(event.name);
     },
 
     /**
-     * @param {number} entryIndex
-     * @return {?Array.<!{title: string, text: string}>}
+     * @override
+     * @param {number} index
+     * @return {string}
      */
-    prepareHighlightedEntryInfo: function(entryIndex)
+    markerTitle: function(index)
     {
-        return null;
+        var event = this._markerEvents[index];
+        return WebInspector.TracingTimelineUIUtils.eventTitle(event, this._model);
     },
 
-    /**
-     * @param {number} entryIndex
-     * @return {boolean}
-     */
-    canJumpToEntry: function(entryIndex)
+    reset: function()
     {
-        return false;
+        this._timelineData = null;
+        /** @type {!Array.<!WebInspector.TracingModel.Event>} */
+        this._entryEvents = [];
+        this._entryIndexToTitle = {};
+        this._markerEvents = [];
+        this._entryIndexToFrame = {};
     },
 
     /**
-     * @param {number} entryIndex
-     * @return {string}
+     * @return {!WebInspector.FlameChart.TimelineData}
      */
-    entryColor: function(entryIndex)
+    timelineData: function()
     {
-        var record = this._records[entryIndex];
-        if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
-            return "#555";
+        if (this._timelineData)
+            return this._timelineData;
 
-        if (record.type() === WebInspector.TimelineModel.RecordType.JSFrame)
-            return WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator().colorForID(record.data()["functionName"]);
+        this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []);
 
-        return record.category().fillColorStop1;
+        this._minimumBoundary = this._model.minimumRecordTime();
+        this._timeSpan = this._model.isEmpty() ?  1000 : this._model.maximumRecordTime() - this._minimumBoundary;
+        this._currentLevel = 0;
+        this._appendFrameBars(this._frameModel.frames());
+        this._appendThreadTimelineData(WebInspector.UIString("Main Thread"), this._model.mainThreadEvents());
+        var threads = this._model.virtualThreads();
+        for (var i = 0; i < threads.length; i++) {
+            var thread = threads[i];
+            this._appendThreadTimelineData(thread.name, thread.events);
+        }
+        return this._timelineData;
     },
 
-
     /**
-     * @param {number} entryIndex
-     * @param {!CanvasRenderingContext2D} context
-     * @param {?string} text
-     * @param {number} barX
-     * @param {number} barY
-     * @param {number} barWidth
-     * @param {number} barHeight
-     * @param {function(number):number} offsetToPosition
-     * @return {boolean}
+     * @param {string} headerName
+     * @param {!Array.<!WebInspector.TracingModel.Event>} traceEvents
      */
-    decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
+    _appendThreadTimelineData: function(headerName, traceEvents)
     {
-        if (barWidth < 5)
-            return false;
-
-        var record = this._records[entryIndex];
-        var timelineData = this._timelineData;
-
-        var category = record.category();
-        // Paint text using white color on dark background.
-        if (text) {
-            context.save();
-            context.fillStyle = "white";
-            context.shadowColor = "rgba(0, 0, 0, 0.1)";
-            context.shadowOffsetX = 1;
-            context.shadowOffsetY = 1;
-            context.font = this._font;
-            context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
-            context.restore();
+        var maxStackDepth = 0;
+        var openEvents = [];
+        var headerAppended = false;
+        var events = traceEvents;
+        if (WebInspector.experimentsSettings.timelineJSCPUProfile.isEnabled()) {
+            var jsFrameEvents = this._generateJSFrameEvents(traceEvents);
+            events = jsFrameEvents.mergeOrdered(traceEvents, WebInspector.TracingModel.Event.orderedCompareStartTime);
         }
-
-        if (record.children().length) {
-            var entryStartTime = timelineData.entryStartTimes[entryIndex];
-            var barSelf = offsetToPosition(entryStartTime + record.selfTime())
-
-            context.beginPath();
-            context.fillStyle = category.backgroundColor;
-            context.rect(barSelf, barY, barX + barWidth - barSelf, barHeight);
-            context.fill();
-
-            // Paint text using dark color on light background.
-            if (text) {
-                context.save();
-                context.clip();
-                context.fillStyle = category.borderColor;
-                context.shadowColor = "rgba(0, 0, 0, 0.1)";
-                context.shadowOffsetX = 1;
-                context.shadowOffsetY = 1;
-                context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline());
-                context.restore();
+        for (var i = 0; i < events.length; ++i) {
+            var e = events[i];
+            // FIXME: clean up once phase name is unified between Blink and Chromium.
+            if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant && e.phase !== "I")
+                continue;
+            if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(e)) {
+                this._markerEvents.push(e);
+                this._timelineData.markerTimestamps.push(e.startTime);
             }
+            if (!this._isVisible(e))
+                continue;
+            while (openEvents.length && openEvents.peekLast().endTime <= e.startTime)
+                openEvents.pop();
+            if (!headerAppended) {
+                this._appendHeaderRecord(headerName, this._currentLevel++);
+                headerAppended = true;
+            }
+            this._appendEvent(e, this._currentLevel + openEvents.length);
+            maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1);
+            if (e.endTime)
+                openEvents.push(e);
         }
-
-        if (record.warnings()) {
-            context.save();
-
-            context.rect(barX, barY, barWidth, this.barHeight());
-            context.clip();
-
-            context.beginPath();
-            context.fillStyle = record.warnings() ? "red" : "rgba(255, 0, 0, 0.5)";
-            context.moveTo(barX + barWidth - 15, barY + 1);
-            context.lineTo(barX + barWidth - 1, barY + 1);
-            context.lineTo(barX + barWidth - 1, barY + 15);
-            context.fill();
-
-            context.restore();
-        }
-
-        return true;
+        this._currentLevel += maxStackDepth;
+        if (headerAppended)
+            ++this._currentLevel;
     },
 
     /**
-     * @param {number} entryIndex
-     * @return {boolean}
+     * @param {!Array.<!WebInspector.TimelineFrame>} frames
      */
-    forceDecoration: function(entryIndex)
+    _appendFrameBars: function(frames)
     {
-        var record = this._records[entryIndex];
-        return !!record.warnings();
+        this._frameBarsLevel = this._currentLevel++;
+        for (var i = 0; i < frames.length; ++i)
+            this._appendFrame(frames[i]);
     },
 
     /**
-     * @param {number} entryIndex
-     * @return {?{startTime: number, endTime: number}}
+     * @param {!Array.<!WebInspector.TracingModel.Event>} events
+     * @return {!Array.<!WebInspector.TracingModel.Event>}
      */
-    highlightTimeRange: function(entryIndex)
+    _generateJSFrameEvents: function(events)
     {
-        var record = this._records[entryIndex];
-        if (record === this._cpuThreadRecord || record === this._gpuThreadRecord)
-            return null;
-        return {
-            startTime: record.startTime(),
-            endTime: record.endTime()
-        };
-    },
-
-    /**
-     * @return {number}
-     */
-    paddingLeft: function()
-    {
-        return 0;
-    },
-
-    /**
-     * @param {number} entryIndex
-     * @return {string}
-     */
-    textColor: function(entryIndex)
-    {
-        return "white";
-    },
-
-    /**
-     * @param {number} entryIndex
-     * @return {?WebInspector.TimelineSelection}
-     */
-    createSelection: function(entryIndex)
-    {
-        var record = this._records[entryIndex];
-        if (record instanceof WebInspector.TimelineModel.RecordImpl) {
-            this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex);
-            return this._lastSelection.timelineSelection;
+        function equalFrames(frame1, frame2)
+        {
+            return frame1.scriptId === frame2.scriptId && frame1.functionName === frame2.functionName;
         }
-        return null;
-    },
-
-    /**
-     * @param {?WebInspector.TimelineSelection} selection
-     * @return {number}
-     */
-    entryIndexForSelection: function(selection)
-    {
-        if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.Record)
-            return -1;
-        var record = /** @type{!WebInspector.TimelineModel.Record} */ (selection.object());
-        if (this._lastSelection && this._lastSelection.timelineSelection.object() === record)
-            return this._lastSelection.entryIndex;
-        var entryRecords = this._records;
-        for (var entryIndex = 0; entryIndex < entryRecords.length; ++entryIndex) {
-            if (entryRecords[entryIndex] === record) {
-                this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex);
-                return entryIndex;
-            }
+        function eventEndTime(e)
+        {
+            return e.endTime || e.startTime + WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
         }
-        return -1;
-    }
-}
-
-/**
- * @constructor
- * @implements {WebInspector.FlameChartDataProvider}
- * @implements {WebInspector.TimelineFlameChart.SelectionProvider}
- * @param {!WebInspector.TracingTimelineModel} model
- * @param {!WebInspector.TimelineFrameModelBase} frameModel
- * @param {!WebInspector.Target} target
- */
-WebInspector.TracingBasedTimelineFlameChartDataProvider = function(model, frameModel, target)
-{
-    WebInspector.FlameChartDataProvider.call(this);
-    this._model = model;
-    this._frameModel = frameModel;
-    this._target = target;
-    this._font = "12px " + WebInspector.fontFamily();
-    this._linkifier = new WebInspector.Linkifier();
-    this._palette = new WebInspector.TraceViewPalette();
-    this._entryIndexToTitle = {};
-}
-
-WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
-    /**
-     * @return {number}
-     */
-    barHeight: function()
-    {
-        return 20;
-    },
-
-    /**
-     * @return {number}
-     */
-    textBaseline: function()
-    {
-        return 6;
-    },
-
-    /**
-     * @return {number}
-     */
-    textPadding: function()
-    {
-        return 5;
-    },
-
-    /**
-     * @param {number} entryIndex
-     * @return {string}
-     */
-    entryFont: function(entryIndex)
-    {
-        return this._font;
-    },
-
-    /**
-     * @param {number} entryIndex
-     * @return {?string}
-     */
-    entryTitle: function(entryIndex)
-    {
-        var event = this._entryEvents[entryIndex];
-        if (event) {
-            var name = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name).title;
-            // TODO(yurys): support event dividers
-            var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier, false, this._target);
-            return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name;
+        function isJSInvocationEvent(e)
+        {
+            switch (e.name) {
+            case WebInspector.TracingTimelineModel.RecordType.FunctionCall:
+            case WebInspector.TracingTimelineModel.RecordType.EvaluateScript:
+                return true;
+            }
+            return false;
         }
-        var title = this._entryIndexToTitle[entryIndex];
-        if (!title) {
-            title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex);
-            console.error(title);
+        var jsFrameEvents = [];
+        var stackTraceOpenEvents = [];
+        var currentJSInvocationEndTime = 0;
+        var coalesceThresholdMs = WebInspector.TimelineFlameChartDataProvider.JSFrameCoalsceThresholdMs;
+        for (var i = 0; i < events.length; ++i) {
+            var e = events[i];
+            if (e.startTime >= currentJSInvocationEndTime) {
+                stackTraceOpenEvents.length = 0;
+                currentJSInvocationEndTime = 0;
+            }
+            if (isJSInvocationEvent(e))
+                currentJSInvocationEndTime = e.endTime;
+            if (!currentJSInvocationEndTime)
+                continue;
+            if (!e.stackTrace)
+                continue;
+            while (stackTraceOpenEvents.length && eventEndTime(stackTraceOpenEvents.peekLast()) + coalesceThresholdMs <= e.startTime)
+                stackTraceOpenEvents.pop();
+            var numFrames = e.stackTrace.length;
+            for (var j = 0; j < numFrames && j < stackTraceOpenEvents.length; ++j) {
+                var frame = e.stackTrace[numFrames - 1 - j];
+                if (!equalFrames(frame, stackTraceOpenEvents[j].args["data"]))
+                    break;
+                stackTraceOpenEvents[j].endTime = Math.max(stackTraceOpenEvents[j].endTime, eventEndTime(e));
+                stackTraceOpenEvents[j].duration = stackTraceOpenEvents[j].endTime - stackTraceOpenEvents[j].startTime;
+            }
+            stackTraceOpenEvents.length = j;
+            var timestampUs = e.startTime * 1000;
+            var durationUs = (e.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs) * 1000;
+            for (; j < numFrames; ++j) {
+                var frame = e.stackTrace[numFrames - 1 - j];
+                var payload = /** @type {!WebInspector.TracingModel.EventPayload} */ ({
+                    ph: e.phase,
+                    cat: WebInspector.TracingModel.DevToolsMetadataEventCategory,
+                    name: WebInspector.TracingTimelineModel.RecordType.JSFrame,
+                    ts: timestampUs,
+                    dur: durationUs,
+                    args: {
+                        data: frame
+                    }
+                });
+                var jsFrameEvent = new WebInspector.TracingModel.Event(payload, 0, e.thread);
+                stackTraceOpenEvents.push(jsFrameEvent);
+                jsFrameEvents.push(jsFrameEvent);
+            }
         }
-        return title;
+        return jsFrameEvents;
     },
 
     /**
-     * @param {number} startTime
-     * @param {number} endTime
-     * @return {?Array.<number>}
+     * @param {!WebInspector.TracingTimelineModel.Filter} filter
      */
-    dividerOffsets: function(startTime, endTime)
+    addFilter: function(filter)
     {
-        return null;
-    },
-
-    reset: function()
-    {
-        this._timelineData = null;
-        /** @type {!Array.<!WebInspector.TracingModel.Event>} */
-        this._entryEvents = [];
-        this._entryIndexToTitle = {};
+        this._filters.push(filter);
     },
 
     /**
-     * @return {!WebInspector.FlameChart.TimelineData}
+     * @param {!WebInspector.TracingModel.Event} event
+     * @return {boolean}
      */
-    timelineData: function()
+    _isVisible: function(event)
     {
-        if (this._timelineData)
-            return this._timelineData;
-
-        /**
-         * @type {?WebInspector.FlameChart.TimelineData}
-         */
-        this._timelineData = {
-            entryLevels: [],
-            entryTotalTimes: [],
-            entryStartTimes: []
-        };
-
-        this._currentLevel = 0;
-        this._minimumBoundary = this._model.minimumRecordTime();
-        this._timeSpan = Math.max(this._model.maximumRecordTime() - this._minimumBoundary, 1000);
-        this._appendHeaderRecord("CPU");
-        var events = this._model.mainThreadEvents();
-        var maxStackDepth = 0;
-        for (var eventIndex = 0; eventIndex < events.length; ++eventIndex) {
-            var event = events[eventIndex];
-            var category = event.category;
-            if (category !== "disabled-by-default-devtools.timeline" && category !== "devtools")
-                continue;
-            if (event.duration || event.phase === WebInspector.TracingModel.Phase.Instant) {
-                this._appendEvent(event);
-                if (maxStackDepth < event.level)
-                    maxStackDepth = event.level;
-            }
-        }
-        this._currentLevel += maxStackDepth + 1;
-
-        this._appendHeaderRecord("GPU");
-        return this._timelineData;
+        return this._filters.every(function (filter) { return filter.accept(event); });
     },
 
     /**
@@ -621,7 +362,9 @@ WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
     {
         var event = this._entryEvents[entryIndex];
         if (!event)
-            return "#555";
+            return this._entryIndexToFrame[entryIndex] ? "white" : "#555";
+        if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame)
+            return WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator().colorForID(event.args["data"]["functionName"]);
         var style = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name);
         return style.category.fillColorStop1;
     },
@@ -639,11 +382,60 @@ WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
      */
     decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition)
     {
+        var frame = this._entryIndexToFrame[entryIndex];
+        if (frame) {
+            context.save();
+
+            context.translate(0.5, 0.5);
+
+            context.beginPath();
+            context.moveTo(barX, barY);
+            context.lineTo(barX, context.canvas.height);
+            context.strokeStyle = "rgba(100, 100, 100, 0.4)";
+            context.setLineDash([5]);
+            context.stroke();
+            context.setLineDash([]);
+
+
+            var padding = 4 * window.devicePixelRatio;
+            barX += padding;
+            barWidth -= 2 * padding;
+            barY += padding;
+            barHeight -= 2 * padding;
+
+            var cornerRadis = 3;
+            var radiusY = cornerRadis;
+            var radiusX = Math.min(cornerRadis, barWidth / 2);
+
+            context.beginPath();
+            context.moveTo(barX + radiusX, barY);
+            context.lineTo(barX + barWidth - radiusX, barY);
+            context.quadraticCurveTo(barX + barWidth, barY, barX + barWidth, barY + radiusY);
+            context.lineTo(barX + barWidth, barY + barHeight - radiusY);
+            context.quadraticCurveTo(barX + barWidth, barY + barHeight, barX + barWidth - radiusX, barY + barHeight);
+            context.lineTo(barX + radiusX, barY + barHeight);
+            context.quadraticCurveTo(barX, barY + barHeight, barX, barY + barHeight - radiusY);
+            context.lineTo(barX, barY + radiusY);
+            context.quadraticCurveTo(barX, barY, barX + radiusX, barY);
+            context.closePath();
+
+            context.fillStyle = "rgba(200, 200, 200, 0.8)";
+            context.fill();
+            context.strokeStyle = "rgba(150, 150, 150, 0.8)";
+            context.stroke();
+
+            var frameDurationText = Number.millisToString(frame.duration, true);
+            var textWidth = context.measureText(frameDurationText).width;
+            if (barWidth > textWidth) {
+                context.fillStyle = "#555";
+                context.fillText(frameDurationText, barX + ((barWidth - textWidth) >> 1), barY + barHeight - 2);
+            }
+            context.restore();
+            return true;
+        }
         if (barWidth < 5)
             return false;
 
-        var timelineData = this._timelineData;
-
         // Paint text using white color on dark background.
         if (text) {
             context.save();
@@ -684,7 +476,7 @@ WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
     {
         var event = this._entryEvents[entryIndex];
         if (!event)
-            return false;
+            return !!this._entryIndexToFrame[entryIndex];
         return !!event.warning;
     },
 
@@ -694,12 +486,12 @@ WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
      */
     highlightTimeRange: function(entryIndex)
     {
-        var event = this._entryEvents[entryIndex];
+        var event = this._entryEvents[entryIndex] || this._entryIndexToFrame[entryIndex];
         if (!event)
             return null;
         return {
             startTime: event.startTime,
-            endTime: event.endTime
+            endTime: event.endTime || event.startTime + WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs
         }
     },
 
@@ -722,40 +514,62 @@ WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
 
     /**
      * @param {string} title
+     * @param {number} level
      */
-    _appendHeaderRecord: function(title)
+    _appendHeaderRecord: function(title, level)
     {
         var index = this._entryEvents.length;
         this._entryIndexToTitle[index] = title;
         this._entryEvents.push(null);
-        this._timelineData.entryLevels[index] = this._currentLevel++;
+        this._timelineData.entryLevels[index] = level;
         this._timelineData.entryTotalTimes[index] = this._timeSpan;
         this._timelineData.entryStartTimes[index] = this._minimumBoundary;
     },
 
     /**
      * @param {!WebInspector.TracingModel.Event} event
+     * @param {number} level
      */
-    _appendEvent: function(event)
+    _appendEvent: function(event, level)
     {
         var index = this._entryEvents.length;
         this._entryEvents.push(event);
-        this._timelineData.entryLevels[index] = this._currentLevel + event.level;
-        this._timelineData.entryTotalTimes[index] = event.duration || 1;
+        this._timelineData.entryLevels[index] = level;
+        this._timelineData.entryTotalTimes[index] = event.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs;
         this._timelineData.entryStartTimes[index] = event.startTime;
     },
 
     /**
+     * @param {!WebInspector.TimelineFrame} frame
+     */
+    _appendFrame: function(frame)
+    {
+        var index = this._entryEvents.length;
+        this._entryEvents.push(null);
+        this._entryIndexToFrame[index] = frame;
+        this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true);
+        this._timelineData.entryLevels[index] = this._frameBarsLevel;
+        this._timelineData.entryTotalTimes[index] = frame.duration;
+        this._timelineData.entryStartTimes[index] = frame.startTime;
+    },
+
+    /**
      * @param {number} entryIndex
      * @return {?WebInspector.TimelineSelection}
      */
     createSelection: function(entryIndex)
     {
         var event = this._entryEvents[entryIndex];
-        if (!event)
-            return null;
-        this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
-        return this._lastSelection.timelineSelection;
+        if (event) {
+            this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
+            return this._lastSelection.timelineSelection;
+        }
+        var frame = this._entryIndexToFrame[entryIndex];
+        if (frame) {
+            this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), entryIndex);
+            return this._lastSelection.timelineSelection;
+        }
+        return null;
     },
 
     /**
@@ -764,17 +578,31 @@ WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = {
      */
     entryIndexForSelection: function(selection)
     {
-        if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.TraceEvent)
+        if (!selection)
             return -1;
-        var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
-        if (this._lastSelection && this._lastSelection.timelineSelection.object() === event)
+
+        if (this._lastSelection && this._lastSelection.timelineSelection.object() === selection.object())
             return this._lastSelection.entryIndex;
-        var entryEvents = this._entryEvents;
-        for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
-            if (entryEvents[entryIndex] === event) {
-                this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
-                return entryIndex;
+        switch  (selection.type()) {
+        case WebInspector.TimelineSelection.Type.TraceEvent:
+            var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object());
+            var entryEvents = this._entryEvents;
+            for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) {
+                if (entryEvents[entryIndex] === event) {
+                    this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex);
+                    return entryIndex;
+                }
             }
+            break;
+        case WebInspector.TimelineSelection.Type.Frame:
+            var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object());
+            for (var frameIndex in this._entryIndexToFrame) {
+                if (this._entryIndexToFrame[frameIndex] === frame) {
+                    this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), Number(frameIndex));
+                    return Number(frameIndex);
+                }
+            }
+            break;
         }
         return -1;
     }
@@ -803,20 +631,17 @@ WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator = function()
  * @implements {WebInspector.TimelineModeView}
  * @implements {WebInspector.FlameChartDelegate}
  * @param {!WebInspector.TimelineModeViewDelegate} delegate
- * @param {!WebInspector.TimelineModel} model
- * @param {?WebInspector.TracingTimelineModel} tracingModel
+ * @param {!WebInspector.TracingTimelineModel} tracingModel
  * @param {!WebInspector.TimelineFrameModelBase} frameModel
  */
-WebInspector.TimelineFlameChart = function(delegate, model, tracingModel, frameModel)
+WebInspector.TimelineFlameChart = function(delegate, tracingModel, frameModel)
 {
     WebInspector.VBox.call(this);
     this.element.classList.add("timeline-flamechart");
     this.registerRequiredCSS("flameChart.css");
     this._delegate = delegate;
-    this._model = model;
-    this._dataProvider = tracingModel
-        ? new WebInspector.TracingBasedTimelineFlameChartDataProvider(tracingModel, frameModel, model.target())
-        : new WebInspector.TimelineFlameChartDataProvider(/** @type {!WebInspector.TimelineModelImpl} */(model), frameModel);
+    this._model = tracingModel;
+    this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(tracingModel, frameModel)
     this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true);
     this._mainView.show(this.element);
     this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this);
@@ -845,15 +670,14 @@ WebInspector.TimelineFlameChart.prototype = {
     refreshRecords: function(textFilter)
     {
         this._dataProvider.reset();
-        this._mainView._scheduleUpdate();
+        this._mainView.scheduleUpdate();
     },
 
     wasShown: function()
     {
-        this._mainView._scheduleUpdate();
+        this._mainView.scheduleUpdate();
     },
 
-
     /**
      * @return {!WebInspector.View}
      */
@@ -888,7 +712,7 @@ WebInspector.TimelineFlameChart.prototype = {
                 this._automaticallySizeWindow = false;
                 this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000);
             }
-            this._mainView._scheduleUpdate();
+            this._mainView.scheduleUpdate();
         } else {
             if (!this._pendingUpdateTimer)
                 this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300);
@@ -898,7 +722,7 @@ WebInspector.TimelineFlameChart.prototype = {
     _updateOnAddRecord: function()
     {
         delete this._pendingUpdateTimer;
-        this._mainView._scheduleUpdate();
+        this._mainView.scheduleUpdate();
     },
 
     /**
@@ -960,21 +784,3 @@ WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex)
     this.timelineSelection = selection;
     this.entryIndex = entryIndex;
 }
-
-/**
-  * @interface
-  */
-WebInspector.TimelineFlameChart.SelectionProvider = function() { }
-
-WebInspector.TimelineFlameChart.SelectionProvider.prototype = {
-    /**
-     * @param {number} entryIndex
-     * @return {?WebInspector.TimelineSelection}
-     */
-    createSelection: function(entryIndex) { },
-    /**
-     * @param {?WebInspector.TimelineSelection} selection
-     * @return {number}
-     */
-    entryIndexForSelection: function(selection) { }
-}