Skip to content

Commit

Permalink
Add replaceChromeGridTemplateAreas utility function to fix grid-templ…
Browse files Browse the repository at this point in the history
…ate-area styles that are improperly improperly parsed by Chrome into rule.cssText and causing broken recordings when attempting to play back
  • Loading branch information
jaj1014 committed Oct 18, 2024
1 parent 5fbb904 commit 726d380
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 4 deletions.
46 changes: 42 additions & 4 deletions packages/rrweb-snapshot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,14 @@ export function stringifyRule(rule: CSSRule, sheetHref: string | null): string {
return importStringified;
} else {
let ruleStringified = 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
ruleStringified = fixSafariColons(ruleStringified);
if (isCSSStyleRule(rule)) {
ruleStringified = replaceChromeGridTemplateAreas(rule);

if (rule.selectorText.includes(':')) {
// Safari does not escape selectors with : properly
// see https://bugs.webkit.org/show_bug.cgi?id=184604
ruleStringified = fixSafariColons(ruleStringified);
}
}
if (sheetHref) {
return absolutifyURLs(ruleStringified, sheetHref);
Expand All @@ -160,6 +164,40 @@ export function stringifyRule(rule: CSSRule, sheetHref: string | null): string {
}
}

export function replaceChromeGridTemplateAreas(rule: CSSStyleRule): string {
// chrome does not correctly provide the grid-template-areas in the rule.cssText
// when it parses them from grid-template short-hand syntax
// e.g. https://bugs.chromium.org/p/chromium/issues/detail?id=1303968
// so, we manually rebuild the cssText using rule.style when
// we find the cssText contains grid-template:, rule.style contains grid-template-areas, but
// cssText does not include grid-template-areas
const hasGridTemplateInCSSText = rule.cssText.includes('grid-template:');
const hasGridTemplateAreaInStyleRules =
rule.style.getPropertyValue('grid-template-areas') !== '';
const hasGridTemplateAreaInCSSText = rule.cssText.includes(
'grid-template-areas:',
);

if (
hasGridTemplateInCSSText &&
hasGridTemplateAreaInStyleRules &&
!hasGridTemplateAreaInCSSText
) {
const styleDeclarations = [];

for (let i = 0; i < rule.style.length; i++) {
const styleName = rule.style[i];
const styleValue = rule.style.getPropertyValue(styleName);

styleDeclarations.push(`${styleName}: ${styleValue}`);
}

return `${rule.selectorText} { ${styleDeclarations.join('; ')}; }`
}

return rule.cssText;
}

export function fixSafariColons(cssStringified: string): string {
// Replace e.g. [aa:bb] with [aa\\:bb]
const regex = /(\[(?:[\w-]+)[^\\])(:(?:[\w-]+)\])/gm;
Expand Down
91 changes: 91 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 { NodeType, serializedNode } from '../src/types';
import {
escapeImportStatement,
extractFileExtension,
replaceChromeGridTemplateAreas,
fixSafariColons,
isNodeMetaEqual,
} from '../src/utils';
Expand Down Expand Up @@ -268,6 +269,96 @@ describe('utils', () => {
expect(out5).toEqual(`@import url("/foo.css;900;800\\"") layer;`);
});
});

describe('replaceChromeGridTemplateAreas', () => {
it('does not alter corectly parsed grid template rules', () => {
const cssText = '#wrapper { display: grid; width: 100%; height: 100%; grid-template: minmax(2, 1fr); margin: 0px auto; }';
const mockCssRule = {
cssText,
selectorText: '#wrapper',
style: {
getPropertyValue (prop) {
return {
'grid-template-areas': ''
}[prop]
}
}
} as Partial<CSSStyleRule> as CSSStyleRule

expect(replaceChromeGridTemplateAreas(mockCssRule)).toEqual(cssText);
});

it('fixes incorrectly parsed grid template rules', () => {
const cssText1 = '#wrapper { grid-template-areas: "header header" "main main" "footer footer"; grid-template-rows: minmax(2, 1fr); grid-template-columns: minmax(2, 1fr); display: grid; margin: 0px auto; }';
const cssText2 = '.some-class { color: purple; grid-template: "TopNav TopNav" 65px "SideNav Content" 52px "SideNav Content" / 255px auto; column-gap: 32px; }';

const mockCssRule1 = {
cssText: cssText1,
selectorText: '#wrapper',
style: {
length: 5,
0: 'grid-template-areas',
1: 'grid-template-rows',
2: 'grid-template-columns',
3: 'display',
4: 'margin',
getPropertyValue: (key: string): string => {
switch (key) {
case 'grid-template-areas':
return '"header header" "main main" "footer footer"'
case 'grid-template-rows':
return 'minmax(2, 1fr)';
case 'grid-template-columns':
return 'minmax(2, 1fr)';
case'display':
return 'grid';
case'margin':
return '0px auto'
default:
return ''
}
},
} as Record<string | number, any>
} as Partial<CSSStyleRule> as CSSStyleRule

const mockCssRule2 = {
cssText: cssText2,
selectorText: '.some-class',
style: {
length: 5,
0: 'color',
1: 'grid-template-areas',
2: 'grid-template-rows',
3: 'grid-template-columns',
4: 'column-gap',
getPropertyValue: (key: string): string => {
switch (key) {
case'color':
return 'purple';
case 'grid-template-areas':
return '"TopNav TopNav" "SideNav Content" "SideNav Content"'
case 'grid-template-rows':
return '65px 52px auto';
case 'grid-template-columns':
return '255px auto';
case'column-gap':
return '32px'
default:
return ''
}
},
} as Record<string | number, any>
} as Partial<CSSStyleRule> as CSSStyleRule

expect(replaceChromeGridTemplateAreas(mockCssRule1)).toEqual(
'#wrapper { grid-template-areas: "header header" "main main" "footer footer"; grid-template-rows: minmax(2, 1fr); grid-template-columns: minmax(2, 1fr); display: grid; margin: 0px auto; }'
);
expect(replaceChromeGridTemplateAreas(mockCssRule2)).toEqual(
'.some-class { color: purple; grid-template-areas: "TopNav TopNav" "SideNav Content" "SideNav Content"; grid-template-rows: 65px 52px auto; grid-template-columns: 255px auto; column-gap: 32px; }'
);
});
});

describe('fixSafariColons', () => {
it('parses : in attribute selectors correctly', () => {
const out1 = fixSafariColons('[data-foo] { color: red; }');
Expand Down

0 comments on commit 726d380

Please sign in to comment.