Skip to content

Commit

Permalink
fix: handle size/position/attachment in background
Browse files Browse the repository at this point in the history
Fixes [#3169](jsdom/jsdom#3169).
  • Loading branch information
cdoublev committed May 23, 2021
1 parent ae50b33 commit 57799cc
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 25 deletions.
39 changes: 39 additions & 0 deletions lib/CSSStyleDeclaration.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,45 @@ describe('CSSStyleDeclaration', () => {

describe('properties', () => {
describe('background', () => {
test('invalid', () => {
const style = new CSSStyleDeclaration();
const invalid = [
'left top',
'red / 100% 100% 100%',
'red / cover cover cover',
'red / 100% 100% repeat 100%',
'red / cover cover repeat cover',
];
invalid.forEach(value => {
style.background = value;
expect(style.background).toBe('');
});
});
test('valid', () => {
const style = new CSSStyleDeclaration();
const canonical =
'red url("bg.jpg") left 10% / 100px 100% no-repeat fixed content-box padding-box';
style.background = canonical;
expect(style.background).toBe(canonical);
// Non canonical order
style.background =
'left 10% url("bg.jpg") red / no-repeat content-box padding-box 100px 100% fixed';
expect(style.background).toBe(canonical);
style.background =
'url("bg.jpg") left 10% red / content-box padding-box no-repeat fixed 100px 100%';
expect(style.background).toBe(canonical);
style.background = 'red url("bg.jpg") left 10% / 100px 100% content-box no-repeat';
expect(style.background).toBe(
'red url("bg.jpg") left 10% / 100px 100% no-repeat content-box'
);
style.background = 'red url("bg.jpg") left 10% / 100px content-box padding-box no-repeat';
expect(style.background).toBe(
'red url("bg.jpg") left 10% / 100px no-repeat content-box padding-box'
);
// No space around separator
style.background = 'red/100px';
expect(style.background).toBe('red / 100px');
});
test('null should set property to empty string', () => {
const style = new CSSStyleDeclaration();
style.background = 'red';
Expand Down
45 changes: 31 additions & 14 deletions lib/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ const urlRegEx = new RegExp(`^url\\(${ws}([^"'() \\t${newline}\\\\]|${escape})+$
const whitespaceRegEx = new RegExp(`^${whitespace}$`);
const trailingWhitespaceRegEx = new RegExp(`.*${whitespace}$`);

exports.ws = ws;

/**
* CSS types
*
Expand Down Expand Up @@ -1390,9 +1392,18 @@ exports.shorthandParser = function parse(v, shorthand_for) {
return obj;
};

exports.shorthandSetter = function(property, shorthand_for) {
exports.shorthandSetter = function(
property,
shorthand_for,
shorthandParser = exports.shorthandParser,
separator
) {
let longhands = shorthand_for;
if (Array.isArray(shorthand_for)) {
shorthand_for = shorthand_for.reduce((obj, part) => ({ ...obj, ...part }), {});
}
return function(v) {
var obj = exports.shorthandParser(v, shorthand_for);
const obj = shorthandParser(v, shorthand_for);
if (obj === undefined) {
return;
}
Expand Down Expand Up @@ -1421,26 +1432,32 @@ exports.shorthandSetter = function(property, shorthand_for) {
// if it already exists, then call the shorthandGetter, if it's an empty
// string, don't set the property
this.removeProperty(property);
var calculated = exports.shorthandGetter(property, shorthand_for).call(this);
const calculated = exports.shorthandGetter(property, longhands, separator).call(this);
if (calculated !== '') {
this._setProperty(property, calculated);
}
};
};

exports.shorthandGetter = function(property, shorthand_for) {
exports.shorthandGetter = function(shorthand, longhands, separator = ' / ') {
return function() {
if (this._values[property] !== undefined) {
return this.getPropertyValue(property);
if (this._values[shorthand] !== undefined) {
return this.getPropertyValue(shorthand);
}
if (!Array.isArray(longhands)) {
longhands = [longhands];
}
return Object.keys(shorthand_for)
.map(function(subprop) {
return this.getPropertyValue(subprop);
}, this)
.filter(function(value) {
return value !== '';
})
.join(' ');
return longhands
.map(
longhands =>
Object.keys(longhands)
.map(longhand => this.getPropertyValue(longhand), this)
.filter(value => value !== '')
.join(' '),
this
)
.filter(value => value !== '')
.join(separator);
};
};

Expand Down
164 changes: 153 additions & 11 deletions lib/properties/background.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,161 @@
'use strict';

var shorthandSetter = require('../parsers').shorthandSetter;
var shorthandGetter = require('../parsers').shorthandGetter;

var shorthand_for = {
'background-color': require('./backgroundColor'),
'background-image': require('./backgroundImage'),
'background-repeat': require('./backgroundRepeat'),
'background-attachment': require('./backgroundAttachment'),
'background-position': require('./backgroundPosition'),
const { shorthandGetter, shorthandSetter, splitTokens, ws } = require('../parsers');
const { parse: parseBackgroundAttachment } = require('./backgroundAttachment');
const { parse: parseBackgroundColor } = require('./backgroundColor');
const { parse: parseBackgroundImage } = require('./backgroundImage');
const { parse: parseBackgroundPosition } = require('./backgroundPosition');
const { parse: parseBackgroundSize } = require('./backgroundSize');
const { parse: parseBackgroundRepeat } = require('./backgroundRepeat');
const { parse: parseBackgroundOrigin } = require('./backgroundOrigin');
const { parse: parseBackgroundClip } = require('./backgroundClip');

const before = {
'background-color': '',
'background-image': '',
'background-position': '',
};
const after = {
'background-size': '',
'background-repeat': '',
'background-attachment': '',
'background-origin': '',
'background-clip': '',
};
const separatorRegExp = new RegExp(`${ws}/${ws}`);
const shorthandFor = [before, after];

function shorthandParser(v, shorthandFor) {
if (v.toLowerCase() === 'inherit') {
return {};
}

const longhands = { ...shorthandFor };
if (v === '') {
return longhands;
}

let [[argsBefore, argsAfter]] = splitTokens(v, separatorRegExp);

[argsBefore] = splitTokens(argsBefore);

const { length: argsBeforeLength } = argsBefore;
let positionArg = [];
let positionArgIndex = 0;
let i = 0;
for (; i < argsBeforeLength; i++) {
const arg = argsBefore[i];
const color = parseBackgroundColor(arg);
if (color) {
if (longhands['background-color']) {
return undefined;
}
longhands['background-color'] = color;
continue;
}
const image = parseBackgroundImage(arg);
if (image) {
if (longhands['background-image']) {
return undefined;
}
longhands['background-image'] = image;
continue;
}
// First or consecutive <position>
if (positionArg.length === 0 || positionArgIndex - i === -1) {
positionArgIndex = i;
positionArg.push(arg);
continue;
}
return undefined;
}
if (!(longhands['background-color'] || longhands['background-image'])) {
return undefined;
}
if ((positionArg = positionArg.join(' '))) {
const position = parseBackgroundPosition(positionArg);
if (position === undefined) {
return undefined;
}
longhands['background-position'] = position;
}

if (argsAfter) {
[argsAfter] = splitTokens(argsAfter);

const { length: argsAfterLength } = argsAfter;
const sizeBoxArgs = [];
let sizeBoxArgIndex = 0;
for (let i = 0; i < argsAfterLength; i++) {
const arg = argsAfter[i];
const repeat = parseBackgroundRepeat(arg);
if (repeat) {
if (longhands['background-repeat']) {
return undefined;
}
longhands['background-repeat'] = repeat;
continue;
}
const attachment = parseBackgroundAttachment(arg);
if (attachment) {
if (longhands['background-attachment']) {
return undefined;
}
longhands['background-attachment'] = attachment;
continue;
}
// First or consecutive <box|size>
if (sizeBoxArgs.length === 0 || sizeBoxArgs.length === 2 || sizeBoxArgIndex - i === -1) {
sizeBoxArgIndex = i;
sizeBoxArgs.push(arg);
continue;
}
return undefined;
}

const { length: sizeBoxArgsLength } = sizeBoxArgs;
if (sizeBoxArgsLength > 4) {
return undefined;
}
if (sizeBoxArgsLength > 0) {
const [sizeX, sizeY = '', origin = '', clip = ''] = sizeBoxArgs;
let parsedSize = parseBackgroundSize(`${sizeX}${sizeY ? ` ${sizeY}` : ''}`);
let parsedOrigin = parseBackgroundOrigin(origin);
let parsedClip = parseBackgroundClip(clip);
if (parsedSize !== undefined && parsedOrigin !== undefined && parsedClip !== undefined) {
longhands['background-size'] = parsedSize;
longhands['background-origin'] = parsedOrigin;
longhands['background-clip'] = parsedClip;
return longhands;
}
parsedSize = parseBackgroundSize(`${origin}${clip ? ` ${clip}` : ''}`);
parsedOrigin = parseBackgroundOrigin(sizeX);
parsedClip = parseBackgroundOrigin(sizeY);
if (parsedSize !== undefined && parsedOrigin !== undefined && parsedClip !== undefined) {
longhands['background-size'] = parsedSize;
longhands['background-origin'] = parsedOrigin;
longhands['background-clip'] = parsedClip;
return longhands;
}
parsedSize = parseBackgroundSize(sizeX);
parsedOrigin = parseBackgroundOrigin(sizeY);
parsedClip = parseBackgroundOrigin(origin);
if (parsedSize !== undefined && parsedOrigin !== undefined && parsedClip !== undefined) {
longhands['background-size'] = parsedSize;
longhands['background-origin'] = parsedOrigin;
longhands['background-clip'] = parsedClip;
return longhands;
}
return undefined;
}
}

return longhands;
}

module.exports.definition = {
set: shorthandSetter('background', shorthand_for),
get: shorthandGetter('background', shorthand_for),
set: shorthandSetter('background', shorthandFor, shorthandParser),
get: shorthandGetter('background', shorthandFor),
enumerable: true,
configurable: true,
};

0 comments on commit 57799cc

Please sign in to comment.