Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / FlameChart.js
index a6f04c8..464639d 100644 (file)
@@ -1,4 +1,4 @@
-/*
+/**
  * Copyright (C) 2013 Google Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 
 /**
+ * @interface
+ */
+WebInspector.FlameChartDelegate = function() { }
+
+WebInspector.FlameChartDelegate.prototype = {
+    /**
+     * @param {number} startTime
+     * @param {number} endTime
+     */
+    requestWindowTimes: function(startTime, endTime) { },
+}
+
+/**
  * @constructor
- * @extends {WebInspector.View}
- * @param {WebInspector.CPUProfileView} cpuProfileView
+ * @extends {WebInspector.HBox}
+ * @param {!WebInspector.FlameChartDataProvider} dataProvider
+ * @param {!WebInspector.FlameChartDelegate} flameChartDelegate
+ * @param {boolean} isTopDown
+ * @param {boolean} timeBasedWindow
  */
-WebInspector.FlameChart = function(cpuProfileView)
+WebInspector.FlameChart = function(dataProvider, flameChartDelegate, isTopDown, timeBasedWindow)
 {
-    WebInspector.View.call(this);
-    this.registerRequiredCSS("flameChart.css");
-    this.element.className = "fill";
-    this.element.id = "cpu-flame-chart";
-
-    this._overviewContainer = this.element.createChild("div", "overview-container");
-    this._overviewGrid = new WebInspector.OverviewGrid("flame-chart");
-    this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas");
-    this._overviewContainer.appendChild(this._overviewGrid.element);
-    this._overviewCalculator = new WebInspector.FlameChart.OverviewCalculator();
-    this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
-
-    this._chartContainer = this.element.createChild("div", "chart-container");
-    this._timelineGrid = new WebInspector.TimelineGrid();
-    this._chartContainer.appendChild(this._timelineGrid.element);
+    WebInspector.HBox.call(this);
+    this.element.classList.add("flame-chart-main-pane");
+    this._flameChartDelegate = flameChartDelegate;
+    this._isTopDown = isTopDown;
+    this._timeBasedWindow = timeBasedWindow;
+
     this._calculator = new WebInspector.FlameChart.Calculator();
 
-    this._canvas = this._chartContainer.createChild("canvas");
+    this._canvas = this.element.createChild("canvas");
     this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this));
-    WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "col-resize");
+    this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
+    this._canvas.addEventListener("click", this._onClick.bind(this), false);
+    WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "move", null);
+
+    this._vScrollElement = this.element.createChild("div", "flame-chart-v-scroll");
+    this._vScrollContent = this._vScrollElement.createChild("div");
+    this._vScrollElement.addEventListener("scroll", this._scheduleUpdate.bind(this), false);
 
-    this._entryInfo = this._chartContainer.createChild("div", "entry-info");
+    this._entryInfo = this.element.createChild("div", "profile-entry-info");
+    this._highlightElement = this.element.createChild("div", "flame-chart-highlight-element");
+    this._selectedElement = this.element.createChild("div", "flame-chart-selected-element");
+
+    this._dataProvider = dataProvider;
 
-    this._cpuProfileView = cpuProfileView;
     this._windowLeft = 0.0;
     this._windowRight = 1.0;
-    this._barHeight = 15;
+    this._windowWidth = 1.0;
+    this._timeWindowLeft = 0;
+    this._timeWindowRight = Infinity;
+    this._barHeight = dataProvider.barHeight();
+    this._barHeightDelta = this._isTopDown ? -this._barHeight : this._barHeight;
     this._minWidth = 1;
-    this._paddingLeft = 15;
-    this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
-    this._canvas.addEventListener("click", this._onClick.bind(this), false);
-    this._linkifier = new WebInspector.Linkifier();
+    this._paddingLeft = this._dataProvider.paddingLeft();
     this._highlightedEntryIndex = -1;
-
-    if (!WebInspector.FlameChart._colorGenerator)
-        WebInspector.FlameChart._colorGenerator = new WebInspector.FlameChart.ColorGenerator();
+    this._selectedEntryIndex = -1;
+    this._textWidth = {};
 }
 
+WebInspector.FlameChart.DividersBarHeight = 20;
+
 /**
- * @constructor
- * @implements {WebInspector.TimelineGrid.Calculator}
+ * @interface
  */
-WebInspector.FlameChart.Calculator = function()
+WebInspector.FlameChartDataProvider = function()
 {
 }
 
