Skip to content

Commit 036d3df

Browse files
authored
Allow blocking elements by selector (#50)
* Extract method (isElementBlocked) and add tests * Add blockSelector argument to snapshot If blockSelector is passed, it will be matched against the element. Reasoning: Mutating class names can get messy, so providing another hook helps keep code clean by using data-attributes instead.
1 parent 14bdd67 commit 036d3df

File tree

4 files changed

+61
-13
lines changed

4 files changed

+61
-13
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@
3434
"homepage": "https://github.com/rrweb-io/rrweb-snapshot#readme",
3535
"devDependencies": {
3636
"@types/chai": "^4.1.4",
37+
"@types/jsdom": "^16.2.4",
3738
"@types/mocha": "^5.2.5",
3839
"@types/node": "^10.11.3",
3940
"@types/puppeteer": "^1.12.4",
4041
"chai": "^4.1.2",
4142
"cross-env": "^5.2.0",
4243
"jest-snapshot": "^23.6.0",
44+
"jsdom": "^16.4.0",
4345
"mocha": "^5.2.0",
4446
"puppeteer": "^1.15.0",
4547
"rollup": "^0.66.4",

src/snapshot.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,34 @@ export function transformAttribute(
162162
}
163163
}
164164

165+
export function _isBlockedElement(
166+
element: HTMLElement,
167+
blockClass: string | RegExp,
168+
blockSelector: string | null,
169+
): boolean {
170+
if (typeof blockClass === 'string') {
171+
if (element.classList.contains(blockClass)) {
172+
return true;
173+
}
174+
} else {
175+
element.classList.forEach((className) => {
176+
if (blockClass.test(className)) {
177+
return true;
178+
}
179+
});
180+
}
181+
if (blockSelector) {
182+
return element.matches(blockSelector)
183+
}
184+
185+
return false;
186+
}
187+
165188
function serializeNode(
166189
n: Node,
167190
doc: Document,
168191
blockClass: string | RegExp,
192+
blockSelector: string | null,
169193
inlineStylesheet: boolean,
170194
maskInputOptions: MaskInputOptions = {},
171195
recordCanvas: boolean,
@@ -184,16 +208,7 @@ function serializeNode(
184208
systemId: (n as DocumentType).systemId,
185209
};
186210
case n.ELEMENT_NODE:
187-
let needBlock = false;
188-
if (typeof blockClass === 'string') {
189-
needBlock = (n as HTMLElement).classList.contains(blockClass);
190-
} else {
191-
(n as HTMLElement).classList.forEach((className) => {
192-
if (blockClass.test(className)) {
193-
needBlock = true;
194-
}
195-
});
196-
}
211+
const needBlock = _isBlockedElement(n as HTMLElement, blockClass, blockSelector);
197212
const tagName = getValidTagName((n as HTMLElement).tagName);
198213
let attributes: attributes = {};
199214
for (const { name, value } of Array.from((n as HTMLElement).attributes)) {
@@ -406,6 +421,7 @@ export function serializeNodeWithId(
406421
doc: Document,
407422
map: idNodeMap,
408423
blockClass: string | RegExp,
424+
blockSelector: string | null,
409425
skipChild = false,
410426
inlineStylesheet = true,
411427
maskInputOptions?: MaskInputOptions,
@@ -417,6 +433,7 @@ export function serializeNodeWithId(
417433
n,
418434
doc,
419435
blockClass,
436+
blockSelector,
420437
inlineStylesheet,
421438
maskInputOptions,
422439
recordCanvas || false,
@@ -472,6 +489,7 @@ export function serializeNodeWithId(
472489
doc,
473490
map,
474491
blockClass,
492+
blockSelector,
475493
skipChild,
476494
inlineStylesheet,
477495
maskInputOptions,
@@ -494,6 +512,7 @@ function snapshot(
494512
maskAllInputsOrOptions: boolean | MaskInputOptions,
495513
slimDOMSensibleOrOptions: boolean | SlimDOMOptions,
496514
recordCanvas?: boolean,
515+
blockSelector: string | null = null,
497516
): [serializedNodeWithId | null, idNodeMap] {
498517
const idNodeMap: idNodeMap = {};
499518
const maskInputOptions: MaskInputOptions =
@@ -543,6 +562,7 @@ function snapshot(
543562
n,
544563
idNodeMap,
545564
blockClass,
565+
blockSelector,
546566
false,
547567
inlineStylesheet,
548568
maskInputOptions,

test/snapshot.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'mocha';
2+
import { JSDOM } from 'jsdom';
23
import { expect } from 'chai';
3-
import { absoluteToStylesheet } from '../src/snapshot';
4+
import { absoluteToStylesheet, _isBlockedElement } from '../src/snapshot';
45

56
describe('absolute url to stylesheet', () => {
67
const href = 'http://localhost/css/style.css';
@@ -83,3 +84,27 @@ describe('absolute url to stylesheet', () => {
8384
expect(absoluteToStylesheet(`url('')`, href)).to.equal(`url('')`);
8485
});
8586
});
87+
88+
describe('isBlockedElement()', () => {
89+
const subject = (html: string, opt: any = {}) =>
90+
_isBlockedElement(render(html), 'rr-block', opt.blockSelector)
91+
92+
const render = (html: string): HTMLElement =>
93+
JSDOM.fragment(html).querySelector('div')!
94+
95+
it('can handle empty elements', () => {
96+
expect(subject('<div />')).to.equal(false)
97+
})
98+
99+
it('blocks prohibited className', () => {
100+
expect(subject('<div class="foo rr-block bar" />')).to.equal(true)
101+
})
102+
103+
it('does not block random data selector', () => {
104+
expect(subject('<div data-rr-block />')).to.equal(false)
105+
})
106+
107+
it('blocks blocked selector', () => {
108+
expect(subject('<div data-rr-block />', { blockSelector: '[data-rr-block]' })).to.equal(true)
109+
})
110+
})

typings/snapshot.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ export declare const IGNORED_NODE = -2;
33
export declare function absoluteToStylesheet(cssText: string | null, href: string): string;
44
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
55
export declare function transformAttribute(doc: Document, name: string, value: string): string;
6-
export declare function serializeNodeWithId(n: Node | INode, doc: Document, map: idNodeMap, blockClass: string | RegExp, skipChild?: boolean, inlineStylesheet?: boolean, maskInputOptions?: MaskInputOptions, slimDOMOptions?: SlimDOMOptions, recordCanvas?: boolean, preserveWhiteSpace?: boolean): serializedNodeWithId | null;
7-
declare function snapshot(n: Document, blockClass: string | RegExp | undefined, inlineStylesheet: boolean | undefined, maskAllInputsOrOptions: boolean | MaskInputOptions, slimDOMSensibleOrOptions: boolean | SlimDOMOptions, recordCanvas?: boolean): [serializedNodeWithId | null, idNodeMap];
6+
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
7+
export declare function serializeNodeWithId(n: Node | INode, doc: Document, map: idNodeMap, blockClass: string | RegExp, blockSelector: string | null, skipChild?: boolean, inlineStylesheet?: boolean, maskInputOptions?: MaskInputOptions, slimDOMOptions?: SlimDOMOptions, recordCanvas?: boolean, preserveWhiteSpace?: boolean): serializedNodeWithId | null;
8+
declare function snapshot(n: Document, blockClass: string | RegExp | undefined, inlineStylesheet: boolean | undefined, maskAllInputsOrOptions: boolean | MaskInputOptions, slimDOMSensibleOrOptions: boolean | SlimDOMOptions, recordCanvas?: boolean, blockSelector?: string | null): [serializedNodeWithId | null, idNodeMap];
89
export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
910
export declare function cleanupSnapshot(): void;
1011
export default snapshot;

0 commit comments

Comments
 (0)