Skip to content

Commit aa79db7

Browse files
authored
fix: Fix input.type check (#1184)
* fix: Fix input.type check Actually I noticed that `el.type` returns `text` when type is not explicitly set, so this is slightly incorrect. * fix linting * Apply formatting changes
1 parent d0fdc0f commit aa79db7

File tree

9 files changed

+298
-23
lines changed

9 files changed

+298
-23
lines changed

.changeset/nice-pugs-reply.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'rrweb': patch
3+
---
4+
5+
fix: Ensure getting the type of inputs works

packages/rrweb-snapshot/src/snapshot.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
maskInputValue,
2121
isNativeShadowDom,
2222
getCssRulesString,
23+
getInputType,
2324
} from './utils';
2425

2526
let _id = 1;
@@ -682,11 +683,7 @@ function serializeElementNode(
682683
attributes.type !== 'button' &&
683684
value
684685
) {
685-
const type: string | null = n.hasAttribute('data-rr-is-password')
686-
? 'password'
687-
: typeof attributes.type === 'string'
688-
? attributes.type.toLowerCase()
689-
: null;
686+
const type = getInputType(n);
690687
attributes.value = maskInputValue({
691688
type,
692689
tagName,

packages/rrweb-snapshot/src/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,20 @@ export function isNodeMetaEqual(a: serializedNode, b: serializedNode): boolean {
248248
);
249249
return false;
250250
}
251+
252+
/**
253+
* Get the type of an input element.
254+
* This takes care of the case where a password input is changed to a text input.
255+
* In this case, we continue to consider this of type password, in order to avoid leaking sensitive data
256+
* where passwords should be masked.
257+
*/
258+
export function getInputType(element: HTMLElement): Lowercase<string> | null {
259+
const type = (element as HTMLInputElement).type;
260+
261+
return element.hasAttribute('data-rr-is-password')
262+
? 'password'
263+
: type
264+
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
265+
(type.toLowerCase() as Lowercase<string>)
266+
: null;
267+
}

packages/rrweb/src/record/mutation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
maskInputValue,
99
Mirror,
1010
isNativeShadowDom,
11+
getInputType,
1112
} from 'rrweb-snapshot';
1213
import type { observerParam, MutationBufferParam } from '../types';
1314
import type {
@@ -29,7 +30,6 @@ import {
2930
isSerializedStylesheet,
3031
inDom,
3132
getShadowHost,
32-
getInputType,
3333
} from '../utils';
3434

3535
type DoubleLinkedListNode = {

packages/rrweb/src/record/observer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
import { MaskInputOptions, maskInputValue, Mirror } from 'rrweb-snapshot';
1+
import {
2+
MaskInputOptions,
3+
maskInputValue,
4+
Mirror,
5+
getInputType,
6+
} from 'rrweb-snapshot';
27
import type { FontFaceSet } from 'css-font-loading-module';
38
import {
49
throttle,
510
on,
611
hookSetter,
7-
getInputType,
812
getWindowScroll,
913
getWindowHeight,
1014
getWindowWidth,

packages/rrweb/src/utils.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -563,18 +563,3 @@ export function inDom(n: Node): boolean {
563563
if (!doc) return false;
564564
return doc.contains(n) || shadowHostInDom(n);
565565
}
566-
567-
/**
568-
* Get the type of an input element.
569-
* This takes care of the case where a password input is changed to a text input.
570-
* In this case, we continue to consider this of type password, in order to avoid leaking sensitive data
571-
* where passwords should be masked.
572-
*/
573-
export function getInputType(element: HTMLElement): Lowercase<string> | null {
574-
return element.hasAttribute('data-rr-is-password')
575-
? 'password'
576-
: element.hasAttribute('type')
577-
? // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/no-non-null-assertion
578-
(element.getAttribute('type')!.toLowerCase() as Lowercase<string>)
579-
: null;
580-
}

packages/rrweb/test/__snapshots__/integration.test.ts.snap

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7473,6 +7473,238 @@ exports[`record integration tests should not record input events on ignored elem
74737473
]"
74747474
`;
74757475

7476+
exports[`record integration tests should not record input values if dynamically added and maskAllInputs is true 1`] = `
7477+
"[
7478+
{
7479+
\\"type\\": 0,
7480+
\\"data\\": {}
7481+
},
7482+
{
7483+
\\"type\\": 1,
7484+
\\"data\\": {}
7485+
},
7486+
{
7487+
\\"type\\": 4,
7488+
\\"data\\": {
7489+
\\"href\\": \\"about:blank\\",
7490+
\\"width\\": 1920,
7491+
\\"height\\": 1080
7492+
}
7493+
},
7494+
{
7495+
\\"type\\": 2,
7496+
\\"data\\": {
7497+
\\"node\\": {
7498+
\\"type\\": 0,
7499+
\\"childNodes\\": [
7500+
{
7501+
\\"type\\": 1,
7502+
\\"name\\": \\"html\\",
7503+
\\"publicId\\": \\"\\",
7504+
\\"systemId\\": \\"\\",
7505+
\\"id\\": 2
7506+
},
7507+
{
7508+
\\"type\\": 2,
7509+
\\"tagName\\": \\"html\\",
7510+
\\"attributes\\": {
7511+
\\"lang\\": \\"en\\"
7512+
},
7513+
\\"childNodes\\": [
7514+
{
7515+
\\"type\\": 2,
7516+
\\"tagName\\": \\"head\\",
7517+
\\"attributes\\": {},
7518+
\\"childNodes\\": [
7519+
{
7520+
\\"type\\": 3,
7521+
\\"textContent\\": \\"\\\\n \\",
7522+
\\"id\\": 5
7523+
},
7524+
{
7525+
\\"type\\": 2,
7526+
\\"tagName\\": \\"meta\\",
7527+
\\"attributes\\": {
7528+
\\"charset\\": \\"UTF-8\\"
7529+
},
7530+
\\"childNodes\\": [],
7531+
\\"id\\": 6
7532+
},
7533+
{
7534+
\\"type\\": 3,
7535+
\\"textContent\\": \\"\\\\n \\",
7536+
\\"id\\": 7
7537+
},
7538+
{
7539+
\\"type\\": 2,
7540+
\\"tagName\\": \\"meta\\",
7541+
\\"attributes\\": {
7542+
\\"name\\": \\"viewport\\",
7543+
\\"content\\": \\"width=device-width, initial-scale=1.0\\"
7544+
},
7545+
\\"childNodes\\": [],
7546+
\\"id\\": 8
7547+
},
7548+
{
7549+
\\"type\\": 3,
7550+
\\"textContent\\": \\"\\\\n \\",
7551+
\\"id\\": 9
7552+
},
7553+
{
7554+
\\"type\\": 2,
7555+
\\"tagName\\": \\"title\\",
7556+
\\"attributes\\": {},
7557+
\\"childNodes\\": [
7558+
{
7559+
\\"type\\": 3,
7560+
\\"textContent\\": \\"Empty\\",
7561+
\\"id\\": 11
7562+
}
7563+
],
7564+
\\"id\\": 10
7565+
},
7566+
{
7567+
\\"type\\": 3,
7568+
\\"textContent\\": \\"\\\\n \\",
7569+
\\"id\\": 12
7570+
}
7571+
],
7572+
\\"id\\": 4
7573+
},
7574+
{
7575+
\\"type\\": 3,
7576+
\\"textContent\\": \\"\\\\n \\",
7577+
\\"id\\": 13
7578+
},
7579+
{
7580+
\\"type\\": 2,
7581+
\\"tagName\\": \\"body\\",
7582+
\\"attributes\\": {},
7583+
\\"childNodes\\": [
7584+
{
7585+
\\"type\\": 3,
7586+
\\"textContent\\": \\"\\\\n \\",
7587+
\\"id\\": 15
7588+
},
7589+
{
7590+
\\"type\\": 2,
7591+
\\"tagName\\": \\"div\\",
7592+
\\"attributes\\": {
7593+
\\"id\\": \\"one\\"
7594+
},
7595+
\\"childNodes\\": [],
7596+
\\"id\\": 16
7597+
},
7598+
{
7599+
\\"type\\": 3,
7600+
\\"textContent\\": \\"\\\\n \\\\n \\",
7601+
\\"id\\": 17
7602+
},
7603+
{
7604+
\\"type\\": 2,
7605+
\\"tagName\\": \\"script\\",
7606+
\\"attributes\\": {},
7607+
\\"childNodes\\": [
7608+
{
7609+
\\"type\\": 3,
7610+
\\"textContent\\": \\"SCRIPT_PLACEHOLDER\\",
7611+
\\"id\\": 19
7612+
}
7613+
],
7614+
\\"id\\": 18
7615+
},
7616+
{
7617+
\\"type\\": 3,
7618+
\\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\",
7619+
\\"id\\": 20
7620+
}
7621+
],
7622+
\\"id\\": 14
7623+
}
7624+
],
7625+
\\"id\\": 3
7626+
}
7627+
],
7628+
\\"id\\": 1
7629+
},
7630+
\\"initialOffset\\": {
7631+
\\"left\\": 0,
7632+
\\"top\\": 0
7633+
}
7634+
}
7635+
},
7636+
{
7637+
\\"type\\": 3,
7638+
\\"data\\": {
7639+
\\"source\\": 0,
7640+
\\"texts\\": [],
7641+
\\"attributes\\": [],
7642+
\\"removes\\": [],
7643+
\\"adds\\": [
7644+
{
7645+
\\"parentId\\": 14,
7646+
\\"nextId\\": 16,
7647+
\\"node\\": {
7648+
\\"type\\": 2,
7649+
\\"tagName\\": \\"input\\",
7650+
\\"attributes\\": {
7651+
\\"id\\": \\"input\\",
7652+
\\"value\\": \\"**********************\\"
7653+
},
7654+
\\"childNodes\\": [],
7655+
\\"id\\": 21
7656+
}
7657+
}
7658+
]
7659+
}
7660+
},
7661+
{
7662+
\\"type\\": 3,
7663+
\\"data\\": {
7664+
\\"source\\": 5,
7665+
\\"text\\": \\"**********************\\",
7666+
\\"isChecked\\": false,
7667+
\\"id\\": 21
7668+
}
7669+
},
7670+
{
7671+
\\"type\\": 3,
7672+
\\"data\\": {
7673+
\\"source\\": 2,
7674+
\\"type\\": 5,
7675+
\\"id\\": 21
7676+
}
7677+
},
7678+
{
7679+
\\"type\\": 3,
7680+
\\"data\\": {
7681+
\\"source\\": 5,
7682+
\\"text\\": \\"***********************\\",
7683+
\\"isChecked\\": false,
7684+
\\"id\\": 21
7685+
}
7686+
},
7687+
{
7688+
\\"type\\": 3,
7689+
\\"data\\": {
7690+
\\"source\\": 5,
7691+
\\"text\\": \\"************************\\",
7692+
\\"isChecked\\": false,
7693+
\\"id\\": 21
7694+
}
7695+
},
7696+
{
7697+
\\"type\\": 3,
7698+
\\"data\\": {
7699+
\\"source\\": 5,
7700+
\\"text\\": \\"*************************\\",
7701+
\\"isChecked\\": false,
7702+
\\"id\\": 21
7703+
}
7704+
}
7705+
]"
7706+
`;
7707+
74767708
exports[`record integration tests should not record input values if maskAllInputs is enabled 1`] = `
74777709
"[
74787710
{
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Empty</title>
7+
</head>
8+
<body>
9+
<div id="one"></div>
10+
</body>
11+
</html>

packages/rrweb/test/integration.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,30 @@ describe('record integration tests', function (this: ISuite) {
455455
assertSnapshot(snapshots);
456456
});
457457

458+
it('should not record input values if dynamically added and maskAllInputs is true', async () => {
459+
const page: puppeteer.Page = await browser.newPage();
460+
await page.goto('about:blank');
461+
await page.setContent(
462+
getHtml.call(this, 'empty.html', { maskAllInputs: true }),
463+
);
464+
465+
await page.evaluate(() => {
466+
const el = document.createElement('input');
467+
el.id = 'input';
468+
el.value = 'input should be masked';
469+
470+
const nextElement = document.querySelector('#one')!;
471+
nextElement.parentNode!.insertBefore(el, nextElement);
472+
});
473+
474+
await page.type('#input', 'moo');
475+
476+
const snapshots = (await page.evaluate(
477+
'window.snapshots',
478+
)) as eventWithTime[];
479+
assertSnapshot(snapshots);
480+
});
481+
458482
it('should record webgl canvas mutations', async () => {
459483
const page: puppeteer.Page = await browser.newPage();
460484
await page.goto('about:blank');

0 commit comments

Comments
 (0)