diff --git a/.changeset/silver-pianos-attack.md b/.changeset/silver-pianos-attack.md
new file mode 100644
index 0000000000..594e32c84b
--- /dev/null
+++ b/.changeset/silver-pianos-attack.md
@@ -0,0 +1,7 @@
+---
+"rrweb-snapshot": patch
+"rrweb": patch
+"@rrweb/types": patch
+---
+
+Adds `blockElementFN` to record options to allow consumer finer-grained element blocking.
diff --git a/guide.md b/guide.md
index 764e359fb4..acd3557d23 100644
--- a/guide.md
+++ b/guide.md
@@ -137,7 +137,8 @@ The parameter of `rrweb.record` accepts the following options.
| checkoutEveryNth | - | take a full snapshot after every N events
refer to the [checkout](#checkout) chapter |
| checkoutEveryNms | - | take a full snapshot after every N ms
refer to the [checkout](#checkout) chapter |
| blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) chapter |
-| blockSelector | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
+| blockElementFN | null | Use a string to configure which selector should be blocked, refer to the [privacy](#privacy) chapter |
+| blockSelector | null | Manually determine what element should be blocked, refer to the [privacy](#privacy) chapter |
| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
| ignoreSelector | null | Use a string to configure which selector should be ignored, refer to the [privacy](#privacy) chapter |
| ignoreCSSAttributes | null | array of CSS attributes that should be ignored |
@@ -171,6 +172,7 @@ You may find some contents on the webpage which are not willing to be recorded,
- All text of elements with the class name `.rr-mask` and their children will be masked.
- `input[type="password"]` will be masked by default.
- Mask options to mask the content in input elements.
+- `blockElementFN` takes precedence over `blockSelector` and `blockClass`, this callback function is passed a single argument: the elment to block.
#### Checkout
diff --git a/packages/rrweb-snapshot/src/snapshot.ts b/packages/rrweb-snapshot/src/snapshot.ts
index 0e8ec68be9..42a26daa0b 100644
--- a/packages/rrweb-snapshot/src/snapshot.ts
+++ b/packages/rrweb-snapshot/src/snapshot.ts
@@ -16,6 +16,7 @@ import type {
attributes,
mediaAttributes,
DataURLOptions,
+ BlockElementFn,
} from '@rrweb/types';
import {
Mirror,
@@ -219,8 +220,12 @@ export function _isBlockedElement(
element: HTMLElement,
blockClass: string | RegExp,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
): boolean {
try {
+ if (blockElementFn) {
+ return blockElementFn(element);
+ }
if (typeof blockClass === 'string') {
if (element.classList.contains(blockClass)) {
return true;
@@ -392,6 +397,7 @@ function serializeNode(
doc: Document;
mirror: Mirror;
blockClass: string | RegExp;
+ blockElementFn: BlockElementFn | null;
blockSelector: string | null;
needsMask: boolean;
inlineStylesheet: boolean;
@@ -413,6 +419,7 @@ function serializeNode(
doc,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
needsMask,
inlineStylesheet,
@@ -454,6 +461,7 @@ function serializeNode(
return serializeElementNode(n as HTMLElement, {
doc,
blockClass,
+ blockElementFn,
blockSelector,
inlineStylesheet,
maskInputOptions,
@@ -544,6 +552,7 @@ function serializeElementNode(
options: {
doc: Document;
blockClass: string | RegExp;
+ blockElementFn: BlockElementFn | null;
blockSelector: string | null;
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
@@ -562,6 +571,7 @@ function serializeElementNode(
const {
doc,
blockClass,
+ blockElementFn,
blockSelector,
inlineStylesheet,
maskInputOptions = {},
@@ -573,7 +583,12 @@ function serializeElementNode(
newlyAddedElement = false,
rootId,
} = options;
- const needBlock = _isBlockedElement(n, blockClass, blockSelector);
+ const needBlock = _isBlockedElement(
+ n,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ );
const tagName = getValidTagName(n);
let attributes: attributes = {};
const len = n.attributes.length;
@@ -903,6 +918,7 @@ export function serializeNodeWithId(
doc: Document;
mirror: Mirror;
blockClass: string | RegExp;
+ blockElementFn: BlockElementFn | null;
blockSelector: string | null;
maskTextClass: string | RegExp;
maskTextSelector: string | null;
@@ -937,6 +953,7 @@ export function serializeNodeWithId(
doc,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
@@ -976,6 +993,7 @@ export function serializeNodeWithId(
doc,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
needsMask,
inlineStylesheet,
@@ -1047,6 +1065,7 @@ export function serializeNodeWithId(
doc,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
needsMask,
maskTextClass,
@@ -1123,6 +1142,7 @@ export function serializeNodeWithId(
doc: iframeDoc,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
needsMask,
maskTextClass,
@@ -1175,6 +1195,7 @@ export function serializeNodeWithId(
doc,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
needsMask,
maskTextClass,
@@ -1217,6 +1238,7 @@ function snapshot(
options?: {
mirror?: Mirror;
blockClass?: string | RegExp;
+ blockElementFn?: BlockElementFn | null;
blockSelector?: string | null;
maskTextClass?: string | RegExp;
maskTextSelector?: string | null;
@@ -1246,6 +1268,7 @@ function snapshot(
const {
mirror = new Mirror(),
blockClass = 'rr-block',
+ blockElementFn = null,
blockSelector = null,
maskTextClass = 'rr-mask',
maskTextSelector = null,
@@ -1312,6 +1335,7 @@ function snapshot(
doc: n,
mirror,
blockClass,
+ blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
diff --git a/packages/rrweb-snapshot/test/snapshot.test.ts b/packages/rrweb-snapshot/test/snapshot.test.ts
index 5778eb0aff..7a2ce70606 100644
--- a/packages/rrweb-snapshot/test/snapshot.test.ts
+++ b/packages/rrweb-snapshot/test/snapshot.test.ts
@@ -16,6 +16,7 @@ const serializeNode = (node: Node): serializedNodeWithId | null => {
doc: document,
mirror: new Mirror(),
blockClass: 'blockblock',
+ blockElementFn: null,
blockSelector: null,
maskTextClass: 'maskmask',
maskTextSelector: null,
@@ -129,7 +130,12 @@ describe('absolute url to stylesheet', () => {
describe('isBlockedElement()', () => {
const subject = (html: string, opt: any = {}) =>
- _isBlockedElement(render(html), 'rr-block', opt.blockSelector);
+ _isBlockedElement(
+ render(html),
+ 'rr-block',
+ opt.blockSelector,
+ opt.blockElementFn,
+ );
const render = (html: string): HTMLElement =>
JSDOM.fragment(html).querySelector('div')!;
@@ -151,6 +157,14 @@ describe('isBlockedElement()', () => {
subject('
', { blockSelector: '[data-rr-block]' }),
).toEqual(true);
});
+
+ it('blocks with blockElementFn', () => {
+ expect(
+ subject('', {
+ blockElementFn: (e: HTMLElement) => e.matches('div.special'),
+ }),
+ ).toEqual(true);
+ });
});
describe('style elements', () => {
diff --git a/packages/rrweb/src/record/index.ts b/packages/rrweb/src/record/index.ts
index 1308c378a6..bf5560438a 100644
--- a/packages/rrweb/src/record/index.ts
+++ b/packages/rrweb/src/record/index.ts
@@ -70,6 +70,7 @@ function record(
checkoutEveryNms,
checkoutEveryNth,
blockClass = 'rr-block',
+ blockElementFn = null,
blockSelector = null,
ignoreClass = 'rr-ignore',
ignoreSelector = null,
@@ -321,6 +322,7 @@ function record(
mutationCb: wrappedCanvasMutationEmit,
win: window,
blockClass,
+ blockElementFn,
blockSelector,
mirror,
sampling: sampling.canvas,
@@ -332,6 +334,7 @@ function record(
scrollCb: wrappedScrollEmit,
bypassOptions: {
blockClass,
+ blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
@@ -378,6 +381,7 @@ function record(
const node = snapshot(document, {
mirror,
blockClass,
+ blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
@@ -543,6 +547,7 @@ function record(
maskInputFn,
maskTextFn,
keepIframeSrcFn,
+ blockElementFn,
blockSelector,
slimDOMOptions,
dataURLOptions,
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index 08e927a98f..75ccaa061c 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -173,6 +173,7 @@ export default class MutationBuffer {
private mutationCb: observerParam['mutationCb'];
private blockClass: observerParam['blockClass'];
+ private blockElementFn: observerParam['blockElementFn'];
private blockSelector: observerParam['blockSelector'];
private maskTextClass: observerParam['maskTextClass'];
private maskTextSelector: observerParam['maskTextSelector'];
@@ -199,6 +200,7 @@ export default class MutationBuffer {
[
'mutationCb',
'blockClass',
+ 'blockElementFn',
'blockSelector',
'maskTextClass',
'maskTextSelector',
@@ -316,6 +318,7 @@ export default class MutationBuffer {
doc: this.doc,
mirror: this.mirror,
blockClass: this.blockClass,
+ blockElementFn: this.blockElementFn,
blockSelector: this.blockSelector,
maskTextClass: this.maskTextClass,
maskTextSelector: this.maskTextSelector,
@@ -556,7 +559,13 @@ export default class MutationBuffer {
const value = dom.textContent(m.target);
if (
- !isBlocked(m.target, this.blockClass, this.blockSelector, false) &&
+ !isBlocked(
+ m.target,
+ this.blockClass,
+ this.blockSelector,
+ this.blockElementFn,
+ false,
+ ) &&
value !== m.oldValue
) {
this.texts.push({
@@ -594,7 +603,13 @@ export default class MutationBuffer {
});
}
if (
- isBlocked(m.target, this.blockClass, this.blockSelector, false) ||
+ isBlocked(
+ m.target,
+ this.blockClass,
+ this.blockSelector,
+ this.blockElementFn,
+ false,
+ ) ||
value === m.oldValue
) {
return;
@@ -695,7 +710,15 @@ export default class MutationBuffer {
/**
* Parent is blocked, ignore all child mutations
*/
- if (isBlocked(m.target, this.blockClass, this.blockSelector, true))
+ if (
+ isBlocked(
+ m.target,
+ this.blockClass,
+ this.blockSelector,
+ this.blockElementFn,
+ true,
+ )
+ )
return;
if ((m.target as Element).tagName === 'TEXTAREA') {
@@ -711,7 +734,13 @@ export default class MutationBuffer {
? this.mirror.getId(dom.host(m.target))
: this.mirror.getId(m.target);
if (
- isBlocked(m.target, this.blockClass, this.blockSelector, false) ||
+ isBlocked(
+ m.target,
+ this.blockClass,
+ this.blockSelector,
+ this.blockElementFn,
+ false,
+ ) ||
isIgnored(n, this.mirror, this.slimDOMOptions) ||
!isSerialized(n, this.mirror)
) {
@@ -790,7 +819,15 @@ export default class MutationBuffer {
// if this node is blocked `serializeNode` will turn it into a placeholder element
// but we have to remove it's children otherwise they will be added as placeholders too
- if (!isBlocked(n, this.blockClass, this.blockSelector, false)) {
+ if (
+ !isBlocked(
+ n,
+ this.blockClass,
+ this.blockSelector,
+ this.blockElementFn,
+ false,
+ )
+ ) {
dom.childNodes(n).forEach((childN) => this.genAdds(childN));
if (hasShadowRoot(n)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
diff --git a/packages/rrweb/src/record/observer.ts b/packages/rrweb/src/record/observer.ts
index 8326d79651..deceeb7c47 100644
--- a/packages/rrweb/src/record/observer.ts
+++ b/packages/rrweb/src/record/observer.ts
@@ -194,6 +194,7 @@ function initMouseInteractionObserver({
mirror,
blockClass,
blockSelector,
+ blockElementFn,
sampling,
}: observerParam): listenerHandler {
if (sampling.mouseInteraction === false) {
@@ -212,7 +213,7 @@ function initMouseInteractionObserver({
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
return (event: MouseEvent | TouchEvent | PointerEvent) => {
const target = getEventTarget(event) as Node;
- if (isBlocked(target, blockClass, blockSelector, true)) {
+ if (isBlocked(target, blockClass, blockSelector, blockElementFn, true)) {
return;
}
let pointerType: PointerTypes | null = null;
@@ -313,10 +314,17 @@ export function initScrollObserver({
mirror,
blockClass,
blockSelector,
+ blockElementFn,
sampling,
}: Pick<
observerParam,
- 'scrollCb' | 'doc' | 'mirror' | 'blockClass' | 'blockSelector' | 'sampling'
+ | 'scrollCb'
+ | 'doc'
+ | 'mirror'
+ | 'blockClass'
+ | 'blockSelector'
+ | 'blockElementFn'
+ | 'sampling'
>): listenerHandler {
const updatePosition = callbackWrapper(
throttle(
@@ -324,7 +332,13 @@ export function initScrollObserver({
const target = getEventTarget(evt);
if (
!target ||
- isBlocked(target as Node, blockClass, blockSelector, true)
+ isBlocked(
+ target as Node,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ true,
+ )
) {
return;
}
@@ -384,6 +398,7 @@ function initInputObserver({
mirror,
blockClass,
blockSelector,
+ blockElementFn,
ignoreClass,
ignoreSelector,
maskInputOptions,
@@ -407,7 +422,7 @@ function initInputObserver({
!target ||
!tagName ||
INPUT_TAGS.indexOf(tagName) < 0 ||
- isBlocked(target as Node, blockClass, blockSelector, true)
+ isBlocked(target as Node, blockClass, blockSelector, blockElementFn, true)
) {
return;
}
@@ -1017,6 +1032,7 @@ function initMediaInteractionObserver({
mediaInteractionCb,
blockClass,
blockSelector,
+ blockElementFn,
mirror,
sampling,
doc,
@@ -1027,7 +1043,13 @@ function initMediaInteractionObserver({
const target = getEventTarget(event);
if (
!target ||
- isBlocked(target as Node, blockClass, blockSelector, true)
+ isBlocked(
+ target as Node,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ true,
+ )
) {
return;
}
@@ -1120,7 +1142,14 @@ function initFontObserver({ fontCb, doc }: observerParam): listenerHandler {
}
function initSelectionObserver(param: observerParam): listenerHandler {
- const { doc, mirror, blockClass, blockSelector, selectionCb } = param;
+ const {
+ doc,
+ mirror,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ selectionCb,
+ } = param;
let collapsed = true;
const updateSelection = callbackWrapper(() => {
@@ -1139,8 +1168,20 @@ function initSelectionObserver(param: observerParam): listenerHandler {
const { startContainer, startOffset, endContainer, endOffset } = range;
const blocked =
- isBlocked(startContainer, blockClass, blockSelector, true) ||
- isBlocked(endContainer, blockClass, blockSelector, true);
+ isBlocked(
+ startContainer,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ true,
+ ) ||
+ isBlocked(
+ endContainer,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ true,
+ );
if (blocked) continue;
diff --git a/packages/rrweb/src/record/observers/canvas/2d.ts b/packages/rrweb/src/record/observers/canvas/2d.ts
index ce1332a890..c44d487b7f 100644
--- a/packages/rrweb/src/record/observers/canvas/2d.ts
+++ b/packages/rrweb/src/record/observers/canvas/2d.ts
@@ -1,5 +1,6 @@
import {
type blockClass,
+ type BlockElementFn,
CanvasContext,
type canvasManagerMutationCallback,
type IWindow,
@@ -14,6 +15,7 @@ export default function initCanvas2DMutationObserver(
win: IWindow,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
): listenerHandler {
const handlers: listenerHandler[] = [];
const props2D = Object.getOwnPropertyNames(
@@ -41,7 +43,15 @@ export default function initCanvas2DMutationObserver(
this: CanvasRenderingContext2D,
...args: Array
) {
- if (!isBlocked(this.canvas, blockClass, blockSelector, true)) {
+ if (
+ !isBlocked(
+ this.canvas,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ true,
+ )
+ ) {
// Using setTimeout as toDataURL can be heavy
// and we'd rather not block the main thread
setTimeout(() => {
diff --git a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts
index 82c756ba24..062118b547 100644
--- a/packages/rrweb/src/record/observers/canvas/canvas-manager.ts
+++ b/packages/rrweb/src/record/observers/canvas/canvas-manager.ts
@@ -1,6 +1,7 @@
import type { ICanvas, Mirror } from 'rrweb-snapshot';
import type {
blockClass,
+ BlockElementFn,
canvasManagerMutationCallback,
canvasMutationCallback,
canvasMutationCommand,
@@ -61,6 +62,7 @@ export class CanvasManager {
mutationCb: canvasMutationCallback;
win: IWindow;
blockClass: blockClass;
+ blockElementFn: BlockElementFn | null;
blockSelector: string | null;
mirror: Mirror;
sampling?: 'all' | number;
@@ -70,6 +72,7 @@ export class CanvasManager {
sampling = 'all',
win,
blockClass,
+ blockElementFn,
blockSelector,
recordCanvas,
dataURLOptions,
@@ -78,11 +81,23 @@ export class CanvasManager {
this.mirror = options.mirror;
if (recordCanvas && sampling === 'all')
- this.initCanvasMutationObserver(win, blockClass, blockSelector);
+ this.initCanvasMutationObserver(
+ win,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ );
if (recordCanvas && typeof sampling === 'number')
- this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector, {
- dataURLOptions,
- });
+ this.initCanvasFPSObserver(
+ sampling,
+ win,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ {
+ dataURLOptions,
+ },
+ );
}
private processMutation: canvasManagerMutationCallback = (
@@ -107,6 +122,7 @@ export class CanvasManager {
win: IWindow,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
options: {
dataURLOptions: DataURLOptions;
},
@@ -115,6 +131,7 @@ export class CanvasManager {
win,
blockClass,
blockSelector,
+ blockElementFn,
true,
);
const snapshotInProgressMap: Map = new Map();
@@ -163,7 +180,9 @@ export class CanvasManager {
const getCanvas = (): HTMLCanvasElement[] => {
const matchedCanvas: HTMLCanvasElement[] = [];
win.document.querySelectorAll('canvas').forEach((canvas) => {
- if (!isBlocked(canvas, blockClass, blockSelector, true)) {
+ if (
+ !isBlocked(canvas, blockClass, blockSelector, blockElementFn, true)
+ ) {
matchedCanvas.push(canvas);
}
});
@@ -241,6 +260,7 @@ export class CanvasManager {
win: IWindow,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
): void {
this.startRAFTimestamping();
this.startPendingCanvasMutationFlusher();
@@ -249,6 +269,7 @@ export class CanvasManager {
win,
blockClass,
blockSelector,
+ blockElementFn,
false,
);
const canvas2DReset = initCanvas2DMutationObserver(
@@ -256,6 +277,7 @@ export class CanvasManager {
win,
blockClass,
blockSelector,
+ blockElementFn,
);
const canvasWebGL1and2Reset = initCanvasWebGLMutationObserver(
@@ -263,6 +285,7 @@ export class CanvasManager {
win,
blockClass,
blockSelector,
+ blockElementFn,
);
this.resetObservers = () => {
diff --git a/packages/rrweb/src/record/observers/canvas/canvas.ts b/packages/rrweb/src/record/observers/canvas/canvas.ts
index aa9be94d51..a6e718941f 100644
--- a/packages/rrweb/src/record/observers/canvas/canvas.ts
+++ b/packages/rrweb/src/record/observers/canvas/canvas.ts
@@ -1,5 +1,10 @@
import type { ICanvas } from 'rrweb-snapshot';
-import type { blockClass, IWindow, listenerHandler } from '@rrweb/types';
+import type {
+ blockClass,
+ BlockElementFn,
+ IWindow,
+ listenerHandler,
+} from '@rrweb/types';
import { isBlocked } from '../../../utils';
import { patch } from '@rrweb/utils';
@@ -11,6 +16,7 @@ export default function initCanvasContextObserver(
win: IWindow,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
setPreserveDrawingBufferToTrue: boolean,
): listenerHandler {
const handlers: listenerHandler[] = [];
@@ -30,7 +36,9 @@ export default function initCanvasContextObserver(
contextType: string,
...args: Array
) {
- if (!isBlocked(this, blockClass, blockSelector, true)) {
+ if (
+ !isBlocked(this, blockClass, blockSelector, blockElementFn, true)
+ ) {
const ctxName = getNormalizedContextName(contextType);
if (!('__context' in this)) (this as ICanvas).__context = ctxName;
diff --git a/packages/rrweb/src/record/observers/canvas/webgl.ts b/packages/rrweb/src/record/observers/canvas/webgl.ts
index 3c51323b2d..465084cfdd 100644
--- a/packages/rrweb/src/record/observers/canvas/webgl.ts
+++ b/packages/rrweb/src/record/observers/canvas/webgl.ts
@@ -1,5 +1,6 @@
import {
type blockClass,
+ type BlockElementFn,
CanvasContext,
type canvasManagerMutationCallback,
type canvasMutationWithType,
@@ -16,6 +17,7 @@ function patchGLPrototype(
cb: canvasManagerMutationCallback,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
win: IWindow,
): listenerHandler[] {
const handlers: listenerHandler[] = [];
@@ -50,7 +52,13 @@ function patchGLPrototype(
saveWebGLVar(result, win, this);
if (
'tagName' in this.canvas &&
- !isBlocked(this.canvas, blockClass, blockSelector, true)
+ !isBlocked(
+ this.canvas,
+ blockClass,
+ blockSelector,
+ blockElementFn,
+ true,
+ )
) {
const recordArgs = serializeArgs(args, win, this);
const mutation: canvasMutationWithType = {
@@ -92,6 +100,7 @@ export default function initCanvasWebGLMutationObserver(
win: IWindow,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
): listenerHandler {
const handlers: listenerHandler[] = [];
@@ -102,6 +111,7 @@ export default function initCanvasWebGLMutationObserver(
cb,
blockClass,
blockSelector,
+ blockElementFn,
win,
),
);
@@ -114,6 +124,7 @@ export default function initCanvasWebGLMutationObserver(
cb,
blockClass,
blockSelector,
+ blockElementFn,
win,
),
);
diff --git a/packages/rrweb/src/types.ts b/packages/rrweb/src/types.ts
index a03e326b6f..fa5136aadf 100644
--- a/packages/rrweb/src/types.ts
+++ b/packages/rrweb/src/types.ts
@@ -15,6 +15,7 @@ import type {
DataURLOptions,
addedNodeMutation,
blockClass,
+ BlockElementFn,
canvasMutationCallback,
customElementCallback,
eventWithTime,
@@ -46,6 +47,7 @@ export type recordOptions = {
checkoutEveryNth?: number;
checkoutEveryNms?: number;
blockClass?: blockClass;
+ blockElementFn?: BlockElementFn;
blockSelector?: string;
ignoreClass?: string;
ignoreSelector?: string;
@@ -86,6 +88,7 @@ export type observerParam = {
mediaInteractionCb: mediaInteractionCallback;
selectionCb: selectionCallback;
blockClass: blockClass;
+ blockElementFn: BlockElementFn | null;
blockSelector: string | null;
ignoreClass: string;
ignoreSelector: string | null;
@@ -132,6 +135,7 @@ export type MutationBufferParam = Pick<
observerParam,
| 'mutationCb'
| 'blockClass'
+ | 'blockElementFn'
| 'blockSelector'
| 'maskTextClass'
| 'maskTextSelector'
diff --git a/packages/rrweb/src/utils.ts b/packages/rrweb/src/utils.ts
index 13b15ec6c1..7cf7bb8dec 100644
--- a/packages/rrweb/src/utils.ts
+++ b/packages/rrweb/src/utils.ts
@@ -3,6 +3,7 @@ import type {
listenerHandler,
hookResetter,
blockClass,
+ BlockElementFn,
addedNodeMutation,
DocumentDimension,
IWindow,
@@ -196,6 +197,7 @@ export function closestElementOfNode(node: Node | null): HTMLElement | null {
* @param node - node to check
* @param blockClass - class name to check
* @param blockSelector - css selectors to check
+ * @param blockElementFn - callback function to manually check node
* @param checkAncestors - whether to search through parent nodes for the block class
* @returns true/false if the node was blocked or not
*/
@@ -203,6 +205,7 @@ export function isBlocked(
node: Node | null,
blockClass: blockClass,
blockSelector: string | null,
+ blockElementFn: BlockElementFn | null,
checkAncestors: boolean,
): boolean {
if (!node) {
@@ -214,6 +217,10 @@ export function isBlocked(
return false;
}
+ if (blockElementFn) {
+ return blockElementFn(el);
+ }
+
try {
if (typeof blockClass === 'string') {
if (el.classList.contains(blockClass)) return true;
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index bba276e483..77b6f43396 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -182,6 +182,7 @@ export type canvasEventWithTime = eventWithTime & {
};
export type blockClass = string | RegExp;
+export type BlockElementFn = (element: HTMLElement) => boolean;
export type maskTextClass = string | RegExp;