5d2756e9743bddf1b0d58ba39cbff3269f5433bf
[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     shown()
106     {
107         super.shown();
108
109         this._updateHighlight();
110
111         this._dataGrid.shown();
112     }
113
114     hidden()
115     {
116         this._hideHighlightIfNeeded();
117
118         this._dataGrid.hidden();
119
120         super.hidden();
121     }
122
123     closed()
124     {
125         console.assert(this.representedObject instanceof WebInspector.Timeline);
126         this.representedObject.removeEventListener(null, null, this);
127
128         this._dataGrid.closed();
129     }
130
131     filterDidChange()
132     {
133         super.filterDidChange();
134
135         this._updateHighlight();
136     }
137
138     matchTreeElementAgainstCustomFilters(treeElement)
139     {
140         return this._dataGrid.treeElementMatchesActiveScopeFilters(treeElement);
141     }
142
143     reset()
144     {
145         super.reset();
146
147         this._hideHighlightIfNeeded();
148
149         this._dataGrid.reset();
150
151         this._pendingRecords = [];
152     }
153
154     // Protected
155
156     treeElementPathComponentSelected(event)
157     {
158         var dataGridNode = this._dataGrid.dataGridNodeForTreeElement(event.data.pathComponent.generalTreeElement);
159         if (!dataGridNode)
160             return;
161         dataGridNode.revealAndSelect();
162     }
163
164     treeElementDeselected(treeElement)
165     {
166         super.treeElementDeselected(treeElement);
167
168         this._updateHighlight();
169     }
170
171     treeElementSelected(treeElement, selectedByUser)
172     {
173         if (this._dataGrid.shouldIgnoreSelectionEvent())
174             return;
175
176         super.treeElementSelected(treeElement, selectedByUser);
177
178         this._updateHighlight();
179     }
180
181     layout()
182     {
183         this._processPendingRecords();
184     }
185
186     // Private
187
188     _processPendingRecords()
189     {
190         if (!this._pendingRecords.length)
191             return;
192
193         for (var layoutTimelineRecord of this._pendingRecords) {
194             var treeElement = new WebInspector.TimelineRecordTreeElement(layoutTimelineRecord, WebInspector.SourceCodeLocation.NameStyle.Short);
195             var dataGridNode = new WebInspector.LayoutTimelineDataGridNode(layoutTimelineRecord, this.zeroTime);
196
197             this._dataGrid.addRowInSortOrder(treeElement, dataGridNode);
198
199             var stack = [{children: layoutTimelineRecord.children, parentTreeElement: treeElement, index: 0}];
200             while (stack.length) {
201                 var entry = stack.lastValue;
202                 if (entry.index >= entry.children.length) {
203                     stack.pop();
204                     continue;
205                 }
206
207                 var childRecord = entry.children[entry.index];
208                 console.assert(childRecord.type === WebInspector.TimelineRecord.Type.Layout, childRecord);
209
210                 var childTreeElement = new WebInspector.TimelineRecordTreeElement(childRecord, WebInspector.SourceCodeLocation.NameStyle.Short);
211                 var layoutDataGridNode = new WebInspector.LayoutTimelineDataGridNode(childRecord, this.zeroTime);
212                 console.assert(entry.parentTreeElement, "entry without parent!");
213                 this._dataGrid.addRowInSortOrder(childTreeElement, layoutDataGridNode, entry.parentTreeElement);
214
215                 if (childTreeElement && childRecord.children.length)
216                     stack.push({children: childRecord.children, parentTreeElement: childTreeElement, index: 0});
217                 ++entry.index;
218             }
219         }
220
221         this._pendingRecords = [];
222     }
223
224     _layoutTimelineRecordAdded(event)
225     {
226         var layoutTimelineRecord = event.data.record;
227         console.assert(layoutTimelineRecord instanceof WebInspector.LayoutTimelineRecord);
228
229         // Only add top-level records, to avoid processing child records multiple times.
230         if (layoutTimelineRecord.parent instanceof WebInspector.LayoutTimelineRecord)
231             return;
232
233         this._pendingRecords.push(layoutTimelineRecord);
234
235         this.needsLayout();
236     }
237
238     _dataGridFiltersDidChange(event)
239     {
240         this.timelineSidebarPanel.updateFilter();
241     }
242
243     _dataGridNodeSelected(event)
244     {
245         this.dispatchEventToListeners(WebInspector.ContentView.Event.SelectionPathComponentsDidChange);
246     }
247
248     _updateHighlight()
249     {
250         var record = this._hoveredOrSelectedRecord();
251         if (!record) {
252             this._hideHighlightIfNeeded();
253             return;
254         }
255
256         this._showHighlightForRecord(record);
257     }
258
259     _showHighlightForRecord(record)
260     {
261         if (this._showingHighlightForRecord === record)
262             return;
263
264         this._showingHighlightForRecord = record;
265
266         const contentColor = {r: 111, g: 168, b: 220, a: 0.66};
267         const outlineColor = {r: 255, g: 229, b: 153, a: 0.66};
268
269         var quad = record.quad;
270         if (quad) {
271             DOMAgent.highlightQuad(quad.toProtocol(), contentColor, outlineColor);
272             this._showingHighlight = true;
273             return;
274         }
275
276         // This record doesn't have a highlight, so hide any existing highlight.
277         if (this._showingHighlight) {
278             this._showingHighlight = false;
279             DOMAgent.hideHighlight();
280         }
281     }
282
283     _hideHighlightIfNeeded()
284     {
285         this._showingHighlightForRecord = null;
286
287         if (this._showingHighlight) {
288             this._showingHighlight = false;
289             DOMAgent.hideHighlight();
290         }
291     }
292
293     _hoveredOrSelectedRecord()
294     {
295         if (this._hoveredDataGridNode)
296             return this._hoveredDataGridNode.record;
297
298         if (this._hoveredTreeElement)
299             return this._hoveredTreeElement.record;
300
301         if (this._dataGrid.selectedNode) {
302             var treeElement = this._dataGrid.treeElementForDataGridNode(this._dataGrid.selectedNode);
303             if (treeElement.revealed())
304                 return this._dataGrid.selectedNode.record;
305         }
306
307         return null;
308     }
309
310     _mouseOverDataGrid(event)
311     {
312         var hoveredDataGridNode = this._dataGrid.dataGridNodeFromNode(event.target);
313         if (!hoveredDataGridNode)
314             return;
315
316         this._hoveredDataGridNode = hoveredDataGridNode;
317         this._updateHighlight();
318     }
319
320     _mouseLeaveDataGrid(event)
321     {
322         this._hoveredDataGridNode = null;
323         this._updateHighlight();
324     }
325
326     _mouseOverTreeOutline(event)
327     {
328         var hoveredTreeElement = this.navigationSidebarTreeOutline.treeElementFromNode(event.target);
329         if (!hoveredTreeElement)
330             return;
331
332         this._hoveredTreeElement = hoveredTreeElement;
333         this._updateHighlight();
334     }
335
336     _mouseLeaveTreeOutline(event)
337     {
338         this._hoveredTreeElement = null;
339         this._updateHighlight();
340     }
341 };