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