From dc20cd45cc63058325784444af6bd32ed2cace48 Mon Sep 17 00:00:00 2001 From: Eoghan Murray Date: Fri, 10 Jan 2025 18:27:13 +0000 Subject: [PATCH] Fix performance of splitCssText (#1615) - Fix bug where the right split point was not being picked for the 3rd section onwards - Fix that it wasn't able to find a split when both halves were identical - Add test to put splitCssText through it's paces with a large file - Introduce a limit on the iteration which causes the 'efficiently' test to fail - Fix poor 'crawling' performance in the 'matching' algorithm for large css texts - e.g. for a (doubled) benchmark.css, we were running `normalizeCssText` 9480 times before `k` got to the right place - Further algorithm efficiency: need to take larger jumps; use the scaling factor to make better guess at how big a jump to make --- .changeset/efficiently-splitCssText-1603.md | 6 ++ packages/rrweb-snapshot/src/utils.ts | 65 +++++++++++++++---- packages/rrweb-snapshot/test/css.test.ts | 58 +++++++++++++++++ .../test/__snapshots__/replayer.test.ts.snap | 4 +- .../test/events/style-sheet-rule-events.ts | 2 +- 5 files changed, 121 insertions(+), 14 deletions(-) create mode 100644 .changeset/efficiently-splitCssText-1603.md diff --git a/.changeset/efficiently-splitCssText-1603.md b/.changeset/efficiently-splitCssText-1603.md new file mode 100644 index 0000000000..57c6d5e6c4 --- /dev/null +++ b/.changeset/efficiently-splitCssText-1603.md @@ -0,0 +1,6 @@ +--- +"rrweb-snapshot": patch +"rrweb": patch +--- + +Improve performance of splitCssText for '; + const style = document.querySelector('style'); + if (style) { + style.append(textContent); + style.append(textContent); + + const expected = [textContent, textContent]; + const browserSheet = expected.join(''); + expect(splitCssText(browserSheet, style)).toEqual(expected); + + style.append(textContent); + const expected3 = [textContent, textContent, textContent]; + const browserSheet3 = expected3.join(''); + expect(splitCssText(browserSheet3, style)).toEqual(expected3); + } + }); + it('finds css textElement splits correctly when vendor prefixed rules have been removed', () => { const style = JSDOM.fragment(``).querySelector('style'); if (style) { @@ -169,6 +199,34 @@ describe('css splitter', () => { expect(splitCssText(browserSheet, style)).toEqual(expected); } }); + + it('efficiently finds split points in large files', () => { + const cssText = fs.readFileSync( + path.resolve(__dirname, './css/benchmark.css'), + 'utf8', + ); + + const parts = cssText.split('}'); + const sections = []; + for (let i = 0; i < parts.length - 1; i++) { + if (i % 100 === 0) { + sections.push(parts[i] + '}'); + } else { + sections[sections.length - 1] += parts[i] + '}'; + } + } + sections[sections.length - 1] += parts[parts.length - 1]; + + expect(cssText.length).toEqual(sections.join('').length); + + const style = JSDOM.fragment(``).querySelector('style'); + if (style) { + sections.forEach((section) => { + style.appendChild(JSDOM.fragment(section)); + }); + } + expect(splitCssText(cssText, style)).toEqual(sections); + }); }); describe('applyCssSplits css rejoiner', function () { diff --git a/packages/rrweb/test/__snapshots__/replayer.test.ts.snap b/packages/rrweb/test/__snapshots__/replayer.test.ts.snap index c4edd6d540..4d4711ba63 100644 --- a/packages/rrweb/test/__snapshots__/replayer.test.ts.snap +++ b/packages/rrweb/test/__snapshots__/replayer.test.ts.snap @@ -72,7 +72,7 @@ file-cid-3 .css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); } -.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; } +.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease-in 0.1s; } .css-added-at-1000-deleted-at-2500 { display: flex; flex-direction: column; min-width: 60rem; min-height: 100vh; color: blue; } @@ -152,7 +152,7 @@ file-cid-3 .css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); } -.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; } +.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease-in 0.1s; } .css-added-at-200.alt2 { padding-left: 4rem; } " diff --git a/packages/rrweb/test/events/style-sheet-rule-events.ts b/packages/rrweb/test/events/style-sheet-rule-events.ts index 577c0caf41..4835961d32 100644 --- a/packages/rrweb/test/events/style-sheet-rule-events.ts +++ b/packages/rrweb/test/events/style-sheet-rule-events.ts @@ -65,7 +65,7 @@ const events: eventWithTime[] = [ tagName: 'style', attributes: { _cssText: - '.css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease 0s; }.css-added-at-200.alt2 { padding-left: 4rem; }', + '.css-added-at-200 { position: fixed; top: 0px; right: 0px; left: 4rem; z-index: 15; flex-shrink: 0; height: 0.25rem; overflow: hidden; background-color: rgb(17, 171, 209); }.css-added-at-200.alt { height: 0.25rem; background-color: rgb(190, 232, 242); opacity: 0; transition: opacity 0.5s ease-in 0.1s; }.css-added-at-200.alt2 { padding-left: 4rem; }', 'data-emotion': 'css', }, childNodes: [