Home | History | Annotate | Download | only in sdk
      1 /*
      2  * Copyright (C) 2013 Google 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 are
      6  * met:
      7  *
      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
     13  * distribution.
     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.
     17  *
     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.
     29  */
     30 
     31 /** @typedef {!{
     32         bounds: {height: number, width: number},
     33         children: Array.<!WebInspector.TracingLayerPayload>,
     34         layer_id: number,
     35         position: Array.<number>,
     36         scroll_offset: Array.<number>,
     37         layer_quad: Array.<number>,
     38         draws_content: number,
     39         transform: Array.<number>,
     40         owner_node: number
     41     }}
     42 */
     43 WebInspector.TracingLayerPayload;
     44 
     45 /**
     46   * @constructor
     47   * @extends {WebInspector.SDKModel}
     48   */
     49 WebInspector.LayerTreeModel = function(target)
     50 {
     51     WebInspector.SDKModel.call(this, WebInspector.LayerTreeModel, target);
     52     target.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this));
     53     target.domModel.addEventListener(WebInspector.DOMModel.Events.DocumentUpdated, this._onDocumentUpdated, this);
     54     /** @type {?WebInspector.LayerTreeBase} */
     55     this._layerTree = null;
     56 }
     57 
     58 WebInspector.LayerTreeModel.Events = {
     59     LayerTreeChanged: "LayerTreeChanged",
     60     LayerPainted: "LayerPainted",
     61 }
     62 
     63 WebInspector.LayerTreeModel.ScrollRectType = {
     64     NonFastScrollable: {name: "NonFastScrollable", description: "Non fast scrollable"},
     65     TouchEventHandler: {name: "TouchEventHandler", description: "Touch event handler"},
     66     WheelEventHandler: {name: "WheelEventHandler", description: "Wheel event handler"},
     67     RepaintsOnScroll: {name: "RepaintsOnScroll", description: "Repaints on scroll"}
     68 }
     69 
     70 WebInspector.LayerTreeModel.prototype = {
     71     disable: function()
     72     {
     73         if (!this._enabled)
     74             return;
     75         this._enabled = false;
     76         this._layerTree = null;
     77         this.target().layerTreeAgent().disable();
     78     },
     79 
     80     enable: function()
     81     {
     82         if (this._enabled)
     83             return;
     84         this._enabled = true;
     85         this._layerTree = new WebInspector.AgentLayerTree(this.target());
     86         this._lastPaintRectByLayerId = {};
     87         this.target().layerTreeAgent().enable();
     88     },
     89 
     90     /**
     91      * @param {!WebInspector.LayerTreeBase} layerTree
     92      */
     93     setLayerTree: function(layerTree)
     94     {
     95         this.disable();
     96         this._layerTree = layerTree;
     97         this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
     98     },
     99 
    100     /**
    101      * @return {?WebInspector.LayerTreeBase}
    102      */
    103     layerTree: function()
    104     {
    105         return this._layerTree;
    106     },
    107 
    108     /**
    109      * @param {?Array.<!LayerTreeAgent.Layer>} layers
    110      */
    111     _layerTreeChanged: function(layers)
    112     {
    113         if (!this._enabled)
    114             return;
    115         var layerTree = /** @type {!WebInspector.AgentLayerTree} */ (this._layerTree);
    116         layerTree.setLayers(layers, onLayersSet.bind(this));
    117 
    118         /**
    119          * @this {WebInspector.LayerTreeModel}
    120          */
    121         function onLayersSet()
    122         {
    123             for (var layerId in this._lastPaintRectByLayerId) {
    124                 var lastPaintRect = this._lastPaintRectByLayerId[layerId];
    125                 var layer = layerTree.layerById(layerId);
    126                 if (layer)
    127                     layer._lastPaintRect = lastPaintRect;
    128             }
    129             this._lastPaintRectByLayerId = {};
    130 
    131             this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged);
    132         }
    133     },
    134 
    135     /**
    136      * @param {!LayerTreeAgent.LayerId} layerId
    137      * @param {!DOMAgent.Rect} clipRect
    138      */
    139     _layerPainted: function(layerId, clipRect)
    140     {
    141         if (!this._enabled)
    142             return;
    143         var layerTree = /** @type {!WebInspector.AgentLayerTree} */ (this._layerTree);
    144         var layer = layerTree.layerById(layerId);
    145         if (!layer) {
    146             this._lastPaintRectByLayerId[layerId] = clipRect;
    147             return;
    148         }
    149         layer._didPaint(clipRect);
    150         this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted, layer);
    151     },
    152 
    153     _onDocumentUpdated: function()
    154     {
    155         if (!this._enabled)
    156             return;
    157         this.disable();
    158         this.enable();
    159     },
    160 
    161     __proto__: WebInspector.SDKModel.prototype
    162 }
    163 
    164 /**
    165   * @constructor
    166   * @param {?WebInspector.Target} target
    167   */
    168 WebInspector.LayerTreeBase = function(target)
    169 {
    170     this._target = target;
    171     this._layersById = {};
    172     this._backendNodeIdToNodeId = {};
    173     this._reset();
    174 }
    175 
    176 WebInspector.LayerTreeBase.prototype = {
    177     _reset: function()
    178     {
    179         this._root = null;
    180         this._contentRoot = null;
    181     },
    182 
    183     /**
    184      * @return {?WebInspector.Layer}
    185      */
    186     root: function()
    187     {
    188         return this._root;
    189     },
    190 
    191     /**
    192      * @return {?WebInspector.Layer}
    193      */
    194     contentRoot: function()
    195     {
    196         return this._contentRoot;
    197     },
    198 
    199     /**
    200      * @param {function(!WebInspector.Layer)} callback
    201      * @param {?WebInspector.Layer=} root
    202      * @return {boolean}
    203      */
    204     forEachLayer: function(callback, root)
    205     {
    206         if (!root) {
    207             root = this.root();
    208             if (!root)
    209                 return false;
    210         }
    211         return callback(root) || root.children().some(this.forEachLayer.bind(this, callback));
    212     },
    213 
    214     /**
    215      * @param {string} id
    216      * @return {?WebInspector.Layer}
    217      */
    218     layerById: function(id)
    219     {
    220         return this._layersById[id] || null;
    221     },
    222 
    223     /**
    224      * @param {!Array.<number>} requestedNodeIds
    225      * @param {function()} callback
    226      */
    227     _resolveBackendNodeIds: function(requestedNodeIds, callback)
    228     {
    229         if (!requestedNodeIds.length || !this._target) {
    230             callback();
    231             return;
    232         }
    233 
    234         this._target.domModel.pushNodesByBackendIdsToFrontend(requestedNodeIds, populateBackendNodeIdMap.bind(this));
    235 
    236         /**
    237          * @this {WebInspector.LayerTreeBase}
    238          * @param {?Array.<number>} nodeIds
    239          */
    240         function populateBackendNodeIdMap(nodeIds)
    241         {
    242             if (nodeIds) {
    243                 for (var i = 0; i < requestedNodeIds.length; ++i) {
    244                     var nodeId = nodeIds[i];
    245                     if (nodeId)
    246                         this._backendNodeIdToNodeId[requestedNodeIds[i]] = nodeId;
    247                 }
    248             }
    249             callback();
    250         }
    251     },
    252 
    253     /**
    254      * @param {!Object} viewportSize
    255      */
    256     setViewportSize: function(viewportSize)
    257     {
    258         this._viewportSize = viewportSize;
    259     },
    260 
    261     /**
    262      * @return {!Object | undefined}
    263      */
    264     viewportSize: function()
    265     {
    266         return this._viewportSize;
    267     },
    268 
    269     /**
    270      * @param {number} id
    271      * @return {?WebInspector.DOMNode}
    272      */
    273     _nodeForId: function(id)
    274     {
    275         return this._target ? this._target.domModel.nodeForId(id) : null;
    276     }
    277 };
    278 
    279 /**
    280   * @constructor
    281   * @extends {WebInspector.LayerTreeBase}
    282   * @param {?WebInspector.Target} target
    283   */
    284 WebInspector.TracingLayerTree = function(target)
    285 {
    286     WebInspector.LayerTreeBase.call(this, target);
    287 }
    288 
    289 WebInspector.TracingLayerTree.prototype = {
    290     /**
    291      * @param {!WebInspector.TracingLayerPayload} root
    292      * @param {!function()} callback
    293      */
    294     setLayers: function(root, callback)
    295     {
    296         var idsToResolve = [];
    297         this._extractNodeIdsToResolve(idsToResolve, {}, root);
    298         this._resolveBackendNodeIds(idsToResolve, onBackendNodeIdsResolved.bind(this));
    299 
    300         /**
    301          * @this {WebInspector.TracingLayerTree}
    302          */
    303         function onBackendNodeIdsResolved()
    304         {
    305             var oldLayersById = this._layersById;
    306             this._layersById = {};
    307             this._contentRoot = null;
    308             this._root = this._innerSetLayers(oldLayersById, root);
    309             callback();
    310         }
    311     },
    312 
    313     /**
    314      * @param {!Object.<(string|number), !WebInspector.Layer>} oldLayersById
    315      * @param {!WebInspector.TracingLayerPayload} payload
    316      * @return {!WebInspector.TracingLayer}
    317      */
    318     _innerSetLayers: function(oldLayersById, payload)
    319     {
    320         var layer = /** @type {?WebInspector.TracingLayer} */ (oldLayersById[payload.layer_id]);
    321         if (layer)
    322             layer._reset(payload);
    323         else
    324             layer = new WebInspector.TracingLayer(payload);
    325         this._layersById[payload.layer_id] = layer;
    326         if (!this._contentRoot && payload.draws_content)
    327             this._contentRoot = layer;
    328 
    329         if (payload.owner_node && this._backendNodeIdToNodeId[payload.owner_node])
    330             layer._setNode(this._nodeForId(this._backendNodeIdToNodeId[payload.owner_node]));
    331 
    332         for (var i = 0; payload.children && i < payload.children.length; ++i)
    333             layer.addChild(this._innerSetLayers(oldLayersById, payload.children[i]));
    334         return layer;
    335     },
    336 
    337     /**
    338      * @param {!Array.<number>} nodeIdsToResolve
    339      * @param {!Object} seenNodeIds
    340      * @param {!WebInspector.TracingLayerPayload} payload
    341      */
    342     _extractNodeIdsToResolve: function(nodeIdsToResolve, seenNodeIds, payload)
    343     {
    344         var backendNodeId = payload.owner_node;
    345         if (backendNodeId && !seenNodeIds[backendNodeId] && !(this._backendNodeIdToNodeId[backendNodeId] && this._nodeForId(backendNodeId))) {
    346             seenNodeIds[backendNodeId] = true;
    347             nodeIdsToResolve.push(backendNodeId);
    348         }
    349         for (var i = 0; payload.children && i < payload.children.length; ++i)
    350             this._extractNodeIdsToResolve(nodeIdsToResolve, seenNodeIds, payload.children[i]);
    351     },
    352 
    353     __proto__: WebInspector.LayerTreeBase.prototype
    354 }
    355 
    356 /**
    357   * @constructor
    358   * @param {?WebInspector.Target} target
    359   * @extends {WebInspector.LayerTreeBase}
    360   */
    361 WebInspector.AgentLayerTree = function(target)
    362 {
    363     WebInspector.LayerTreeBase.call(this, target);
    364 }
    365 
    366 WebInspector.AgentLayerTree.prototype = {
    367     /**
    368      * @param {?Array.<!LayerTreeAgent.Layer>} payload
    369      * @param {function()} callback
    370      */
    371     setLayers: function(payload, callback)
    372     {
    373         if (!payload) {
    374             onBackendNodeIdsResolved.call(this);
    375             return;
    376         }
    377 
    378         var idsToResolve = {};
    379         var requestedIds = [];
    380         for (var i = 0; i < payload.length; ++i) {
    381             var backendNodeId = payload[i].backendNodeId;
    382             if (!backendNodeId || idsToResolve[backendNodeId] ||
    383                 (this._backendNodeIdToNodeId[backendNodeId] && this._nodeForId(this._backendNodeIdToNodeId[backendNodeId]))) {
    384                 continue;
    385             }
    386             idsToResolve[backendNodeId] = true;
    387             requestedIds.push(backendNodeId);
    388         }
    389         this._resolveBackendNodeIds(requestedIds, onBackendNodeIdsResolved.bind(this));
    390 
    391         /**
    392          * @this {WebInspector.AgentLayerTree}
    393          */
    394         function onBackendNodeIdsResolved()
    395         {
    396             this._innerSetLayers(payload);
    397             callback();
    398         }
    399     },
    400 
    401     /**
    402      * @param {?Array.<!LayerTreeAgent.Layer>} layers
    403      */
    404     _innerSetLayers: function(layers)
    405     {
    406         this._reset();
    407         // Payload will be null when not in the composited mode.
    408         if (!layers)
    409             return;
    410         var oldLayersById = this._layersById;
    411         this._layersById = {};
    412         for (var i = 0; i < layers.length; ++i) {
    413             var layerId = layers[i].layerId;
    414             var layer = oldLayersById[layerId];
    415             if (layer)
    416                 layer._reset(layers[i]);
    417             else
    418                 layer = new WebInspector.AgentLayer(this._target, layers[i]);
    419             this._layersById[layerId] = layer;
    420             if (layers[i].backendNodeId) {
    421                 layer._setNode(this._nodeForId(this._backendNodeIdToNodeId[layers[i].backendNodeId]));
    422                 if (!this._contentRoot)
    423                     this._contentRoot = layer;
    424             }
    425             var parentId = layer.parentId();
    426             if (parentId) {
    427                 var parent = this._layersById[parentId];
    428                 if (!parent)
    429                     console.assert(parent, "missing parent " + parentId + " for layer " + layerId);
    430                 parent.addChild(layer);
    431             } else {
    432                 if (this._root)
    433                     console.assert(false, "Multiple root layers");
    434                 this._root = layer;
    435             }
    436         }
    437         if (this._root)
    438             this._root._calculateQuad(new WebKitCSSMatrix());
    439     },
    440 
    441     __proto__: WebInspector.LayerTreeBase.prototype
    442 }
    443 
    444 /**
    445  * @interface
    446  */
    447 WebInspector.Layer = function()
    448 {
    449 }
    450 
    451 WebInspector.Layer.prototype = {
    452     /**
    453      * @return {string}
    454      */
    455     id: function() { },
    456 
    457     /**
    458      * @return {?string}
    459      */
    460     parentId: function() { },
    461 
    462     /**
    463      * @return {?WebInspector.Layer}
    464      */
    465     parent: function() { },
    466 
    467     /**
    468      * @return {boolean}
    469      */
    470     isRoot: function() { },
    471 
    472     /**
    473      * @return {!Array.<!WebInspector.Layer>}
    474      */
    475     children: function() { },
    476 
    477     /**
    478      * @param {!WebInspector.Layer} child
    479      */
    480     addChild: function(child) { },
    481 
    482     /**
    483      * @return {?WebInspector.DOMNode}
    484      */
    485     node: function() { },
    486 
    487     /**
    488      * @return {?WebInspector.DOMNode}
    489      */
    490     nodeForSelfOrAncestor: function() { },
    491 
    492     /**
    493      * @return {number}
    494      */
    495     offsetX: function() { },
    496 
    497     /**
    498      * @return {number}
    499      */
    500     offsetY: function() { },
    501 
    502     /**
    503      * @return {number}
    504      */
    505     width: function() { },
    506 
    507     /**
    508      * @return {number}
    509      */
    510     height: function() { },
    511 
    512     /**
    513      * @return {?Array.<number>}
    514      */
    515     transform: function() { },
    516 
    517     /**
    518      * @return {!Array.<number>}
    519      */
    520     quad: function() { },
    521 
    522     /**
    523      * @return {!Array.<number>}
    524      */
    525     anchorPoint: function() { },
    526 
    527     /**
    528      * @return {boolean}
    529      */
    530     invisible: function() { },
    531 
    532     /**
    533      * @return {number}
    534      */
    535     paintCount: function() { },
    536 
    537     /**
    538      * @return {?DOMAgent.Rect}
    539      */
    540     lastPaintRect: function() { },
    541 
    542     /**
    543      * @return {!Array.<!LayerTreeAgent.ScrollRect>}
    544      */
    545     scrollRects: function() { },
    546 
    547     /**
    548      * @param {function(!Array.<string>)} callback
    549      */
    550     requestCompositingReasons: function(callback) { },
    551 
    552     /**
    553      * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
    554      */
    555     requestSnapshot: function(callback) { },
    556 }
    557 
    558 /**
    559  * @constructor
    560  * @implements {WebInspector.Layer}
    561  * @param {?WebInspector.Target} target
    562  * @param {!LayerTreeAgent.Layer} layerPayload
    563  */
    564 WebInspector.AgentLayer = function(target, layerPayload)
    565 {
    566     this._target = target;
    567     this._reset(layerPayload);
    568 }
    569 
    570 WebInspector.AgentLayer.prototype = {
    571     /**
    572      * @return {string}
    573      */
    574     id: function()
    575     {
    576         return this._layerPayload.layerId;
    577     },
    578 
    579     /**
    580      * @return {?string}
    581      */
    582     parentId: function()
    583     {
    584         return this._layerPayload.parentLayerId;
    585     },
    586 
    587     /**
    588      * @return {?WebInspector.Layer}
    589      */
    590     parent: function()
    591     {
    592         return this._parent;
    593     },
    594 
    595     /**
    596      * @return {boolean}
    597      */
    598     isRoot: function()
    599     {
    600         return !this.parentId();
    601     },
    602 
    603     /**
    604      * @return {!Array.<!WebInspector.Layer>}
    605      */
    606     children: function()
    607     {
    608         return this._children;
    609     },
    610 
    611     /**
    612      * @param {!WebInspector.Layer} child
    613      */
    614     addChild: function(child)
    615     {
    616         if (child._parent)
    617             console.assert(false, "Child already has a parent");
    618         this._children.push(child);
    619         child._parent = this;
    620     },
    621 
    622     /**
    623      * @param {?WebInspector.DOMNode} node
    624      */
    625     _setNode: function(node)
    626     {
    627         this._node = node;
    628     },
    629 
    630     /**
    631      * @return {?WebInspector.DOMNode}
    632      */
    633     node: function()
    634     {
    635         return this._node;
    636     },
    637 
    638     /**
    639      * @return {?WebInspector.DOMNode}
    640      */
    641     nodeForSelfOrAncestor: function()
    642     {
    643         for (var layer = this; layer; layer = layer._parent) {
    644             if (layer._node)
    645                 return layer._node;
    646         }
    647         return null;
    648     },
    649 
    650     /**
    651      * @return {number}
    652      */
    653     offsetX: function()
    654     {
    655         return this._layerPayload.offsetX;
    656     },
    657 
    658     /**
    659      * @return {number}
    660      */
    661     offsetY: function()
    662     {
    663         return this._layerPayload.offsetY;
    664     },
    665 
    666     /**
    667      * @return {number}
    668      */
    669     width: function()
    670     {
    671         return this._layerPayload.width;
    672     },
    673 
    674     /**
    675      * @return {number}
    676      */
    677     height: function()
    678     {
    679         return this._layerPayload.height;
    680     },
    681 
    682     /**
    683      * @return {?Array.<number>}
    684      */
    685     transform: function()
    686     {
    687         return this._layerPayload.transform;
    688     },
    689 
    690     /**
    691      * @return {!Array.<number>}
    692      */
    693     quad: function()
    694     {
    695         return this._quad;
    696     },
    697 
    698     /**
    699      * @return {!Array.<number>}
    700      */
    701     anchorPoint: function()
    702     {
    703         return [
    704             this._layerPayload.anchorX || 0,
    705             this._layerPayload.anchorY || 0,
    706             this._layerPayload.anchorZ || 0,
    707         ];
    708     },
    709 
    710     /**
    711      * @return {boolean}
    712      */
    713     invisible: function()
    714     {
    715         return this._layerPayload.invisible;
    716     },
    717 
    718     /**
    719      * @return {number}
    720      */
    721     paintCount: function()
    722     {
    723         return this._paintCount || this._layerPayload.paintCount;
    724     },
    725 
    726     /**
    727      * @return {?DOMAgent.Rect}
    728      */
    729     lastPaintRect: function()
    730     {
    731         return this._lastPaintRect;
    732     },
    733 
    734     /**
    735      * @return {!Array.<!LayerTreeAgent.ScrollRect>}
    736      */
    737     scrollRects: function()
    738     {
    739         return this._scrollRects;
    740     },
    741 
    742     /**
    743      * @param {function(!Array.<string>)} callback
    744      */
    745     requestCompositingReasons: function(callback)
    746     {
    747         if (!this._target) {
    748             callback([]);
    749             return;
    750         }
    751 
    752         var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []);
    753         this._target.layerTreeAgent().compositingReasons(this.id(), wrappedCallback);
    754     },
    755 
    756     /**
    757      * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
    758      */
    759     requestSnapshot: function(callback)
    760     {
    761         if (!this._target) {
    762             callback();
    763             return;
    764         }
    765 
    766         var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot.bind(null, this._target));
    767         this._target.layerTreeAgent().makeSnapshot(this.id(), wrappedCallback);
    768     },
    769 
    770     /**
    771      * @param {!DOMAgent.Rect} rect
    772      */
    773     _didPaint: function(rect)
    774     {
    775         this._lastPaintRect = rect;
    776         this._paintCount = this.paintCount() + 1;
    777         this._image = null;
    778     },
    779 
    780     /**
    781      * @param {!LayerTreeAgent.Layer} layerPayload
    782      */
    783     _reset: function(layerPayload)
    784     {
    785         /** @type {?WebInspector.DOMNode} */
    786         this._node = null;
    787         this._children = [];
    788         this._parent = null;
    789         this._paintCount = 0;
    790         this._layerPayload = layerPayload;
    791         this._image = null;
    792         this._scrollRects = this._layerPayload.scrollRects || [];
    793     },
    794 
    795     /**
    796      * @param {!Array.<number>} a
    797      * @return {!CSSMatrix}
    798      */
    799     _matrixFromArray: function(a)
    800     {
    801         function toFixed9(x) { return x.toFixed(9); }
    802         return new WebKitCSSMatrix("matrix3d(" + a.map(toFixed9).join(",") + ")");
    803     },
    804 
    805     /**
    806      * @param {!CSSMatrix} parentTransform
    807      * @return {!CSSMatrix}
    808      */
    809     _calculateTransformToViewport: function(parentTransform)
    810     {
    811         var offsetMatrix = new WebKitCSSMatrix().translate(this._layerPayload.offsetX, this._layerPayload.offsetY);
    812         var matrix = offsetMatrix;
    813 
    814         if (this._layerPayload.transform) {
    815             var transformMatrix = this._matrixFromArray(this._layerPayload.transform);
    816             var anchorVector = new WebInspector.Geometry.Vector(this._layerPayload.width * this.anchorPoint()[0], this._layerPayload.height * this.anchorPoint()[1], this.anchorPoint()[2]);
    817             var anchorPoint = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(anchorVector, matrix);
    818             var anchorMatrix = new WebKitCSSMatrix().translate(-anchorPoint.x, -anchorPoint.y, -anchorPoint.z);
    819             matrix = anchorMatrix.inverse().multiply(transformMatrix.multiply(anchorMatrix.multiply(matrix)));
    820         }
    821 
    822         matrix = parentTransform.multiply(matrix);
    823         return matrix;
    824     },
    825 
    826     /**
    827      * @param {number} width
    828      * @param {number} height
    829      * @return {!Array.<number>}
    830      */
    831     _createVertexArrayForRect: function(width, height)
    832     {
    833         return [0, 0, 0, width, 0, 0, width, height, 0, 0, height, 0];
    834     },
    835 
    836     /**
    837      * @param {!CSSMatrix} parentTransform
    838      */
    839     _calculateQuad: function(parentTransform)
    840     {
    841         var matrix = this._calculateTransformToViewport(parentTransform);
    842         this._quad = [];
    843         var vertices = this._createVertexArrayForRect(this._layerPayload.width, this._layerPayload.height);
    844         for (var i = 0; i < 4; ++i) {
    845             var point = WebInspector.Geometry.multiplyVectorByMatrixAndNormalize(new WebInspector.Geometry.Vector(vertices[i * 3], vertices[i * 3 + 1], vertices[i * 3 + 2]), matrix);
    846             this._quad.push(point.x, point.y);
    847         }
    848 
    849         function calculateQuadForLayer(layer)
    850         {
    851             layer._calculateQuad(matrix);
    852         }
    853 
    854         this._children.forEach(calculateQuadForLayer);
    855     }
    856 }
    857 
    858 /**
    859  * @constructor
    860  * @param {!WebInspector.TracingLayerPayload} payload
    861  * @implements {WebInspector.Layer}
    862  */
    863 WebInspector.TracingLayer = function(payload)
    864 {
    865     this._reset(payload);
    866 }
    867 
    868 WebInspector.TracingLayer.prototype = {
    869     /**
    870      * @param {!WebInspector.TracingLayerPayload} payload
    871      */
    872     _reset: function(payload)
    873     {
    874         /** @type {?WebInspector.DOMNode} */
    875         this._node = null;
    876         this._layerId = String(payload.layer_id);
    877         this._offsetX = payload.position[0];
    878         this._offsetY = payload.position[1];
    879         this._width = payload.bounds.width;
    880         this._height = payload.bounds.height;
    881         this._children = [];
    882         this._parentLayerId = null;
    883         this._parent = null;
    884         this._quad = payload.layer_quad || [];
    885         this._createScrollRects(payload);
    886     },
    887 
    888     /**
    889      * @return {string}
    890      */
    891     id: function()
    892     {
    893         return this._layerId;
    894     },
    895 
    896     /**
    897      * @return {?string}
    898      */
    899     parentId: function()
    900     {
    901         return this._parentLayerId;
    902     },
    903 
    904     /**
    905      * @return {?WebInspector.Layer}
    906      */
    907     parent: function()
    908     {
    909         return this._parent;
    910     },
    911 
    912     /**
    913      * @return {boolean}
    914      */
    915     isRoot: function()
    916     {
    917         return !this.parentId();
    918     },
    919 
    920     /**
    921      * @return {!Array.<!WebInspector.Layer>}
    922      */
    923     children: function()
    924     {
    925         return this._children;
    926     },
    927 
    928     /**
    929      * @param {!WebInspector.Layer} child
    930      */
    931     addChild: function(child)
    932     {
    933         if (child._parent)
    934             console.assert(false, "Child already has a parent");
    935         this._children.push(child);
    936         child._parent = this;
    937         child._parentLayerId = this._layerId;
    938     },
    939 
    940 
    941     /**
    942      * @param {?WebInspector.DOMNode} node
    943      */
    944     _setNode: function(node)
    945     {
    946         this._node = node;
    947     },
    948 
    949     /**
    950      * @return {?WebInspector.DOMNode}
    951      */
    952     node: function()
    953     {
    954         return this._node;
    955     },
    956 
    957     /**
    958      * @return {?WebInspector.DOMNode}
    959      */
    960     nodeForSelfOrAncestor: function()
    961     {
    962         for (var layer = this; layer; layer = layer._parent) {
    963             if (layer._node)
    964                 return layer._node;
    965         }
    966         return null;
    967     },
    968 
    969     /**
    970      * @return {number}
    971      */
    972     offsetX: function()
    973     {
    974         return this._offsetX;
    975     },
    976 
    977     /**
    978      * @return {number}
    979      */
    980     offsetY: function()
    981     {
    982         return this._offsetY;
    983     },
    984 
    985     /**
    986      * @return {number}
    987      */
    988     width: function()
    989     {
    990         return this._width;
    991     },
    992 
    993     /**
    994      * @return {number}
    995      */
    996     height: function()
    997     {
    998         return this._height;
    999     },
   1000 
   1001     /**
   1002      * @return {?Array.<number>}
   1003      */
   1004     transform: function()
   1005     {
   1006         return null;
   1007     },
   1008 
   1009     /**
   1010      * @return {!Array.<number>}
   1011      */
   1012     quad: function()
   1013     {
   1014         return this._quad;
   1015     },
   1016 
   1017     /**
   1018      * @return {!Array.<number>}
   1019      */
   1020     anchorPoint: function()
   1021     {
   1022         return [0.5, 0.5, 0];
   1023     },
   1024 
   1025     /**
   1026      * @return {boolean}
   1027      */
   1028     invisible: function()
   1029     {
   1030         return false;
   1031     },
   1032 
   1033     /**
   1034      * @return {number}
   1035      */
   1036     paintCount: function()
   1037     {
   1038         return 0;
   1039     },
   1040 
   1041     /**
   1042      * @return {?DOMAgent.Rect}
   1043      */
   1044     lastPaintRect: function()
   1045     {
   1046         return null;
   1047     },
   1048 
   1049     /**
   1050      * @return {!Array.<!LayerTreeAgent.ScrollRect>}
   1051      */
   1052     scrollRects: function()
   1053     {
   1054         return this._scrollRects;
   1055     },
   1056 
   1057     /**
   1058      * @param {!Array.<number>} params
   1059      * @param {string} type
   1060      * @return {!Object}
   1061      */
   1062     _scrollRectsFromParams: function(params, type)
   1063     {
   1064         return {rect: {x: params[0], y: params[1], width: params[2], height: params[3]}, type: type};
   1065     },
   1066 
   1067     /**
   1068      * @param {!WebInspector.TracingLayerPayload} payload
   1069      */
   1070     _createScrollRects: function(payload)
   1071     {
   1072         this._scrollRects = [];
   1073         if (payload.non_fast_scrollable_region)
   1074             this._scrollRects.push(this._scrollRectsFromParams(payload.non_fast_scrollable_region, WebInspector.LayerTreeModel.ScrollRectType.NonFastScrollable.name));
   1075         if (payload.touch_event_handler_region)
   1076             this._scrollRects.push(this._scrollRectsFromParams(payload.touch_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.TouchEventHandler.name));
   1077         if (payload.wheel_event_handler_region)
   1078             this._scrollRects.push(this._scrollRectsFromParams(payload.wheel_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.WheelEventHandler.name));
   1079         if (payload.scroll_event_handler_region)
   1080             this._scrollRects.push(this._scrollRectsFromParams(payload.scroll_event_handler_region, WebInspector.LayerTreeModel.ScrollRectType.RepaintsOnScroll.name));
   1081     },
   1082 
   1083     /**
   1084      * @param {function(!Array.<string>)} callback
   1085      */
   1086     requestCompositingReasons: function(callback)
   1087     {
   1088         var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []);
   1089         LayerTreeAgent.compositingReasons(this.id(), wrappedCallback);
   1090     },
   1091 
   1092     /**
   1093      * @param {function(!WebInspector.PaintProfilerSnapshot=)} callback
   1094      */
   1095     requestSnapshot: function(callback)
   1096     {
   1097         var wrappedCallback = InspectorBackend.wrapClientCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.PaintProfilerSnapshot);
   1098         LayerTreeAgent.makeSnapshot(this.id(), wrappedCallback);
   1099     }
   1100 }
   1101 
   1102 /**
   1103  * @constructor
   1104  * @param {?WebInspector.Target} target
   1105  */
   1106 WebInspector.DeferredLayerTree = function(target)
   1107 {
   1108     this._target = target;
   1109 }
   1110 
   1111 WebInspector.DeferredLayerTree.prototype = {
   1112     /**
   1113      * @param {function(!WebInspector.LayerTreeBase)} callback
   1114      */
   1115     resolve: function(callback) { },
   1116 
   1117     /**
   1118      * @return {?WebInspector.Target}
   1119      */
   1120     target: function()
   1121     {
   1122         return this._target;
   1123     }
   1124 };
   1125 
   1126 /**
   1127  * @constructor
   1128  * @extends {WebInspector.DeferredLayerTree}
   1129  * @param {?WebInspector.Target} target
   1130  * @param {!Array.<!LayerTreeAgent.Layer>} layers
   1131  */
   1132 WebInspector.DeferredAgentLayerTree = function(target, layers)
   1133 {
   1134     WebInspector.DeferredLayerTree.call(this, target);
   1135     this._layers = layers;
   1136 }
   1137 
   1138 WebInspector.DeferredAgentLayerTree.prototype = {
   1139     /**
   1140      * @param {function(!WebInspector.LayerTreeBase)} callback
   1141      */
   1142     resolve: function(callback)
   1143     {
   1144         var result = new WebInspector.AgentLayerTree(this._target);
   1145         result.setLayers(this._layers, callback.bind(null, result));
   1146     },
   1147 
   1148     __proto__: WebInspector.DeferredLayerTree.prototype
   1149 };
   1150 
   1151 /**
   1152  * @constructor
   1153  * @implements {LayerTreeAgent.Dispatcher}
   1154  * @param {!WebInspector.LayerTreeModel} layerTreeModel
   1155  */
   1156 WebInspector.LayerTreeDispatcher = function(layerTreeModel)
   1157 {
   1158     this._layerTreeModel = layerTreeModel;
   1159 }
   1160 
   1161 WebInspector.LayerTreeDispatcher.prototype = {
   1162     /**
   1163      * @param {!Array.<!LayerTreeAgent.Layer>=} layers
   1164      */
   1165     layerTreeDidChange: function(layers)
   1166     {
   1167         this._layerTreeModel._layerTreeChanged(layers || null);
   1168     },
   1169 
   1170     /**
   1171      * @param {!LayerTreeAgent.LayerId} layerId
   1172      * @param {!DOMAgent.Rect} clipRect
   1173      */
   1174     layerPainted: function(layerId, clipRect)
   1175     {
   1176         this._layerTreeModel._layerPainted(layerId, clipRect);
   1177     }
   1178 }
   1179