From 6d58442965b3055a3e3b49454c9e13155b0762f8 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Sun, 16 Jul 2023 16:54:35 +0100 Subject: [PATCH] Don't use the CSSOM when there's `var()` present as it fails badly https://github.com/rrweb-io/rrweb/pull/1246 --- packages/rrweb/src/record/mutation.ts | 5 +- .../__snapshots__/integration.test.ts.snap | 128 ++++++++++++++++++ packages/rrweb/test/integration.test.ts | 23 ++++ 3 files changed, 155 insertions(+), 1 deletion(-) diff --git a/packages/rrweb/src/record/mutation.ts b/packages/rrweb/src/record/mutation.ts index 39a6107635..e587c62d31 100644 --- a/packages/rrweb/src/record/mutation.ts +++ b/packages/rrweb/src/record/mutation.ts @@ -562,7 +562,10 @@ export default class MutationBuffer { target.setAttribute('data-rr-is-password', 'true'); } - if (attributeName === 'style') { + if (attributeName === 'style' && + (value as string).indexOf('var(') === -1 && + typeof item.attributes.style !== 'string' + ) { const old = unattachedDoc.createElement('span'); if (m.oldValue) { old.setAttribute('style', m.oldValue); diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index f244948b67..0b0cd72862 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -3391,6 +3391,134 @@ exports[`record integration tests can record node mutations 1`] = ` ]" `; +exports[`record integration tests can record style changes compactly and preserve css var() functions 1`] = ` +"[ + { + \\"type\\": 0, + \\"data\\": {} + }, + { + \\"type\\": 1, + \\"data\\": {} + }, + { + \\"type\\": 4, + \\"data\\": { + \\"href\\": \\"about:blank\\", + \\"width\\": 1920, + \\"height\\": 1080 + } + }, + { + \\"type\\": 2, + \\"data\\": { + \\"node\\": { + \\"type\\": 0, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [], + \\"id\\": 3 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 7 + } + ], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 8 + } + ], + \\"id\\": 4 + } + ], + \\"id\\": 2 + } + ], + \\"compatMode\\": \\"BackCompat\\", + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 4, + \\"attributes\\": { + \\"style\\": \\"background: var(--mystery)\\" + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [ + { + \\"id\\": 4, + \\"attributes\\": { + \\"style\\": { + \\"background-color\\": \\"black\\", + \\"background-image\\": false, + \\"background-position-x\\": false, + \\"background-position-y\\": false, + \\"background-size\\": false, + \\"background-repeat-x\\": false, + \\"background-repeat-y\\": false, + \\"background-attachment\\": false, + \\"background-origin\\": false, + \\"background-clip\\": false + } + } + } + ], + \\"removes\\": [], + \\"adds\\": [] + } + } +]" +`; + exports[`record integration tests can use maskInputOptions to configure which type of inputs should be masked 1`] = ` "[ { diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts index 39e32dbcd6..55171770c6 100644 --- a/packages/rrweb/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -209,6 +209,29 @@ describe('record integration tests', function (this: ISuite) { assertSnapshot(snapshots); }); + it('can record style changes compactly and preserve css var() functions', async () => { + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'blank.html'), { + waitUntil: 'networkidle0', + }); + + // goal here is to ensure var(--mystery) ends up in the mutations + await page.evaluate( + 'document.body.setAttribute("style", "background: var(--mystery)")', + ); + await page.waitForTimeout(50); + // and here that we can revert to { background: false, background-color: 'black' } format (assuming we've used string format for above) + await page.evaluate( + 'document.body.setAttribute("style", "background-color: black")', + ); + + const snapshots = (await page.evaluate( + 'window.snapshots', + )) as eventWithTime[]; + assertSnapshot(snapshots); + }); + it('can freeze mutations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank');