Web Inspector: Timelines UI redesign: use DataGridNode for TimelineView selection...
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Views / LayoutTimelineView.js
1 /*
2  * Copyright (C) 2014, 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.LayoutTimelineView = class LayoutTimelineView extends WebInspector.TimelineView
27 {
28     constructor(timeline, extraArguments)
29     {
30         super(timeline, extraArguments);
31
32         console.assert(timeline.type === WebInspector.TimelineRecord.Type.Layout, timeline);
33
34         this.navigationSidebarTreeOutline.element.classList.add("layout");
35
36         let columns = {name: {}, location: {}, width: {}, height: {}, startTime: {}, totalTime: {}};
37
38         columns.name.title = WebInspector.UIString("Type");
39         columns.name.width = "15%";
40
41         var typeToLabelMap = new Map;
42         for (var key in WebInspector.LayoutTimelineRecord.EventType) {
43             var value = WebInspector.LayoutTimelineRecord.EventType[key];
44             typeToLabelMap.set(value, WebInspector.LayoutTimelineRecord.displayNameForEventType(value));
45         }
46
47         columns.name.scopeBar = WebInspector.TimelineDataGrid.createColumnScopeBar("layout", typeToLabelMap);
48         columns.name.disclosure = true;
49         columns.name.icon = true;
50
51         this._scopeBar = columns.name.scopeBar;
52
53         columns.location.title = WebInspector.UIString("Initiator");
54         columns.location.width = "25%";
55
56         columns.width.title = WebInspector.UIString("Width");
57         columns.width.width = "8%";
58
59         columns.height.title = WebInspector.UIString("Height");
60         columns.height.width = "8%";
61
62         columns.startTime.title = WebInspector.UIString("Start Time");
63         columns.startTime.width = "8%";
64         columns.startTime.aligned = "right";
65
66         columns.totalTime.title = WebInspector.UIString("Duration");
67         columns.totalTime.width = "8%";
68         columns.totalTime.aligned = "right";
69
70         for (var column in columns)
71             columns[column].sortable = true;
72
73         this._dataGrid = new WebInspector.LayoutTimelineDataGrid(this.navigationSidebarTreeOutline, columns);
74         this._dataGrid.addEventListener(WebInspector.TimelineDataGrid.Event.FiltersDidChange, this._dataGridFiltersDidChange, this);
75         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._dataGridNodeSelected, this);
76
77         this._dataGrid.sortColumnIdentifierSetting = new WebInspector.Setting("layout-timeline-view-sort", "startTime");
78         this._dataGrid.sortOrderSetting = new WebInspector.Setting("layout-timeline-view-sort-order", WebInspector.DataGrid.SortOrder.Ascending);
79
80         this._hoveredTreeElement = null;
81         this._hoveredDataGridNode = null;
82         this._showingHighlight = false;
83         this._showingHighlightForRecord = null;
84
85         this._dataGrid.element.addEventListener("mouseover", this._mouseOverDataGrid.bind(this));
86         this._dataGrid.element.addEventListener("mouseleave", this._mouseLeaveDataGrid.bind(this));
87         this.navigationSidebarTreeOutline.element.addEventListener("mouseover", this._mouseOverTreeOutline.bind(this));
88         this.navigationSidebarTreeOutline.element.addEventListener("mouseleave", this._mouseLeaveTreeOutline.bind(this));
89
90         this.element.classList.add("layout");
91         this.addSubview(this._dataGrid);
92
93         timeline.addEventListener(WebInspector.Timeline.Event.RecordAdded, this._layoutTimelineRecordAdded, this);
94
95         this._pendingRecords = [];
96     }
97
98     // Public
99
100     get navigationSidebarTreeOutlineLabel()
101     {
102         return WebInspector.UIString("Records");
103     }
104
105     get selectionPathComponents()
106     {
107         if (!this._dataGrid.selectedNode || this._dataGrid.selectedNode.hidden)
108             return null;
109
110         let timelineDataGridNode = this._dataGrid.selectedNode;
111         console.assert(timelineDataGridNode instanceof WebInspector.TimelineDataGridNode);
112
113         let pathComponent = new WebInspector.TimelineDataGridNodePathComponent(timelineDataGridNode);
114         pathComponent.addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this.dataGridNodePathComponentSelected, this);
115         return [pathComponent];
116     }
117
118     shown()
119     {
120         super.shown();
121
122         this._updateHighlight();
123
124         this._dataGrid.shown();
125     }
126
127     hidden()
128     {
129         this._hideHighlightIfNeeded();
130
131         this._dataGrid.hidden();
132
133         super.hidden();
134     }
135
136     closed()
137     {
138         console.assert(this.representedObject instanceof WebInspector.Timeline);
139         this.representedObject.removeEventListener(null, null, this);
140
141         this._dataGrid.closed();
142     }
143
144     filterDidChange()
145     {
146         super.filterDidChange();
147
148         this._updateHighlight();
149     }
150
151     matchTreeElementAgainstCustomFilters(treeElement)
152     {
153         return this._dataGrid.treeElementMatchesActiveScopeFilters(treeElement);
154     }
155
156     reset()
157     {
158         super.reset();
159
160         this._hideHighlightIfNeeded();
161
162         this._dataGrid.reset();
163
164         this._pendingRecords = [];
165     }
166
167     // Protected
168
169     dataGridNodePathComponentSelected(event)
170     {
171         let dataGridNode = event.data.pathComponent.timelineDataGridNode;
172         console.assert(dataGridNode.dataGrid === this._dataGrid);
173
174         dataGridNode.revealAndSelect();
175     }
176
177     treeElementDeselected(treeElement)
178     {
179         super.treeElementDeselected(treeElement);
180
181         this._updateHighlight();
182     }
183
184     treeElementSelected(treeElement, selectedByUser)
185     {
186         if (this._dataGrid.shouldIgnoreSelectionEvent())
187             return;
188
189         super.treeElementSelected(treeElement, selectedByUser);
190
191         this._updateHighlight();
192     }
193
194     layout()
195     {
196         this._processPendingRecords();
197     }
198
199     // Private
200
201     _processPendingRecords()
202     {
203         if (!this._pendingRecords.length)
204             return;
205
206         for (var layoutTimelineRecord of this._pendingRecords) {
207             var treeElement = new WebInspector.TimelineRecordTreeElement(layoutTimelineRecord, WebInspector.SourceCodeLocation.NameStyle.Short);
208             var dataGridNode = new WebInspector.LayoutTimelineDataGridNode(layoutTimelineRecord, this.zeroTime);
209
210             this._dataGrid.addRowInSortOrder(treeElement, dataGridNode);
211
212             var stack = [{children: layoutTimelineRecord.children, parentTreeElement: treeElement, index: 0}];
213             while (stack.length) {
214                 var entry = stack.lastValue;
215                 if (entry.index >= entry.children.length) {
216                     stack.pop();
217                     continue;
218                 }
219
220                 var childRecord = entry.children[entry.index];
221                 console.assert(childRecord.type === WebInspector.TimelineRecord.Type.Layout, childRecord);
222
223                 var childTreeElement = new WebInspector.TimelineRecordTreeElement(childRecord, WebInspector.SourceCodeLocation.NameStyle.Short);
224                 var layoutDataGridNode = new WebInspector.LayoutTimelineDataGridNode(childRecord, this.zeroTime);
225                 console.assert(entry.parentTreeElement, "entry without parent!");
226                 this._dataGrid.addRowInSortOrder(childTreeElement, layoutDataGridNode, entry.parentTreeElement);
227
228                 if (childTreeElement && childRecord.children.length)
229                     stack.push({children: childRecord.children, parentTreeElement: childTreeElement, index: 0});
230                 ++entry.index;
231             }
232         }
233
234         this._pendingRecords = [];
235     }
236
237     _layoutTimelineRecordAdded(event)
238     {
239         var layoutTimelineRecord = event.data.record;
240         console.assert(layoutTimelineRecord instanceof WebInspector.LayoutTimelineRecord);
241
242         // Only add top-level records, to avoid processing child records multiple times.
243         if (layoutTimelineRecord.parent instanceof WebInspector.LayoutTimelineRecord)
244             return;
245
246         this._pendingRecords.push(layoutTimelineRecord);
247
248         this.needsLayout();
249     }
250
251     _dataGridFiltersDidChange(event)
252     {
253         this.timelineSidebarPanel.updateFilter();
254     }
255
256     _dataGridNodeSelected(event)
257     {
258         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
259     }
260
261     _updateHighlight()
262     {
263         var record = this._hoveredOrSelectedRecord();
264         if (!record) {
265             this._hideHighlightIfNeeded();
266             return;
267         }
268
269         this._showHighlightForRecord(record);
270     }
271
272     _showHighlightForRecord(record)
273     {
274         if (this._showingHighlightForRecord === record)
275             return;
276
277         this._showingHighlightForRecord = record;
278
279         const contentColor = {r: 111, g: 168, b: 220, a: 0.66};
280         const outlineColor = {r: 255, g: 229, b: 153, a: 0.66};
281
282         var quad = record.quad;
283         if (quad) {
284             DOMAgent.highlightQuad(quad.toProtocol(), contentColor, outlineColor);
285             this._showingHighlight = true;
286             return;
287         }
288
289         // This record doesn't have a highlight, so hide any existing highlight.
290         if (this._showingHighlight) {
291             this._showingHighlight = false;
292             DOMAgent.hideHighlight();
293         }
294     }
295
296     _hideHighlightIfNeeded()
297     {
298         this._showingHighlightForRecord = null;
299
300         if (this._showingHighlight) {
301             this._showingHighlight = false;
302             DOMAgent.hideHighlight();
303         }
304     }
305
306     _hoveredOrSelectedRecord()
307     {
308         if (this._hoveredDataGridNode)
309             return this._hoveredDataGridNode.record;
310
311         if (this._hoveredTreeElement)
312             return this._hoveredTreeElement.record;
313
314         if (this._dataGrid.selectedNode) {
315             var treeElement = this._dataGrid.treeElementForDataGridNode(this._dataGrid.selectedNode);
316             if (treeElement.revealed())
317                 return this._dataGrid.selectedNode.record;
318         }
319
320         return null;
321     }
322
323     _mouseOverDataGrid(event)
324     {
325         var hoveredDataGridNode = this._dataGrid.dataGridNodeFromNode(event.target);
326         if (!hoveredDataGridNode)
327             return;
328
329         this._hoveredDataGridNode = hoveredDataGridNode;
330         this._updateHighlight();
331     }
332
333     _mouseLeaveDataGrid(event)
334     {
335         this._hoveredDataGridNode = null;
336         this._updateHighlight();
337     }
338
339     _mouseOverTreeOutline(event)
340     {
341         var hoveredTreeElement = this.navigationSidebarTreeOutline.treeElementFromNode(event.target);
342         if (!hoveredTreeElement)
343             return;
344
345         this._hoveredTreeElement = hoveredTreeElement;
346         this._updateHighlight();
347     }
348
349     _mouseLeaveTreeOutline(event)
350     {
351         this._hoveredTreeElement = null;
352         this._updateHighlight();
353     }
354 };