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