feat(rrweb): add adaptCssInTextMutations player config option - #1917
Open
creasty wants to merge 1 commit into
Open
feat(rrweb): add adaptCssInTextMutations player config option#1917creasty wants to merge 1 commit into
adaptCssInTextMutations player config option#1917creasty wants to merge 1 commit into
Conversation
The replayer rewrites CSS for replay (`:hover` -> `.\:hover`, `max-device-width` -> `max-width`) when applying a text mutation to a child of a `<style>` element. The rewrite is a postcss parse of the whole value, so a consumer that replays a stylesheet built up over many text mutations pays it once per mutation on the accumulated CSS, which is quadratic in the number of mutations. This adds `adaptCssInTextMutations` (default `true`, so existing behaviour is unchanged) so such a consumer can pass already-adapted values and skip the repeated parse. Nodes added via `adds` are unaffected and always rewritten, because their values come straight from the recording.
🦋 Changeset detectedLatest commit: f1f3284 The changes in this PR will be included in the next version bump. This PR includes changesets to release 22 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
When the replayer applies a text mutation to a child of a
<style>element, itrewrites the value for replay (
:hover→.\:hover,max-device-width→max-width):https://github.com/rrweb-io/rrweb/blob/main/packages/rrweb/src/replay/index.ts#L1761-L1766
That is correct, and it fixed a real gap — before #1431 / #1437 the rewrite was
only applied to nodes added via
adds, so a stylesheet updated through textmutations lost its
:hoverrules on replay.The cost, though, is a postcss parse of the whole value, once per mutation.
That is fine when each mutation replaces a small stylesheet. It is not fine when
a consumer replays a stylesheet that is built up over many text mutations,
because each mutation then carries the accumulated CSS and the total work is
quadratic in the number of mutations.
We hit this in a session-replay tool for an app whose CSS-in-JS runtime appends
one rule at a time. To keep seeking fast we pre-process the recording so that the
rules for one
<style>are folded into a single text node, and the additionsafter the first become text mutations carrying the CSS accumulated so far. That
turns ~1250 stylesheet child insertions (each of which makes the browser reparse
the whole sheet) into one, which is a large win — but the replayer then parses
the accumulated CSS ~1250 times.
Measured in Chromium on a synthetic recording with 1251 rules in one
<style>(124 KB of CSS in total, 14k DOM nodes, 175 s), median of 3:
The 9 s is entirely postcss. The per-
Replayercache inadaptCssForReplaymakes later seeks cheap, but the first pass over a stretch of unvisited events
blocks the main thread for ~9 s.
Rewriting per rule and concatenating produces the same result and is O(n) — the
two plugins in
adaptCssForReplaywork per rule, so processing each rule andjoining is equivalent to processing the join. There is currently no way for a
consumer to say "I already did this", and the cache is private to
Replayer, soit cannot be pre-populated either.
Change
Adds
adaptCssInTextMutationstoplayerConfig, defaulting totrue— existingbehaviour is unchanged, and nothing needs to be updated by existing consumers.
Setting it to
falsemeans "the text mutation values I supply are alreadyreplay-ready".
Only text mutations are covered. Nodes added via
addsare always rewritten,because their values come straight from the recording.
With the option set to
falseand the rewrite done per rule on our side, thesame measurement lands at 63 ms for the first sync apply and 10–55 ms for
backward seeks — i.e. at the floor above — and the CSS that ends up in the DOM is
byte-for-byte identical to what the replayer produces on its own (129,217 bytes,
250
:hover, 125.\:hover).Tests
Two tests in
packages/rrweb/test/replayer.test.tsover a new fixture(
test/events/style-text-mutation-hover.ts) — a<style>whose text node isreplaced by a mutation carrying a
:hoverrule:adaptCssInTextMutations: false→ it is passed through unchangedBoth pass. Note that 6 snapshot tests in
replayer.test.tsfail in myenvironment both with and without this change (they look Chromium-version
dependent), so those failures are unrelated.
A changeset is included (
minorforrrweband@rrweb/replay).