diff --git a/.changeset/textarea-inner-html.md b/.changeset/textarea-inner-html.md
new file mode 100644
index 0000000000..c7f0c9df9e
--- /dev/null
+++ b/.changeset/textarea-inner-html.md
@@ -0,0 +1,5 @@
+---
+"rrweb": patch
+---
+
+#1596 Add masking for innerText mutations on textarea elements
diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts
index 42170b4940..08e927a98f 100644
--- a/packages/rrweb/src/record/mutation.ts
+++ b/packages/rrweb/src/record/mutation.ts
@@ -533,10 +533,18 @@ export default class MutationBuffer {
this.attributes.push(item);
this.attributeMap.set(textarea, item);
}
- item.attributes.value = Array.from(
+ const value = Array.from(
dom.childNodes(textarea),
(cn) => dom.textContent(cn) || '',
).join('');
+ item.attributes.value = maskInputValue({
+ element: textarea,
+ maskInputOptions: this.maskInputOptions,
+ tagName: textarea.tagName,
+ type: getInputType(textarea),
+ value,
+ maskInputFn: this.maskInputFn,
+ });
};
private processMutation = (m: mutationRecord) => {
diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
index 4fa6c2f35b..738f2fe8e2 100644
--- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap
+++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap
@@ -9964,6 +9964,21 @@ exports[`record integration tests > should not record input values if dynamicall
{
\\"parentId\\": 14,
\\"nextId\\": 16,
+ \\"node\\": {
+ \\"type\\": 2,
+ \\"tagName\\": \\"textarea\\",
+ \\"attributes\\": {
+ \\"id\\": \\"textarea\\",
+ \\"size\\": \\"50\\",
+ \\"value\\": \\"*************************\\"
+ },
+ \\"childNodes\\": [],
+ \\"id\\": 21
+ }
+ },
+ {
+ \\"parentId\\": 14,
+ \\"nextId\\": 21,
\\"node\\": {
\\"type\\": 2,
\\"tagName\\": \\"input\\",
@@ -9973,7 +9988,7 @@ exports[`record integration tests > should not record input values if dynamicall
\\"value\\": \\"**********************\\"
},
\\"childNodes\\": [],
- \\"id\\": 21
+ \\"id\\": 22
}
}
]
@@ -9985,6 +10000,15 @@ exports[`record integration tests > should not record input values if dynamicall
\\"source\\": 5,
\\"text\\": \\"**********************\\",
\\"isChecked\\": false,
+ \\"id\\": 22
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 5,
+ \\"text\\": \\"*************************\\",
+ \\"isChecked\\": false,
\\"id\\": 21
}
},
@@ -9993,7 +10017,7 @@ exports[`record integration tests > should not record input values if dynamicall
\\"data\\": {
\\"source\\": 2,
\\"type\\": 5,
- \\"id\\": 21
+ \\"id\\": 22
}
},
{
@@ -10002,7 +10026,7 @@ exports[`record integration tests > should not record input values if dynamicall
\\"source\\": 5,
\\"text\\": \\"***********************\\",
\\"isChecked\\": false,
- \\"id\\": 21
+ \\"id\\": 22
}
},
{
@@ -10011,7 +10035,7 @@ exports[`record integration tests > should not record input values if dynamicall
\\"source\\": 5,
\\"text\\": \\"************************\\",
\\"isChecked\\": false,
- \\"id\\": 21
+ \\"id\\": 22
}
},
{
@@ -10020,8 +10044,109 @@ exports[`record integration tests > should not record input values if dynamicall
\\"source\\": 5,
\\"text\\": \\"*************************\\",
\\"isChecked\\": false,
+ \\"id\\": 22
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 2,
+ \\"type\\": 6,
+ \\"id\\": 22
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 2,
+ \\"type\\": 5,
+ \\"id\\": 21
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 5,
+ \\"text\\": \\"**************************\\",
+ \\"isChecked\\": false,
+ \\"id\\": 21
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 5,
+ \\"text\\": \\"***************************\\",
+ \\"isChecked\\": false,
\\"id\\": 21
}
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 5,
+ \\"text\\": \\"****************************\\",
+ \\"isChecked\\": false,
+ \\"id\\": 21
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 5,
+ \\"text\\": \\"**********************************************\\",
+ \\"isChecked\\": false,
+ \\"id\\": 22
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 5,
+ \\"text\\": \\"*************************************************\\",
+ \\"isChecked\\": false,
+ \\"id\\": 21
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 0,
+ \\"texts\\": [],
+ \\"attributes\\": [
+ {
+ \\"id\\": 22,
+ \\"attributes\\": {
+ \\"value\\": \\"**********************************************************************************************\\"
+ }
+ },
+ {
+ \\"id\\": 21,
+ \\"attributes\\": {
+ \\"value\\": \\"*************************************************************************************************\\"
+ }
+ }
+ ],
+ \\"removes\\": [],
+ \\"adds\\": []
+ }
+ },
+ {
+ \\"type\\": 3,
+ \\"data\\": {
+ \\"source\\": 0,
+ \\"texts\\": [],
+ \\"attributes\\": [
+ {
+ \\"id\\": 21,
+ \\"attributes\\": {
+ \\"value\\": \\"****************************************************************\\"
+ }
+ }
+ ],
+ \\"removes\\": [],
+ \\"adds\\": []
+ }
}
]"
`;
diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts
index bc244620e0..cc32916060 100644
--- a/packages/rrweb/test/integration.test.ts
+++ b/packages/rrweb/test/integration.test.ts
@@ -783,9 +783,46 @@ describe('record integration tests', function (this: ISuite) {
const nextElement = document.querySelector('#one')!;
nextElement.parentNode!.insertBefore(el, nextElement);
+
+ const ta = document.createElement('textarea');
+ ta.size = 50;
+ ta.id = 'textarea';
+ ta.setAttribute('size', '50');
+ ta.value = 'textarea should be masked';
+
+ nextElement.parentNode!.insertBefore(ta, nextElement);
});
await page.type('#input', 'moo');
+ await page.type('#textarea', 'boo');
+
+ await page.evaluate(() => {
+ const el = document.querySelector('input');
+ el.value = 'input attribute mutation should also be masked';
+
+ const ta = document.querySelector('textarea');
+ ta.value = 'textarea attribute mutation should also be masked';
+ });
+
+ await page.evaluate(() => {
+ const el = document.querySelector('input');
+ el.setAttribute(
+ 'value',
+ "input attribute mutation should also be masked (even though the new value doesn't take effect)",
+ );
+
+ const ta = document.querySelector('textarea');
+ ta.setAttribute(
+ 'value',
+ "textarea attribute mutation should also be masked (even though the new value doesn't take effect)",
+ );
+ });
+
+ await page.evaluate(() => {
+ const ta = document.querySelector('textarea');
+ ta.innerText =
+ 'textarea attribute mutation via innerText should also be masked ';
+ });
await assertSnapshot(page);
});