fix(session-replay-browser): prevent broken styles while recording with more CSS-in-JS libraries
Summary
This PR fixes broken CSS styles which can occur while recording a session replay. It happens when the client is using a CSS-in-JS library to create styles, and there is a single bad CSS rule somewhere.
Session replay already has explicit support for styled-components, but it has two problems:
- Does not support other CSS-in-JS libraries which use
CSSGroupingRule.insertRule, such as stitches - The support check seems to be a Chrome-only check, without support for Safari or Firefox
This is a critical issue for my team, as we cannot use session replay at all if there's a possibility broken styles occur while recording. Even worse, the failure seems to cascade, producing more and more broken styles.
Details
After investigation and testing, I believe I have a full understanding of the problem:
- Session replay wraps various DOM functions for tracking
- Session replay has an error handler for these wrapped DOM functions. This error handler has explicit logic to rethrow errors from the inner functions, as some libraries expect these errors. Such as styled-components.
- This logic checks for
CSSStyleSheet.insertRule - However, other CSS-in-JS libraries, such as stitches, also use
CSSGroupingRule.insertRule - In the case of stitches, it maintains an index counter, and expects to see an error when there's an error, so it can skip incrementing the index
- Session replay swallows the error here, making stitches' index faulty
- So when we give stitches a bad CSS rule, an error occurs, triggering this process, and producing broken styles. This error state only worsens over time.
An example bad selector is this:
/* WRONG */
&:not:has([data-bad-selector]) {
}
/* RIGHT */
&:not(:has([data-good-selector])) {
}
The problem code in the session replay implementation:
https://github.com/amplitude/Amplitude-TypeScript/blob/752b4c0d9f22fcaa4b920b6fff09455a58f92da2/packages/session-replay-browser/src/session-replay.ts#L636-L639
As an example, here is where the problem breaks stitches:
https://github.com/stitchesjs/stitches/blob/50fd8a1adc6360340fe348a8b3ebc8b06d38e230/packages/core/src/sheet.js#L189
try {
groupingRule.insertRule(cssText, index)
++index
} catch (__) {
// do nothing and continue
}
Proposed solution
A desired solution would be one that supports both CSSStyleSheet.insertRule and CSSGroupingRule.insertRule, while also supporting the various error formats created by the different browsers.
An even better solution would be one which avoids parsing string values from return errors. However, in lieu of a bigger refactor to do so, I've examined the various returned browser errors and have come up with this simple change:
// Supports CSSStyleSheet and CSSGroupingRule within Chrome, Safari, and Firefox
if (typedError.stack?.includes('insertRule')) {
throw typedError;
}
Here's an example error in each browser for comparison:
| Chrome | Safari | Firefox |
|---|---|---|
Checklist
- [x] Does your PR title have the correct title format?
- Does your PR have a breaking change?: No
[!NOTE] Improves compatibility with CSS-in-JS during capture to prevent suppressed errors from breaking styles.
- Updates
errorHandlerto rethrow whentypedError.stackincludesinsertRule, covering bothCSSStyleSheet.insertRuleandCSSGroupingRule.insertRule- Broadens support beyond Chrome to Safari and Firefox; clarifies comments accordingly
Written by Cursor Bugbot for commit 77a9556a2d5c2845ba4d5edeacb3b1edf4479d7e. This will update automatically on new commits. Configure here.