Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/smooth-ghosts-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rrweb/record": patch
---

fix 'CssSyntaxError: Unclosed string' for invalid css rules
16 changes: 15 additions & 1 deletion packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
* Browsers sometimes incorrectly escape `@import` on `.cssText` statements.
* This function tries to correct the escaping.
* more info: https://bugs.chromium.org/p/chromium/issues/detail?id=1472259
* @param cssImportRule

Check warning on line 83 in packages/rrweb-snapshot/src/utils.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/utils.ts#L83

[tsdoc/syntax] tsdoc-param-tag-missing-hyphen: The @param block should be followed by a parameter name and then a hyphen
* @returns `cssText` with browser inconsistencies fixed, or null if not applicable.
*/
export function escapeImportStatement(rule: CSSImportRule): string {
Expand Down Expand Up @@ -150,7 +150,9 @@
}
return importStringified;
} else {
let ruleStringified = rule.cssText;
// Removes incomprehensible empty .cssText rules
// see https://github.com/rrweb-io/rrweb/issues/1734
let ruleStringified = fixEmptyCssStyles(rule.cssText);
if (isCSSStyleRule(rule) && rule.selectorText.includes(':')) {
// Safari does not escape selectors with : properly
// see https://bugs.webkit.org/show_bug.cgi?id=184604
Expand All @@ -169,6 +171,18 @@
return cssStringified.replace(regex, '$1\\$2');
}

export function fixEmptyCssStyles(cssStringified: string) {
const quickTestRegExp = /:\s*;/;

if (quickTestRegExp.test(cssStringified)) {
Comment on lines +175 to +177
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bug is rare, so it worth to check "if original css rules even has pattern of :\s*;"

If it hasn't, then we don't need to execute regex.

This will save some CPU cycles

// Remove e.g. "border-top-style: ;"
const regex = /(?<=^|\{|;)\s*[_a-zA-Z][_a-zA-Z0-9-]*:\s*;/gm;
return cssStringified.replace(regex, '');
}

return cssStringified;
}

export function isCSSImportRule(rule: CSSRule): rule is CSSImportRule {
return 'styleSheet' in rule;
}
Expand Down Expand Up @@ -488,7 +502,7 @@
typeof childNodes[i].textContent === 'string'
) {
const textContentNorm = normalizeCssString(
childNodes[i].textContent!,

Check warning on line 505 in packages/rrweb-snapshot/src/utils.ts

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

packages/rrweb-snapshot/src/utils.ts#L505

[@typescript-eslint/no-non-null-assertion] Forbidden non-null assertion.
_testNoPxNorm,
);
const jLimit = 100; // how many iterations for the first part of searching
Expand Down
14 changes: 14 additions & 0 deletions packages/rrweb-snapshot/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
escapeImportStatement,
extractFileExtension,
fixSafariColons,
fixEmptyCssStyles,
isNodeMetaEqual,
stringifyStylesheet,
} from '../src/utils';
Expand Down Expand Up @@ -282,6 +283,19 @@ describe('utils', () => {
});
});

describe('fixEmptyCssStyles', () => {
it('removes empty css styles', () => {
const input =
'.cl { font-family: sans-serif; font-size: 13px; color: var(--bug-text); border-top-style: ; border-top-width: ; border-right-style: ; border-right-width: ; border-bottom-style: ; border-bottom-width: ; border-left-style: ; border-left-width: ; border-image-source: ; border-image-slice: ; border-image-width: ; border-image-outset: ; border-image-repeat: ; border-color: var(--bug-border); background-color: var(--bug-background-primary); }';

const out1 = fixEmptyCssStyles(input);

expect(out1).toEqual(
'.cl { font-family: sans-serif; font-size: 13px; color: var(--bug-text); border-color: var(--bug-border); background-color: var(--bug-background-primary); }',
);
});
});

describe('stringifyStylesheet', () => {
it('returns null if rules are missing', () => {
const mockSheet = {
Expand Down
Loading