| /* |
| * 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. |
| */ |
| |
| /** |
| * @unrestricted |
| */ |
| export default class LayerTreeModel extends SDK.SDKModel { |
| constructor(target) { |
| super(target); |
| this._layerTreeAgent = target.layerTreeAgent(); |
| target.registerLayerTreeDispatcher(new LayerTreeDispatcher(this)); |
| this._paintProfilerModel = /** @type {!SDK.PaintProfilerModel} */ (target.model(SDK.PaintProfilerModel)); |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel); |
| if (resourceTreeModel) { |
| resourceTreeModel.addEventListener( |
| SDK.ResourceTreeModel.Events.MainFrameNavigated, this._onMainFrameNavigated, this); |
| } |
| /** @type {?SDK.LayerTreeBase} */ |
| this._layerTree = null; |
| this._throttler = new Common.Throttler(20); |
| } |
| |
| disable() { |
| if (!this._enabled) { |
| return; |
| } |
| this._enabled = false; |
| this._layerTreeAgent.disable(); |
| } |
| |
| enable() { |
| if (this._enabled) { |
| return; |
| } |
| this._enabled = true; |
| this._forceEnable(); |
| } |
| |
| _forceEnable() { |
| this._lastPaintRectByLayerId = {}; |
| if (!this._layerTree) { |
| this._layerTree = new AgentLayerTree(this); |
| } |
| this._layerTreeAgent.enable(); |
| } |
| |
| /** |
| * @return {?SDK.LayerTreeBase} |
| */ |
| layerTree() { |
| return this._layerTree; |
| } |
| |
| /** |
| * @param {?Array.<!Protocol.LayerTree.Layer>} layers |
| */ |
| async _layerTreeChanged(layers) { |
| if (!this._enabled) { |
| return; |
| } |
| this._throttler.schedule(this._innerSetLayers.bind(this, layers)); |
| } |
| |
| /** |
| * @param {?Array.<!Protocol.LayerTree.Layer>} layers |
| */ |
| async _innerSetLayers(layers) { |
| const layerTree = /** @type {!Layers.AgentLayerTree} */ (this._layerTree); |
| |
| await layerTree.setLayers(layers); |
| |
| for (const layerId in this._lastPaintRectByLayerId) { |
| const lastPaintRect = this._lastPaintRectByLayerId[layerId]; |
| const layer = layerTree.layerById(layerId); |
| if (layer) { |
| /** @type {!Layers.AgentLayer} */ (layer)._lastPaintRect = lastPaintRect; |
| } |
| } |
| this._lastPaintRectByLayerId = {}; |
| |
| this.dispatchEventToListeners(Events.LayerTreeChanged); |
| } |
| |
| /** |
| * @param {!Protocol.LayerTree.LayerId} layerId |
| * @param {!Protocol.DOM.Rect} clipRect |
| */ |
| _layerPainted(layerId, clipRect) { |
| if (!this._enabled) { |
| return; |
| } |
| const layerTree = /** @type {!Layers.AgentLayerTree} */ (this._layerTree); |
| const layer = /** @type {!Layers.AgentLayer} */ (layerTree.layerById(layerId)); |
| if (!layer) { |
| this._lastPaintRectByLayerId[layerId] = clipRect; |
| return; |
| } |
| layer._didPaint(clipRect); |
| this.dispatchEventToListeners(Events.LayerPainted, layer); |
| } |
| |
| _onMainFrameNavigated() { |
| this._layerTree = null; |
| if (this._enabled) { |
| this._forceEnable(); |
| } |
| } |
| } |
| |
| SDK.SDKModel.register(LayerTreeModel, SDK.Target.Capability.DOM, false); |
| |
| /** @enum {symbol} */ |
| export const Events = { |
| LayerTreeChanged: Symbol('LayerTreeChanged'), |
| LayerPainted: Symbol('LayerPainted'), |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| export class AgentLayerTree extends SDK.LayerTreeBase { |
| /** |
| * @param {!Layers.LayerTreeModel} layerTreeModel |
| */ |
| constructor(layerTreeModel) { |
| super(layerTreeModel.target()); |
| this._layerTreeModel = layerTreeModel; |
| } |
| |
| /** |
| * @param {?Array<!Protocol.LayerTree.Layer>} payload |
| * @return {!Promise} |
| */ |
| async setLayers(payload) { |
| if (!payload) { |
| this._innerSetLayers(payload); |
| return; |
| } |
| const idsToResolve = new Set(); |
| for (let i = 0; i < payload.length; ++i) { |
| const backendNodeId = payload[i].backendNodeId; |
| if (!backendNodeId || this.backendNodeIdToNode().has(backendNodeId)) { |
| continue; |
| } |
| idsToResolve.add(backendNodeId); |
| } |
| await this.resolveBackendNodeIds(idsToResolve); |
| this._innerSetLayers(payload); |
| } |
| |
| /** |
| * @param {?Array.<!Protocol.LayerTree.Layer>} layers |
| */ |
| _innerSetLayers(layers) { |
| this.setRoot(null); |
| this.setContentRoot(null); |
| // Payload will be null when not in the composited mode. |
| if (!layers) { |
| return; |
| } |
| let root; |
| const oldLayersById = this._layersById; |
| this._layersById = {}; |
| for (let i = 0; i < layers.length; ++i) { |
| const layerId = layers[i].layerId; |
| let layer = oldLayersById[layerId]; |
| if (layer) { |
| layer._reset(layers[i]); |
| } else { |
| layer = new AgentLayer(this._layerTreeModel, layers[i]); |
| } |
| this._layersById[layerId] = layer; |
| const backendNodeId = layers[i].backendNodeId; |
| if (backendNodeId) { |
| layer._setNode(this.backendNodeIdToNode().get(backendNodeId)); |
| } |
| if (!this.contentRoot() && layer.drawsContent()) { |
| this.setContentRoot(layer); |
| } |
| const parentId = layer.parentId(); |
| if (parentId) { |
| const parent = this._layersById[parentId]; |
| if (!parent) { |
| console.assert(parent, 'missing parent ' + parentId + ' for layer ' + layerId); |
| } |
| parent.addChild(layer); |
| } else { |
| if (root) { |
| console.assert(false, 'Multiple root layers'); |
| } |
| root = layer; |
| } |
| } |
| if (root) { |
| this.setRoot(root); |
| root._calculateQuad(new WebKitCSSMatrix()); |
| } |
| } |
| } |
| |
| /** |
| * @implements {SDK.Layer} |
| * @unrestricted |
| */ |
| export class AgentLayer { |
| /** |
| * @param {!Layers.LayerTreeModel} layerTreeModel |
| * @param {!Protocol.LayerTree.Layer} layerPayload |
| */ |
| constructor(layerTreeModel, layerPayload) { |
| this._layerTreeModel = layerTreeModel; |
| this._reset(layerPayload); |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| id() { |
| return this._layerPayload.layerId; |
| } |
| |
| /** |
| * @override |
| * @return {?string} |
| */ |
| parentId() { |
| return this._layerPayload.parentLayerId; |
| } |
| |
| /** |
| * @override |
| * @return {?SDK.Layer} |
| */ |
| parent() { |
| return this._parent; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| isRoot() { |
| return !this.parentId(); |
| } |
| |
| /** |
| * @override |
| * @return {!Array.<!SDK.Layer>} |
| */ |
| children() { |
| return this._children; |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.Layer} childParam |
| */ |
| addChild(childParam) { |
| const child = /** @type {!Layers.AgentLayer} */ (childParam); |
| if (child._parent) { |
| console.assert(false, 'Child already has a parent'); |
| } |
| this._children.push(child); |
| child._parent = this; |
| } |
| |
| /** |
| * @param {?SDK.DOMNode} node |
| */ |
| _setNode(node) { |
| this._node = node; |
| } |
| |
| /** |
| * @override |
| * @return {?SDK.DOMNode} |
| */ |
| node() { |
| return this._node; |
| } |
| |
| /** |
| * @override |
| * @return {?SDK.DOMNode} |
| */ |
| nodeForSelfOrAncestor() { |
| for (let layer = this; layer; layer = layer._parent) { |
| if (layer._node) { |
| return layer._node; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| offsetX() { |
| return this._layerPayload.offsetX; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| offsetY() { |
| return this._layerPayload.offsetY; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| width() { |
| return this._layerPayload.width; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| height() { |
| return this._layerPayload.height; |
| } |
| |
| /** |
| * @override |
| * @return {?Array.<number>} |
| */ |
| transform() { |
| return this._layerPayload.transform; |
| } |
| |
| /** |
| * @override |
| * @return {!Array.<number>} |
| */ |
| quad() { |
| return this._quad; |
| } |
| |
| /** |
| * @override |
| * @return {!Array.<number>} |
| */ |
| anchorPoint() { |
| return [ |
| this._layerPayload.anchorX || 0, |
| this._layerPayload.anchorY || 0, |
| this._layerPayload.anchorZ || 0, |
| ]; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| invisible() { |
| return this._layerPayload.invisible; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| paintCount() { |
| return this._paintCount || this._layerPayload.paintCount; |
| } |
| |
| /** |
| * @override |
| * @return {?Protocol.DOM.Rect} |
| */ |
| lastPaintRect() { |
| return this._lastPaintRect; |
| } |
| |
| /** |
| * @override |
| * @return {!Array.<!Protocol.LayerTree.ScrollRect>} |
| */ |
| scrollRects() { |
| return this._scrollRects; |
| } |
| |
| /** |
| * @override |
| * @return {?SDK.Layer.StickyPositionConstraint} |
| */ |
| stickyPositionConstraint() { |
| return this._stickyPositionConstraint; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<!Array<string>>} |
| */ |
| async requestCompositingReasons() { |
| const reasons = await this._layerTreeModel._layerTreeAgent.compositingReasons(this.id()); |
| return reasons || []; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| drawsContent() { |
| return this._layerPayload.drawsContent; |
| } |
| |
| /** |
| * @override |
| * @return {number} |
| */ |
| gpuMemoryUsage() { |
| /** |
| * @const |
| */ |
| const bytesPerPixel = 4; |
| return this.drawsContent() ? this.width() * this.height() * bytesPerPixel : 0; |
| } |
| |
| /** |
| * @override |
| * @return {!Array<!Promise<?SDK.SnapshotWithRect>>} |
| */ |
| snapshots() { |
| const promise = this._layerTreeModel._paintProfilerModel.makeSnapshot(this.id()).then(snapshot => { |
| if (!snapshot) { |
| return null; |
| } |
| return {rect: {x: 0, y: 0, width: this.width(), height: this.height()}, snapshot: snapshot}; |
| }); |
| return [promise]; |
| } |
| |
| /** |
| * @param {!Protocol.DOM.Rect} rect |
| */ |
| _didPaint(rect) { |
| this._lastPaintRect = rect; |
| this._paintCount = this.paintCount() + 1; |
| this._image = null; |
| } |
| |
| /** |
| * @param {!Protocol.LayerTree.Layer} layerPayload |
| */ |
| _reset(layerPayload) { |
| /** @type {?SDK.DOMNode} */ |
| this._node = null; |
| this._children = []; |
| this._parent = null; |
| this._paintCount = 0; |
| this._layerPayload = layerPayload; |
| this._image = null; |
| this._scrollRects = this._layerPayload.scrollRects || []; |
| this._stickyPositionConstraint = this._layerPayload.stickyPositionConstraint ? |
| new SDK.Layer.StickyPositionConstraint( |
| this._layerTreeModel.layerTree(), this._layerPayload.stickyPositionConstraint) : |
| null; |
| } |
| |
| /** |
| * @param {!Array.<number>} a |
| * @return {!CSSMatrix} |
| */ |
| _matrixFromArray(a) { |
| function toFixed9(x) { |
| return x.toFixed(9); |
| } |
| return new WebKitCSSMatrix('matrix3d(' + a.map(toFixed9).join(',') + ')'); |
| } |
| |
| /** |
| * @param {!CSSMatrix} parentTransform |
| * @return {!CSSMatrix} |
| */ |
| _calculateTransformToViewport(parentTransform) { |
| const offsetMatrix = new WebKitCSSMatrix().translate(this._layerPayload.offsetX, this._layerPayload.offsetY); |
| let matrix = offsetMatrix; |
| |
| if (this._layerPayload.transform) { |
| const transformMatrix = this._matrixFromArray(this._layerPayload.transform); |
| const anchorVector = new UI.Geometry.Vector( |
| this._layerPayload.width * this.anchorPoint()[0], this._layerPayload.height * this.anchorPoint()[1], |
| this.anchorPoint()[2]); |
| const anchorPoint = UI.Geometry.multiplyVectorByMatrixAndNormalize(anchorVector, matrix); |
| const anchorMatrix = new WebKitCSSMatrix().translate(-anchorPoint.x, -anchorPoint.y, -anchorPoint.z); |
| matrix = anchorMatrix.inverse().multiply(transformMatrix.multiply(anchorMatrix.multiply(matrix))); |
| } |
| |
| matrix = parentTransform.multiply(matrix); |
| return matrix; |
| } |
| |
| /** |
| * @param {number} width |
| * @param {number} height |
| * @return {!Array.<number>} |
| */ |
| _createVertexArrayForRect(width, height) { |
| return [0, 0, 0, width, 0, 0, width, height, 0, 0, height, 0]; |
| } |
| |
| /** |
| * @param {!CSSMatrix} parentTransform |
| */ |
| _calculateQuad(parentTransform) { |
| const matrix = this._calculateTransformToViewport(parentTransform); |
| this._quad = []; |
| const vertices = this._createVertexArrayForRect(this._layerPayload.width, this._layerPayload.height); |
| for (let i = 0; i < 4; ++i) { |
| const point = UI.Geometry.multiplyVectorByMatrixAndNormalize( |
| new UI.Geometry.Vector(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]), matrix); |
| this._quad.push(point.x, point.y); |
| } |
| |
| function calculateQuadForLayer(layer) { |
| layer._calculateQuad(matrix); |
| } |
| |
| this._children.forEach(calculateQuadForLayer); |
| } |
| } |
| |
| /** |
| * @implements {Protocol.LayerTreeDispatcher} |
| * @unrestricted |
| */ |
| class LayerTreeDispatcher { |
| /** |
| * @param {!Layers.LayerTreeModel} layerTreeModel |
| */ |
| constructor(layerTreeModel) { |
| this._layerTreeModel = layerTreeModel; |
| } |
| |
| /** |
| * @override |
| * @param {!Array.<!Protocol.LayerTree.Layer>=} layers |
| */ |
| layerTreeDidChange(layers) { |
| this._layerTreeModel._layerTreeChanged(layers || null); |
| } |
| |
| /** |
| * @override |
| * @param {!Protocol.LayerTree.LayerId} layerId |
| * @param {!Protocol.DOM.Rect} clipRect |
| */ |
| layerPainted(layerId, clipRect) { |
| this._layerTreeModel._layerPainted(layerId, clipRect); |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.Layers = self.Layers || {}; |
| |
| /* Legacy exported object */ |
| Layers = Layers || {}; |
| |
| /** |
| * @constructor |
| */ |
| Layers.LayerTreeModel = LayerTreeModel; |
| |
| /** @enum {symbol} */ |
| Layers.LayerTreeModel.Events = Events; |
| |
| /** |
| * @constructor |
| */ |
| Layers.AgentLayerTree = AgentLayerTree; |
| |
| /** |
| * @constructor |
| */ |
| Layers.AgentLayer = AgentLayer; |