Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix chrome grid template #1586

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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/clean-plants-look.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rrweb-snapshot": patch
---

Fix issue with chrome improperly parsing grid-template-areas to grid-template shorthand.
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 to 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
94 changes: 94 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,99 @@ 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