@@ -72,6 +72,8 @@ export class CanvasManager {
7272 blockSelector : string | null ;
7373 mirror : Mirror ;
7474 sampling ?: 'all' | number ;
75+ clearWebGLBuffer ?: boolean ;
76+ initialSnapshotDelay ?: number ;
7577 dataURLOptions : DataURLOptions ;
7678 resizeFactor ?: number ;
7779 maxSnapshotDimension ?: number ;
@@ -87,6 +89,8 @@ export class CanvasManager {
8789 blockSelector,
8890 recordCanvas,
8991 recordVideos,
92+ clearWebGLBuffer,
93+ initialSnapshotDelay,
9094 dataURLOptions,
9195 } = options ;
9296 this . mutationCb = options . mutationCb ;
@@ -103,6 +107,8 @@ export class CanvasManager {
103107 blockClass ,
104108 blockSelector ,
105109 {
110+ clearWebGLBuffer,
111+ initialSnapshotDelay,
106112 dataURLOptions,
107113 } ,
108114 options . resizeFactor ,
@@ -111,15 +117,19 @@ export class CanvasManager {
111117 }
112118
113119 private debug (
114- element : HTMLCanvasElement | HTMLVideoElement ,
120+ element : HTMLCanvasElement | HTMLVideoElement | null ,
115121 ...args : Parameters < typeof console . log >
116122 ) {
117123 if ( ! this . logger ) return ;
118- let prefix = `[highlight-${ element . tagName . toLowerCase ( ) } ]` ;
119- if ( element . tagName . toLowerCase ( ) === 'canvas' ) {
120- prefix += ` [ctx:${ ( element as ICanvas ) . __context } ]` ;
124+ const id = this . mirror . getId ( element ) ;
125+ let prefix = '[highlight-canvas-manager]' ;
126+ if ( element ) {
127+ prefix = `[highlight-${ element . tagName . toLowerCase ( ) } ] [id:${ id } ]` ;
128+ if ( element . tagName . toLowerCase ( ) === 'canvas' ) {
129+ prefix += ` [ctx:${ ( element as ICanvas ) . __context } ]` ;
130+ }
121131 }
122- this . logger . debug ( prefix , element , ...args ) ;
132+ this . logger . debug ( prefix , ...args ) ;
123133 }
124134
125135 private processMutation : canvasManagerMutationCallback = (
@@ -146,6 +156,8 @@ export class CanvasManager {
146156 blockClass : blockClass ,
147157 blockSelector : string | null ,
148158 options : {
159+ clearWebGLBuffer ?: boolean ;
160+ initialSnapshotDelay ?: number ;
149161 dataURLOptions : DataURLOptions ;
150162 } ,
151163 resizeFactor ?: number ,
@@ -164,7 +176,12 @@ export class CanvasManager {
164176 const { id } = e . data ;
165177 snapshotInProgressMap . set ( id , false ) ;
166178
167- if ( ! ( 'base64' in e . data ) ) return ;
179+ if ( ! ( 'base64' in e . data ) ) {
180+ this . debug ( null , 'canvas worker received empty message' , {
181+ status : e . data . status ,
182+ } ) ;
183+ return ;
184+ }
168185
169186 const { base64, type, dx, dy, dw, dh } = e . data ;
170187 this . mutationCb ( {
@@ -236,61 +253,73 @@ export class CanvasManager {
236253 }
237254 lastSnapshotTime = timestamp ;
238255
239- const promises : Promise < void > [ ] = [ ]
240- promises . push ( ...getCanvas ( ) . map ( async ( canvas : HTMLCanvasElement ) => {
241- this . debug ( canvas , 'starting snapshotting' ) ;
242- const id = this . mirror . getId ( canvas ) ;
243- if ( snapshotInProgressMap . get ( id ) ) {
244- this . debug ( canvas , 'snapshotting already in progress for' , id ) ;
245- return ;
246- }
247- snapshotInProgressMap . set ( id , true ) ;
248- try {
249- if ( [ 'webgl' , 'webgl2' ] . includes ( ( canvas as ICanvas ) . __context ) ) {
250- // if the canvas hasn't been modified recently,
251- // its contents won't be in memory and `createImageBitmap`
252- // will return a transparent imageBitmap
253-
254- const context = canvas . getContext ( ( canvas as ICanvas ) . __context ) as
255- | WebGLRenderingContext
256- | WebGL2RenderingContext
257- | null ;
256+ const promises : Promise < void > [ ] = [ ] ;
257+ promises . push (
258+ ...getCanvas ( ) . map ( async ( canvas : HTMLCanvasElement ) => {
259+ this . debug ( canvas , 'starting snapshotting' ) ;
260+ const id = this . mirror . getId ( canvas ) ;
261+ if ( snapshotInProgressMap . get ( id ) ) {
262+ this . debug ( canvas , 'snapshotting already in progress for' , id ) ;
263+ return ;
264+ }
265+ snapshotInProgressMap . set ( id , true ) ;
266+ try {
258267 if (
259- context ?. getContextAttributes ( ) ?. preserveDrawingBuffer === false
268+ options . clearWebGLBuffer !== false &&
269+ [ 'webgl' , 'webgl2' ] . includes ( ( canvas as ICanvas ) . __context )
260270 ) {
261- // Hack to load canvas back into memory so `createImageBitmap` can grab it's contents.
262- // Context: https://twitter.com/Juice10/status/1499775271758704643
263- // Preferably we set `preserveDrawingBuffer` to true, but that's not always possible,
271+ // if the canvas hasn't been modified recently,
272+ // its contents won't be in memory and `createImageBitmap`
273+ // will return a transparent imageBitmap
274+
275+ const context = canvas . getContext (
276+ ( canvas as ICanvas ) . __context ,
277+ ) as WebGLRenderingContext | WebGL2RenderingContext | null ;
278+ if (
279+ context ?. getContextAttributes ( ) ?. preserveDrawingBuffer === false
280+ ) {
281+ // Hack to load canvas back into memory so `createImageBitmap` can grab it's contents.
282+ // Context: https://twitter.com/Juice10/status/1499775271758704643
283+ // Preferably we set `preserveDrawingBuffer` to true, but that's not always possible,
264284 // especially when canvas is loaded before rrweb.
265285 // This hack can wipe the background color of the canvas in the (unlikely) event that
266- // the canvas background was changed but clear was not called directly afterwards.
286+ // the canvas background was changed but clear was not called directly afterwards.
267287 // Example of this hack having negative side effect: https://visgl.github.io/react-map-gl/examples/layers
268- context . clear ( context . COLOR_BUFFER_BIT ) ;
288+ context . clear ( context ?. COLOR_BUFFER_BIT ) ;
289+ this . debug (
290+ canvas ,
291+ 'cleared webgl canvas to load it into memory' ,
292+ { attributes : context ?. getContextAttributes ( ) } ,
293+ ) ;
294+ }
269295 }
270- }
271- // canvas is not yet ready... this retry on the next sampling iteration.
272- // we don't want to crash the worker if the canvas is not yet rendered.
273- if ( canvas . width === 0 || canvas . height === 0 ) {
274- this . debug ( canvas , 'not yet ready' , {
275- width : canvas . width ,
276- height : canvas . height ,
296+ // canvas is not yet ready... this retry on the next sampling iteration.
297+ // we don't want to crash the worker by sending an undefined bitmap
298+ // if the canvas is not yet rendered.
299+ if ( canvas . width === 0 || canvas . height === 0 ) {
300+ this . debug ( canvas , 'not yet ready' , {
301+ width : canvas . width ,
302+ height : canvas . height ,
303+ } ) ;
304+ return ;
305+ }
306+ let scale = resizeFactor || 1 ;
307+ if ( maxSnapshotDimension ) {
308+ const maxDim = Math . max ( canvas . width , canvas . height ) ;
309+ scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
310+ }
311+ const width = canvas . width * scale ;
312+ const height = canvas . height * scale ;
313+
314+ const bitmap = await createImageBitmap ( canvas , {
315+ resizeWidth : width ,
316+ resizeHeight : height ,
277317 } ) ;
278- return ;
279- }
280- let scale = resizeFactor || 1 ;
281- if ( maxSnapshotDimension ) {
282- const maxDim = Math . max ( canvas . width , canvas . height ) ;
283- scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
284- }
285- const width = canvas . width * scale ;
286- const height = canvas . height * scale ;
287-
288- const bitmap = await createImageBitmap ( canvas , {
289- resizeWidth : width ,
290- resizeHeight : height ,
291- } ) ;
292- this . debug ( canvas , 'created image bitmap' ) ;
293- worker . postMessage (
318+ this . debug ( canvas , 'created image bitmap' , {
319+ width : bitmap . width ,
320+ height : bitmap . height ,
321+ } ) ;
322+ worker . postMessage (
294323 {
295324 id,
296325 bitmap,
@@ -303,93 +332,101 @@ export class CanvasManager {
303332 dataURLOptions : options . dataURLOptions ,
304333 } ,
305334 [ bitmap ] ,
306- ) ;
307- this . debug ( canvas , 'sent message' ) ;
308- } catch ( e ) {
309- this . debug ( canvas , 'failed to snapshot' , e ) ;
310- } finally {
311- snapshotInProgressMap . set ( id , false ) ;
312- }
313- } ) )
314- promises . push ( ...getVideos ( ) . map ( async ( video : HTMLVideoElement ) => {
315- this . debug ( video , 'starting video snapshotting' ) ;
316- const id = this . mirror . getId ( video ) ;
317- if ( snapshotInProgressMap . get ( id ) ) {
318- this . debug ( video , 'video snapshotting already in progress for' , id ) ;
319- return ;
320- }
321- snapshotInProgressMap . set ( id , true ) ;
322- try {
323- const { width : boxWidth , height : boxHeight } =
324- video . getBoundingClientRect ( ) ;
325- const { actualWidth, actualHeight } = {
326- actualWidth : video . videoWidth ,
327- actualHeight : video . videoHeight ,
328- } ;
329- const maxDim = Math . max ( actualWidth , actualHeight ) ;
330- let scale = resizeFactor || 1 ;
331- if ( maxSnapshotDimension ) {
332- scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
335+ ) ;
336+ this . debug ( canvas , 'sent message' ) ;
337+ } catch ( e ) {
338+ this . debug ( canvas , 'failed to snapshot' , e ) ;
339+ } finally {
340+ snapshotInProgressMap . set ( id , false ) ;
333341 }
334- const width = actualWidth * scale ;
335- const height = actualHeight * scale ;
336-
337- const bitmap = await createImageBitmap ( video , {
338- resizeWidth : width ,
339- resizeHeight : height ,
340- } ) ;
341-
342- let outputScale = Math . max ( boxWidth , boxHeight ) / maxDim ;
343- const outputWidth = actualWidth * outputScale ;
344- const outputHeight = actualHeight * outputScale ;
345- const offsetX = ( boxWidth - outputWidth ) / 2 ;
346- const offsetY = ( boxHeight - outputHeight ) / 2 ;
347- this . debug ( video , 'created image bitmap' , {
348- actualWidth,
349- actualHeight,
350- boxWidth,
351- boxHeight,
352- outputWidth,
353- outputHeight,
354- resizeWidth : width ,
355- resizeHeight : height ,
356- scale,
357- outputScale,
358- offsetX,
359- offsetY,
360- } ) ;
361-
362- worker . postMessage (
363- {
364- id,
365- bitmap,
366- width,
367- height,
368- dx : offsetX ,
369- dy : offsetY ,
370- dw : outputWidth ,
371- dh : outputHeight ,
372- dataURLOptions : options . dataURLOptions ,
373- } ,
374- [ bitmap ] ,
375- ) ;
376- this . debug ( video , 'send message' ) ;
377- } catch ( e ) {
378- this . debug ( video , 'failed to snapshot' , e ) ;
379- } finally {
380- snapshotInProgressMap . set ( id , false ) ;
381- }
382- } ) )
383- await Promise . all ( promises )
342+ } ) ,
343+ ) ;
344+ promises . push (
345+ ...getVideos ( ) . map ( async ( video : HTMLVideoElement ) => {
346+ this . debug ( video , 'starting video snapshotting' ) ;
347+ const id = this . mirror . getId ( video ) ;
348+ if ( snapshotInProgressMap . get ( id ) ) {
349+ this . debug ( video , 'video snapshotting already in progress for' , id ) ;
350+ return ;
351+ }
352+ snapshotInProgressMap . set ( id , true ) ;
353+ try {
354+ const { width : boxWidth , height : boxHeight } =
355+ video . getBoundingClientRect ( ) ;
356+ const { actualWidth, actualHeight } = {
357+ actualWidth : video . videoWidth ,
358+ actualHeight : video . videoHeight ,
359+ } ;
360+ const maxDim = Math . max ( actualWidth , actualHeight ) ;
361+ let scale = resizeFactor || 1 ;
362+ if ( maxSnapshotDimension ) {
363+ scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
364+ }
365+ const width = actualWidth * scale ;
366+ const height = actualHeight * scale ;
367+
368+ const bitmap = await createImageBitmap ( video , {
369+ resizeWidth : width ,
370+ resizeHeight : height ,
371+ } ) ;
372+
373+ let outputScale = Math . max ( boxWidth , boxHeight ) / maxDim ;
374+ const outputWidth = actualWidth * outputScale ;
375+ const outputHeight = actualHeight * outputScale ;
376+ const offsetX = ( boxWidth - outputWidth ) / 2 ;
377+ const offsetY = ( boxHeight - outputHeight ) / 2 ;
378+ this . debug ( video , 'created image bitmap' , {
379+ actualWidth,
380+ actualHeight,
381+ boxWidth,
382+ boxHeight,
383+ outputWidth,
384+ outputHeight,
385+ resizeWidth : width ,
386+ resizeHeight : height ,
387+ scale,
388+ outputScale,
389+ offsetX,
390+ offsetY,
391+ } ) ;
392+
393+ worker . postMessage (
394+ {
395+ id,
396+ bitmap,
397+ width,
398+ height,
399+ dx : offsetX ,
400+ dy : offsetY ,
401+ dw : outputWidth ,
402+ dh : outputHeight ,
403+ dataURLOptions : options . dataURLOptions ,
404+ } ,
405+ [ bitmap ] ,
406+ ) ;
407+ this . debug ( video , 'send message' ) ;
408+ } catch ( e ) {
409+ this . debug ( video , 'failed to snapshot' , e ) ;
410+ } finally {
411+ snapshotInProgressMap . set ( id , false ) ;
412+ }
413+ } ) ,
414+ ) ;
415+ Promise . all ( promises ) . catch ( console . error ) ;
384416
385417 rafId = requestAnimationFrame ( takeSnapshots ) ;
386418 } ;
387419
388- rafId = requestAnimationFrame ( takeSnapshots ) ;
420+ const delay = setTimeout ( ( ) => {
421+ rafId = requestAnimationFrame ( takeSnapshots ) ;
422+ } , options . initialSnapshotDelay ) ;
389423
390424 this . resetObservers = ( ) => {
391425 canvasContextReset ( ) ;
392- cancelAnimationFrame ( rafId ) ;
426+ clearTimeout ( delay ) ;
427+ if ( rafId ) {
428+ cancelAnimationFrame ( rafId ) ;
429+ }
393430 } ;
394431 }
395432
0 commit comments