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>,
41 compositing_reasons: Array.<string>
44 WebInspector.TracingLayerPayload;
48 * @extends {WebInspector.SDKModel}
50 WebInspector.LayerTreeModel = function(target)
52 WebInspector.SDKModel.call(this, WebInspector.LayerTreeModel, target);
53 target.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this));
54 WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.MainFrameNavigated, this._onMainFrameNavigated, this);
55 /** @type {?WebInspector.LayerTreeBase} */
56 this._layerTree = null;
59 WebInspector.LayerTreeModel.Events = {
60 LayerTreeChanged: "LayerTreeChanged",
61 LayerPainted: "LayerPainted",
64 WebInspector.LayerTreeModel.ScrollRectType = {
65 NonFastScrollable: {name: "NonFastScrollable", description: "Non fast scrollable"},
66 TouchEventHandler: {name: "TouchEventHandler", description: "Touch event handler"},
67 WheelEventHandler: {name: "WheelEventHandler", description: "Wheel event handler"},
68 RepaintsOnScroll: {name: "RepaintsOnScroll", description: "Repaints on scroll"}
71 WebInspector.LayerTreeModel.prototype = {
76 this._enabled = false;
77 this._layerTree = null;
78 this.target().layerTreeAgent().disable();
89 _forceEnable: function()
91 this._layerTree = new WebInspector.AgentLayerTree(this.target());
92 this._lastPaintRectByLayerId = {};
93 this.target().layerTreeAgent().enable();
97 * @param {!WebInspector.LayerTreeBase} layerTree
99 setLayerTree: function(layerTree)
102 this._layerTree = layerTree;
103 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
107 * @return {?WebInspector.LayerTreeBase}
109 layerTree: function()
111 return this._layerTree;
115 * @param {?Array.<!LayerTreeAgent.Layer>} layers
117 _layerTreeChanged: function(layers)
121 var layerTree = /** @type {!WebInspector.AgentLayerTree} */ (this._layerTree);
122 layerTree.setLayers(layers, onLayersSet.bind(this));
125 * @this {WebInspector.LayerTreeModel}
127 function onLayersSet()
129 for (var layerId in this._lastPaintRectByLayerId) {
130 var lastPaintRect = this._lastPaintRectByLayerId[layerId];
131 var layer = layerTree.layerById(layerId);
133 layer._lastPaintRect = lastPaintRect;
135 this._lastPaintRectByLayerId = {};
137 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
142 * @param {!LayerTreeAgent.LayerId} layerId
143 * @param {!DOMAgent.Rect} clipRect
145 _layerPainted: function(layerId, clipRect)
149 var layerTree = /** @type {!WebInspector.AgentLayerTree} */ (this._layerTree);
150 var layer = layerTree.layerById(layerId);
152 this._lastPaintRectByLayerId[layerId] = clipRect;
155 layer._didPaint(clipRect);
156 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted, layer);
159 _onMainFrameNavigated: function()
165 __proto__: WebInspector.SDKModel.prototype
170 * @param {?WebInspector.Target} target
172 WebInspector.LayerTreeBase = function(target)
174 this._target = target;
175 this._layersById = {};
176 this._backendNodeIdToNodeId = {};
180 WebInspector.LayerTreeBase.prototype = {
184 this._contentRoot = null;
188 * @return {?WebInspector.Layer}
196 * @return {?WebInspector.Layer}
198 contentRoot: function()
200 return this._contentRoot;
204 * @param {function(!WebInspector.Layer)} callback
205 * @param {?WebInspector.Layer=} root
208 forEachLayer: function(callback, root)
215 return callback(root) || root.children().some(this.forEachLayer.bind(this, callback));
220 * @return {?WebInspector.Layer}
222 layerById: function(id)
224 return this._layersById[id] || null;
228 * @param {!Array.<number>} requestedNodeIds
229 * @param {function()} callback
231 _resolveBackendNodeIds: function(requestedNodeIds, callback)
233 if (!requestedNodeIds.length || !this._target) {
238 this._target.domModel.pushNodesByBackendIdsToFrontend(requestedNodeIds, populateBackendNodeIdMap.bind(this));
241 * @this {WebInspector.LayerTreeBase}
242 * @param {?Array.<number>} nodeIds
244 function populateBackendNodeIdMap(nodeIds)
247 for (var i = 0; i < requestedNodeIds.length; ++i) {
248 var nodeId = nodeIds[i];
250 this._backendNodeIdToNodeId[requestedNodeIds[i]] = nodeId;
258 * @param {!Object} viewportSize
260 setViewportSize: function(viewportSize)
262 this._viewportSize = viewportSize;
266 * @return {!Object | undefined}
268 viewportSize: function()
270 return this._viewportSize;
275 * @return {?WebInspector.DOMNode}
277 _nodeForId: function(id)
279 return this._target ? this._target.domModel.nodeForId(id) : null;
285 * @extends {WebInspector.LayerTreeBase}
286 * @param {?WebInspector.Target} target
288 WebInspector.TracingLayerTree = function(target)
290 WebInspector.LayerTreeBase.call(this, target);
293 WebInspector.TracingLayerTree.prototype = {
295 * @param {!WebInspector.TracingLayerPayload} root
296 * @param {!function()} callback
298 setLayers: function(root, callback)
300 var idsToResolve = [];
301 this._extractNodeIdsToResolve(idsToResolve, {}, root);
302 this._resolveBackendNodeIds(idsToResolve, onBackendNodeIdsResolved.bind(this));
305 * @this {WebInspector.TracingLayerTree}
307 function onBackendNodeIdsResolved()
309 var oldLayersById = this._layersById;
310 this._layersById = {};
311 this._contentRoot = null;
312 this._root = this._innerSetLayers(oldLayersById, root);
318 * @param {!Object.<(string|number), !WebInspector.Layer>} oldLayersById
319 * @param {!WebInspector.TracingLayerPayload} payload
320 * @return {!WebInspector.TracingLayer}
322 _innerSetLayers: function(oldLayersById, payload)
324 var layer = /** @type {?WebInspector.TracingLayer} */ (oldLayersById[payload.layer_id]);
326 layer._reset(payload);
328 layer = new WebInspector.TracingLayer(payload);
329 this._layersById[payload.layer_id] = layer;
330 if (payload.owner_node) {
331 if (!this._contentRoot && payload.draws_content)
332 this._contentRoot = layer;
334 if (this._backendNodeIdToNodeId[payload.owner_node])
335 layer._setNode(this._nodeForId(this._backendNodeIdToNodeId[payload.owner_node]));
338 for (var i = 0; payload.children && i < payload.children.length; ++i)
339 layer.addChild(this._innerSetLayers(oldLayersById, payload.children[i]));
344 * @param {!Array.<number>} nodeIdsToResolve
345 * @param {!Object} seenNodeIds
346 * @param {!WebInspector.TracingLayerPayload} payload
348 _extractNodeIdsToResolve: function(nodeIdsToResolve, seenNodeIds, payload)
350 var backendNodeId = payload.owner_node;
351 if (backendNodeId && !seenNodeIds[backendNodeId] && !(this._backendNodeIdToNodeId[backendNodeId] && this._nodeForId(backendNodeId))) {
352 seenNodeIds[backendNodeId] = true;
353 nodeIdsToResolve.push(backendNodeId);
355 for (var i = 0; payload.children && i < payload.children.length; ++i)
356 this._extractNodeIdsToResolve(nodeIdsToResolve, seenNodeIds, payload.children[i]);
359 __proto__: WebInspector.LayerTreeBase.prototype
364 * @param {?WebInspector.Target} target
365 * @extends {WebInspector.LayerTreeBase}
367 WebInspector.AgentLayerTree = function(target)
369 WebInspector.LayerTreeBase.call(this, target);
372 WebInspector.AgentLayerTree.prototype = {
374 * @param {?Array.<!LayerTreeAgent.Layer>} payload
375 * @param {function()} callback
377 setLayers: function(payload, callback)
380 onBackendNodeIdsResolved.call(this);
384 var idsToResolve = {};
385 var requestedIds = [];
386 for (var i = 0; i < payload.length; ++i) {
387 var backendNodeId = payload[i].backendNodeId;
388 if (!backendNodeId || idsToResolve[backendNodeId] ||
389 (this._backendNodeIdToNodeId[backendNodeId] && this._nodeForId(this._backendNodeIdToNodeId[backendNodeId]))) {
392 idsToResolve[backendNodeId] = true;
393 requestedIds.push(backendNodeId);
395 this._resolveBackendNodeIds(requestedIds, onBackendNodeIdsResolved.bind(this));
398 * @this {WebInspector.AgentLayerTree}
400 function onBackendNodeIdsResolved()
402 this._innerSetLayers(payload);
408 * @param {?Array.<!LayerTreeAgent.Layer>} layers
410 _innerSetLayers: function(layers)
413 // Payload will be null when not in the composited mode.
416 var oldLayersById = this._layersById;
417 this._layersById = {};
418 for (var i = 0; i < layers.length; ++i) {
419 var layerId = layers[i].layerId;
420 var layer = oldLayersById[layerId];
422 layer._reset(layers[i]);
424 layer = new WebInspector.AgentLayer(this._target, layers[i]);
425 this._layersById[layerId] = layer;
426 if (layers[i].backendNodeId) {
427 layer._setNode(this._nodeForId(this._backendNodeIdToNodeId[layers[i].backendNodeId]));
428 if (!this._contentRoot)
429 this._contentRoot = layer;
431 var parentId = layer.parentId();
433 var parent = this._layersById[parentId];
435 console.assert(parent, "missing parent " + parentId + " for layer " + layerId);
436 parent.addChild(layer);
439 console.assert(false, "Multiple root layers");
444 this._root._calculateQuad(new WebKitCSSMatrix());
447 __proto__: WebInspector.LayerTreeBase.prototype
453 WebInspector.Layer = function()
457 WebInspector.Layer.prototype = {
466 parentId: function() { },
469 * @return {?WebInspector.Layer}
471 parent: function() { },
476 isRoot: function() { },
479 * @return {!Array.<!WebInspector.Layer>}
481 children: function() { },
484 * @param {!WebInspector.Layer} child
486 addChild: function(child) { },
489 * @return {?WebInspector.DOMNode}
491 node: function() { },
494 * @return {?WebInspector.DOMNode}
496 nodeForSelfOrAncestor: function() { },
501 offsetX: function() { },
506 offsetY: function() { },
511 width: function() { },
516 height: function() { },
519 * @return {?Array.<number>}
521 transform: function() { },
524 * @return {!Array.<number>}
526 quad: function() { },
529 * @return {!Array.<number>}
531 anchorPoint: function() { },
536 invisible: function() { },
541 paintCount: function() { },
544 * @return {?DOMAgent.Rect}
546 lastPaintRect: function() { },
549 * @return {!Array.<!LayerTreeAgent.ScrollRect>}
551 scrollRects: function() { },
554 * @param {function(!Array.<string>)} callback
556 requestCompositingReasons: function(callback) { },
559 * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
561 requestSnapshot: function(callback) { },
566 * @implements {WebInspector.Layer}
567 * @param {?WebInspector.Target} target
568 * @param {!LayerTreeAgent.Layer} layerPayload
570 WebInspector.AgentLayer = function(target, layerPayload)
572 this._target = target;
573 this._reset(layerPayload);
576 WebInspector.AgentLayer.prototype = {
582 return this._layerPayload.layerId;
590 return this._layerPayload.parentLayerId;
594 * @return {?WebInspector.Layer}
606 return !this.parentId();
610 * @return {!Array.<!WebInspector.Layer>}
614 return this._children;
618 * @param {!WebInspector.Layer} child
620 addChild: function(child)
623 console.assert(false, "Child already has a parent");
624 this._children.push(child);
625 child._parent = this;
629 * @param {?WebInspector.DOMNode} node
631 _setNode: function(node)
637 * @return {?WebInspector.DOMNode}
645 * @return {?WebInspector.DOMNode}
647 nodeForSelfOrAncestor: function()
649 for (var layer = this; layer; layer = layer._parent) {
661 return this._layerPayload.offsetX;
669 return this._layerPayload.offsetY;
677 return this._layerPayload.width;
685 return this._layerPayload.height;
689 * @return {?Array.<number>}
691 transform: function()
693 return this._layerPayload.transform;
697 * @return {!Array.<number>}
705 * @return {!Array.<number>}
707 anchorPoint: function()
710 this._layerPayload.anchorX || 0,
711 this._layerPayload.anchorY || 0,
712 this._layerPayload.anchorZ || 0,
719 invisible: function()
721 return this._layerPayload.invisible;
727 paintCount: function()
729 return this._paintCount || this._layerPayload.paintCount;
733 * @return {?DOMAgent.Rect}
735 lastPaintRect: function()
737 return this._lastPaintRect;
741 * @return {!Array.<!LayerTreeAgent.ScrollRect>}
743 scrollRects: function()
745 return this._scrollRects;
749 * @param {function(!Array.<string>)} callback
751 requestCompositingReasons: function(callback)
758 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []);
759 this._target.layerTreeAgent().compositingReasons(this.id(), wrappedCallback);
763 * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
765 requestSnapshot: function(callback)
772 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot.bind(null, this._target));
773 this._target.layerTreeAgent().makeSnapshot(this.id(), wrappedCallback);
777 * @param {!DOMAgent.Rect} rect
779 _didPaint: function(rect)
781 this._lastPaintRect = rect;
782 this._paintCount = this.paintCount() + 1;
787 * @param {!LayerTreeAgent.Layer} layerPayload
789 _reset: function(layerPayload)
791 /** @type {?WebInspector.DOMNode} */
795 this._paintCount = 0;
796 this._layerPayload = layerPayload;
798 this._scrollRects = this._layerPayload.scrollRects || [];
802 * @param {!Array.<number>} a
803 * @return {!CSSMatrix}
805 _matrixFromArray: function(a)
807 function toFixed9(x) { return x.toFixed(9); }
808 return new WebKitCSSMatrix("matrix3d(" + a.map(toFixed9).join(",") + ")");
812 * @param {!CSSMatrix} parentTransform
813 * @return {!CSSMatrix}
815 _calculateTransformToViewport: function(parentTransform)
817 var offsetMatrix = new WebKitCSSMatrix().translate(this._layerPayload.offsetX, this._layerPayload.offsetY);
818 var matrix = offsetMatrix;
820 if (this._layerPayload.transform) {
821 var transformMatrix = this._matrixFromArray(this._layerPayload.transform);
822 var anchorVector = new WebInspector.Geometry.Vector(this._layerPayload.width * this.anchorPoint()[0], this._layerPayload.height * this.anchorPoint()[1], this.anchorPoint()[2]);
823 var anchorPoint = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(anchorVector, matrix);
824 var anchorMatrix = new WebKitCSSMatrix().translate(-anchorPoint.x, -anchorPoint.y, -anchorPoint.z);
825 matrix = anchorMatrix.inverse().multiply(transformMatrix.multiply(anchorMatrix.multiply(matrix)));
828 matrix = parentTransform.multiply(matrix);
833 * @param {number} width
834 * @param {number} height
835 * @return {!Array.<number>}
837 _createVertexArrayForRect: function(width, height)
839 return [0, 0, 0, width, 0, 0, width, height, 0, 0, height, 0];
843 * @param {!CSSMatrix} parentTransform
845 _calculateQuad: function(parentTransform)
847 var matrix = this._calculateTransformToViewport(parentTransform);
849 var vertices = this._createVertexArrayForRect(this._layerPayload.width, this._layerPayload.height);
850 for (var i = 0; i < 4; ++i) {
851 var point = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]), matrix);
852 this._quad.push(point.x, point.y);
855 function calculateQuadForLayer(layer)
857 layer._calculateQuad(matrix);
860 this._children.forEach(calculateQuadForLayer);
866 * @param {!WebInspector.TracingLayerPayload} payload
867 * @implements {WebInspector.Layer}
869 WebInspector.TracingLayer = function(payload)
871 this._reset(payload);
874 WebInspector.TracingLayer.prototype = {
876 * @param {!WebInspector.TracingLayerPayload} payload
878 _reset: function(payload)
880 /** @type {?WebInspector.DOMNode} */
882 this._layerId = String(payload.layer_id);
883 this._offsetX = payload.position[0];
884 this._offsetY = payload.position[1];
885 this._width = payload.bounds.width;
886 this._height = payload.bounds.height;
888 this._parentLayerId = null;
890 this._quad = payload.layer_quad || [];
891 this._createScrollRects(payload);
892 this._compositingReasons = payload.compositing_reasons || [];
900 return this._layerId;
908 return this._parentLayerId;
912 * @return {?WebInspector.Layer}
924 return !this.parentId();
928 * @return {!Array.<!WebInspector.Layer>}
932 return this._children;
936 * @param {!WebInspector.Layer} child
938 addChild: function(child)
941 console.assert(false, "Child already has a parent");
942 this._children.push(child);
943 child._parent = this;
944 child._parentLayerId = this._layerId;
949 * @param {?WebInspector.DOMNode} node
951 _setNode: function(node)
957 * @return {?WebInspector.DOMNode}
965 * @return {?WebInspector.DOMNode}
967 nodeForSelfOrAncestor: function()
969 for (var layer = this; layer; layer = layer._parent) {
981 return this._offsetX;
989 return this._offsetY;
1005 return this._height;
1009 * @return {?Array.<number>}
1011 transform: function()
1017 * @return {!Array.<number>}
1025 * @return {!Array.<number>}
1027 anchorPoint: function()
1029 return [0.5, 0.5, 0];
1035 invisible: function()
1043 paintCount: function()
1049 * @return {?DOMAgent.Rect}
1051 lastPaintRect: function()
1057 * @return {!Array.<!LayerTreeAgent.ScrollRect>}
1059 scrollRects: function()
1061 return this._scrollRects;
1065 * @param {!Array.<number>} params
1066 * @param {string} type
1069 _scrollRectsFromParams: function(params, type)
1071 return {rect: {x: params[0], y: params[1], width: params[2], height: params[3]}, type: type};
1075 * @param {!WebInspector.TracingLayerPayload} payload
1077 _createScrollRects: function(payload)
1079 this._scrollRects = [];
1080 if (payload.non_fast_scrollable_region)
1081 this._scrollRects.push(this._scrollRectsFromParams(payload.non_fast_scrollable_region, WebInspector.LayerTreeModel.ScrollRectType.NonFastScrollable.name));
1082 if (payload.touch_event_handler_region)
1083 this._scrollRects.push(this._scrollRectsFromParams(payload.touch_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.TouchEventHandler.name));
1084 if (payload.wheel_event_handler_region)
1085 this._scrollRects.push(this._scrollRectsFromParams(payload.wheel_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.WheelEventHandler.name));
1086 if (payload.scroll_event_handler_region)
1087 this._scrollRects.push(this._scrollRectsFromParams(payload.scroll_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.RepaintsOnScroll.name));
1091 * @param {function(!Array.<string>)} callback
1093 requestCompositingReasons: function(callback)
1095 callback(this._compositingReasons);
1099 * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
1101 requestSnapshot: function(callback)
1103 var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot);
1104 LayerTreeAgent.makeSnapshot(this.id(), wrappedCallback);
1110 * @param {?WebInspector.Target} target
1112 WebInspector.DeferredLayerTree = function(target)
1114 this._target = target;
1117 WebInspector.DeferredLayerTree.prototype = {
1119 * @param {function(!WebInspector.LayerTreeBase)} callback
1121 resolve: function(callback) { },
1124 * @return {?WebInspector.Target}
1128 return this._target;
1134 * @extends {WebInspector.DeferredLayerTree}
1135 * @param {?WebInspector.Target} target
1136 * @param {!Array.<!LayerTreeAgent.Layer>} layers
1138 WebInspector.DeferredAgentLayerTree = function(target, layers)
1140 WebInspector.DeferredLayerTree.call(this, target);
1141 this._layers = layers;
1144 WebInspector.DeferredAgentLayerTree.prototype = {
1146 * @param {function(!WebInspector.LayerTreeBase)} callback
1148 resolve: function(callback)
1150 var result = new WebInspector.AgentLayerTree(this._target);
1151 result.setLayers(this._layers, callback.bind(null, result));
1154 __proto__: WebInspector.DeferredLayerTree.prototype
1159 * @implements {LayerTreeAgent.Dispatcher}
1160 * @param {!WebInspector.LayerTreeModel} layerTreeModel
1162 WebInspector.LayerTreeDispatcher = function(layerTreeModel)
1164 this._layerTreeModel = layerTreeModel;
1167 WebInspector.LayerTreeDispatcher.prototype = {
1169 * @param {!Array.<!LayerTreeAgent.Layer>=} layers
1171 layerTreeDidChange: function(layers)
1173 this._layerTreeModel._layerTreeChanged(layers || null);
1177 * @param {!LayerTreeAgent.LayerId} layerId
1178 * @param {!DOMAgent.Rect} clipRect
1180 layerPainted: function(layerId, clipRect)
1182 this._layerTreeModel._layerPainted(layerId, clipRect);