diff --git a/packages/rrweb-snapshot/src/rebuild.ts b/packages/rrweb-snapshot/src/rebuild.ts index 10bf067a76..e32d4ca876 100644 --- a/packages/rrweb-snapshot/src/rebuild.ts +++ b/packages/rrweb-snapshot/src/rebuild.ts @@ -62,6 +62,24 @@ function escapeRegExp(str: string) { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } +// Check if parentheses are balanced +function areParenthesesBalanced(str: string) { + let balance = 0; + + for (const char of str) { + if (char === '(') { + balance++; + } else if (char === ')') { + balance--; + if (balance < 0) { + return false; + } + } + } + + return balance === 0; // Balanced if balance is 0 +} + const HOVER_SELECTOR = /([^\\]):hover/; const HOVER_SELECTOR_GLOBAL = new RegExp(HOVER_SELECTOR.source, 'g'); export function addHoverClass(cssText: string, cache: BuildCache): string { @@ -81,7 +99,9 @@ export function addHoverClass(cssText: string, cache: BuildCache): string { if ('selectors' in rule) { (rule.selectors || []).forEach((selector: string) => { if (HOVER_SELECTOR.test(selector)) { - selectors.push(selector); + if (areParenthesesBalanced(selector)) { + selectors.push(selector); + } } }); } diff --git a/packages/rrweb-snapshot/test/rebuild.test.ts b/packages/rrweb-snapshot/test/rebuild.test.ts index 357cd2fb3c..eed13b846c 100644 --- a/packages/rrweb-snapshot/test/rebuild.test.ts +++ b/packages/rrweb-snapshot/test/rebuild.test.ts @@ -124,6 +124,11 @@ describe('rebuild', function () { expect(addHoverClass(cssText, cache)).toEqual(cssText); }); + it('check that parentheses are balanced', () => { + const cssText = '[_nghost-ng-c4172599085]:not(.fit-content).aim-select:hover:not(:disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--disabled, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--invalid, [_nghost-ng-c4172599085]:not(.fit-content).aim-select--active) { border-color: rgb(84, 84, 84); }'; + expect(addHoverClass(cssText, cache)).toEqual(cssText); + }); + // this benchmark is unreliable when run in parallel with other tests it.skip('benchmark', () => { const cssText = fs.readFileSync(