isBlocked factors in the selector (#894)
* isBlocked factors in the selector * Ensure contains parameter is a node * Fix blockSelector blocking for closest nodes * Fix integration test * adding ignoreCSSAttributes to ignore the addition of certain css attributes * tested ignoreCSSAttributes * Update test snapshot * swapped the wrapping of htmlelement to be element * Fix linter errors * Address MR feedback * Rebase Co-authored-by: Filip <filipslatinac@gmail.com>
This commit is contained in:
1
guide.md
1
guide.md
@@ -143,6 +143,7 @@ The parameter of `rrweb.record` accepts the following options.
|
|||||||
| blockClass | 'rr-block' | Use a string or RegExp to configure which elements should be blocked, refer to the [privacy](#privacy) 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 |
|
| blockSelector | null | Use a string to configure which selector 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 |
|
| ignoreClass | 'rr-ignore' | Use a string or RegExp to configure which elements should be ignored, refer to the [privacy](#privacy) chapter |
|
||||||
|
| ignoreCSSAttributes | null | array of CSS attributes that should be ignored |
|
||||||
| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
|
| maskTextClass | 'rr-mask' | Use a string or RegExp to configure which elements should be masked, refer to the [privacy](#privacy) chapter |
|
||||||
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
|
| maskTextSelector | null | Use a string to configure which selector should be masked, refer to the [privacy](#privacy) chapter |
|
||||||
| maskAllInputs | false | mask all input content as \* |
|
| maskAllInputs | false | mask all input content as \* |
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ setInterval(save, 10 * 1000);
|
|||||||
| blockClass | 'rr-block' | 字符串或正则表达式,可用于自定义屏蔽元素的类名,详见[“隐私”](#隐私)章节 |
|
| blockClass | 'rr-block' | 字符串或正则表达式,可用于自定义屏蔽元素的类名,详见[“隐私”](#隐私)章节 |
|
||||||
| blockSelector | null | 所有 element.matches(blockSelector)为 true 的元素都不会被录制,回放时取而代之的是一个同等宽高的占位元素 |
|
| blockSelector | null | 所有 element.matches(blockSelector)为 true 的元素都不会被录制,回放时取而代之的是一个同等宽高的占位元素 |
|
||||||
| ignoreClass | 'rr-ignore' | 字符串或正则表达式,可用于自定义忽略元素的类名,详见[“隐私”](#隐私)章节 |
|
| ignoreClass | 'rr-ignore' | 字符串或正则表达式,可用于自定义忽略元素的类名,详见[“隐私”](#隐私)章节 |
|
||||||
|
| ignoreCSSAttributes | null | 应该被忽略的 CSS 属性数组 |
|
||||||
| maskTextClass | 'rr-mask' | 字符串或正则表达式,可用于自定义忽略元素 text 内容的类名,详见[“隐私”](#隐私)章节 |
|
| maskTextClass | 'rr-mask' | 字符串或正则表达式,可用于自定义忽略元素 text 内容的类名,详见[“隐私”](#隐私)章节 |
|
||||||
| maskTextSelector | null | 所有 element.matches(maskTextSelector)为 true 的元素及其子元素的 text 内容将会被屏蔽 |
|
| maskTextSelector | null | 所有 element.matches(maskTextSelector)为 true 的元素及其子元素的 text 内容将会被屏蔽 |
|
||||||
| maskAllInputs | false | 将所有输入内容记录为 \* |
|
| maskAllInputs | false | 将所有输入内容记录为 \* |
|
||||||
|
|||||||
@@ -70,7 +70,9 @@ function record<T = eventWithTime>(
|
|||||||
inlineImages = false,
|
inlineImages = false,
|
||||||
plugins,
|
plugins,
|
||||||
keepIframeSrcFn = () => false,
|
keepIframeSrcFn = () => false,
|
||||||
|
ignoreCSSAttributes = new Set([]),
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
// runtime checks for user options
|
// runtime checks for user options
|
||||||
if (!emit) {
|
if (!emit) {
|
||||||
throw new Error('emit function is required');
|
throw new Error('emit function is required');
|
||||||
@@ -226,6 +228,7 @@ function record<T = eventWithTime>(
|
|||||||
mutationCb: wrappedCanvasMutationEmit,
|
mutationCb: wrappedCanvasMutationEmit,
|
||||||
win: window,
|
win: window,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
mirror,
|
mirror,
|
||||||
sampling: sampling.canvas,
|
sampling: sampling.canvas,
|
||||||
});
|
});
|
||||||
@@ -464,6 +467,7 @@ function record<T = eventWithTime>(
|
|||||||
stylesheetManager,
|
stylesheetManager,
|
||||||
shadowDomManager,
|
shadowDomManager,
|
||||||
canvasManager,
|
canvasManager,
|
||||||
|
ignoreCSSAttributes,
|
||||||
plugins:
|
plugins:
|
||||||
plugins
|
plugins
|
||||||
?.filter((p) => p.observer)
|
?.filter((p) => p.observer)
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ export default class MutationBuffer {
|
|||||||
rootShadowHost =
|
rootShadowHost =
|
||||||
(rootShadowHost?.getRootNode?.() as ShadowRoot | undefined)?.host ||
|
(rootShadowHost?.getRootNode?.() as ShadowRoot | undefined)?.host ||
|
||||||
null;
|
null;
|
||||||
// ensure shadowHost is a Node, or doc.contains will throw an error
|
// ensure contains is passed a Node, or it will throw an error
|
||||||
const notInDoc =
|
const notInDoc =
|
||||||
!this.doc.contains(n) &&
|
!this.doc.contains(n) &&
|
||||||
(!rootShadowHost || !this.doc.contains(rootShadowHost));
|
(!rootShadowHost || !this.doc.contains(rootShadowHost));
|
||||||
@@ -446,7 +446,7 @@ export default class MutationBuffer {
|
|||||||
case 'characterData': {
|
case 'characterData': {
|
||||||
const value = m.target.textContent;
|
const value = m.target.textContent;
|
||||||
if (
|
if (
|
||||||
!isBlocked(m.target, this.blockClass, false) &&
|
!isBlocked(m.target, this.blockClass, this.blockSelector, false) &&
|
||||||
value !== m.oldValue
|
value !== m.oldValue
|
||||||
) {
|
) {
|
||||||
this.texts.push({
|
this.texts.push({
|
||||||
@@ -478,7 +478,7 @@ export default class MutationBuffer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
isBlocked(m.target, this.blockClass, false) ||
|
isBlocked(m.target, this.blockClass, this.blockSelector, false) ||
|
||||||
value === m.oldValue
|
value === m.oldValue
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
@@ -554,7 +554,7 @@ export default class MutationBuffer {
|
|||||||
/**
|
/**
|
||||||
* Parent is blocked, ignore all child mutations
|
* Parent is blocked, ignore all child mutations
|
||||||
*/
|
*/
|
||||||
if (isBlocked(m.target, this.blockClass, true)) return;
|
if (isBlocked(m.target, this.blockClass, this.blockSelector, true)) return;
|
||||||
|
|
||||||
m.addedNodes.forEach((n) => this.genAdds(n, m.target));
|
m.addedNodes.forEach((n) => this.genAdds(n, m.target));
|
||||||
m.removedNodes.forEach((n) => {
|
m.removedNodes.forEach((n) => {
|
||||||
@@ -563,7 +563,7 @@ export default class MutationBuffer {
|
|||||||
? this.mirror.getId(m.target.host)
|
? this.mirror.getId(m.target.host)
|
||||||
: this.mirror.getId(m.target);
|
: this.mirror.getId(m.target);
|
||||||
if (
|
if (
|
||||||
isBlocked(m.target, this.blockClass, false) ||
|
isBlocked(m.target, this.blockClass, this.blockSelector, false) ||
|
||||||
isIgnored(n, this.mirror) ||
|
isIgnored(n, this.mirror) ||
|
||||||
!isSerialized(n, this.mirror)
|
!isSerialized(n, this.mirror)
|
||||||
) {
|
) {
|
||||||
@@ -635,7 +635,7 @@ export default class MutationBuffer {
|
|||||||
|
|
||||||
// if this node is blocked `serializeNode` will turn it into a placeholder element
|
// 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
|
// but we have to remove it's children otherwise they will be added as placeholders too
|
||||||
if (!isBlocked(n, this.blockClass, false))
|
if (!isBlocked(n, this.blockClass, this.blockSelector, false))
|
||||||
n.childNodes.forEach((childN) => this.genAdds(childN));
|
n.childNodes.forEach((childN) => this.genAdds(childN));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -210,6 +210,7 @@ function initMouseInteractionObserver({
|
|||||||
doc,
|
doc,
|
||||||
mirror,
|
mirror,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
sampling,
|
sampling,
|
||||||
}: observerParam): listenerHandler {
|
}: observerParam): listenerHandler {
|
||||||
if (sampling.mouseInteraction === false) {
|
if (sampling.mouseInteraction === false) {
|
||||||
@@ -227,7 +228,7 @@ function initMouseInteractionObserver({
|
|||||||
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
|
const getHandler = (eventKey: keyof typeof MouseInteractions) => {
|
||||||
return (event: MouseEvent | TouchEvent) => {
|
return (event: MouseEvent | TouchEvent) => {
|
||||||
const target = getEventTarget(event) as Node;
|
const target = getEventTarget(event) as Node;
|
||||||
if (isBlocked(target, blockClass, true)) {
|
if (isBlocked(target, blockClass, blockSelector, true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const e = isTouchEvent(event) ? event.changedTouches[0] : event;
|
const e = isTouchEvent(event) ? event.changedTouches[0] : event;
|
||||||
@@ -266,14 +267,15 @@ export function initScrollObserver({
|
|||||||
doc,
|
doc,
|
||||||
mirror,
|
mirror,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
sampling,
|
sampling,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
observerParam,
|
observerParam,
|
||||||
'scrollCb' | 'doc' | 'mirror' | 'blockClass' | 'sampling'
|
'scrollCb' | 'doc' | 'mirror' | 'blockClass' | 'blockSelector' | 'sampling'
|
||||||
>): listenerHandler {
|
>): listenerHandler {
|
||||||
const updatePosition = throttle<UIEvent>((evt) => {
|
const updatePosition = throttle<UIEvent>((evt) => {
|
||||||
const target = getEventTarget(evt);
|
const target = getEventTarget(evt);
|
||||||
if (!target || isBlocked(target as Node, blockClass, true)) {
|
if (!target || isBlocked(target as Node, blockClass, blockSelector, true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const id = mirror.getId(target as Node);
|
const id = mirror.getId(target as Node);
|
||||||
@@ -331,6 +333,7 @@ function initInputObserver({
|
|||||||
doc,
|
doc,
|
||||||
mirror,
|
mirror,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
ignoreClass,
|
ignoreClass,
|
||||||
maskInputOptions,
|
maskInputOptions,
|
||||||
maskInputFn,
|
maskInputFn,
|
||||||
@@ -350,7 +353,7 @@ function initInputObserver({
|
|||||||
!target ||
|
!target ||
|
||||||
!(target as Element).tagName ||
|
!(target as Element).tagName ||
|
||||||
INPUT_TAGS.indexOf((target as Element).tagName) < 0 ||
|
INPUT_TAGS.indexOf((target as Element).tagName) < 0 ||
|
||||||
isBlocked(target as Node, blockClass, true)
|
isBlocked(target as Node, blockClass, blockSelector, true)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -613,7 +616,7 @@ function initStyleSheetObserver(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initStyleDeclarationObserver(
|
function initStyleDeclarationObserver(
|
||||||
{ styleDeclarationCb, mirror }: observerParam,
|
{ styleDeclarationCb, mirror, ignoreCSSAttributes }: observerParam,
|
||||||
{ win }: { win: IWindow },
|
{ win }: { win: IWindow },
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
@@ -624,6 +627,10 @@ function initStyleDeclarationObserver(
|
|||||||
value: string,
|
value: string,
|
||||||
priority: string,
|
priority: string,
|
||||||
) {
|
) {
|
||||||
|
// ignore this mutation if we do not care about this css attribute
|
||||||
|
if (ignoreCSSAttributes.has(property)) {
|
||||||
|
return setProperty.apply(this, [property, value, priority]);
|
||||||
|
}
|
||||||
const id = mirror.getId(this.parentRule?.parentStyleSheet?.ownerNode);
|
const id = mirror.getId(this.parentRule?.parentStyleSheet?.ownerNode);
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
styleDeclarationCb({
|
styleDeclarationCb({
|
||||||
@@ -645,6 +652,10 @@ function initStyleDeclarationObserver(
|
|||||||
this: CSSStyleDeclaration,
|
this: CSSStyleDeclaration,
|
||||||
property: string,
|
property: string,
|
||||||
) {
|
) {
|
||||||
|
// ignore this mutation if we do not care about this css attribute
|
||||||
|
if (ignoreCSSAttributes.has(property)) {
|
||||||
|
return removeProperty.apply(this, [property]);
|
||||||
|
}
|
||||||
const id = mirror.getId(this.parentRule?.parentStyleSheet?.ownerNode);
|
const id = mirror.getId(this.parentRule?.parentStyleSheet?.ownerNode);
|
||||||
if (id !== -1) {
|
if (id !== -1) {
|
||||||
styleDeclarationCb({
|
styleDeclarationCb({
|
||||||
@@ -667,13 +678,14 @@ function initStyleDeclarationObserver(
|
|||||||
function initMediaInteractionObserver({
|
function initMediaInteractionObserver({
|
||||||
mediaInteractionCb,
|
mediaInteractionCb,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
mirror,
|
mirror,
|
||||||
sampling,
|
sampling,
|
||||||
}: observerParam): listenerHandler {
|
}: observerParam): listenerHandler {
|
||||||
const handler = (type: MediaInteractions) =>
|
const handler = (type: MediaInteractions) =>
|
||||||
throttle((event: Event) => {
|
throttle((event: Event) => {
|
||||||
const target = getEventTarget(event);
|
const target = getEventTarget(event);
|
||||||
if (!target || isBlocked(target as Node, blockClass, true)) {
|
if (!target || isBlocked(target as Node, blockClass, blockSelector, true)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { currentTime, volume, muted } = target as HTMLMediaElement;
|
const { currentTime, volume, muted } = target as HTMLMediaElement;
|
||||||
@@ -755,7 +767,7 @@ function initFontObserver({ fontCb, doc }: observerParam): listenerHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function initSelectionObserver(param: observerParam): listenerHandler {
|
function initSelectionObserver(param: observerParam): listenerHandler {
|
||||||
const { doc, mirror, blockClass, selectionCb } = param;
|
const { doc, mirror, blockClass, blockSelector, selectionCb } = param;
|
||||||
let collapsed = true;
|
let collapsed = true;
|
||||||
|
|
||||||
const updateSelection = () => {
|
const updateSelection = () => {
|
||||||
@@ -774,8 +786,8 @@ function initSelectionObserver(param: observerParam): listenerHandler {
|
|||||||
const { startContainer, startOffset, endContainer, endOffset } = range;
|
const { startContainer, startOffset, endContainer, endOffset } = range;
|
||||||
|
|
||||||
const blocked =
|
const blocked =
|
||||||
isBlocked(startContainer, blockClass, true) ||
|
isBlocked(startContainer, blockClass, blockSelector, true) ||
|
||||||
isBlocked(endContainer, blockClass, true);
|
isBlocked(endContainer, blockClass, blockSelector, true);
|
||||||
|
|
||||||
if (blocked) continue;
|
if (blocked) continue;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export default function initCanvas2DMutationObserver(
|
|||||||
cb: canvasManagerMutationCallback,
|
cb: canvasManagerMutationCallback,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const handlers: listenerHandler[] = [];
|
const handlers: listenerHandler[] = [];
|
||||||
@@ -41,7 +42,7 @@ export default function initCanvas2DMutationObserver(
|
|||||||
this: CanvasRenderingContext2D,
|
this: CanvasRenderingContext2D,
|
||||||
...args: Array<unknown>
|
...args: Array<unknown>
|
||||||
) {
|
) {
|
||||||
if (!isBlocked(this.canvas, blockClass, true)) {
|
if (!isBlocked(this.canvas, blockClass, blockSelector, true)) {
|
||||||
// Using setTimeout as toDataURL can be heavy
|
// Using setTimeout as toDataURL can be heavy
|
||||||
// and we'd rather not block the main thread
|
// and we'd rather not block the main thread
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -60,17 +60,18 @@ export class CanvasManager {
|
|||||||
mutationCb: canvasMutationCallback;
|
mutationCb: canvasMutationCallback;
|
||||||
win: IWindow;
|
win: IWindow;
|
||||||
blockClass: blockClass;
|
blockClass: blockClass;
|
||||||
|
blockSelector: string | null,
|
||||||
mirror: Mirror;
|
mirror: Mirror;
|
||||||
sampling?: 'all' | number;
|
sampling?: 'all' | number;
|
||||||
}) {
|
}) {
|
||||||
const { sampling = 'all', win, blockClass, recordCanvas } = options;
|
const { sampling = 'all', win, blockClass, blockSelector, recordCanvas } = options;
|
||||||
this.mutationCb = options.mutationCb;
|
this.mutationCb = options.mutationCb;
|
||||||
this.mirror = options.mirror;
|
this.mirror = options.mirror;
|
||||||
|
|
||||||
if (recordCanvas && sampling === 'all')
|
if (recordCanvas && sampling === 'all')
|
||||||
this.initCanvasMutationObserver(win, blockClass);
|
this.initCanvasMutationObserver(win, blockClass, blockSelector);
|
||||||
if (recordCanvas && typeof sampling === 'number')
|
if (recordCanvas && typeof sampling === 'number')
|
||||||
this.initCanvasFPSObserver(sampling, win, blockClass);
|
this.initCanvasFPSObserver(sampling, win, blockClass, blockSelector);
|
||||||
}
|
}
|
||||||
|
|
||||||
private processMutation: canvasManagerMutationCallback = (
|
private processMutation: canvasManagerMutationCallback = (
|
||||||
@@ -94,8 +95,9 @@ export class CanvasManager {
|
|||||||
fps: number,
|
fps: number,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
) {
|
) {
|
||||||
const canvasContextReset = initCanvasContextObserver(win, blockClass);
|
const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector);
|
||||||
const snapshotInProgressMap: Map<number, boolean> = new Map();
|
const snapshotInProgressMap: Map<number, boolean> = new Map();
|
||||||
const worker = new ImageBitmapDataURLWorker() as ImageBitmapDataURLRequestWorker;
|
const worker = new ImageBitmapDataURLWorker() as ImageBitmapDataURLRequestWorker;
|
||||||
worker.onmessage = (e) => {
|
worker.onmessage = (e) => {
|
||||||
@@ -141,7 +143,7 @@ export class CanvasManager {
|
|||||||
const getCanvas = (): HTMLCanvasElement[] => {
|
const getCanvas = (): HTMLCanvasElement[] => {
|
||||||
const matchedCanvas: HTMLCanvasElement[] = [];
|
const matchedCanvas: HTMLCanvasElement[] = [];
|
||||||
win.document.querySelectorAll('canvas').forEach(canvas => {
|
win.document.querySelectorAll('canvas').forEach(canvas => {
|
||||||
if (!isBlocked(canvas, blockClass, true)) {
|
if (!isBlocked(canvas, blockClass, blockSelector, true)) {
|
||||||
matchedCanvas.push(canvas);
|
matchedCanvas.push(canvas);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -208,15 +210,17 @@ export class CanvasManager {
|
|||||||
private initCanvasMutationObserver(
|
private initCanvasMutationObserver(
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
): void {
|
): void {
|
||||||
this.startRAFTimestamping();
|
this.startRAFTimestamping();
|
||||||
this.startPendingCanvasMutationFlusher();
|
this.startPendingCanvasMutationFlusher();
|
||||||
|
|
||||||
const canvasContextReset = initCanvasContextObserver(win, blockClass);
|
const canvasContextReset = initCanvasContextObserver(win, blockClass, blockSelector);
|
||||||
const canvas2DReset = initCanvas2DMutationObserver(
|
const canvas2DReset = initCanvas2DMutationObserver(
|
||||||
this.processMutation.bind(this),
|
this.processMutation.bind(this),
|
||||||
win,
|
win,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
this.mirror,
|
this.mirror,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -224,6 +228,7 @@ export class CanvasManager {
|
|||||||
this.processMutation.bind(this),
|
this.processMutation.bind(this),
|
||||||
win,
|
win,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
this.mirror,
|
this.mirror,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { isBlocked, patch } from '../../../utils';
|
|||||||
export default function initCanvasContextObserver(
|
export default function initCanvasContextObserver(
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const handlers: listenerHandler[] = [];
|
const handlers: listenerHandler[] = [];
|
||||||
try {
|
try {
|
||||||
@@ -23,7 +24,7 @@ export default function initCanvasContextObserver(
|
|||||||
contextType: string,
|
contextType: string,
|
||||||
...args: Array<unknown>
|
...args: Array<unknown>
|
||||||
) {
|
) {
|
||||||
if (!isBlocked(this, blockClass, true)) {
|
if (!isBlocked(this, blockClass, blockSelector, true)) {
|
||||||
if (!('__context' in this)) this.__context = contextType;
|
if (!('__context' in this)) this.__context = contextType;
|
||||||
}
|
}
|
||||||
return original.apply(this, [contextType, ...args]);
|
return original.apply(this, [contextType, ...args]);
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ function patchGLPrototype(
|
|||||||
type: CanvasContext,
|
type: CanvasContext,
|
||||||
cb: canvasManagerMutationCallback,
|
cb: canvasManagerMutationCallback,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
): listenerHandler[] {
|
): listenerHandler[] {
|
||||||
@@ -36,7 +37,7 @@ function patchGLPrototype(
|
|||||||
return function (this: typeof prototype, ...args: Array<unknown>) {
|
return function (this: typeof prototype, ...args: Array<unknown>) {
|
||||||
const result = original.apply(this, args);
|
const result = original.apply(this, args);
|
||||||
saveWebGLVar(result, win, prototype);
|
saveWebGLVar(result, win, prototype);
|
||||||
if (!isBlocked(this.canvas, blockClass, true)) {
|
if (!isBlocked(this.canvas, blockClass, blockSelector, true)) {
|
||||||
const recordArgs = serializeArgs([...args], win, prototype);
|
const recordArgs = serializeArgs([...args], win, prototype);
|
||||||
const mutation: canvasMutationWithType = {
|
const mutation: canvasMutationWithType = {
|
||||||
type,
|
type,
|
||||||
@@ -76,6 +77,7 @@ export default function initCanvasWebGLMutationObserver(
|
|||||||
cb: canvasManagerMutationCallback,
|
cb: canvasManagerMutationCallback,
|
||||||
win: IWindow,
|
win: IWindow,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
mirror: Mirror,
|
mirror: Mirror,
|
||||||
): listenerHandler {
|
): listenerHandler {
|
||||||
const handlers: listenerHandler[] = [];
|
const handlers: listenerHandler[] = [];
|
||||||
@@ -86,6 +88,7 @@ export default function initCanvasWebGLMutationObserver(
|
|||||||
CanvasContext.WebGL,
|
CanvasContext.WebGL,
|
||||||
cb,
|
cb,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
mirror,
|
mirror,
|
||||||
win,
|
win,
|
||||||
),
|
),
|
||||||
@@ -98,6 +101,7 @@ export default function initCanvasWebGLMutationObserver(
|
|||||||
CanvasContext.WebGL2,
|
CanvasContext.WebGL2,
|
||||||
cb,
|
cb,
|
||||||
blockClass,
|
blockClass,
|
||||||
|
blockSelector,
|
||||||
mirror,
|
mirror,
|
||||||
win,
|
win,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export class ShadowDomManager {
|
|||||||
const manager = this;
|
const manager = this;
|
||||||
this.restorePatches.push(
|
this.restorePatches.push(
|
||||||
patch(
|
patch(
|
||||||
HTMLElement.prototype,
|
Element.prototype,
|
||||||
'attachShadow',
|
'attachShadow',
|
||||||
function (original: (init: ShadowRootInit) => ShadowRoot) {
|
function (original: (init: ShadowRootInit) => ShadowRoot) {
|
||||||
return function (this: HTMLElement, option: ShadowRootInit) {
|
return function (this: HTMLElement, option: ShadowRootInit) {
|
||||||
|
|||||||
@@ -245,6 +245,7 @@ export type recordOptions<T> = {
|
|||||||
maskInputFn?: MaskInputFn;
|
maskInputFn?: MaskInputFn;
|
||||||
maskTextFn?: MaskTextFn;
|
maskTextFn?: MaskTextFn;
|
||||||
slimDOMOptions?: SlimDOMOptions | 'all' | true;
|
slimDOMOptions?: SlimDOMOptions | 'all' | true;
|
||||||
|
ignoreCSSAttributes?:Set<string>;
|
||||||
inlineStylesheet?: boolean;
|
inlineStylesheet?: boolean;
|
||||||
hooks?: hooksParam;
|
hooks?: hooksParam;
|
||||||
packFn?: PackFn;
|
packFn?: PackFn;
|
||||||
@@ -294,6 +295,7 @@ export type observerParam = {
|
|||||||
stylesheetManager: StylesheetManager;
|
stylesheetManager: StylesheetManager;
|
||||||
shadowDomManager: ShadowDomManager;
|
shadowDomManager: ShadowDomManager;
|
||||||
canvasManager: CanvasManager;
|
canvasManager: CanvasManager;
|
||||||
|
ignoreCSSAttributes:Set<string>;
|
||||||
plugins: Array<{
|
plugins: Array<{
|
||||||
observer: (
|
observer: (
|
||||||
cb: (...arg: Array<unknown>) => void,
|
cb: (...arg: Array<unknown>) => void,
|
||||||
|
|||||||
@@ -187,12 +187,14 @@ export function getWindowWidth(): number {
|
|||||||
* Checks if the given element set to be blocked by rrweb
|
* Checks if the given element set to be blocked by rrweb
|
||||||
* @param node - node to check
|
* @param node - node to check
|
||||||
* @param blockClass - class name to check
|
* @param blockClass - class name to check
|
||||||
* @param ignoreParents - whether to search through parent nodes for the block class
|
* @param blockSelector - css selectors to check
|
||||||
|
* @param checkAncestors - whether to search through parent nodes for the block class
|
||||||
* @returns true/false if the node was blocked or not
|
* @returns true/false if the node was blocked or not
|
||||||
*/
|
*/
|
||||||
export function isBlocked(
|
export function isBlocked(
|
||||||
node: Node | null,
|
node: Node | null,
|
||||||
blockClass: blockClass,
|
blockClass: blockClass,
|
||||||
|
blockSelector: string | null,
|
||||||
checkAncestors: boolean,
|
checkAncestors: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -210,6 +212,10 @@ export function isBlocked(
|
|||||||
} else {
|
} else {
|
||||||
if (classMatchesRegex(el, blockClass, checkAncestors)) return true;
|
if (classMatchesRegex(el, blockClass, checkAncestors)) return true;
|
||||||
}
|
}
|
||||||
|
if (blockSelector) {
|
||||||
|
if ((node as HTMLElement).matches(blockSelector)) return true;
|
||||||
|
if (checkAncestors && el.closest(blockSelector) !== null) return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -581,7 +581,7 @@ exports[`record captures style property changes 1`] = `
|
|||||||
\\"source\\": 13,
|
\\"source\\": 13,
|
||||||
\\"id\\": 9,
|
\\"id\\": 9,
|
||||||
\\"set\\": {
|
\\"set\\": {
|
||||||
\\"property\\": \\"color\\",
|
\\"property\\": \\"border-color\\",
|
||||||
\\"value\\": \\"green\\"
|
\\"value\\": \\"green\\"
|
||||||
},
|
},
|
||||||
\\"index\\": [
|
\\"index\\": [
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ describe('record', function (this: ISuite) {
|
|||||||
|
|
||||||
record({
|
record({
|
||||||
emit: ((window as unknown) as IWindow).emit,
|
emit: ((window as unknown) as IWindow).emit,
|
||||||
|
ignoreCSSAttributes: new Set(['color']),
|
||||||
});
|
});
|
||||||
|
|
||||||
const styleElement = document.createElement('style');
|
const styleElement = document.createElement('style');
|
||||||
@@ -393,10 +394,18 @@ describe('record', function (this: ISuite) {
|
|||||||
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
const styleSheet = <CSSStyleSheet>styleElement.sheet;
|
||||||
styleSheet.insertRule('body { background: #000; }');
|
styleSheet.insertRule('body { background: #000; }');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// should be ignored
|
||||||
(styleSheet.cssRules[0] as CSSStyleRule).style.setProperty(
|
(styleSheet.cssRules[0] as CSSStyleRule).style.setProperty(
|
||||||
'color',
|
'color',
|
||||||
'green',
|
'green',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// should be captured because we did not block it
|
||||||
|
(styleSheet.cssRules[0] as CSSStyleRule).style.setProperty(
|
||||||
|
'border-color',
|
||||||
|
'green',
|
||||||
|
);
|
||||||
|
|
||||||
(styleSheet.cssRules[0] as CSSStyleRule).style.removeProperty(
|
(styleSheet.cssRules[0] as CSSStyleRule).style.removeProperty(
|
||||||
'background',
|
'background',
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user