| /* |
| * (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 "third_party/blink/renderer/core/css/style_property_serializer.h" |
| |
| #include <bitset> |
| |
| #include "base/stl_util.h" |
| #include "third_party/blink/renderer/core/css/css_custom_property_declaration.h" |
| #include "third_party/blink/renderer/core/css/css_identifier_value.h" |
| #include "third_party/blink/renderer/core/css/css_markup.h" |
| #include "third_party/blink/renderer/core/css/css_pending_substitution_value.h" |
| #include "third_party/blink/renderer/core/css/css_value_pool.h" |
| #include "third_party/blink/renderer/core/css/properties/css_property.h" |
| #include "third_party/blink/renderer/core/css_value_keywords.h" |
| #include "third_party/blink/renderer/core/style_property_shorthand.h" |
| #include "third_party/blink/renderer/platform/runtime_enabled_features.h" |
| #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h" |
| #include "third_party/blink/renderer/platform/wtf/text/string_builder.h" |
| |
| namespace blink { |
| |
| StylePropertySerializer::CSSPropertyValueSetForSerializer:: |
| CSSPropertyValueSetForSerializer(const CSSPropertyValueSet& properties) |
| : property_set_(&properties), |
| all_index_(property_set_->FindPropertyIndex(CSSPropertyAll)), |
| need_to_expand_all_(false) { |
| if (!HasAllProperty()) |
| return; |
| |
| CSSPropertyValueSet::PropertyReference all_property = |
| property_set_->PropertyAt(all_index_); |
| for (unsigned i = 0; i < property_set_->PropertyCount(); ++i) { |
| CSSPropertyValueSet::PropertyReference property = |
| property_set_->PropertyAt(i); |
| if (property.Property().IsAffectedByAll()) { |
| if (all_property.IsImportant() && !property.IsImportant()) |
| continue; |
| if (static_cast<unsigned>(all_index_) >= i) |
| continue; |
| if (property.Value() == all_property.Value() && |
| property.IsImportant() == all_property.IsImportant()) |
| continue; |
| need_to_expand_all_ = true; |
| } |
| if (!isCSSPropertyIDWithName(property.Id())) |
| continue; |
| longhand_property_used_.set(property.Id() - firstCSSProperty); |
| } |
| } |
| |
| void StylePropertySerializer::CSSPropertyValueSetForSerializer::Trace( |
| blink::Visitor* visitor) { |
| visitor->Trace(property_set_); |
| } |
| |
| unsigned |
| StylePropertySerializer::CSSPropertyValueSetForSerializer::PropertyCount() |
| const { |
| if (!HasExpandedAllProperty()) |
| return property_set_->PropertyCount(); |
| return lastCSSProperty - firstCSSProperty + 1; |
| } |
| |
| StylePropertySerializer::PropertyValueForSerializer |
| StylePropertySerializer::CSSPropertyValueSetForSerializer::PropertyAt( |
| unsigned index) const { |
| if (!HasExpandedAllProperty()) |
| return StylePropertySerializer::PropertyValueForSerializer( |
| property_set_->PropertyAt(index)); |
| |
| CSSPropertyID property_id = |
| static_cast<CSSPropertyID>(index + firstCSSProperty); |
| DCHECK(isCSSPropertyIDWithName(property_id)); |
| if (longhand_property_used_.test(index)) { |
| int real_index = property_set_->FindPropertyIndex(property_id); |
| DCHECK_NE(real_index, -1); |
| return StylePropertySerializer::PropertyValueForSerializer( |
| property_set_->PropertyAt(real_index)); |
| } |
| |
| CSSPropertyValueSet::PropertyReference property = |
| property_set_->PropertyAt(all_index_); |
| return StylePropertySerializer::PropertyValueForSerializer( |
| CSSProperty::Get(property_id), &property.Value(), property.IsImportant()); |
| } |
| |
| bool StylePropertySerializer::CSSPropertyValueSetForSerializer:: |
| ShouldProcessPropertyAt(unsigned index) const { |
| // CSSPropertyValueSet 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 (!need_to_expand_all_) { |
| CSSPropertyValueSet::PropertyReference property = |
| property_set_->PropertyAt(index); |
| if (property.Property().IDEquals(CSSPropertyAll) || |
| !property.Property().IsAffectedByAll()) |
| return true; |
| if (!isCSSPropertyIDWithName(property.Id())) |
| return false; |
| return longhand_property_used_.test(property.Id() - firstCSSProperty); |
| } |
| |
| CSSPropertyID property_id = |
| static_cast<CSSPropertyID>(index + firstCSSProperty); |
| DCHECK(isCSSPropertyIDWithName(property_id)); |
| const CSSProperty& property_class = |
| CSSProperty::Get(resolveCSSPropertyID(property_id)); |
| |
| // 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 (property_class.IsShorthand() || property_class.IDEquals(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. https://drafts.csswg.org/css-cascade/#all-shorthand |
| if (!property_class.IsAffectedByAll()) |
| return longhand_property_used_.test(index); |
| |
| return true; |
| } |
| |
| int StylePropertySerializer::CSSPropertyValueSetForSerializer:: |
| FindPropertyIndex(const CSSProperty& property) const { |
| CSSPropertyID property_id = property.PropertyID(); |
| if (!HasExpandedAllProperty()) |
| return property_set_->FindPropertyIndex(property_id); |
| return property_id - firstCSSProperty; |
| } |
| |
| const CSSValue* |
| StylePropertySerializer::CSSPropertyValueSetForSerializer::GetPropertyCSSValue( |
| const CSSProperty& property) const { |
| int index = FindPropertyIndex(property); |
| if (index == -1) |
| return nullptr; |
| StylePropertySerializer::PropertyValueForSerializer value = PropertyAt(index); |
| return value.Value(); |
| } |
| |
| bool StylePropertySerializer::CSSPropertyValueSetForSerializer:: |
| IsDescriptorContext() const { |
| return property_set_->CssParserMode() == kCSSViewportRuleMode || |
| property_set_->CssParserMode() == kCSSFontFaceRuleMode; |
| } |
| |
| StylePropertySerializer::StylePropertySerializer( |
| const CSSPropertyValueSet& properties) |
| : property_set_(properties) {} |
| |
| String StylePropertySerializer::GetCustomPropertyText( |
| const PropertyValueForSerializer& property, |
| bool is_not_first_decl) const { |
| DCHECK_EQ(property.Property().PropertyID(), CSSPropertyVariable); |
| StringBuilder result; |
| if (is_not_first_decl) |
| result.Append(' '); |
| const auto* value = To<CSSCustomPropertyDeclaration>(property.Value()); |
| SerializeIdentifier(value->GetName(), result, is_not_first_decl); |
| result.Append(':'); |
| if (!value->Value()) |
| result.Append(' '); |
| result.Append(value->CustomCSSText()); |
| if (property.IsImportant()) |
| result.Append(" !important"); |
| result.Append(';'); |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::GetPropertyText(const CSSProperty& property, |
| const String& value, |
| bool is_important, |
| bool is_not_first_decl) const { |
| StringBuilder result; |
| if (is_not_first_decl) |
| result.Append(' '); |
| result.Append(property.GetPropertyName()); |
| result.Append(": "); |
| result.Append(value); |
| if (is_important) |
| result.Append(" !important"); |
| result.Append(';'); |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::AsText() const { |
| StringBuilder result; |
| |
| std::bitset<numCSSProperties> longhand_serialized; |
| std::bitset<numCSSProperties> shorthand_appeared; |
| |
| unsigned size = property_set_.PropertyCount(); |
| unsigned num_decls = 0; |
| for (unsigned n = 0; n < size; ++n) { |
| if (!property_set_.ShouldProcessPropertyAt(n)) |
| continue; |
| |
| StylePropertySerializer::PropertyValueForSerializer property = |
| property_set_.PropertyAt(n); |
| const CSSProperty& property_class = property.Property(); |
| CSSPropertyID property_id = property_class.PropertyID(); |
| |
| // Only enabled properties should be part of the style. |
| DCHECK(property_class.IsEnabled()); |
| // All shorthand properties should have been expanded at parse time. |
| DCHECK(property_set_.IsDescriptorContext() || |
| (property_class.IsProperty() && !property_class.IsShorthand())); |
| DCHECK(!property_set_.IsDescriptorContext() || |
| property_class.IsDescriptor()); |
| |
| switch (property_id) { |
| case CSSPropertyVariable: |
| result.Append(GetCustomPropertyText(property, num_decls++)); |
| continue; |
| case CSSPropertyAll: |
| result.Append(GetPropertyText(property_class, |
| property.Value()->CssText(), |
| property.IsImportant(), num_decls++)); |
| continue; |
| default: |
| break; |
| } |
| if (longhand_serialized.test(property_id - firstCSSProperty)) |
| continue; |
| |
| Vector<StylePropertyShorthand, 4> shorthands; |
| getMatchingShorthandsForLonghand(property_id, &shorthands); |
| bool serialized_as_shorthand = false; |
| for (const StylePropertyShorthand& shorthand : shorthands) { |
| // Some aliases are implemented as a shorthand, in which case |
| // we prefer to not use the shorthand. |
| if (shorthand.length() == 1) |
| continue; |
| |
| CSSPropertyID shorthand_property = shorthand.id(); |
| int shorthand_property_index = shorthand_property - firstCSSProperty; |
| // We already tried serializing as this shorthand |
| if (shorthand_appeared.test(shorthand_property_index)) |
| continue; |
| |
| shorthand_appeared.set(shorthand_property_index); |
| bool serialized_other_longhand = false; |
| for (unsigned i = 0; i < shorthand.length(); i++) { |
| if (longhand_serialized.test(shorthand.properties()[i]->PropertyID() - |
| firstCSSProperty)) { |
| serialized_other_longhand = true; |
| break; |
| } |
| } |
| if (serialized_other_longhand) |
| continue; |
| |
| String shorthand_result = SerializeShorthand(shorthand_property); |
| if (shorthand_result.IsEmpty()) |
| continue; |
| |
| result.Append(GetPropertyText(CSSProperty::Get(shorthand_property), |
| shorthand_result, property.IsImportant(), |
| num_decls++)); |
| serialized_as_shorthand = true; |
| for (unsigned i = 0; i < shorthand.length(); i++) { |
| longhand_serialized.set(shorthand.properties()[i]->PropertyID() - |
| firstCSSProperty); |
| } |
| break; |
| } |
| |
| if (serialized_as_shorthand) |
| continue; |
| |
| result.Append(GetPropertyText(property_class, property.Value()->CssText(), |
| property.IsImportant(), num_decls++)); |
| } |
| |
| DCHECK(!num_decls ^ !result.IsEmpty()); |
| return result.ToString(); |
| } |
| |
| // As per css-cascade, shorthands do not expand longhands to the value |
| // "initial", except when the shorthand is set to "initial", instead |
| // setting "missing" sub-properties to their initial values. This means |
| // that a shorthand can never represent a list of subproperties where |
| // some are "initial" and some are not, and so serialization should |
| // always fail in these cases (as per cssom). However we currently use |
| // "initial" instead of the initial values for certain shorthands, so |
| // these are special-cased here. |
| // TODO(timloh): Don't use "initial" in shorthands and remove this |
| // special-casing |
| static bool AllowInitialInShorthand(CSSPropertyID property_id) { |
| switch (property_id) { |
| case CSSPropertyBackground: |
| case CSSPropertyBorder: |
| case CSSPropertyBorderTop: |
| case CSSPropertyBorderRight: |
| case CSSPropertyBorderBottom: |
| case CSSPropertyBorderLeft: |
| case CSSPropertyOutline: |
| case CSSPropertyColumnRule: |
| case CSSPropertyColumns: |
| case CSSPropertyFlex: |
| case CSSPropertyFlexFlow: |
| case CSSPropertyGridColumn: |
| case CSSPropertyGridRow: |
| case CSSPropertyGridArea: |
| case CSSPropertyGap: |
| case CSSPropertyListStyle: |
| case CSSPropertyOffset: |
| case CSSPropertyTextDecoration: |
| case CSSPropertyWebkitMarginCollapse: |
| case CSSPropertyWebkitMask: |
| case CSSPropertyWebkitTextEmphasis: |
| case CSSPropertyWebkitTextStroke: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| String StylePropertySerializer::CommonShorthandChecks( |
| const StylePropertyShorthand& shorthand) const { |
| int longhand_count = shorthand.length(); |
| DCHECK_LE(longhand_count, 17); |
| const CSSValue* longhands[17] = {}; |
| |
| bool has_important = false; |
| bool has_non_important = false; |
| |
| for (int i = 0; i < longhand_count; i++) { |
| int index = property_set_.FindPropertyIndex(*shorthand.properties()[i]); |
| if (index == -1) |
| return g_empty_string; |
| PropertyValueForSerializer value = property_set_.PropertyAt(index); |
| |
| has_important |= value.IsImportant(); |
| has_non_important |= !value.IsImportant(); |
| longhands[i] = value.Value(); |
| } |
| |
| if (has_important && has_non_important) |
| return g_empty_string; |
| |
| if (longhands[0]->IsCSSWideKeyword() || |
| longhands[0]->IsPendingSubstitutionValue()) { |
| bool success = true; |
| for (int i = 1; i < longhand_count; i++) { |
| if (!DataEquivalent(longhands[i], longhands[0])) { |
| // This should just return emptyString but some shorthands currently |
| // allow 'initial' for their longhands. |
| success = false; |
| break; |
| } |
| } |
| if (success) { |
| if (const auto* substitution_value = |
| DynamicTo<CSSPendingSubstitutionValue>(longhands[0])) { |
| if (substitution_value->ShorthandPropertyId() != shorthand.id()) |
| return g_empty_string; |
| return substitution_value->ShorthandValue()->CssText(); |
| } |
| return longhands[0]->CssText(); |
| } |
| } |
| |
| bool allow_initial = AllowInitialInShorthand(shorthand.id()); |
| for (int i = 0; i < longhand_count; i++) { |
| const CSSValue& value = *longhands[i]; |
| if (!allow_initial && value.IsInitialValue()) |
| return g_empty_string; |
| if (value.IsInheritedValue() || value.IsUnsetValue() || |
| value.IsPendingSubstitutionValue()) |
| return g_empty_string; |
| if (value.IsVariableReferenceValue()) |
| return g_empty_string; |
| } |
| |
| return String(); |
| } |
| |
| String StylePropertySerializer::SerializeShorthand( |
| CSSPropertyID property_id) const { |
| const StylePropertyShorthand& shorthand = shorthandForProperty(property_id); |
| DCHECK(shorthand.length()); |
| |
| String result = CommonShorthandChecks(shorthand); |
| if (!result.IsNull()) |
| return result; |
| |
| switch (property_id) { |
| case CSSPropertyAnimation: |
| return GetLayeredShorthandValue(animationShorthand()); |
| case CSSPropertyBorderSpacing: |
| return Get2Values(borderSpacingShorthand()); |
| case CSSPropertyBackgroundPosition: |
| return GetLayeredShorthandValue(backgroundPositionShorthand()); |
| case CSSPropertyBackgroundRepeat: |
| return BackgroundRepeatPropertyValue(); |
| case CSSPropertyBackground: |
| return GetLayeredShorthandValue(backgroundShorthand()); |
| case CSSPropertyBorder: |
| return BorderPropertyValue(borderWidthShorthand(), borderStyleShorthand(), |
| borderColorShorthand()); |
| case CSSPropertyBorderImage: |
| return BorderImagePropertyValue(); |
| case CSSPropertyBorderTop: |
| return GetShorthandValue(borderTopShorthand()); |
| case CSSPropertyBorderRight: |
| return GetShorthandValue(borderRightShorthand()); |
| case CSSPropertyBorderBottom: |
| return GetShorthandValue(borderBottomShorthand()); |
| case CSSPropertyBorderLeft: |
| return GetShorthandValue(borderLeftShorthand()); |
| case CSSPropertyBorderBlock: |
| return BorderPropertyValue(borderBlockWidthShorthand(), |
| borderBlockStyleShorthand(), |
| borderBlockColorShorthand()); |
| case CSSPropertyBorderBlockColor: |
| return Get2Values(borderBlockColorShorthand()); |
| case CSSPropertyBorderBlockStyle: |
| return Get2Values(borderBlockStyleShorthand()); |
| case CSSPropertyBorderBlockWidth: |
| return Get2Values(borderBlockWidthShorthand()); |
| case CSSPropertyBorderBlockStart: |
| return GetShorthandValue(borderBlockStartShorthand()); |
| case CSSPropertyBorderBlockEnd: |
| return GetShorthandValue(borderBlockEndShorthand()); |
| case CSSPropertyBorderInline: |
| return BorderPropertyValue(borderInlineWidthShorthand(), |
| borderInlineStyleShorthand(), |
| borderInlineColorShorthand()); |
| case CSSPropertyBorderInlineColor: |
| return Get2Values(borderInlineColorShorthand()); |
| case CSSPropertyBorderInlineStyle: |
| return Get2Values(borderInlineStyleShorthand()); |
| case CSSPropertyBorderInlineWidth: |
| return Get2Values(borderInlineWidthShorthand()); |
| case CSSPropertyBorderInlineStart: |
| return GetShorthandValue(borderInlineStartShorthand()); |
| case CSSPropertyBorderInlineEnd: |
| return GetShorthandValue(borderInlineEndShorthand()); |
| case CSSPropertyOutline: |
| return GetShorthandValue(outlineShorthand()); |
| case CSSPropertyBorderColor: |
| return Get4Values(borderColorShorthand()); |
| case CSSPropertyBorderWidth: |
| return Get4Values(borderWidthShorthand()); |
| case CSSPropertyBorderStyle: |
| return Get4Values(borderStyleShorthand()); |
| case CSSPropertyColumnRule: |
| return GetShorthandValue(columnRuleShorthand()); |
| case CSSPropertyColumns: |
| return GetShorthandValue(columnsShorthand()); |
| case CSSPropertyFlex: |
| return GetShorthandValue(flexShorthand()); |
| case CSSPropertyFlexFlow: |
| return GetShorthandValue(flexFlowShorthand()); |
| case CSSPropertyGridColumn: |
| return GetShorthandValue(gridColumnShorthand(), " / "); |
| case CSSPropertyGridRow: |
| return GetShorthandValue(gridRowShorthand(), " / "); |
| case CSSPropertyGridArea: |
| return GetShorthandValue(gridAreaShorthand(), " / "); |
| case CSSPropertyGap: |
| return GetShorthandValue(gapShorthand()); |
| case CSSPropertyInset: |
| return Get4Values(insetShorthand()); |
| case CSSPropertyInsetBlock: |
| return Get2Values(insetBlockShorthand()); |
| case CSSPropertyInsetInline: |
| return Get2Values(insetInlineShorthand()); |
| case CSSPropertyPlaceContent: |
| return Get2Values(placeContentShorthand()); |
| case CSSPropertyPlaceItems: |
| return Get2Values(placeItemsShorthand()); |
| case CSSPropertyPlaceSelf: |
| return Get2Values(placeSelfShorthand()); |
| case CSSPropertyFont: |
| return FontValue(); |
| case CSSPropertyFontVariant: |
| return FontVariantValue(); |
| case CSSPropertyMargin: |
| return Get4Values(marginShorthand()); |
| case CSSPropertyMarginBlock: |
| return Get2Values(marginBlockShorthand()); |
| case CSSPropertyMarginInline: |
| return Get2Values(marginInlineShorthand()); |
| case CSSPropertyOffset: |
| return OffsetValue(); |
| case CSSPropertyWebkitMarginCollapse: |
| return GetShorthandValue(webkitMarginCollapseShorthand()); |
| case CSSPropertyOverflow: |
| return Get2Values(overflowShorthand()); |
| case CSSPropertyOverscrollBehavior: |
| return GetShorthandValue(overscrollBehaviorShorthand()); |
| case CSSPropertyPadding: |
| return Get4Values(paddingShorthand()); |
| case CSSPropertyPaddingBlock: |
| return Get2Values(paddingBlockShorthand()); |
| case CSSPropertyPaddingInline: |
| return Get2Values(paddingInlineShorthand()); |
| case CSSPropertyTextDecoration: |
| return GetShorthandValue(textDecorationShorthand()); |
| 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 = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerStart())) |
| return value->CssText(); |
| return String(); |
| } |
| case CSSPropertyBorderRadius: |
| return Get4Values(borderRadiusShorthand()); |
| case CSSPropertyScrollPadding: |
| return Get4Values(scrollPaddingShorthand()); |
| case CSSPropertyScrollPaddingBlock: |
| return Get2Values(scrollPaddingBlockShorthand()); |
| case CSSPropertyScrollPaddingInline: |
| return Get2Values(scrollPaddingInlineShorthand()); |
| case CSSPropertyScrollMargin: |
| return Get4Values(scrollMarginShorthand()); |
| case CSSPropertyScrollMarginBlock: |
| return Get2Values(scrollMarginBlockShorthand()); |
| case CSSPropertyScrollMarginInline: |
| return Get2Values(scrollMarginInlineShorthand()); |
| case CSSPropertyPageBreakAfter: |
| return PageBreakPropertyValue(pageBreakAfterShorthand()); |
| case CSSPropertyPageBreakBefore: |
| return PageBreakPropertyValue(pageBreakBeforeShorthand()); |
| case CSSPropertyPageBreakInside: |
| return PageBreakPropertyValue(pageBreakInsideShorthand()); |
| default: |
| return String(); |
| } |
| } |
| |
| // The font shorthand only allows keyword font-stretch values. Thus, we check if |
| // a percentage value can be parsed as a keyword, and if so, serialize it as |
| // that keyword. |
| const CSSValue* GetFontStretchKeyword(const CSSValue* font_stretch_value) { |
| if (font_stretch_value->IsIdentifierValue()) |
| return font_stretch_value; |
| if (font_stretch_value->IsPrimitiveValue()) { |
| double value = ToCSSPrimitiveValue(font_stretch_value)->GetDoubleValue(); |
| if (value == 50) |
| return CSSIdentifierValue::Create(CSSValueUltraCondensed); |
| if (value == 62.5) |
| return CSSIdentifierValue::Create(CSSValueExtraCondensed); |
| if (value == 75) |
| return CSSIdentifierValue::Create(CSSValueCondensed); |
| if (value == 87.5) |
| return CSSIdentifierValue::Create(CSSValueSemiCondensed); |
| if (value == 100) |
| return CSSIdentifierValue::Create(CSSValueNormal); |
| if (value == 112.5) |
| return CSSIdentifierValue::Create(CSSValueSemiExpanded); |
| if (value == 125) |
| return CSSIdentifierValue::Create(CSSValueExpanded); |
| if (value == 150) |
| return CSSIdentifierValue::Create(CSSValueExtraExpanded); |
| if (value == 200) |
| return CSSIdentifierValue::Create(CSSValueUltraExpanded); |
| } |
| return nullptr; |
| } |
| |
| // Returns false if the value cannot be represented in the font shorthand |
| bool StylePropertySerializer::AppendFontLonghandValueIfNotNormal( |
| const CSSProperty& property, |
| StringBuilder& result) const { |
| int found_property_index = property_set_.FindPropertyIndex(property); |
| DCHECK_NE(found_property_index, -1); |
| |
| const CSSValue* val = property_set_.PropertyAt(found_property_index).Value(); |
| if (property.IDEquals(CSSPropertyFontStretch)) { |
| const CSSValue* keyword = GetFontStretchKeyword(val); |
| if (!keyword) |
| return false; |
| val = keyword; |
| } |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(val); |
| if (identifier_value && identifier_value->GetValueID() == CSSValueNormal) |
| return true; |
| |
| char prefix = '\0'; |
| switch (property.PropertyID()) { |
| case CSSPropertyFontStyle: |
| break; // No prefix. |
| case CSSPropertyFontFamily: |
| case CSSPropertyFontStretch: |
| case CSSPropertyFontVariantCaps: |
| case CSSPropertyFontVariantLigatures: |
| case CSSPropertyFontVariantNumeric: |
| case CSSPropertyFontVariantEastAsian: |
| case CSSPropertyFontWeight: |
| prefix = ' '; |
| break; |
| case CSSPropertyLineHeight: |
| prefix = '/'; |
| break; |
| default: |
| NOTREACHED(); |
| } |
| |
| if (prefix && !result.IsEmpty()) |
| result.Append(prefix); |
| |
| String value; |
| // In the font-variant shorthand a "none" ligatures value needs to be |
| // expanded. |
| if (property.IDEquals(CSSPropertyFontVariantLigatures) && identifier_value && |
| identifier_value->GetValueID() == CSSValueNone) { |
| value = |
| "no-common-ligatures no-discretionary-ligatures " |
| "no-historical-ligatures no-contextual"; |
| } else { |
| value = val->CssText(); |
| } |
| |
| result.Append(value); |
| return true; |
| } |
| |
| String StylePropertySerializer::FontValue() const { |
| int font_size_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontSize()); |
| int font_family_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontFamily()); |
| int font_variant_caps_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariantCaps()); |
| int font_variant_ligatures_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariantLigatures()); |
| int font_variant_numeric_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariantNumeric()); |
| int font_variant_east_asian_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariantEastAsian()); |
| DCHECK_NE(font_size_property_index, -1); |
| DCHECK_NE(font_family_property_index, -1); |
| DCHECK_NE(font_variant_caps_property_index, -1); |
| DCHECK_NE(font_variant_ligatures_property_index, -1); |
| DCHECK_NE(font_variant_numeric_property_index, -1); |
| DCHECK_NE(font_variant_east_asian_property_index, -1); |
| |
| PropertyValueForSerializer font_size_property = |
| property_set_.PropertyAt(font_size_property_index); |
| PropertyValueForSerializer font_family_property = |
| property_set_.PropertyAt(font_family_property_index); |
| PropertyValueForSerializer font_variant_caps_property = |
| property_set_.PropertyAt(font_variant_caps_property_index); |
| PropertyValueForSerializer font_variant_ligatures_property = |
| property_set_.PropertyAt(font_variant_ligatures_property_index); |
| PropertyValueForSerializer font_variant_numeric_property = |
| property_set_.PropertyAt(font_variant_numeric_property_index); |
| PropertyValueForSerializer font_variant_east_asian_property = |
| property_set_.PropertyAt(font_variant_east_asian_property_index); |
| |
| // Check that non-initial font-variant subproperties are not conflicting with |
| // this serialization. |
| const CSSValue* ligatures_value = font_variant_ligatures_property.Value(); |
| const CSSValue* numeric_value = font_variant_numeric_property.Value(); |
| const CSSValue* east_asian_value = font_variant_east_asian_property.Value(); |
| |
| auto* ligatures_identifier_value = |
| DynamicTo<CSSIdentifierValue>(ligatures_value); |
| if ((ligatures_identifier_value && |
| ligatures_identifier_value->GetValueID() != CSSValueNormal) || |
| ligatures_value->IsValueList()) |
| return g_empty_string; |
| |
| auto* numeric_identifier_value = DynamicTo<CSSIdentifierValue>(numeric_value); |
| if ((numeric_identifier_value && |
| numeric_identifier_value->GetValueID() != CSSValueNormal) || |
| numeric_value->IsValueList()) |
| return g_empty_string; |
| |
| auto* east_asian_identifier_value = |
| DynamicTo<CSSIdentifierValue>(east_asian_value); |
| if ((east_asian_identifier_value && |
| east_asian_identifier_value->GetValueID() != CSSValueNormal) || |
| east_asian_value->IsValueList()) |
| return g_empty_string; |
| |
| StringBuilder result; |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontStyle(), result); |
| |
| const CSSValue* val = font_variant_caps_property.Value(); |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(val); |
| if (identifier_value && |
| (identifier_value->GetValueID() != CSSValueSmallCaps && |
| identifier_value->GetValueID() != CSSValueNormal)) |
| return g_empty_string; |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantCaps(), result); |
| |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontWeight(), result); |
| bool font_stretch_valid = |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontStretch(), result); |
| if (!font_stretch_valid) |
| return String(); |
| if (!result.IsEmpty()) |
| result.Append(' '); |
| result.Append(font_size_property.Value()->CssText()); |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyLineHeight(), result); |
| if (!result.IsEmpty()) |
| result.Append(' '); |
| result.Append(font_family_property.Value()->CssText()); |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::FontVariantValue() const { |
| StringBuilder result; |
| |
| // TODO(drott): Decide how we want to return ligature values in shorthands, |
| // reduced to "none" or spelled out, filed as W3C bug: |
| // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29594 |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantLigatures(), |
| result); |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantCaps(), result); |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantNumeric(), |
| result); |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantEastAsian(), |
| result); |
| |
| if (result.IsEmpty()) { |
| return "normal"; |
| } |
| |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::OffsetValue() const { |
| StringBuilder result; |
| if (RuntimeEnabledFeatures::CSSOffsetPositionAnchorEnabled()) { |
| const CSSValue* position = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetPosition()); |
| if (!position->IsInitialValue()) { |
| result.Append(position->CssText()); |
| } |
| } |
| const CSSValue* path = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetPath()); |
| const CSSValue* distance = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetDistance()); |
| const CSSValue* rotate = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetRotate()); |
| if (!path->IsInitialValue()) { |
| if (!result.IsEmpty()) |
| result.Append(" "); |
| result.Append(path->CssText()); |
| if (!distance->IsInitialValue()) { |
| result.Append(" "); |
| result.Append(distance->CssText()); |
| } |
| if (!rotate->IsInitialValue()) { |
| result.Append(" "); |
| result.Append(rotate->CssText()); |
| } |
| } else { |
| DCHECK(distance->IsInitialValue()); |
| DCHECK(rotate->IsInitialValue()); |
| } |
| if (RuntimeEnabledFeatures::CSSOffsetPositionAnchorEnabled()) { |
| const CSSValue* anchor = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetAnchor()); |
| if (!anchor->IsInitialValue()) { |
| result.Append(" / "); |
| result.Append(anchor->CssText()); |
| } |
| } |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::Get2Values( |
| const StylePropertyShorthand& shorthand) const { |
| // Assume the properties are in the usual order start, end. |
| int start_value_index = |
| property_set_.FindPropertyIndex(*shorthand.properties()[0]); |
| int end_value_index = |
| property_set_.FindPropertyIndex(*shorthand.properties()[1]); |
| |
| if (start_value_index == -1 || end_value_index == -1) |
| return String(); |
| |
| PropertyValueForSerializer start = |
| property_set_.PropertyAt(start_value_index); |
| PropertyValueForSerializer end = property_set_.PropertyAt(end_value_index); |
| |
| bool show_end = !DataEquivalent(start.Value(), end.Value()); |
| |
| StringBuilder result; |
| result.Append(start.Value()->CssText()); |
| if (show_end) { |
| result.Append(' '); |
| result.Append(end.Value()->CssText()); |
| } |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::Get4Values( |
| const StylePropertyShorthand& shorthand) const { |
| // Assume the properties are in the usual order top, right, bottom, left. |
| int top_value_index = |
| property_set_.FindPropertyIndex(*shorthand.properties()[0]); |
| int right_value_index = |
| property_set_.FindPropertyIndex(*shorthand.properties()[1]); |
| int bottom_value_index = |
| property_set_.FindPropertyIndex(*shorthand.properties()[2]); |
| int left_value_index = |
| property_set_.FindPropertyIndex(*shorthand.properties()[3]); |
| |
| if (top_value_index == -1 || right_value_index == -1 || |
| bottom_value_index == -1 || left_value_index == -1) |
| return String(); |
| |
| PropertyValueForSerializer top = property_set_.PropertyAt(top_value_index); |
| PropertyValueForSerializer right = |
| property_set_.PropertyAt(right_value_index); |
| PropertyValueForSerializer bottom = |
| property_set_.PropertyAt(bottom_value_index); |
| PropertyValueForSerializer left = property_set_.PropertyAt(left_value_index); |
| |
| bool show_left = !DataEquivalent(right.Value(), left.Value()); |
| bool show_bottom = !DataEquivalent(top.Value(), bottom.Value()) || show_left; |
| bool show_right = !DataEquivalent(top.Value(), right.Value()) || show_bottom; |
| |
| StringBuilder result; |
| result.Append(top.Value()->CssText()); |
| if (show_right) { |
| result.Append(' '); |
| result.Append(right.Value()->CssText()); |
| } |
| if (show_bottom) { |
| result.Append(' '); |
| result.Append(bottom.Value()->CssText()); |
| } |
| if (show_left) { |
| 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. |
| HeapVector<Member<const CSSValue>> values(size); |
| // If the below loop succeeds, there should always be at minimum 1 layer. |
| wtf_size_t num_layers = 1U; |
| |
| // TODO(timloh): Shouldn't we fail if the lists are differently sized, with |
| // the exception of background-color? |
| for (unsigned i = 0; i < size; i++) { |
| values[i] = property_set_.GetPropertyCSSValue(*shorthand.properties()[i]); |
| if (values[i]->IsBaseValueList()) { |
| const CSSValueList* value_list = ToCSSValueList(values[i]); |
| num_layers = std::max(num_layers, value_list->length()); |
| } |
| } |
| |
| StringBuilder result; |
| |
| // Now stitch the properties together. |
| for (wtf_size_t layer = 0; layer < num_layers; layer++) { |
| StringBuilder layer_result; |
| bool use_repeat_x_shorthand = false; |
| bool use_repeat_y_shorthand = false; |
| bool use_single_word_shorthand = false; |
| bool found_position_xcss_property = false; |
| bool found_position_ycss_property = false; |
| |
| for (unsigned property_index = 0; property_index < size; property_index++) { |
| const CSSValue* value = nullptr; |
| const CSSProperty* property = shorthand.properties()[property_index]; |
| |
| // Get a CSSValue for this property and layer. |
| if (values[property_index]->IsBaseValueList()) { |
| const CSSValueList* property_values = |
| ToCSSValueList(values[property_index]); |
| // There might not be an item for this layer for this property. |
| if (layer < property_values->length()) |
| value = &property_values->Item(layer); |
| } else if ((layer == 0 && |
| !property->IDEquals(CSSPropertyBackgroundColor)) || |
| (layer == num_layers - 1 && |
| property->IDEquals(CSSPropertyBackgroundColor))) { |
| // Singletons except background color belong in the 0th layer. |
| // Background color belongs in the last layer. |
| value = values[property_index]; |
| } |
| // No point proceeding if there's not a value to look at. |
| if (!value) |
| continue; |
| |
| // Special case for background-repeat. |
| if (property->IDEquals(CSSPropertyBackgroundRepeatX) || |
| property->IDEquals(CSSPropertyWebkitMaskRepeatX)) { |
| DCHECK(shorthand.properties()[property_index + 1]->IDEquals( |
| CSSPropertyBackgroundRepeatY) || |
| shorthand.properties()[property_index + 1]->IDEquals( |
| CSSPropertyWebkitMaskRepeatY)); |
| const CSSValue& y_value = |
| values[property_index + 1]->IsValueList() |
| ? ToCSSValueList(values[property_index + 1])->Item(layer) |
| : *values[property_index + 1]; |
| |
| // 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 (!IsA<CSSIdentifierValue>(value) || |
| !IsA<CSSIdentifierValue>(y_value)) |
| continue; |
| |
| CSSValueID x_id = To<CSSIdentifierValue>(value)->GetValueID(); |
| CSSValueID y_id = To<CSSIdentifierValue>(y_value).GetValueID(); |
| // Maybe advance propertyIndex to look at the next CSSValue in the list |
| // for the checks below. |
| if (x_id == y_id) { |
| use_single_word_shorthand = true; |
| property = shorthand.properties()[++property_index]; |
| } else if (x_id == CSSValueRepeat && y_id == CSSValueNoRepeat) { |
| use_repeat_x_shorthand = true; |
| property = shorthand.properties()[++property_index]; |
| } else if (x_id == CSSValueNoRepeat && y_id == CSSValueRepeat) { |
| use_repeat_y_shorthand = true; |
| property = shorthand.properties()[++property_index]; |
| } |
| } |
| |
| if (!value->IsInitialValue()) { |
| if (property->IDEquals(CSSPropertyBackgroundSize) || |
| property->IDEquals(CSSPropertyWebkitMaskSize)) { |
| if (found_position_ycss_property || found_position_xcss_property) |
| layer_result.Append(" / "); |
| else |
| layer_result.Append(" 0% 0% / "); |
| } else if (!layer_result.IsEmpty()) { |
| // Do this second to avoid ending up with an extra space in the output |
| // if we hit the continue above. |
| layer_result.Append(' '); |
| } |
| |
| if (use_repeat_x_shorthand) { |
| use_repeat_x_shorthand = false; |
| layer_result.Append(getValueName(CSSValueRepeatX)); |
| } else if (use_repeat_y_shorthand) { |
| use_repeat_y_shorthand = false; |
| layer_result.Append(getValueName(CSSValueRepeatY)); |
| } else { |
| if (use_single_word_shorthand) |
| use_single_word_shorthand = false; |
| layer_result.Append(value->CssText()); |
| } |
| if (property->IDEquals(CSSPropertyBackgroundPositionX) || |
| property->IDEquals(CSSPropertyWebkitMaskPositionX)) |
| found_position_xcss_property = true; |
| if (property->IDEquals(CSSPropertyBackgroundPositionY) || |
| property->IDEquals(CSSPropertyWebkitMaskPositionY)) { |
| found_position_ycss_property = 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 (!layer_result.IsEmpty()) { |
| if (!result.IsEmpty()) |
| result.Append(", "); |
| result.Append(layer_result); |
| } |
| } |
| |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::GetShorthandValue( |
| const StylePropertyShorthand& shorthand, |
| String separator) const { |
| StringBuilder result; |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSValue* value = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[i]); |
| String value_text = value->CssText(); |
| if (value->IsInitialValue()) |
| continue; |
| if (!result.IsEmpty()) |
| result.Append(separator); |
| result.Append(value_text); |
| } |
| 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; |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const CSSValue* value = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[i]); |
| // FIXME: CSSInitialValue::CssText should generate the right value. |
| String text = value->CssText(); |
| if (res.IsNull()) |
| res = text; |
| else if (res != text) |
| return String(); |
| } |
| return res; |
| } |
| |
| String StylePropertySerializer::BorderPropertyValue( |
| const StylePropertyShorthand& width, |
| const StylePropertyShorthand& style, |
| const StylePropertyShorthand& color) const { |
| const StylePropertyShorthand properties[3] = {width, style, color}; |
| StringBuilder result; |
| for (size_t i = 0; i < base::size(properties); ++i) { |
| String value = GetCommonValue(properties[i]); |
| if (value.IsNull()) |
| return String(); |
| if (value == "initial") |
| continue; |
| if (!result.IsEmpty()) |
| result.Append(' '); |
| result.Append(value); |
| } |
| return result.IsEmpty() ? String() : result.ToString(); |
| } |
| |
| String StylePropertySerializer::BorderImagePropertyValue() const { |
| StringBuilder result; |
| const CSSProperty* properties[] = { |
| &GetCSSPropertyBorderImageSource(), &GetCSSPropertyBorderImageSlice(), |
| &GetCSSPropertyBorderImageWidth(), &GetCSSPropertyBorderImageOutset(), |
| &GetCSSPropertyBorderImageRepeat()}; |
| size_t length = base::size(properties); |
| for (size_t i = 0; i < length; ++i) { |
| const CSSValue& value = *property_set_.GetPropertyCSSValue(*properties[i]); |
| if (!result.IsEmpty()) |
| result.Append(" "); |
| if (i == 2 || i == 3) |
| result.Append("/ "); |
| result.Append(value.CssText()); |
| } |
| return result.ToString(); |
| } |
| |
| static void AppendBackgroundRepeatValue(StringBuilder& builder, |
| const CSSValue& repeat_xcss_value, |
| const CSSValue& repeat_ycss_value) { |
| // FIXME: Ensure initial values do not appear in CSS_VALUE_LISTS. |
| DEFINE_STATIC_LOCAL(Persistent<CSSIdentifierValue>, initial_repeat_value, |
| (CSSIdentifierValue::Create(CSSValueRepeat))); |
| const CSSIdentifierValue& repeat_x = |
| repeat_xcss_value.IsInitialValue() |
| ? *initial_repeat_value |
| : To<CSSIdentifierValue>(repeat_xcss_value); |
| const CSSIdentifierValue& repeat_y = |
| repeat_ycss_value.IsInitialValue() |
| ? *initial_repeat_value |
| : To<CSSIdentifierValue>(repeat_ycss_value); |
| CSSValueID repeat_x_value_id = repeat_x.GetValueID(); |
| CSSValueID repeat_y_value_id = repeat_y.GetValueID(); |
| if (repeat_x_value_id == repeat_y_value_id) { |
| builder.Append(repeat_x.CssText()); |
| } else if (repeat_x_value_id == CSSValueNoRepeat && |
| repeat_y_value_id == CSSValueRepeat) { |
| builder.Append("repeat-y"); |
| } else if (repeat_x_value_id == CSSValueRepeat && |
| repeat_y_value_id == CSSValueNoRepeat) { |
| builder.Append("repeat-x"); |
| } else { |
| builder.Append(repeat_x.CssText()); |
| builder.Append(' '); |
| builder.Append(repeat_y.CssText()); |
| } |
| } |
| |
| String StylePropertySerializer::BackgroundRepeatPropertyValue() const { |
| const CSSValue& repeat_x = |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyBackgroundRepeatX()); |
| const CSSValue& repeat_y = |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyBackgroundRepeatY()); |
| |
| const CSSValueList* repeat_x_list = nullptr; |
| int repeat_x_length = 1; |
| if (repeat_x.IsValueList()) { |
| repeat_x_list = &ToCSSValueList(repeat_x); |
| repeat_x_length = repeat_x_list->length(); |
| } else if (!repeat_x.IsIdentifierValue()) { |
| return String(); |
| } |
| |
| const CSSValueList* repeat_y_list = nullptr; |
| int repeat_y_length = 1; |
| if (repeat_y.IsValueList()) { |
| repeat_y_list = &ToCSSValueList(repeat_y); |
| repeat_y_length = repeat_y_list->length(); |
| } else if (!repeat_y.IsIdentifierValue()) { |
| return String(); |
| } |
| |
| size_t shorthand_length = |
| lowestCommonMultiple(repeat_x_length, repeat_y_length); |
| StringBuilder builder; |
| for (size_t i = 0; i < shorthand_length; ++i) { |
| if (i) |
| builder.Append(", "); |
| |
| const CSSValue& x_value = |
| repeat_x_list ? repeat_x_list->Item(i % repeat_x_list->length()) |
| : repeat_x; |
| const CSSValue& y_value = |
| repeat_y_list ? repeat_y_list->Item(i % repeat_y_list->length()) |
| : repeat_y; |
| AppendBackgroundRepeatValue(builder, x_value, y_value); |
| } |
| return builder.ToString(); |
| } |
| |
| String StylePropertySerializer::PageBreakPropertyValue( |
| const StylePropertyShorthand& shorthand) const { |
| const CSSValue* value = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[0]); |
| CSSValueID value_id = To<CSSIdentifierValue>(value)->GetValueID(); |
| // https://drafts.csswg.org/css-break/#page-break-properties |
| if (value_id == CSSValuePage) |
| return "always"; |
| if (value_id == CSSValueAuto || value_id == CSSValueLeft || |
| value_id == CSSValueRight || value_id == CSSValueAvoid) |
| return value->CssText(); |
| return String(); |
| } |
| |
| } // namespace blink |