-WebInspector.FlameChart.Calculator.prototype = {
+/** @typedef {!{
+        entryLevels: !Array.<number>,
+        entryTotalTimes: !Array.<number>,
+        entryOffsets: !Array.<number>
+    }}
+ */
+WebInspector.FlameChart.TimelineData;
+
+WebInspector.FlameChartDataProvider.prototype = {
     /**
-     * @param {WebInspector.FlameChart} flameChart
+     * @return {number}
      */
-    _updateBoundaries: function(flameChart)
-    {
-        function log10(x)
-        {
-            return Math.log(x) / Math.LN10;
-        }
-        this._decimalDigits = Math.max(0, -Math.floor(log10(flameChart._timelineGrid.gridSliceTime * 1.01)));
-        this._minimumBoundaries = flameChart._windowLeft * flameChart._timelineData.totalTime;
-        this._maximumBoundaries = flameChart._windowRight * flameChart._timelineData.totalTime;
-        this.paddingLeft = flameChart._paddingLeft;
-        this._width = flameChart._canvas.width - this.paddingLeft;
-        this._timeToPixel = this._width / this.boundarySpan();
-    },
+    barHeight: function() { },
 
     /**
-     * @param {number} time
+     * @param {number} startTime
+     * @param {number} endTime
+     * @return {?Array.<number>}
      */
-    computePosition: function(time)
-    {
-        return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft;
-    },
+    dividerOffsets: function(startTime, endTime) { },
 
-    formatTime: function(value)
-    {
-        var format = "%." + this._decimalDigits + "f\u2009ms";
-        return WebInspector.UIString(format, value + this._minimumBoundaries);
-    },
+    /**
+     * @return {number}
+     */
+    zeroTime: function() { },
 
-    maximumBoundary: function()
-    {
-        return this._maximumBoundaries;
-    },
+    /**
+     * @return {number}
+     */
+    totalTime: function() { },
 
-    minimumBoundary: function()
-    {
-        return this._minimumBoundaries;
-    },
+    /**
+     * @return {number}
+     */
+    maxStackDepth: function() { },
 
-    zeroTime: function()
-    {
-        return 0;
-    },
+    /**
+     * @return {?WebInspector.FlameChart.TimelineData}
+     */
+    timelineData: function() { },
 
-    boundarySpan: function()
-    {
-        return this._maximumBoundaries - this._minimumBoundaries;
-    }
+    /**
+     * @param {number} entryIndex
+     * @return {?Array.<!{title: string, text: string}>}
+     */
+    prepareHighlightedEntryInfo: function(entryIndex) { },
+
+    /**
+     * @param {number} entryIndex
+     * @return {boolean}
+     */
+    canJumpToEntry: function(entryIndex) { },
+
+    /**
+     * @param {number} entryIndex
+     * @return {?string}
+     */
+    entryTitle: function(entryIndex) { },
+
+    /**
+     * @param {number} entryIndex
+     * @return {?string}
+     */
+    entryFont: function(entryIndex) { },
+
+    /**
+     * @param {number} entryIndex
+     * @return {!string}
+     */
+    entryColor: function(entryIndex) { },
+
+    /**
+     * @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}
+     */
+    decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition) { },
+
+    /**
+     * @param {number} entryIndex
+     * @return {boolean}
+     */
+    forceDecoration: function(entryIndex) { },
+
+    /**
+     * @param {number} entryIndex
+     * @return {!string}
+     */
+    textColor: function(entryIndex) { },
+
+    /**
+     * @return {number}
+     */
+    textBaseline: function() { },
+
+    /**
+     * @return {number}
+     */
+    textPadding: function() { },
+
+    /**
+     * @return {?{startTimeOffset: number, endTimeOffset: number}}
+     */
+    highlightTimeRange: function(entryIndex) { },
+
+    /**
+     * @return {number}
+     */
+    paddingLeft: function() { }
+}
+
+WebInspector.FlameChart.Events = {
+    EntrySelected: "EntrySelected"
 }
 
 /**
  * @constructor
  * @implements {WebInspector.TimelineGrid.Calculator}
  */
-WebInspector.FlameChart.OverviewCalculator = function()
+WebInspector.FlameChart.Calculator = function()
 {
+    this._paddingLeft = 0;
 }
 
