2 * Copyright (C) 2011 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
31 WebInspector.TimelinePanel = function()
33 WebInspector.Panel.call(this, "timeline");
35 this.element.appendChild(this._createTopPane());
36 this.element.addEventListener("contextmenu", this._contextMenu.bind(this), true);
37 this.element.tabIndex = 0;
39 this._sidebarBackgroundElement = document.createElement("div");
40 this._sidebarBackgroundElement.className = "sidebar timeline-sidebar-background";
41 this.element.appendChild(this._sidebarBackgroundElement);
43 this._containerElement = document.createElement("div");
44 this._containerElement.id = "timeline-container";
45 this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
46 this.element.appendChild(this._containerElement);
48 this.createSidebar(this._containerElement, this._containerElement);
49 var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
50 itemsTreeElement.expanded = true;
51 this.sidebarTree.appendChild(itemsTreeElement);
53 this._sidebarListElement = document.createElement("div");
54 this.sidebarElement.appendChild(this._sidebarListElement);
56 this._containerContentElement = document.createElement("div");
57 this._containerContentElement.id = "resources-container-content";
58 this._containerElement.appendChild(this._containerContentElement);
60 this._timelineGrid = new WebInspector.TimelineGrid();
61 this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
62 this._itemsGraphsElement.id = "timeline-graphs";
63 this._itemsGraphsElement.addEventListener("mousewheel", this._overviewPane.scrollWindow.bind(this._overviewPane), true);
64 this._containerContentElement.appendChild(this._timelineGrid.element);
66 this._topGapElement = document.createElement("div");
67 this._topGapElement.className = "timeline-gap";
68 this._itemsGraphsElement.appendChild(this._topGapElement);
70 this._graphRowsElement = document.createElement("div");
71 this._itemsGraphsElement.appendChild(this._graphRowsElement);
73 this._bottomGapElement = document.createElement("div");
74 this._bottomGapElement.className = "timeline-gap";
75 this._itemsGraphsElement.appendChild(this._bottomGapElement);
77 this._expandElements = document.createElement("div");
78 this._expandElements.id = "orphan-expand-elements";
79 this._itemsGraphsElement.appendChild(this._expandElements);
81 this._rootRecord = this._createRootRecord();
82 this._sendRequestRecords = {};
83 this._scheduledResourceRequests = {};
84 this._timerRecords = {};
85 this._registeredAnimationCallbackRecords = {};
87 this._calculator = new WebInspector.TimelineCalculator();
88 this._calculator._showShortEvents = false;
89 var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold);
90 this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle);
91 this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle);
92 this._createStatusbarButtons();
94 this._boundariesAreValid = true;
97 this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this));
98 this._containerElement.addEventListener("mousemove", this._mouseMove.bind(this), false);
99 this._containerElement.addEventListener("mouseout", this._mouseOut.bind(this), false);
101 // Disable short events filter by default.
102 this.toggleFilterButton.toggled = true;
103 this._calculator._showShortEvents = this.toggleFilterButton.toggled;
104 this._timeStampRecords = [];
105 this._expandOffset = 15;
107 this._createFileSelector();
108 this._model = new WebInspector.TimelineModel(this);
110 this._registerShortcuts();
111 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, this._onTimelineEventRecorded, this);
114 // Define row height, should be in sync with styles for timeline graphs.
115 WebInspector.TimelinePanel.rowHeight = 18;
116 WebInspector.TimelinePanel.shortRecordThreshold = 0.015;
118 WebInspector.TimelinePanel.prototype = {
119 _createTopPane: function() {
120 var topPaneElement = document.createElement("div");
121 topPaneElement.id = "timeline-overview-panel";
123 this._topPaneSidebarElement = document.createElement("div");
124 this._topPaneSidebarElement.id = "timeline-overview-sidebar";
126 var overviewTreeElement = document.createElement("ol");
127 overviewTreeElement.className = "sidebar-tree";
128 this._topPaneSidebarElement.appendChild(overviewTreeElement);
129 topPaneElement.appendChild(this._topPaneSidebarElement);
131 var topPaneSidebarTree = new TreeOutline(overviewTreeElement);
132 var timelinesOverviewItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Timelines"));
133 topPaneSidebarTree.appendChild(timelinesOverviewItem);
134 timelinesOverviewItem.revealAndSelect(false);
135 timelinesOverviewItem.onselect = this._timelinesOverviewItemSelected.bind(this);
137 var memoryOverviewItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Memory"));
138 topPaneSidebarTree.appendChild(memoryOverviewItem);
139 memoryOverviewItem.onselect = this._memoryOverviewItemSelected.bind(this);
141 this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories);
142 this._overviewPane.addEventListener("window changed", this._windowChanged, this);
143 this._overviewPane.addEventListener("filter changed", this._refresh, this);
144 topPaneElement.appendChild(this._overviewPane.element);
146 var separatorElement = document.createElement("div");
147 separatorElement.id = "timeline-overview-separator";
148 topPaneElement.appendChild(separatorElement);
149 return topPaneElement;
152 get toolbarItemLabel()
154 return WebInspector.UIString("Timeline");
159 return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.garbageCollectButton.element, this.clearButton.element, this._overviewPane.statusBarFilters];
164 if (!this._categories) {
166 loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"),
167 scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"),
168 rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)")
171 return this._categories;
174 get defaultFocusedElement()
181 if (!this._recordStylesArray) {
182 var recordTypes = WebInspector.TimelineAgent.RecordType;
183 var recordStyles = {};
184 recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
185 recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
186 recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
187 recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
188 recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
189 recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
190 recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
191 recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
192 recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
193 recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
194 recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
195 recordStyles[recordTypes.TimeStamp] = { title: WebInspector.UIString("Stamp"), category: this.categories.scripting };
196 recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
197 recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
198 recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
199 recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting };
200 recordStyles[recordTypes.ResourceReceivedData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading };
201 recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting };
202 recordStyles[recordTypes.MarkDOMContent] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting };
203 recordStyles[recordTypes.MarkLoad] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting };
204 recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: this.categories.loading };
205 recordStyles[recordTypes.RegisterAnimationFrameCallback] = { title: WebInspector.UIString("Register Animation Callback"), category: this.categories.scripting };
206 recordStyles[recordTypes.CancelAnimationFrameCallback] = { title: WebInspector.UIString("Cancel Animation Callback"), category: this.categories.scripting };
207 recordStyles[recordTypes.FireAnimationFrameEvent] = { title: WebInspector.UIString("Animation Frame Event"), category: this.categories.scripting };
208 this._recordStylesArray = recordStyles;
210 return this._recordStylesArray;
213 _createStatusbarButtons: function()
215 this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item");
216 this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);
218 this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
219 this.clearButton.addEventListener("click", this._clearPanel.bind(this), false);
221 this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item");
222 this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked.bind(this), false);
224 this.garbageCollectButton = new WebInspector.StatusBarButton(WebInspector.UIString("Collect Garbage"), "garbage-collect-status-bar-item");
225 this.garbageCollectButton.addEventListener("click", this._garbageCollectButtonClicked.bind(this), false);
227 this.recordsCounter = document.createElement("span");
228 this.recordsCounter.className = "timeline-records-counter";
231 _registerShortcuts: function()
233 var shortcut = WebInspector.KeyboardShortcut;
234 var modifiers = shortcut.Modifiers;
235 var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("Timeline Panel"));
237 this._shortcuts[shortcut.makeKey("e", modifiers.CtrlOrMeta)] = this._toggleTimelineButtonClicked.bind(this);
238 section.addKey(shortcut.shortcutToString("e", modifiers.CtrlOrMeta), WebInspector.UIString("Start/stop recording"));
240 var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold);
241 this._shortcuts[shortcut.makeKey("f", modifiers.Shift | modifiers.CtrlOrMeta)] = this._toggleFilterButtonClicked.bind(this);
242 section.addKey(shortcut.shortcutToString("f", modifiers.Shift | modifiers.CtrlOrMeta), WebInspector.UIString("Toggle filter for the records that are shorter than %s", shortRecordThresholdTitle));
244 this._shortcuts[shortcut.makeKey("s", modifiers.CtrlOrMeta)] = this._saveToFile.bind(this);
245 section.addKey(shortcut.shortcutToString("s", modifiers.CtrlOrMeta), WebInspector.UIString("Save Timeline data\u2026"));
247 this._shortcuts[shortcut.makeKey("o", modifiers.CtrlOrMeta)] = this._fileSelectorElement.click.bind(this._fileSelectorElement);
248 section.addKey(shortcut.shortcutToString("o", modifiers.CtrlOrMeta), WebInspector.UIString("Load Timeline data\u2026"));
251 _createFileSelector: function()
253 if (this._fileSelectorElement)
254 this.element.removeChild(this._fileSelectorElement);
256 var fileSelectorElement = document.createElement("input");
257 fileSelectorElement.type = "file";
258 fileSelectorElement.style.opacity = 0;
259 fileSelectorElement.onchange = this._loadFromFile.bind(this);
260 this.element.appendChild(fileSelectorElement);
261 this._fileSelectorElement = fileSelectorElement;
264 _contextMenu: function(event)
266 var contextMenu = new WebInspector.ContextMenu();
267 contextMenu.appendItem(WebInspector.UIString("&Save Timeline data\u2026"), this._saveToFile.bind(this));
268 contextMenu.appendItem(WebInspector.UIString("L&oad Timeline data\u2026"), this._fileSelectorElement.click.bind(this._fileSelectorElement));
269 contextMenu.show(event);
272 _saveToFile: function()
274 this._model._saveToFile();
277 _loadFromFile: function()
279 if (this.toggleTimelineButton.toggled)
280 WebInspector.timelineManager.stop();
284 this._model._loadFromFile(this._fileSelectorElement.files[0]);
285 this._createFileSelector();
288 _updateRecordsCounter: function()
290 this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount);
293 _updateEventDividers: function()
295 this._timelineGrid.removeEventDividers();
296 var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset;
298 for (var i = 0; i < this._timeStampRecords.length; ++i) {
299 var record = this._timeStampRecords[i];
300 var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth);
301 var dividerPosition = Math.round(positions.left);
302 if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
304 var divider = this._createEventDivider(record);
305 divider.style.left = (dividerPosition + this._expandOffset) + "px";
306 dividers[dividerPosition] = divider;
308 this._timelineGrid.addEventDividers(dividers);
309 this._overviewPane.updateEventDividers(this._timeStampRecords, this._createEventDivider.bind(this));
312 _createEventDivider: function(record)
314 var eventDivider = document.createElement("div");
315 eventDivider.className = "resources-event-divider";
316 var recordTypes = WebInspector.TimelineAgent.RecordType;
318 var eventDividerPadding = document.createElement("div");
319 eventDividerPadding.className = "resources-event-divider-padding";
320 eventDividerPadding.title = record.title;
322 if (record.type === recordTypes.MarkDOMContent)
323 eventDivider.className += " resources-blue-divider";
324 else if (record.type === recordTypes.MarkLoad)
325 eventDivider.className += " resources-red-divider";
326 else if (record.type === recordTypes.TimeStamp) {
327 eventDivider.className += " resources-orange-divider";
328 eventDividerPadding.title = record.data.message;
330 eventDividerPadding.appendChild(eventDivider);
331 return eventDividerPadding;
334 _timelinesOverviewItemSelected: function(event)
336 this._overviewPane.showTimelines();
339 _memoryOverviewItemSelected: function(event)
341 this._overviewPane.showMemoryGraph(this._rootRecord.children);
344 _toggleTimelineButtonClicked: function()
346 if (this.toggleTimelineButton.toggled)
347 WebInspector.timelineManager.stop();
350 WebInspector.timelineManager.start();
351 WebInspector.userMetrics.TimelineStarted.record();
353 this.toggleTimelineButton.toggled = !this.toggleTimelineButton.toggled;
356 _toggleFilterButtonClicked: function()
358 this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled;
359 this._calculator._showShortEvents = this.toggleFilterButton.toggled;
360 this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText;
361 this._scheduleRefresh(true);
364 _garbageCollectButtonClicked: function()
366 ProfilerAgent.collectGarbage();
369 _onTimelineEventRecorded: function(event)
371 if (this.toggleTimelineButton.toggled)
372 this._addRecordToTimeline(event.data);
375 _addRecordToTimeline: function(record)
377 if (record.type === WebInspector.TimelineAgent.RecordType.ResourceSendRequest) {
378 var isMainResource = (record.data.requestId === WebInspector.mainResource.requestId);
379 if (isMainResource && this._mainRequestId !== record.data.requestId) {
380 // We are loading new main resource -> clear the panel. Check above is necessary since
381 // there may be several resource loads with main resource marker upon redirects, redirects are reported with
382 // the original request id.
383 this._mainRequestId = record.data.requestId;
387 this._model._addRecord(record);
388 this._innerAddRecordToTimeline(record, this._rootRecord);
389 this._scheduleRefresh();
392 _findParentRecord: function(record)
394 var recordTypes = WebInspector.TimelineAgent.RecordType;
396 if (record.type === recordTypes.ResourceReceiveResponse ||
397 record.type === recordTypes.ResourceFinish ||
398 record.type === recordTypes.ResourceReceivedData)
399 parentRecord = this._sendRequestRecords[record.data.requestId];
400 else if (record.type === recordTypes.TimerFire)
401 parentRecord = this._timerRecords[record.data.timerId];
402 else if (record.type === recordTypes.ResourceSendRequest)
403 parentRecord = this._scheduledResourceRequests[record.data.url];
407 _innerAddRecordToTimeline: function(record, parentRecord)
409 var connectedToOldRecord = false;
410 var recordTypes = WebInspector.TimelineAgent.RecordType;
412 if (record.type === recordTypes.RegisterAnimationFrameCallback) {
413 this._registeredAnimationCallbackRecords[record.data.id] = record;
417 if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad)
418 parentRecord = null; // No bar entry for load events.
419 else if (parentRecord === this._rootRecord ||
420 record.type === recordTypes.ResourceReceiveResponse ||
421 record.type === recordTypes.ResourceFinish ||
422 record.type === recordTypes.ResourceReceivedData) {
423 var newParentRecord = this._findParentRecord(record);
424 if (newParentRecord) {
425 parentRecord = newParentRecord;
426 connectedToOldRecord = true;
430 var children = record.children;
432 if (record.data && record.data.scriptName) {
434 scriptName: record.data.scriptName,
435 scriptLine: record.data.scriptLine
439 if ((record.type === recordTypes.TimerFire || record.type === recordTypes.FireAnimationFrameEvent) && children && children.length) {
440 var childRecord = children[0];
441 if (childRecord.type === recordTypes.FunctionCall) {
443 scriptName: childRecord.data.scriptName,
444 scriptLine: childRecord.data.scriptLine
446 children = childRecord.children.concat(children.slice(1));
450 var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this, scriptDetails);
452 if (record.type === recordTypes.MarkDOMContent || record.type === recordTypes.MarkLoad) {
453 this._timeStampRecords.push(formattedRecord);
457 ++this._rootRecord._allRecordsCount;
458 formattedRecord.collapsed = (parentRecord === this._rootRecord);
460 var childrenCount = children ? children.length : 0;
461 for (var i = 0; i < childrenCount; ++i)
462 this._innerAddRecordToTimeline(children[i], formattedRecord);
464 formattedRecord._calculateAggregatedStats(this.categories);
466 if (connectedToOldRecord) {
467 var record = formattedRecord;
469 var parent = record.parent;
470 parent._cpuTime += formattedRecord._cpuTime;
471 if (parent._lastChildEndTime < record._lastChildEndTime)
472 parent._lastChildEndTime = record._lastChildEndTime;
473 for (var category in formattedRecord._aggregatedStats)
474 parent._aggregatedStats[category] += formattedRecord._aggregatedStats[category];
476 } while (record.parent);
478 if (parentRecord !== this._rootRecord)
479 parentRecord._selfTime -= formattedRecord.endTime - formattedRecord.startTime;
481 // Keep bar entry for mark timeline since nesting might be interesting to the user.
482 if (record.type === recordTypes.TimeStamp)
483 this._timeStampRecords.push(formattedRecord);
486 setSidebarWidth: function(width)
488 WebInspector.Panel.prototype.setSidebarWidth.call(this, width);
489 this._sidebarBackgroundElement.style.width = width + "px";
490 this._topPaneSidebarElement.style.width = width + "px";
493 updateMainViewWidth: function(width)
495 this._containerContentElement.style.left = width + "px";
496 this._scheduleRefresh();
497 this._overviewPane.updateMainViewWidth(width);
502 this._closeRecordDetails();
503 this._scheduleRefresh();
506 _createRootRecord: function()
509 rootRecord.children = [];
510 rootRecord._visibleRecordsCount = 0;
511 rootRecord._allRecordsCount = 0;
512 rootRecord._aggregatedStats = {};
516 _clearPanel: function()
518 this._timeStampRecords = [];
519 this._sendRequestRecords = {};
520 this._scheduledResourceRequests = {};
521 this._timerRecords = {};
522 this._registeredAnimationCallbackRecords = {};
523 this._rootRecord = this._createRootRecord();
524 this._boundariesAreValid = false;
525 this._overviewPane.reset();
526 this._adjustScrollPosition(0);
528 this._closeRecordDetails();
529 this._model._reset();
532 elementsToRestoreScrollPositionsFor: function()
534 return [this._containerElement];
539 WebInspector.Panel.prototype.show.call(this);
541 WebInspector.drawer.currentPanelCounters = this.recordsCounter;
546 WebInspector.Panel.prototype.hide.call(this);
547 this._closeRecordDetails();
548 WebInspector.drawer.currentPanelCounters = null;
551 _onScroll: function(event)
553 this._closeRecordDetails();
554 var scrollTop = this._containerElement.scrollTop;
555 var dividersTop = Math.max(0, scrollTop);
556 this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
557 this._scheduleRefresh(true);
560 _windowChanged: function()
562 this._closeRecordDetails();
563 this._scheduleRefresh();
566 _scheduleRefresh: function(preserveBoundaries)
568 this._closeRecordDetails();
569 this._boundariesAreValid &= preserveBoundaries;
574 if (preserveBoundaries)
577 if (!this._refreshTimeout)
578 this._refreshTimeout = setTimeout(this._refresh.bind(this), 100);
583 if (this._refreshTimeout) {
584 clearTimeout(this._refreshTimeout);
585 delete this._refreshTimeout;
588 this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents);
589 this._refreshRecords(!this._boundariesAreValid);
590 this._updateRecordsCounter();
591 if(!this._boundariesAreValid)
592 this._updateEventDividers();
593 this._boundariesAreValid = true;
596 _updateBoundaries: function()
598 this._calculator.reset();
599 this._calculator.windowLeft = this._overviewPane.windowLeft;
600 this._calculator.windowRight = this._overviewPane.windowRight;
602 for (var i = 0; i < this._rootRecord.children.length; ++i)
603 this._calculator.updateBoundaries(this._rootRecord.children[i]);
605 this._calculator.calculateWindow();
608 _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed)
610 if (!this._calculator._showShortEvents && !record.isLong())
612 var percentages = this._calculator.computeBarGraphPercentages(record);
613 if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) {
614 ++this._rootRecord._visibleRecordsCount;
615 ++record.parent._invisibleChildrenCount;
616 if (!parentIsCollapsed)
617 recordsWindow.push(record);
620 var index = recordsWindow.length;
621 record._invisibleChildrenCount = 0;
622 for (var i = 0; i < record.children.length; ++i)
623 this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed);
624 record._visibleChildrenCount = recordsWindow.length - index;
627 _filterRecords: function()
629 var recordsInWindow = [];
630 this._rootRecord._visibleRecordsCount = 0;
631 for (var i = 0; i < this._rootRecord.children.length; ++i)
632 this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow);
633 return recordsInWindow;
636 _refreshRecords: function(updateBoundaries)
638 if (updateBoundaries)
639 this._updateBoundaries();
641 var recordsInWindow = this._filterRecords();
643 // Calculate the visible area.
644 this._scrollTop = this._containerElement.scrollTop;
645 var visibleTop = this._scrollTop;
646 var visibleBottom = visibleTop + this._containerElement.clientHeight;
648 const rowHeight = WebInspector.TimelinePanel.rowHeight;
650 // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
651 var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1));
652 var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
654 // Resize gaps first.
655 const top = (startIndex * rowHeight) + "px";
656 this._topGapElement.style.height = top;
657 this.sidebarElement.style.top = top;
658 this.sidebarResizeElement.style.top = top;
659 this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
661 // Update visible rows.
662 var listRowElement = this._sidebarListElement.firstChild;
663 var width = this._graphRowsElement.offsetWidth;
664 this._itemsGraphsElement.removeChild(this._graphRowsElement);
665 var graphRowElement = this._graphRowsElement.firstChild;
666 var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
667 this._itemsGraphsElement.removeChild(this._expandElements);
668 this._expandElements.removeChildren();
670 for (var i = 0; i < endIndex; ++i) {
671 var record = recordsInWindow[i];
672 var isEven = !(i % 2);
674 if (i < startIndex) {
675 var lastChildIndex = i + record._visibleChildrenCount;
676 if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
677 var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
678 expandElement._update(record, i, this._calculator.computeBarGraphWindowPosition(record, width - this._expandOffset));
681 if (!listRowElement) {
682 listRowElement = new WebInspector.TimelineRecordListRow().element;
683 this._sidebarListElement.appendChild(listRowElement);
685 if (!graphRowElement) {
686 graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback, rowHeight).element;
687 this._graphRowsElement.appendChild(graphRowElement);
690 listRowElement.row.update(record, isEven, this._calculator, visibleTop);
691 graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i);
693 listRowElement = listRowElement.nextSibling;
694 graphRowElement = graphRowElement.nextSibling;
698 // Remove extra rows.
699 while (listRowElement) {
700 var nextElement = listRowElement.nextSibling;
701 listRowElement.row.dispose();
702 listRowElement = nextElement;
704 while (graphRowElement) {
705 var nextElement = graphRowElement.nextSibling;
706 graphRowElement.row.dispose();
707 graphRowElement = nextElement;
710 this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
711 this._itemsGraphsElement.appendChild(this._expandElements);
712 this.sidebarResizeElement.style.height = this.sidebarElement.clientHeight + "px";
713 // Reserve some room for expand / collapse controls to the left for records that start at 0ms.
714 var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0;
715 if (updateBoundaries)
716 this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft);
717 this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight);
720 _adjustScrollPosition: function(totalHeight)
722 // Prevent the container from being scrolled off the end.
723 if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1)
724 this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
727 _getPopoverAnchor: function(element)
729 return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item");
732 _mouseOut: function(e)
734 this._hideRectHighlight();
737 _mouseMove: function(e)
739 var anchor = this._getPopoverAnchor(e.target);
741 if (anchor && anchor.row._record.type === "Paint")
742 this._highlightRect(anchor.row._record);
744 this._hideRectHighlight();
747 _highlightRect: function(record)
749 if (this._highlightedRect === record.data)
751 this._highlightedRect = record.data;
752 DOMAgent.highlightRect(this._highlightedRect.x, this._highlightedRect.y, this._highlightedRect.width, this._highlightedRect.height, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
755 _hideRectHighlight: function()
757 if (this._highlightedRect) {
758 delete this._highlightedRect;
759 DOMAgent.hideHighlight();
763 _showPopover: function(anchor, popover)
765 var record = anchor.row._record;
766 popover.show(record._generatePopupContent(this._calculator, this.categories), anchor);
769 _closeRecordDetails: function()
771 this._popoverHelper.hidePopover();
775 WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
777 WebInspector.TimelineCategory = function(name, title, color)
784 WebInspector.TimelineCalculator = function()
787 this.windowLeft = 0.0;
788 this.windowRight = 1.0;
791 WebInspector.TimelineCalculator.prototype = {
792 computeBarGraphPercentages: function(record)
794 var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
795 var end = (record.startTime + record._selfTime - this.minimumBoundary) / this.boundarySpan * 100;
796 var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100;
797 var cpuWidth = record._cpuTime / this.boundarySpan * 100;
798 return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
801 computeBarGraphWindowPosition: function(record, clientWidth)
804 const borderWidth = 4;
805 var workingArea = clientWidth - minWidth - borderWidth;
806 var percentages = this.computeBarGraphPercentages(record);
807 var left = percentages.start / 100 * workingArea;
808 var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth;
809 var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * workingArea;
810 var cpuWidth = percentages.cpuWidth / 100 * workingArea + minWidth;
811 if (percentages.endWithChildren > percentages.end)
812 widthWithChildren += borderWidth + minWidth;
813 return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
816 calculateWindow: function()
818 this.minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
819 this.maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
820 this.boundarySpan = this.maximumBoundary - this.minimumBoundary;
825 this._absoluteMinimumBoundary = -1;
826 this._absoluteMaximumBoundary = -1;
829 updateBoundaries: function(record)
831 var lowerBound = record.startTime;
832 if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary)
833 this._absoluteMinimumBoundary = lowerBound;
835 const minimumTimeFrame = 0.1;
836 const minimumDeltaForZeroSizeEvents = 0.01;
837 var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame);
838 if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary)
839 this._absoluteMaximumBoundary = upperBound;
842 formatValue: function(value)
844 return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary);
849 WebInspector.TimelineRecordListRow = function()
851 this.element = document.createElement("div");
852 this.element.row = this;
853 this.element.style.cursor = "pointer";
854 var iconElement = document.createElement("span");
855 iconElement.className = "timeline-tree-icon";
856 this.element.appendChild(iconElement);
858 this._typeElement = document.createElement("span");
859 this._typeElement.className = "type";
860 this.element.appendChild(this._typeElement);
862 var separatorElement = document.createElement("span");
863 separatorElement.className = "separator";
864 separatorElement.textContent = " ";
866 this._dataElement = document.createElement("span");
867 this._dataElement.className = "data dimmed";
869 this.element.appendChild(separatorElement);
870 this.element.appendChild(this._dataElement);
873 WebInspector.TimelineRecordListRow.prototype = {
874 update: function(record, isEven, calculator, offset)
876 this._record = record;
877 this._calculator = calculator;
878 this._offset = offset;
880 this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
881 this._typeElement.textContent = record.title;
883 if (this._dataElement.firstChild)
884 this._dataElement.removeChildren();
885 if (record.details) {
886 var detailsContainer = document.createElement("span");
887 if (typeof record.details === "object") {
888 detailsContainer.appendChild(document.createTextNode("("));
889 detailsContainer.appendChild(record.details);
890 detailsContainer.appendChild(document.createTextNode(")"));
892 detailsContainer.textContent = "(" + record.details + ")";
893 this._dataElement.appendChild(detailsContainer);
899 this.element.parentElement.removeChild(this.element);
903 WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh)
905 this.element = document.createElement("div");
906 this.element.row = this;
908 this._barAreaElement = document.createElement("div");
909 this._barAreaElement.className = "timeline-graph-bar-area";
910 this.element.appendChild(this._barAreaElement);
912 this._barWithChildrenElement = document.createElement("div");
913 this._barWithChildrenElement.className = "timeline-graph-bar with-children";
914 this._barWithChildrenElement.row = this;
915 this._barAreaElement.appendChild(this._barWithChildrenElement);
917 this._barCpuElement = document.createElement("div");
918 this._barCpuElement.className = "timeline-graph-bar cpu"
919 this._barCpuElement.row = this;
920 this._barAreaElement.appendChild(this._barCpuElement);
922 this._barElement = document.createElement("div");
923 this._barElement.className = "timeline-graph-bar";
924 this._barElement.row = this;
925 this._barAreaElement.appendChild(this._barElement);
927 this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
928 this._expandElement._element.addEventListener("click", this._onClick.bind(this));
930 this._scheduleRefresh = scheduleRefresh;
933 WebInspector.TimelineRecordGraphRow.prototype = {
934 update: function(record, isEven, calculator, clientWidth, expandOffset, index)
936 this._record = record;
937 this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
938 var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset);
939 this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px";
940 this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
941 this._barElement.style.left = barPosition.left + expandOffset + "px";
942 this._barElement.style.width = barPosition.width + "px";
943 this._barCpuElement.style.left = barPosition.left + expandOffset + "px";
944 this._barCpuElement.style.width = barPosition.cpuWidth + "px";
945 this._expandElement._update(record, index, barPosition);
948 _onClick: function(event)
950 this._record.collapsed = !this._record.collapsed;
951 this._scheduleRefresh();
956 this.element.parentElement.removeChild(this.element);
957 this._expandElement._dispose();
961 WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, panel, scriptDetails)
963 var recordTypes = WebInspector.TimelineAgent.RecordType;
964 var style = panel._recordStyles[record.type];
965 this.parent = parentRecord;
967 parentRecord.children.push(this);
968 this.category = style.category;
969 this.title = style.title;
970 this.startTime = record.startTime / 1000;
971 this.data = record.data;
972 this.type = record.type;
973 this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime;
974 this._selfTime = this.endTime - this.startTime;
975 this._lastChildEndTime = this.endTime;
976 if (record.stackTrace && record.stackTrace.length)
977 this.stackTrace = record.stackTrace;
978 this.totalHeapSize = record.totalHeapSize;
979 this.usedHeapSize = record.usedHeapSize;
980 if (record.data && record.data.url)
981 this.url = record.data.url;
983 this.scriptName = scriptDetails.scriptName;
984 this.scriptLine = scriptDetails.scriptLine;
986 // Make resource receive record last since request was sent; make finish record last since response received.
987 if (record.type === recordTypes.ResourceSendRequest) {
988 panel._sendRequestRecords[record.data.requestId] = this;
989 } else if (record.type === recordTypes.ScheduleResourceRequest) {
990 panel._scheduledResourceRequests[record.data.url] = this;
991 } else if (record.type === recordTypes.ResourceReceiveResponse) {
992 var sendRequestRecord = panel._sendRequestRecords[record.data.requestId];
993 if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
994 this.url = sendRequestRecord.url;
995 // Now that we have resource in the collection, recalculate details in order to display short url.
996 sendRequestRecord._refreshDetails();
997 if (sendRequestRecord.parent !== panel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
998 sendRequestRecord.parent._refreshDetails();
1000 } else if (record.type === recordTypes.ResourceReceivedData || record.type === recordTypes.ResourceFinish) {
1001 var sendRequestRecord = panel._sendRequestRecords[record.data.requestId];
1002 if (sendRequestRecord) // False for main resource.
1003 this.url = sendRequestRecord.url;
1004 } else if (record.type === recordTypes.TimerInstall) {
1005 this.timeout = record.data.timeout;
1006 this.singleShot = record.data.singleShot;
1007 panel._timerRecords[record.data.timerId] = this;
1008 } else if (record.type === recordTypes.TimerFire) {
1009 var timerInstalledRecord = panel._timerRecords[record.data.timerId];
1010 if (timerInstalledRecord) {
1011 this.callSiteStackTrace = timerInstalledRecord.stackTrace;
1012 this.timeout = timerInstalledRecord.timeout;
1013 this.singleShot = timerInstalledRecord.singleShot;
1015 } else if (record.type === recordTypes.FireAnimationFrameEvent) {
1016 var registerCallbackRecord = panel._registeredAnimationCallbackRecords[record.data.id];
1017 if (registerCallbackRecord)
1018 this.callSiteStackTrace = registerCallbackRecord.stackTrace;
1020 this._refreshDetails();
1023 WebInspector.TimelinePanel.FormattedRecord.prototype = {
1026 return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold;
1031 if (!this._children)
1032 this._children = [];
1033 return this._children;
1036 _generateAggregatedInfo: function()
1038 var cell = document.createElement("span");
1039 cell.className = "timeline-aggregated-info";
1040 for (var index in this._aggregatedStats) {
1041 var label = document.createElement("div");
1042 label.className = "timeline-aggregated-category timeline-" + index;
1043 cell.appendChild(label);
1044 var text = document.createElement("span");
1045 text.textContent = Number.secondsToString(this._aggregatedStats[index] + 0.0001);
1046 cell.appendChild(text);
1051 _generatePopupContent: function(calculator, categories)
1053 var contentHelper = new WebInspector.TimelinePanel.PopupContentHelper(this.title);
1055 if (this._children && this._children.length) {
1056 contentHelper._appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime + 0.0001));
1057 contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"), this._generateAggregatedInfo());
1059 var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime),
1060 calculator.formatValue(this.startTime - calculator.minimumBoundary));
1061 contentHelper._appendTextRow(WebInspector.UIString("Duration"), text);
1063 const recordTypes = WebInspector.TimelineAgent.RecordType;
1065 switch (this.type) {
1066 case recordTypes.GCEvent:
1067 contentHelper._appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data.usedHeapSizeDelta));
1069 case recordTypes.TimerInstall:
1070 case recordTypes.TimerFire:
1071 case recordTypes.TimerRemove:
1072 contentHelper._appendTextRow(WebInspector.UIString("Timer ID"), this.data.timerId);
1073 if (typeof this.timeout === "number") {
1074 contentHelper._appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000));
1075 contentHelper._appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
1078 case recordTypes.FireAnimationFrameEvent:
1079 contentHelper._appendTextRow(WebInspector.UIString("Callback ID"), this.data.id);
1081 case recordTypes.FunctionCall:
1082 contentHelper._appendLinkRow(WebInspector.UIString("Location"), this.scriptName, this.scriptLine);
1084 case recordTypes.ScheduleResourceRequest:
1085 case recordTypes.ResourceSendRequest:
1086 case recordTypes.ResourceReceiveResponse:
1087 case recordTypes.ResourceReceivedData:
1088 case recordTypes.ResourceFinish:
1089 contentHelper._appendLinkRow(WebInspector.UIString("Resource"), this.url);
1090 if (this.data.requestMethod)
1091 contentHelper._appendTextRow(WebInspector.UIString("Request Method"), this.data.requestMethod);
1092 if (typeof this.data.statusCode === "number")
1093 contentHelper._appendTextRow(WebInspector.UIString("Status Code"), this.data.statusCode);
1094 if (this.data.mimeType)
1095 contentHelper._appendTextRow(WebInspector.UIString("MIME Type"), this.data.mimeType);
1097 case recordTypes.EvaluateScript:
1098 if (this.data && this.url)
1099 contentHelper._appendLinkRow(WebInspector.UIString("Script"), this.url, this.data.lineNumber);
1101 case recordTypes.Paint:
1102 contentHelper._appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data.x, this.data.y));
1103 contentHelper._appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", this.data.width, this.data.height));
1104 case recordTypes.RecalculateStyles: // We don't want to see default details.
1108 contentHelper._appendTextRow(WebInspector.UIString("Details"), this.details);
1112 if (this.scriptName && this.type !== recordTypes.FunctionCall)
1113 contentHelper._appendLinkRow(WebInspector.UIString("Function Call"), this.scriptName, this.scriptLine);
1115 if (this.usedHeapSize)
1116 contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"), WebInspector.UIString("%s of %s", Number.bytesToString(this.usedHeapSize), Number.bytesToString(this.totalHeapSize)));
1118 if (this.callSiteStackTrace && this.callSiteStackTrace.length)
1119 contentHelper._appendStackTrace(WebInspector.UIString("Call Site stack"), this.callSiteStackTrace);
1121 if (this.stackTrace)
1122 contentHelper._appendStackTrace(WebInspector.UIString("Call Stack"), this.stackTrace);
1124 return contentHelper._contentTable;
1127 _refreshDetails: function()
1129 this.details = this._getRecordDetails();
1132 _getRecordDetails: function()
1134 switch (this.type) {
1135 case WebInspector.TimelineAgent.RecordType.GCEvent:
1136 return WebInspector.UIString("%s collected", Number.bytesToString(this.data.usedHeapSizeDelta));
1137 case WebInspector.TimelineAgent.RecordType.TimerFire:
1138 return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : this.data.timerId;
1139 case WebInspector.TimelineAgent.RecordType.FunctionCall:
1140 return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : null;
1141 case WebInspector.TimelineAgent.RecordType.FireAnimationFrameEvent:
1142 return this.scriptName ? this._linkifyLocation(this.scriptName, this.scriptLine, 0) : this.data.id;
1143 case WebInspector.TimelineAgent.RecordType.EventDispatch:
1144 return this.data ? this.data.type : null;
1145 case WebInspector.TimelineAgent.RecordType.Paint:
1146 return this.data.width + "\u2009\u00d7\u2009" + this.data.height;
1147 case WebInspector.TimelineAgent.RecordType.TimerInstall:
1148 case WebInspector.TimelineAgent.RecordType.TimerRemove:
1149 return this.stackTrace ? this._linkifyCallFrame(this.stackTrace[0]) : this.data.timerId;
1150 case WebInspector.TimelineAgent.RecordType.RegisterAnimationFrameCallback:
1151 case WebInspector.TimelineAgent.RecordType.CancelAnimationFrameCallback:
1152 return this.stackTrace ? this._linkifyCallFrame(this.stackTrace[0]) : this.data.id;
1153 case WebInspector.TimelineAgent.RecordType.ParseHTML:
1154 case WebInspector.TimelineAgent.RecordType.RecalculateStyles:
1155 return this.stackTrace ? this._linkifyCallFrame(this.stackTrace[0]) : null;
1156 case WebInspector.TimelineAgent.RecordType.EvaluateScript:
1157 return this.url ? this._linkifyLocation(this.url, this.data.lineNumber, 0) : null;
1158 case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
1159 case WebInspector.TimelineAgent.RecordType.XHRLoad:
1160 case WebInspector.TimelineAgent.RecordType.ScheduleResourceRequest:
1161 case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
1162 case WebInspector.TimelineAgent.RecordType.ResourceReceivedData:
1163 case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
1164 case WebInspector.TimelineAgent.RecordType.ResourceFinish:
1165 return WebInspector.displayNameForURL(this.url);
1166 case WebInspector.TimelineAgent.RecordType.TimeStamp:
1167 return this.data.message;
1173 _linkifyLocation: function(url, lineNumber, columnNumber)
1175 // FIXME(62725): stack trace line/column numbers are one-based.
1176 lineNumber = lineNumber ? lineNumber - 1 : lineNumber;
1177 columnNumber = columnNumber ? columnNumber - 1 : 0;
1178 return WebInspector.debuggerPresentationModel.linkifyLocation(url, lineNumber, columnNumber, "timeline-details");
1181 _linkifyCallFrame: function(callFrame)
1183 return this._linkifyLocation(callFrame.url, callFrame.lineNumber, callFrame.columnNumber);
1186 _calculateAggregatedStats: function(categories)
1188 this._aggregatedStats = {};
1189 for (var category in categories)
1190 this._aggregatedStats[category] = 0;
1191 this._cpuTime = this._selfTime;
1193 if (this._children) {
1194 for (var index = this._children.length; index; --index) {
1195 var child = this._children[index - 1];
1196 this._aggregatedStats[child.category.name] += child._selfTime;
1197 for (var category in categories)
1198 this._aggregatedStats[category] += child._aggregatedStats[category];
1200 for (var category in this._aggregatedStats)
1201 this._cpuTime += this._aggregatedStats[category];
1206 WebInspector.TimelinePanel.PopupContentHelper = function(title)
1208 this._contentTable = document.createElement("table");;
1209 var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
1210 titleCell.colSpan = 2;
1211 var titleRow = document.createElement("tr");
1212 titleRow.appendChild(titleCell);
1213 this._contentTable.appendChild(titleRow);
1216 WebInspector.TimelinePanel.PopupContentHelper.prototype = {
1217 _createCell: function(content, styleName)
1219 var text = document.createElement("label");
1220 text.appendChild(document.createTextNode(content));
1221 var cell = document.createElement("td");
1222 cell.className = "timeline-details";
1224 cell.className += " " + styleName;
1225 cell.textContent = content;
1229 _appendTextRow: function(title, content)
1231 var row = document.createElement("tr");
1232 row.appendChild(this._createCell(title, "timeline-details-row-title"));
1233 row.appendChild(this._createCell(content, "timeline-details-row-data"));
1234 this._contentTable.appendChild(row);
1237 _appendElementRow: function(title, content, titleStyle)
1239 var row = document.createElement("tr");
1240 var titleCell = this._createCell(title, "timeline-details-row-title");
1242 titleCell.addStyleClass(titleStyle);
1243 row.appendChild(titleCell);
1244 var cell = document.createElement("td");
1245 cell.className = "timeline-details";
1246 cell.appendChild(content);
1247 row.appendChild(cell);
1248 this._contentTable.appendChild(row);
1251 _appendLinkRow: function(title, scriptName, scriptLine)
1253 var link = WebInspector.TimelinePanel.FormattedRecord.prototype._linkifyLocation(scriptName, scriptLine, 0, "timeline-details");
1254 this._appendElementRow(title, link);
1257 _appendStackTrace: function(title, stackTrace)
1259 this._appendTextRow("", "");
1260 var framesTable = document.createElement("table");
1261 for (var i = 0; i < stackTrace.length; ++i) {
1262 var stackFrame = stackTrace[i];
1263 var row = document.createElement("tr");
1264 row.className = "timeline-details";
1265 row.appendChild(this._createCell(stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"), "timeline-function-name"));
1266 row.appendChild(this._createCell(" @ "));
1267 var linkCell = document.createElement("td");
1268 var urlElement = WebInspector.TimelinePanel.FormattedRecord.prototype._linkifyCallFrame(stackFrame);
1269 linkCell.appendChild(urlElement);
1270 row.appendChild(linkCell);
1271 framesTable.appendChild(row);
1273 this._appendElementRow(title, framesTable, "timeline-stacktrace-title");
1277 WebInspector.TimelineExpandableElement = function(container)
1279 this._element = document.createElement("div");
1280 this._element.className = "timeline-expandable";
1282 var leftBorder = document.createElement("div");
1283 leftBorder.className = "timeline-expandable-left";
1284 this._element.appendChild(leftBorder);
1286 container.appendChild(this._element);
1289 WebInspector.TimelineExpandableElement.prototype = {
1290 _update: function(record, index, barPosition)
1292 const rowHeight = WebInspector.TimelinePanel.rowHeight;
1293 if (record._visibleChildrenCount || record._invisibleChildrenCount) {
1294 this._element.style.top = index * rowHeight + "px";
1295 this._element.style.left = barPosition.left + "px";
1296 this._element.style.width = Math.max(12, barPosition.width + 25) + "px";
1297 if (!record.collapsed) {
1298 this._element.style.height = (record._visibleChildrenCount + 1) * rowHeight + "px";
1299 this._element.addStyleClass("timeline-expandable-expanded");
1300 this._element.removeStyleClass("timeline-expandable-collapsed");
1302 this._element.style.height = rowHeight + "px";
1303 this._element.addStyleClass("timeline-expandable-collapsed");
1304 this._element.removeStyleClass("timeline-expandable-expanded");
1306 this._element.removeStyleClass("hidden");
1308 this._element.addStyleClass("hidden");
1311 _dispose: function()
1313 this._element.parentElement.removeChild(this._element);
1317 WebInspector.TimelineModel = function(timelinePanel)
1319 this._panel = timelinePanel;
1323 WebInspector.TimelineModel.prototype = {
1324 _addRecord: function(record)
1326 this._records.push(record);
1329 _loadNextChunk: function(data, index)
1331 for (var i = 0; i < 20 && index < data.length; ++i, ++index)
1332 this._panel._addRecordToTimeline(data[index]);
1334 if (index !== data.length)
1335 setTimeout(this._loadNextChunk.bind(this, data, index), 0);
1338 _loadFromFile: function(file)
1342 var data = JSON.parse(e.target.result);
1343 var version = data[0];
1344 this._loadNextChunk(data, 1);
1349 switch(e.target.error.code) {
1350 case e.target.error.NOT_FOUND_ERR:
1351 WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: File "%s" not found.', file.name));
1353 case e.target.error.NOT_READABLE_ERR:
1354 WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: File "%s" is not readable', file.name));
1356 case e.target.error.ABORT_ERR:
1359 WebInspector.log(WebInspector.UIString('Timeline.loadFromFile: An error occurred while reading the file "%s"', file.name));
1363 var reader = new FileReader();
1364 reader.onload = onLoad.bind(this);
1365 reader.onerror = onError;
1366 reader.readAsText(file);
1369 _saveToFile: function()
1371 var records = ['[' + JSON.stringify(window.navigator.appVersion)];
1372 for (var i = 0; i < this._records.length; ++i)
1373 records.push(JSON.stringify(this._records[i]));
1374 records[records.length - 1] = records[records.length - 1] + "]";
1376 var now= new Date();
1377 InspectorFrontendHost.saveAs("TimelineRawData-" + now.toRFC3339() + ".json", records.join(",\n"));