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