26d28f8f3a03dba97177272d3ae13c443c1627ef
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / LayerTreeSidebarPanel.js
1 /*
2  * Copyright (C) 2013 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.LayerTreeSidebarPanel = function() {
27     WebInspector.DOMDetailsSidebarPanel.call(this, "layer-tree", WebInspector.UIString("Layers"), WebInspector.UIString("Layer"), "Images/NavigationItemLayers.svg", "3");
28
29     this._dataGridNodesByLayerId = {};
30
31     this.element.classList.add(WebInspector.LayerTreeSidebarPanel.StyleClassName);
32
33     WebInspector.showShadowDOMSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showShadowDOMSettingChanged, this);
34
35     window.addEventListener("resize", this._windowResized.bind(this));
36
37     // Add a container that will host all sections as a vertical stack.
38     this._container = this.element.appendChild(document.createElement("div"));
39     this._container.className = "box-container";
40
41     this._buildLayerInfoSection();
42     this._buildDataGridSection();
43     this._buildBottomBar();
44 };
45
46 WebInspector.LayerTreeSidebarPanel.StyleClassName = "layer-tree";
47
48 WebInspector.LayerTreeSidebarPanel.prototype = {
49     constructor: WebInspector.LayerTreeSidebarPanel,
50
51     // DetailsSidebarPanel Overrides.
52
53     shown: function()
54     {
55         WebInspector.layerTreeManager.addEventListener(WebInspector.LayerTreeManager.Event.LayerTreeDidChange, this._layerTreeDidChange, this);
56
57         console.assert(this.parentSidebar);
58
59         this.needsRefresh();
60
61         WebInspector.DOMDetailsSidebarPanel.prototype.shown.call(this);
62     },
63
64     hidden: function()
65     {
66         WebInspector.layerTreeManager.removeEventListener(WebInspector.LayerTreeManager.Event.LayerTreeDidChange, this._layerTreeDidChange, this);
67
68         WebInspector.DOMDetailsSidebarPanel.prototype.hidden.call(this);
69     },
70
71     refresh: function()
72     {
73         if (!this.domNode)
74             return;
75
76         WebInspector.layerTreeManager.layersForNode(this.domNode, function(layerForNode, childLayers) {
77             this._unfilteredChildLayers = childLayers;
78             this._updateDisplayWithLayers(layerForNode, childLayers);
79         }.bind(this));
80     },
81
82     // DOMDetailsSidebarPanel Overrides
83
84     supportsDOMNode: function(nodeToInspect)
85     {
86         return WebInspector.layerTreeManager.supported && nodeToInspect.nodeType() === Node.ELEMENT_NODE;
87     },
88
89     // Private
90
91     _layerTreeDidChange: function(event)
92     {
93         this.needsRefresh();
94     },
95
96     _showShadowDOMSettingChanged: function(event)
97     {
98         if (this.selected)
99             this._updateDisplayWithLayers(this._layerForNode, this._unfilteredChildLayers);
100     },
101
102     _windowResized: function(event)
103     {
104         if (this._popover && this._popover.visible)
105             this._updatePopoverForSelectedNode();
106     },
107
108     _buildLayerInfoSection: function()
109     {
110         var rows = this._layerInfoRows = {};
111         var rowsArray = [];
112
113         rowsArray.push(rows["Width"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Width")));
114         rowsArray.push(rows["Height"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Height")));
115         rowsArray.push(rows["Paints"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Paints")));
116         rowsArray.push(rows["Memory"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Memory")));
117
118         this._layerInfoGroup = new WebInspector.DetailsSectionGroup(rowsArray);
119
120         var emptyRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Layer Available"));
121         emptyRow.showEmptyMessage();
122         this._noLayerInformationGroup = new WebInspector.DetailsSectionGroup([emptyRow]);
123
124         this._layerInfoSection = new WebInspector.DetailsSection("layer-info", WebInspector.UIString("Layer Info"), [this._noLayerInformationGroup]);
125
126         this._container.appendChild(this._layerInfoSection.element);
127     },
128
129     _buildDataGridSection: function()
130     {
131         this._dataGrid = new WebInspector.LayerTreeDataGrid;
132
133         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._sortDataGrid, this);
134         this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._selectedDataGridNodeChanged, this);
135
136         var element = this._dataGrid.element;
137         element.addEventListener("focus", this._dataGridGainedFocus.bind(this), false);
138         element.addEventListener("blur", this._dataGridLostFocus.bind(this), false);
139         element.addEventListener("click", this._dataGridWasClicked.bind(this), false);
140
141         this._childLayersRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Child Layers"));
142         var group = new WebInspector.DetailsSectionGroup([this._childLayersRow]);
143         var section = new WebInspector.DetailsSection("layer-children", WebInspector.UIString("Child Layers"), [group], null, true);
144
145         // Display it in the container with a class name so we can easily style it differently to other sections,
146         // this specific section is meant to scale to fill the space available vertically.
147         var element = this._container.appendChild(section.element);
148         element.classList.add(section.identifier);
149     },
150
151     _buildBottomBar: function()
152     {
153         var bottomBar = this._container.appendChild(document.createElement("div"));
154         bottomBar.className = "bottom-bar";
155
156         this._layersCountLabel = bottomBar.appendChild(document.createElement("div"));
157         this._layersCountLabel.className = "layers-count-label";
158
159         this._layersMemoryLabel = bottomBar.appendChild(document.createElement("div"));
160         this._layersMemoryLabel.className = "layers-memory-label";
161     },
162     
163     _sortDataGrid: function()
164     {
165         var dataGrid = this._dataGrid;
166
167         var nodes = dataGrid.children.slice();
168         var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
169         var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
170
171         function comparator(a, b)
172         {
173             var item1 = a.layer[sortColumnIdentifier] || 0;
174             var item2 = b.layer[sortColumnIdentifier] || 0;
175             return sortDirection * (item1 - item2);
176         };
177
178         nodes.sort(comparator);
179
180         dataGrid.setChildren(nodes);
181     },
182
183     _selectedDataGridNodeChanged: function()
184     {
185         if (this._dataGrid.selectedNode) {
186             this._highlightSelectedNode();
187             this._showPopoverForSelectedNode();
188         } else {
189             WebInspector.domTreeManager.hideDOMNodeHighlight();
190             this._hidePopover();
191         }
192     },
193
194     _dataGridGainedFocus: function(event)
195     {
196         this._highlightSelectedNode();
197         this._showPopoverForSelectedNode();
198     },
199
200     _dataGridLostFocus: function(event)
201     {
202         WebInspector.domTreeManager.hideDOMNodeHighlight();
203         this._hidePopover();
204     },
205
206     _dataGridWasClicked: function(event)
207     {
208         if (this._dataGrid.selectedNode && event.target.parentNode.classList.contains("filler"))
209             this._dataGrid.selectedNode.deselect();
210     },
211
212     _highlightSelectedNode: function()
213     {
214         var dataGridNode = this._dataGrid.selectedNode;
215         if (!dataGridNode)
216             return;
217
218         var layer = dataGridNode.layer;
219         if (layer.isGeneratedContent || layer.isReflection || layer.isAnonymous)
220             WebInspector.domTreeManager.highlightRect(layer.bounds, true);
221         else
222             WebInspector.domTreeManager.highlightDOMNode(layer.nodeId);
223     },
224
225     _updateDisplayWithLayers: function(layerForNode, childLayers)
226     {
227         if (!WebInspector.showShadowDOMSetting.value) {
228             childLayers = childLayers.filter(function(layer) {
229                 return !layer.isInShadowTree;
230             });
231         }
232
233         this._updateLayerInfoSection(layerForNode);
234         this._updateDataGrid(layerForNode, childLayers);
235         this._updateMetrics(layerForNode, childLayers);
236
237         this._layerForNode = layerForNode;
238         this._childLayers = childLayers;
239     },
240
241     _updateLayerInfoSection: function(layer)
242     {
243         const emDash = "\u2014";
244
245         this._layerInfoSection.groups = layer ? [this._layerInfoGroup] : [this._noLayerInformationGroup];
246
247         if (!layer)
248             return;
249
250         this._layerInfoRows["Memory"].value = Number.bytesToString(layer.memory);
251         this._layerInfoRows["Width"].value = layer.compositedBounds.width + "px";
252         this._layerInfoRows["Height"].value = layer.compositedBounds.height + "px";
253         this._layerInfoRows["Paints"].value = layer.paintCount + "";
254     },
255
256     _updateDataGrid: function(layerForNode, childLayers)
257     {
258         var dataGrid = this._dataGrid;
259
260         var mutations = WebInspector.layerTreeManager.layerTreeMutations(this._childLayers, childLayers);
261
262         mutations.removals.forEach(function(layer) {
263             var node = this._dataGridNodesByLayerId[layer.layerId];
264             if (node) {
265                 dataGrid.removeChild(node);
266                 delete this._dataGridNodesByLayerId[layer.layerId];
267             }
268         }.bind(this));
269
270         mutations.additions.forEach(function(layer) {
271             var node = this._dataGridNodeForLayer(layer);
272             if (node)
273                 dataGrid.appendChild(node);
274         }.bind(this));
275
276         mutations.preserved.forEach(function(layer) {
277             var node = this._dataGridNodesByLayerId[layer.layerId];
278             if (node)
279                 node.layer = layer;
280         }.bind(this));
281
282         this._sortDataGrid();
283
284         this._childLayersRow.dataGrid = !isEmptyObject(childLayers) ? this._dataGrid : null;
285     },
286     
287     _dataGridNodeForLayer: function(layer)
288     {
289         var node = new WebInspector.LayerTreeDataGridNode(layer);
290
291         this._dataGridNodesByLayerId[layer.layerId] = node;
292
293         return node;
294     },
295     
296     _updateMetrics: function(layerForNode, childLayers)
297     {
298         var layerCount = 0;
299         var totalMemory = 0;
300
301         if (layerForNode) {
302             layerCount++;
303             totalMemory += layerForNode.memory || 0;
304         }
305
306         childLayers.forEach(function(layer) {
307             layerCount++;
308             totalMemory += layer.memory || 0;
309         });
310
311         this._layersCountLabel.textContent = WebInspector.UIString("Layer count: %d").format(layerCount);
312         this._layersMemoryLabel.textContent = WebInspector.UIString("Memory: %s").format(Number.bytesToString(totalMemory));
313     },
314
315     _showPopoverForSelectedNode: function()
316     {
317         var dataGridNode = this._dataGrid.selectedNode;
318         if (!dataGridNode)
319             return;
320
321         this._contentForPopover(dataGridNode.layer, function(content) {
322             if (dataGridNode === this._dataGrid.selectedNode)
323                 this._updatePopoverForSelectedNode(content);
324         }.bind(this));
325     },
326
327     _updatePopoverForSelectedNode: function(content)
328     {
329         var dataGridNode = this._dataGrid.selectedNode;
330         if (!dataGridNode)
331             return;
332
333         var popover = this._popover;
334         if (!popover)
335             popover = this._popover = new WebInspector.Popover;
336
337         var targetFrame = WebInspector.Rect.rectFromClientRect(dataGridNode.element.getBoundingClientRect());
338         const padding = 2;
339         targetFrame.origin.x -= padding;
340         targetFrame.origin.y -= padding;
341         targetFrame.size.width += padding * 2;
342         targetFrame.size.height += padding * 2;
343
344         if (content)
345             popover.content = content;
346
347         popover.present(targetFrame, [WebInspector.RectEdge.MIN_X]);
348     },
349
350     _hidePopover: function()
351     {
352         if (this._popover)
353             this._popover.dismiss();
354     },
355
356     _contentForPopover: function(layer, callback)
357     {
358         var content = document.createElement("div");
359         content.className = "layer-tree-popover";
360         
361         content.appendChild(document.createElement("p")).textContent = WebInspector.UIString("Reasons for compositing:");
362
363         var list = content.appendChild(document.createElement("ul"));
364
365         WebInspector.layerTreeManager.reasonsForCompositingLayer(layer, function(compositingReasons) {
366             if (isEmptyObject(compositingReasons)) {
367                 callback(content);
368                 return;
369             }
370
371             this._populateListOfCompositingReasons(list, compositingReasons);
372
373             callback(content);
374         }.bind(this));
375
376         return content;
377     },
378
379     _populateListOfCompositingReasons: function(list, compositingReasons)
380     {
381         function addReason(reason)
382         {
383             list.appendChild(document.createElement("li")).textContent = reason;
384         }
385
386         if (compositingReasons.transform3D)
387             addReason(WebInspector.UIString("Element has a 3D transform"));
388         if (compositingReasons.video)
389             addReason(WebInspector.UIString("Element is <video>"));
390         if (compositingReasons.canvas)
391             addReason(WebInspector.UIString("Element is <canvas>"));
392         if (compositingReasons.plugin)
393             addReason(WebInspector.UIString("Element is a plug-in"));
394         if (compositingReasons.iFrame)
395             addReason(WebInspector.UIString("Element is <iframe>"));
396         if (compositingReasons.backfaceVisibilityHidden)
397             addReason(WebInspector.UIString("Element has “backface-visibility: hidden” style"));
398         if (compositingReasons.clipsCompositingDescendants)
399             addReason(WebInspector.UIString("Element clips compositing descendants"));
400         if (compositingReasons.animation)
401             addReason(WebInspector.UIString("Element is animated"));
402         if (compositingReasons.filters)
403             addReason(WebInspector.UIString("Element has CSS filters applied"));
404         if (compositingReasons.positionFixed)
405             addReason(WebInspector.UIString("Element has “position: fixed” style"));
406         if (compositingReasons.positionSticky)
407             addReason(WebInspector.UIString("Element has “position: sticky” style"));
408         if (compositingReasons.overflowScrollingTouch)
409             addReason(WebInspector.UIString("Element has “-webkit-overflow-scrolling: touch” style"));
410         if (compositingReasons.stacking)
411             addReason(WebInspector.UIString("Element establishes a stacking context"));
412         if (compositingReasons.overlap)
413             addReason(WebInspector.UIString("Element overlaps other compositing element"));
414         if (compositingReasons.negativeZIndexChildren)
415             addReason(WebInspector.UIString("Element has children with a negative z-index"));
416         if (compositingReasons.transformWithCompositedDescendants)
417             addReason(WebInspector.UIString("Element has a 2D transform and composited descendants"));
418         if (compositingReasons.opacityWithCompositedDescendants)
419             addReason(WebInspector.UIString("Element has opacity applied and composited descendants"));
420         if (compositingReasons.maskWithCompositedDescendants)
421             addReason(WebInspector.UIString("Element is masked and composited descendants"));
422         if (compositingReasons.reflectionWithCompositedDescendants)
423             addReason(WebInspector.UIString("Element has a reflection and composited descendants"));
424         if (compositingReasons.filterWithCompositedDescendants)
425             addReason(WebInspector.UIString("Element has CSS filters applied and composited descendants"));
426         if (compositingReasons.blendingWithCompositedDescendants)
427             addReason(WebInspector.UIString("Element has CSS blending applied and composited descendants"));
428         if (compositingReasons.perspective)
429             addReason(WebInspector.UIString("Element has perspective applied"));
430         if (compositingReasons.preserve3D)
431             addReason(WebInspector.UIString("Element has “transform-style: preserve-3d” style"));
432         if (compositingReasons.root)
433             addReason(WebInspector.UIString("Element is the root element"));
434     }
435 };
436
437 WebInspector.LayerTreeSidebarPanel.prototype.__proto__ = WebInspector.DOMDetailsSidebarPanel.prototype;