1 /* 2 * Copyright 2014 The Chromium Authors. All rights reserved. 3 * Use of this source code is governed by a BSD-style license that can be 4 * found in the LICENSE file. 5 */ 6 7 /** 8 * @constructor 9 * @implements {WebInspector.TimelineModeView} 10 * @implements {WebInspector.FlameChartDelegate} 11 * @extends {WebInspector.VBox} 12 * @param {!WebInspector.TimelineModeViewDelegate} delegate 13 * @param {!WebInspector.TracingModel} tracingModel 14 * @param {!WebInspector.TimelineModel} modelForMinimumBoundary 15 */ 16 WebInspector.TimelineTracingView = function(delegate, tracingModel, modelForMinimumBoundary) 17 { 18 WebInspector.VBox.call(this); 19 this._delegate = delegate; 20 this._tracingModel = tracingModel; 21 this.element.classList.add("timeline-flamechart"); 22 this.registerRequiredCSS("flameChart.css"); 23 this._dataProvider = new WebInspector.TraceViewFlameChartDataProvider(this._tracingModel, modelForMinimumBoundary); 24 this._mainView = new WebInspector.FlameChart(this._dataProvider, this, true); 25 this._mainView.show(this.element); 26 this._mainView.addEventListener(WebInspector.FlameChart.Events.EntrySelected, this._onEntrySelected, this); 27 } 28 29 WebInspector.TimelineTracingView.prototype = { 30 /** 31 * @param {number} windowStartTime 32 * @param {number} windowEndTime 33 */ 34 requestWindowTimes: function(windowStartTime, windowEndTime) 35 { 36 this._delegate.requestWindowTimes(windowStartTime, windowEndTime); 37 }, 38 39 wasShown: function() 40 { 41 this._mainView._scheduleUpdate(); 42 }, 43 44 /** 45 * @return {!WebInspector.View} 46 */ 47 view: function() 48 { 49 return this; 50 }, 51 52 dispose: function() 53 { 54 }, 55 56 reset: function() 57 { 58 this._dataProvider.reset(); 59 this._mainView.setWindowTimes(0, Infinity); 60 }, 61 62 /** 63 * @param {?RegExp} textFilter 64 */ 65 refreshRecords: function(textFilter) 66 { 67 this._dataProvider.reset(); 68 this._mainView._scheduleUpdate(); 69 }, 70 71 /** 72 * @param {!WebInspector.TimelineModel.Record} record 73 */ 74 addRecord: function(record) {}, 75 76 /** 77 * @param {?WebInspector.TimelineModel.Record} record 78 * @param {string=} regex 79 * @param {boolean=} selectRecord 80 */ 81 highlightSearchResult: function(record, regex, selectRecord) {}, 82 83 /** 84 * @param {number} startTime 85 * @param {number} endTime 86 */ 87 setWindowTimes: function(startTime, endTime) 88 { 89 this._mainView.setWindowTimes(startTime, endTime); 90 }, 91 92 /** 93 * @param {number} width 94 */ 95 setSidebarSize: function(width) {}, 96 97 /** 98 * @param {?WebInspector.TimelineSelection} selection 99 */ 100 setSelection: function(selection) {}, 101 102 /** 103 * @param {!WebInspector.Event} event 104 */ 105 _onEntrySelected: function(event) 106 { 107 var index = /** @type {number} */ (event.data); 108 var record = this._dataProvider._recordAt(index); 109 if (!record || this._dataProvider._isHeaderRecord(record)) { 110 this._delegate.showInDetails("", document.createTextNode("")); 111 return; 112 } 113 var contentHelper = new WebInspector.TimelineDetailsContentHelper(null, null, false); 114 contentHelper.appendTextRow(WebInspector.UIString("Name"), record.name); 115 contentHelper.appendTextRow(WebInspector.UIString("Category"), record.category); 116 contentHelper.appendTextRow(WebInspector.UIString("Start"), Number.millisToString(record.startTime - this._tracingModel.minimumRecordTime(), true)); 117 contentHelper.appendTextRow(WebInspector.UIString("Duration"), Number.millisToString(record.duration, true)); 118 if (!Object.isEmpty(record.args)) 119 contentHelper.appendElementRow(WebInspector.UIString("Arguments"), this._formatArguments(record.args)); 120 /** 121 * @this {WebInspector.TimelineTracingView} 122 */ 123 function reveal() 124 { 125 WebInspector.Revealer.reveal(new WebInspector.DeferredTracingLayerTree(this._tracingModel.target(), record.args["snapshot"]["active_tree"]["root_layer"], record.args["snapshot"]["device_viewport_size"])); 126 } 127 /** 128 * @param {!Node=} node 129 * @this {WebInspector.TimelineTracingView} 130 */ 131 function appendPreviewAndshowDetails(node) 132 { 133 if (node) 134 contentHelper.appendElementRow("Preview", node); 135 this._delegate.showInDetails(WebInspector.UIString("Selected Event"), contentHelper.element); 136 } 137 var recordTypes = WebInspector.TracingTimelineModel.RecordType; 138 switch (record.name) { 139 case recordTypes.PictureSnapshot: 140 WebInspector.TracingTimelineUIUtils._buildPicturePreviewContent(record.args["snapshot"]["skp64"], appendPreviewAndshowDetails.bind(this)); 141 break; 142 case recordTypes.LayerTreeHostImplSnapshot: 143 var link = document.createElement("span"); 144 link.classList.add("revealable-link"); 145 link.textContent = "show"; 146 link.addEventListener("click", reveal.bind(this), false); 147 contentHelper.appendElementRow(WebInspector.UIString("Layer tree"), link); 148 // Fall-through intended. 149 default: 150 this._delegate.showInDetails(WebInspector.UIString("Selected Event"), contentHelper.element); 151 } 152 }, 153 154 /** 155 * @param {!Object} args 156 * @return {!Element} 157 */ 158 _formatArguments: function(args) 159 { 160 var table = document.createElement("table"); 161 for (var name in args) { 162 var row = table.createChild("tr"); 163 row.createChild("td", "timeline-details-row-title").textContent = name + ":"; 164 var valueContainer = row.createChild("td", "timeline-details-row-data"); 165 var value = args[name]; 166 if (typeof value === "object" && value) { 167 var localObject = new WebInspector.LocalJSONObject(value); 168 var propertiesSection = new WebInspector.ObjectPropertiesSection(localObject, localObject.description); 169 valueContainer.appendChild(propertiesSection.element); 170 } else { 171 valueContainer.textContent = String(value); 172 } 173 } 174 return table; 175 }, 176 177 __proto__: WebInspector.VBox.prototype 178 }; 179 180 /** 181 * @constructor 182 * @implements {WebInspector.FlameChartDataProvider} 183 * @param {!WebInspector.TracingModel} model 184 * @param {!WebInspector.TimelineModel} timelineModelForMinimumBoundary 185 */ 186 WebInspector.TraceViewFlameChartDataProvider = function(model, timelineModelForMinimumBoundary) 187 { 188 WebInspector.FlameChartDataProvider.call(this); 189 this._model = model; 190 this._timelineModelForMinimumBoundary = timelineModelForMinimumBoundary; 191 this._font = "12px " + WebInspector.fontFamily(); 192 this._palette = new WebInspector.TraceViewPalette(); 193 var dummyEventPayload = { 194 cat: "dummy", 195 pid: 0, 196 tid: 0, 197 ts: 0, 198 ph: "dummy", 199 name: "dummy", 200 args: {}, 201 dur: 0, 202 id: 0, 203 s: "" 204 } 205 this._processHeaderRecord = new WebInspector.TracingModel.Event(dummyEventPayload, 0, null); 206 this._threadHeaderRecord = new WebInspector.TracingModel.Event(dummyEventPayload, 0, null); 207 } 208 209 WebInspector.TraceViewFlameChartDataProvider.prototype = { 210 /** 211 * @return {number} 212 */ 213 barHeight: function() 214 { 215 return 20; 216 }, 217 218 /** 219 * @return {number} 220 */ 221 textBaseline: function() 222 { 223 return 6; 224 }, 225 226 /** 227 * @return {number} 228 */ 229 textPadding: function() 230 { 231 return 5; 232 }, 233 234 /** 235 * @param {number} entryIndex 236 * @return {string} 237 */ 238 entryFont: function(entryIndex) 239 { 240 return this._font; 241 }, 242 243 /** 244 * @param {number} entryIndex 245 * @return {?string} 246 */ 247 entryTitle: function(entryIndex) 248 { 249 var record = this._records[entryIndex]; 250 if (this._isHeaderRecord(record)) 251 return this._headerTitles[entryIndex] 252 return record.name; 253 }, 254 255 /** 256 * @param {number} startTime 257 * @param {number} endTime 258 * @return {?Array.<number>} 259 */ 260 dividerOffsets: function(startTime, endTime) 261 { 262 return null; 263 }, 264 265 reset: function() 266 { 267 this._timelineData = null; 268 /** @type {!Array.<!WebInspector.TracingModel.Event>} */ 269 this._records = []; 270 }, 271 272 /** 273 * @return {!WebInspector.FlameChart.TimelineData} 274 */ 275 timelineData: function() 276 { 277 if (this._timelineData) 278 return this._timelineData; 279 280 /** 281 * @type {?WebInspector.FlameChart.TimelineData} 282 */ 283 this._timelineData = { 284 entryLevels: [], 285 entryTotalTimes: [], 286 entryStartTimes: [] 287 }; 288 289 this._currentLevel = 0; 290 this._headerTitles = {}; 291 this._minimumBoundary = this._timelineModelForMinimumBoundary.minimumRecordTime(); 292 this._timeSpan = Math.max(this._model.maximumRecordTime() - this._minimumBoundary, 1000); 293 var processes = this._model.sortedProcesses(); 294 for (var processIndex = 0; processIndex < processes.length; ++processIndex) { 295 var process = processes[processIndex]; 296 this._appendHeaderRecord(process.name(), this._processHeaderRecord); 297 var objectNames = process.sortedObjectNames(); 298 for (var objectNameIndex = 0; objectNameIndex < objectNames.length; ++objectNameIndex) { 299 this._appendHeaderRecord(WebInspector.UIString("Object %s", objectNames[objectNameIndex]), this._threadHeaderRecord); 300 var objects = process.objectsByName(objectNames[objectNameIndex]); 301 for (var objectIndex = 0; objectIndex < objects.length; ++objectIndex) 302 this._appendRecord(objects[objectIndex], 0); 303 ++this._currentLevel; 304 } 305 var threads = process.sortedThreads(); 306 for (var threadIndex = 0; threadIndex < threads.length; ++threadIndex) { 307 this._appendHeaderRecord(threads[threadIndex].name(), this._threadHeaderRecord); 308 var events = threads[threadIndex].events(); 309 for (var eventIndex = 0; eventIndex < events.length; ++eventIndex) { 310 var event = events[eventIndex]; 311 if (event.duration) 312 this._appendRecord(event, event.level); 313 } 314 this._currentLevel += threads[threadIndex].maxStackDepth(); 315 } 316 ++this._currentLevel; 317 } 318 return this._timelineData; 319 }, 320 321 /** 322 * @return {number} 323 */ 324 minimumBoundary: function() 325 { 326 return this._minimumBoundary; 327 }, 328 329 /** 330 * @return {number} 331 */ 332 totalTime: function() 333 { 334 return this._timeSpan; 335 }, 336 337 /** 338 * @return {number} 339 */ 340 maxStackDepth: function() 341 { 342 return this._currentLevel; 343 }, 344 345 /** 346 * @param {number} entryIndex 347 * @return {?Array.<!{title: string, text: string}>} 348 */ 349 prepareHighlightedEntryInfo: function(entryIndex) 350 { 351 return null; 352 }, 353 354 /** 355 * @param {number} entryIndex 356 * @return {boolean} 357 */ 358 canJumpToEntry: function(entryIndex) 359 { 360 var record = this._records[entryIndex]; 361 return record.phase === WebInspector.TracingModel.Phase.SnapshotObject; 362 }, 363 364 /** 365 * @param {number} entryIndex 366 * @return {string} 367 */ 368 entryColor: function(entryIndex) 369 { 370 var record = this._records[entryIndex]; 371 if (record.phase === WebInspector.TracingModel.Phase.SnapshotObject) 372 return "rgb(20, 150, 20)"; 373 if (record === this._processHeaderRecord) 374 return "#555"; 375 if (record === this._threadHeaderRecord) 376 return "#777"; 377 return this._palette.colorForString(record.name); 378 }, 379 380 /** 381 * @param {number} entryIndex 382 * @param {!CanvasRenderingContext2D} context 383 * @param {?string} text 384 * @param {number} barX 385 * @param {number} barY 386 * @param {number} barWidth 387 * @param {number} barHeight 388 * @param {function(number):number} timeToPosition 389 * @return {boolean} 390 */ 391 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, barHeight, timeToPosition) 392 { 393 return false; 394 }, 395 396 /** 397 * @param {number} entryIndex 398 * @return {boolean} 399 */ 400 forceDecoration: function(entryIndex) 401 { 402 return false; 403 }, 404 405 /** 406 * @param {number} entryIndex 407 * @return {?{startTime: number, endTime: number}} 408 */ 409 highlightTimeRange: function(entryIndex) 410 { 411 var record = this._records[entryIndex]; 412 if (!record || this._isHeaderRecord(record)) 413 return null; 414 return { 415 startTime: record.startTime, 416 endTime: record.endTime 417 } 418 }, 419 420 /** 421 * @return {number} 422 */ 423 paddingLeft: function() 424 { 425 return 0; 426 }, 427 428 /** 429 * @param {number} entryIndex 430 * @return {string} 431 */ 432 textColor: function(entryIndex) 433 { 434 return "white"; 435 }, 436 437 /** 438 * @param {string} title 439 * @param {!WebInspector.TracingModel.Event} record 440 */ 441 _appendHeaderRecord: function(title, record) 442 { 443 var index = this._records.length; 444 this._records.push(record); 445 this._timelineData.entryLevels[index] = this._currentLevel++; 446 this._timelineData.entryTotalTimes[index] = this.totalTime(); 447 this._timelineData.entryStartTimes[index] = this._minimumBoundary; 448 this._headerTitles[index] = title; 449 }, 450 451 /** 452 * @param {!WebInspector.TracingModel.Event} record 453 * @param {number} level 454 */ 455 _appendRecord: function(record, level) 456 { 457 var index = this._records.length; 458 this._records.push(record); 459 this._timelineData.entryLevels[index] = this._currentLevel + level; 460 this._timelineData.entryTotalTimes[index] = record.phase === WebInspector.TracingModel.Phase.SnapshotObject ? NaN : record.duration || 0; 461 this._timelineData.entryStartTimes[index] = record.startTime; 462 }, 463 464 /** 465 * @param {!WebInspector.TracingModel.Event} record 466 */ 467 _isHeaderRecord: function(record) 468 { 469 return record === this._threadHeaderRecord || record === this._processHeaderRecord; 470 }, 471 472 /** 473 * @param {number} index 474 * @return {!WebInspector.TracingModel.Event|undefined} 475 */ 476 _recordAt: function(index) 477 { 478 return this._records[index]; 479 } 480 } 481 482 // The below logic is shamelessly stolen from https://code.google.com/p/trace-viewer/source/browse/trunk/trace_viewer/tracing/color_scheme.js 483 484 /** 485 * @constructor 486 */ 487 WebInspector.TraceViewPalette = function() 488 { 489 this._palette = WebInspector.TraceViewPalette._paletteBase.map(WebInspector.TraceViewPalette._rgbToString); 490 } 491 492 WebInspector.TraceViewPalette._paletteBase = [ 493 [138, 113, 152], 494 [175, 112, 133], 495 [127, 135, 225], 496 [93, 81, 137], 497 [116, 143, 119], 498 [178, 214, 122], 499 [87, 109, 147], 500 [119, 155, 95], 501 [114, 180, 160], 502 [132, 85, 103], 503 [157, 210, 150], 504 [148, 94, 86], 505 [164, 108, 138], 506 [139, 191, 150], 507 [110, 99, 145], 508 [80, 129, 109], 509 [125, 140, 149], 510 [93, 124, 132], 511 [140, 85, 140], 512 [104, 163, 162], 513 [132, 141, 178], 514 [131, 105, 147], 515 [135, 183, 98], 516 [152, 134, 177], 517 [141, 188, 141], 518 [133, 160, 210], 519 [126, 186, 148], 520 [112, 198, 205], 521 [180, 122, 195], 522 [203, 144, 152] 523 ]; 524 525 /** 526 * @param {string} string 527 * @return {number} 528 */ 529 WebInspector.TraceViewPalette._stringHash = function(string) 530 { 531 var hash = 0; 532 for (var i = 0; i < string.length; ++i) 533 hash = (hash + 37 * hash + 11 * string.charCodeAt(i)) % 0xFFFFFFFF; 534 return hash; 535 } 536 537 /** 538 * @param {!Array.<number>} rgb 539 * @return {string} 540 */ 541 WebInspector.TraceViewPalette._rgbToString = function(rgb) 542 { 543 return "rgb(" + rgb.join(",") + ")"; 544 } 545 546 WebInspector.TraceViewPalette.prototype = { 547 /** 548 * @param {string} string 549 * @return {string} 550 */ 551 colorForString: function(string) 552 { 553 var hash = WebInspector.TraceViewPalette._stringHash(string); 554 return this._palette[hash % this._palette.length]; 555 } 556 }; 557