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