Skip to content

Commit b1a1922

Browse files
mdellanocecolingm
authored andcommitted
apply text mask settings to inputs #1096
1 parent f1e6a51 commit b1a1922

File tree

9 files changed

+742
-4
lines changed

9 files changed

+742
-4
lines changed

.changeset/brave-spoons-sleep.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'rrweb-snapshot': patch
3+
'rrweb': patch
4+
---
5+
6+
text masking settings apply to inputs

packages/rrweb-snapshot/src/snapshot.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ export function needMaskingText(
321321
? (node as HTMLElement)
322322
: node.parentElement;
323323
if (el === null) return false;
324+
if (maskTextSelector === '*') return true;
324325
if (typeof maskTextClass === 'string') {
325326
if (checkAncestors) {
326327
if (el.closest(`.${maskTextClass}`)) return true;
@@ -503,11 +504,14 @@ function serializeNode(
503504
keepIframeSrcFn,
504505
newlyAddedElement,
505506
rootId,
507+
needsMask,
506508
});
507509
case n.TEXT_NODE:
508510
return serializeTextNode(n as Text, {
509511
needsMask,
510512
maskTextFn,
513+
maskInputOptions,
514+
maskInputFn,
511515
rootId,
512516
});
513517
case n.CDATA_SECTION_NODE:
@@ -538,16 +542,20 @@ function serializeTextNode(
538542
options: {
539543
needsMask: boolean | undefined;
540544
maskTextFn: MaskTextFn | undefined;
545+
maskInputOptions: MaskInputOptions;
546+
maskInputFn: MaskInputFn | undefined;
541547
rootId: number | undefined;
542548
},
543549
): serializedNode {
544-
const { needsMask, maskTextFn, rootId } = options;
550+
const { needsMask, maskTextFn, maskInputOptions, maskInputFn, rootId } =
551+
options;
545552
// The parent node may not be a html element which has a tagName attribute.
546553
// So just let it be undefined which is ok in this use case.
547554
const parentTagName = n.parentNode && (n.parentNode as HTMLElement).tagName;
548555
let textContent = n.textContent;
549556
const isStyle = parentTagName === 'STYLE' ? true : undefined;
550557
const isScript = parentTagName === 'SCRIPT' ? true : undefined;
558+
const isTextarea = parentTagName === 'TEXTAREA' ? true : undefined;
551559
if (isStyle && textContent) {
552560
try {
553561
// try to read style sheet
@@ -577,6 +585,11 @@ function serializeTextNode(
577585
? maskTextFn(textContent, n.parentElement)
578586
: textContent.replace(/[\S]/g, '*');
579587
}
588+
if (isTextarea && textContent && maskInputOptions.textarea) {
589+
textContent = maskInputFn
590+
? maskInputFn(textContent, n.parentNode as HTMLElement)
591+
: textContent.replace(/[\S]/g, '*');
592+
}
580593

581594
return {
582595
type: NodeType.Text,
@@ -604,6 +617,7 @@ function serializeElementNode(
604617
*/
605618
newlyAddedElement?: boolean;
606619
rootId: number | undefined;
620+
needsMask?: boolean;
607621
},
608622
): serializedNode | false {
609623
const {
@@ -619,6 +633,7 @@ function serializeElementNode(
619633
keepIframeSrcFn,
620634
newlyAddedElement = false,
621635
rootId,
636+
needsMask,
622637
} = options;
623638
const needBlock = _isBlockedElement(n, blockClass, blockSelector);
624639
const tagName = getValidTagName(n);
@@ -682,6 +697,7 @@ function serializeElementNode(
682697
value,
683698
maskInputOptions,
684699
maskInputFn,
700+
needsMask,
685701
});
686702
} else if (checked) {
687703
attributes.checked = checked;
@@ -1246,7 +1262,7 @@ function snapshot(
12461262
inlineStylesheet?: boolean;
12471263
maskAllInputs?: boolean | MaskInputOptions;
12481264
maskTextFn?: MaskTextFn;
1249-
maskInputFn?: MaskTextFn;
1265+
maskInputFn?: MaskInputFn;
12501266
slimDOM?: 'all' | boolean | SlimDOMOptions;
12511267
dataURLOptions?: DataURLOptions;
12521268
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
@@ -362,6 +362,7 @@ function record<T = eventWithTime>(
362362
maskTextSelector,
363363
inlineStylesheet,
364364
maskAllInputs: maskInputOptions,
365+
maskInputFn,
365366
maskTextFn,
366367
slimDOM: slimDOMOptions,
367368
dataURLOptions,

packages/rrweb/src/record/mutation.ts

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

569+
const needsMask = needMaskingText(
570+
m.target,
571+
this.maskTextClass,
572+
this.maskTextSelector,
573+
true,
574+
);
575+
569576
value = maskInputValue({
570577
element: target,
571578
maskInputOptions: this.maskInputOptions,
572579
tagName: target.tagName,
573580
type,
574581
value,
575582
maskInputFn: this.maskInputFn,
583+
needsMask,
576584
});
577585
}
578586
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)