UNPKG

chrome-devtools-frontend

Version:
552 lines (499 loc) • 14.7 kB
// 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. import * as SDK from '../sdk/sdk.js'; import * as Common from '../common/common.js'; import {LayerPaintEvent} from './TimelineFrameModel.js'; // eslint-disable-line no-unused-vars export class TracingLayerTree extends SDK.LayerTreeBase.LayerTreeBase { /** * @param {?SDK.SDKModel.Target} target */ constructor(target) { super(target); /** @type {!Map.<string, !TracingLayerTile>} */ this._tileById = new Map(); this._paintProfilerModel = target && target.model(SDK.PaintProfiler.PaintProfilerModel); } /** * @param {?TracingLayerPayload} root * @param {?Array<!TracingLayerPayload>} layers * @param {!Array<!LayerPaintEvent>} paints */ 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 if (layers) { for (let i = 0; i < layers.length; ++i) { this._extractNodeIdsToResolve(idsToResolve, {}, layers[i]); } } await this.resolveBackendNodeIds(idsToResolve); const oldLayersById = this.layersById; this.layersById = new Map(); this.setContentRoot(null); if (root) { const convertedLayers = this._innerSetLayers(oldLayersById, root); this.setRoot(convertedLayers); } else if (layers) { const processedLayers = layers.map(this._innerSetLayers.bind(this, oldLayersById)); const contentRoot = this.contentRoot(); if (!contentRoot) { throw new Error('Content root is not set.'); } 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.<!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.PaintProfiler.SnapshotWithRect>} */ pictureForRasterTile(tileId) { const tile = this._tileById.get('cc::Tile/' + tileId); if (!tile) { Common.Console.Console.instance().error(`Tile ${tileId} is missing`); return /** @type {!Promise<?SDK.PaintProfiler.SnapshotWithRect>} */ (Promise.resolve(null)); } const layer = /** @type {?TracingLayer} */ (this.layerById(tile.layer_id)); if (!layer) { Common.Console.Console.instance().error(`Layer ${tile.layer_id} for tile ${tileId} is not found`); return /** @type {!Promise<?SDK.PaintProfiler.SnapshotWithRect>} */ (Promise.resolve(null)); } return layer._pictureForRect(tile.content_rect); } /** * @param {!Array<!LayerPaintEvent>} paints */ _setPaints(paints) { for (let i = 0; i < paints.length; ++i) { const layer = /** @type {?TracingLayer} */ (this.layersById.get(paints[i].layerId())); if (layer) { layer._addPaintEvent(paints[i]); } } } /** * @param {!Map<(string|number), !SDK.LayerTreeBase.Layer>} oldLayersById * @param {!TracingLayerPayload} payload * @return {!TracingLayer} */ _innerSetLayers(oldLayersById, payload) { let layer = /** @type {?TracingLayer} */ (oldLayersById.get(payload.layer_id)); if (layer) { layer._reset(payload); } else { layer = new TracingLayer(this._paintProfilerModel, payload); } this.layersById.set(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 {!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.LayerTreeBase.Layer} */ export class TracingLayer { /** * @param {?SDK.PaintProfiler.PaintProfilerModel} paintProfilerModel * @param {!TracingLayerPayload} payload */ constructor(paintProfilerModel, payload) { /** @type {?string} */ this._parentLayerId = null; /** @type {?SDK.LayerTreeBase.Layer} */ this._parent = null; this._layerId = ''; /** @type {?SDK.DOMModel.DOMNode} */ this._node = null; this._offsetX = -1; this._offsetY = -1; this._width = -1; this._height = -1; /** @type {!Array<!SDK.LayerTreeBase.Layer>} */ this._children = []; /** @type {!Array<number>} */ this._quad = []; /** @type {!Array<!Protocol.LayerTree.ScrollRect>}*/ this._scrollRects = []; this._gpuMemoryUsage = -1; /** @type {!Array<!LayerPaintEvent>} */ this._paints = []; /** @type {!Array<string>} */ this._compositingReasonIds = []; this._drawsContent = false; this._paintProfilerModel = paintProfilerModel; this._reset(payload); } /** * @param {!TracingLayerPayload} payload */ _reset(payload) { 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._compositingReasonIds = payload.compositing_reason_ids || (payload.debug_info && payload.debug_info.compositing_reason_ids) || []; this._drawsContent = Boolean(payload.draws_content); this._gpuMemoryUsage = payload.gpu_memory_usage; /** @type {!Array<!LayerPaintEvent>} */ this._paints = []; } /** * @override * @return {string} */ id() { return this._layerId; } /** * @override * @return {?string} */ parentId() { return this._parentLayerId; } /** * @override * @return {?SDK.LayerTreeBase.Layer} */ parent() { return this._parent; } /** * @override * @return {boolean} */ isRoot() { return !this.parentId(); } /** * @override * @return {!Array.<!SDK.LayerTreeBase.Layer>} */ children() { return this._children; } /** * @override * @param {!SDK.LayerTreeBase.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.DOMModel.DOMNode} node */ _setNode(node) { this._node = node; } /** * @override * @return {?SDK.DOMModel.DOMNode} */ node() { return this._node; } /** * @override * @return {?SDK.DOMModel.DOMNode} */ nodeForSelfOrAncestor() { /** @type {?SDK.LayerTreeBase.Layer} */ let layer = this; for (; 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.LayerTreeBase.StickyPositionConstraint} */ stickyPositionConstraint() { // TODO(smcgruer): Provide sticky layer information in traces. return null; } /** * @override * @return {number} */ gpuMemoryUsage() { return this._gpuMemoryUsage; } /** * @override * @return {!Array<!Promise<?SDK.PaintProfiler.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.PaintProfiler.SnapshotWithRect>} */ _pictureForRect(targetRect) { return Promise.all(this._paints.map(paint => paint.picturePromise())).then(pictures => { const filteredPictures = /** @type {!Array<!{rect: !Array<number>, serializedPicture: string}>} */ ( pictures.filter(picture => picture && rectsOverlap(picture.rect, targetRect))); const fragments = filteredPictures.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 {!TracingLayerPayload} payload */ _createScrollRects(payload) { /** @type {!Array<*>} */ const nonPayloadScrollRects = []; if (payload.non_fast_scrollable_region) { nonPayloadScrollRects.push(this._scrollRectsFromParams( payload.non_fast_scrollable_region, SDK.LayerTreeBase.Layer.ScrollRectType.NonFastScrollable)); } if (payload.touch_event_handler_region) { nonPayloadScrollRects.push(this._scrollRectsFromParams( payload.touch_event_handler_region, SDK.LayerTreeBase.Layer.ScrollRectType.TouchEventHandler)); } if (payload.wheel_event_handler_region) { nonPayloadScrollRects.push(this._scrollRectsFromParams( payload.wheel_event_handler_region, SDK.LayerTreeBase.Layer.ScrollRectType.WheelEventHandler)); } if (payload.scroll_event_handler_region) { nonPayloadScrollRects.push(this._scrollRectsFromParams( payload.scroll_event_handler_region, SDK.LayerTreeBase.Layer.ScrollRectType.RepaintsOnScroll)); } // SDK.LayerBaseTree.Layer.ScrollRectType and Protocol.LayerTree.ScrollRectType are the // same type, but we need to use the indirection of the nonPayloadScrollRects since // the ScrollRectType is defined as a string in SDK.LayerBaseTree.Layer.ScrollRectType. this._scrollRects = nonPayloadScrollRects; } /** * @param {!LayerPaintEvent} paint */ _addPaintEvent(paint) { this._paints.push(paint); } /** * @override * @return {!Promise<!Array<string>>} */ requestCompositingReasonIds() { return Promise.resolve(this._compositingReasonIds); } /** * @override * @return {boolean} */ drawsContent() { return this._drawsContent; } } /** @typedef {!{ bounds: {height: number, width: number}, children: Array.<!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, reasons: Array.<string>, compositing_reason: Array.<string>, compositing_reason_ids: Array.<string>, debug_info: {compositing_reason_ids: Array.<string>}, non_fast_scrollable_region: Array.<number>, touch_event_handler_region: Array.<number>, wheel_event_handler_region: Array.<number>, scroll_event_handler_region: Array.<number> }} */ // @ts-ignore typedef export let TracingLayerPayload; /** @typedef {!{ id: string, layer_id: string, gpu_memory_usage: number, content_rect: !Array.<number> }} */ // @ts-ignore typedef export let TracingLayerTile;