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