/* * (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 "StyleProperties.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 #include #ifndef NDEBUG #include #include #include #endif namespace WebCore { static size_t sizeForImmutableStylePropertiesWithPropertyCount(unsigned count) { return sizeof(ImmutableStyleProperties) - sizeof(void*) + sizeof(CSSValue*) * count + sizeof(StylePropertyMetadata) * count; } static bool isInitialOrInherit(const String& value) { return value.length() == 7 && (value == "initial" || value == "inherit"); } PassRef ImmutableStyleProperties::create(const CSSProperty* properties, unsigned count, CSSParserMode cssParserMode) { void* slot = WTF::fastMalloc(sizeForImmutableStylePropertiesWithPropertyCount(count)); return adoptRef(*new (NotNull, slot) ImmutableStyleProperties(properties, count, cssParserMode)); } PassRef StyleProperties::immutableCopyIfNeeded() const { if (!isMutable()) return static_cast(const_cast(*this)); const MutableStyleProperties& mutableThis = static_cast(*this); return ImmutableStyleProperties::create(mutableThis.m_propertyVector.data(), mutableThis.m_propertyVector.size(), cssParserMode()); } MutableStyleProperties::MutableStyleProperties(CSSParserMode cssParserMode) : StyleProperties(cssParserMode) { } MutableStyleProperties::MutableStyleProperties(const CSSProperty* properties, unsigned length) : StyleProperties(CSSStrictMode) { m_propertyVector.reserveInitialCapacity(length); for (unsigned i = 0; i < length; ++i) m_propertyVector.uncheckedAppend(properties[i]); } MutableStyleProperties::~MutableStyleProperties() { } ImmutableStyleProperties::ImmutableStyleProperties(const CSSProperty* properties, unsigned length, CSSParserMode cssParserMode) : StyleProperties(cssParserMode, length) { StylePropertyMetadata* metadataArray = const_cast(this->metadataArray()); CSSValue** valueArray = const_cast(this->valueArray()); for (unsigned i = 0; i < length; ++i) { metadataArray[i] = properties[i].metadata(); valueArray[i] = properties[i].value(); valueArray[i]->ref(); } } ImmutableStyleProperties::~ImmutableStyleProperties() { CSSValue** valueArray = const_cast(this->valueArray()); for (unsigned i = 0; i < m_arraySize; ++i) valueArray[i]->deref(); } MutableStyleProperties::MutableStyleProperties(const StyleProperties& other) : StyleProperties(other.cssParserMode()) { if (other.isMutable()) m_propertyVector = static_cast(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 StyleProperties::getPropertyValue(CSSPropertyID propertyID) const { RefPtr 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()); #if ENABLE(CSS_GRID_LAYOUT) case CSSPropertyWebkitGridArea: return getShorthandValue(webkitGridAreaShorthand()); case CSSPropertyWebkitGridTemplate: return getShorthandValue(webkitGridTemplateShorthand()); case CSSPropertyWebkitGrid: return getShorthandValue(webkitGridShorthand()); case CSSPropertyWebkitGridColumn: return getShorthandValue(webkitGridColumnShorthand()); case CSSPropertyWebkitGridRow: return getShorthandValue(webkitGridRowShorthand()); #endif 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()); case CSSPropertyMarker: { RefPtr value = getPropertyCSSValue(CSSPropertyMarkerStart); if (value) return value->cssText(); return String(); } case CSSPropertyBorderRadius: return get4Values(borderRadiusShorthand()); default: return String(); } } String StyleProperties::borderSpacingValue(const StylePropertyShorthand& shorthand) const { RefPtr horizontalValue = getPropertyCSSValue(shorthand.properties()[0]); RefPtr verticalValue = getPropertyCSSValue(shorthand.properties()[1]); // While standard border-spacing property does not allow specifying border-spacing-vertical without // specifying border-spacing-horizontal , // -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 StyleProperties::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 StyleProperties::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 StyleProperties::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 StyleProperties::getLayeredShorthandValue(const StylePropertyShorthand& shorthand) const { StringBuilder result; const unsigned size = shorthand.length(); // Begin by collecting the properties into an array. Vector< RefPtr> 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(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 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) // 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 yValue; RefPtr nextValue = values[j + 1]; if (nextValue->isValueList()) yValue = toCSSValueList(nextValue.get())->itemWithoutBoundsCheck(i); else yValue = nextValue; if (!value->isPrimitiveValue() || !yValue->isPrimitiveValue()) continue; 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 StyleProperties::getShorthandValue(const StylePropertyShorthand& shorthand) const { String commonValue; StringBuilder result; for (unsigned i = 0; i < shorthand.length(); ++i) { if (!isPropertyImplicit(shorthand.properties()[i])) { RefPtr 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 StyleProperties::getCommonValue(const StylePropertyShorthand& shorthand) const { String res; bool lastPropertyWasImportant = false; for (unsigned i = 0; i < shorthand.length(); ++i) { RefPtr 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 StyleProperties::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 StyleProperties::getPropertyCSSValue(CSSPropertyID propertyID) const { int foundPropertyIndex = findPropertyIndex(propertyID); if (foundPropertyIndex == -1) return 0; return propertyAt(foundPropertyIndex).value(); } bool MutableStyleProperties::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 MutableStyleProperties::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 MutableStyleProperties::removePrefixedOrUnprefixedProperty(CSSPropertyID propertyID) { int foundPropertyIndex = findPropertyIndex(prefixingVariantForPropertyId(propertyID)); if (foundPropertyIndex == -1) return; m_propertyVector.remove(foundPropertyIndex); } bool StyleProperties::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 StyleProperties::getPropertyShorthand(CSSPropertyID propertyID) const { int foundPropertyIndex = findPropertyIndex(propertyID); if (foundPropertyIndex == -1) return String(); return getPropertyNameString(propertyAt(foundPropertyIndex).shorthandID()); } bool StyleProperties::isPropertyImplicit(CSSPropertyID propertyID) const { int foundPropertyIndex = findPropertyIndex(propertyID); if (foundPropertyIndex == -1) return false; return propertyAt(foundPropertyIndex).isImplicit(); } bool MutableStyleProperties::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 MutableStyleProperties::setProperty(CSSPropertyID propertyID, PassRefPtr prpValue, bool important) { StylePropertyShorthand shorthand = shorthandForProperty(propertyID); if (!shorthand.length()) { setProperty(CSSProperty(propertyID, prpValue, important)); return; } removePropertiesInSet(shorthand.properties(), shorthand.length()); RefPtr value = prpValue; for (unsigned i = 0; i < shorthand.length(); ++i) m_propertyVector.append(CSSProperty(shorthand.properties()[i], value, important)); } void MutableStyleProperties::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 MutableStyleProperties::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 MutableStyleProperties::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 MutableStyleProperties::setProperty(CSSPropertyID propertyID, CSSValueID identifier, bool important) { setProperty(CSSProperty(propertyID, cssValuePool().createIdentifierValue(identifier), important)); return true; } bool MutableStyleProperties::setProperty(CSSPropertyID propertyID, CSSPropertyID identifier, bool important) { setProperty(CSSProperty(propertyID, cssValuePool().createIdentifierValue(identifier), important)); return true; } void MutableStyleProperties::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 MutableStyleProperties::addParsedProperties(const Vector& properties) { m_propertyVector.reserveCapacity(m_propertyVector.size() + properties.size()); for (unsigned i = 0; i < properties.size(); ++i) addParsedProperty(properties[i]); } void MutableStyleProperties::addParsedProperty(const CSSProperty& property) { // Only add properties that have no !important counterpart present if (!propertyIsImportant(property.id()) || property.isImportant()) setProperty(property); } String StyleProperties::asText() const { StringBuilder result; int positionXPropertyIndex = -1; int positionYPropertyIndex = -1; int repeatXPropertyIndex = -1; int repeatYPropertyIndex = -1; std::bitset shorthandPropertyUsed; std::bitset 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) { 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; FALLTHROUGH; case CSSPropertyBorderTopStyle: case CSSPropertyBorderRightStyle: case CSSPropertyBorderBottomStyle: case CSSPropertyBorderLeftStyle: if (!borderFallbackShorthandProperty) borderFallbackShorthandProperty = CSSPropertyBorderStyle; FALLTHROUGH; 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. ASSERT(CSSPropertyBorder - firstCSSProperty < shorthandPropertyAppeared.size()); if (!shorthandPropertyAppeared[CSSPropertyBorder - firstCSSProperty]) { value = borderPropertyValue(ReturnNullOnUncommonValues); if (value.isNull()) shorthandPropertyAppeared.set(CSSPropertyBorder - firstCSSProperty); else shorthandPropertyID = CSSPropertyBorder; } else if (shorthandPropertyUsed[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) { ASSERT(shortPropertyIndex < shorthandPropertyUsed.size()); if (shorthandPropertyUsed[shortPropertyIndex]) continue; if (!shorthandPropertyAppeared[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 () // 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 StyleProperties::hasCSSOMWrapper() const { return m_isMutable && static_cast(this)->m_cssomWrapper; } void MutableStyleProperties::mergeAndOverrideOnConflict(const StyleProperties& other) { unsigned size = other.propertyCount(); for (unsigned i = 0; i < size; ++i) addParsedProperty(other.propertyAt(i).toCSSProperty()); } void StyleProperties::addSubresourceStyleURLs(ListHashSet& urls, StyleSheetContents* contextStyleSheet) const { unsigned size = propertyCount(); for (unsigned i = 0; i < size; ++i) propertyAt(i).value()->addSubresourceStyleURLs(urls, contextStyleSheet); } bool StyleProperties::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 MutableStyleProperties::clear() { m_propertyVector.clear(); } const unsigned numBlockProperties = WTF_ARRAY_LENGTH(blockProperties); PassRef StyleProperties::copyBlockProperties() const { return copyPropertiesInSet(blockProperties, numBlockProperties); } void MutableStyleProperties::removeBlockProperties() { removePropertiesInSet(blockProperties, numBlockProperties); } bool MutableStyleProperties::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 toRemove; for (unsigned i = 0; i < length; ++i) toRemove.add(set[i]); Vector 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 ImmutableStyleProperties::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(propertyID); for (int n = m_arraySize - 1 ; n >= 0; --n) { if (metadataArray()[n].m_propertyID == id) return n; } return -1; } int MutableStyleProperties::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(propertyID); for (int n = m_propertyVector.size() - 1 ; n >= 0; --n) { if (m_propertyVector.at(n).metadata().m_propertyID == id) return n; } return -1; } CSSProperty* MutableStyleProperties::findCSSPropertyWithID(CSSPropertyID propertyID) { int foundPropertyIndex = findPropertyIndex(propertyID); if (foundPropertyIndex == -1) return 0; return &m_propertyVector.at(foundPropertyIndex); } bool StyleProperties::propertyMatches(CSSPropertyID propertyID, const CSSValue* propertyValue) const { int foundPropertyIndex = findPropertyIndex(propertyID); if (foundPropertyIndex == -1) return false; return propertyAt(foundPropertyIndex).value()->equals(*propertyValue); } PassRef StyleProperties::mutableCopy() const { return adoptRef(*new MutableStyleProperties(*this)); } PassRef StyleProperties::copyPropertiesInSet(const CSSPropertyID* set, unsigned length) const { Vector list; list.reserveInitialCapacity(length); for (unsigned i = 0; i < length; ++i) { RefPtr value = getPropertyCSSValue(set[i]); if (value) list.append(CSSProperty(set[i], value.release(), false)); } return MutableStyleProperties::create(list.data(), list.size()); } PropertySetCSSStyleDeclaration* MutableStyleProperties::cssStyleDeclaration() { return m_cssomWrapper.get(); } CSSStyleDeclaration* MutableStyleProperties::ensureCSSStyleDeclaration() { if (m_cssomWrapper) { ASSERT(!static_cast(m_cssomWrapper.get())->parentRule()); ASSERT(!m_cssomWrapper->parentElement()); return m_cssomWrapper.get(); } m_cssomWrapper = std::make_unique(this); return m_cssomWrapper.get(); } CSSStyleDeclaration* MutableStyleProperties::ensureInlineCSSStyleDeclaration(StyledElement* parentElement) { if (m_cssomWrapper) { ASSERT(m_cssomWrapper->parentElement() == parentElement); return m_cssomWrapper.get(); } m_cssomWrapper = std::make_unique(this, parentElement); return m_cssomWrapper.get(); } unsigned StyleProperties::averageSizeInBytes() { // Please update this if the storage scheme changes so that this longer reflects the actual size. return sizeForImmutableStylePropertiesWithPropertyCount(4); } // See the function above if you need to update this. struct SameSizeAsStyleProperties : public RefCounted { unsigned bitfield; }; COMPILE_ASSERT(sizeof(StyleProperties) == sizeof(SameSizeAsStyleProperties), style_property_set_should_stay_small); #ifndef NDEBUG void StyleProperties::showStyle() { fprintf(stderr, "%s\n", asText().ascii().data()); } #endif PassRef MutableStyleProperties::create(CSSParserMode cssParserMode) { return adoptRef(*new MutableStyleProperties(cssParserMode)); } PassRef MutableStyleProperties::create(const CSSProperty* properties, unsigned count) { return adoptRef(*new MutableStyleProperties(properties, count)); } String StyleProperties::PropertyReference::cssName() const { return getPropertyNameString(id()); } String StyleProperties::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