-WebInspector.FlameChart.OverviewCalculator.prototype = {
+WebInspector.FlameChart.Calculator.prototype = {
     /**
-     * @param {WebInspector.FlameChart} flameChart
+     * @return {number}
      */
-    _updateBoundaries: function(flameChart)
+    paddingLeft: function()
     {
-        this._minimumBoundaries = 0;
-        this._maximumBoundaries = flameChart._timelineData.totalTime;
-        this._xScaleFactor = flameChart._canvas.width / flameChart._timelineData.totalTime;
+        return this._paddingLeft;
+    },
+
+    /**
+     * @param {!WebInspector.FlameChart} mainPane
+     */
+    _updateBoundaries: function(mainPane)
+    {
+        this._totalTime = mainPane._dataProvider.totalTime();
+        this._zeroTime = mainPane._dataProvider.zeroTime();
+        this._minimumBoundaries = this._zeroTime + mainPane._windowLeft * this._totalTime;
+        this._maximumBoundaries = this._zeroTime + mainPane._windowRight * this._totalTime;
+        this._paddingLeft = mainPane._paddingLeft;
+        this._width = mainPane._canvas.width / window.devicePixelRatio - this._paddingLeft;
+        this._timeToPixel = this._width / this.boundarySpan();
     },
 
     /**
      * @param {number} time
+     * @return {number}
      */
     computePosition: function(time)
     {
-        return (time - this._minimumBoundaries) * this._xScaleFactor;
+        return Math.round((time - this._minimumBoundaries) * this._timeToPixel + this._paddingLeft);
     },
 
-    formatTime: function(value)
+    /**
+     * @param {number} value
+     * @param {number=} precision
+     * @return {string}
+     */
+    formatTime: function(value, precision)
     {
-        return Number.secondsToString((value + this._minimumBoundaries) / 1000);
+        return Number.preciseMillisToString(value - this._zeroTime, precision);
     },
 
+    /**
+     * @return {number}
+     */
     maximumBoundary: function()
     {
         return this._maximumBoundaries;
     },
 
+    /**
+     * @return {number}
+     */
     minimumBoundary: function()
     {
         return this._minimumBoundaries;
     },
 
+    /**
+     * @return {number}
+     */
     zeroTime: function()
     {
-        return this._minimumBoundaries;
+        return this._zeroTime;
     },
 
+    /**
+     * @return {number}
+     */
     boundarySpan: function()
     {
         return this._maximumBoundaries - this._minimumBoundaries;
     }
 }
 
-WebInspector.FlameChart.Events = {
-    SelectedNode: "SelectedNode"
-}
-
-/**
- * @constructor
- */
-WebInspector.FlameChart.ColorGenerator = function()
-{
-    this._colorPairs = {};
-    this._colorIndexes = [];
-    this._currentColorIndex = 0;
-    this._colorPairForID("(idle)::0", 50);
-    this._colorPairForID("(program)::0", 50);
-    this._colorPairForID("(garbage collector)::0", 50);
-}
-
-WebInspector.FlameChart.ColorGenerator.prototype = {
-    /**
-     * @param {!string} id
-     * @param {number=} sat
-     */
-    _colorPairForID: function(id, sat)
+WebInspector.FlameChart.prototype = {
+    _resetCanvas: function()
     {
-        if (typeof sat !== "number")
-            sat = 100;
-        var colorPairs = this._colorPairs;
-        var colorPair = colorPairs[id];
-        if (!colorPair) {
-            colorPairs[id] = colorPair = this._createPair(this._currentColorIndex++, sat);
-            this._colorIndexes[colorPair.index] = colorPair;
-        }
-        return colorPair;
+        var ratio = window.devicePixelRatio;
+        this._canvas.width = this._offsetWidth * ratio;
+        this._canvas.height = this._offsetHeight * ratio;
     },
 
     /**
-     * @param {!number} index
+     * @return {?WebInspector.FlameChart.TimelineData}
      */
-    _colorPairForIndex: function(index)
+    _timelineData: function()
     {
-        return this._colorIndexes[index];
+        return this._dataProvider.timelineData();
     },
 
     /**
-     * @param {!number} index
-     * @param {!number} sat
+     * @param {!number} windowLeft
+     * @param {!number} windowRight
      */
-    _createPair: function(index, sat)
+    changeWindow: function(windowLeft, windowRight)
     {
-        var hue = (index * 7 + 12 * (index % 2)) % 360;
-        return {index: index, highlighted: "hsla(" + hue + ", " + sat + "%, 33%, 0.7)", normal: "hsla(" + hue + ", " + sat + "%, 66%, 0.7)"}
-    }
-}
+        console.assert(!this._timeBasedWindow);
+        this._windowLeft = windowLeft;
+        this._windowRight = windowRight;
+        this._windowWidth = this._windowRight - this._windowLeft;
 
-/**
- * @constructor
- * @param {!Object} colorPair
- * @param {!number} depth
- * @param {!number} duration
- * @param {!number} startTime
- * @param {Object} node
- */
-WebInspector.FlameChart.Entry = function(colorPair, depth, duration, startTime, node)
-{
-    this.colorPair = colorPair;
-    this.depth = depth;
-    this.duration = duration;
-    this.startTime = startTime;
-    this.node = node;
-    this.selfTime = 0;
-}
+        this._scheduleUpdate();
+    },
 
-WebInspector.FlameChart.prototype = {
     /**
-     * @param {!number} timeLeft
-     * @param {!number} timeRight
+     * @param {number} startTime
+     * @param {number} endTime
      */
-    selectRange: function(timeLeft, timeRight)
-    {
-        this._overviewGrid.setWindow(timeLeft / this._totalTime, timeRight / this._totalTime);
-    },
-
-    _onWindowChanged: function(event)
+    setWindowTimes: function(startTime, endTime)
     {
+        console.assert(this._timeBasedWindow);
+        this._timeWindowLeft = startTime;
+        this._timeWindowRight = endTime;
         this._scheduleUpdate();
     },
 
+    /**
+     * @param {!MouseEvent} event
+     */
     _startCanvasDragging: function(event)
     {
-        if (!this._timelineData)
+        if (!this._timelineData())
             return false;
         this._isDragging = true;
-        this._wasDragged = false;
-        this._dragStartPoint = event.pageX;
-        this._dragStartWindowLeft = this._windowLeft;
-        this._dragStartWindowRight = this._windowRight;
+        this._maxDragOffset = 0;
+        this._dragStartPointX = event.pageX;
+        this._dragStartPointY = event.pageY;
+        this._dragStartScrollTop = this._vScrollElement.scrollTop;
+        this._dragStartWindowLeft = this._timeWindowLeft;
+        this._dragStartWindowRight = this._timeWindowRight;
+        this._canvas.style.cursor = "";
 
         return true;
     },
 
+    /**
+     * @param {!MouseEvent} event
+     */
     _canvasDragging: function(event)
     {
-        var pixelShift = this._dragStartPoint - event.pageX;
+        var pixelShift = this._dragStartPointX - event.pageX;
+        var pixelScroll = this._dragStartPointY - event.pageY;
+        this._vScrollElement.scrollTop = this._dragStartScrollTop + pixelScroll;
         var windowShift = pixelShift / this._totalPixels;
-
-        var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift);
-        if (windowLeft === this._windowLeft)
-            return;
-        windowShift = windowLeft - this._dragStartWindowLeft;
-
-        var windowRight = Math.min(1, this._dragStartWindowRight + windowShift);
-        if (windowRight === this._windowRight)
-            return;
-        windowShift = windowRight - this._dragStartWindowRight;
-        this._overviewGrid.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift);
-        this._wasDragged = true;
+        var windowTime = this._windowWidth * this._totalTime;
+        var timeShift = windowTime * pixelShift / this._pixelWindowWidth;
+        timeShift = Number.constrain(
+            timeShift,
+            this._zeroTime - this._dragStartWindowLeft,
+            this._zeroTime + this._totalTime - this._dragStartWindowRight
+        );
+        var windowLeft = this._dragStartWindowLeft + timeShift;
+        var windowRight = this._dragStartWindowRight + timeShift;
+        this._flameChartDelegate.requestWindowTimes(windowLeft, windowRight);
+        this._maxDragOffset = Math.max(this._maxDragOffset, Math.abs(pixelShift));
     },
 
     _endCanvasDragging: function()
@@ -310,215 +391,87 @@ WebInspector.FlameChart.prototype = {
         this._isDragging = false;
     },
 
