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