From: Timur Kristóf Date: Fri, 28 Feb 2014 13:28:35 +0000 (+0100) Subject: Splitting the new property optimizer into separate modules X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=19bccdfdeaf35e631037a609aa4bc03eb90d2d1d;p=clean-css.git Splitting the new property optimizer into separate modules --- diff --git a/lib/properties/compact.js b/lib/properties/compact.js deleted file mode 100644 index 02f1809e..00000000 --- a/lib/properties/compact.js +++ /dev/null @@ -1,1290 +0,0 @@ - -// The algorithm here is designed to optimize properties in a CSS selector block -// and output the smallest possible equivalent code. It is capable of -// -// 1. Merging properties that can override each other -// 2. Shorthanding properties when it makes sense -// -// Details are determined by `processable` - look at its comments to see how. -// This design has many benefits: -// -// * Can break up shorthands to their granular values -// * Deals with cases when a shorthand overrides more granular properties -// * Leaves fallbacks intact but merges equally understandable values -// * Removes default values from shorthand declarations -// * Opens up opportunities for further optimalizations because granular components of shorthands are much easier to compare/process individually -// - -module.exports = (function () { - - // Creates a property token with its default value - var makeDefaultProperty = function (prop, important, newValue) { - return { - prop: prop, - value: newValue || processable[prop].defaultValue, - isImportant: important - }; - }; - - // Creates an array of property tokens with their default values - var makeDefaultProperties = function (props, important) { - return props.map(function(prop) { return makeDefaultProperty(prop, important); }); - }; - - // Regexes used for stuff - var cssUnitRegexStr = '(\\-?\\.?\\d+\\.?\\d*(px|%|em|rem|in|cm|mm|ex|pt|pc|)|auto|inherit)'; - var cssFunctionNoVendorRegexStr = '[A-Z]+(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)'; - var cssFunctionVendorRegexStr = '\\-(\\-|[A-Z]|[0-9])+\\(([A-Z]|[0-9]|\\ |\\,|\\#|\\+|\\-|\\%|\\.)*\\)'; - var cssFunctionAnyRegexStr = '(' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')'; - var cssUnitAnyRegexStr = '(' + cssUnitRegexStr + '|' + cssFunctionNoVendorRegexStr + '|' + cssFunctionVendorRegexStr + ')'; - - // Validator - // NOTE: The point here is not semantical but syntactical validity - var validator = { - isValidHexColor: function (s) { - return (s.length === 4 || s.length === 7) && s[0] === '#'; - }, - isValidRgbaColor: function (s) { - s = s.split(' ').join(''); - return s.length > 0 && s.indexOf('rgba(') === 0 && s.indexOf(')') === s.length - 1; - }, - isValidHslaColor: function (s) { - s = s.split(' ').join(''); - return s.length > 0 && s.indexOf('hsla(') === 0 && s.indexOf(')') === s.length - 1; - }, - isValidNamedColor: function (s) { - // TODO: we don't really check if it's a valid color value, but allow any letters in it - return s !== 'auto' && (s === 'transparent' || s === 'inherit' || /^[a-zA-Z]+$/.test(s)); - }, - isValidColor: function (s) { - // http://www.w3schools.com/cssref/css_colors_legal.asp - return validator.isValidNamedColor(s) || validator.isValidHexColor(s) || validator.isValidRgbaColor(s) || validator.isValidHslaColor(s); - }, - isValidUrl: function (s) { - // NOTE: at this point all URLs are replaced with placeholders by clean-css, so we check for those placeholders - return s.indexOf('__ESCAPED_URL_CLEAN_CSS') === 0; - }, - isValidUnit: function (s) { - return new RegExp('^' + cssUnitAnyRegexStr + '$', 'gi').test(s); - }, - isValidUnitWithoutFunction: function (s) { - return new RegExp('^' + cssUnitRegexStr + '$', 'gi').test(s); - }, - isValidFunctionWithoutVendorPrefix: function (s) { - return new RegExp('^' + cssFunctionNoVendorRegexStr + '$', 'gi').test(s); - }, - isValidFunctionWithVendorPrefix: function (s) { - return new RegExp('^' + cssFunctionVendorRegexStr + '$', 'gi').test(s); - }, - isValidFunction: function (s) { - return new RegExp('^' + cssFunctionAnyRegexStr + '$', 'gi').test(s); - }, - isValidBackgroundRepeat: function (s) { - return s === 'repeat' || s === 'no-repeat' || s === 'repeat-x' || s === 'repeat-y' || s === 'inherit'; - }, - isValidBackgroundAttachment: function (s) { - return s === 'inherit' || s === 'scroll' || s === 'fixed' || s === 'local'; - }, - isValidBackgroundPositionPart: function (s) { - if (s === 'center' || s === 'top' || s === 'bottom' || s === 'left' || s === 'right') - return true; - // LIMITATION: currently we don't support functions in here because otherwise we'd confuse things like linear-gradient() - // we need to figure out the complete list of functions that are allowed for units and then we can use isValidUnit here. - return new RegExp('^' + cssUnitRegexStr + '$', 'gi').test(s); - }, - isValidBackgroundPosition: function (s) { - if (s === 'inherit') - return true; - return s.split(' ').every(function(p) { return validator.isValidBackgroundPositionPart(p); }); - }, - isValidListStyleType: function (s) { - return s === 'armenian' || s === 'circle' || s === 'cjk-ideographic' || s === 'decimal' || s === 'decimal-leading-zero' || s === 'disc' || s === 'georgian' || s === 'hebrew' || s === 'hiragana' || s === 'hiragana-iroha' || s === 'inherit' || s === 'katakana' || s === 'katakana-iroha' || s === 'lower-alpha' || s === 'lower-greek' || s === 'lower-latin' || s === 'lower-roman' || s === 'none' || s === 'square' || s === 'upper-alpha' || s === 'upper-latin' || s === 'upper-roman'; - }, - isValidListStylePosition: function (s) { - return s === 'inside' || s === 'outside' || s === 'inherit'; - }, - isValidOutlineColor: function (s) { - return s === 'invert' || validator.isValidColor(s) || validator.isValidVendorPrefixedValue(s); - }, - isValidOutlineStyle: function (s) { - return s === 'inherit' || s === 'hidden' || s === 'none' || s === 'dotted' || s === 'dashed' || s === 'solid' || s === 'double' || s === 'groove' || s === 'ridge' || s === 'inset' || s === 'outset'; - }, - isValidOutlineWidth: function (s) { - return validator.isValidUnit(s) || s === 'thin' || s === 'thick' || s === 'medium' || s === 'inherit'; - }, - isValidVendorPrefixedValue: function (s) { - return /^-([A-Za-z0-9]|-)*$/gi.test(s); - }, - areSameFunction: function (a, b) { - if (!validator.isValidFunction(a) || !validator.isValidFunction(b)) { - return false; - } - var f1name = a.substring(0, a.indexOf('(')); - var f2name = b.substring(0, b.indexOf('(')); - - return f1name === f2name; - } - }; - validator = Object.freeze(validator); - - // Merge functions - var canMerge = { - // Use when two tokens of the same property can always be merged - always: function () { - // NOTE: We could have (t1, t2) parameters here but jshint complains because we don't use them - return true; - }, - // Use when two tokens of the same property can only be merged if they have the same value - sameValue: function (t1, t2) { - - return t1.value === t2.value; - }, - sameFunctionOrValue: function (t1, t2) { - // Functions with the same name can override each other - if (validator.areSameFunction(t1.value, t2.value)) { - return true; - } - - return t1.value === t2.value; - }, - // Use for properties containing CSS units (margin-top, padding-left, etc.) - unit: function (t1, t2) { - // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa - // Understandability: (unit without functions) > (same functions | standard functions) > anything else - // NOTE: there is no point in having different vendor-specific functions override each other or standard functions, - // or having standard functions override vendor-specific functions, but standard functions can override each other - // NOTE: vendor-specific property values are not taken into consideration here at the moment - - if (validator.isValidUnitWithoutFunction(t2.value)) - return true; - if (validator.isValidUnitWithoutFunction(t1.value)) - return false; - - // Standard non-vendor-prefixed functions can override each other - if (validator.isValidFunctionWithoutVendorPrefix(t2.value) && validator.isValidFunctionWithoutVendorPrefix(t1.value)) { - return true; - } - - // Functions with the same name can override each other; same values can override each other - return canMerge.sameFunctionOrValue(t1, t2); - }, - // Use for color properties (color, background-color, border-color, etc.) - color: function (t1, t2) { - // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa - // Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else - // NOTE: at this point rgb and hsl are replaced by hex values by clean-css - - // (hex | named) - if (validator.isValidNamedColor(t2.value) || validator.isValidHexColor(t2.value)) - return true; - if (validator.isValidNamedColor(t1.value) || validator.isValidHexColor(t1.value)) - return false; - - // (rgba|hsla) - if (validator.isValidRgbaColor(t2.value) || validator.isValidHslColor(t2.value) || validator.isValidHslaColor(t2.value)) - return true; - if (validator.isValidRgbaColor(t1.value) || validator.isValidHslColor(t1.value) || validator.isValidHslaColor(t1.value)) - return false; - - // Functions with the same name can override each other; same values can override each other - return canMerge.sameFunctionOrValue(t1, t2); - }, - // Use for background-image - backgroundImage: function (t1, t2) { - // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa - // Understandability: (none | url | inherit) > (same function) > (same value) - - // (none | url) - if (t2.value === 'none' || t2.value === 'inherit' || validator.isValidUrl(t2.value)) - return true; - if (t1.value === 'none' || t1.value === 'inherit' || validator.isValidUrl(t1.value)) - return false; - - // Functions with the same name can override each other; same values can override each other - return canMerge.sameFunctionOrValue(t1, t2); - } - // TODO: add more - }; - canMerge = Object.freeze(canMerge); - - // Functions for breaking up shorthands to components - var breakUp = { - // Use this for properties with 4 unit values (like margin or padding) - // NOTE: it handles shorter forms of these properties too (like, only 1, 2, or 3 units) - fourUnits: function (token) { - var descriptor = processable[token.prop]; - var result = []; - var splitval = token.value.match(new RegExp(cssUnitAnyRegexStr, 'gi')); - - if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) { - // This token is malformed and we have no idea how to fix it. So let's just keep it intact - return [token]; - } - - // Fix those that we do know how to fix - if (splitval.length < descriptor.components.length && splitval.length < 2) { - // foo{margin:1px} -> foo{margin:1px 1px} - splitval[1] = splitval[0]; - } - if (splitval.length < descriptor.components.length && splitval.length < 3) { - // foo{margin:1px 2px} -> foo{margin:1px 2px 1px} - splitval[2] = splitval[0]; - } - if (splitval.length < descriptor.components.length && splitval.length < 4) { - // foo{margin:1px 2px 3px} -> foo{margin:1px 2px 3px 2px} - splitval[3] = splitval[1]; - } - - // Now break it up to its components - for (var i = 0; i < descriptor.components.length; i++) { - var t = { - prop: descriptor.components[i], - value: splitval[i], - isImportant: token.isImportant - }; - result.push(t); - } - - return result; - }, - // Breaks up a background property value - background: function (token) { - // Default values - var result = makeDefaultProperties(['background-color', 'background-image', 'background-repeat', 'background-position', 'background-attachment'], token.isImportant); - var color = result[0], image = result[1], repeat = result[2], position = result[3], attachment = result[4]; - - // Take care of inherit - if (token.value === 'inherit') { - // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value - color.value = image.value = repeat.value = position.value = attachment.value = 'inherit'; - return result; - } - - // Break the background up into parts - var parts = token.value.split(' '); - if (parts.length === 0) { - return result; - } - - // The trick here is that we start going through the parts from the end, then stop after background repeat, - // then start from the from the beginning until we find a valid color value. What remains will be treated as background-image. - - var currentIndex = parts.length - 1; - var current = parts[currentIndex]; - // Attachment - if (validator.isValidBackgroundAttachment(current)) { - // Found attachment - attachment.value = current; - currentIndex--; - current = parts[currentIndex]; - } - // Position - var pos = parts[currentIndex - 1] + ' ' + parts[currentIndex]; - if (currentIndex >= 1 && validator.isValidBackgroundPosition(pos)) { - // Found position (containing two parts) - position.value = pos; - currentIndex -= 2; - current = parts[currentIndex]; - } - else if (currentIndex >= 0 && validator.isValidBackgroundPosition(current)) { - // Found position (containing just one part) - position.value = current; - currentIndex--; - current = parts[currentIndex]; - } - // Repeat - if (currentIndex >= 0 && validator.isValidBackgroundRepeat(current)) { - // Found repeat - repeat.value = current; - currentIndex--; - current = parts[currentIndex]; - } - // Color - var fromBeginning = 0; - if (validator.isValidColor(parts[0])) { - // Found color - color.value = parts[0]; - fromBeginning = 1; - } - // Image - image.value = (parts.splice(fromBeginning, currentIndex - fromBeginning + 1).join(' ')) || 'none'; - - return result; - }, - // Breaks up a list-style property value - listStyle: function (token) { - // Default values - var result = makeDefaultProperties(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant); - var type = result[0], position = result[1], image = result[2]; - - if (token.value === 'inherit') { - type.value = position.value = image.value = 'inherit'; - return result; - } - - var parts = token.value.split(' '); - var ci = 0; - - // Type - if (ci < parts.length && validator.isValidListStyleType(parts[ci])) { - type.value = parts[ci]; - ci++; - } - // Position - if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) { - position.value = parts[ci]; - ci++; - } - // Image - if (ci < parts.length) { - image.value = parts.splice(ci, parts.length - ci + 1).join(' '); - } - - return result; - }, - // Breaks up outline - outline: function (token) { - // Default values - var result = makeDefaultProperties(['outline-color', 'outline-style', 'outline-width'], token.isImportant); - var color = result[0], style = result[1], width = result[2]; - - // Take care of inherit - if (token.value === 'inherit' || token.value === 'inherit inherit inherit') { - color.value = style.value = width.value = 'inherit'; - return result; - } - - // NOTE: usually users don't follow the required order of parts in this shorthand, - // so we'll try to parse it caring as little about order as possible - - var parts = token.value.split(' '), w; - - if (parts.length === 0) { - return result; - } - - if (parts.length >= 1) { - // Try to find outline-width, excluding inherit because that can be anything - w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); }); - if (w.length) { - width.value = w[0]; - parts.splice(parts.indexOf(w[0]), 1); - } - } - if (parts.length >= 1) { - // Try to find outline-style, excluding inherit because that can be anything - w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); }); - if (w.length) { - style.value = w[0]; - parts.splice(parts.indexOf(w[0]), 1); - } - } - if (parts.length >= 1) { - // Find outline-color but this time can catch inherit - w = parts.filter(function(p) { return validator.isValidOutlineColor(p); }); - if (w.length) { - color.value = w[0]; - parts.splice(parts.indexOf(w[0]), 1); - } - } - - return result; - } - }; - breakUp = Object.freeze(breakUp); - - // Contains functions that can put together shorthands from their components - // NOTE: correct order of tokens is assumed inside these functions! - var putTogether = { - // Use this for properties which have four unit values (margin, padding, etc.) - // NOTE: optimizes to shorter forms too (that only specify 1, 2, or 3 values) - fourUnits: function (prop, tokens, isImportant) { - // See about irrelevant tokens - // NOTE: This will enable some crazy optimalizations for us. - if (tokens[0].isIrrelevant) { - tokens[0].value = tokens[2].value; - } - if (tokens[2].isIrrelevant) { - tokens[2].value = tokens[0].value; - } - if (tokens[1].isIrrelevant) { - tokens[1].value = tokens[3].value; - } - if (tokens[3].isIrrelevant) { - tokens[3].value = tokens[1].value; - } - if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) { - if (tokens[1].value === tokens[3].value) { - tokens[0].value = tokens[2].value = tokens[1].value; - } - else { - tokens[0].value = tokens[2].value = '0'; - } - } - if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) { - if (tokens[0].value === tokens[2].value) { - tokens[1].value = tokens[3].value = tokens[0].value; - } - else { - tokens[1].value = tokens[3].value = '0'; - } - } - - var result = { - prop: prop, - value: tokens[0].value, - isImportant: isImportant, - granularValues: { } - }; - result.granularValues[tokens[0].prop] = tokens[0].value; - result.granularValues[tokens[1].prop] = tokens[1].value; - result.granularValues[tokens[2].prop] = tokens[2].value; - result.granularValues[tokens[3].prop] = tokens[3].value; - - // If all of them are irrelevant - if (tokens[0].isIrrelevant && tokens[1].isIrrelevant && tokens[2].isIrrelevant && tokens[3].isIrrelevant) { - result.value = processable[prop].shortestValue || processable[prop].defaultValue; - return result; - } - - // 1-value short form: all four components are equal - if (tokens[0].value === tokens[1].value && tokens[0].value === tokens[2].value && tokens[0].value === tokens[3].value) { - return result; - } - result.value += ' ' + tokens[1].value; - // 2-value short form: first and third; second and fourth values are equal - if (tokens[0].value === tokens[2].value && tokens[1].value === tokens[3].value) { - return result; - } - result.value += ' ' + tokens[2].value; - // 3-value short form: second and fourth values are equal - if (tokens[1].value === tokens[3].value) { - return result; - } - // 4-value form (none of the above optimalizations could be accomplished) - result.value += ' ' + tokens[3].value; - return result; - }, - // Puts together the components by spaces and omits default values (this is the case for most shorthands) - bySpacesOmitDefaults: function (prop, tokens, isImportant) { - var result = { - prop: prop, - value: '', - isImportant: isImportant - }; - // Get irrelevant tokens - var irrelevantTokens = tokens.filter(function (t) { return t.isIrrelevant; }); - - // If every token is irrelevant, return shortest possible value, fallback to default value - if (irrelevantTokens.length === tokens.length) { - result.isIrrelevant = true; - result.value = processable[prop].shortestValue || processable[prop].defaultValue; - return result; - } - - // This will be the value of the shorthand if all the components are default - var valueIfAllDefault = processable[prop].defaultValue; - - // Go through all tokens and concatenate their values as necessary - for (var i = 0; i < tokens.length; i++) { - var token = tokens[i]; - - // Set granular value so that other parts of the code can use this for optimalization opportunities - result.granularValues = result.granularValues || { }; - result.granularValues[token.prop] = token.value; - - // Use irrelevant tokens for optimalization opportunity - if (token.isIrrelevant) { - // Get shortest possible value, fallback to default value - var tokenShortest = processable[token.prop].shortestValue || processable[token.prop].defaultValue; - // If the shortest possible value of this token is shorter than the default value of the shorthand, use it instead - if (tokenShortest.length < valueIfAllDefault.length) { - valueIfAllDefault = tokenShortest; - } - } - - // Omit default / irrelevant value - if (token.isIrrelevant || (processable[token.prop] && processable[token.prop].defaultValue === token.value)) { - continue; - } - - result.value += ' ' + token.value; - } - - result.value = result.value.trim(); - if (!result.value) { - result.value = valueIfAllDefault; - } - - return result; - }, - // Handles the cases when some or all the fine-grained properties are set to inherit - takeCareOfInherit: function (innerFunc) { - return function (prop, tokens, isImportant) { - // Filter out the inheriting and non-inheriting tokens in one iteration - var inheritingTokens = []; - var nonInheritingTokens = []; - var result2Shorthandable = []; - var i; - for (i = 0; i < tokens.length; i++) { - if (tokens[i].value === 'inherit') { - inheritingTokens.push(tokens[i]); - result2Shorthandable.push({ - prop: tokens[i].prop, - value: processable[tokens[i].prop].defaultValue, - isImportant: tokens[i].isImportant, - // Indicate that this property is irrelevant and its value can safely be set to anything else - isIrrelevant: true - }); - } - else { - nonInheritingTokens.push(tokens[i]); - result2Shorthandable.push(tokens[i]); - } - } - - // When all the tokens are 'inherit' - if (nonInheritingTokens.length === 0) { - return { - prop: prop, - value: 'inherit', - isImportant: isImportant - }; - } - // When some (but not all) of the tokens are 'inherit' - else if (inheritingTokens.length > 0) { - // Result 1. Shorthand just the inherit values and have it overridden with the non-inheriting ones - var result1 = [{ - prop: prop, - value: 'inherit', - isImportant: isImportant - }].concat(nonInheritingTokens); - - // Result 2. Shorthand every non-inherit value and then have it overridden with the inheriting ones - var result2 = [innerFunc(prop, result2Shorthandable, isImportant)].concat(inheritingTokens); - - // Return whichever is shorter - var dl1 = getDetokenizedLength(result1); - var dl2 = getDetokenizedLength(result2); - - return dl1 < dl2 ? result1 : result2; - } - // When none of tokens are 'inherit' - else { - return innerFunc(prop, tokens, isImportant); - } - }; - } - }; - putTogether = Object.freeze(putTogether); - - // Properties to process - // Extend this object in order to add support for more properties in the optimizer. - // - // Each key in this object represents a CSS property and should be an object. - // Such an object contains properties that describe how the represented CSS property should be handled. - // Possible options: - // - // * components: array (Only specify for shorthand properties.) - // Contains the names of the granular properties this shorthand compacts. - // - // * canMerge: function (Default is canMerge.sameValue - meaning that they'll only be merged if they have the same value.) - // Returns whether two tokens of this property can be merged with each other. - // This property has no meaning for shorthands. - // - // * defaultValue: string - // Specifies the default value of the property according to the CSS standard. - // For shorthand, this is used when every component is set to its default value, therefore it should be the shortest possible default value of all the components. - // - // * shortestValue: string - // Specifies the shortest possible value the property can possibly have. - // (Falls back to defaultValue if unspecified.) - // - // * breakUp: function (Only specify for shorthand properties.) - // Breaks the shorthand up to its components. - // - // * putTogether: function (Only specify for shorthand properties.) - // Puts the shorthand together from its components. - // - var processable = { - 'margin': { - components: [ - 'margin-top', - 'margin-right', - 'margin-bottom', - 'margin-left' - ], - breakUp: breakUp.fourUnits, - putTogether: putTogether.takeCareOfInherit(putTogether.fourUnits), - defaultValue: '0' - }, - 'margin-top': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'margin-right': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'margin-bottom': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'margin-left': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'padding': { - components: [ - 'padding-top', - 'padding-right', - 'padding-bottom', - 'padding-left' - ], - breakUp: breakUp.fourUnits, - putTogether: putTogether.takeCareOfInherit(putTogether.fourUnits), - defaultValue: '0' - }, - 'padding-top': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'padding-right': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'padding-bottom': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'padding-left': { - defaultValue: '0', - canMerge: canMerge.unit - }, - 'background': { - components: [ - 'background-color', - 'background-image', - 'background-repeat', - 'background-position', - 'background-attachment' - ], - breakUp: breakUp.background, - putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults), - defaultValue: '0 0', - shortestValue: '0' - }, - 'color': { - canMerge: canMerge.color, - defaultValue: 'transparent', - shortestValue: 'red' - }, - 'background-color': { - // http://www.w3schools.com/cssref/pr_background-color.asp - canMerge: canMerge.color, - defaultValue: 'transparent', - shortestValue: 'red' - }, - 'background-image': { - // http://www.w3schools.com/cssref/pr_background-image.asp - canMerge: canMerge.backgroundImage, - defaultValue: 'none' - }, - 'background-repeat': { - // http://www.w3schools.com/cssref/pr_background-repeat.asp - canMerge: canMerge.always, - defaultValue: 'repeat' - }, - 'background-position': { - // http://www.w3schools.com/cssref/pr_background-position.asp - canMerge: canMerge.always, - defaultValue: '0 0', - shortestValue: '0' - }, - 'background-attachment': { - // http://www.w3schools.com/cssref/pr_background-attachment.asp - canMerge: canMerge.always, - defaultValue: 'scroll' - }, - 'list-style': { - // http://www.w3schools.com/cssref/pr_list-style.asp - components: [ - 'list-style-type', - 'list-style-position', - 'list-style-image' - ], - canMerge: canMerge.always, - breakUp: breakUp.listStyle, - putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults), - defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for
    - shortestValue: 'none' - }, - 'list-style-type' : { - // http://www.w3schools.com/cssref/pr_list-style-type.asp - canMerge: canMerge.always, - shortestValue: 'none', - defaultValue: '__hack' - // NOTE: we can't tell the real default value here, it's 'disc' for