1e1b687a74082c301383b522f00fa9b4add04cac
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / OverviewTimelineView.js
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
26 WebInspector.OverviewTimelineView = function(recording, extraArguments)
27 {
28     WebInspector.TimelineView.call(this, recording, extraArguments);
29
30     this.navigationSidebarTreeOutline.onselect = this._treeElementSelected.bind(this);
31     this.navigationSidebarTreeOutline.ondeselect = this._treeElementDeselected.bind(this);
32
33     this._recording = recording;
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.navigationSidebarTreeOutline, 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
61 WebInspector.OverviewTimelineView.StyleClassName = "overview";
62
63 WebInspector.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     set secondsPerPixel(x)
80     {
81         this._timelineRuler.secondsPerPixel = x;
82     },
83
84     shown: function()
85     {
86         WebInspector.ContentView.prototype.shown.call(this);
87
88         this._treeOutlineDataGridSynchronizer.synchronize();
89     },
90
91     updateLayout: function()
92     {
93         WebInspector.TimelineView.prototype.updateLayout.call(this);
94
95         var oldZeroTime = this._timelineRuler.zeroTime;
96         var oldStartTime = this._timelineRuler.startTime;
97         var oldEndTime = this._timelineRuler.endTime;
98         var oldCurrentTime = this._currentTimeMarker.time;
99
100         this._timelineRuler.zeroTime = this.zeroTime;
101         this._timelineRuler.startTime = this.startTime;
102         this._timelineRuler.endTime = this.endTime;
103         this._currentTimeMarker.time = this.currentTime;
104
105         // The TimelineDataGridNode graphs are positioned with percentages, so they auto resize with the view.
106         // We only need to refresh the graphs when the any of the times change.
107         if (this.zeroTime !== oldZeroTime || this.startTime !== oldStartTime || this.endTime !== oldEndTime || this.currentTime !== oldCurrentTime) {
108             var dataGridNode = this._dataGrid.children[0];
109             while (dataGridNode) {
110                 dataGridNode.refreshGraph();
111                 dataGridNode = dataGridNode.traverseNextNode(true, null, true);
112             }
113         }
114
115         this._timelineRuler.updateLayout();
116
117         this._processPendingRepresentedObjects();
118     },
119
120     get selectionPathComponents()
121     {
122         var dataGridNode = this._dataGrid.selectedNode;
123         if (!dataGridNode)
124             return null;
125
126         var pathComponents = [];
127
128         while (dataGridNode && !dataGridNode.root) {
129             var treeElement = this._treeOutlineDataGridSynchronizer.treeElementForDataGridNode(dataGridNode);
130             console.assert(treeElement);
131             if (!treeElement)
132                 break;
133
134             if (treeElement.hidden)
135                 return null;
136
137             var pathComponent = new WebInspector.GeneralTreeElementPathComponent(treeElement);
138             pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.treeElementPathComponentSelected, this);
139             pathComponents.unshift(pathComponent);
140             dataGridNode = dataGridNode.parent;
141         }
142
143         return pathComponents;
144     },
145
146     // Protected
147
148     treeElementPathComponentSelected: function(event)
149     {
150         var dataGridNode = this._treeOutlineDataGridSynchronizer.dataGridNodeForTreeElement(event.data.pathComponent.generalTreeElement);
151         if (!dataGridNode)
152             return;
153         dataGridNode.revealAndSelect();
154     },
155
156     // Private
157
158     _compareTreeElementsByDetails: function(a, b)
159     {
160         if (a instanceof WebInspector.SourceCodeTimelineTreeElement && b instanceof WebInspector.ResourceTreeElement)
161             return -1;
162
163         if (a instanceof WebInspector.ResourceTreeElement && b instanceof WebInspector.SourceCodeTimelineTreeElement)
164             return 1;
165
166         if (a instanceof WebInspector.SourceCodeTimelineTreeElement && b instanceof WebInspector.SourceCodeTimelineTreeElement) {
167             var aTimeline = a.sourceCodeTimeline;
168             var bTimeline = b.sourceCodeTimeline;
169
170             if (!aTimeline.sourceCodeLocation && !bTimeline.sourceCodeLocation) {
171                 if (aTimeline.recordType !== bTimeline.recordType)
172                     return aTimeline.recordType.localeCompare(bTimeline.recordType);
173
174                 return a.mainTitle.localeCompare(b.mainTitle);
175             }
176
177             if (!aTimeline.sourceCodeLocation || !bTimeline.sourceCodeLocation)
178                 return !!aTimeline.sourceCodeLocation - !!bTimeline.sourceCodeLocation;
179
180             if (aTimeline.sourceCodeLocation.lineNumber !== bTimeline.sourceCodeLocation.lineNumber)
181                 return aTimeline.sourceCodeLocation.lineNumber - bTimeline.sourceCodeLocation.lineNumber;
182
183             return aTimeline.sourceCodeLocation.columnNumber - bTimeline.sourceCodeLocation.columnNumber;
184         }
185
186         // Fallback to comparing by start time for ResourceTreeElement or anything else.
187         return this._compareTreeElementsByStartTime(a, b);
188     },
189
190     _compareTreeElementsByStartTime: function(a, b)
191     {
192         function getStartTime(treeElement)
193         {
194             if (treeElement instanceof WebInspector.ResourceTreeElement)
195                 return treeElement.resource.firstTimestamp;
196             if (treeElement instanceof WebInspector.SourceCodeTimelineTreeElement)
197                 return treeElement.sourceCodeTimeline.startTime;
198
199             console.error("Unknown tree element.");
200             return 0;
201         }
202
203         var result = getStartTime(a) - getStartTime(b);
204         if (result)
205             return result;
206
207         // Fallback to comparing titles.
208         return a.mainTitle.localeCompare(b.mainTitle);
209     },
210
211     _insertTreeElement: function(treeElement, parentTreeElement)
212     {
213         console.assert(treeElement);
214         console.assert(!treeElement.parent);
215         console.assert(parentTreeElement);
216
217         parentTreeElement.insertChild(treeElement, insertionIndexForObjectInListSortedByFunction(treeElement, parentTreeElement.children, this._compareTreeElementsByStartTime.bind(this)));
218     },
219
220     _addResourceToTreeIfNeeded: function(resource)
221     {
222         console.assert(resource);
223         if (!resource)
224             return null;
225
226         var treeElement = this.navigationSidebarTreeOutline.findTreeElement(resource);
227         if (treeElement)
228             return treeElement;
229
230         var parentFrame = resource.parentFrame;
231         if (!parentFrame)
232             return;
233
234         var expandedByDefault = false;
235         if (parentFrame.mainResource === resource || parentFrame.provisionalMainResource === resource) {
236             parentFrame = parentFrame.parentFrame;
237             expandedByDefault = !parentFrame; // Main frame expands by default.
238         }
239
240         var resourceTreeElement = new WebInspector.ResourceTreeElement(resource);
241         if (expandedByDefault)
242             resourceTreeElement.expand();
243
244         var resourceTimelineRecord = this._networkTimeline ? this._networkTimeline.recordForResource(resource) : null;
245         if (!resourceTimelineRecord)
246             resourceTimelineRecord = new WebInspector.ResourceTimelineRecord(resource);
247
248         var resourceDataGridNode = new WebInspector.ResourceTimelineDataGridNode(resourceTimelineRecord, true, this);
249         this._treeOutlineDataGridSynchronizer.associate(resourceTreeElement, resourceDataGridNode);
250
251         var parentTreeElement = this.navigationSidebarTreeOutline;
252         if (parentFrame) {
253             // Find the parent main resource, adding it if needed, to append this resource as a child.
254             var parentResource = parentFrame.provisionalMainResource || parentFrame.mainResource;
255
256             parentTreeElement = this._addResourceToTreeIfNeeded(parentResource);
257             console.assert(parentTreeElement);
258             if (!parentTreeElement)
259                 return;
260         }
261
262         this._insertTreeElement(resourceTreeElement, parentTreeElement);
263
264         return resourceTreeElement;
265     },
266
267     _addSourceCodeTimeline: function(sourceCodeTimeline)
268     {
269         var parentTreeElement = sourceCodeTimeline.sourceCodeLocation ? this._addResourceToTreeIfNeeded(sourceCodeTimeline.sourceCode) : this.navigationSidebarTreeOutline;
270         console.assert(parentTreeElement);
271         if (!parentTreeElement)
272             return;
273
274         var sourceCodeTimelineTreeElement = new WebInspector.SourceCodeTimelineTreeElement(sourceCodeTimeline);
275         var sourceCodeTimelineDataGridNode = new WebInspector.SourceCodeTimelineTimelineDataGridNode(sourceCodeTimeline, this);
276
277         this._treeOutlineDataGridSynchronizer.associate(sourceCodeTimelineTreeElement, sourceCodeTimelineDataGridNode);
278         this._insertTreeElement(sourceCodeTimelineTreeElement, parentTreeElement);
279     },
280
281     _processPendingRepresentedObjects: function()
282     {
283         if (!this._pendingRepresentedObjects || !this._pendingRepresentedObjects.length)
284             return;
285
286         for (var representedObject of this._pendingRepresentedObjects) {
287             if (representedObject instanceof WebInspector.Resource)
288                 this._addResourceToTreeIfNeeded(representedObject);
289             else if (representedObject instanceof WebInspector.SourceCodeTimeline)
290                 this._addSourceCodeTimeline(representedObject);
291             else
292                 console.error("Unknown represented object");
293         }
294
295         this._pendingRepresentedObjects = [];
296     },
297
298     _networkTimelineRecordAdded: function(event)
299     {
300         var resourceTimelineRecord = event.data.record;
301         console.assert(resourceTimelineRecord instanceof WebInspector.ResourceTimelineRecord);
302
303         this._pendingRepresentedObjects.push(resourceTimelineRecord.resource);
304
305         this.needsLayout();
306
307         // We don't expect to have any source code timelines yet. Those should be added with _sourceCodeTimelineAdded.
308         console.assert(!this._recording.sourceCodeTimelinesForSourceCode(resourceTimelineRecord.resource).length);
309     },
310
311     _sourceCodeTimelineAdded: function(event)
312     {
313         var sourceCodeTimeline = event.data.sourceCodeTimeline;
314         console.assert(sourceCodeTimeline);
315         if (!sourceCodeTimeline)
316             return;
317
318         this._pendingRepresentedObjects.push(sourceCodeTimeline);
319
320         this.needsLayout();
321     },
322
323     _dataGridNodeSelected: function(event)
324     {
325         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
326     },
327
328     _treeElementDeselected: function(treeElement)
329     {
330         if (treeElement.status)
331             treeElement.status = "";
332     },
333
334     _treeElementSelected: function(treeElement, selectedByUser)
335     {
336         if (!this.timelineSidebarPanel.canShowDifferentContentView())
337             return;
338
339         if (treeElement instanceof WebInspector.FolderTreeElement)
340             return;
341
342         if (treeElement instanceof WebInspector.ResourceTreeElement || treeElement instanceof WebInspector.ScriptTreeElement) {
343             WebInspector.resourceSidebarPanel.showSourceCode(treeElement.representedObject);
344             this._updateTreeElementWithCloseButton(treeElement);
345             return;
346         }
347
348         if (!(treeElement instanceof WebInspector.SourceCodeTimelineTreeElement)) {
349             console.error("Unknown tree element selected.");
350             return;
351         }
352
353         if (!treeElement.sourceCodeTimeline.sourceCodeLocation) {
354             this.timelineSidebarPanel.showTimelineOverview();
355             this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
356             return;
357         }
358
359         WebInspector.resourceSidebarPanel.showOriginalOrFormattedSourceCodeLocation(treeElement.sourceCodeTimeline.sourceCodeLocation);
360         this._updateTreeElementWithCloseButton(treeElement);
361     },
362
363     _updateTreeElementWithCloseButton: function(treeElement)
364     {
365         if (this._closeStatusButton) {
366             treeElement.status = this._closeStatusButton.element;
367             return;
368         }
369
370         wrappedSVGDocument(platformImagePath("Close.svg"), null, WebInspector.UIString("Close resource view"), function(element) {
371             this._closeStatusButton = new WebInspector.TreeElementStatusButton(element);
372             this._closeStatusButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._closeStatusButtonClicked, this);
373             if (treeElement === this.navigationSidebarTreeOutline.selectedTreeElement)
374                 this._updateTreeElementWithCloseButton(treeElement);
375         }.bind(this));
376     },
377
378     _closeStatusButtonClicked: function(event)
379     {
380         this.navigationSidebarTreeOutline.selectedTreeElement.deselect();
381         this.timelineSidebarPanel.showTimelineOverview();
382     }
383 };