1 /* 2 * Copyright (C) 2009 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 WebInspector.TimelinePanel = function() 32 { 33 WebInspector.Panel.call(this, "timeline"); 34 35 this.element.appendChild(this._createTopPane()); 36 this.element.tabIndex = 0; 37 38 this._sidebarBackgroundElement = document.createElement("div"); 39 this._sidebarBackgroundElement.className = "sidebar timeline-sidebar-background"; 40 this.element.appendChild(this._sidebarBackgroundElement); 41 42 this._containerElement = document.createElement("div"); 43 this._containerElement.id = "timeline-container"; 44 this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false); 45 this.element.appendChild(this._containerElement); 46 47 this.createSidebar(this._containerElement, this._containerElement); 48 var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true); 49 itemsTreeElement.expanded = true; 50 this.sidebarTree.appendChild(itemsTreeElement); 51 52 this._sidebarListElement = document.createElement("div"); 53 this.sidebarElement.appendChild(this._sidebarListElement); 54 55 this._containerContentElement = document.createElement("div"); 56 this._containerContentElement.id = "resources-container-content"; 57 this._containerElement.appendChild(this._containerContentElement); 58 59 this._timelineGrid = new WebInspector.TimelineGrid(); 60 this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement; 61 this._itemsGraphsElement.id = "timeline-graphs"; 62 this._containerContentElement.appendChild(this._timelineGrid.element); 63 64 this._topGapElement = document.createElement("div"); 65 this._topGapElement.className = "timeline-gap"; 66 this._itemsGraphsElement.appendChild(this._topGapElement); 67 68 this._graphRowsElement = document.createElement("div"); 69 this._itemsGraphsElement.appendChild(this._graphRowsElement); 70 71 this._bottomGapElement = document.createElement("div"); 72 this._bottomGapElement.className = "timeline-gap"; 73 this._itemsGraphsElement.appendChild(this._bottomGapElement); 74 75 this._expandElements = document.createElement("div"); 76 this._expandElements.id = "orphan-expand-elements"; 77 this._itemsGraphsElement.appendChild(this._expandElements); 78 79 this._rootRecord = this._createRootRecord(); 80 this._sendRequestRecords = {}; 81 this._scheduledResourceRequests = {}; 82 this._timerRecords = {}; 83 84 this._calculator = new WebInspector.TimelineCalculator(); 85 this._calculator._showShortEvents = false; 86 var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold); 87 this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle); 88 this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle); 89 this._createStatusbarButtons(); 90 91 this._boundariesAreValid = true; 92 this._scrollTop = 0; 93 94 this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true); 95 96 // Disable short events filter by default. 97 this.toggleFilterButton.toggled = true; 98 this._calculator._showShortEvents = this.toggleFilterButton.toggled; 99 this._markTimelineRecords = []; 100 this._expandOffset = 15; 101 102 InspectorBackend.registerDomainDispatcher("Timeline", new WebInspector.TimelineDispatcher(this)); 103 } 104 105 // Define row height, should be in sync with styles for timeline graphs. 106 WebInspector.TimelinePanel.rowHeight = 18; 107 WebInspector.TimelinePanel.shortRecordThreshold = 0.015; 108 109 WebInspector.TimelinePanel.prototype = { 110 _createTopPane: function() { 111 var topPaneElement = document.createElement("div"); 112 topPaneElement.id = "timeline-overview-panel"; 113 114 this._topPaneSidebarElement = document.createElement("div"); 115 this._topPaneSidebarElement.id = "timeline-overview-sidebar"; 116 117 var overviewTreeElement = document.createElement("ol"); 118 overviewTreeElement.className = "sidebar-tree"; 119 this._topPaneSidebarElement.appendChild(overviewTreeElement); 120 topPaneElement.appendChild(this._topPaneSidebarElement); 121 122 var topPaneSidebarTree = new TreeOutline(overviewTreeElement); 123 var timelinesOverviewItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Timelines")); 124 topPaneSidebarTree.appendChild(timelinesOverviewItem); 125 timelinesOverviewItem.onselect = this._timelinesOverviewItemSelected.bind(this); 126 timelinesOverviewItem.select(true); 127 128 var memoryOverviewItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Memory")); 129 topPaneSidebarTree.appendChild(memoryOverviewItem); 130 memoryOverviewItem.onselect = this._memoryOverviewItemSelected.bind(this); 131 132 this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories); 133 this._overviewPane.addEventListener("window changed", this._windowChanged, this); 134 this._overviewPane.addEventListener("filter changed", this._refresh, this); 135 topPaneElement.appendChild(this._overviewPane.element); 136 137 var separatorElement = document.createElement("div"); 138 separatorElement.id = "timeline-overview-separator"; 139 topPaneElement.appendChild(separatorElement); 140 return topPaneElement; 141 }, 142 143 get toolbarItemLabel() 144 { 145 return WebInspector.UIString("Timeline"); 146 }, 147 148 get statusBarItems() 149 { 150 return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.garbageCollectButton.element, this.clearButton.element, this._overviewPane.statusBarFilters]; 151 }, 152 153 get categories() 154 { 155 if (!this._categories) { 156 this._categories = { 157 loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"), 158 scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"), 159 rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)") 160 }; 161 } 162 return this._categories; 163 }, 164 165 get defaultFocusedElement() 166 { 167 return this.element; 168 }, 169 170 get _recordStyles() 171 { 172 if (!this._recordStylesArray) { 173 var recordTypes = WebInspector.TimelineAgent.RecordType; 174 var recordStyles = {}; 175 recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting }; 176 recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering }; 177 recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering }; 178 recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering }; 179 recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading }; 180 recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting }; 181 recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting }; 182 recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting }; 183 recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting }; 184 recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting }; 185 recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting }; 186 recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting }; 187 recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading }; 188 recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading }; 189 recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading }; 190 recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting }; 191 recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading }; 192 recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting }; 193 recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting }; 194 recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting }; 195 recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: this.categories.loading }; 196 this._recordStylesArray = recordStyles; 197 } 198 return this._recordStylesArray; 199 }, 200 201 _createStatusbarButtons: function() 202 { 203 this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item"); 204 this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false); 205 206 this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); 207 this.clearButton.addEventListener("click", this._clearPanel.bind(this), false); 208 209 this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item"); 210 this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked.bind(this), false); 211 212 this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item"); 213 this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked.bind(this), false); 214 215 this.recordsCounter = document.createElement("span"); 216 this.recordsCounter.className = "timeline-records-counter"; 217 }, 218 219 _updateRecordsCounter: function() 220 { 221 this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount); 222 }, 223 224 _updateEventDividers: function() 225 { 226 this._timelineGrid.removeEventDividers(); 227 var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset; 228 var dividers = []; 229 for (var i = 0; i < this._markTimelineRecords.length; ++i) { 230 var record = this._markTimelineRecords[i]; 231 var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth); 232 var dividerPosition = Math.round(positions.left); 233 if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition]) 234 continue; 235 var divider = this._createEventDivider(record); 236 divider.style.left = (dividerPosition + this._expandOffset) + "px"; 237 dividers[dividerPosition] = divider; 238 } 239 this._timelineGrid.addEventDividers(dividers); 240 this._overviewPane.updateEventDividers(this._markTimelineRecords, this._createEventDivider.bind(this)); 241 }, 242 243 _createEventDivider: function(record) 244 { 245 var eventDivider = document.createElement("div"); 246 eventDivider.className = "resources-event-divider"; 247 var recordTypes = WebInspector.TimelineAgent.RecordType; 248 249 var eventDividerPadding = document.createElement("div"); 250 eventDividerPadding.className = "resources-event-divider-padding"; 251 eventDividerPadding.title = record.title; 252 253 if (record.type === recordTypes.MarkDOMContent) 254 eventDivider.className += " resources-blue-divider"; 255 else if (record.type === recordTypes.MarkLoad) 256 eventDivider.className += " resources-red-divider"; 257 else if (record.type === recordTypes.MarkTimeline) { 258 eventDivider.className += " resources-orange-divider"; 259 eventDividerPadding.title = record.data.message; 260 } 261 eventDividerPadding.appendChild(eventDivider); 262 return eventDividerPadding; 263 }, 264 265 _timelinesOverviewItemSelected: function(event) { 266 this._overviewPane.showTimelines(); 267 }, 268 269 _memoryOverviewItemSelected: function(event) { 270 this._overviewPane.showMemoryGraph(this._rootRecord.children); 271 }, 272 273 _toggleTimelineButtonClicked: function() 274 { 275 if (this.toggleTimelineButton.toggled) 276 TimelineAgent.stop(); 277 else { 278 this._clearPanel(); 279 TimelineAgent.start(); 280 } 281 }, 282 283 _toggleFilterButtonClicked: function() 284 { 285 this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled; 286 this._calculator._showShortEvents = this.toggleFilterButton.toggled; 287 this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText; 288 this._scheduleRefresh(true); 289 }, 290 291 _garbageCollectButtonClicked: function() 292 { 293 ProfilerAgent.collectGarbage(); 294 }, 295 296 _timelineProfilerWasStarted: function() 297 { 298 this.toggleTimelineButton.toggled = true; 299 }, 300 301 _timelineProfilerWasStopped: function() 302 { 303 this.toggleTimelineButton.toggled = false; 304 }, 305 306 _addRecordToTimeline: function(record) 307 { 308 if (record.type == WebInspector.TimelineAgent.RecordType.ResourceSendRequest) { 309 var isMainResource = (record.data.identifier === WebInspector.mainResource.identifier); 310 if (isMainResource && this._mainResourceIdentifier !== record.data.identifier) { 311 // We are loading new main resource -> clear the panel. Check above is necessary since 312 // there may be several resource loads with main resource marker upon redirects, redirects are reported with 313 // the original identifier. 314 this._mainResourceIdentifier = record.data.identifier; 315 this._clearPanel(); 316 } 317 } 318 this._innerAddRecordToTimeline(record, this._rootRecord); 319 this._scheduleRefresh(); 320 }, 321 322 _findParentRecord: function(record) 323 { 324 var recordTypes = WebInspector.TimelineAgent.RecordType; 325 var parentRecord; 326 if (record.type === recordTypes.ResourceReceiveResponse || 327 record.type === recordTypes.ResourceFinish || 328 record.type === recordTypes.ResourceReceivedData) 329 parentRecord = this._sendRequestRecords[record.data.identifier]; 330 else if (record.type === recordTypes.TimerFire) 331 parentRecord = this._timerRecords[record.data.timerId]; 332 else if (record.type === recordTypes.ResourceSendRequest) 333 parentRecord = this._scheduledResourceRequests[record.data.url]; 334 return parentRecord; 335 }, 336 337 _innerAddRecordToTimeline: function(record, parentRecord) 338 { 339 var connectedToOldRecord = false; 340 var recordTypes = WebInspector.TimelineAgent.RecordType; 341 if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) 342 parentRecord = null; // No bar entry for load events. 343 else if (parentRecord === this._rootRecord) { 344 var newParentRecord = this._findParentRecord(record); 345 if (newParentRecord) { 346 parentRecord = newParentRecord; 347 connectedToOldRecord = true; 348 } 349 } 350 351 if (record.type == recordTypes.TimerFire && record.children && record.children.length) { 352 var childRecord = record.children[0]; 353 if (childRecord.type === recordTypes.FunctionCall) { 354 record.data.scriptName = childRecord.data.scriptName; 355 record.data.scriptLine = childRecord.data.scriptLine; 356 record.children.shift(); 357 record.children = childRecord.children.concat(record.children); 358 } 359 } 360 361 var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this); 362 363 if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) { 364 this._markTimelineRecords.push(formattedRecord); 365 return; 366 } 367 368 ++this._rootRecord._allRecordsCount; 369 formattedRecord.collapsed = (parentRecord === this._rootRecord); 370 371 var childrenCount = record.children ? record.children.length : 0; 372 for (var i = 0; i < childrenCount; ++i) 373 this._innerAddRecordToTimeline(record.children[i], formattedRecord); 374 375 formattedRecord._calculateAggregatedStats(this.categories); 376 377 if (connectedToOldRecord) { 378 var record = formattedRecord; 379 do { 380 var parent = record.parent; 381 parent._cpuTime += formattedRecord._cpuTime; 382 if (parent._lastChildEndTime < record._lastChildEndTime) 383 parent._lastChildEndTime = record._lastChildEndTime; 384 for (var category in formattedRecord._aggregatedStats) 385 parent._aggregatedStats[category] += formattedRecord._aggregatedStats[category]; 386 record = parent; 387 } while (record.parent); 388 } else 389 if (parentRecord !== this._rootRecord) 390 parentRecord._selfTime -= formattedRecord.endTime - formattedRecord.startTime; 391 392 // Keep bar entry for mark timeline since nesting might be interesting to the user. 393 if (record.type === recordTypes.MarkTimeline) 394 this._markTimelineRecords.push(formattedRecord); 395 }, 396 397 setSidebarWidth: function(width) 398 { 399 WebInspector.Panel.prototype.setSidebarWidth.call(this, width); 400 this._sidebarBackgroundElement.style.width = width + "px"; 401 this._topPaneSidebarElement.style.width = width + "px"; 402 }, 403 404 updateMainViewWidth: function(width) 405 { 406 this._containerContentElement.style.left = width + "px"; 407 this._scheduleRefresh(); 408 this._overviewPane.updateMainViewWidth(width); 409 }, 410 411 resize: function() 412 { 413 this._closeRecordDetails(); 414 this._scheduleRefresh(); 415 }, 416 417 _createRootRecord: function() 418 { 419 var rootRecord = {}; 420 rootRecord.children = []; 421 rootRecord._visibleRecordsCount = 0; 422 rootRecord._allRecordsCount = 0; 423 rootRecord._aggregatedStats = {}; 424 return rootRecord; 425 }, 426 427 _clearPanel: function() 428 { 429 this._markTimelineRecords = []; 430 this._sendRequestRecords = {}; 431 this._scheduledResourceRequests = {}; 432 this._timerRecords = {}; 433 this._rootRecord = this._createRootRecord(); 434 this._boundariesAreValid = false; 435 this._overviewPane.reset(); 436 this._adjustScrollPosition(0); 437 this._refresh(); 438 this._closeRecordDetails(); 439 }, 440 441 show: function() 442 { 443 WebInspector.Panel.prototype.show.call(this); 444 if (typeof this._scrollTop === "number") 445 this._containerElement.scrollTop = this._scrollTop; 446 this._refresh(); 447 WebInspector.drawer.currentPanelCounters = this.recordsCounter; 448 }, 449 450 hide: function() 451 { 452 WebInspector.Panel.prototype.hide.call(this); 453 this._closeRecordDetails(); 454 WebInspector.drawer.currentPanelCounters = null; 455 }, 456 457 _onScroll: function(event) 458 { 459 this._closeRecordDetails(); 460 var scrollTop = this._containerElement.scrollTop; 461 var dividersTop = Math.max(0, scrollTop); 462 this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop); 463 this._scheduleRefresh(true); 464 }, 465 466 _windowChanged: function() 467 { 468 this._closeRecordDetails(); 469 this._scheduleRefresh(); 470 }, 471 472 _scheduleRefresh: function(preserveBoundaries) 473 { 474 this._closeRecordDetails(); 475 this._boundariesAreValid &= preserveBoundaries; 476 477 if (!this.visible) 478 return; 479 480 if (preserveBoundaries) 481 this._refresh(); 482 else 483 if (!this._refreshTimeout) 484 this._refreshTimeout = setTimeout(this._refresh.bind(this), 100); 485 }, 486 487 _refresh: function() 488 { 489 if (this._refreshTimeout) { 490 clearTimeout(this._refreshTimeout); 491 delete this._refreshTimeout; 492 } 493 494 this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents); 495 this._refreshRecords(!this._boundariesAreValid); 496 this._updateRecordsCounter(); 497 if(!this._boundariesAreValid) 498 this._updateEventDividers(); 499 this._boundariesAreValid = true; 500 }, 501 502 _updateBoundaries: function() 503 { 504 this._calculator.reset(); 505 this._calculator.windowLeft = this._overviewPane.windowLeft; 506 this._calculator.windowRight = this._overviewPane.windowRight; 507 508 for (var i = 0; i < this._rootRecord.children.length; ++i) 509 this._calculator.updateBoundaries(this._rootRecord.children[i]); 510 511 this._calculator.calculateWindow(); 512 }, 513 514 _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed) 515 { 516 if (!this._calculator._showShortEvents && !record.isLong()) 517 return; 518 var percentages = this._calculator.computeBarGraphPercentages(record); 519 if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) { 520 ++this._rootRecord._visibleRecordsCount; 521 ++record.parent._invisibleChildrenCount; 522 if (!parentIsCollapsed) 523 recordsWindow.push(record); 524 } 525 526 var index = recordsWindow.length; 527 record._invisibleChildrenCount = 0; 528 for (var i = 0; i < record.children.length; ++i) 529 this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed); 530 record._visibleChildrenCount = recordsWindow.length - index; 531 }, 532 533 _filterRecords: function() 534 { 535 var recordsInWindow = []; 536 this._rootRecord._visibleRecordsCount = 0; 537 for (var i = 0; i < this._rootRecord.children.length; ++i) 538 this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow); 539 return recordsInWindow; 540 }, 541 542 _refreshRecords: function(updateBoundaries) 543 { 544 if (updateBoundaries) 545 this._updateBoundaries(); 546 547 var recordsInWindow = this._filterRecords(); 548 549 // Calculate the visible area. 550 this._scrollTop = this._containerElement.scrollTop; 551 var visibleTop = this._scrollTop; 552 var visibleBottom = visibleTop + this._containerElement.clientHeight; 553 554 const rowHeight = WebInspector.TimelinePanel.rowHeight; 555 556 // Convert visible area to visible indexes. Always include top-level record for a visible nested record. 557 var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1)); 558 var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight)); 559 560 // Resize gaps first. 561 const top = (startIndex * rowHeight) + "px"; 562 this._topGapElement.style.height = top; 563 this.sidebarElement.style.top = top; 564 this.sidebarResizeElement.style.top = top; 565 this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px"; 566 567 // Update visible rows. 568 var listRowElement = this._sidebarListElement.firstChild; 569 var width = this._graphRowsElement.offsetWidth; 570 this._itemsGraphsElement.removeChild(this._graphRowsElement); 571 var graphRowElement = this._graphRowsElement.firstChild; 572 var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true); 573 this._itemsGraphsElement.removeChild(this._expandElements); 574 this._expandElements.removeChildren(); 575 576 for (var i = 0; i < endIndex; ++i) { 577 var record = recordsInWindow[i]; 578 var isEven = !(i % 2); 579 580 if (i < startIndex) { 581 var lastChildIndex = i + record._visibleChildrenCount; 582 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) { 583 var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements); 584 expandElement._update(record, i, this._calculator.computeBarGraphWindowPosition(record, width - this._expandOffset)); 585 } 586 } else { 587 if (!listRowElement) { 588 listRowElement = new WebInspector.TimelineRecordListRow().element; 589 this._sidebarListElement.appendChild(listRowElement); 590 } 591 if (!graphRowElement) { 592 graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback, rowHeight).element; 593 this._graphRowsElement.appendChild(graphRowElement); 594 } 595 596 listRowElement.row.update(record, isEven, this._calculator, visibleTop); 597 graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i); 598 599 listRowElement = listRowElement.nextSibling; 600 graphRowElement = graphRowElement.nextSibling; 601 } 602 } 603 604 // Remove extra rows. 605 while (listRowElement) { 606 var nextElement = listRowElement.nextSibling; 607 listRowElement.row.dispose(); 608 listRowElement = nextElement; 609 } 610 while (graphRowElement) { 611 var nextElement = graphRowElement.nextSibling; 612 graphRowElement.row.dispose(); 613 graphRowElement = nextElement; 614 } 615 616 this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement); 617 this._itemsGraphsElement.appendChild(this._expandElements); 618 this.sidebarResizeElement.style.height = this.sidebarElement.clientHeight + "px"; 619 // Reserve some room for expand / collapse controls to the left for records that start at 0ms. 620 var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0; 621 if (updateBoundaries) 622 this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft); 623 this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight); 624 }, 625 626 _adjustScrollPosition: function(totalHeight) 627 { 628 // Prevent the container from being scrolled off the end. 629 if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1) 630 this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight); 631 }, 632 633 _getPopoverAnchor: function(element) 634 { 635 return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item"); 636 }, 637 638 _showPopover: function(anchor) 639 { 640 var record = anchor.row._record; 641 var popover = new WebInspector.Popover(record._generatePopupContent(this._calculator, this.categories)); 642 popover.show(anchor); 643 return popover; 644 }, 645 646 _closeRecordDetails: function() 647 { 648 this._popoverHelper.hidePopup(); 649 } 650 } 651 652 WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype; 653 654 WebInspector.TimelineDispatcher = function(timelinePanel) 655 { 656 this._timelinePanel = timelinePanel; 657 } 658 659 WebInspector.TimelineDispatcher.prototype = { 660 started: function() 661 { 662 this._timelinePanel._timelineProfilerWasStarted(); 663 }, 664 665 stopped: function() 666 { 667 this._timelinePanel._timelineProfilerWasStopped(); 668 }, 669 670 eventRecorded: function(record) 671 { 672 this._timelinePanel._addRecordToTimeline(record); 673 } 674 } 675 676 WebInspector.TimelineCategory = function(name, title, color) 677 { 678 this.name = name; 679 this.title = title; 680 this.color = color; 681 } 682 683 WebInspector.TimelineCalculator = function() 684 { 685 this.reset(); 686 this.windowLeft = 0.0; 687 this.windowRight = 1.0; 688 } 689 690 WebInspector.TimelineCalculator.prototype = { 691 computeBarGraphPercentages: function(record) 692 { 693 var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100; 694 var end = (record.startTime + record._selfTime - this.minimumBoundary) / this.boundarySpan * 100; 695 var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100; 696 var cpuWidth = record._cpuTime / this.boundarySpan * 100; 697 return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth}; 698 }, 699 700 computeBarGraphWindowPosition: function(record, clientWidth) 701 { 702 const minWidth = 5; 703 const borderWidth = 4; 704 var workingArea = clientWidth - minWidth - borderWidth; 705 var percentages = this.computeBarGraphPercentages(record); 706 var left = percentages.start / 100 * workingArea; 707 var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth; 708 var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * workingArea; 709 var cpuWidth = percentages.cpuWidth / 100 * workingArea + minWidth; 710 if (percentages.endWithChildren > percentages.end) 711 widthWithChildren += borderWidth + minWidth; 712 return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth}; 713 }, 714 715 calculateWindow: function() 716 { 717 this.minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary); 718 this.maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary); 719 this.boundarySpan = this.maximumBoundary - this.minimumBoundary; 720 }, 721 722 reset: function() 723 { 724 this._absoluteMinimumBoundary = -1; 725 this._absoluteMaximumBoundary = -1; 726 }, 727 728 updateBoundaries: function(record) 729 { 730 var lowerBound = record.startTime; 731 if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary) 732 this._absoluteMinimumBoundary = lowerBound; 733 734 const minimumTimeFrame = 0.1; 735 const minimumDeltaForZeroSizeEvents = 0.01; 736 var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame); 737 if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary) 738 this._absoluteMaximumBoundary = upperBound; 739 }, 740 741 formatValue: function(value) 742 { 743 return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary); 744 } 745 } 746 747 748 WebInspector.TimelineRecordListRow = function() 749 { 750 this.element = document.createElement("div"); 751 this.element.row = this; 752 this.element.style.cursor = "pointer"; 753 var iconElement = document.createElement("span"); 754 iconElement.className = "timeline-tree-icon"; 755 this.element.appendChild(iconElement); 756 757 this._typeElement = document.createElement("span"); 758 this._typeElement.className = "type"; 759 this.element.appendChild(this._typeElement); 760 761 var separatorElement = document.createElement("span"); 762 separatorElement.className = "separator"; 763 separatorElement.textContent = " "; 764 765 this._dataElement = document.createElement("span"); 766 this._dataElement.className = "data dimmed"; 767 768 this.element.appendChild(separatorElement); 769 this.element.appendChild(this._dataElement); 770 } 771 772 WebInspector.TimelineRecordListRow.prototype = { 773 update: function(record, isEven, calculator, offset) 774 { 775 this._record = record; 776 this._calculator = calculator; 777 this._offset = offset; 778 779 this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : ""); 780 this._typeElement.textContent = record.title; 781 782 if (this._dataElement.firstChild) 783 this._dataElement.removeChildren(); 784 if (record.details) { 785 var detailsContainer = document.createElement("span"); 786 if (typeof record.details === "object") { 787 detailsContainer.appendChild(document.createTextNode("(")); 788 detailsContainer.appendChild(record.details); 789 detailsContainer.appendChild(document.createTextNode(")")); 790 } else 791 detailsContainer.textContent = "(" + record.details + ")"; 792 this._dataElement.appendChild(detailsContainer); 793 } 794 }, 795 796 dispose: function() 797 { 798 this.element.parentElement.removeChild(this.element); 799 } 800 } 801 802 WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh) 803 { 804 this.element = document.createElement("div"); 805 this.element.row = this; 806 807 this._barAreaElement = document.createElement("div"); 808 this._barAreaElement.className = "timeline-graph-bar-area"; 809 this.element.appendChild(this._barAreaElement); 810 811 this._barWithChildrenElement = document.createElement("div"); 812 this._barWithChildrenElement.className = "timeline-graph-bar with-children"; 813 this._barWithChildrenElement.row = this; 814 this._barAreaElement.appendChild(this._barWithChildrenElement); 815 816 this._barCpuElement = document.createElement("div"); 817 this._barCpuElement.className = "timeline-graph-bar cpu" 818 this._barCpuElement.row = this; 819 this._barAreaElement.appendChild(this._barCpuElement); 820 821 this._barElement = document.createElement("div"); 822 this._barElement.className = "timeline-graph-bar"; 823 this._barElement.row = this; 824 this._barAreaElement.appendChild(this._barElement); 825 826 this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer); 827 this._expandElement._element.addEventListener("click", this._onClick.bind(this)); 828 829 this._scheduleRefresh = scheduleRefresh; 830 } 831 832 WebInspector.TimelineRecordGraphRow.prototype = { 833 update: function(record, isEven, calculator, clientWidth, expandOffset, index) 834 { 835 this._record = record; 836 this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : ""); 837 var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset); 838 this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px"; 839 this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px"; 840 this._barElement.style.left = barPosition.left + expandOffset + "px"; 841 this._barElement.style.width = barPosition.width + "px"; 842 this._barCpuElement.style.left = barPosition.left + expandOffset + "px"; 843 this._barCpuElement.style.width = barPosition.cpuWidth + "px"; 844 this._expandElement._update(record, index, barPosition); 845 }, 846 847 _onClick: function(event) 848 { 849 this._record.collapsed = !this._record.collapsed; 850 this._scheduleRefresh(); 851 }, 852 853 dispose: function() 854 { 855 this.element.parentElement.removeChild(this.element); 856 this._expandElement._dispose(); 857 } 858 } 859 860 WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, panel) 861 { 862 var recordTypes = WebInspector.TimelineAgent.RecordType; 863 var style = panel._recordStyles[record.type]; 864 this.parent = parentRecord; 865 if (parentRecord) 866 parentRecord.children.push(this); 867 this.category = style.category; 868 this.title = style.title; 869 this.startTime = record.startTime / 1000; 870 this.data = record.data; 871 this.type = record.type; 872 this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime; 873 this._selfTime = this.endTime - this.startTime; 874 this._lastChildEndTime = this.endTime; 875 if (record.stackTrace && record.stackTrace.length) 876 this.stackTrace = record.stackTrace; 877 this.totalHeapSize = record.totalHeapSize; 878 this.usedHeapSize = record.usedHeapSize; 879 880 // Make resource receive record last since request was sent; make finish record last since response received. 881 if (record.type === recordTypes.ResourceSendRequest) { 882 panel._sendRequestRecords[record.data.identifier] = this; 883 } else if (record.type === recordTypes.ScheduleResourceRequest) { 884 panel._scheduledResourceRequests[record.data.url] = this; 885 } else if (record.type === recordTypes.ResourceReceiveResponse) { 886 var sendRequestRecord = panel._sendRequestRecords[record.data.identifier]; 887 if (sendRequestRecord) { // False if we started instrumentation in the middle of request. 888 record.data.url = sendRequestRecord.data.url; 889 // Now that we have resource in the collection, recalculate details in order to display short url. 890 sendRequestRecord.details = this._getRecordDetails(sendRequestRecord, panel._sendRequestRecords); 891 if (sendRequestRecord.parent !== panel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest) 892 sendRequestRecord.parent.details = this._getRecordDetails(sendRequestRecord, panel._sendRequestRecords); 893 } 894 } else if (record.type === recordTypes.ResourceReceivedData) { 895 var sendRequestRecord = panel._sendRequestRecords[record.data.identifier]; 896 if (sendRequestRecord) // False for main resource. 897 record.data.url = sendRequestRecord.data.url; 898 } else if (record.type === recordTypes.ResourceFinish) { 899 var sendRequestRecord = panel._sendRequestRecords[record.data.identifier]; 900 if (sendRequestRecord) // False for main resource. 901 record.data.url = sendRequestRecord.data.url; 902 } else if (record.type === recordTypes.TimerInstall) { 903 this.timeout = record.data.timeout; 904 this.singleShot = record.data.singleShot; 905 panel._timerRecords[record.data.timerId] = this; 906 } else if (record.type === recordTypes.TimerFire) { 907 var timerInstalledRecord = panel._timerRecords[record.data.timerId]; 908 if (timerInstalledRecord) { 909 this.callSiteStackTrace = timerInstalledRecord.stackTrace; 910 this.timeout = timerInstalledRecord.timeout; 911 this.singleShot = timerInstalledRecord.singleShot; 912 } 913 } 914 this.details = this._getRecordDetails(record, panel._sendRequestRecords); 915 } 916 917 WebInspector.TimelinePanel.FormattedRecord.prototype = { 918 isLong: function() 919 { 920 return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold; 921 }, 922 923 get children() 924 { 925 if (!this._children) 926 this._children = []; 927 return this._children; 928 }, 929 930 _generateAggregatedInfo: function() 931 { 932 var cell = document.createElement("span"); 933 cell.className = "timeline-aggregated-info"; 934 for (var index in this._aggregatedStats) { 935 var label = document.createElement("div"); 936 label.className = "timeline-aggregated-category timeline-" + index; 937 cell.appendChild(label); 938 var text = document.createElement("span"); 939 text.textContent = Number.secondsToString(this._aggregatedStats[index] + 0.0001); 940 cell.appendChild(text); 941 } 942 return cell; 943 }, 944 945 _generatePopupContent: function(calculator, categories) 946 { 947 var contentHelper = new WebInspector.TimelinePanel.PopupContentHelper(this.title); 948 949 if (this._children && this._children.length) { 950 contentHelper._appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime + 0.0001)); 951 contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"), this._generateAggregatedInfo()); 952 } 953 var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime), 954 calculator.formatValue(this.startTime - calculator.minimumBoundary)); 955 contentHelper._appendTextRow(WebInspector.UIString("Duration"), text); 956 957 const recordTypes = WebInspector.TimelineAgent.RecordType; 958 959 switch (this.type) { 960 case recordTypes.GCEvent: 961 contentHelper._appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data.usedHeapSizeDelta)); 962 break; 963 case recordTypes.TimerInstall: 964 case recordTypes.TimerFire: 965 case recordTypes.TimerRemove: 966 contentHelper._appendTextRow(WebInspector.UIString("Timer ID"), this.data.timerId); 967 if (typeof this.timeout === "number") { 968 contentHelper._appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000)); 969 contentHelper._appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot); 970 } 971 break; 972 case recordTypes.FunctionCall: 973 contentHelper._appendLinkRow(WebInspector.UIString("Location"), this.data.scriptName, this.data.scriptLine); 974 break; 975 case recordTypes.ScheduleResourceRequest: 976 case recordTypes.ResourceSendRequest: 977 case recordTypes.ResourceReceiveResponse: 978 case recordTypes.ResourceReceivedData: 979 case recordTypes.ResourceFinish: 980 contentHelper._appendLinkRow(WebInspector.UIString("Resource"), this.data.url); 981 if (this.data.requestMethod) 982 contentHelper._appendTextRow(WebInspector.UIString("Request Method"), this.data.requestMethod); 983 if (typeof this.data.statusCode === "number") 984 contentHelper._appendTextRow(WebInspector.UIString("Status Code"), this.data.statusCode); 985 if (this.data.mimeType) 986 contentHelper._appendTextRow(WebInspector.UIString("MIME Type"), this.data.mimeType); 987 break; 988 case recordTypes.EvaluateScript: 989 if (this.data && this.data.url) 990 contentHelper._appendLinkRow(WebInspector.UIString("Script"), this.data.url, this.data.lineNumber); 991 break; 992 case recordTypes.Paint: 993 contentHelper._appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data.x, this.data.y)); 994 contentHelper._appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d %d", this.data.width, this.data.height)); 995 case recordTypes.RecalculateStyles: // We don't want to see default details. 996 break; 997 default: 998 if (this.details) 999 contentHelper._appendTextRow(WebInspector.UIString("Details"), this.details); 1000 break; 1001 } 1002 1003 if (this.data.scriptName && this.type !== recordTypes.FunctionCall) 1004 contentHelper._appendLinkRow(WebInspector.UIString("Function Call"), this.data.scriptName, this.data.scriptLine); 1005 1006 if (this.usedHeapSize) 1007 contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"), WebInspector.UIString("%s of %s", Number.bytesToString(this.usedHeapSize), Number.bytesToString(this.totalHeapSize))); 1008 1009 if (this.callSiteStackTrace && this.callSiteStackTrace.length) 1010 contentHelper._appendStackTrace(WebInspector.UIString("Call Site stack"), this.callSiteStackTrace); 1011 1012 if (this.stackTrace) 1013 contentHelper._appendStackTrace(WebInspector.UIString("Call Stack"), this.stackTrace); 1014 1015 return contentHelper._contentTable; 1016 }, 1017 1018 _getRecordDetails: function(record, sendRequestRecords) 1019 { 1020 switch (record.type) { 1021 case WebInspector.TimelineAgent.RecordType.GCEvent: 1022 return WebInspector.UIString("%s collected", Number.bytesToString(record.data.usedHeapSizeDelta)); 1023 case WebInspector.TimelineAgent.RecordType.TimerFire: 1024 return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine, "", "") : record.data.timerId; 1025 case WebInspector.TimelineAgent.RecordType.FunctionCall: 1026 return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine, "", "") : null; 1027 case WebInspector.TimelineAgent.RecordType.EventDispatch: 1028 return record.data ? record.data.type : null; 1029 case WebInspector.TimelineAgent.RecordType.Paint: 1030 return record.data.width + "\u2009\u00d7\u2009" + record.data.height; 1031 case WebInspector.TimelineAgent.RecordType.TimerInstall: 1032 case WebInspector.TimelineAgent.RecordType.TimerRemove: 1033 return this.stackTrace ? WebInspector.linkifyResourceAsNode(this.stackTrace[0].url, "scripts", this.stackTrace[0].lineNumber, "", "") : record.data.timerId; 1034 case WebInspector.TimelineAgent.RecordType.ParseHTML: 1035 case WebInspector.TimelineAgent.RecordType.RecalculateStyles: 1036 return this.stackTrace ? WebInspector.linkifyResourceAsNode(this.stackTrace[0].url, "scripts", this.stackTrace[0].lineNumber, "", "") : null; 1037 case WebInspector.TimelineAgent.RecordType.EvaluateScript: 1038 return record.data.url ? WebInspector.linkifyResourceAsNode(record.data.url, "scripts", record.data.lineNumber, "", "") : null; 1039 case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange: 1040 case WebInspector.TimelineAgent.RecordType.XHRLoad: 1041 case WebInspector.TimelineAgent.RecordType.ScheduleResourceRequest: 1042 case WebInspector.TimelineAgent.RecordType.ResourceSendRequest: 1043 case WebInspector.TimelineAgent.RecordType.ResourceReceivedData: 1044 case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse: 1045 case WebInspector.TimelineAgent.RecordType.ResourceFinish: 1046 return WebInspector.displayNameForURL(record.data.url); 1047 case WebInspector.TimelineAgent.RecordType.MarkTimeline: 1048 return record.data.message; 1049 default: 1050 return null; 1051 } 1052 }, 1053 1054 _calculateAggregatedStats: function(categories) 1055 { 1056 this._aggregatedStats = {}; 1057 for (var category in categories) 1058 this._aggregatedStats[category] = 0; 1059 this._cpuTime = this._selfTime; 1060 1061 if (this._children) { 1062 for (var index = this._children.length; index; --index) { 1063 var child = this._children[index - 1]; 1064 this._aggregatedStats[child.category.name] += child._selfTime; 1065 for (var category in categories) 1066 this._aggregatedStats[category] += child._aggregatedStats[category]; 1067 } 1068 for (var category in this._aggregatedStats) 1069 this._cpuTime += this._aggregatedStats[category]; 1070 } 1071 } 1072 } 1073 1074 WebInspector.TimelinePanel.PopupContentHelper = function(title) 1075 { 1076 this._contentTable = document.createElement("table");; 1077 var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title"); 1078 titleCell.colSpan = 2; 1079 var titleRow = document.createElement("tr"); 1080 titleRow.appendChild(titleCell); 1081 this._contentTable.appendChild(titleRow); 1082 } 1083 1084 WebInspector.TimelinePanel.PopupContentHelper.prototype = { 1085 _createCell: function(content, styleName) 1086 { 1087 var text = document.createElement("label"); 1088 text.appendChild(document.createTextNode(content)); 1089 var cell = document.createElement("td"); 1090 cell.className = "timeline-details"; 1091 if (styleName) 1092 cell.className += " " + styleName; 1093 cell.textContent = content; 1094 return cell; 1095 }, 1096 1097 _appendTextRow: function(title, content) 1098 { 1099 var row = document.createElement("tr"); 1100 row.appendChild(this._createCell(title, "timeline-details-row-title")); 1101 row.appendChild(this._createCell(content, "timeline-details-row-data")); 1102 this._contentTable.appendChild(row); 1103 }, 1104 1105 _appendElementRow: function(title, content, titleStyle) 1106 { 1107 var row = document.createElement("tr"); 1108 var titleCell = this._createCell(title, "timeline-details-row-title"); 1109 if (titleStyle) 1110 titleCell.addStyleClass(titleStyle); 1111 row.appendChild(titleCell); 1112 var cell = document.createElement("td"); 1113 cell.className = "timeline-details"; 1114 cell.appendChild(content); 1115 row.appendChild(cell); 1116 this._contentTable.appendChild(row); 1117 }, 1118 1119 _appendLinkRow: function(title, scriptName, scriptLine) 1120 { 1121 var link = WebInspector.linkifyResourceAsNode(scriptName, "scripts", scriptLine, "timeline-details"); 1122 this._appendElementRow(title, link); 1123 }, 1124 1125 _appendStackTrace: function(title, stackTrace) 1126 { 1127 this._appendTextRow("", ""); 1128 var framesTable = document.createElement("table"); 1129 for (var i = 0; i < stackTrace.length; ++i) { 1130 var stackFrame = stackTrace[i]; 1131 var row = document.createElement("tr"); 1132 row.className = "timeline-details"; 1133 row.appendChild(this._createCell(stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"), "timeline-function-name")); 1134 row.appendChild(this._createCell(" @ ")); 1135 var linkCell = document.createElement("td"); 1136 linkCell.appendChild(WebInspector.linkifyResourceAsNode(stackFrame.url, "scripts", stackFrame.lineNumber, "timeline-details")); 1137 row.appendChild(linkCell); 1138 framesTable.appendChild(row); 1139 } 1140 this._appendElementRow(title, framesTable, "timeline-stacktrace-title"); 1141 } 1142 } 1143 1144 WebInspector.TimelineExpandableElement = function(container) 1145 { 1146 this._element = document.createElement("div"); 1147 this._element.className = "timeline-expandable"; 1148 1149 var leftBorder = document.createElement("div"); 1150 leftBorder.className = "timeline-expandable-left"; 1151 this._element.appendChild(leftBorder); 1152 1153 container.appendChild(this._element); 1154 } 1155 1156 WebInspector.TimelineExpandableElement.prototype = { 1157 _update: function(record, index, barPosition) 1158 { 1159 const rowHeight = WebInspector.TimelinePanel.rowHeight; 1160 if (record._visibleChildrenCount || record._invisibleChildrenCount) { 1161 this._element.style.top = index * rowHeight + "px"; 1162 this._element.style.left = barPosition.left + "px"; 1163 this._element.style.width = Math.max(12, barPosition.width + 25) + "px"; 1164 if (!record.collapsed) { 1165 this._element.style.height = (record._visibleChildrenCount + 1) * rowHeight + "px"; 1166 this._element.addStyleClass("timeline-expandable-expanded"); 1167 this._element.removeStyleClass("timeline-expandable-collapsed"); 1168 } else { 1169 this._element.style.height = rowHeight + "px"; 1170 this._element.addStyleClass("timeline-expandable-collapsed"); 1171 this._element.removeStyleClass("timeline-expandable-expanded"); 1172 } 1173 this._element.removeStyleClass("hidden"); 1174 } else 1175 this._element.addStyleClass("hidden"); 1176 }, 1177 1178 _dispose: function() 1179 { 1180 this._element.parentElement.removeChild(this._element); 1181 } 1182 } 1183