Web Inspector: hook up grid row filtering in the new Timelines UI
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / TimelineView.js
1 /*
2  * Copyright (C) 2013, 2015 Apple Inc. All rights reserved.
3  * Copyright (C) 2015 University of Washington.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 WebInspector.TimelineView = class TimelineView extends WebInspector.ContentView
28 {
29     constructor(representedObject)
30     {
31         super(representedObject);
32
33         // This class should not be instantiated directly. Create a concrete subclass instead.
34         console.assert(this.constructor !== WebInspector.TimelineView && this instanceof WebInspector.TimelineView);
35
36         this.element.classList.add("timeline-view");
37
38         this._zeroTime = 0;
39         this._startTime = 0;
40         this._endTime = 5;
41         this._currentTime = 0;
42     }
43
44     // Public
45
46     get navigationItems()
47     {
48         return this._scopeBar ? [this._scopeBar] : [];
49     }
50
51     get navigationSidebarTreeOutlineScopeBar()
52     {
53         return this._scopeBar;
54     }
55
56     get selectionPathComponents()
57     {
58         // Implemented by sub-classes if needed.
59         return null;
60     }
61
62     get zeroTime()
63     {
64         return this._zeroTime;
65     }
66
67     set zeroTime(x)
68     {
69         x = x || 0;
70
71         if (this._zeroTime === x)
72             return;
73
74         this._zeroTime = x;
75
76         this.needsLayout();
77     }
78
79     get startTime()
80     {
81         return this._startTime;
82     }
83
84     set startTime(x)
85     {
86         x = x || 0;
87
88         if (this._startTime === x)
89             return;
90
91         this._startTime = x;
92
93         this._filterTimesDidChange();
94         this.needsLayout();
95     }
96
97     get endTime()
98     {
99         return this._endTime;
100     }
101
102     set endTime(x)
103     {
104         x = x || 0;
105
106         if (this._endTime === x)
107             return;
108
109         this._endTime = x;
110
111         this._filterTimesDidChange();
112         this.needsLayout();
113     }
114
115     get currentTime()
116     {
117         return this._currentTime;
118     }
119
120     set currentTime(x)
121     {
122         x = x || 0;
123
124         if (this._currentTime === x)
125             return;
126
127         let oldCurrentTime = this._currentTime;
128
129         this._currentTime = x;
130
131         function checkIfLayoutIsNeeded(currentTime)
132         {
133             // Include some wiggle room since the current time markers can be clipped off the ends a bit and still partially visible.
134             const wiggleTime = 0.05; // 50ms
135             return this._startTime - wiggleTime <= currentTime && currentTime <= this._endTime + wiggleTime;
136         }
137
138         if (checkIfLayoutIsNeeded.call(this, oldCurrentTime) || checkIfLayoutIsNeeded.call(this, this._currentTime)) {
139             this._filterTimesDidChange();
140             this.needsLayout();
141         }
142     }
143
144     get filterStartTime()
145     {
146         // Implemented by sub-classes if needed.
147         return this.startTime;
148     }
149
150     get filterEndTime()
151     {
152         // Implemented by sub-classes if needed.
153         return this.endTime;
154     }
155
156     setupDataGrid(dataGrid)
157     {
158         console.assert(!this._timelineDataGrid);
159
160         this._timelineDataGrid = dataGrid;
161         this._timelineDataGrid.filterDelegate = this;
162         this._timelineDataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, () => {
163             this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
164         });
165
166         this._timelineDataGrid.addEventListener(WebInspector.DataGrid.Event.NodeWasFiltered, (event) => {
167             let node = event.data.node;
168             if (!(node instanceof WebInspector.TimelineDataGridNode))
169                 return;
170
171             this.dispatchEventToListeners(WebInspector.TimelineView.Event.RecordWasFiltered, {record: node.record, filtered: node.hidden});
172         });
173
174         this._timelineDataGrid.addEventListener(WebInspector.DataGrid.Event.FilterDidChange, (event) => {
175             this.filterDidChange();
176         });
177     }
178
179     selectRecord(record)
180     {
181         if (!this._timelineDataGrid)
182             return;
183
184         let selectedDataGridNode = this._timelineDataGrid.selectedNode;
185         if (!record) {
186             if (selectedDataGridNode)
187                 selectedDataGridNode.deselect();
188             return;
189         }
190
191         let dataGridNode = this._timelineDataGrid.findNode((node) => node.record === record);
192         console.assert(dataGridNode, "Timeline view has no grid node for record selected in timeline overview.", this, record);
193         if (!dataGridNode || dataGridNode.selected)
194             return;
195
196         // Don't select the record's grid node if one of it's children is already selected.
197         if (selectedDataGridNode && selectedDataGridNode.hasAncestor(dataGridNode))
198             return;
199
200         dataGridNode.revealAndSelect();
201     }
202
203     reset()
204     {
205         // Implemented by sub-classes if needed.
206     }
207
208     updateFilter(filters)
209     {
210         if (!this._timelineDataGrid)
211             return;
212
213         this._timelineDataGrid.filterText = filters ? filters.text : "";
214     }
215
216     matchDataGridNodeAgainstCustomFilters(node)
217     {
218         // Implemented by sub-classes if needed.
219         return true;
220     }
221
222     needsLayout()
223     {
224         // FIXME: needsLayout can be removed once <https://webkit.org/b/150741> is fixed.
225         if (!this.visible)
226             return;
227
228         super.needsLayout();
229     }
230
231     // DataGrid filter delegate
232
233     dataGridMatchNodeAgainstCustomFilters(node)
234     {
235         console.assert(node);
236         if (!this.matchDataGridNodeAgainstCustomFilters(node))
237             return false;
238
239         let startTime = this.filterStartTime;
240         let endTime = this.filterEndTime;
241         let currentTime = this.currentTime;
242
243         function checkTimeBounds(itemStartTime, itemEndTime)
244         {
245             itemStartTime = itemStartTime || currentTime;
246             itemEndTime = itemEndTime || currentTime;
247
248             return startTime <= itemEndTime && itemStartTime <= endTime;
249         }
250
251         if (node instanceof WebInspector.ResourceTimelineDataGridNode) {
252             let resource = node.resource;
253             return checkTimeBounds(resource.requestSentTimestamp, resource.finishedOrFailedTimestamp);
254         }
255
256         if (node instanceof WebInspector.SourceCodeTimelineTimelineDataGridNode) {
257             let sourceCodeTimeline = node.sourceCodeTimeline;
258
259             // Do a quick check of the timeline bounds before we check each record.
260             if (!checkTimeBounds(sourceCodeTimeline.startTime, sourceCodeTimeline.endTime))
261                 return false;
262
263             for (let record of sourceCodeTimeline.records) {
264                 if (checkTimeBounds(record.startTime, record.endTime))
265                     return true;
266             }
267
268             return false;
269         }
270
271         if (node instanceof WebInspector.ProfileNodeDataGridNode) {
272             let profileNode = node.profileNode;
273             if (checkTimeBounds(profileNode.startTime, profileNode.endTime))
274                 return true;
275
276             return false;
277         }
278
279         if (node instanceof WebInspector.TimelineDataGridNode) {
280             let record = node.record;
281             return checkTimeBounds(record.startTime, record.endTime);
282         }
283
284         console.error("Unknown DataGridNode, can't filter by time.");
285         return true;
286     }
287
288     // Protected
289
290     userSelectedRecordFromOverview(timelineRecord)
291     {
292         // Implemented by sub-classes if needed.
293     }
294
295     filterDidChange()
296     {
297         // Implemented by sub-classes if needed.
298     }
299
300     // Private
301
302     _filterTimesDidChange()
303     {
304         if (!this._timelineDataGrid || this._updateFilterTimeout)
305             return;
306
307         function delayedWork()
308         {
309             this._updateFilterTimeout = undefined;
310             this._timelineDataGrid.filterDidChange();
311         }
312
313         this._updateFilterTimeout = setTimeout(delayedWork.bind(this), 0);
314     }
315 };
316
317 WebInspector.TimelineView.Event = {
318     RecordWasFiltered: "record-was-filtered"
319 };