diff --git a/.size-limit.js b/.size-limit.js index 49183d09ea..4ae0dc49d8 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -6,6 +6,12 @@ module.exports = [ import: '{ record }', gzip: true }, + { + name: 'rrweb - record & getCanvasManager only (gzipped)', + path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', + import: '{ record, getCanvasManager }', + gzip: true + }, { name: 'rrweb - record only (min)', path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js', @@ -21,7 +27,6 @@ module.exports = [ const webpack = require('webpack'); config.plugins.push( new webpack.DefinePlugin({ - __RRWEB_EXCLUDE_CANVAS__: true, __RRWEB_EXCLUDE_SHADOW_DOM__: true, __RRWEB_EXCLUDE_IFRAME__: true, }), diff --git a/packages/rrweb/src/index.ts b/packages/rrweb/src/index.ts index b30f7a1b4c..ab96a595c9 100644 --- a/packages/rrweb/src/index.ts +++ b/packages/rrweb/src/index.ts @@ -1,4 +1,5 @@ import record from './record'; + import { Replayer } from './replay'; import * as utils from './utils'; @@ -20,4 +21,10 @@ export type { recordOptions } from './types'; export { record, Replayer, utils }; -export { takeFullSnapshot, mirror, freezePage, addCustomEvent } from './record'; +export { + takeFullSnapshot, + mirror, + freezePage, + addCustomEvent, + getCanvasManager, +} from './record'; diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts index de0ed6be76..cbd2bbaad1 100644 --- a/packages/rrweb/src/record/index.ts +++ b/packages/rrweb/src/record/index.ts @@ -3,6 +3,7 @@ import { MaskInputOptions, SlimDOMOptions, createMirror, + DataURLOptions, } from '@sentry-internal/rrweb-snapshot'; import { initObservers, mutationBuffers } from './observer'; import { @@ -41,6 +42,7 @@ import { } from './shadow-dom-manager'; import { CanvasManager, + CanvasManagerConstructorOptions, CanvasManagerInterface, CanvasManagerNoop, } from './observers/canvas/canvas-manager'; @@ -59,7 +61,6 @@ function wrapEvent(e: event): eventWithTime { } declare global { - const __RRWEB_EXCLUDE_CANVAS__: boolean; const __RRWEB_EXCLUDE_SHADOW_DOM__: boolean; const __RRWEB_EXCLUDE_IFRAME__: boolean; } @@ -112,6 +113,7 @@ function record( ignoreCSSAttributes = new Set([]), errorHandler, onMutation, + getCanvasManager, } = options; registerErrorHandler(errorHandler); @@ -322,20 +324,16 @@ function record( const processedNodeManager = new ProcessedNodeManager(); - const canvasManager: CanvasManagerInterface = - typeof __RRWEB_EXCLUDE_CANVAS__ === 'boolean' && __RRWEB_EXCLUDE_CANVAS__ - ? new CanvasManagerNoop() - : new CanvasManager({ - recordCanvas, - mutationCb: wrappedCanvasMutationEmit, - win: window, - blockClass, - blockSelector, - unblockSelector, - mirror, - sampling: sampling.canvas, - dataURLOptions, - }); + const canvasManager: CanvasManagerInterface = getCanvasManager + ? getCanvasManager({ + recordCanvas, + blockClass, + blockSelector, + unblockSelector, + sampling: sampling['canvas'], + dataURLOptions, + }) + : new CanvasManagerNoop(); const shadowDomManager: ShadowDomManagerInterface = typeof __RRWEB_EXCLUDE_SHADOW_DOM__ === 'boolean' && @@ -687,6 +685,14 @@ export function takeFullSnapshot(isCheckout?: boolean) { _takeFullSnapshot(isCheckout); } +function wrappedEmit(e: eventWithTime) { + if (!_wrappedEmit) { + return; + } + + _wrappedEmit(e); +} + // record.addCustomEvent is removed because Sentry Session Replay does not use it // record.freezePage is removed because Sentry Session Replay does not use it @@ -695,3 +701,28 @@ record.mirror = mirror; record.takeFullSnapshot = takeFullSnapshot; export default record; + +const wrappedCanvasMutationEmit = (p: canvasMutationParam) => + wrappedEmit( + wrapEvent({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.CanvasMutation, + ...p, + }, + }), + ); + +export function getCanvasManager( + options: Omit< + CanvasManagerConstructorOptions, + 'mutationCb' | 'win' | 'mirror' + >, +): CanvasManagerInterface { + return new CanvasManager({ + ...options, + mutationCb: wrappedCanvasMutationEmit, + win: window, + mirror, + }); +} diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts index 93dcd9e674..beea54b12f 100644 --- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts +++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts @@ -36,6 +36,18 @@ export interface CanvasManagerInterface { unlock(): void; } +export interface CanvasManagerConstructorOptions { + recordCanvas: boolean; + mutationCb: canvasMutationCallback; + win: IWindow; + blockClass: blockClass; + blockSelector: string | null; + unblockSelector: string | null; + mirror: Mirror; + sampling?: 'all' | number; + dataURLOptions: DataURLOptions; +} + export class CanvasManagerNoop implements CanvasManagerInterface { public reset() { // noop @@ -85,17 +97,7 @@ export class CanvasManager implements CanvasManagerInterface { this.locked = false; } - constructor(options: { - recordCanvas: boolean; - mutationCb: canvasMutationCallback; - win: IWindow; - blockClass: blockClass; - blockSelector: string | null; - unblockSelector: string | null; - mirror: Mirror; - sampling?: 'all' | number; - dataURLOptions: DataURLOptions; - }) { + constructor(options: CanvasManagerConstructorOptions) { const { sampling = 'all', win, diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts index 883fd8308e..fad5a2b77b 100644 --- a/packages/rrweb/src/types.ts +++ b/packages/rrweb/src/types.ts @@ -12,7 +12,10 @@ import type { IframeManagerInterface } from './record/iframe-manager'; import type { ShadowDomManagerInterface } from './record/shadow-dom-manager'; import type { Replayer } from './replay'; import type { RRNode } from '@sentry-internal/rrdom'; -import type { CanvasManagerInterface } from './record/observers/canvas/canvas-manager'; +import type { + CanvasManagerConstructorOptions, + CanvasManagerInterface, +} from './record/observers/canvas/canvas-manager'; import type { StylesheetManager } from './record/stylesheet-manager'; import type { addedNodeMutation, @@ -80,6 +83,12 @@ export type recordOptions = { keepIframeSrcFn?: KeepIframeSrcFn; errorHandler?: ErrorHandler; onMutation?: (mutations: MutationRecord[]) => boolean; + getCanvasManager?: ( + options: Omit< + CanvasManagerConstructorOptions, + 'mutationCb' | 'win' | 'mirror' + >, + ) => CanvasManagerInterface; }; export type observerParam = { diff --git a/packages/rrweb/test/record/cross-origin-iframes.test.ts b/packages/rrweb/test/record/cross-origin-iframes.test.ts index 0055a014b8..c48bd78368 100644 --- a/packages/rrweb/test/record/cross-origin-iframes.test.ts +++ b/packages/rrweb/test/record/cross-origin-iframes.test.ts @@ -17,6 +17,7 @@ import { } from '../utils'; import { unpack } from '../../src/packer/unpack'; import type * as http from 'http'; +import type { CanvasManagerInterface } from '../../src/record/observers/canvas/canvas-manager'; interface ISuite { code: string; @@ -34,6 +35,7 @@ interface IWindow extends Window { ) => listenerHandler | undefined; addCustomEvent(tag: string, payload: T): void; pack: (e: eventWithTime) => string; + getCanvasManager: () => CanvasManagerInterface; }; emit: (e: eventWithTime) => undefined; snapshots: eventWithTime[]; @@ -52,10 +54,12 @@ async function injectRecordScript( options = options || {}; await frame.evaluate((options) => { (window as unknown as IWindow).snapshots = []; - const { record, pack } = (window as unknown as IWindow).rrweb; + const { record, pack, getCanvasManager } = (window as unknown as IWindow) + .rrweb; const config: recordOptions = { recordCrossOriginIframes: true, recordCanvas: true, + getCanvasManager, emit(event) { (window as unknown as IWindow).snapshots.push(event); (window as unknown as IWindow).emit(event); diff --git a/packages/rrweb/test/record/webgl.test.ts b/packages/rrweb/test/record/webgl.test.ts index 867a5f1083..d189e74256 100644 --- a/packages/rrweb/test/record/webgl.test.ts +++ b/packages/rrweb/test/record/webgl.test.ts @@ -16,6 +16,7 @@ import { waitForRAF, } from '../utils'; import type { ICanvas } from '@sentry-internal/rrweb-snapshot'; +import type { CanvasManagerInterface } from '../../src/record/observers/canvas/canvas-manager'; interface ISuite { code: string; @@ -30,6 +31,7 @@ interface IWindow extends Window { options: recordOptions, ) => listenerHandler | undefined; addCustomEvent(tag: string, payload: T): void; + getCanvasManager: () => CanvasManagerInterface; }; emit: (e: eventWithTime) => undefined; } @@ -64,9 +66,10 @@ const setup = function ( ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text())); await ctx.page.evaluate((canvasSample) => { - const { record } = (window as unknown as IWindow).rrweb; + const { record, getCanvasManager } = (window as unknown as IWindow).rrweb; record({ recordCanvas: true, + getCanvasManager, sampling: { canvas: canvasSample, }, diff --git a/packages/rrweb/test/utils.ts b/packages/rrweb/test/utils.ts index fc2485bb05..28e965834c 100644 --- a/packages/rrweb/test/utils.ts +++ b/packages/rrweb/test/utils.ts @@ -710,7 +710,10 @@ export function generateRecordSnippet(options: recordOptions) { recordCanvas: ${options.recordCanvas}, recordAfter: '${options.recordAfter || 'load'}', inlineImages: ${options.inlineImages}, - plugins: ${options.plugins} + plugins: ${options.plugins}, + getCanvasManager: ${ + options.recordCanvas ? 'rrweb.getCanvasManager' : 'undefined' + } }); `; }