/* * Copyright (C) 2013 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.LayerTreeSidebarPanel = function() { WebInspector.DOMDetailsSidebarPanel.call(this, "layer-tree", WebInspector.UIString("Layers"), WebInspector.UIString("Layer"), "Images/NavigationItemLayers.svg", "3"); this._dataGridNodesByLayerId = {}; this.element.classList.add(WebInspector.LayerTreeSidebarPanel.StyleClassName); WebInspector.showShadowDOMSetting.addEventListener(WebInspector.Setting.Event.Changed, this._showShadowDOMSettingChanged, this); window.addEventListener("resize", this._windowResized.bind(this)); // Add a container that will host all sections as a vertical stack. this._container = this.element.appendChild(document.createElement("div")); this._container.className = "box-container"; this._buildLayerInfoSection(); this._buildDataGridSection(); this._buildBottomBar(); }; WebInspector.LayerTreeSidebarPanel.StyleClassName = "layer-tree"; WebInspector.LayerTreeSidebarPanel.prototype = { constructor: WebInspector.LayerTreeSidebarPanel, // DetailsSidebarPanel Overrides. shown: function() { WebInspector.layerTreeManager.addEventListener(WebInspector.LayerTreeManager.Event.LayerTreeDidChange, this._layerTreeDidChange, this); console.assert(this.parentSidebar); this.needsRefresh(); WebInspector.DOMDetailsSidebarPanel.prototype.shown.call(this); }, hidden: function() { WebInspector.layerTreeManager.removeEventListener(WebInspector.LayerTreeManager.Event.LayerTreeDidChange, this._layerTreeDidChange, this); WebInspector.DOMDetailsSidebarPanel.prototype.hidden.call(this); }, refresh: function() { if (!this.domNode) return; WebInspector.layerTreeManager.layersForNode(this.domNode, function(layerForNode, childLayers) { this._unfilteredChildLayers = childLayers; this._updateDisplayWithLayers(layerForNode, childLayers); }.bind(this)); }, // DOMDetailsSidebarPanel Overrides supportsDOMNode: function(nodeToInspect) { return WebInspector.layerTreeManager.supported && nodeToInspect.nodeType() === Node.ELEMENT_NODE; }, // Private _layerTreeDidChange: function(event) { this.needsRefresh(); }, _showShadowDOMSettingChanged: function(event) { if (this.selected) this._updateDisplayWithLayers(this._layerForNode, this._unfilteredChildLayers); }, _windowResized: function(event) { if (this._popover && this._popover.visible) this._updatePopoverForSelectedNode(); }, _buildLayerInfoSection: function() { var rows = this._layerInfoRows = {}; var rowsArray = []; rowsArray.push(rows["Width"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Width"))); rowsArray.push(rows["Height"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Height"))); rowsArray.push(rows["Paints"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Paints"))); rowsArray.push(rows["Memory"] = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Memory"))); this._layerInfoGroup = new WebInspector.DetailsSectionGroup(rowsArray); var emptyRow = new WebInspector.DetailsSectionRow(WebInspector.UIString("No Layer Available")); emptyRow.showEmptyMessage(); this._noLayerInformationGroup = new WebInspector.DetailsSectionGroup([emptyRow]); this._layerInfoSection = new WebInspector.DetailsSection("layer-info", WebInspector.UIString("Layer Info"), [this._noLayerInformationGroup]); this._container.appendChild(this._layerInfoSection.element); }, _buildDataGridSection: function() { this._dataGrid = new WebInspector.LayerTreeDataGrid; this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SortChanged, this._sortDataGrid, this); this._dataGrid.addEventListener(WebInspector.DataGrid.Event.SelectedNodeChanged, this._selectedDataGridNodeChanged, this); var element = this._dataGrid.element; element.addEventListener("focus", this._dataGridGainedFocus.bind(this), false); element.addEventListener("blur", this._dataGridLostFocus.bind(this), false); element.addEventListener("click", this._dataGridWasClicked.bind(this), false); this._childLayersRow = new WebInspector.DetailsSectionDataGridRow(null, WebInspector.UIString("No Child Layers")); var group = new WebInspector.DetailsSectionGroup([this._childLayersRow]); var section = new WebInspector.DetailsSection("layer-children", WebInspector.UIString("Child Layers"), [group], null, true); // Display it in the container with a class name so we can easily style it differently to other sections, // this specific section is meant to scale to fill the space available vertically. var element = this._container.appendChild(section.element); element.classList.add(section.identifier); }, _buildBottomBar: function() { var bottomBar = this._container.appendChild(document.createElement("div")); bottomBar.className = "bottom-bar"; this._layersCountLabel = bottomBar.appendChild(document.createElement("div")); this._layersCountLabel.className = "layers-count-label"; this._layersMemoryLabel = bottomBar.appendChild(document.createElement("div")); this._layersMemoryLabel.className = "layers-memory-label"; }, _sortDataGrid: function() { var dataGrid = this._dataGrid; var nodes = dataGrid.children.slice(); var sortColumnIdentifier = dataGrid.sortColumnIdentifier; var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1; function comparator(a, b) { var item1 = a.layer[sortColumnIdentifier] || 0; var item2 = b.layer[sortColumnIdentifier] || 0; return sortDirection * (item1 - item2); }; nodes.sort(comparator); dataGrid.setChildren(nodes); this._updatePopoverForSelectedNode(); }, _selectedDataGridNodeChanged: function() { if (this._dataGrid.selectedNode) { this._highlightSelectedNode(); this._showPopoverForSelectedNode(); } else { WebInspector.domTreeManager.hideDOMNodeHighlight(); this._hidePopover(); } }, _dataGridGainedFocus: function(event) { this._highlightSelectedNode(); this._showPopoverForSelectedNode(); }, _dataGridLostFocus: function(event) { WebInspector.domTreeManager.hideDOMNodeHighlight(); this._hidePopover(); }, _dataGridWasClicked: function(event) { if (this._dataGrid.selectedNode && event.target.parentNode.classList.contains("filler")) this._dataGrid.selectedNode.deselect(); }, _highlightSelectedNode: function() { var dataGridNode = this._dataGrid.selectedNode; if (!dataGridNode) return; var layer = dataGridNode.layer; if (layer.isGeneratedContent || layer.isReflection || layer.isAnonymous) WebInspector.domTreeManager.highlightRect(layer.bounds, true); else WebInspector.domTreeManager.highlightDOMNode(layer.nodeId); }, _updateDisplayWithLayers: function(layerForNode, childLayers) { if (!WebInspector.showShadowDOMSetting.value) { childLayers = childLayers.filter(function(layer) { return !layer.isInShadowTree; }); } this._updateLayerInfoSection(layerForNode); this._updateDataGrid(layerForNode, childLayers); this._updateMetrics(layerForNode, childLayers); this._layerForNode = layerForNode; this._childLayers = childLayers; }, _updateLayerInfoSection: function(layer) { const emDash = "\u2014"; this._layerInfoSection.groups = layer ? [this._layerInfoGroup] : [this._noLayerInformationGroup]; if (!layer) return; this._layerInfoRows["Memory"].value = Number.bytesToString(layer.memory); this._layerInfoRows["Width"].value = layer.compositedBounds.width + "px"; this._layerInfoRows["Height"].value = layer.compositedBounds.height + "px"; this._layerInfoRows["Paints"].value = layer.paintCount + ""; }, _updateDataGrid: function(layerForNode, childLayers) { var dataGrid = this._dataGrid; var mutations = WebInspector.layerTreeManager.layerTreeMutations(this._childLayers, childLayers); mutations.removals.forEach(function(layer) { var node = this._dataGridNodesByLayerId[layer.layerId]; if (node) { dataGrid.removeChild(node); delete this._dataGridNodesByLayerId[layer.layerId]; } }.bind(this)); mutations.additions.forEach(function(layer) { var node = this._dataGridNodeForLayer(layer); if (node) dataGrid.appendChild(node); }.bind(this)); mutations.preserved.forEach(function(layer) { var node = this._dataGridNodesByLayerId[layer.layerId]; if (node) node.layer = layer; }.bind(this)); this._sortDataGrid(); this._childLayersRow.dataGrid = !isEmptyObject(childLayers) ? this._dataGrid : null; }, _dataGridNodeForLayer: function(layer) { var node = new WebInspector.LayerTreeDataGridNode(layer); this._dataGridNodesByLayerId[layer.layerId] = node; return node; }, _updateMetrics: function(layerForNode, childLayers) { var layerCount = 0; var totalMemory = 0; if (layerForNode) { layerCount++; totalMemory += layerForNode.memory || 0; } childLayers.forEach(function(layer) { layerCount++; totalMemory += layer.memory || 0; }); this._layersCountLabel.textContent = WebInspector.UIString("Layer count: %d").format(layerCount); this._layersMemoryLabel.textContent = WebInspector.UIString("Memory: %s").format(Number.bytesToString(totalMemory)); }, _showPopoverForSelectedNode: function() { var dataGridNode = this._dataGrid.selectedNode; if (!dataGridNode) return; this._contentForPopover(dataGridNode.layer, function(content) { if (dataGridNode === this._dataGrid.selectedNode) this._updatePopoverForSelectedNode(content); }.bind(this)); }, _updatePopoverForSelectedNode: function(content) { var dataGridNode = this._dataGrid.selectedNode; if (!dataGridNode) return; var popover = this._popover; if (!popover) popover = this._popover = new WebInspector.Popover; var targetFrame = WebInspector.Rect.rectFromClientRect(dataGridNode.element.getBoundingClientRect()); if (content) popover.content = content; popover.present(targetFrame.pad(2), [WebInspector.RectEdge.MIN_X]); }, _hidePopover: function() { if (this._popover) this._popover.dismiss(); }, _contentForPopover: function(layer, callback) { var content = document.createElement("div"); content.className = "layer-tree-popover"; content.appendChild(document.createElement("p")).textContent = WebInspector.UIString("Reasons for compositing:"); var list = content.appendChild(document.createElement("ul")); WebInspector.layerTreeManager.reasonsForCompositingLayer(layer, function(compositingReasons) { if (isEmptyObject(compositingReasons)) { callback(content); return; } this._populateListOfCompositingReasons(list, compositingReasons); callback(content); }.bind(this)); return content; }, _populateListOfCompositingReasons: function(list, compositingReasons) { function addReason(reason) { list.appendChild(document.createElement("li")).textContent = reason; } if (compositingReasons.transform3D) addReason(WebInspector.UIString("Element has a 3D transform")); if (compositingReasons.video) addReason(WebInspector.UIString("Element is