Skip to content

Commit fe88c35

Browse files
committed
apply text mask settings to inputs #1096
1 parent 07ac5c9 commit fe88c35

File tree

8 files changed

+737
-4
lines changed

8 files changed

+737
-4
lines changed

packages/rrweb-snapshot/src/snapshot.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ export function needMaskingText(
318318
? (node as HTMLElement)
319319
: node.parentElement;
320320
if (el === null) return false;
321+
if (maskTextSelector === '*') return true;
321322
if (typeof maskTextClass === 'string') {
322323
if (checkAncestors) {
323324
if (el.closest(`.${maskTextClass}`)) return true;
@@ -500,11 +501,14 @@ function serializeNode(
500501
keepIframeSrcFn,
501502
newlyAddedElement,
502503
rootId,
504+
needsMask,
503505
});
504506
case n.TEXT_NODE:
505507
return serializeTextNode(n as Text, {
506508
needsMask,
507509
maskTextFn,
510+
maskInputOptions,
511+
maskInputFn,
508512
rootId,
509513
});
510514
case n.CDATA_SECTION_NODE:
@@ -535,16 +539,20 @@ function serializeTextNode(
535539
options: {
536540
needsMask: boolean | undefined;
537541
maskTextFn: MaskTextFn | undefined;
542+
maskInputOptions: MaskInputOptions;
543+
maskInputFn: MaskInputFn | undefined;
538544
rootId: number | undefined;
539545
},
540546
): serializedNode {
541-
const { needsMask, maskTextFn, rootId } = options;
547+
const { needsMask, maskTextFn, maskInputOptions, maskInputFn, rootId } =
548+
options;
542549
// The parent node may not be a html element which has a tagName attribute.
543550
// So just let it be undefined which is ok in this use case.
544551
const parentTagName = n.parentNode && (n.parentNode as HTMLElement).tagName;
545552
let textContent = n.textContent;
546553
const isStyle = parentTagName === 'STYLE' ? true : undefined;
547554
const isScript = parentTagName === 'SCRIPT' ? true : undefined;
555+
const isTextarea = parentTagName === 'TEXTAREA' ? true : undefined;
548556
if (isStyle && textContent) {
549557
try {
550558
// try to read style sheet
@@ -574,6 +582,11 @@ function serializeTextNode(
574582
? maskTextFn(textContent, n.parentElement)
575583
: textContent.replace(/[\S]/g, '*');
576584
}
585+
if (isTextarea && textContent && maskInputOptions.textarea) {
586+
textContent = maskInputFn
587+
? maskInputFn(textContent, n.parentNode as HTMLElement)
588+
: textContent.replace(/[\S]/g, '*');
589+
}
577590

578591
return {
579592
type: NodeType.Text,
@@ -601,6 +614,7 @@ function serializeElementNode(
601614
*/
602615
newlyAddedElement?: boolean;
603616
rootId: number | undefined;
617+
needsMask?: boolean;
604618
},
605619
): serializedNode | false {
606620
const {
@@ -616,6 +630,7 @@ function serializeElementNode(
616630
keepIframeSrcFn,
617631
newlyAddedElement = false,
618632
rootId,
633+
needsMask,
619634
} = options;
620635
const needBlock = _isBlockedElement(n, blockClass, blockSelector);
621636
const tagName = getValidTagName(n);
@@ -673,13 +688,15 @@ function serializeElementNode(
673688
value
674689
) {
675690
const type = getInputType(n);
691+
676692
attributes.value = maskInputValue({
677693
element: n,
678694
type,
679695
tagName,
680696
value,
681697
maskInputOptions,
682698
maskInputFn,
699+
needsMask,
683700
});
684701
} else if (checked) {
685702
attributes.checked = checked;
@@ -1226,7 +1243,7 @@ function snapshot(
12261243
inlineStylesheet?: boolean;
12271244
maskAllInputs?: boolean | MaskInputOptions;
12281245
maskTextFn?: MaskTextFn;
1229-
maskInputFn?: MaskTextFn;
1246+
maskInputFn?: MaskInputFn;
12301247
slimDOM?: 'all' | boolean | SlimDOMOptions;
12311248
dataURLOptions?: DataURLOptions;
12321249
inlineImages?: boolean;

packages/rrweb-snapshot/src/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,20 +219,23 @@ export function maskInputValue({
219219
type,
220220
value,
221221
maskInputFn,
222+
needsMask,
222223
}: {
223224
element: HTMLElement;
224225
maskInputOptions: MaskInputOptions;
225226
tagName: string;
226227
type: string | null;
227228
value: string | null;
228229
maskInputFn?: MaskInputFn;
230+
needsMask?: boolean;
229231
}): string {
230232
let text = value || '';
231233
const actualType = type && toLowerCase(type);
232234

233235
if (
234236
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
235-
(actualType && maskInputOptions[actualType as keyof MaskInputOptions])
237+
(actualType && maskInputOptions[actualType as keyof MaskInputOptions]) ||
238+
needsMask
236239
) {
237240
if (maskInputFn) {
238241
text = maskInputFn(text, element);

packages/rrweb/src/record/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ function record<T = eventWithTime>(
375375
maskTextSelector,
376376
inlineStylesheet,
377377
maskAllInputs: maskInputOptions,
378+
maskInputFn,
378379
maskTextFn,
379380
slimDOM: slimDOMOptions,
380381
dataURLOptions,

packages/rrweb/src/record/mutation.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,13 +534,21 @@ export default class MutationBuffer {
534534
if (attributeName === 'value') {
535535
const type = getInputType(target);
536536

537+
const needsMask = needMaskingText(
538+
m.target,
539+
this.maskTextClass,
540+
this.maskTextSelector,
541+
true,
542+
);
543+
537544
value = maskInputValue({
538545
element: target,
539546
maskInputOptions: this.maskInputOptions,
540547
tagName: target.tagName,
541548
type,
542549
value,
543550
maskInputFn: this.maskInputFn,
551+
needsMask,
544552
});
545553
}
546554
if (

packages/rrweb/src/record/observer.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Mirror,
55
getInputType,
66
toLowerCase,
7+
needMaskingText,
78
} from 'rrweb-snapshot';
89
import type { FontFaceSet } from 'css-font-loading-module';
910
import {
@@ -420,6 +421,8 @@ function initInputObserver({
420421
maskInputFn,
421422
sampling,
422423
userTriggeredOnInput,
424+
maskTextClass,
425+
maskTextSelector,
423426
}: observerParam): listenerHandler {
424427
function eventHandler(event: Event) {
425428
let target = getEventTarget(event) as HTMLElement | null;
@@ -452,11 +455,19 @@ function initInputObserver({
452455
let isChecked = false;
453456
const type: Lowercase<string> = getInputType(target) || '';
454457

458+
const needsMask = needMaskingText(
459+
target as Node,
460+
maskTextClass,
461+
maskTextSelector,
462+
true,
463+
);
464+
455465
if (type === 'radio' || type === 'checkbox') {
456466
isChecked = (target as HTMLInputElement).checked;
457467
} else if (
458468
maskInputOptions[tagName.toLowerCase() as keyof MaskInputOptions] ||
459-
maskInputOptions[type as keyof MaskInputOptions]
469+
maskInputOptions[type as keyof MaskInputOptions] ||
470+
needsMask
460471
) {
461472
text = maskInputValue({
462473
element: target,
@@ -465,6 +476,7 @@ function initInputObserver({
465476
type,
466477
value: text,
467478
maskInputFn,
479+
needsMask,
468480
});
469481
}
470482
cbWithDedup(

0 commit comments

Comments
 (0)