| /* |
| * Copyright (C) 2013 Google 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: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT |
| * OWNER OR 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. |
| */ |
| |
| /** |
| * @constructor |
| * @extends {WebInspector.Object} |
| */ |
| WebInspector.LayerTreeModel = function() |
| { |
| WebInspector.Object.call(this); |
| this._layersById = {}; |
| // We fetch layer tree lazily and get paint events asynchronously, so keep the last painted |
| // rect separate from layer so we can get it after refreshing the tree. |
| this._lastPaintRectByLayerId = {}; |
| InspectorBackend.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this)); |
| WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._onDocumentUpdated, this); |
| } |
| |
| WebInspector.LayerTreeModel.Events = { |
| LayerTreeChanged: "LayerTreeChanged", |
| LayerPainted: "LayerPainted", |
| } |
| |
| WebInspector.LayerTreeModel.prototype = { |
| disable: function() |
| { |
| if (!this._enabled) |
| return; |
| this._enabled = false; |
| LayerTreeAgent.disable(); |
| }, |
| |
| /** |
| * @param {function()=} callback |
| */ |
| enable: function(callback) |
| { |
| if (this._enabled) |
| return; |
| this._enabled = true; |
| WebInspector.domAgent.requestDocument(onDocumentAvailable.bind(this)); |
| function onDocumentAvailable() |
| { |
| // The agent might have been disabled while we were waiting for the document. |
| if (!this._enabled) |
| return; |
| LayerTreeAgent.enable(); |
| } |
| }, |
| |
| /** |
| * @return {?WebInspector.Layer} |
| */ |
| root: function() |
| { |
| return this._root; |
| }, |
| |
| /** |
| * @return {?WebInspector.Layer} |
| */ |
| contentRoot: function() |
| { |
| return this._contentRoot; |
| }, |
| |
| /** |
| * @param {function(WebInspector.Layer)} callback |
| * @param {WebInspector.Layer=} root |
| * @return {boolean} |
| */ |
| forEachLayer: function(callback, root) |
| { |
| if (!root) { |
| root = this.root(); |
| if (!root) |
| return false; |
| } |
| return callback(root) || root.children().some(this.forEachLayer.bind(this, callback)); |
| }, |
| |
| /** |
| * @param {string} id |
| * @return {WebInspector.Layer?} |
| */ |
| layerById: function(id) |
| { |
| return this._layersById[id]; |
| }, |
| |
| /** |
| * @param{Array.<LayerTreeAgent.Layer>} payload |
| */ |
| _repopulate: function(payload) |
| { |
| var oldLayersById = this._layersById; |
| this._layersById = {}; |
| for (var i = 0; i < payload.length; ++i) { |
| var layerId = payload[i].layerId; |
| var layer = oldLayersById[layerId]; |
| if (layer) |
| layer._reset(payload[i]); |
| else |
| layer = new WebInspector.Layer(payload[i]); |
| this._layersById[layerId] = layer; |
| var parentId = layer.parentId(); |
| if (!this._contentRoot && layer.nodeId()) |
| this._contentRoot = layer; |
| var lastPaintRect = this._lastPaintRectByLayerId[layerId]; |
| if (lastPaintRect) |
| layer._lastPaintRect = lastPaintRect; |
| if (parentId) { |
| var parent = this._layersById[parentId]; |
| if (!parent) |
| console.assert(parent, "missing parent " + parentId + " for layer " + layerId); |
| parent.addChild(layer); |
| } else { |
| if (this._root) |
| console.assert(false, "Multiple root layers"); |
| this._root = layer; |
| } |
| } |
| this._lastPaintRectByLayerId = {}; |
| }, |
| |
| /** |
| * @param {Array.<LayerTreeAgent.Layer>=} payload |
| */ |
| _layerTreeChanged: function(payload) |
| { |
| this._root = null; |
| this._contentRoot = null; |
| // Payload will be null when not in the composited mode. |
| if (payload) |
| this._repopulate(payload); |
| this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged); |
| }, |
| |
| /** |
| * @param {LayerTreeAgent.LayerId} layerId |
| * @param {DOMAgent.Rect} clipRect |
| */ |
| _layerPainted: function(layerId, clipRect) |
| { |
| var layer = this._layersById[layerId]; |
| if (!layer) { |
| this._lastPaintRectByLayerId[layerId] = clipRect; |
| return; |
| } |
| layer._didPaint(clipRect); |
| this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted, layer); |
| }, |
| |
| _onDocumentUpdated: function() |
| { |
| this.disable(); |
| this.enable(); |
| }, |
| |
| __proto__: WebInspector.Object.prototype |
| } |
| |
| /** |
| * @constructor |
| * @param {LayerTreeAgent.Layer} layerPayload |
| */ |
| WebInspector.Layer = function(layerPayload) |
| { |
| this._reset(layerPayload); |
| } |
| |
| WebInspector.Layer.prototype = { |
| /** |
| * @return {string} |
| */ |
| id: function() |
| { |
| return this._layerPayload.layerId; |
| }, |
| |
| /** |
| * @return {string?} |
| */ |
| parentId: function() |
| { |
| return this._layerPayload.parentLayerId; |
| }, |
| |
| /** |
| * @return {WebInspector.Layer} |
| */ |
| parent: function() |
| { |
| return this._parent; |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| isRoot: function() |
| { |
| return !this.parentId(); |
| }, |
| |
| /** |
| * @return {Array.<WebInspector.Layer>} |
| */ |
| children: function() |
| { |
| return this._children; |
| }, |
| |
| /** |
| * @param {WebInspector.Layer} child |
| */ |
| addChild: function(child) |
| { |
| if (child._parent) |
| console.assert(false, "Child already has a parent"); |
| this._children.push(child); |
| child._parent = this; |
| }, |
| |
| /** |
| * @return {DOMAgent.NodeId?} |
| */ |
| nodeId: function() |
| { |
| return this._layerPayload.nodeId; |
| }, |
| |
| /** |
| * @return {DOMAgent.NodeId?} |
| */ |
| nodeIdForSelfOrAncestor: function() |
| { |
| for (var layer = this; layer; layer = layer._parent) { |
| var nodeId = layer._layerPayload.nodeId; |
| if (nodeId) |
| return nodeId; |
| } |
| return null; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| offsetX: function() |
| { |
| return this._layerPayload.offsetX; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| offsetY: function() |
| { |
| return this._layerPayload.offsetY; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| width: function() |
| { |
| return this._layerPayload.width; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| height: function() |
| { |
| return this._layerPayload.height; |
| }, |
| |
| /** |
| * @return {Array.<number>} |
| */ |
| transform: function() |
| { |
| return this._layerPayload.transform; |
| }, |
| |
| /** |
| * @return {Array.<number>} |
| */ |
| anchorPoint: function() |
| { |
| return [ |
| this._layerPayload.anchorX || 0, |
| this._layerPayload.anchorY || 0, |
| this._layerPayload.anchorZ || 0, |
| ]; |
| }, |
| |
| /** |
| * @return {boolean} |
| */ |
| invisible: function() |
| { |
| return this._layerPayload.invisible; |
| }, |
| |
| /** |
| * @return {number} |
| */ |
| paintCount: function() |
| { |
| return this._paintCount || this._layerPayload.paintCount; |
| }, |
| |
| /** |
| * @return {?DOMAgent.Rect} |
| */ |
| lastPaintRect: function() |
| { |
| return this._lastPaintRect; |
| }, |
| |
| /** |
| * @param {function(Array.<string>?)} callback |
| */ |
| requestCompositingReasons: function(callback) |
| { |
| /** |
| * @param {?string} error |
| * @param {?Array.<string>} compositingReasons |
| */ |
| function callbackWrapper(error, compositingReasons) |
| { |
| if (error) { |
| console.error("LayerTreeAgent.reasonsForCompositingLayer(): " + error); |
| callback(null); |
| return; |
| } |
| callback(compositingReasons); |
| } |
| LayerTreeAgent.compositingReasons(this.id(), callbackWrapper.bind(this)); |
| }, |
| |
| /** |
| * @param {DOMAgent.Rect} rect |
| */ |
| _didPaint: function(rect) |
| { |
| this._lastPaintRect = rect; |
| this._paintCount = this.paintCount() + 1; |
| }, |
| |
| /** |
| * @param {LayerTreeAgent.Layer} layerPayload |
| */ |
| _reset: function(layerPayload) |
| { |
| this._children = []; |
| this._parent = null; |
| this._paintCount = 0; |
| this._layerPayload = layerPayload; |
| } |
| } |
| |
| /** |
| * @constructor |
| * @implements {LayerTreeAgent.Dispatcher} |
| * @param {WebInspector.LayerTreeModel} layerTreeModel |
| */ |
| WebInspector.LayerTreeDispatcher = function(layerTreeModel) |
| { |
| this._layerTreeModel = layerTreeModel; |
| } |
| |
| WebInspector.LayerTreeDispatcher.prototype = { |
| /** |
| * @param {Array.<LayerTreeAgent.Layer>=} payload |
| */ |
| layerTreeDidChange: function(payload) |
| { |
| this._layerTreeModel._layerTreeChanged(payload); |
| }, |
| |
| /** |
| * @param {LayerTreeAgent.LayerId} layerId |
| * @param {DOMAgent.Rect} clipRect |
| */ |
| layerPainted: function(layerId, clipRect) |
| { |
| this._layerTreeModel._layerPainted(layerId, clipRect); |
| } |
| } |