@@ -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 ,
@@ -163,7 +175,12 @@ export class CanvasManager {
163175 const { id } = e . data ;
164176 snapshotInProgressMap . set ( id , false ) ;
165177
166- if ( ! ( 'base64' in e . data ) ) return ;
178+ if ( ! ( 'base64' in e . data ) ) {
179+ this . debug ( null , 'canvas worker received empty message' , {
180+ status : e . data . status ,
181+ } ) ;
182+ return ;
183+ }
167184
168185 const { base64, type, dx, dy, dw, dh } = e . data ;
169186 this . mutationCb ( {
@@ -235,58 +252,70 @@ export class CanvasManager {
235252 }
236253 lastSnapshotTime = timestamp ;
237254
238- const promises : Promise < void > [ ] = [ ]
239- promises . push ( ...getCanvas ( ) . map ( async ( canvas : HTMLCanvasElement ) => {
240- this . debug ( canvas , 'starting snapshotting' ) ;
241- const id = this . mirror . getId ( canvas ) ;
242- if ( snapshotInProgressMap . get ( id ) ) {
243- this . debug ( canvas , 'snapshotting already in progress for' , id ) ;
244- return ;
245- }
246- snapshotInProgressMap . set ( id , true ) ;
247- try {
248- if ( [ 'webgl' , 'webgl2' ] . includes ( ( canvas as ICanvas ) . __context ) ) {
249- // if the canvas hasn't been modified recently,
250- // its contents won't be in memory and `createImageBitmap`
251- // will return a transparent imageBitmap
252-
253- const context = canvas . getContext ( ( canvas as ICanvas ) . __context ) as
254- | WebGLRenderingContext
255- | WebGL2RenderingContext
256- | null ;
255+ const promises : Promise < void > [ ] = [ ] ;
256+ promises . push (
257+ ...getCanvas ( ) . map ( async ( canvas : HTMLCanvasElement ) => {
258+ this . debug ( canvas , 'starting snapshotting' ) ;
259+ const id = this . mirror . getId ( canvas ) ;
260+ if ( snapshotInProgressMap . get ( id ) ) {
261+ this . debug ( canvas , 'snapshotting already in progress for' , id ) ;
262+ return ;
263+ }
264+ snapshotInProgressMap . set ( id , true ) ;
265+ try {
257266 if (
258- context ?. getContextAttributes ( ) ?. preserveDrawingBuffer === false
267+ options . clearWebGLBuffer !== false &&
268+ [ 'webgl' , 'webgl2' ] . includes ( ( canvas as ICanvas ) . __context )
259269 ) {
260- // Hack to load canvas back into memory so `createImageBitmap` can grab it's contents.
261- // Context: https://twitter.com/Juice10/status/1499775271758704643
262- // This hack might change the background color of the canvas in the unlikely event that
263- // the canvas background was changed but clear was not called directly afterwards.
264- context ?. clear ( context . COLOR_BUFFER_BIT ) ;
270+ // if the canvas hasn't been modified recently,
271+ // its contents won't be in memory and `createImageBitmap`
272+ // will return a transparent imageBitmap
273+
274+ const context = canvas . getContext (
275+ ( canvas as ICanvas ) . __context ,
276+ ) as WebGLRenderingContext | WebGL2RenderingContext | null ;
277+ if (
278+ context ?. getContextAttributes ( ) ?. preserveDrawingBuffer === false
279+ ) {
280+ // Hack to load canvas back into memory so `createImageBitmap` can grab it's contents.
281+ // Context: https://twitter.com/Juice10/status/1499775271758704643
282+ // This hack might change the background color of the canvas in the unlikely event that
283+ // the canvas background was changed but clear was not called directly afterwards.
284+ context ?. clear ( context ?. COLOR_BUFFER_BIT ) ;
285+ this . debug (
286+ canvas ,
287+ 'cleared webgl canvas to load it into memory' ,
288+ { attributes : context ?. getContextAttributes ( ) } ,
289+ ) ;
290+ }
265291 }
266- }
267- // canvas is not yet ready... this retry on the next sampling iteration.
268- // we don't want to crash the worker if the canvas is not yet rendered.
269- if ( canvas . width === 0 || canvas . height === 0 ) {
270- this . debug ( canvas , 'not yet ready' , {
271- width : canvas . width ,
272- height : canvas . height ,
292+ // canvas is not yet ready... this retry on the next sampling iteration.
293+ // we don't want to crash the worker by sending an undefined bitmap
294+ // if the canvas is not yet rendered.
295+ if ( canvas . width === 0 || canvas . height === 0 ) {
296+ this . debug ( canvas , 'not yet ready' , {
297+ width : canvas . width ,
298+ height : canvas . height ,
299+ } ) ;
300+ return ;
301+ }
302+ let scale = resizeFactor || 1 ;
303+ if ( maxSnapshotDimension ) {
304+ const maxDim = Math . max ( canvas . width , canvas . height ) ;
305+ scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
306+ }
307+ const width = canvas . width * scale ;
308+ const height = canvas . height * scale ;
309+
310+ const bitmap = await createImageBitmap ( canvas , {
311+ resizeWidth : width ,
312+ resizeHeight : height ,
273313 } ) ;
274- return ;
275- }
276- let scale = resizeFactor || 1 ;
277- if ( maxSnapshotDimension ) {
278- const maxDim = Math . max ( canvas . width , canvas . height ) ;
279- scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
280- }
281- const width = canvas . width * scale ;
282- const height = canvas . height * scale ;
283-
284- const bitmap = await createImageBitmap ( canvas , {
285- resizeWidth : width ,
286- resizeHeight : height ,
287- } ) ;
288- this . debug ( canvas , 'created image bitmap' ) ;
289- worker . postMessage (
314+ this . debug ( canvas , 'created image bitmap' , {
315+ width : bitmap . width ,
316+ height : bitmap . height ,
317+ } ) ;
318+ worker . postMessage (
290319 {
291320 id,
292321 bitmap,
@@ -299,93 +328,101 @@ export class CanvasManager {
299328 dataURLOptions : options . dataURLOptions ,
300329 } ,
301330 [ bitmap ] ,
302- ) ;
303- this . debug ( canvas , 'sent message' ) ;
304- } catch ( e ) {
305- this . debug ( canvas , 'failed to snapshot' , e ) ;
306- } finally {
307- snapshotInProgressMap . set ( id , false ) ;
308- }
309- } ) )
310- promises . push ( ...getVideos ( ) . map ( async ( video : HTMLVideoElement ) => {
311- this . debug ( video , 'starting video snapshotting' ) ;
312- const id = this . mirror . getId ( video ) ;
313- if ( snapshotInProgressMap . get ( id ) ) {
314- this . debug ( video , 'video snapshotting already in progress for' , id ) ;
315- return ;
316- }
317- snapshotInProgressMap . set ( id , true ) ;
318- try {
319- const { width : boxWidth , height : boxHeight } =
320- video . getBoundingClientRect ( ) ;
321- const { actualWidth, actualHeight } = {
322- actualWidth : video . videoWidth ,
323- actualHeight : video . videoHeight ,
324- } ;
325- const maxDim = Math . max ( actualWidth , actualHeight ) ;
326- let scale = resizeFactor || 1 ;
327- if ( maxSnapshotDimension ) {
328- scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
331+ ) ;
332+ this . debug ( canvas , 'sent message' ) ;
333+ } catch ( e ) {
334+ this . debug ( canvas , 'failed to snapshot' , e ) ;
335+ } finally {
336+ snapshotInProgressMap . set ( id , false ) ;
329337 }
330- const width = actualWidth * scale ;
331- const height = actualHeight * scale ;
332-
333- const bitmap = await createImageBitmap ( video , {
334- resizeWidth : width ,
335- resizeHeight : height ,
336- } ) ;
337-
338- let outputScale = Math . max ( boxWidth , boxHeight ) / maxDim ;
339- const outputWidth = actualWidth * outputScale ;
340- const outputHeight = actualHeight * outputScale ;
341- const offsetX = ( boxWidth - outputWidth ) / 2 ;
342- const offsetY = ( boxHeight - outputHeight ) / 2 ;
343- this . debug ( video , 'created image bitmap' , {
344- actualWidth,
345- actualHeight,
346- boxWidth,
347- boxHeight,
348- outputWidth,
349- outputHeight,
350- resizeWidth : width ,
351- resizeHeight : height ,
352- scale,
353- outputScale,
354- offsetX,
355- offsetY,
356- } ) ;
357-
358- worker . postMessage (
359- {
360- id,
361- bitmap,
362- width,
363- height,
364- dx : offsetX ,
365- dy : offsetY ,
366- dw : outputWidth ,
367- dh : outputHeight ,
368- dataURLOptions : options . dataURLOptions ,
369- } ,
370- [ bitmap ] ,
371- ) ;
372- this . debug ( video , 'send message' ) ;
373- } catch ( e ) {
374- this . debug ( video , 'failed to snapshot' , e ) ;
375- } finally {
376- snapshotInProgressMap . set ( id , false ) ;
377- }
378- } ) )
379- await Promise . all ( promises )
338+ } ) ,
339+ ) ;
340+ promises . push (
341+ ...getVideos ( ) . map ( async ( video : HTMLVideoElement ) => {
342+ this . debug ( video , 'starting video snapshotting' ) ;
343+ const id = this . mirror . getId ( video ) ;
344+ if ( snapshotInProgressMap . get ( id ) ) {
345+ this . debug ( video , 'video snapshotting already in progress for' , id ) ;
346+ return ;
347+ }
348+ snapshotInProgressMap . set ( id , true ) ;
349+ try {
350+ const { width : boxWidth , height : boxHeight } =
351+ video . getBoundingClientRect ( ) ;
352+ const { actualWidth, actualHeight } = {
353+ actualWidth : video . videoWidth ,
354+ actualHeight : video . videoHeight ,
355+ } ;
356+ const maxDim = Math . max ( actualWidth , actualHeight ) ;
357+ let scale = resizeFactor || 1 ;
358+ if ( maxSnapshotDimension ) {
359+ scale = Math . min ( scale , maxSnapshotDimension / maxDim ) ;
360+ }
361+ const width = actualWidth * scale ;
362+ const height = actualHeight * scale ;
363+
364+ const bitmap = await createImageBitmap ( video , {
365+ resizeWidth : width ,
366+ resizeHeight : height ,
367+ } ) ;
368+
369+ let outputScale = Math . max ( boxWidth , boxHeight ) / maxDim ;
370+ const outputWidth = actualWidth * outputScale ;
371+ const outputHeight = actualHeight * outputScale ;
372+ const offsetX = ( boxWidth - outputWidth ) / 2 ;
373+ const offsetY = ( boxHeight - outputHeight ) / 2 ;
374+ this . debug ( video , 'created image bitmap' , {
375+ actualWidth,
376+ actualHeight,
377+ boxWidth,
378+ boxHeight,
379+ outputWidth,
380+ outputHeight,
381+ resizeWidth : width ,
382+ resizeHeight : height ,
383+ scale,
384+ outputScale,
385+ offsetX,
386+ offsetY,
387+ } ) ;
388+
389+ worker . postMessage (
390+ {
391+ id,
392+ bitmap,
393+ width,
394+ height,
395+ dx : offsetX ,
396+ dy : offsetY ,
397+ dw : outputWidth ,
398+ dh : outputHeight ,
399+ dataURLOptions : options . dataURLOptions ,
400+ } ,
401+ [ bitmap ] ,
402+ ) ;
403+ this . debug ( video , 'send message' ) ;
404+ } catch ( e ) {
405+ this . debug ( video , 'failed to snapshot' , e ) ;
406+ } finally {
407+ snapshotInProgressMap . set ( id , false ) ;
408+ }
409+ } ) ,
410+ ) ;
411+ Promise . all ( promises ) . catch ( console . error ) ;
380412
381413 rafId = requestAnimationFrame ( takeSnapshots ) ;
382414 } ;
383415
384- rafId = requestAnimationFrame ( takeSnapshots ) ;
416+ const delay = setTimeout ( ( ) => {
417+ rafId = requestAnimationFrame ( takeSnapshots ) ;
418+ } , options . initialSnapshotDelay ) ;
385419
386420 this . resetObservers = ( ) => {
387421 canvasContextReset ( ) ;
388- cancelAnimationFrame ( rafId ) ;
422+ clearTimeout ( delay ) ;
423+ if ( rafId ) {
424+ cancelAnimationFrame ( rafId ) ;
425+ }
389426 } ;
390427 }
391428
0 commit comments