blob: d06986cc643bb905a0cbc1b22e35e524a118b85a [file] [log] [blame]
/*
* (C) 1999-2003 Lars Knoll (knoll@kde.org)
* Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2012, 2013 Apple Inc. All rights reserved.
* Copyright (C) 2011 Research In Motion Limited. All rights reserved.
* Copyright (C) 2013 Intel Corporation. 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 "CSSParser.h"
#include "CSSValueKeywords.h"
#include "CSSValueList.h"
#include "CSSValuePool.h"
#include "Document.h"
#include "PropertySetCSSStyleDeclaration.h"
#include "StylePropertyShorthand.h"
#include "StyleSheetContents.h"
#include <wtf/BitArray.h>
#include <wtf/text/StringBuilder.h>
#if ENABLE(CSS_VARIABLES)
#include "CSSVariableValue.h"
#endif
#ifndef NDEBUG
#include <stdio.h>
#include <wtf/ASCIICType.h>
#include <wtf/text/CString.h>
#endif
namespace WebCore {
static size_t sizeForImmutableStylePropertySetWithPropertyCount(unsigned count)
{
return sizeof(ImmutableStylePropertySet) - sizeof(void*) + sizeof(CSSValue*) * count + sizeof(StylePropertyMetadata) * count;
}
static bool isInitialOrInherit(const String& value)
{
DEFINE_STATIC_LOCAL(String, initial, ("initial"));
DEFINE_STATIC_LOCAL(String, inherit, ("inherit"));
return value.length() == 7 && (value == initial || value == inherit);
}
PassRef<ImmutableStylePropertySet> ImmutableStylePropertySet::create(const CSSProperty* properties, unsigned count, CSSParserMode cssParserMode)
{
void* slot = WTF::fastMalloc(sizeForImmutableStylePropertySetWithPropertyCount(count));
return adoptRef(*new (NotNull, slot) ImmutableStylePropertySet(properties, count, cssParserMode));
}
PassRef<ImmutableStylePropertySet> StylePropertySet::immutableCopyIfNeeded() const
{
if (!isMutable())
return static_cast<ImmutableStylePropertySet&>(const_cast<StylePropertySet&>(*this));
const MutableStylePropertySet& mutableThis = static_cast<const MutableStylePropertySet&>(*this);
return ImmutableStylePropertySet::create(mutableThis.m_propertyVector.data(), mutableThis.m_propertyVector.size(), cssParserMode());
}
MutableStylePropertySet::MutableStylePropertySet(CSSParserMode cssParserMode)
: StylePropertySet(cssParserMode)
{
}
MutableStylePropertySet::MutableStylePropertySet(const CSSProperty* properties, unsigned length)
: StylePropertySet(CSSStrictMode)
{
m_propertyVector.reserveInitialCapacity(length);
for (unsigned i = 0; i < length; ++i)
m_propertyVector.uncheckedAppend(properties[i]);
}
MutableStylePropertySet::~MutableStylePropertySet()
{
}
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 {
m_propertyVector.reserveInitialCapacity(other.propertyCount());
for (unsigned i = 0; i < other.propertyCount(); ++i)
m_propertyVector.uncheckedAppend(other.propertyAt(i).toCSSProperty());
}
}
String StylePropertySet::getPropertyValue(CSSPropertyID propertyID) const
{
RefPtr<CSSValue> value = getPropertyCSSValue(propertyID);
if (value)
return value->cssText();
// Shorthand and 4-values properties
switch (propertyID) {
case CSSPropertyBorderSpacing:
return borderSpacingValue(borderSpacingShorthand());
case CSSPropertyBackgroundPosition:
return getLayeredShorthandValue(backgroundPositionShorthand());
case CSSPropertyBackgroundRepeat:
return getLayeredShorthandValue(backgroundRepeatShorthand());
case CSSPropertyBackground:
return getLayeredShorthandValue(backgroundShorthand());
case CSSPropertyBorder:
return borderPropertyValue(OmitUncommonValues);
case CSSPropertyBorderTop:
return getShorthandValue(borderTopShorthand());
case CSSPropertyBorderRight:
return getShorthandValue(borderRightShorthand());
case CSSPropertyBorderBottom:
return getShorthandValue(borderBottomShorthand());
case CSSPropertyBorderLeft:
return getShorthandValue(borderLeftShorthand());
case CSSPropertyOutline:
return getShorthandValue(outlineShorthand());
case CSSPropertyBorderColor:
return get4Values(borderColorShorthand());
case CSSPropertyBorderWidth:
return get4Values(borderWidthShorthand());
case CSSPropertyBorderStyle:
return get4Values(borderStyleShorthand());
case CSSPropertyWebkitColumnRule:
return getShorthandValue(webkitColumnRuleShorthand());
case CSSPropertyWebkitColumns:
return getShorthandValue(webkitColumnsShorthand());
case CSSPropertyWebkitFlex:
return getShorthandValue(webkitFlexShorthand());
case CSSPropertyWebkitFlexFlow:
return getShorthandValue(webkitFlexFlowShorthand());
case CSSPropertyWebkitGridArea:
return getShorthandValue(webkitGridAreaShorthand());
case CSSPropertyWebkitGridColumn:
return getShorthandValue(webkitGridColumnShorthand());
case CSSPropertyWebkitGridRow:
return getShorthandValue(webkitGridRowShorthand());
case CSSPropertyFont:
return fontValue();
case CSSPropertyMargin:
return get4Values(marginShorthand());
case CSSPropertyWebkitMarginCollapse:
return getShorthandValue(webkitMarginCollapseShorthand());
case CSSPropertyOverflow:
return getCommonValue(overflowShorthand());
case CSSPropertyPadding:
return get4Values(paddingShorthand());
case CSSPropertyTransition:
return getLayeredShorthandValue(transitionShorthand());
case CSSPropertyListStyle:
return getShorthandValue(listStyleShorthand());
case CSSPropertyWebkitMarquee:
return getShorthandValue(webkitMarqueeShorthand());
case CSSPropertyWebkitMaskPosition:
return getLayeredShorthandValue(webkitMaskPositionShorthand());
case CSSPropertyWebkitMaskRepeat:
return getLayeredShorthandValue(webkitMaskRepeatShorthand());
case CSSPropertyWebkitMask:
return getLayeredShorthandValue(webkitMaskShorthand());
case CSSPropertyWebkitTextEmphasis:
return getShorthandValue(webkitTextEmphasisShorthand());
case CSSPropertyWebkitTextStroke:
return getShorthandValue(webkitTextStrokeShorthand());
case CSSPropertyWebkitTransformOrigin:
return getShorthandValue(webkitTransformOriginShorthand());
case CSSPropertyWebkitTransition:
return getLayeredShorthandValue(webkitTransitionShorthand());
case CSSPropertyWebkitAnimation:
return getLayeredShorthandValue(webkitAnimationShorthand());
#if ENABLE(SVG)
case CSSPropertyMarker: {
RefPtr<CSSValue> value = getPropertyCSSValue(CSSPropertyMarkerStart);
if (value)
return value->cssText();
return String();
}
#endif
case CSSPropertyBorderRadius:
return get4Values(borderRadiusShorthand());
default:
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;
}
char prefix = '\0';
switch (propertyID) {
case CSSPropertyFontStyle:
break; // No prefix.
case CSSPropertyFontFamily:
case CSSPropertyFontVariant:
case CSSPropertyFontWeight:
prefix = ' ';
break;
case CSSPropertyLineHeight:
prefix = '/';
break;
default:
ASSERT_NOT_REACHED();
}
if (prefix && !result.isEmpty())
result.append(prefix);
String value = propertyAt(foundPropertyIndex).value()->cssText();
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(CSSPropertyFontVariant, result, commonValue);
appendFontLonghandValueIfExplicit(CSSPropertyFontWeight, 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 (isInitialOrInherit(commonValue))
return commonValue;
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();
// Begin by collecting the properties into an array.
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]) {
if (values[i]->isBaseValueList())
numLayers = std::max(toCSSValueList(values[i].get())->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++) {
RefPtr<CSSValue> value;
if (values[j]) {
if (values[j]->isBaseValueList())
value = toCSSValueList(values[j].get())->item(i);
else {
value = values[j];
// Color only belongs in the last layer.
if (shorthand.properties()[j] == CSSPropertyBackgroundColor) {
if (i != numLayers - 1)
value = 0;
} else if (i != 0) // Other singletons only belong in the first layer.
value = 0;
}
}
// 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 ((shorthand.properties()[j] == CSSPropertyBackgroundRepeatX && isPropertyImplicit(shorthand.properties()[j]))
|| (shorthand.properties()[j] == CSSPropertyWebkitMaskRepeatX && isPropertyImplicit(shorthand.properties()[j]))) {
// BUG 49055: make sure the value was not reset in the layer check just above.
if ((j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyBackgroundRepeatY && value)
|| (j < size - 1 && shorthand.properties()[j + 1] == CSSPropertyWebkitMaskRepeatY && value)) {
RefPtr<CSSValue> yValue;
RefPtr<CSSValue> nextValue = values[j + 1];
if (nextValue->isValueList())
yValue = toCSSValueList(nextValue.get())->itemWithoutBoundsCheck(i);
else
yValue = nextValue;
CSSValueID xId = toCSSPrimitiveValue(value.get())->getValueID();
CSSValueID 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;
}
}
}
String valueText;
if (value && !value->isImplicitInitialValue()) {
if (!layerResult.isEmpty())
layerResult.append(' ');
if (foundPositionYCSSProperty
&& (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
layerResult.appendLiteral("/ ");
if (!foundPositionYCSSProperty
&& (shorthand.properties()[j] == CSSPropertyBackgroundSize || shorthand.properties()[j] == CSSPropertyWebkitMaskSize))
continue;
if (useRepeatXShorthand) {
useRepeatXShorthand = false;
layerResult.append(getValueName(CSSValueRepeatX));
} else if (useRepeatYShorthand) {
useRepeatYShorthand = false;
layerResult.append(getValueName(CSSValueRepeatY));
} else {
if (useSingleWordShorthand)
useSingleWordShorthand = false;
valueText = value->cssText();
layerResult.append(valueText);
}
if (shorthand.properties()[j] == CSSPropertyBackgroundPositionY
|| shorthand.properties()[j] == CSSPropertyWebkitMaskPositionY) {
foundPositionYCSSProperty = true;
// background-position is a special case: if only the first offset is specified,
// the second one defaults to "center", not the same value.
if (commonValueInitialized && commonValue != "initial" && commonValue != "inherit")
commonValue = String();
}
}
if (!commonValueInitialized) {
commonValue = valueText;
commonValueInitialized = true;
} else if (!commonValue.isNull() && commonValue != valueText)
commonValue = String();
}
if (!layerResult.isEmpty()) {
if (!result.isEmpty())
result.appendLiteral(", ");
result.append(layerResult);
}
}
if (isInitialOrInherit(commonValue))
return commonValue;
if (result.isEmpty())
return String();
return result.toString();
}
String StylePropertySet::getShorthandValue(const StylePropertyShorthand& shorthand) 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(' ');
result.append(valueText);
} else
commonValue = String();
}
if (isInitialOrInherit(commonValue))
return commonValue;
if (result.isEmpty())
return String();
return result.toString();
}
// only returns a non-null value if all properties have the same, non-null value
String StylePropertySet::getCommonValue(const StylePropertyShorthand& shorthand) const
{
String res;
bool lastPropertyWasImportant = false;
for (unsigned i = 0; i < shorthand.length(); ++i) {
RefPtr<CSSValue> value = getPropertyCSSValue(shorthand.properties()[i]);
// FIXME: CSSInitialValue::cssText should generate the right value.
if (!value)
return String();
String text = value->cssText();
if (text.isNull())
return String();
if (res.isNull())
res = text;
else if (res != text)
return String();
bool currentPropertyIsImportant = propertyIsImportant(shorthand.properties()[i]);
if (i && lastPropertyWasImportant != currentPropertyIsImportant)
return String();
lastPropertyWasImportant = currentPropertyIsImportant;
}
return res;
}
String StylePropertySet::borderPropertyValue(CommonValueMode valueMode) const
{
const StylePropertyShorthand properties[3] = { borderWidthShorthand(), borderStyleShorthand(), borderColorShorthand() };
String commonValue;
StringBuilder result;
for (size_t i = 0; i < WTF_ARRAY_LENGTH(properties); ++i) {
String value = getCommonValue(properties[i]);
if (value.isNull()) {
if (valueMode == ReturnNullOnUncommonValues)
return String();
ASSERT(valueMode == OmitUncommonValues);
continue;
}
if (!i)
commonValue = value;
else if (!commonValue.isNull() && commonValue != value)
commonValue = String();
if (value == "initial")
continue;
if (!result.isEmpty())
result.append(' ');
result.append(value);
}
if (isInitialOrInherit(commonValue))
return commonValue;
return result.isEmpty() ? String() : result.toString();
}
PassRefPtr<CSSValue> StylePropertySet::getPropertyCSSValue(CSSPropertyID propertyID) const
{
int foundPropertyIndex = findPropertyIndex(propertyID);
if (foundPropertyIndex == -1)
return 0;
return propertyAt(foundPropertyIndex).value();
}
bool MutableStylePropertySet::removeShorthandProperty(CSSPropertyID propertyID)
{
StylePropertyShorthand shorthand = shorthandForProperty(propertyID);
if (!shorthand.length())
return false;
bool ret = removePropertiesInSet(shorthand.properties(), shorthand.length());
CSSPropertyID prefixingVariant = prefixingVariantForPropertyId(propertyID);
if (prefixingVariant == propertyID)
return ret;
StylePropertyShorthand shorthandPrefixingVariant = shorthandForProperty(prefixingVariant);
return removePropertiesInSet(shorthandPrefixingVariant.properties(), shorthandPrefixingVariant.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);
removePrefixedOrUnprefixedProperty(propertyID);
return true;
}
void MutableStylePropertySet::removePrefixedOrUnprefixedProperty(CSSPropertyID propertyID)
{
int foundPropertyIndex = findPropertyIndex(prefixingVariantForPropertyId(propertyID));
if (foundPropertyIndex == -1)
return;
m_propertyVector.remove(foundPropertyIndex);
}
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;
}
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, StyleSheetContents* contextStyleSheet)
{
// 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);
// 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, cssParserMode(), contextStyleSheet);
}
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));
}
void MutableStylePropertySet::setProperty(const CSSProperty& property, CSSProperty* slot)
{
if (!removeShorthandProperty(property.id())) {
CSSProperty* toReplace = slot ? slot : findCSSPropertyWithID(property.id());
if (toReplace) {
*toReplace = property;
setPrefixingVariantProperty(property);
return;
}
}
appendPrefixingVariantProperty(property);
}
static unsigned getIndexInShorthandVectorForPrefixingVariant(const CSSProperty& property, CSSPropertyID prefixingVariant)
{
if (!property.isSetFromShorthand())
return 0;
CSSPropertyID prefixedShorthand = prefixingVariantForPropertyId(property.shorthandID());
return indexOfShorthandForLonghand(prefixedShorthand, matchingShorthandsForLonghand(prefixingVariant));
}
void MutableStylePropertySet::appendPrefixingVariantProperty(const CSSProperty& property)
{
m_propertyVector.append(property);
CSSPropertyID prefixingVariant = prefixingVariantForPropertyId(property.id());
if (prefixingVariant == property.id())
return;
m_propertyVector.append(CSSProperty(prefixingVariant, property.value(), property.isImportant(), property.isSetFromShorthand(), getIndexInShorthandVectorForPrefixingVariant(property, prefixingVariant), property.metadata().m_implicit));
}
void MutableStylePropertySet::setPrefixingVariantProperty(const CSSProperty& property)
{
CSSPropertyID prefixingVariant = prefixingVariantForPropertyId(property.id());
CSSProperty* toReplace = findCSSPropertyWithID(prefixingVariant);
if (toReplace && prefixingVariant != property.id())
*toReplace = CSSProperty(prefixingVariant, property.value(), property.isImportant(), property.isSetFromShorthand(), getIndexInShorthandVectorForPrefixingVariant(property, prefixingVariant), property.metadata().m_implicit);
}
bool MutableStylePropertySet::setProperty(CSSPropertyID propertyID, CSSValueID identifier, bool important)
{
setProperty(CSSProperty(propertyID, cssValuePool().createIdentifierValue(identifier), important));
return true;
}
bool MutableStylePropertySet::setProperty(CSSPropertyID propertyID, CSSPropertyID identifier, bool important)
{
setProperty(CSSProperty(propertyID, cssValuePool().createIdentifierValue(identifier), important));
return true;
}
void MutableStylePropertySet::parseDeclaration(const String& styleDeclaration, StyleSheetContents* contextStyleSheet)
{
m_propertyVector.clear();
CSSParserContext context(cssParserMode());
if (contextStyleSheet) {
context = contextStyleSheet->parserContext();
context.mode = cssParserMode();
}
CSSParser parser(context);
parser.parseDeclaration(this, styleDeclaration, 0, contextStyleSheet);
}
void MutableStylePropertySet::addParsedProperties(const Vector<CSSProperty>& properties)
{
m_propertyVector.reserveCapacity(m_propertyVector.size() + properties.size());
for (unsigned i = 0; i < properties.size(); ++i)
addParsedProperty(properties[i]);
}
void MutableStylePropertySet::addParsedProperty(const CSSProperty& property)
{
// Only add properties that have no !important counterpart present
if (!propertyIsImportant(property.id()) || property.isImportant())
setProperty(property);
}
String StylePropertySet::asText() 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();
CSSPropertyID shorthandPropertyID = CSSPropertyInvalid;
CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid;
String value;
switch (propertyID) {
#if ENABLE(CSS_VARIABLES)
case CSSPropertyVariable:
if (numDecls++)
result.append(' ');
result.append(property.cssText());
continue;
#endif
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:
if (!borderFallbackShorthandProperty)
borderFallbackShorthandProperty = CSSPropertyBorderWidth;
case CSSPropertyBorderTopStyle:
case CSSPropertyBorderRightStyle:
case CSSPropertyBorderBottomStyle:
case CSSPropertyBorderLeftStyle:
if (!borderFallbackShorthandProperty)
borderFallbackShorthandProperty = CSSPropertyBorderStyle;
case CSSPropertyBorderTopColor:
case CSSPropertyBorderRightColor:
case CSSPropertyBorderBottomColor:
case CSSPropertyBorderLeftColor:
if (!borderFallbackShorthandProperty)
borderFallbackShorthandProperty = CSSPropertyBorderColor;
// FIXME: Deal with cases where only some of border-(top|right|bottom|left) are specified.
if (!shorthandPropertyAppeared.get(CSSPropertyBorder - firstCSSProperty)) {
value = borderPropertyValue(ReturnNullOnUncommonValues);
if (value.isNull())
shorthandPropertyAppeared.set(CSSPropertyBorder - firstCSSProperty);
else
shorthandPropertyID = CSSPropertyBorder;
} else if (shorthandPropertyUsed.get(CSSPropertyBorder - firstCSSProperty))
shorthandPropertyID = CSSPropertyBorder;
if (!shorthandPropertyID)
shorthandPropertyID = borderFallbackShorthandProperty;
break;
case CSSPropertyWebkitBorderHorizontalSpacing:
case CSSPropertyWebkitBorderVerticalSpacing:
shorthandPropertyID = CSSPropertyBorderSpacing;
break;
case CSSPropertyFontFamily:
case CSSPropertyLineHeight:
case CSSPropertyFontSize:
case CSSPropertyFontStyle:
case CSSPropertyFontVariant:
case CSSPropertyFontWeight:
// Don't use CSSPropertyFont because old UAs can't recognize them but are important for editing.
break;
case CSSPropertyListStyleType:
case CSSPropertyListStylePosition:
case CSSPropertyListStyleImage:
shorthandPropertyID = CSSPropertyListStyle;
break;
case CSSPropertyMarginTop:
case CSSPropertyMarginRight:
case CSSPropertyMarginBottom:
case CSSPropertyMarginLeft:
shorthandPropertyID = CSSPropertyMargin;
break;
case CSSPropertyOutlineWidth:
case CSSPropertyOutlineStyle:
case CSSPropertyOutlineColor:
shorthandPropertyID = CSSPropertyOutline;
break;
case CSSPropertyOverflowX:
case CSSPropertyOverflowY:
shorthandPropertyID = CSSPropertyOverflow;
break;
case CSSPropertyPaddingTop:
case CSSPropertyPaddingRight:
case CSSPropertyPaddingBottom:
case CSSPropertyPaddingLeft:
shorthandPropertyID = CSSPropertyPadding;
break;
case CSSPropertyTransitionProperty:
case CSSPropertyTransitionDuration:
case CSSPropertyTransitionTimingFunction:
case CSSPropertyTransitionDelay:
shorthandPropertyID = CSSPropertyTransition;
break;
case CSSPropertyWebkitAnimationName:
case CSSPropertyWebkitAnimationDuration:
case CSSPropertyWebkitAnimationTimingFunction:
case CSSPropertyWebkitAnimationDelay:
case CSSPropertyWebkitAnimationIterationCount:
case CSSPropertyWebkitAnimationDirection:
case CSSPropertyWebkitAnimationFillMode:
shorthandPropertyID = CSSPropertyWebkitAnimation;
break;
case CSSPropertyWebkitFlexDirection:
case CSSPropertyWebkitFlexWrap:
shorthandPropertyID = CSSPropertyWebkitFlexFlow;
break;
case CSSPropertyWebkitFlexBasis:
case CSSPropertyWebkitFlexGrow:
case CSSPropertyWebkitFlexShrink:
shorthandPropertyID = CSSPropertyWebkitFlex;
break;
case CSSPropertyWebkitMaskPositionX:
case CSSPropertyWebkitMaskPositionY:
case CSSPropertyWebkitMaskRepeatX:
case CSSPropertyWebkitMaskRepeatY:
case CSSPropertyWebkitMaskImage:
case CSSPropertyWebkitMaskRepeat:
case CSSPropertyWebkitMaskPosition:
case CSSPropertyWebkitMaskClip:
case CSSPropertyWebkitMaskOrigin:
shorthandPropertyID = CSSPropertyWebkitMask;
break;
case CSSPropertyWebkitTransformOriginX:
case CSSPropertyWebkitTransformOriginY:
case CSSPropertyWebkitTransformOriginZ:
shorthandPropertyID = CSSPropertyWebkitTransformOrigin;
break;
case CSSPropertyWebkitTransitionProperty:
case CSSPropertyWebkitTransitionDuration:
case CSSPropertyWebkitTransitionTimingFunction:
case CSSPropertyWebkitTransitionDelay:
shorthandPropertyID = CSSPropertyWebkitTransition;
break;
default:
break;
}
unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty;
if (shorthandPropertyID) {
if (shorthandPropertyUsed.get(shortPropertyIndex))
continue;
if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull())
value = getPropertyValue(shorthandPropertyID);
shorthandPropertyAppeared.set(shortPropertyIndex);
}
if (!value.isNull()) {
propertyID = shorthandPropertyID;
shorthandPropertyUsed.set(shortPropertyIndex);
} else
value = property.value()->cssText();
if (value == "initial" && !CSSProperty::isInheritedProperty(propertyID))
continue;
if (numDecls++)
result.append(' ');
result.append(getPropertyName(propertyID));
result.appendLiteral(": ");
result.append(value);
if (property.isImportant())
result.appendLiteral(" !important");
result.append(';');
}
// FIXME: This is a not-so-nice way to turn x/y positions into single background-position in output.
// It is required because background-position-x/y are non-standard properties and WebKit generated output
// would not work in Firefox (<rdar://problem/5143183>)
// It would be a better solution if background-position was CSS_PAIR.
if (positionXPropertyIndex != -1 && positionYPropertyIndex != -1 && propertyAt(positionXPropertyIndex).isImportant() == propertyAt(positionYPropertyIndex).isImportant()) {
PropertyReference positionXProperty = propertyAt(positionXPropertyIndex);
PropertyReference positionYProperty = propertyAt(positionYPropertyIndex);
if (numDecls++)
result.append(' ');
result.appendLiteral("background-position: ");
if (positionXProperty.value()->isValueList() || positionYProperty.value()->isValueList())
result.append(getLayeredShorthandValue(backgroundPositionShorthand()));
else {
result.append(positionXProperty.value()->cssText());
result.append(' ');
result.append(positionYProperty.value()->cssText());
}
if (positionXProperty.isImportant())
result.appendLiteral(" !important");
result.append(';');
} else {
if (positionXPropertyIndex != -1) {
if (numDecls++)
result.append(' ');
result.append(propertyAt(positionXPropertyIndex).cssText());
}
if (positionYPropertyIndex != -1) {
if (numDecls++)
result.append(' ');
result.append(propertyAt(positionYPropertyIndex).cssText());
}
}
// FIXME: We need to do the same for background-repeat.
if (repeatXPropertyIndex != -1 && repeatYPropertyIndex != -1 && propertyAt(repeatXPropertyIndex).isImportant() == propertyAt(repeatYPropertyIndex).isImportant()) {
PropertyReference repeatXProperty = propertyAt(repeatXPropertyIndex);
PropertyReference repeatYProperty = propertyAt(repeatYPropertyIndex);
if (numDecls++)
result.append(' ');
result.appendLiteral("background-repeat: ");
if (repeatXProperty.value()->isValueList() || repeatYProperty.value()->isValueList())
result.append(getLayeredShorthandValue(backgroundRepeatShorthand()));
else {
result.append(repeatXProperty.value()->cssText());
result.append(' ');
result.append(repeatYProperty.value()->cssText());
}
if (repeatXProperty.isImportant())
result.appendLiteral(" !important");
result.append(';');
} else {
if (repeatXPropertyIndex != -1) {
if (numDecls++)
result.append(' ');
result.append(propertyAt(repeatXPropertyIndex).cssText());
}
if (repeatYPropertyIndex != -1) {
if (numDecls++)
result.append(' ');
result.append(propertyAt(repeatYPropertyIndex).cssText());
}
}
ASSERT(!numDecls ^ !result.isEmpty());
return result.toString();
}
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());
}
void StylePropertySet::addSubresourceStyleURLs(ListHashSet<URL>& urls, StyleSheetContents* contextStyleSheet) const
{
unsigned size = propertyCount();
for (unsigned i = 0; i < size; ++i)
propertyAt(i).value()->addSubresourceStyleURLs(urls, contextStyleSheet);
}
bool StylePropertySet::hasFailedOrCanceledSubresources() const
{
unsigned size = propertyCount();
for (unsigned i = 0; i < size; ++i) {
if (propertyAt(i).value()->hasFailedOrCanceledSubresources())
return true;
}
return false;
}
// 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
CSSPropertyWebkitAspectRatio,
CSSPropertyWebkitColumnCount,
CSSPropertyWebkitColumnGap,
CSSPropertyWebkitColumnRuleColor,
CSSPropertyWebkitColumnRuleStyle,
CSSPropertyWebkitColumnRuleWidth,
CSSPropertyWebkitColumnBreakBefore,
CSSPropertyWebkitColumnBreakAfter,
CSSPropertyWebkitColumnBreakInside,
CSSPropertyWebkitColumnWidth,
CSSPropertyPageBreakAfter,
CSSPropertyPageBreakBefore,
CSSPropertyPageBreakInside,
#if ENABLE(CSS_REGIONS)
CSSPropertyWebkitRegionBreakAfter,
CSSPropertyWebkitRegionBreakBefore,
CSSPropertyWebkitRegionBreakInside,
#endif
CSSPropertyTextAlign,
#if ENABLE(CSS3_TEXT)
CSSPropertyWebkitTextAlignLast,
CSSPropertyWebkitTextJustify,
#endif // CSS3_TEXT
CSSPropertyTextIndent,
CSSPropertyWidows
};
void MutableStylePropertySet::clear()
{
m_propertyVector.clear();
}
const unsigned numBlockProperties = WTF_ARRAY_LENGTH(blockProperties);
PassRef<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]);
Vector<CSSProperty> newProperties;
newProperties.reserveInitialCapacity(m_propertyVector.size());
unsigned size = m_propertyVector.size();
for (unsigned n = 0; n < size; ++n) {
const CSSProperty& property = m_propertyVector.at(n);
// Not quite sure if the isImportant test is needed but it matches the existing behavior.
if (!property.isImportant()) {
if (toRemove.contains(property.id()))
continue;
}
newProperties.append(property);
}
bool changed = newProperties.size() != m_propertyVector.size();
m_propertyVector = newProperties;
return changed;
}
int StylePropertySet::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 = propertyCount() - 1 ; n >= 0; --n) {
if (id == propertyAt(n).propertyMetadata().m_propertyID)
return n;
}
return -1;
}
CSSProperty* MutableStylePropertySet::findCSSPropertyWithID(CSSPropertyID propertyID)
{
int foundPropertyIndex = findPropertyIndex(propertyID);
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(const 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]);
}
PassRef<MutableStylePropertySet> StylePropertySet::mutableCopy() const
{
return adoptRef(*new MutableStylePropertySet(*this));
}
PassRef<MutableStylePropertySet> StylePropertySet::copyPropertiesInSet(const CSSPropertyID* set, unsigned length) const
{
Vector<CSSProperty, 256> list;
list.reserveInitialCapacity(length);
for (unsigned i = 0; i < length; ++i) {
RefPtr<CSSValue> value = getPropertyCSSValue(set[i]);
if (value)
list.append(CSSProperty(set[i], value.release(), false));
}
return MutableStylePropertySet::create(list.data(), list.size());
}
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.get();
}
m_cssomWrapper = adoptPtr(new PropertySetCSSStyleDeclaration(this));
return m_cssomWrapper.get();
}
CSSStyleDeclaration* MutableStylePropertySet::ensureInlineCSSStyleDeclaration(StyledElement* parentElement)
{
if (m_cssomWrapper) {
ASSERT(m_cssomWrapper->parentElement() == parentElement);
return m_cssomWrapper.get();
}
m_cssomWrapper = adoptPtr(new InlineCSSStyleDeclaration(this, parentElement));
return m_cssomWrapper.get();
}
unsigned StylePropertySet::averageSizeInBytes()
{
// Please update this if the storage scheme changes so that this longer reflects the actual size.
return sizeForImmutableStylePropertySetWithPropertyCount(4);
}
// 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
PassRef<MutableStylePropertySet> MutableStylePropertySet::create(CSSParserMode cssParserMode)
{
return adoptRef(*new MutableStylePropertySet(cssParserMode));
}
PassRef<MutableStylePropertySet> MutableStylePropertySet::create(const CSSProperty* properties, unsigned count)
{
return adoptRef(*new MutableStylePropertySet(properties, count));
}
String StylePropertySet::PropertyReference::cssName() const
{
#if ENABLE(CSS_VARIABLES)
if (id() == CSSPropertyVariable) {
ASSERT(propertyValue()->isVariableValue());
if (!propertyValue()->isVariableValue())
return emptyString(); // Should not happen, but if it does, avoid a bad cast.
return "-webkit-var-" + toCSSVariableValue(propertyValue())->name();
}
#endif
return getPropertyNameString(id());
}
String StylePropertySet::PropertyReference::cssText() const
{
StringBuilder result;
result.append(cssName());
result.appendLiteral(": ");
result.append(propertyValue()->cssText());
if (isImportant())
result.appendLiteral(" !important");
result.append(';');
return result.toString();
}
} // namespace WebCore