From d9b44966a16091ea2a7fc5929a203caa4d12ff0d Mon Sep 17 00:00:00 2001 From: Vadim Korolik Date: Fri, 28 Apr 2023 14:51:29 -0700 Subject: [PATCH] ensure rrweb records canvas elements that are not ready on start add a verbose logger for the canvas manager fix rrweb dev build --- packages/rrweb-snapshot/package.json | 2 +- packages/rrweb/src/record/index.ts | 2 + .../record/observers/canvas/canvas-manager.ts | 58 ++++++++++++++++--- packages/rrweb/src/types.ts | 4 ++ 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/packages/rrweb-snapshot/package.json b/packages/rrweb-snapshot/package.json index 6e93d39f..cf6046d4 100644 --- a/packages/rrweb-snapshot/package.json +++ b/packages/rrweb-snapshot/package.json @@ -9,7 +9,7 @@ "test": "jest", "test:watch": "jest --watch", "build": "rollup --config && ES_ONLY=true rollup --config", - "dev": "yarn bundle:es-only --watch", + "dev": "ES_ONLY=true rollup --config --watch", "typegen": "tsc -d --declarationDir typings", "prepublish": "npm run typegen && npm run build", "lint": "yarn eslint src" diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index 26d7ebb1..9e12f2b8 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -90,6 +90,7 @@ function record( enableStrictPrivacy = false, ignoreCSSAttributes = new Set([]), errorHandler, + logger, } = options; registerErrorHandler(errorHandler); @@ -319,6 +320,7 @@ function record( resizeQuality: sampling?.canvas?.resizeQuality, resizeFactor: sampling?.canvas?.resizeFactor, maxSnapshotDimension: sampling?.canvas?.maxSnapshotDimension, + logger: logger, }); const shadowDomManager = new ShadowDomManager({ diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts index d597a4b7..0b6d1afd 100644 --- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts +++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts @@ -32,6 +32,10 @@ export class CanvasManager { private pendingCanvasMutations: pendingCanvasMutationsMap = new Map(); private rafStamps: RafStamps = { latestId: 0, invokeId: null }; private mirror: Mirror; + private logger?: { + debug: (...args: Parameters) => void; + warn: (...args: Parameters) => void; + }; private mutationCb: canvasMutationCallback; private resetObservers?: listenerHandler; @@ -71,6 +75,10 @@ export class CanvasManager { resizeQuality?: 'pixelated' | 'low' | 'medium' | 'high'; resizeFactor?: number; maxSnapshotDimension?: number; + logger?: { + debug: (...args: Parameters) => void; + warn: (...args: Parameters) => void; + }; }) { const { sampling = 'all', @@ -82,6 +90,7 @@ export class CanvasManager { } = options; this.mutationCb = options.mutationCb; this.mirror = options.mirror; + this.logger = options.logger; if (recordCanvas && sampling === 'all') this.initCanvasMutationObserver(win, blockClass, blockSelector); @@ -100,6 +109,18 @@ export class CanvasManager { ); } + private debug( + canvas?: HTMLCanvasElement, + ...args: Parameters + ) { + if (!this.logger) return; + let prefix = '[highlight-canvas]'; + if (canvas) { + prefix += ` [ctx:${(canvas as ICanvas).__context}]`; + } + this.logger.debug(prefix, canvas, ...args); + } + private processMutation: canvasManagerMutationCallback = ( target, mutation, @@ -183,6 +204,7 @@ export class CanvasManager { const matchedCanvas: HTMLCanvasElement[] = []; win.document.querySelectorAll('canvas').forEach((canvas) => { if (!isBlocked(canvas, blockClass, blockSelector, true)) { + this.debug(canvas, 'discovered canvas'); matchedCanvas.push(canvas); } }); @@ -199,12 +221,15 @@ export class CanvasManager { } lastSnapshotTime = timestamp; - getCanvas() - // eslint-disable-next-line @typescript-eslint/no-misused-promises - .forEach(async (canvas: HTMLCanvasElement) => { - const id = this.mirror.getId(canvas); - if (snapshotInProgressMap.get(id)) return; - snapshotInProgressMap.set(id, true); + getCanvas().forEach(async (canvas: HTMLCanvasElement) => { + this.debug(canvas, 'starting snapshotting'); + const id = this.mirror.getId(canvas); + if (snapshotInProgressMap.get(id)) { + this.debug(canvas, 'snapshotting already in progress for', id); + return; + } + snapshotInProgressMap.set(id, true); + try { if (['webgl', 'webgl2'].includes((canvas as ICanvas).__context)) { // if the canvas hasn't been modified recently, // its contents won't be in memory and `createImageBitmap` @@ -227,6 +252,10 @@ export class CanvasManager { // canvas is not yet ready... this retry on the next sampling iteration. // we don't want to crash the worker if the canvas is not yet rendered. if (canvas.width === 0 || canvas.height === 0) { + this.debug(canvas, 'not yet ready', { + width: canvas.width, + height: canvas.height, + }); return; } let scale = resizeFactor || 1; @@ -237,11 +266,18 @@ export class CanvasManager { const width = canvas.width * scale; const height = canvas.height * scale; + window.performance.mark(`canvas-${canvas.id}-snapshot`); const bitmap = await createImageBitmap(canvas, { resizeQuality: resizeQuality || 'low', resizeWidth: width, resizeHeight: height, }); + this.debug( + canvas, + 'took a snapshot in', + window.performance.measure(`canvas-snapshot`), + ); + window.performance.mark(`canvas-postMessage`); worker.postMessage( { id, @@ -254,7 +290,15 @@ export class CanvasManager { }, [bitmap], ); - }); + this.debug( + canvas, + 'send message in', + window.performance.measure(`canvas-postMessage`), + ); + } finally { + snapshotInProgressMap.set(id, false); + } + }); rafId = requestAnimationFrame(takeCanvasSnapshots); }; diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index 1d2b0f52..ef22ee27 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -75,6 +75,10 @@ export type recordOptions = { * Text will be randomized. Instead of seeing "Hello World" in a recording, you will see "1fds1 j59a0". */ enableStrictPrivacy?: boolean; + logger?: { + debug: (...args: Parameters) => void; + warn: (...args: Parameters) => void; + }; }; export type observerParam = {