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); });