| /* |
| * (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. |
| * 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 "core/css/StylePropertySerializer.h" |
| |
| #include "core/CSSValueKeywords.h" |
| #include "core/StylePropertyShorthand.h" |
| #include "core/css/CSSCustomPropertyDeclaration.h" |
| #include "core/css/CSSPropertyMetadata.h" |
| #include "core/css/CSSValuePool.h" |
| #include "wtf/BitArray.h" |
| #include "wtf/text/StringBuilder.h" |
| |
| namespace blink { |
| |
| 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); |
| } |
| |
| StylePropertySerializer::StylePropertySetForSerializer::StylePropertySetForSerializer(const StylePropertySet& properties) |
| : m_propertySet(&properties) |
| , m_allIndex(m_propertySet->findPropertyIndex(CSSPropertyAll)) |
| , m_needToExpandAll(false) |
| { |
| if (!hasAllProperty()) |
| return; |
| |
| StylePropertySet::PropertyReference allProperty = m_propertySet->propertyAt(m_allIndex); |
| for (unsigned i = 0; i < m_propertySet->propertyCount(); ++i) { |
| StylePropertySet::PropertyReference property = m_propertySet->propertyAt(i); |
| if (CSSProperty::isAffectedByAllProperty(property.id())) { |
| if (allProperty.isImportant() && !property.isImportant()) |
| continue; |
| if (static_cast<unsigned>(m_allIndex) >= i) |
| continue; |
| if (property.value()->equals(*allProperty.value()) |
| && property.isImportant() == allProperty.isImportant()) |
| continue; |
| m_needToExpandAll = true; |
| } |
| m_longhandPropertyUsed.set(property.id() - firstCSSProperty); |
| } |
| } |
| |
| DEFINE_TRACE(StylePropertySerializer::StylePropertySetForSerializer) |
| { |
| visitor->trace(m_propertySet); |
| } |
| |
| unsigned StylePropertySerializer::StylePropertySetForSerializer::propertyCount() const |
| { |
| if (!hasExpandedAllProperty()) |
| return m_propertySet->propertyCount(); |
| return lastCSSProperty - firstCSSProperty + 1; |
| } |
| |
| StylePropertySerializer::PropertyValueForSerializer StylePropertySerializer::StylePropertySetForSerializer::propertyAt(unsigned index) const |
| { |
| if (!hasExpandedAllProperty()) |
| return StylePropertySerializer::PropertyValueForSerializer(m_propertySet->propertyAt(index)); |
| |
| CSSPropertyID propertyID = static_cast<CSSPropertyID>(index + firstCSSProperty); |
| ASSERT(firstCSSProperty <= propertyID && propertyID <= lastCSSProperty); |
| if (m_longhandPropertyUsed.get(index)) { |
| int index = m_propertySet->findPropertyIndex(propertyID); |
| ASSERT(index != -1); |
| return StylePropertySerializer::PropertyValueForSerializer(m_propertySet->propertyAt(index)); |
| } |
| |
| StylePropertySet::PropertyReference property = m_propertySet->propertyAt(m_allIndex); |
| return StylePropertySerializer::PropertyValueForSerializer(propertyID, property.value(), property.isImportant()); |
| } |
| |
| bool StylePropertySerializer::StylePropertySetForSerializer::shouldProcessPropertyAt(unsigned index) const |
| { |
| // StylePropertySet has all valid longhands. We should process. |
| if (!hasAllProperty()) |
| return true; |
| |
| // If all is not expanded, we need to process "all" and properties which |
| // are not overwritten by "all". |
| if (!m_needToExpandAll) { |
| StylePropertySet::PropertyReference property = m_propertySet->propertyAt(index); |
| if (property.id() == CSSPropertyAll || !CSSProperty::isAffectedByAllProperty(property.id())) |
| return true; |
| return m_longhandPropertyUsed.get(property.id() - firstCSSProperty); |
| } |
| |
| CSSPropertyID propertyID = static_cast<CSSPropertyID>(index + firstCSSProperty); |
| ASSERT(firstCSSProperty <= propertyID && propertyID <= lastCSSProperty); |
| |
| // Since "all" is expanded, we don't need to process "all". |
| // We should not process expanded shorthands (e.g. font, background, |
| // and so on) either. |
| if (isShorthandProperty(propertyID) || propertyID == CSSPropertyAll) |
| return false; |
| |
| // The all property is a shorthand that resets all CSS properties except |
| // direction and unicode-bidi. It only accepts the CSS-wide keywords. |
| // c.f. http://dev.w3.org/csswg/css-cascade/#all-shorthand |
| if (!CSSProperty::isAffectedByAllProperty(propertyID)) |
| return m_longhandPropertyUsed.get(index); |
| |
| return true; |
| } |
| |
| int StylePropertySerializer::StylePropertySetForSerializer::findPropertyIndex(CSSPropertyID propertyID) const |
| { |
| if (!hasExpandedAllProperty()) |
| return m_propertySet->findPropertyIndex(propertyID); |
| return propertyID - firstCSSProperty; |
| } |
| |
| const CSSValue* StylePropertySerializer::StylePropertySetForSerializer::getPropertyCSSValue(CSSPropertyID propertyID) const |
| { |
| int index = findPropertyIndex(propertyID); |
| if (index == -1) |
| return nullptr; |
| StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index); |
| return value.value(); |
| } |
| |
| String StylePropertySerializer::StylePropertySetForSerializer::getPropertyValue(CSSPropertyID propertyID) const |
| { |
| if (!hasExpandedAllProperty()) |
| return m_propertySet->getPropertyValue(propertyID); |
| |
| const CSSValue* value = getPropertyCSSValue(propertyID); |
| if (!value) |
| return String(); |
| return value->cssText(); |
| } |
| |
| bool StylePropertySerializer::StylePropertySetForSerializer::isPropertyImplicit(CSSPropertyID propertyID) const |
| { |
| int index = findPropertyIndex(propertyID); |
| if (index == -1) |
| return false; |
| StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index); |
| return value.isImplicit(); |
| } |
| |
| bool StylePropertySerializer::StylePropertySetForSerializer::propertyIsImportant(CSSPropertyID propertyID) const |
| { |
| int index = findPropertyIndex(propertyID); |
| if (index == -1) |
| return false; |
| StylePropertySerializer::PropertyValueForSerializer value = propertyAt(index); |
| return value.isImportant(); |
| } |
| |
| StylePropertySerializer::StylePropertySerializer(const StylePropertySet& properties) |
| : m_propertySet(properties) |
| { |
| } |
| |
| String StylePropertySerializer::getCustomPropertyText(const PropertyValueForSerializer& property, bool isNotFirstDecl) const |
| { |
| ASSERT(property.id() == CSSPropertyVariable); |
| StringBuilder result; |
| if (isNotFirstDecl) |
| result.append(' '); |
| const CSSCustomPropertyDeclaration* value = toCSSCustomPropertyDeclaration(property.value()); |
| result.append(value->name()); |
| result.appendLiteral(": "); |
| result.append(value->customCSSText()); |
| if (property.isImportant()) |
| result.appendLiteral(" !important"); |
| result.append(';'); |
| return result.toString(); |
| } |
| |
| String StylePropertySerializer::getPropertyText(CSSPropertyID propertyID, const String& value, bool isImportant, bool isNotFirstDecl) const |
| { |
| StringBuilder result; |
| if (isNotFirstDecl) |
| result.append(' '); |
| result.append(getPropertyName(propertyID)); |
| result.appendLiteral(": "); |
| result.append(value); |
| if (isImportant) |
| result.appendLiteral(" !important"); |
| result.append(';'); |
| return result.toString(); |
| } |
| |
| String StylePropertySerializer::asText() const |
| { |
| StringBuilder result; |
| |
| BitArray<numCSSProperties> shorthandPropertyUsed; |
| BitArray<numCSSProperties> shorthandPropertyAppeared; |
| |
| unsigned size = m_propertySet.propertyCount(); |
| unsigned numDecls = 0; |
| for (unsigned n = 0; n < size; ++n) { |
| if (!m_propertySet.shouldProcessPropertyAt(n)) |
| continue; |
| |
| StylePropertySerializer::PropertyValueForSerializer property = m_propertySet.propertyAt(n); |
| CSSPropertyID propertyID = property.id(); |
| // Only enabled properties should be part of the style. |
| ASSERT(CSSPropertyMetadata::isEnabledProperty(propertyID)); |
| CSSPropertyID shorthandPropertyID = CSSPropertyInvalid; |
| CSSPropertyID borderFallbackShorthandProperty = CSSPropertyInvalid; |
| String value; |
| // Shorthands with variable references are not expanded at parse time |
| // and hence may still be observed during serialization. |
| ASSERT(!isShorthandProperty(propertyID) || property.value()->isVariableReferenceValue()); |
| |
| switch (propertyID) { |
| case CSSPropertyBackgroundAttachment: |
| case CSSPropertyBackgroundClip: |
| case CSSPropertyBackgroundColor: |
| case CSSPropertyBackgroundImage: |
| case CSSPropertyBackgroundOrigin: |
| case CSSPropertyBackgroundPositionX: |
| case CSSPropertyBackgroundPositionY: |
| case CSSPropertyBackgroundSize: |
| case CSSPropertyBackgroundRepeatX: |
| case CSSPropertyBackgroundRepeatY: |
| shorthandPropertyAppeared.set(CSSPropertyBackground - firstCSSProperty); |
| 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 CSSPropertyBorderTopLeftRadius: |
| case CSSPropertyBorderTopRightRadius: |
| case CSSPropertyBorderBottomLeftRadius: |
| case CSSPropertyBorderBottomRightRadius: |
| shorthandPropertyID = CSSPropertyBorderRadius; |
| break; |
| case CSSPropertyWebkitBorderHorizontalSpacing: |
| case CSSPropertyWebkitBorderVerticalSpacing: |
| shorthandPropertyID = CSSPropertyBorderSpacing; |
| break; |
| case CSSPropertyFontFamily: |
| case CSSPropertyLineHeight: |
| case CSSPropertyFontSize: |
| case CSSPropertyFontStretch: |
| 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 CSSPropertyMotionPath: |
| case CSSPropertyMotionOffset: |
| case CSSPropertyMotionRotation: |
| shorthandPropertyID = CSSPropertyMotion; |
| 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 CSSPropertyAnimationName: |
| case CSSPropertyAnimationDuration: |
| case CSSPropertyAnimationTimingFunction: |
| case CSSPropertyAnimationDelay: |
| case CSSPropertyAnimationIterationCount: |
| case CSSPropertyAnimationDirection: |
| case CSSPropertyAnimationFillMode: |
| case CSSPropertyAnimationPlayState: |
| shorthandPropertyID = CSSPropertyAnimation; |
| break; |
| case CSSPropertyFlexDirection: |
| case CSSPropertyFlexWrap: |
| shorthandPropertyID = CSSPropertyFlexFlow; |
| break; |
| case CSSPropertyFlexBasis: |
| case CSSPropertyFlexGrow: |
| case CSSPropertyFlexShrink: |
| shorthandPropertyID = CSSPropertyFlex; |
| break; |
| case CSSPropertyWebkitMaskPositionX: |
| case CSSPropertyWebkitMaskPositionY: |
| case CSSPropertyWebkitMaskRepeatX: |
| case CSSPropertyWebkitMaskRepeatY: |
| case CSSPropertyWebkitMaskImage: |
| case CSSPropertyWebkitMaskRepeat: |
| case CSSPropertyWebkitMaskPosition: |
| case CSSPropertyWebkitMaskClip: |
| case CSSPropertyWebkitMaskOrigin: |
| shorthandPropertyID = CSSPropertyWebkitMask; |
| break; |
| case CSSPropertyVariable: |
| result.append(getCustomPropertyText(property, numDecls++)); |
| continue; |
| case CSSPropertyAll: |
| result.append(getPropertyText(propertyID, property.value()->cssText(), property.isImportant(), numDecls++)); |
| continue; |
| default: |
| break; |
| } |
| |
| unsigned shortPropertyIndex = shorthandPropertyID - firstCSSProperty; |
| if (shorthandPropertyID) { |
| if (shorthandPropertyUsed.get(shortPropertyIndex)) |
| continue; |
| if (!shorthandPropertyAppeared.get(shortPropertyIndex) && value.isNull()) |
| value = m_propertySet.getPropertyValue(shorthandPropertyID); |
| shorthandPropertyAppeared.set(shortPropertyIndex); |
| } |
| |
| if (!value.isNull()) { |
| if (shorthandPropertyID) { |
| propertyID = shorthandPropertyID; |
| shorthandPropertyUsed.set(shortPropertyIndex); |
| } |
| } else { |
| // We should not show "initial" when the "initial" is implicit. |
| // If explicit "initial", we need to show. |
| if (property.value()->isImplicitInitialValue()) |
| continue; |
| value = property.value()->cssText(); |
| } |
| |
| result.append(getPropertyText(propertyID, value, property.isImportant(), numDecls++)); |
| } |
| |
| if (shorthandPropertyAppeared.get(CSSPropertyBackground - firstCSSProperty)) |
| appendBackgroundPropertyAsText(result, numDecls); |
| |
| ASSERT(!numDecls ^ !result.isEmpty()); |
| return result.toString(); |
| } |
| |
| String StylePropertySerializer::getPropertyValue(CSSPropertyID propertyID) const |
| { |
| // Shorthand and 4-values properties |
| switch (propertyID) { |
| case CSSPropertyAnimation: |
| return getLayeredShorthandValue(animationShorthand()); |
| case CSSPropertyBorderSpacing: |
| return borderSpacingValue(borderSpacingShorthand()); |
| case CSSPropertyBackgroundPosition: |
| return getLayeredShorthandValue(backgroundPositionShorthand()); |
| case CSSPropertyBackgroundRepeat: |
| return backgroundRepeatPropertyValue(); |
| 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 CSSPropertyColumnRule: |
| return getShorthandValue(columnRuleShorthand()); |
| case CSSPropertyColumns: |
| return getShorthandValue(columnsShorthand()); |
| case CSSPropertyFlex: |
| return getShorthandValue(flexShorthand()); |
| case CSSPropertyFlexFlow: |
| return getShorthandValue(flexFlowShorthand()); |
| case CSSPropertyGridColumn: |
| return getShorthandValue(gridColumnShorthand(), " / "); |
| case CSSPropertyGridRow: |
| return getShorthandValue(gridRowShorthand(), " / "); |
| case CSSPropertyGridArea: |
| return getShorthandValue(gridAreaShorthand(), " / "); |
| case CSSPropertyGridGap: |
| return getShorthandValue(gridGapShorthand()); |
| case CSSPropertyFont: |
| return fontValue(); |
| case CSSPropertyMargin: |
| return get4Values(marginShorthand()); |
| case CSSPropertyMotion: |
| return getShorthandValue(motionShorthand()); |
| 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 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 CSSPropertyMarker: { |
| if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyMarkerStart)) |
| return value->cssText(); |
| return String(); |
| } |
| case CSSPropertyBorderRadius: |
| return get4Values(borderRadiusShorthand()); |
| default: |
| return String(); |
| } |
| } |
| |
| String StylePropertySerializer::borderSpacingValue(const StylePropertyShorthand& shorthand) const |
| { |
| const CSSValue* horizontalValue = m_propertySet.getPropertyCSSValue(shorthand.properties()[0]); |
| const CSSValue* verticalValue = m_propertySet.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 StylePropertySerializer::appendFontLonghandValueIfNotNormal(CSSPropertyID propertyID, StringBuilder& result, String& commonValue) const |
| { |
| int foundPropertyIndex = m_propertySet.findPropertyIndex(propertyID); |
| ASSERT(foundPropertyIndex != -1); |
| |
| const CSSValue* val = m_propertySet.propertyAt(foundPropertyIndex).value(); |
| if (val->isPrimitiveValue() && toCSSPrimitiveValue(val)->getValueID() == CSSValueNormal) { |
| commonValue = String(); |
| return; |
| } |
| |
| char prefix = '\0'; |
| switch (propertyID) { |
| case CSSPropertyFontStyle: |
| break; // No prefix. |
| case CSSPropertyFontFamily: |
| case CSSPropertyFontStretch: |
| case CSSPropertyFontVariant: |
| case CSSPropertyFontWeight: |
| prefix = ' '; |
| break; |
| case CSSPropertyLineHeight: |
| prefix = '/'; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| if (prefix && !result.isEmpty()) |
| result.append(prefix); |
| String value = m_propertySet.propertyAt(foundPropertyIndex).value()->cssText(); |
| result.append(value); |
| if (!commonValue.isNull() && commonValue != value) |
| commonValue = String(); |
| } |
| |
| String StylePropertySerializer::fontValue() const |
| { |
| if (!isPropertyShorthandAvailable(fontShorthand()) && !shorthandHasOnlyInitialOrInheritedValue(fontShorthand())) |
| return emptyString(); |
| |
| int fontSizePropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontSize); |
| int fontFamilyPropertyIndex = m_propertySet.findPropertyIndex(CSSPropertyFontFamily); |
| ASSERT(fontSizePropertyIndex != -1 && fontFamilyPropertyIndex != -1); |
| |
| PropertyValueForSerializer fontSizeProperty = m_propertySet.propertyAt(fontSizePropertyIndex); |
| PropertyValueForSerializer fontFamilyProperty = m_propertySet.propertyAt(fontFamilyPropertyIndex); |
| |
| String commonValue = fontSizeProperty.value()->cssText(); |
| StringBuilder result; |
| appendFontLonghandValueIfNotNormal(CSSPropertyFontStyle, result, commonValue); |
| appendFontLonghandValueIfNotNormal(CSSPropertyFontVariant, result, commonValue); |
| appendFontLonghandValueIfNotNormal(CSSPropertyFontWeight, result, commonValue); |
| appendFontLonghandValueIfNotNormal(CSSPropertyFontStretch, result, commonValue); |
| if (!result.isEmpty()) |
| result.append(' '); |
| result.append(fontSizeProperty.value()->cssText()); |
| appendFontLonghandValueIfNotNormal(CSSPropertyLineHeight, result, commonValue); |
| if (!result.isEmpty()) |
| result.append(' '); |
| result.append(fontFamilyProperty.value()->cssText()); |
| if (isInitialOrInherit(commonValue)) |
| return commonValue; |
| return result.toString(); |
| } |
| |
| String StylePropertySerializer::get4Values(const StylePropertyShorthand& shorthand) const |
| { |
| // Assume the properties are in the usual order top, right, bottom, left. |
| int topValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[0]); |
| int rightValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[1]); |
| int bottomValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[2]); |
| int leftValueIndex = m_propertySet.findPropertyIndex(shorthand.properties()[3]); |
| |
| if (topValueIndex == -1 || rightValueIndex == -1 || bottomValueIndex == -1 || leftValueIndex == -1) |
| return String(); |
| |
| PropertyValueForSerializer top = m_propertySet.propertyAt(topValueIndex); |
| PropertyValueForSerializer right = m_propertySet.propertyAt(rightValueIndex); |
| PropertyValueForSerializer bottom = m_propertySet.propertyAt(bottomValueIndex); |
| PropertyValueForSerializer left = m_propertySet.propertyAt(leftValueIndex); |
| |
| // All 4 properties must be specified. |
| if (!top.value() || !right.value() || !bottom.value() || !left.value()) |
| return String(); |
| |
| if (top.isImportant() != right.isImportant() || right.isImportant() != bottom.isImportant() || bottom.isImportant() != left.isImportant()) |
| return String(); |
| |
| if (top.isInherited() && right.isInherited() && bottom.isInherited() && left.isInherited()) |
| return getValueName(CSSValueInherit); |
| |
| unsigned numInitial = top.value()->isInitialValue() + right.value()->isInitialValue() + bottom.value()->isInitialValue() + left.value()->isInitialValue(); |
| if (numInitial == 4) |
| return getValueName(CSSValueInitial); |
| if (numInitial > 0) |
| 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 StylePropertySerializer::getLayeredShorthandValue(const StylePropertyShorthand& shorthand) const |
| { |
| const unsigned size = shorthand.length(); |
| |
| // Begin by collecting the properties into a vector. |
| WillBeHeapVector<RawPtrWillBeMember<const CSSValue>> values(size); |
| // If the below loop succeeds, there should always be at minimum 1 layer. |
| size_t numLayers = 1U; |
| |
| for (size_t i = 0; i < size; i++) { |
| values[i] = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); |
| // A shorthand is not available if getPropertyCSSValue didn't resolve to anything. |
| if (!values[i]) |
| return String(); |
| if (values[i]->isBaseValueList()) { |
| const CSSValueList* valueList = toCSSValueList(values[i]); |
| numLayers = std::max(numLayers, valueList->length()); |
| } |
| } |
| |
| StringBuilder result; |
| // Tracks whether or not all the values are initial or all the values are inherit. |
| // Start out assuming there is a common value. It will get set to false below if there isn't one. |
| bool hasCommonValue = true; |
| const CSSValue* commonValue = nullptr; |
| |
| // Now stitch the properties together. Implicit initial values are flagged as such and |
| // can safely be omitted. |
| for (size_t layer = 0; layer < numLayers; layer++) { |
| StringBuilder layerResult; |
| bool useRepeatXShorthand = false; |
| bool useRepeatYShorthand = false; |
| bool useSingleWordShorthand = false; |
| bool foundPositionXCSSProperty = false; |
| bool foundPositionYCSSProperty = false; |
| |
| for (unsigned propertyIndex = 0; propertyIndex < size; propertyIndex++) { |
| const CSSValue* value = nullptr; |
| CSSPropertyID property = shorthand.properties()[propertyIndex]; |
| |
| // Get a CSSValue for this property and layer. |
| if (values[propertyIndex]->isBaseValueList()) { |
| // Might return 0 if there is not an item for this layer for this property. |
| value = toCSSValueList(values[propertyIndex])->itemWithBoundsCheck(layer); |
| } else if (layer == 0 || (layer != numLayers - 1 && property == CSSPropertyBackgroundColor)) { |
| // Singletons except background color belong in the 0th layer. |
| // Background color belongs in the last layer. |
| value = values[propertyIndex]; |
| } |
| // No point proceeding if there's not a value to look at. |
| if (!value) |
| continue; |
| |
| // Special case for background-repeat. |
| if ((propertyIndex < size - 1 && m_propertySet.isPropertyImplicit(property)) |
| && (property == CSSPropertyBackgroundRepeatX || property == CSSPropertyWebkitMaskRepeatX)) { |
| ASSERT(shorthand.properties()[propertyIndex + 1] == CSSPropertyBackgroundRepeatY |
| || shorthand.properties()[propertyIndex + 1] == CSSPropertyWebkitMaskRepeatY); |
| const CSSValue* yValue = values[propertyIndex + 1]->isValueList() ? |
| toCSSValueList(values[propertyIndex + 1])->item(layer) : values[propertyIndex + 1].get(); |
| |
| |
| // FIXME: At some point we need to fix this code to avoid returning an invalid shorthand, |
| // since some longhand combinations are not serializable into a single shorthand. |
| if (!value->isPrimitiveValue() || !yValue->isPrimitiveValue()) |
| continue; |
| |
| CSSValueID xId = toCSSPrimitiveValue(value)->getValueID(); |
| CSSValueID yId = toCSSPrimitiveValue(yValue)->getValueID(); |
| // Maybe advance propertyIndex to look at the next CSSValue in the list for the checks below. |
| if (xId == yId) { |
| useSingleWordShorthand = true; |
| property = shorthand.properties()[++propertyIndex]; |
| } else if (xId == CSSValueRepeat && yId == CSSValueNoRepeat) { |
| useRepeatXShorthand = true; |
| property = shorthand.properties()[++propertyIndex]; |
| } else if (xId == CSSValueNoRepeat && yId == CSSValueRepeat) { |
| useRepeatYShorthand = true; |
| property = shorthand.properties()[++propertyIndex]; |
| } |
| } |
| |
| if (!(value->isInitialValue() && toCSSInitialValue(value)->isImplicit())) { |
| if (property == CSSPropertyBackgroundSize || property == CSSPropertyWebkitMaskSize) { |
| if (foundPositionYCSSProperty || foundPositionXCSSProperty) |
| layerResult.appendLiteral(" / "); |
| else |
| layerResult.appendLiteral(" 0% 0% / "); |
| } else if (!layerResult.isEmpty()) { |
| // Do this second to avoid ending up with an extra space in the output if we hit the continue above. |
| layerResult.append(' '); |
| } |
| |
| if (useRepeatXShorthand) { |
| useRepeatXShorthand = false; |
| layerResult.append(getValueName(CSSValueRepeatX)); |
| } else if (useRepeatYShorthand) { |
| useRepeatYShorthand = false; |
| layerResult.append(getValueName(CSSValueRepeatY)); |
| } else { |
| if (useSingleWordShorthand) |
| useSingleWordShorthand = false; |
| layerResult.append(value->cssText()); |
| } |
| if (property == CSSPropertyBackgroundPositionX || property == CSSPropertyWebkitMaskPositionX) |
| foundPositionXCSSProperty = true; |
| if (property == CSSPropertyBackgroundPositionY || property == 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 (hasCommonValue && !value->isInitialValue() && !value->isInheritedValue()) |
| hasCommonValue = false; |
| } |
| } |
| |
| if (hasCommonValue && !commonValue) |
| commonValue = value; |
| else if (!value->equals(*commonValue)) |
| hasCommonValue = false; |
| } |
| if (!layerResult.isEmpty()) { |
| if (!result.isEmpty()) |
| result.appendLiteral(", "); |
| result.append(layerResult); |
| } |
| } |
| |
| if (hasCommonValue && (commonValue->isInitialValue() || commonValue->isInheritedValue())) |
| return commonValue->cssText(); |
| |
| return result.toString(); |
| } |
| |
| String StylePropertySerializer::getShorthandValue(const StylePropertyShorthand& shorthand, String separator) const |
| { |
| String commonValue; |
| StringBuilder result; |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSValue* value = m_propertySet.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); |
| } |
| if (isInitialOrInherit(commonValue)) |
| return commonValue; |
| return result.toString(); |
| } |
| |
| // only returns a non-null value if all properties have the same, non-null value |
| String StylePropertySerializer::getCommonValue(const StylePropertyShorthand& shorthand) const |
| { |
| String res; |
| bool lastPropertyWasImportant = false; |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSValue* value = m_propertySet.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 = m_propertySet.propertyIsImportant(shorthand.properties()[i]); |
| if (i && lastPropertyWasImportant != currentPropertyIsImportant) |
| return String(); |
| lastPropertyWasImportant = currentPropertyIsImportant; |
| } |
| return res; |
| } |
| |
| String StylePropertySerializer::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(); |
| } |
| |
| static void appendBackgroundRepeatValue(StringBuilder& builder, const CSSValue& repeatXCSSValue, const CSSValue& repeatYCSSValue) |
| { |
| // FIXME: Ensure initial values do not appear in CSS_VALUE_LISTS. |
| DEFINE_STATIC_REF_WILL_BE_PERSISTENT(CSSPrimitiveValue, initialRepeatValue, (CSSPrimitiveValue::createIdentifier(CSSValueRepeat))); |
| const CSSPrimitiveValue& repeatX = repeatXCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatXCSSValue); |
| const CSSPrimitiveValue& repeatY = repeatYCSSValue.isInitialValue() ? *initialRepeatValue : toCSSPrimitiveValue(repeatYCSSValue); |
| CSSValueID repeatXValueId = repeatX.getValueID(); |
| CSSValueID repeatYValueId = repeatY.getValueID(); |
| if (repeatXValueId == repeatYValueId) { |
| builder.append(repeatX.cssText()); |
| } else if (repeatXValueId == CSSValueNoRepeat && repeatYValueId == CSSValueRepeat) { |
| builder.appendLiteral("repeat-y"); |
| } else if (repeatXValueId == CSSValueRepeat && repeatYValueId == CSSValueNoRepeat) { |
| builder.appendLiteral("repeat-x"); |
| } else { |
| builder.append(repeatX.cssText()); |
| builder.appendLiteral(" "); |
| builder.append(repeatY.cssText()); |
| } |
| } |
| |
| String StylePropertySerializer::backgroundRepeatPropertyValue() const |
| { |
| const CSSValue* repeatX = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatX); |
| const CSSValue* repeatY = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundRepeatY); |
| if (!repeatX || !repeatY) |
| return String(); |
| if (m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX) != m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatY)) |
| return String(); |
| if ((repeatX->isInitialValue() && repeatY->isInitialValue()) || (repeatX->isInheritedValue() && repeatY->isInheritedValue())) |
| return repeatX->cssText(); |
| |
| const CSSValueList* repeatXList = 0; |
| int repeatXLength = 1; |
| if (repeatX->isValueList()) { |
| repeatXList = toCSSValueList(repeatX); |
| repeatXLength = repeatXList->length(); |
| } else if (!repeatX->isPrimitiveValue()) { |
| return String(); |
| } |
| |
| const CSSValueList* repeatYList = 0; |
| int repeatYLength = 1; |
| if (repeatY->isValueList()) { |
| repeatYList = toCSSValueList(repeatY); |
| repeatYLength = repeatYList->length(); |
| } else if (!repeatY->isPrimitiveValue()) { |
| return String(); |
| } |
| |
| size_t shorthandLength = lowestCommonMultiple(repeatXLength, repeatYLength); |
| StringBuilder builder; |
| for (size_t i = 0; i < shorthandLength; ++i) { |
| if (i) |
| builder.appendLiteral(", "); |
| |
| const CSSValue* xValue = repeatXList ? repeatXList->item(i % repeatXList->length()) : repeatX; |
| const CSSValue* yValue = repeatYList ? repeatYList->item(i % repeatYList->length()) : repeatY; |
| appendBackgroundRepeatValue(builder, *xValue, *yValue); |
| } |
| return builder.toString(); |
| } |
| |
| void StylePropertySerializer::appendBackgroundPropertyAsText(StringBuilder& result, unsigned& numDecls) const |
| { |
| if (isPropertyShorthandAvailable(backgroundShorthand())) { |
| String backgroundValue = getPropertyValue(CSSPropertyBackground); |
| bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage); |
| result.append(getPropertyText(CSSPropertyBackground, backgroundValue, isImportant, numDecls++)); |
| return; |
| } |
| if (shorthandHasOnlyInitialOrInheritedValue(backgroundShorthand())) { |
| const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundImage); |
| bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundImage); |
| result.append(getPropertyText(CSSPropertyBackground, value->cssText(), isImportant, numDecls++)); |
| return; |
| } |
| |
| // backgroundShorthandProperty without layered shorhand properties |
| const CSSPropertyID backgroundPropertyIds[] = { |
| CSSPropertyBackgroundImage, |
| CSSPropertyBackgroundAttachment, |
| CSSPropertyBackgroundColor, |
| CSSPropertyBackgroundSize, |
| CSSPropertyBackgroundOrigin, |
| CSSPropertyBackgroundClip |
| }; |
| |
| for (unsigned i = 0; i < WTF_ARRAY_LENGTH(backgroundPropertyIds); ++i) { |
| CSSPropertyID propertyID = backgroundPropertyIds[i]; |
| const CSSValue* value = m_propertySet.getPropertyCSSValue(propertyID); |
| if (!value) |
| continue; |
| result.append(getPropertyText(propertyID, value->cssText(), m_propertySet.propertyIsImportant(propertyID), numDecls++)); |
| } |
| |
| // 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 UnitType::Pair. |
| if (shorthandHasOnlyInitialOrInheritedValue(backgroundPositionShorthand())) { |
| const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX); |
| bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX); |
| result.append(getPropertyText(CSSPropertyBackgroundPosition, value->cssText(), isImportant, numDecls++)); |
| } else if (isPropertyShorthandAvailable(backgroundPositionShorthand())) { |
| String positionValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundPosition); |
| bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX); |
| if (!positionValue.isNull()) |
| result.append(getPropertyText(CSSPropertyBackgroundPosition, positionValue, isImportant, numDecls++)); |
| } else { |
| // should check background-position-x or background-position-y. |
| if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionX)) { |
| if (!value->isImplicitInitialValue()) { |
| bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionX); |
| result.append(getPropertyText(CSSPropertyBackgroundPositionX, value->cssText(), isImportant, numDecls++)); |
| } |
| } |
| if (const CSSValue* value = m_propertySet.getPropertyCSSValue(CSSPropertyBackgroundPositionY)) { |
| if (!value->isImplicitInitialValue()) { |
| bool isImportant = m_propertySet.propertyIsImportant(CSSPropertyBackgroundPositionY); |
| result.append(getPropertyText(CSSPropertyBackgroundPositionY, value->cssText(), isImportant, numDecls++)); |
| } |
| } |
| } |
| |
| String repeatValue = m_propertySet.getPropertyValue(CSSPropertyBackgroundRepeat); |
| if (!repeatValue.isNull()) |
| result.append(getPropertyText(CSSPropertyBackgroundRepeat, repeatValue, m_propertySet.propertyIsImportant(CSSPropertyBackgroundRepeatX), numDecls++)); |
| } |
| |
| bool StylePropertySerializer::isPropertyShorthandAvailable(const StylePropertyShorthand& shorthand) const |
| { |
| ASSERT(shorthand.length() > 0); |
| |
| bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]); |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); |
| if (!value || (value->isInitialValue() && !value->isImplicitInitialValue()) || value->isInheritedValue()) |
| return false; |
| if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i])) |
| return false; |
| } |
| return true; |
| } |
| |
| bool StylePropertySerializer::shorthandHasOnlyInitialOrInheritedValue(const StylePropertyShorthand& shorthand) const |
| { |
| ASSERT(shorthand.length() > 0); |
| bool isImportant = m_propertySet.propertyIsImportant(shorthand.properties()[0]); |
| bool isInitialValue = true; |
| bool isInheritedValue = true; |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSValue* value = m_propertySet.getPropertyCSSValue(shorthand.properties()[i]); |
| if (!value) |
| return false; |
| if (!value->isInitialValue()) |
| isInitialValue = false; |
| if (!value->isInheritedValue()) |
| isInheritedValue = false; |
| if (isImportant != m_propertySet.propertyIsImportant(shorthand.properties()[i])) |
| return false; |
| } |
| return isInitialValue || isInheritedValue; |
| } |
| |
| } // namespace blink |