| // Copyright 2016 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| /** | |
| * @unrestricted | |
| */ | |
| export class TracingLayerTree extends SDK.LayerTreeBase { | |
| /** | |
| * @param {?SDK.Target} target | |
| */ | |
| constructor(target) { | |
| super(target); | |
| /** @type {!Map.<string, !TimelineModel.TracingLayerTile>} */ | |
| this._tileById = new Map(); | |
| this._paintProfilerModel = target && target.model(SDK.PaintProfilerModel); | |
| } | |
| /** | |
| * @param {?TimelineModel.TracingLayerPayload} root | |
| * @param {?Array<!TimelineModel.TracingLayerPayload>} layers | |
| * @param {!Array<!TimelineModel.LayerPaintEvent>} paints | |
| * @return {!Promise} | |
| */ | |
| async setLayers(root, layers, paints) { | |
| const idsToResolve = new Set(); | |
| if (root) { | |
| // This is a legacy code path for compatibility, as cc is removing | |
| // layer tree hierarchy, this code will eventually be removed. | |
| this._extractNodeIdsToResolve(idsToResolve, {}, root); | |
| } else { | |
| for (let i = 0; i < layers.length; ++i) { | |
| this._extractNodeIdsToResolve(idsToResolve, {}, layers[i]); | |
| } | |
| } | |
| await this.resolveBackendNodeIds(idsToResolve); | |
| const oldLayersById = this._layersById; | |
| this._layersById = {}; | |
| this.setContentRoot(null); | |
| if (root) { | |
| const convertedLayers = this._innerSetLayers(oldLayersById, root); | |
| this.setRoot(convertedLayers); | |
| } else { | |
| const processedLayers = layers.map(this._innerSetLayers.bind(this, oldLayersById)); | |
| const contentRoot = this.contentRoot(); | |
| this.setRoot(contentRoot); | |
| for (let i = 0; i < processedLayers.length; ++i) { | |
| if (processedLayers[i].id() !== contentRoot.id()) { | |
| contentRoot.addChild(processedLayers[i]); | |
| } | |
| } | |
| } | |
| this._setPaints(paints); | |
| } | |
| /** | |
| * @param {!Array.<!TimelineModel.TracingLayerTile>} tiles | |
| */ | |
| setTiles(tiles) { | |
| this._tileById = new Map(); | |
| for (const tile of tiles) { | |
| this._tileById.set(tile.id, tile); | |
| } | |
| } | |
| /** | |
| * @param {string} tileId | |
| * @return {!Promise<?SDK.SnapshotWithRect>} | |
| */ | |
| pictureForRasterTile(tileId) { | |
| const tile = this._tileById.get('cc::Tile/' + tileId); | |
| if (!tile) { | |
| Common.console.error(`Tile ${tileId} is missing`); | |
| return /** @type {!Promise<?SDK.SnapshotWithRect>} */ (Promise.resolve(null)); | |
| } | |
| const layer = /** @type {?TracingLayer} */ (this.layerById(tile.layer_id)); | |
| if (!layer) { | |
| Common.console.error(`Layer ${tile.layer_id} for tile ${tileId} is not found`); | |
| return /** @type {!Promise<?SDK.SnapshotWithRect>} */ (Promise.resolve(null)); | |
| } | |
| return layer._pictureForRect(tile.content_rect); | |
| } | |
| /** | |
| * @param {!Array<!TimelineModel.LayerPaintEvent>} paints | |
| */ | |
| _setPaints(paints) { | |
| for (let i = 0; i < paints.length; ++i) { | |
| const layer = this._layersById[paints[i].layerId()]; | |
| if (layer) { | |
| layer._addPaintEvent(paints[i]); | |
| } | |
| } | |
| } | |
| /** | |
| * @param {!Object<(string|number), !SDK.Layer>} oldLayersById | |
| * @param {!TimelineModel.TracingLayerPayload} payload | |
| * @return {!TracingLayer} | |
| */ | |
| _innerSetLayers(oldLayersById, payload) { | |
| let layer = /** @type {?TracingLayer} */ (oldLayersById[payload.layer_id]); | |
| if (layer) { | |
| layer._reset(payload); | |
| } else { | |
| layer = new TracingLayer(this._paintProfilerModel, payload); | |
| } | |
| this._layersById[payload.layer_id] = layer; | |
| if (payload.owner_node) { | |
| layer._setNode(this.backendNodeIdToNode().get(payload.owner_node) || null); | |
| } | |
| if (!this.contentRoot() && layer.drawsContent()) { | |
| this.setContentRoot(layer); | |
| } | |
| for (let i = 0; payload.children && i < payload.children.length; ++i) { | |
| layer.addChild(this._innerSetLayers(oldLayersById, payload.children[i])); | |
| } | |
| return layer; | |
| } | |
| /** | |
| * @param {!Set<number>} nodeIdsToResolve | |
| * @param {!Object} seenNodeIds | |
| * @param {!TimelineModel.TracingLayerPayload} payload | |
| */ | |
| _extractNodeIdsToResolve(nodeIdsToResolve, seenNodeIds, payload) { | |
| const backendNodeId = payload.owner_node; | |
| if (backendNodeId && !this.backendNodeIdToNode().has(backendNodeId)) { | |
| nodeIdsToResolve.add(backendNodeId); | |
| } | |
| for (let i = 0; payload.children && i < payload.children.length; ++i) { | |
| this._extractNodeIdsToResolve(nodeIdsToResolve, seenNodeIds, payload.children[i]); | |
| } | |
| } | |
| } | |
| /** | |
| * @implements {SDK.Layer} | |
| * @unrestricted | |
| */ | |
| export class TracingLayer { | |
| /** | |
| * @param {?SDK.PaintProfilerModel} paintProfilerModel | |
| * @param {!TimelineModel.TracingLayerPayload} payload | |
| */ | |
| constructor(paintProfilerModel, payload) { | |
| this._paintProfilerModel = paintProfilerModel; | |
| this._reset(payload); | |
| } | |
| /** | |
| * @param {!TimelineModel.TracingLayerPayload} payload | |
| */ | |
| _reset(payload) { | |
| /** @type {?SDK.DOMNode} */ | |
| this._node = null; | |
| this._layerId = String(payload.layer_id); | |
| this._offsetX = payload.position[0]; | |
| this._offsetY = payload.position[1]; | |
| this._width = payload.bounds.width; | |
| this._height = payload.bounds.height; | |
| this._children = []; | |
| this._parentLayerId = null; | |
| this._parent = null; | |
| this._quad = payload.layer_quad || []; | |
| this._createScrollRects(payload); | |
| // Keep payload.compositing_reasons as a default | |
| // but use the newer payload.debug_info.compositing_reasons | |
| // if the first one is not set. | |
| this._compositingReasons = | |
| payload.compositing_reasons || (payload.debug_info && payload.debug_info.compositing_reasons) || []; | |
| this._drawsContent = !!payload.draws_content; | |
| this._gpuMemoryUsage = payload.gpu_memory_usage; | |
| this._paints = []; | |
| } | |
| /** | |
| * @override | |
| * @return {string} | |
| */ | |
| id() { | |
| return this._layerId; | |
| } | |
| /** | |
| * @override | |
| * @return {?string} | |
| */ | |
| parentId() { | |
| return this._parentLayerId; | |
| } | |
| /** | |
| * @override | |
| * @return {?SDK.Layer} | |
| */ | |
| parent() { | |
| return this._parent; | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| isRoot() { | |
| return !this.parentId(); | |
| } | |
| /** | |
| * @override | |
| * @return {!Array.<!SDK.Layer>} | |
| */ | |
| children() { | |
| return this._children; | |
| } | |
| /** | |
| * @override | |
| * @param {!SDK.Layer} childParam | |
| */ | |
| addChild(childParam) { | |
| const child = /** @type {!TracingLayer} */ (childParam); | |
| if (child._parent) { | |
| console.assert(false, 'Child already has a parent'); | |
| } | |
| this._children.push(child); | |
| child._parent = this; | |
| child._parentLayerId = this._layerId; | |
| } | |
| /** | |
| * @param {?SDK.DOMNode} node | |
| */ | |
| _setNode(node) { | |
| this._node = node; | |
| } | |
| /** | |
| * @override | |
| * @return {?SDK.DOMNode} | |
| */ | |
| node() { | |
| return this._node; | |
| } | |
| /** | |
| * @override | |
| * @return {?SDK.DOMNode} | |
| */ | |
| nodeForSelfOrAncestor() { | |
| for (let layer = this; layer; layer = layer._parent) { | |
| if (layer._node) { | |
| return layer._node; | |
| } | |
| } | |
| return null; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| offsetX() { | |
| return this._offsetX; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| offsetY() { | |
| return this._offsetY; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| width() { | |
| return this._width; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| height() { | |
| return this._height; | |
| } | |
| /** | |
| * @override | |
| * @return {?Array.<number>} | |
| */ | |
| transform() { | |
| return null; | |
| } | |
| /** | |
| * @override | |
| * @return {!Array.<number>} | |
| */ | |
| quad() { | |
| return this._quad; | |
| } | |
| /** | |
| * @override | |
| * @return {!Array.<number>} | |
| */ | |
| anchorPoint() { | |
| return [0.5, 0.5, 0]; | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| invisible() { | |
| return false; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| paintCount() { | |
| return 0; | |
| } | |
| /** | |
| * @override | |
| * @return {?Protocol.DOM.Rect} | |
| */ | |
| lastPaintRect() { | |
| return null; | |
| } | |
| /** | |
| * @override | |
| * @return {!Array.<!Protocol.LayerTree.ScrollRect>} | |
| */ | |
| scrollRects() { | |
| return this._scrollRects; | |
| } | |
| /** | |
| * @override | |
| * @return {?SDK.Layer.StickyPositionConstraint} | |
| */ | |
| stickyPositionConstraint() { | |
| // TODO(smcgruer): Provide sticky layer information in traces. | |
| return null; | |
| } | |
| /** | |
| * @override | |
| * @return {number} | |
| */ | |
| gpuMemoryUsage() { | |
| return this._gpuMemoryUsage; | |
| } | |
| /** | |
| * @override | |
| * @return {!Array<!Promise<?SDK.SnapshotWithRect>>} | |
| */ | |
| snapshots() { | |
| return this._paints.map(paint => paint.snapshotPromise().then(snapshot => { | |
| if (!snapshot) { | |
| return null; | |
| } | |
| const rect = {x: snapshot.rect[0], y: snapshot.rect[1], width: snapshot.rect[2], height: snapshot.rect[3]}; | |
| return {rect: rect, snapshot: snapshot.snapshot}; | |
| })); | |
| } | |
| /** | |
| * @param {!Array<number>} targetRect | |
| * @return {!Promise<?SDK.SnapshotWithRect>} | |
| */ | |
| _pictureForRect(targetRect) { | |
| return Promise.all(this._paints.map(paint => paint.picturePromise())).then(pictures => { | |
| const fragments = | |
| pictures.filter(picture => picture && rectsOverlap(picture.rect, targetRect)) | |
| .map(picture => ({x: picture.rect[0], y: picture.rect[1], picture: picture.serializedPicture})); | |
| if (!fragments.length || !this._paintProfilerModel) { | |
| return null; | |
| } | |
| const x0 = fragments.reduce((min, item) => Math.min(min, item.x), Infinity); | |
| const y0 = fragments.reduce((min, item) => Math.min(min, item.y), Infinity); | |
| // Rect is in layer content coordinates, make it relative to picture by offsetting to the top left corner. | |
| const rect = {x: targetRect[0] - x0, y: targetRect[1] - y0, width: targetRect[2], height: targetRect[3]}; | |
| return this._paintProfilerModel.loadSnapshotFromFragments(fragments).then( | |
| snapshot => snapshot ? {rect: rect, snapshot: snapshot} : null); | |
| }); | |
| /** | |
| * @param {number} a1 | |
| * @param {number} a2 | |
| * @param {number} b1 | |
| * @param {number} b2 | |
| * @return {boolean} | |
| */ | |
| function segmentsOverlap(a1, a2, b1, b2) { | |
| console.assert(a1 <= a2 && b1 <= b2, 'segments should be specified as ordered pairs'); | |
| return a2 > b1 && a1 < b2; | |
| } | |
| /** | |
| * @param {!Array.<number>} a | |
| * @param {!Array.<number>} b | |
| * @return {boolean} | |
| */ | |
| function rectsOverlap(a, b) { | |
| return segmentsOverlap(a[0], a[0] + a[2], b[0], b[0] + b[2]) && | |
| segmentsOverlap(a[1], a[1] + a[3], b[1], b[1] + b[3]); | |
| } | |
| } | |
| /** | |
| * @param {!Array.<number>} params | |
| * @param {string} type | |
| * @return {!Object} | |
| */ | |
| _scrollRectsFromParams(params, type) { | |
| return {rect: {x: params[0], y: params[1], width: params[2], height: params[3]}, type: type}; | |
| } | |
| /** | |
| * @param {!TimelineModel.TracingLayerPayload} payload | |
| */ | |
| _createScrollRects(payload) { | |
| this._scrollRects = []; | |
| if (payload.non_fast_scrollable_region) { | |
| this._scrollRects.push(this._scrollRectsFromParams( | |
| payload.non_fast_scrollable_region, SDK.Layer.ScrollRectType.NonFastScrollable.name)); | |
| } | |
| if (payload.touch_event_handler_region) { | |
| this._scrollRects.push(this._scrollRectsFromParams( | |
| payload.touch_event_handler_region, SDK.Layer.ScrollRectType.TouchEventHandler.name)); | |
| } | |
| if (payload.wheel_event_handler_region) { | |
| this._scrollRects.push(this._scrollRectsFromParams( | |
| payload.wheel_event_handler_region, SDK.Layer.ScrollRectType.WheelEventHandler.name)); | |
| } | |
| if (payload.scroll_event_handler_region) { | |
| this._scrollRects.push(this._scrollRectsFromParams( | |
| payload.scroll_event_handler_region, SDK.Layer.ScrollRectType.RepaintsOnScroll.name)); | |
| } | |
| } | |
| /** | |
| * @param {!TimelineModel.LayerPaintEvent} paint | |
| */ | |
| _addPaintEvent(paint) { | |
| this._paints.push(paint); | |
| } | |
| /** | |
| * @override | |
| * @return {!Promise<!Array<string>>} | |
| */ | |
| requestCompositingReasons() { | |
| return Promise.resolve(this._compositingReasons); | |
| } | |
| /** | |
| * @override | |
| * @return {boolean} | |
| */ | |
| drawsContent() { | |
| return this._drawsContent; | |
| } | |
| } | |
| /* Legacy exported object */ | |
| self.TimelineModel = self.TimelineModel || {}; | |
| /* Legacy exported object */ | |
| TimelineModel = TimelineModel || {}; | |
| /** @constructor */ | |
| TimelineModel.TracingLayerTree = TracingLayerTree; | |
| /** @constructor */ | |
| TimelineModel.TracingLayer = TracingLayer; | |
| /** @typedef {!{ | |
| bounds: {height: number, width: number}, | |
| children: Array.<!TimelineModel.TracingLayerPayload>, | |
| layer_id: number, | |
| position: Array.<number>, | |
| scroll_offset: Array.<number>, | |
| layer_quad: Array.<number>, | |
| draws_content: number, | |
| gpu_memory_usage: number, | |
| transform: Array.<number>, | |
| owner_node: number, | |
| compositing_reasons: Array.<string> | |
| }} | |
| */ | |
| TimelineModel.TracingLayerPayload; | |
| /** @typedef {!{ | |
| id: string, | |
| layer_id: string, | |
| gpu_memory_usage: number, | |
| content_rect: !Array.<number> | |
| }} | |
| */ | |
| TimelineModel.TracingLayerTile; |