/*
 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012 Apple Inc. All rights reserved.
 * Copyright (C) 2011 Research In Motion Limited. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include "StylePropertySet.h"

#include "CSSComputedStyleDeclaration.h"
#include "CSSCustomPropertyValue.h"
#include "CSSGridLineNamesValue.h"
#include "CSSGridTemplateAreasValue.h"
#include "CSSParser.h"
#include "CSSPendingSubstitutionValue.h"
#include "CSSPropertyParser.h"
#include "CSSTokenizer.h"
#include "CSSValueInlines.h"
#include "CSSValueKeywords.h"
#include "CSSValueList.h"
#include "CSSValuePool.h"
#include "CachedResource.h"
#include "Document.h"
#include "Pair.h"
#include "Rect.h"
#include "PropertySetCSSStyleDeclaration.h"
#include "StylePropertyShorthand.h"
#include "StyleSheetContents.h"
#include "SVGFontFaceElement.h"
#include <wtf/BitArray.h>
#include <wtf/text/StringBuilder.h>

#ifndef NDEBUG
#include <stdio.h>
#include <wtf/ASCIICType.h>
#include <wtf/text/CString.h>
#endif

using namespace std;

namespace WebCore {

static size_t sizeForImmutableStylePropertySetWithPropertyCount(unsigned count)
{
    return sizeof(ImmutableStylePropertySet) - sizeof(void*) + sizeof(CSSValue*) * count + sizeof(StylePropertyMetadata) * count;
}

static bool isCSSWideValueKeyword(StringView value)
{
    return value == "initial" || value == "inherit" || value == "unset" || value == "revert";
}

static bool isNoneValue(const RefPtr<CSSValue>& value)
{
    return value && value->isPrimitiveValue() && toCSSPrimitiveValue(value.get())->isValueID() && toCSSPrimitiveValue(value.get())->valueID() == CSSValueNone;
}

static bool isValueID(const CSSValue& value, CSSValueID id)
{
    return value.isPrimitiveValue() && toCSSPrimitiveValue(value).isValueID() && toCSSPrimitiveValue(value).valueID() == id;
}

static bool isValueID(const RefPtr<CSSValue>& value, CSSValueID id)
{
    return value && isValueID(*value, id);
}

static bool isValueIDIncludingList(const Ref<CSSValue>& value, CSSValueID id)
{
    if (value->isValueList()) {
        if (toCSSValueList(value.get()).size() != 1)
            return false;
        return isValueID(toCSSValueList(value.get()).item(0), id);
    }
    return isValueID(value, id);
}

static bool isValueIDIncludingList(const RefPtr<CSSValue>& value, CSSValueID id)
{
    return value && isValueIDIncludingList(Ref<CSSValue>(*value), id);
}

PassRefPtr<ImmutableStylePropertySet> StylePropertySet::createImmutable(const CSSProperty* properties, unsigned count, CSSParserMode cssParserMode)
{
    void* slot = WTF::fastMalloc(sizeForImmutableStylePropertySetWithPropertyCount(count));
    return adoptRef(new (slot) ImmutableStylePropertySet(properties, count, cssParserMode));
}

PassRefPtr<StylePropertySet> StylePropertySet::immutableCopyIfNeeded() const
{
    if (!isMutable())
        return const_cast<StylePropertySet*>(this);
    const MutableStylePropertySet* mutableThis = static_cast<const MutableStylePropertySet*>(this);
    return createImmutable(mutableThis->m_propertyVector.data(), mutableThis->m_propertyVector.size(), cssParserMode());
}

MutableStylePropertySet::MutableStylePropertySet(CSSParserMode cssParserMode)
    : StylePropertySet(cssParserMode)
{
}

MutableStylePropertySet::MutableStylePropertySet(Vector<CSSProperty>&& properties)
    : StylePropertySet(HTMLStandardMode)
	, m_propertyVector(WTFMove(properties))
{
}

MutableStylePropertySet::~MutableStylePropertySet() = default;

ImmutableStylePropertySet::ImmutableStylePropertySet(const CSSProperty* properties, unsigned length, CSSParserMode cssParserMode)
    : StylePropertySet(cssParserMode, length)
{
    StylePropertyMetadata* metadataArray = const_cast<StylePropertyMetadata*>(this->metadataArray());
    CSSValue** valueArray = const_cast<CSSValue**>(this->valueArray());
    for (unsigned i = 0; i < length; ++i) {
        metadataArray[i] = properties[i].metadata();
        valueArray[i] = properties[i].value();
        valueArray[i]->ref();
    }
}

ImmutableStylePropertySet::~ImmutableStylePropertySet()
{
    CSSValue** valueArray = const_cast<CSSValue**>(this->valueArray());
    for (unsigned i = 0; i < m_arraySize; ++i)
    	valueArray[i]->deref();
}

MutableStylePropertySet::MutableStylePropertySet(const StylePropertySet& other)
    : StylePropertySet(other.cssParserMode())
{
    if (other.isMutable())
        m_propertyVector = static_cast<const MutableStylePropertySet&>(other).m_propertyVector;
    else {
        auto& immutableOther = toImmutableStyleProperties(other);
        unsigned propertyCount = immutableOther.propertyCount();
        m_propertyVector.reserveInitialCapacity(propertyCount);
        for (unsigned i = 0; i < propertyCount; ++i)
            m_propertyVector.uncheckedAppend(immutableOther.propertyAt(i).toCSSProperty());
    }
}

bool StyleProperties::shorthandHasVariableReference(CSSPropertyID propertyID, String& shorthandValue) const
{
    auto shorthand = shorthandForProperty(propertyID);
    if (shorthand.length()) {
        size_t numSetFromShorthand = 0;
        // Checks for shorthand property if any of its longhand properties have set to a variable
        // or are all pending substitution
        for (size_t i = 0; i < shorthand.length(); i++) {
        	RefPtr<CSSValue> cssPropertyValue =  getPropertyCSSValue(shorthand.properties()[i]);

            auto hasBeenSetFromLonghand = cssPropertyValue && cssPropertyValue->isVariableReferenceValue();
            auto hasBeenSetFromShorthand = cssPropertyValue && cssPropertyValue->isPendingSubstitutionValue();
            auto hasNotBeenSetFromRequestedShorthand = hasBeenSetFromShorthand && toCSSPendingSubstitutionValue(*cssPropertyValue).shorthandPropertyId() != propertyID;

            // Request for shorthand value should return empty string if any longhand values have been
            // set to a variable or if they were set to a variable by a different shorthand.
            if (hasBeenSetFromLonghand || hasNotBeenSetFromRequestedShorthand)
                return true;
            if (hasBeenSetFromShorthand)
                numSetFromShorthand += 1;
        }
        if (numSetFromShorthand) {
            if (numSetFromShorthand != shorthand.length())
                return true;
            shorthandValue = toCSSPendingSubstitutionValue(* getPropertyCSSValue(shorthand.properties()[0])).shorthandValue().cssText();
            return true;
        }
    }
    return false;
}

String StylePropertySet::getPropertyValue(CSSPropertyID propertyID) const
{
    if (RefPtr<CSSValue> value = getPropertyCSSValue(propertyID)) {
        switch (propertyID) {
        case CSSPropertyFillOpacity:
        case CSSPropertyFloodOpacity:
        case CSSPropertyOpacity:
        case CSSPropertyStopOpacity:
        case CSSPropertyStrokeOpacity:
            // Opacity percentage values serialize as a fraction in the range 0-1, no "%".
            if ((*value).isPrimitiveValue() && toCSSPrimitiveValue(*value).isPercentage())
                return String::numberToStringECMAScript(toCSSPrimitiveValue(*value).doubleValue() / 100);
            FALLTHROUGH;
        default:
            return value->cssText();
        }
    }

    {
    	String shorthandValue;
        if (shorthandHasVariableReference(propertyID, shorthandValue))
            return shorthandValue;
    }

    // Shorthand and 4-values properties
    switch (propertyID) {
    case CSSPropertyAll:
        return getCommonValue(allShorthand());
    case CSSPropertyAnimation:
        return getLayeredShorthandValue(animationShorthand());
    case CSSPropertyBorderSpacing:
        return borderSpacingValue(borderSpacingShorthand());
    case CSSPropertyBackgroundPosition:
        return getLayeredShorthandValue(backgroundPositionShorthand());
    case CSSPropertyBackgroundRepeat:
        return getLayeredShorthandValue(backgroundRepeatShorthand());
    case CSSPropertyBackground:
        return getLayeredShorthandValue(backgroundShorthand());
    case CSSPropertyBorder:
		return borderPropertyValue(borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand());
	case CSSPropertyBorderTop:
        return getShorthandValue(borderTopShorthand());
    case CSSPropertyBorderRight:
        return getShorthandValue(borderRightShorthand());
    case CSSPropertyBorderBottom:
        return getShorthandValue(borderBottomShorthand());
    case CSSPropertyBorderLeft:
        return getShorthandValue(borderLeftShorthand());
    case CSSPropertyBorderBlock:
        return borderPropertyValue(borderBlockWidthShorthand(), borderBlockStyleShorthand(), borderBlockColorShorthand());
    case CSSPropertyBorderBlockColor:
        return get2Values(borderBlockColorShorthand());
    case CSSPropertyBorderBlockStyle:
        return get2Values(borderBlockStyleShorthand());
    case CSSPropertyBorderBlockWidth:
        return get2Values(borderBlockWidthShorthand());
    case CSSPropertyBorderBlockStart:
        return getShorthandValue(borderBlockStartShorthand());
    case CSSPropertyBorderBlockEnd:
        return getShorthandValue(borderBlockEndShorthand());
    case CSSPropertyBorderImage:
        return borderImagePropertyValue();
    case CSSPropertyBorderInline:
        return borderPropertyValue(borderInlineWidthShorthand(), borderInlineStyleShorthand(), borderInlineColorShorthand());
    case CSSPropertyBorderInlineColor:
        return get2Values(borderInlineColorShorthand());
    case CSSPropertyBorderInlineStyle:
        return get2Values(borderInlineStyleShorthand());
    case CSSPropertyBorderInlineWidth:
        return get2Values(borderInlineWidthShorthand());
    case CSSPropertyBorderInlineStart:
        return getShorthandValue(borderInlineStartShorthand());
    case CSSPropertyBorderInlineEnd:
        return getShorthandValue(borderInlineEndShorthand());
    case CSSPropertyOutline:
        return getShorthandValue(outlineShorthand());
    case CSSPropertyBorderColor:
        return get4Values(borderColorShorthand());
    case CSSPropertyBorderWidth:
        return get4Values(borderWidthShorthand());
    case CSSPropertyBorderStyle:
        return get4Values(borderStyleShorthand());
    case CSSPropertyColumnRule:
        return getShorthandValue(columnRuleShorthand());
    case CSSPropertyColumns:
        return getShorthandValue(columnsShorthand());
    case CSSPropertyFlex:
        return getShorthandValue(flexShorthand());
    case CSSPropertyFlexFlow:
        return getShorthandValue(flexFlowShorthand());
    case CSSPropertyGridArea:
        return getGridAreaShorthandValue();
    case CSSPropertyGridTemplate:
        return getGridTemplateValue();
    case CSSPropertyGrid:
        return getGridValue();
    case CSSPropertyGridColumn:
        return getGridRowColumnShorthandValue(gridColumnShorthand());
    case CSSPropertyGridRow:
        return getGridRowColumnShorthandValue(gridRowShorthand());
    case CSSPropertyPageBreakAfter:
        return pageBreakPropertyValue(pageBreakAfterShorthand());
    case CSSPropertyPageBreakBefore:
        return pageBreakPropertyValue(pageBreakBeforeShorthand());
    case CSSPropertyPageBreakInside:
        return pageBreakPropertyValue(pageBreakInsideShorthand());
    case CSSPropertyPlaceContent:
        return getAlignmentShorthandValue(placeContentShorthand());
    case CSSPropertyPlaceItems:
        return getAlignmentShorthandValue(placeItemsShorthand());
    case CSSPropertyPlaceSelf:
        return getAlignmentShorthandValue(placeSelfShorthand());
    case CSSPropertyFont:
        return fontValue();
    case CSSPropertyInset:
        return get4Values(insetShorthand());
    case CSSPropertyInsetBlock:
        return get2Values(insetBlockShorthand());
    case CSSPropertyInsetInline:
        return get2Values(insetInlineShorthand());
    case CSSPropertyMargin:
        return get4Values(marginShorthand());
    case CSSPropertyMarginBlock:
        return get2Values(marginBlockShorthand());
    case CSSPropertyMarginInline:
        return get2Values(marginInlineShorthand());
    case CSSPropertyOverflow:
        return get2Values(overflowShorthand());
    case CSSPropertyPadding:
        return get4Values(paddingShorthand());
    case CSSPropertyPaddingBlock:
        return get2Values(paddingBlockShorthand());
    case CSSPropertyPaddingInline:
        return get2Values(paddingInlineShorthand());
    case CSSPropertyTransition:
        return getLayeredShorthandValue(transitionShorthand());
    case CSSPropertyListStyle:
        return getShorthandValue(listStyleShorthand());
    case CSSPropertyWebkitMarquee:
        return getShorthandValue(webkitMarqueeShorthand());
    case CSSPropertyMaskPosition:
        return getLayeredShorthandValue(maskPositionShorthand());
    case CSSPropertyWebkitMaskPosition:
        return getLayeredShorthandValue(webkitMaskPositionShorthand());
    case CSSPropertyMaskRepeat:
        return getLayeredShorthandValue(maskRepeatShorthand());
    case CSSPropertyMask:
    case CSSPropertyWebkitMask:
        return getLayeredShorthandValue(shorthandForProperty(propertyID));
    case CSSPropertyWebkitTextEmphasis:
        return getShorthandValue(webkitTextEmphasisShorthand());
    case CSSPropertyWebkitTextStroke:
        return getShorthandValue(webkitTextStrokeShorthand());
    case CSSPropertyPerspectiveOrigin:
        return getShorthandValue(perspectiveOriginShorthand());
    case CSSPropertyTransformOrigin:
        return getShorthandValue(transformOriginShorthand());
    case CSSPropertyMarker:
        if (RefPtr<CSSValue> value = getPropertyCSSValue(CSSPropertyMarkerStart))
            return value->cssText();
        return String();
    case CSSPropertyBorderRadius:
        return get4Values(borderRadiusShorthand());
    case CSSPropertyGap:
        return get2Values(gapShorthand());
#if ENABLE(CSS_SCROLL_SNAP)
    case CSSPropertyScrollSnapMargin:
        return get4Values(scrollSnapMarginShorthand());
    case CSSPropertyScrollPadding:
        return get4Values(scrollPaddingShorthand());
#endif
    default:
          return String();
    }
}

static bool canOmitTrailingGridAreaValue(const CSSValue& value, const CSSValue& trailing)
{
	auto isCustomIdentValue = [&] (const CSSValue& value) -> bool
	{
	    return value.isPrimitiveValue() && toCSSPrimitiveValue(value).isCustomIdent();
	};

    if (isCustomIdentValue(value))
        return isCustomIdentValue(trailing) && value.cssText() == trailing.cssText();
    return isValueID(trailing, CSSValueAuto);
}

String StyleProperties::getGridRowColumnShorthandValue(const StylePropertyShorthand& shorthand) const
{
    auto start = getPropertyCSSValue(shorthand.properties()[0]);
    auto end = getPropertyCSSValue(shorthand.properties()[1]);

    if (!start || !end)
        return String();

    StringBuilder result;
    result.append(start->cssText());
    if (!canOmitTrailingGridAreaValue(*start, *end)) {
        result.append(" / ");
        result.append(end->cssText());
    }

    return result.toString();
}

String StyleProperties::getGridAreaShorthandValue() const
{
    RefPtr<CSSValue> values[4];
    values[0] = getPropertyCSSValue(CSSPropertyGridRowStart);
    values[1] = getPropertyCSSValue(CSSPropertyGridColumnStart);
    values[2] = getPropertyCSSValue(CSSPropertyGridRowEnd);
    values[3] = getPropertyCSSValue(CSSPropertyGridColumnEnd);

    if (!values[0] || !values[1] || !values[2] || !values[3])
        return String();

    StringBuilder result;
    result.append(values[0]->cssText());

    unsigned trailingValues = 3;
    if (canOmitTrailingGridAreaValue(*values[1], *values[3])) {
        trailingValues--;
        if (canOmitTrailingGridAreaValue(*values[0], *values[2])) {
            trailingValues--;
            if (canOmitTrailingGridAreaValue(*values[0], *values[1]))
                trailingValues--;
        }
    }

    for (unsigned i = 1; i <= trailingValues; ++i) {
        result.append(" / ");
        result.append(values[i]->cssText());
    }

    return result.toString();
}

String StylePropertySet::getCustomPropertyValue(const String& propertyName) const
{
    RefPtr<CSSValue> value = getCustomPropertyCSSValue(propertyName);
    if (value)
        return value->cssText();
    return String();
}

String StylePropertySet::borderSpacingValue(const StylePropertyShorthand& shorthand) const
{
    RefPtr<CSSValue> horizontalValue = getPropertyCSSValue(shorthand.properties()[0]);
    RefPtr<CSSValue> verticalValue = getPropertyCSSValue(shorthand.properties()[1]);

    // While standard border-spacing property does not allow specifying border-spacing-vertical without
    // specifying border-spacing-horizontal <http://www.w3.org/TR/CSS21/tables.html#separated-borders>,
    // -webkit-border-spacing-vertical can be set without -webkit-border-spacing-horizontal.
    if (!horizontalValue || !verticalValue)
        return String();

    String horizontalValueCSSText = horizontalValue->cssText();
    String verticalValueCSSText = verticalValue->cssText();
    if (horizontalValueCSSText == verticalValueCSSText)
        return horizontalValueCSSText;
    return horizontalValueCSSText + ' ' + verticalValueCSSText;
}

void StylePropertySet::appendFontLonghandValueIfExplicit(CSSPropertyID propertyID, StringBuilder& result, String& commonValue) const
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1)
        return; // All longhands must have at least implicit values if "font" is specified.

    if (propertyAt(foundPropertyIndex).isImplicit()) {
        commonValue = String();
        return;
    }

    const char* prefix = "";
    switch (propertyID) {
    case CSSPropertyFontStyle:
        break; // No prefix.
    case CSSPropertyFontFamily:
    case CSSPropertyFontVariantCaps:
    case CSSPropertyFontWeight:
    case CSSPropertyFontStretch:
        prefix = " ";
        break;
    case CSSPropertyLineHeight:
    	prefix = " / ";
        break;
    default:
        ASSERT_NOT_REACHED();
    }

    String value = propertyAt(foundPropertyIndex).value()->cssText();
    if (!result.isEmpty())
    	result.append(prefix);
    result.append(value);
    if (!commonValue.isNull() && commonValue != value)
        commonValue = String();
}

String StylePropertySet::fontValue() const
{
    int fontSizePropertyIndex = findPropertyIndex(CSSPropertyFontSize);
    int fontFamilyPropertyIndex = findPropertyIndex(CSSPropertyFontFamily);
    if (fontSizePropertyIndex == -1 || fontFamilyPropertyIndex == -1)
        return emptyString();

    PropertyReference fontSizeProperty = propertyAt(fontSizePropertyIndex);
    PropertyReference fontFamilyProperty = propertyAt(fontFamilyPropertyIndex);
    if (fontSizeProperty.isImplicit() || fontFamilyProperty.isImplicit())
        return emptyString();

    String commonValue = fontSizeProperty.value()->cssText();
    StringBuilder result;
    appendFontLonghandValueIfExplicit(CSSPropertyFontStyle, result, commonValue);
    appendFontLonghandValueIfExplicit(CSSPropertyFontVariantCaps, result, commonValue);
    appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, result, commonValue);
    appendFontLonghandValueIfExplicit(CSSPropertyFontStretch, result, commonValue);
    if (!result.isEmpty())
        result.append(' ');
    result.append(fontSizeProperty.value()->cssText());
    appendFontLonghandValueIfExplicit(CSSPropertyLineHeight, result, commonValue);
    if (!result.isEmpty())
        result.append(' ');
    result.append(fontFamilyProperty.value()->cssText());
    if (isCSSWideValueKeyword(commonValue))
        return commonValue;
    return result.toString();
}

String StyleProperties::get2Values(const StylePropertyShorthand& shorthand) const
{
    // Assume the properties are in the usual order start, end.
    int startValueIndex = findPropertyIndex(shorthand.properties()[0]);
    int endValueIndex = findPropertyIndex(shorthand.properties()[1]);

    if (startValueIndex == -1 || endValueIndex == -1)
		return String();

    auto start = propertyAt(startValueIndex);
    auto end = propertyAt(endValueIndex);

    // All 2 properties must be specified.
    if (!start.value() || !end.value())
		return String();

    // Important flags must be the same
    if (start.isImportant() != end.isImportant())
		return String();

    if (start.isInherited() && end.isInherited())
        return getValueName(CSSValueInherit);

    if (start.value()->isInitialValue() || end.value()->isInitialValue()) {
        if (start.value()->isInitialValue() && end.value()->isInitialValue() && !start.isImplicit())
            return getValueName(CSSValueInitial);
		return String();
    }

    StringBuilder result;
    result.append(start.value()->cssText());
    if (!start.value()->equals(*end.value())) {
        result.append(' ');
        result.append(end.value()->cssText());
    }
    return result.toString();
}

String StylePropertySet::get4Values(const StylePropertyShorthand& shorthand) const
{
    // Assume the properties are in the usual order top, right, bottom, left.
	int topValueIndex = findPropertyIndex(shorthand.properties()[0]);
	int rightValueIndex = findPropertyIndex(shorthand.properties()[1]);
	int bottomValueIndex = findPropertyIndex(shorthand.properties()[2]);
	int leftValueIndex = findPropertyIndex(shorthand.properties()[3]);

	if (topValueIndex == -1 || rightValueIndex == -1 || bottomValueIndex == -1 || leftValueIndex == -1)
		return String();

	PropertyReference top = propertyAt(topValueIndex);
	PropertyReference right = propertyAt(rightValueIndex);
	PropertyReference bottom = propertyAt(bottomValueIndex);
	PropertyReference left = propertyAt(leftValueIndex);

    // All 4 properties must be specified.
	if (!top.value() || !right.value() || !bottom.value() || !left.value())
		return String();

    if (top.isInherited() && right.isInherited() && bottom.isInherited() && left.isInherited())
        return getValueName(CSSValueInherit);

    if (top.value()->isInitialValue() || right.value()->isInitialValue() || bottom.value()->isInitialValue() || left.value()->isInitialValue()) {
        if (top.value()->isInitialValue() && right.value()->isInitialValue() && bottom.value()->isInitialValue() && left.value()->isInitialValue() && !top.isImplicit()) {
            // All components are "initial" and "top" is not implicit.
            return getValueName(CSSValueInitial);
        }
        return String();
    }
	if (top.isImportant() != right.isImportant() || right.isImportant() != bottom.isImportant() || bottom.isImportant() != left.isImportant())
		return String();

    bool showLeft = !right.value()->equals(*left.value());
    bool showBottom = !top.value()->equals(*bottom.value()) || showLeft;
    bool showRight = !top.value()->equals(*right.value()) || showBottom;

	StringBuilder result;
	result.append(top.value()->cssText());
	if (showRight) {
		result.append(' ');
		result.append(right.value()->cssText());
	}
	if (showBottom) {
		result.append(' ');
		result.append(bottom.value()->cssText());
	}
	if (showLeft) {
		result.append(' ');
		result.append(left.value()->cssText());
	}
	return result.toString();
}

String StylePropertySet::getLayeredShorthandValue(const StylePropertyShorthand& shorthand) const
{
    StringBuilder result;

    const unsigned size = shorthand.length();
    Vector<RefPtr<CSSValue>> values(size);
    size_t numLayers = 0;

    for (unsigned i = 0; i < size; ++i) {
        values[i] = getPropertyCSSValue(shorthand.properties()[i]);
        if (!values[i]) {
			// We don't have all longhand properties defined as required for the shorthand
			// property and thus should not serialize to a shorthand value. See spec at
			// https://www.w3.org/TR/cssom-1/#serialize-a-css-declaration-block
			return String();
		}
		if (values[i]->isBaseValueList()) {
			CSSValueList* valueList = static_cast<CSSValueList*>(values[i].get());
			numLayers = std::max(valueList->length(), numLayers);
		} else
			numLayers = std::max<size_t>(1U, numLayers);
    }

    String commonValue;
    bool commonValueInitialized = false;

    // Now stitch the properties together.
    // Implicit initial values are flagged as such and can safely be omitted.
    for (size_t i = 0; i < numLayers; i++) {
        StringBuilder layerResult;
        bool useRepeatXShorthand = false;
        bool useRepeatYShorthand = false;
        bool useSingleWordShorthand = false;
        bool foundPositionYCSSProperty = false;
        for (unsigned j = 0; j < size; j++) {
            auto property = shorthand.properties()[j];

            RefPtr<CSSValue> value = values[j];
            if (value) {
                if (value->isBaseValueList())
                    value = toCSSValueList(*value).item(i);
                else {
                    // Color only belongs in the last layer.
                    if (property == CSSPropertyBackgroundColor) {
                        if (i != numLayers - 1)
                            value = nullptr;
                    } else if (i != 0) // Other singletons only belong in the first layer.
                        value = nullptr;
                }
            }

            // We need to report background-repeat as it was written in the CSS.
            // If the property is implicit, then it was written with only one value. Here we figure out which value that was so we can report back correctly.
            if (value && j < size - 1 && (property == CSSPropertyBackgroundRepeatX || property == CSSPropertyMaskRepeatX) && isPropertyImplicit(property)) {
                // Make sure the value was not reset in the layer check just above.
                auto nextProperty = shorthand.properties()[j + 1];
                if (nextProperty == CSSPropertyBackgroundRepeatY || nextProperty == CSSPropertyMaskRepeatY) {
                    if (RefPtr<CSSValue> yValue = values[j + 1]) {
                        if ((*yValue).isValueList())
                        	yValue = toCSSValueList(*yValue).item(i);
						if (!(*value).isPrimitiveValue() || !yValue || !(*yValue).isPrimitiveValue())
							continue;

						auto xId = toCSSPrimitiveValue(value.get())->getValueID();
						auto yId = toCSSPrimitiveValue(yValue.get())->getValueID();
						if (xId != yId) {
							if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) {
								useRepeatXShorthand = true;
								++j;
							} else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) {
								useRepeatYShorthand = true;
								continue;
							}
						} else {
							useSingleWordShorthand = true;
							++j;
						}
					}
				}
            }

            auto canOmitValue = [&]() {
                if (shorthand.id() == CSSPropertyMask) {
                    if (property == CSSPropertyMaskClip) {
                        // If the mask-clip value is the same as the value for mask-origin (the previous value),
                        // then we can skip serializing it, as one value sets both properties.
                        ASSERT(j > 0);
                        ASSERT(shorthand.properties()[j - 1] == CSSPropertyMaskOrigin);
                        auto originValue = values[j - 1];
                        if ((*originValue).isValueList())
                            originValue = toCSSValueList(*originValue).item(i);
                        if (!(*value).isPrimitiveValue() || (originValue && !(*originValue).isPrimitiveValue()))
                            return false;

                        auto maskId = toCSSPrimitiveValue(*value).valueID();
                        auto originId = toCSSPrimitiveValue(*originValue).valueID();
                        return maskId == originId && (!isCSSWideValueKeyword(getValueName(maskId)) || value->isImplicitInitialValue());
                    }
                    if (property == CSSPropertyMaskOrigin) {
                        // We can skip serializing mask-origin if it's the initial value, but only if we're also going to skip serializing
                        // the mask-clip as well (otherwise the single value for mask-clip would be assumed to be setting the value for both).
                        ASSERT(j + 1 < size);
                        ASSERT(shorthand.properties()[j + 1] == CSSPropertyMaskClip);
                        auto clipValue = values[j + 1];
                        if ((*clipValue).isValueList())
                            clipValue = toCSSValueList(*clipValue).item(i);
                        return value->isImplicitInitialValue() && (!clipValue || clipValue->isImplicitInitialValue());
					}
                }

                return value->isImplicitInitialValue();
            };

            String valueText;
            if (value && !canOmitValue()) {
                if (!layerResult.isEmpty())
                    layerResult.append(' ');

                if (property == CSSPropertyBackgroundSize || property == CSSPropertyMaskSize) {
                    if (!foundPositionYCSSProperty)
                        continue;
                    layerResult.appendLiteral("/ ");
                }

                if (useRepeatXShorthand) {
                    useRepeatXShorthand = false;
                    layerResult.appendLiteral(getValueName(CSSValueRepeatX));
                } else if (useRepeatYShorthand) {
                    useRepeatYShorthand = false;
                    layerResult.appendLiteral(getValueName(CSSValueRepeatY));
                } else if (shorthand.id() == CSSPropertyMask && property == CSSPropertyMaskOrigin && value->isImplicitInitialValue()) {
					// If we're about to write the value for mask-origin, but it's an implicit initial value that's just a placeholder
					// for a 'real' mask-clip value, then write the actual value not 'initial'.
					layerResult.appendLiteral(getValueName(CSSValueBorderBox));
				} else {
                	if (useSingleWordShorthand)
                		useSingleWordShorthand = false;
                	layerResult.append(value->cssText());
                }

                if (property == CSSPropertyBackgroundPositionY || property == CSSPropertyWebkitMaskPositionY)
                    foundPositionYCSSProperty = true;
            }

            if (commonValue.isNull())
                commonValue = valueText;
            else if (commonValue != valueText)
                commonValue = emptyString(); // Could use value here other than a CSS-wide value keyword or the null string.
        }

        if (shorthand.id() == CSSPropertyMask && layerResult.isEmpty())
            layerResult.appendLiteral(getValueName(CSSValueNone));

        if (!layerResult.isEmpty()) {
            result.append(result.isEmpty() ? "" : ", ");
            result.append(layerResult.toString());
        }
    }

    if (isCSSWideValueKeyword(commonValue))
        return commonValue;

    return result.isEmpty() ? String() : result.toString();
}

String StylePropertySet::getGridTemplateValue() const
{
    StringBuilder result;
    int areasIndex = findPropertyIndex(CSSPropertyGridTemplateAreas);
    if (areasIndex == -1)
        return String();
    auto rows = getPropertyCSSValue(CSSPropertyGridTemplateRows);
    if (!rows)
        return String();
    auto columns = getPropertyCSSValue(CSSPropertyGridTemplateColumns);
    if (!columns)
        return String();

    auto areas = propertyAt(areasIndex);
    if (!areas.value()->isGridTemplateAreasValue()) {
        String rowsText = rows->cssText();
        result.append(rowsText);

        String columnsText = columns->cssText();
        // If the values are identical, and either a css wide keyword
        // or 'none', then we can just output it once.
        if (columnsText != rowsText || (!isCSSWideValueKeyword(columnsText) && !isValueID(columns, CSSValueNone))) {
            result.append(" / ");
            result.append(columnsText);
        }
        return result.toString();
    }
    // We only want to try serializing the interleaved areas/templates
    // format if it was set from this shorthand, since that automatically
    // excludes values that can't be represented in this format (subgrid,
    // and the repeat() function).
    if (!areas.toCSSProperty().isSetFromShorthand())
        return String();

    ASSERT(is<CSSValueList>(rows));
    ASSERT(is<CSSGridTemplateAreasValue>(areas.value()));
    auto& areasValue = toCSSGridTemplateAreasValue(*areas.value());
    bool first = true;
    unsigned row = 0;
    for (auto& currentValue : toCSSValueList(*rows)) {
        if (!first)
            result.append(" ");
        first = false;

        if (currentValue->isGridLineNamesValue())
            result.append(currentValue->cssText());
        else {
            result.append("\"");
            result.append(areasValue.stringForRow(row));
            result.append("\"");

            if (!isValueID(currentValue, CSSValueAuto)) {
                result.append(" ");
                result.append(currentValue->cssText());
            }
            row++;
        }
    }

    String columnsText = columns->cssText();
    if (!isNoneValue(columns)) {
        result.append(" / ");
        result.append(columnsText);
    }

    return result.toString();
}

static bool gridAutoFlowContains(const RefPtr<CSSValue>& autoFlow, CSSValueID id)
{
    if (autoFlow && autoFlow->isValueList()) {
        for (auto& currentValue : toCSSValueList(*autoFlow)) {
            if (isValueID(currentValue, id))
                return true;
        }
        return false;
    }
    return isValueID(autoFlow, id);
}

String StylePropertySet::getGridValue() const
{
	auto autoColumns = getPropertyCSSValue(CSSPropertyGridAutoColumns);
	auto autoRows = getPropertyCSSValue(CSSPropertyGridAutoRows);
	auto autoFlow = getPropertyCSSValue(CSSPropertyGridAutoFlow);
	if (!autoColumns || !autoRows || !autoFlow)
		return String();

	if (isValueIDIncludingList(autoColumns, CSSValueAuto) && isValueIDIncludingList(autoRows, CSSValueAuto) && isValueIDIncludingList(autoFlow, CSSValueRow))
		return getGridTemplateValue();

	if (!isValueID(getPropertyCSSValue(CSSPropertyGridTemplateAreas), CSSValueNone))
		return String();

	auto rows = getPropertyCSSValue(CSSPropertyGridTemplateRows);
	auto columns = getPropertyCSSValue(CSSPropertyGridTemplateColumns);
	if (!rows || !columns)
		return String();

	StringBuilder result;

	if (gridAutoFlowContains(autoFlow, CSSValueColumn)) {
		if (!isValueIDIncludingList(autoRows, CSSValueAuto) || !isValueIDIncludingList(columns, CSSValueNone))
			return String();

		result.append(rows->cssText());
		result.append(" / auto-flow");
		if (gridAutoFlowContains(autoFlow, CSSValueDense))
			result.append(" dense");

		if (!isValueIDIncludingList(autoColumns, CSSValueAuto)) {
			result.append(" ");
			result.append(autoColumns->cssText());
		}

		return result.toString();
	}

    if (!gridAutoFlowContains(autoFlow, CSSValueRow) && !gridAutoFlowContains(autoFlow, CSSValueDense))
        return String();

    if (!isValueIDIncludingList(autoColumns, CSSValueAuto) || !isValueIDIncludingList(rows, CSSValueNone))
        return String();

    result.append("auto-flow");
    if (gridAutoFlowContains(autoFlow, CSSValueDense))
        result.append(" dense");

    if (!isValueIDIncludingList(autoRows, CSSValueAuto)) {
        result.append(" ");
        result.append(autoRows->cssText());
    }

    result.append(" / ");
    result.append(columns->cssText());

    return result.toString();
}

String StyleProperties::getGridShorthandValue(const StylePropertyShorthand& shorthand) const
{
    return getShorthandValue(shorthand, " / ");
}

String StylePropertySet::getShorthandValue(const StylePropertyShorthand& shorthand, const char* separator) const
{
    String commonValue;
    StringBuilder result;
    for (unsigned i = 0; i < shorthand.length(); ++i) {
        if (!isPropertyImplicit(shorthand.properties()[i])) {
            RefPtr<CSSValue> value = getPropertyCSSValue(shorthand.properties()[i]);
            if (!value)
                return String();
            String valueText = value->cssText();
            if (!i)
                commonValue = valueText;
            else if (!commonValue.isNull() && commonValue != valueText)
                commonValue = String();
            if (value->isInitialValue())
                continue;
            if (!result.isEmpty())
                result.append(separator);
            result.append(valueText);
        } else
            commonValue = String();
    }
    if (isCSSWideValueKeyword(commonValue))
        return commonValue;
    if (result.isEmpty())
        return String();
    return result.toString();
}

// Returns a non-null value if all properties have the same value.
String StylePropertySet::getCommonValue(const StylePropertyShorthand& shorthand) const
{
    String result;
    bool lastPropertyWasImportant = false;
    for (unsigned i = 0; i < shorthand.length(); ++i) {
        RefPtr<CSSValue> value = getPropertyCSSValue(shorthand.properties()[i]);
        if (!value)
            return String();
        // FIXME: CSSInitialValue::cssText should generate the right value.
        String text = value->cssText();
        if (text.isNull())
            return String();
        if (result.isNull())
        	result = text;
        else if (result != text)
            return String();
        bool currentPropertyIsImportant = propertyIsImportant(shorthand.properties()[i]);
        if (i && lastPropertyWasImportant != currentPropertyIsImportant)
            return String();
        lastPropertyWasImportant = currentPropertyIsImportant;
    }
    return result;
}

String StylePropertySet::getAlignmentShorthandValue(const StylePropertyShorthand& shorthand) const
{
    String value = getCommonValue(shorthand);
    if (value.isNull() || value.isEmpty())
        return getShorthandValue(shorthand);
    return value;
}

String StyleProperties::borderImagePropertyValue() const
{
    const StylePropertyShorthand& shorthand = borderImageShorthand();
    StringBuilder result;
    bool lastPropertyWasImportant = false;
    bool omittedSlice = false;
    bool omittedWidth = false;
    String commonWideValueText;
    const char* separator = "";
    for (unsigned i = 0; i < shorthand.length(); ++i) {
        // All longhands should have the same importance.
        auto longhand = shorthand.properties()[i];
        bool currentPropertyIsImportant = propertyIsImportant(longhand);
        if (i && lastPropertyWasImportant != currentPropertyIsImportant)
            return String();
        lastPropertyWasImportant = currentPropertyIsImportant;

        // All longhands should be present.
        auto value = getPropertyCSSValue(longhand);
        if (!value)
            return String();

        // Omit implicit initial values. However, border-image-width and border-image-outset require border-image-slice.
        if (value->isInitialValue() && isPropertyImplicit(longhand)) {
            if (longhand == CSSPropertyBorderImageSlice)
                omittedSlice = true;
            else if (longhand == CSSPropertyBorderImageWidth)
                omittedWidth = true;
            continue;
        }
        if (omittedSlice && (longhand == CSSPropertyBorderImageWidth || longhand == CSSPropertyBorderImageOutset))
            return String();

        // If a longhand is set to a css-wide keyword, the others should be the same.
        String valueText = value->cssText();
        if (isCSSWideValueKeyword(valueText)) {
            if (!i)
                commonWideValueText = valueText;
            else if (commonWideValueText != valueText)
                return String();
            continue;
        }
        if (!commonWideValueText.isNull())
            return String();

        // Append separator and text.
        if (longhand == CSSPropertyBorderImageWidth)
            separator = " / ";
        else if (longhand == CSSPropertyBorderImageOutset)
            separator = omittedWidth ? " / / " : " / ";
        result.append(separator);
        result.append(valueText);
        separator = " ";
    }
    if (!commonWideValueText.isNull())
        return commonWideValueText;
    return result.toString();
}

String StyleProperties::borderPropertyValue(const StylePropertyShorthand& width, const StylePropertyShorthand& style, const StylePropertyShorthand& color) const
{
    const StylePropertyShorthand properties[3] = { width, style, color };
    String commonValue;
    StringBuilder result;
    for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) {
        String value = getCommonValue(properties[i]);
        if (value.isNull())
            return String();
        if (!i)
            commonValue = value;
        else if (commonValue != value)
            commonValue = String();
        if (value == "initial")
            continue;
        if (!result.isEmpty())
            result.append(' ');
        result.append(value);
    }
    if (isCSSWideValueKeyword(commonValue))
        return commonValue;
    return result.toString();
}

String StyleProperties::pageBreakPropertyValue(const StylePropertyShorthand& shorthand) const
{
	RefPtr<CSSValue> value = getPropertyCSSValue(shorthand.properties()[0]);
    if (!value)
        return String();
    // FIXME: Remove this isCSSWideKeyword check after we do this consistently for all shorthands in getPropertyValue.
    if (value->isCSSWideKeyword())
        return value->cssText();

    if (!(*value).isPrimitiveValue())
        return String();

    CSSValueID valueId = toCSSPrimitiveValue(*value).valueID();
    switch (valueId) {
    case CSSValuePage:
        return String("always");
    case CSSValueAuto:
    case CSSValueAvoid:
    case CSSValueLeft:
    case CSSValueRight:
        return value->cssText();
    default:
        return String();
    }
}

PassRefPtr<CSSValue> StylePropertySet::getPropertyCSSValue(CSSPropertyID propertyID) const
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1)
        return nullptr;
    return propertyAt(foundPropertyIndex).value();
}

PassRefPtr<CSSValue> StylePropertySet::getCustomPropertyCSSValue(const String& propertyName) const
{
    int foundPropertyIndex = findCustomPropertyIndex(propertyName);
    if (foundPropertyIndex == -1)
        return nullptr;
    return propertyAt(foundPropertyIndex).value();
}

bool MutableStylePropertySet::removeShorthandProperty(CSSPropertyID propertyID)
{
    StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
    if (!shorthand.length())
        return false;

    return removePropertiesInSet(shorthand.properties(), shorthand.length());
}

bool MutableStylePropertySet::removeProperty(CSSPropertyID propertyID, String* returnText)
{
    if (removeShorthandProperty(propertyID)) {
        // FIXME: Return an equivalent shorthand when possible.
        if (returnText)
            *returnText = "";
        return true;
    }

    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1) {
        if (returnText)
            *returnText = "";
        return false;
    }

    if (returnText)
        *returnText = propertyAt(foundPropertyIndex).value()->cssText();

    // A more efficient removal strategy would involve marking entries as empty
    // and sweeping them when the vector grows too big.
    m_propertyVector.remove(foundPropertyIndex);

    return true;
}

bool MutableStylePropertySet::removeCustomProperty(const String& propertyName, String* returnText)
{
    int foundPropertyIndex = findCustomPropertyIndex(propertyName);
    if (foundPropertyIndex == -1) {
        if (returnText)
            *returnText = "";
        return false;
    }

    if (returnText)
        *returnText = propertyAt(foundPropertyIndex).value()->cssText();

    // A more efficient removal strategy would involve marking entries as empty
    // and sweeping them when the vector grows too big.
    m_propertyVector.remove(foundPropertyIndex);

    return true;
}

bool StylePropertySet::propertyIsImportant(CSSPropertyID propertyID) const
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex != -1)
        return propertyAt(foundPropertyIndex).isImportant();

    StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
    if (!shorthand.length())
        return false;

    for (unsigned i = 0; i < shorthand.length(); ++i) {
        if (!propertyIsImportant(shorthand.properties()[i]))
            return false;
    }
    return true;
}

bool StylePropertySet::customPropertyIsImportant(const String& propertyName) const
{
    int foundPropertyIndex = findCustomPropertyIndex(propertyName);
    if (foundPropertyIndex != -1)
        return propertyAt(foundPropertyIndex).isImportant();
    return false;
}

String StylePropertySet::getPropertyShorthand(CSSPropertyID propertyID) const
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1)
        return String();
    return getPropertyNameString(propertyAt(foundPropertyIndex).shorthandID());
}

bool StylePropertySet::isPropertyImplicit(CSSPropertyID propertyID) const
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1)
        return false;
    return propertyAt(foundPropertyIndex).isImplicit();
}

bool MutableStylePropertySet::setProperty(CSSPropertyID propertyID, const String& value, bool important, CSSParserContext parserContext, StyleSheetContents* contextStyleSheet)
{
    ASSERT(isMutable());

    if (!isEnabledCSSProperty(propertyID))
        return false;

    // Setting the value to an empty string just removes the property in both IE and Gecko.
    // Setting it to null seems to produce less consistent results, but we treat it just the same.
    if (value.isEmpty())
        return removeProperty(propertyID);

    parserContext.mode = cssParserMode();

    // When replacing an existing property value, this moves the property to the end of the list.
    // Firefox preserves the position, and MSIE moves the property to the beginning.
    return CSSParser::parseValue(this, propertyID, value, important, parserContext, contextStyleSheet) == CSSParser::ParseResult::Changed;
}

bool MutableStylePropertySet::setProperty(CSSPropertyID propertyID, const String& value, bool important)
{
    CSSParserContext parserContext(cssParserMode());
    return setProperty(propertyID, value, important, parserContext);
}

bool MutableStylePropertySet::setCustomProperty(const Document* document, const String& propertyName, const String& value, bool important, CSSParserContext parserContext)
{
    // Setting the value to an empty string just removes the property in both IE and Gecko.
    // Setting it to null seems to produce less consistent results, but we treat it just the same.
    if (value.isEmpty())
        return removeCustomProperty(propertyName);

    parserContext.mode = cssParserMode();

    String syntax = "*";
    CSSRegisteredCustomProperty* registered = document ? document->getCSSRegisteredCustomPropertySet().get(propertyName) : nullptr;

    if (registered)
        syntax = registered->syntax;

    CSSTokenizer tokenizer(value);
    if (!CSSPropertyParser::canParseTypedCustomPropertyValue(syntax, tokenizer.tokenRange(), parserContext))
        return false;

    // When replacing an existing property value, this moves the property to the end of the list.
    // Firefox preserves the position, and MSIE moves the property to the beginning.
    return CSSParser::parseCustomPropertyValue(this, propertyName, value, important, parserContext) == CSSParser::ParseResult::Changed;
}

void MutableStylePropertySet::setProperty(CSSPropertyID propertyID, PassRefPtr<CSSValue> prpValue, bool important)
{
    StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
    if (!shorthand.length()) {
        setProperty(CSSProperty(propertyID, prpValue, important));
        return;
    }

    removePropertiesInSet(shorthand.properties(), shorthand.length());

    RefPtr<CSSValue> value = prpValue;
    for (unsigned i = 0; i < shorthand.length(); ++i)
    	m_propertyVector.append(CSSProperty(shorthand.properties()[i], value, important));
}

bool MutableStylePropertySet::setProperty(const CSSProperty& property, CSSProperty* slot)
{
    if (!removeShorthandProperty(property.id())) {
        CSSProperty* toReplace = slot;
        if (!slot) {
            if (property.id() == CSSPropertyCustom) {
                if (property.value())
                    toReplace = findCustomCSSPropertyWithName(static_cast<CSSCustomPropertyValue&>(*property.value()).name());
            } else
                toReplace = findCSSPropertyWithID(property.id());
        }

        if (toReplace) {
            if (*toReplace == property)
                return false;
            *toReplace = property;
            return true;
        }
    }

    m_propertyVector.append(property);
    return true;
}

bool MutableStylePropertySet::setProperty(CSSPropertyID propertyID, CSSValueID identifier, bool important)
{
    return setProperty(CSSProperty(propertyID, CSSValuePool::singleton().createIdentifierValue(identifier), important));
}

bool MutableStylePropertySet::setProperty(CSSPropertyID propertyID, CSSPropertyID identifier, bool important)
{
    setProperty(CSSProperty(propertyID, CSSValuePool::singleton().createIdentifierValue(identifier), important));
    return true;
}

bool MutableStylePropertySet::parseDeclaration(const String& styleDeclaration, CSSParserContext context)
{
	Vector<CSSProperty, 4> oldProperties;
	oldProperties.swap(m_propertyVector);

	context.mode = cssParserMode();

    CSSParser parser(context);
    parser.parseDeclaration(this, styleDeclaration);

    // We could do better. Just changing property order does not require style invalidation.
    return oldProperties != m_propertyVector;
}

bool MutableStylePropertySet::addParsedProperties(const ParsedPropertyVector& properties)
{
    bool anyChanged = false;
    m_propertyVector.reserveCapacity(m_propertyVector.size() + properties.size());
    for (unsigned i = 0; i < properties.size(); ++i) {
        if(addParsedProperty(properties[i]))
        	anyChanged = true;
    }

    return anyChanged;
}

bool MutableStylePropertySet::addParsedProperty(const CSSProperty& property)
{
    if (property.id() == CSSPropertyCustom) {
        if ((property.value() && !customPropertyIsImportant(static_cast<CSSCustomPropertyValue&>(*property.value()).name())) || property.isImportant())
            return setProperty(property);
        return false;
    }
	return setProperty(property);
}

String StyleProperties::asText() const
{
    return asTextInternal().toString();
}

AtomString StyleProperties::asTextAtom() const
{
    return asTextInternal().toAtomicString();
}

StringBuilder StyleProperties::asTextInternal() const
{
    StringBuilder result;

    int positionXPropertyIndex = -1;
    int positionYPropertyIndex = -1;
    int repeatXPropertyIndex = -1;
    int repeatYPropertyIndex = -1;

    BitArray<numCSSProperties> shorthandPropertyUsed;
    BitArray<numCSSProperties> shorthandPropertyAppeared;

    unsigned size = propertyCount();
    unsigned numDecls = 0;
    for (unsigned n = 0; n < size; ++n) {
    	PropertyReference property = propertyAt(n);
        CSSPropertyID propertyID = property.id();
        Vector<CSSPropertyID> shorthands;

        if (property.value() && property.value()->isPendingSubstitutionValue()) {
            auto& substitutionValue = toCSSPendingSubstitutionValue(*property.value());
            shorthands.append(substitutionValue.shorthandPropertyId());
        } else {
			switch (propertyID) {
			case CSSPropertyAnimationName:
			case CSSPropertyAnimationDuration:
			case CSSPropertyAnimationTimingFunction:
			case CSSPropertyAnimationDelay:
			case CSSPropertyAnimationIterationCount:
			case CSSPropertyAnimationDirection:
			case CSSPropertyAnimationFillMode:
            case CSSPropertyAnimationPlayState:
            	shorthands.append(CSSPropertyAnimation);
				break;
			case CSSPropertyBackgroundPositionX:
				positionXPropertyIndex = n;
				continue;
			case CSSPropertyBackgroundPositionY:
				positionYPropertyIndex = n;
				continue;
			case CSSPropertyBackgroundRepeatX:
				repeatXPropertyIndex = n;
				continue;
			case CSSPropertyBackgroundRepeatY:
				repeatYPropertyIndex = n;
				continue;
			case CSSPropertyBorderTopWidth:
			case CSSPropertyBorderRightWidth:
			case CSSPropertyBorderBottomWidth:
			case CSSPropertyBorderLeftWidth:
				// FIXME: Deal with cases where only some of border sides are specified.
				shorthands.append(CSSPropertyBorder);
				shorthands.append(CSSPropertyBorderWidth);
				break;
			case CSSPropertyBorderTopStyle:
			case CSSPropertyBorderRightStyle:
			case CSSPropertyBorderBottomStyle:
			case CSSPropertyBorderLeftStyle:
                shorthands.append(CSSPropertyBorder);
                shorthands.append(CSSPropertyBorderStyle);
                break;
			case CSSPropertyBorderTopColor:
			case CSSPropertyBorderRightColor:
			case CSSPropertyBorderBottomColor:
			case CSSPropertyBorderLeftColor:
				shorthands.append(CSSPropertyBorder);
				shorthands.append(CSSPropertyBorderColor);
				break;
            case CSSPropertyBorderBlockStartWidth:
            case CSSPropertyBorderBlockEndWidth:
                shorthands.append(CSSPropertyBorderBlock);
                shorthands.append(CSSPropertyBorderBlockWidth);
                break;
            case CSSPropertyBorderBlockStartStyle:
            case CSSPropertyBorderBlockEndStyle:
            	shorthands.append(CSSPropertyBorderBlock);
				shorthands.append(CSSPropertyBorderBlockStyle);
				break;
            case CSSPropertyBorderBlockStartColor:
            case CSSPropertyBorderBlockEndColor:
            	shorthands.append(CSSPropertyBorderBlock);
				shorthands.append(CSSPropertyBorderBlockColor);
				break;
            case CSSPropertyBorderInlineStartWidth:
            case CSSPropertyBorderInlineEndWidth:
            	shorthands.append(CSSPropertyBorderInline);
				shorthands.append(CSSPropertyBorderInlineWidth);
				break;
            case CSSPropertyBorderInlineStartStyle:
            case CSSPropertyBorderInlineEndStyle:
                shorthands.append(CSSPropertyBorderInline);
                shorthands.append(CSSPropertyBorderInlineStyle);
                break;
            case CSSPropertyBorderInlineStartColor:
            case CSSPropertyBorderInlineEndColor:
            	shorthands.append(CSSPropertyBorderInline);
				shorthands.append(CSSPropertyBorderInlineColor);
				break;
            case CSSPropertyBorderTopLeftRadius:
            case CSSPropertyBorderTopRightRadius:
            case CSSPropertyBorderBottomRightRadius:
            case CSSPropertyBorderBottomLeftRadius:
            	shorthands.append(CSSPropertyBorderRadius);
                break;
			case CSSPropertyWebkitBorderHorizontalSpacing:
			case CSSPropertyWebkitBorderVerticalSpacing:
                shorthands.append(CSSPropertyBorderSpacing);
                break;
            case CSSPropertyBorderImageSource:
            case CSSPropertyBorderImageSlice:
            case CSSPropertyBorderImageWidth:
            case CSSPropertyBorderImageOutset:
            case CSSPropertyBorderImageRepeat:
            	shorthands.append(CSSPropertyBorderImage);
                break;
			case CSSPropertyFontFamily:
			case CSSPropertyLineHeight:
			case CSSPropertyFontSize:
			case CSSPropertyWebkitHorizontalFontSize:
			case CSSPropertyFontStyle:
			case CSSPropertyFontVariantCaps:
			case CSSPropertyFontWeight:
				// Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing.
				break;
            case CSSPropertyTop:
            case CSSPropertyRight:
            case CSSPropertyBottom:
            case CSSPropertyLeft:
            	shorthands.append(CSSPropertyInset);
                break;
            case CSSPropertyInsetBlockStart:
            case CSSPropertyInsetBlockEnd:
            	shorthands.append(CSSPropertyInsetBlock);
                break;
            case CSSPropertyInsetInlineStart:
            case CSSPropertyInsetInlineEnd:
            	shorthands.append(CSSPropertyInsetInline);
                break;
			case CSSPropertyListStyleType:
			case CSSPropertyListStylePosition:
			case CSSPropertyListStyleImage:
				shorthands.append(CSSPropertyListStyle);
				break;
			case CSSPropertyMarginTop:
			case CSSPropertyMarginRight:
			case CSSPropertyMarginBottom:
			case CSSPropertyMarginLeft:
				shorthands.append(CSSPropertyMargin);
				break;
            case CSSPropertyMarginBlockStart:
            case CSSPropertyMarginBlockEnd:
            	shorthands.append(CSSPropertyMarginBlock);
                break;
            case CSSPropertyMarginInlineStart:
            case CSSPropertyMarginInlineEnd:
            	shorthands.append(CSSPropertyMarginInline);
                break;
			case CSSPropertyOutlineWidth:
			case CSSPropertyOutlineStyle:
			case CSSPropertyOutlineColor:
				shorthands.append(CSSPropertyOutline);
				break;
			case CSSPropertyOverflowX:
			case CSSPropertyOverflowY:
				shorthands.append(CSSPropertyOverflow);
				break;
			case CSSPropertyPaddingTop:
			case CSSPropertyPaddingRight:
			case CSSPropertyPaddingBottom:
			case CSSPropertyPaddingLeft:
				shorthands.append(CSSPropertyPadding);
				break;
            case CSSPropertyPaddingBlockStart:
            case CSSPropertyPaddingBlockEnd:
            	shorthands.append(CSSPropertyPaddingBlock);
				break;
            case CSSPropertyPaddingInlineStart:
            case CSSPropertyPaddingInlineEnd:
            	shorthands.append(CSSPropertyPaddingInline);
                break;
			case CSSPropertyTransitionProperty:
			case CSSPropertyTransitionDuration:
			case CSSPropertyTransitionTimingFunction:
			case CSSPropertyTransitionDelay:
				shorthands.append(CSSPropertyTransition);
				break;
			case CSSPropertyFlexDirection:
			case CSSPropertyFlexWrap:
				shorthands.append(CSSPropertyFlexFlow);
				break;
			case CSSPropertyFlexBasis:
			case CSSPropertyFlexGrow:
			case CSSPropertyFlexShrink:
				shorthands.append(CSSPropertyFlex);
				break;
            case CSSPropertyWebkitMaskPositionX:
            case CSSPropertyWebkitMaskPositionY:
            case CSSPropertyMaskRepeatX:
            case CSSPropertyMaskRepeatY:
            case CSSPropertyMaskImage:
            case CSSPropertyMaskRepeat:
            case CSSPropertyMaskPosition:
            case CSSPropertyMaskClip:
            case CSSPropertyMaskOrigin:
            	shorthands.append(CSSPropertyMask);
                break;
            case CSSPropertyWebkitMaskClip:
            case CSSPropertyWebkitMaskPosition:
                // TODO: A lot of the above properties can be both prefixed and unprefixed?
            	shorthands.append(CSSPropertyWebkitMask);
				break;
	        case CSSPropertyPerspectiveOriginX:
	        case CSSPropertyPerspectiveOriginY:
	        	shorthands.append(CSSPropertyPerspectiveOrigin);
	            break;
			case CSSPropertyTransformOriginX:
			case CSSPropertyTransformOriginY:
			case CSSPropertyTransformOriginZ:
				shorthands.append(CSSPropertyTransformOrigin);
				break;
            case CSSPropertyGridTemplateRows:
            case CSSPropertyGridTemplateColumns:
            case CSSPropertyGridTemplateAreas:
                shorthands.append(CSSPropertyGrid);
                shorthands.append(CSSPropertyGridTemplate);
                break;
            case CSSPropertyGridAutoFlow:
            case CSSPropertyGridAutoRows:
            case CSSPropertyGridAutoColumns:
                shorthands.append(CSSPropertyGrid);
                break;
			default:
				break;
			}
        }

        String value;
        bool alreadyUsedShorthand = false;
		for (auto& shorthandPropertyID : shorthands) {
			unsigned shorthandPropertyIndex = shorthandPropertyID - firstCSSProperty;

			ASSERT(shorthandPropertyIndex < shorthandPropertyUsed.size());
			if (shorthandPropertyUsed[shorthandPropertyIndex]) {
				alreadyUsedShorthand = true;
				break;
			}
			if (shorthandPropertyAppeared[shorthandPropertyIndex])
				continue;
			shorthandPropertyAppeared.set(shorthandPropertyIndex);

			value = getPropertyValue(shorthandPropertyID);
			if (!value.isNull()) {
				propertyID = shorthandPropertyID;
				shorthandPropertyUsed.set(shorthandPropertyIndex);
				break;
			}
		}
		if (alreadyUsedShorthand)
			continue;

		if (value.isNull())
			value = property.value()->cssText();

        if (propertyID != CSSPropertyCustom && value == "initial" && !CSSProperty::isInheritedProperty(propertyID))
            continue;

        if (numDecls++)
            result.append(' ');

        if (propertyID == CSSPropertyCustom)
            result.append(static_cast<CSSCustomPropertyValue&>(*property.value()).name());
        else
        	result.appendLiteral(getPropertyName(propertyID));

        result.appendLiteral(": ");
        result.append(value);
		result.append(property.isImportant() ? " !important" : "");
        result.append(';');
    }

    // FIXME: This is a not-so-nice way to turn x/y positions into single background-position/repeat in output.
    // In 2007 we decided this was required because background-position/repeat-x/y are non-standard properties and WebKit generated output would not work in Firefox (<rdar://problem/5143183>).
    auto appendPositionOrProperty = [&] (int xIndex, int yIndex, const char* name, const StylePropertyShorthand& shorthand) {
        if (xIndex != -1 && yIndex != -1 && propertyAt(xIndex).isImportant() == propertyAt(yIndex).isImportant()) {
            String value;
            auto xProperty = propertyAt(xIndex);
            auto yProperty = propertyAt(yIndex);
            if (xProperty.value()->isValueList() || yProperty.value()->isValueList())
                value = getLayeredShorthandValue(shorthand);
            else {
                auto x = xProperty.value()->cssText();
                auto y = yProperty.value()->cssText();
                if (x == y && isCSSWideValueKeyword(x))
                    value = x;
                else
                    value = makeString(x, ' ', y);
            }
            if (value != "initial") {
                result.append(numDecls ? " " : "");
                result.append(name);
                result.append(": ");
                result.append(value);
                result.append(xProperty.isImportant() ? " !important" : "");
                result.append(';');
                ++numDecls;
            }
        } else {
            if (xIndex != -1) {
                if (numDecls++)
                    result.append(' ');
                result.append(propertyAt(xIndex).cssText());
            }
            if (yIndex != -1) {
                if (numDecls++)
                    result.append(' ');
                result.append(propertyAt(yIndex).cssText());
            }
        }
    };

    appendPositionOrProperty(positionXPropertyIndex, positionYPropertyIndex, "background-position", backgroundPositionShorthand());
    appendPositionOrProperty(repeatXPropertyIndex, repeatYPropertyIndex, "background-repeat", backgroundRepeatShorthand());

    ASSERT(!numDecls ^ !result.isEmpty());
    return result;
}

void StylePropertySet::addSubresourceStyleURLs(ListHashSet<KURL>& urls) const
{
    class TraverseSubresourcesFunctor
    {
    public:
    	TraverseSubresourcesFunctor(ListHashSet<KURL>& urls) : urls(urls) { }
    	bool operator()(const CachedResource& resource) const {
            urls.add(resource.url());
            return false;
        }
    private:
    	ListHashSet<KURL>& urls;
    };
    TraverseSubresourcesFunctor functor(urls);
    traverseSubresources(functor);
}

bool StylePropertySet::hasCSSOMWrapper() const
{
    return m_isMutable && static_cast<const MutableStylePropertySet*>(this)->m_cssomWrapper;
}

void MutableStylePropertySet::mergeAndOverrideOnConflict(const StylePropertySet& other)
{
    unsigned size = other.propertyCount();
    for (unsigned i = 0; i < size; ++i)
        addParsedProperty(other.propertyAt(i).toCSSProperty());
}

// This is the list of properties we want to copy in the copyBlockProperties() function.
// It is the list of CSS properties that apply specially to block-level elements.
static const CSSPropertyID blockProperties[] = {
    CSSPropertyOrphans,
    CSSPropertyOverflow, // This can be also be applied to replaced elements
    CSSPropertyColumnCount,
    CSSPropertyColumnGap,
    CSSPropertyRowGap,
    CSSPropertyColumnRuleColor,
    CSSPropertyColumnRuleStyle,
    CSSPropertyColumnRuleWidth,
    CSSPropertyWebkitColumnBreakBefore,
    CSSPropertyWebkitColumnBreakAfter,
    CSSPropertyWebkitColumnBreakInside,
    CSSPropertyColumnWidth,
    CSSPropertyPageBreakAfter,
    CSSPropertyPageBreakBefore,
    CSSPropertyPageBreakInside,
    CSSPropertyTextAlign,
    CSSPropertyTextAlignLast,
    CSSPropertyWebkitTextJustify,
    CSSPropertyTextIndent,
    CSSPropertyWidows
};

void MutableStylePropertySet::clear()
{
	m_propertyVector.clear();
}

const unsigned numBlockProperties = WTF_ARRAY_LENGTH(blockProperties);

PassRefPtr<MutableStylePropertySet> StylePropertySet::copyBlockProperties() const
{
    return copyPropertiesInSet(blockProperties, numBlockProperties);
}

void MutableStylePropertySet::removeBlockProperties()
{
    removePropertiesInSet(blockProperties, numBlockProperties);
}

bool MutableStylePropertySet::removePropertiesInSet(const CSSPropertyID* set, unsigned length)
{
    if (m_propertyVector.isEmpty())
        return false;

    // FIXME: This is always used with static sets and in that case constructing the hash repeatedly is pretty pointless.
    HashSet<CSSPropertyID> toRemove;
    for (unsigned i = 0; i < length; ++i)
        toRemove.add(set[i]);

    class Functor
    {
    public:
    	Functor(HashSet<CSSPropertyID>& toRemove) : toRemove(toRemove) { }
    	bool operator()(const CSSProperty& property) const {
            return toRemove.contains(property.id());
        }
    private:
    	HashSet<CSSPropertyID>& toRemove;
    };
    Functor functor(toRemove);
    return m_propertyVector.removeAllMatching(functor) > 0;
}

int ImmutableStylePropertySet::findPropertyIndex(CSSPropertyID propertyID) const
{
    // Convert here propertyID into an uint16_t to compare it with the metadata's m_propertyID to avoid
    // the compiler converting it to an int multiple times in the loop.
    uint16_t id = static_cast<uint16_t>(propertyID);
    for (int n = m_arraySize - 1 ; n >= 0; --n) {
        if (metadataArray()[n].m_propertyID == id)
            return n;
    }

    return -1;
}

int MutableStylePropertySet::findPropertyIndex(CSSPropertyID propertyID) const
{
    // Convert here propertyID into an uint16_t to compare it with the metadata's m_propertyID to avoid
    // the compiler converting it to an int multiple times in the loop.
    uint16_t id = static_cast<uint16_t>(propertyID);
    for (int n = m_propertyVector.size() - 1 ; n >= 0; --n) {
        if (m_propertyVector.at(n).metadata().m_propertyID == id)
            return n;
    }

    return -1;
}

int ImmutableStylePropertySet::findCustomPropertyIndex(const String& propertyName) const
{
    for (int n = m_arraySize - 1 ; n >= 0; --n) {
        if (metadataArray()[n].m_propertyID == CSSPropertyCustom) {
            // We found a custom property. See if the name matches.
            if (!valueArray()[n])
                continue;
            if (static_cast<const CSSCustomPropertyValue&>(*valueArray()[n]).name() == propertyName)
                return n;
        }
    }

    return -1;
}

int MutableStylePropertySet::findCustomPropertyIndex(const String& propertyName) const
{
    for (int n = m_propertyVector.size() - 1 ; n >= 0; --n) {
        if (m_propertyVector.at(n).metadata().m_propertyID == CSSPropertyCustom) {
            // We found a custom property. See if the name matches.
            if (!m_propertyVector.at(n).value())
                continue;
            if (static_cast<CSSCustomPropertyValue&>(*m_propertyVector.at(n).value()).name() == propertyName)
                return n;
        }
    }

    return -1;
}

CSSProperty* MutableStylePropertySet::findCSSPropertyWithID(CSSPropertyID propertyID)
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1)
        return 0;
    return &m_propertyVector.at(foundPropertyIndex);
}

CSSProperty* MutableStylePropertySet::findCustomCSSPropertyWithName(const String& propertyName)
{
    int foundPropertyIndex = findCustomPropertyIndex(propertyName);
    if (foundPropertyIndex == -1)
        return 0;
    return &m_propertyVector.at(foundPropertyIndex);
}

bool StylePropertySet::propertyMatches(CSSPropertyID propertyID, const CSSValue* propertyValue) const
{
    int foundPropertyIndex = findPropertyIndex(propertyID);
    if (foundPropertyIndex == -1)
        return false;
    return propertyAt(foundPropertyIndex).value()->equals(*propertyValue);
}
    
void MutableStylePropertySet::removeEquivalentProperties(const StylePropertySet* style)
{
    Vector<CSSPropertyID> propertiesToRemove;
    unsigned size = m_propertyVector.size();
    for (unsigned i = 0; i < size; ++i) {
        PropertyReference property = propertyAt(i);
        if (style->propertyMatches(property.id(), property.value()))
            propertiesToRemove.append(property.id());
    }    
    // FIXME: This should use mass removal.
    for (unsigned i = 0; i < propertiesToRemove.size(); ++i)
        removeProperty(propertiesToRemove[i]);
}

void MutableStylePropertySet::removeEquivalentProperties(ComputedStyleExtractor* computedStyle)
{
    Vector<CSSPropertyID> propertiesToRemove;
    unsigned size = m_propertyVector.size();
    for (unsigned i = 0; i < size; ++i) {
        PropertyReference property = propertyAt(i);
        if (computedStyle->propertyMatches(property.id(), property.value()))
            propertiesToRemove.append(property.id());
    }    
    // FIXME: This should use mass removal.
    for (unsigned i = 0; i < propertiesToRemove.size(); ++i)
        removeProperty(propertiesToRemove[i]);
}

Ref<MutableStylePropertySet> StylePropertySet::mutableCopy() const
{
    return adoptRef(*new MutableStylePropertySet(*this));
}

PassRefPtr<MutableStylePropertySet> StylePropertySet::copyPropertiesInSet(const CSSPropertyID* set, unsigned length) const
{
    Vector<CSSProperty> list;
    list.reserveInitialCapacity(length);
    for (unsigned i = 0; i < length; ++i) {
        if (RefPtr<CSSValue> value = getPropertyCSSValue(set[i]))
            list.uncheckedAppend(CSSProperty(set[i], value.release(), false));
    }
    list.shrinkToFit();
    return MutableStylePropertySet::create(WTFMove(list));
}

PropertySetCSSStyleDeclaration* MutableStylePropertySet::cssStyleDeclaration()
{
    return m_cssomWrapper.get();
}

CSSStyleDeclaration& MutableStylePropertySet::ensureCSSStyleDeclaration()
{
    if (m_cssomWrapper) {
        ASSERT(!static_cast<CSSStyleDeclaration*>(m_cssomWrapper.get())->parentRule());
        ASSERT(!m_cssomWrapper->parentElement());
        return *m_cssomWrapper;
    }
    m_cssomWrapper = adoptPtr(new PropertySetCSSStyleDeclaration(*this));
    return *m_cssomWrapper;
}

CSSStyleDeclaration& MutableStylePropertySet::ensureInlineCSSStyleDeclaration(StyledElement& parentElement)
{
    if (m_cssomWrapper) {
        ASSERT(m_cssomWrapper->parentElement() == parentElement);
        return *m_cssomWrapper;
    }
    m_cssomWrapper = adoptPtr(new InlineCSSStyleDeclaration(*this, parentElement));
    return *m_cssomWrapper;
}

unsigned StylePropertySet::averageSizeInBytes()
{
    // Please update this if the storage scheme changes so that this longer reflects the actual size.
	return sizeForImmutableStylePropertySetWithPropertyCount(2);
}

// See the function above if you need to update this.
struct SameSizeAsStylePropertySet : public RefCounted<SameSizeAsStylePropertySet> {
    unsigned bitfield;
};
COMPILE_ASSERT(sizeof(StylePropertySet) == sizeof(SameSizeAsStylePropertySet), style_property_set_should_stay_small);

#ifndef NDEBUG
void StylePropertySet::showStyle()
{
    fprintf(stderr, "%s\n", asText().ascii().data());
}
#endif

Ref<MutableStylePropertySet> MutableStylePropertySet::create(CSSParserMode cssParserMode)
{
    return adoptRef(*new MutableStylePropertySet(cssParserMode));
}

Ref<MutableStylePropertySet> MutableStylePropertySet::create(Vector<CSSProperty>&& properties)
{
    return adoptRef(*new MutableStylePropertySet(WTFMove(properties)));
}

String StylePropertySet::PropertyReference::cssName() const
{
    if (id() == CSSPropertyCustom)
        return static_cast<const CSSCustomPropertyValue&>(*value()).name();
    return getPropertyName(id());
}

String StylePropertySet::PropertyReference::cssText() const
{
    StringBuilder result;
    result.append(cssName());
    result.appendLiteral(": ");
    result.append(m_value->cssText());
    result.append(isImportant() ? " !important" : "");
    result.append(';');
    return result.toString();
}

} // namespace WebCore
