설명 없음

processable.js 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. // Contains the interpretation of CSS properties, as used by the property optimizer
  2. module.exports = (function () {
  3. var tokenModule = require('./token');
  4. var validator = require('./validator');
  5. var Splitter = require('../text/splitter');
  6. // Functions that decide what value can override what.
  7. // The main purpose is to disallow removing CSS fallbacks.
  8. // A separate implementation is needed for every different kind of CSS property.
  9. // -----
  10. // The generic idea is that properties that have wider browser support are 'more understandable'
  11. // than others and that 'less understandable' values can't override more understandable ones.
  12. var canOverride = {
  13. // Use when two tokens of the same property can always be merged
  14. always: function () {
  15. // NOTE: We could have (val1, val2) parameters here but jshint complains because we don't use them
  16. return true;
  17. },
  18. // Use when two tokens of the same property can only be merged if they have the same value
  19. sameValue: function(val1, val2) {
  20. return val1 === val2;
  21. },
  22. sameFunctionOrValue: function(val1, val2) {
  23. // Functions with the same name can override each other
  24. if (validator.areSameFunction(val1, val2)) {
  25. return true;
  26. }
  27. return val1 === val2;
  28. },
  29. // Use for properties containing CSS units (margin-top, padding-left, etc.)
  30. unit: function(val1, val2) {
  31. // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
  32. // Understandability: (unit without functions) > (same functions | standard functions) > anything else
  33. // NOTE: there is no point in having different vendor-specific functions override each other or standard functions,
  34. // or having standard functions override vendor-specific functions, but standard functions can override each other
  35. // NOTE: vendor-specific property values are not taken into consideration here at the moment
  36. if (validator.isValidUnitWithoutFunction(val2))
  37. return true;
  38. if (validator.isValidUnitWithoutFunction(val1))
  39. return false;
  40. // Standard non-vendor-prefixed functions can override each other
  41. if (validator.isValidFunctionWithoutVendorPrefix(val2) && validator.isValidFunctionWithoutVendorPrefix(val1)) {
  42. return true;
  43. }
  44. // Functions with the same name can override each other; same values can override each other
  45. return canOverride.sameFunctionOrValue(val1, val2);
  46. },
  47. // Use for color properties (color, background-color, border-color, etc.)
  48. color: function(val1, val2) {
  49. // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
  50. // Understandability: (hex | named) > (rgba | hsla) > (same function name) > anything else
  51. // NOTE: at this point rgb and hsl are replaced by hex values by clean-css
  52. // (hex | named)
  53. if (validator.isValidNamedColor(val2) || validator.isValidHexColor(val2))
  54. return true;
  55. if (validator.isValidNamedColor(val1) || validator.isValidHexColor(val1))
  56. return false;
  57. // (rgba|hsla)
  58. if (validator.isValidRgbaColor(val2) || validator.isValidHslaColor(val2))
  59. return true;
  60. if (validator.isValidRgbaColor(val1) || validator.isValidHslaColor(val1))
  61. return false;
  62. // Functions with the same name can override each other; same values can override each other
  63. return canOverride.sameFunctionOrValue(val1, val2);
  64. },
  65. // Use for background-image
  66. backgroundImage: function(val1, val2) {
  67. // The idea here is that 'more understandable' values override 'less understandable' values, but not vice versa
  68. // Understandability: (none | url | inherit) > (same function) > (same value)
  69. // (none | url)
  70. if (val2 === 'none' || val2 === 'inherit' || validator.isValidUrl(val2))
  71. return true;
  72. if (val1 === 'none' || val1 === 'inherit' || validator.isValidUrl(val1))
  73. return false;
  74. // Functions with the same name can override each other; same values can override each other
  75. return canOverride.sameFunctionOrValue(val1, val2);
  76. },
  77. border: function(val1, val2) {
  78. var brokenUp1 = breakUp.border(Token.tokenizeOne(val1));
  79. var brokenUp2 = breakUp.border(Token.tokenizeOne(val2));
  80. return canOverride.color(brokenUp1[2].value, brokenUp2[2].value);
  81. }
  82. };
  83. canOverride = Object.freeze(canOverride);
  84. // Functions for breaking up shorthands to components
  85. var breakUp = {};
  86. breakUp.takeCareOfFourValues = function (splitfunc) {
  87. return function (token) {
  88. var descriptor = processable[token.prop];
  89. var result = [];
  90. var splitval = splitfunc(token.value);
  91. if (splitval.length === 0 || (splitval.length < descriptor.components.length && descriptor.components.length > 4)) {
  92. // This token is malformed and we have no idea how to fix it. So let's just keep it intact
  93. return [token];
  94. }
  95. // Fix those that we do know how to fix
  96. if (splitval.length < descriptor.components.length && splitval.length < 2) {
  97. // foo{margin:1px} -> foo{margin:1px 1px}
  98. splitval[1] = splitval[0];
  99. }
  100. if (splitval.length < descriptor.components.length && splitval.length < 3) {
  101. // foo{margin:1px 2px} -> foo{margin:1px 2px 1px}
  102. splitval[2] = splitval[0];
  103. }
  104. if (splitval.length < descriptor.components.length && splitval.length < 4) {
  105. // foo{margin:1px 2px 3px} -> foo{margin:1px 2px 3px 2px}
  106. splitval[3] = splitval[1];
  107. }
  108. // Now break it up to its components
  109. for (var i = 0; i < descriptor.components.length; i++) {
  110. var t = new Token(descriptor.components[i], splitval[i], token.isImportant);
  111. result.push(t);
  112. }
  113. return result;
  114. };
  115. };
  116. // Use this when you simply want to break up four values along spaces
  117. breakUp.fourBySpaces = breakUp.takeCareOfFourValues(function (val) {
  118. return new Splitter(' ').split(val).filter(function (v) { return v; });
  119. });
  120. // Breaks up a background property value
  121. breakUp.commaSeparatedMulitpleValues = function (splitfunc) {
  122. return function (token) {
  123. if (token.value.indexOf(',') === -1)
  124. return splitfunc(token);
  125. var values = new Splitter(',').split(token.value);
  126. var components = [];
  127. for (var i = 0, l = values.length; i < l; i++) {
  128. token.value = values[i];
  129. components.push(splitfunc(token));
  130. }
  131. for (var j = 0, m = components[0].length; j < m; j++) {
  132. for (var k = 0, n = components.length, newValues = []; k < n; k++) {
  133. newValues.push(components[k][j].value);
  134. }
  135. components[0][j].value = newValues.join(',');
  136. }
  137. return components[0];
  138. };
  139. };
  140. breakUp.background = function (token) {
  141. // Default values
  142. var result = Token.makeDefaults(['background-image', 'background-position', 'background-size', 'background-repeat', 'background-attachment', 'background-color'], token.isImportant);
  143. var image = result[0];
  144. var position = result[1];
  145. var size = result[2];
  146. var repeat = result[3];
  147. var attachment = result[4];
  148. var color = result[5];
  149. var positionSet = false;
  150. // Take care of inherit
  151. if (token.value === 'inherit') {
  152. // NOTE: 'inherit' is not a valid value for background-attachment so there we'll leave the default value
  153. color.value = image.value = repeat.value = position.value = size.value = attachment.value = 'inherit';
  154. return result;
  155. }
  156. // Break the background up into parts
  157. var parts = new Splitter(' ').split(token.value);
  158. if (parts.length === 0)
  159. return result;
  160. // Iterate over all parts and try to fit them into positions
  161. for (var i = parts.length - 1; i >= 0; i--) {
  162. var currentPart = parts[i];
  163. if (validator.isValidBackgroundAttachment(currentPart)) {
  164. attachment.value = currentPart;
  165. } else if (validator.isValidBackgroundRepeat(currentPart)) {
  166. repeat.value = currentPart;
  167. } else if (validator.isValidBackgroundPositionPart(currentPart) || validator.isValidBackgroundSizePart(currentPart)) {
  168. if (i > 0) {
  169. var previousPart = parts[i - 1];
  170. if (previousPart.indexOf('/') > 0) {
  171. var twoParts = new Splitter('/').split(previousPart);
  172. size.value = twoParts.pop() + ' ' + currentPart;
  173. parts[i - 1] = twoParts.pop();
  174. } else if (i > 1 && parts[i - 2] == '/') {
  175. size.value = previousPart + ' ' + currentPart;
  176. i -= 2;
  177. } else if (parts[i - 1] == '/') {
  178. size.value = currentPart;
  179. } else {
  180. position.value = currentPart + (positionSet ? ' ' + position.value : '');
  181. positionSet = true;
  182. }
  183. } else {
  184. position.value = currentPart + (positionSet ? ' ' + position.value : '');
  185. positionSet = true;
  186. }
  187. } else if (validator.isValidBackgroundPositionAndSize(currentPart)) {
  188. var sizeValue = new Splitter('/').split(currentPart);
  189. size.value = sizeValue.pop();
  190. position.value = sizeValue.pop();
  191. } else if ((color.value == processable[color.prop].defaultValue || color.value == 'none') && validator.isValidColor(currentPart)) {
  192. color.value = currentPart;
  193. } else if (validator.isValidUrl(currentPart) || validator.isValidFunction(currentPart)) {
  194. image.value = currentPart;
  195. }
  196. }
  197. return result;
  198. };
  199. // Breaks up a list-style property value
  200. breakUp.listStyle = function (token) {
  201. // Default values
  202. var result = Token.makeDefaults(['list-style-type', 'list-style-position', 'list-style-image'], token.isImportant);
  203. var type = result[0], position = result[1], image = result[2];
  204. if (token.value === 'inherit') {
  205. type.value = position.value = image.value = 'inherit';
  206. return result;
  207. }
  208. var parts = new Splitter(' ').split(token.value);
  209. var ci = 0;
  210. // Type
  211. if (ci < parts.length && validator.isValidListStyleType(parts[ci])) {
  212. type.value = parts[ci];
  213. ci++;
  214. }
  215. // Position
  216. if (ci < parts.length && validator.isValidListStylePosition(parts[ci])) {
  217. position.value = parts[ci];
  218. ci++;
  219. }
  220. // Image
  221. if (ci < parts.length) {
  222. image.value = parts.splice(ci, parts.length - ci + 1).join(' ');
  223. }
  224. return result;
  225. };
  226. breakUp._widthStyleColor = function(token, prefix, order) {
  227. // Default values
  228. var components = order.map(function(prop) {
  229. return prefix + '-' + prop;
  230. });
  231. var result = Token.makeDefaults(components, token.isImportant);
  232. var color = result[order.indexOf('color')];
  233. var style = result[order.indexOf('style')];
  234. var width = result[order.indexOf('width')];
  235. // Take care of inherit
  236. if (token.value === 'inherit' || token.value === 'inherit inherit inherit') {
  237. color.value = style.value = width.value = 'inherit';
  238. return result;
  239. }
  240. // NOTE: usually users don't follow the required order of parts in this shorthand,
  241. // so we'll try to parse it caring as little about order as possible
  242. var parts = new Splitter(' ').split(token.value), w;
  243. if (parts.length === 0) {
  244. return result;
  245. }
  246. if (parts.length >= 1) {
  247. // Try to find -width, excluding inherit because that can be anything
  248. w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineWidth(p); });
  249. if (w.length) {
  250. width.value = w[0];
  251. parts.splice(parts.indexOf(w[0]), 1);
  252. }
  253. }
  254. if (parts.length >= 1) {
  255. // Try to find -style, excluding inherit because that can be anything
  256. w = parts.filter(function(p) { return p !== 'inherit' && validator.isValidOutlineStyle(p); });
  257. if (w.length) {
  258. style.value = w[0];
  259. parts.splice(parts.indexOf(w[0]), 1);
  260. }
  261. }
  262. if (parts.length >= 1) {
  263. // Find -color but this time can catch inherit
  264. w = parts.filter(function(p) { return validator.isValidOutlineColor(p); });
  265. if (w.length) {
  266. color.value = w[0];
  267. parts.splice(parts.indexOf(w[0]), 1);
  268. }
  269. }
  270. return result;
  271. };
  272. breakUp.outline = function(token) {
  273. return breakUp._widthStyleColor(token, 'outline', ['color', 'style', 'width']);
  274. };
  275. breakUp.border = function(token) {
  276. return breakUp._widthStyleColor(token, 'border', ['width', 'style', 'color']);
  277. };
  278. breakUp.borderRadius = function(token) {
  279. var parts = token.value.split('/');
  280. if (parts.length == 1)
  281. return breakUp.fourBySpaces(token);
  282. var horizontalPart = token.clone();
  283. var verticalPart = token.clone();
  284. horizontalPart.value = parts[0];
  285. verticalPart.value = parts[1];
  286. var horizontalBreakUp = breakUp.fourBySpaces(horizontalPart);
  287. var verticalBreakUp = breakUp.fourBySpaces(verticalPart);
  288. for (var i = 0; i < 4; i++) {
  289. horizontalBreakUp[i].value = [horizontalBreakUp[i].value, verticalBreakUp[i].value];
  290. }
  291. return horizontalBreakUp;
  292. };
  293. // Contains functions that can put together shorthands from their components
  294. // NOTE: correct order of tokens is assumed inside these functions!
  295. var putTogether = {
  296. // Use this for properties which have four unit values (margin, padding, etc.)
  297. // NOTE: optimizes to shorter forms too (that only specify 1, 2, or 3 values)
  298. fourUnits: function (prop, tokens, isImportant) {
  299. // See about irrelevant tokens
  300. // NOTE: This will enable some crazy optimalizations for us.
  301. if (tokens[0].isIrrelevant)
  302. tokens[0].value = tokens[2].value;
  303. if (tokens[2].isIrrelevant)
  304. tokens[2].value = tokens[0].value;
  305. if (tokens[1].isIrrelevant)
  306. tokens[1].value = tokens[3].value;
  307. if (tokens[3].isIrrelevant)
  308. tokens[3].value = tokens[1].value;
  309. if (tokens[0].isIrrelevant && tokens[2].isIrrelevant) {
  310. if (tokens[1].value === tokens[3].value)
  311. tokens[0].value = tokens[2].value = tokens[1].value;
  312. else
  313. tokens[0].value = tokens[2].value = '0';
  314. }
  315. if (tokens[1].isIrrelevant && tokens[3].isIrrelevant) {
  316. if (tokens[0].value === tokens[2].value)
  317. tokens[1].value = tokens[3].value = tokens[0].value;
  318. else
  319. tokens[1].value = tokens[3].value = '0';
  320. }
  321. var result = new Token(prop, tokens[0].value, isImportant);
  322. result.granularValues = [];
  323. result.granularValues[tokens[0].prop] = tokens[0].value;
  324. result.granularValues[tokens[1].prop] = tokens[1].value;
  325. result.granularValues[tokens[2].prop] = tokens[2].value;
  326. result.granularValues[tokens[3].prop] = tokens[3].value;
  327. // If all of them are irrelevant
  328. if (tokens[0].isIrrelevant && tokens[1].isIrrelevant && tokens[2].isIrrelevant && tokens[3].isIrrelevant) {
  329. result.value = processable[prop].shortestValue || processable[prop].defaultValue;
  330. return result;
  331. }
  332. // 1-value short form: all four components are equal
  333. if (tokens[0].value === tokens[1].value && tokens[0].value === tokens[2].value && tokens[0].value === tokens[3].value) {
  334. return result;
  335. }
  336. result.value += ' ' + tokens[1].value;
  337. // 2-value short form: first and third; second and fourth values are equal
  338. if (tokens[0].value === tokens[2].value && tokens[1].value === tokens[3].value) {
  339. return result;
  340. }
  341. result.value += ' ' + tokens[2].value;
  342. // 3-value short form: second and fourth values are equal
  343. if (tokens[1].value === tokens[3].value) {
  344. return result;
  345. }
  346. // 4-value form (none of the above optimalizations could be accomplished)
  347. result.value += ' ' + tokens[3].value;
  348. return result;
  349. },
  350. // Puts together the components by spaces and omits default values (this is the case for most shorthands)
  351. bySpacesOmitDefaults: function (prop, tokens, isImportant, meta) {
  352. var result = new Token(prop, '', isImportant);
  353. // Get irrelevant tokens
  354. var irrelevantTokens = tokens.filter(function (t) { return t.isIrrelevant; });
  355. // If every token is irrelevant, return shortest possible value, fallback to default value
  356. if (irrelevantTokens.length === tokens.length) {
  357. result.isIrrelevant = true;
  358. result.value = processable[prop].shortestValue || processable[prop].defaultValue;
  359. return result;
  360. }
  361. // This will be the value of the shorthand if all the components are default
  362. var valueIfAllDefault = processable[prop].defaultValue;
  363. // Go through all tokens and concatenate their values as necessary
  364. for (var i = 0; i < tokens.length; i++) {
  365. var token = tokens[i];
  366. // Set granular value so that other parts of the code can use this for optimalization opportunities
  367. result.granularValues = result.granularValues || { };
  368. result.granularValues[token.prop] = token.value;
  369. // Use irrelevant tokens for optimalization opportunity
  370. if (token.isIrrelevant) {
  371. // Get shortest possible value, fallback to default value
  372. var tokenShortest = processable[token.prop].shortestValue || processable[token.prop].defaultValue;
  373. // If the shortest possible value of this token is shorter than the default value of the shorthand, use it instead
  374. if (tokenShortest.length < valueIfAllDefault.length) {
  375. valueIfAllDefault = tokenShortest;
  376. }
  377. }
  378. // Omit default / irrelevant value
  379. if (token.isIrrelevant || (processable[token.prop] && processable[token.prop].defaultValue === token.value)) {
  380. continue;
  381. }
  382. if (meta && meta.partsCount && meta.position < meta.partsCount - 1 && processable[token.prop].multiValueLastOnly)
  383. continue;
  384. var requiresPreceeding = processable[token.prop].shorthandFollows;
  385. if (requiresPreceeding && (tokens[i - 1].value == processable[requiresPreceeding].defaultValue)) {
  386. result.value += ' ' + tokens[i - 1].value;
  387. }
  388. result.value += (processable[token.prop].prefixShorthandValueWith || ' ') + token.value;
  389. }
  390. result.value = result.value.trim();
  391. if (!result.value) {
  392. result.value = valueIfAllDefault;
  393. }
  394. return result;
  395. },
  396. commaSeparatedMulitpleValues: function (assembleFunction) {
  397. return function(prop, tokens, isImportant) {
  398. var tokenSplitLengths = tokens.map(function (token) {
  399. return new Splitter(',').split(token.value).length;
  400. });
  401. var partsCount = Math.max.apply(Math, tokenSplitLengths);
  402. if (partsCount == 1)
  403. return assembleFunction(prop, tokens, isImportant);
  404. var merged = [];
  405. for (var i = 0; i < partsCount; i++) {
  406. merged.push([]);
  407. for (var j = 0; j < tokens.length; j++) {
  408. var split = new Splitter(',').split(tokens[j].value);
  409. merged[i].push(split[i] || split[0]);
  410. }
  411. }
  412. var mergedValues = [];
  413. var firstProcessed;
  414. for (i = 0; i < partsCount; i++) {
  415. var newTokens = [];
  416. for (var k = 0, n = merged[i].length; k < n; k++) {
  417. var newToken = tokens[k].clone();
  418. newToken.value = merged[i][k];
  419. newTokens.push(newToken);
  420. }
  421. var meta = {
  422. partsCount: partsCount,
  423. position: i
  424. };
  425. var processed = assembleFunction(prop, newTokens, isImportant, meta);
  426. mergedValues.push(processed.value);
  427. if (!firstProcessed)
  428. firstProcessed = processed;
  429. }
  430. firstProcessed.value = mergedValues.join(',');
  431. return firstProcessed;
  432. };
  433. },
  434. // Handles the cases when some or all the fine-grained properties are set to inherit
  435. takeCareOfInherit: function (innerFunc) {
  436. return function (prop, tokens, isImportant, meta) {
  437. // Filter out the inheriting and non-inheriting tokens in one iteration
  438. var inheritingTokens = [];
  439. var nonInheritingTokens = [];
  440. var result2Shorthandable = [];
  441. var i;
  442. for (i = 0; i < tokens.length; i++) {
  443. if (tokens[i].value === 'inherit') {
  444. inheritingTokens.push(tokens[i]);
  445. // Indicate that this property is irrelevant and its value can safely be set to anything else
  446. var r2s = new Token(tokens[i].prop, tokens[i].isImportant);
  447. r2s.isIrrelevant = true;
  448. result2Shorthandable.push(r2s);
  449. } else {
  450. nonInheritingTokens.push(tokens[i]);
  451. result2Shorthandable.push(tokens[i]);
  452. }
  453. }
  454. if (nonInheritingTokens.length === 0) {
  455. // When all the tokens are 'inherit'
  456. return new Token(prop, 'inherit', isImportant);
  457. } else if (inheritingTokens.length > 0) {
  458. // When some (but not all) of the tokens are 'inherit'
  459. // Result 1. Shorthand just the inherit values and have it overridden with the non-inheriting ones
  460. var result1 = [new Token(prop, 'inherit', isImportant)].concat(nonInheritingTokens);
  461. // Result 2. Shorthand every non-inherit value and then have it overridden with the inheriting ones
  462. var result2 = [innerFunc(prop, result2Shorthandable, isImportant, meta)].concat(inheritingTokens);
  463. // Return whichever is shorter
  464. var dl1 = Token.getDetokenizedLength(result1);
  465. var dl2 = Token.getDetokenizedLength(result2);
  466. return dl1 < dl2 ? result1 : result2;
  467. } else {
  468. // When none of tokens are 'inherit'
  469. return innerFunc(prop, tokens, isImportant, meta);
  470. }
  471. };
  472. },
  473. borderRadius: function (prop, tokens, isImportant) {
  474. var verticalTokens = [];
  475. var newTokens = [];
  476. for (var i = 0, l = tokens.length; i < l; i++) {
  477. var token = tokens[i];
  478. var newToken = token.clone();
  479. newTokens.push(newToken);
  480. if (!Array.isArray(token.value))
  481. continue;
  482. if (token.value.length > 1) {
  483. verticalTokens.push({
  484. prop: token.prop,
  485. value: token.value[1],
  486. isImportant: token.isImportant
  487. });
  488. }
  489. newToken.value = token.value[0];
  490. }
  491. var result = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, newTokens, isImportant);
  492. if (verticalTokens.length > 0) {
  493. var verticalResult = putTogether.takeCareOfInherit(putTogether.fourUnits)(prop, verticalTokens, isImportant);
  494. if (result.value != verticalResult.value)
  495. result.value += '/' + verticalResult.value;
  496. }
  497. return result;
  498. }
  499. };
  500. // Properties to process
  501. // Extend this object in order to add support for more properties in the optimizer.
  502. //
  503. // Each key in this object represents a CSS property and should be an object.
  504. // Such an object contains properties that describe how the represented CSS property should be handled.
  505. // Possible options:
  506. //
  507. // * components: array (Only specify for shorthand properties.)
  508. // Contains the names of the granular properties this shorthand compacts.
  509. //
  510. // * canOverride: function (Default is canOverride.sameValue - meaning that they'll only be merged if they have the same value.)
  511. // Returns whether two tokens of this property can be merged with each other.
  512. // This property has no meaning for shorthands.
  513. //
  514. // * defaultValue: string
  515. // Specifies the default value of the property according to the CSS standard.
  516. // 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.
  517. //
  518. // * shortestValue: string
  519. // Specifies the shortest possible value the property can possibly have.
  520. // (Falls back to defaultValue if unspecified.)
  521. //
  522. // * breakUp: function (Only specify for shorthand properties.)
  523. // Breaks the shorthand up to its components.
  524. //
  525. // * putTogether: function (Only specify for shorthand properties.)
  526. // Puts the shorthand together from its components.
  527. //
  528. var processable = {
  529. 'color': {
  530. canOverride: canOverride.color,
  531. defaultValue: 'transparent',
  532. shortestValue: 'red'
  533. },
  534. // background ------------------------------------------------------------------------------
  535. 'background': {
  536. components: [
  537. 'background-image',
  538. 'background-position',
  539. 'background-size',
  540. 'background-repeat',
  541. 'background-attachment',
  542. 'background-color'
  543. ],
  544. breakUp: breakUp.commaSeparatedMulitpleValues(breakUp.background),
  545. putTogether: putTogether.commaSeparatedMulitpleValues(
  546. putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
  547. ),
  548. defaultValue: '0 0',
  549. shortestValue: '0'
  550. },
  551. 'background-color': {
  552. canOverride: canOverride.color,
  553. defaultValue: 'transparent',
  554. multiValueLastOnly: true,
  555. shortestValue: 'red'
  556. },
  557. 'background-image': {
  558. canOverride: canOverride.backgroundImage,
  559. defaultValue: 'none'
  560. },
  561. 'background-repeat': {
  562. canOverride: canOverride.always,
  563. defaultValue: 'repeat'
  564. },
  565. 'background-position': {
  566. canOverride: canOverride.always,
  567. defaultValue: '0 0',
  568. shortestValue: '0'
  569. },
  570. 'background-size': {
  571. canOverride: canOverride.always,
  572. defaultValue: 'auto',
  573. shortestValue: '0 0',
  574. prefixShorthandValueWith: '/',
  575. shorthandFollows: 'background-position'
  576. },
  577. 'background-attachment': {
  578. canOverride: canOverride.always,
  579. defaultValue: 'scroll'
  580. },
  581. 'border': {
  582. breakUp: breakUp.border,
  583. canOverride: canOverride.border,
  584. components: [
  585. 'border-width',
  586. 'border-style',
  587. 'border-color'
  588. ],
  589. defaultValue: 'none',
  590. putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults)
  591. },
  592. 'border-color': {
  593. canOverride: canOverride.color,
  594. defaultValue: 'none'
  595. },
  596. 'border-style': {
  597. canOverride: canOverride.always,
  598. defaultValue: 'none'
  599. },
  600. 'border-width': {
  601. canOverride: canOverride.unit,
  602. defaultValue: 'medium',
  603. shortestValue: '0'
  604. },
  605. // list-style ------------------------------------------------------------------------------
  606. 'list-style': {
  607. components: [
  608. 'list-style-type',
  609. 'list-style-position',
  610. 'list-style-image'
  611. ],
  612. canOverride: canOverride.always,
  613. breakUp: breakUp.listStyle,
  614. putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
  615. defaultValue: 'outside', // can't use 'disc' because that'd override default 'decimal' for <ol>
  616. shortestValue: 'none'
  617. },
  618. 'list-style-type' : {
  619. canOverride: canOverride.always,
  620. shortestValue: 'none',
  621. defaultValue: '__hack'
  622. // NOTE: we can't tell the real default value here, it's 'disc' for <ul> and 'decimal' for <ol>
  623. // -- this is a hack, but it doesn't matter because this value will be either overridden or it will disappear at the final step anyway
  624. },
  625. 'list-style-position' : {
  626. canOverride: canOverride.always,
  627. defaultValue: 'outside',
  628. shortestValue: 'inside'
  629. },
  630. 'list-style-image' : {
  631. canOverride: canOverride.always,
  632. defaultValue: 'none'
  633. },
  634. // outline ------------------------------------------------------------------------------
  635. 'outline': {
  636. components: [
  637. 'outline-color',
  638. 'outline-style',
  639. 'outline-width'
  640. ],
  641. breakUp: breakUp.outline,
  642. putTogether: putTogether.takeCareOfInherit(putTogether.bySpacesOmitDefaults),
  643. defaultValue: '0'
  644. },
  645. 'outline-color': {
  646. canOverride: canOverride.color,
  647. defaultValue: 'invert',
  648. shortestValue: 'red'
  649. },
  650. 'outline-style': {
  651. canOverride: canOverride.always,
  652. defaultValue: 'none'
  653. },
  654. 'outline-width': {
  655. canOverride: canOverride.unit,
  656. defaultValue: 'medium',
  657. shortestValue: '0'
  658. },
  659. // transform
  660. '-moz-transform': {
  661. canOverride: canOverride.sameFunctionOrValue
  662. },
  663. '-ms-transform': {
  664. canOverride: canOverride.sameFunctionOrValue
  665. },
  666. '-webkit-transform': {
  667. canOverride: canOverride.sameFunctionOrValue
  668. },
  669. 'transform': {
  670. canOverride: canOverride.sameFunctionOrValue
  671. }
  672. };
  673. var addFourValueShorthand = function (prop, components, options) {
  674. options = options || {};
  675. processable[prop] = {
  676. components: components,
  677. breakUp: options.breakUp || breakUp.fourBySpaces,
  678. putTogether: options.putTogether || putTogether.takeCareOfInherit(putTogether.fourUnits),
  679. defaultValue: options.defaultValue || '0',
  680. shortestValue: options.shortestValue
  681. };
  682. for (var i = 0; i < components.length; i++) {
  683. processable[components[i]] = {
  684. breakUp: options.breakUp || breakUp.fourBySpaces,
  685. canOverride: options.canOverride || canOverride.unit,
  686. defaultValue: options.defaultValue || '0',
  687. shortestValue: options.shortestValue
  688. };
  689. }
  690. };
  691. ['', '-moz-', '-o-', '-webkit-'].forEach(function (prefix) {
  692. addFourValueShorthand(prefix + 'border-radius', [
  693. prefix + 'border-top-left-radius',
  694. prefix + 'border-top-right-radius',
  695. prefix + 'border-bottom-right-radius',
  696. prefix + 'border-bottom-left-radius'
  697. ], {
  698. breakUp: breakUp.borderRadius,
  699. putTogether: putTogether.borderRadius
  700. });
  701. });
  702. addFourValueShorthand('border-color', [
  703. 'border-top-color',
  704. 'border-right-color',
  705. 'border-bottom-color',
  706. 'border-left-color'
  707. ], {
  708. breakUp: breakUp.fourBySpaces,
  709. canOverride: canOverride.color,
  710. defaultValue: 'currentColor',
  711. shortestValue: 'red'
  712. });
  713. addFourValueShorthand('border-style', [
  714. 'border-top-style',
  715. 'border-right-style',
  716. 'border-bottom-style',
  717. 'border-left-style'
  718. ], {
  719. breakUp: breakUp.fourBySpaces,
  720. canOverride: canOverride.always,
  721. defaultValue: 'none'
  722. });
  723. addFourValueShorthand('border-width', [
  724. 'border-top-width',
  725. 'border-right-width',
  726. 'border-bottom-width',
  727. 'border-left-width'
  728. ], {
  729. defaultValue: 'medium',
  730. shortestValue: '0'
  731. });
  732. addFourValueShorthand('padding', [
  733. 'padding-top',
  734. 'padding-right',
  735. 'padding-bottom',
  736. 'padding-left'
  737. ]);
  738. addFourValueShorthand('margin', [
  739. 'margin-top',
  740. 'margin-right',
  741. 'margin-bottom',
  742. 'margin-left'
  743. ]);
  744. // Set some stuff iteratively
  745. for (var proc in processable) {
  746. if (!processable.hasOwnProperty(proc))
  747. continue;
  748. var currDesc = processable[proc];
  749. if (!(currDesc.components instanceof Array) || currDesc.components.length === 0)
  750. continue;
  751. currDesc.isShorthand = true;
  752. for (var cI = 0; cI < currDesc.components.length; cI++) {
  753. if (!processable[currDesc.components[cI]]) {
  754. throw new Error('"' + currDesc.components[cI] + '" is defined as a component of "' + proc + '" but isn\'t defined in processable.');
  755. }
  756. processable[currDesc.components[cI]].componentOf = proc;
  757. }
  758. }
  759. var Token = tokenModule.createTokenPrototype(processable);
  760. return {
  761. implementedFor: /background|border|color|list|margin|outline|padding|transform/,
  762. processable: processable,
  763. Token: Token
  764. };
  765. })();