Amplitude-TypeScript icon indicating copy to clipboard operation
Amplitude-TypeScript copied to clipboard

fix(session-replay-browser): prevent broken styles while recording with more CSS-in-JS libraries

Open sullvn opened this issue 1 month ago • 1 comments

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:

  1. Session replay wraps various DOM functions for tracking
  2. 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.
  3. This logic checks for CSSStyleSheet.insertRule
  4. However, other CSS-in-JS libraries, such as stitches, also use CSSGroupingRule.insertRule
  5. 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
  6. Session replay swallows the error here, making stitches' index faulty
  7. 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
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 errorHandler to rethrow when typedError.stack includes insertRule, covering both CSSStyleSheet.insertRule and CSSGroupingRule.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.

sullvn avatar Jan 16 '26 18:01 sullvn