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 26WebInspector.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 42WebInspector.LayerTreeSidebarPanel.StyleClassName = "layer-tree"; 43 44WebInspector.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 443WebInspector.LayerTreeSidebarPanel.prototype.__proto__ = WebInspector.DOMDetailsSidebarPanel.prototype; 444