blob: b231065d4708cb33d97a1260fa52868f2fc323ea [file] [log] [blame]
// 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;