Skip to content

Commit

Permalink
fix(core): fix hash collisions with at rules
Browse files Browse the repository at this point in the history
  • Loading branch information
layershifter committed May 21, 2024
1 parent 3cac9f0 commit fe1937f
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix(core): fix hash collisions with at rules",
"packageName": "@griffel/core",
"email": "olfedias@microsoft.com",
"dependentChangeType": "patch"
}
2 changes: 1 addition & 1 deletion e2e/rspack/src/snapshots/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
outline-offset: 5px;
}
@media (min-width: 968px) and (orientation: landscape) {
.fnj14hp {
.fftofzc {
width: 400px;
}
}
12 changes: 6 additions & 6 deletions packages/babel-preset/__fixtures__/at-rules/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import { __styles } from '@griffel/react';
export const useStyles = __styles(
{
root: {
cvzfql: ['f13wceqr', 'fmtq1hn'],
g42fl4: ['fmtq1hn', 'f13wceqr'],
B6rh290: ['f16pi78z', 'fem5dos'],
rth2jb: ['fem5dos', 'f16pi78z'],
B3z2k6w: ['f1wfh65i', 'f1efhert'],
Bdn5tj5: ['f1efhert', 'f1wfh65i'],
k2kg1z: ['f1dyc5nu', 'f1kq3x14'],
Blnc60a: ['f1kq3x14', 'f1dyc5nu'],
},
},
{
m: [
[
'@media (min-width: 600px){.f13wceqr{padding-left:4px;}.fmtq1hn{padding-right:4px;}}',
'@media (min-width: 600px){.f1efhert{padding-right:4px;}.f1wfh65i{padding-left:4px;}}',
{
m: '(min-width: 600px)',
},
],
],
t: ['@supports (display: flex){.f16pi78z{padding-left:4px;}.fem5dos{padding-right:4px;}}'],
t: ['@supports (display: flex){.f1dyc5nu{padding-left:4px;}.f1kq3x14{padding-right:4px;}}'],
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { __styles } from '@griffel/react';
export const useStyles = __styles(
{
media: {
Bulngiv: 'fr5o61b',
Be2dmvj: 'foh03df',
},
},
{
m: [
[
'@media screen and (max-width: 100px){.fr5o61b{color:red;}}',
'@media screen and (max-width: 100px){.foh03df{color:red;}}',
{
m: 'screen and (max-width: 100px)',
},
Expand Down
44 changes: 22 additions & 22 deletions packages/core/src/runtime/resolveStyleRules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,12 @@ describe('resolveStyleRules', () => {
}),
).toMatchInlineSnapshot(`
@media screen and (max-width: 992px) {
.f133ge8t {
text-align: right;
}
.f1f6067w {
.f1el5hyx {
text-align: left;
}
.f1qqk4p1 {
text-align: right;
}
}
`);
});
Expand Down Expand Up @@ -496,14 +496,14 @@ describe('resolveStyleRules', () => {

expect(result).toMatchInlineSnapshot(`
@container foo (max-width: 1px) {
.f16pkaez {
.fbmp7kx {
color: red;
}
}
`);
expect(result[0]).toMatchInlineSnapshot(`
Object {
"Blqc43h": "f16pkaez",
"b7rg5g": "fbmp7kx",
}
`);
});
Expand All @@ -513,14 +513,14 @@ describe('resolveStyleRules', () => {

expect(result).toMatchInlineSnapshot(`
@container (max-width: 1px) {
.f15adh02 {
.f4ivup9 {
color: red;
}
}
`);
expect(result[0]).toMatchInlineSnapshot(`
Object {
"qc8hou": "f15adh02",
"pmeytk": "f4ivup9",
}
`);
});
Expand All @@ -536,14 +536,14 @@ describe('resolveStyleRules', () => {
color: red;
}
@container foo (max-width: 1px) {
.f16pkaez {
.fbmp7kx {
color: red;
}
}
`);
expect(result[0]).toMatchInlineSnapshot(`
Object {
"Blqc43h": "f16pkaez",
"b7rg5g": "fbmp7kx",
"sj55zd": "fe3e8s9",
}
`);
Expand All @@ -560,7 +560,7 @@ describe('resolveStyleRules', () => {
color: green;
}
@media screen and (max-width: 992px) {
.f1ojdyje {
.f2bvvla {
color: red;
}
}
Expand All @@ -582,7 +582,7 @@ describe('resolveStyleRules', () => {
color: green;
}
@media screen and (max-width: 992px) {
.f7wpa5l:hover {
.fuy1c5o:hover {
color: red;
}
}
Expand All @@ -603,12 +603,12 @@ describe('resolveStyleRules', () => {
color: red;
}
@media screen and (max-width: 992px) {
.f1ojdyje {
.f2bvvla {
color: red;
}
}
@media screen and (max-width: 992px) and (min-width: 100px) {
.f19a6424 {
.f1imqg8u {
color: red;
}
}
Expand All @@ -626,7 +626,7 @@ describe('resolveStyleRules', () => {
color: green;
}
@layer color {
.f1hjcal7 {
.f1al6es7 {
color: red;
}
}
Expand All @@ -640,7 +640,7 @@ describe('resolveStyleRules', () => {
}),
).toMatchInlineSnapshot(`
@layer framework.utilities {
.faxdetk {
.f12ei13l {
color: red;
}
}
Expand All @@ -661,7 +661,7 @@ describe('resolveStyleRules', () => {
color: green;
}
@layer color {
.f1jv0g3z:hover {
.f1hrbkey:hover {
color: red;
}
}
Expand All @@ -682,12 +682,12 @@ describe('resolveStyleRules', () => {
color: red;
}
@layer color {
.f1hjcal7 {
.f1al6es7 {
color: red;
}
}
@layer color.theme {
.f132c7g3 {
.f1mzin3h {
color: red;
}
}
Expand All @@ -701,7 +701,7 @@ describe('resolveStyleRules', () => {
}),
).toMatchInlineSnapshot(`
@supports (display: block) {
.f1yofsfp {
.fp97nsu {
color: green;
}
}
Expand Down Expand Up @@ -985,8 +985,8 @@ describe('resolveStyleRules', () => {

expect(result[1]).toEqual({
m: [
['@media screen{.f101iwbs{color:red;}}', { m: 'screen' }],
['@media screen{.fqfls7b{padding:10px;}}', { m: 'screen', p: -1 }],
['@media screen{.f1f7xnks{color:red;}}', { m: 'screen' }],
['@media screen{.f1pegaf3{padding:10px;}}', { m: 'screen', p: -1 }],
],
});
});
Expand Down
30 changes: 30 additions & 0 deletions packages/core/src/runtime/utils/hashClassName.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { hashClassName } from './hashClassName';

const defaultOptions = {
property: 'color',
selector: '',
value: 'red',

salt: '',
};
const defaultAtRules = {
container: '',
media: '',
layer: '',
supports: '',
};

describe('hashClassName', () => {
it('should hash the className', () => {
expect(hashClassName(defaultOptions, defaultAtRules)).toMatchInlineSnapshot(`"fe3e8s9"`);
});

it('should generate non-colliding hashes', () => {
const hashA = hashClassName(defaultOptions, { ...defaultAtRules, container: '(min-width: 500px)' });
const hashB = hashClassName(defaultOptions, { ...defaultAtRules, media: '(min-width: 500px)' });

expect(hashA).toMatchInlineSnapshot(`"f18ymuke"`);
expect(hashB).toMatchInlineSnapshot(`"f16oyr76"`);
expect(hashA).not.toBe(hashB);
});
});
6 changes: 2 additions & 4 deletions packages/core/src/runtime/utils/hashClassName.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import hashString from '@emotion/hash';

import { HASH_PREFIX } from '../../constants';
import { atRulesToString } from './hashPropertyKey';
import type { AtRules } from './types';

interface HashedClassNameParts {
Expand All @@ -14,10 +15,7 @@ export function hashClassName({ property, selector, value }: HashedClassNamePart
HASH_PREFIX +
hashString(
selector +
atRules.container +
atRules.media +
atRules.layer +
atRules.supports +
atRulesToString(atRules) +
property +
// Trimming of value is required to generate consistent hashes
value.trim(),
Expand Down
19 changes: 14 additions & 5 deletions packages/core/src/runtime/utils/hashPropertyKey.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { hashPropertyKey } from './hashPropertyKey';

const defaultAtRules = { container: '', media: '', supports: '', layer: '' };

describe('hashPropertyKey', () => {
it('generates hashes that always start with letters', () => {
const atRules = { container: '', media: '', supports: '', layer: '' };
expect(hashPropertyKey('', 'color', defaultAtRules)).toBe('sj55zd');
expect(hashPropertyKey('', 'display', defaultAtRules)).toBe('mc9l5x');

expect(hashPropertyKey('', 'backgroundColor', defaultAtRules)).toBe('De3pzq');
expect(hashPropertyKey(':hover', 'color', defaultAtRules)).toBe('Bi91k9c');
});

expect(hashPropertyKey('', 'color', atRules)).toBe('sj55zd');
expect(hashPropertyKey('', 'display', atRules)).toBe('mc9l5x');
it('generates non-colliding hashes', () => {
const hashA = hashPropertyKey('', 'color', { ...defaultAtRules, container: '(min-width: 500px)' });
const hashB = hashPropertyKey('', 'color', { ...defaultAtRules, media: '(min-width: 500px)' });

expect(hashPropertyKey('', 'backgroundColor', atRules)).toBe('De3pzq');
expect(hashPropertyKey(':hover', 'color', atRules)).toBe('Bi91k9c');
expect(hashA).toMatchInlineSnapshot(`"Bhkzl7a"`);
expect(hashB).toMatchInlineSnapshot(`"zc7s4b"`);
expect(hashA).not.toBe(hashB);
});
});
15 changes: 14 additions & 1 deletion packages/core/src/runtime/utils/hashPropertyKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,22 @@ import hash from '@emotion/hash';
import type { PropertyHash } from '../../types';
import type { AtRules } from './types';

function addAtRulePrefix(atRule: string, prefix: string): string {
return atRule ? prefix + atRule : atRule;
}

export function atRulesToString(atRules: AtRules): string {
return (
addAtRulePrefix(atRules.container, 'c') +
addAtRulePrefix(atRules.media, 'l') +
addAtRulePrefix(atRules.layer, 'l') +
addAtRulePrefix(atRules.supports, 's')
);
}

export function hashPropertyKey(selector: string, property: string, atRules: AtRules): PropertyHash {
// uniq key based on property & selector, used for merging later
const computedKey = selector + atRules.container + atRules.media + atRules.supports + property;
const computedKey = selector + atRulesToString(atRules) + property;

// "key" can be really long as it includes selectors, we use hashes to reduce sizes of keys
// ".foo :hover" => "abcd"
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/createDOMRenderer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ describe('createDOMRenderer', () => {
".rp2atum:hover{color:blue;}": "r",
".rp2atum{color:red;}": "r",
"@keyframes f1kgwxhb{from{height:10px;}to{height:20px;}}": "k",
"@media screen and (max-width: 992px){.fnao3vb:hover{color:blue;}}": "m",
"@media screen and (max-width: 992px){.f1lksov6:hover{color:blue;}}": "m",
}
`);
// There is no DOM on a server, style nodes should not be present
Expand Down
14 changes: 7 additions & 7 deletions packages/react/src/renderToStyleElements-node.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@supports (display: grid) {
.f1ofq0jl {
.fo1gfrc {
color: red;
}
}</style
Expand All @@ -123,7 +123,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@media screen and (max-width: 992px) {
.fnao3vb:hover {
.f1lksov6:hover {
color: blue;
}
}
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@supports (display: grid) {
.f1vq01kz {
.fui0tgz {
color: green;
}
}</style
Expand All @@ -197,7 +197,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@media (max-width: 1px) {
.f1f7njb2:hover {
.faiyvxi:hover {
color: blue;
}
}</style
Expand All @@ -208,7 +208,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@media (max-width: 2px) {
.f1c6999y:hover {
.f1i64cxd:hover {
color: blue;
}
}</style
Expand All @@ -219,7 +219,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@media (max-width: 3px) {
.f1qdcc3n:hover {
.f132yv2m:hover {
color: blue;
}
}</style
Expand All @@ -230,7 +230,7 @@ describe('renderToStyleElements (node)', () => {
data-make-styles-rehydration="true"
>
@media (max-width: 4px) {
.f1b4up97:hover {
.f1i44wst:hover {
color: blue;
}
}
Expand Down

0 comments on commit fe1937f

Please sign in to comment.