/* * Copyright (C) 2013 Apple 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: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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.TimelinesContentView = function(representedObject) { WebInspector.ContentView.call(this, representedObject); this.element.classList.add(WebInspector.TimelinesContentView.StyleClassName); this._timelineOverview = new WebInspector.TimelineOverview(this, [WebInspector.TimelineRecord.Type.Network, WebInspector.TimelineRecord.Type.Layout, WebInspector.TimelineRecord.Type.Script]); this.element.appendChild(this._timelineOverview.element); function createPathComponent(displayName, className, representedObject) { var pathComponent = new WebInspector.HierarchicalPathComponent(displayName, className, representedObject); pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._pathComponentSelected, this); return pathComponent; } var networkPathComponent = createPathComponent.call(this, WebInspector.UIString("Network Requests"), WebInspector.InstrumentSidebarPanel.NetworkIconStyleClass, WebInspector.TimelineRecord.Type.Network); var layoutPathComponent = createPathComponent.call(this, WebInspector.UIString("Layout & Rendering"), WebInspector.InstrumentSidebarPanel.ColorsIconStyleClass, WebInspector.TimelineRecord.Type.Layout); var scriptPathComponent = createPathComponent.call(this, WebInspector.UIString("JavaScript & Events"), WebInspector.InstrumentSidebarPanel.ScriptIconStyleClass, WebInspector.TimelineRecord.Type.Script); networkPathComponent.nextSibling = layoutPathComponent; layoutPathComponent.previousSibling = networkPathComponent; layoutPathComponent.nextSibling = scriptPathComponent; scriptPathComponent.previousSibling = layoutPathComponent; this._currentRecordType = null; this._currentRecordTypeSetting = new WebInspector.Setting("timeline-view-current-record-type", WebInspector.TimelineRecord.Type.Network); this._currentDataGrid = null; var networkDataGridColumns = {name: {}, domain: {}, type: {}, statusCode: {}, cached: {}, size: {}, transferSize: {}, latency: {}, duration: {}, timeline: {}}; networkDataGridColumns.name.title = WebInspector.UIString("Name"); networkDataGridColumns.name.width = "15%"; networkDataGridColumns.domain.title = WebInspector.UIString("Domain"); networkDataGridColumns.domain.width = "10%"; networkDataGridColumns.domain.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.type.title = WebInspector.UIString("Type"); networkDataGridColumns.type.width = "8%"; networkDataGridColumns.type.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.type.scopeBar = this._makeColumnScopeBar("network", WebInspector.Resource.Type); networkDataGridColumns.statusCode.title = WebInspector.UIString("Status"); networkDataGridColumns.statusCode.width = "6%"; networkDataGridColumns.statusCode.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.cached.title = WebInspector.UIString("Cached"); networkDataGridColumns.cached.width = "7%"; networkDataGridColumns.cached.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.size.title = WebInspector.UIString("Size"); networkDataGridColumns.size.width = "8%"; networkDataGridColumns.size.aligned = "right"; networkDataGridColumns.size.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.transferSize.title = WebInspector.UIString("Transferred"); networkDataGridColumns.transferSize.width = "8%"; networkDataGridColumns.transferSize.aligned = "right"; networkDataGridColumns.transferSize.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.latency.title = WebInspector.UIString("Latency"); networkDataGridColumns.latency.width = "9%"; networkDataGridColumns.latency.aligned = "right"; networkDataGridColumns.latency.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.duration.title = WebInspector.UIString("Duration"); networkDataGridColumns.duration.width = "9%"; networkDataGridColumns.duration.aligned = "right"; networkDataGridColumns.duration.group = WebInspector.NetworkDataGrid.DetailsColumnGroup; networkDataGridColumns.timeline.title = WebInspector.UIString("Timeline"); networkDataGridColumns.timeline.width = "20%"; networkDataGridColumns.timeline.sort = "ascending"; networkDataGridColumns.timeline.collapsesGroup = WebInspector.NetworkDataGrid.DetailsColumnGroup; var layoutDataGridColumns = {eventType: {}, initiatorCallFrame: {}, width: {}, height: {}, area: {}, startTime: {}, duration: {}}; layoutDataGridColumns.eventType.title = WebInspector.UIString("Type"); layoutDataGridColumns.eventType.width = "15%"; layoutDataGridColumns.eventType.scopeBar = this._makeColumnScopeBar("layout", WebInspector.LayoutTimelineRecord.EventType); layoutDataGridColumns.initiatorCallFrame.title = WebInspector.UIString("Initiator"); layoutDataGridColumns.initiatorCallFrame.width = "25%"; layoutDataGridColumns.width.title = WebInspector.UIString("Width"); layoutDataGridColumns.width.width = "8%"; layoutDataGridColumns.height.title = WebInspector.UIString("Height"); layoutDataGridColumns.height.width = "8%"; layoutDataGridColumns.area.title = WebInspector.UIString("Area"); layoutDataGridColumns.area.width = "12%"; layoutDataGridColumns.startTime.title = WebInspector.UIString("Start Time"); layoutDataGridColumns.startTime.width = "8%"; layoutDataGridColumns.startTime.aligned = "right"; layoutDataGridColumns.startTime.sort = "ascending"; layoutDataGridColumns.duration.title = WebInspector.UIString("Duration"); layoutDataGridColumns.duration.width = "8%"; layoutDataGridColumns.duration.aligned = "right"; var scriptDataGridColumns = {eventType: {}, details: {}, resource: {}, startTime: {}, duration: {}}; scriptDataGridColumns.eventType.title = WebInspector.UIString("Type"); scriptDataGridColumns.eventType.width = "15%"; scriptDataGridColumns.eventType.scopeBar = this._makeColumnScopeBar("script", WebInspector.ScriptTimelineRecord.EventType); scriptDataGridColumns.details.title = WebInspector.UIString("Details"); scriptDataGridColumns.details.width = "15%"; scriptDataGridColumns.resource.title = WebInspector.UIString("Location"); scriptDataGridColumns.resource.width = "15%"; scriptDataGridColumns.startTime.title = WebInspector.UIString("Start Time"); scriptDataGridColumns.startTime.width = "10%"; scriptDataGridColumns.startTime.aligned = "right"; scriptDataGridColumns.startTime.sort = "ascending"; scriptDataGridColumns.duration.title = WebInspector.UIString("Duration"); scriptDataGridColumns.duration.width = "10%"; scriptDataGridColumns.duration.aligned = "right"; for (var column in networkDataGridColumns) networkDataGridColumns[column].sortable = true; for (var column in layoutDataGridColumns) layoutDataGridColumns[column].sortable = true; for (var column in scriptDataGridColumns) scriptDataGridColumns[column].sortable = true; var networkDataGrid = new WebInspector.NetworkDataGrid(networkDataGridColumns); var layoutDataGrid = new WebInspector.LayoutTimelineDataGrid(layoutDataGridColumns); var scriptDataGrid = new WebInspector.ScriptTimelineDataGrid(scriptDataGridColumns); networkDataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._selectedNodeChanged, this); this._pathComponentMap = {}; this._pathComponentMap[WebInspector.TimelineRecord.Type.Network] = networkPathComponent; this._pathComponentMap[WebInspector.TimelineRecord.Type.Layout] = layoutPathComponent; this._pathComponentMap[WebInspector.TimelineRecord.Type.Script] = scriptPathComponent; this._dataGridMap = {}; this._dataGridMap[WebInspector.TimelineRecord.Type.Network] = networkDataGrid; this._dataGridMap[WebInspector.TimelineRecord.Type.Layout] = layoutDataGrid; this._dataGridMap[WebInspector.TimelineRecord.Type.Script] = scriptDataGrid; for (var type in this._dataGridMap) { var dataGrid = this._dataGridMap[type]; dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._sortCurrentDataGrid, this); dataGrid.addEventListener(WebInspector.TimelineDataGrid.Event.FiltersDidChange, this._dataGridFiltersDidChange, this); dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this)); } this._pendingRecords = {}; for (var typeName in WebInspector.TimelineRecord.Type) { var type = WebInspector.TimelineRecord.Type[typeName]; this._pendingRecords[type] = WebInspector.timelineManager.recordsWithType(type); } this._pendingRefreshGridNodes = {}; this._pendingEventMarkers = {}; WebInspector.ResourceTimelineDataGridNode.addEventListener(WebInspector.ResourceTimelineDataGridNode.Event.NeedsRefresh, this._scheduleGridNodeForRefresh, this); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordsCleared, this._recordsCleared, this); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordAdded, this._recordAdded, this); WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.Event.RecordedEventMarker, this._recordedEventMarker, this); WebInspector.TimelinesContentView.generateEmbossedCollapseImages(); }; WebInspector.TimelinesContentView.StyleClassName = "timelines"; WebInspector.TimelinesContentView.OffscreenDataGridRowStyleClassName = "offscreen"; WebInspector.TimelinesContentView.UpdateInterval = 500; // 0.5 seconds WebInspector.TimelinesContentView.CollapseButton = {}; WebInspector.TimelinesContentView.CollapseButton.More = "more"; WebInspector.TimelinesContentView.CollapseButton.Less = "less"; WebInspector.TimelinesContentView.CollapseButton.States = {}; WebInspector.TimelinesContentView.CollapseButton.States.Normal = "normal"; WebInspector.TimelinesContentView.CollapseButton.States.Active = "active"; WebInspector.TimelinesContentView.generateEmbossedCollapseImages = function() { if (WebInspector.TimelinesContentView._generatedImages) return; WebInspector.TimelinesContentView._generatedImages = true; generateEmbossedImages("Images/MoreColumns.pdf", 15, 13, WebInspector.TimelinesContentView.CollapseButton.States, canvasIdentifier.bind(this, WebInspector.TimelinesContentView.CollapseButton.More)); generateEmbossedImages("Images/LessColumns.pdf", 15, 13, WebInspector.TimelinesContentView.CollapseButton.States, canvasIdentifier.bind(this, WebInspector.TimelinesContentView.CollapseButton.Less)); function canvasIdentifier(type, state) { return "timeline-datagrid-collapse-button-" + type + "-" + state; } } WebInspector.TimelinesContentView.prototype = { constructor: WebInspector.TimelinesContentView, // Public get allowedNavigationSidebarPanels() { return ["instrument"]; }, showTimelineForRecordType: function(type) { if (this._currentRecordType === type) return; this._currentRecordType = type; this._currentRecordTypeSetting.value = type; if (this._currentDataGrid) { // Save scroll positon before removing from the document. if (this._currentDataGrid.isScrolledToLastRow()) { this._currentDataGrid._savedIsScrolledToLastRow = true; delete this._currentDataGrid._savedScrollTop; } else { this._currentDataGrid._savedScrollTop = this._currentDataGrid.scrollContainer.scrollTop; delete this._currentDataGrid._savedIsScrolledToLastRow; } this.element.removeChild(this._currentDataGrid.element); this._currentDataGrid.hidden(); } this._currentDataGrid = this._dataGridMap[type]; console.assert(this._currentDataGrid); this.element.appendChild(this._currentDataGrid.element); this._currentDataGrid.updateLayout(); this._currentDataGrid.shown(); // Restore scroll positon now that we are back in the document. if (this._currentDataGrid._savedIsScrolledToLastRow) this._currentDataGrid.scrollToLastRow() else if (this._currentDataGrid._savedScrollTop) this._currentDataGrid.scrollContainer.scrollTop = this._currentDataGrid._savedScrollTop; this._updatePendingRecords(); this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange); WebInspector.instrumentSidebarPanel.showTimelineForRecordType(type); }, get supportsSplitContentBrowser() { // The layout of the overview and split content browser don't work well. return false; }, get selectionPathComponents() { var pathComponents = [this._pathComponentMap[this._currentRecordType]] || []; if (this._currentDataGrid) { var selectedNode = this._currentDataGrid.selectedNode; if (selectedNode instanceof WebInspector.ResourceTimelineDataGridNode) { var pathComponent = new WebInspector.ResourceTimelineDataGridNodePathComponent(selectedNode); pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._dataGridNodePathComponentSelected, this); pathComponents.push(pathComponent); } } return pathComponents; }, updateLayout: function() { if (this._currentDataGrid) this._currentDataGrid.updateLayout(); this._timelineOverview.updateLayout(); this._updateOffscreenRows(); }, get scrollableElements() { if (!this._currentDataGrid) return null; return [this._currentDataGrid.scrollContainer]; }, get shouldKeepElementsScrolledToBottom() { return true; }, shown: function() { if (this._currentRecordType) { this._updatePendingRecords(); return; } if (this._currentDataGrid) this._currentDataGrid.shown(); this.showTimelineForRecordType(this._currentRecordTypeSetting.value); }, hidden: function() { if (this._currentDataGrid) this._currentDataGrid.hidden(); }, timelineOverviewRecordsWithType: function(type) { return this._filterRecordsWithType(WebInspector.timelineManager.recordsWithType(type), type); }, // Private _pathComponentSelected: function(event) { this.showTimelineForRecordType(event.data.pathComponent.representedObject); }, _dataGridNodePathComponentSelected: function(event) { console.assert(event.data.pathComponent instanceof WebInspector.ResourceTimelineDataGridNodePathComponent); if (!(event.data.pathComponent instanceof WebInspector.ResourceTimelineDataGridNodePathComponent)) return; event.data.pathComponent.resourceTimelineDataGridNode.revealAndSelect(); event.data.pathComponent.resourceTimelineDataGridNode.dataGrid.element.focus(); }, _selectedNodeChanged: function(event) { if (this._ignoreSelectionEvent) return; this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange); }, _recordsCleared: function(event) { this._pendingRecords = {}; this._pendingRefreshGridNodes = {}; this._pendingEventMarkers = {}; for (var type in this._dataGridMap) { var dataGrid = this._dataGridMap[type]; delete dataGrid._savedIsScrolledToLastRow; delete dataGrid._savedScrollTop; dataGrid.removeChildren(); dataGrid.reset(); } this._timelineOverview.clear(); }, _recordAdded: function(event) { this._addRecordToDataGrid(event.data.record); }, _recordedEventMarker: function(event) { var eventMarker = event.data.eventMarker; this._timelineOverview.addTimelineEventMarker(eventMarker); for (var type in this._dataGridMap) { if (!this._pendingEventMarkers[type]) this._pendingEventMarkers[type] = []; this._pendingEventMarkers[type].push(eventMarker); this._dataGridMap[type].addTimelineEventMarker(eventMarker); } this._updatePendingRecordsSoon(); }, _addRecordToDataGrid: function(record) { if (!this._pendingRecords[record.type]) this._pendingRecords[record.type] = []; this._pendingRecords[record.type].push(record); this._updatePendingRecordsSoon(); }, _updatePendingRecordsSoon: function() { if (!this.visible || this._updatePendingRecordsTimeout) return; this._updatePendingRecordsTimeout = setTimeout(this._updatePendingRecords.bind(this), WebInspector.TimelinesContentView.UpdateInterval); }, _updatePendingRecords: function() { if (this._updatePendingRecordsTimeout) { clearTimeout(this._updatePendingRecordsTimeout); delete this._updatePendingRecordsTimeout; } this._timelineOverview.update(); console.assert(this._dataGridMap[this._currentRecordType] === this._currentDataGrid); if (this._dataGridMap[this._currentRecordType] !== this._currentDataGrid) return; var isScrolledToLastRow = this._currentDataGrid.isScrolledToLastRow(); // If the data grid has a timeline calculator, pass through all the pending records first // to update the timeline bounds. If the bounds change we need to refresh all nodes // in the data grid, otherwise we can just proceed and update the pending records. var wasBoundsChange = this._updateCalculatorBoundsForPendingRecordsAndEventMarkers(); this._updatePendingRecordsWithNewBounds(wasBoundsChange); this._updateOffscreenRows(); this._currentDataGrid.update(); if (isScrolledToLastRow) this._currentDataGrid.scrollToLastRow(); }, _updateCalculatorBoundsForPendingRecordsAndEventMarkers: function() { var currentDataGrid = this._currentDataGrid; if (!currentDataGrid.currentCalculator) return false; var wasBoundsChange = false; var pendingRecords = this._pendingRecords[this._currentRecordType]; if (pendingRecords) { for (var i = 0; i < pendingRecords.length; ++i) { if (currentDataGrid.updateCalculatorBoundariesWithRecord(pendingRecords[i])) wasBoundsChange = true; } } var pendingRefreshGridNodes = this._pendingRefreshGridNodes[this._currentRecordType]; if (pendingRefreshGridNodes) { for (var i = 0; i < pendingRefreshGridNodes.length; ++i) { if (currentDataGrid.updateCalculatorBoundariesWithDataGridNode(pendingRefreshGridNodes[i])) wasBoundsChange = true; } } var pendingEventMarkers = this._pendingEventMarkers[this._currentRecordType]; if (pendingEventMarkers) { delete this._pendingEventMarkers[this._currentRecordType]; for (var i = 0; i < pendingEventMarkers.length; ++i) { if (currentDataGrid.updateCalculatorBoundariesWithEventMarker(pendingEventMarkers[i])) wasBoundsChange = true; } } return wasBoundsChange; }, _updatePendingRecordsWithNewBounds: function(refreshAllNodes) { var sortComparator = this._sortComparator.bind(this); var pendingRecords = this._pendingRecords[this._currentRecordType]; if (pendingRecords) { delete this._pendingRecords[this._currentRecordType]; pendingRecords = this._filterRecordsWithType(pendingRecords, this._currentRecordType); for (var i = 0; i < pendingRecords.length; ++i) { var dataGridNode = this._createDataGridNodeForRecord(pendingRecords[i]); if (this._currentDataGrid.sortColumnIdentifier) { var insertionIndex = insertionIndexForObjectInListSortedByFunction(dataGridNode, this._currentDataGrid.children, sortComparator); this._currentDataGrid.insertChild(dataGridNode, insertionIndex); } else this._currentDataGrid.appendChild(dataGridNode); } } var pendingRefreshGridNodes = refreshAllNodes ? this._currentDataGrid.children.slice() : this._pendingRefreshGridNodes[this._currentRecordType]; if (pendingRefreshGridNodes) { delete this._pendingRefreshGridNodes[this._currentRecordType]; var selectedNode = this._currentDataGrid.selectedNode; for (var i = 0; i < pendingRefreshGridNodes.length; ++i) { var dataGridNode = pendingRefreshGridNodes[i]; delete dataGridNode._pendingRefresh; dataGridNode.refresh(); if (!this._currentDataGrid.sortColumnIdentifier) continue; if (dataGridNode === selectedNode) this._ignoreSelectionEvent = true; // Remove the data grid node so we can find the right sorted location to reinsert it. We need to // remove it first so insertionIndexForObjectInListSortedByFunction does not return the current index. this._currentDataGrid.removeChild(dataGridNode); var insertionIndex = insertionIndexForObjectInListSortedByFunction(dataGridNode, this._currentDataGrid.children, sortComparator); this._currentDataGrid.insertChild(dataGridNode, insertionIndex); if (dataGridNode === selectedNode) { selectedNode.revealAndSelect(); delete this._ignoreSelectionEvent; } } } }, _updateOffscreenRows: function() { var dataTableBody = this._currentDataGrid.dataTableBody; var rows = dataTableBody.children; var recordsCount = rows.length; if (recordsCount < 2) return; // Filler row only. const overflowPadding = 100; var visibleTop = this._currentDataGrid.scrollContainer.scrollTop - overflowPadding; var visibleBottom = visibleTop + this._currentDataGrid.scrollContainer.offsetHeight + overflowPadding; var rowHeight = 0; // Filler is at recordsCount - 1. for (var i = 0; i < recordsCount - 1; ++i) { var row = rows[i]; if (!rowHeight) rowHeight = row.offsetHeight; var rowIsVisible = (i * rowHeight) < visibleBottom && ((i + 1) * rowHeight) > visibleTop; if (rowIsVisible !== row.rowIsVisible) { if (rowIsVisible) row.classList.remove(WebInspector.TimelinesContentView.OffscreenDataGridRowStyleClassName); else row.classList.add(WebInspector.TimelinesContentView.OffscreenDataGridRowStyleClassName); row.rowIsVisible = rowIsVisible; } } }, _sortCurrentDataGrid: function() { console.assert(this._currentDataGrid); if (!this._currentDataGrid) return; var sortColumnIdentifier = this._currentDataGrid.sortColumnIdentifier; if (!sortColumnIdentifier) return; var selectedNode = this._currentDataGrid.selectedNode; this._ignoreSelectionEvent = true; var nodes = this._currentDataGrid.children.slice(); nodes.sort(this._sortComparator.bind(this)); this._currentDataGrid.removeChildren(); for (var i = 0; i < nodes.length; ++i) this._currentDataGrid.appendChild(nodes[i]); if (selectedNode) selectedNode.revealAndSelect(); delete this._ignoreSelectionEvent; this._updateOffscreenRows(); }, _sortComparator: function(node1, node2) { var sortColumnIdentifier = this._currentDataGrid.sortColumnIdentifier; if (!sortColumnIdentifier) return 0; var sortDirection = this._currentDataGrid.sortOrder === "ascending" ? 1 : -1; var value1 = node1.data[sortColumnIdentifier]; var value2 = node2.data[sortColumnIdentifier]; if (typeof value1 === "number" && typeof value2 === "number") { if (isNaN(value1) && isNaN(value2)) return 0; if (isNaN(value1)) return sortDirection * -1; if (isNaN(value2)) return sortDirection * 1; return (value1 - value2) * sortDirection; } if (typeof value1 === "string" && typeof value2 === "string") return value1.localeCompare(value2) * sortDirection; if (value1 instanceof WebInspector.CallFrame || value2 instanceof WebInspector.CallFrame) { // Sort by function name if available, then fall back to the source code object. value1 = value1 && value1.functionName ? value1.functionName : (value1 && value1.sourceCodeLocation ? value1.sourceCodeLocation.sourceCode : ""); value2 = value2 && value2.functionName ? value2.functionName : (value2 && value2.sourceCodeLocation ? value2.sourceCodeLocation.sourceCode : ""); } if (value1 instanceof WebInspector.SourceCode || value2 instanceof WebInspector.SourceCode) { value1 = value1 ? value1.displayName || "" : ""; value2 = value2 ? value2.displayName || "" : ""; } // For everything else (mostly booleans). return (value1 < value2 ? -1 : (value1 > value2 ? 1 : 0)) * sortDirection; }, _createDataGridNodeForRecord: function(record) { var baseStartTime = WebInspector.timelineManager.records[0].startTime; switch (record.type) { case WebInspector.TimelineRecord.Type.Network: return new WebInspector.ResourceTimelineDataGridNode(record); case WebInspector.TimelineRecord.Type.Layout: return new WebInspector.LayoutTimelineDataGridNode(record, baseStartTime); case WebInspector.TimelineRecord.Type.Script: return new WebInspector.ScriptTimelineDataGridNode(record, baseStartTime); } console.error("Unknown record type: " + record.type); return null; }, _scheduleGridNodeForRefresh: function(event) { var gridNode = event.target; var record = gridNode.record; if (gridNode._pendingRefresh) return; gridNode._pendingRefresh = true; if (!this._pendingRefreshGridNodes[record.type]) this._pendingRefreshGridNodes[record.type] = []; this._pendingRefreshGridNodes[record.type].push(gridNode); this._updatePendingRecordsSoon(); }, _makeColumnScopeBar: function(prefix, dictionary) { prefix = "timeline-" + prefix + "-data-grid-"; var keys = Object.keys(dictionary).filter(function(key) { return typeof dictionary[key] === "string" || dictionary[key] instanceof String; }); var scopeBarItems = keys.map(function(key) { var value = dictionary[key]; var id = prefix + value; var label = dictionary.displayName(value, true); var item = new WebInspector.ScopeBarItem(id, label); item.value = value; return item; }); scopeBarItems.unshift(new WebInspector.ScopeBarItem(prefix + "type-all", WebInspector.UIString("All"), true)); return new WebInspector.ScopeBar(prefix + "scope-bar", scopeBarItems, scopeBarItems[0]); }, _dataGridFiltersDidChange: function(event) { var dataGrid = event.target; if (this._currentDataGrid === dataGrid) this._refreshAllRecordsForCurrentDataGrid(); this._timelineOverview.update(); }, _refreshAllRecordsForCurrentDataGrid: function() { var dataGrid = this._currentDataGrid; var recordType = this._currentRecordType; var records = this.timelineOverviewRecordsWithType(recordType); var nodes = records.map(function(record) { return this._createDataGridNodeForRecord(record); }, this); dataGrid.removeChildren(); nodes.sort(this._sortComparator.bind(this)).forEach(function(node) { dataGrid.appendChild(node); }); delete this._pendingRefreshGridNodes[recordType]; this._updateOffscreenRows(); }, _filterRecordsWithType: function(records, type) { var dataGrid = this._dataGridMap[type]; var identifiers = dataGrid.filterableColumns; function filterableValueForRecordAndIdentifier(record, identifier) { switch (type) { case WebInspector.TimelineRecord.Type.Network: return record.resource[identifier]; case WebInspector.TimelineRecord.Type.Layout: return record[identifier]; case WebInspector.TimelineRecord.Type.Script: return record[identifier]; } } return records.filter(function(record) { for (var i = 0; i < identifiers.length; i++) { var identifier = identifiers[i]; var scopeBar = dataGrid.columns[identifier].scopeBar; if (!scopeBar || scopeBar.defaultItem.selected) continue; var value = filterableValueForRecordAndIdentifier(record, identifier); var matchesFilter = scopeBar.selectedItems.some(function(scopeBarItem) { return (scopeBarItem.value === value); }); if (!matchesFilter) return false; } return true; }); } }; WebInspector.TimelinesContentView.prototype.__proto__ = WebInspector.ContentView.prototype;