1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26WebInspector.OverviewTimelineView = function(recording) 27{ 28 WebInspector.TimelineView.call(this); 29 30 this._recording = recording; 31 32 this.navigationSidebarTreeOutline.onselect = this._treeElementSelected.bind(this); 33 this.navigationSidebarTreeOutline.ondeselect = this._treeElementDeselected.bind(this); 34 35 var columns = {"graph": {width: "100%"}}; 36 37 this._dataGrid = new WebInspector.DataGrid(columns); 38 this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this); 39 this._dataGrid.element.classList.add("no-header"); 40 41 this._treeOutlineDataGridSynchronizer = new WebInspector.TreeOutlineDataGridSynchronizer(this._contentTreeOutline, this._dataGrid); 42 43 this._timelineRuler = new WebInspector.TimelineRuler; 44 this._timelineRuler.allowsClippedLabels = true; 45 this.element.appendChild(this._timelineRuler.element); 46 47 this._currentTimeMarker = new WebInspector.TimelineMarker(0, WebInspector.TimelineMarker.Type.CurrentTime); 48 this._timelineRuler.addMarker(this._currentTimeMarker); 49 50 this.element.classList.add(WebInspector.OverviewTimelineView.StyleClassName); 51 this.element.appendChild(this._dataGrid.element); 52 53 this._networkTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Network); 54 this._networkTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this); 55 56 recording.addEventListener(WebInspector.TimelineRecording.Event.SourceCodeTimelineAdded, this._sourceCodeTimelineAdded, this); 57 58 this._pendingRepresentedObjects = []; 59}; 60 61WebInspector.OverviewTimelineView.StyleClassName = "overview"; 62 63WebInspector.OverviewTimelineView.prototype = { 64 constructor: WebInspector.OverviewTimelineView, 65 __proto__: WebInspector.TimelineView.prototype, 66 67 // Public 68 69 get navigationSidebarTreeOutlineLabel() 70 { 71 return WebInspector.UIString("Timeline Events"); 72 }, 73 74 get secondsPerPixel() 75 { 76 return this._timelineRuler.secondsPerPixel; 77 }, 78 79 shown: function() 80 { 81 WebInspector.TimelineView.prototype.shown.call(this); 82 83 this._treeOutlineDataGridSynchronizer.synchronize(); 84 }, 85 86 updateLayout: function() 87 { 88 WebInspector.TimelineView.prototype.updateLayout.call(this); 89 90 var oldZeroTime = this._timelineRuler.zeroTime; 91 var oldStartTime = this._timelineRuler.startTime; 92 var oldEndTime = this._timelineRuler.endTime; 93 var oldCurrentTime = this._currentTimeMarker.time; 94 95 this._timelineRuler.zeroTime = this.zeroTime; 96 this._timelineRuler.startTime = this.startTime; 97 this._timelineRuler.endTime = this.endTime; 98 this._currentTimeMarker.time = this.currentTime; 99 100 // The TimelineDataGridNode graphs are positioned with percentages, so they auto resize with the view. 101 // We only need to refresh the graphs when the any of the times change. 102 if (this.zeroTime !== oldZeroTime || this.startTime !== oldStartTime || this.endTime !== oldEndTime || this.currentTime !== oldCurrentTime) { 103 var dataGridNode = this._dataGrid.children[0]; 104 while (dataGridNode) { 105 dataGridNode.refreshGraph(); 106 dataGridNode = dataGridNode.traverseNextNode(true, null, true); 107 } 108 } 109 110 if (this.currentTime !== oldCurrentTime) { 111 var selectedTreeElement = this.navigationSidebarTreeOutline.selectedTreeElement; 112 var selectionWasHidden = selectedTreeElement && selectedTreeElement.hidden; 113 114 // Check the filters again since the current time change might have revealed this node. Start and end time changes are handled by TimelineContentView. 115 WebInspector.timelineSidebarPanel.updateFilter(); 116 117 if (selectedTreeElement && selectedTreeElement.hidden !== selectionWasHidden) 118 this.dispatchEventToListeners(WebInspector.TimelineView.Event.SelectionPathComponentsDidChange); 119 } 120 121 this._timelineRuler.updateLayout(); 122 123 this._processPendingRepresentedObjects(); 124 }, 125 126 get selectionPathComponents() 127 { 128 var dataGridNode = this._dataGrid.selectedNode; 129 if (!dataGridNode) 130 return null; 131 132 var pathComponents = []; 133 134 while (dataGridNode && !dataGridNode.root) { 135 var treeElement = this._treeOutlineDataGridSynchronizer.treeElementForDataGridNode(dataGridNode); 136 console.assert(treeElement); 137 if (!treeElement) 138 break; 139 140 if (treeElement.hidden) 141 return null; 142 143 var pathComponent = new WebInspector.GeneralTreeElementPathComponent(treeElement); 144 pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.treeElementPathComponentSelected, this); 145 pathComponents.unshift(pathComponent); 146 dataGridNode = dataGridNode.parent; 147 } 148 149 return pathComponents; 150 }, 151 152 // Protected 153 154 treeElementPathComponentSelected: function(event) 155 { 156 var dataGridNode = this._treeOutlineDataGridSynchronizer.dataGridNodeForTreeElement(event.data.pathComponent.generalTreeElement); 157 if (!dataGridNode) 158 return; 159 dataGridNode.revealAndSelect(); 160 }, 161 162 // Private 163 164 _compareTreeElementsByDetails: function(a, b) 165 { 166 if (a instanceof WebInspector.SourceCodeTimelineTreeElement && b instanceof WebInspector.ResourceTreeElement) 167 return -1; 168 169 if (a instanceof WebInspector.ResourceTreeElement && b instanceof WebInspector.SourceCodeTimelineTreeElement) 170 return 1; 171 172 if (a instanceof WebInspector.SourceCodeTimelineTreeElement && b instanceof WebInspector.SourceCodeTimelineTreeElement) { 173 aTimeline = a.sourceCodeTimeline; 174 bTimeline = b.sourceCodeTimeline; 175 176 if (!aTimeline.sourceCodeLocation && !bTimeline.sourceCodeLocation) { 177 if (aTimeline.recordType !== bTimeline.recordType) 178 return aTimeline.recordType.localeCompare(bTimeline.recordType); 179 180 return a.mainTitle.localeCompare(b.mainTitle); 181 } 182 183 if (!aTimeline.sourceCodeLocation || !bTimeline.sourceCodeLocation) 184 return !!aTimeline.sourceCodeLocation - !!bTimeline.sourceCodeLocation; 185 186 if (aTimeline.sourceCodeLocation.lineNumber !== bTimeline.sourceCodeLocation.lineNumber) 187 return aTimeline.sourceCodeLocation.lineNumber - bTimeline.sourceCodeLocation.lineNumber; 188 189 return aTimeline.sourceCodeLocation.columnNumber - bTimeline.sourceCodeLocation.columnNumber; 190 } 191 192 // Fallback to comparing by start time for ResourceTreeElement or anything else. 193 return this._compareTreeElementsByStartTime(a, b); 194 }, 195 196 _compareTreeElementsByStartTime: function(a, b) 197 { 198 function getStartTime(treeElement) 199 { 200 if (treeElement instanceof WebInspector.ResourceTreeElement) 201 return treeElement.resource.firstTimestamp; 202 if (treeElement instanceof WebInspector.SourceCodeTimelineTreeElement) 203 return treeElement.sourceCodeTimeline.startTime; 204 205 console.error("Unknown tree element."); 206 return 0; 207 } 208 209 var result = getStartTime(a) - getStartTime(b); 210 if (result) 211 return result; 212 213 // Fallback to comparing titles. 214 return a.mainTitle.localeCompare(b.mainTitle); 215 }, 216 217 _insertTreeElement: function(treeElement, parentTreeElement) 218 { 219 console.assert(treeElement); 220 console.assert(!treeElement.parent); 221 console.assert(parentTreeElement); 222 223 parentTreeElement.insertChild(treeElement, insertionIndexForObjectInListSortedByFunction(treeElement, parentTreeElement.children, this._compareTreeElementsByStartTime.bind(this))); 224 }, 225 226 _addResourceToTreeIfNeeded: function(resource) 227 { 228 console.assert(resource); 229 if (!resource) 230 return null; 231 232 var treeElement = this.navigationSidebarTreeOutline.findTreeElement(resource); 233 if (treeElement) 234 return treeElement; 235 236 var parentFrame = resource.parentFrame; 237 if (!parentFrame) 238 return; 239 240 var expandedByDefault = false; 241 if (parentFrame.mainResource === resource || parentFrame.provisionalMainResource === resource) { 242 parentFrame = parentFrame.parentFrame; 243 expandedByDefault = !parentFrame; // Main frame expands by default. 244 } 245 246 var resourceTreeElement = new WebInspector.ResourceTreeElement(resource); 247 if (expandedByDefault) 248 resourceTreeElement.expand(); 249 250 var resourceTimelineRecord = this._networkTimeline ? this._networkTimeline.recordForResource(resource) : null; 251 if (!resourceTimelineRecord) 252 resourceTimelineRecord = new WebInspector.ResourceTimelineRecord(resource); 253 254 var resourceDataGridNode = new WebInspector.ResourceTimelineDataGridNode(resourceTimelineRecord, true, this); 255 this._treeOutlineDataGridSynchronizer.associate(resourceTreeElement, resourceDataGridNode); 256 257 var parentTreeElement = this.navigationSidebarTreeOutline; 258 if (parentFrame) { 259 // Find the parent main resource, adding it if needed, to append this resource as a child. 260 var parentResource = parentFrame.provisionalMainResource || parentFrame.mainResource; 261 262 parentTreeElement = this._addResourceToTreeIfNeeded(parentResource); 263 console.assert(parentTreeElement); 264 if (!parentTreeElement) 265 return; 266 } 267 268 this._insertTreeElement(resourceTreeElement, parentTreeElement); 269 270 return resourceTreeElement; 271 }, 272 273 _addSourceCodeTimeline: function(sourceCodeTimeline) 274 { 275 var parentTreeElement = sourceCodeTimeline.sourceCodeLocation ? this._addResourceToTreeIfNeeded(sourceCodeTimeline.sourceCode) : this.navigationSidebarTreeOutline; 276 console.assert(parentTreeElement); 277 if (!parentTreeElement) 278 return; 279 280 var sourceCodeTimelineTreeElement = new WebInspector.SourceCodeTimelineTreeElement(sourceCodeTimeline); 281 var sourceCodeTimelineDataGridNode = new WebInspector.SourceCodeTimelineTimelineDataGridNode(sourceCodeTimeline, this); 282 283 this._treeOutlineDataGridSynchronizer.associate(sourceCodeTimelineTreeElement, sourceCodeTimelineDataGridNode); 284 this._insertTreeElement(sourceCodeTimelineTreeElement, parentTreeElement); 285 }, 286 287 _processPendingRepresentedObjects: function() 288 { 289 if (!this._pendingRepresentedObjects || !this._pendingRepresentedObjects.length) 290 return; 291 292 for (var representedObject of this._pendingRepresentedObjects) { 293 if (representedObject instanceof WebInspector.Resource) 294 this._addResourceToTreeIfNeeded(representedObject); 295 else if (representedObject instanceof WebInspector.SourceCodeTimeline) 296 this._addSourceCodeTimeline(representedObject); 297 else 298 console.error("Unknown represented object"); 299 } 300 301 this._pendingRepresentedObjects = []; 302 }, 303 304 _networkTimelineRecordAdded: function(event) 305 { 306 var resourceTimelineRecord = event.data.record; 307 console.assert(resourceTimelineRecord instanceof WebInspector.ResourceTimelineRecord); 308 309 this._pendingRepresentedObjects.push(resourceTimelineRecord.resource); 310 311 this.needsLayout(); 312 313 // We don't expect to have any source code timelines yet. Those should be added with _sourceCodeTimelineAdded. 314 console.assert(!this._recording.sourceCodeTimelinesForSourceCode(resourceTimelineRecord.resource).length); 315 }, 316 317 _sourceCodeTimelineAdded: function(event) 318 { 319 var sourceCodeTimeline = event.data.sourceCodeTimeline; 320 console.assert(sourceCodeTimeline); 321 if (!sourceCodeTimeline) 322 return; 323 324 this._pendingRepresentedObjects.push(sourceCodeTimeline); 325 326 this.needsLayout(); 327 }, 328 329 _dataGridNodeSelected: function(event) 330 { 331 this.dispatchEventToListeners(WebInspector.TimelineView.Event.SelectionPathComponentsDidChange); 332 }, 333 334 _treeElementDeselected: function(treeElement) 335 { 336 if (treeElement.status) 337 treeElement.status = ""; 338 }, 339 340 _treeElementSelected: function(treeElement, selectedByUser) 341 { 342 if (!WebInspector.timelineSidebarPanel.canShowDifferentContentView()) 343 return; 344 345 if (treeElement instanceof WebInspector.FolderTreeElement) 346 return; 347 348 if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement) { 349 WebInspector.resourceSidebarPanel.showSourceCode(treeElement.representedObject); 350 this._updateTreeElementWithCloseButton(treeElement); 351 return; 352 } 353 354 if (!(treeElement instanceof WebInspector.SourceCodeTimelineTreeElement)) { 355 console.error("Unknown tree element selected."); 356 return; 357 } 358 359 if (!treeElement.sourceCodeTimeline.sourceCodeLocation) { 360 WebInspector.timelineSidebarPanel.showTimelineOverview(); 361 return; 362 } 363 364 WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(treeElement.sourceCodeTimeline.sourceCodeLocation); 365 this._updateTreeElementWithCloseButton(treeElement); 366 }, 367 368 _updateTreeElementWithCloseButton: function(treeElement) 369 { 370 if (this._closeStatusButton) { 371 treeElement.status = this._closeStatusButton.element; 372 return; 373 } 374 375 wrappedSVGDocument(platformImagePath("Close.svg"), null, WebInspector.UIString("Close resource view"), function(element) { 376 this._closeStatusButton = new WebInspector.TreeElementStatusButton(element); 377 this._closeStatusButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._closeStatusButtonClicked, this); 378 if (treeElement === this.navigationSidebarTreeOutline.selectedTreeElement) 379 this._updateTreeElementWithCloseButton(treeElement); 380 }.bind(this)); 381 }, 382 383 _closeStatusButtonClicked: function(event) 384 { 385 this.navigationSidebarTreeOutline.selectedTreeElement.deselect(); 386 WebInspector.timelineSidebarPanel.showTimelineOverview(); 387 } 388}; 389