Skip to content

Commit 850a14b

Browse files
authored
feat: Export getCanvasManager & allow passing it to record() (#122)
This PR exports a new `getCanvasManager()` method which can be used to dynamically pass a canvas manager, allowing tree shaking. This also removes the `__RRWEB_EXCLUDE_CANVAS__` build flag - canvas will _always_ be excluded now by default. Expected usage: ```js import { record, getCanvasManager } from '@sentry-internal/canvas'; record({ // other config... getCanvasManager, }); ``` The idea is that we can expose this somehow (?) from replay, so users can do e.g.: ```js import { Replay, getReplayCanvasManager } from '@sentry/browser'; Sentry.init({ integrations: [ new Replay({ canvasManager: getReplayCanvasManager() }) ] }); ``` Or something like this, allowing people to opt-in to canvas recording at runtime, vs requiring a specific build step for it.
1 parent 88e5a85 commit 850a14b

File tree

8 files changed

+96
-32
lines changed

8 files changed

+96
-32
lines changed

.size-limit.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ module.exports = [
66
import: '{ record }',
77
gzip: true
88
},
9+
{
10+
name: 'rrweb - record & getCanvasManager only (gzipped)',
11+
path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js',
12+
import: '{ record, getCanvasManager }',
13+
gzip: true
14+
},
915
{
1016
name: 'rrweb - record only (min)',
1117
path: 'packages/rrweb/es/rrweb/packages/rrweb/src/entries/all.js',
@@ -21,7 +27,6 @@ module.exports = [
2127
const webpack = require('webpack');
2228
config.plugins.push(
2329
new webpack.DefinePlugin({
24-
__RRWEB_EXCLUDE_CANVAS__: true,
2530
__RRWEB_EXCLUDE_SHADOW_DOM__: true,
2631
__RRWEB_EXCLUDE_IFRAME__: true,
2732
}),

packages/rrweb/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import record from './record';
2+
23
import { Replayer } from './replay';
34
import * as utils from './utils';
45

@@ -20,4 +21,10 @@ export type { recordOptions } from './types';
2021

2122
export { record, Replayer, utils };
2223

23-
export { takeFullSnapshot, mirror, freezePage, addCustomEvent } from './record';
24+
export {
25+
takeFullSnapshot,
26+
mirror,
27+
freezePage,
28+
addCustomEvent,
29+
getCanvasManager,
30+
} from './record';

packages/rrweb/src/record/index.ts

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
MaskInputOptions,
44
SlimDOMOptions,
55
createMirror,
6+
DataURLOptions,
67
} from '@sentry-internal/rrweb-snapshot';
78
import { initObservers, mutationBuffers } from './observer';
89
import {
@@ -41,6 +42,7 @@ import {
4142
} from './shadow-dom-manager';
4243
import {
4344
CanvasManager,
45+
CanvasManagerConstructorOptions,
4446
CanvasManagerInterface,
4547
CanvasManagerNoop,
4648
} from './observers/canvas/canvas-manager';
@@ -59,7 +61,6 @@ function wrapEvent(e: event): eventWithTime {
5961
}
6062

6163
declare global {
62-
const __RRWEB_EXCLUDE_CANVAS__: boolean;
6364
const __RRWEB_EXCLUDE_SHADOW_DOM__: boolean;
6465
const __RRWEB_EXCLUDE_IFRAME__: boolean;
6566
}
@@ -112,6 +113,7 @@ function record<T = eventWithTime>(
112113
ignoreCSSAttributes = new Set([]),
113114
errorHandler,
114115
onMutation,
116+
getCanvasManager,
115117
} = options;
116118

117119
registerErrorHandler(errorHandler);
@@ -322,20 +324,16 @@ function record<T = eventWithTime>(
322324

323325
const processedNodeManager = new ProcessedNodeManager();
324326

325-
const canvasManager: CanvasManagerInterface =
326-
typeof __RRWEB_EXCLUDE_CANVAS__ === 'boolean' && __RRWEB_EXCLUDE_CANVAS__
327-
? new CanvasManagerNoop()
328-
: new CanvasManager({
329-
recordCanvas,
330-
mutationCb: wrappedCanvasMutationEmit,
331-
win: window,
332-
blockClass,
333-
blockSelector,
334-
unblockSelector,
335-
mirror,
336-
sampling: sampling.canvas,
337-
dataURLOptions,
338-
});
327+
const canvasManager: CanvasManagerInterface = getCanvasManager
328+
? getCanvasManager({
329+
recordCanvas,
330+
blockClass,
331+
blockSelector,
332+
unblockSelector,
333+
sampling: sampling['canvas'],
334+
dataURLOptions,
335+
})
336+
: new CanvasManagerNoop();
339337

340338
const shadowDomManager: ShadowDomManagerInterface =
341339
typeof __RRWEB_EXCLUDE_SHADOW_DOM__ === 'boolean' &&
@@ -690,6 +688,14 @@ export function takeFullSnapshot(isCheckout?: boolean) {
690688
_takeFullSnapshot(isCheckout);
691689
}
692690

691+
function wrappedEmit(e: eventWithTime) {
692+
if (!_wrappedEmit) {
693+
return;
694+
}
695+
696+
_wrappedEmit(e);
697+
}
698+
693699
// record.addCustomEvent is removed because Sentry Session Replay does not use it
694700
// record.freezePage is removed because Sentry Session Replay does not use it
695701

@@ -698,3 +704,28 @@ record.mirror = mirror;
698704
record.takeFullSnapshot = takeFullSnapshot;
699705

700706
export default record;
707+
708+
const wrappedCanvasMutationEmit = (p: canvasMutationParam) =>
709+
wrappedEmit(
710+
wrapEvent({
711+
type: EventType.IncrementalSnapshot,
712+
data: {
713+
source: IncrementalSource.CanvasMutation,
714+
...p,
715+
},
716+
}),
717+
);
718+
719+
export function getCanvasManager(
720+
options: Omit<
721+
CanvasManagerConstructorOptions,
722+
'mutationCb' | 'win' | 'mirror'
723+
>,
724+
): CanvasManagerInterface {
725+
return new CanvasManager({
726+
...options,
727+
mutationCb: wrappedCanvasMutationEmit,
728+
win: window,
729+
mirror,
730+
});
731+
}

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ export interface CanvasManagerInterface {
3636
unlock(): void;
3737
}
3838

39+
export interface CanvasManagerConstructorOptions {
40+
recordCanvas: boolean;
41+
mutationCb: canvasMutationCallback;
42+
win: IWindow;
43+
blockClass: blockClass;
44+
blockSelector: string | null;
45+
unblockSelector: string | null;
46+
mirror: Mirror;
47+
sampling?: 'all' | number;
48+
dataURLOptions: DataURLOptions;
49+
}
50+
3951
export class CanvasManagerNoop implements CanvasManagerInterface {
4052
public reset() {
4153
// noop
@@ -85,17 +97,7 @@ export class CanvasManager implements CanvasManagerInterface {
8597
this.locked = false;
8698
}
8799

88-
constructor(options: {
89-
recordCanvas: boolean;
90-
mutationCb: canvasMutationCallback;
91-
win: IWindow;
92-
blockClass: blockClass;
93-
blockSelector: string | null;
94-
unblockSelector: string | null;
95-
mirror: Mirror;
96-
sampling?: 'all' | number;
97-
dataURLOptions: DataURLOptions;
98-
}) {
100+
constructor(options: CanvasManagerConstructorOptions) {
99101
const {
100102
sampling = 'all',
101103
win,

packages/rrweb/src/types.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ import type { IframeManagerInterface } from './record/iframe-manager';
1212
import type { ShadowDomManagerInterface } from './record/shadow-dom-manager';
1313
import type { Replayer } from './replay';
1414
import type { RRNode } from '@sentry-internal/rrdom';
15-
import type { CanvasManagerInterface } from './record/observers/canvas/canvas-manager';
15+
import type {
16+
CanvasManagerConstructorOptions,
17+
CanvasManagerInterface,
18+
} from './record/observers/canvas/canvas-manager';
1619
import type { StylesheetManager } from './record/stylesheet-manager';
1720
import type {
1821
addedNodeMutation,
@@ -80,6 +83,12 @@ export type recordOptions<T> = {
8083
keepIframeSrcFn?: KeepIframeSrcFn;
8184
errorHandler?: ErrorHandler;
8285
onMutation?: (mutations: MutationRecord[]) => boolean;
86+
getCanvasManager?: (
87+
options: Omit<
88+
CanvasManagerConstructorOptions,
89+
'mutationCb' | 'win' | 'mirror'
90+
>,
91+
) => CanvasManagerInterface;
8392
};
8493

8594
export type observerParam = {

packages/rrweb/test/record/cross-origin-iframes.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from '../utils';
1818
import { unpack } from '../../src/packer/unpack';
1919
import type * as http from 'http';
20+
import type { CanvasManagerInterface } from '../../src/record/observers/canvas/canvas-manager';
2021

2122
interface ISuite {
2223
code: string;
@@ -34,6 +35,7 @@ interface IWindow extends Window {
3435
) => listenerHandler | undefined;
3536
addCustomEvent<T>(tag: string, payload: T): void;
3637
pack: (e: eventWithTime) => string;
38+
getCanvasManager: () => CanvasManagerInterface;
3739
};
3840
emit: (e: eventWithTime) => undefined;
3941
snapshots: eventWithTime[];
@@ -52,10 +54,12 @@ async function injectRecordScript(
5254
options = options || {};
5355
await frame.evaluate((options) => {
5456
(window as unknown as IWindow).snapshots = [];
55-
const { record, pack } = (window as unknown as IWindow).rrweb;
57+
const { record, pack, getCanvasManager } = (window as unknown as IWindow)
58+
.rrweb;
5659
const config: recordOptions<eventWithTime> = {
5760
recordCrossOriginIframes: true,
5861
recordCanvas: true,
62+
getCanvasManager,
5963
emit(event) {
6064
(window as unknown as IWindow).snapshots.push(event);
6165
(window as unknown as IWindow).emit(event);

packages/rrweb/test/record/webgl.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
waitForRAF,
1717
} from '../utils';
1818
import type { ICanvas } from '@sentry-internal/rrweb-snapshot';
19+
import type { CanvasManagerInterface } from '../../src/record/observers/canvas/canvas-manager';
1920

2021
interface ISuite {
2122
code: string;
@@ -30,6 +31,7 @@ interface IWindow extends Window {
3031
options: recordOptions<eventWithTime>,
3132
) => listenerHandler | undefined;
3233
addCustomEvent<T>(tag: string, payload: T): void;
34+
getCanvasManager: () => CanvasManagerInterface;
3335
};
3436
emit: (e: eventWithTime) => undefined;
3537
}
@@ -64,9 +66,10 @@ const setup = function (
6466
ctx.page.on('console', (msg) => console.log('PAGE LOG:', msg.text()));
6567

6668
await ctx.page.evaluate((canvasSample) => {
67-
const { record } = (window as unknown as IWindow).rrweb;
69+
const { record, getCanvasManager } = (window as unknown as IWindow).rrweb;
6870
record({
6971
recordCanvas: true,
72+
getCanvasManager,
7073
sampling: {
7174
canvas: canvasSample,
7275
},

packages/rrweb/test/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,10 @@ export function generateRecordSnippet(options: recordOptions<eventWithTime>) {
710710
recordCanvas: ${options.recordCanvas},
711711
recordAfter: '${options.recordAfter || 'load'}',
712712
inlineImages: ${options.inlineImages},
713-
plugins: ${options.plugins}
713+
plugins: ${options.plugins},
714+
getCanvasManager: ${
715+
options.recordCanvas ? 'rrweb.getCanvasManager' : 'undefined'
716+
}
714717
});
715718
`;
716719
}

0 commit comments

Comments
 (0)