Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2013 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  * @extends {WebInspector.View}
     34  * @param {WebInspector.CPUProfileView} cpuProfileView
     35  */
     36 WebInspector.FlameChart = function(cpuProfileView)
     37 {
     38     WebInspector.View.call(this);
     39     this.registerRequiredCSS("flameChart.css");
     40     this.element.className = "fill";
     41     this.element.id = "cpu-flame-chart";
     42 
     43     this._overviewContainer = this.element.createChild("div", "overview-container");
     44     this._overviewGrid = new WebInspector.OverviewGrid("flame-chart");
     45     this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas");
     46     this._overviewContainer.appendChild(this._overviewGrid.element);
     47     this._overviewCalculator = new WebInspector.FlameChart.OverviewCalculator();
     48     this._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this);
     49 
     50     this._chartContainer = this.element.createChild("div", "chart-container");
     51     this._timelineGrid = new WebInspector.TimelineGrid();
     52     this._chartContainer.appendChild(this._timelineGrid.element);
     53     this._calculator = new WebInspector.FlameChart.Calculator();
     54 
     55     this._canvas = this._chartContainer.createChild("canvas");
     56     this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this));
     57     WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "col-resize");
     58 
     59     this._cpuProfileView = cpuProfileView;
     60     this._windowLeft = 0.0;
     61     this._windowRight = 1.0;
     62     this._barHeight = 15;
     63     this._minWidth = 2;
     64     this._paddingLeft = 15;
     65     this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false);
     66     this.element.addEventListener("click", this._onClick.bind(this), false);
     67     this._linkifier = new WebInspector.Linkifier();
     68     this._highlightedEntryIndex = -1;
     69 
     70     if (!WebInspector.FlameChart._colorGenerator)
     71         WebInspector.FlameChart._colorGenerator = new WebInspector.FlameChart.ColorGenerator();
     72 }
     73 
     74 /**
     75  * @constructor
     76  * @implements {WebInspector.TimelineGrid.Calculator}
     77  */
     78 WebInspector.FlameChart.Calculator = function()
     79 {
     80 }
     81 
     82 WebInspector.FlameChart.Calculator.prototype = {
     83     /**
     84      * @param {WebInspector.FlameChart} flameChart
     85      */
     86     _updateBoundaries: function(flameChart)
     87     {
     88         this._minimumBoundaries = flameChart._windowLeft * flameChart._timelineData.totalTime;
     89         this._maximumBoundaries = flameChart._windowRight * flameChart._timelineData.totalTime;
     90         this.paddingLeft = flameChart._paddingLeft;
     91         this._width = flameChart._canvas.width - this.paddingLeft;
     92         this._timeToPixel = this._width / this.boundarySpan();
     93     },
     94 
     95     /**
     96      * @param {number} time
     97      */
     98     computePosition: function(time)
     99     {
    100         return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft;
    101     },
    102 
    103     formatTime: function(value)
    104     {
    105         return WebInspector.UIString("%s\u2009ms", Number.withThousandsSeparator(Math.round(value + this._minimumBoundaries)));
    106     },
    107 
    108     maximumBoundary: function()
    109     {
    110         return this._maximumBoundaries;
    111     },
    112 
    113     minimumBoundary: function()
    114     {
    115         return this._minimumBoundaries;
    116     },
    117 
    118     zeroTime: function()
    119     {
    120         return 0;
    121     },
    122 
    123     boundarySpan: function()
    124     {
    125         return this._maximumBoundaries - this._minimumBoundaries;
    126     }
    127 }
    128 
    129 /**
    130  * @constructor
    131  * @implements {WebInspector.TimelineGrid.Calculator}
    132  */
    133 WebInspector.FlameChart.OverviewCalculator = function()
    134 {
    135 }
    136 
    137 WebInspector.FlameChart.OverviewCalculator.prototype = {
    138     /**
    139      * @param {WebInspector.FlameChart} flameChart
    140      */
    141     _updateBoundaries: function(flameChart)
    142     {
    143         this._minimumBoundaries = 0;
    144         this._maximumBoundaries = flameChart._timelineData.totalTime;
    145         this._xScaleFactor = flameChart._canvas.width / flameChart._timelineData.totalTime;
    146     },
    147 
    148     /**
    149      * @param {number} time
    150      */
    151     computePosition: function(time)
    152     {
    153         return (time - this._minimumBoundaries) * this._xScaleFactor;
    154     },
    155 
    156     formatTime: function(value)
    157     {
    158         return Number.secondsToString((value + this._minimumBoundaries) / 1000);
    159     },
    160 
    161     maximumBoundary: function()
    162     {
    163         return this._maximumBoundaries;
    164     },
    165 
    166     minimumBoundary: function()
    167     {
    168         return this._minimumBoundaries;
    169     },
    170 
    171     zeroTime: function()
    172     {
    173         return this._minimumBoundaries;
    174     },
    175 
    176     boundarySpan: function()
    177     {
    178         return this._maximumBoundaries - this._minimumBoundaries;
    179     }
    180 }
    181 
    182 WebInspector.FlameChart.Events = {
    183     SelectedNode: "SelectedNode"
    184 }
    185 
    186 /**
    187  * @constructor
    188  */
    189 WebInspector.FlameChart.ColorGenerator = function()
    190 {
    191     this._colorPairs = {};
    192     this._currentColorIndex = 0;
    193     this._colorPairs["(idle)::0"] = this._createPair(0, 50);
    194     this._colorPairs["(program)::0"] = this._createPair(5, 50);
    195     this._colorPairs["(garbage collector)::0"] = this._createPair(10, 50);
    196 }
    197 
    198 WebInspector.FlameChart.ColorGenerator.prototype = {
    199     /**
    200      * @param {!string} id
    201      */
    202     _colorPairForID: function(id)
    203     {
    204         var colorPairs = this._colorPairs;
    205         var colorPair = colorPairs[id];
    206         if (!colorPair)
    207             colorPairs[id] = colorPair = this._createPair(++this._currentColorIndex);
    208         return colorPair;
    209     },
    210 
    211     /**
    212      * @param {!number} index
    213      * @param {number=} sat
    214      */
    215     _createPair: function(index, sat)
    216     {
    217         var hue = (index * 7 + 12 * (index % 2)) % 360;
    218         if (typeof sat !== "number")
    219             sat = 100;
    220         return {highlighted: "hsla(" + hue + ", " + sat + "%, 33%, 0.7)", normal: "hsla(" + hue + ", " + sat + "%, 66%, 0.7)"}
    221     }
    222 }
    223 
    224 /**
    225  * @constructor
    226  * @param {!Object} colorPair
    227  * @param {!number} depth
    228  * @param {!number} duration
    229  * @param {!number} startTime
    230  * @param {Object} node
    231  */
    232 WebInspector.FlameChart.Entry = function(colorPair, depth, duration, startTime, node)
    233 {
    234     this.colorPair = colorPair;
    235     this.depth = depth;
    236     this.duration = duration;
    237     this.startTime = startTime;
    238     this.node = node;
    239     this.selfTime = 0;
    240 }
    241 
    242 WebInspector.FlameChart.prototype = {
    243     /**
    244      * @param {!number} timeLeft
    245      * @param {!number} timeRight
    246      */
    247     selectRange: function(timeLeft, timeRight)
    248     {
    249         this._overviewGrid.setWindow(timeLeft / this._totalTime, timeRight / this._totalTime);
    250     },
    251 
    252     _onWindowChanged: function(event)
    253     {
    254         this._scheduleUpdate();
    255     },
    256 
    257     _startCanvasDragging: function(event)
    258     {
    259         if (!this._timelineData)
    260             return false;
    261         this._isDragging = true;
    262         this._dragStartPoint = event.pageX;
    263         this._dragStartWindowLeft = this._windowLeft;
    264         this._dragStartWindowRight = this._windowRight;
    265         return true;
    266     },
    267 
    268     _canvasDragging: function(event)
    269     {
    270         var pixelShift = this._dragStartPoint - event.pageX;
    271         var windowShift = pixelShift / this._totalPixels;
    272 
    273         var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift);
    274         if (windowLeft === this._windowLeft)
    275             return;
    276         windowShift = windowLeft - this._dragStartWindowLeft;
    277 
    278         var windowRight = Math.min(1, this._dragStartWindowRight + windowShift);
    279         if (windowRight === this._windowRight)
    280             return;
    281         windowShift = windowRight - this._dragStartWindowRight;
    282         this._overviewGrid.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift);
    283     },
    284 
    285     _endCanvasDragging: function()
    286     {
    287         this._isDragging = false;
    288     },
    289 
    290     _calculateTimelineData: function()
    291     {
    292         if (this._cpuProfileView.samples)
    293             return this._calculateTimelineDataForSamples();
    294 
    295         if (this._timelineData)
    296             return this._timelineData;
    297 
    298         if (!this._cpuProfileView.profileHead)
    299             return null;
    300 
    301         var index = 0;
    302         var entries = [];
    303 
    304         function appendReversedArray(toArray, fromArray)
    305         {
    306             for (var i = fromArray.length - 1; i >= 0; --i)
    307                 toArray.push(fromArray[i]);
    308         }
    309 
    310         var stack = [];
    311         appendReversedArray(stack, this._cpuProfileView.profileHead.children);
    312 
    313         var levelOffsets = /** @type {Array.<!number>} */ ([0]);
    314         var levelExitIndexes = /** @type {Array.<!number>} */ ([0]);
    315         var colorGenerator = WebInspector.FlameChart._colorGenerator;
    316 
    317         while (stack.length) {
    318             var level = levelOffsets.length - 1;
    319             var node = stack.pop();
    320             var offset = levelOffsets[level];
    321 
    322             var colorPair = colorGenerator._colorPairForID(node.functionName + ":" + node.url + ":" + node.lineNumber);
    323 
    324             entries.push(new WebInspector.FlameChart.Entry(colorPair, level, node.totalTime, offset, node));
    325 
    326             ++index;
    327 
    328             levelOffsets[level] += node.totalTime;
    329             if (node.children.length) {
    330                 levelExitIndexes.push(stack.length);
    331                 levelOffsets.push(offset + node.selfTime / 2);
    332                 appendReversedArray(stack, node.children);
    333             }
    334 
    335             while (stack.length === levelExitIndexes[levelExitIndexes.length - 1]) {
    336                 levelOffsets.pop();
    337                 levelExitIndexes.pop();
    338             }
    339         }
    340 
    341         this._timelineData = {
    342             entries: entries,
    343             totalTime: this._cpuProfileView.profileHead.totalTime,
    344         }
    345 
    346         return this._timelineData;
    347     },
    348 
    349     _calculateTimelineDataForSamples: function()
    350     {
    351         if (this._timelineData)
    352             return this._timelineData;
    353 
    354         if (!this._cpuProfileView.profileHead)
    355             return null;
    356 
    357         var samples = this._cpuProfileView.samples;
    358         var idToNode = this._cpuProfileView._idToNode;
    359         var gcNode = this._cpuProfileView._gcNode;
    360         var samplesCount = samples.length;
    361 
    362         var index = 0;
    363         var entries = /** @type {Array.<!WebInspector.FlameChart.Entry>} */ ([]);
    364 
    365         var openIntervals = [];
    366         var stackTrace = [];
    367         var colorGenerator = WebInspector.FlameChart._colorGenerator;
    368         for (var sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) {
    369             var node = idToNode[samples[sampleIndex]];
    370             stackTrace.length = 0;
    371             while (node) {
    372                 stackTrace.push(node);
    373                 node = node.parent;
    374             }
    375             stackTrace.pop(); // Remove (root) node
    376 
    377             var depth = 0;
    378             node = stackTrace.pop();
    379             var intervalIndex;
    380 
    381             // GC samples have no stack, so we just put GC node on top of the last recoreded sample.
    382             if (node === gcNode) {
    383                 while (depth < openIntervals.length) {
    384                     intervalIndex = openIntervals[depth].index;
    385                     entries[intervalIndex].duration += 1;
    386                     ++depth;
    387                 }
    388                 // If previous stack is also GC then just continue.
    389                 if (openIntervals.length > 0 && openIntervals.peekLast().node === node) {
    390                     entries[intervalIndex].selfTime += 1;
    391                     continue;
    392                 }
    393             }
    394 
    395             while (node && depth < openIntervals.length && node === openIntervals[depth].node) {
    396                 intervalIndex = openIntervals[depth].index;
    397                 entries[intervalIndex].duration += 1;
    398                 node = stackTrace.pop();
    399                 ++depth;
    400             }
    401             if (depth < openIntervals.length)
    402                 openIntervals.length = depth;
    403             if (!node) {
    404                 entries[intervalIndex].selfTime += 1;
    405                 continue;
    406             }
    407 
    408             while (node) {
    409                 var colorPair = colorGenerator._colorPairForID(node.functionName + ":" + node.url + ":" + node.lineNumber);
    410 
    411                 entries.push(new WebInspector.FlameChart.Entry(colorPair, depth, 1, sampleIndex, node));
    412                 openIntervals.push({node: node, index: index});
    413                 ++index;
    414 
    415                 node = stackTrace.pop();
    416                 ++depth;
    417             }
    418             entries[entries.length - 1].selfTime += 1;
    419         }
    420 
    421         this._timelineData = {
    422             entries: entries,
    423             totalTime: samplesCount,
    424         };
    425 
    426         return this._timelineData;
    427     },
    428 
    429     _onMouseMove: function(event)
    430     {
    431         if (this._isDragging)
    432             return;
    433 
    434         var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY);
    435 
    436         if (this._highlightedEntryIndex === entryIndex)
    437             return;
    438 
    439         if (entryIndex === -1 || this._timelineData.entries[entryIndex].node.scriptId === "0")
    440             this._canvas.style.cursor = "default";
    441         else
    442             this._canvas.style.cursor = "pointer";
    443 
    444         this._highlightedEntryIndex = entryIndex;
    445         this._scheduleUpdate();
    446     },
    447 
    448     _prepareHighlightedEntryInfo: function()
    449     {
    450         if (this._isDragging)
    451             return null;
    452         var entry = this._timelineData.entries[this._highlightedEntryIndex];
    453         if (!entry)
    454             return null;
    455         var node = entry.node;
    456         if (!node)
    457             return null;
    458 
    459         var entryInfo = [];
    460         function pushEntryInfoRow(title, text)
    461         {
    462             var row = {};
    463             row.title = title;
    464             row.text = text;
    465             entryInfo.push(row);
    466         }
    467 
    468         pushEntryInfoRow(WebInspector.UIString("Name"), node.functionName);
    469         if (this._cpuProfileView.samples) {
    470             pushEntryInfoRow(WebInspector.UIString("Self time"), Number.secondsToString(entry.selfTime / 1000, true));
    471             pushEntryInfoRow(WebInspector.UIString("Total time"), Number.secondsToString(entry.duration / 1000, true));
    472         }
    473         if (node.url)
    474             pushEntryInfoRow(WebInspector.UIString("URL"), node.url + ":" + node.lineNumber);
    475         pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.secondsToString(node.selfTime / 1000, true));
    476         pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.secondsToString(node.totalTime / 1000, true));
    477         return entryInfo;
    478     },
    479 
    480     _onClick: function(e)
    481     {
    482         if (this._highlightedEntryIndex === -1)
    483             return;
    484         var node = this._timelineData.entries[this._highlightedEntryIndex].node;
    485         this.dispatchEventToListeners(WebInspector.FlameChart.Events.SelectedNode, node);
    486     },
    487 
    488     _onMouseWheel: function(e)
    489     {
    490         if (e.wheelDeltaY) {
    491             const zoomFactor = 1.1;
    492             const mouseWheelZoomSpeed = 1 / 120;
    493 
    494             var zoom = Math.pow(zoomFactor, -e.wheelDeltaY * mouseWheelZoomSpeed);
    495             var overviewReference = (this._pixelWindowLeft + e.offsetX - this._paddingLeft) / this._totalPixels;
    496             this._overviewGrid.zoom(zoom, overviewReference);
    497         } else {
    498             var shift = Number.constrain(-1 * this._windowWidth / 4 * e.wheelDeltaX / 120, -this._windowLeft, 1 - this._windowRight);
    499             this._overviewGrid.setWindow(this._windowLeft + shift, this._windowRight + shift);
    500         }
    501     },
    502 
    503     /**
    504      * @param {!number} x
    505      * @param {!number} y
    506      */
    507     _coordinatesToEntryIndex: function(x, y)
    508     {
    509         var timelineData = this._timelineData;
    510         if (!timelineData)
    511             return -1;
    512         var timelineEntries = timelineData.entries;
    513         var cursorTime = (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime;
    514         var cursorLevel = Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight);
    515 
    516         for (var i = 0; i < timelineEntries.length; ++i) {
    517             if (cursorTime < timelineEntries[i].startTime)
    518                 return -1;
    519             if (cursorTime < (timelineEntries[i].startTime + timelineEntries[i].duration)
    520                 && cursorLevel === timelineEntries[i].depth)
    521                 return i;
    522         }
    523         return -1;
    524     },
    525 
    526     onResize: function()
    527     {
    528         this._updateOverviewCanvas = true;
    529         this._scheduleUpdate();
    530     },
    531 
    532     _drawOverviewCanvas: function(width, height)
    533     {
    534         if (!this._timelineData)
    535             return;
    536 
    537         var timelineEntries = this._timelineData.entries;
    538 
    539         var drawData = new Uint8Array(width);
    540         var scaleFactor = width / this._totalTime;
    541         var maxStackDepth = 5; // minimum stack depth for the case when we see no activity.
    542 
    543         for (var entryIndex = 0; entryIndex < timelineEntries.length; ++entryIndex) {
    544             var entry = timelineEntries[entryIndex];
    545             var start = Math.floor(entry.startTime * scaleFactor);
    546             var finish = Math.floor((entry.startTime + entry.duration) * scaleFactor);
    547             for (var x = start; x < finish; ++x) {
    548                 drawData[x] = Math.max(drawData[x], entry.depth + 1);
    549                 maxStackDepth = Math.max(maxStackDepth, entry.depth + 1);
    550             }
    551         }
    552 
    553         var ratio = window.devicePixelRatio;
    554         var canvasWidth = width * ratio;
    555         var canvasHeight = height * ratio;
    556         this._overviewCanvas.width = canvasWidth;
    557         this._overviewCanvas.height = canvasHeight;
    558         this._overviewCanvas.style.width = width + "px";
    559         this._overviewCanvas.style.height = height + "px";
    560 
    561         var context = this._overviewCanvas.getContext("2d");
    562 
    563         var yScaleFactor = canvasHeight / (maxStackDepth * 1.1);
    564         context.lineWidth = 1;
    565         context.translate(0.5, 0.5);
    566         context.strokeStyle = "rgba(20,0,0,0.4)";
    567         context.fillStyle = "rgba(214,225,254,0.8)";
    568         context.moveTo(-1, canvasHeight - 1);
    569         if (drawData)
    570           context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1));
    571         var value;
    572         for (var x = 0; x < width; ++x) {
    573             value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1);
    574             context.lineTo(x * ratio, value);
    575         }
    576         context.lineTo(canvasWidth + 1, value);
    577         context.lineTo(canvasWidth + 1, canvasHeight - 1);
    578         context.fill();
    579         context.stroke();
    580         context.closePath();
    581     },
    582 
    583     /**
    584      * @param {WebInspector.FlameChart.Entry} entry
    585      * @param {AnchorBox} anchorBox
    586      */
    587     _entryToAnchorBox: function(entry, anchorBox)
    588     {
    589         anchorBox.x = Math.floor(entry.startTime * this._timeToPixel) - this._pixelWindowLeft + this._paddingLeft;
    590         anchorBox.y = this._canvas.height / window.devicePixelRatio - (entry.depth + 1) * this._barHeight;
    591         anchorBox.width = Math.max(Math.ceil(entry.duration * this._timeToPixel), this._minWidth);
    592         anchorBox.height = this._barHeight;
    593         if (anchorBox.x < 0) {
    594             anchorBox.width += anchorBox.x;
    595             anchorBox.x = 0;
    596         }
    597         anchorBox.width = Number.constrain(anchorBox.width, 0, this._canvas.width - anchorBox.x);
    598     },
    599 
    600     /**
    601      * @param {!number} height
    602      * @param {!number} width
    603      */
    604     draw: function(width, height)
    605     {
    606         var timelineData = this._calculateTimelineData();
    607         if (!timelineData)
    608             return;
    609         var timelineEntries = timelineData.entries;
    610 
    611         var ratio = window.devicePixelRatio;
    612         var canvasWidth = width * ratio;
    613         var canvasHeight = height * ratio;
    614         this._canvas.width = canvasWidth;
    615         this._canvas.height = canvasHeight;
    616         this._canvas.style.width = width + "px";
    617         this._canvas.style.height = height + "px";
    618 
    619         var barHeight = this._barHeight;
    620 
    621         var context = this._canvas.getContext("2d");
    622         var textPaddingLeft = 2;
    623         context.scale(ratio, ratio);
    624         context.font = (barHeight - 4) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
    625         context.textBaseline = "alphabetic";
    626         this._dotsWidth = context.measureText("\u2026").width;
    627         var visibleTimeLeft = this._timeWindowLeft - this._paddingLeftTime;
    628 
    629         var anchorBox = new AnchorBox();
    630         for (var i = 0; i < timelineEntries.length; ++i) {
    631             var entry = timelineEntries[i];
    632             var startTime = entry.startTime;
    633             if (startTime > this._timeWindowRight)
    634                 break;
    635             if ((startTime + entry.duration) < visibleTimeLeft)
    636                 continue;
    637             this._entryToAnchorBox(entry, anchorBox);
    638 
    639             var colorPair = entry.colorPair;
    640             var color;
    641             if (this._highlightedEntryIndex === i)
    642                 color =  colorPair.highlighted;
    643             else
    644                 color = colorPair.normal;
    645 
    646             context.beginPath();
    647             context.rect(anchorBox.x, anchorBox.y, anchorBox.width - 1, anchorBox.height - 1);
    648             context.fillStyle = color;
    649             context.fill();
    650 
    651             var xText = Math.max(0, anchorBox.x);
    652             var widthText = anchorBox.width - textPaddingLeft + anchorBox.x - xText;
    653             var title = this._prepareText(context, entry.node.functionName, widthText);
    654             if (title) {
    655                 context.fillStyle = "#333";
    656                 context.fillText(title, xText + textPaddingLeft, anchorBox.y + barHeight - 4);
    657             }
    658         }
    659 
    660         var entryInfo = this._prepareHighlightedEntryInfo();
    661         if (entryInfo)
    662             this._printEntryInfo(context, entryInfo, 0, 25, width);
    663     },
    664 
    665     _printEntryInfo: function(context, entryInfo, x, y, width)
    666     {
    667         const lineHeight = 18;
    668         const paddingLeft = 10;
    669         const paddingTop = 5;
    670         var maxTitleWidth = 0;
    671         var basicFont = "100% " + window.getComputedStyle(this.element, null).getPropertyValue("font-family");
    672         context.font = "bold " + basicFont;
    673         context.textBaseline = "top";
    674         for (var i = 0; i < entryInfo.length; ++i)
    675             maxTitleWidth = Math.max(maxTitleWidth, context.measureText(entryInfo[i].title).width);
    676 
    677         var maxTextWidth = 0;
    678         for (var i = 0; i < entryInfo.length; ++i)
    679             maxTextWidth = Math.max(maxTextWidth, context.measureText(entryInfo[i].text).width);
    680         maxTextWidth = Math.min(maxTextWidth, width - 2 * paddingLeft - maxTitleWidth);
    681 
    682         context.beginPath();
    683         context.rect(x, y, maxTitleWidth + maxTextWidth + 5, lineHeight * entryInfo.length + 5);
    684         context.strokeStyle = "rgba(0,0,0,0)";
    685         context.fillStyle = "rgba(254,254,254,0.8)";
    686         context.fill();
    687         context.stroke();
    688 
    689         context.fillStyle = "#333";
    690         for (var i = 0; i < entryInfo.length; ++i)
    691             context.fillText(entryInfo[i].title, x + paddingLeft, y + lineHeight * i);
    692 
    693         context.font = basicFont;
    694         for (var i = 0; i < entryInfo.length; ++i) {
    695             var text = this._prepareText(context, entryInfo[i].text, maxTextWidth);
    696             context.fillText(text, x + paddingLeft + maxTitleWidth + paddingLeft, y + lineHeight * i);
    697         }
    698     },
    699 
    700     _prepareText: function(context, title, maxSize)
    701     {
    702         if (maxSize < this._dotsWidth)
    703             return null;
    704         var titleWidth = context.measureText(title).width;
    705         if (maxSize > titleWidth)
    706             return title;
    707         maxSize -= this._dotsWidth;
    708         var dotRegExp=/[\.\$]/g;
    709         var match = dotRegExp.exec(title);
    710         if (!match) {
    711             var visiblePartSize = maxSize / titleWidth;
    712             var newTextLength = Math.floor(title.length * visiblePartSize) + 1;
    713             var minTextLength = 4;
    714             if (newTextLength < minTextLength)
    715                 return null;
    716             var substring;
    717             do {
    718                 --newTextLength;
    719                 substring = title.substring(0, newTextLength);
    720             } while (context.measureText(substring).width > maxSize);
    721             return title.substring(0, newTextLength) + "\u2026";
    722         }
    723         while (match) {
    724             var substring = title.substring(match.index + 1);
    725             var width = context.measureText(substring).width;
    726             if (maxSize > width)
    727                 return "\u2026" + substring;
    728             match = dotRegExp.exec(title);
    729         }
    730         var i = 0;
    731         do {
    732             ++i;
    733         } while (context.measureText(title.substring(0, i)).width < maxSize);
    734         return title.substring(0, i - 1) + "\u2026";
    735     },
    736 
    737     _scheduleUpdate: function()
    738     {
    739         if (this._updateTimerId)
    740             return;
    741         this._updateTimerId = setTimeout(this.update.bind(this), 10);
    742     },
    743 
    744     _updateBoundaries: function()
    745     {
    746         this._windowLeft = this._overviewGrid.windowLeft();
    747         this._windowRight = this._overviewGrid.windowRight();
    748         this._windowWidth = this._windowRight - this._windowLeft;
    749 
    750         this._totalTime = this._timelineData.totalTime;
    751         this._timeWindowLeft = this._windowLeft * this._totalTime;
    752         this._timeWindowRight = this._windowRight * this._totalTime;
    753 
    754         this._pixelWindowWidth = this._chartContainer.clientWidth;
    755         this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth);
    756         this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft);
    757         this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight);
    758 
    759         this._timeToPixel = this._totalPixels / this._totalTime;
    760         this._pixelToTime = this._totalTime / this._totalPixels;
    761         this._paddingLeftTime = this._paddingLeft / this._timeToPixel;
    762     },
    763 
    764     update: function()
    765     {
    766         this._updateTimerId = 0;
    767         if (!this._timelineData)
    768             this._calculateTimelineData();
    769         if (!this._timelineData)
    770             return;
    771         this._updateBoundaries();
    772         this.draw(this._chartContainer.clientWidth, this._chartContainer.clientHeight);
    773         this._calculator._updateBoundaries(this);
    774         this._overviewCalculator._updateBoundaries(this);
    775         this._timelineGrid.element.style.width = this.element.clientWidth;
    776         this._timelineGrid.updateDividers(this._calculator);
    777         this._overviewGrid.updateDividers(this._overviewCalculator);
    778         if (this._updateOverviewCanvas) {
    779             this._drawOverviewCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20);
    780             this._updateOverviewCanvas = false;
    781         }
    782     },
    783 
    784     __proto__: WebInspector.View.prototype
    785 };
    786