-    _calculateTimelineData: function()
-    {
-        if (this._timelineData)
-            return this._timelineData;
-
-        if (!this._cpuProfileView.profileHead)
-            return null;
-
-        var samples = this._cpuProfileView.samples;
-        var idToNode = this._cpuProfileView._idToNode;
-        var gcNode = this._cpuProfileView._gcNode;
-        var samplesCount = samples.length;
-        var samplingInterval = this._cpuProfileView.samplingIntervalMs;
-
-        var index = 0;
-        var entries = /** @type {Array.<!WebInspector.FlameChart.Entry>} */ ([]);
-
-        var openIntervals = [];
-        var stackTrace = [];
-        var colorGenerator = WebInspector.FlameChart._colorGenerator;
-        var colorEntryIndexes = [];
-        var maxDepth = 5; // minimum stack depth for the case when we see no activity.
-        var depth = 0;
-
-        for (var sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
-            var node = idToNode[samples[sampleIndex]];
-            stackTrace.length = 0;
-            while (node) {
-                stackTrace.push(node);
-                node = node.parent;
-            }
-            stackTrace.pop(); // Remove (root) node
-
-            maxDepth = Math.max(maxDepth, depth);
-            depth = 0;
-            node = stackTrace.pop();
-            var intervalIndex;
-
-            // GC samples have no stack, so we just put GC node on top of the last recoreded sample.
-            if (node === gcNode) {
-                while (depth < openIntervals.length) {
-                    intervalIndex = openIntervals[depth].index;
-                    entries[intervalIndex].duration += samplingInterval;
-                    ++depth;
-                }
-                // If previous stack is also GC then just continue.
-                if (openIntervals.length > 0 && openIntervals.peekLast().node === node) {
-                    entries[intervalIndex].selfTime += samplingInterval;
-                    continue;
-                }
-            }
-
-            while (node && depth < openIntervals.length && node === openIntervals[depth].node) {
-                intervalIndex = openIntervals[depth].index;
-                entries[intervalIndex].duration += samplingInterval;
-                node = stackTrace.pop();
-                ++depth;
-            }
-            if (depth < openIntervals.length)
-                openIntervals.length = depth;
-            if (!node) {
-                entries[intervalIndex].selfTime += samplingInterval;
-                continue;
-            }
-
-            while (node) {
-                var colorPair = colorGenerator._colorPairForID(node.functionName + ":" + node.url + ":" + node.lineNumber);
-                var indexesForColor = colorEntryIndexes[colorPair.index];
-                if (!indexesForColor)
-                    indexesForColor = colorEntryIndexes[colorPair.index] = [];
-
-                var entry = new WebInspector.FlameChart.Entry(colorPair, depth, samplingInterval, sampleIndex * samplingInterval, node);
-                indexesForColor.push(entries.length);
-                entries.push(entry);
-                openIntervals.push({node: node, index: index});
-                ++index;
-
-                node = stackTrace.pop();
-                ++depth;
-            }
-            entries[entries.length - 1].selfTime += samplingInterval;
-        }
-
-        this._maxStackDepth = Math.max(maxDepth, depth);
-
-        var entryColorIndexes = new Uint16Array(entries.length);
-        var entryLevels = new Uint8Array(entries.length);
-        var entryTotalTimes = new Float32Array(entries.length);
-        var entryOffsets = new Float32Array(entries.length);
-        var entryTitles = new Array(entries.length);
-        var entryDeoptFlags = new Uint8Array(entries.length);
-
-        for (var i = 0; i < entries.length; ++i) {
-            var entry = entries[i];
-            entryColorIndexes[i] = colorPair.index;
-            entryLevels[i] = entry.depth;
-            entryTotalTimes[i] = entry.duration;
-            entryOffsets[i] = entry.startTime;
-            entryTitles[i] = entry.node.functionName;
-            var reason = entry.node.deoptReason;
-            entryDeoptFlags[i] = (reason && reason !== "no reason");
-        }
-
-        this._timelineData = {
-            entries: entries,
-            totalTime: this._cpuProfileView.profileHead.totalTime,
-            entryColorIndexes: entryColorIndexes,
-            entryLevels: entryLevels,
-            entryTotalTimes: entryTotalTimes,
-            entryOffsets: entryOffsets,
-            colorEntryIndexes: colorEntryIndexes,
-            entryTitles: entryTitles,
-            entryDeoptFlags: entryDeoptFlags
-        };
-
-        return this._timelineData;
-    },
-
+    /**
+     * @param {?MouseEvent} event
+     */
     _onMouseMove: function(event)
     {
         if (this._isDragging)
             return;
-
         var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY);
 
         if (this._highlightedEntryIndex === entryIndex)
             return;
 
-        if (entryIndex === -1 || this._timelineData.entries[entryIndex].node.scriptId === "0")
+        if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex))
             this._canvas.style.cursor = "default";
         else
             this._canvas.style.cursor = "pointer";
 
         this._highlightedEntryIndex = entryIndex;
-        this._scheduleUpdate();
-    },
 
-    _millisecondsToString: function(ms)
-    {
-        if (ms === 0)
-            return "0";
-        if (ms < 1000)
-            return WebInspector.UIString("%.1f\u2009ms", ms);
-        return Number.secondsToString(ms / 1000, true);
-    },
+        this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
+        this._entryInfo.removeChildren();
 
-    _prepareHighlightedEntryInfo: function()
-    {
-        if (this._isDragging)
-            return null;
-        var entry = this._timelineData.entries[this._highlightedEntryIndex];
-        if (!entry)
-            return null;
-        var node = entry.node;
-        if (!node)
-            return null;
-
-        var entryInfo = [];
-        function pushEntryInfoRow(title, text)
-        {
-            var row = {};
-            row.title = title;
-            row.text = text;
-            entryInfo.push(row);
-        }
+        if (this._highlightedEntryIndex === -1)
+            return;
 
-        pushEntryInfoRow(WebInspector.UIString("Name"), node.functionName);
-        if (this._cpuProfileView.samples) {
-            var selfTime = this._millisecondsToString(entry.selfTime);
-            var totalTime = this._millisecondsToString(entry.duration);
-            pushEntryInfoRow(WebInspector.UIString("Self time"), selfTime);
-            pushEntryInfoRow(WebInspector.UIString("Total time"), totalTime);
+        if (!this._isDragging) {
+            var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this._highlightedEntryIndex);
+            if (entryInfo)
+                this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
         }
-        if (node.url)
-            pushEntryInfoRow(WebInspector.UIString("URL"), node.url + ":" + node.lineNumber);
-        pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.selfTime / 1000, true));
-        pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.totalTime / 1000, true));
-        if (node.deoptReason && node.deoptReason !== "no reason")
-            pushEntryInfoRow(WebInspector.UIString("Not optimized"), node.deoptReason);
-
-        return entryInfo;
     },
 
-    _onClick: function(e)
+    _onClick: function()
     {
         // onClick comes after dragStart and dragEnd events.
         // So if there was drag (mouse move) in the middle of that events
         // we skip the click. Otherwise we jump to the sources.
-        if (this._wasDragged)
+        const clickThreshold = 5;
+        if (this._maxDragOffset > clickThreshold)
             return;
         if (this._highlightedEntryIndex === -1)
             return;
-        var node = this._timelineData.entries[this._highlightedEntryIndex].node;
-        this.dispatchEventToListeners(WebInspector.FlameChart.Events.SelectedNode, node);
+        this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, this._highlightedEntryIndex);
     },
 
