Skip to content

Commit

Permalink
Merge pull request #985 from malko/#976@minor
Browse files Browse the repository at this point in the history
#976@minor Fix incorrect comma split on some css properties
  • Loading branch information
capricorn86 committed Jul 17, 2023
2 parents 0ff221e + b63a555 commit 7b33345
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 28 deletions.
5 changes: 4 additions & 1 deletion packages/happy-dom/src/css/CSSParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,10 @@ export default class CSSParser {
stack.push(parentRule);
} else {
if (parentRule) {
const cssText = css.substring(lastIndex, match.index).trim();
const cssText = css
.substring(lastIndex, match.index)
.trim()
.replace(/([^;])$/, '$1;'); // Ensure last semicolon
switch (parentRule.type) {
case CSSRule.FONT_FACE_RULE:
case CSSRule.KEYFRAME_RULE:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
// PropName => \s*([^:;]+?)\s*:
// PropValue => \s*((?:[^(;]*?(?:\([^)]*\))?)*?) <- will match any non ';' char except inside (), nested parentheses are not supported
// !important => \s*(!important)?
// EndOfRule => \s*(?:$|;)
const SPLIT_RULES_REGEXP =
/\s*([^:;]+?)\s*:\s*((?:[^(;]*?(?:\([^)]*\))?)*?)\s*(!important)?\s*(?:$|;)/g;

/**
* CSS parser.
*/
Expand All @@ -12,23 +19,10 @@ export default class CSSStyleDeclarationCSSParser {
cssText: string,
callback: (name: string, value: string, important: boolean) => void
): void {
const parts = cssText.split(';');

for (const part of parts) {
if (part) {
const [name, value]: string[] = part.trim().split(':');
if (value) {
const trimmedName = name.trim();
const trimmedValue = value.trim();
if (trimmedName && trimmedValue) {
const important = trimmedValue.endsWith(' !important');
const valueWithoutImportant = trimmedValue.replace(' !important', '');

if (valueWithoutImportant) {
callback(trimmedName, valueWithoutImportant, important);
}
}
}
const rules = Array.from(cssText.matchAll(SPLIT_RULES_REGEXP));
for (const [, key, value, important] of rules) {
if (key && value) {
callback(key.trim(), value.trim(), !!important);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import CSSStyleDeclarationValueParser from './CSSStyleDeclarationValueParser.js'
import ICSSStyleDeclarationPropertyValue from './ICSSStyleDeclarationPropertyValue.js';

const RECT_REGEXP = /^rect\((.*)\)$/i;
const SPLIT_PARTS_REGEXP = /,(?=(?:(?:(?!\))[\s\S])*\()|[^\(\)]*$)/; // Split on commas that are outside of parentheses
const BORDER_STYLE = [
'none',
'hidden',
Expand Down Expand Up @@ -2383,7 +2384,7 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-size': { value: lowerValue, important } };
}

const imageParts = lowerValue.split(',');
const imageParts = lowerValue.split(SPLIT_PARTS_REGEXP);
const parsed = [];

for (const imagePart of imageParts) {
Expand Down Expand Up @@ -2554,7 +2555,7 @@ export default class CSSStyleDeclarationPropertySetParser {
};
}

const imageParts = value.replace(/ *, */g, ',').split(',');
const imageParts = value.split(SPLIT_PARTS_REGEXP);
let x = '';
let y = '';

Expand Down Expand Up @@ -2667,7 +2668,7 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-position-x': { value: lowerValue, important } };
}

const imageParts = lowerValue.replace(/ *, */g, ',').split(',');
const imageParts = lowerValue.split(SPLIT_PARTS_REGEXP);
let parsedValue = '';

for (const imagePart of imageParts) {
Expand Down Expand Up @@ -2718,7 +2719,7 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-position-y': { value: lowerValue, important } };
}

const imageParts = lowerValue.replace(/ *, */g, ',').split(',');
const imageParts = lowerValue.split(SPLIT_PARTS_REGEXP);
let parsedValue = '';

for (const imagePart of imageParts) {
Expand Down Expand Up @@ -2794,7 +2795,7 @@ export default class CSSStyleDeclarationPropertySetParser {
return { 'background-image': { value: lowerValue, important } };
}

const parts = value.replace(/ *, */g, ',').split(',');
const parts = value.split(SPLIT_PARTS_REGEXP);
const parsed = [];

for (const part of parts) {
Expand Down
10 changes: 7 additions & 3 deletions packages/happy-dom/test/css/CSSParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,26 @@ describe('CSSParser', () => {
expect((<CSSStyleRule>cssRules[1]).parentStyleSheet).toBe(cssStyleSheet);
expect((<CSSStyleRule>cssRules[1]).selectorText).toBe('.container');
expect((<CSSStyleRule>cssRules[1]).cssText).toBe(
'.container { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; --css-variable: 1px; }'
'.container { flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; --css-variable: 1px; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="), url("test.jpg"); }'
);
expect((<CSSStyleRule>cssRules[1]).style.length).toBe(5);
expect((<CSSStyleRule>cssRules[1]).style.length).toBe(6);
expect((<CSSStyleRule>cssRules[1]).style.parentRule).toBe(cssRules[1]);
expect((<CSSStyleRule>cssRules[1]).style[0]).toBe('flex-grow');
expect((<CSSStyleRule>cssRules[1]).style[1]).toBe('display');
expect((<CSSStyleRule>cssRules[1]).style[2]).toBe('flex-direction');
expect((<CSSStyleRule>cssRules[1]).style[3]).toBe('overflow');
expect((<CSSStyleRule>cssRules[1]).style[4]).toBe('--css-variable');
expect((<CSSStyleRule>cssRules[1]).style[5]).toBe('background-image');
expect((<CSSStyleRule>cssRules[1]).style.flexGrow).toBe('1');
expect((<CSSStyleRule>cssRules[1]).style.display).toBe('flex');
expect((<CSSStyleRule>cssRules[1]).style.flexDirection).toBe('column');
expect((<CSSStyleRule>cssRules[1]).style.overflow).toBe('hidden');
expect((<CSSStyleRule>cssRules[1]).style.getPropertyValue('--css-variable')).toBe('1px');
expect((<CSSStyleRule>cssRules[1]).style.backgroundImage).toBe(
'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="), url("test.jpg")'
);
expect((<CSSStyleRule>cssRules[1]).style.cssText).toBe(
'flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; --css-variable: 1px;'
'flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; --css-variable: 1px; background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="), url("test.jpg");'
);

// CSSMediaRule
Expand Down
8 changes: 6 additions & 2 deletions packages/happy-dom/test/css/data/CSSParserInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export default `
flex-direction: column;
overflow: hidden;
--css-variable: 1px;
background-image:
url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="),
url(test.jpg)
;
}
@media screen and (max-width: 36rem) {
Expand Down Expand Up @@ -58,7 +62,7 @@ export default `
color: red;
}
}
@supports (display: flex) {
.container {
color: green;
Expand All @@ -74,5 +78,5 @@ export default `
/* Single-line comment */
.foo { color: red; }
`.trim();
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,15 @@ describe('CSSStyleDeclaration', () => {
element.setAttribute('style', 'background-image: url(test.jpg), url(test2.jpg)');

expect(declaration.backgroundImage).toBe('url("test.jpg"), url("test2.jpg")');

element.setAttribute(
'style',
'background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=)'
);

expect(declaration.backgroundImage).toBe(
'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=")'
);
});
});

Expand Down

0 comments on commit 7b33345

Please sign in to comment.