Skip to content

Commit 72548e4

Browse files
committed
ensure rrweb records canvas elements that are not ready on start (#102)
- Adds a verbose logger for the canvas manager - Fixes issue with WebGL canvas elements not being recorded if they were not ready (context not ready) when recording started. - Fixes rrweb dev build (`yarn dev`)
1 parent af37f76 commit 72548e4

File tree

4 files changed

+58
-8
lines changed

4 files changed

+58
-8
lines changed

packages/rrweb-snapshot/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"build": "rollup --config && ES_ONLY=true rollup --config",
1313
"bundle": "rollup --config",
1414
"bundle:es-only": "cross-env ES_ONLY=true rollup --config",
15-
"dev": "yarn bundle:es-only --watch",
15+
"dev": "ES_ONLY=true rollup --config --watch",
1616
"typegen": "tsc -d --declarationDir typings",
1717
"prepublish": "yarn typings && yarn bundle",
1818
"lint": "yarn eslint src"

packages/rrweb/src/record/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ function record<T = eventWithTime>(
9393
enableStrictPrivacy = false,
9494
ignoreCSSAttributes = new Set([]),
9595
errorHandler,
96+
logger,
9697
} = options;
9798

9899
registerErrorHandler(errorHandler);
@@ -322,6 +323,7 @@ function record<T = eventWithTime>(
322323
resizeQuality: sampling?.canvas?.resizeQuality,
323324
resizeFactor: sampling?.canvas?.resizeFactor,
324325
maxSnapshotDimension: sampling?.canvas?.maxSnapshotDimension,
326+
logger: logger,
325327
});
326328

327329
const shadowDomManager = new ShadowDomManager({

packages/rrweb/src/record/observers/canvas/canvas-manager.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export class CanvasManager {
3232
private pendingCanvasMutations: pendingCanvasMutationsMap = new Map();
3333
private rafStamps: RafStamps = { latestId: 0, invokeId: null };
3434
private mirror: Mirror;
35+
private logger?: {
36+
debug: (...args: Parameters<typeof console.debug>) => void;
37+
warn: (...args: Parameters<typeof console.warn>) => void;
38+
};
3539

3640
private mutationCb: canvasMutationCallback;
3741
private resetObservers?: listenerHandler;
@@ -71,6 +75,10 @@ export class CanvasManager {
7175
resizeQuality?: 'pixelated' | 'low' | 'medium' | 'high';
7276
resizeFactor?: number;
7377
maxSnapshotDimension?: number;
78+
logger?: {
79+
debug: (...args: Parameters<typeof console.debug>) => void;
80+
warn: (...args: Parameters<typeof console.warn>) => void;
81+
};
7482
}) {
7583
const {
7684
sampling = 'all',
@@ -82,6 +90,7 @@ export class CanvasManager {
8290
} = options;
8391
this.mutationCb = options.mutationCb;
8492
this.mirror = options.mirror;
93+
this.logger = options.logger;
8594

8695
if (recordCanvas && sampling === 'all')
8796
this.initCanvasMutationObserver(win, blockClass, blockSelector);
@@ -100,6 +109,18 @@ export class CanvasManager {
100109
);
101110
}
102111

112+
private debug(
113+
canvas?: HTMLCanvasElement,
114+
...args: Parameters<typeof console.log>
115+
) {
116+
if (!this.logger) return;
117+
let prefix = '[highlight-canvas]';
118+
if (canvas) {
119+
prefix += ` [ctx:${(canvas as ICanvas).__context}]`;
120+
}
121+
this.logger.debug(prefix, canvas, ...args);
122+
}
123+
103124
private processMutation: canvasManagerMutationCallback = (
104125
target,
105126
mutation,
@@ -184,6 +205,7 @@ export class CanvasManager {
184205
const matchedCanvas: HTMLCanvasElement[] = [];
185206
win.document.querySelectorAll('canvas').forEach((canvas) => {
186207
if (!isBlocked(canvas, blockClass, blockSelector, true)) {
208+
this.debug(canvas, 'discovered canvas');
187209
matchedCanvas.push(canvas);
188210
}
189211
});
@@ -200,12 +222,15 @@ export class CanvasManager {
200222
}
201223
lastSnapshotTime = timestamp;
202224

203-
getCanvas()
204-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
205-
.forEach(async (canvas: HTMLCanvasElement) => {
206-
const id = this.mirror.getId(canvas);
207-
if (snapshotInProgressMap.get(id)) return;
208-
snapshotInProgressMap.set(id, true);
225+
getCanvas().forEach(async (canvas: HTMLCanvasElement) => {
226+
this.debug(canvas, 'starting snapshotting');
227+
const id = this.mirror.getId(canvas);
228+
if (snapshotInProgressMap.get(id)) {
229+
this.debug(canvas, 'snapshotting already in progress for', id);
230+
return;
231+
}
232+
snapshotInProgressMap.set(id, true);
233+
try {
209234
if (['webgl', 'webgl2'].includes((canvas as ICanvas).__context)) {
210235
// if the canvas hasn't been modified recently,
211236
// its contents won't be in memory and `createImageBitmap`
@@ -231,6 +256,10 @@ export class CanvasManager {
231256
// canvas is not yet ready... this retry on the next sampling iteration.
232257
// we don't want to crash the worker if the canvas is not yet rendered.
233258
if (canvas.width === 0 || canvas.height === 0) {
259+
this.debug(canvas, 'not yet ready', {
260+
width: canvas.width,
261+
height: canvas.height,
262+
});
234263
return;
235264
}
236265
let scale = resizeFactor || 1;
@@ -241,11 +270,18 @@ export class CanvasManager {
241270
const width = canvas.width * scale;
242271
const height = canvas.height * scale;
243272

273+
window.performance.mark(`canvas-${canvas.id}-snapshot`);
244274
const bitmap = await createImageBitmap(canvas, {
245275
resizeQuality: resizeQuality || 'low',
246276
resizeWidth: width,
247277
resizeHeight: height,
248278
});
279+
this.debug(
280+
canvas,
281+
'took a snapshot in',
282+
window.performance.measure(`canvas-snapshot`),
283+
);
284+
window.performance.mark(`canvas-postMessage`);
249285
worker.postMessage(
250286
{
251287
id,
@@ -258,7 +294,15 @@ export class CanvasManager {
258294
},
259295
[bitmap],
260296
);
261-
});
297+
this.debug(
298+
canvas,
299+
'send message in',
300+
window.performance.measure(`canvas-postMessage`),
301+
);
302+
} finally {
303+
snapshotInProgressMap.set(id, false);
304+
}
305+
});
262306
rafId = requestAnimationFrame(takeCanvasSnapshots);
263307
};
264308

packages/rrweb/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ export type recordOptions<T> = {
7878
* Text will be randomized. Instead of seeing "Hello World" in a recording, you will see "1fds1 j59a0".
7979
*/
8080
enableStrictPrivacy?: boolean;
81+
logger?: {
82+
debug: (...args: Parameters<typeof console.debug>) => void;
83+
warn: (...args: Parameters<typeof console.warn>) => void;
84+
};
8185
};
8286

8387
export type observerParam = {

0 commit comments

Comments
 (0)