+    /**
+     * @param {?MouseEvent} e
+     */
     _onMouseWheel: function(e)
     {
-        if (e.wheelDeltaY) {
-            const zoomFactor = 1.1;
-            const mouseWheelZoomSpeed = 1 / 120;
+        var windowLeft = this._timeWindowLeft ? this._timeWindowLeft : this._dataProvider.zeroTime();
+        var windowRight = this._timeWindowRight !== Infinity ? this._timeWindowRight : this._dataProvider.zeroTime() + this._dataProvider.totalTime();
 
-            var zoom = Math.pow(zoomFactor, -e.wheelDeltaY * mouseWheelZoomSpeed);
-            var overviewReference = (this._pixelWindowLeft + e.offsetX - this._paddingLeft) / this._totalPixels;
-            this._overviewGrid.zoom(zoom, overviewReference);
+        if (e.wheelDeltaY) {
+            if (!e.altKey) {
+                const mouseWheelZoomSpeed = 1 / 120;
+                var zoom = Math.pow(1.2, -e.wheelDeltaY * mouseWheelZoomSpeed) - 1;
+                var cursorTime = this._cursorTime(e.offsetX);
+                windowLeft += (windowLeft - cursorTime) * zoom;
+                windowRight += (windowRight - cursorTime) * zoom;
+            } else {
+                this._vScrollElement.scrollTop -= e.wheelDeltaY / 120 * this._offsetHeight / 8;
+            }
         } else {
-            var shift = Number.constrain(-1 * this._windowWidth / 4 * e.wheelDeltaX / 120, -this._windowLeft, 1 - this._windowRight);
-            this._overviewGrid.setWindow(this._windowLeft + shift, this._windowRight + shift);
+            var shift = e.wheelDeltaX * this._pixelToTime;
+            shift = Number.constrain(shift, this._zeroTime - windowLeft, this._totalTime + this._zeroTime - windowRight);
+            windowLeft += shift;
+            windowRight += shift;
         }
+        windowLeft = Number.constrain(windowLeft, this._zeroTime, this._totalTime + this._zeroTime);
+        windowRight = Number.constrain(windowRight, this._zeroTime, this._totalTime + this._zeroTime);
+        this._flameChartDelegate.requestWindowTimes(windowLeft, windowRight);
+    },
+
+    /**
+     * @param {number} x
+     * @return {number}
+     */
+    _cursorTime: function(x)
+    {
+        return (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime + this._zeroTime;
     },
 
     /**
@@ -527,207 +480,211 @@ WebInspector.FlameChart.prototype = {
      */
     _coordinatesToEntryIndex: function(x, y)
     {
-        var timelineData = this._timelineData;
+        y += this._scrollTop;
+        var timelineData = this._timelineData();
         if (!timelineData)
             return -1;
-        var timelineEntries = timelineData.entries;
-        var cursorTime = (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime;
-        var cursorLevel = Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight);
-
-        for (var i = 0; i < timelineEntries.length; ++i) {
-            if (cursorTime < timelineEntries[i].startTime)
+        var cursorTimeOffset = this._cursorTime(x) - this._zeroTime;
+        var cursorLevel = this._isTopDown ? Math.floor((y - WebInspector.FlameChart.DividersBarHeight) / this._barHeight) : Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight);
+        var entryOffsets = timelineData.entryOffsets;
+        var entryTotalTimes = timelineData.entryTotalTimes;
+        var entryLevels = timelineData.entryLevels;
+        var length = entryOffsets.length;
+        for (var i = 0; i < length; ++i) {
+            var entryLevel = entryLevels[i];
+            if (cursorLevel !== entryLevel)
+                continue;
+            if (cursorTimeOffset < entryOffsets[i])
                 return -1;
-            if (cursorTime < (timelineEntries[i].startTime + timelineEntries[i].duration)
-                && cursorLevel === timelineEntries[i].depth)
+            if (cursorTimeOffset < (entryOffsets[i] + entryTotalTimes[i]))
                 return i;
         }
         return -1;
     },
 
-    onResize: function()
-    {
-        this._updateOverviewCanvas = true;
-        this._scheduleUpdate();
-    },
-
-    _drawOverviewCanvas: function(width, height)
-    {
-        if (!this._timelineData)
-            return;
-
-        var timelineEntries = this._timelineData.entries;
-
-        var drawData = new Uint8Array(width);
-        var scaleFactor = width / this._totalTime;
-
-        for (var entryIndex = 0; entryIndex < timelineEntries.length; ++entryIndex) {
-            var entry = timelineEntries[entryIndex];
-            var start = Math.floor(entry.startTime * scaleFactor);
-            var finish = Math.floor((entry.startTime + entry.duration) * scaleFactor);
-            for (var x = start; x < finish; ++x)
-                drawData[x] = Math.max(drawData[x], entry.depth + 1);
-        }
-
-        var ratio = window.devicePixelRatio;
-        var canvasWidth = width * ratio;
-        var canvasHeight = height * ratio;
-        this._overviewCanvas.width = canvasWidth;
-        this._overviewCanvas.height = canvasHeight;
-        this._overviewCanvas.style.width = width + "px";
-        this._overviewCanvas.style.height = height + "px";
-
-        var context = this._overviewCanvas.getContext("2d");
-
-        var yScaleFactor = canvasHeight / (this._maxStackDepth * 1.1);
-        context.lineWidth = 1;
-        context.translate(0.5, 0.5);
-        context.strokeStyle = "rgba(20,0,0,0.4)";
-        context.fillStyle = "rgba(214,225,254,0.8)";
-        context.moveTo(-1, canvasHeight - 1);
-        if (drawData)
-          context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1));
-        var value;
-        for (var x = 0; x < width; ++x) {
-            value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1);
-            context.lineTo(x * ratio, value);
-        }
-        context.lineTo(canvasWidth + 1, value);
-        context.lineTo(canvasWidth + 1, canvasHeight - 1);
-        context.fill();
-        context.stroke();
-        context.closePath();
-    },
-
     /**
      * @param {!number} height
      * @param {!number} width
      */
     draw: function(width, height)
     {
-        var timelineData = this._calculateTimelineData();
+        var timelineData = this._timelineData();
         if (!timelineData)
             return;
 
-        var ratio = window.devicePixelRatio;
-        this._canvas.width = width * ratio;
-        this._canvas.height = height * ratio;
-        this._canvas.style.width = width + "px";
-        this._canvas.style.height = height + "px";
-
         var context = this._canvas.getContext("2d");
+        context.save();
+        var ratio = window.devicePixelRatio;
         context.scale(ratio, ratio);
-        var timeWindowRight = this._timeWindowRight;
+
+        var timeWindowRight = this._timeWindowRight - this._zeroTime;
+        var timeWindowLeft = this._timeWindowLeft - this._zeroTime;
         var timeToPixel = this._timeToPixel;
         var pixelWindowLeft = this._pixelWindowLeft;
         var paddingLeft = this._paddingLeft;
         var minWidth = this._minWidth;
+        var entryTotalTimes = timelineData.entryTotalTimes;
+        var entryOffsets = timelineData.entryOffsets;
+        var entryLevels = timelineData.entryLevels;
 
-        var entryTotalTimes = this._timelineData.entryTotalTimes;
-        var entryOffsets = this._timelineData.entryOffsets;
-        var entryLevels = this._timelineData.entryLevels;
-        var colorEntryIndexes = this._timelineData.colorEntryIndexes;
-        var entryTitles = this._timelineData.entryTitles;
-        var entryDeoptFlags = this._timelineData.entryDeoptFlags;
-
-        var colorGenerator = WebInspector.FlameChart._colorGenerator;
-        var titleIndexes = new Uint32Array(this._timelineData.entryTotalTimes);
+        var titleIndexes = new Uint32Array(timelineData.entryTotalTimes);
         var lastTitleIndex = 0;
-        var dotsWidth = context.measureText("\u2026").width;
-        var textPaddingLeft = 2;
-        this._minTextWidth = context.measureText("\u2026").width + textPaddingLeft;
+        var textPadding = this._dataProvider.textPadding();
+        this._minTextWidth = 2 * textPadding + this._measureWidth(context, "\u2026");
         var minTextWidth = this._minTextWidth;
 
-        var marksField = [];
-        for (var i = 0; i < this._maxStackDepth; ++i)
-            marksField.push(new Uint16Array(width));
+        var lastDrawOffset = new Int32Array(this._dataProvider.maxStackDepth());
+        for (var i = 0; i < lastDrawOffset.length; ++i)
+            lastDrawOffset[i] = -1;
 
         var barHeight = this._barHeight;
-        var barX = 0;
-        var barWidth = 0;
-        var barRight = 0;
-        var barLevel = 0;
-        var bHeight = height - barHeight;
-        context.strokeStyle = "black";
-        var colorPair;
-        var entryIndex = 0;
-        var entryOffset = 0;
-        for (var colorIndex = 0; colorIndex < colorEntryIndexes.length; ++colorIndex) {
-            colorPair = colorGenerator._colorPairForIndex(colorIndex);
-            context.fillStyle = colorPair.normal;
-            var indexes = colorEntryIndexes[colorIndex];
-            if (!indexes)
+
+        var offsetToPosition = this._offsetToPosition.bind(this);
+        var textBaseHeight = this._baseHeight + barHeight - this._dataProvider.textBaseline();
+        var colorBuckets = {};
+        var minVisibleBarLevel = Math.max(0, Math.floor((this._scrollTop - this._baseHeight) / barHeight));
+        var maxVisibleBarLevel = Math.min(this._dataProvider.maxStackDepth(), Math.ceil((height + this._scrollTop) / barHeight));
+        var visibleBarsCount = maxVisibleBarLevel - minVisibleBarLevel + 1;
+
+        context.translate(0, -this._scrollTop);
+        var levelsCompleted = 0;
+        var lastEntryOnLevelPainted = [];
+        for (var i = 0; i < visibleBarsCount; ++i)
+            lastEntryOnLevelPainted[i] = false;
+
+        for (var entryIndex = 0; levelsCompleted < visibleBarsCount && entryIndex < entryOffsets.length; ++entryIndex) {
+            // skip if it is not visible (top/bottom side)
+            var barLevel = entryLevels[entryIndex];
+            if (barLevel < minVisibleBarLevel || barLevel > maxVisibleBarLevel || lastEntryOnLevelPainted[barLevel - minVisibleBarLevel])
+                continue;
+
+            // stop if we reached right border in time (entries were ordered by start time).
+            var entryOffset = entryOffsets[entryIndex];
+            if (entryOffset > timeWindowRight) {
+                lastEntryOnLevelPainted[barLevel - minVisibleBarLevel] = true;
+                levelsCompleted++;
+                continue;
+            }
+
+            // skip if it is not visible (left side).
+            var entryOffsetRight = entryOffset + entryTotalTimes[entryIndex];
+            if (entryOffsetRight < timeWindowLeft)
                 continue;
+
+            var barRight = this._offsetToPosition(entryOffsetRight);
+            if (barRight <= lastDrawOffset[barLevel])
+                continue;
+            var barX = Math.max(this._offsetToPosition(entryOffset), lastDrawOffset[barLevel]);
+            lastDrawOffset[barLevel] = barRight;
+
+            var barWidth = barRight - barX;
+            var color = this._dataProvider.entryColor(entryIndex);
+            var bucket = colorBuckets[color];
+            if (!bucket) {
+                bucket = [];
+                colorBuckets[color] = bucket;
+            }
+            bucket.push(entryIndex);
+        }
+
+        var colors = Object.keys(colorBuckets);
+        // We don't use for in here because it couldn't be optimized.
+        for (var c = 0; c < colors.length; ++c) {
+            var color = colors[c];
+            context.fillStyle = color;
+            context.strokeStyle = color;
+            var indexes = colorBuckets[color];
+
+            // First fill the boxes.
             context.beginPath();
-            for (var i = 0; i < indexes.length; ++i) {
-                entryIndex = indexes[i];
-                entryOffset = entryOffsets[entryIndex];
-                if (entryOffset > timeWindowRight)
-                    break;
-                barX = Math.ceil(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
-                barRight = Math.floor((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
-                if (barRight < 0)
-                    continue;
-                barWidth = (barRight - barX) || minWidth;
-                barLevel = entryLevels[entryIndex];
-                var marksRow = marksField[barLevel];
-                if (barWidth <= marksRow[barX])
-                    continue;
-                marksRow[barX] = barWidth;
-                if (entryIndex === this._highlightedEntryIndex) {
-                    context.fill();
-                    context.beginPath();
-                    context.fillStyle = colorPair.highlighted;
-                }
-                context.rect(barX, bHeight - barLevel * barHeight, barWidth, barHeight);
-                if (entryIndex === this._highlightedEntryIndex) {
-                    context.fill();
-                    context.beginPath();
-                    context.fillStyle = colorPair.normal;
-                }
-                if (barWidth > minTextWidth)
+            for (i = 0; i < indexes.length; ++i) {
+                var entryIndex = indexes[i];
+                var entryOffset = entryOffsets[entryIndex];
+                var barX = this._offsetToPosition(entryOffset);
+                var barRight = this._offsetToPosition(entryOffset + entryTotalTimes[entryIndex]);
+                var barWidth = Math.max(barRight - barX, minWidth);
+                var barLevel = entryLevels[entryIndex];
+                var barY = this._levelToHeight(barLevel);
+                context.rect(barX, barY, barWidth, barHeight);
+                if (barWidth > minTextWidth || this._dataProvider.forceDecoration(entryIndex))
                     titleIndexes[lastTitleIndex++] = entryIndex;
             }
             context.fill();
         }
 
-        var font = (barHeight - 4) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
-        var boldFont = "bold " + font;
-        var isBoldFontSelected = false;
-        context.font = font;
         context.textBaseline = "alphabetic";
-        context.fillStyle = "#333";
-        this._dotsWidth = context.measureText("\u2026").width;
 
-        var textBaseHeight = bHeight + barHeight - 4;
         for (var i = 0; i < lastTitleIndex; ++i) {
-            entryIndex = titleIndexes[i];
-            if (isBoldFontSelected) {
-                if (!entryDeoptFlags[entryIndex]) {
-                    context.font = font;
-                    isBoldFontSelected = false;
-                }
-            } else {
-                if (entryDeoptFlags[entryIndex]) {
-                    context.font = boldFont;
-                    isBoldFontSelected = true;
-                }
-            }
+            var entryIndex = titleIndexes[i];
+            var entryOffset = entryOffsets[entryIndex];
+            var barX = this._offsetToPosition(entryOffset);
+            var barRight = this._offsetToPosition(entryOffset + entryTotalTimes[entryIndex]);
+            var barWidth = Math.max(barRight - barX, minWidth);
+            var barLevel = entryLevels[entryIndex];
+            var barY = this._levelToHeight(barLevel);
+            var text = this._dataProvider.entryTitle(entryIndex);
+            if (text && text.length)
+                text = this._prepareText(context, text, barWidth - 2 * textPadding);
+
+            if (this._dataProvider.decorateEntry(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition))
+                continue;
 
-            entryOffset = entryOffsets[entryIndex];
-            barX = Math.floor(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft;
-            barRight = Math.ceil((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft;
-            barWidth = (barRight - barX) || minWidth;
-            var xText = Math.max(0, barX);
-            var widthText = barWidth - textPaddingLeft + barX - xText;
-            var title = this._prepareText(context, entryTitles[entryIndex], widthText);
-            if (title)
-                context.fillText(title, xText + textPaddingLeft, textBaseHeight - entryLevels[entryIndex] * barHeight);
+            if (!text || !text.length)
+                continue;
+
+            context.font = this._dataProvider.entryFont(entryIndex);
+            context.fillStyle = this._dataProvider.textColor(entryIndex);
+            context.fillText(text, barX + textPadding, textBaseHeight - barLevel * this._barHeightDelta);
         }
+        context.restore();
 
-        var entryInfo = this._prepareHighlightedEntryInfo();
-        this._entryInfo.removeChildren();
-        if (entryInfo)
-            this._entryInfo.appendChild(this._buildEntryInfo(entryInfo));
+        var offsets = this._dataProvider.dividerOffsets(this._calculator.minimumBoundary(), this._calculator.maximumBoundary());
+        if (timelineData.entryOffsets.length)
+            WebInspector.TimelineGrid.drawCanvasGrid(this._canvas, this._calculator, offsets);
+
+        this._updateElementPosition(this._highlightElement, this._highlightedEntryIndex);
+        this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
+    },
+
+    setSelectedEntry: function(entryIndex)
+    {
+        this._selectedEntryIndex = entryIndex;
+        this._updateElementPosition(this._selectedElement, this._selectedEntryIndex);
+    },
+
+    _updateElementPosition: function(element, entryIndex)
+    {
+        if (element.parentElement)
+            element.remove();
+        if (entryIndex === -1)
+            return;
+        var timeRange = this._dataProvider.highlightTimeRange(entryIndex);
+        if (!timeRange)
+            return;
+        var timelineData = this._timelineData();
+        var barX = this._offsetToPosition(timeRange.startTimeOffset);
+        var barRight = this._offsetToPosition(timeRange.endTimeOffset);
+        if (barRight === 0 || barX === this._canvas.width)
+            return;
+        var barWidth = Math.max(barRight - barX, this._minWidth);
+        var barY = this._levelToHeight(timelineData.entryLevels[entryIndex]) - this._scrollTop;
+        var style = element.style;
+        style.left = barX + "px";
+        style.top = barY + "px";
+        style.width = barWidth + "px";
+        style.height = this._barHeight + "px";
+        this.element.appendChild(element);
+    },
+
+    _offsetToPosition: function(offset)
+    {
+        var value = Math.floor(offset * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
+        return Math.min(this._canvas.width, Math.max(0, value));
+    },
+
+    _levelToHeight: function(level)
+    {
+         return this._baseHeight - level * this._barHeightDelta;
     },
 
     _buildEntryInfo: function(entryInfo)
@@ -747,59 +704,66 @@ WebInspector.FlameChart.prototype = {
 
     _prepareText: function(context, title, maxSize)
     {
-        if (maxSize < this._dotsWidth)
-            return null;
-        var titleWidth = context.measureText(title).width;
+        var titleWidth = this._measureWidth(context, title);
         if (maxSize > titleWidth)
             return title;
-        maxSize -= this._dotsWidth;
-        var dotRegExp=/[\.\$]/g;
-        var match = dotRegExp.exec(title);
-        if (!match) {
-            var visiblePartSize = maxSize / titleWidth;
-            var newTextLength = Math.floor(title.length * visiblePartSize) + 1;
-            var minTextLength = 4;
-            if (newTextLength < minTextLength)
-                return null;
-            var substring;
-            do {
-                --newTextLength;
-                substring = title.substring(0, newTextLength);
-            } while (context.measureText(substring).width > maxSize);
-            return title.substring(0, newTextLength) + "\u2026";
-        }
-        while (match) {
-            var substring = title.substring(match.index + 1);
-            var width = context.measureText(substring).width;
-            if (maxSize > width)
-                return "\u2026" + substring;
-            match = dotRegExp.exec(title);
+
+        var l = 3;
+        var r = title.length;
+        while (l < r) {
+            var m = (l + r) >> 1;
+            if (this._measureWidth(context, title.trimMiddle(m)) < maxSize)
+                l = m + 1;
+            else
+                r = m;
         }
-        var i = 0;
-        do {
-            ++i;
-        } while (context.measureText(title.substring(0, i)).width < maxSize);
-        return title.substring(0, i - 1) + "\u2026";
+        title = title.trimMiddle(r - 1);
+        titleWidth = this._measureWidth(context, title);
+        if (titleWidth <= maxSize)
+            return title;
+        if (maxSize > this._measureWidth(context, "\u2026"))
+            return "\u2026";
+        return "";
     },
 
-    _scheduleUpdate: function()
+    /**
+     * @param {!CanvasRenderingContext2D} context
+     * @param {string} text
+     * @return {number}
+     */
+    _measureWidth: function(context, text)
     {
-        if (this._updateTimerId)
-            return;
-        this._updateTimerId = setTimeout(this.update.bind(this), 10);
+        if (text.length > 20)
+            return context.measureText(text).width;
+
+        var width = this._textWidth[text];
+        if (!width) {
+            width = context.measureText(text).width;
+            this._textWidth[text] = width;
+        }
+        return width;
     },
 
     _updateBoundaries: function()
     {
-        this._windowLeft = this._overviewGrid.windowLeft();
-        this._windowRight = this._overviewGrid.windowRight();
-        this._windowWidth = this._windowRight - this._windowLeft;
-
-        this._totalTime = this._timelineData.totalTime;
-        this._timeWindowLeft = this._windowLeft * this._totalTime;
-        this._timeWindowRight = this._windowRight * this._totalTime;
+        this._totalTime = this._dataProvider.totalTime();
+        this._zeroTime = this._dataProvider.zeroTime();
+        if (this._timeBasedWindow) {
+            if (this._timeWindowRight !== Infinity) {
+                this._windowLeft = (this._timeWindowLeft - this._zeroTime) / this._totalTime;
+                this._windowRight = (this._timeWindowRight - this._zeroTime) / this._totalTime;
+                this._windowWidth = this._windowRight - this._windowLeft;
+            } else {
+                this._windowLeft = 0;
+                this._windowRight = 1;
+                this._windowWidth = 1;
+            }
+        } else {
+            this._timeWindowLeft = this._windowLeft * this._totalTime;
+            this._timeWindowRight = this._windowRight * this._totalTime;
+        }
 
-        this._pixelWindowWidth = this._chartContainer.clientWidth;
+        this._pixelWindowWidth = this._offsetWidth - this._paddingLeft;
         this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth);
         this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft);
         this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight);
@@ -807,27 +771,48 @@ WebInspector.FlameChart.prototype = {
         this._timeToPixel = this._totalPixels / this._totalTime;
         this._pixelToTime = this._totalTime / this._totalPixels;
         this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
+
+        this._baseHeight = this._isTopDown ? WebInspector.FlameChart.DividersBarHeight : this._offsetHeight - this._barHeight;
+
+        var totalHeight = this._levelToHeight(this._dataProvider.maxStackDepth());
+        this._vScrollContent.style.height = totalHeight + "px";
+        this._scrollTop = this._vScrollElement.scrollTop;
+    },
+
+    onResize: function()
+    {
+        this._offsetWidth = this.element.offsetWidth - this._vScrollElement.offsetWidth;
+        this._offsetHeight = this.element.offsetHeight;
+        this._canvas.style.width = this._offsetWidth + "px";
+        this._canvas.style.height = this._offsetHeight + "px";
+        this._scheduleUpdate();
+    },
+
+    _scheduleUpdate: function()
+    {
+        if (this._updateTimerId)
+            return;
+        this._updateTimerId = requestAnimationFrame(this.update.bind(this));
     },
 
     update: function()
     {
         this._updateTimerId = 0;
-        if (!this._timelineData)
-            this._calculateTimelineData();
-        if (!this._timelineData)
+        if (!this._timelineData())
             return;
+        this._resetCanvas();
         this._updateBoundaries();
-        this.draw(this._chartContainer.clientWidth, this._chartContainer.clientHeight);
         this._calculator._updateBoundaries(this);
-        this._overviewCalculator._updateBoundaries(this);
-        this._timelineGrid.element.style.width = this.element.clientWidth;
-        this._timelineGrid.updateDividers(this._calculator);
-        this._overviewGrid.updateDividers(this._overviewCalculator);
-        if (this._updateOverviewCanvas) {
-            this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
-            this._updateOverviewCanvas = false;
-        }
+        this.draw(this._offsetWidth, this._offsetHeight);
     },
 
-    __proto__: WebInspector.View.prototype
-};
+    reset: function()
+    {
+        this._highlightedEntryIndex = -1;
+        this._selectedEntryIndex = -1;
+        this._textWidth = {};
+        this.update();
+    },
+
+    __proto__: WebInspector.HBox.prototype
+}