41541d7287b8d5b1c778f9cb4fad7309c24c88a8
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / OverviewTimelineView.js
1 /*
2  * Copyright (C) 2013, 2015 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 = class OverviewTimelineView extends WebInspector.TimelineView
27 {
28     constructor(recording, extraArguments)
29     {
30         super(recording, extraArguments);
31
32         this._recording = recording;
33
34         let columns = {name: {}, graph: {}};
35
36         columns.name.title = WebInspector.UIString("Name");
37         columns.name.width = "20%";
38         columns.name.icon = true;
39         columns.name.disclosure = true;
40
41         this._timelineRuler = new WebInspector.TimelineRuler;
42         this._timelineRuler.allowsClippedLabels = true;
43
44         columns.graph.width = "80%";
45         columns.graph.headerView = this._timelineRuler;
46
47         this._dataGrid = new WebInspector.DataGrid(columns);
48         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
49
50         this._currentTimeMarker = new WebInspector.TimelineMarker(0, WebInspector.TimelineMarker.Type.CurrentTime);
51         this._timelineRuler.addMarker(this._currentTimeMarker);
52
53         this.element.classList.add("overview");
54         this.addSubview(this._dataGrid);
55
56         this._networkTimeline = recording.timelines.get(WebInspector.TimelineRecord.Type.Network);
57         if (this._networkTimeline)
58             this._networkTimeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._networkTimelineRecordAdded, this);
59
60         recording.addEventListener(WebInspector.TimelineRecording.Event.SourceCodeTimelineAdded, this._sourceCodeTimelineAdded, this);
61         recording.addEventListener(WebInspector.TimelineRecording.Event.MarkerAdded, this._markerAdded, this);
62         recording.addEventListener(WebInspector.TimelineRecording.Event.Reset, this._recordingReset, this);
63
64         this._pendingRepresentedObjects = [];
65         this._resourceDataGridNodeMap = new Map;
66     }
67
68     // Public
69
70     get secondsPerPixel()
71     {
72         return this._timelineRuler.secondsPerPixel;
73     }
74
75     set secondsPerPixel(x)
76     {
77         this._timelineRuler.secondsPerPixel = x;
78     }
79
80     shown()
81     {
82         super.shown();
83
84         this._timelineRuler.updateLayout(WebInspector.View.LayoutReason.Resize);
85     }
86
87     closed()
88     {
89         if (this._networkTimeline)
90             this._networkTimeline.removeEventListener(null, null, this);
91         this._recording.removeEventListener(null, null, this);
92     }
93
94     get selectionPathComponents()
95     {
96         let dataGridNode = this._dataGrid.selectedNode;
97         if (!dataGridNode || dataGridNode.hidden)
98             return null;
99
100         let pathComponents = [];
101
102         while (dataGridNode && !dataGridNode.root) {
103             console.assert(dataGridNode instanceof WebInspector.TimelineDataGridNode);
104             if (dataGridNode.hidden)
105                 return null;
106
107             let pathComponent = new WebInspector.TimelineDataGridNodePathComponent(dataGridNode);
108             pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.dataGridNodePathComponentSelected, this);
109             pathComponents.unshift(pathComponent);
110             dataGridNode = dataGridNode.parent;
111         }
112
113         return pathComponents;
114     }
115
116     reset()
117     {
118         super.reset();
119
120         this._dataGrid.removeChildren();
121
122         this._pendingRepresentedObjects = [];
123     }
124
125     // Protected
126
127     dataGridNodePathComponentSelected(event)
128     {
129         let dataGridNode = event.data.pathComponent.timelineDataGridNode;
130         console.assert(dataGridNode.dataGrid === this._dataGrid);
131
132         dataGridNode.revealAndSelect();
133     }
134
135     layout()
136     {
137         let oldZeroTime = this._timelineRuler.zeroTime;
138         let oldStartTime = this._timelineRuler.startTime;
139         let oldEndTime = this._timelineRuler.endTime;
140         let oldCurrentTime = this._currentTimeMarker.time;
141
142         this._timelineRuler.zeroTime = this.zeroTime;
143         this._timelineRuler.startTime = this.startTime;
144         this._timelineRuler.endTime = this.endTime;
145         this._currentTimeMarker.time = this.currentTime;
146
147         // The TimelineDataGridNode graphs are positioned with percentages, so they auto resize with the view.
148         // We only need to refresh the graphs when the any of the times change.
149         if (this.zeroTime !== oldZeroTime || this.startTime !== oldStartTime || this.endTime !== oldEndTime || this.currentTime !== oldCurrentTime) {
150             let dataGridNode = this._dataGrid.children[0];
151             while (dataGridNode) {
152                 dataGridNode.refreshGraph();
153                 dataGridNode = dataGridNode.traverseNextNode(true, null, true);
154             }
155         }
156
157         this._processPendingRepresentedObjects();
158     }
159
160     // Private
161
162     _compareDataGridNodesByStartTime(a, b)
163     {
164         function getStartTime(dataGridNode)
165         {
166             if (dataGridNode instanceof WebInspector.ResourceTimelineDataGridNode)
167                 return dataGridNode.resource.firstTimestamp;
168             if (dataGridNode instanceof WebInspector.SourceCodeTimelineTimelineDataGridNode)
169                 return dataGridNode.sourceCodeTimeline.startTime;
170
171             console.error("Unknown data grid node.", dataGridNode);
172             return 0;
173         }
174
175         let result = getStartTime(a) - getStartTime(b);
176         if (result)
177             return result;
178
179         // Fallback to comparing titles.
180         return a.displayName().localeCompare(b.displayName());
181     }
182
183     _insertDataGridNode(dataGridNode, parentDataGridNode)
184     {
185         console.assert(dataGridNode);
186         console.assert(!dataGridNode.parent);
187
188         if (parentDataGridNode)
189             parentDataGridNode.insertChild(dataGridNode, insertionIndexForObjectInListSortedByFunction(dataGridNode, parentDataGridNode.children, this._compareDataGridNodesByStartTime.bind(this)));
190         else
191             this._dataGrid.appendChild(dataGridNode);
192     }
193
194     _addResourceToDataGridIfNeeded(resource)
195     {
196         console.assert(resource);
197         if (!resource)
198             return null;
199
200         // FIXME: replace with this._dataGrid.findDataGridNode(resource) once <https://webkit.org/b/155305> is fixed.
201         let dataGridNode = this._resourceDataGridNodeMap.get(resource);
202         if (dataGridNode)
203             return dataGridNode;
204
205         let parentFrame = resource.parentFrame;
206         if (!parentFrame)
207             return;
208
209         let resourceTimelineRecord = this._networkTimeline ? this._networkTimeline.recordForResource(resource) : null;
210         if (!resourceTimelineRecord)
211             resourceTimelineRecord = new WebInspector.ResourceTimelineRecord(resource);
212
213         let resourceDataGridNode = new WebInspector.ResourceTimelineDataGridNode(resourceTimelineRecord, true, this);
214         this._resourceDataGridNodeMap.set(resource, resourceDataGridNode);
215
216         let expandedByDefault = false;
217         if (parentFrame.mainResource === resource || parentFrame.provisionalMainResource === resource) {
218             parentFrame = parentFrame.parentFrame;
219             expandedByDefault = !parentFrame; // Main frame expands by default.
220         }
221
222         if (expandedByDefault)
223             resourceDataGridNode.expand();
224
225         let parentDataGridNode = null;
226         if (parentFrame) {
227             // Find the parent main resource, adding it if needed, to append this resource as a child.
228             let parentResource = parentFrame.provisionalMainResource || parentFrame.mainResource;
229
230             parentDataGridNode = this._addResourceToDataGridIfNeeded(parentResource);
231             console.assert(parentDataGridNode);
232             if (!parentDataGridNode)
233                 return;
234         }
235
236         this._insertDataGridNode(resourceDataGridNode, parentDataGridNode);
237
238         return resourceDataGridNode;
239     }
240
241     _addSourceCodeTimeline(sourceCodeTimeline)
242     {
243         let parentDataGridNode = sourceCodeTimeline.sourceCodeLocation ? this._addResourceToDataGridIfNeeded(sourceCodeTimeline.sourceCode) : null;
244         let sourceCodeTimelineDataGridNode = new WebInspector.SourceCodeTimelineTimelineDataGridNode(sourceCodeTimeline, this);
245         this._resourceDataGridNodeMap.set(sourceCodeTimeline, sourceCodeTimelineDataGridNode);
246
247         this._insertDataGridNode(sourceCodeTimelineDataGridNode, parentDataGridNode);
248     }
249
250     _processPendingRepresentedObjects()
251     {
252         if (!this._pendingRepresentedObjects.length)
253             return;
254
255         for (var representedObject of this._pendingRepresentedObjects) {
256             if (representedObject instanceof WebInspector.Resource)
257                 this._addResourceToDataGridIfNeeded(representedObject);
258             else if (representedObject instanceof WebInspector.SourceCodeTimeline)
259                 this._addSourceCodeTimeline(representedObject);
260             else
261                 console.error("Unknown represented object");
262         }
263
264         this._pendingRepresentedObjects = [];
265     }
266
267     _networkTimelineRecordAdded(event)
268     {
269         var resourceTimelineRecord = event.data.record;
270         console.assert(resourceTimelineRecord instanceof WebInspector.ResourceTimelineRecord);
271
272         this._pendingRepresentedObjects.push(resourceTimelineRecord.resource);
273
274         this.needsLayout();
275
276         // We don't expect to have any source code timelines yet. Those should be added with _sourceCodeTimelineAdded.
277         console.assert(!this._recording.sourceCodeTimelinesForSourceCode(resourceTimelineRecord.resource).length);
278     }
279
280     _sourceCodeTimelineAdded(event)
281     {
282         var sourceCodeTimeline = event.data.sourceCodeTimeline;
283         console.assert(sourceCodeTimeline);
284         if (!sourceCodeTimeline)
285             return;
286
287         this._pendingRepresentedObjects.push(sourceCodeTimeline);
288
289         this.needsLayout();
290     }
291
292     _markerAdded(event)
293     {
294         this._timelineRuler.addMarker(event.data.marker);
295     }
296
297     _dataGridNodeSelected(event)
298     {
299         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
300     }
301
302     _recordingReset(event)
303     {
304         this._timelineRuler.clearMarkers();
305         this._timelineRuler.addMarker(this._currentTimeMarker);
306     }
307 };