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