deserializeCanvasArgs.ts 3.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /**
  2. * Taken from https://github.com/rrweb-io/rrweb/blob/master/packages/rrweb/src/replay/canvas/deserialize-args.ts
  3. * Modified to limit support to only canvas snapshots (rather than draw commands)
  4. */
  5. import type {Replayer} from '@sentry-internal/rrweb';
  6. import type {CanvasArg} from '@sentry-internal/rrweb-types';
  7. import {decode} from 'base64-arraybuffer';
  8. // TODO: add ability to wipe this list
  9. type GLVarMap = Map<string, any[]>;
  10. type CanvasContexts =
  11. | CanvasRenderingContext2D
  12. | WebGLRenderingContext
  13. | WebGL2RenderingContext;
  14. const webGLVarMap: Map<CanvasContexts, GLVarMap> = new Map();
  15. function variableListFor(ctx: CanvasContexts, ctor: string) {
  16. let contextMap = webGLVarMap.get(ctx);
  17. if (!contextMap) {
  18. contextMap = new Map();
  19. webGLVarMap.set(ctx, contextMap);
  20. }
  21. if (!contextMap.has(ctor)) {
  22. contextMap.set(ctor, []);
  23. }
  24. // eslint-disable-next-line @typescript-eslint/no-unsafe-return
  25. return contextMap.get(ctor) as any[];
  26. }
  27. export function deserializeCanvasArg(
  28. imageMap: Replayer['imageMap'],
  29. ctx: CanvasContexts | null,
  30. preload?: {
  31. isUnchanged: boolean;
  32. }
  33. ): (arg: CanvasArg) => Promise<any> {
  34. return async (arg: CanvasArg): Promise<any> => {
  35. if (arg && typeof arg === 'object' && 'rr_type' in arg) {
  36. if (preload) {
  37. preload.isUnchanged = false;
  38. }
  39. if (arg.rr_type === 'ImageBitmap' && 'args' in arg) {
  40. const args = await deserializeCanvasArg(imageMap, ctx, preload)(arg.args);
  41. return await createImageBitmap.apply(null, args);
  42. }
  43. if ('index' in arg) {
  44. if (preload || ctx === null) {
  45. return arg;
  46. } // we are preloading, ctx is unknown
  47. const {rr_type: name, index} = arg;
  48. return variableListFor(ctx, name)[index];
  49. }
  50. if ('args' in arg) {
  51. // XXX: This differs from rrweb, we only support snapshots for now, so
  52. // this shouldn't be necessary
  53. // const {rr_type: name, args} = arg;
  54. //
  55. // const ctor = window[name as keyof Window];
  56. //
  57. // return new ctor(
  58. // ...(await Promise.all(args.map(deserializeCanvasArg(imageMap, ctx, preload))))
  59. // );
  60. return arg;
  61. }
  62. if ('base64' in arg) {
  63. return decode(arg.base64);
  64. }
  65. if ('src' in arg) {
  66. // XXX: Likewise, snapshots means there will be no need for image support
  67. // const image = imageMap.get(arg.src);
  68. // if (image) {
  69. // return image;
  70. // }
  71. // const newImage = new Image();
  72. // newImage.src = arg.src;
  73. // imageMap.set(arg.src, newImage);
  74. // return newImage;
  75. return arg;
  76. }
  77. if ('data' in arg && arg.rr_type === 'Blob') {
  78. const blobContents = await Promise.all(
  79. arg.data.map(deserializeCanvasArg(imageMap, ctx, preload))
  80. );
  81. const blob = new Blob(blobContents, {
  82. type: arg.type,
  83. });
  84. return blob;
  85. }
  86. } else if (Array.isArray(arg)) {
  87. const result = await Promise.all(
  88. arg.map(deserializeCanvasArg(imageMap, ctx, preload))
  89. );
  90. return result;
  91. }
  92. return arg;
  93. };
  94. }