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