Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow more fine grained control over blocking elements #1642

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/silver-pianos-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"rrweb-snapshot": patch
"rrweb": patch
"@rrweb/types": patch
---

Adds `blockElementFN` to record options to allow consumer finer-grained element blocking.
4 changes: 3 additions & 1 deletion guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ The parameter of `rrweb.record` accepts the following options.
| checkoutEveryNth | - | take a full snapshot after every N events<br />refer to the [checkout](#checkout) chapter |
| checkoutEveryNms | - | take a full snapshot after every N ms<br />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 |
Expand Down Expand Up @@ -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

Expand Down
26 changes: 25 additions & 1 deletion packages/rrweb-snapshot/src/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
attributes,
mediaAttributes,
DataURLOptions,
BlockElementFn,
} from '@rrweb/types';
import {
Mirror,
Expand Down Expand Up @@ -219,8 +220,12 @@
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;
Expand Down Expand Up @@ -282,7 +287,7 @@
// should warn? maybe a text node isn't attached to a parent node yet?
return false;
} else {
el = dom.parentElement(node)!;

Check warning on line 290 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L290

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
}
try {
if (typeof maskTextClass === 'string') {
Expand Down Expand Up @@ -392,6 +397,7 @@
doc: Document;
mirror: Mirror;
blockClass: string | RegExp;
blockElementFn: BlockElementFn | null;
blockSelector: string | null;
needsMask: boolean;
inlineStylesheet: boolean;
Expand All @@ -413,6 +419,7 @@
doc,
mirror,
blockClass,
blockElementFn,
blockSelector,
needsMask,
inlineStylesheet,
Expand Down Expand Up @@ -454,6 +461,7 @@
return serializeElementNode(n as HTMLElement, {
doc,
blockClass,
blockElementFn,
blockSelector,
inlineStylesheet,
maskInputOptions,
Expand Down Expand Up @@ -544,6 +552,7 @@
options: {
doc: Document;
blockClass: string | RegExp;
blockElementFn: BlockElementFn | null;
blockSelector: string | null;
inlineStylesheet: boolean;
maskInputOptions: MaskInputOptions;
Expand All @@ -562,6 +571,7 @@
const {
doc,
blockClass,
blockElementFn,
blockSelector,
inlineStylesheet,
maskInputOptions = {},
Expand All @@ -573,7 +583,12 @@
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;
Expand Down Expand Up @@ -702,10 +717,10 @@
const recordInlineImage = () => {
image.removeEventListener('load', recordInlineImage);
try {
canvasService!.width = image.naturalWidth;

Check warning on line 720 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L720

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
canvasService!.height = image.naturalHeight;

Check warning on line 721 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L721

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
canvasCtx!.drawImage(image, 0, 0);

Check warning on line 722 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L722

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
attributes.rr_dataURL = canvasService!.toDataURL(

Check warning on line 723 in packages/rrweb-snapshot/src/snapshot.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/snapshot.ts#L723

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
dataURLOptions.type,
dataURLOptions.quality,
);
Expand Down Expand Up @@ -903,6 +918,7 @@
doc: Document;
mirror: Mirror;
blockClass: string | RegExp;
blockElementFn: BlockElementFn | null;
blockSelector: string | null;
maskTextClass: string | RegExp;
maskTextSelector: string | null;
Expand Down Expand Up @@ -937,6 +953,7 @@
doc,
mirror,
blockClass,
blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
Expand Down Expand Up @@ -976,6 +993,7 @@
doc,
mirror,
blockClass,
blockElementFn,
blockSelector,
needsMask,
inlineStylesheet,
Expand Down Expand Up @@ -1047,6 +1065,7 @@
doc,
mirror,
blockClass,
blockElementFn,
blockSelector,
needsMask,
maskTextClass,
Expand Down Expand Up @@ -1123,6 +1142,7 @@
doc: iframeDoc,
mirror,
blockClass,
blockElementFn,
blockSelector,
needsMask,
maskTextClass,
Expand Down Expand Up @@ -1175,6 +1195,7 @@
doc,
mirror,
blockClass,
blockElementFn,
blockSelector,
needsMask,
maskTextClass,
Expand Down Expand Up @@ -1217,6 +1238,7 @@
options?: {
mirror?: Mirror;
blockClass?: string | RegExp;
blockElementFn?: BlockElementFn | null;
blockSelector?: string | null;
maskTextClass?: string | RegExp;
maskTextSelector?: string | null;
Expand Down Expand Up @@ -1246,6 +1268,7 @@
const {
mirror = new Mirror(),
blockClass = 'rr-block',
blockElementFn = null,
blockSelector = null,
maskTextClass = 'rr-mask',
maskTextSelector = null,
Expand Down Expand Up @@ -1312,6 +1335,7 @@
doc: n,
mirror,
blockClass,
blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
Expand Down
16 changes: 15 additions & 1 deletion packages/rrweb-snapshot/test/snapshot.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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')!;
Expand All @@ -151,6 +157,14 @@ describe('isBlockedElement()', () => {
subject('<div data-rr-block />', { blockSelector: '[data-rr-block]' }),
).toEqual(true);
});

it('blocks with blockElementFn', () => {
expect(
subject('<div class="special" />', {
blockElementFn: (e: HTMLElement) => e.matches('div.special'),
}),
).toEqual(true);
});
});

describe('style elements', () => {
Expand Down
5 changes: 5 additions & 0 deletions packages/rrweb/src/record/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
checkoutEveryNms,
checkoutEveryNth,
blockClass = 'rr-block',
blockElementFn = null,
blockSelector = null,
ignoreClass = 'rr-ignore',
ignoreSelector = null,
Expand Down Expand Up @@ -321,6 +322,7 @@
mutationCb: wrappedCanvasMutationEmit,
win: window,
blockClass,
blockElementFn,
blockSelector,
mirror,
sampling: sampling.canvas,
Expand All @@ -332,6 +334,7 @@
scrollCb: wrappedScrollEmit,
bypassOptions: {
blockClass,
blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
Expand Down Expand Up @@ -378,6 +381,7 @@
const node = snapshot(document, {
mirror,
blockClass,
blockElementFn,
blockSelector,
maskTextClass,
maskTextSelector,
Expand Down Expand Up @@ -543,6 +547,7 @@
maskInputFn,
maskTextFn,
keepIframeSrcFn,
blockElementFn,
blockSelector,
slimDOMOptions,
dataURLOptions,
Expand All @@ -557,7 +562,7 @@
plugins
?.filter((p) => p.observer)
?.map((p) => ({
observer: p.observer!,

Check warning on line 565 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/index.ts#L565

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
options: p.options,
callback: (payload: object) =>
wrappedEmit({
Expand All @@ -575,7 +580,7 @@

iframeManager.addLoadListener((iframeEl) => {
try {
handlers.push(observe(iframeEl.contentDocument!));

Check warning on line 583 in packages/rrweb/src/record/index.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/index.ts#L583

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
} catch (error) {
// TODO: handle internal error
console.warn(error);
Expand Down
47 changes: 42 additions & 5 deletions packages/rrweb/src/record/mutation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@

private mutationCb: observerParam['mutationCb'];
private blockClass: observerParam['blockClass'];
private blockElementFn: observerParam['blockElementFn'];
private blockSelector: observerParam['blockSelector'];
private maskTextClass: observerParam['maskTextClass'];
private maskTextSelector: observerParam['maskTextSelector'];
Expand All @@ -199,6 +200,7 @@
[
'mutationCb',
'blockClass',
'blockElementFn',
'blockSelector',
'maskTextClass',
'maskTextSelector',
Expand Down Expand Up @@ -316,6 +318,7 @@
doc: this.doc,
mirror: this.mirror,
blockClass: this.blockClass,
blockElementFn: this.blockElementFn,
blockSelector: this.blockSelector,
maskTextClass: this.maskTextClass,
maskTextSelector: this.maskTextSelector,
Expand Down Expand Up @@ -363,13 +366,13 @@
};

while (this.mapRemoves.length) {
this.mirror.removeNodeFromMap(this.mapRemoves.shift()!);

Check warning on line 369 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/mutation.ts#L369

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
}

for (const n of this.movedSet) {
if (
isParentRemoved(this.removesSubTreeCache, n, this.mirror) &&
!this.movedSet.has(dom.parentNode(n)!)

Check warning on line 375 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/mutation.ts#L375

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
) {
continue;
}
Expand Down Expand Up @@ -556,7 +559,13 @@
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({
Expand Down Expand Up @@ -594,7 +603,13 @@
});
}
if (
isBlocked(m.target, this.blockClass, this.blockSelector, false) ||
isBlocked(
m.target,
this.blockClass,
this.blockSelector,
this.blockElementFn,
false,
) ||
value === m.oldValue
) {
return;
Expand Down Expand Up @@ -695,7 +710,15 @@
/**
* 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') {
Expand All @@ -711,7 +734,13 @@
? 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)
) {
Expand Down Expand Up @@ -790,7 +819,15 @@

// 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
Expand Down Expand Up @@ -836,7 +873,7 @@
function _isParentRemoved(
removes: Set<Node>,
n: Node,
_mirror: Mirror,

Check warning on line 876 in packages/rrweb/src/record/mutation.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb/src/record/mutation.ts#L876

[@typescript-eslint/no-unused-vars] '_mirror' is defined but never used.
): boolean {
const node: ParentNode | null = dom.parentNode(n);
if (!node) return false;
Expand Down
Loading
Loading