| /* | 
 |  * Copyright (C) 2014 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. | 
 |  */ | 
 | /** | 
 |  * @implements {LayerViewer.LayerView} | 
 |  * @unrestricted | 
 |  */ | 
 | export class Layers3DView extends UI.VBox { | 
 |   /** | 
 |    * @param {!LayerViewer.LayerViewHost} layerViewHost | 
 |    */ | 
 |   constructor(layerViewHost) { | 
 |     super(true); | 
 |     this.registerRequiredCSS('layer_viewer/layers3DView.css'); | 
 |     this.contentElement.classList.add('layers-3d-view'); | 
 |     this._failBanner = new UI.VBox(); | 
 |     this._failBanner.element.classList.add('full-widget-dimmed-banner'); | 
 |     this._failBanner.element.createTextChild(Common.UIString('Layer information is not yet available.')); | 
 |  | 
 |     this._layerViewHost = layerViewHost; | 
 |     this._layerViewHost.registerView(this); | 
 |  | 
 |     this._transformController = new LayerViewer.TransformController(this.contentElement); | 
 |     this._transformController.addEventListener( | 
 |         LayerViewer.TransformController.Events.TransformChanged, this._update, this); | 
 |     this._initToolbar(); | 
 |  | 
 |     this._canvasElement = this.contentElement.createChild('canvas'); | 
 |     this._canvasElement.tabIndex = 0; | 
 |     this._canvasElement.addEventListener('dblclick', this._onDoubleClick.bind(this), false); | 
 |     this._canvasElement.addEventListener('mousedown', this._onMouseDown.bind(this), false); | 
 |     this._canvasElement.addEventListener('mouseup', this._onMouseUp.bind(this), false); | 
 |     this._canvasElement.addEventListener('mouseleave', this._onMouseMove.bind(this), false); | 
 |     this._canvasElement.addEventListener('mousemove', this._onMouseMove.bind(this), false); | 
 |     this._canvasElement.addEventListener('contextmenu', this._onContextMenu.bind(this), false); | 
 |     UI.ARIAUtils.setAccessibleName(this._canvasElement, ls`3D Layers View`); | 
 |  | 
 |     this._lastSelection = {}; | 
 |     this._layerTree = null; | 
 |  | 
 |     this._textureManager = new LayerTextureManager(this._update.bind(this)); | 
 |  | 
 |     /** @type Array.<!WebGLTexture|undefined> */ | 
 |     this._chromeTextures = []; | 
 |     this._rects = []; | 
 |  | 
 |     /** @type Map<SDK.Layer, LayerViewer.LayerView.SnapshotSelection> */ | 
 |     this._snapshotLayers = new Map(); | 
 |     this._layerViewHost.setLayerSnapshotMap(this._snapshotLayers); | 
 |  | 
 |     this._layerViewHost.showInternalLayersSetting().addChangeListener(this._update, this); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?SDK.LayerTreeBase} layerTree | 
 |    * @override | 
 |    */ | 
 |   setLayerTree(layerTree) { | 
 |     this._layerTree = layerTree; | 
 |     this._layerTexture = null; | 
 |     delete this._oldTextureScale; | 
 |     if (this._showPaints()) { | 
 |       this._textureManager.setLayerTree(layerTree); | 
 |     } | 
 |     this._update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @param {string=} imageURL | 
 |    */ | 
 |   showImageForLayer(layer, imageURL) { | 
 |     if (!imageURL) { | 
 |       this._layerTexture = null; | 
 |       this._update(); | 
 |       return; | 
 |     } | 
 |     UI.loadImage(imageURL).then(image => { | 
 |       const texture = image && LayerTextureManager._createTextureForImage(this._gl, image); | 
 |       this._layerTexture = texture ? {layer: layer, texture: texture} : null; | 
 |       this._update(); | 
 |     }); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   onResize() { | 
 |     this._resizeCanvas(); | 
 |     this._update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   willHide() { | 
 |     this._textureManager.suspend(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @override | 
 |    */ | 
 |   wasShown() { | 
 |     this._textureManager.resume(); | 
 |     if (!this._needsUpdate) { | 
 |       return; | 
 |     } | 
 |     this._resizeCanvas(); | 
 |     this._update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    */ | 
 |   updateLayerSnapshot(layer) { | 
 |     this._textureManager.layerNeedsUpdate(layer); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!LayerViewer.Layers3DView.OutlineType} type | 
 |    * @param {?LayerViewer.LayerView.Selection} selection | 
 |    */ | 
 |   _setOutline(type, selection) { | 
 |     this._lastSelection[type] = selection; | 
 |     this._update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?LayerViewer.LayerView.Selection} selection | 
 |    * @override | 
 |    */ | 
 |   hoverObject(selection) { | 
 |     this._setOutline(OutlineType.Hovered, selection); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?LayerViewer.LayerView.Selection} selection | 
 |    * @override | 
 |    */ | 
 |   selectObject(selection) { | 
 |     this._setOutline(OutlineType.Hovered, null); | 
 |     this._setOutline(OutlineType.Selected, selection); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!LayerViewer.LayerView.Selection} selection | 
 |    * @return {!Promise<?SDK.SnapshotWithRect>} | 
 |    */ | 
 |   snapshotForSelection(selection) { | 
 |     if (selection.type() === LayerViewer.LayerView.Selection.Type.Snapshot) { | 
 |       const snapshotWithRect = /** @type {!LayerViewer.LayerView.SnapshotSelection} */ (selection).snapshot(); | 
 |       snapshotWithRect.snapshot.addReference(); | 
 |       return /** @type {!Promise<?SDK.SnapshotWithRect>} */ (Promise.resolve(snapshotWithRect)); | 
 |     } | 
 |     if (selection.layer()) { | 
 |       const promise = selection.layer().snapshots()[0]; | 
 |       if (promise) { | 
 |         return promise; | 
 |       } | 
 |     } | 
 |     return /** @type {!Promise<?SDK.SnapshotWithRect>} */ (Promise.resolve(null)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Element} canvas | 
 |    * @return {?WebGLRenderingContext} | 
 |    */ | 
 |   _initGL(canvas) { | 
 |     const gl = canvas.getContext('webgl'); | 
 |     if (!gl) { | 
 |       return null; | 
 |     } | 
 |     gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); | 
 |     gl.enable(gl.BLEND); | 
 |     gl.clearColor(0.0, 0.0, 0.0, 0.0); | 
 |     gl.enable(gl.DEPTH_TEST); | 
 |     return gl; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Object} type | 
 |    * @param {string} script | 
 |    */ | 
 |   _createShader(type, script) { | 
 |     const shader = this._gl.createShader(type); | 
 |     this._gl.shaderSource(shader, script); | 
 |     this._gl.compileShader(shader); | 
 |     this._gl.attachShader(this._shaderProgram, shader); | 
 |   } | 
 |  | 
 |   _initShaders() { | 
 |     this._shaderProgram = this._gl.createProgram(); | 
 |     this._createShader(this._gl.FRAGMENT_SHADER, FragmentShader); | 
 |     this._createShader(this._gl.VERTEX_SHADER, VertexShader); | 
 |     this._gl.linkProgram(this._shaderProgram); | 
 |     this._gl.useProgram(this._shaderProgram); | 
 |  | 
 |     this._shaderProgram.vertexPositionAttribute = this._gl.getAttribLocation(this._shaderProgram, 'aVertexPosition'); | 
 |     this._gl.enableVertexAttribArray(this._shaderProgram.vertexPositionAttribute); | 
 |     this._shaderProgram.vertexColorAttribute = this._gl.getAttribLocation(this._shaderProgram, 'aVertexColor'); | 
 |     this._gl.enableVertexAttribArray(this._shaderProgram.vertexColorAttribute); | 
 |     this._shaderProgram.textureCoordAttribute = this._gl.getAttribLocation(this._shaderProgram, 'aTextureCoord'); | 
 |     this._gl.enableVertexAttribArray(this._shaderProgram.textureCoordAttribute); | 
 |  | 
 |     this._shaderProgram.pMatrixUniform = this._gl.getUniformLocation(this._shaderProgram, 'uPMatrix'); | 
 |     this._shaderProgram.samplerUniform = this._gl.getUniformLocation(this._shaderProgram, 'uSampler'); | 
 |   } | 
 |  | 
 |   _resizeCanvas() { | 
 |     this._canvasElement.width = this._canvasElement.offsetWidth * window.devicePixelRatio; | 
 |     this._canvasElement.height = this._canvasElement.offsetHeight * window.devicePixelRatio; | 
 |   } | 
 |  | 
 |   _updateTransformAndConstraints() { | 
 |     const paddingFraction = 0.1; | 
 |     const viewport = this._layerTree.viewportSize(); | 
 |     const baseWidth = viewport ? viewport.width : this._dimensionsForAutoscale.width; | 
 |     const baseHeight = viewport ? viewport.height : this._dimensionsForAutoscale.height; | 
 |     const canvasWidth = this._canvasElement.width; | 
 |     const canvasHeight = this._canvasElement.height; | 
 |     const paddingX = canvasWidth * paddingFraction; | 
 |     const paddingY = canvasHeight * paddingFraction; | 
 |     const scaleX = (canvasWidth - 2 * paddingX) / baseWidth; | 
 |     const scaleY = (canvasHeight - 2 * paddingY) / baseHeight; | 
 |     const viewScale = Math.min(scaleX, scaleY); | 
 |     const minScaleConstraint = | 
 |         Math.min(baseWidth / this._dimensionsForAutoscale.width, baseHeight / this._dimensionsForAutoscale.width) / 2; | 
 |     this._transformController.setScaleConstraints( | 
 |         minScaleConstraint, | 
 |         10 / viewScale);  // 1/viewScale is 1:1 in terms of pixels, so allow zooming to 10x of native size | 
 |     const scale = this._transformController.scale(); | 
 |     const rotateX = this._transformController.rotateX(); | 
 |     const rotateY = this._transformController.rotateY(); | 
 |  | 
 |     this._scale = scale * viewScale; | 
 |     const textureScale = Number.constrain(this._scale, 0.1, 1); | 
 |     if (textureScale !== this._oldTextureScale) { | 
 |       this._oldTextureScale = textureScale; | 
 |       this._textureManager.setScale(textureScale); | 
 |       this.dispatchEventToListeners(Events.ScaleChanged, textureScale); | 
 |     } | 
 |     const scaleAndRotationMatrix = new WebKitCSSMatrix() | 
 |                                        .scale(scale, scale, scale) | 
 |                                        .translate(canvasWidth / 2, canvasHeight / 2, 0) | 
 |                                        .rotate(rotateX, rotateY, 0) | 
 |                                        .scale(viewScale, viewScale, viewScale) | 
 |                                        .translate(-baseWidth / 2, -baseHeight / 2, 0); | 
 |  | 
 |     let bounds; | 
 |     for (let i = 0; i < this._rects.length; ++i) { | 
 |       bounds = UI.Geometry.boundsForTransformedPoints(scaleAndRotationMatrix, this._rects[i].vertices, bounds); | 
 |     } | 
 |  | 
 |     this._transformController.clampOffsets( | 
 |         (paddingX - bounds.maxX) / window.devicePixelRatio, | 
 |         (canvasWidth - paddingX - bounds.minX) / window.devicePixelRatio, | 
 |         (paddingY - bounds.maxY) / window.devicePixelRatio, | 
 |         (canvasHeight - paddingY - bounds.minY) / window.devicePixelRatio); | 
 |     const offsetX = this._transformController.offsetX() * window.devicePixelRatio; | 
 |     const offsetY = this._transformController.offsetY() * window.devicePixelRatio; | 
 |     // Multiply to translation matrix on the right rather than translate (which would implicitly multiply on the left). | 
 |     this._projectionMatrix = new WebKitCSSMatrix().translate(offsetX, offsetY, 0).multiply(scaleAndRotationMatrix); | 
 |  | 
 |     const glProjectionMatrix = new WebKitCSSMatrix() | 
 |                                    .scale(1, -1, -1) | 
 |                                    .translate(-1, -1, 0) | 
 |                                    .scale(2 / this._canvasElement.width, 2 / this._canvasElement.height, 1 / 1000000) | 
 |                                    .multiply(this._projectionMatrix); | 
 |     this._gl.uniformMatrix4fv(this._shaderProgram.pMatrixUniform, false, this._arrayFromMatrix(glProjectionMatrix)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!CSSMatrix} m | 
 |    * @return {!Float32Array} | 
 |    */ | 
 |   _arrayFromMatrix(m) { | 
 |     return new Float32Array([ | 
 |       m.m11, m.m12, m.m13, m.m14, m.m21, m.m22, m.m23, m.m24, m.m31, m.m32, m.m33, m.m34, m.m41, m.m42, m.m43, m.m44 | 
 |     ]); | 
 |   } | 
 |  | 
 |   _initWhiteTexture() { | 
 |     this._whiteTexture = this._gl.createTexture(); | 
 |     this._gl.bindTexture(this._gl.TEXTURE_2D, this._whiteTexture); | 
 |     const whitePixel = new Uint8Array([255, 255, 255, 255]); | 
 |     this._gl.texImage2D( | 
 |         this._gl.TEXTURE_2D, 0, this._gl.RGBA, 1, 1, 0, this._gl.RGBA, this._gl.UNSIGNED_BYTE, whitePixel); | 
 |   } | 
 |  | 
 |   _initChromeTextures() { | 
 |     /** | 
 |      * @this {LayerViewer.Layers3DView} | 
 |      * @param {!LayerViewer.Layers3DView.ChromeTexture} index | 
 |      * @param {string} url | 
 |      */ | 
 |     function loadChromeTexture(index, url) { | 
 |       UI.loadImage(url).then(image => { | 
 |         this._chromeTextures[index] = image && LayerTextureManager._createTextureForImage(this._gl, image) || undefined; | 
 |       }); | 
 |     } | 
 |     loadChromeTexture.call(this, ChromeTexture.Left, 'Images/chromeLeft.png'); | 
 |     loadChromeTexture.call(this, ChromeTexture.Middle, 'Images/chromeMiddle.png'); | 
 |     loadChromeTexture.call(this, ChromeTexture.Right, 'Images/chromeRight.png'); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @return {?WebGLRenderingContext} | 
 |    */ | 
 |   _initGLIfNecessary() { | 
 |     if (this._gl) { | 
 |       return this._gl; | 
 |     } | 
 |     this._gl = this._initGL(this._canvasElement); | 
 |     if (!this._gl) { | 
 |       return null; | 
 |     } | 
 |     this._initShaders(); | 
 |     this._initWhiteTexture(); | 
 |     this._initChromeTextures(); | 
 |     this._textureManager.setContext(this._gl); | 
 |     return this._gl; | 
 |   } | 
 |  | 
 |   _calculateDepthsAndVisibility() { | 
 |     this._depthByLayerId = {}; | 
 |     let depth = 0; | 
 |     const showInternalLayers = this._layerViewHost.showInternalLayersSetting().get(); | 
 |     const root = | 
 |         showInternalLayers ? this._layerTree.root() : (this._layerTree.contentRoot() || this._layerTree.root()); | 
 |     const queue = [root]; | 
 |     this._depthByLayerId[root.id()] = 0; | 
 |     /** @type {!Set<!SDK.Layer>} */ | 
 |     this._visibleLayers = new Set(); | 
 |     while (queue.length > 0) { | 
 |       const layer = queue.shift(); | 
 |       if (showInternalLayers || layer.drawsContent()) { | 
 |         this._visibleLayers.add(layer); | 
 |       } | 
 |       const children = layer.children(); | 
 |       for (let i = 0; i < children.length; ++i) { | 
 |         this._depthByLayerId[children[i].id()] = ++depth; | 
 |         queue.push(children[i]); | 
 |       } | 
 |     } | 
 |     this._maxDepth = depth; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @return {number} | 
 |    */ | 
 |   _depthForLayer(layer) { | 
 |     return this._depthByLayerId[layer.id()] * LayerSpacing; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @param {number} index | 
 |    * @return {number} | 
 |    */ | 
 |   _calculateScrollRectDepth(layer, index) { | 
 |     return this._depthForLayer(layer) + index * ScrollRectSpacing + 1; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    */ | 
 |   _updateDimensionsForAutoscale(layer) { | 
 |     // We don't want to be precise, but rather pick something least affected by | 
 |     // animationtransforms, so that we don't change scale too often. So let's | 
 |     // disregard transforms, scrolling and relative layer positioning and choose | 
 |     // the largest dimensions of all layers. | 
 |     this._dimensionsForAutoscale.width = Math.max(layer.width(), this._dimensionsForAutoscale.width); | 
 |     this._dimensionsForAutoscale.height = Math.max(layer.height(), this._dimensionsForAutoscale.height); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    */ | 
 |   _calculateLayerRect(layer) { | 
 |     if (!this._visibleLayers.has(layer)) { | 
 |       return; | 
 |     } | 
 |     const selection = new LayerViewer.LayerView.LayerSelection(layer); | 
 |     const rect = new Rectangle(selection); | 
 |     rect.setVertices(layer.quad(), this._depthForLayer(layer)); | 
 |     this._appendRect(rect); | 
 |     this._updateDimensionsForAutoscale(layer); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!LayerViewer.Layers3DView.Rectangle} rect | 
 |    */ | 
 |   _appendRect(rect) { | 
 |     const selection = rect.relatedObject; | 
 |     const isSelected = LayerViewer.LayerView.Selection.isEqual(this._lastSelection[OutlineType.Selected], selection); | 
 |     const isHovered = LayerViewer.LayerView.Selection.isEqual(this._lastSelection[OutlineType.Hovered], selection); | 
 |     if (isSelected) { | 
 |       rect.borderColor = SelectedBorderColor; | 
 |     } else if (isHovered) { | 
 |       rect.borderColor = HoveredBorderColor; | 
 |       const fillColor = rect.fillColor || [255, 255, 255, 1]; | 
 |       const maskColor = HoveredImageMaskColor; | 
 |       rect.fillColor = [ | 
 |         fillColor[0] * maskColor[0] / 255, fillColor[1] * maskColor[1] / 255, fillColor[2] * maskColor[2] / 255, | 
 |         fillColor[3] * maskColor[3] | 
 |       ]; | 
 |     } else { | 
 |       rect.borderColor = BorderColor; | 
 |     } | 
 |     rect.lineWidth = isSelected ? SelectedBorderWidth : BorderWidth; | 
 |     this._rects.push(rect); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    */ | 
 |   _calculateLayerScrollRects(layer) { | 
 |     const scrollRects = layer.scrollRects(); | 
 |     for (let i = 0; i < scrollRects.length; ++i) { | 
 |       const selection = new LayerViewer.LayerView.ScrollRectSelection(layer, i); | 
 |       const rect = new Rectangle(selection); | 
 |       rect.calculateVerticesFromRect(layer, scrollRects[i].rect, this._calculateScrollRectDepth(layer, i)); | 
 |       rect.fillColor = ScrollRectBackgroundColor; | 
 |       this._appendRect(rect); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    */ | 
 |   _calculateLayerTileRects(layer) { | 
 |     const tiles = this._textureManager.tilesForLayer(layer); | 
 |     for (let i = 0; i < tiles.length; ++i) { | 
 |       const tile = tiles[i]; | 
 |       if (!tile.texture) { | 
 |         continue; | 
 |       } | 
 |       const selection = new LayerViewer.LayerView.SnapshotSelection(layer, {rect: tile.rect, snapshot: tile.snapshot}); | 
 |       const rect = new Rectangle(selection); | 
 |       if (!this._snapshotLayers.has(layer)) { | 
 |         this._snapshotLayers.set(layer, selection); | 
 |       } | 
 |  | 
 |       rect.calculateVerticesFromRect(layer, tile.rect, this._depthForLayer(layer) + 1); | 
 |       rect.texture = tile.texture; | 
 |       this._appendRect(rect); | 
 |     } | 
 |   } | 
 |  | 
 |   _calculateRects() { | 
 |     this._rects = []; | 
 |     this._snapshotLayers.clear(); | 
 |     this._dimensionsForAutoscale = {width: 0, height: 0}; | 
 |     this._layerTree.forEachLayer(this._calculateLayerRect.bind(this)); | 
 |  | 
 |     if (this._showSlowScrollRectsSetting.get()) { | 
 |       this._layerTree.forEachLayer(this._calculateLayerScrollRects.bind(this)); | 
 |     } | 
 |  | 
 |     if (this._layerTexture && this._visibleLayers.has(this._layerTexture.layer)) { | 
 |       const layer = this._layerTexture.layer; | 
 |       const selection = new LayerViewer.LayerView.LayerSelection(layer); | 
 |       const rect = new Rectangle(selection); | 
 |       rect.setVertices(layer.quad(), this._depthForLayer(layer)); | 
 |       rect.texture = this._layerTexture.texture; | 
 |       this._appendRect(rect); | 
 |     } else if (this._showPaints()) { | 
 |       this._layerTree.forEachLayer(this._calculateLayerTileRects.bind(this)); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Array.<number>} color | 
 |    * @return {!Array.<number>} | 
 |    */ | 
 |   _makeColorsArray(color) { | 
 |     let colors = []; | 
 |     const normalizedColor = [color[0] / 255, color[1] / 255, color[2] / 255, color[3]]; | 
 |     for (let i = 0; i < 4; i++) { | 
 |       colors = colors.concat(normalizedColor); | 
 |     } | 
 |     return colors; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Object} attribute | 
 |    * @param {!Array.<number>} array | 
 |    * @param {number} length | 
 |    */ | 
 |   _setVertexAttribute(attribute, array, length) { | 
 |     const gl = this._gl; | 
 |     const buffer = gl.createBuffer(); | 
 |     gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | 
 |     gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(array), gl.STATIC_DRAW); | 
 |     gl.vertexAttribPointer(attribute, length, gl.FLOAT, false, 0, 0); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Array.<number>} vertices | 
 |    * @param {number} mode | 
 |    * @param {!Array.<number>=} color | 
 |    * @param {!Object=} texture | 
 |    */ | 
 |   _drawRectangle(vertices, mode, color, texture) { | 
 |     const gl = this._gl; | 
 |     const white = [255, 255, 255, 1]; | 
 |     color = color || white; | 
 |     this._setVertexAttribute(this._shaderProgram.vertexPositionAttribute, vertices, 3); | 
 |     this._setVertexAttribute(this._shaderProgram.textureCoordAttribute, [0, 1, 1, 1, 1, 0, 0, 0], 2); | 
 |     this._setVertexAttribute(this._shaderProgram.vertexColorAttribute, this._makeColorsArray(color), color.length); | 
 |  | 
 |     if (texture) { | 
 |       gl.activeTexture(gl.TEXTURE0); | 
 |       gl.bindTexture(gl.TEXTURE_2D, texture); | 
 |       gl.uniform1i(this._shaderProgram.samplerUniform, 0); | 
 |     } else { | 
 |       gl.bindTexture(gl.TEXTURE_2D, this._whiteTexture); | 
 |     } | 
 |  | 
 |     const numberOfVertices = vertices.length / 3; | 
 |     gl.drawArrays(mode, 0, numberOfVertices); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Array.<number>} vertices | 
 |    * @param {!WebGLTexture} texture | 
 |    * @param {!Array.<number>=} color | 
 |    */ | 
 |   _drawTexture(vertices, texture, color) { | 
 |     this._drawRectangle(vertices, this._gl.TRIANGLE_FAN, color, texture); | 
 |   } | 
 |  | 
 |   _drawViewportAndChrome() { | 
 |     const viewport = this._layerTree.viewportSize(); | 
 |     if (!viewport) { | 
 |       return; | 
 |     } | 
 |  | 
 |     const drawChrome = !Common.moduleSetting('frameViewerHideChromeWindow').get() && this._chromeTextures.length >= 3 && | 
 |         this._chromeTextures.indexOf(undefined) < 0; | 
 |     const z = (this._maxDepth + 1) * LayerSpacing; | 
 |     const borderWidth = Math.ceil(ViewportBorderWidth * this._scale); | 
 |     let vertices = [viewport.width, 0, z, viewport.width, viewport.height, z, 0, viewport.height, z, 0, 0, z]; | 
 |     this._gl.lineWidth(borderWidth); | 
 |     this._drawRectangle(vertices, drawChrome ? this._gl.LINE_STRIP : this._gl.LINE_LOOP, ViewportBorderColor); | 
 |  | 
 |     if (!drawChrome) { | 
 |       return; | 
 |     } | 
 |  | 
 |     const borderAdjustment = ViewportBorderWidth / 2; | 
 |     const viewportWidth = this._layerTree.viewportSize().width + 2 * borderAdjustment; | 
 |     const chromeHeight = this._chromeTextures[0].image.naturalHeight; | 
 |     const middleFragmentWidth = | 
 |         viewportWidth - this._chromeTextures[0].image.naturalWidth - this._chromeTextures[2].image.naturalWidth; | 
 |     let x = -borderAdjustment; | 
 |     const y = -chromeHeight; | 
 |     for (let i = 0; i < this._chromeTextures.length; ++i) { | 
 |       const width = i === ChromeTexture.Middle ? middleFragmentWidth : this._chromeTextures[i].image.naturalWidth; | 
 |       if (width < 0 || x + width > viewportWidth) { | 
 |         break; | 
 |       } | 
 |       vertices = [x, y, z, x + width, y, z, x + width, y + chromeHeight, z, x, y + chromeHeight, z]; | 
 |       this._drawTexture(vertices, /** @type {!WebGLTexture} */ (this._chromeTextures[i])); | 
 |       x += width; | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!LayerViewer.Layers3DView.Rectangle} rect | 
 |    */ | 
 |   _drawViewRect(rect) { | 
 |     const vertices = rect.vertices; | 
 |     if (rect.texture) { | 
 |       this._drawTexture(vertices, rect.texture, rect.fillColor || undefined); | 
 |     } else if (rect.fillColor) { | 
 |       this._drawRectangle(vertices, this._gl.TRIANGLE_FAN, rect.fillColor); | 
 |     } | 
 |     this._gl.lineWidth(rect.lineWidth); | 
 |     if (rect.borderColor) { | 
 |       this._drawRectangle(vertices, this._gl.LINE_LOOP, rect.borderColor); | 
 |     } | 
 |   } | 
 |  | 
 |   _update() { | 
 |     if (!this.isShowing()) { | 
 |       this._needsUpdate = true; | 
 |       return; | 
 |     } | 
 |     if (!this._layerTree || !this._layerTree.root()) { | 
 |       this._failBanner.show(this.contentElement); | 
 |       return; | 
 |     } | 
 |     const gl = this._initGLIfNecessary(); | 
 |     if (!gl) { | 
 |       this._failBanner.element.removeChildren(); | 
 |       this._failBanner.element.appendChild(this._webglDisabledBanner()); | 
 |       this._failBanner.show(this.contentElement); | 
 |       return; | 
 |     } | 
 |     this._failBanner.detach(); | 
 |     this._gl.viewportWidth = this._canvasElement.width; | 
 |     this._gl.viewportHeight = this._canvasElement.height; | 
 |  | 
 |     this._calculateDepthsAndVisibility(); | 
 |     this._calculateRects(); | 
 |     this._updateTransformAndConstraints(); | 
 |  | 
 |     gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); | 
 |     gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | 
 |  | 
 |     this._rects.forEach(this._drawViewRect.bind(this)); | 
 |     this._drawViewportAndChrome(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @return {!Node} | 
 |    */ | 
 |   _webglDisabledBanner() { | 
 |     const fragment = this.contentElement.ownerDocument.createDocumentFragment(); | 
 |     fragment.createChild('div').textContent = Common.UIString('Can\'t display layers,'); | 
 |     fragment.createChild('div').textContent = Common.UIString('WebGL support is disabled in your browser.'); | 
 |     fragment.appendChild(UI.formatLocalized('Check %s for possible reasons.', [UI.XLink.create('about:gpu')])); | 
 |     return fragment; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Event} event | 
 |    * @return {?LayerViewer.LayerView.Selection} | 
 |    */ | 
 |   _selectionFromEventPoint(event) { | 
 |     if (!this._layerTree) { | 
 |       return null; | 
 |     } | 
 |     let closestIntersectionPoint = Infinity; | 
 |     let closestObject = null; | 
 |     const projectionMatrix = | 
 |         new WebKitCSSMatrix().scale(1, -1, -1).translate(-1, -1, 0).multiply(this._projectionMatrix); | 
 |     const x0 = (event.clientX - this._canvasElement.totalOffsetLeft()) * window.devicePixelRatio; | 
 |     const y0 = -(event.clientY - this._canvasElement.totalOffsetTop()) * window.devicePixelRatio; | 
 |  | 
 |     /** | 
 |      * @param {!LayerViewer.Layers3DView.Rectangle} rect | 
 |      */ | 
 |     function checkIntersection(rect) { | 
 |       if (!rect.relatedObject) { | 
 |         return; | 
 |       } | 
 |       const t = rect.intersectWithLine(projectionMatrix, x0, y0); | 
 |       if (t < closestIntersectionPoint) { | 
 |         closestIntersectionPoint = t; | 
 |         closestObject = rect.relatedObject; | 
 |       } | 
 |     } | 
 |  | 
 |     this._rects.forEach(checkIntersection); | 
 |     return closestObject; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {string} caption | 
 |    * @param {string} name | 
 |    * @param {boolean} value | 
 |    * @param {!UI.Toolbar} toolbar | 
 |    * @return {!Common.Setting} | 
 |    */ | 
 |   _createVisibilitySetting(caption, name, value, toolbar) { | 
 |     const setting = Common.settings.createSetting(name, value); | 
 |     setting.setTitle(Common.UIString(caption)); | 
 |     setting.addChangeListener(this._update, this); | 
 |     toolbar.appendToolbarItem(new UI.ToolbarSettingCheckbox(setting)); | 
 |     return setting; | 
 |   } | 
 |  | 
 |   _initToolbar() { | 
 |     this._panelToolbar = this._transformController.toolbar(); | 
 |     this.contentElement.appendChild(this._panelToolbar.element); | 
 |     this._showSlowScrollRectsSetting = this._createVisibilitySetting( | 
 |         ls`Slow scroll rects`, 'frameViewerShowSlowScrollRects', true, this._panelToolbar); | 
 |     this._showPaintsSetting = | 
 |         this._createVisibilitySetting(ls`Paints`, 'frameViewerShowPaints', true, this._panelToolbar); | 
 |     this._showPaintsSetting.addChangeListener(this._updatePaints, this); | 
 |     Common.moduleSetting('frameViewerHideChromeWindow').addChangeListener(this._update, this); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Event} event | 
 |    */ | 
 |   _onContextMenu(event) { | 
 |     const contextMenu = new UI.ContextMenu(event); | 
 |     contextMenu.defaultSection().appendItem( | 
 |         Common.UIString('Reset View'), this._transformController.resetAndNotify.bind(this._transformController), false); | 
 |     const selection = this._selectionFromEventPoint(event); | 
 |     if (selection && selection.type() === LayerViewer.LayerView.Selection.Type.Snapshot) { | 
 |       contextMenu.defaultSection().appendItem( | 
 |           Common.UIString('Show Paint Profiler'), | 
 |           this.dispatchEventToListeners.bind(this, Events.PaintProfilerRequested, selection), false); | 
 |     } | 
 |     this._layerViewHost.showContextMenu(contextMenu, selection); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Event} event | 
 |    */ | 
 |   _onMouseMove(event) { | 
 |     if (event.which) { | 
 |       return; | 
 |     } | 
 |     this._layerViewHost.hoverObject(this._selectionFromEventPoint(event)); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Event} event | 
 |    */ | 
 |   _onMouseDown(event) { | 
 |     this._mouseDownX = event.clientX; | 
 |     this._mouseDownY = event.clientY; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Event} event | 
 |    */ | 
 |   _onMouseUp(event) { | 
 |     const maxDistanceInPixels = 6; | 
 |     if (this._mouseDownX && Math.abs(event.clientX - this._mouseDownX) < maxDistanceInPixels && | 
 |         Math.abs(event.clientY - this._mouseDownY) < maxDistanceInPixels) { | 
 |       this._layerViewHost.selectObject(this._selectionFromEventPoint(event)); | 
 |     } | 
 |     delete this._mouseDownX; | 
 |     delete this._mouseDownY; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Event} event | 
 |    */ | 
 |   _onDoubleClick(event) { | 
 |     const selection = this._selectionFromEventPoint(event); | 
 |     if (selection && (selection.type() === LayerViewer.LayerView.Selection.Type.Snapshot || selection.layer())) { | 
 |       this.dispatchEventToListeners(Events.PaintProfilerRequested, selection); | 
 |     } | 
 |     event.stopPropagation(); | 
 |   } | 
 |  | 
 |   _updatePaints() { | 
 |     if (this._showPaints()) { | 
 |       this._textureManager.setLayerTree(this._layerTree); | 
 |       this._textureManager.forceUpdate(); | 
 |     } else { | 
 |       this._textureManager.reset(); | 
 |     } | 
 |     this._update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @return {boolean} | 
 |    */ | 
 |   _showPaints() { | 
 |     return this._showPaintsSetting.get(); | 
 |   } | 
 | } | 
 |  | 
 | /** | 
 |  * @enum {string} | 
 |  */ | 
 | export const OutlineType = { | 
 |   Hovered: 'hovered', | 
 |   Selected: 'selected' | 
 | }; | 
 |  | 
 | /** @enum {symbol} */ | 
 | export const Events = { | 
 |   PaintProfilerRequested: Symbol('PaintProfilerRequested'), | 
 |   ScaleChanged: Symbol('ScaleChanged') | 
 | }; | 
 |  | 
 | /** | 
 |  * @enum {number} | 
 |  */ | 
 | export const ChromeTexture = { | 
 |   Left: 0, | 
 |   Middle: 1, | 
 |   Right: 2 | 
 | }; | 
 |  | 
 | /** | 
 |  * @enum {string} | 
 |  */ | 
 | export const ScrollRectTitles = { | 
 |   RepaintsOnScroll: Common.UIString('repaints on scroll'), | 
 |   TouchEventHandler: Common.UIString('touch event listener'), | 
 |   WheelEventHandler: Common.UIString('mousewheel event listener') | 
 | }; | 
 |  | 
 | export const FragmentShader = '' + | 
 |     'precision mediump float;\n' + | 
 |     'varying vec4 vColor;\n' + | 
 |     'varying vec2 vTextureCoord;\n' + | 
 |     'uniform sampler2D uSampler;\n' + | 
 |     'void main(void)\n' + | 
 |     '{\n' + | 
 |     '    gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.s, vTextureCoord.t)) * vColor;\n' + | 
 |     '}'; | 
 |  | 
 | export const VertexShader = '' + | 
 |     'attribute vec3 aVertexPosition;\n' + | 
 |     'attribute vec2 aTextureCoord;\n' + | 
 |     'attribute vec4 aVertexColor;\n' + | 
 |     'uniform mat4 uPMatrix;\n' + | 
 |     'varying vec2 vTextureCoord;\n' + | 
 |     'varying vec4 vColor;\n' + | 
 |     'void main(void)\n' + | 
 |     '{\n' + | 
 |     'gl_Position = uPMatrix * vec4(aVertexPosition, 1.0);\n' + | 
 |     'vColor = aVertexColor;\n' + | 
 |     'vTextureCoord = aTextureCoord;\n' + | 
 |     '}'; | 
 |  | 
 | export const HoveredBorderColor = [0, 0, 255, 1]; | 
 | export const SelectedBorderColor = [0, 255, 0, 1]; | 
 | export const BorderColor = [0, 0, 0, 1]; | 
 | export const ViewportBorderColor = [160, 160, 160, 1]; | 
 | export const ScrollRectBackgroundColor = [178, 100, 100, 0.6]; | 
 | export const HoveredImageMaskColor = [200, 200, 255, 1]; | 
 | export const BorderWidth = 1; | 
 | export const SelectedBorderWidth = 2; | 
 | export const ViewportBorderWidth = 3; | 
 |  | 
 | export const LayerSpacing = 20; | 
 | export const ScrollRectSpacing = 4; | 
 |  | 
 | /** | 
 |  * @unrestricted | 
 |  */ | 
 | export class LayerTextureManager { | 
 |   /** | 
 |    * @param {function()} textureUpdatedCallback | 
 |    */ | 
 |   constructor(textureUpdatedCallback) { | 
 |     this._textureUpdatedCallback = textureUpdatedCallback; | 
 |     this._throttler = new Common.Throttler(0); | 
 |     this._scale = 0; | 
 |     this._active = false; | 
 |     this.reset(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Image} image | 
 |    * @param {!WebGLRenderingContext} gl | 
 |    * @return {!WebGLTexture} texture | 
 |    */ | 
 |   static _createTextureForImage(gl, image) { | 
 |     const texture = gl.createTexture(); | 
 |     texture.image = image; | 
 |     gl.bindTexture(gl.TEXTURE_2D, texture); | 
 |     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); | 
 |     gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, texture.image); | 
 |     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | 
 |     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | 
 |     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | 
 |     gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | 
 |     gl.bindTexture(gl.TEXTURE_2D, null); | 
 |     return texture; | 
 |   } | 
 |  | 
 |   reset() { | 
 |     if (this._tilesByLayer) { | 
 |       this.setLayerTree(null); | 
 |     } | 
 |  | 
 |     /** @type {!Map<!SDK.Layer, !Array<!LayerViewer.LayerTextureManager.Tile>>} */ | 
 |     this._tilesByLayer = new Map(); | 
 |     /** @type {!Array<!SDK.Layer>} */ | 
 |     this._queue = []; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!WebGLRenderingContext} glContext | 
 |    */ | 
 |   setContext(glContext) { | 
 |     this._gl = glContext; | 
 |     if (this._scale) { | 
 |       this._updateTextures(); | 
 |     } | 
 |   } | 
 |  | 
 |   suspend() { | 
 |     this._active = false; | 
 |   } | 
 |  | 
 |   resume() { | 
 |     this._active = true; | 
 |     if (this._queue.length) { | 
 |       this._update(); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {?SDK.LayerTreeBase} layerTree | 
 |    */ | 
 |   setLayerTree(layerTree) { | 
 |     const newLayers = new Set(); | 
 |     const oldLayers = Array.from(this._tilesByLayer.keys()); | 
 |     if (layerTree) { | 
 |       layerTree.forEachLayer(layer => { | 
 |         if (!layer.drawsContent()) { | 
 |           return; | 
 |         } | 
 |         newLayers.add(layer); | 
 |         if (!this._tilesByLayer.has(layer)) { | 
 |           this._tilesByLayer.set(layer, []); | 
 |           this.layerNeedsUpdate(layer); | 
 |         } | 
 |       }); | 
 |     } | 
 |     if (!oldLayers.length) { | 
 |       this.forceUpdate(); | 
 |     } | 
 |     for (const layer of oldLayers) { | 
 |       if (newLayers.has(layer)) { | 
 |         continue; | 
 |       } | 
 |       this._tilesByLayer.get(layer).forEach(tile => tile.dispose()); | 
 |       this._tilesByLayer.delete(layer); | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @param {!Array<!SDK.SnapshotWithRect>} snapshots | 
 |    * @return {!Promise} | 
 |    */ | 
 |   _setSnapshotsForLayer(layer, snapshots) { | 
 |     const oldSnapshotsToTiles = new Map((this._tilesByLayer.get(layer) || []).map(tile => [tile.snapshot, tile])); | 
 |     const newTiles = []; | 
 |     const reusedTiles = []; | 
 |     for (const snapshot of snapshots) { | 
 |       const oldTile = oldSnapshotsToTiles.get(snapshot); | 
 |       if (oldTile) { | 
 |         reusedTiles.push(oldTile); | 
 |         oldSnapshotsToTiles.delete(oldTile); | 
 |       } else { | 
 |         newTiles.push(new Tile(snapshot)); | 
 |       } | 
 |     } | 
 |     this._tilesByLayer.set(layer, reusedTiles.concat(newTiles)); | 
 |     for (const tile of oldSnapshotsToTiles.values()) { | 
 |       tile.dispose(); | 
 |     } | 
 |     if (!this._gl || !this._scale) { | 
 |       return Promise.resolve(); | 
 |     } | 
 |     return Promise.all(newTiles.map(tile => tile.update(this._gl, this._scale))).then(this._textureUpdatedCallback); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {number} scale | 
 |    */ | 
 |   setScale(scale) { | 
 |     if (this._scale && this._scale >= scale) { | 
 |       return; | 
 |     } | 
 |     this._scale = scale; | 
 |     this._updateTextures(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @return {!Array<!LayerViewer.LayerTextureManager.Tile>} | 
 |    */ | 
 |   tilesForLayer(layer) { | 
 |     return this._tilesByLayer.get(layer) || []; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    */ | 
 |   layerNeedsUpdate(layer) { | 
 |     if (this._queue.indexOf(layer) < 0) { | 
 |       this._queue.push(layer); | 
 |     } | 
 |     if (this._active) { | 
 |       this._throttler.schedule(this._update.bind(this)); | 
 |     } | 
 |   } | 
 |  | 
 |   forceUpdate() { | 
 |     this._queue.forEach(layer => this._updateLayer(layer)); | 
 |     this._queue = []; | 
 |     this._update(); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @return {!Promise} | 
 |    */ | 
 |   _update() { | 
 |     const layer = this._queue.shift(); | 
 |     if (!layer) { | 
 |       return Promise.resolve(); | 
 |     } | 
 |     if (this._queue.length) { | 
 |       this._throttler.schedule(this._update.bind(this)); | 
 |     } | 
 |     return this._updateLayer(layer); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @return {!Promise} | 
 |    */ | 
 |   _updateLayer(layer) { | 
 |     return Promise.all(layer.snapshots()) | 
 |         .then(snapshots => this._setSnapshotsForLayer(layer, snapshots.filter(snapshot => !!snapshot))); | 
 |   } | 
 |  | 
 |   _updateTextures() { | 
 |     if (!this._gl) { | 
 |       return; | 
 |     } | 
 |     if (!this._scale) { | 
 |       return; | 
 |     } | 
 |  | 
 |     for (const tiles of this._tilesByLayer.values()) { | 
 |       for (const tile of tiles) { | 
 |         const promise = tile.updateScale(this._gl, this._scale); | 
 |         if (promise) { | 
 |           promise.then(this._textureUpdatedCallback); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | /** | 
 |  * @unrestricted | 
 |  */ | 
 | export class Rectangle { | 
 |   /** | 
 |    * @param {?LayerViewer.LayerView.Selection} relatedObject | 
 |    */ | 
 |   constructor(relatedObject) { | 
 |     this.relatedObject = relatedObject; | 
 |     /** @type {number} */ | 
 |     this.lineWidth = 1; | 
 |     /** @type {?Array.<number>} */ | 
 |     this.borderColor = null; | 
 |     /** @type {?Array.<number>} */ | 
 |     this.fillColor = null; | 
 |     /** @type {?WebGLTexture} */ | 
 |     this.texture = null; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!Array.<number>} quad | 
 |    * @param {number} z | 
 |    */ | 
 |   setVertices(quad, z) { | 
 |     this.vertices = [quad[0], quad[1], z, quad[2], quad[3], z, quad[4], quad[5], z, quad[6], quad[7], z]; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Finds coordinates of point on layer quad, having offsets (ratioX * width) and (ratioY * height) | 
 |    * from the left corner of the initial layer rect, where width and heigth are layer bounds. | 
 |    * @param {!Array.<number>} quad | 
 |    * @param {number} ratioX | 
 |    * @param {number} ratioY | 
 |    * @return {!Array.<number>} | 
 |    */ | 
 |   _calculatePointOnQuad(quad, ratioX, ratioY) { | 
 |     const x0 = quad[0]; | 
 |     const y0 = quad[1]; | 
 |     const x1 = quad[2]; | 
 |     const y1 = quad[3]; | 
 |     const x2 = quad[4]; | 
 |     const y2 = quad[5]; | 
 |     const x3 = quad[6]; | 
 |     const y3 = quad[7]; | 
 |     // Point on the first quad side clockwise | 
 |     const firstSidePointX = x0 + ratioX * (x1 - x0); | 
 |     const firstSidePointY = y0 + ratioX * (y1 - y0); | 
 |     // Point on the third quad side clockwise | 
 |     const thirdSidePointX = x3 + ratioX * (x2 - x3); | 
 |     const thirdSidePointY = y3 + ratioX * (y2 - y3); | 
 |     const x = firstSidePointX + ratioY * (thirdSidePointX - firstSidePointX); | 
 |     const y = firstSidePointY + ratioY * (thirdSidePointY - firstSidePointY); | 
 |     return [x, y]; | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!SDK.Layer} layer | 
 |    * @param {!Protocol.DOM.Rect} rect | 
 |    * @param {number} z | 
 |    */ | 
 |   calculateVerticesFromRect(layer, rect, z) { | 
 |     const quad = layer.quad(); | 
 |     const rx1 = rect.x / layer.width(); | 
 |     const rx2 = (rect.x + rect.width) / layer.width(); | 
 |     const ry1 = rect.y / layer.height(); | 
 |     const ry2 = (rect.y + rect.height) / layer.height(); | 
 |     const rectQuad = this._calculatePointOnQuad(quad, rx1, ry1) | 
 |                          .concat(this._calculatePointOnQuad(quad, rx2, ry1)) | 
 |                          .concat(this._calculatePointOnQuad(quad, rx2, ry2)) | 
 |                          .concat(this._calculatePointOnQuad(quad, rx1, ry2)); | 
 |     this.setVertices(rectQuad, z); | 
 |   } | 
 |  | 
 |   /** | 
 |    * Intersects quad with given transform matrix and line l(t) = (x0, y0, t) | 
 |    * @param {!CSSMatrix} matrix | 
 |    * @param {number} x0 | 
 |    * @param {number} y0 | 
 |    * @return {(number|undefined)} | 
 |    */ | 
 |   intersectWithLine(matrix, x0, y0) { | 
 |     let i; | 
 |     // Vertices of the quad with transform matrix applied | 
 |     const points = []; | 
 |     for (i = 0; i < 4; ++i) { | 
 |       points[i] = UI.Geometry.multiplyVectorByMatrixAndNormalize( | 
 |           new UI.Geometry.Vector(this.vertices[i * 3], this.vertices[i * 3 + 1], this.vertices[i * 3 + 2]), matrix); | 
 |     } | 
 |     // Calculating quad plane normal | 
 |     const normal = UI.Geometry.crossProduct( | 
 |         UI.Geometry.subtract(points[1], points[0]), UI.Geometry.subtract(points[2], points[1])); | 
 |     // General form of the equation of the quad plane: A * x + B * y + C * z + D = 0 | 
 |     const A = normal.x; | 
 |     const B = normal.y; | 
 |     const C = normal.z; | 
 |     const D = -(A * points[0].x + B * points[0].y + C * points[0].z); | 
 |     // Finding t from the equation | 
 |     const t = -(D + A * x0 + B * y0) / C; | 
 |     // Point of the intersection | 
 |     const pt = new UI.Geometry.Vector(x0, y0, t); | 
 |     // Vectors from the intersection point to vertices of the quad | 
 |     const tVects = points.map(UI.Geometry.subtract.bind(null, pt)); | 
 |     // Intersection point lies inside of the polygon if scalar products of normal of the plane and | 
 |     // cross products of successive tVects are all nonstrictly above or all nonstrictly below zero | 
 |     for (i = 0; i < tVects.length; ++i) { | 
 |       const product = | 
 |           UI.Geometry.scalarProduct(normal, UI.Geometry.crossProduct(tVects[i], tVects[(i + 1) % tVects.length])); | 
 |       if (product < 0) { | 
 |         return undefined; | 
 |       } | 
 |     } | 
 |     return t; | 
 |   } | 
 | } | 
 |  | 
 | /** | 
 |  * @unrestricted | 
 |  */ | 
 | export class Tile { | 
 |   /** | 
 |    * @param {!SDK.SnapshotWithRect} snapshotWithRect | 
 |    */ | 
 |   constructor(snapshotWithRect) { | 
 |     this.snapshot = snapshotWithRect.snapshot; | 
 |     this.rect = snapshotWithRect.rect; | 
 |     this.scale = 0; | 
 |     /** @type {?WebGLTexture} */ | 
 |     this.texture = null; | 
 |   } | 
 |  | 
 |   dispose() { | 
 |     this.snapshot.release(); | 
 |     if (this.texture) { | 
 |       this._gl.deleteTexture(this.texture); | 
 |       this.texture = null; | 
 |     } | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!WebGLRenderingContext} glContext | 
 |    * @param {number} scale | 
 |    * @return {?Promise} | 
 |    */ | 
 |   updateScale(glContext, scale) { | 
 |     if (this.texture && this.scale >= scale) { | 
 |       return null; | 
 |     } | 
 |     return this.update(glContext, scale); | 
 |   } | 
 |  | 
 |   /** | 
 |    * @param {!WebGLRenderingContext} glContext | 
 |    * @param {number} scale | 
 |    * @return {!Promise} | 
 |    */ | 
 |   async update(glContext, scale) { | 
 |     this._gl = glContext; | 
 |     this.scale = scale; | 
 |     const imageURL = await this.snapshot.replay(scale); | 
 |     const image = imageURL ? await UI.loadImage(imageURL) : null; | 
 |     this.texture = image ? LayerTextureManager._createTextureForImage(glContext, image) : null; | 
 |   } | 
 | } | 
 |  | 
 | /* Legacy exported object */ | 
 | self.LayerViewer = self.LayerViewer || {}; | 
 |  | 
 | /* Legacy exported object */ | 
 | LayerViewer = LayerViewer || {}; | 
 |  | 
 | /** | 
 |  * @constructor | 
 |  */ | 
 | LayerViewer.Layers3DView = Layers3DView; | 
 |  | 
 | /** @typedef {{borderColor: !Array<number>, borderWidth: number}} */ | 
 | LayerViewer.Layers3DView.LayerStyle; | 
 |  | 
 | /** | 
 |  * @enum {string} | 
 |  */ | 
 | LayerViewer.Layers3DView.OutlineType = OutlineType; | 
 |  | 
 | /** | 
 |  * @enum {symbol} | 
 |  */ | 
 | LayerViewer.Layers3DView.Events = Events; | 
 |  | 
 | /** | 
 |  * @enum {number} | 
 |  */ | 
 | LayerViewer.Layers3DView.ChromeTexture = ChromeTexture; | 
 |  | 
 | /** | 
 |  * @enum {string} | 
 |  */ | 
 | LayerViewer.Layers3DView.ScrollRectTitles = ScrollRectTitles; | 
 |  | 
 | LayerViewer.Layers3DView.FragmentShader = FragmentShader; | 
 | LayerViewer.Layers3DView.VertexShader = VertexShader; | 
 |  | 
 | LayerViewer.Layers3DView.HoveredBorderColor = HoveredBorderColor; | 
 | LayerViewer.Layers3DView.SelectedBorderColor = SelectedBorderColor; | 
 | LayerViewer.Layers3DView.BorderColor = BorderColor; | 
 | LayerViewer.Layers3DView.ViewportBorderColor = ViewportBorderColor; | 
 | LayerViewer.Layers3DView.ScrollRectBackgroundColor = ScrollRectBackgroundColor; | 
 | LayerViewer.Layers3DView.HoveredImageMaskColor = HoveredImageMaskColor; | 
 | LayerViewer.Layers3DView.BorderWidth = BorderWidth; | 
 |  | 
 | LayerViewer.Layers3DView.SelectedBorderWidth = SelectedBorderWidth; | 
 | LayerViewer.Layers3DView.ViewportBorderWidth = ViewportBorderWidth; | 
 |  | 
 | LayerViewer.Layers3DView.LayerSpacing = LayerSpacing; | 
 | LayerViewer.Layers3DView.ScrollRectSpacing = ScrollRectSpacing; | 
 |  | 
 | /** | 
 |  * @constructor | 
 |  */ | 
 | LayerViewer.Layers3DView.Rectangle = Rectangle; | 
 |  | 
 | /** | 
 |  * @constructor | 
 |  */ | 
 | LayerViewer.LayerTextureManager = LayerTextureManager; | 
 |  | 
 | /** | 
 |  * @constructor | 
 |  */ | 
 | LayerViewer.LayerTextureManager.Tile = Tile; |