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.FlameChartDataProvider} dataProvider 35 */ 36WebInspector.FlameChart = function(dataProvider) 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._overviewPane = new WebInspector.FlameChart.OverviewPane(dataProvider); 44 this._overviewPane.show(this.element); 45 46 this._mainPane = new WebInspector.FlameChart.MainPane(dataProvider, this._overviewPane); 47 this._mainPane.show(this.element); 48 this._mainPane.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); 49 this._overviewPane._overviewGrid.addEventListener(WebInspector.OverviewGrid.Events.WindowChanged, this._onWindowChanged, this); 50 51 if (!WebInspector.FlameChart._colorGenerator) 52 WebInspector.FlameChart._colorGenerator = new WebInspector.FlameChart.ColorGenerator(); 53} 54 55WebInspector.FlameChart.prototype = { 56 /** 57 * @param {!WebInspector.Event} event 58 */ 59 _onWindowChanged: function(event) 60 { 61 this._mainPane.changeWindow(this._overviewPane._overviewGrid.windowLeft(), this._overviewPane._overviewGrid.windowRight()); 62 }, 63 64 /** 65 * @param {!number} timeLeft 66 * @param {!number} timeRight 67 */ 68 selectRange: function(timeLeft, timeRight) 69 { 70 this._overviewPane._selectRange(timeLeft, timeRight); 71 }, 72 73 /** 74 * @param {!WebInspector.Event} event 75 */ 76 _onEntrySelected: function(event) 77 { 78 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, event.data); 79 }, 80 81 update: function() 82 { 83 this._overviewPane.update(); 84 this._mainPane.update(); 85 }, 86 87 __proto__: WebInspector.View.prototype 88}; 89 90/** 91 * @interface 92 */ 93WebInspector.FlameChartDataProvider = function() 94{ 95} 96 97WebInspector.FlameChartDataProvider.prototype = { 98 /** 99 * @param {!WebInspector.FlameChart.ColorGenerator} colorGenerator 100 * @return {!Object} 101 */ 102 timelineData: function(colorGenerator) { }, 103 104 /** 105 * @param {number} entryIndex 106 */ 107 prepareHighlightedEntryInfo: function(entryIndex) { }, 108 109 /** 110 * @param {number} entryIndex 111 * @return {boolean} 112 */ 113 canJumpToEntry: function(entryIndex) { }, 114 115 /** 116 * @param {number} entryIndex 117 * @return {!Object} 118 */ 119 entryData: function(entryIndex) { } 120} 121 122/** 123 * @constructor 124 * @implements {WebInspector.TimelineGrid.Calculator} 125 */ 126WebInspector.FlameChart.Calculator = function() 127{ 128} 129 130WebInspector.FlameChart.Calculator.prototype = { 131 /** 132 * @param {!WebInspector.FlameChart.MainPane} mainPane 133 */ 134 _updateBoundaries: function(mainPane) 135 { 136 function log10(x) 137 { 138 return Math.log(x) / Math.LN10; 139 } 140 this._decimalDigits = Math.max(0, -Math.floor(log10(mainPane._timelineGrid.gridSliceTime * 1.01))); 141 var totalTime = mainPane._timelineData().totalTime; 142 this._minimumBoundaries = mainPane._windowLeft * totalTime; 143 this._maximumBoundaries = mainPane._windowRight * totalTime; 144 this.paddingLeft = mainPane._paddingLeft; 145 this._width = mainPane._canvas.width - this.paddingLeft; 146 this._timeToPixel = this._width / this.boundarySpan(); 147 }, 148 149 /** 150 * @param {number} time 151 * @return {number} 152 */ 153 computePosition: function(time) 154 { 155 return (time - this._minimumBoundaries) * this._timeToPixel + this.paddingLeft; 156 }, 157 158 /** 159 * @param {number} value 160 * @param {boolean=} hires 161 * @return {string} 162 */ 163 formatTime: function(value, hires) 164 { 165 var format = "%." + this._decimalDigits + "f\u2009ms"; 166 return WebInspector.UIString(format, value + this._minimumBoundaries); 167 }, 168 169 /** 170 * @return {number} 171 */ 172 maximumBoundary: function() 173 { 174 return this._maximumBoundaries; 175 }, 176 177 /** 178 * @return {number} 179 */ 180 minimumBoundary: function() 181 { 182 return this._minimumBoundaries; 183 }, 184 185 /** 186 * @return {number} 187 */ 188 zeroTime: function() 189 { 190 return 0; 191 }, 192 193 /** 194 * @return {number} 195 */ 196 boundarySpan: function() 197 { 198 return this._maximumBoundaries - this._minimumBoundaries; 199 } 200} 201 202/** 203 * @constructor 204 * @implements {WebInspector.TimelineGrid.Calculator} 205 */ 206WebInspector.FlameChart.OverviewCalculator = function() 207{ 208} 209 210WebInspector.FlameChart.OverviewCalculator.prototype = { 211 /** 212 * @param {!WebInspector.FlameChart.OverviewPane} overviewPane 213 */ 214 _updateBoundaries: function(overviewPane) 215 { 216 this._minimumBoundaries = 0; 217 var totalTime = overviewPane._timelineData().totalTime; 218 this._maximumBoundaries = totalTime; 219 this._xScaleFactor = overviewPane._overviewCanvas.width / totalTime; 220 }, 221 222 /** 223 * @param {number} time 224 * @return {number} 225 */ 226 computePosition: function(time) 227 { 228 return (time - this._minimumBoundaries) * this._xScaleFactor; 229 }, 230 231 /** 232 * @param {number} value 233 * @param {boolean=} hires 234 * @return {string} 235 */ 236 formatTime: function(value, hires) 237 { 238 return Number.secondsToString((value + this._minimumBoundaries) / 1000, hires); 239 }, 240 241 /** 242 * @return {number} 243 */ 244 maximumBoundary: function() 245 { 246 return this._maximumBoundaries; 247 }, 248 249 /** 250 * @return {number} 251 */ 252 minimumBoundary: function() 253 { 254 return this._minimumBoundaries; 255 }, 256 257 /** 258 * @return {number} 259 */ 260 zeroTime: function() 261 { 262 return this._minimumBoundaries; 263 }, 264 265 /** 266 * @return {number} 267 */ 268 boundarySpan: function() 269 { 270 return this._maximumBoundaries - this._minimumBoundaries; 271 } 272} 273 274WebInspector.FlameChart.Events = { 275 EntrySelected: "EntrySelected" 276} 277 278/** 279 * @constructor 280 */ 281WebInspector.FlameChart.ColorGenerator = function() 282{ 283 this._colorPairs = {}; 284 this._colorIndexes = []; 285 this._currentColorIndex = 0; 286 this._colorPairForID("(idle)::0", 50); 287 this._colorPairForID("(program)::0", 50); 288 this._colorPairForID("(garbage collector)::0", 50); 289} 290 291WebInspector.FlameChart.ColorGenerator.prototype = { 292 /** 293 * @param {!string} id 294 * @param {number=} sat 295 */ 296 _colorPairForID: function(id, sat) 297 { 298 if (typeof sat !== "number") 299 sat = 100; 300 var colorPairs = this._colorPairs; 301 var colorPair = colorPairs[id]; 302 if (!colorPair) { 303 colorPairs[id] = colorPair = this._createPair(this._currentColorIndex++, sat); 304 this._colorIndexes[colorPair.index] = colorPair; 305 } 306 return colorPair; 307 }, 308 309 /** 310 * @param {!number} index 311 */ 312 _colorPairForIndex: function(index) 313 { 314 return this._colorIndexes[index]; 315 }, 316 317 /** 318 * @param {!number} index 319 * @param {!number} sat 320 */ 321 _createPair: function(index, sat) 322 { 323 var hue = (index * 7 + 12 * (index % 2)) % 360; 324 return {index: index, highlighted: "hsla(" + hue + ", " + sat + "%, 33%, 0.7)", normal: "hsla(" + hue + ", " + sat + "%, 66%, 0.7)"} 325 } 326} 327 328/** 329 * @interface 330 */ 331WebInspector.FlameChart.OverviewPaneInterface = function() 332{ 333} 334 335WebInspector.FlameChart.OverviewPaneInterface.prototype = { 336 /** 337 * @param {number} zoom 338 * @param {number} referencePoint 339 */ 340 zoom: function(zoom, referencePoint) { }, 341 342 /** 343 * @param {number} windowLeft 344 * @param {number} windowRight 345 */ 346 setWindow: function(windowLeft, windowRight) { }, 347} 348 349/** 350 * @constructor 351 * @extends {WebInspector.View} 352 * @implements {WebInspector.FlameChart.OverviewPaneInterface} 353 * @param {!WebInspector.FlameChartDataProvider} dataProvider 354 */ 355WebInspector.FlameChart.OverviewPane = function(dataProvider) 356{ 357 WebInspector.View.call(this); 358 this._overviewContainer = this.element.createChild("div", "overview-container"); 359 this._overviewGrid = new WebInspector.OverviewGrid("flame-chart"); 360 this._overviewGrid.element.classList.add("fill"); 361 this._overviewCanvas = this._overviewContainer.createChild("canvas", "flame-chart-overview-canvas"); 362 this._overviewContainer.appendChild(this._overviewGrid.element); 363 this._overviewCalculator = new WebInspector.FlameChart.OverviewCalculator(); 364 this._dataProvider = dataProvider; 365} 366 367WebInspector.FlameChart.OverviewPane.prototype = { 368 /** 369 * @param {number} zoom 370 * @param {number} referencePoint 371 */ 372 zoom: function(zoom, referencePoint) 373 { 374 this._overviewGrid.zoom(zoom, referencePoint); 375 }, 376 377 /** 378 * @param {number} windowLeft 379 * @param {number} windowRight 380 */ 381 setWindow: function(windowLeft, windowRight) 382 { 383 this._overviewGrid.setWindow(windowLeft, windowRight); 384 }, 385 386 /** 387 * @param {!number} timeLeft 388 * @param {!number} timeRight 389 */ 390 _selectRange: function(timeLeft, timeRight) 391 { 392 var timelineData = this._timelineData(); 393 if (!timelineData) 394 return; 395 this._overviewGrid.setWindow(timeLeft / timelineData._totalTime, timeRight / timelineData._totalTime); 396 }, 397 398 _timelineData: function() 399 { 400 return this._dataProvider.timelineData(WebInspector.FlameChart._colorGenerator); 401 }, 402 403 onResize: function() 404 { 405 this._scheduleUpdate(); 406 }, 407 408 _scheduleUpdate: function() 409 { 410 if (this._updateTimerId) 411 return; 412 this._updateTimerId = setTimeout(this.update.bind(this), 10); 413 }, 414 415 update: function() 416 { 417 this._updateTimerId = 0; 418 var timelineData = this._timelineData(); 419 if (!timelineData) 420 return; 421 this._resetCanvas(this._overviewContainer.clientWidth, this._overviewContainer.clientHeight - 20); 422 this._overviewCalculator._updateBoundaries(this); 423 this._overviewGrid.updateDividers(this._overviewCalculator); 424 WebInspector.FlameChart.OverviewPane.drawOverviewCanvas( 425 timelineData, 426 this._overviewCanvas.getContext("2d"), 427 this._overviewContainer.clientWidth, 428 this._overviewContainer.clientHeight - 20 429 ); 430 }, 431 432 /** 433 * @param {!number} width 434 * @param {!number} height 435 */ 436 _resetCanvas: function(width, height) 437 { 438 var ratio = window.devicePixelRatio; 439 this._overviewCanvas.width = width * ratio; 440 this._overviewCanvas.height = height * ratio; 441 }, 442 443 __proto__: WebInspector.View.prototype 444} 445 446/** 447 * @param {!Object} timelineData 448 * @param {!number} width 449 */ 450WebInspector.FlameChart.OverviewPane.calculateDrawData = function(timelineData, width) 451{ 452 var entryOffsets = timelineData.entryOffsets; 453 var entryTotalTimes = timelineData.entryTotalTimes; 454 var entryLevels = timelineData.entryLevels; 455 var length = entryOffsets.length; 456 457 var drawData = new Uint8Array(width); 458 var scaleFactor = width / timelineData.totalTime; 459 460 for (var entryIndex = 0; entryIndex < length; ++entryIndex) { 461 var start = Math.floor(entryOffsets[entryIndex] * scaleFactor); 462 var finish = Math.floor((entryOffsets[entryIndex] + entryTotalTimes[entryIndex]) * scaleFactor); 463 for (var x = start; x <= finish; ++x) 464 drawData[x] = Math.max(drawData[x], entryLevels[entryIndex] + 1); 465 } 466 return drawData; 467} 468 469/** 470 * @param {!Object} timelineData 471 * @param {!Object} context 472 * @param {!number} width 473 * @param {!number} height 474 */ 475WebInspector.FlameChart.OverviewPane.drawOverviewCanvas = function(timelineData, context, width, height) 476{ 477 var drawData = WebInspector.FlameChart.OverviewPane.calculateDrawData(timelineData, width); 478 if (!drawData) 479 return; 480 481 var ratio = window.devicePixelRatio; 482 var canvasWidth = width * ratio; 483 var canvasHeight = height * ratio; 484 485 var yScaleFactor = canvasHeight / (timelineData.maxStackDepth * 1.1); 486 context.lineWidth = 1; 487 context.translate(0.5, 0.5); 488 context.strokeStyle = "rgba(20,0,0,0.4)"; 489 context.fillStyle = "rgba(214,225,254,0.8)"; 490 context.moveTo(-1, canvasHeight - 1); 491 if (drawData) 492 context.lineTo(-1, Math.round(height - drawData[0] * yScaleFactor - 1)); 493 var value; 494 for (var x = 0; x < width; ++x) { 495 value = Math.round(canvasHeight - drawData[x] * yScaleFactor - 1); 496 context.lineTo(x * ratio, value); 497 } 498 context.lineTo(canvasWidth + 1, value); 499 context.lineTo(canvasWidth + 1, canvasHeight - 1); 500 context.fill(); 501 context.stroke(); 502 context.closePath(); 503} 504 505/** 506 * @constructor 507 * @extends {WebInspector.View} 508 * @param {!WebInspector.FlameChartDataProvider} dataProvider 509 * @param {!WebInspector.FlameChart.OverviewPaneInterface} overviewPane 510 */ 511WebInspector.FlameChart.MainPane = function(dataProvider, overviewPane) 512{ 513 WebInspector.View.call(this); 514 this._overviewPane = overviewPane; 515 this._chartContainer = this.element.createChild("div", "chart-container"); 516 this._timelineGrid = new WebInspector.TimelineGrid(); 517 this._chartContainer.appendChild(this._timelineGrid.element); 518 this._calculator = new WebInspector.FlameChart.Calculator(); 519 520 this._canvas = this._chartContainer.createChild("canvas"); 521 this._canvas.addEventListener("mousemove", this._onMouseMove.bind(this)); 522 this._canvas.addEventListener("mousewheel", this._onMouseWheel.bind(this), false); 523 this._canvas.addEventListener("click", this._onClick.bind(this), false); 524 WebInspector.installDragHandle(this._canvas, this._startCanvasDragging.bind(this), this._canvasDragging.bind(this), this._endCanvasDragging.bind(this), "col-resize"); 525 526 this._entryInfo = this._chartContainer.createChild("div", "entry-info"); 527 528 this._dataProvider = dataProvider; 529 530 this._windowLeft = 0.0; 531 this._windowRight = 1.0; 532 this._windowWidth = 1.0; 533 this._barHeight = 15; 534 this._minWidth = 1; 535 this._paddingLeft = 15; 536 this._highlightedEntryIndex = -1; 537} 538 539WebInspector.FlameChart.MainPane.prototype = { 540 _timelineData: function() 541 { 542 return this._dataProvider.timelineData(WebInspector.FlameChart._colorGenerator); 543 }, 544 545 /** 546 * @param {!number} windowLeft 547 * @param {!number} windowRight 548 */ 549 changeWindow: function(windowLeft, windowRight) 550 { 551 this._windowLeft = windowLeft; 552 this._windowRight = windowRight; 553 this._windowWidth = this._windowRight - this._windowLeft; 554 555 this._scheduleUpdate(); 556 }, 557 558 /** 559 * @param {!MouseEvent} event 560 */ 561 _startCanvasDragging: function(event) 562 { 563 if (!this._timelineData()) 564 return false; 565 this._isDragging = true; 566 this._wasDragged = false; 567 this._dragStartPoint = event.pageX; 568 this._dragStartWindowLeft = this._windowLeft; 569 this._dragStartWindowRight = this._windowRight; 570 571 return true; 572 }, 573 574 /** 575 * @param {!MouseEvent} event 576 */ 577 _canvasDragging: function(event) 578 { 579 var pixelShift = this._dragStartPoint - event.pageX; 580 var windowShift = pixelShift / this._totalPixels; 581 582 var windowLeft = Math.max(0, this._dragStartWindowLeft + windowShift); 583 if (windowLeft === this._windowLeft) 584 return; 585 windowShift = windowLeft - this._dragStartWindowLeft; 586 587 var windowRight = Math.min(1, this._dragStartWindowRight + windowShift); 588 if (windowRight === this._windowRight) 589 return; 590 windowShift = windowRight - this._dragStartWindowRight; 591 this._overviewPane.setWindow(this._dragStartWindowLeft + windowShift, this._dragStartWindowRight + windowShift); 592 this._wasDragged = true; 593 }, 594 595 _endCanvasDragging: function() 596 { 597 this._isDragging = false; 598 }, 599 600 /** 601 * @param {?MouseEvent} event 602 */ 603 _onMouseMove: function(event) 604 { 605 if (this._isDragging) 606 return; 607 608 var entryIndex = this._coordinatesToEntryIndex(event.offsetX, event.offsetY); 609 610 if (this._highlightedEntryIndex === entryIndex) 611 return; 612 613 if (entryIndex === -1 || !this._dataProvider.canJumpToEntry(entryIndex)) 614 this._canvas.style.cursor = "default"; 615 else 616 this._canvas.style.cursor = "pointer"; 617 618 this._highlightedEntryIndex = entryIndex; 619 this._scheduleUpdate(); 620 }, 621 622 _onClick: function() 623 { 624 // onClick comes after dragStart and dragEnd events. 625 // So if there was drag (mouse move) in the middle of that events 626 // we skip the click. Otherwise we jump to the sources. 627 if (this._wasDragged) 628 return; 629 if (this._highlightedEntryIndex === -1) 630 return; 631 var data = this._dataProvider.entryData(this._highlightedEntryIndex); 632 this.dispatchEventToListeners(WebInspector.FlameChart.Events.EntrySelected, data); 633 }, 634 635 /** 636 * @param {?MouseEvent} e 637 */ 638 _onMouseWheel: function(e) 639 { 640 if (e.wheelDeltaY) { 641 const zoomFactor = 1.1; 642 const mouseWheelZoomSpeed = 1 / 120; 643 644 var zoom = Math.pow(zoomFactor, -e.wheelDeltaY * mouseWheelZoomSpeed); 645 var referencePoint = (this._pixelWindowLeft + e.offsetX - this._paddingLeft) / this._totalPixels; 646 this._overviewPane.zoom(zoom, referencePoint); 647 } else { 648 var shift = Number.constrain(-1 * this._windowWidth / 4 * e.wheelDeltaX / 120, -this._windowLeft, 1 - this._windowRight); 649 this._overviewPane.setWindow(this._windowLeft + shift, this._windowRight + shift); 650 } 651 }, 652 653 /** 654 * @param {!number} x 655 * @param {!number} y 656 */ 657 _coordinatesToEntryIndex: function(x, y) 658 { 659 var timelineData = this._timelineData(); 660 if (!timelineData) 661 return -1; 662 var cursorTime = (x + this._pixelWindowLeft - this._paddingLeft) * this._pixelToTime; 663 var cursorLevel = Math.floor((this._canvas.height / window.devicePixelRatio - y) / this._barHeight); 664 665 var entryOffsets = timelineData.entryOffsets; 666 var entryTotalTimes = timelineData.entryTotalTimes; 667 var entryLevels = timelineData.entryLevels; 668 var length = entryOffsets.length; 669 for (var i = 0; i < length; ++i) { 670 if (cursorTime < entryOffsets[i]) 671 return -1; 672 if (cursorTime < (entryOffsets[i] + entryTotalTimes[i]) 673 && cursorLevel === entryLevels[i]) 674 return i; 675 } 676 return -1; 677 }, 678 679 /** 680 * @param {!number} height 681 * @param {!number} width 682 */ 683 draw: function(width, height) 684 { 685 var timelineData = this._timelineData(); 686 if (!timelineData) 687 return; 688 689 var ratio = window.devicePixelRatio; 690 this._canvas.width = width * ratio; 691 this._canvas.height = height * ratio; 692 this._canvas.style.width = width + "px"; 693 this._canvas.style.height = height + "px"; 694 695 var context = this._canvas.getContext("2d"); 696 context.scale(ratio, ratio); 697 var timeWindowRight = this._timeWindowRight; 698 var timeToPixel = this._timeToPixel; 699 var pixelWindowLeft = this._pixelWindowLeft; 700 var paddingLeft = this._paddingLeft; 701 var minWidth = this._minWidth; 702 var entryTotalTimes = timelineData.entryTotalTimes; 703 var entryOffsets = timelineData.entryOffsets; 704 var entryLevels = timelineData.entryLevels; 705 var colorEntryIndexes = timelineData.colorEntryIndexes; 706 var entryTitles = timelineData.entryTitles; 707 var entryDeoptFlags = timelineData.entryDeoptFlags; 708 709 var colorGenerator = WebInspector.FlameChart._colorGenerator; 710 var titleIndexes = new Uint32Array(timelineData.entryTotalTimes); 711 var lastTitleIndex = 0; 712 var dotsWidth = context.measureText("\u2026").width; 713 var textPaddingLeft = 2; 714 this._minTextWidth = context.measureText("\u2026").width + textPaddingLeft; 715 var minTextWidth = this._minTextWidth; 716 717 var marksField = []; 718 for (var i = 0; i < timelineData.maxStackDepth; ++i) 719 marksField.push(new Uint16Array(width)); 720 721 var barHeight = this._barHeight; 722 var barX = 0; 723 var barWidth = 0; 724 var barRight = 0; 725 var barLevel = 0; 726 var bHeight = height - barHeight; 727 context.strokeStyle = "black"; 728 var colorPair; 729 var entryIndex = 0; 730 var entryOffset = 0; 731 for (var colorIndex = 0; colorIndex < colorEntryIndexes.length; ++colorIndex) { 732 colorPair = colorGenerator._colorPairForIndex(colorIndex); 733 context.fillStyle = colorPair.normal; 734 var indexes = colorEntryIndexes[colorIndex]; 735 if (!indexes) 736 continue; 737 context.beginPath(); 738 for (var i = 0; i < indexes.length; ++i) { 739 entryIndex = indexes[i]; 740 entryOffset = entryOffsets[entryIndex]; 741 if (entryOffset > timeWindowRight) 742 break; 743 barX = Math.ceil(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft; 744 if (barX >= width) 745 continue; 746 barRight = Math.floor((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft; 747 if (barRight < 0) 748 continue; 749 barWidth = (barRight - barX) || minWidth; 750 barLevel = entryLevels[entryIndex]; 751 var marksRow = marksField[barLevel]; 752 if (barWidth <= marksRow[barX]) 753 continue; 754 marksRow[barX] = barWidth; 755 if (entryIndex === this._highlightedEntryIndex) { 756 context.fill(); 757 context.beginPath(); 758 context.fillStyle = colorPair.highlighted; 759 } 760 context.rect(barX, bHeight - barLevel * barHeight, barWidth, barHeight); 761 if (entryIndex === this._highlightedEntryIndex) { 762 context.fill(); 763 context.beginPath(); 764 context.fillStyle = colorPair.normal; 765 } 766 if (barWidth > minTextWidth) 767 titleIndexes[lastTitleIndex++] = entryIndex; 768 } 769 context.fill(); 770 } 771 772 var font = (barHeight - 4) + "px " + window.getComputedStyle(this.element, null).getPropertyValue("font-family"); 773 var boldFont = "bold " + font; 774 var isBoldFontSelected = false; 775 context.font = font; 776 context.textBaseline = "alphabetic"; 777 context.fillStyle = "#333"; 778 this._dotsWidth = context.measureText("\u2026").width; 779 780 var textBaseHeight = bHeight + barHeight - 4; 781 for (var i = 0; i < lastTitleIndex; ++i) { 782 entryIndex = titleIndexes[i]; 783 if (isBoldFontSelected) { 784 if (!entryDeoptFlags[entryIndex]) { 785 context.font = font; 786 isBoldFontSelected = false; 787 } 788 } else { 789 if (entryDeoptFlags[entryIndex]) { 790 context.font = boldFont; 791 isBoldFontSelected = true; 792 } 793 } 794 795 entryOffset = entryOffsets[entryIndex]; 796 barX = Math.floor(entryOffset * timeToPixel) - pixelWindowLeft + paddingLeft; 797 barRight = Math.ceil((entryOffset + entryTotalTimes[entryIndex]) * timeToPixel) - pixelWindowLeft + paddingLeft; 798 barWidth = (barRight - barX) || minWidth; 799 var xText = Math.max(0, barX); 800 var widthText = barWidth - textPaddingLeft + barX - xText; 801 var title = this._prepareText(context, entryTitles[entryIndex], widthText); 802 if (title) 803 context.fillText(title, xText + textPaddingLeft, textBaseHeight - entryLevels[entryIndex] * barHeight); 804 } 805 806 this._entryInfo.removeChildren(); 807 if (!this._isDragging) { 808 var entryInfo = this._dataProvider.prepareHighlightedEntryInfo(this._highlightedEntryIndex); 809 if (entryInfo) 810 this._entryInfo.appendChild(this._buildEntryInfo(entryInfo)); 811 } 812 }, 813 814 _buildEntryInfo: function(entryInfo) 815 { 816 var infoTable = document.createElement("table"); 817 infoTable.className = "info-table"; 818 for (var i = 0; i < entryInfo.length; ++i) { 819 var row = infoTable.createChild("tr"); 820 var titleCell = row.createChild("td"); 821 titleCell.textContent = entryInfo[i].title; 822 titleCell.className = "title"; 823 var textCell = row.createChild("td"); 824 textCell.textContent = entryInfo[i].text; 825 } 826 return infoTable; 827 }, 828 829 _prepareText: function(context, title, maxSize) 830 { 831 if (maxSize < this._dotsWidth) 832 return null; 833 var titleWidth = context.measureText(title).width; 834 if (maxSize > titleWidth) 835 return title; 836 maxSize -= this._dotsWidth; 837 var dotRegExp=/[\.\$]/g; 838 var match = dotRegExp.exec(title); 839 if (!match) { 840 var visiblePartSize = maxSize / titleWidth; 841 var newTextLength = Math.floor(title.length * visiblePartSize) + 1; 842 var minTextLength = 4; 843 if (newTextLength < minTextLength) 844 return null; 845 var substring; 846 do { 847 --newTextLength; 848 substring = title.substring(0, newTextLength); 849 } while (context.measureText(substring).width > maxSize); 850 return title.substring(0, newTextLength) + "\u2026"; 851 } 852 while (match) { 853 var substring = title.substring(match.index + 1); 854 var width = context.measureText(substring).width; 855 if (maxSize > width) 856 return "\u2026" + substring; 857 match = dotRegExp.exec(title); 858 } 859 var i = 0; 860 do { 861 ++i; 862 } while (context.measureText(title.substring(0, i)).width < maxSize); 863 return title.substring(0, i - 1) + "\u2026"; 864 }, 865 866 _updateBoundaries: function() 867 { 868 this._totalTime = this._timelineData().totalTime; 869 this._timeWindowLeft = this._windowLeft * this._totalTime; 870 this._timeWindowRight = this._windowRight * this._totalTime; 871 872 this._pixelWindowWidth = this._chartContainer.clientWidth - this._paddingLeft; 873 this._totalPixels = Math.floor(this._pixelWindowWidth / this._windowWidth); 874 this._pixelWindowLeft = Math.floor(this._totalPixels * this._windowLeft); 875 this._pixelWindowRight = Math.floor(this._totalPixels * this._windowRight); 876 877 this._timeToPixel = this._totalPixels / this._totalTime; 878 this._pixelToTime = this._totalTime / this._totalPixels; 879 this._paddingLeftTime = this._paddingLeft / this._timeToPixel; 880 }, 881 882 onResize: function() 883 { 884 this._scheduleUpdate(); 885 }, 886 887 _scheduleUpdate: function() 888 { 889 if (this._updateTimerId) 890 return; 891 this._updateTimerId = setTimeout(this.update.bind(this), 10); 892 }, 893 894 update: function() 895 { 896 this._updateTimerId = 0; 897 if (!this._timelineData()) 898 return; 899 this._updateBoundaries(); 900 this.draw(this._chartContainer.clientWidth, this._chartContainer.clientHeight); 901 this._calculator._updateBoundaries(this); 902 this._timelineGrid.element.style.width = this.element.clientWidth; 903 this._timelineGrid.updateDividers(this._calculator); 904 }, 905 906 __proto__: WebInspector.View.prototype 907} 908