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 * @param {!WebInspector.TracingTimelineModel} model 35 * @param {!WebInspector.TimelineFrameModelBase} frameModel 36 */ 37 WebInspector.TimelineFlameChartDataProvider = function(model, frameModel) 38 { 39 WebInspector.FlameChartDataProvider.call(this); 40 this.reset(); 41 this._model = model; 42 this._frameModel = frameModel; 43 this._font = "12px " + WebInspector.fontFamily(); 44 this._linkifier = new WebInspector.Linkifier(); 45 this._captureStacksSetting = WebInspector.settings.createSetting("timelineCaptureStacks", true); 46 this._filters = []; 47 this.addFilter(WebInspector.TracingTimelineUIUtils.hiddenEventsFilter()); 48 this.addFilter(new WebInspector.TracingTimelineModel.ExclusiveEventNameFilter([WebInspector.TracingTimelineModel.RecordType.Program])); 49 } 50 51 WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs = 0.01; 52 WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs = 1.1; 53 54 /** 55 * @return {!WebInspector.FlameChart.ColorGenerator} 56 */ 57 WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator = function() 58 { 59 if (!WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator) { 60 var hueSpace = { min: 30, max: 55, count: 5 }; 61 var satSpace = { min: 70, max: 100, count: 6 }; 62 var colorGenerator = new WebInspector.FlameChart.ColorGenerator(hueSpace, satSpace, 50, 0.7); 63 WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator = colorGenerator; 64 } 65 return WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator._consoleEventsColorGenerator; 66 } 67 68 WebInspector.TimelineFlameChartDataProvider.prototype = { 69 /** 70 * @return {number} 71 */ 72 barHeight: function() 73 { 74 return 20; 75 }, 76 77 /** 78 * @return {number} 79 */ 80 textBaseline: function() 81 { 82 return 6; 83 }, 84 85 /** 86 * @return {number} 87 */ 88 textPadding: function() 89 { 90 return 5; 91 }, 92 93 /** 94 * @param {number} entryIndex 95 * @return {string} 96 */ 97 entryFont: function(entryIndex) 98 { 99 return this._font; 100 }, 101 102 /** 103 * @param {number} entryIndex 104 * @return {?string} 105 */ 106 entryTitle: function(entryIndex) 107 { 108 var event = this._entryEvents[entryIndex]; 109 if (event) { 110 if (event.phase === WebInspector.TracingModel.Phase.AsyncStepInto || event.phase === WebInspector.TracingModel.Phase.AsyncStepPast) 111 return event.name + ":" + event.args["step"]; 112 113 var name = WebInspector.TracingTimelineUIUtils.eventStyle(event).title; 114 // TODO(yurys): support event dividers 115 var details = WebInspector.TracingTimelineUIUtils.buildDetailsNodeForTraceEvent(event, this._linkifier); 116 if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame && details) 117 return details.textContent; 118 return details ? WebInspector.UIString("%s (%s)", name, details.textContent) : name; 119 } 120 var title = this._entryIndexToTitle[entryIndex]; 121 if (!title) { 122 title = WebInspector.UIString("Unexpected entryIndex %d", entryIndex); 123 console.error(title); 124 } 125 return title; 126 }, 127 128 /** 129 * @param {number} startTime 130 * @param {number} endTime 131 * @return {?Array.<number>} 132 */ 133 dividerOffsets: function(startTime, endTime) 134 { 135 return null; 136 }, 137 138 /** 139 * @override 140 * @param {number} index 141 * @return {string} 142 */ 143 markerColor: function(index) 144 { 145 var event = this._markerEvents[index]; 146 return WebInspector.TracingTimelineUIUtils.markerEventColor(event); 147 }, 148 149 /** 150 * @override 151 * @param {number} index 152 * @return {string} 153 */ 154 markerTitle: function(index) 155 { 156 var event = this._markerEvents[index]; 157 return WebInspector.TracingTimelineUIUtils.eventTitle(event, this._model); 158 }, 159 160 reset: function() 161 { 162 this._timelineData = null; 163 /** @type {!Array.<!WebInspector.TracingModel.Event>} */ 164 this._entryEvents = []; 165 this._entryIndexToTitle = {}; 166 this._markerEvents = []; 167 this._entryIndexToFrame = {}; 168 this._asyncColorByCategory = {}; 169 }, 170 171 /** 172 * @return {!WebInspector.FlameChart.TimelineData} 173 */ 174 timelineData: function() 175 { 176 if (this._timelineData) 177 return this._timelineData; 178 179 this._timelineData = new WebInspector.FlameChart.TimelineData([], [], []); 180 181 this._minimumBoundary = this._model.minimumRecordTime(); 182 this._timeSpan = this._model.isEmpty() ? 1000 : this._model.maximumRecordTime() - this._minimumBoundary; 183 this._currentLevel = 0; 184 this._appendFrameBars(this._frameModel.frames()); 185 this._appendThreadTimelineData(WebInspector.UIString("Main Thread"), this._model.mainThreadEvents(), this._model.mainThreadAsyncEvents()); 186 var threads = this._model.virtualThreads(); 187 for (var i = 0; i < threads.length; i++) 188 this._appendThreadTimelineData(threads[i].name, threads[i].events, threads[i].asyncEvents); 189 return this._timelineData; 190 }, 191 192 /** 193 * @param {string} threadTitle 194 * @param {!Array.<!WebInspector.TracingModel.Event>} syncEvents 195 * @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} asyncEvents 196 */ 197 _appendThreadTimelineData: function(threadTitle, syncEvents, asyncEvents) 198 { 199 var levelCount = this._appendAsyncEvents(threadTitle, asyncEvents); 200 if (Runtime.experiments.isEnabled("timelineJSCPUProfile")) { 201 if (this._captureStacksSetting.get()) { 202 var jsFrameEvents = this._generateJSFrameEvents(syncEvents); 203 syncEvents = jsFrameEvents.mergeOrdered(syncEvents, WebInspector.TracingModel.Event.orderedCompareStartTime); 204 } 205 } 206 levelCount += this._appendSyncEvents(levelCount ? null : threadTitle, syncEvents); 207 if (levelCount) 208 ++this._currentLevel; 209 }, 210 211 /** 212 * @param {?string} headerName 213 * @param {!Array.<!WebInspector.TracingModel.Event>} events 214 * @return {boolean} 215 */ 216 _appendSyncEvents: function(headerName, events) 217 { 218 var openEvents = []; 219 var headerAppended = false; 220 221 var maxStackDepth = 0; 222 for (var i = 0; i < events.length; ++i) { 223 var e = events[i]; 224 if (WebInspector.TracingTimelineUIUtils.isMarkerEvent(e)) { 225 this._markerEvents.push(e); 226 this._timelineData.markerTimestamps.push(e.startTime); 227 } 228 if (!e.endTime && e.phase !== WebInspector.TracingModel.Phase.Instant) 229 continue; 230 if (!this._isVisible(e)) 231 continue; 232 while (openEvents.length && openEvents.peekLast().endTime <= e.startTime) 233 openEvents.pop(); 234 if (!headerAppended && headerName) { 235 this._appendHeaderRecord(headerName, this._currentLevel++); 236 headerAppended = true; 237 } 238 this._appendEvent(e, this._currentLevel + openEvents.length); 239 maxStackDepth = Math.max(maxStackDepth, openEvents.length + 1); 240 if (e.endTime) 241 openEvents.push(e); 242 } 243 this._currentLevel += maxStackDepth; 244 return !!maxStackDepth; 245 }, 246 247 /** 248 * @param {string} header 249 * @param {!Array.<!Array.<!WebInspector.TracingModel.Event>>} eventSteps 250 */ 251 _appendAsyncEvents: function(header, eventSteps) 252 { 253 var lastUsedTimeByLevel = []; 254 var headerAppended = false; 255 256 var maxStackDepth = 0; 257 for (var i = 0; i < eventSteps.length; ++i) { 258 var e = eventSteps[i][0]; 259 if (!this._isVisible(e)) 260 continue; 261 if (!headerAppended && header) { 262 this._appendHeaderRecord(header, this._currentLevel++); 263 headerAppended = true; 264 } 265 var level; 266 for (level = 0; level < lastUsedTimeByLevel.length && lastUsedTimeByLevel[level] > e.startTime; ++level) {} 267 this._appendAsyncEventSteps(eventSteps[i], this._currentLevel + level); 268 var lastStep = eventSteps[i].peekLast(); 269 lastUsedTimeByLevel[level] = lastStep.phase === WebInspector.TracingModel.Phase.AsyncEnd ? lastStep.startTime : Infinity; 270 } 271 this._currentLevel += lastUsedTimeByLevel.length; 272 return lastUsedTimeByLevel.length; 273 }, 274 275 /** 276 * @param {!Array.<!WebInspector.TimelineFrame>} frames 277 */ 278 _appendFrameBars: function(frames) 279 { 280 this._frameBarsLevel = this._currentLevel++; 281 for (var i = 0; i < frames.length; ++i) 282 this._appendFrame(frames[i]); 283 }, 284 285 /** 286 * @param {!Array.<!WebInspector.TracingModel.Event>} events 287 * @return {!Array.<!WebInspector.TracingModel.Event>} 288 */ 289 _generateJSFrameEvents: function(events) 290 { 291 function equalFrames(frame1, frame2) 292 { 293 return frame1.scriptId === frame2.scriptId && frame1.functionName === frame2.functionName; 294 } 295 296 function eventEndTime(e) 297 { 298 return e.endTime || e.startTime; 299 } 300 301 function isJSInvocationEvent(e) 302 { 303 switch (e.name) { 304 case WebInspector.TracingTimelineModel.RecordType.FunctionCall: 305 case WebInspector.TracingTimelineModel.RecordType.EvaluateScript: 306 return true; 307 } 308 return false; 309 } 310 311 var jsFrameEvents = []; 312 var jsFramesStack = []; 313 var coalesceThresholdMs = WebInspector.TimelineFlameChartDataProvider.JSFrameCoalesceThresholdMs; 314 315 function onStartEvent(e) 316 { 317 extractStackTrace(e); 318 } 319 320 function onInstantEvent(e, top) 321 { 322 if (e.name === WebInspector.TracingTimelineModel.RecordType.JSSample && top && !isJSInvocationEvent(top)) 323 return; 324 extractStackTrace(e); 325 } 326 327 function onEndEvent(e) 328 { 329 if (isJSInvocationEvent(e)) 330 jsFramesStack.length = 0; 331 } 332 333 function extractStackTrace(e) 334 { 335 if (!e.stackTrace) 336 return; 337 while (jsFramesStack.length && eventEndTime(jsFramesStack.peekLast()) + coalesceThresholdMs <= e.startTime) 338 jsFramesStack.pop(); 339 var endTime = eventEndTime(e); 340 var numFrames = e.stackTrace.length; 341 var minFrames = Math.min(numFrames, jsFramesStack.length); 342 var j; 343 for (j = 0; j < minFrames; ++j) { 344 var newFrame = e.stackTrace[numFrames - 1 - j]; 345 var oldFrame = jsFramesStack[j].args["data"]; 346 if (!equalFrames(newFrame, oldFrame)) 347 break; 348 jsFramesStack[j].setEndTime(Math.max(jsFramesStack[j].endTime, endTime)); 349 } 350 jsFramesStack.length = j; 351 for (; j < numFrames; ++j) { 352 var frame = e.stackTrace[numFrames - 1 - j]; 353 var jsFrameEvent = new WebInspector.TracingModel.Event(WebInspector.TracingModel.DevToolsMetadataEventCategory, WebInspector.TracingTimelineModel.RecordType.JSFrame, 354 WebInspector.TracingModel.Phase.Complete, e.startTime, e.thread); 355 jsFrameEvent.addArgs({ data: frame }); 356 jsFrameEvent.setEndTime(endTime); 357 jsFramesStack.push(jsFrameEvent); 358 jsFrameEvents.push(jsFrameEvent); 359 } 360 } 361 362 var stack = []; 363 for (var i = 0; i < events.length; ++i) { 364 var e = events[i]; 365 var top = stack.peekLast(); 366 if (top && top.endTime <= e.startTime) 367 onEndEvent(stack.pop()); 368 if (e.duration) { 369 onStartEvent(e); 370 stack.push(e); 371 } else { 372 onInstantEvent(e, stack.peekLast()); 373 } 374 } 375 while (stack.length) 376 onEndEvent(stack.pop()); 377 378 return jsFrameEvents; 379 }, 380 381 /** 382 * @param {!WebInspector.TracingTimelineModel.Filter} filter 383 */ 384 addFilter: function(filter) 385 { 386 this._filters.push(filter); 387 }, 388 389 /** 390 * @param {!WebInspector.TracingModel.Event} event 391 * @return {boolean} 392 */ 393 _isVisible: function(event) 394 { 395 return this._filters.every(function (filter) { return filter.accept(event); }); 396 }, 397 398 /** 399 * @return {number} 400 */ 401 minimumBoundary: function() 402 { 403 return this._minimumBoundary; 404 }, 405 406 /** 407 * @return {number} 408 */ 409 totalTime: function() 410 { 411 return this._timeSpan; 412 }, 413 414 /** 415 * @return {number} 416 */ 417 maxStackDepth: function() 418 { 419 return this._currentLevel; 420 }, 421 422 /** 423 * @param {number} entryIndex 424 * @return {?Array.<!{title: string, text: string}>} 425 */ 426 prepareHighlightedEntryInfo: function(entryIndex) 427 { 428 return null; 429 }, 430 431 /** 432 * @param {number} entryIndex 433 * @return {boolean} 434 */ 435 canJumpToEntry: function(entryIndex) 436 { 437 return false; 438 }, 439 440 /** 441 * @param {number} entryIndex 442 * @return {string} 443 */ 444 entryColor: function(entryIndex) 445 { 446 var event = this._entryEvents[entryIndex]; 447 if (!event) 448 return this._entryIndexToFrame[entryIndex] ? "white" : "#555"; 449 if (event.name === WebInspector.TracingTimelineModel.RecordType.JSFrame) 450 return this._timelineData.entryLevels[entryIndex] % 2 ? "#efb320" : "#fcc02d"; 451 var category = WebInspector.TracingTimelineUIUtils.eventStyle(event).category; 452 if (WebInspector.TracingModel.isAsyncPhase(event.phase)) { 453 if (event.category === WebInspector.TracingModel.ConsoleEventCategory) 454 return WebInspector.TimelineFlameChartDataProvider.consoleEventsColorGenerator().colorForID(event.name); 455 var color = this._asyncColorByCategory[category.name]; 456 if (color) 457 return color; 458 var parsedColor = WebInspector.Color.parse(category.fillColorStop1); 459 color = parsedColor.setAlpha(0.7).toString(WebInspector.Color.Format.RGBA) || ""; 460 this._asyncColorByCategory[category.name] = color; 461 return color; 462 } 463 return category.fillColorStop1; 464 }, 465 466 /** 467 * @param {number} entryIndex 468 * @param {!CanvasRenderingContext2D} context 469 * @param {?string} text 470 * @param {number} barX 471 * @param {number} barY 472 * @param {number} barWidth 473 * @param {number} barHeight 474 * @param {function(number):number} offsetToPosition 475 * @return {boolean} 476 */ 477 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, offsetToPosition) 478 { 479 var frame = this._entryIndexToFrame[entryIndex]; 480 if (frame) { 481 context.save(); 482 483 context.translate(0.5, 0.5); 484 485 context.beginPath(); 486 context.moveTo(barX, barY); 487 context.lineTo(barX, context.canvas.height); 488 context.strokeStyle = "rgba(100, 100, 100, 0.4)"; 489 context.setLineDash([5]); 490 context.stroke(); 491 context.setLineDash([]); 492 493 494 var padding = 4 * window.devicePixelRatio; 495 barX += padding; 496 barWidth -= 2 * padding; 497 barY += padding; 498 barHeight -= 2 * padding; 499 500 var cornerRadis = 3; 501 var radiusY = cornerRadis; 502 var radiusX = Math.min(cornerRadis, barWidth / 2); 503 504 context.beginPath(); 505 context.moveTo(barX + radiusX, barY); 506 context.lineTo(barX + barWidth - radiusX, barY); 507 context.quadraticCurveTo(barX + barWidth, barY, barX + barWidth, barY + radiusY); 508 context.lineTo(barX + barWidth, barY + barHeight - radiusY); 509 context.quadraticCurveTo(barX + barWidth, barY + barHeight, barX + barWidth - radiusX, barY + barHeight); 510 context.lineTo(barX + radiusX, barY + barHeight); 511 context.quadraticCurveTo(barX, barY + barHeight, barX, barY + barHeight - radiusY); 512 context.lineTo(barX, barY + radiusY); 513 context.quadraticCurveTo(barX, barY, barX + radiusX, barY); 514 context.closePath(); 515 516 context.fillStyle = "rgba(200, 200, 200, 0.8)"; 517 context.fill(); 518 context.strokeStyle = "rgba(150, 150, 150, 0.8)"; 519 context.stroke(); 520 521 var frameDurationText = Number.millisToString(frame.duration, true); 522 var textWidth = context.measureText(frameDurationText).width; 523 if (barWidth > textWidth) { 524 context.fillStyle = "#555"; 525 context.fillText(frameDurationText, barX + ((barWidth - textWidth) >> 1), barY + barHeight - 2); 526 } 527 context.restore(); 528 return true; 529 } 530 if (barWidth < 5) 531 return false; 532 533 // Paint text using white color on dark background. 534 if (text) { 535 context.save(); 536 context.fillStyle = "white"; 537 context.shadowColor = "rgba(0, 0, 0, 0.1)"; 538 context.shadowOffsetX = 1; 539 context.shadowOffsetY = 1; 540 context.font = this._font; 541 context.fillText(text, barX + this.textPadding(), barY + barHeight - this.textBaseline()); 542 context.restore(); 543 } 544 545 var event = this._entryEvents[entryIndex]; 546 if (event && event.warning) { 547 context.save(); 548 549 context.rect(barX, barY, barWidth, this.barHeight()); 550 context.clip(); 551 552 context.beginPath(); 553 context.fillStyle = "red"; 554 context.moveTo(barX + barWidth - 15, barY + 1); 555 context.lineTo(barX + barWidth - 1, barY + 1); 556 context.lineTo(barX + barWidth - 1, barY + 15); 557 context.fill(); 558 559 context.restore(); 560 } 561 562 return true; 563 }, 564 565 /** 566 * @param {number} entryIndex 567 * @return {boolean} 568 */ 569 forceDecoration: function(entryIndex) 570 { 571 var event = this._entryEvents[entryIndex]; 572 if (!event) 573 return !!this._entryIndexToFrame[entryIndex]; 574 return !!event.warning; 575 }, 576 577 /** 578 * @param {number} entryIndex 579 * @return {?{startTime: number, endTime: number}} 580 */ 581 highlightTimeRange: function(entryIndex) 582 { 583 var startTime = this._timelineData.entryStartTimes[entryIndex]; 584 if (!startTime) 585 return null; 586 return { 587 startTime: startTime, 588 endTime: startTime + this._timelineData.entryTotalTimes[entryIndex] 589 } 590 }, 591 592 /** 593 * @return {number} 594 */ 595 paddingLeft: function() 596 { 597 return 0; 598 }, 599 600 /** 601 * @param {number} entryIndex 602 * @return {string} 603 */ 604 textColor: function(entryIndex) 605 { 606 return "white"; 607 }, 608 609 /** 610 * @param {string} title 611 * @param {number} level 612 */ 613 _appendHeaderRecord: function(title, level) 614 { 615 var index = this._entryEvents.length; 616 this._entryIndexToTitle[index] = title; 617 this._entryEvents.push(null); 618 this._timelineData.entryLevels[index] = level; 619 this._timelineData.entryTotalTimes[index] = this._timeSpan; 620 this._timelineData.entryStartTimes[index] = this._minimumBoundary; 621 }, 622 623 /** 624 * @param {!WebInspector.TracingModel.Event} event 625 * @param {number} level 626 */ 627 _appendEvent: function(event, level) 628 { 629 var index = this._entryEvents.length; 630 this._entryEvents.push(event); 631 this._timelineData.entryLevels[index] = level; 632 this._timelineData.entryTotalTimes[index] = event.duration || WebInspector.TimelineFlameChartDataProvider.InstantEventVisibleDurationMs; 633 this._timelineData.entryStartTimes[index] = event.startTime; 634 }, 635 636 /** 637 * @param {!Array.<!WebInspector.TracingModel.Event>} steps 638 * @param {number} level 639 */ 640 _appendAsyncEventSteps: function(steps, level) 641 { 642 // If we have past steps, put the end event for each range rather than start one. 643 var eventOffset = steps[1].phase === WebInspector.TracingModel.Phase.AsyncStepPast ? 1 : 0; 644 for (var i = 0; i < steps.length - 1; ++i) { 645 var index = this._entryEvents.length; 646 this._entryEvents.push(steps[i + eventOffset]); 647 var startTime = steps[i].startTime; 648 this._timelineData.entryLevels[index] = level; 649 this._timelineData.entryTotalTimes[index] = steps[i + 1].startTime - startTime; 650 this._timelineData.entryStartTimes[index] = startTime; 651 } 652 }, 653 654 /** 655 * @param {!WebInspector.TimelineFrame} frame 656 */ 657 _appendFrame: function(frame) 658 { 659 var index = this._entryEvents.length; 660 this._entryEvents.push(null); 661 this._entryIndexToFrame[index] = frame; 662 this._entryIndexToTitle[index] = Number.millisToString(frame.duration, true); 663 this._timelineData.entryLevels[index] = this._frameBarsLevel; 664 this._timelineData.entryTotalTimes[index] = frame.duration; 665 this._timelineData.entryStartTimes[index] = frame.startTime; 666 }, 667 668 /** 669 * @param {number} entryIndex 670 * @return {?WebInspector.TimelineSelection} 671 */ 672 createSelection: function(entryIndex) 673 { 674 var event = this._entryEvents[entryIndex]; 675 if (event) { 676 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex); 677 return this._lastSelection.timelineSelection; 678 } 679 var frame = this._entryIndexToFrame[entryIndex]; 680 if (frame) { 681 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), entryIndex); 682 return this._lastSelection.timelineSelection; 683 } 684 return null; 685 }, 686 687 /** 688 * @param {?WebInspector.TimelineSelection} selection 689 * @return {number} 690 */ 691 entryIndexForSelection: function(selection) 692 { 693 if (!selection) 694 return -1; 695 696 if (this._lastSelection && this._lastSelection.timelineSelection.object() === selection.object()) 697 return this._lastSelection.entryIndex; 698 switch (selection.type()) { 699 case WebInspector.TimelineSelection.Type.TraceEvent: 700 var event = /** @type{!WebInspector.TracingModel.Event} */ (selection.object()); 701 var entryEvents = this._entryEvents; 702 for (var entryIndex = 0; entryIndex < entryEvents.length; ++entryIndex) { 703 if (entryEvents[entryIndex] === event) { 704 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromTraceEvent(event), entryIndex); 705 return entryIndex; 706 } 707 } 708 break; 709 case WebInspector.TimelineSelection.Type.Frame: 710 var frame = /** @type {!WebInspector.TimelineFrame} */ (selection.object()); 711 for (var frameIndex in this._entryIndexToFrame) { 712 if (this._entryIndexToFrame[frameIndex] === frame) { 713 this._lastSelection = new WebInspector.TimelineFlameChart.Selection(WebInspector.TimelineSelection.fromFrame(frame), Number(frameIndex)); 714 return Number(frameIndex); 715 } 716 } 717 break; 718 } 719 return -1; 720 } 721 } 722 723 /** 724 * @constructor 725 * @extends {WebInspector.VBox} 726 * @implements {WebInspector.TimelineModeView} 727 * @implements {WebInspector.FlameChartDelegate} 728 * @param {!WebInspector.TimelineModeViewDelegate} delegate 729 * @param {!WebInspector.TracingTimelineModel} tracingModel 730 * @param {!WebInspector.TimelineFrameModelBase} frameModel 731 */ 732 WebInspector.TimelineFlameChart = function(delegate, tracingModel, frameModel) 733 { 734 WebInspector.VBox.call(this); 735 this.element.classList.add("timeline-flamechart"); 736 this.registerRequiredCSS("flameChart.css"); 737 this._delegate = delegate; 738 this._model = tracingModel; 739 this._dataProvider = new WebInspector.TimelineFlameChartDataProvider(tracingModel, frameModel) 740 this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true); 741 this._mainView.show(this.element); 742 this._model.addEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this); 743 this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); 744 } 745 746 WebInspector.TimelineFlameChart.prototype = { 747 dispose: function() 748 { 749 this._model.removeEventListener(WebInspector.TimelineModel.Events.RecordingStarted, this._onRecordingStarted, this); 750 this._mainView.removeEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); 751 }, 752 753 /** 754 * @param {number} windowStartTime 755 * @param {number} windowEndTime 756 */ 757 requestWindowTimes: function(windowStartTime, windowEndTime) 758 { 759 this._delegate.requestWindowTimes(windowStartTime, windowEndTime); 760 }, 761 762 /** 763 * @param {?RegExp} textFilter 764 */ 765 refreshRecords: function(textFilter) 766 { 767 this._dataProvider.reset(); 768 this._mainView.scheduleUpdate(); 769 }, 770 771 wasShown: function() 772 { 773 this._mainView.scheduleUpdate(); 774 }, 775 776 /** 777 * @return {!WebInspector.View} 778 */ 779 view: function() 780 { 781 return this; 782 }, 783 784 reset: function() 785 { 786 this._automaticallySizeWindow = true; 787 this._dataProvider.reset(); 788 this._mainView.reset(); 789 this._mainView.setWindowTimes(0, Infinity); 790 }, 791 792 _onRecordingStarted: function() 793 { 794 this._automaticallySizeWindow = true; 795 this._mainView.reset(); 796 }, 797 798 /** 799 * @param {!WebInspector.TimelineModel.Record} record 800 */ 801 addRecord: function(record) 802 { 803 this._dataProvider.reset(); 804 if (this._automaticallySizeWindow) { 805 var minimumRecordTime = this._model.minimumRecordTime(); 806 if (record.startTime() > (minimumRecordTime + 1000)) { 807 this._automaticallySizeWindow = false; 808 this._delegate.requestWindowTimes(minimumRecordTime, minimumRecordTime + 1000); 809 } 810 this._mainView.scheduleUpdate(); 811 } else { 812 if (!this._pendingUpdateTimer) 813 this._pendingUpdateTimer = window.setTimeout(this._updateOnAddRecord.bind(this), 300); 814 } 815 }, 816 817 _updateOnAddRecord: function() 818 { 819 delete this._pendingUpdateTimer; 820 this._mainView.scheduleUpdate(); 821 }, 822 823 /** 824 * @param {number} startTime 825 * @param {number} endTime 826 */ 827 setWindowTimes: function(startTime, endTime) 828 { 829 this._mainView.setWindowTimes(startTime, endTime); 830 this._delegate.select(null); 831 }, 832 833 /** 834 * @param {number} width 835 */ 836 setSidebarSize: function(width) 837 { 838 }, 839 840 /** 841 * @param {?WebInspector.TimelineModel.Record} record 842 * @param {string=} regex 843 * @param {boolean=} selectRecord 844 */ 845 highlightSearchResult: function(record, regex, selectRecord) 846 { 847 }, 848 849 /** 850 * @param {?WebInspector.TimelineSelection} selection 851 */ 852 setSelection: function(selection) 853 { 854 var index = this._dataProvider.entryIndexForSelection(selection); 855 this._mainView.setSelectedEntry(index); 856 }, 857 858 /** 859 * @param {!WebInspector.Event} event 860 */ 861 _onEntrySelected: function(event) 862 { 863 var entryIndex = /** @type{number} */ (event.data); 864 var timelineSelection = this._dataProvider.createSelection(entryIndex); 865 if (timelineSelection) 866 this._delegate.select(timelineSelection); 867 }, 868 869 __proto__: WebInspector.VBox.prototype 870 } 871 872 /** 873 * @constructor 874 * @param {!WebInspector.TimelineSelection} selection 875 * @param {number} entryIndex 876 */ 877 WebInspector.TimelineFlameChart.Selection = function(selection, entryIndex) 878 { 879 this.timelineSelection = selection; 880 this.entryIndex = entryIndex; 881 } 882