summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/inspector/front-end/TimelinePanel.js
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/inspector/front-end/TimelinePanel.js
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/inspector/front-end/TimelinePanel.js')
-rw-r--r--Source/WebCore/inspector/front-end/TimelinePanel.js1155
1 files changed, 1155 insertions, 0 deletions
diff --git a/Source/WebCore/inspector/front-end/TimelinePanel.js b/Source/WebCore/inspector/front-end/TimelinePanel.js
new file mode 100644
index 0000000..a661b75
--- /dev/null
+++ b/Source/WebCore/inspector/front-end/TimelinePanel.js
@@ -0,0 +1,1155 @@
+/*
+ * Copyright (C) 2009 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WebInspector.TimelinePanel = function()
+{
+ WebInspector.Panel.call(this, "timeline");
+
+ this.element.appendChild(this._createTopPane());
+ this.element.tabIndex = 0;
+
+ this._sidebarBackgroundElement = document.createElement("div");
+ this._sidebarBackgroundElement.className = "sidebar timeline-sidebar-background";
+ this.element.appendChild(this._sidebarBackgroundElement);
+
+ this._containerElement = document.createElement("div");
+ this._containerElement.id = "timeline-container";
+ this._containerElement.addEventListener("scroll", this._onScroll.bind(this), false);
+ this.element.appendChild(this._containerElement);
+
+ this.createSidebar(this._containerElement, this._containerElement);
+ var itemsTreeElement = new WebInspector.SidebarSectionTreeElement(WebInspector.UIString("RECORDS"), {}, true);
+ itemsTreeElement.expanded = true;
+ this.sidebarTree.appendChild(itemsTreeElement);
+
+ this._sidebarListElement = document.createElement("div");
+ this.sidebarElement.appendChild(this._sidebarListElement);
+
+ this._containerContentElement = document.createElement("div");
+ this._containerContentElement.id = "resources-container-content";
+ this._containerElement.appendChild(this._containerContentElement);
+
+ this._timelineGrid = new WebInspector.TimelineGrid();
+ this._itemsGraphsElement = this._timelineGrid.itemsGraphsElement;
+ this._itemsGraphsElement.id = "timeline-graphs";
+ this._containerContentElement.appendChild(this._timelineGrid.element);
+
+ this._topGapElement = document.createElement("div");
+ this._topGapElement.className = "timeline-gap";
+ this._itemsGraphsElement.appendChild(this._topGapElement);
+
+ this._graphRowsElement = document.createElement("div");
+ this._itemsGraphsElement.appendChild(this._graphRowsElement);
+
+ this._bottomGapElement = document.createElement("div");
+ this._bottomGapElement.className = "timeline-gap";
+ this._itemsGraphsElement.appendChild(this._bottomGapElement);
+
+ this._expandElements = document.createElement("div");
+ this._expandElements.id = "orphan-expand-elements";
+ this._itemsGraphsElement.appendChild(this._expandElements);
+
+ this._rootRecord = this._createRootRecord();
+ this._sendRequestRecords = {};
+ this._scheduledResourceRequests = {};
+ this._timerRecords = {};
+
+ this._calculator = new WebInspector.TimelineCalculator();
+ this._calculator._showShortEvents = false;
+ var shortRecordThresholdTitle = Number.secondsToString(WebInspector.TimelinePanel.shortRecordThreshold, WebInspector.UIString);
+ this._showShortRecordsTitleText = WebInspector.UIString("Show the records that are shorter than %s", shortRecordThresholdTitle);
+ this._hideShortRecordsTitleText = WebInspector.UIString("Hide the records that are shorter than %s", shortRecordThresholdTitle);
+ this._createStatusbarButtons();
+
+ this._boundariesAreValid = true;
+ this._scrollTop = 0;
+
+ this._popoverHelper = new WebInspector.PopoverHelper(this._containerElement, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), true);
+
+ // Disable short events filter by default.
+ this.toggleFilterButton.toggled = true;
+ this._calculator._showShortEvents = this.toggleFilterButton.toggled;
+ this._markTimelineRecords = [];
+ this._expandOffset = 15;
+
+ InspectorBackend.registerDomainDispatcher("Timeline", this);
+}
+
+// Define row height, should be in sync with styles for timeline graphs.
+WebInspector.TimelinePanel.rowHeight = 18;
+WebInspector.TimelinePanel.shortRecordThreshold = 0.015;
+
+WebInspector.TimelinePanel.prototype = {
+ _createTopPane: function() {
+ var topPaneElement = document.createElement("div");
+ topPaneElement.id = "timeline-overview-panel";
+
+ this._topPaneSidebarElement = document.createElement("div");
+ this._topPaneSidebarElement.id = "timeline-overview-sidebar";
+
+ var overviewTreeElement = document.createElement("ol");
+ overviewTreeElement.className = "sidebar-tree";
+ this._topPaneSidebarElement.appendChild(overviewTreeElement);
+ topPaneElement.appendChild(this._topPaneSidebarElement);
+
+ var topPaneSidebarTree = new TreeOutline(overviewTreeElement);
+ var timelinesOverviewItem = new WebInspector.SidebarTreeElement("resources-time-graph-sidebar-item", WebInspector.UIString("Timelines"));
+ topPaneSidebarTree.appendChild(timelinesOverviewItem);
+ timelinesOverviewItem.onselect = this._timelinesOverviewItemSelected.bind(this);
+ timelinesOverviewItem.select(true);
+
+ var memoryOverviewItem = new WebInspector.SidebarTreeElement("resources-size-graph-sidebar-item", WebInspector.UIString("Memory"));
+ topPaneSidebarTree.appendChild(memoryOverviewItem);
+ memoryOverviewItem.onselect = this._memoryOverviewItemSelected.bind(this);
+
+ this._overviewPane = new WebInspector.TimelineOverviewPane(this.categories);
+ this._overviewPane.addEventListener("window changed", this._windowChanged, this);
+ this._overviewPane.addEventListener("filter changed", this._refresh, this);
+ topPaneElement.appendChild(this._overviewPane.element);
+
+ var separatorElement = document.createElement("div");
+ separatorElement.id = "timeline-overview-separator";
+ topPaneElement.appendChild(separatorElement);
+ return topPaneElement;
+ },
+
+ get toolbarItemLabel()
+ {
+ return WebInspector.UIString("Timeline");
+ },
+
+ get statusBarItems()
+ {
+ return [this.toggleFilterButton.element, this.toggleTimelineButton.element, this.clearButton.element, this._overviewPane.statusBarFilters];
+ },
+
+ get categories()
+ {
+ if (!this._categories) {
+ this._categories = {
+ loading: new WebInspector.TimelineCategory("loading", WebInspector.UIString("Loading"), "rgb(47,102,236)"),
+ scripting: new WebInspector.TimelineCategory("scripting", WebInspector.UIString("Scripting"), "rgb(157,231,119)"),
+ rendering: new WebInspector.TimelineCategory("rendering", WebInspector.UIString("Rendering"), "rgb(164,60,255)")
+ };
+ }
+ return this._categories;
+ },
+
+ get defaultFocusedElement()
+ {
+ return this.element;
+ },
+
+ get _recordStyles()
+ {
+ if (!this._recordStylesArray) {
+ var recordTypes = WebInspector.TimelineAgent.RecordType;
+ var recordStyles = {};
+ recordStyles[recordTypes.EventDispatch] = { title: WebInspector.UIString("Event"), category: this.categories.scripting };
+ recordStyles[recordTypes.Layout] = { title: WebInspector.UIString("Layout"), category: this.categories.rendering };
+ recordStyles[recordTypes.RecalculateStyles] = { title: WebInspector.UIString("Recalculate Style"), category: this.categories.rendering };
+ recordStyles[recordTypes.Paint] = { title: WebInspector.UIString("Paint"), category: this.categories.rendering };
+ recordStyles[recordTypes.ParseHTML] = { title: WebInspector.UIString("Parse"), category: this.categories.loading };
+ recordStyles[recordTypes.TimerInstall] = { title: WebInspector.UIString("Install Timer"), category: this.categories.scripting };
+ recordStyles[recordTypes.TimerRemove] = { title: WebInspector.UIString("Remove Timer"), category: this.categories.scripting };
+ recordStyles[recordTypes.TimerFire] = { title: WebInspector.UIString("Timer Fired"), category: this.categories.scripting };
+ recordStyles[recordTypes.XHRReadyStateChange] = { title: WebInspector.UIString("XHR Ready State Change"), category: this.categories.scripting };
+ recordStyles[recordTypes.XHRLoad] = { title: WebInspector.UIString("XHR Load"), category: this.categories.scripting };
+ recordStyles[recordTypes.EvaluateScript] = { title: WebInspector.UIString("Evaluate Script"), category: this.categories.scripting };
+ recordStyles[recordTypes.MarkTimeline] = { title: WebInspector.UIString("Mark"), category: this.categories.scripting };
+ recordStyles[recordTypes.ResourceSendRequest] = { title: WebInspector.UIString("Send Request"), category: this.categories.loading };
+ recordStyles[recordTypes.ResourceReceiveResponse] = { title: WebInspector.UIString("Receive Response"), category: this.categories.loading };
+ recordStyles[recordTypes.ResourceFinish] = { title: WebInspector.UIString("Finish Loading"), category: this.categories.loading };
+ recordStyles[recordTypes.FunctionCall] = { title: WebInspector.UIString("Function Call"), category: this.categories.scripting };
+ recordStyles[recordTypes.ResourceReceiveData] = { title: WebInspector.UIString("Receive Data"), category: this.categories.loading };
+ recordStyles[recordTypes.GCEvent] = { title: WebInspector.UIString("GC Event"), category: this.categories.scripting };
+ recordStyles[recordTypes.MarkDOMContentEventType] = { title: WebInspector.UIString("DOMContent event"), category: this.categories.scripting };
+ recordStyles[recordTypes.MarkLoadEventType] = { title: WebInspector.UIString("Load event"), category: this.categories.scripting };
+ recordStyles[recordTypes.ScheduleResourceRequest] = { title: WebInspector.UIString("Schedule Request"), category: this.categories.loading };
+ this._recordStylesArray = recordStyles;
+ }
+ return this._recordStylesArray;
+ },
+
+ _createStatusbarButtons: function()
+ {
+ this.toggleTimelineButton = new WebInspector.StatusBarButton(WebInspector.UIString("Record"), "record-profile-status-bar-item");
+ this.toggleTimelineButton.addEventListener("click", this._toggleTimelineButtonClicked.bind(this), false);
+
+ this.clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item");
+ this.clearButton.addEventListener("click", this._clearPanel.bind(this), false);
+
+ this.toggleFilterButton = new WebInspector.StatusBarButton(this._hideShortRecordsTitleText, "timeline-filter-status-bar-item");
+ this.toggleFilterButton.addEventListener("click", this._toggleFilterButtonClicked.bind(this), false);
+
+ this.recordsCounter = document.createElement("span");
+ this.recordsCounter.className = "timeline-records-counter";
+ },
+
+ _updateRecordsCounter: function()
+ {
+ this.recordsCounter.textContent = WebInspector.UIString("%d of %d captured records are visible", this._rootRecord._visibleRecordsCount, this._rootRecord._allRecordsCount);
+ },
+
+ _updateEventDividers: function()
+ {
+ this._timelineGrid.removeEventDividers();
+ var clientWidth = this._graphRowsElement.offsetWidth - this._expandOffset;
+ var dividers = [];
+ for (var i = 0; i < this._markTimelineRecords.length; ++i) {
+ var record = this._markTimelineRecords[i];
+ var positions = this._calculator.computeBarGraphWindowPosition(record, clientWidth);
+ var dividerPosition = Math.round(positions.left);
+ if (dividerPosition < 0 || dividerPosition >= clientWidth || dividers[dividerPosition])
+ continue;
+ var divider = this._createEventDivider(record);
+ divider.style.left = (dividerPosition + this._expandOffset) + "px";
+ dividers[dividerPosition] = divider;
+ }
+ this._timelineGrid.addEventDividers(dividers);
+ this._overviewPane.updateEventDividers(this._markTimelineRecords, this._createEventDivider.bind(this));
+ },
+
+ _createEventDivider: function(record)
+ {
+ var eventDivider = document.createElement("div");
+ eventDivider.className = "resources-event-divider";
+ var recordTypes = WebInspector.TimelineAgent.RecordType;
+
+ var eventDividerPadding = document.createElement("div");
+ eventDividerPadding.className = "resources-event-divider-padding";
+ eventDividerPadding.title = record.title;
+
+ if (record.type === recordTypes.MarkDOMContentEventType)
+ eventDivider.className += " resources-blue-divider";
+ else if (record.type === recordTypes.MarkLoadEventType)
+ eventDivider.className += " resources-red-divider";
+ else if (record.type === recordTypes.MarkTimeline) {
+ eventDivider.className += " resources-orange-divider";
+ eventDividerPadding.title = record.data.message;
+ }
+ eventDividerPadding.appendChild(eventDivider);
+ return eventDividerPadding;
+ },
+
+ _timelinesOverviewItemSelected: function(event) {
+ this._overviewPane.showTimelines();
+ },
+
+ _memoryOverviewItemSelected: function(event) {
+ this._overviewPane.showMemoryGraph(this._rootRecord.children);
+ },
+
+ _toggleTimelineButtonClicked: function()
+ {
+ if (this.toggleTimelineButton.toggled)
+ InspectorBackend.stopTimelineProfiler();
+ else {
+ this._clearPanel();
+ InspectorBackend.startTimelineProfiler();
+ }
+ },
+
+ _toggleFilterButtonClicked: function()
+ {
+ this.toggleFilterButton.toggled = !this.toggleFilterButton.toggled;
+ this._calculator._showShortEvents = this.toggleFilterButton.toggled;
+ this.toggleFilterButton.element.title = this._calculator._showShortEvents ? this._hideShortRecordsTitleText : this._showShortRecordsTitleText;
+ this._scheduleRefresh(true);
+ },
+
+ timelineProfilerWasStarted: function()
+ {
+ this.toggleTimelineButton.toggled = true;
+ },
+
+ timelineProfilerWasStopped: function()
+ {
+ this.toggleTimelineButton.toggled = false;
+ },
+
+ addRecordToTimeline: function(record)
+ {
+ if (record.type == WebInspector.TimelineAgent.RecordType.ResourceSendRequest && record.data.isMainResource) {
+ if (this._mainResourceIdentifier != record.data.identifier) {
+ // We are loading new main resource -> clear the panel. Check above is necessary since
+ // there may be several resource loads with main resource marker upon redirects, redirects are reported with
+ // the original identifier.
+ this._mainResourceIdentifier = record.data.identifier;
+ this._clearPanel();
+ }
+ }
+ this._innerAddRecordToTimeline(record, this._rootRecord);
+ this._scheduleRefresh();
+ },
+
+ _findParentRecord: function(record)
+ {
+ var recordTypes = WebInspector.TimelineAgent.RecordType;
+ var parentRecord;
+ if (record.type === recordTypes.ResourceReceiveResponse ||
+ record.type === recordTypes.ResourceFinish ||
+ record.type === recordTypes.ResourceReceiveData)
+ parentRecord = this._sendRequestRecords[record.data.identifier];
+ else if (record.type === recordTypes.TimerFire)
+ parentRecord = this._timerRecords[record.data.timerId];
+ else if (record.type === recordTypes.ResourceSendRequest)
+ parentRecord = this._scheduledResourceRequests[record.data.url];
+ return parentRecord;
+ },
+
+ _innerAddRecordToTimeline: function(record, parentRecord)
+ {
+ var connectedToOldRecord = false;
+ var recordTypes = WebInspector.TimelineAgent.RecordType;
+ if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType)
+ parentRecord = null; // No bar entry for load events.
+ else if (parentRecord === this._rootRecord) {
+ var newParentRecord = this._findParentRecord(record);
+ if (newParentRecord) {
+ parentRecord = newParentRecord;
+ connectedToOldRecord = true;
+ }
+ }
+
+ if (record.type == recordTypes.TimerFire && record.children && record.children.length) {
+ var childRecord = record.children[0];
+ if (childRecord.type === recordTypes.FunctionCall) {
+ record.data.scriptName = childRecord.data.scriptName;
+ record.data.scriptLine = childRecord.data.scriptLine;
+ record.children.shift();
+ record.children = childRecord.children.concat(record.children);
+ }
+ }
+
+ var formattedRecord = new WebInspector.TimelinePanel.FormattedRecord(record, parentRecord, this);
+
+ if (record.type === recordTypes.MarkDOMContentEventType || record.type === recordTypes.MarkLoadEventType) {
+ this._markTimelineRecords.push(formattedRecord);
+ return;
+ }
+
+ ++this._rootRecord._allRecordsCount;
+ formattedRecord.collapsed = (parentRecord === this._rootRecord);
+
+ var childrenCount = record.children ? record.children.length : 0;
+ for (var i = 0; i < childrenCount; ++i)
+ this._innerAddRecordToTimeline(record.children[i], formattedRecord);
+
+ formattedRecord._calculateAggregatedStats(this.categories);
+
+ if (connectedToOldRecord) {
+ var record = formattedRecord;
+ do {
+ var parent = record.parent;
+ parent._cpuTime += formattedRecord._cpuTime;
+ if (parent._lastChildEndTime < record._lastChildEndTime)
+ parent._lastChildEndTime = record._lastChildEndTime;
+ for (var category in formattedRecord._aggregatedStats)
+ parent._aggregatedStats[category] += formattedRecord._aggregatedStats[category];
+ record = parent;
+ } while (record.parent);
+ } else
+ if (parentRecord !== this._rootRecord)
+ parentRecord._selfTime -= formattedRecord.endTime - formattedRecord.startTime;
+
+ // Keep bar entry for mark timeline since nesting might be interesting to the user.
+ if (record.type === recordTypes.MarkTimeline)
+ this._markTimelineRecords.push(formattedRecord);
+ },
+
+ setSidebarWidth: function(width)
+ {
+ WebInspector.Panel.prototype.setSidebarWidth.call(this, width);
+ this._sidebarBackgroundElement.style.width = width + "px";
+ this._topPaneSidebarElement.style.width = width + "px";
+ },
+
+ updateMainViewWidth: function(width)
+ {
+ this._containerContentElement.style.left = width + "px";
+ this._scheduleRefresh();
+ this._overviewPane.updateMainViewWidth(width);
+ },
+
+ resize: function()
+ {
+ this._closeRecordDetails();
+ this._scheduleRefresh();
+ },
+
+ _createRootRecord: function()
+ {
+ var rootRecord = {};
+ rootRecord.children = [];
+ rootRecord._visibleRecordsCount = 0;
+ rootRecord._allRecordsCount = 0;
+ rootRecord._aggregatedStats = {};
+ return rootRecord;
+ },
+
+ _clearPanel: function()
+ {
+ this._markTimelineRecords = [];
+ this._sendRequestRecords = {};
+ this._scheduledResourceRequests = {};
+ this._timerRecords = {};
+ this._rootRecord = this._createRootRecord();
+ this._boundariesAreValid = false;
+ this._overviewPane.reset();
+ this._adjustScrollPosition(0);
+ this._refresh();
+ this._closeRecordDetails();
+ },
+
+ show: function()
+ {
+ WebInspector.Panel.prototype.show.call(this);
+ if (typeof this._scrollTop === "number")
+ this._containerElement.scrollTop = this._scrollTop;
+ this._refresh();
+ WebInspector.drawer.currentPanelCounters = this.recordsCounter;
+ },
+
+ hide: function()
+ {
+ WebInspector.Panel.prototype.hide.call(this);
+ this._closeRecordDetails();
+ WebInspector.drawer.currentPanelCounters = null;
+ },
+
+ _onScroll: function(event)
+ {
+ this._closeRecordDetails();
+ var scrollTop = this._containerElement.scrollTop;
+ var dividersTop = Math.max(0, scrollTop);
+ this._timelineGrid.setScrollAndDividerTop(scrollTop, dividersTop);
+ this._scheduleRefresh(true);
+ },
+
+ _windowChanged: function()
+ {
+ this._closeRecordDetails();
+ this._scheduleRefresh();
+ },
+
+ _scheduleRefresh: function(preserveBoundaries)
+ {
+ this._closeRecordDetails();
+ this._boundariesAreValid &= preserveBoundaries;
+
+ if (!this.visible)
+ return;
+
+ if (preserveBoundaries)
+ this._refresh();
+ else
+ if (!this._refreshTimeout)
+ this._refreshTimeout = setTimeout(this._refresh.bind(this), 100);
+ },
+
+ _refresh: function()
+ {
+ if (this._refreshTimeout) {
+ clearTimeout(this._refreshTimeout);
+ delete this._refreshTimeout;
+ }
+
+ this._overviewPane.update(this._rootRecord.children, this._calculator._showShortEvents);
+ this._refreshRecords(!this._boundariesAreValid);
+ this._updateRecordsCounter();
+ if(!this._boundariesAreValid)
+ this._updateEventDividers();
+ this._boundariesAreValid = true;
+ },
+
+ _updateBoundaries: function()
+ {
+ this._calculator.reset();
+ this._calculator.windowLeft = this._overviewPane.windowLeft;
+ this._calculator.windowRight = this._overviewPane.windowRight;
+
+ for (var i = 0; i < this._rootRecord.children.length; ++i)
+ this._calculator.updateBoundaries(this._rootRecord.children[i]);
+
+ this._calculator.calculateWindow();
+ },
+
+ _addToRecordsWindow: function(record, recordsWindow, parentIsCollapsed)
+ {
+ if (!this._calculator._showShortEvents && !record.isLong())
+ return;
+ var percentages = this._calculator.computeBarGraphPercentages(record);
+ if (percentages.start < 100 && percentages.endWithChildren >= 0 && !record.category.hidden) {
+ ++this._rootRecord._visibleRecordsCount;
+ ++record.parent._invisibleChildrenCount;
+ if (!parentIsCollapsed)
+ recordsWindow.push(record);
+ }
+
+ var index = recordsWindow.length;
+ record._invisibleChildrenCount = 0;
+ for (var i = 0; i < record.children.length; ++i)
+ this._addToRecordsWindow(record.children[i], recordsWindow, parentIsCollapsed || record.collapsed);
+ record._visibleChildrenCount = recordsWindow.length - index;
+ },
+
+ _filterRecords: function()
+ {
+ var recordsInWindow = [];
+ this._rootRecord._visibleRecordsCount = 0;
+ for (var i = 0; i < this._rootRecord.children.length; ++i)
+ this._addToRecordsWindow(this._rootRecord.children[i], recordsInWindow);
+ return recordsInWindow;
+ },
+
+ _refreshRecords: function(updateBoundaries)
+ {
+ if (updateBoundaries)
+ this._updateBoundaries();
+
+ var recordsInWindow = this._filterRecords();
+
+ // Calculate the visible area.
+ this._scrollTop = this._containerElement.scrollTop;
+ var visibleTop = this._scrollTop;
+ var visibleBottom = visibleTop + this._containerElement.clientHeight;
+
+ const rowHeight = WebInspector.TimelinePanel.rowHeight;
+
+ // Convert visible area to visible indexes. Always include top-level record for a visible nested record.
+ var startIndex = Math.max(0, Math.min(Math.floor(visibleTop / rowHeight) - 1, recordsInWindow.length - 1));
+ var endIndex = Math.min(recordsInWindow.length, Math.ceil(visibleBottom / rowHeight));
+
+ // Resize gaps first.
+ const top = (startIndex * rowHeight) + "px";
+ this._topGapElement.style.height = top;
+ this.sidebarElement.style.top = top;
+ this.sidebarResizeElement.style.top = top;
+ this._bottomGapElement.style.height = (recordsInWindow.length - endIndex) * rowHeight + "px";
+
+ // Update visible rows.
+ var listRowElement = this._sidebarListElement.firstChild;
+ var width = this._graphRowsElement.offsetWidth;
+ this._itemsGraphsElement.removeChild(this._graphRowsElement);
+ var graphRowElement = this._graphRowsElement.firstChild;
+ var scheduleRefreshCallback = this._scheduleRefresh.bind(this, true);
+ this._itemsGraphsElement.removeChild(this._expandElements);
+ this._expandElements.removeChildren();
+
+ for (var i = 0; i < endIndex; ++i) {
+ var record = recordsInWindow[i];
+ var isEven = !(i % 2);
+
+ if (i < startIndex) {
+ var lastChildIndex = i + record._visibleChildrenCount;
+ if (lastChildIndex >= startIndex && lastChildIndex < endIndex) {
+ var expandElement = new WebInspector.TimelineExpandableElement(this._expandElements);
+ expandElement._update(record, i, this._calculator.computeBarGraphWindowPosition(record, width - this._expandOffset));
+ }
+ } else {
+ if (!listRowElement) {
+ listRowElement = new WebInspector.TimelineRecordListRow().element;
+ this._sidebarListElement.appendChild(listRowElement);
+ }
+ if (!graphRowElement) {
+ graphRowElement = new WebInspector.TimelineRecordGraphRow(this._itemsGraphsElement, scheduleRefreshCallback, rowHeight).element;
+ this._graphRowsElement.appendChild(graphRowElement);
+ }
+
+ listRowElement.row.update(record, isEven, this._calculator, visibleTop);
+ graphRowElement.row.update(record, isEven, this._calculator, width, this._expandOffset, i);
+
+ listRowElement = listRowElement.nextSibling;
+ graphRowElement = graphRowElement.nextSibling;
+ }
+ }
+
+ // Remove extra rows.
+ while (listRowElement) {
+ var nextElement = listRowElement.nextSibling;
+ listRowElement.row.dispose();
+ listRowElement = nextElement;
+ }
+ while (graphRowElement) {
+ var nextElement = graphRowElement.nextSibling;
+ graphRowElement.row.dispose();
+ graphRowElement = nextElement;
+ }
+
+ this._itemsGraphsElement.insertBefore(this._graphRowsElement, this._bottomGapElement);
+ this._itemsGraphsElement.appendChild(this._expandElements);
+ this.sidebarResizeElement.style.height = this.sidebarElement.clientHeight + "px";
+ // Reserve some room for expand / collapse controls to the left for records that start at 0ms.
+ var timelinePaddingLeft = this._calculator.windowLeft === 0 ? this._expandOffset : 0;
+ if (updateBoundaries)
+ this._timelineGrid.updateDividers(true, this._calculator, timelinePaddingLeft);
+ this._adjustScrollPosition((recordsInWindow.length + 1) * rowHeight);
+ },
+
+ _adjustScrollPosition: function(totalHeight)
+ {
+ // Prevent the container from being scrolled off the end.
+ if ((this._containerElement.scrollTop + this._containerElement.offsetHeight) > totalHeight + 1)
+ this._containerElement.scrollTop = (totalHeight - this._containerElement.offsetHeight);
+ },
+
+ _getPopoverAnchor: function(element)
+ {
+ return element.enclosingNodeOrSelfWithClass("timeline-graph-bar") || element.enclosingNodeOrSelfWithClass("timeline-tree-item");
+ },
+
+ _showPopover: function(anchor)
+ {
+ var record = anchor.row._record;
+ var popover = new WebInspector.Popover(record._generatePopupContent(this._calculator, this.categories));
+ popover.show(anchor);
+ return popover;
+ },
+
+ _closeRecordDetails: function()
+ {
+ this._popoverHelper.hidePopup();
+ }
+}
+
+WebInspector.TimelinePanel.prototype.__proto__ = WebInspector.Panel.prototype;
+
+WebInspector.TimelineCategory = function(name, title, color)
+{
+ this.name = name;
+ this.title = title;
+ this.color = color;
+}
+
+WebInspector.TimelineCalculator = function()
+{
+ this.reset();
+ this.windowLeft = 0.0;
+ this.windowRight = 1.0;
+}
+
+WebInspector.TimelineCalculator.prototype = {
+ computeBarGraphPercentages: function(record)
+ {
+ var start = (record.startTime - this.minimumBoundary) / this.boundarySpan * 100;
+ var end = (record.startTime + record._selfTime - this.minimumBoundary) / this.boundarySpan * 100;
+ var endWithChildren = (record._lastChildEndTime - this.minimumBoundary) / this.boundarySpan * 100;
+ var cpuWidth = record._cpuTime / this.boundarySpan * 100;
+ return {start: start, end: end, endWithChildren: endWithChildren, cpuWidth: cpuWidth};
+ },
+
+ computeBarGraphWindowPosition: function(record, clientWidth)
+ {
+ const minWidth = 5;
+ const borderWidth = 4;
+ var workingArea = clientWidth - minWidth - borderWidth;
+ var percentages = this.computeBarGraphPercentages(record);
+ var left = percentages.start / 100 * workingArea;
+ var width = (percentages.end - percentages.start) / 100 * workingArea + minWidth;
+ var widthWithChildren = (percentages.endWithChildren - percentages.start) / 100 * workingArea;
+ var cpuWidth = percentages.cpuWidth / 100 * workingArea + minWidth;
+ if (percentages.endWithChildren > percentages.end)
+ widthWithChildren += borderWidth + minWidth;
+ return {left: left, width: width, widthWithChildren: widthWithChildren, cpuWidth: cpuWidth};
+ },
+
+ calculateWindow: function()
+ {
+ this.minimumBoundary = this._absoluteMinimumBoundary + this.windowLeft * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
+ this.maximumBoundary = this._absoluteMinimumBoundary + this.windowRight * (this._absoluteMaximumBoundary - this._absoluteMinimumBoundary);
+ this.boundarySpan = this.maximumBoundary - this.minimumBoundary;
+ },
+
+ reset: function()
+ {
+ this._absoluteMinimumBoundary = -1;
+ this._absoluteMaximumBoundary = -1;
+ },
+
+ updateBoundaries: function(record)
+ {
+ var lowerBound = record.startTime;
+ if (this._absoluteMinimumBoundary === -1 || lowerBound < this._absoluteMinimumBoundary)
+ this._absoluteMinimumBoundary = lowerBound;
+
+ const minimumTimeFrame = 0.1;
+ const minimumDeltaForZeroSizeEvents = 0.01;
+ var upperBound = Math.max(record._lastChildEndTime + minimumDeltaForZeroSizeEvents, lowerBound + minimumTimeFrame);
+ if (this._absoluteMaximumBoundary === -1 || upperBound > this._absoluteMaximumBoundary)
+ this._absoluteMaximumBoundary = upperBound;
+ },
+
+ formatValue: function(value)
+ {
+ return Number.secondsToString(value + this.minimumBoundary - this._absoluteMinimumBoundary, WebInspector.UIString);
+ }
+}
+
+
+WebInspector.TimelineRecordListRow = function()
+{
+ this.element = document.createElement("div");
+ this.element.row = this;
+ this.element.style.cursor = "pointer";
+ var iconElement = document.createElement("span");
+ iconElement.className = "timeline-tree-icon";
+ this.element.appendChild(iconElement);
+
+ this._typeElement = document.createElement("span");
+ this._typeElement.className = "type";
+ this.element.appendChild(this._typeElement);
+
+ var separatorElement = document.createElement("span");
+ separatorElement.className = "separator";
+ separatorElement.textContent = " ";
+
+ this._dataElement = document.createElement("span");
+ this._dataElement.className = "data dimmed";
+
+ this.element.appendChild(separatorElement);
+ this.element.appendChild(this._dataElement);
+}
+
+WebInspector.TimelineRecordListRow.prototype = {
+ update: function(record, isEven, calculator, offset)
+ {
+ this._record = record;
+ this._calculator = calculator;
+ this._offset = offset;
+
+ this.element.className = "timeline-tree-item timeline-category-" + record.category.name + (isEven ? " even" : "");
+ this._typeElement.textContent = record.title;
+
+ if (this._dataElement.firstChild)
+ this._dataElement.removeChildren();
+ if (record.details) {
+ var detailsContainer = document.createElement("span");
+ if (typeof record.details === "object") {
+ detailsContainer.appendChild(document.createTextNode("("));
+ detailsContainer.appendChild(record.details);
+ detailsContainer.appendChild(document.createTextNode(")"));
+ } else
+ detailsContainer.textContent = "(" + record.details + ")";
+ this._dataElement.appendChild(detailsContainer);
+ }
+ },
+
+ dispose: function()
+ {
+ this.element.parentElement.removeChild(this.element);
+ }
+}
+
+WebInspector.TimelineRecordGraphRow = function(graphContainer, scheduleRefresh)
+{
+ this.element = document.createElement("div");
+ this.element.row = this;
+
+ this._barAreaElement = document.createElement("div");
+ this._barAreaElement.className = "timeline-graph-bar-area";
+ this.element.appendChild(this._barAreaElement);
+
+ this._barWithChildrenElement = document.createElement("div");
+ this._barWithChildrenElement.className = "timeline-graph-bar with-children";
+ this._barWithChildrenElement.row = this;
+ this._barAreaElement.appendChild(this._barWithChildrenElement);
+
+ this._barCpuElement = document.createElement("div");
+ this._barCpuElement.className = "timeline-graph-bar cpu"
+ this._barCpuElement.row = this;
+ this._barAreaElement.appendChild(this._barCpuElement);
+
+ this._barElement = document.createElement("div");
+ this._barElement.className = "timeline-graph-bar";
+ this._barElement.row = this;
+ this._barAreaElement.appendChild(this._barElement);
+
+ this._expandElement = new WebInspector.TimelineExpandableElement(graphContainer);
+ this._expandElement._element.addEventListener("click", this._onClick.bind(this));
+
+ this._scheduleRefresh = scheduleRefresh;
+}
+
+WebInspector.TimelineRecordGraphRow.prototype = {
+ update: function(record, isEven, calculator, clientWidth, expandOffset, index)
+ {
+ this._record = record;
+ this.element.className = "timeline-graph-side timeline-category-" + record.category.name + (isEven ? " even" : "");
+ var barPosition = calculator.computeBarGraphWindowPosition(record, clientWidth - expandOffset);
+ this._barWithChildrenElement.style.left = barPosition.left + expandOffset + "px";
+ this._barWithChildrenElement.style.width = barPosition.widthWithChildren + "px";
+ this._barElement.style.left = barPosition.left + expandOffset + "px";
+ this._barElement.style.width = barPosition.width + "px";
+ this._barCpuElement.style.left = barPosition.left + expandOffset + "px";
+ this._barCpuElement.style.width = barPosition.cpuWidth + "px";
+ this._expandElement._update(record, index, barPosition);
+ },
+
+ _onClick: function(event)
+ {
+ this._record.collapsed = !this._record.collapsed;
+ this._scheduleRefresh();
+ },
+
+ dispose: function()
+ {
+ this.element.parentElement.removeChild(this.element);
+ this._expandElement._dispose();
+ }
+}
+
+WebInspector.TimelinePanel.FormattedRecord = function(record, parentRecord, panel)
+{
+ var recordTypes = WebInspector.TimelineAgent.RecordType;
+ var style = panel._recordStyles[record.type];
+
+ this.parent = parentRecord;
+ if (parentRecord)
+ parentRecord.children.push(this);
+ this.category = style.category;
+ this.title = style.title;
+ this.startTime = record.startTime / 1000;
+ this.data = record.data;
+ this.type = record.type;
+ this.endTime = (typeof record.endTime !== "undefined") ? record.endTime / 1000 : this.startTime;
+ this._selfTime = this.endTime - this.startTime;
+ this._lastChildEndTime = this.endTime;
+ this.originalRecordForTests = record;
+ if (record.stackTrace && record.stackTrace.length)
+ this.stackTrace = record.stackTrace;
+ this.totalHeapSize = record.totalHeapSize;
+ this.usedHeapSize = record.usedHeapSize;
+
+ // Make resource receive record last since request was sent; make finish record last since response received.
+ if (record.type === recordTypes.ResourceSendRequest) {
+ panel._sendRequestRecords[record.data.identifier] = this;
+ } else if (record.type === recordTypes.ScheduleResourceRequest) {
+ panel._scheduledResourceRequests[record.data.url] = this;
+ } else if (record.type === recordTypes.ResourceReceiveResponse) {
+ var sendRequestRecord = panel._sendRequestRecords[record.data.identifier];
+ if (sendRequestRecord) { // False if we started instrumentation in the middle of request.
+ record.data.url = sendRequestRecord.data.url;
+ // Now that we have resource in the collection, recalculate details in order to display short url.
+ sendRequestRecord.details = this._getRecordDetails(sendRequestRecord, panel._sendRequestRecords);
+ if (sendRequestRecord.parent !== panel._rootRecord && sendRequestRecord.parent.type === recordTypes.ScheduleResourceRequest)
+ sendRequestRecord.parent.details = this._getRecordDetails(sendRequestRecord, panel._sendRequestRecords);
+ }
+ } else if (record.type === recordTypes.ResourceReceiveData) {
+ var sendRequestRecord = panel._sendRequestRecords[record.data.identifier];
+ if (sendRequestRecord) // False for main resource.
+ record.data.url = sendRequestRecord.data.url;
+ } else if (record.type === recordTypes.ResourceFinish) {
+ var sendRequestRecord = panel._sendRequestRecords[record.data.identifier];
+ if (sendRequestRecord) // False for main resource.
+ record.data.url = sendRequestRecord.data.url;
+ } else if (record.type === recordTypes.TimerInstall) {
+ this.timeout = record.data.timeout;
+ this.singleShot = record.data.singleShot;
+ panel._timerRecords[record.data.timerId] = this;
+ } else if (record.type === recordTypes.TimerFire) {
+ var timerInstalledRecord = panel._timerRecords[record.data.timerId];
+ if (timerInstalledRecord) {
+ this.callSiteStackTrace = timerInstalledRecord.stackTrace;
+ this.timeout = timerInstalledRecord.timeout;
+ this.singleShot = timerInstalledRecord.singleShot;
+ }
+ }
+ this.details = this._getRecordDetails(record, panel._sendRequestRecords);
+}
+
+WebInspector.TimelinePanel.FormattedRecord.prototype = {
+ isLong: function()
+ {
+ return (this._lastChildEndTime - this.startTime) > WebInspector.TimelinePanel.shortRecordThreshold;
+ },
+
+ get children()
+ {
+ if (!this._children)
+ this._children = [];
+ return this._children;
+ },
+
+ _generateAggregatedInfo: function()
+ {
+ var cell = document.createElement("span");
+ cell.className = "timeline-aggregated-info";
+ for (var index in this._aggregatedStats) {
+ var label = document.createElement("div");
+ label.className = "timeline-aggregated-category timeline-" + index;
+ cell.appendChild(label);
+ var text = document.createElement("span");
+ text.textContent = Number.secondsToString(this._aggregatedStats[index] + 0.0001, WebInspector.UIString);
+ cell.appendChild(text);
+ }
+ return cell;
+ },
+
+ _generatePopupContent: function(calculator, categories)
+ {
+ var contentHelper = new WebInspector.TimelinePanel.PopupContentHelper(this.title);
+
+ if (this._children && this._children.length) {
+ contentHelper._appendTextRow(WebInspector.UIString("Self Time"), Number.secondsToString(this._selfTime + 0.0001, WebInspector.UIString));
+ contentHelper._appendElementRow(WebInspector.UIString("Aggregated Time"), this._generateAggregatedInfo());
+ }
+ var text = WebInspector.UIString("%s (at %s)", Number.secondsToString(this._lastChildEndTime - this.startTime, WebInspector.UIString),
+ calculator.formatValue(this.startTime - calculator.minimumBoundary));
+ contentHelper._appendTextRow(WebInspector.UIString("Duration"), text);
+
+ const recordTypes = WebInspector.TimelineAgent.RecordType;
+
+ switch (this.type) {
+ case recordTypes.GCEvent:
+ contentHelper._appendTextRow(WebInspector.UIString("Collected"), Number.bytesToString(this.data.usedHeapSizeDelta, WebInspector.UIString));
+ break;
+ case recordTypes.TimerInstall:
+ case recordTypes.TimerFire:
+ case recordTypes.TimerRemove:
+ contentHelper._appendTextRow(WebInspector.UIString("Timer ID"), this.data.timerId);
+ if (typeof this.timeout === "number") {
+ contentHelper._appendTextRow(WebInspector.UIString("Timeout"), Number.secondsToString(this.timeout / 1000, WebInspector.UIString));
+ contentHelper._appendTextRow(WebInspector.UIString("Repeats"), !this.singleShot);
+ }
+ break;
+ case recordTypes.FunctionCall:
+ contentHelper._appendLinkRow(WebInspector.UIString("Location"), this.data.scriptName, this.data.scriptLine);
+ break;
+ case recordTypes.ScheduleResourceRequest:
+ case recordTypes.ResourceSendRequest:
+ case recordTypes.ResourceReceiveResponse:
+ case recordTypes.ResourceReceiveData:
+ case recordTypes.ResourceFinish:
+ contentHelper._appendLinkRow(WebInspector.UIString("Resource"), this.data.url);
+ if (this.data.requestMethod)
+ contentHelper._appendTextRow(WebInspector.UIString("Request Method"), this.data.requestMethod);
+ if (typeof this.data.statusCode === "number")
+ contentHelper._appendTextRow(WebInspector.UIString("Status Code"), this.data.statusCode);
+ if (this.data.mimeType)
+ contentHelper._appendTextRow(WebInspector.UIString("MIME Type"), this.data.mimeType);
+ if (typeof this.data.expectedContentLength === "number" && this.data.expectedContentLength !== -1)
+ contentHelper._appendTextRow(WebInspector.UIString("Expected Content Length"), this.data.expectedContentLength);
+ break;
+ case recordTypes.EvaluateScript:
+ if (this.data && this.data.url)
+ contentHelper._appendLinkRow(WebInspector.UIString("Script"), this.data.url, this.data.lineNumber);
+ break;
+ case recordTypes.Paint:
+ contentHelper._appendTextRow(WebInspector.UIString("Location"), WebInspector.UIString("(%d, %d)", this.data.x, this.data.y));
+ contentHelper._appendTextRow(WebInspector.UIString("Dimensions"), WebInspector.UIString("%d × %d", this.data.width, this.data.height));
+ case recordTypes.RecalculateStyles: // We don't want to see default details.
+ break;
+ default:
+ if (this.details)
+ contentHelper._appendTextRow(WebInspector.UIString("Details"), this.details);
+ break;
+ }
+
+ if (this.data.scriptName && this.type !== recordTypes.FunctionCall)
+ contentHelper._appendLinkRow(WebInspector.UIString("Function Call"), this.data.scriptName, this.data.scriptLine);
+
+ if (this.usedHeapSize)
+ contentHelper._appendTextRow(WebInspector.UIString("Used Heap Size"), WebInspector.UIString("%s of %s", Number.bytesToString(this.usedHeapSize, WebInspector.UIString), Number.bytesToString(this.totalHeapSize, WebInspector.UIString)));
+
+ if (this.callSiteStackTrace && this.callSiteStackTrace.length)
+ contentHelper._appendStackTrace(WebInspector.UIString("Call Site stack"), this.callSiteStackTrace);
+
+ if (this.stackTrace)
+ contentHelper._appendStackTrace(WebInspector.UIString("Call Stack"), this.stackTrace);
+
+ return contentHelper._contentTable;
+ },
+
+ _getRecordDetails: function(record, sendRequestRecords)
+ {
+ switch (record.type) {
+ case WebInspector.TimelineAgent.RecordType.GCEvent:
+ return WebInspector.UIString("%s collected", Number.bytesToString(record.data.usedHeapSizeDelta, WebInspector.UIString));
+ case WebInspector.TimelineAgent.RecordType.TimerFire:
+ return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine, "", "") : record.data.timerId;
+ case WebInspector.TimelineAgent.RecordType.FunctionCall:
+ return record.data.scriptName ? WebInspector.linkifyResourceAsNode(record.data.scriptName, "scripts", record.data.scriptLine, "", "") : null;
+ case WebInspector.TimelineAgent.RecordType.EventDispatch:
+ return record.data ? record.data.type : null;
+ case WebInspector.TimelineAgent.RecordType.Paint:
+ return record.data.width + "\u2009\u00d7\u2009" + record.data.height;
+ case WebInspector.TimelineAgent.RecordType.TimerInstall:
+ case WebInspector.TimelineAgent.RecordType.TimerRemove:
+ return this.stackTrace ? WebInspector.linkifyResourceAsNode(this.stackTrace[0].scriptName, "scripts", this.stackTrace[0].lineNumber, "", "") : record.data.timerId;
+ case WebInspector.TimelineAgent.RecordType.ParseHTML:
+ case WebInspector.TimelineAgent.RecordType.RecalculateStyles:
+ return this.stackTrace ? WebInspector.linkifyResourceAsNode(this.stackTrace[0].scriptName, "scripts", this.stackTrace[0].lineNumber, "", "") : null;
+ case WebInspector.TimelineAgent.RecordType.EvaluateScript:
+ return record.data.url ? WebInspector.linkifyResourceAsNode(record.data.url, "scripts", record.data.lineNumber, "", "") : null;
+ case WebInspector.TimelineAgent.RecordType.XHRReadyStateChange:
+ case WebInspector.TimelineAgent.RecordType.XHRLoad:
+ case WebInspector.TimelineAgent.RecordType.ScheduleResourceRequest:
+ case WebInspector.TimelineAgent.RecordType.ResourceSendRequest:
+ case WebInspector.TimelineAgent.RecordType.ResourceReceiveData:
+ case WebInspector.TimelineAgent.RecordType.ResourceReceiveResponse:
+ case WebInspector.TimelineAgent.RecordType.ResourceFinish:
+ return WebInspector.displayNameForURL(record.data.url);
+ case WebInspector.TimelineAgent.RecordType.MarkTimeline:
+ return record.data.message;
+ default:
+ return null;
+ }
+ },
+
+ _calculateAggregatedStats: function(categories)
+ {
+ this._aggregatedStats = {};
+ for (var category in categories)
+ this._aggregatedStats[category] = 0;
+ this._cpuTime = this._selfTime;
+
+ if (this._children) {
+ for (var index = this._children.length; index; --index) {
+ var child = this._children[index - 1];
+ this._aggregatedStats[child.category.name] += child._selfTime;
+ for (var category in categories)
+ this._aggregatedStats[category] += child._aggregatedStats[category];
+ }
+ for (var category in this._aggregatedStats)
+ this._cpuTime += this._aggregatedStats[category];
+ }
+ }
+}
+
+WebInspector.TimelinePanel.PopupContentHelper = function(title)
+{
+ this._contentTable = document.createElement("table");;
+ var titleCell = this._createCell(WebInspector.UIString("%s - Details", title), "timeline-details-title");
+ titleCell.colSpan = 2;
+ var titleRow = document.createElement("tr");
+ titleRow.appendChild(titleCell);
+ this._contentTable.appendChild(titleRow);
+}
+
+WebInspector.TimelinePanel.PopupContentHelper.prototype = {
+ _createCell: function(content, styleName)
+ {
+ var text = document.createElement("label");
+ text.appendChild(document.createTextNode(content));
+ var cell = document.createElement("td");
+ cell.className = "timeline-details";
+ if (styleName)
+ cell.className += " " + styleName;
+ cell.textContent = content;
+ return cell;
+ },
+
+ _appendTextRow: function(title, content)
+ {
+ var row = document.createElement("tr");
+ row.appendChild(this._createCell(title, "timeline-details-row-title"));
+ row.appendChild(this._createCell(content, "timeline-details-row-data"));
+ this._contentTable.appendChild(row);
+ },
+
+ _appendElementRow: function(title, content, titleStyle)
+ {
+ var row = document.createElement("tr");
+ var titleCell = this._createCell(title, "timeline-details-row-title");
+ if (titleStyle)
+ titleCell.addStyleClass(titleStyle);
+ row.appendChild(titleCell);
+ var cell = document.createElement("td");
+ cell.className = "timeline-details";
+ cell.appendChild(content);
+ row.appendChild(cell);
+ this._contentTable.appendChild(row);
+ },
+
+ _appendLinkRow: function(title, scriptName, scriptLine)
+ {
+ var link = WebInspector.linkifyResourceAsNode(scriptName, "scripts", scriptLine, "timeline-details");
+ this._appendElementRow(title, link);
+ },
+
+ _appendStackTrace: function(title, stackTrace)
+ {
+ this._appendTextRow("", "");
+ var framesTable = document.createElement("table");
+ for (var i = 0; i < stackTrace.length; ++i) {
+ var stackFrame = stackTrace[i];
+ var row = document.createElement("tr");
+ row.className = "timeline-details";
+ row.appendChild(this._createCell(stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"), "timeline-function-name"));
+ row.appendChild(this._createCell(" @ "));
+ var linkCell = document.createElement("td");
+ linkCell.appendChild(WebInspector.linkifyResourceAsNode(stackFrame.scriptName, "scripts", stackFrame.lineNumber, "timeline-details"));
+ row.appendChild(linkCell);
+ framesTable.appendChild(row);
+ }
+ this._appendElementRow(title, framesTable, "timeline-stacktrace-title");
+ }
+}
+
+WebInspector.TimelineExpandableElement = function(container)
+{
+ this._element = document.createElement("div");
+ this._element.className = "timeline-expandable";
+
+ var leftBorder = document.createElement("div");
+ leftBorder.className = "timeline-expandable-left";
+ this._element.appendChild(leftBorder);
+
+ container.appendChild(this._element);
+}
+
+WebInspector.TimelineExpandableElement.prototype = {
+ _update: function(record, index, barPosition)
+ {
+ const rowHeight = WebInspector.TimelinePanel.rowHeight;
+ if (record._visibleChildrenCount || record._invisibleChildrenCount) {
+ this._element.style.top = index * rowHeight + "px";
+ this._element.style.left = barPosition.left + "px";
+ this._element.style.width = Math.max(12, barPosition.width + 25) + "px";
+ if (!record.collapsed) {
+ this._element.style.height = (record._visibleChildrenCount + 1) * rowHeight + "px";
+ this._element.addStyleClass("timeline-expandable-expanded");
+ this._element.removeStyleClass("timeline-expandable-collapsed");
+ } else {
+ this._element.style.height = rowHeight + "px";
+ this._element.addStyleClass("timeline-expandable-collapsed");
+ this._element.removeStyleClass("timeline-expandable-expanded");
+ }
+ this._element.removeStyleClass("hidden");
+ } else
+ this._element.addStyleClass("hidden");
+ },
+
+ _dispose: function()
+ {
+ this._element.parentElement.removeChild(this._element);
+ }
+}