2 * Copyright (C) 2013 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 bounds: {height: number, width: number},
33 children: Array.<!WebInspector.TracingLayerPayload>,
35 position: Array.<number>,
36 scroll_offset: Array.<number>,
37 layer_quad: Array.<number>,
38 draws_content: number,
39 transform: Array.<number>
42 WebInspector.TracingLayerPayload;
46 * @extends {WebInspector.TargetAwareObject}
48 WebInspector.LayerTreeModel = function(target)
50 WebInspector.TargetAwareObject.call(this, target);
51 this._layersById = {};
52 // We fetch layer tree lazily and get paint events asynchronously, so keep the last painted
53 // rect separate from layer so we can get it after refreshing the tree.
54 this._lastPaintRectByLayerId = {};
55 this._backendNodeIdToNodeId = {};
56 InspectorBackend.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this));
57 target.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._onDocumentUpdated, this);
60 WebInspector.LayerTreeModel.Events = {
61 LayerTreeChanged: "LayerTreeChanged",
62 LayerPainted: "LayerPainted",
65 WebInspector.LayerTreeModel.ScrollRectType = {
66 NonFastScrollable: {name: "NonFastScrollable", description: "Non fast scrollable"},
67 TouchEventHandler: {name: "TouchEventHandler", description: "Touch event handler"},
68 WheelEventHandler: {name: "WheelEventHandler", description: "Wheel event handler"},
69 RepaintsOnScroll: {name: "RepaintsOnScroll", description: "Repaints on scroll"}
72 WebInspector.LayerTreeModel.prototype = {
77 this._enabled = false;
78 this._backendNodeIdToNodeId = {};
79 LayerTreeAgent.disable();
83 * @param {function()=} callback
85 enable: function(callback)
90 LayerTreeAgent.enable();
94 * @param {!WebInspector.LayerTreeSnapshot} snapshot
96 setSnapshot: function(snapshot)
99 this._resolveNodesAndRepopulate(snapshot.layers);
103 * @param {!WebInspector.TracingLayerSnapshot} snapshot
105 setTracingSnapshot: function(snapshot)
108 this._importTracingLayers(snapshot.root);
112 * @return {?WebInspector.Layer}
120 * @return {?WebInspector.Layer}
122 contentRoot: function()
124 return this._contentRoot;
128 * @param {function(!WebInspector.Layer)} callback
129 * @param {?WebInspector.Layer=} root
132 forEachLayer: function(callback, root)
139 return callback(root) || root.children().some(this.forEachLayer.bind(this, callback));
144 * @return {?WebInspector.Layer}
146 layerById: function(id)
148 return this._layersById[id] || null;
152 * @param {!Array.<!LayerTreeAgent.Layer>=} payload
154 _resolveNodesAndRepopulate: function(payload)
157 this._resolveBackendNodeIdsForLayers(payload, onBackendNodeIdsResolved.bind(this));
159 onBackendNodeIdsResolved.call(this);
161 * @this {WebInspector.LayerTreeModel}
163 function onBackendNodeIdsResolved()
165 this._repopulate(payload || []);
166 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
171 * @param {!Array.<!LayerTreeAgent.Layer>} layers
173 _repopulate: function(layers)
176 this._contentRoot = null;
177 // Payload will be null when not in the composited mode.
180 var oldLayersById = this._layersById;
181 this._layersById = {};
182 for (var i = 0; i < layers.length; ++i) {
183 var layerId = layers[i].layerId;
184 var layer = oldLayersById[layerId];
186 layer._reset(layers[i]);
188 layer = new WebInspector.AgentLayer(layers[i]);
189 this._layersById[layerId] = layer;
190 if (layers[i].backendNodeId) {
191 layer._setNode(this._target.domModel.nodeForId(this._backendNodeIdToNodeId[layers[i].backendNodeId]));
192 if (!this._contentRoot)
193 this._contentRoot = layer;
195 var lastPaintRect = this._lastPaintRectByLayerId[layerId];
197 layer._lastPaintRect = lastPaintRect;
198 var parentId = layer.parentId();
200 var parent = this._layersById[parentId];
202 console.assert(parent, "missing parent " + parentId + " for layer " + layerId);
203 parent.addChild(layer);
206 console.assert(false, "Multiple root layers");
211 this._root._calculateQuad(new WebKitCSSMatrix());
212 this._lastPaintRectByLayerId = {};
216 * @param {!WebInspector.TracingLayerPayload} root
218 _importTracingLayers: function(root)
220 this._layersById = {};
221 this._contentRoot = null;
222 this._root = this._innerImportTracingLayers(root);
223 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
227 * @param {!WebInspector.TracingLayerPayload} payload
228 * @return {!WebInspector.TracingLayer}
230 _innerImportTracingLayers: function(payload)
232 var layer = new WebInspector.TracingLayer(payload);
233 if (!this._contentRoot && payload.draws_content)
234 this._contentRoot = layer;
235 for (var i = 0; i < payload.children.length; ++i)
236 layer.addChild(this._innerImportTracingLayers(payload.children[i]));
241 * @param {!Array.<!LayerTreeAgent.Layer>=} layers
243 _layerTreeChanged: function(layers)
247 this._resolveNodesAndRepopulate(layers);
251 * @param {!Array.<!LayerTreeAgent.Layer>} layers
252 * @param {function()} callback
254 _resolveBackendNodeIdsForLayers: function(layers, callback)
256 var idsToResolve = {};
257 var requestedIds = [];
258 for (var i = 0; i < layers.length; ++i) {
259 var backendNodeId = layers[i].backendNodeId;
260 if (!backendNodeId || idsToResolve[backendNodeId] ||
261 (this._backendNodeIdToNodeId[backendNodeId] && this.target().domModel.nodeForId(this._backendNodeIdToNodeId[backendNodeId]))) {
264 idsToResolve[backendNodeId] = true;
265 requestedIds.push(backendNodeId);
267 if (!requestedIds.length) {
271 this.target().domModel.pushNodesByBackendIdsToFrontend(requestedIds, populateBackendNodeIdMap.bind(this));
274 * @this {WebInspector.LayerTreeModel}
275 * @param {?Array.<number>} nodeIds
277 function populateBackendNodeIdMap(nodeIds)
280 for (var i = 0; i < requestedIds.length; ++i) {
281 var nodeId = nodeIds[i];
283 this._backendNodeIdToNodeId[requestedIds[i]] = nodeId;
291 * @param {!LayerTreeAgent.LayerId} layerId
292 * @param {!DOMAgent.Rect} clipRect
294 _layerPainted: function(layerId, clipRect)
296 var layer = this._layersById[layerId];
298 this._lastPaintRectByLayerId[layerId] = clipRect;
301 layer._didPaint(clipRect);
302 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted, layer);
305 _onDocumentUpdated: function()
311 __proto__: WebInspector.TargetAwareObject.prototype
317 WebInspector.Layer = function()
321 WebInspector.Layer.prototype = {
330 parentId: function() { },
333 * @return {?WebInspector.Layer}
335 parent: function() { },
340 isRoot: function() { },
343 * @return {!Array.<!WebInspector.Layer>}
345 children: function() { },
348 * @param {!WebInspector.Layer} child
350 addChild: function(child) { },
353 * @return {?WebInspector.DOMNode}
355 node: function() { },
358 * @return {?WebInspector.DOMNode}
360 nodeForSelfOrAncestor: function() { },
365 offsetX: function() { },
370 offsetY: function() { },
375 width: function() { },
380 height: function() { },
383 * @return {?Array.<number>}
385 transform: function() { },
388 * @return {!Array.<number>}
390 quad: function() { },
393 * @return {!Array.<number>}
395 anchorPoint: function() { },
400 invisible: function() { },
405 paintCount: function() { },
408 * @return {?DOMAgent.Rect}
410 lastPaintRect: function() { },
413 * @return {!Array.<!LayerTreeAgent.ScrollRect>}
415 scrollRects: function() { },
418 * @param {function(!Array.<string>)} callback
420 requestCompositingReasons: function(callback) { },
423 * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
425 requestSnapshot: function(callback) { },
430 * @implements {WebInspector.Layer}
431 * @param {!LayerTreeAgent.Layer} layerPayload
433 WebInspector.AgentLayer = function(layerPayload)
435 this._scrollRects = [];
436 this._reset(layerPayload);
439 WebInspector.AgentLayer.prototype = {
445 return this._layerPayload.layerId;
453 return this._layerPayload.parentLayerId;
457 * @return {?WebInspector.Layer}
469 return !this.parentId();
473 * @return {!Array.<!WebInspector.Layer>}
477 return this._children;
481 * @param {!WebInspector.Layer} child
483 addChild: function(child)
486 console.assert(false, "Child already has a parent");
487 this._children.push(child);
488 child._parent = this;
492 * @param {?WebInspector.DOMNode} node
494 _setNode: function(node)
500 * @return {?WebInspector.DOMNode}
508 * @return {?WebInspector.DOMNode}
510 nodeForSelfOrAncestor: function()
512 for (var layer = this; layer; layer = layer._parent) {
524 return this._layerPayload.offsetX;
532 return this._layerPayload.offsetY;
540 return this._layerPayload.width;
548 return this._layerPayload.height;
552 * @return {?Array.<number>}
554 transform: function()
556 return this._layerPayload.transform;
560 * @return {!Array.<number>}
568 * @return {!Array.<number>}
570 anchorPoint: function()
573 this._layerPayload.anchorX || 0,
574 this._layerPayload.anchorY || 0,
575 this._layerPayload.anchorZ || 0,
582 invisible: function()
584 return this._layerPayload.invisible;
590 paintCount: function()
592 return this._paintCount || this._layerPayload.paintCount;
596 * @return {?DOMAgent.Rect}
598 lastPaintRect: function()
600 return this._lastPaintRect;
604 * @return {!Array.<!LayerTreeAgent.ScrollRect>}
606 scrollRects: function()
608 return this._scrollRects;
612 * @param {function(!Array.<string>)} callback
614 requestCompositingReasons: function(callback)
616 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []);
617 LayerTreeAgent.compositingReasons(this.id(), wrappedCallback);
621 * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
623 requestSnapshot: function(callback)
625 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot);
626 LayerTreeAgent.makeSnapshot(this.id(), wrappedCallback);
630 * @param {!DOMAgent.Rect} rect
632 _didPaint: function(rect)
634 this._lastPaintRect = rect;
635 this._paintCount = this.paintCount() + 1;
640 * @param {!LayerTreeAgent.Layer} layerPayload
642 _reset: function(layerPayload)
646 this._paintCount = 0;
647 this._layerPayload = layerPayload;
649 this._scrollRects = this._layerPayload.scrollRects || [];
653 * @param {!Array.<number>} a
654 * @return {!CSSMatrix}
656 _matrixFromArray: function(a)
658 function toFixed9(x) { return x.toFixed(9); }
659 return new WebKitCSSMatrix("matrix3d(" + a.map(toFixed9).join(",") + ")");
663 * @param {!CSSMatrix} parentTransform
664 * @return {!CSSMatrix}
666 _calculateTransformToViewport: function(parentTransform)
668 var offsetMatrix = new WebKitCSSMatrix().translate(this._layerPayload.offsetX, this._layerPayload.offsetY);
669 var matrix = offsetMatrix;
671 if (this._layerPayload.transform) {
672 var transformMatrix = this._matrixFromArray(this._layerPayload.transform);
673 var anchorVector = new WebInspector.Geometry.Vector(this._layerPayload.width * this.anchorPoint()[0], this._layerPayload.height * this.anchorPoint()[1], this.anchorPoint()[2]);
674 var anchorPoint = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(anchorVector, matrix);
675 var anchorMatrix = new WebKitCSSMatrix().translate(-anchorPoint.x, -anchorPoint.y, -anchorPoint.z);
676 matrix = anchorMatrix.inverse().multiply(transformMatrix.multiply(anchorMatrix.multiply(matrix)));
679 matrix = parentTransform.multiply(matrix);
684 * @param {number} width
685 * @param {number} height
686 * @return {!Array.<number>}
688 _createVertexArrayForRect: function(width, height)
690 return [0, 0, 0, width, 0, 0, width, height, 0, 0, height, 0];
694 * @param {!CSSMatrix} parentTransform
696 _calculateQuad: function(parentTransform)
698 var matrix = this._calculateTransformToViewport(parentTransform);
700 var vertices = this._createVertexArrayForRect(this._layerPayload.width, this._layerPayload.height);
701 for (var i = 0; i < 4; ++i) {
702 var point = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]), matrix);
703 this._quad.push(point.x, point.y);
706 function calculateQuadForLayer(layer)
708 layer._calculateQuad(matrix);
711 this._children.forEach(calculateQuadForLayer);
717 * @param {!WebInspector.TracingLayerPayload} payload
718 * @implements {WebInspector.Layer}
720 WebInspector.TracingLayer = function(payload)
722 this._layerId = String(payload.layer_id);
723 this._offsetX = payload.position[0];
724 this._offsetY = payload.position[1];
725 this._width = payload.bounds.width;
726 this._height = payload.bounds.height;
728 this._parentLayerId = null;
730 this._quad = payload.layer_quad || [];
731 this._createScrollRects(payload);
734 WebInspector.TracingLayer.prototype = {
740 return this._layerId;
748 return this._parentLayerId;
752 * @return {?WebInspector.Layer}
764 return !this.parentId();
768 * @return {!Array.<!WebInspector.Layer>}
772 return this._children;
776 * @param {!WebInspector.Layer} child
778 addChild: function(child)
781 console.assert(false, "Child already has a parent");
782 this._children.push(child);
783 child._parent = this;
784 child._parentLayerId = this._layerId;
788 * @return {?WebInspector.DOMNode}
796 * @return {?WebInspector.DOMNode}
798 nodeForSelfOrAncestor: function()
808 return this._offsetX;
816 return this._offsetY;
836 * @return {?Array.<number>}
838 transform: function()
844 * @return {!Array.<number>}
852 * @return {!Array.<number>}
854 anchorPoint: function()
856 return [0.5, 0.5, 0];
862 invisible: function()
870 paintCount: function()
876 * @return {?DOMAgent.Rect}
878 lastPaintRect: function()
884 * @return {!Array.<!LayerTreeAgent.ScrollRect>}
886 scrollRects: function()
888 return this._scrollRects;
892 * @param {!Array.<number>} params
893 * @param {string} type
896 _scrollRectsFromParams: function(params, type)
898 return {rect: {x: params[0], y: params[1], width: params[2], height: params[3]}, type: type};
902 * @param {!WebInspector.TracingLayerPayload} payload
904 _createScrollRects: function(payload)
906 this._scrollRects = [];
907 if (payload.non_fast_scrollable_region)
908 this._scrollRects.push(this._scrollRectsFromParams(payload.non_fast_scrollable_region, WebInspector.LayerTreeModel.ScrollRectType.NonFastScrollable.name));
909 if (payload.touch_event_handler_region)
910 this._scrollRects.push(this._scrollRectsFromParams(payload.touch_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.TouchEventHandler.name));
911 if (payload.wheel_event_handler_region)
912 this._scrollRects.push(this._scrollRectsFromParams(payload.wheel_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.WheelEventHandler.name));
913 if (payload.scroll_event_handler_region)
914 this._scrollRects.push(this._scrollRectsFromParams(payload.scroll_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.RepaintsOnScroll.name));
918 * @param {function(!Array.<string>)} callback
920 requestCompositingReasons: function(callback)
922 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []);
923 LayerTreeAgent.compositingReasons(this.id(), wrappedCallback);
927 * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
929 requestSnapshot: function(callback)
931 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot);
932 LayerTreeAgent.makeSnapshot(this.id(), wrappedCallback);
938 * @param {!Array.<!LayerTreeAgent.Layer>} layers
940 WebInspector.LayerTreeSnapshot = function(layers)
942 this.layers = layers;
947 * @param {!WebInspector.TracingLayerPayload} root
949 WebInspector.TracingLayerSnapshot = function(root)
956 * @implements {LayerTreeAgent.Dispatcher}
957 * @param {!WebInspector.LayerTreeModel} layerTreeModel
959 WebInspector.LayerTreeDispatcher = function(layerTreeModel)
961 this._layerTreeModel = layerTreeModel;
964 WebInspector.LayerTreeDispatcher.prototype = {
966 * @param {!Array.<!LayerTreeAgent.Layer>=} layers
968 layerTreeDidChange: function(layers)
970 this._layerTreeModel._layerTreeChanged(layers);
974 * @param {!LayerTreeAgent.LayerId} layerId
975 * @param {!DOMAgent.Rect} clipRect
977 layerPainted: function(layerId, clipRect)
979 this._layerTreeModel._layerPainted(layerId, clipRect);