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.
This commit is contained in:
committed by
GitHub
parent
65671be739
commit
0c62d31002
@@ -34,12 +34,14 @@
|
||||
"homepage": "https://github.com/rrweb-io/rrweb-snapshot#readme",
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.1.4",
|
||||
"@types/jsdom": "^16.2.4",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.11.3",
|
||||
"@types/puppeteer": "^1.12.4",
|
||||
"chai": "^4.1.2",
|
||||
"cross-env": "^5.2.0",
|
||||
"jest-snapshot": "^23.6.0",
|
||||
"jsdom": "^16.4.0",
|
||||
"mocha": "^5.2.0",
|
||||
"puppeteer": "^1.15.0",
|
||||
"rollup": "^0.66.4",
|
||||
|
||||
@@ -162,10 +162,34 @@ export function transformAttribute(
|
||||
}
|
||||
}
|
||||
|
||||
export function _isBlockedElement(
|
||||
element: HTMLElement,
|
||||
blockClass: string | RegExp,
|
||||
blockSelector: string | null,
|
||||
): boolean {
|
||||
if (typeof blockClass === 'string') {
|
||||
if (element.classList.contains(blockClass)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
element.classList.forEach((className) => {
|
||||
if (blockClass.test(className)) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (blockSelector) {
|
||||
return element.matches(blockSelector)
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function serializeNode(
|
||||
n: Node,
|
||||
doc: Document,
|
||||
blockClass: string | RegExp,
|
||||
blockSelector: string | null,
|
||||
inlineStylesheet: boolean,
|
||||
maskInputOptions: MaskInputOptions = {},
|
||||
recordCanvas: boolean,
|
||||
@@ -184,16 +208,7 @@ function serializeNode(
|
||||
systemId: (n as DocumentType).systemId,
|
||||
};
|
||||
case n.ELEMENT_NODE:
|
||||
let needBlock = false;
|
||||
if (typeof blockClass === 'string') {
|
||||
needBlock = (n as HTMLElement).classList.contains(blockClass);
|
||||
} else {
|
||||
(n as HTMLElement).classList.forEach((className) => {
|
||||
if (blockClass.test(className)) {
|
||||
needBlock = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
const needBlock = _isBlockedElement(n as HTMLElement, blockClass, blockSelector);
|
||||
const tagName = getValidTagName((n as HTMLElement).tagName);
|
||||
let attributes: attributes = {};
|
||||
for (const { name, value } of Array.from((n as HTMLElement).attributes)) {
|
||||
@@ -406,6 +421,7 @@ export function serializeNodeWithId(
|
||||
doc: Document,
|
||||
map: idNodeMap,
|
||||
blockClass: string | RegExp,
|
||||
blockSelector: string | null,
|
||||
skipChild = false,
|
||||
inlineStylesheet = true,
|
||||
maskInputOptions?: MaskInputOptions,
|
||||
@@ -417,6 +433,7 @@ export function serializeNodeWithId(
|
||||
n,
|
||||
doc,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
inlineStylesheet,
|
||||
maskInputOptions,
|
||||
recordCanvas || false,
|
||||
@@ -472,6 +489,7 @@ export function serializeNodeWithId(
|
||||
doc,
|
||||
map,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
skipChild,
|
||||
inlineStylesheet,
|
||||
maskInputOptions,
|
||||
@@ -494,6 +512,7 @@ function snapshot(
|
||||
maskAllInputsOrOptions: boolean | MaskInputOptions,
|
||||
slimDOMSensibleOrOptions: boolean | SlimDOMOptions,
|
||||
recordCanvas?: boolean,
|
||||
blockSelector: string | null = null,
|
||||
): [serializedNodeWithId | null, idNodeMap] {
|
||||
const idNodeMap: idNodeMap = {};
|
||||
const maskInputOptions: MaskInputOptions =
|
||||
@@ -543,6 +562,7 @@ function snapshot(
|
||||
n,
|
||||
idNodeMap,
|
||||
blockClass,
|
||||
blockSelector,
|
||||
false,
|
||||
inlineStylesheet,
|
||||
maskInputOptions,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'mocha';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { expect } from 'chai';
|
||||
import { absoluteToStylesheet } from '../src/snapshot';
|
||||
import { absoluteToStylesheet, _isBlockedElement } from '../src/snapshot';
|
||||
|
||||
describe('absolute url to stylesheet', () => {
|
||||
const href = 'http://localhost/css/style.css';
|
||||
@@ -83,3 +84,27 @@ describe('absolute url to stylesheet', () => {
|
||||
expect(absoluteToStylesheet(`url('')`, href)).to.equal(`url('')`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isBlockedElement()', () => {
|
||||
const subject = (html: string, opt: any = {}) =>
|
||||
_isBlockedElement(render(html), 'rr-block', opt.blockSelector)
|
||||
|
||||
const render = (html: string): HTMLElement =>
|
||||
JSDOM.fragment(html).querySelector('div')!
|
||||
|
||||
it('can handle empty elements', () => {
|
||||
expect(subject('<div />')).to.equal(false)
|
||||
})
|
||||
|
||||
it('blocks prohibited className', () => {
|
||||
expect(subject('<div class="foo rr-block bar" />')).to.equal(true)
|
||||
})
|
||||
|
||||
it('does not block random data selector', () => {
|
||||
expect(subject('<div data-rr-block />')).to.equal(false)
|
||||
})
|
||||
|
||||
it('blocks blocked selector', () => {
|
||||
expect(subject('<div data-rr-block />', { blockSelector: '[data-rr-block]' })).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
5
typings/snapshot.d.ts
vendored
5
typings/snapshot.d.ts
vendored
@@ -3,8 +3,9 @@ export declare const IGNORED_NODE = -2;
|
||||
export declare function absoluteToStylesheet(cssText: string | null, href: string): string;
|
||||
export declare function absoluteToDoc(doc: Document, attributeValue: string): string;
|
||||
export declare function transformAttribute(doc: Document, name: string, value: string): string;
|
||||
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;
|
||||
declare function snapshot(n: Document, blockClass: string | RegExp | undefined, inlineStylesheet: boolean | undefined, maskAllInputsOrOptions: boolean | MaskInputOptions, slimDOMSensibleOrOptions: boolean | SlimDOMOptions, recordCanvas?: boolean): [serializedNodeWithId | null, idNodeMap];
|
||||
export declare function _isBlockedElement(element: HTMLElement, blockClass: string | RegExp, blockSelector: string | null): boolean;
|
||||
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;
|
||||
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];
|
||||
export declare function visitSnapshot(node: serializedNodeWithId, onVisit: (node: serializedNodeWithId) => unknown): void;
|
||||
export declare function cleanupSnapshot(): void;
|
||||
export default snapshot;
|
||||
|
||||
Reference in New Issue
Block a user