1 /* 2 * Copyright (C) 2014 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 /** 32 * @constructor 33 * @implements {WebInspector.FlameChartDataProvider} 34 * @implements {WebInspector.TimelineFlameChart.SelectionProvider} 35 * @param {!WebInspector.TimelineModelImpl} model 36 * @param {!WebInspector.TimelineFrameModelBase} frameModel 37 */ 38 WebInspector.TimelineFlameChartDataProvider = function(model, frameModel) 39 { 40 WebInspector.FlameChartDataProvider.call(this); 41 this._model = model; 42 this._frameModel = frameModel; 43 this._font = "12px " + WebInspector.fontFamily(); 44 this._linkifier = new WebInspector.Linkifier(); 45 } 46 47 WebInspector.TimelineFlameChartDataProvider.prototype = { 48 /** 49 * @return {number} 50 */ 51 barHeight: function() 52 { 53 return 20; 54 }, 55 56 /** 57 * @return {number} 58 */ 59 textBaseline: function() 60 { 61 return 6; 62 }, 63 64 /** 65 * @return {number} 66 */ 67 textPadding: function() 68 { 69 return 5; 70 }, 71 72 /** 73 * @param {number} entryIndex 74 * @return {string} 75 */ 76 entryFont: function(entryIndex) 77 { 78 return this._font; 79 }, 80 81 /** 82 * @param {number} entryIndex 83 * @return {?string} 84 */ 85 entryTitle: function(entryIndex) 86 { 87 var record = this._records[entryIndex]; 88 if (record === this._cpuThreadRecord) 89 return WebInspector.UIString("CPU"); 90 else if (record === this._gpuThreadRecord) 91 return WebInspector.UIString("GPU"); 92 var details = WebInspector.TimelineUIUtilsImpl.buildDetailsNode(record, this._linkifier, this._model.loadedFromFile()); 93 var title = WebInspector.TimelineUIUtilsImpl.recordTitle(record); 94 return details ? WebInspector.UIString("%s (%s)", title, details.textContent) : title; 95 }, 96 97 /** 98 * @param {number} startTime 99 * @param {number} endTime 100 * @return {?Array.<number>} 101 */ 102 dividerOffsets: function(startTime, endTime) 103 { 104 // While we have tracing and timeline flame chart on screen at a time, 105 // we don't want to render frame-based grid. 106 return null; 107 }, 108 109 reset: function() 110 { 111 this._timelineData = null; 112 }, 113 114 /** 115 * @return {!WebInspector.FlameChart.TimelineData} 116 */ 117 timelineData: function() 118 { 119 if (this._timelineData) 120 return this._timelineData; 121 122 this._linkifier.reset(); 123 124 /** 125 * @type {?WebInspector.FlameChart.TimelineData} 126 */ 127 this._timelineData = { 128 entryLevels: [], 129 entryTotalTimes: [], 130 entryStartTimes: [] 131 }; 132 133 this._records = []; 134 this._entryThreadDepths = {}; 135 this._minimumBoundary = this._model.minimumRecordTime(); 136 137 var cpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program }; 138 this._cpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (cpuThreadRecordPayload), null); 139 this._pushRecord(this._cpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary())); 140 141 this._gpuThreadRecord = null; 142 143 var records = this._model.records(); 144 for (var i = 0; i < records.length; ++i) { 145 var record = records[i]; 146 var thread = record.thread(); 147 if (thread === "gpu") 148 continue; 149 if (!thread) { 150 for (var j = 0; j < record.children().length; ++j) 151 this._appendRecord(record.children()[j], 1); 152 } else { 153 var visible = this._appendRecord(records[i], 1); 154 if (visible && !this._gpuThreadRecord) { 155 var gpuThreadRecordPayload = { type: WebInspector.TimelineModel.RecordType.Program }; 156 this._gpuThreadRecord = new WebInspector.TimelineModel.RecordImpl(this._model, /** @type {!TimelineAgent.TimelineEvent} */ (gpuThreadRecordPayload), null); 157 this._pushRecord(this._gpuThreadRecord, 0, this.minimumBoundary(), Math.max(this._model.maximumRecordTime(), this.totalTime() + this.minimumBoundary())); 158 } 159 } 160 } 161 162 var cpuStackDepth = Math.max(4, this._entryThreadDepths[undefined]); 163 delete this._entryThreadDepths[undefined]; 164 this._maxStackDepth = cpuStackDepth; 165 166 if (this._gpuThreadRecord) { 167 // We have multiple threads, update levels. 168 var threadBaselines = {}; 169 var threadBaseline = cpuStackDepth + 2; 170 171 for (var thread in this._entryThreadDepths) { 172 threadBaselines[thread] = threadBaseline; 173 threadBaseline += this._entryThreadDepths[thread]; 174 } 175 this._maxStackDepth = threadBaseline; 176 177 for (var i = 0; i < this._records.length; ++i) { 178 var record = this._records[i]; 179 var level = this._timelineData.entryLevels[i]; 180 if (record === this._cpuThreadRecord) 181 level = 0; 182 else if (record === this._gpuThreadRecord) 183 level = cpuStackDepth + 2; 184 else if (record.thread()) 185 level += threadBaselines[record.thread()]; 186 this._timelineData.entryLevels[i] = level; 187 } 188 } 189 190 return this._timelineData; 191 }, 192 193 /** 194 * @return {number} 195 */ 196 minimumBoundary: function() 197 { 198 return this._minimumBoundary; 199 }, 200 201 /** 202 * @return {number} 203 */ 204 totalTime: function() 205 { 206 return Math.max(1000, this._model.maximumRecordTime() - this._model.minimumRecordTime()); 207 }, 208 209 /** 210 * @return {number} 211 */ 212 maxStackDepth: function() 213 { 214 return this._maxStackDepth; 215 }, 216 217 /** 218 * @param {!WebInspector.TimelineModel.Record} record 219 * @param {number} level 220 * @return {boolean} 221 */ 222 _appendRecord: function(record, level) 223 { 224 var result = false; 225 if (!this._model.isVisible(record)) { 226 for (var i = 0; i < record.children().length; ++i) 227 result = this._appendRecord(record.children()[i], level) || result; 228 return result; 229 } 230 231 this._pushRecord(record, level, record.startTime(), record.endTime()); 232 for (var i = 0; i < record.children().length; ++i) 233 this._appendRecord(record.children()[i], level + 1); 234 return true; 235 }, 236 237 /** 238 * @param {!WebInspector.TimelineModel.Record} record 239 * @param {number} level 240 * @param {number} startTime 241 * @param {number} endTime 242 * @return {number} 243 */ 244 _pushRecord: function(record, level, startTime, endTime) 245 { 246 var index = this._records.length; 247 this._records.push(record); 248 this._timelineData.entryStartTimes[index] = startTime; 249 this._timelineData.entryLevels[index] = level; 250 this._timelineData.entryTotalTimes[index] = endTime - startTime; 251 this._entryThreadDepths[record.thread()] = Math.max(level, this._entryThreadDepths[record.thread()] || 0); 252 return index; 253 }, 254 255 /** 256 * @param {number} entryIndex 257 * @return {?Array.<!{title: string, text: string}>} 258 */ 259 prepareHighlightedEntryInfo: function(entryIndex) 260 { 261 return null; 262 }, 263 264 /** 265 * @param {number} entryIndex 266 * @return {boolean} 267 */ 268 canJumpToEntry: function(entryIndex) 269 { 270 return false; 271 }, 272 273 /** 274 * @param {number} entryIndex 275 * @return {string} 276 */ 277 entryColor: function(entryIndex) 278 { 279 var record = this._records[entryIndex]; 280 if (record === this._cpuThreadRecord || record === this._gpuThreadRecord) 281 return "#555"; 282 283 if (record.type() === WebInspector.TimelineModel.RecordType.JSFrame) 284 return WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator().colorForID(record.data()["functionName"]); 285 286 return record.category().fillColorStop1; 287 }, 288 289 290 /** 291 * @param {number} entryIndex 292 * @param {!CanvasRenderingContext2D} context 293 * @param {?string} text 294 * @param {number} barX 295 * @param {number} barY 296 * @param {number} barWidth 297 * @param {number} barHeight 298 * @param {function(number):number} offsetToPosition 299 * @return {boolean} 300 */ 301 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition) 302 { 303 if (barWidth < 5) 304 return false; 305 306 var record = this._records[entryIndex]; 307 var timelineData = this._timelineData; 308 309 var category = record.category(); 310 // Paint text using white color on dark background. 311 if (text) { 312 context.save(); 313 context.fillStyle = "white"; 314 context.shadowColor = "rgba(0, 0, 0, 0.1)"; 315 context.shadowOffsetX = 1; 316 context.shadowOffsetY = 1; 317 context.font = this._font; 318 context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline()); 319 context.restore(); 320 } 321 322 if (record.children().length) { 323 var entryStartTime = timelineData.entryStartTimes[entryIndex]; 324 var barSelf = offsetToPosition(entryStartTime + record.selfTime()) 325 326 context.beginPath(); 327 context.fillStyle = category.backgroundColor; 328 context.rect(barSelf, barY, barX + barWidth - barSelf, barHeight); 329 context.fill(); 330 331 // Paint text using dark color on light background. 332 if (text) { 333 context.save(); 334 context.clip(); 335 context.fillStyle = category.borderColor; 336 context.shadowColor = "rgba(0, 0, 0, 0.1)"; 337 context.shadowOffsetX = 1; 338 context.shadowOffsetY = 1; 339 context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline()); 340 context.restore(); 341 } 342 } 343 344 if (record.warnings()) { 345 context.save(); 346 347 context.rect(barX, barY, barWidth, this.barHeight()); 348 context.clip(); 349 350 context.beginPath(); 351 context.fillStyle = record.warnings() ? "red" : "rgba(255, 0, 0, 0.5)"; 352 context.moveTo(barX + barWidth - 15, barY + 1); 353 context.lineTo(barX + barWidth - 1, barY + 1); 354 context.lineTo(barX + barWidth - 1, barY + 15); 355 context.fill(); 356 357 context.restore(); 358 } 359 360 return true; 361 }, 362 363 /** 364 * @param {number} entryIndex 365 * @return {boolean} 366 */ 367 forceDecoration: function(entryIndex) 368 { 369 var record = this._records[entryIndex]; 370 return !!record.warnings(); 371 }, 372 373 /** 374 * @param {number} entryIndex 375 * @return {?{startTime: number, endTime: number}} 376 */ 377 highlightTimeRange: function(entryIndex) 378 { 379 var record = this._records[entryIndex]; 380 if (record === this._cpuThreadRecord || record === this._gpuThreadRecord) 381 return null; 382 return { 383 startTime: record.startTime(), 384 endTime: record.endTime() 385 }; 386 }, 387 388 /** 389 * @return {number} 390 */ 391 paddingLeft: function() 392 { 393 return 0; 394 }, 395 396 /** 397 * @param {number} entryIndex 398 * @return {string} 399 */ 400 textColor: function(entryIndex) 401 { 402 return "white"; 403 }, 404 405 /** 406 * @param {number} entryIndex 407 * @return {?WebInspector.TimelineSelection} 408 */ 409 createSelection: function(entryIndex) 410 { 411 var record = this._records[entryIndex]; 412 if (record instanceof WebInspector.TimelineModel.RecordImpl) { 413 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex); 414 return this._lastSelection.timelineSelection; 415 } 416 return null; 417 }, 418 419 /** 420 * @param {?WebInspector.TimelineSelection} selection 421 * @return {number} 422 */ 423 entryIndexForSelection: function(selection) 424 { 425 if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.Record) 426 return -1; 427 var record = /** @type{!WebInspector.TimelineModel.Record} */ (selection.object()); 428 if (this._lastSelection && this._lastSelection.timelineSelection.object() === record) 429 return this._lastSelection.entryIndex; 430 var entryRecords = this._records; 431 for (var entryIndex = 0; entryIndex < entryRecords.length; ++entryIndex) { 432 if (entryRecords[entryIndex] === record) { 433 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromRecord(record), entryIndex); 434 return entryIndex; 435 } 436 } 437 return -1; 438 } 439 } 440 441 /** 442 * @constructor 443 * @implements {WebInspector.FlameChartDataProvider} 444 * @implements {WebInspector.TimelineFlameChart.SelectionProvider} 445 * @param {!WebInspector.TracingTimelineModel} model 446 * @param {!WebInspector.TimelineFrameModelBase} frameModel 447 * @param {!WebInspector.Target} target 448 */ 449 WebInspector.TracingBasedTimelineFlameChartDataProvider = function(model, frameModel, target) 450 { 451 WebInspector.FlameChartDataProvider.call(this); 452 this._model = model; 453 this._frameModel = frameModel; 454 this._target = target; 455 this._font = "12px " + WebInspector.fontFamily(); 456 this._linkifier = new WebInspector.Linkifier(); 457 this._palette = new WebInspector.TraceViewPalette(); 458 this._entryIndexToTitle = {}; 459 } 460 461 WebInspector.TracingBasedTimelineFlameChartDataProvider.prototype = { 462 /** 463 * @return {number} 464 */ 465 barHeight: function() 466 { 467 return 20; 468 }, 469 470 /** 471 * @return {number} 472 */ 473 textBaseline: function() 474 { 475 return 6; 476 }, 477 478 /** 479 * @return {number} 480 */ 481 textPadding: function() 482 { 483 return 5; 484 }, 485 486 /** 487 * @param {number} entryIndex 488 * @return {string} 489 */ 490 entryFont: function(entryIndex) 491 { 492 return this._font; 493 }, 494 495 /** 496 * @param {number} entryIndex 497 * @return {?string} 498 */ 499 entryTitle: function(entryIndex) 500 { 501 var event = this._entryEvents[entryIndex]; 502 if (event) { 503 var name = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name).title; 504 // TODO(yurys): support event dividers 505 var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier, false, this._target); 506 return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name; 507 } 508 var title = this._entryIndexToTitle[entryIndex]; 509 if (!title) { 510 title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex); 511 console.error(title); 512 } 513 return title; 514 }, 515 516 /** 517 * @param {number} startTime 518 * @param {number} endTime 519 * @return {?Array.<number>} 520 */ 521 dividerOffsets: function(startTime, endTime) 522 { 523 return null; 524 }, 525 526 reset: function() 527 { 528 this._timelineData = null; 529 /** @type {!Array.<!WebInspector.TracingModel.Event>} */ 530 this._entryEvents = []; 531 this._entryIndexToTitle = {}; 532 }, 533 534 /** 535 * @return {!WebInspector.FlameChart.TimelineData} 536 */ 537 timelineData: function() 538 { 539 if (this._timelineData) 540 return this._timelineData; 541 542 /** 543 * @type {?WebInspector.FlameChart.TimelineData} 544 */ 545 this._timelineData = { 546 entryLevels: [], 547 entryTotalTimes: [], 548 entryStartTimes: [] 549 }; 550 551 this._currentLevel = 0; 552 this._minimumBoundary = this._model.minimumRecordTime(); 553 this._timeSpan = Math.max(this._model.maximumRecordTime() - this._minimumBoundary, 1000); 554 this._appendHeaderRecord("CPU"); 555 var events = this._model.mainThreadEvents(); 556 var maxStackDepth = 0; 557 for (var eventIndex = 0; eventIndex < events.length; ++eventIndex) { 558 var event = events[eventIndex]; 559 var category = event.category; 560 if (category !== "disabled-by-default-devtools.timeline" && category !== "devtools") 561 continue; 562 if (event.duration || event.phase === WebInspector.TracingModel.Phase.Instant) { 563 this._appendEvent(event); 564 if (maxStackDepth < event.level) 565 maxStackDepth = event.level; 566 } 567 } 568 this._currentLevel += maxStackDepth + 1; 569 570 this._appendHeaderRecord("GPU"); 571 return this._timelineData; 572 }, 573 574 /** 575 * @return {number} 576 */ 577 minimumBoundary: function() 578 { 579 return this._minimumBoundary; 580 }, 581 582 /** 583 * @return {number} 584 */ 585 totalTime: function() 586 { 587 return this._timeSpan; 588 }, 589 590 /** 591 * @return {number} 592 */ 593 maxStackDepth: function() 594 { 595 return this._currentLevel; 596 }, 597 598 /** 599 * @param {number} entryIndex 600 * @return {?Array.<!{title: string, text: string}>} 601 */ 602 prepareHighlightedEntryInfo: function(entryIndex) 603 { 604 return null; 605 }, 606 607 /** 608 * @param {number} entryIndex 609 * @return {boolean} 610 */ 611 canJumpToEntry: function(entryIndex) 612 { 613 return false; 614 }, 615 616 /** 617 * @param {number} entryIndex 618 * @return {string} 619 */ 620 entryColor: function(entryIndex) 621 { 622 var event = this._entryEvents[entryIndex]; 623 if (!event) 624 return "#555"; 625 var style = WebInspector.TracingTimelineUIUtils.styleForTraceEvent(event.name); 626 return style.category.fillColorStop1; 627 }, 628 629 /** 630 * @param {number} entryIndex 631 * @param {!CanvasRenderingContext2D} context 632 * @param {?string} text 633 * @param {number} barX 634 * @param {number} barY 635 * @param {number} barWidth 636 * @param {number} barHeight 637 * @param {function(number):number} offsetToPosition 638 * @return {boolean} 639 */ 640 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition) 641 { 642 if (barWidth < 5) 643 return false; 644 645 var timelineData = this._timelineData; 646 647 // Paint text using white color on dark background. 648 if (text) { 649 context.save(); 650 context.fillStyle = "white"; 651 context.shadowColor = "rgba(0, 0, 0, 0.1)"; 652 context.shadowOffsetX = 1; 653 context.shadowOffsetY = 1; 654 context.font = this._font; 655 context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline()); 656 context.restore(); 657 } 658 659 var event = this._entryEvents[entryIndex]; 660 if (event && event.warning) { 661 context.save(); 662 663 context.rect(barX, barY, barWidth, this.barHeight()); 664 context.clip(); 665 666 context.beginPath(); 667 context.fillStyle = "red"; 668 context.moveTo(barX + barWidth - 15, barY + 1); 669 context.lineTo(barX + barWidth - 1, barY + 1); 670 context.lineTo(barX + barWidth - 1, barY + 15); 671 context.fill(); 672 673 context.restore(); 674 } 675 676 return true; 677 }, 678 679 /** 680 * @param {number} entryIndex 681 * @return {boolean} 682 */ 683 forceDecoration: function(entryIndex) 684 { 685 var event = this._entryEvents[entryIndex]; 686 if (!event) 687 return false; 688 return !!event.warning; 689 }, 690 691 /** 692 * @param {number} entryIndex 693 * @return {?{startTime: number, endTime: number}} 694 */ 695 highlightTimeRange: function(entryIndex) 696 { 697 var event = this._entryEvents[entryIndex]; 698 if (!event) 699 return null; 700 return { 701 startTime: event.startTime, 702 endTime: event.endTime 703 } 704 }, 705 706 /** 707 * @return {number} 708 */ 709 paddingLeft: function() 710 { 711 return 0; 712 }, 713 714 /** 715 * @param {number} entryIndex 716 * @return {string} 717 */ 718 textColor: function(entryIndex) 719 { 720 return "white"; 721 }, 722 723 /** 724 * @param {string} title 725 */ 726 _appendHeaderRecord: function(title) 727 { 728 var index = this._entryEvents.length; 729 this._entryIndexToTitle[index] = title; 730 this._entryEvents.push(null); 731 this._timelineData.entryLevels[index] = this._currentLevel++; 732 this._timelineData.entryTotalTimes[index] = this._timeSpan; 733 this._timelineData.entryStartTimes[index] = this._minimumBoundary; 734 }, 735 736 /** 737 * @param {!WebInspector.TracingModel.Event} event 738 */ 739 _appendEvent: function(event) 740 { 741 var index = this._entryEvents.length; 742 this._entryEvents.push(event); 743 this._timelineData.entryLevels[index] = this._currentLevel + event.level; 744 this._timelineData.entryTotalTimes[index] = event.duration || 1; 745 this._timelineData.entryStartTimes[index] = event.startTime; 746 }, 747 748 /** 749 * @param {number} entryIndex 750 * @return {?WebInspector.TimelineSelection} 751 */ 752 createSelection: function(entryIndex) 753 { 754 var event = this._entryEvents[entryIndex]; 755 if (!event) 756 return null; 757 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex); 758 return this._lastSelection.timelineSelection; 759 }, 760 761 /** 762 * @param {?WebInspector.TimelineSelection} selection 763 * @return {number} 764 */ 765 entryIndexForSelection: function(selection) 766 { 767 if (!selection || selection.type() !== WebInspector.TimelineSelection.Type.TraceEvent) 768 return -1; 769 var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object()); 770 if (this._lastSelection && this._lastSelection.timelineSelection.object() === event) 771 return this._lastSelection.entryIndex; 772 var entryEvents = this._entryEvents; 773 for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) { 774 if (entryEvents[entryIndex] === event) { 775 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex); 776 return entryIndex; 777 } 778 } 779 return -1; 780 } 781 } 782 783 /** 784 * @return {!WebInspector.FlameChart.ColorGenerator} 785 */ 786 WebInspector.TimelineFlameChartDataProvider.jsFrameColorGenerator = function() 787 { 788 if (!WebInspector.TimelineFlameChartDataProvider._jsFrameColorGenerator) { 789 var hueSpace = { min: 30, max: 55, count: 5 }; 790 var satSpace = { min: 70, max: 100, count: 6 }; 791 var colorGenerator = new WebInspector.FlameChart.ColorGenerator(hueSpace, satSpace, 50); 792 colorGenerator.setColorForID("(idle)", "hsl(0, 0%, 60%)"); 793 colorGenerator.setColorForID("(program)", "hsl(0, 0%, 60%)"); 794 colorGenerator.setColorForID("(garbage collector)", "hsl(0, 0%, 60%)"); 795 WebInspector.TimelineFlameChartDataProvider._jsFrameColorGenerator = colorGenerator; 796 } 797 return WebInspector.TimelineFlameChartDataProvider._jsFrameColorGenerator; 798 } 799 800 /** 801 * @constructor 802 * @extends {WebInspector.VBox} 803 * @implements {WebInspector.TimelineModeView} 804 * @implements {WebInspector.FlameChartDelegate} 805 * @param {!WebInspector.TimelineModeViewDelegate} delegate 806 * @param {!WebInspector.TimelineModel} model 807 * @param {?WebInspector.TracingTimelineModel} tracingModel 808 * @param {!WebInspector.TimelineFrameModelBase} frameModel 809 */ 810 WebInspector.TimelineFlameChart = function(delegate, model, tracingModel, frameModel) 811 { 812 WebInspector.VBox.call(this); 813 this.element.classList.add("timeline-flamechart"); 814 this.registerRequiredCSS("flameChart.css"); 815 this._delegate = delegate; 816 this._model = model; 817 this._dataProvider = tracingModel 818 ? new WebInspector.TracingBasedTimelineFlameChartDataProvider(tracingModel, frameModel, model.target()) 819 : new WebInspector.TimelineFlameChartDataProvider(/** @type {!WebInspector.TimelineModelImpl} */(model), frameModel); 820 this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true); 821 this._mainView.show(this.element); 822 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this); 823 this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); 824 } 825 826 WebInspector.TimelineFlameChart.prototype = { 827 dispose: function() 828 { 829 this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this); 830 this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); 831 }, 832 833 /** 834 * @param {number} windowStartTime 835 * @param {number} windowEndTime 836 */ 837 requestWindowTimes: function(windowStartTime, windowEndTime) 838 { 839 this._delegate.requestWindowTimes(windowStartTime, windowEndTime); 840 }, 841 842 /** 843 * @param {?RegExp} textFilter 844 */ 845 refreshRecords: function(textFilter) 846 { 847 this._dataProvider.reset(); 848 this._mainView._scheduleUpdate(); 849 }, 850 851 wasShown: function() 852 { 853 this._mainView._scheduleUpdate(); 854 }, 855 856 857 /** 858 * @return {!WebInspector.View} 859 */ 860 view: function() 861 { 862 return this; 863 }, 864 865 reset: function() 866 { 867 this._automaticallySizeWindow = true; 868 this._dataProvider.reset(); 869 this._mainView.reset(); 870 this._mainView.setWindowTimes(0, Infinity); 871 }, 872 873 _onRecordingStarted: function() 874 { 875 this._automaticallySizeWindow = true; 876 this._mainView.reset(); 877 }, 878 879 /** 880 * @param {!WebInspector.TimelineModel.Record} record 881 */ 882 addRecord: function(record) 883 { 884 this._dataProvider.reset(); 885 if (this._automaticallySizeWindow) { 886 var minimumRecordTime = this._model.minimumRecordTime(); 887 if (record.startTime() > (minimumRecordTime + 1000)) { 888 this._automaticallySizeWindow = false; 889 this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000); 890 } 891 this._mainView._scheduleUpdate(); 892 } else { 893 if (!this._pendingUpdateTimer) 894 this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300); 895 } 896 }, 897 898 _updateOnAddRecord: function() 899 { 900 delete this._pendingUpdateTimer; 901 this._mainView._scheduleUpdate(); 902 }, 903 904 /** 905 * @param {number} startTime 906 * @param {number} endTime 907 */ 908 setWindowTimes: function(startTime, endTime) 909 { 910 this._mainView.setWindowTimes(startTime, endTime); 911 this._delegate.select(null); 912 }, 913 914 /** 915 * @param {number} width 916 */ 917 setSidebarSize: function(width) 918 { 919 }, 920 921 /** 922 * @param {?WebInspector.TimelineModel.Record} record 923 * @param {string=} regex 924 * @param {boolean=} selectRecord 925 */ 926 highlightSearchResult: function(record, regex, selectRecord) 927 { 928 }, 929 930 /** 931 * @param {?WebInspector.TimelineSelection} selection 932 */ 933 setSelection: function(selection) 934 { 935 var index = this._dataProvider.entryIndexForSelection(selection); 936 this._mainView.setSelectedEntry(index); 937 }, 938 939 /** 940 * @param {!WebInspector.Event} event 941 */ 942 _onEntrySelected: function(event) 943 { 944 var entryIndex = /** @type{number} */ (event.data); 945 var timelineSelection = this._dataProvider.createSelection(entryIndex); 946 if (timelineSelection) 947 this._delegate.select(timelineSelection); 948 }, 949 950 __proto__: WebInspector.VBox.prototype 951 } 952 953 /** 954 * @constructor 955 * @param {!WebInspector.TimelineSelection} selection 956 * @param {number} entryIndex 957 */ 958 WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex) 959 { 960 this.timelineSelection = selection; 961 this.entryIndex = entryIndex; 962 } 963 964 /** 965 * @interface 966 */ 967 WebInspector.TimelineFlameChart.SelectionProvider = function() { } 968 969 WebInspector.TimelineFlameChart.SelectionProvider.prototype = { 970 /** 971 * @param {number} entryIndex 972 * @return {?WebInspector.TimelineSelection} 973 */ 974 createSelection: function(entryIndex) { }, 975 /** 976 * @param {?WebInspector.TimelineSelection} selection 977 * @return {number} 978 */ 979 entryIndexForSelection: function(selection) { } 980 } 981