1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * Copyright (C) 2012 Intel Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /** 33 * @constructor 34 * @extends {WebInspector.Object} 35 */ 36 WebInspector.TimelinePresentationModel = function() 37 { 38 this._linkifier = new WebInspector.Linkifier(); 39 this._glueRecords = false; 40 this._filters = []; 41 this.reset(); 42 } 43 44 WebInspector.TimelinePresentationModel.categories = function() 45 { 46 if (WebInspector.TimelinePresentationModel._categories) 47 return WebInspector.TimelinePresentationModel._categories; 48 WebInspector.TimelinePresentationModel._categories = { 49 loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), 0, "#5A8BCC", "#8EB6E9", "#70A2E3"), 50 scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), 1, "#D8AA34", "#F3D07A", "#F1C453"), 51 rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), 2, "#8266CC", "#AF9AEB", "#9A7EE6"), 52 painting: new WebInspector.TimelineCategory("painting", WebInspector.UIString("Painting"), 2, "#5FA050", "#8DC286", "#71B363"), 53 other: new WebInspector.TimelineCategory("other", WebInspector.UIString("Other"), -1, "#BBBBBB", "#DDDDDD", "#DDDDDD"), 54 idle: new WebInspector.TimelineCategory("idle", WebInspector.UIString("Idle"), -1, "#DDDDDD", "#FFFFFF", "#FFFFFF") 55 }; 56 return WebInspector.TimelinePresentationModel._categories; 57 }; 58 59 /** 60 * @return {!Object.<string, {title: string, category: !WebInspector.TimelineCategory}>} 61 */ 62 WebInspector.TimelinePresentationModel._initRecordStyles = function() 63 { 64 if (WebInspector.TimelinePresentationModel._recordStylesMap) 65 return WebInspector.TimelinePresentationModel._recordStylesMap; 66 67 var recordTypes = WebInspector.TimelineModel.RecordType; 68 var categories = WebInspector.TimelinePresentationModel.categories(); 69 70 var recordStyles = {}; 71 recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] }; 72 recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] }; 73 recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] }; 74 recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] }; 75 recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] }; 76 recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] }; 77 recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] }; 78 recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] }; 79 recordStyles[recordTypes.AutosizeText] = { title: WebInspector.UIString("Autosize Text"), category: categories["rendering"] }; 80 recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] }; 81 recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] }; 82 recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Paint"), category: categories["painting"] }; 83 recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] }; 84 recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] }; 85 recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] }; 86 recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] }; 87 recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] }; 88 recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] }; 89 recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] }; 90 recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] }; 91 recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] }; 92 recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] }; 93 recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] }; 94 recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] }; 95 recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] }; 96 recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] }; 97 recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] }; 98 recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] }; 99 recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] }; 100 recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] }; 101 recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] }; 102 recordStyles[recordTypes.MarkFirstPaint] = { title: WebInspector.UIString("First paint"), category: categories["painting"] }; 103 recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] }; 104 recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] }; 105 recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] }; 106 recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] }; 107 recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] }; 108 recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] }; 109 recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] }; 110 recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] }; 111 recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] }; 112 recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] }; 113 recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] }; 114 115 WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles; 116 return recordStyles; 117 } 118 119 /** 120 * @param {!Object} record 121 * @return {{title: string, category: !WebInspector.TimelineCategory}} 122 */ 123 WebInspector.TimelinePresentationModel.recordStyle = function(record) 124 { 125 var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles(); 126 var result = recordStyles[record.type]; 127 if (!result) { 128 result = { 129 title: WebInspector.UIString("Unknown: %s", record.type), 130 category: WebInspector.TimelinePresentationModel.categories()["other"] 131 }; 132 recordStyles[record.type] = result; 133 } 134 return result; 135 } 136 137 WebInspector.TimelinePresentationModel.categoryForRecord = function(record) 138 { 139 return WebInspector.TimelinePresentationModel.recordStyle(record).category; 140 } 141 142 WebInspector.TimelinePresentationModel.isEventDivider = function(record) 143 { 144 var recordTypes = WebInspector.TimelineModel.RecordType; 145 if (record.type === recordTypes.TimeStamp) 146 return true; 147 if (record.type === recordTypes.MarkFirstPaint) 148 return true; 149 if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) { 150 if (record.data && ((typeof record.data.isMainFrame) === "boolean")) 151 return record.data.isMainFrame; 152 } 153 return false; 154 } 155 156 /** 157 * @param {!Array.<*>} recordsArray 158 * @param {?function(*)} preOrderCallback 159 * @param {function(*)=} postOrderCallback 160 */ 161 WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback) 162 { 163 if (!recordsArray) 164 return; 165 var stack = [{array: recordsArray, index: 0}]; 166 while (stack.length) { 167 var entry = stack[stack.length - 1]; 168 var records = entry.array; 169 if (entry.index < records.length) { 170 var record = records[entry.index]; 171 if (preOrderCallback && preOrderCallback(record)) 172 return; 173 if (record.children) 174 stack.push({array: record.children, index: 0, record: record}); 175 else if (postOrderCallback && postOrderCallback(record)) 176 return; 177 ++entry.index; 178 } else { 179 if (entry.record && postOrderCallback && postOrderCallback(entry.record)) 180 return; 181 stack.pop(); 182 } 183 } 184 } 185 186 /** 187 * @param {string=} recordType 188 * @return {boolean} 189 */ 190 WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType) 191 { 192 if (!recordType) 193 return false; 194 const recordTypes = WebInspector.TimelineModel.RecordType; 195 switch (recordType) { 196 case recordTypes.ScheduleResourceRequest: 197 case recordTypes.ResourceSendRequest: 198 case recordTypes.ResourceReceiveResponse: 199 case recordTypes.ResourceReceivedData: 200 case recordTypes.ResourceFinish: 201 return true; 202 default: 203 return false; 204 } 205 } 206 207 /** 208 * @param {string} recordType 209 * @param {string=} title 210 */ 211 WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title) 212 { 213 var eventDivider = document.createElement("div"); 214 eventDivider.className = "resources-event-divider"; 215 var recordTypes = WebInspector.TimelineModel.RecordType; 216 217 if (recordType === recordTypes.MarkDOMContent) 218 eventDivider.className += " resources-blue-divider"; 219 else if (recordType === recordTypes.MarkLoad) 220 eventDivider.className += " resources-red-divider"; 221 else if (recordType === recordTypes.MarkFirstPaint) 222 eventDivider.className += " resources-green-divider"; 223 else if (recordType === recordTypes.TimeStamp) 224 eventDivider.className += " resources-orange-divider"; 225 else if (recordType === recordTypes.BeginFrame) 226 eventDivider.className += " timeline-frame-divider"; 227 228 if (title) 229 eventDivider.title = title; 230 231 return eventDivider; 232 } 233 234 WebInspector.TimelinePresentationModel._hiddenRecords = { } 235 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1; 236 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1; 237 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkFirstPaint] = 1; 238 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1; 239 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1; 240 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.GPUTask] = 1; 241 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ActivateLayerTree] = 1; 242 243 WebInspector.TimelinePresentationModel.prototype = { 244 /** 245 * @param {!WebInspector.TimelinePresentationModel.Filter} filter 246 */ 247 addFilter: function(filter) 248 { 249 this._filters.push(filter); 250 }, 251 252 /** 253 * @param {?WebInspector.TimelinePresentationModel.Filter} filter 254 */ 255 setSearchFilter: function(filter) 256 { 257 this._searchFilter = filter; 258 }, 259 260 rootRecord: function() 261 { 262 return this._rootRecord; 263 }, 264 265 frames: function() 266 { 267 return this._frames; 268 }, 269 270 reset: function() 271 { 272 this._linkifier.reset(); 273 this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false); 274 this._sendRequestRecords = {}; 275 this._scheduledResourceRequests = {}; 276 this._timerRecords = {}; 277 this._requestAnimationFrameRecords = {}; 278 this._eventDividerRecords = []; 279 this._timeRecords = {}; 280 this._timeRecordStack = []; 281 this._frames = []; 282 this._minimumRecordTime = -1; 283 this._layoutInvalidateStack = {}; 284 this._lastScheduleStyleRecalculation = {}; 285 this._webSocketCreateRecords = {}; 286 this._coalescingBuckets = {}; 287 }, 288 289 addFrame: function(frame) 290 { 291 if (!frame.isBackground) 292 this._frames.push(frame); 293 }, 294 295 /** 296 * @param {!TimelineAgent.TimelineEvent} record 297 * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>} 298 */ 299 addRecord: function(record) 300 { 301 if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime) 302 this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record); 303 304 var records; 305 if (record.type === WebInspector.TimelineModel.RecordType.Program) 306 records = this._foldSyncTimeRecords(record.children || []); 307 else 308 records = [record]; 309 var result = Array(records.length); 310 for (var i = 0; i < records.length; ++i) 311 result[i] = this._innerAddRecord(this._rootRecord, records[i]); 312 return result; 313 }, 314 315 /** 316 * @param {!WebInspector.TimelinePresentationModel.Record} parentRecord 317 * @param {!TimelineAgent.TimelineEvent} record 318 * @return {!WebInspector.TimelinePresentationModel.Record} 319 */ 320 _innerAddRecord: function(parentRecord, record) 321 { 322 const recordTypes = WebInspector.TimelineModel.RecordType; 323 var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords; 324 var origin; 325 var coalescingBucket; 326 327 if (!isHiddenRecord) { 328 var newParentRecord = this._findParentRecord(record); 329 if (newParentRecord) { 330 origin = parentRecord; 331 parentRecord = newParentRecord; 332 } 333 // On main thread, only coalesce if the last event is of same type. 334 if (parentRecord === this._rootRecord) 335 coalescingBucket = record.thread ? record.type : "mainThread"; 336 var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket); 337 if (coalescedRecord) { 338 if (!origin) 339 origin = parentRecord; 340 parentRecord = coalescedRecord; 341 } 342 } 343 344 var children = record.children; 345 var scriptDetails = null; 346 if (record.data && record.data["scriptName"]) { 347 scriptDetails = { 348 scriptName: record.data["scriptName"], 349 scriptLine: record.data["scriptLine"] 350 } 351 }; 352 353 if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) { 354 var childRecord = children[0]; 355 if (childRecord.type === recordTypes.FunctionCall) { 356 scriptDetails = { 357 scriptName: childRecord.data["scriptName"], 358 scriptLine: childRecord.data["scriptLine"] 359 }; 360 children = childRecord.children.concat(children.slice(1)); 361 } 362 } 363 364 var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord); 365 366 if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord)) 367 this._eventDividerRecords.push(formattedRecord); 368 369 if (isHiddenRecord) 370 return formattedRecord; 371 372 formattedRecord.collapsed = parentRecord === this._rootRecord; 373 if (coalescingBucket) 374 this._coalescingBuckets[coalescingBucket] = formattedRecord; 375 376 if (children) { 377 children = this._foldSyncTimeRecords(children); 378 for (var i = 0; i < children.length; ++i) 379 this._innerAddRecord(formattedRecord, children[i]); 380 } 381 382 formattedRecord.calculateAggregatedStats(); 383 if (parentRecord.coalesced) 384 this._updateCoalescingParent(formattedRecord); 385 else if (origin) 386 this._updateAncestorStats(formattedRecord); 387 388 origin = formattedRecord.origin(); 389 if (!origin.isRoot() && !origin.coalesced) 390 origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime; 391 return formattedRecord; 392 }, 393 394 /** 395 * @param {!WebInspector.TimelinePresentationModel.Record} record 396 */ 397 _updateAncestorStats: function(record) 398 { 399 var lastChildEndTime = record.lastChildEndTime; 400 var aggregatedStats = record.aggregatedStats; 401 for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) { 402 currentRecord._cpuTime += record._cpuTime; 403 if (currentRecord.lastChildEndTime < lastChildEndTime) 404 currentRecord.lastChildEndTime = lastChildEndTime; 405 for (var category in aggregatedStats) 406 currentRecord.aggregatedStats[category] += aggregatedStats[category]; 407 } 408 }, 409 410 /** 411 * @param {!Object} record 412 * @param {!Object} newParent 413 * @param {string=} bucket 414 * @return {?WebInspector.TimelinePresentationModel.Record} 415 */ 416 _findCoalescedParent: function(record, newParent, bucket) 417 { 418 const coalescingThresholdSeconds = 0.005; 419 420 var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent.children.peekLast(); 421 if (lastRecord && lastRecord.coalesced) 422 lastRecord = lastRecord.children.peekLast(); 423 var startTime = WebInspector.TimelineModel.startTimeInSeconds(record); 424 var endTime = WebInspector.TimelineModel.endTimeInSeconds(record); 425 if (!lastRecord) 426 return null; 427 if (lastRecord.type !== record.type) 428 return null; 429 if (lastRecord.endTime + coalescingThresholdSeconds < startTime) 430 return null; 431 if (endTime + coalescingThresholdSeconds < lastRecord.startTime) 432 return null; 433 if (WebInspector.TimelinePresentationModel.coalescingKeyForRecord(record) !== WebInspector.TimelinePresentationModel.coalescingKeyForRecord(lastRecord._record)) 434 return null; 435 if (lastRecord.parent.coalesced) 436 return lastRecord.parent; 437 return this._replaceWithCoalescedRecord(lastRecord); 438 }, 439 440 /** 441 * @param {!WebInspector.TimelinePresentationModel.Record} record 442 * @return {!WebInspector.TimelinePresentationModel.Record} 443 */ 444 _replaceWithCoalescedRecord: function(record) 445 { 446 var rawRecord = { 447 type: record._record.type, 448 startTime: record._record.startTime, 449 endTime: record._record.endTime, 450 data: { } 451 }; 452 if (record._record.thread) 453 rawRecord.thread = "aggregated"; 454 if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp) 455 rawRecord.data.message = record.data.message; 456 457 var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false); 458 var parent = record.parent; 459 460 coalescedRecord.coalesced = true; 461 coalescedRecord.collapsed = true; 462 coalescedRecord._children.push(record); 463 record.parent = coalescedRecord; 464 if (record.hasWarnings() || record.childHasWarnings()) 465 coalescedRecord._childHasWarnings = true; 466 467 coalescedRecord.parent = parent; 468 parent._children[parent._children.indexOf(record)] = coalescedRecord; 469 WebInspector.TimelineModel.aggregateTimeByCategory(coalescedRecord._aggregatedStats, record._aggregatedStats); 470 471 return coalescedRecord; 472 }, 473 474 /** 475 * @param {!WebInspector.TimelinePresentationModel.Record} record 476 */ 477 _updateCoalescingParent: function(record) 478 { 479 var parentRecord = record.parent; 480 WebInspector.TimelineModel.aggregateTimeByCategory(parentRecord._aggregatedStats, record._aggregatedStats); 481 if (parentRecord.startTime > record._record.startTime) 482 parentRecord._record.startTime = record._record.startTime; 483 if (parentRecord.endTime < record._record.endTime) { 484 parentRecord._record.endTime = record._record.endTime; 485 parentRecord.lastChildEndTime = parentRecord.endTime; 486 } 487 }, 488 489 /** 490 * @param {!Array.<!TimelineAgent.TimelineEvent>} records 491 */ 492 _foldSyncTimeRecords: function(records) 493 { 494 var recordTypes = WebInspector.TimelineModel.RecordType; 495 // Fast case -- if there are no Time records, return input as is. 496 for (var i = 0; i < records.length && records[i].type !== recordTypes.Time; ++i) {} 497 if (i === records.length) 498 return records; 499 500 var result = []; 501 var stack = []; 502 for (var i = 0; i < records.length; ++i) { 503 result.push(records[i]); 504 if (records[i].type === recordTypes.Time) { 505 stack.push(result.length - 1); 506 continue; 507 } 508 if (records[i].type !== recordTypes.TimeEnd) 509 continue; 510 while (stack.length) { 511 var begin = stack.pop(); 512 if (result[begin].data.message !== records[i].data.message) 513 continue; 514 var timeEndRecord = /** @type {!TimelineAgent.TimelineEvent} */ (result.pop()); 515 var children = result.splice(begin + 1, result.length - begin); 516 result[begin] = this._createSynchronousTimeRecord(result[begin], timeEndRecord, children); 517 break; 518 } 519 } 520 return result; 521 }, 522 523 /** 524 * @param {!TimelineAgent.TimelineEvent} beginRecord 525 * @param {!TimelineAgent.TimelineEvent} endRecord 526 * @param {!Array.<!TimelineAgent.TimelineEvent>} children 527 * @return {!TimelineAgent.TimelineEvent} 528 */ 529 _createSynchronousTimeRecord: function(beginRecord, endRecord, children) 530 { 531 return { 532 type: beginRecord.type, 533 startTime: beginRecord.startTime, 534 endTime: endRecord.startTime, 535 stackTrace: beginRecord.stackTrace, 536 children: children, 537 data: { 538 message: beginRecord.data.message, 539 isSynchronous: true 540 }, 541 }; 542 }, 543 544 _findParentRecord: function(record) 545 { 546 if (!this._glueRecords) 547 return null; 548 var recordTypes = WebInspector.TimelineModel.RecordType; 549 550 switch (record.type) { 551 case recordTypes.ResourceReceiveResponse: 552 case recordTypes.ResourceFinish: 553 case recordTypes.ResourceReceivedData: 554 return this._sendRequestRecords[record.data["requestId"]]; 555 556 case recordTypes.ResourceSendRequest: 557 return this._rootRecord; 558 559 case recordTypes.TimerFire: 560 return this._timerRecords[record.data["timerId"]]; 561 562 case recordTypes.ResourceSendRequest: 563 return this._scheduledResourceRequests[record.data["url"]]; 564 565 case recordTypes.FireAnimationFrame: 566 return this._requestAnimationFrameRecords[record.data["id"]]; 567 } 568 }, 569 570 setGlueRecords: function(glue) 571 { 572 this._glueRecords = glue; 573 }, 574 575 invalidateFilteredRecords: function() 576 { 577 delete this._filteredRecords; 578 }, 579 580 filteredRecords: function() 581 { 582 if (this._filteredRecords) 583 return this._filteredRecords; 584 585 var recordsInWindow = []; 586 var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false, parentRecord: {}}]; 587 var revealedDepth = 0; 588 589 function revealRecordsInStack() { 590 for (var depth = revealedDepth + 1; depth < stack.length; ++depth) { 591 if (stack[depth - 1].parentIsCollapsed) { 592 stack[depth].parentRecord.parent._expandable = true; 593 return; 594 } 595 stack[depth - 1].parentRecord.collapsed = false; 596 recordsInWindow.push(stack[depth].parentRecord); 597 stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length; 598 stack[depth].parentIsRevealed = true; 599 revealedDepth = depth; 600 } 601 } 602 603 while (stack.length) { 604 var entry = stack[stack.length - 1]; 605 var records = entry.children; 606 if (records && entry.index < records.length) { 607 var record = records[entry.index]; 608 ++entry.index; 609 610 if (this.isVisible(record)) { 611 record.parent._expandable = true; 612 if (this._searchFilter) 613 revealRecordsInStack(); 614 if (!entry.parentIsCollapsed) { 615 recordsInWindow.push(record); 616 revealedDepth = stack.length; 617 entry.parentRecord.collapsed = false; 618 } 619 } 620 621 record._expandable = false; 622 623 stack.push({children: record.children, 624 index: 0, 625 parentIsCollapsed: (entry.parentIsCollapsed || (record.collapsed && (!this._searchFilter || record.clicked))), 626 parentRecord: record, 627 windowLengthBeforeChildrenTraversal: recordsInWindow.length}); 628 } else { 629 stack.pop(); 630 revealedDepth = Math.min(revealedDepth, stack.length - 1); 631 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal; 632 } 633 } 634 635 this._filteredRecords = recordsInWindow; 636 return recordsInWindow; 637 }, 638 639 filteredFrames: function(startTime, endTime) 640 { 641 function compareStartTime(value, object) 642 { 643 return value - object.startTime; 644 } 645 function compareEndTime(value, object) 646 { 647 return value - object.endTime; 648 } 649 var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime); 650 var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime); 651 while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime) 652 ++lastFrame; 653 return this._frames.slice(firstFrame, lastFrame); 654 }, 655 656 eventDividerRecords: function() 657 { 658 return this._eventDividerRecords; 659 }, 660 661 isVisible: function(record) 662 { 663 for (var i = 0; i < this._filters.length; ++i) { 664 if (!this._filters[i].accept(record)) 665 return false; 666 } 667 return !this._searchFilter || this._searchFilter.accept(record); 668 }, 669 670 /** 671 * @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info 672 * @return {!Element} 673 */ 674 generateMainThreadBarPopupContent: function(info) 675 { 676 var firstTaskIndex = info.firstTaskIndex; 677 var lastTaskIndex = info.lastTaskIndex; 678 var tasks = info.tasks; 679 var messageCount = lastTaskIndex - firstTaskIndex + 1; 680 var cpuTime = 0; 681 682 for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) { 683 var task = tasks[i]; 684 cpuTime += WebInspector.TimelineModel.endTimeInSeconds(task) - WebInspector.TimelineModel.startTimeInSeconds(task); 685 } 686 var startTime = WebInspector.TimelineModel.startTimeInSeconds(tasks[firstTaskIndex]); 687 var endTime = WebInspector.TimelineModel.endTimeInSeconds(tasks[lastTaskIndex]); 688 var duration = endTime - startTime; 689 var offset = this._minimumRecordTime; 690 691 var contentHelper = new WebInspector.TimelinePopupContentHelper(info.name); 692 var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true), 693 Number.secondsToString(startTime - offset, true)); 694 contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); 695 contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true)); 696 contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount); 697 return contentHelper.contentTable(); 698 }, 699 700 __proto__: WebInspector.Object.prototype 701 } 702 703 /** 704 * @constructor 705 * @param {!WebInspector.TimelinePresentationModel} presentationModel 706 * @param {!Object} record 707 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord 708 * @param {?WebInspector.TimelinePresentationModel.Record} origin 709 * @param {?Object} scriptDetails 710 * @param {boolean} hidden 711 */ 712 WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden) 713 { 714 this._linkifier = presentationModel._linkifier; 715 this._aggregatedStats = {}; 716 this._record = record; 717 this._children = []; 718 if (!hidden && parentRecord) { 719 this.parent = parentRecord; 720 if (this.isBackground) 721 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this); 722 else 723 parentRecord.children.push(this); 724 } 725 if (origin) 726 this._origin = origin; 727 728 this._selfTime = this.endTime - this.startTime; 729 this._lastChildEndTime = this.endTime; 730 this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime; 731 732 if (record.data) { 733 if (record.data["url"]) 734 this.url = record.data["url"]; 735 if (record.data["rootNode"]) 736 this._relatedBackendNodeId = record.data["rootNode"]; 737 else if (record.data["elementId"]) 738 this._relatedBackendNodeId = record.data["elementId"]; 739 } 740 if (scriptDetails) { 741 this.scriptName = scriptDetails.scriptName; 742 this.scriptLine = scriptDetails.scriptLine; 743 } 744 if (parentRecord && parentRecord.callSiteStackTrace) 745 this.callSiteStackTrace = parentRecord.callSiteStackTrace; 746 747 var recordTypes = WebInspector.TimelineModel.RecordType; 748 switch (record.type) { 749 case recordTypes.ResourceSendRequest: 750 // Make resource receive record last since request was sent; make finish record last since response received. 751 presentationModel._sendRequestRecords[record.data["requestId"]] = this; 752 break; 753 754 case recordTypes.ScheduleResourceRequest: 755 presentationModel._scheduledResourceRequests[record.data["url"]] = this; 756 break; 757 758 case recordTypes.ResourceReceiveResponse: 759 var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]]; 760 if (sendRequestRecord) { // False if we started instrumentation in the middle of request. 761 this.url = sendRequestRecord.url; 762 // Now that we have resource in the collection, recalculate details in order to display short url. 763 sendRequestRecord._refreshDetails(); 764 if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest) 765 sendRequestRecord.parent._refreshDetails(); 766 } 767 break; 768 769 case recordTypes.ResourceReceivedData: 770 case recordTypes.ResourceFinish: 771 var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]]; 772 if (sendRequestRecord) // False for main resource. 773 this.url = sendRequestRecord.url; 774 break; 775 776 case recordTypes.TimerInstall: 777 this.timeout = record.data["timeout"]; 778 this.singleShot = record.data["singleShot"]; 779 presentationModel._timerRecords[record.data["timerId"]] = this; 780 break; 781 782 case recordTypes.TimerFire: 783 var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]]; 784 if (timerInstalledRecord) { 785 this.callSiteStackTrace = timerInstalledRecord.stackTrace; 786 this.timeout = timerInstalledRecord.timeout; 787 this.singleShot = timerInstalledRecord.singleShot; 788 } 789 break; 790 791 case recordTypes.RequestAnimationFrame: 792 presentationModel._requestAnimationFrameRecords[record.data["id"]] = this; 793 break; 794 795 case recordTypes.FireAnimationFrame: 796 var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]]; 797 if (requestAnimationRecord) 798 this.callSiteStackTrace = requestAnimationRecord.stackTrace; 799 break; 800 801 case recordTypes.Time: 802 if (record.data.isSynchronous) 803 break; 804 var message = record.data["message"]; 805 var oldReference = presentationModel._timeRecords[message]; 806 if (oldReference) 807 break; 808 presentationModel._timeRecords[message] = this; 809 if (origin) 810 presentationModel._timeRecordStack.push(this); 811 break; 812 813 case recordTypes.TimeEnd: 814 var message = record.data["message"]; 815 var timeRecord = presentationModel._timeRecords[message]; 816 delete presentationModel._timeRecords[message]; 817 if (timeRecord) { 818 this.timeRecord = timeRecord; 819 timeRecord.timeEndRecord = this; 820 var intervalDuration = this.startTime - timeRecord.startTime; 821 this.intervalDuration = intervalDuration; 822 timeRecord.intervalDuration = intervalDuration; 823 } 824 break; 825 826 case recordTypes.ScheduleStyleRecalculation: 827 presentationModel._lastScheduleStyleRecalculation[this.frameId] = this; 828 break; 829 830 case recordTypes.RecalculateStyles: 831 var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId]; 832 if (!scheduleStyleRecalculationRecord) 833 break; 834 this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace; 835 break; 836 837 case recordTypes.InvalidateLayout: 838 // Consider style recalculation as a reason for layout invalidation, 839 // but only if we had no earlier layout invalidation records. 840 var styleRecalcStack; 841 if (!presentationModel._layoutInvalidateStack[this.frameId]) { 842 for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) { 843 if (outerRecord.type === recordTypes.RecalculateStyles) { 844 styleRecalcStack = outerRecord.callSiteStackTrace; 845 break; 846 } 847 } 848 } 849 presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace; 850 break; 851 852 case recordTypes.Layout: 853 var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId]; 854 if (layoutInvalidateStack) 855 this.callSiteStackTrace = layoutInvalidateStack; 856 if (this.stackTrace) 857 this.addWarning(WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.")); 858 859 presentationModel._layoutInvalidateStack[this.frameId] = null; 860 this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data); 861 this._relatedBackendNodeId = record.data["rootNode"]; 862 break; 863 864 case recordTypes.AutosizeText: 865 if (record.data.needsRelayout && parentRecord.type === recordTypes.Layout) 866 parentRecord.addWarning(WebInspector.UIString("Layout required two passes due to text autosizing, consider setting viewport.")); 867 break; 868 869 case recordTypes.Paint: 870 this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data); 871 break; 872 873 case recordTypes.WebSocketCreate: 874 this.webSocketURL = record.data["url"]; 875 if (typeof record.data["webSocketProtocol"] !== "undefined") 876 this.webSocketProtocol = record.data["webSocketProtocol"]; 877 presentationModel._webSocketCreateRecords[record.data["identifier"]] = this; 878 break; 879 880 case recordTypes.WebSocketSendHandshakeRequest: 881 case recordTypes.WebSocketReceiveHandshakeResponse: 882 case recordTypes.WebSocketDestroy: 883 var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]]; 884 if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request. 885 this.webSocketURL = webSocketCreateRecord.webSocketURL; 886 if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined") 887 this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol; 888 } 889 break; 890 } 891 } 892 893 WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record) 894 { 895 record.parent.children.splice(record.parent.children.indexOf(record)); 896 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record); 897 record.parent = newParent; 898 } 899 900 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record) 901 { 902 function compareStartTime(value, record) 903 { 904 return value < record.startTime ? -1 : 1; 905 } 906 907 parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record); 908 } 909 910 WebInspector.TimelinePresentationModel.Record.prototype = { 911 get lastChildEndTime() 912 { 913 return this._lastChildEndTime; 914 }, 915 916 set lastChildEndTime(time) 917 { 918 this._lastChildEndTime = time; 919 }, 920 921 get selfTime() 922 { 923 return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime; 924 }, 925 926 set selfTime(time) 927 { 928 this._selfTime = time; 929 }, 930 931 get cpuTime() 932 { 933 return this._cpuTime; 934 }, 935 936 /** 937 * @return {boolean} 938 */ 939 isRoot: function() 940 { 941 return this.type === WebInspector.TimelineModel.RecordType.Root; 942 }, 943 944 /** 945 * @return {!WebInspector.TimelinePresentationModel.Record} 946 */ 947 origin: function() 948 { 949 return this._origin || this.parent; 950 }, 951 952 /** 953 * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>} 954 */ 955 get children() 956 { 957 return this._children; 958 }, 959 960 /** 961 * @return {number} 962 */ 963 get visibleChildrenCount() 964 { 965 return this._visibleChildrenCount || 0; 966 }, 967 968 /** 969 * @return {boolean} 970 */ 971 get expandable() 972 { 973 return !!this._expandable; 974 }, 975 976 /** 977 * @return {!WebInspector.TimelineCategory} 978 */ 979 get category() 980 { 981 return WebInspector.TimelinePresentationModel.recordStyle(this._record).category 982 }, 983 984 /** 985 * @return {string} 986 */ 987 get title() 988 { 989 return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] : 990 WebInspector.TimelinePresentationModel.recordStyle(this._record).title; 991 }, 992 993 /** 994 * @return {number} 995 */ 996 get startTime() 997 { 998 return WebInspector.TimelineModel.startTimeInSeconds(this._record); 999 }, 1000 1001 /** 1002 * @return {number} 1003 */ 1004 get endTime() 1005 { 1006 return WebInspector.TimelineModel.endTimeInSeconds(this._record); 1007 }, 1008 1009 /** 1010 * @return {boolean} 1011 */ 1012 get isBackground() 1013 { 1014 return !!this._record.thread; 1015 }, 1016 1017 /** 1018 * @return {!Object} 1019 */ 1020 get data() 1021 { 1022 return this._record.data; 1023 }, 1024 1025 /** 1026 * @return {string} 1027 */ 1028 get type() 1029 { 1030 return this._record.type; 1031 }, 1032 1033 /** 1034 * @return {string} 1035 */ 1036 get frameId() 1037 { 1038 return this._record.frameId; 1039 }, 1040 1041 /** 1042 * @return {number} 1043 */ 1044 get usedHeapSizeDelta() 1045 { 1046 return this._record.usedHeapSizeDelta || 0; 1047 }, 1048 1049 /** 1050 * @return {number} 1051 */ 1052 get usedHeapSize() 1053 { 1054 return this._record.usedHeapSize; 1055 }, 1056 1057 /** 1058 * @return {?Array.<!ConsoleAgent.CallFrame>} 1059 */ 1060 get stackTrace() 1061 { 1062 if (this._record.stackTrace && this._record.stackTrace.length) 1063 return this._record.stackTrace; 1064 return null; 1065 }, 1066 1067 containsTime: function(time) 1068 { 1069 return this.startTime <= time && time <= this.endTime; 1070 }, 1071 1072 /** 1073 * @param {function(!DocumentFragment)} callback 1074 */ 1075 generatePopupContent: function(callback) 1076 { 1077 var barrier = new CallbackBarrier(); 1078 if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type) && !this._imagePreviewElement) 1079 WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, barrier.createCallback(this._setImagePreviewElement.bind(this))); 1080 if (this._relatedBackendNodeId && !this._relatedNode) 1081 WebInspector.domAgent.pushNodeByBackendIdToFrontend(this._relatedBackendNodeId, barrier.createCallback(this._setRelatedNode.bind(this))); 1082 1083 barrier.callWhenDone(callbackWrapper.bind(this)); 1084 1085 /** 1086 * @this {WebInspector.TimelinePresentationModel.Record} 1087 */ 1088 function callbackWrapper() 1089 { 1090 callback(this._generatePopupContentSynchronously()); 1091 } 1092 }, 1093 1094 /** 1095 * @param {string} key 1096 * @return {?Object} 1097 */ 1098 getUserObject: function(key) 1099 { 1100 if (!this._userObjects) 1101 return null; 1102 return this._userObjects.get(key); 1103 }, 1104 1105 /** 1106 * @param {string} key 1107 * @param {!Object} value 1108 */ 1109 setUserObject: function(key, value) 1110 { 1111 if (!this._userObjects) 1112 this._userObjects = new StringMap(); 1113 this._userObjects.put(key, value); 1114 }, 1115 1116 /** 1117 * @param {!Element} element 1118 */ 1119 _setImagePreviewElement: function(element) 1120 { 1121 this._imagePreviewElement = element; 1122 }, 1123 1124 /** 1125 * @param {?DOMAgent.NodeId} nodeId 1126 */ 1127 _setRelatedNode: function(nodeId) 1128 { 1129 if (typeof nodeId === "number") 1130 this._relatedNode = WebInspector.domAgent.nodeForId(nodeId); 1131 }, 1132 1133 /** 1134 * @return {!DocumentFragment} 1135 */ 1136 _generatePopupContentSynchronously: function() 1137 { 1138 var fragment = document.createDocumentFragment(); 1139 var pie = WebInspector.TimelinePresentationModel.generatePieChart(this._aggregatedStats, this.category.name); 1140 // Insert self time. 1141 if (!this.coalesced && this._children.length) { 1142 pie.pieChart.addSlice(this._selfTime, this.category.fillColorStop1); 1143 var rowElement = document.createElement("div"); 1144 pie.footerElement.insertBefore(rowElement, pie.footerElement.firstChild); 1145 rowElement.createChild("div", "timeline-aggregated-category timeline-" + this.category.name); 1146 rowElement.createTextChild(WebInspector.UIString("%s %s (Self)", Number.secondsToString(this._selfTime, true), this.category.title)); 1147 } 1148 fragment.appendChild(pie.element); 1149 1150 var contentHelper = new WebInspector.TimelineDetailsContentHelper(true); 1151 1152 if (this.coalesced) 1153 return fragment; 1154 1155 const recordTypes = WebInspector.TimelineModel.RecordType; 1156 1157 // The messages may vary per record type; 1158 var callSiteStackTraceLabel; 1159 var callStackLabel; 1160 var relatedNodeLabel; 1161 1162 switch (this.type) { 1163 case recordTypes.GCEvent: 1164 contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"])); 1165 break; 1166 case recordTypes.TimerFire: 1167 callSiteStackTraceLabel = WebInspector.UIString("Timer installed"); 1168 // Fall-through intended. 1169 1170 case recordTypes.TimerInstall: 1171 case recordTypes.TimerRemove: 1172 contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]); 1173 if (typeof this.timeout === "number") { 1174 contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000)); 1175 contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot); 1176 } 1177 break; 1178 case recordTypes.FireAnimationFrame: 1179 callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested"); 1180 contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]); 1181 break; 1182 case recordTypes.FunctionCall: 1183 if (this.scriptName) 1184 contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyLocation(this.scriptName, this.scriptLine, 0)); 1185 break; 1186 case recordTypes.ScheduleResourceRequest: 1187 case recordTypes.ResourceSendRequest: 1188 case recordTypes.ResourceReceiveResponse: 1189 case recordTypes.ResourceReceivedData: 1190 case recordTypes.ResourceFinish: 1191 contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url)); 1192 if (this._imagePreviewElement) 1193 contentHelper.appendElementRow(WebInspector.UIString("Preview"), this._imagePreviewElement); 1194 if (this.data["requestMethod"]) 1195 contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]); 1196 if (typeof this.data["statusCode"] === "number") 1197 contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]); 1198 if (this.data["mimeType"]) 1199 contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]); 1200 if (this.data["encodedDataLength"]) 1201 contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"])); 1202 break; 1203 case recordTypes.EvaluateScript: 1204 if (this.data && this.url) 1205 contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"])); 1206 break; 1207 case recordTypes.Paint: 1208 var clip = this.data["clip"]; 1209 if (clip) { 1210 contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1])); 1211 var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip); 1212 var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip); 1213 contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d %d", clipWidth, clipHeight)); 1214 } else { 1215 // Backward compatibility: older version used x, y, width, height fields directly in data. 1216 if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined") 1217 contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"])); 1218 if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined") 1219 contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"])); 1220 } 1221 // Fall-through intended. 1222 1223 case recordTypes.PaintSetup: 1224 case recordTypes.Rasterize: 1225 case recordTypes.ScrollLayer: 1226 relatedNodeLabel = WebInspector.UIString("Layer root"); 1227 break; 1228 case recordTypes.AutosizeText: 1229 relatedNodeLabel = WebInspector.UIString("Root node"); 1230 break; 1231 case recordTypes.DecodeImage: 1232 case recordTypes.ResizeImage: 1233 relatedNodeLabel = WebInspector.UIString("Image element"); 1234 if (this.url) 1235 contentHelper.appendElementRow(WebInspector.UIString("Image URL"), WebInspector.linkifyResourceAsNode(this.url)); 1236 break; 1237 case recordTypes.RecalculateStyles: // We don't want to see default details. 1238 if (this.data["elementCount"]) 1239 contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]); 1240 callStackLabel = WebInspector.UIString("Styles recalculation forced"); 1241 break; 1242 case recordTypes.Layout: 1243 if (this.data["dirtyObjects"]) 1244 contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]); 1245 if (this.data["totalObjects"]) 1246 contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]); 1247 if (typeof this.data["partialLayout"] === "boolean") { 1248 contentHelper.appendTextRow(WebInspector.UIString("Layout scope"), 1249 this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document")); 1250 } 1251 callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated"); 1252 callStackLabel = WebInspector.UIString("Layout forced"); 1253 relatedNodeLabel = WebInspector.UIString("Layout root"); 1254 break; 1255 case recordTypes.Time: 1256 case recordTypes.TimeEnd: 1257 contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]); 1258 if (typeof this.intervalDuration === "number") 1259 contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true)); 1260 break; 1261 case recordTypes.WebSocketCreate: 1262 case recordTypes.WebSocketSendHandshakeRequest: 1263 case recordTypes.WebSocketReceiveHandshakeResponse: 1264 case recordTypes.WebSocketDestroy: 1265 if (typeof this.webSocketURL !== "undefined") 1266 contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL); 1267 if (typeof this.webSocketProtocol !== "undefined") 1268 contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol); 1269 if (typeof this.data["message"] !== "undefined") 1270 contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]); 1271 break; 1272 default: 1273 if (this.detailsNode()) 1274 contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode()); 1275 break; 1276 } 1277 1278 if (this._relatedNode) 1279 contentHelper.appendElementRow(relatedNodeLabel || WebInspector.UIString("Related node"), this._createNodeAnchor(this._relatedNode)); 1280 1281 if (this.scriptName && this.type !== recordTypes.FunctionCall) 1282 contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyLocation(this.scriptName, this.scriptLine, 0)); 1283 1284 if (this.usedHeapSize) { 1285 if (this.usedHeapSizeDelta) { 1286 var sign = this.usedHeapSizeDelta > 0 ? "+" : "-"; 1287 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), 1288 WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(Math.abs(this.usedHeapSizeDelta)))); 1289 } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting) 1290 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize)); 1291 } 1292 1293 if (this.callSiteStackTrace) 1294 contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this)); 1295 1296 if (this.stackTrace) 1297 contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this)); 1298 1299 if (this._warnings) { 1300 var ul = document.createElement("ul"); 1301 for (var i = 0; i < this._warnings.length; ++i) 1302 ul.createChild("li").textContent = this._warnings[i]; 1303 contentHelper.appendElementRow(WebInspector.UIString("Warning"), ul); 1304 } 1305 fragment.appendChild(contentHelper.element); 1306 return fragment; 1307 }, 1308 1309 /** 1310 * @param {!WebInspector.DOMAgent} node 1311 */ 1312 _createNodeAnchor: function(node) 1313 { 1314 var span = document.createElement("span"); 1315 span.classList.add("node-link"); 1316 span.addEventListener("click", onClick, false); 1317 WebInspector.DOMPresentationUtils.decorateNodeLabel(node, span); 1318 function onClick() 1319 { 1320 WebInspector.showPanel("elements").revealAndSelectNode(node.id); 1321 } 1322 return span; 1323 }, 1324 1325 _refreshDetails: function() 1326 { 1327 delete this._detailsNode; 1328 }, 1329 1330 /** 1331 * @return {?Node} 1332 */ 1333 detailsNode: function() 1334 { 1335 if (typeof this._detailsNode === "undefined") { 1336 this._detailsNode = this._getRecordDetails(); 1337 1338 if (this._detailsNode && !this.coalesced) { 1339 this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild); 1340 this._detailsNode.appendChild(document.createTextNode(")")); 1341 } 1342 } 1343 return this._detailsNode; 1344 }, 1345 1346 _createSpanWithText: function(textContent) 1347 { 1348 var node = document.createElement("span"); 1349 node.textContent = textContent; 1350 return node; 1351 }, 1352 1353 /** 1354 * @return {?Node} 1355 */ 1356 _getRecordDetails: function() 1357 { 1358 var details; 1359 if (this.coalesced) 1360 return this._createSpanWithText(WebInspector.UIString(" %d", this.children.length)); 1361 1362 switch (this.type) { 1363 case WebInspector.TimelineModel.RecordType.GCEvent: 1364 details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"])); 1365 break; 1366 case WebInspector.TimelineModel.RecordType.TimerFire: 1367 details = this._linkifyScriptLocation(this.data["timerId"]); 1368 break; 1369 case WebInspector.TimelineModel.RecordType.FunctionCall: 1370 if (this.scriptName) 1371 details = this._linkifyLocation(this.scriptName, this.scriptLine, 0); 1372 break; 1373 case WebInspector.TimelineModel.RecordType.FireAnimationFrame: 1374 details = this._linkifyScriptLocation(this.data["id"]); 1375 break; 1376 case WebInspector.TimelineModel.RecordType.EventDispatch: 1377 details = this.data ? this.data["type"] : null; 1378 break; 1379 case WebInspector.TimelineModel.RecordType.Paint: 1380 var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width; 1381 var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height; 1382 if (width && height) 1383 details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height); 1384 break; 1385 case WebInspector.TimelineModel.RecordType.TimerInstall: 1386 case WebInspector.TimelineModel.RecordType.TimerRemove: 1387 details = this._linkifyTopCallFrame(this.data["timerId"]); 1388 break; 1389 case WebInspector.TimelineModel.RecordType.RequestAnimationFrame: 1390 case WebInspector.TimelineModel.RecordType.CancelAnimationFrame: 1391 details = this._linkifyTopCallFrame(this.data["id"]); 1392 break; 1393 case WebInspector.TimelineModel.RecordType.ParseHTML: 1394 case WebInspector.TimelineModel.RecordType.RecalculateStyles: 1395 details = this._linkifyTopCallFrame(); 1396 break; 1397 case WebInspector.TimelineModel.RecordType.EvaluateScript: 1398 details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null; 1399 break; 1400 case WebInspector.TimelineModel.RecordType.XHRReadyStateChange: 1401 case WebInspector.TimelineModel.RecordType.XHRLoad: 1402 case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest: 1403 case WebInspector.TimelineModel.RecordType.ResourceSendRequest: 1404 case WebInspector.TimelineModel.RecordType.ResourceReceivedData: 1405 case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse: 1406 case WebInspector.TimelineModel.RecordType.ResourceFinish: 1407 case WebInspector.TimelineModel.RecordType.DecodeImage: 1408 case WebInspector.TimelineModel.RecordType.ResizeImage: 1409 details = WebInspector.displayNameForURL(this.url); 1410 break; 1411 case WebInspector.TimelineModel.RecordType.Time: 1412 case WebInspector.TimelineModel.RecordType.TimeEnd: 1413 details = this.data["message"]; 1414 break; 1415 default: 1416 details = this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : (this._linkifyTopCallFrame() || null); 1417 break; 1418 } 1419 1420 if (details) { 1421 if (details instanceof Node) 1422 details.tabIndex = -1; 1423 else 1424 return this._createSpanWithText("" + details); 1425 } 1426 1427 return details || null; 1428 }, 1429 1430 /** 1431 * @param {string} url 1432 * @param {number} lineNumber 1433 * @param {number=} columnNumber 1434 */ 1435 _linkifyLocation: function(url, lineNumber, columnNumber) 1436 { 1437 // FIXME(62725): stack trace line/column numbers are one-based. 1438 columnNumber = columnNumber ? columnNumber - 1 : 0; 1439 return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details"); 1440 }, 1441 1442 /** 1443 * @param {!ConsoleAgent.CallFrame} callFrame 1444 */ 1445 _linkifyCallFrame: function(callFrame) 1446 { 1447 return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber); 1448 }, 1449 1450 /** 1451 * @param {string=} defaultValue 1452 */ 1453 _linkifyTopCallFrame: function(defaultValue) 1454 { 1455 if (this.stackTrace) 1456 return this._linkifyCallFrame(this.stackTrace[0]); 1457 if (this.callSiteStackTrace) 1458 return this._linkifyCallFrame(this.callSiteStackTrace[0]); 1459 return defaultValue; 1460 }, 1461 1462 /** 1463 * @param {*} defaultValue 1464 * @return {!Element|string} 1465 */ 1466 _linkifyScriptLocation: function(defaultValue) 1467 { 1468 return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : "" + defaultValue; 1469 }, 1470 1471 calculateAggregatedStats: function() 1472 { 1473 this._aggregatedStats = {}; 1474 this._cpuTime = this._selfTime; 1475 1476 for (var index = this._children.length; index; --index) { 1477 var child = this._children[index - 1]; 1478 for (var category in child._aggregatedStats) 1479 this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category]; 1480 } 1481 for (var category in this._aggregatedStats) 1482 this._cpuTime += this._aggregatedStats[category]; 1483 this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime; 1484 }, 1485 1486 get aggregatedStats() 1487 { 1488 return this._aggregatedStats; 1489 }, 1490 1491 /** 1492 * @param {string} message 1493 */ 1494 addWarning: function(message) 1495 { 1496 if (this._warnings) 1497 this._warnings.push(message); 1498 else 1499 this._warnings = [message]; 1500 for (var parent = this.parent; parent && !parent._childHasWarnings; parent = parent.parent) 1501 parent._childHasWarnings = true; 1502 }, 1503 1504 /** 1505 * @return {boolean} 1506 */ 1507 hasWarnings: function() 1508 { 1509 return !!this._warnings; 1510 }, 1511 1512 /** 1513 * @return {boolean} 1514 */ 1515 childHasWarnings: function() 1516 { 1517 return this._childHasWarnings; 1518 } 1519 } 1520 1521 /** 1522 * @param {!Object} aggregatedStats 1523 */ 1524 WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats) 1525 { 1526 var cell = document.createElement("span"); 1527 cell.className = "timeline-aggregated-info"; 1528 for (var index in aggregatedStats) { 1529 var label = document.createElement("div"); 1530 label.className = "timeline-aggregated-category timeline-" + index; 1531 cell.appendChild(label); 1532 var text = document.createElement("span"); 1533 text.textContent = Number.secondsToString(aggregatedStats[index], true); 1534 cell.appendChild(text); 1535 } 1536 return cell; 1537 } 1538 1539 /** 1540 * @param {!Object} aggregatedStats 1541 * @param {string=} firstCategoryName 1542 * @return {{pieChart: !WebInspector.PieChart, element: !Element, footerElement: !Element}} 1543 */ 1544 WebInspector.TimelinePresentationModel.generatePieChart = function(aggregatedStats, firstCategoryName) 1545 { 1546 var element = document.createElement("div"); 1547 element.className = "timeline-aggregated-info"; 1548 1549 var total = 0; 1550 var categoryNames = []; 1551 if (firstCategoryName) 1552 categoryNames.push(firstCategoryName); 1553 for (var categoryName in WebInspector.TimelinePresentationModel.categories()) { 1554 if (aggregatedStats[categoryName]) { 1555 total += aggregatedStats[categoryName]; 1556 if (firstCategoryName !== categoryName) 1557 categoryNames.push(categoryName); 1558 } 1559 } 1560 1561 var pieChart = new WebInspector.PieChart(total); 1562 element.appendChild(pieChart.element); 1563 var footerElement = element.createChild("div", "timeline-aggregated-info-legend"); 1564 1565 for (var i = 0; i < categoryNames.length; ++i) { 1566 var category = WebInspector.TimelinePresentationModel.categories()[categoryNames[i]]; 1567 pieChart.addSlice(aggregatedStats[category.name], category.fillColorStop0); 1568 var rowElement = footerElement.createChild("div"); 1569 rowElement.createChild("div", "timeline-aggregated-category timeline-" + category.name); 1570 rowElement.createTextChild(WebInspector.UIString("%s %s", Number.secondsToString(aggregatedStats[category.name], true), category.title)); 1571 } 1572 return { pieChart: pieChart, element: element, footerElement: footerElement }; 1573 } 1574 1575 WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame) 1576 { 1577 var contentHelper = new WebInspector.TimelinePopupContentHelper(WebInspector.UIString("Frame")); 1578 var durationInSeconds = frame.endTime - frame.startTime; 1579 var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true), 1580 Number.secondsToString(frame.startTimeOffset, true)); 1581 contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); 1582 contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds)); 1583 contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true)); 1584 contentHelper.appendTextRow(WebInspector.UIString("Thread"), frame.isBackground ? WebInspector.UIString("background") : WebInspector.UIString("main")); 1585 contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), 1586 WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory)); 1587 return contentHelper.contentTable(); 1588 } 1589 1590 /** 1591 * @param {!WebInspector.FrameStatistics} statistics 1592 */ 1593 WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics) 1594 { 1595 /** 1596 * @param {number} time 1597 */ 1598 function formatTimeAndFPS(time) 1599 { 1600 return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time); 1601 } 1602 1603 var contentHelper = new WebInspector.TimelineDetailsContentHelper(false); 1604 contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration)); 1605 contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average)); 1606 contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration)); 1607 contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true)); 1608 1609 return contentHelper.element; 1610 } 1611 1612 /** 1613 * @param {!CanvasRenderingContext2D} context 1614 * @param {number} width 1615 * @param {number} height 1616 * @param {string} color0 1617 * @param {string} color1 1618 * @param {string} color2 1619 */ 1620 WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2) 1621 { 1622 var gradient = context.createLinearGradient(0, 0, width, height); 1623 gradient.addColorStop(0, color0); 1624 gradient.addColorStop(0.25, color1); 1625 gradient.addColorStop(0.75, color1); 1626 gradient.addColorStop(1, color2); 1627 return gradient; 1628 } 1629 1630 /** 1631 * @param {!CanvasRenderingContext2D} context 1632 * @param {number} width 1633 * @param {number} height 1634 * @param {!WebInspector.TimelineCategory} category 1635 */ 1636 WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category) 1637 { 1638 return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor); 1639 } 1640 1641 /** 1642 * @param {!WebInspector.TimelineCategory} category 1643 */ 1644 WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category) 1645 { 1646 var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " + 1647 ".panel.timeline .timeline-filters-header .filter-checkbox-filter.filter-checkbox-filter-" + category.name + " .checkbox-filter-checkbox, " + 1648 ".popover .timeline-" + category.name + ", " + 1649 ".timeline-details-view .timeline-" + category.name + ", " + 1650 ".timeline-category-" + category.name + " .timeline-tree-icon" 1651 1652 return selector + " { background-image: -webkit-linear-gradient(" + 1653 category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" + 1654 " border-color: " + category.borderColor + 1655 "}"; 1656 } 1657 1658 1659 /** 1660 * @param {!Object} rawRecord 1661 * @return {?string} 1662 */ 1663 WebInspector.TimelinePresentationModel.coalescingKeyForRecord = function(rawRecord) 1664 { 1665 var recordTypes = WebInspector.TimelineModel.RecordType; 1666 switch (rawRecord.type) 1667 { 1668 case recordTypes.EventDispatch: return rawRecord.data["type"]; 1669 case recordTypes.Time: return rawRecord.data["message"]; 1670 case recordTypes.TimeStamp: return rawRecord.data["message"]; 1671 default: return null; 1672 } 1673 } 1674 1675 /** 1676 * @param {!Array.<number>} quad 1677 * @return {number} 1678 */ 1679 WebInspector.TimelinePresentationModel.quadWidth = function(quad) 1680 { 1681 return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2))); 1682 } 1683 1684 /** 1685 * @param {!Array.<number>} quad 1686 * @return {number} 1687 */ 1688 WebInspector.TimelinePresentationModel.quadHeight = function(quad) 1689 { 1690 return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2))); 1691 } 1692 1693 /** 1694 * @param {!Object} data 1695 * @return {?Array.<number>} 1696 */ 1697 WebInspector.TimelinePresentationModel.quadFromRectData = function(data) 1698 { 1699 if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined") 1700 return null; 1701 var x0 = data["x"]; 1702 var x1 = data["x"] + data["width"]; 1703 var y0 = data["y"]; 1704 var y1 = data["y"] + data["height"]; 1705 return [x0, y0, x1, y0, x1, y1, x0, y1]; 1706 } 1707 1708 /** 1709 * @interface 1710 */ 1711 WebInspector.TimelinePresentationModel.Filter = function() 1712 { 1713 } 1714 1715 WebInspector.TimelinePresentationModel.Filter.prototype = { 1716 /** 1717 * @param {!WebInspector.TimelinePresentationModel.Record} record 1718 * @return {boolean} 1719 */ 1720 accept: function(record) { return false; } 1721 } 1722 1723 /** 1724 * @constructor 1725 * @extends {WebInspector.Object} 1726 * @param {string} name 1727 * @param {string} title 1728 * @param {number} overviewStripGroupIndex 1729 * @param {string} borderColor 1730 * @param {string} fillColorStop0 1731 * @param {string} fillColorStop1 1732 */ 1733 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1) 1734 { 1735 this.name = name; 1736 this.title = title; 1737 this.overviewStripGroupIndex = overviewStripGroupIndex; 1738 this.borderColor = borderColor; 1739 this.fillColorStop0 = fillColorStop0; 1740 this.fillColorStop1 = fillColorStop1; 1741 this.hidden = false; 1742 } 1743 1744 WebInspector.TimelineCategory.Events = { 1745 VisibilityChanged: "VisibilityChanged" 1746 }; 1747 1748 WebInspector.TimelineCategory.prototype = { 1749 /** 1750 * @return {boolean} 1751 */ 1752 get hidden() 1753 { 1754 return this._hidden; 1755 }, 1756 1757 set hidden(hidden) 1758 { 1759 this._hidden = hidden; 1760 this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this); 1761 }, 1762 1763 __proto__: WebInspector.Object.prototype 1764 } 1765 1766 /** 1767 * @constructor 1768 * @param {string} title 1769 */ 1770 WebInspector.TimelinePopupContentHelper = function(title) 1771 { 1772 this._contentTable = document.createElement("table"); 1773 var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title"); 1774 titleCell.colSpan = 2; 1775 var titleRow = document.createElement("tr"); 1776 titleRow.appendChild(titleCell); 1777 this._contentTable.appendChild(titleRow); 1778 } 1779 1780 WebInspector.TimelinePopupContentHelper.prototype = { 1781 contentTable: function() 1782 { 1783 return this._contentTable; 1784 }, 1785 1786 /** 1787 * @param {string=} styleName 1788 */ 1789 _createCell: function(content, styleName) 1790 { 1791 var text = document.createElement("label"); 1792 text.appendChild(document.createTextNode(content)); 1793 var cell = document.createElement("td"); 1794 cell.className = "timeline-details"; 1795 if (styleName) 1796 cell.className += " " + styleName; 1797 cell.textContent = content; 1798 return cell; 1799 }, 1800 1801 /** 1802 * @param {string} title 1803 * @param {string|number|boolean} content 1804 */ 1805 appendTextRow: function(title, content) 1806 { 1807 var row = document.createElement("tr"); 1808 row.appendChild(this._createCell(title, "timeline-details-row-title")); 1809 row.appendChild(this._createCell(content, "timeline-details-row-data")); 1810 this._contentTable.appendChild(row); 1811 }, 1812 1813 /** 1814 * @param {string} title 1815 * @param {!Element|string} content 1816 */ 1817 appendElementRow: function(title, content) 1818 { 1819 var row = document.createElement("tr"); 1820 var titleCell = this._createCell(title, "timeline-details-row-title"); 1821 row.appendChild(titleCell); 1822 var cell = document.createElement("td"); 1823 cell.className = "details"; 1824 if (content instanceof Node) 1825 cell.appendChild(content); 1826 else 1827 cell.createTextChild(content || ""); 1828 row.appendChild(cell); 1829 this._contentTable.appendChild(row); 1830 } 1831 } 1832 1833 /** 1834 * @constructor 1835 * @param {boolean} monospaceValues 1836 */ 1837 WebInspector.TimelineDetailsContentHelper = function(monospaceValues) 1838 { 1839 this.element = document.createElement("div"); 1840 this.element.className = "timeline-details-view-block"; 1841 this._monospaceValues = monospaceValues; 1842 } 1843 1844 WebInspector.TimelineDetailsContentHelper.prototype = { 1845 /** 1846 * @param {string} title 1847 * @param {string|number|boolean} value 1848 */ 1849 appendTextRow: function(title, value) 1850 { 1851 var rowElement = this.element.createChild("div", "timeline-details-view-row"); 1852 rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title); 1853 rowElement.createChild("span", "timeline-details-view-row-value" + (this._monospaceValues ? " monospace" : "")).textContent = value; 1854 }, 1855 1856 /** 1857 * @param {string} title 1858 * @param {!Element|string} content 1859 */ 1860 appendElementRow: function(title, content) 1861 { 1862 var rowElement = this.element.createChild("div", "timeline-details-view-row"); 1863 rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title); 1864 var valueElement = rowElement.createChild("span", "timeline-details-view-row-details" + (this._monospaceValues ? " monospace" : "")); 1865 if (content instanceof Node) 1866 valueElement.appendChild(content); 1867 else 1868 valueElement.createTextChild(content || ""); 1869 }, 1870 1871 /** 1872 * @param {string} title 1873 * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace 1874 * @param {function(!ConsoleAgent.CallFrame)} callFrameLinkifier 1875 */ 1876 appendStackTrace: function(title, stackTrace, callFrameLinkifier) 1877 { 1878 var rowElement = this.element.createChild("div", "timeline-details-view-row"); 1879 rowElement.createChild("span", "timeline-details-view-row-title").textContent = WebInspector.UIString("%s: ", title); 1880 var stackTraceElement = rowElement.createChild("div", "timeline-details-view-row-stack-trace monospace"); 1881 1882 for (var i = 0; i < stackTrace.length; ++i) { 1883 var stackFrame = stackTrace[i]; 1884 var row = stackTraceElement.createChild("div"); 1885 row.createTextChild(stackFrame.functionName || WebInspector.UIString("(anonymous function)")); 1886 row.createTextChild(" @ "); 1887 var urlElement = callFrameLinkifier(stackFrame); 1888 row.appendChild(urlElement); 1889 } 1890 } 1891 } 1892