Upstream version 8.37.180.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / css / StylePropertySerializer.cpp
1 /*
2  * (C) 1999-2003 Lars Knoll (knoll@kde.org)
3  * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
4  * Copyright (C) 2011 Research In Motion Limited. All rights reserved.
5  * Copyright (C) 2013 Intel Corporation. All rights reserved.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Library General Public License
18  * along with this library; see the file COPYING.LIB.  If not, write to
19  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21 */
22
23 #include "config.h"
24 #include "core/css/StylePropertySerializer.h"
25
26 #include "core/CSSValueKeywords.h"
27 #include "core/StylePropertyShorthand.h"
28 #include "core/css/RuntimeCSSEnabled.h"
29 #include "wtf/BitArray.h"
30 #include "wtf/text/StringBuilder.h"
31
32 namespace WebCore {
33
34 static bool isInitialOrInherit(const String& value)
35 {
36     DEFINE_STATIC_LOCAL(String, initial, ("initial"));
37     DEFINE_STATIC_LOCAL(String, inherit, ("inherit"));
38     return value.length() == 7 && (value == initial || value == inherit);
39 }
40
41 StylePropertySerializer::StylePropertySerializer(const StylePropertySet& properties)
42     : m_propertySet(properties)
43 {
44 }
45
46 String StylePropertySerializer::getPropertyText(CSSPropertyID propertyID, const String& value, bool isImportant, bool isNotFirstDecl) const
47 {
48     StringBuilder result;
49     if (isNotFirstDecl)
50         result.append(' ');
51     result.append(getPropertyName(propertyID));
52     result.appendLiteral(": ");
53     result.append(value);
54     if (isImportant)
55         result.appendLiteral(" !important");
56     result.append(';');
57     return result.toString();
58 }
59
60 String StylePropertySerializer::asText() const
61 {
62     StringBuilder result;
63
64     BitArray<numCSSProperties> shorthandPropertyUsed;
65     BitArray<numCSSProperties> shorthandPropertyAppeared;
66
67     unsigned size = m_propertySet.propertyCount();
68     unsigned numDecls = 0;
69     for (unsigned n = 0; n < size; ++n) {
70         StylePropertySet::PropertyReference property = m_propertySet.propertyAt(n);
71         CSSPropertyID propertyID = property.id();
72         // Only enabled or internal properties should be part of the style.
73         ASSERT(RuntimeCSSEnabled::isCSSPropertyEnabled(propertyID) || isInternalProperty(propertyID));
74         CSSPropertyID shorthandPropertyID = CSSPropertyInvalid;
75         CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid;
76         String value;
77
78         switch (propertyID) {
79         case CSSPropertyBackgroundAttachment:
80         case CSSPropertyBackgroundClip:
81         case CSSPropertyBackgroundColor:
82         case CSSPropertyBackgroundImage:
83         case CSSPropertyBackgroundOrigin:
84         case CSSPropertyBackgroundPositionX:
85         case CSSPropertyBackgroundPositionY:
86         case CSSPropertyBackgroundSize:
87         case CSSPropertyBackgroundRepeatX:
88         case CSSPropertyBackgroundRepeatY:
89             shorthandPropertyAppeared.set(CSSPropertyBackground - firstCSSProperty);
90             continue;
91         case CSSPropertyContent:
92             if (property.value()->isValueList())
93                 value = toCSSValueList(property.value())->customCSSText(AlwaysQuoteCSSString);
94             break;
95         case CSSPropertyBorderTopWidth:
96         case CSSPropertyBorderRightWidth:
97         case CSSPropertyBorderBottomWidth:
98         case CSSPropertyBorderLeftWidth:
99             if (!borderFallbackShorthandProperty)
100                 borderFallbackShorthandProperty = CSSPropertyBorderWidth;
101         case CSSPropertyBorderTopStyle:
102         case CSSPropertyBorderRightStyle:
103         case CSSPropertyBorderBottomStyle:
104         case CSSPropertyBorderLeftStyle:
105             if (!borderFallbackShorthandProperty)
106                 borderFallbackShorthandProperty = CSSPropertyBorderStyle;
107         case CSSPropertyBorderTopColor:
108         case CSSPropertyBorderRightColor:
109         case CSSPropertyBorderBottomColor:
110         case CSSPropertyBorderLeftColor:
111             if (!borderFallbackShorthandProperty)
112                 borderFallbackShorthandProperty = CSSPropertyBorderColor;
113
114             // FIXME: Deal with cases where only some of border-(top|right|bottom|left) are specified.
115             if (!shorthandPropertyAppeared.get(CSSPropertyBorder - firstCSSProperty)) {
116                 value = borderPropertyValue(ReturnNullOnUncommonValues);
117                 if (value.isNull())
118                     shorthandPropertyAppeared.set(CSSPropertyBorder - firstCSSProperty);
119                 else
120                     shorthandPropertyID = CSSPropertyBorder;
121             } else if (shorthandPropertyUsed.get(CSSPropertyBorder - firstCSSProperty))
122                 shorthandPropertyID = CSSPropertyBorder;
123             if (!shorthandPropertyID)
124                 shorthandPropertyID = borderFallbackShorthandProperty;
125             break;
126         case CSSPropertyWebkitBorderHorizontalSpacing:
127         case CSSPropertyWebkitBorderVerticalSpacing:
128             shorthandPropertyID = CSSPropertyBorderSpacing;
129             break;
130         case CSSPropertyFontFamily:
131         case CSSPropertyLineHeight:
132         case CSSPropertyFontSize:
133         case CSSPropertyFontStyle:
134         case CSSPropertyFontVariant:
135         case CSSPropertyFontWeight:
136             // Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing.
137             break;
138         case CSSPropertyListStyleType:
139         case CSSPropertyListStylePosition:
140         case CSSPropertyListStyleImage:
141             shorthandPropertyID = CSSPropertyListStyle;
142             break;
143         case CSSPropertyMarginTop:
144         case CSSPropertyMarginRight:
145         case CSSPropertyMarginBottom:
146         case CSSPropertyMarginLeft:
147             shorthandPropertyID = CSSPropertyMargin;
148             break;
149         case CSSPropertyOutlineWidth:
150         case CSSPropertyOutlineStyle:
151         case CSSPropertyOutlineColor:
152             shorthandPropertyID = CSSPropertyOutline;
153             break;
154         case CSSPropertyOverflowX:
155         case CSSPropertyOverflowY:
156             shorthandPropertyID = CSSPropertyOverflow;
157             break;
158         case CSSPropertyPaddingTop:
159         case CSSPropertyPaddingRight:
160         case CSSPropertyPaddingBottom:
161         case CSSPropertyPaddingLeft:
162             shorthandPropertyID = CSSPropertyPadding;
163             break;
164         case CSSPropertyTransitionProperty:
165         case CSSPropertyTransitionDuration:
166         case CSSPropertyTransitionTimingFunction:
167         case CSSPropertyTransitionDelay:
168             shorthandPropertyID = CSSPropertyTransition;
169             break;
170         case CSSPropertyWebkitAnimationName:
171         case CSSPropertyWebkitAnimationDuration:
172         case CSSPropertyWebkitAnimationTimingFunction:
173         case CSSPropertyWebkitAnimationDelay:
174         case CSSPropertyWebkitAnimationIterationCount:
175         case CSSPropertyWebkitAnimationDirection:
176         case CSSPropertyWebkitAnimationFillMode:
177             shorthandPropertyID = CSSPropertyWebkitAnimation;
178             break;
179         case CSSPropertyFlexDirection:
180         case CSSPropertyFlexWrap:
181             shorthandPropertyID = CSSPropertyFlexFlow;
182             break;
183         case CSSPropertyFlexBasis:
184         case CSSPropertyFlexGrow:
185         case CSSPropertyFlexShrink:
186             shorthandPropertyID = CSSPropertyFlex;
187             break;
188         case CSSPropertyWebkitMaskPositionX:
189         case CSSPropertyWebkitMaskPositionY:
190         case CSSPropertyWebkitMaskRepeatX:
191         case CSSPropertyWebkitMaskRepeatY:
192         case CSSPropertyWebkitMaskImage:
193         case CSSPropertyWebkitMaskRepeat:
194         case CSSPropertyWebkitMaskPosition:
195         case CSSPropertyWebkitMaskClip:
196         case CSSPropertyWebkitMaskOrigin:
197             shorthandPropertyID = CSSPropertyWebkitMask;
198             break;
199         case CSSPropertyWebkitTransformOriginX:
200         case CSSPropertyWebkitTransformOriginY:
201         case CSSPropertyWebkitTransformOriginZ:
202             shorthandPropertyID = CSSPropertyWebkitTransformOrigin;
203             break;
204         case CSSPropertyWebkitTransitionProperty:
205         case CSSPropertyWebkitTransitionDuration:
206         case CSSPropertyWebkitTransitionTimingFunction:
207         case CSSPropertyWebkitTransitionDelay:
208             shorthandPropertyID = CSSPropertyWebkitTransition;
209             break;
210         default:
211             break;
212         }
213
214         unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty;
215         if (shorthandPropertyID) {
216             if (shorthandPropertyUsed.get(shortPropertyIndex))
217                 continue;
218             if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull())
219                 value = m_propertySet.getPropertyValue(shorthandPropertyID);
220             shorthandPropertyAppeared.set(shortPropertyIndex);
221         }
222
223         if (!value.isNull()) {
224             if (shorthandPropertyID) {
225                 propertyID = shorthandPropertyID;
226                 shorthandPropertyUsed.set(shortPropertyIndex);
227             }
228         } else
229             value = property.value()->cssText();
230
231         if (value == "initial" && !CSSProperty::isInheritedProperty(propertyID))
232             continue;
233
234         result.append(getPropertyText(propertyID, value, property.isImportant(), numDecls++));
235     }
236
237     if (shorthandPropertyAppeared.get(CSSPropertyBackground - firstCSSProperty))
238         appendBackgroundPropertyAsText(result, numDecls);
239
240     ASSERT(!numDecls ^ !result.isEmpty());
241     return result.toString();
242 }
243
244 String StylePropertySerializer::getPropertyValue(CSSPropertyID propertyID) const
245 {
246     // Shorthand and 4-values properties
247     switch (propertyID) {
248     case CSSPropertyAnimation:
249         return getLayeredShorthandValue(animationShorthand());
250     case CSSPropertyBorderSpacing:
251         return borderSpacingValue(borderSpacingShorthand());
252     case CSSPropertyBackgroundPosition:
253         return getLayeredShorthandValue(backgroundPositionShorthand());
254     case CSSPropertyBackgroundRepeat:
255         return backgroundRepeatPropertyValue();
256     case CSSPropertyBackground:
257         return getLayeredShorthandValue(backgroundShorthand());
258     case CSSPropertyBorder:
259         return borderPropertyValue(OmitUncommonValues);
260     case CSSPropertyBorderTop:
261         return getShorthandValue(borderTopShorthand());
262     case CSSPropertyBorderRight:
263         return getShorthandValue(borderRightShorthand());
264     case CSSPropertyBorderBottom:
265         return getShorthandValue(borderBottomShorthand());
266     case CSSPropertyBorderLeft:
267         return getShorthandValue(borderLeftShorthand());
268     case CSSPropertyOutline:
269         return getShorthandValue(outlineShorthand());
270     case CSSPropertyBorderColor:
271         return get4Values(borderColorShorthand());
272     case CSSPropertyBorderWidth:
273         return get4Values(borderWidthShorthand());
274     case CSSPropertyBorderStyle:
275         return get4Values(borderStyleShorthand());
276     case CSSPropertyWebkitColumnRule:
277         return getShorthandValue(webkitColumnRuleShorthand());
278     case CSSPropertyWebkitColumns:
279         return getShorthandValue(webkitColumnsShorthand());
280     case CSSPropertyFlex:
281         return getShorthandValue(flexShorthand());
282     case CSSPropertyFlexFlow:
283         return getShorthandValue(flexFlowShorthand());
284     case CSSPropertyGridColumn:
285         return getShorthandValue(gridColumnShorthand());
286     case CSSPropertyGridRow:
287         return getShorthandValue(gridRowShorthand());
288     case CSSPropertyGridArea:
289         return getShorthandValue(gridAreaShorthand());
290     case CSSPropertyFont:
291         return fontValue();
292     case CSSPropertyMargin:
293         return get4Values(marginShorthand());
294     case CSSPropertyWebkitMarginCollapse:
295         return getShorthandValue(webkitMarginCollapseShorthand());
296     case CSSPropertyOverflow:
297         return getCommonValue(overflowShorthand());
298     case CSSPropertyPadding:
299         return get4Values(paddingShorthand());
300     case CSSPropertyTransition:
301         return getLayeredShorthandValue(transitionShorthand());
302     case CSSPropertyListStyle:
303         return getShorthandValue(listStyleShorthand());
304     case CSSPropertyWebkitMaskPosition:
305         return getLayeredShorthandValue(webkitMaskPositionShorthand());
306     case CSSPropertyWebkitMaskRepeat:
307         return getLayeredShorthandValue(webkitMaskRepeatShorthand());
308     case CSSPropertyWebkitMask:
309         return getLayeredShorthandValue(webkitMaskShorthand());
310     case CSSPropertyWebkitTextEmphasis:
311         return getShorthandValue(webkitTextEmphasisShorthand());
312     case CSSPropertyWebkitTextStroke:
313         return getShorthandValue(webkitTextStrokeShorthand());
314     case CSSPropertyTransformOrigin:
315     case CSSPropertyWebkitTransformOrigin:
316         return getShorthandValue(webkitTransformOriginShorthand());
317     case CSSPropertyWebkitTransition:
318         return getLayeredShorthandValue(webkitTransitionShorthand());
319     case CSSPropertyWebkitAnimation:
320         return getLayeredShorthandValue(webkitAnimationShorthand());
321     case CSSPropertyMarker: {
322         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyMarkerStart);
323         if (value)
324             return value->cssText();
325         return String();
326     }
327     case CSSPropertyBorderRadius:
328         return get4Values(borderRadiusShorthand());
329     default:
330         return String();
331     }
332 }
333
334 String StylePropertySerializer::borderSpacingValue(const StylePropertyShorthand& shorthand) const
335 {
336     RefPtrWillBeRawPtr<CSSValue> horizontalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[0]);
337     RefPtrWillBeRawPtr<CSSValue> verticalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[1]);
338
339     // While standard border-spacing property does not allow specifying border-spacing-vertical without
340     // specifying border-spacing-horizontal <http://www.w3.org/TR/CSS21/tables.html#separated-borders>,
341     // -webkit-border-spacing-vertical can be set without -webkit-border-spacing-horizontal.
342     if (!horizontalValue || !verticalValue)
343         return String();
344
345     String horizontalValueCSSText = horizontalValue->cssText();
346     String verticalValueCSSText = verticalValue->cssText();
347     if (horizontalValueCSSText == verticalValueCSSText)
348         return horizontalValueCSSText;
349     return horizontalValueCSSText + ' ' + verticalValueCSSText;
350 }
351
352 void StylePropertySerializer::appendFontLonghandValueIfExplicit(CSSPropertyID propertyID, StringBuilder& result, String& commonValue) const
353 {
354     int foundPropertyIndex = m_propertySet.findPropertyIndex(propertyID);
355     if (foundPropertyIndex == -1)
356         return; // All longhands must have at least implicit values if "font" is specified.
357
358     if (m_propertySet.propertyAt(foundPropertyIndex).isImplicit()) {
359         commonValue = String();
360         return;
361     }
362
363     char prefix = '\0';
364     switch (propertyID) {
365     case CSSPropertyFontStyle:
366         break; // No prefix.
367     case CSSPropertyFontFamily:
368     case CSSPropertyFontVariant:
369     case CSSPropertyFontWeight:
370         prefix = ' ';
371         break;
372     case CSSPropertyLineHeight:
373         prefix = '/';
374         break;
375     default:
376         ASSERT_NOT_REACHED();
377     }
378
379     if (prefix && !result.isEmpty())
380         result.append(prefix);
381     String value = m_propertySet.propertyAt(foundPropertyIndex).value()->cssText();
382     result.append(value);
383     if (!commonValue.isNull() && commonValue != value)
384         commonValue = String();
385 }
386
387 String StylePropertySerializer::fontValue() const
388 {
389     int fontSizePropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontSize);
390     int fontFamilyPropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontFamily);
391     if (fontSizePropertyIndex == -1 || fontFamilyPropertyIndex == -1)
392         return emptyString();
393
394     StylePropertySet::PropertyReference fontSizeProperty = m_propertySet.propertyAt(fontSizePropertyIndex);
395     StylePropertySet::PropertyReference fontFamilyProperty = m_propertySet.propertyAt(fontFamilyPropertyIndex);
396     if (fontSizeProperty.isImplicit() || fontFamilyProperty.isImplicit())
397         return emptyString();
398
399     String commonValue = fontSizeProperty.value()->cssText();
400     StringBuilder result;
401     appendFontLonghandValueIfExplicit(CSSPropertyFontStyle, result, commonValue);
402     appendFontLonghandValueIfExplicit(CSSPropertyFontVariant, result, commonValue);
403     appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, result, commonValue);
404     if (!result.isEmpty())
405         result.append(' ');
406     result.append(fontSizeProperty.value()->cssText());
407     appendFontLonghandValueIfExplicit(CSSPropertyLineHeight, result, commonValue);
408     if (!result.isEmpty())
409         result.append(' ');
410     result.append(fontFamilyProperty.value()->cssText());
411     if (isInitialOrInherit(commonValue))
412         return commonValue;
413     return result.toString();
414 }
415
416 String StylePropertySerializer::get4Values(const StylePropertyShorthand& shorthand) const
417 {
418     // Assume the properties are in the usual order top, right, bottom, left.
419     int topValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[0]);
420     int rightValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[1]);
421     int bottomValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[2]);
422     int leftValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[3]);
423
424     if (topValueIndex == -1 || rightValueIndex == -1 || bottomValueIndex == -1 || leftValueIndex == -1)
425         return String();
426
427     StylePropertySet::PropertyReference top = m_propertySet.propertyAt(topValueIndex);
428     StylePropertySet::PropertyReference right = m_propertySet.propertyAt(rightValueIndex);
429     StylePropertySet::PropertyReference bottom = m_propertySet.propertyAt(bottomValueIndex);
430     StylePropertySet::PropertyReference left = m_propertySet.propertyAt(leftValueIndex);
431
432         // All 4 properties must be specified.
433     if (!top.value() || !right.value() || !bottom.value() || !left.value())
434         return String();
435
436     if (top.isInherited() && right.isInherited() && bottom.isInherited() && left.isInherited())
437         return getValueName(CSSValueInherit);
438
439     if (top.value()->isInitialValue() || right.value()->isInitialValue() || bottom.value()->isInitialValue() || left.value()->isInitialValue()) {
440         if (top.value()->isInitialValue() && right.value()->isInitialValue() && bottom.value()->isInitialValue() && left.value()->isInitialValue() && !top.isImplicit()) {
441             // All components are "initial" and "top" is not implicit.
442             return getValueName(CSSValueInitial);
443         }
444         return String();
445     }
446     if (top.isImportant() != right.isImportant() || right.isImportant() != bottom.isImportant() || bottom.isImportant() != left.isImportant())
447         return String();
448
449     bool showLeft = !right.value()->equals(*left.value());
450     bool showBottom = !top.value()->equals(*bottom.value()) || showLeft;
451     bool showRight = !top.value()->equals(*right.value()) || showBottom;
452
453     StringBuilder result;
454     result.append(top.value()->cssText());
455     if (showRight) {
456         result.append(' ');
457         result.append(right.value()->cssText());
458     }
459     if (showBottom) {
460         result.append(' ');
461         result.append(bottom.value()->cssText());
462     }
463     if (showLeft) {
464         result.append(' ');
465         result.append(left.value()->cssText());
466     }
467     return result.toString();
468 }
469
470 String StylePropertySerializer::getLayeredShorthandValue(const StylePropertyShorthand& shorthand) const
471 {
472     StringBuilder result;
473
474     const unsigned size = shorthand.length();
475     // Begin by collecting the properties into an array.
476     WillBeHeapVector<RefPtrWillBeMember<CSSValue> > values(size);
477     size_t numLayers = 0;
478
479     for (unsigned i = 0; i < size; ++i) {
480         values[i] = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
481         if (values[i]) {
482             if (values[i]->isBaseValueList()) {
483                 CSSValueList* valueList = toCSSValueList(values[i].get());
484                 numLayers = std::max(valueList->length(), numLayers);
485             } else {
486                 numLayers = std::max<size_t>(1U, numLayers);
487             }
488         }
489     }
490
491     String commonValue;
492     bool commonValueInitialized = false;
493
494     // Now stitch the properties together. Implicit initial values are flagged as such and
495     // can safely be omitted.
496     for (size_t i = 0; i < numLayers; i++) {
497         StringBuilder layerResult;
498         bool useRepeatXShorthand = false;
499         bool useRepeatYShorthand = false;
500         bool useSingleWordShorthand = false;
501         bool foundPositionYCSSProperty = false;
502         for (unsigned j = 0; j < size; j++) {
503             RefPtrWillBeRawPtr<CSSValue> value = nullptr;
504             if (values[j]) {
505                 if (values[j]->isBaseValueList())
506                     value = toCSSValueList(values[j].get())->item(i);
507                 else {
508                     value = values[j];
509
510                     // Color only belongs in the last layer.
511                     if (shorthand.properties()[j] == CSSPropertyBackgroundColor) {
512                         if (i != numLayers - 1)
513                             value = nullptr;
514                     } else if (i) {
515                         // Other singletons only belong in the first layer.
516                         value = nullptr;
517                     }
518                 }
519             }
520
521             // We need to report background-repeat as it was written in the CSS. If the property is implicit,
522             // then it was written with only one value. Here we figure out which value that was so we can
523             // report back correctly.
524             if ((shorthand.properties()[j] == CSSPropertyBackgroundRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))
525                 || (shorthand.properties()[j] == CSSPropertyWebkitMaskRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))) {
526
527                 // BUG 49055: make sure the value was not reset in the layer check just above.
528                 if ((j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyBackgroundRepeatY && value)
529                     || (j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyWebkitMaskRepeatY && value)) {
530                     RefPtrWillBeRawPtr<CSSValue> yValue = nullptr;
531                     RefPtrWillBeRawPtr<CSSValue> nextValue = values[j + 1];
532                     if (nextValue->isValueList())
533                         yValue = toCSSValueList(nextValue.get())->itemWithoutBoundsCheck(i);
534                     else
535                         yValue = nextValue;
536
537                     // background-repeat-x(y) or mask-repeat-x(y) may be like this : "initial, repeat". We can omit the implicit initial values
538                     // before starting to compare their values.
539                     if (value->isImplicitInitialValue() || yValue->isImplicitInitialValue())
540                         continue;
541
542                     // FIXME: At some point we need to fix this code to avoid returning an invalid shorthand,
543                     // since some longhand combinations are not serializable into a single shorthand.
544                     if (!value->isPrimitiveValue() || !yValue->isPrimitiveValue())
545                         continue;
546
547                     CSSValueID xId = toCSSPrimitiveValue(value.get())->getValueID();
548                     CSSValueID yId = toCSSPrimitiveValue(yValue.get())->getValueID();
549                     if (xId != yId) {
550                         if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) {
551                             useRepeatXShorthand = true;
552                             ++j;
553                         } else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) {
554                             useRepeatYShorthand = true;
555                             continue;
556                         }
557                     } else {
558                         useSingleWordShorthand = true;
559                         ++j;
560                     }
561                 }
562             }
563
564             String valueText;
565             if (value && !value->isImplicitInitialValue()) {
566                 if (!layerResult.isEmpty())
567                     layerResult.append(' ');
568                 if (foundPositionYCSSProperty
569                     && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
570                     layerResult.appendLiteral("/ ");
571                 if (!foundPositionYCSSProperty
572                     && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
573                     continue;
574
575                 if (useRepeatXShorthand) {
576                     useRepeatXShorthand = false;
577                     layerResult.append(getValueName(CSSValueRepeatX));
578                 } else if (useRepeatYShorthand) {
579                     useRepeatYShorthand = false;
580                     layerResult.append(getValueName(CSSValueRepeatY));
581                 } else {
582                     if (useSingleWordShorthand)
583                         useSingleWordShorthand = false;
584                     valueText = value->cssText();
585                     layerResult.append(valueText);
586                 }
587
588                 if (shorthand.properties()[j] == CSSPropertyBackgroundPositionY
589                     || shorthand.properties()[j] == CSSPropertyWebkitMaskPositionY) {
590                     foundPositionYCSSProperty = true;
591
592                     // background-position is a special case: if only the first offset is specified,
593                     // the second one defaults to "center", not the same value.
594                     if (commonValueInitialized && commonValue != "initial" && commonValue != "inherit")
595                         commonValue = String();
596                 }
597             }
598
599             if (!commonValueInitialized) {
600                 commonValue = valueText;
601                 commonValueInitialized = true;
602             } else if (!commonValue.isNull() && commonValue != valueText)
603                 commonValue = String();
604         }
605
606         if (!layerResult.isEmpty()) {
607             if (!result.isEmpty())
608                 result.appendLiteral(", ");
609             result.append(layerResult);
610         }
611     }
612
613     if (isInitialOrInherit(commonValue))
614         return commonValue;
615
616     if (result.isEmpty())
617         return String();
618     return result.toString();
619 }
620
621 String StylePropertySerializer::getShorthandValue(const StylePropertyShorthand& shorthand) const
622 {
623     String commonValue;
624     StringBuilder result;
625     for (unsigned i = 0; i < shorthand.length(); ++i) {
626         if (!m_propertySet.isPropertyImplicit(shorthand.properties()[i])) {
627             RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
628             if (!value)
629                 return String();
630             String valueText = value->cssText();
631             if (!i)
632                 commonValue = valueText;
633             else if (!commonValue.isNull() && commonValue != valueText)
634                 commonValue = String();
635             if (value->isInitialValue())
636                 continue;
637             if (!result.isEmpty())
638                 result.append(' ');
639             result.append(valueText);
640         } else
641             commonValue = String();
642     }
643     if (isInitialOrInherit(commonValue))
644         return commonValue;
645     if (result.isEmpty())
646         return String();
647     return result.toString();
648 }
649
650 // only returns a non-null value if all properties have the same, non-null value
651 String StylePropertySerializer::getCommonValue(const StylePropertyShorthand& shorthand) const
652 {
653     String res;
654     bool lastPropertyWasImportant = false;
655     for (unsigned i = 0; i < shorthand.length(); ++i) {
656         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
657         // FIXME: CSSInitialValue::cssText should generate the right value.
658         if (!value)
659             return String();
660         String text = value->cssText();
661         if (text.isNull())
662             return String();
663         if (res.isNull())
664             res = text;
665         else if (res != text)
666             return String();
667
668         bool currentPropertyIsImportant = m_propertySet.propertyIsImportant(shorthand.properties()[i]);
669         if (i && lastPropertyWasImportant != currentPropertyIsImportant)
670             return String();
671         lastPropertyWasImportant = currentPropertyIsImportant;
672     }
673     return res;
674 }
675
676 String StylePropertySerializer::borderPropertyValue(CommonValueMode valueMode) const
677 {
678     const StylePropertyShorthand properties[3] = { borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand() };
679     String commonValue;
680     StringBuilder result;
681     for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) {
682         String value = getCommonValue(properties[i]);
683         if (value.isNull()) {
684             if (valueMode == ReturnNullOnUncommonValues)
685                 return String();
686             ASSERT(valueMode == OmitUncommonValues);
687             continue;
688         }
689         if (!i)
690             commonValue = value;
691         else if (!commonValue.isNull() && commonValue != value)
692             commonValue = String();
693         if (value == "initial")
694             continue;
695         if (!result.isEmpty())
696             result.append(' ');
697         result.append(value);
698     }
699     if (isInitialOrInherit(commonValue))
700         return commonValue;
701     return result.isEmpty() ? String() : result.toString();
702 }
703
704 static void appendBackgroundRepeatValue(StringBuilder& builder, const CSSValue& repeatXCSSValue, const CSSValue& repeatYCSSValue)
705 {
706     // FIXME: Ensure initial values do not appear in CSS_VALUE_LISTS.
707     DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, initialRepeatValue, (CSSPrimitiveValue::create(CSSValueRepeat)));
708     const CSSPrimitiveValue& repeatX = repeatXCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatXCSSValue);
709     const CSSPrimitiveValue& repeatY = repeatYCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatYCSSValue);
710     CSSValueID repeatXValueId = repeatX.getValueID();
711     CSSValueID repeatYValueId = repeatY.getValueID();
712     if (repeatXValueId == repeatYValueId) {
713         builder.append(repeatX.cssText());
714     } else if (repeatXValueId == CSSValueNoRepeat && repeatYValueId == CSSValueRepeat) {
715         builder.append("repeat-y");
716     } else if (repeatXValueId == CSSValueRepeat && repeatYValueId == CSSValueNoRepeat) {
717         builder.append("repeat-x");
718     } else {
719         builder.append(repeatX.cssText());
720         builder.append(" ");
721         builder.append(repeatY.cssText());
722     }
723 }
724
725 String StylePropertySerializer::backgroundRepeatPropertyValue() const
726 {
727     RefPtrWillBeRawPtr<CSSValue> repeatX = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatX);
728     RefPtrWillBeRawPtr<CSSValue> repeatY = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatY);
729     if (!repeatX || !repeatY)
730         return String();
731     if (m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX) != m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatY))
732         return String();
733     if (repeatX->cssValueType() == repeatY->cssValueType()
734         && (repeatX->cssValueType() == CSSValue::CSS_INITIAL || repeatX->cssValueType() == CSSValue::CSS_INHERIT)) {
735         return repeatX->cssText();
736     }
737
738     RefPtrWillBeRawPtr<CSSValueList> repeatXList;
739     if (repeatX->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
740         repeatXList = CSSValueList::createCommaSeparated();
741         repeatXList->append(repeatX);
742     } else if (repeatX->cssValueType() == CSSValue::CSS_VALUE_LIST) {
743         repeatXList = toCSSValueList(repeatX.get());
744     } else {
745         return String();
746     }
747
748     RefPtrWillBeRawPtr<CSSValueList> repeatYList;
749     if (repeatY->cssValueType() == CSSValue::CSS_PRIMITIVE_VALUE) {
750         repeatYList = CSSValueList::createCommaSeparated();
751         repeatYList->append(repeatY);
752     } else if (repeatY->cssValueType() == CSSValue::CSS_VALUE_LIST) {
753         repeatYList = toCSSValueList(repeatY.get());
754     } else {
755         return String();
756     }
757
758     size_t shorthandLength = lowestCommonMultiple(repeatXList->length(), repeatYList->length());
759     StringBuilder builder;
760     for (size_t i = 0; i < shorthandLength; ++i) {
761         if (i)
762             builder.append(", ");
763         appendBackgroundRepeatValue(builder,
764             *repeatXList->item(i % repeatXList->length()),
765             *repeatYList->item(i % repeatYList->length()));
766     }
767     return builder.toString();
768 }
769
770 void StylePropertySerializer::appendBackgroundPropertyAsText(StringBuilder& result, unsigned& numDecls) const
771 {
772     if (isPropertyShorthandAvailable(backgroundShorthand())) {
773         String backgroundValue = getPropertyValue(CSSPropertyBackground);
774         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage);
775         result.append(getPropertyText(CSSPropertyBackground, backgroundValue, isImportant, numDecls++));
776         return;
777     }
778     if (shorthandHasOnlyInitialOrInheritedValue(backgroundShorthand())) {
779         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundImage);
780         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage);
781         result.append(getPropertyText(CSSPropertyBackground, value->cssText(), isImportant, numDecls++));
782         return;
783     }
784
785     // backgroundShorthandProperty without layered shorhand properties
786     const CSSPropertyID backgroundPropertyIds[] = {
787         CSSPropertyBackgroundImage,
788         CSSPropertyBackgroundAttachment,
789         CSSPropertyBackgroundColor,
790         CSSPropertyBackgroundSize,
791         CSSPropertyBackgroundOrigin,
792         CSSPropertyBackgroundClip
793     };
794
795     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(backgroundPropertyIds); ++i) {
796         CSSPropertyID propertyID = backgroundPropertyIds[i];
797         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(propertyID);
798         if (!value)
799             continue;
800         result.append(getPropertyText(propertyID, value->cssText(), m_propertySet.propertyIsImportant(propertyID), numDecls++));
801     }
802
803     // FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output.
804     // It is required because background-position-x/y are non-standard properties and WebKit generated output
805     // would not work in Firefox (<rdar://problem/5143183>)
806     // It would be a better solution if background-position was CSS_PAIR.
807     if (shorthandHasOnlyInitialOrInheritedValue(backgroundPositionShorthand())) {
808         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX);
809         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
810         result.append(getPropertyText(CSSPropertyBackgroundPosition, value->cssText(), isImportant, numDecls++));
811     } else if (isPropertyShorthandAvailable(backgroundPositionShorthand())) {
812         String positionValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundPosition);
813         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
814         if (!positionValue.isNull())
815             result.append(getPropertyText(CSSPropertyBackgroundPosition, positionValue, isImportant, numDecls++));
816     } else {
817         // should check background-position-x or background-position-y.
818         if (RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX)) {
819             if (!value->isImplicitInitialValue()) {
820                 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
821                 result.append(getPropertyText(CSSPropertyBackgroundPositionX, value->cssText(), isImportant, numDecls++));
822             }
823         }
824         if (RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionY)) {
825             if (!value->isImplicitInitialValue()) {
826                 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionY);
827                 result.append(getPropertyText(CSSPropertyBackgroundPositionY, value->cssText(), isImportant, numDecls++));
828             }
829         }
830     }
831
832     String repeatValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundRepeat);
833     if (!repeatValue.isNull())
834         result.append(getPropertyText(CSSPropertyBackgroundRepeat, repeatValue, m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX), numDecls++));
835 }
836
837 bool StylePropertySerializer::isPropertyShorthandAvailable(const StylePropertyShorthand& shorthand) const
838 {
839     ASSERT(shorthand.length() > 0);
840
841     bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]);
842     for (unsigned i = 0; i < shorthand.length(); ++i) {
843         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
844         if (!value || (value->isInitialValue() && !value->isImplicitInitialValue()) || value->isInheritedValue())
845             return false;
846         if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i]))
847             return false;
848     }
849     return true;
850 }
851
852 bool StylePropertySerializer::shorthandHasOnlyInitialOrInheritedValue(const StylePropertyShorthand& shorthand) const
853 {
854     ASSERT(shorthand.length() > 0);
855     bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]);
856     bool isInitialValue = true;
857     bool isInheritedValue = true;
858     for (unsigned i = 0; i < shorthand.length(); ++i) {
859         RefPtrWillBeRawPtr<CSSValue> value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
860         if (!value)
861             return false;
862         if (!value->isInitialValue())
863             isInitialValue = false;
864         if (!value->isInheritedValue())
865             isInheritedValue = false;
866         if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i]))
867             return false;
868     }
869     return isInitialValue || isInheritedValue;
870 }
871
872 }