Fix some css issues with :hover and rewrite max-device-width (#1431)

* We weren't recursing into media queries (or @supports etc.) to rewrite hover pseudoclasses

* The early return meant that the stylesWithHoverClass cache wasn't being populated if there were no hover selectors on the stylesheet

 - not committing the test, but modifying the existing 'add a hover class to a previously processed css string' as follows shows the problem:

--- a/packages/rrweb-snapshot/test/rebuild.test.ts
+++ b/packages/rrweb-snapshot/test/rebuild.test.ts
@@ -151,6 +185,7 @@ describe('rebuild', function () {
         path.resolve(__dirname, './css/benchmark.css'),
         'utf8',
       );
+      cssText = cssText.replace(/:hover/g, '');

       const start = process.hrtime();
       addHoverClass(cssText, cache);

* Replace `min-device-width` and similar with `min-width` as the former looks out at the browser viewport whereas we need it to look at the replayer iframe viewport

* Add some tests to show how the hover replacement works against selector lists. I believe these were failing in a previous version of rrweb as I had some local patches that no longer seem to be needed to handle these cases

* Update name of function to reflect that 'addHoverClass' does more than just :hover. I believe this function is only exported for the purposes of use in the tests

* Apply formatting changes

* Create rotten-spies-enjoy.md

* Apply formatting changes

* Add correct typing on `getSelectors`

* Refactor CSS interfaces to include optional rules

* Change `rules` to be non optional

---------

Co-authored-by: eoghanmurray <eoghanmurray@users.noreply.github.com>
Co-authored-by: Justin Halsall <Juice10@users.noreply.github.com>
This commit is contained in:
Eoghan Murray
2026-04-01 12:00:00 +08:00
committed by GitHub
parent 3da2652950
commit 585c5c7ac3
5 changed files with 133 additions and 57 deletions

View File

@@ -56,6 +56,11 @@ export interface Node {
};
}
export interface NodeWithRules extends Node {
/** Array of nodes with the types rule, comment and any of the at-rule types. */
rules: Array<Rule | Comment | AtRule>;
}
export interface Rule extends Node {
/** The list of selectors of the rule, split on commas. Each selector is trimmed from whitespace and comments. */
selectors?: string[];
@@ -98,13 +103,11 @@ export interface CustomMedia extends Node {
/**
* The @document at-rule.
*/
export interface Document extends Node {
export interface Document extends NodeWithRules {
/** The part following @document. */
document?: string;
/** The vendor prefix in @document, or undefined if there is none. */
vendor?: string;
/** Array of nodes with the types rule, comment and any of the at-rule types. */
rules?: Array<Rule | Comment | AtRule>;
}
/**
@@ -118,10 +121,7 @@ export interface FontFace extends Node {
/**
* The @host at-rule.
*/
export interface Host extends Node {
/** Array of nodes with the types rule, comment and any of the at-rule types. */
rules?: Array<Rule | Comment | AtRule>;
}
export type Host = NodeWithRules;
/**
* The @import at-rule.
@@ -153,11 +153,9 @@ export interface KeyFrame extends Node {
/**
* The @media at-rule.
*/
export interface Media extends Node {
export interface Media extends NodeWithRules {
/** The part following @media. */
media?: string;
/** Array of nodes with the types rule, comment and any of the at-rule types. */
rules?: Array<Rule | Comment | AtRule>;
}
/**
@@ -181,11 +179,9 @@ export interface Page extends Node {
/**
* The @supports at-rule.
*/
export interface Supports extends Node {
export interface Supports extends NodeWithRules {
/** The part following @supports. */
supports?: string;
/** Array of nodes with the types rule, comment and any of the at-rule types. */
rules?: Array<Rule | Comment | AtRule>;
}
/** All at-rules. */
@@ -205,10 +201,8 @@ export type AtRule =
/**
* A collection of rules
*/
export interface StyleRules {
export interface StyleRules extends NodeWithRules {
source?: string;
/** Array of nodes with the types rule, comment and any of the at-rule types. */
rules: Array<Rule | Comment | AtRule>;
/** Array of Errors. Errors collected during parsing when option silent is true. */
parsingErrors?: ParserError[];
}
@@ -224,7 +218,7 @@ export interface Stylesheet extends Node {
// https://github.com/visionmedia/css-parse/pull/49#issuecomment-30088027
const commentre = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g;
export function parse(css: string, options: ParserOptions = {}) {
export function parse(css: string, options: ParserOptions = {}): Stylesheet {
/**
* Positional.
*/
@@ -882,7 +876,7 @@ function trim(str: string) {
* Adds non-enumerable parent node reference to each node.
*/
function addParent(obj: Stylesheet, parent?: Stylesheet) {
function addParent(obj: Stylesheet, parent?: Stylesheet): Stylesheet {
const isNode = obj && typeof obj.type === 'string';
const childParent = isNode ? obj : parent;