Upstream version 11.40.277.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/CSSPropertyMetadata.h"
29 #include "core/css/CSSValuePool.h"
30 #include "wtf/BitArray.h"
31 #include "wtf/text/StringBuilder.h"
32
33 namespace blink {
34
35 static bool isInitialOrInherit(const String& value)
36 {
37     DEFINE_STATIC_LOCAL(String, initial, ("initial"));
38     DEFINE_STATIC_LOCAL(String, inherit, ("inherit"));
39     return value.length() == 7 && (value == initial || value == inherit);
40 }
41
42 StylePropertySerializer::StylePropertySetForSerializer::StylePropertySetForSerializer(const StylePropertySet& properties)
43     : m_propertySet(properties)
44     , m_allIndex(m_propertySet.findPropertyIndex(CSSPropertyAll))
45     , m_needToExpandAll(false)
46 {
47     if (!hasAllProperty())
48         return;
49
50     StylePropertySet::PropertyReference allProperty = m_propertySet.propertyAt(m_allIndex);
51     for (unsigned i = 0; i < m_propertySet.propertyCount(); ++i) {
52         StylePropertySet::PropertyReference property = m_propertySet.propertyAt(i);
53         if (CSSProperty::isAffectedByAllProperty(property.id())) {
54             if (allProperty.isImportant() && !property.isImportant())
55                 continue;
56             if (static_cast<unsigned>(m_allIndex) >= i)
57                 continue;
58             if (property.value()->equals(*allProperty.value())
59                 && property.isImportant() == allProperty.isImportant())
60                 continue;
61             m_needToExpandAll = true;
62         }
63         m_longhandPropertyUsed.set(property.id() - firstCSSProperty);
64     }
65 }
66
67 unsigned StylePropertySerializer::StylePropertySetForSerializer::propertyCount() const
68 {
69     if (!hasExpandedAllProperty())
70         return m_propertySet.propertyCount();
71     return lastCSSProperty - firstCSSProperty + 1;
72 }
73
74 StylePropertySerializer::PropertyValueForSerializer StylePropertySerializer::StylePropertySetForSerializer::propertyAt(unsigned index) const
75 {
76     if (!hasExpandedAllProperty())
77         return StylePropertySerializer::PropertyValueForSerializer(m_propertySet.propertyAt(index));
78
79     CSSPropertyID propertyID = static_cast<CSSPropertyID>(index + firstCSSProperty);
80     ASSERT(firstCSSProperty <= propertyID && propertyID <= lastCSSProperty);
81     if (m_longhandPropertyUsed.get(index)) {
82         int index = m_propertySet.findPropertyIndex(propertyID);
83         ASSERT(index != -1);
84         return StylePropertySerializer::PropertyValueForSerializer(m_propertySet.propertyAt(index));
85     }
86
87     StylePropertySet::PropertyReference property = m_propertySet.propertyAt(m_allIndex);
88     const CSSValue* value = property.value();
89
90     // FIXME: Firefox shows properties with "unset" when some cssRule has
91     // expanded "all" with "unset". So we should use "unset" here.
92     // After implementing "unset" value correctly, (i.e. StyleBuilder should
93     // support "display: unset", "color: unset", ... and so on),
94     // we should fix the following code.
95     if (!value->isInitialValue() && !value->isInheritedValue()) {
96         if (CSSPropertyMetadata::isInheritedProperty(propertyID))
97             value = cssValuePool().createInheritedValue().get();
98         else
99             value = cssValuePool().createExplicitInitialValue().get();
100     }
101     return StylePropertySerializer::PropertyValueForSerializer(propertyID, value, property.isImportant());
102 }
103
104 bool StylePropertySerializer::StylePropertySetForSerializer::shouldProcessPropertyAt(unsigned index) const
105 {
106     // StylePropertySet has all valid longhands. We should process.
107     if (!hasAllProperty())
108         return true;
109
110     // If all is not expanded, we need to process "all" and properties which
111     // are not overwritten by "all".
112     if (!m_needToExpandAll) {
113         StylePropertySet::PropertyReference property = m_propertySet.propertyAt(index);
114         if (property.id() == CSSPropertyAll || !CSSProperty::isAffectedByAllProperty(property.id()))
115             return true;
116         return m_longhandPropertyUsed.get(property.id() - firstCSSProperty);
117     }
118
119     CSSPropertyID propertyID = static_cast<CSSPropertyID>(index + firstCSSProperty);
120     ASSERT(firstCSSProperty <= propertyID && propertyID <= lastCSSProperty);
121
122     // Since "all" is expanded, we don't need to process "all".
123     // We should not process expanded shorthands (e.g. font, background,
124     // and so on) either.
125     if (isShorthandProperty(propertyID) || propertyID == CSSPropertyAll)
126         return false;
127     // We should not serialize internal properties.
128     if (isInternalProperty(propertyID))
129         return false;
130
131     // The all property is a shorthand that resets all CSS properties except
132     // direction and unicode-bidi. It only accepts the CSS-wide keywords.
133     // c.f. http://dev.w3.org/csswg/css-cascade/#all-shorthand
134     if (!CSSProperty::isAffectedByAllProperty(propertyID))
135         return m_longhandPropertyUsed.get(index);
136
137     return true;
138 }
139
140 int StylePropertySerializer::StylePropertySetForSerializer::findPropertyIndex(CSSPropertyID propertyID) const
141 {
142     if (!hasExpandedAllProperty())
143         return m_propertySet.findPropertyIndex(propertyID);
144     return propertyID - firstCSSProperty;
145 }
146
147 const CSSValue* StylePropertySerializer::StylePropertySetForSerializer::getPropertyCSSValue(CSSPropertyID propertyID) const
148 {
149     int index = findPropertyIndex(propertyID);
150     if (index == -1)
151         return 0;
152     StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index);
153     return value.value();
154 }
155
156 String StylePropertySerializer::StylePropertySetForSerializer::getPropertyValue(CSSPropertyID propertyID) const
157 {
158     if (!hasExpandedAllProperty())
159         return m_propertySet.getPropertyValue(propertyID);
160
161     const CSSValue* value = getPropertyCSSValue(propertyID);
162     if (!value)
163         return String();
164     return value->cssText();
165 }
166
167 bool StylePropertySerializer::StylePropertySetForSerializer::isPropertyImplicit(CSSPropertyID propertyID) const
168 {
169     int index = findPropertyIndex(propertyID);
170     if (index == -1)
171         return false;
172     StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index);
173     return value.isImplicit();
174 }
175
176 bool StylePropertySerializer::StylePropertySetForSerializer::propertyIsImportant(CSSPropertyID propertyID) const
177 {
178     int index = findPropertyIndex(propertyID);
179     if (index == -1)
180         return false;
181     StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index);
182     return value.isImportant();
183 }
184
185 StylePropertySerializer::StylePropertySerializer(const StylePropertySet& properties)
186     : m_propertySet(properties)
187 {
188 }
189
190 String StylePropertySerializer::getPropertyText(CSSPropertyID propertyID, const String& value, bool isImportant, bool isNotFirstDecl) const
191 {
192     StringBuilder result;
193     if (isNotFirstDecl)
194         result.append(' ');
195     result.append(getPropertyName(propertyID));
196     result.appendLiteral(": ");
197     result.append(value);
198     if (isImportant)
199         result.appendLiteral(" !important");
200     result.append(';');
201     return result.toString();
202 }
203
204 String StylePropertySerializer::asText() const
205 {
206     StringBuilder result;
207
208     BitArray<numCSSProperties> shorthandPropertyUsed;
209     BitArray<numCSSProperties> shorthandPropertyAppeared;
210
211     unsigned size = m_propertySet.propertyCount();
212     unsigned numDecls = 0;
213     for (unsigned n = 0; n < size; ++n) {
214         if (!m_propertySet.shouldProcessPropertyAt(n))
215             continue;
216
217         StylePropertySerializer::PropertyValueForSerializer property = m_propertySet.propertyAt(n);
218         CSSPropertyID propertyID = property.id();
219         // Only enabled or internal properties should be part of the style.
220         ASSERT(CSSPropertyMetadata::isEnabledProperty(propertyID) || isInternalProperty(propertyID));
221         CSSPropertyID shorthandPropertyID = CSSPropertyInvalid;
222         CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid;
223         String value;
224         ASSERT(!isShorthandProperty(propertyID));
225
226         switch (propertyID) {
227         case CSSPropertyBackgroundAttachment:
228         case CSSPropertyBackgroundClip:
229         case CSSPropertyBackgroundColor:
230         case CSSPropertyBackgroundImage:
231         case CSSPropertyBackgroundOrigin:
232         case CSSPropertyBackgroundPositionX:
233         case CSSPropertyBackgroundPositionY:
234         case CSSPropertyBackgroundSize:
235         case CSSPropertyBackgroundRepeatX:
236         case CSSPropertyBackgroundRepeatY:
237             shorthandPropertyAppeared.set(CSSPropertyBackground - firstCSSProperty);
238             continue;
239         case CSSPropertyContent:
240             if (property.value()->isValueList())
241                 value = toCSSValueList(property.value())->customCSSText(AlwaysQuoteCSSString);
242             break;
243         case CSSPropertyBorderTopWidth:
244         case CSSPropertyBorderRightWidth:
245         case CSSPropertyBorderBottomWidth:
246         case CSSPropertyBorderLeftWidth:
247             if (!borderFallbackShorthandProperty)
248                 borderFallbackShorthandProperty = CSSPropertyBorderWidth;
249         case CSSPropertyBorderTopStyle:
250         case CSSPropertyBorderRightStyle:
251         case CSSPropertyBorderBottomStyle:
252         case CSSPropertyBorderLeftStyle:
253             if (!borderFallbackShorthandProperty)
254                 borderFallbackShorthandProperty = CSSPropertyBorderStyle;
255         case CSSPropertyBorderTopColor:
256         case CSSPropertyBorderRightColor:
257         case CSSPropertyBorderBottomColor:
258         case CSSPropertyBorderLeftColor:
259             if (!borderFallbackShorthandProperty)
260                 borderFallbackShorthandProperty = CSSPropertyBorderColor;
261
262             // FIXME: Deal with cases where only some of border-(top|right|bottom|left) are specified.
263             if (!shorthandPropertyAppeared.get(CSSPropertyBorder - firstCSSProperty)) {
264                 value = borderPropertyValue(ReturnNullOnUncommonValues);
265                 if (value.isNull())
266                     shorthandPropertyAppeared.set(CSSPropertyBorder - firstCSSProperty);
267                 else
268                     shorthandPropertyID = CSSPropertyBorder;
269             } else if (shorthandPropertyUsed.get(CSSPropertyBorder - firstCSSProperty))
270                 shorthandPropertyID = CSSPropertyBorder;
271             if (!shorthandPropertyID)
272                 shorthandPropertyID = borderFallbackShorthandProperty;
273             break;
274         case CSSPropertyBorderTopLeftRadius:
275         case CSSPropertyBorderTopRightRadius:
276         case CSSPropertyBorderBottomLeftRadius:
277         case CSSPropertyBorderBottomRightRadius:
278             shorthandPropertyID = CSSPropertyBorderRadius;
279             break;
280         case CSSPropertyWebkitBorderHorizontalSpacing:
281         case CSSPropertyWebkitBorderVerticalSpacing:
282             shorthandPropertyID = CSSPropertyBorderSpacing;
283             break;
284         case CSSPropertyFontFamily:
285         case CSSPropertyLineHeight:
286         case CSSPropertyFontSize:
287         case CSSPropertyFontStretch:
288         case CSSPropertyFontStyle:
289         case CSSPropertyFontVariant:
290         case CSSPropertyFontWeight:
291             // Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing.
292             break;
293         case CSSPropertyListStyleType:
294         case CSSPropertyListStylePosition:
295         case CSSPropertyListStyleImage:
296             shorthandPropertyID = CSSPropertyListStyle;
297             break;
298         case CSSPropertyMarginTop:
299         case CSSPropertyMarginRight:
300         case CSSPropertyMarginBottom:
301         case CSSPropertyMarginLeft:
302             shorthandPropertyID = CSSPropertyMargin;
303             break;
304         case CSSPropertyOutlineWidth:
305         case CSSPropertyOutlineStyle:
306         case CSSPropertyOutlineColor:
307             shorthandPropertyID = CSSPropertyOutline;
308             break;
309         case CSSPropertyOverflowX:
310         case CSSPropertyOverflowY:
311             shorthandPropertyID = CSSPropertyOverflow;
312             break;
313         case CSSPropertyPaddingTop:
314         case CSSPropertyPaddingRight:
315         case CSSPropertyPaddingBottom:
316         case CSSPropertyPaddingLeft:
317             shorthandPropertyID = CSSPropertyPadding;
318             break;
319         case CSSPropertyTransitionProperty:
320         case CSSPropertyTransitionDuration:
321         case CSSPropertyTransitionTimingFunction:
322         case CSSPropertyTransitionDelay:
323             shorthandPropertyID = CSSPropertyTransition;
324             break;
325         case CSSPropertyWebkitAnimationName:
326         case CSSPropertyWebkitAnimationDuration:
327         case CSSPropertyWebkitAnimationTimingFunction:
328         case CSSPropertyWebkitAnimationDelay:
329         case CSSPropertyWebkitAnimationIterationCount:
330         case CSSPropertyWebkitAnimationDirection:
331         case CSSPropertyWebkitAnimationFillMode:
332             shorthandPropertyID = CSSPropertyWebkitAnimation;
333             break;
334         case CSSPropertyFlexDirection:
335         case CSSPropertyFlexWrap:
336             shorthandPropertyID = CSSPropertyFlexFlow;
337             break;
338         case CSSPropertyFlexBasis:
339         case CSSPropertyFlexGrow:
340         case CSSPropertyFlexShrink:
341             shorthandPropertyID = CSSPropertyFlex;
342             break;
343         case CSSPropertyWebkitMaskPositionX:
344         case CSSPropertyWebkitMaskPositionY:
345         case CSSPropertyWebkitMaskRepeatX:
346         case CSSPropertyWebkitMaskRepeatY:
347         case CSSPropertyWebkitMaskImage:
348         case CSSPropertyWebkitMaskRepeat:
349         case CSSPropertyWebkitMaskPosition:
350         case CSSPropertyWebkitMaskClip:
351         case CSSPropertyWebkitMaskOrigin:
352             shorthandPropertyID = CSSPropertyWebkitMask;
353             break;
354         case CSSPropertyWebkitTransitionProperty:
355         case CSSPropertyWebkitTransitionDuration:
356         case CSSPropertyWebkitTransitionTimingFunction:
357         case CSSPropertyWebkitTransitionDelay:
358             shorthandPropertyID = CSSPropertyWebkitTransition;
359             break;
360         case CSSPropertyAll:
361             result.append(getPropertyText(propertyID, property.value()->cssText(), property.isImportant(), numDecls++));
362             continue;
363         default:
364             break;
365         }
366
367         unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty;
368         if (shorthandPropertyID) {
369             if (shorthandPropertyUsed.get(shortPropertyIndex))
370                 continue;
371             if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull())
372                 value = m_propertySet.getPropertyValue(shorthandPropertyID);
373             shorthandPropertyAppeared.set(shortPropertyIndex);
374         }
375
376         if (!value.isNull()) {
377             if (shorthandPropertyID) {
378                 propertyID = shorthandPropertyID;
379                 shorthandPropertyUsed.set(shortPropertyIndex);
380             }
381         } else {
382             // We should not show "initial" when the "initial" is implicit.
383             // If explicit "initial", we need to show.
384             if (property.value()->isImplicitInitialValue())
385                 continue;
386             value = property.value()->cssText();
387         }
388
389         result.append(getPropertyText(propertyID, value, property.isImportant(), numDecls++));
390     }
391
392     if (shorthandPropertyAppeared.get(CSSPropertyBackground - firstCSSProperty))
393         appendBackgroundPropertyAsText(result, numDecls);
394
395     ASSERT(!numDecls ^ !result.isEmpty());
396     return result.toString();
397 }
398
399 String StylePropertySerializer::getPropertyValue(CSSPropertyID propertyID) const
400 {
401     // Shorthand and 4-values properties
402     switch (propertyID) {
403     case CSSPropertyAnimation:
404         return getLayeredShorthandValue(animationShorthand(), true);
405     case CSSPropertyBorderSpacing:
406         return borderSpacingValue(borderSpacingShorthand());
407     case CSSPropertyBackgroundPosition:
408         return getLayeredShorthandValue(backgroundPositionShorthand());
409     case CSSPropertyBackgroundRepeat:
410         return backgroundRepeatPropertyValue();
411     case CSSPropertyBackground:
412         return getLayeredShorthandValue(backgroundShorthand());
413     case CSSPropertyBorder:
414         return borderPropertyValue(OmitUncommonValues);
415     case CSSPropertyBorderTop:
416         return getShorthandValue(borderTopShorthand());
417     case CSSPropertyBorderRight:
418         return getShorthandValue(borderRightShorthand());
419     case CSSPropertyBorderBottom:
420         return getShorthandValue(borderBottomShorthand());
421     case CSSPropertyBorderLeft:
422         return getShorthandValue(borderLeftShorthand());
423     case CSSPropertyOutline:
424         return getShorthandValue(outlineShorthand());
425     case CSSPropertyBorderColor:
426         return get4Values(borderColorShorthand());
427     case CSSPropertyBorderWidth:
428         return get4Values(borderWidthShorthand());
429     case CSSPropertyBorderStyle:
430         return get4Values(borderStyleShorthand());
431     case CSSPropertyWebkitColumnRule:
432         return getShorthandValue(webkitColumnRuleShorthand());
433     case CSSPropertyWebkitColumns:
434         return getShorthandValue(webkitColumnsShorthand());
435     case CSSPropertyFlex:
436         return getShorthandValue(flexShorthand());
437     case CSSPropertyFlexFlow:
438         return getShorthandValue(flexFlowShorthand());
439     case CSSPropertyGridColumn:
440         return getShorthandValue(gridColumnShorthand());
441     case CSSPropertyGridRow:
442         return getShorthandValue(gridRowShorthand());
443     case CSSPropertyGridArea:
444         return getShorthandValue(gridAreaShorthand());
445     case CSSPropertyFont:
446         return fontValue();
447     case CSSPropertyMargin:
448         return get4Values(marginShorthand());
449     case CSSPropertyWebkitMarginCollapse:
450         return getShorthandValue(webkitMarginCollapseShorthand());
451     case CSSPropertyOverflow:
452         return getCommonValue(overflowShorthand());
453     case CSSPropertyPadding:
454         return get4Values(paddingShorthand());
455     case CSSPropertyTransition:
456         return getLayeredShorthandValue(transitionShorthand(), true);
457     case CSSPropertyListStyle:
458         return getShorthandValue(listStyleShorthand());
459     case CSSPropertyWebkitMaskPosition:
460         return getLayeredShorthandValue(webkitMaskPositionShorthand());
461     case CSSPropertyWebkitMaskRepeat:
462         return getLayeredShorthandValue(webkitMaskRepeatShorthand());
463     case CSSPropertyWebkitMask:
464         return getLayeredShorthandValue(webkitMaskShorthand());
465     case CSSPropertyWebkitTextEmphasis:
466         return getShorthandValue(webkitTextEmphasisShorthand());
467     case CSSPropertyWebkitTextStroke:
468         return getShorthandValue(webkitTextStrokeShorthand());
469     case CSSPropertyWebkitTransition:
470         return getLayeredShorthandValue(webkitTransitionShorthand(), true);
471     case CSSPropertyWebkitAnimation:
472         return getLayeredShorthandValue(webkitAnimationShorthand(), true);
473     case CSSPropertyMarker: {
474         if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyMarkerStart))
475             return value->cssText();
476         return String();
477     }
478     case CSSPropertyBorderRadius:
479         return get4Values(borderRadiusShorthand());
480     default:
481         return String();
482     }
483 }
484
485 String StylePropertySerializer::borderSpacingValue(const StylePropertyShorthand& shorthand) const
486 {
487     const CSSValue* horizontalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[0]);
488     const CSSValue* verticalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[1]);
489
490     // While standard border-spacing property does not allow specifying border-spacing-vertical without
491     // specifying border-spacing-horizontal <http://www.w3.org/TR/CSS21/tables.html#separated-borders>,
492     // -webkit-border-spacing-vertical can be set without -webkit-border-spacing-horizontal.
493     if (!horizontalValue || !verticalValue)
494         return String();
495
496     String horizontalValueCSSText = horizontalValue->cssText();
497     String verticalValueCSSText = verticalValue->cssText();
498     if (horizontalValueCSSText == verticalValueCSSText)
499         return horizontalValueCSSText;
500     return horizontalValueCSSText + ' ' + verticalValueCSSText;
501 }
502
503 void StylePropertySerializer::appendFontLonghandValueIfExplicit(CSSPropertyID propertyID, StringBuilder& result, String& commonValue) const
504 {
505     int foundPropertyIndex = m_propertySet.findPropertyIndex(propertyID);
506     if (foundPropertyIndex == -1)
507         return; // All longhands must have at least implicit values if "font" is specified.
508
509     if (m_propertySet.propertyAt(foundPropertyIndex).isImplicit()) {
510         commonValue = String();
511         return;
512     }
513
514     char prefix = '\0';
515     switch (propertyID) {
516     case CSSPropertyFontStyle:
517         break; // No prefix.
518     case CSSPropertyFontFamily:
519     case CSSPropertyFontStretch:
520     case CSSPropertyFontVariant:
521     case CSSPropertyFontWeight:
522         prefix = ' ';
523         break;
524     case CSSPropertyLineHeight:
525         prefix = '/';
526         break;
527     default:
528         ASSERT_NOT_REACHED();
529     }
530
531     if (prefix && !result.isEmpty())
532         result.append(prefix);
533     String value = m_propertySet.propertyAt(foundPropertyIndex).value()->cssText();
534     result.append(value);
535     if (!commonValue.isNull() && commonValue != value)
536         commonValue = String();
537 }
538
539 String StylePropertySerializer::fontValue() const
540 {
541     int fontSizePropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontSize);
542     int fontFamilyPropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontFamily);
543     if (fontSizePropertyIndex == -1 || fontFamilyPropertyIndex == -1)
544         return emptyString();
545
546     PropertyValueForSerializer fontSizeProperty = m_propertySet.propertyAt(fontSizePropertyIndex);
547     PropertyValueForSerializer fontFamilyProperty = m_propertySet.propertyAt(fontFamilyPropertyIndex);
548     if (fontSizeProperty.isImplicit() || fontFamilyProperty.isImplicit())
549         return emptyString();
550
551     String commonValue = fontSizeProperty.value()->cssText();
552     StringBuilder result;
553     appendFontLonghandValueIfExplicit(CSSPropertyFontStyle, result, commonValue);
554     appendFontLonghandValueIfExplicit(CSSPropertyFontVariant, result, commonValue);
555     appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, result, commonValue);
556     appendFontLonghandValueIfExplicit(CSSPropertyFontStretch, result, commonValue);
557     if (!result.isEmpty())
558         result.append(' ');
559     result.append(fontSizeProperty.value()->cssText());
560     appendFontLonghandValueIfExplicit(CSSPropertyLineHeight, result, commonValue);
561     if (!result.isEmpty())
562         result.append(' ');
563     result.append(fontFamilyProperty.value()->cssText());
564     if (isInitialOrInherit(commonValue))
565         return commonValue;
566     return result.toString();
567 }
568
569 String StylePropertySerializer::get4Values(const StylePropertyShorthand& shorthand) const
570 {
571     // Assume the properties are in the usual order top, right, bottom, left.
572     int topValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[0]);
573     int rightValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[1]);
574     int bottomValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[2]);
575     int leftValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[3]);
576
577     if (topValueIndex == -1 || rightValueIndex == -1 || bottomValueIndex == -1 || leftValueIndex == -1)
578         return String();
579
580     PropertyValueForSerializer top = m_propertySet.propertyAt(topValueIndex);
581     PropertyValueForSerializer right = m_propertySet.propertyAt(rightValueIndex);
582     PropertyValueForSerializer bottom = m_propertySet.propertyAt(bottomValueIndex);
583     PropertyValueForSerializer left = m_propertySet.propertyAt(leftValueIndex);
584
585         // All 4 properties must be specified.
586     if (!top.value() || !right.value() || !bottom.value() || !left.value())
587         return String();
588
589     if (top.isInherited() && right.isInherited() && bottom.isInherited() && left.isInherited())
590         return getValueName(CSSValueInherit);
591
592     if (top.value()->isInitialValue() || right.value()->isInitialValue() || bottom.value()->isInitialValue() || left.value()->isInitialValue()) {
593         if (top.value()->isInitialValue() && right.value()->isInitialValue() && bottom.value()->isInitialValue() && left.value()->isInitialValue() && !top.isImplicit()) {
594             // All components are "initial" and "top" is not implicit.
595             return getValueName(CSSValueInitial);
596         }
597         return String();
598     }
599     if (top.isImportant() != right.isImportant() || right.isImportant() != bottom.isImportant() || bottom.isImportant() != left.isImportant())
600         return String();
601
602     bool showLeft = !right.value()->equals(*left.value());
603     bool showBottom = !top.value()->equals(*bottom.value()) || showLeft;
604     bool showRight = !top.value()->equals(*right.value()) || showBottom;
605
606     StringBuilder result;
607     result.append(top.value()->cssText());
608     if (showRight) {
609         result.append(' ');
610         result.append(right.value()->cssText());
611     }
612     if (showBottom) {
613         result.append(' ');
614         result.append(bottom.value()->cssText());
615     }
616     if (showLeft) {
617         result.append(' ');
618         result.append(left.value()->cssText());
619     }
620     return result.toString();
621 }
622
623 String StylePropertySerializer::getLayeredShorthandValue(const StylePropertyShorthand& shorthand, bool checkShorthandAvailable) const
624 {
625     StringBuilder result;
626
627     const unsigned size = shorthand.length();
628     // Begin by collecting the properties into an array.
629     WillBeHeapVector<const CSSValue*> values(size);
630     size_t numLayers = 0;
631
632     for (unsigned i = 0; i < size; ++i) {
633         values[i] = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
634         if (values[i]) {
635             if (values[i]->isBaseValueList()) {
636                 const CSSValueList* valueList = toCSSValueList(values[i]);
637                 numLayers = std::max(valueList->length(), numLayers);
638             } else {
639                 numLayers = std::max<size_t>(1U, numLayers);
640             }
641         } else if (checkShorthandAvailable) {
642             return String();
643         }
644     }
645
646
647     String commonValue;
648     bool commonValueInitialized = false;
649
650     // Now stitch the properties together. Implicit initial values are flagged as such and
651     // can safely be omitted.
652     for (size_t i = 0; i < numLayers; i++) {
653         StringBuilder layerResult;
654         bool useRepeatXShorthand = false;
655         bool useRepeatYShorthand = false;
656         bool useSingleWordShorthand = false;
657         bool foundPositionYCSSProperty = false;
658         for (unsigned j = 0; j < size; j++) {
659             const CSSValue* value = 0;
660             if (values[j]) {
661                 if (values[j]->isBaseValueList()) {
662                     value = toCSSValueList(values[j])->itemWithBoundsCheck(i);
663                 } else {
664                     value = values[j];
665
666                     // Color only belongs in the last layer.
667                     if (shorthand.properties()[j] == CSSPropertyBackgroundColor) {
668                         if (i != numLayers - 1)
669                             value = 0;
670                     } else if (i) {
671                         // Other singletons only belong in the first layer.
672                         value = 0;
673                     }
674                 }
675             }
676
677             // We need to report background-repeat as it was written in the CSS. If the property is implicit,
678             // then it was written with only one value. Here we figure out which value that was so we can
679             // report back correctly.
680             if ((shorthand.properties()[j] == CSSPropertyBackgroundRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))
681                 || (shorthand.properties()[j] == CSSPropertyWebkitMaskRepeatX && m_propertySet.isPropertyImplicit(shorthand.properties()[j]))) {
682
683                 // BUG 49055: make sure the value was not reset in the layer check just above.
684                 if ((j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyBackgroundRepeatY && value)
685                     || (j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyWebkitMaskRepeatY && value)) {
686                     const CSSValue* yValue = 0;
687                     const CSSValue* nextValue = values[j + 1];
688                     if (nextValue->isValueList())
689                         yValue = toCSSValueList(nextValue)->item(i);
690                     else
691                         yValue = nextValue;
692
693                     // background-repeat-x(y) or mask-repeat-x(y) may be like this : "initial, repeat". We can omit the implicit initial values
694                     // before starting to compare their values.
695                     if (value->isImplicitInitialValue() || yValue->isImplicitInitialValue())
696                         continue;
697
698                     // FIXME: At some point we need to fix this code to avoid returning an invalid shorthand,
699                     // since some longhand combinations are not serializable into a single shorthand.
700                     if (!value->isPrimitiveValue() || !yValue->isPrimitiveValue())
701                         continue;
702
703                     CSSValueID xId = toCSSPrimitiveValue(value)->getValueID();
704                     CSSValueID yId = toCSSPrimitiveValue(yValue)->getValueID();
705                     if (xId != yId) {
706                         if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) {
707                             useRepeatXShorthand = true;
708                             ++j;
709                         } else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) {
710                             useRepeatYShorthand = true;
711                             continue;
712                         }
713                     } else {
714                         useSingleWordShorthand = true;
715                         ++j;
716                     }
717                 }
718             }
719
720             String valueText;
721             if (value && !value->isImplicitInitialValue()) {
722                 if (!layerResult.isEmpty())
723                     layerResult.append(' ');
724                 if (foundPositionYCSSProperty
725                     && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
726                     layerResult.appendLiteral("/ ");
727                 if (!foundPositionYCSSProperty
728                     && (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
729                     continue;
730
731                 if (useRepeatXShorthand) {
732                     useRepeatXShorthand = false;
733                     layerResult.append(getValueName(CSSValueRepeatX));
734                 } else if (useRepeatYShorthand) {
735                     useRepeatYShorthand = false;
736                     layerResult.append(getValueName(CSSValueRepeatY));
737                 } else {
738                     if (useSingleWordShorthand)
739                         useSingleWordShorthand = false;
740                     valueText = value->cssText();
741                     layerResult.append(valueText);
742                 }
743
744                 if (shorthand.properties()[j] == CSSPropertyBackgroundPositionY
745                     || shorthand.properties()[j] == CSSPropertyWebkitMaskPositionY) {
746                     foundPositionYCSSProperty = true;
747
748                     // background-position is a special case: if only the first offset is specified,
749                     // the second one defaults to "center", not the same value.
750                     if (commonValueInitialized && commonValue != "initial" && commonValue != "inherit")
751                         commonValue = String();
752                 }
753             }
754
755             if (!commonValueInitialized) {
756                 commonValue = valueText;
757                 commonValueInitialized = true;
758             } else if (!commonValue.isNull() && commonValue != valueText)
759                 commonValue = String();
760         }
761
762         if (!layerResult.isEmpty()) {
763             if (!result.isEmpty())
764                 result.appendLiteral(", ");
765             result.append(layerResult);
766         }
767     }
768
769     if (isInitialOrInherit(commonValue))
770         return commonValue;
771
772     if (result.isEmpty())
773         return String();
774     return result.toString();
775 }
776
777 String StylePropertySerializer::getShorthandValue(const StylePropertyShorthand& shorthand) const
778 {
779     String commonValue;
780     StringBuilder result;
781     for (unsigned i = 0; i < shorthand.length(); ++i) {
782         if (!m_propertySet.isPropertyImplicit(shorthand.properties()[i])) {
783             const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
784             if (!value)
785                 return String();
786             String valueText = value->cssText();
787             if (!i)
788                 commonValue = valueText;
789             else if (!commonValue.isNull() && commonValue != valueText)
790                 commonValue = String();
791             if (value->isInitialValue())
792                 continue;
793             if (!result.isEmpty())
794                 result.append(' ');
795             result.append(valueText);
796         } else
797             commonValue = String();
798     }
799     if (isInitialOrInherit(commonValue))
800         return commonValue;
801     if (result.isEmpty())
802         return String();
803     return result.toString();
804 }
805
806 // only returns a non-null value if all properties have the same, non-null value
807 String StylePropertySerializer::getCommonValue(const StylePropertyShorthand& shorthand) const
808 {
809     String res;
810     bool lastPropertyWasImportant = false;
811     for (unsigned i = 0; i < shorthand.length(); ++i) {
812         const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
813         // FIXME: CSSInitialValue::cssText should generate the right value.
814         if (!value)
815             return String();
816         String text = value->cssText();
817         if (text.isNull())
818             return String();
819         if (res.isNull())
820             res = text;
821         else if (res != text)
822             return String();
823
824         bool currentPropertyIsImportant = m_propertySet.propertyIsImportant(shorthand.properties()[i]);
825         if (i && lastPropertyWasImportant != currentPropertyIsImportant)
826             return String();
827         lastPropertyWasImportant = currentPropertyIsImportant;
828     }
829     return res;
830 }
831
832 String StylePropertySerializer::borderPropertyValue(CommonValueMode valueMode) const
833 {
834     const StylePropertyShorthand properties[3] = { borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand() };
835     String commonValue;
836     StringBuilder result;
837     for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) {
838         String value = getCommonValue(properties[i]);
839         if (value.isNull()) {
840             if (valueMode == ReturnNullOnUncommonValues)
841                 return String();
842             ASSERT(valueMode == OmitUncommonValues);
843             continue;
844         }
845         if (!i)
846             commonValue = value;
847         else if (!commonValue.isNull() && commonValue != value)
848             commonValue = String();
849         if (value == "initial")
850             continue;
851         if (!result.isEmpty())
852             result.append(' ');
853         result.append(value);
854     }
855     if (isInitialOrInherit(commonValue))
856         return commonValue;
857     return result.isEmpty() ? String() : result.toString();
858 }
859
860 static void appendBackgroundRepeatValue(StringBuilder& builder, const CSSValue& repeatXCSSValue, const CSSValue& repeatYCSSValue)
861 {
862     // FIXME: Ensure initial values do not appear in CSS_VALUE_LISTS.
863     DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, initialRepeatValue, (CSSPrimitiveValue::create(CSSValueRepeat)));
864     const CSSPrimitiveValue& repeatX = repeatXCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatXCSSValue);
865     const CSSPrimitiveValue& repeatY = repeatYCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatYCSSValue);
866     CSSValueID repeatXValueId = repeatX.getValueID();
867     CSSValueID repeatYValueId = repeatY.getValueID();
868     if (repeatXValueId == repeatYValueId) {
869         builder.append(repeatX.cssText());
870     } else if (repeatXValueId == CSSValueNoRepeat && repeatYValueId == CSSValueRepeat) {
871         builder.appendLiteral("repeat-y");
872     } else if (repeatXValueId == CSSValueRepeat && repeatYValueId == CSSValueNoRepeat) {
873         builder.appendLiteral("repeat-x");
874     } else {
875         builder.append(repeatX.cssText());
876         builder.appendLiteral(" ");
877         builder.append(repeatY.cssText());
878     }
879 }
880
881 String StylePropertySerializer::backgroundRepeatPropertyValue() const
882 {
883     const CSSValue* repeatX = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatX);
884     const CSSValue* repeatY = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatY);
885     if (!repeatX || !repeatY)
886         return String();
887     if (m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX) != m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatY))
888         return String();
889     if (repeatX->cssValueType() == repeatY->cssValueType()
890         && (repeatX->cssValueType() == CSSValue::CSS_INITIAL || repeatX->cssValueType() == CSSValue::CSS_INHERIT)) {
891         return repeatX->cssText();
892     }
893
894     const CSSValueList* repeatXList = 0;
895     int repeatXLength = 1;
896     if (repeatX->cssValueType() == CSSValue::CSS_VALUE_LIST) {
897         repeatXList = toCSSValueList(repeatX);
898         repeatXLength = repeatXList->length();
899     } else if (repeatX->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) {
900         return String();
901     }
902
903     const CSSValueList* repeatYList = 0;
904     int repeatYLength = 1;
905     if (repeatY->cssValueType() == CSSValue::CSS_VALUE_LIST) {
906         repeatYList = toCSSValueList(repeatY);
907         repeatYLength = repeatYList->length();
908     } else if (repeatY->cssValueType() != CSSValue::CSS_PRIMITIVE_VALUE) {
909         return String();
910     }
911
912     size_t shorthandLength = lowestCommonMultiple(repeatXLength, repeatYLength);
913     StringBuilder builder;
914     for (size_t i = 0; i < shorthandLength; ++i) {
915         if (i)
916             builder.appendLiteral(", ");
917
918         const CSSValue* xValue = repeatXList ? repeatXList->item(i % repeatXList->length()) : repeatX;
919         const CSSValue* yValue = repeatYList ? repeatYList->item(i % repeatYList->length()) : repeatY;
920         appendBackgroundRepeatValue(builder, *xValue, *yValue);
921     }
922     return builder.toString();
923 }
924
925 void StylePropertySerializer::appendBackgroundPropertyAsText(StringBuilder& result, unsigned& numDecls) const
926 {
927     if (isPropertyShorthandAvailable(backgroundShorthand())) {
928         String backgroundValue = getPropertyValue(CSSPropertyBackground);
929         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage);
930         result.append(getPropertyText(CSSPropertyBackground, backgroundValue, isImportant, numDecls++));
931         return;
932     }
933     if (shorthandHasOnlyInitialOrInheritedValue(backgroundShorthand())) {
934         const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundImage);
935         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage);
936         result.append(getPropertyText(CSSPropertyBackground, value->cssText(), isImportant, numDecls++));
937         return;
938     }
939
940     // backgroundShorthandProperty without layered shorhand properties
941     const CSSPropertyID backgroundPropertyIds[] = {
942         CSSPropertyBackgroundImage,
943         CSSPropertyBackgroundAttachment,
944         CSSPropertyBackgroundColor,
945         CSSPropertyBackgroundSize,
946         CSSPropertyBackgroundOrigin,
947         CSSPropertyBackgroundClip
948     };
949
950     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(backgroundPropertyIds); ++i) {
951         CSSPropertyID propertyID = backgroundPropertyIds[i];
952         const CSSValue* value = m_propertySet.getPropertyCSSValue(propertyID);
953         if (!value)
954             continue;
955         result.append(getPropertyText(propertyID, value->cssText(), m_propertySet.propertyIsImportant(propertyID), numDecls++));
956     }
957
958     // FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output.
959     // It is required because background-position-x/y are non-standard properties and WebKit generated output
960     // would not work in Firefox (<rdar://problem/5143183>)
961     // It would be a better solution if background-position was CSS_PAIR.
962     if (shorthandHasOnlyInitialOrInheritedValue(backgroundPositionShorthand())) {
963         const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX);
964         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
965         result.append(getPropertyText(CSSPropertyBackgroundPosition, value->cssText(), isImportant, numDecls++));
966     } else if (isPropertyShorthandAvailable(backgroundPositionShorthand())) {
967         String positionValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundPosition);
968         bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
969         if (!positionValue.isNull())
970             result.append(getPropertyText(CSSPropertyBackgroundPosition, positionValue, isImportant, numDecls++));
971     } else {
972         // should check background-position-x or background-position-y.
973         if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX)) {
974             if (!value->isImplicitInitialValue()) {
975                 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX);
976                 result.append(getPropertyText(CSSPropertyBackgroundPositionX, value->cssText(), isImportant, numDecls++));
977             }
978         }
979         if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionY)) {
980             if (!value->isImplicitInitialValue()) {
981                 bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionY);
982                 result.append(getPropertyText(CSSPropertyBackgroundPositionY, value->cssText(), isImportant, numDecls++));
983             }
984         }
985     }
986
987     String repeatValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundRepeat);
988     if (!repeatValue.isNull())
989         result.append(getPropertyText(CSSPropertyBackgroundRepeat, repeatValue, m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX), numDecls++));
990 }
991
992 bool StylePropertySerializer::isPropertyShorthandAvailable(const StylePropertyShorthand& shorthand) const
993 {
994     ASSERT(shorthand.length() > 0);
995
996     bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]);
997     for (unsigned i = 0; i < shorthand.length(); ++i) {
998         const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
999         if (!value || (value->isInitialValue() && !value->isImplicitInitialValue()) || value->isInheritedValue())
1000             return false;
1001         if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i]))
1002             return false;
1003     }
1004     return true;
1005 }
1006
1007 bool StylePropertySerializer::shorthandHasOnlyInitialOrInheritedValue(const StylePropertyShorthand& shorthand) const
1008 {
1009     ASSERT(shorthand.length() > 0);
1010     bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]);
1011     bool isInitialValue = true;
1012     bool isInheritedValue = true;
1013     for (unsigned i = 0; i < shorthand.length(); ++i) {
1014         const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]);
1015         if (!value)
1016             return false;
1017         if (!value->isInitialValue())
1018             isInitialValue = false;
1019         if (!value->isInheritedValue())
1020             isInheritedValue = false;
1021         if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i]))
1022             return false;
1023     }
1024     return isInitialValue || isInheritedValue;
1025 }
1026
1027 }