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