From 9f6fa2e3ad8d6acaf72489c67980f8810e52ffcf Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 5 Apr 2024 17:04:01 +0100 Subject: [PATCH 01/26] Unrelated, but the presence of a blank `` element when checking the replay took me ages to debug as I thought it was something I introducedg --- packages/rrweb/src/replay/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/rrweb/src/replay/index.ts b/packages/rrweb/src/replay/index.ts index 89aa66d933..03696f385b 100644 --- a/packages/rrweb/src/replay/index.ts +++ b/packages/rrweb/src/replay/index.ts @@ -869,6 +869,9 @@ export class Replayer { 'html.rrweb-paused *, html.rrweb-paused *:before, html.rrweb-paused *:after { animation-play-state: paused !important; }', ); } + if (!injectStylesRules.length) { + return; + } if (this.usingVirtualDom) { const styleEl = this.virtualDom.createElement('style'); this.virtualDom.mirror.add( From aeaacc8f6bd6267edf3ea5652c613dd259d0ad5d Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 5 Apr 2024 17:05:36 +0100 Subject: [PATCH 02/26] Add motivating test which can be used against this PR as well as in #1417 --- .../__snapshots__/integration.test.ts.snap | 219 ++++++++++++++++++ packages/rrweb/test/html/style.html | 13 ++ packages/rrweb/test/integration.test.ts | 68 ++++++ 3 files changed, 300 insertions(+) create mode 100644 packages/rrweb/test/html/style.html diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index f349bd2669..5df126076b 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -3830,6 +3830,225 @@ exports[`record integration tests can record style changes compactly and preserv ]" `; +exports[`record integration tests can record style text mutations 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\\": 1, + \\"name\\": \\"html\\", + \\"publicId\\": \\"\\", + \\"systemId\\": \\"\\", + \\"id\\": 2 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"html\\", + \\"attributes\\": { + \\"lang\\": \\"en\\" + }, + \\"childNodes\\": [ + { + \\"type\\": 2, + \\"tagName\\": \\"head\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 5 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"charset\\": \\"UTF-8\\" + }, + \\"childNodes\\": [], + \\"id\\": 6 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 7 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"meta\\", + \\"attributes\\": { + \\"name\\": \\"viewport\\", + \\"content\\": \\"width=device-width, initial-scale=1.0\\" + }, + \\"childNodes\\": [], + \\"id\\": 8 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 9 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"title\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"style\\", + \\"id\\": 11 + } + ], + \\"id\\": 10 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 12 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"style\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"body { background-color: black; }\\", + \\"isStyle\\": true, + \\"id\\": 14 + } + ], + \\"id\\": 13 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 15 + } + ], + \\"id\\": 4 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\", + \\"id\\": 16 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"body\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\", + \\"id\\": 18 + }, + { + \\"type\\": 2, + \\"tagName\\": \\"script\\", + \\"attributes\\": {}, + \\"childNodes\\": [ + { + \\"type\\": 3, + \\"textContent\\": \\"SCRIPT_PLACEHOLDER\\", + \\"id\\": 20 + } + ], + \\"id\\": 19 + }, + { + \\"type\\": 3, + \\"textContent\\": \\"\\\\n \\\\n \\\\n\\\\n\\", + \\"id\\": 21 + } + ], + \\"id\\": 17 + } + ], + \\"id\\": 3 + } + ], + \\"id\\": 1 + }, + \\"initialOffset\\": { + \\"left\\": 0, + \\"top\\": 0 + } + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [ + { + \\"parentId\\": 13, + \\"nextId\\": null, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"body { background-color: darkgreen; }\\", + \\"isStyle\\": true, + \\"id\\": 22 + } + } + ] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [ + { + \\"id\\": 22, + \\"value\\": \\"body { background-color: purple; }\\" + } + ], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [] + } + }, + { + \\"type\\": 3, + \\"data\\": { + \\"source\\": 0, + \\"texts\\": [ + { + \\"id\\": 14, + \\"value\\": \\"\\\\n body { background-color: black !important; }\\\\n \\" + } + ], + \\"attributes\\": [], + \\"removes\\": [], + \\"adds\\": [] + } + } +]" +`; + exports[`record integration tests can record textarea mutations correctly 1`] = ` "[ { diff --git a/packages/rrweb/test/html/style.html b/packages/rrweb/test/html/style.html new file mode 100644 index 0000000000..0cffb2e516 --- /dev/null +++ b/packages/rrweb/test/html/style.html @@ -0,0 +1,13 @@ + + + + + + style + + + + + diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts index 5ce9b76469..bdde2c3e0a 100644 --- a/packages/rrweb/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -170,6 +170,74 @@ describe('record integration tests', function (this: ISuite) { ]); }); + it('can record style text mutations', async () => { + // TODO: we could get a lot more elaborate here with mixed textContent and insertRule mutations + const page: puppeteer.Page = await browser.newPage(); + await page.goto('about:blank'); + await page.setContent(getHtml.call(this, 'style.html')); + + await waitForRAF(page); // ensure mutations aren't included in fullsnapshot + + await page.evaluate(() => { + let styleEl = document.querySelector('style'); + if (styleEl) { + styleEl.append( + document.createTextNode('body { background-color: darkgreen; }'), + ); + } + }); + await page.waitForTimeout(5); + await page.evaluate(() => { + let styleEl = document.querySelector('style'); + if (styleEl) { + styleEl.childNodes.forEach((cn) => { + if (cn.textContent) { + cn.textContent = cn.textContent.replace('darkgreen', 'purple'); + } + }); + } + }); + await page.waitForTimeout(5); + await page.evaluate(() => { + let styleEl = document.querySelector('style'); + if (styleEl) { + styleEl.childNodes.forEach((cn) => { + if (cn.textContent) { + cn.textContent = cn.textContent.replace( + 'black', + 'black !important', + ); + } + }); + } + }); + + const snapshots = (await page.evaluate( + 'window.snapshots', + )) as eventWithTime[]; + + // following ensures that the ./rel url has been absolutized (in a mutation) + assertSnapshot(snapshots); + + // check after each mutation and text input + const replayStyleValues = await page.evaluate(` + const { Replayer } = rrweb; + const replayer = new Replayer(window.snapshots); + const vals = []; + window.snapshots.filter((e)=>e.data.attributes || e.data.source === 5).forEach((e)=>{ + replayer.pause((e.timestamp - window.snapshots[0].timestamp)+1); + vals.push(getComputedStyle(replayer.iframe.contentDocument.querySelector('body'))['background-color']); +}); + vals; +`); + + expect(replayStyleValues).toEqual([ + 'rgb(0, 100, 0)', // darkgreen + 'rgb(128, 0, 128)', // purple + 'rgb(0, 0, 0)', // black !important + ]); + }); + it('can record childList mutations', async () => { const page: puppeteer.Page = await browser.newPage(); await page.goto('about:blank'); From 56b6d7e19036f8f2ba96c47d1910b9435157a941 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 12 Apr 2024 17:32:57 +0100 Subject: [PATCH 03/26] Recognize that snapshot.ts::serializeTextNode does important work for mutations with `absoluteToStylesheet` - I had accidentally removed that with initial work --- .../test/__snapshots__/integration.test.ts.snap | 14 ++++++++++++-- packages/rrweb/test/integration.test.ts | 7 ++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index 5df126076b..2916717e14 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -4008,10 +4008,20 @@ exports[`record integration tests can record style text mutations 1`] = ` \\"nextId\\": null, \\"node\\": { \\"type\\": 3, - \\"textContent\\": \\"body { background-color: darkgreen; }\\", + \\"textContent\\": \\".absolutify { background-image: url(\\\\\\"http://localhost:3030/rel\\\\\\"); }\\", \\"isStyle\\": true, \\"id\\": 22 } + }, + { + \\"parentId\\": 13, + \\"nextId\\": 22, + \\"node\\": { + \\"type\\": 3, + \\"textContent\\": \\"body { background-color: darkgreen; }\\", + \\"isStyle\\": true, + \\"id\\": 23 + } } ] } @@ -4022,7 +4032,7 @@ exports[`record integration tests can record style text mutations 1`] = ` \\"source\\": 0, \\"texts\\": [ { - \\"id\\": 22, + \\"id\\": 23, \\"value\\": \\"body { background-color: purple; }\\" } ], diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts index bdde2c3e0a..53a6e973d4 100644 --- a/packages/rrweb/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -173,7 +173,7 @@ describe('record integration tests', function (this: ISuite) { it('can record style text mutations', async () => { // TODO: we could get a lot more elaborate here with mixed textContent and insertRule mutations const page: puppeteer.Page = await browser.newPage(); - await page.goto('about:blank'); + await page.goto(`${serverURL}/html`); await page.setContent(getHtml.call(this, 'style.html')); await waitForRAF(page); // ensure mutations aren't included in fullsnapshot @@ -184,6 +184,11 @@ describe('record integration tests', function (this: ISuite) { styleEl.append( document.createTextNode('body { background-color: darkgreen; }'), ); + styleEl.append( + document.createTextNode( + '.absolutify { background-image: url("./rel"); }', + ), + ); } }); await page.waitForTimeout(5); From b2e432eec9e5a67c0439037213ba387be1e95ae9 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 12 Apr 2024 17:33:19 +0100 Subject: [PATCH 04/26] Prep PR for async `); styleEl.sheet?.insertRule('section { color: blue; }'); - expect(serializeNode(styleEl.childNodes[0])).toMatchObject({ - isStyle: true, + expect(serializeNode(styleEl)).toMatchObject({ rootId: undefined, - textContent: 'section {color: blue;}body {color: red;}', - type: 3, + attributes: { + _cssText: 'section {color: blue;}body {color: red;}', + }, + type: 2, }); }); - it('should serialize individual text nodes on stylesheets with multiple child nodes', () => { + it('should serialize all rules on stylesheets with mix of insertion type', () => { const styleEl = render(``); + styleEl.sheet?.insertRule('section.lost { color: unseeable; }'); // browser throws this away after append styleEl.append(document.createTextNode('section { color: blue; }')); - expect(serializeNode(styleEl.childNodes[1])).toMatchObject({ - isStyle: true, + styleEl.sheet?.insertRule('section.working { color: pink; }'); + expect(serializeNode(styleEl)).toMatchObject({ rootId: undefined, - textContent: 'section { color: blue; }', - type: 3, + attributes: { + _cssText: + 'section.working {color: pink;}body {color: red;}section {color: blue;}', + }, + type: 2, }); }); }); diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index 2916717e14..af3d56fbe7 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -3928,12 +3928,13 @@ exports[`record integration tests can record style text mutations 1`] = ` { \\"type\\": 2, \\"tagName\\": \\"style\\", - \\"attributes\\": {}, + \\"attributes\\": { + \\"_cssText\\": \\"body { background-color: black; }\\" + }, \\"childNodes\\": [ { \\"type\\": 3, - \\"textContent\\": \\"body { background-color: black; }\\", - \\"isStyle\\": true, + \\"textContent\\": \\"\\", \\"id\\": 14 } ], @@ -4009,7 +4010,6 @@ exports[`record integration tests can record style text mutations 1`] = ` \\"node\\": { \\"type\\": 3, \\"textContent\\": \\".absolutify { background-image: url(\\\\\\"http://localhost:3030/rel\\\\\\"); }\\", - \\"isStyle\\": true, \\"id\\": 22 } }, @@ -4019,7 +4019,6 @@ exports[`record integration tests can record style text mutations 1`] = ` \\"node\\": { \\"type\\": 3, \\"textContent\\": \\"body { background-color: darkgreen; }\\", - \\"isStyle\\": true, \\"id\\": 23 } } diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts index 53a6e973d4..7adcda15e2 100644 --- a/packages/rrweb/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -171,6 +171,7 @@ describe('record integration tests', function (this: ISuite) { }); it('can record style text mutations', async () => { + // This test shows that the `isStyle` attribute on textContent is not needed in a mutation // TODO: we could get a lot more elaborate here with mixed textContent and insertRule mutations const page: puppeteer.Page = await browser.newPage(); await page.goto(`${serverURL}/html`); From 05b42b51ac45a3146520c11a03b78c11b87b28ab Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 12 Apr 2024 17:34:35 +0100 Subject: [PATCH 05/26] Add a test to show how mutations on multiple text nodes within the + diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts index 7adcda15e2..fe846ce28e 100644 --- a/packages/rrweb/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -232,15 +232,28 @@ describe('record integration tests', function (this: ISuite) { const vals = []; window.snapshots.filter((e)=>e.data.attributes || e.data.source === 5).forEach((e)=>{ replayer.pause((e.timestamp - window.snapshots[0].timestamp)+1); - vals.push(getComputedStyle(replayer.iframe.contentDocument.querySelector('body'))['background-color']); -}); + let bodyStyle = getComputedStyle(replayer.iframe.contentDocument.querySelector('body')) + vals.push({ + 'background-color': bodyStyle['background-color'], + 'color': bodyStyle['color'], + }); + }); vals; `); expect(replayStyleValues).toEqual([ - 'rgb(0, 100, 0)', // darkgreen - 'rgb(128, 0, 128)', // purple - 'rgb(0, 0, 0)', // black !important + { + 'background-color': 'rgb(0, 100, 0)', // darkgreen + color: 'rgb(0, 100, 0)', // darkgreen (from style.html) + }, + { + 'background-color': 'rgb(128, 0, 128)', // purple + color: 'rgb(128, 0, 128)', // purple + }, + { + 'background-color': 'rgb(0, 0, 0)', // black !important + color: 'rgb(128, 0, 128)', // purple + }, ]); }); From 501d2e95fc3b9054c8f1eda648d18c96957d7fe1 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 12 Apr 2024 17:59:25 +0100 Subject: [PATCH 06/26] Multiple `).querySelector('style'); + if (style) { + // as authored, e.g. no spaces + style.appendChild(JSDOM.fragment('.a{background-color:red;}')); + style.appendChild(JSDOM.fragment('.a{background-color:black;}')); + + // how it is currently stringified (spaces present) + let browserSheet = '.a { background-color: red; }'; + let expectedSplit = browserSheet.length; + browserSheet += '.a { background-color: black; }'; + + // can't do this as JSDOM doesn't have style.sheet + //expect(stringifyStylesheet(style.sheet!)).toEqual(browserSheet); + + expect(findCssTextSplits(browserSheet, style)).toEqual([expectedSplit]); + } + }); +}); From 1ed670cfdb50bd822560bb07bfa2cc5fc269022a Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 12 Apr 2024 17:56:12 +0100 Subject: [PATCH 08/26] Missed this which is 'happy path' - add a test to catch catch case --- packages/rrweb-snapshot/src/rebuild.ts | 3 + .../__snapshots__/integration.test.ts.snap | 77 ++++++++++++++++--- packages/rrweb/test/html/style.html | 11 ++- packages/rrweb/test/integration.test.ts | 4 + 4 files changed, 83 insertions(+), 12 deletions(-) diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts index f604fd8447..8f3950e114 100644 --- a/packages/rrweb-snapshot/src/rebuild.ts +++ b/packages/rrweb-snapshot/src/rebuild.ts @@ -256,6 +256,9 @@ function buildNode( } } continue; + } else if (hackCss && isRemoteOrDynamicCss) { + // element or dynamic + + + diff --git a/packages/rrweb/test/integration.test.ts b/packages/rrweb/test/integration.test.ts index a12de22d79..a6c2412354 100644 --- a/packages/rrweb/test/integration.test.ts +++ b/packages/rrweb/test/integration.test.ts @@ -242,6 +242,8 @@ describe('record integration tests', function (this: ISuite) { 'color': bodyStyle['color'], }); }); + vals.push(replayer.iframe.contentDocument.getElementById('single-textContent').innerText); + vals.push(replayer.iframe.contentDocument.getElementById('empty').innerText); vals; `); @@ -258,6 +260,8 @@ describe('record integration tests', function (this: ISuite) { 'background-color': 'rgb(0, 0, 0)', // black !important color: 'rgb(255, 255, 0)', // yellow }, + 'a:hover, a.\\:hover { outline: red solid 1px; }', // has run adaptCssForReplay + 'a:hover, a.\\:hover { outline: blue solid 1px; }', // has run adaptCssForReplay ]); }); From 16edea237965d1e5eebcf5f4cd6c9a4bd3b735e4 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 12 Apr 2024 17:48:19 +0100 Subject: [PATCH 09/26] Create single-style-capture.md --- .changeset/single-style-capture.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/single-style-capture.md diff --git a/.changeset/single-style-capture.md b/.changeset/single-style-capture.md new file mode 100644 index 0000000000..96f81ed621 --- /dev/null +++ b/.changeset/single-style-capture.md @@ -0,0 +1,6 @@ +--- +"rrweb-snapshot": patch +"rrweb": patch +--- + +Edge case: Provide support for mutations on a `).querySelector('style'); + if (style) { + // as authored, with newlines + style.appendChild( + JSDOM.fragment(`.x { + -webkit-transition: all 4s ease; + content: 'try to keep a newline'; + transition: all 4s ease; +}`), + ); + style.appendChild( + JSDOM.fragment(`.y { + -moz-transition: all 5s ease; + transition: all 5s ease; +}`), + ); + // browser .rules would usually omit the vendored versions and modifies the transition value + let browserSheet = + '.x { content: "try to keep a newline"; background: red; transition: 4s; }'; + let expectedSplit = browserSheet.length; + browserSheet += '.y { transition: 5s; }'; + + // can't do this as JSDOM doesn't have style.sheet + //expect(stringifyStylesheet(style.sheet!)).toEqual(browserSheet); + + expect(findCssTextSplits(browserSheet, style)).toEqual([ + expectedSplit, + browserSheet.length, + ]); } }); }); diff --git a/packages/rrweb/test/__snapshots__/integration.test.ts.snap b/packages/rrweb/test/__snapshots__/integration.test.ts.snap index f82dec4e3e..3d451dfc02 100644 --- a/packages/rrweb/test/__snapshots__/integration.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/integration.test.ts.snap @@ -3931,7 +3931,7 @@ exports[`record integration tests can record style text mutations 1`] = ` \\"attributes\\": { \\"id\\": \\"dual-textContent\\", \\"_cssText\\": \\"body { background-color: black; }body { color: orange !important; }\\", - \\"_cssTextSplits\\": \\"33\\" + \\"_cssTextSplits\\": \\"33 67\\" }, \\"childNodes\\": [ { diff --git a/packages/rrweb/test/__snapshots__/record.test.ts.snap b/packages/rrweb/test/__snapshots__/record.test.ts.snap index ad461ec500..0ea3e7a0d0 100644 --- a/packages/rrweb/test/__snapshots__/record.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/record.test.ts.snap @@ -1388,7 +1388,7 @@ exports[`record captures inserted style text nodes correctly 1`] = ` \\"tagName\\": \\"style\\", \\"attributes\\": { \\"_cssText\\": \\"div { color: red; }section { color: blue; }\\", - \\"_cssTextSplits\\": \\"19\\" + \\"_cssTextSplits\\": \\"19 43\\" }, \\"childNodes\\": [ { From ff5e3aee36dbb248d7bdc040eaa380ce621d08db Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Tue, 7 May 2024 12:00:15 +0100 Subject: [PATCH 15/26] Refactor out `applyCssSplits` for clarity and so we can test it --- packages/rrweb-snapshot/src/rebuild.ts | 84 ++++++++++++++---------- packages/rrweb-snapshot/src/types.ts | 14 +++- packages/rrweb-snapshot/test/css.test.ts | 47 +++++++++++++ 3 files changed, 109 insertions(+), 36 deletions(-) diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts index 2744099bf9..6afcbe3b38 100644 --- a/packages/rrweb-snapshot/src/rebuild.ts +++ b/packages/rrweb-snapshot/src/rebuild.ts @@ -146,6 +146,49 @@ export function createCache(): BuildCache { }; } +/** + * undo findCssTextSplits + * (would move to utils.ts but uses `adaptCssForReplay`) + */ +export function applyCssSplits( + n: serializedElementNodeWithId, + cssText: string, + cssTextSplits: number[], + hackCss: boolean, + cache: BuildCache, +): void { + const lenCheckOk = + cssTextSplits.length && + cssTextSplits[cssTextSplits.length - 1] === cssText.length; + for (let j = n.childNodes.length - 1; j >= 0; j--) { + const scn = n.childNodes[j]; + let ix = 0; + if (cssTextSplits.length > j && j > 0) { + ix = cssTextSplits[j - 1]; + } + if (scn.type === NodeType.Text) { + let remainder = ''; + if (ix !== 0 && lenCheckOk) { + remainder = cssText.substring(0, ix); + cssText = cssText.substring(ix); + } else if (j > 1) { + continue; + } + if (hackCss) { + cssText = adaptCssForReplay(cssText, cache); + } + // id will be assigned when these child nodes are + // iterated over in buildNodeWithSN + scn.textContent = cssText; + cssText = remainder; + } + } + if (cssText.length) { + // something has gone wrong + console.warn('Leftover css content after applyCssSplits:', cssText); + } +} + /** * Normally a