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/** 32 * @constructor 33 * @extends {WebInspector.Object} 34 */ 35WebInspector.LayerTreeModel = function() 36{ 37 WebInspector.Object.call(this); 38 this._layersById = {}; 39 // We fetch layer tree lazily and get paint events asynchronously, so keep the last painted 40 // rect separate from layer so we can get it after refreshing the tree. 41 this._lastPaintRectByLayerId = {}; 42 InspectorBackend.registerLayerTreeDispatcher(new WebInspector.LayerTreeDispatcher(this)); 43 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.DocumentUpdated, this._onDocumentUpdated, this); 44} 45 46WebInspector.LayerTreeModel.Events = { 47 LayerTreeChanged: "LayerTreeChanged", 48 LayerPainted: "LayerPainted", 49} 50 51/** 52 * @param {function(T)} clientCallback 53 * @param {string} errorPrefix 54 * @param {function(new:T,S)=} constructor 55 * @param {T=} defaultValue 56 * @return {function(?string, S)} 57 * @template T,S 58 */ 59WebInspector.LayerTreeModel._wrapCallback = function(clientCallback, errorPrefix, constructor, defaultValue) 60{ 61 /** 62 * @param {?string} error 63 * @param {S} value 64 * @template S 65 */ 66 function callbackWrapper(error, value) 67 { 68 if (error) { 69 console.error(errorPrefix + error); 70 clientCallback(defaultValue); 71 return; 72 } 73 if (constructor) 74 clientCallback(new constructor(value)); 75 else 76 clientCallback(value); 77 } 78 return callbackWrapper; 79} 80 81WebInspector.LayerTreeModel.prototype = { 82 disable: function() 83 { 84 if (!this._enabled) 85 return; 86 this._enabled = false; 87 LayerTreeAgent.disable(); 88 }, 89 90 /** 91 * @param {function()=} callback 92 */ 93 enable: function(callback) 94 { 95 if (this._enabled) 96 return; 97 this._enabled = true; 98 WebInspector.domAgent.requestDocument(onDocumentAvailable.bind(this)); 99 100 /** 101 * @this {WebInspector.LayerTreeModel} 102 */ 103 function onDocumentAvailable() 104 { 105 // The agent might have been disabled while we were waiting for the document. 106 if (!this._enabled) 107 return; 108 LayerTreeAgent.enable(); 109 } 110 }, 111 112 /** 113 * @return {?WebInspector.Layer} 114 */ 115 root: function() 116 { 117 return this._root; 118 }, 119 120 /** 121 * @return {?WebInspector.Layer} 122 */ 123 contentRoot: function() 124 { 125 return this._contentRoot; 126 }, 127 128 /** 129 * @param {function(!WebInspector.Layer)} callback 130 * @param {?WebInspector.Layer} root 131 * @return {boolean} 132 */ 133 forEachLayer: function(callback, root) 134 { 135 if (!root) { 136 root = this.root(); 137 if (!root) 138 return false; 139 } 140 return callback(root) || root.children().some(this.forEachLayer.bind(this, callback)); 141 }, 142 143 /** 144 * @param {string} id 145 * @return {?WebInspector.Layer} 146 */ 147 layerById: function(id) 148 { 149 return this._layersById[id] || null; 150 }, 151 152 /** 153 * @param {!Array.<!LayerTreeAgent.Layer>} payload 154 */ 155 _repopulate: function(payload) 156 { 157 var oldLayersById = this._layersById; 158 this._layersById = {}; 159 for (var i = 0; i < payload.length; ++i) { 160 var layerId = payload[i].layerId; 161 var layer = oldLayersById[layerId]; 162 if (layer) 163 layer._reset(payload[i]); 164 else 165 layer = new WebInspector.Layer(payload[i]); 166 this._layersById[layerId] = layer; 167 var parentId = layer.parentId(); 168 if (!this._contentRoot && layer.nodeId()) 169 this._contentRoot = layer; 170 var lastPaintRect = this._lastPaintRectByLayerId[layerId]; 171 if (lastPaintRect) 172 layer._lastPaintRect = lastPaintRect; 173 if (parentId) { 174 var parent = this._layersById[parentId]; 175 if (!parent) 176 console.assert(parent, "missing parent " + parentId + " for layer " + layerId); 177 parent.addChild(layer); 178 } else { 179 if (this._root) 180 console.assert(false, "Multiple root layers"); 181 this._root = layer; 182 } 183 } 184 this._lastPaintRectByLayerId = {}; 185 }, 186 187 /** 188 * @param {!Array.<!LayerTreeAgent.Layer>=} payload 189 */ 190 _layerTreeChanged: function(payload) 191 { 192 this._root = null; 193 this._contentRoot = null; 194 // Payload will be null when not in the composited mode. 195 if (payload) 196 this._repopulate(payload); 197 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerTreeChanged); 198 }, 199 200 /** 201 * @param {!LayerTreeAgent.LayerId} layerId 202 * @param {!DOMAgent.Rect} clipRect 203 */ 204 _layerPainted: function(layerId, clipRect) 205 { 206 var layer = this._layersById[layerId]; 207 if (!layer) { 208 this._lastPaintRectByLayerId[layerId] = clipRect; 209 return; 210 } 211 layer._didPaint(clipRect); 212 this.dispatchEventToListeners(WebInspector.LayerTreeModel.Events.LayerPainted, layer); 213 }, 214 215 _onDocumentUpdated: function() 216 { 217 this.disable(); 218 this.enable(); 219 }, 220 221 __proto__: WebInspector.Object.prototype 222} 223 224/** 225 * @constructor 226 * @param {!LayerTreeAgent.Layer} layerPayload 227 */ 228WebInspector.Layer = function(layerPayload) 229{ 230 this._reset(layerPayload); 231} 232 233WebInspector.Layer.prototype = { 234 /** 235 * @return {string} 236 */ 237 id: function() 238 { 239 return this._layerPayload.layerId; 240 }, 241 242 /** 243 * @return {string} 244 */ 245 parentId: function() 246 { 247 return this._layerPayload.parentLayerId; 248 }, 249 250 /** 251 * @return {!WebInspector.Layer} 252 */ 253 parent: function() 254 { 255 return this._parent; 256 }, 257 258 /** 259 * @return {boolean} 260 */ 261 isRoot: function() 262 { 263 return !this.parentId(); 264 }, 265 266 /** 267 * @return {!Array.<!WebInspector.Layer>} 268 */ 269 children: function() 270 { 271 return this._children; 272 }, 273 274 /** 275 * @param {!WebInspector.Layer} child 276 */ 277 addChild: function(child) 278 { 279 if (child._parent) 280 console.assert(false, "Child already has a parent"); 281 this._children.push(child); 282 child._parent = this; 283 }, 284 285 /** 286 * @return {?DOMAgent.NodeId} 287 */ 288 nodeId: function() 289 { 290 return this._layerPayload.nodeId; 291 }, 292 293 /** 294 * @return {?DOMAgent.NodeId} 295 */ 296 nodeIdForSelfOrAncestor: function() 297 { 298 for (var layer = this; layer; layer = layer._parent) { 299 var nodeId = layer._layerPayload.nodeId; 300 if (nodeId) 301 return nodeId; 302 } 303 return null; 304 }, 305 306 /** 307 * @return {number} 308 */ 309 offsetX: function() 310 { 311 return this._layerPayload.offsetX; 312 }, 313 314 /** 315 * @return {number} 316 */ 317 offsetY: function() 318 { 319 return this._layerPayload.offsetY; 320 }, 321 322 /** 323 * @return {number} 324 */ 325 width: function() 326 { 327 return this._layerPayload.width; 328 }, 329 330 /** 331 * @return {number} 332 */ 333 height: function() 334 { 335 return this._layerPayload.height; 336 }, 337 338 /** 339 * @return {!Array.<number>} 340 */ 341 transform: function() 342 { 343 return this._layerPayload.transform; 344 }, 345 346 /** 347 * @return {!Array.<number>} 348 */ 349 anchorPoint: function() 350 { 351 return [ 352 this._layerPayload.anchorX || 0, 353 this._layerPayload.anchorY || 0, 354 this._layerPayload.anchorZ || 0, 355 ]; 356 }, 357 358 /** 359 * @return {boolean} 360 */ 361 invisible: function() 362 { 363 return this._layerPayload.invisible; 364 }, 365 366 /** 367 * @return {number} 368 */ 369 paintCount: function() 370 { 371 return this._paintCount || this._layerPayload.paintCount; 372 }, 373 374 /** 375 * @return {?DOMAgent.Rect} 376 */ 377 lastPaintRect: function() 378 { 379 return this._lastPaintRect; 380 }, 381 382 /** 383 * @param {function(!Array.<string>)} callback 384 */ 385 requestCompositingReasons: function(callback) 386 { 387 var wrappedCallback = WebInspector.LayerTreeModel._wrapCallback(callback, "LayerTreeAgent.reasonsForCompositingLayer(): ", undefined, []); 388 LayerTreeAgent.compositingReasons(this.id(), wrappedCallback); 389 }, 390 391 /** 392 * @param {function(!WebInspector.LayerSnapshot=)} callback 393 */ 394 requestSnapshot: function(callback) 395 { 396 var wrappedCallback = WebInspector.LayerTreeModel._wrapCallback(callback, "LayerTreeAgent.makeSnapshot(): ", WebInspector.LayerSnapshot.bind(null, this)); 397 LayerTreeAgent.makeSnapshot(this.id(), wrappedCallback); 398 }, 399 400 /** 401 * @param {!DOMAgent.Rect} rect 402 */ 403 _didPaint: function(rect) 404 { 405 this._lastPaintRect = rect; 406 this._paintCount = this.paintCount() + 1; 407 this._image = null; 408 }, 409 410 /** 411 * @param {!LayerTreeAgent.Layer} layerPayload 412 */ 413 _reset: function(layerPayload) 414 { 415 this._children = []; 416 this._parent = null; 417 this._paintCount = 0; 418 this._layerPayload = layerPayload; 419 this._image = null; 420 } 421} 422 423/** 424 * @constructor 425 * @param {!WebInspector.Layer} layer 426 * @param {string} snapshotId 427 */ 428WebInspector.LayerSnapshot = function(layer, snapshotId) 429{ 430 this._id = snapshotId; 431 this._layer = layer; 432} 433 434WebInspector.LayerSnapshot.prototype = { 435 dispose: function() 436 { 437 LayerTreeAgent.releaseSnapshot(this._id); 438 }, 439 440 /** 441 * @param {?number} firstStep 442 * @param {?number} lastStep 443 * @param {function(string=)} callback 444 */ 445 requestImage: function(firstStep, lastStep, callback) 446 { 447 var wrappedCallback = WebInspector.LayerTreeModel._wrapCallback(callback, "LayerTreeAgent.replaySnapshot(): "); 448 LayerTreeAgent.replaySnapshot(this._id, firstStep || undefined, lastStep || undefined, wrappedCallback); 449 }, 450 451 /** 452 * @param {function(!Array.<!LayerTreeAgent.PaintProfile>=)} callback 453 */ 454 profile: function(callback) 455 { 456 var wrappedCallback = WebInspector.LayerTreeModel._wrapCallback(callback, "LayerTreeAgent.profileSnapshot(): "); 457 LayerTreeAgent.profileSnapshot(this._id, 5, 1, wrappedCallback); 458 } 459}; 460 461/** 462 * @constructor 463 * @implements {LayerTreeAgent.Dispatcher} 464 * @param {!WebInspector.LayerTreeModel} layerTreeModel 465 */ 466WebInspector.LayerTreeDispatcher = function(layerTreeModel) 467{ 468 this._layerTreeModel = layerTreeModel; 469} 470 471WebInspector.LayerTreeDispatcher.prototype = { 472 /** 473 * @param {!Array.<!LayerTreeAgent.Layer>=} payload 474 */ 475 layerTreeDidChange: function(payload) 476 { 477 this._layerTreeModel._layerTreeChanged(payload); 478 }, 479 480 /** 481 * @param {!LayerTreeAgent.LayerId} layerId 482 * @param {!DOMAgent.Rect} clipRect 483 */ 484 layerPainted: function(layerId, clipRect) 485 { 486 this._layerTreeModel._layerPainted(layerId, clipRect); 487 } 488} 489