Skip to content

Commit

Permalink
Merge pull request #14 from amplitude/pr-1401-restore
Browse files Browse the repository at this point in the history
fix(rrweb): fix CSS parser for AddHover
  • Loading branch information
jxiwang authored Jun 10, 2024
2 parents 8109f8f + 76d70af commit c51ebed
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/rich-dots-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@amplitude/rrweb': patch
---

Fix css parsing errors
62 changes: 56 additions & 6 deletions packages/rrweb-snapshot/src/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,21 +431,71 @@ export function parse(css: string, options: ParserOptions = {}) {
*/

function selector() {
const m = match(/^([^{]+)/);
whitespace();
while (css[0] == '}') {
error('extra closing bracket');
css = css.slice(1);
whitespace();
}

// Use match logic from https://github.com/NxtChg/pieces/blob/3eb39c8287a97632e9347a24f333d52d916bc816/js/css_parser/css_parse.js#L46C1-L47C1
const m = match(/^(("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|[^{])+)/);
if (!m) {
return;
}
/* @fix Remove all comments from selectors
* http://ostermiller.org/findcomment.html */
return trim(m[0])
const cleanedInput = m[0]
.trim()
.replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '')

// Handle strings by replacing commas inside them
.replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, (m) => {
return m.replace(/,/g, '\u200C');
})
.split(/\s*(?![^(]*\)),\s*/)
.map((s) => {
return s.replace(/\u200C/g, ',');
});

// Split using a custom function and restore commas in strings
return customSplit(cleanedInput).map((s) =>
s.replace(/\u200C/g, ',').trim(),
);
}

/**
* Split selector correctly, ensuring not to split on comma if inside ().
*/

function customSplit(input: string) {
const result = [];
let currentSegment = '';
let depthParentheses = 0; // Track depth of parentheses
let depthBrackets = 0; // Track depth of square brackets

for (const char of input) {
if (char === '(') {
depthParentheses++;
} else if (char === ')') {
depthParentheses--;
} else if (char === '[') {
depthBrackets++;
} else if (char === ']') {
depthBrackets--;
}

// Split point is a comma that is not inside parentheses or square brackets
if (char === ',' && depthParentheses === 0 && depthBrackets === 0) {
result.push(currentSegment);
currentSegment = '';
} else {
currentSegment += char;
}
}

// Add the last segment
if (currentSegment) {
result.push(currentSegment);
}

return result;
}

/**
Expand Down
29 changes: 29 additions & 0 deletions packages/rrweb-snapshot/test/css.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,35 @@ describe('css parser', () => {
expect(errors[0].filename).toEqual('foo.css');
});

it('should parse selector with comma nested inside ()', () => {
const result = parse(
'[_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(result.parent).toEqual(null);

const rules = result.stylesheet!.rules;
expect(rules.length).toEqual(1);

let rule = rules[0] as Rule;
expect(rule.parent).toEqual(result);
expect(rule.selectors?.length).toEqual(1);

let decl = rule.declarations![0];
expect(decl.parent).toEqual(rule);
});

it('parses { and } in attribute selectors correctly', () => {
const result = parse('foo[someAttr~="{someId}"] { color: red; }');
const rules = result.stylesheet!.rules;

expect(rules.length).toEqual(1);

const rule = rules[0] as Rule;

expect(rule.selectors![0]).toEqual('foo[someAttr~="{someId}"]');
});

it('should set parent property', () => {
const result = parse(
'thing { test: value; }\n' +
Expand Down
1 change: 1 addition & 0 deletions packages/rrweb-snapshot/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"target": "ES6",
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
Expand Down

0 comments on commit c51ebed

Please sign in to comment.