1 /* 2 * Copyright (C) 2012 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 }; 55 return WebInspector.TimelinePresentationModel._categories; 56 }; 57 58 /** 59 * @return {!Object.<string, {title: string, category}>} 60 */ 61 WebInspector.TimelinePresentationModel._initRecordStyles = function() 62 { 63 if (WebInspector.TimelinePresentationModel._recordStylesMap) 64 return WebInspector.TimelinePresentationModel._recordStylesMap; 65 66 var recordTypes = WebInspector.TimelineModel.RecordType; 67 var categories = WebInspector.TimelinePresentationModel.categories(); 68 69 var recordStyles = {}; 70 recordStyles[recordTypes.Root] = { title: "#root", category: categories["loading"] }; 71 recordStyles[recordTypes.Program] = { title: WebInspector.UIString("Other"), category: categories["other"] }; 72 recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: categories["scripting"] }; 73 recordStyles[recordTypes.BeginFrame] = { title: WebInspector.UIString("Frame Start"), category: categories["rendering"] }; 74 recordStyles[recordTypes.ScheduleStyleRecalculation] = { title: WebInspector.UIString("Schedule Style Recalculation"), category: categories["rendering"] }; 75 recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: categories["rendering"] }; 76 recordStyles[recordTypes.InvalidateLayout] = { title: WebInspector.UIString("Invalidate Layout"), category: categories["rendering"] }; 77 recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: categories["rendering"] }; 78 recordStyles[recordTypes.PaintSetup] = { title: WebInspector.UIString("Paint Setup"), category: categories["painting"] }; 79 recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: categories["painting"] }; 80 recordStyles[recordTypes.Rasterize] = { title: WebInspector.UIString("Rasterize"), category: categories["painting"] }; 81 recordStyles[recordTypes.ScrollLayer] = { title: WebInspector.UIString("Scroll"), category: categories["rendering"] }; 82 recordStyles[recordTypes.DecodeImage] = { title: WebInspector.UIString("Image Decode"), category: categories["painting"] }; 83 recordStyles[recordTypes.ResizeImage] = { title: WebInspector.UIString("Image Resize"), category: categories["painting"] }; 84 recordStyles[recordTypes.CompositeLayers] = { title: WebInspector.UIString("Composite Layers"), category: categories["painting"] }; 85 recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse HTML"), category: categories["loading"] }; 86 recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: categories["scripting"] }; 87 recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: categories["scripting"] }; 88 recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: categories["scripting"] }; 89 recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: categories["scripting"] }; 90 recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: categories["scripting"] }; 91 recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: categories["scripting"] }; 92 recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: categories["loading"] }; 93 recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: categories["loading"] }; 94 recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: categories["loading"] }; 95 recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: categories["scripting"] }; 96 recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: categories["loading"] }; 97 recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: categories["scripting"] }; 98 recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContentLoaded event"), category: categories["scripting"] }; 99 recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: categories["scripting"] }; 100 recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: categories["scripting"] }; 101 recordStyles[recordTypes.Time] = { title: WebInspector.UIString("Time"), category: categories["scripting"] }; 102 recordStyles[recordTypes.TimeEnd] = { title: WebInspector.UIString("Time End"), category: categories["scripting"] }; 103 recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: categories["loading"] }; 104 recordStyles[recordTypes.RequestAnimationFrame] = { title: WebInspector.UIString("Request Animation Frame"), category: categories["scripting"] }; 105 recordStyles[recordTypes.CancelAnimationFrame] = { title: WebInspector.UIString("Cancel Animation Frame"), category: categories["scripting"] }; 106 recordStyles[recordTypes.FireAnimationFrame] = { title: WebInspector.UIString("Animation Frame Fired"), category: categories["scripting"] }; 107 recordStyles[recordTypes.WebSocketCreate] = { title: WebInspector.UIString("Create WebSocket"), category: categories["scripting"] }; 108 recordStyles[recordTypes.WebSocketSendHandshakeRequest] = { title: WebInspector.UIString("Send WebSocket Handshake"), category: categories["scripting"] }; 109 recordStyles[recordTypes.WebSocketReceiveHandshakeResponse] = { title: WebInspector.UIString("Receive WebSocket Handshake"), category: categories["scripting"] }; 110 recordStyles[recordTypes.WebSocketDestroy] = { title: WebInspector.UIString("Destroy WebSocket"), category: categories["scripting"] }; 111 112 WebInspector.TimelinePresentationModel._recordStylesMap = recordStyles; 113 return recordStyles; 114 } 115 116 /** 117 * @param {Object} record 118 */ 119 WebInspector.TimelinePresentationModel.recordStyle = function(record) 120 { 121 var recordStyles = WebInspector.TimelinePresentationModel._initRecordStyles(); 122 var result = recordStyles[record.type]; 123 if (!result) { 124 result = { 125 title: WebInspector.UIString("Unknown: %s", record.type), 126 category: WebInspector.TimelinePresentationModel.categories()["other"] 127 }; 128 recordStyles[record.type] = result; 129 } 130 return result; 131 } 132 133 WebInspector.TimelinePresentationModel.categoryForRecord = function(record) 134 { 135 return WebInspector.TimelinePresentationModel.recordStyle(record).category; 136 } 137 138 WebInspector.TimelinePresentationModel.isEventDivider = function(record) 139 { 140 var recordTypes = WebInspector.TimelineModel.RecordType; 141 if (record.type === recordTypes.TimeStamp) 142 return true; 143 if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) { 144 if (record.data && ((typeof record.data.isMainFrame) === "boolean")) 145 return record.data.isMainFrame; 146 } 147 return false; 148 } 149 150 /** 151 * @param {Array} recordsArray 152 * @param {?function(*)} preOrderCallback 153 * @param {function(*)=} postOrderCallback 154 */ 155 WebInspector.TimelinePresentationModel.forAllRecords = function(recordsArray, preOrderCallback, postOrderCallback) 156 { 157 if (!recordsArray) 158 return; 159 var stack = [{array: recordsArray, index: 0}]; 160 while (stack.length) { 161 var entry = stack[stack.length - 1]; 162 var records = entry.array; 163 if (entry.index < records.length) { 164 var record = records[entry.index]; 165 if (preOrderCallback && preOrderCallback(record)) 166 return; 167 if (record.children) 168 stack.push({array: record.children, index: 0, record: record}); 169 else if (postOrderCallback && postOrderCallback(record)) 170 return; 171 ++entry.index; 172 } else { 173 if (entry.record && postOrderCallback && postOrderCallback(entry.record)) 174 return; 175 stack.pop(); 176 } 177 } 178 } 179 180 /** 181 * @param {string=} recordType 182 * @return {boolean} 183 */ 184 WebInspector.TimelinePresentationModel.needsPreviewElement = function(recordType) 185 { 186 if (!recordType) 187 return false; 188 const recordTypes = WebInspector.TimelineModel.RecordType; 189 switch (recordType) { 190 case recordTypes.ScheduleResourceRequest: 191 case recordTypes.ResourceSendRequest: 192 case recordTypes.ResourceReceiveResponse: 193 case recordTypes.ResourceReceivedData: 194 case recordTypes.ResourceFinish: 195 return true; 196 default: 197 return false; 198 } 199 } 200 201 /** 202 * @param {string} recordType 203 * @param {string=} title 204 */ 205 WebInspector.TimelinePresentationModel.createEventDivider = function(recordType, title) 206 { 207 var eventDivider = document.createElement("div"); 208 eventDivider.className = "resources-event-divider"; 209 var recordTypes = WebInspector.TimelineModel.RecordType; 210 211 if (recordType === recordTypes.MarkDOMContent) 212 eventDivider.className += " resources-blue-divider"; 213 else if (recordType === recordTypes.MarkLoad) 214 eventDivider.className += " resources-red-divider"; 215 else if (recordType === recordTypes.TimeStamp) 216 eventDivider.className += " resources-orange-divider"; 217 else if (recordType === recordTypes.BeginFrame) 218 eventDivider.className += " timeline-frame-divider"; 219 220 if (title) 221 eventDivider.title = title; 222 223 return eventDivider; 224 } 225 226 WebInspector.TimelinePresentationModel._hiddenRecords = { } 227 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkDOMContent] = 1; 228 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.MarkLoad] = 1; 229 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.ScheduleStyleRecalculation] = 1; 230 WebInspector.TimelinePresentationModel._hiddenRecords[WebInspector.TimelineModel.RecordType.InvalidateLayout] = 1; 231 232 WebInspector.TimelinePresentationModel.prototype = { 233 /** 234 * @param {!WebInspector.TimelinePresentationModel.Filter} filter 235 */ 236 addFilter: function(filter) 237 { 238 this._filters.push(filter); 239 }, 240 241 /** 242 * @param {!WebInspector.TimelinePresentationModel.Filter} filter 243 */ 244 removeFilter: function(filter) 245 { 246 var index = this._filters.indexOf(filter); 247 if (index !== -1) 248 this._filters.splice(index, 1); 249 }, 250 251 rootRecord: function() 252 { 253 return this._rootRecord; 254 }, 255 256 frames: function() 257 { 258 return this._frames; 259 }, 260 261 reset: function() 262 { 263 this._linkifier.reset(); 264 this._rootRecord = new WebInspector.TimelinePresentationModel.Record(this, { type: WebInspector.TimelineModel.RecordType.Root }, null, null, null, false); 265 this._sendRequestRecords = {}; 266 this._scheduledResourceRequests = {}; 267 this._timerRecords = {}; 268 this._requestAnimationFrameRecords = {}; 269 this._eventDividerRecords = []; 270 this._timeRecords = {}; 271 this._timeRecordStack = []; 272 this._frames = []; 273 this._minimumRecordTime = -1; 274 this._layoutInvalidateStack = {}; 275 this._lastScheduleStyleRecalculation = {}; 276 this._webSocketCreateRecords = {}; 277 this._coalescingBuckets = {}; 278 }, 279 280 addFrame: function(frame) 281 { 282 this._frames.push(frame); 283 }, 284 285 addRecord: function(record) 286 { 287 if (this._minimumRecordTime === -1 || record.startTime < this._minimumRecordTime) 288 this._minimumRecordTime = WebInspector.TimelineModel.startTimeInSeconds(record); 289 290 var records; 291 if (record.type === WebInspector.TimelineModel.RecordType.Program) 292 records = record.children; 293 else 294 records = [record]; 295 296 var formattedRecords = []; 297 var recordsCount = records.length; 298 for (var i = 0; i < recordsCount; ++i) 299 formattedRecords.push(this._innerAddRecord(records[i], this._rootRecord)); 300 return formattedRecords; 301 }, 302 303 _innerAddRecord: function(record, parentRecord) 304 { 305 const recordTypes = WebInspector.TimelineModel.RecordType; 306 var isHiddenRecord = record.type in WebInspector.TimelinePresentationModel._hiddenRecords; 307 var origin; 308 var coalescingBucket; 309 310 if (!isHiddenRecord) { 311 var newParentRecord = this._findParentRecord(record); 312 if (newParentRecord) { 313 origin = parentRecord; 314 parentRecord = newParentRecord; 315 } 316 // On main thread, only coalesce if the last event is of same type. 317 if (parentRecord === this._rootRecord) 318 coalescingBucket = record.thread ? record.type : "mainThread"; 319 var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket); 320 if (coalescedRecord) { 321 if (!origin) 322 origin = parentRecord; 323 parentRecord = coalescedRecord; 324 } 325 } 326 327 var children = record.children; 328 var scriptDetails; 329 if (record.data && record.data["scriptName"]) { 330 scriptDetails = { 331 scriptName: record.data["scriptName"], 332 scriptLine: record.data["scriptLine"] 333 } 334 }; 335 336 if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrame) && children && children.length) { 337 var childRecord = children[0]; 338 if (childRecord.type === recordTypes.FunctionCall) { 339 scriptDetails = { 340 scriptName: childRecord.data["scriptName"], 341 scriptLine: childRecord.data["scriptLine"] 342 }; 343 children = childRecord.children.concat(children.slice(1)); 344 } 345 } 346 347 var formattedRecord = new WebInspector.TimelinePresentationModel.Record(this, record, parentRecord, origin, scriptDetails, isHiddenRecord); 348 349 if (WebInspector.TimelinePresentationModel.isEventDivider(formattedRecord)) 350 this._eventDividerRecords.push(formattedRecord); 351 352 if (isHiddenRecord) 353 return formattedRecord; 354 355 formattedRecord.collapsed = parentRecord === this._rootRecord; 356 if (coalescingBucket) 357 this._coalescingBuckets[coalescingBucket] = formattedRecord; 358 359 var childrenCount = children ? children.length : 0; 360 for (var i = 0; i < childrenCount; ++i) 361 this._innerAddRecord(children[i], formattedRecord); 362 363 formattedRecord.calculateAggregatedStats(); 364 365 if (origin) 366 this._updateAncestorStats(formattedRecord); 367 368 if (parentRecord.coalesced && parentRecord.startTime > formattedRecord.startTime) 369 parentRecord._record.startTime = record.startTime; 370 371 origin = formattedRecord.origin(); 372 if (!origin.isRoot() && !origin.coalesced) 373 origin.selfTime -= formattedRecord.endTime - formattedRecord.startTime; 374 return formattedRecord; 375 }, 376 377 /** 378 * @param {WebInspector.TimelinePresentationModel.Record} record 379 */ 380 _updateAncestorStats: function(record) 381 { 382 var lastChildEndTime = record.lastChildEndTime; 383 var aggregatedStats = record.aggregatedStats; 384 for (var currentRecord = record.parent; currentRecord && !currentRecord.isRoot(); currentRecord = currentRecord.parent) { 385 currentRecord._cpuTime += record._cpuTime; 386 if (currentRecord.lastChildEndTime < lastChildEndTime) 387 currentRecord.lastChildEndTime = lastChildEndTime; 388 for (var category in aggregatedStats) 389 currentRecord.aggregatedStats[category] += aggregatedStats[category]; 390 } 391 }, 392 393 /** 394 * @param {Object} record 395 * @param {Object} newParent 396 * @param {String} bucket 397 * @return {WebInspector.TimelinePresentationModel.Record?} 398 */ 399 _findCoalescedParent: function(record, newParent, bucket) 400 { 401 const coalescingThresholdSeconds = 0.005; 402 403 var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent.children.peekLast(); 404 if (lastRecord && lastRecord.coalesced) 405 lastRecord = lastRecord.children.peekLast(); 406 var startTime = WebInspector.TimelineModel.startTimeInSeconds(record); 407 var endTime = WebInspector.TimelineModel.endTimeInSeconds(record); 408 if (!lastRecord) 409 return null; 410 if (lastRecord.type !== record.type) 411 return null; 412 if (lastRecord.endTime + coalescingThresholdSeconds < startTime) 413 return null; 414 if (endTime + coalescingThresholdSeconds < lastRecord.startTime) 415 return null; 416 if (WebInspector.TimelinePresentationModel.coalescingKeyForRecord(record) !== WebInspector.TimelinePresentationModel.coalescingKeyForRecord(lastRecord._record)) 417 return null; 418 if (lastRecord.parent.coalesced) 419 return lastRecord.parent; 420 return this._replaceWithCoalescedRecord(lastRecord); 421 }, 422 423 /** 424 * @param {WebInspector.TimelinePresentationModel.Record} record 425 * @return {WebInspector.TimelinePresentationModel.Record} 426 */ 427 _replaceWithCoalescedRecord: function(record) 428 { 429 var rawRecord = { 430 type: record._record.type, 431 startTime: record._record.startTime, 432 endTime: record._record.endTime, 433 data: { } 434 }; 435 if (record._record.thread) 436 rawRecord.thread = "aggregated"; 437 if (record.type === WebInspector.TimelineModel.RecordType.TimeStamp) 438 rawRecord.data.message = record.data.message; 439 440 var coalescedRecord = new WebInspector.TimelinePresentationModel.Record(this, rawRecord, null, null, null, false); 441 var parent = record.parent; 442 443 coalescedRecord.coalesced = true; 444 coalescedRecord.collapsed = true; 445 coalescedRecord._children.push(record); 446 record.parent = coalescedRecord; 447 coalescedRecord.calculateAggregatedStats(); 448 if (record.hasWarning || record.childHasWarning) 449 coalescedRecord.childHasWarning = true; 450 451 coalescedRecord.parent = parent; 452 parent._children[parent._children.indexOf(record)] = coalescedRecord; 453 return coalescedRecord; 454 }, 455 456 _findParentRecord: function(record) 457 { 458 if (!this._glueRecords) 459 return null; 460 var recordTypes = WebInspector.TimelineModel.RecordType; 461 462 switch (record.type) { 463 case recordTypes.ResourceReceiveResponse: 464 case recordTypes.ResourceFinish: 465 case recordTypes.ResourceReceivedData: 466 return this._sendRequestRecords[record.data["requestId"]]; 467 468 case recordTypes.ResourceSendRequest: 469 return this._rootRecord; 470 471 case recordTypes.TimerFire: 472 return this._timerRecords[record.data["timerId"]]; 473 474 case recordTypes.ResourceSendRequest: 475 return this._scheduledResourceRequests[record.data["url"]]; 476 477 case recordTypes.FireAnimationFrame: 478 return this._requestAnimationFrameRecords[record.data["id"]]; 479 480 case recordTypes.Time: 481 return this._rootRecord; 482 483 case recordTypes.TimeEnd: 484 return this._timeRecords[record.data["message"]]; 485 } 486 }, 487 488 setGlueRecords: function(glue) 489 { 490 this._glueRecords = glue; 491 }, 492 493 invalidateFilteredRecords: function() 494 { 495 delete this._filteredRecords; 496 }, 497 498 filteredRecords: function() 499 { 500 if (this._filteredRecords) 501 return this._filteredRecords; 502 503 var recordsInWindow = []; 504 505 var stack = [{children: this._rootRecord.children, index: 0, parentIsCollapsed: false}]; 506 while (stack.length) { 507 var entry = stack[stack.length - 1]; 508 var records = entry.children; 509 if (records && entry.index < records.length) { 510 var record = records[entry.index]; 511 ++entry.index; 512 513 if (this.isVisible(record)) { 514 ++record.parent._invisibleChildrenCount; 515 if (!entry.parentIsCollapsed) 516 recordsInWindow.push(record); 517 } 518 519 record._invisibleChildrenCount = 0; 520 521 stack.push({children: record.children, 522 index: 0, 523 parentIsCollapsed: (entry.parentIsCollapsed || record.collapsed), 524 parentRecord: record, 525 windowLengthBeforeChildrenTraversal: recordsInWindow.length}); 526 } else { 527 stack.pop(); 528 if (entry.parentRecord) 529 entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal; 530 } 531 } 532 533 this._filteredRecords = recordsInWindow; 534 return recordsInWindow; 535 }, 536 537 filteredFrames: function(startTime, endTime) 538 { 539 function compareStartTime(value, object) 540 { 541 return value - object.startTime; 542 } 543 function compareEndTime(value, object) 544 { 545 return value - object.endTime; 546 } 547 var firstFrame = insertionIndexForObjectInListSortedByFunction(startTime, this._frames, compareStartTime); 548 var lastFrame = insertionIndexForObjectInListSortedByFunction(endTime, this._frames, compareEndTime); 549 while (lastFrame < this._frames.length && this._frames[lastFrame].endTime <= endTime) 550 ++lastFrame; 551 return this._frames.slice(firstFrame, lastFrame); 552 }, 553 554 eventDividerRecords: function() 555 { 556 return this._eventDividerRecords; 557 }, 558 559 isVisible: function(record) 560 { 561 for (var i = 0; i < this._filters.length; ++i) { 562 if (!this._filters[i].accept(record)) 563 return false; 564 } 565 return true; 566 }, 567 568 /** 569 * @param {{tasks: !Array.<{startTime: number, endTime: number}>, firstTaskIndex: number, lastTaskIndex: number}} info 570 * @return {!Element} 571 */ 572 generateMainThreadBarPopupContent: function(info) 573 { 574 var firstTaskIndex = info.firstTaskIndex; 575 var lastTaskIndex = info.lastTaskIndex; 576 var tasks = info.tasks; 577 var messageCount = lastTaskIndex - firstTaskIndex + 1; 578 var cpuTime = 0; 579 580 for (var i = firstTaskIndex; i <= lastTaskIndex; ++i) { 581 var task = tasks[i]; 582 cpuTime += task.endTime - task.startTime; 583 } 584 var startTime = tasks[firstTaskIndex].startTime; 585 var endTime = tasks[lastTaskIndex].endTime; 586 var duration = endTime - startTime; 587 var offset = this._minimumRecordTime; 588 589 var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("CPU")); 590 var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(duration, true), 591 Number.secondsToString(startTime - offset, true)); 592 contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); 593 contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(cpuTime, true)); 594 contentHelper.appendTextRow(WebInspector.UIString("Message Count"), messageCount); 595 return contentHelper.contentTable(); 596 }, 597 598 __proto__: WebInspector.Object.prototype 599 } 600 601 /** 602 * @constructor 603 * @param {WebInspector.TimelinePresentationModel} presentationModel 604 * @param {Object} record 605 * @param {WebInspector.TimelinePresentationModel.Record} parentRecord 606 * @param {WebInspector.TimelinePresentationModel.Record} origin 607 * @param {Object|undefined} scriptDetails 608 * @param {boolean} hidden 609 */ 610 WebInspector.TimelinePresentationModel.Record = function(presentationModel, record, parentRecord, origin, scriptDetails, hidden) 611 { 612 this._linkifier = presentationModel._linkifier; 613 this._aggregatedStats = {}; 614 this._record = record; 615 this._children = []; 616 if (!hidden && parentRecord) { 617 this.parent = parentRecord; 618 if (this.isBackground) 619 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(parentRecord, this); 620 else 621 parentRecord.children.push(this); 622 } 623 if (origin) 624 this._origin = origin; 625 626 this._selfTime = this.endTime - this.startTime; 627 this._lastChildEndTime = this.endTime; 628 this._startTimeOffset = this.startTime - presentationModel._minimumRecordTime; 629 630 if (record.data) { 631 if (record.data["url"]) 632 this.url = record.data["url"]; 633 if (record.data["layerRootNode"]) 634 this._relatedBackendNodeId = record.data["layerRootNode"]; 635 } 636 if (scriptDetails) { 637 this.scriptName = scriptDetails.scriptName; 638 this.scriptLine = scriptDetails.scriptLine; 639 } 640 if (parentRecord && parentRecord.callSiteStackTrace) 641 this.callSiteStackTrace = parentRecord.callSiteStackTrace; 642 643 var recordTypes = WebInspector.TimelineModel.RecordType; 644 switch (record.type) { 645 case recordTypes.ResourceSendRequest: 646 // Make resource receive record last since request was sent; make finish record last since response received. 647 presentationModel._sendRequestRecords[record.data["requestId"]] = this; 648 break; 649 650 case recordTypes.ScheduleResourceRequest: 651 presentationModel._scheduledResourceRequests[record.data["url"]] = this; 652 break; 653 654 case recordTypes.ResourceReceiveResponse: 655 var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]]; 656 if (sendRequestRecord) { // False if we started instrumentation in the middle of request. 657 this.url = sendRequestRecord.url; 658 // Now that we have resource in the collection, recalculate details in order to display short url. 659 sendRequestRecord._refreshDetails(); 660 if (sendRequestRecord.parent !== presentationModel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest) 661 sendRequestRecord.parent._refreshDetails(); 662 } 663 break; 664 665 case recordTypes.ResourceReceivedData: 666 case recordTypes.ResourceFinish: 667 var sendRequestRecord = presentationModel._sendRequestRecords[record.data["requestId"]]; 668 if (sendRequestRecord) // False for main resource. 669 this.url = sendRequestRecord.url; 670 break; 671 672 case recordTypes.TimerInstall: 673 this.timeout = record.data["timeout"]; 674 this.singleShot = record.data["singleShot"]; 675 presentationModel._timerRecords[record.data["timerId"]] = this; 676 break; 677 678 case recordTypes.TimerFire: 679 var timerInstalledRecord = presentationModel._timerRecords[record.data["timerId"]]; 680 if (timerInstalledRecord) { 681 this.callSiteStackTrace = timerInstalledRecord.stackTrace; 682 this.timeout = timerInstalledRecord.timeout; 683 this.singleShot = timerInstalledRecord.singleShot; 684 } 685 break; 686 687 case recordTypes.RequestAnimationFrame: 688 presentationModel._requestAnimationFrameRecords[record.data["id"]] = this; 689 break; 690 691 case recordTypes.FireAnimationFrame: 692 var requestAnimationRecord = presentationModel._requestAnimationFrameRecords[record.data["id"]]; 693 if (requestAnimationRecord) 694 this.callSiteStackTrace = requestAnimationRecord.stackTrace; 695 break; 696 697 case recordTypes.Time: 698 var message = record.data["message"]; 699 var oldReference = presentationModel._timeRecords[message]; 700 if (oldReference) 701 break; 702 presentationModel._timeRecords[message] = this; 703 if (origin) 704 presentationModel._timeRecordStack.push(this); 705 break; 706 707 case recordTypes.TimeEnd: 708 var message = record.data["message"]; 709 var timeRecord = presentationModel._timeRecords[message]; 710 delete presentationModel._timeRecords[message]; 711 if (timeRecord) { 712 this.timeRecord = timeRecord; 713 timeRecord.timeEndRecord = this; 714 var intervalDuration = this.startTime - timeRecord.startTime; 715 this.intervalDuration = intervalDuration; 716 timeRecord.intervalDuration = intervalDuration; 717 if (!origin) 718 break; 719 var recordStack = presentationModel._timeRecordStack; 720 recordStack.splice(recordStack.indexOf(timeRecord), 1); 721 for (var index = recordStack.length; index; --index) { 722 var openRecord = recordStack[index - 1]; 723 if (openRecord.startTime > timeRecord.startTime) 724 continue; 725 WebInspector.TimelinePresentationModel.adoptRecord(openRecord, timeRecord); 726 break; 727 } 728 } 729 break; 730 731 case recordTypes.ScheduleStyleRecalculation: 732 presentationModel._lastScheduleStyleRecalculation[this.frameId] = this; 733 break; 734 735 case recordTypes.RecalculateStyles: 736 var scheduleStyleRecalculationRecord = presentationModel._lastScheduleStyleRecalculation[this.frameId]; 737 if (!scheduleStyleRecalculationRecord) 738 break; 739 this.callSiteStackTrace = scheduleStyleRecalculationRecord.stackTrace; 740 break; 741 742 case recordTypes.InvalidateLayout: 743 // Consider style recalculation as a reason for layout invalidation, 744 // but only if we had no earlier layout invalidation records. 745 var styleRecalcStack; 746 if (!presentationModel._layoutInvalidateStack[this.frameId]) { 747 for (var outerRecord = parentRecord; outerRecord; outerRecord = record.parent) { 748 if (outerRecord.type === recordTypes.RecalculateStyles) { 749 styleRecalcStack = outerRecord.callSiteStackTrace; 750 break; 751 } 752 } 753 } 754 presentationModel._layoutInvalidateStack[this.frameId] = styleRecalcStack || this.stackTrace; 755 break; 756 757 case recordTypes.Layout: 758 var layoutInvalidateStack = presentationModel._layoutInvalidateStack[this.frameId]; 759 if (layoutInvalidateStack) 760 this.callSiteStackTrace = layoutInvalidateStack; 761 if (this.stackTrace) 762 this.setHasWarning(); 763 presentationModel._layoutInvalidateStack[this.frameId] = null; 764 this.highlightQuad = record.data.root || WebInspector.TimelinePresentationModel.quadFromRectData(record.data); 765 this._relatedBackendNodeId = record.data["rootNode"]; 766 break; 767 768 case recordTypes.Paint: 769 this.highlightQuad = record.data.clip || WebInspector.TimelinePresentationModel.quadFromRectData(record.data); 770 break; 771 772 case recordTypes.WebSocketCreate: 773 this.webSocketURL = record.data["url"]; 774 if (typeof record.data["webSocketProtocol"] !== "undefined") 775 this.webSocketProtocol = record.data["webSocketProtocol"]; 776 presentationModel._webSocketCreateRecords[record.data["identifier"]] = this; 777 break; 778 779 case recordTypes.WebSocketSendHandshakeRequest: 780 case recordTypes.WebSocketReceiveHandshakeResponse: 781 case recordTypes.WebSocketDestroy: 782 var webSocketCreateRecord = presentationModel._webSocketCreateRecords[record.data["identifier"]]; 783 if (webSocketCreateRecord) { // False if we started instrumentation in the middle of request. 784 this.webSocketURL = webSocketCreateRecord.webSocketURL; 785 if (typeof webSocketCreateRecord.webSocketProtocol !== "undefined") 786 this.webSocketProtocol = webSocketCreateRecord.webSocketProtocol; 787 } 788 break; 789 } 790 } 791 792 WebInspector.TimelinePresentationModel.adoptRecord = function(newParent, record) 793 { 794 record.parent.children.splice(record.parent.children.indexOf(record)); 795 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord(newParent, record); 796 record.parent = newParent; 797 } 798 799 WebInspector.TimelinePresentationModel.insertRetrospectiveRecord = function(parent, record) 800 { 801 function compareStartTime(value, record) 802 { 803 return value < record.startTime ? -1 : 1; 804 } 805 806 parent.children.splice(insertionIndexForObjectInListSortedByFunction(record.startTime, parent.children, compareStartTime), 0, record); 807 } 808 809 WebInspector.TimelinePresentationModel.Record.prototype = { 810 get lastChildEndTime() 811 { 812 return this._lastChildEndTime; 813 }, 814 815 set lastChildEndTime(time) 816 { 817 this._lastChildEndTime = time; 818 }, 819 820 get selfTime() 821 { 822 return this.coalesced ? this._lastChildEndTime - this.startTime : this._selfTime; 823 }, 824 825 set selfTime(time) 826 { 827 this._selfTime = time; 828 }, 829 830 get cpuTime() 831 { 832 return this._cpuTime; 833 }, 834 835 /** 836 * @return {boolean} 837 */ 838 isRoot: function() 839 { 840 return this.type === WebInspector.TimelineModel.RecordType.Root; 841 }, 842 843 /** 844 * @return {WebInspector.TimelinePresentationModel.Record} 845 */ 846 origin: function() 847 { 848 return this._origin || this.parent; 849 }, 850 851 /** 852 * @return {Array.<WebInspector.TimelinePresentationModel.Record>} 853 */ 854 get children() 855 { 856 return this._children; 857 }, 858 859 /** 860 * @return {number} 861 */ 862 get visibleChildrenCount() 863 { 864 return this._visibleChildrenCount || 0; 865 }, 866 867 /** 868 * @return {number} 869 */ 870 get invisibleChildrenCount() 871 { 872 return this._invisibleChildrenCount || 0; 873 }, 874 875 /** 876 * @return {WebInspector.TimelineCategory} 877 */ 878 get category() 879 { 880 return WebInspector.TimelinePresentationModel.recordStyle(this._record).category 881 }, 882 883 /** 884 * @return {string} 885 */ 886 get title() 887 { 888 return this.type === WebInspector.TimelineModel.RecordType.TimeStamp ? this._record.data["message"] : 889 WebInspector.TimelinePresentationModel.recordStyle(this._record).title; 890 }, 891 892 /** 893 * @return {number} 894 */ 895 get startTime() 896 { 897 return WebInspector.TimelineModel.startTimeInSeconds(this._record); 898 }, 899 900 /** 901 * @return {number} 902 */ 903 get endTime() 904 { 905 return WebInspector.TimelineModel.endTimeInSeconds(this._record); 906 }, 907 908 /** 909 * @return {boolean} 910 */ 911 get isBackground() 912 { 913 return !!this._record.thread; 914 }, 915 916 /** 917 * @return {Object} 918 */ 919 get data() 920 { 921 return this._record.data; 922 }, 923 924 /** 925 * @return {string} 926 */ 927 get type() 928 { 929 return this._record.type; 930 }, 931 932 /** 933 * @return {string} 934 */ 935 get frameId() 936 { 937 return this._record.frameId; 938 }, 939 940 /** 941 * @return {number} 942 */ 943 get usedHeapSizeDelta() 944 { 945 return this._record.usedHeapSizeDelta || 0; 946 }, 947 948 /** 949 * @return {number} 950 */ 951 get usedHeapSize() 952 { 953 return this._record.usedHeapSize; 954 }, 955 956 /** 957 * @return {Array.<DebuggerAgent.CallFrame>?} 958 */ 959 get stackTrace() 960 { 961 if (this._record.stackTrace && this._record.stackTrace.length) 962 return this._record.stackTrace; 963 return null; 964 }, 965 966 containsTime: function(time) 967 { 968 return this.startTime <= time && time <= this.endTime; 969 }, 970 971 /** 972 * @param {function(Element)} callback 973 */ 974 generatePopupContent: function(callback) 975 { 976 var barrier = new CallbackBarrier(); 977 if (WebInspector.TimelinePresentationModel.needsPreviewElement(this.type) && !this._imagePreviewElement) 978 WebInspector.DOMPresentationUtils.buildImagePreviewContents(this.url, false, barrier.createCallback(this._setImagePreviewElement.bind(this))); 979 if (this._relatedBackendNodeId && !this._relatedNode) 980 WebInspector.domAgent.pushNodeByBackendIdToFrontend(this._relatedBackendNodeId, barrier.createCallback(this._setRelatedNode.bind(this))); 981 982 barrier.callWhenDone(callbackWrapper.bind(this)); 983 function callbackWrapper() 984 { 985 callback(this._generatePopupContentSynchronously()); 986 } 987 }, 988 989 /** 990 * @param {Element} element 991 */ 992 _setImagePreviewElement: function(element) 993 { 994 this._imagePreviewElement = element; 995 }, 996 997 /** 998 * @param {?DOMAgent.NodeId} nodeId 999 */ 1000 _setRelatedNode: function(nodeId) 1001 { 1002 if (typeof nodeId === "number") 1003 this._relatedNode = WebInspector.domAgent.nodeForId(nodeId); 1004 }, 1005 1006 /** 1007 * @return {Element} 1008 */ 1009 _generatePopupContentSynchronously: function() 1010 { 1011 var contentHelper = new WebInspector.PopoverContentHelper(this.title); 1012 var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, true), 1013 Number.secondsToString(this._startTimeOffset)); 1014 contentHelper.appendTextRow(WebInspector.UIString("Duration"), text); 1015 1016 if (this._children.length) { 1017 if (!this.coalesced) 1018 contentHelper.appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime, true)); 1019 contentHelper.appendTextRow(WebInspector.UIString("CPU Time"), Number.secondsToString(this._cpuTime, true)); 1020 contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), 1021 WebInspector.TimelinePresentationModel._generateAggregatedInfo(this._aggregatedStats)); 1022 } 1023 1024 if (this.coalesced) 1025 return contentHelper.contentTable(); 1026 1027 const recordTypes = WebInspector.TimelineModel.RecordType; 1028 1029 // The messages may vary per record type; 1030 var callSiteStackTraceLabel; 1031 var callStackLabel; 1032 1033 switch (this.type) { 1034 case recordTypes.GCEvent: 1035 contentHelper.appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data["usedHeapSizeDelta"])); 1036 break; 1037 case recordTypes.TimerFire: 1038 callSiteStackTraceLabel = WebInspector.UIString("Timer installed"); 1039 // Fall-through intended. 1040 1041 case recordTypes.TimerInstall: 1042 case recordTypes.TimerRemove: 1043 contentHelper.appendTextRow(WebInspector.UIString("Timer ID"), this.data["timerId"]); 1044 if (typeof this.timeout === "number") { 1045 contentHelper.appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000)); 1046 contentHelper.appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot); 1047 } 1048 break; 1049 case recordTypes.FireAnimationFrame: 1050 callSiteStackTraceLabel = WebInspector.UIString("Animation frame requested"); 1051 contentHelper.appendTextRow(WebInspector.UIString("Callback ID"), this.data["id"]); 1052 break; 1053 case recordTypes.FunctionCall: 1054 contentHelper.appendElementRow(WebInspector.UIString("Location"), this._linkifyScriptLocation()); 1055 break; 1056 case recordTypes.ScheduleResourceRequest: 1057 case recordTypes.ResourceSendRequest: 1058 case recordTypes.ResourceReceiveResponse: 1059 case recordTypes.ResourceReceivedData: 1060 case recordTypes.ResourceFinish: 1061 contentHelper.appendElementRow(WebInspector.UIString("Resource"), WebInspector.linkifyResourceAsNode(this.url)); 1062 if (this._imagePreviewElement) 1063 contentHelper.appendElementRow(WebInspector.UIString("Preview"), this._imagePreviewElement); 1064 if (this.data["requestMethod"]) 1065 contentHelper.appendTextRow(WebInspector.UIString("Request Method"), this.data["requestMethod"]); 1066 if (typeof this.data["statusCode"] === "number") 1067 contentHelper.appendTextRow(WebInspector.UIString("Status Code"), this.data["statusCode"]); 1068 if (this.data["mimeType"]) 1069 contentHelper.appendTextRow(WebInspector.UIString("MIME Type"), this.data["mimeType"]); 1070 if (this.data["encodedDataLength"]) 1071 contentHelper.appendTextRow(WebInspector.UIString("Encoded Data Length"), WebInspector.UIString("%d Bytes", this.data["encodedDataLength"])); 1072 break; 1073 case recordTypes.EvaluateScript: 1074 if (this.data && this.url) 1075 contentHelper.appendElementRow(WebInspector.UIString("Script"), this._linkifyLocation(this.url, this.data["lineNumber"])); 1076 break; 1077 case recordTypes.Paint: 1078 var clip = this.data["clip"]; 1079 if (clip) { 1080 contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", clip[0], clip[1])); 1081 var clipWidth = WebInspector.TimelinePresentationModel.quadWidth(clip); 1082 var clipHeight = WebInspector.TimelinePresentationModel.quadHeight(clip); 1083 contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d %d", clipWidth, clipHeight)); 1084 } else { 1085 // Backward compatibility: older version used x, y, width, height fields directly in data. 1086 if (typeof this.data["x"] !== "undefined" && typeof this.data["y"] !== "undefined") 1087 contentHelper.appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data["x"], this.data["y"])); 1088 if (typeof this.data["width"] !== "undefined" && typeof this.data["height"] !== "undefined") 1089 contentHelper.appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d\u2009\u00d7\u2009%d", this.data["width"], this.data["height"])); 1090 } 1091 // Fall-through intended. 1092 1093 case recordTypes.PaintSetup: 1094 case recordTypes.Rasterize: 1095 case recordTypes.ScrollLayer: 1096 if (this._relatedNode) 1097 contentHelper.appendElementRow(WebInspector.UIString("Layer root"), this._createNodeAnchor(this._relatedNode)); 1098 break; 1099 case recordTypes.RecalculateStyles: // We don't want to see default details. 1100 if (this.data["elementCount"]) 1101 contentHelper.appendTextRow(WebInspector.UIString("Elements affected"), this.data["elementCount"]); 1102 callStackLabel = WebInspector.UIString("Styles recalculation forced"); 1103 break; 1104 case recordTypes.Layout: 1105 if (this.data["dirtyObjects"]) 1106 contentHelper.appendTextRow(WebInspector.UIString("Nodes that need layout"), this.data["dirtyObjects"]); 1107 if (this.data["totalObjects"]) 1108 contentHelper.appendTextRow(WebInspector.UIString("Layout tree size"), this.data["totalObjects"]); 1109 if (typeof this.data["partialLayout"] === "boolean") { 1110 contentHelper.appendTextRow(WebInspector.UIString("Layout scope"), 1111 this.data["partialLayout"] ? WebInspector.UIString("Partial") : WebInspector.UIString("Whole document")); 1112 } 1113 callSiteStackTraceLabel = WebInspector.UIString("Layout invalidated"); 1114 if (this.stackTrace) { 1115 callStackLabel = WebInspector.UIString("Layout forced"); 1116 contentHelper.appendTextRow(WebInspector.UIString("Note"), WebInspector.UIString("Forced synchronous layout is a possible performance bottleneck.")); 1117 } 1118 if (this._relatedNode) 1119 contentHelper.appendElementRow(WebInspector.UIString("Layout root"), this._createNodeAnchor(this._relatedNode)); 1120 break; 1121 case recordTypes.Time: 1122 case recordTypes.TimeEnd: 1123 contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]); 1124 if (typeof this.intervalDuration === "number") 1125 contentHelper.appendTextRow(WebInspector.UIString("Interval Duration"), Number.secondsToString(this.intervalDuration, true)); 1126 break; 1127 case recordTypes.WebSocketCreate: 1128 case recordTypes.WebSocketSendHandshakeRequest: 1129 case recordTypes.WebSocketReceiveHandshakeResponse: 1130 case recordTypes.WebSocketDestroy: 1131 if (typeof this.webSocketURL !== "undefined") 1132 contentHelper.appendTextRow(WebInspector.UIString("URL"), this.webSocketURL); 1133 if (typeof this.webSocketProtocol !== "undefined") 1134 contentHelper.appendTextRow(WebInspector.UIString("WebSocket Protocol"), this.webSocketProtocol); 1135 if (typeof this.data["message"] !== "undefined") 1136 contentHelper.appendTextRow(WebInspector.UIString("Message"), this.data["message"]) 1137 break; 1138 default: 1139 if (this.detailsNode()) 1140 contentHelper.appendElementRow(WebInspector.UIString("Details"), this.detailsNode().childNodes[1].cloneNode()); 1141 break; 1142 } 1143 1144 if (this.scriptName && this.type !== recordTypes.FunctionCall) 1145 contentHelper.appendElementRow(WebInspector.UIString("Function Call"), this._linkifyScriptLocation()); 1146 1147 if (this.usedHeapSize) { 1148 if (this.usedHeapSizeDelta) { 1149 var sign = this.usedHeapSizeDelta > 0 ? "+" : "-"; 1150 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), 1151 WebInspector.UIString("%s (%s%s)", Number.bytesToString(this.usedHeapSize), sign, Number.bytesToString(Math.abs(this.usedHeapSizeDelta)))); 1152 } else if (this.category === WebInspector.TimelinePresentationModel.categories().scripting) 1153 contentHelper.appendTextRow(WebInspector.UIString("Used Heap Size"), Number.bytesToString(this.usedHeapSize)); 1154 } 1155 1156 if (this.callSiteStackTrace) 1157 contentHelper.appendStackTrace(callSiteStackTraceLabel || WebInspector.UIString("Call Site stack"), this.callSiteStackTrace, this._linkifyCallFrame.bind(this)); 1158 1159 if (this.stackTrace) 1160 contentHelper.appendStackTrace(callStackLabel || WebInspector.UIString("Call Stack"), this.stackTrace, this._linkifyCallFrame.bind(this)); 1161 1162 return contentHelper.contentTable(); 1163 }, 1164 1165 /** 1166 * @param {WebInspector.DOMAgent} node 1167 */ 1168 _createNodeAnchor: function(node) 1169 { 1170 var span = document.createElement("span"); 1171 span.classList.add("node-link"); 1172 span.addEventListener("click", onClick, false); 1173 WebInspector.DOMPresentationUtils.decorateNodeLabel(node, span); 1174 function onClick() 1175 { 1176 WebInspector.showPanel("elements").revealAndSelectNode(node.id); 1177 } 1178 return span; 1179 }, 1180 1181 _refreshDetails: function() 1182 { 1183 delete this._detailsNode; 1184 }, 1185 1186 /** 1187 * @return {?Node} 1188 */ 1189 detailsNode: function() 1190 { 1191 if (typeof this._detailsNode === "undefined") { 1192 this._detailsNode = this._getRecordDetails(); 1193 1194 if (this._detailsNode && !this.coalesced) { 1195 this._detailsNode.insertBefore(document.createTextNode("("), this._detailsNode.firstChild); 1196 this._detailsNode.appendChild(document.createTextNode(")")); 1197 } 1198 } 1199 return this._detailsNode; 1200 }, 1201 1202 _createSpanWithText: function(textContent) 1203 { 1204 var node = document.createElement("span"); 1205 node.textContent = textContent; 1206 return node; 1207 }, 1208 1209 /** 1210 * @return {?Node} 1211 */ 1212 _getRecordDetails: function() 1213 { 1214 var details; 1215 if (this.coalesced) 1216 return this._createSpanWithText(WebInspector.UIString(" %d", this.children.length)); 1217 1218 switch (this.type) { 1219 case WebInspector.TimelineModel.RecordType.GCEvent: 1220 details = WebInspector.UIString("%s collected", Number.bytesToString(this.data["usedHeapSizeDelta"])); 1221 break; 1222 case WebInspector.TimelineModel.RecordType.TimerFire: 1223 details = this._linkifyScriptLocation(this.data["timerId"]); 1224 break; 1225 case WebInspector.TimelineModel.RecordType.FunctionCall: 1226 details = this._linkifyScriptLocation(); 1227 break; 1228 case WebInspector.TimelineModel.RecordType.FireAnimationFrame: 1229 details = this._linkifyScriptLocation(this.data["id"]); 1230 break; 1231 case WebInspector.TimelineModel.RecordType.EventDispatch: 1232 details = this.data ? this.data["type"] : null; 1233 break; 1234 case WebInspector.TimelineModel.RecordType.Paint: 1235 var width = this.data.clip ? WebInspector.TimelinePresentationModel.quadWidth(this.data.clip) : this.data.width; 1236 var height = this.data.clip ? WebInspector.TimelinePresentationModel.quadHeight(this.data.clip) : this.data.height; 1237 if (width && height) 1238 details = WebInspector.UIString("%d\u2009\u00d7\u2009%d", width, height); 1239 break; 1240 case WebInspector.TimelineModel.RecordType.DecodeImage: 1241 details = this.data["imageType"]; 1242 break; 1243 case WebInspector.TimelineModel.RecordType.ResizeImage: 1244 details = this.data["cached"] ? WebInspector.UIString("cached") : WebInspector.UIString("non-cached"); 1245 break; 1246 case WebInspector.TimelineModel.RecordType.TimerInstall: 1247 case WebInspector.TimelineModel.RecordType.TimerRemove: 1248 details = this._linkifyTopCallFrame(this.data["timerId"]); 1249 break; 1250 case WebInspector.TimelineModel.RecordType.RequestAnimationFrame: 1251 case WebInspector.TimelineModel.RecordType.CancelAnimationFrame: 1252 details = this._linkifyTopCallFrame(this.data["id"]); 1253 break; 1254 case WebInspector.TimelineModel.RecordType.ParseHTML: 1255 case WebInspector.TimelineModel.RecordType.RecalculateStyles: 1256 details = this._linkifyTopCallFrame(); 1257 break; 1258 case WebInspector.TimelineModel.RecordType.EvaluateScript: 1259 details = this.url ? this._linkifyLocation(this.url, this.data["lineNumber"], 0) : null; 1260 break; 1261 case WebInspector.TimelineModel.RecordType.XHRReadyStateChange: 1262 case WebInspector.TimelineModel.RecordType.XHRLoad: 1263 case WebInspector.TimelineModel.RecordType.ScheduleResourceRequest: 1264 case WebInspector.TimelineModel.RecordType.ResourceSendRequest: 1265 case WebInspector.TimelineModel.RecordType.ResourceReceivedData: 1266 case WebInspector.TimelineModel.RecordType.ResourceReceiveResponse: 1267 case WebInspector.TimelineModel.RecordType.ResourceFinish: 1268 details = WebInspector.displayNameForURL(this.url); 1269 break; 1270 case WebInspector.TimelineModel.RecordType.Time: 1271 case WebInspector.TimelineModel.RecordType.TimeEnd: 1272 details = this.data["message"]; 1273 break; 1274 default: 1275 details = this._linkifyScriptLocation() || this._linkifyTopCallFrame() || null; 1276 break; 1277 } 1278 1279 if (details) { 1280 if (details instanceof Node) 1281 details.tabIndex = -1; 1282 else 1283 return this._createSpanWithText("" + details); 1284 } 1285 1286 return details || null; 1287 }, 1288 1289 /** 1290 * @param {string} url 1291 * @param {number} lineNumber 1292 * @param {number=} columnNumber 1293 */ 1294 _linkifyLocation: function(url, lineNumber, columnNumber) 1295 { 1296 // FIXME(62725): stack trace line/column numbers are one-based. 1297 columnNumber = columnNumber ? columnNumber - 1 : 0; 1298 return this._linkifier.linkifyLocation(url, lineNumber - 1, columnNumber, "timeline-details"); 1299 }, 1300 1301 _linkifyCallFrame: function(callFrame) 1302 { 1303 return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber); 1304 }, 1305 1306 /** 1307 * @param {string=} defaultValue 1308 */ 1309 _linkifyTopCallFrame: function(defaultValue) 1310 { 1311 if (this.stackTrace) 1312 return this._linkifyCallFrame(this.stackTrace[0]); 1313 if (this.callSiteStackTrace) 1314 return this._linkifyCallFrame(this.callSiteStackTrace[0]); 1315 return defaultValue; 1316 }, 1317 1318 /** 1319 * @param {*=} defaultValue 1320 * @return {Element|string} 1321 */ 1322 _linkifyScriptLocation: function(defaultValue) 1323 { 1324 if (this.scriptName) 1325 return this._linkifyLocation(this.scriptName, this.scriptLine, 0); 1326 else 1327 return defaultValue ? "" + defaultValue : null; 1328 }, 1329 1330 calculateAggregatedStats: function() 1331 { 1332 this._aggregatedStats = {}; 1333 this._cpuTime = this._selfTime; 1334 1335 for (var index = this._children.length; index; --index) { 1336 var child = this._children[index - 1]; 1337 for (var category in child._aggregatedStats) 1338 this._aggregatedStats[category] = (this._aggregatedStats[category] || 0) + child._aggregatedStats[category]; 1339 } 1340 for (var category in this._aggregatedStats) 1341 this._cpuTime += this._aggregatedStats[category]; 1342 this._aggregatedStats[this.category.name] = (this._aggregatedStats[this.category.name] || 0) + this._selfTime; 1343 }, 1344 1345 get aggregatedStats() 1346 { 1347 return this._aggregatedStats; 1348 }, 1349 1350 setHasWarning: function() 1351 { 1352 this.hasWarning = true; 1353 for (var parent = this.parent; parent && !parent.childHasWarning; parent = parent.parent) 1354 parent.childHasWarning = true; 1355 } 1356 } 1357 1358 /** 1359 * @param {Object} aggregatedStats 1360 */ 1361 WebInspector.TimelinePresentationModel._generateAggregatedInfo = function(aggregatedStats) 1362 { 1363 var cell = document.createElement("span"); 1364 cell.className = "timeline-aggregated-info"; 1365 for (var index in aggregatedStats) { 1366 var label = document.createElement("div"); 1367 label.className = "timeline-aggregated-category timeline-" + index; 1368 cell.appendChild(label); 1369 var text = document.createElement("span"); 1370 text.textContent = Number.secondsToString(aggregatedStats[index], true); 1371 cell.appendChild(text); 1372 } 1373 return cell; 1374 } 1375 1376 WebInspector.TimelinePresentationModel.generatePopupContentForFrame = function(frame) 1377 { 1378 var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Frame")); 1379 var durationInSeconds = frame.endTime - frame.startTime; 1380 var durationText = WebInspector.UIString("%s (at %s)", Number.secondsToString(frame.endTime - frame.startTime, true), 1381 Number.secondsToString(frame.startTimeOffset, true)); 1382 contentHelper.appendTextRow(WebInspector.UIString("Duration"), durationText); 1383 contentHelper.appendTextRow(WebInspector.UIString("FPS"), Math.floor(1 / durationInSeconds)); 1384 contentHelper.appendTextRow(WebInspector.UIString("CPU time"), Number.secondsToString(frame.cpuTime, true)); 1385 contentHelper.appendElementRow(WebInspector.UIString("Aggregated Time"), 1386 WebInspector.TimelinePresentationModel._generateAggregatedInfo(frame.timeByCategory)); 1387 1388 return contentHelper.contentTable(); 1389 } 1390 1391 /** 1392 * @param {WebInspector.FrameStatistics} statistics 1393 */ 1394 WebInspector.TimelinePresentationModel.generatePopupContentForFrameStatistics = function(statistics) 1395 { 1396 /** 1397 * @param {number} time 1398 */ 1399 function formatTimeAndFPS(time) 1400 { 1401 return WebInspector.UIString("%s (%.0f FPS)", Number.secondsToString(time, true), 1 / time); 1402 } 1403 1404 var contentHelper = new WebInspector.PopoverContentHelper(WebInspector.UIString("Selected Range")); 1405 1406 contentHelper.appendTextRow(WebInspector.UIString("Selected range"), WebInspector.UIString("%s\u2013%s (%d frames)", 1407 Number.secondsToString(statistics.startOffset, true), Number.secondsToString(statistics.endOffset, true), statistics.frameCount)); 1408 contentHelper.appendTextRow(WebInspector.UIString("Minimum Time"), formatTimeAndFPS(statistics.minDuration)); 1409 contentHelper.appendTextRow(WebInspector.UIString("Average Time"), formatTimeAndFPS(statistics.average)); 1410 contentHelper.appendTextRow(WebInspector.UIString("Maximum Time"), formatTimeAndFPS(statistics.maxDuration)); 1411 contentHelper.appendTextRow(WebInspector.UIString("Standard Deviation"), Number.secondsToString(statistics.stddev, true)); 1412 contentHelper.appendElementRow(WebInspector.UIString("Time by category"), 1413 WebInspector.TimelinePresentationModel._generateAggregatedInfo(statistics.timeByCategory)); 1414 1415 return contentHelper.contentTable(); 1416 } 1417 1418 /** 1419 * @param {CanvasRenderingContext2D} context 1420 * @param {number} width 1421 * @param {number} height 1422 * @param {string} color0 1423 * @param {string} color1 1424 * @param {string} color2 1425 */ 1426 WebInspector.TimelinePresentationModel.createFillStyle = function(context, width, height, color0, color1, color2) 1427 { 1428 var gradient = context.createLinearGradient(0, 0, width, height); 1429 gradient.addColorStop(0, color0); 1430 gradient.addColorStop(0.25, color1); 1431 gradient.addColorStop(0.75, color1); 1432 gradient.addColorStop(1, color2); 1433 return gradient; 1434 } 1435 1436 /** 1437 * @param {CanvasRenderingContext2D} context 1438 * @param {number} width 1439 * @param {number} height 1440 * @param {WebInspector.TimelineCategory} category 1441 */ 1442 WebInspector.TimelinePresentationModel.createFillStyleForCategory = function(context, width, height, category) 1443 { 1444 return WebInspector.TimelinePresentationModel.createFillStyle(context, width, height, category.fillColorStop0, category.fillColorStop1, category.borderColor); 1445 } 1446 1447 /** 1448 * @param {WebInspector.TimelineCategory} category 1449 */ 1450 WebInspector.TimelinePresentationModel.createStyleRuleForCategory = function(category) 1451 { 1452 var selector = ".timeline-category-" + category.name + " .timeline-graph-bar, " + 1453 ".timeline-category-statusbar-item.timeline-category-" + category.name + " .timeline-category-checkbox, " + 1454 ".popover .timeline-" + category.name + ", " + 1455 ".timeline-category-" + category.name + " .timeline-tree-icon" 1456 1457 return selector + " { background-image: -webkit-linear-gradient(" + 1458 category.fillColorStop0 + ", " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + " 25%, " + category.fillColorStop1 + ");" + 1459 " border-color: " + category.borderColor + 1460 "}"; 1461 } 1462 1463 1464 /** 1465 * @param {Object} rawRecord 1466 * @return {string?} 1467 */ 1468 WebInspector.TimelinePresentationModel.coalescingKeyForRecord = function(rawRecord) 1469 { 1470 var recordTypes = WebInspector.TimelineModel.RecordType; 1471 switch (rawRecord.type) 1472 { 1473 case recordTypes.EventDispatch: return rawRecord.data["type"]; 1474 case recordTypes.TimeStamp: return rawRecord.data["message"]; 1475 default: return null; 1476 } 1477 } 1478 1479 /** 1480 * @param {Array.<number>} quad 1481 * @return {number} 1482 */ 1483 WebInspector.TimelinePresentationModel.quadWidth = function(quad) 1484 { 1485 return Math.round(Math.sqrt(Math.pow(quad[0] - quad[2], 2) + Math.pow(quad[1] - quad[3], 2))); 1486 } 1487 1488 /** 1489 * @param {Array.<number>} quad 1490 * @return {number} 1491 */ 1492 WebInspector.TimelinePresentationModel.quadHeight = function(quad) 1493 { 1494 return Math.round(Math.sqrt(Math.pow(quad[0] - quad[6], 2) + Math.pow(quad[1] - quad[7], 2))); 1495 } 1496 1497 /** 1498 * @param {Object} data 1499 * @return {Array.<number>?} 1500 */ 1501 WebInspector.TimelinePresentationModel.quadFromRectData = function(data) 1502 { 1503 if (typeof data["x"] === "undefined" || typeof data["y"] === "undefined") 1504 return null; 1505 var x0 = data["x"]; 1506 var x1 = data["x"] + data["width"]; 1507 var y0 = data["y"]; 1508 var y1 = data["y"] + data["height"]; 1509 return [x0, y0, x1, y0, x1, y1, x0, y1]; 1510 } 1511 1512 /** 1513 * @interface 1514 */ 1515 WebInspector.TimelinePresentationModel.Filter = function() 1516 { 1517 } 1518 1519 WebInspector.TimelinePresentationModel.Filter.prototype = { 1520 /** 1521 * @param {!WebInspector.TimelinePresentationModel.Record} record 1522 * @return {boolean} 1523 */ 1524 accept: function(record) { return false; } 1525 } 1526 1527 /** 1528 * @constructor 1529 * @extends {WebInspector.Object} 1530 * @param {string} name 1531 * @param {string} title 1532 * @param {number} overviewStripGroupIndex 1533 * @param {string} borderColor 1534 * @param {string} fillColorStop0 1535 * @param {string} fillColorStop1 1536 */ 1537 WebInspector.TimelineCategory = function(name, title, overviewStripGroupIndex, borderColor, fillColorStop0, fillColorStop1) 1538 { 1539 this.name = name; 1540 this.title = title; 1541 this.overviewStripGroupIndex = overviewStripGroupIndex; 1542 this.borderColor = borderColor; 1543 this.fillColorStop0 = fillColorStop0; 1544 this.fillColorStop1 = fillColorStop1; 1545 this.hidden = false; 1546 } 1547 1548 WebInspector.TimelineCategory.Events = { 1549 VisibilityChanged: "VisibilityChanged" 1550 }; 1551 1552 WebInspector.TimelineCategory.prototype = { 1553 /** 1554 * @return {boolean} 1555 */ 1556 get hidden() 1557 { 1558 return this._hidden; 1559 }, 1560 1561 set hidden(hidden) 1562 { 1563 this._hidden = hidden; 1564 this.dispatchEventToListeners(WebInspector.TimelineCategory.Events.VisibilityChanged, this); 1565 }, 1566 1567 __proto__: WebInspector.Object.prototype 1568 } 1569