| /* |
| * (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/memory/values_equivalent.h" |
| #include "third_party/blink/renderer/core/animation/css/css_animation_data.h" |
| #include "third_party/blink/renderer/core/css/css_custom_property_declaration.h" |
| #include "third_party/blink/renderer/core/css/css_grid_template_areas_value.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_pending_system_font_value.h" |
| #include "third_party/blink/renderer/core/css/css_value_pair.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/properties/css_property_instances.h" |
| #include "third_party/blink/renderer/core/css/properties/longhand.h" |
| #include "third_party/blink/renderer/core/css/properties/longhands.h" |
| #include "third_party/blink/renderer/core/css/resolver/css_to_style_map.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" |
| |
| #include "base/logging.h" |
| #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| template <typename T> |
| T ConvertIdentifierTo(const CSSValue* value, T initial_value) { |
| if (const auto* ident = DynamicTo<CSSIdentifierValue>(value)) { |
| return ident->ConvertTo<T>(); |
| } |
| DCHECK(value->IsInitialValue()); |
| return initial_value; |
| } |
| |
| inline WhiteSpaceCollapse ToWhiteSpaceCollapse(const CSSValue* value) { |
| return ConvertIdentifierTo<WhiteSpaceCollapse>( |
| value, ComputedStyleInitialValues::InitialWhiteSpaceCollapse()); |
| } |
| |
| inline TextWrap ToTextWrap(const CSSValue* value) { |
| return ConvertIdentifierTo<TextWrap>( |
| value, ComputedStyleInitialValues::InitialTextWrap()); |
| } |
| |
| } // namespace |
| |
| StylePropertySerializer::CSSPropertyValueSetForSerializer:: |
| CSSPropertyValueSetForSerializer(const CSSPropertyValueSet& properties) |
| : property_set_(&properties), |
| all_index_(property_set_->FindPropertyIndex(CSSPropertyID::kAll)), |
| 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.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(GetCSSPropertyIDIndex(property.Id())); |
| } |
| } |
| |
| void StylePropertySerializer::CSSPropertyValueSetForSerializer::Trace( |
| blink::Visitor* visitor) const { |
| visitor->Trace(property_set_); |
| } |
| |
| unsigned |
| StylePropertySerializer::CSSPropertyValueSetForSerializer::PropertyCount() |
| const { |
| if (!HasExpandedAllProperty()) { |
| return property_set_->PropertyCount(); |
| } |
| return kIntLastCSSProperty - kIntFirstCSSProperty + 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 + kIntFirstCSSProperty); |
| 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).GetCSSPropertyName(), &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.Id() == CSSPropertyID::kAll || !property.IsAffectedByAll()) { |
| return true; |
| } |
| if (!IsCSSPropertyIDWithName(property.Id())) { |
| return false; |
| } |
| return longhand_property_used_.test(GetCSSPropertyIDIndex(property.Id())); |
| } |
| |
| CSSPropertyID property_id = |
| static_cast<CSSPropertyID>(index + kIntFirstCSSProperty); |
| 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(CSSPropertyID::kAll)) { |
| 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 GetCSSPropertyIDIndex(property_id); |
| } |
| |
| 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() == kCSSFontFaceRuleMode; |
| } |
| |
| StylePropertySerializer::StylePropertySerializer( |
| const CSSPropertyValueSet& properties) |
| : property_set_(properties) {} |
| |
| String StylePropertySerializer::GetCustomPropertyText( |
| const PropertyValueForSerializer& property, |
| bool is_not_first_decl) const { |
| DCHECK_EQ(property.Name().Id(), CSSPropertyID::kVariable); |
| StringBuilder result; |
| if (is_not_first_decl) { |
| result.Append(' '); |
| } |
| const CSSValue* value = property.Value(); |
| SerializeIdentifier(property.Name().ToAtomicString(), result, |
| is_not_first_decl); |
| result.Append(": "); |
| result.Append(value->CssText()); |
| if (property.IsImportant()) { |
| result.Append(" !important"); |
| } |
| result.Append(';'); |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::GetPropertyText(const CSSPropertyName& name, |
| const String& value, |
| bool is_important, |
| bool is_not_first_decl) const { |
| StringBuilder result; |
| if (is_not_first_decl) { |
| result.Append(' '); |
| } |
| result.Append(name.ToAtomicString()); |
| result.Append(": "); |
| result.Append(value); |
| if (is_important) { |
| result.Append(" !important"); |
| } |
| result.Append(';'); |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::AsText() const { |
| StringBuilder result; |
| |
| std::bitset<kNumCSSPropertyIDs> longhand_serialized; |
| std::bitset<kNumCSSPropertyIDs> 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 CSSPropertyName& name = property.Name(); |
| CSSPropertyID property_id = name.Id(); |
| |
| #if DCHECK_IS_ON() |
| if (property_id != CSSPropertyID::kVariable) { |
| const CSSProperty& property_class = CSSProperty::Get(property_id); |
| // Only web exposed properties should be part of the style. |
| DCHECK(property_class.IsWebExposed()); |
| // 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()); |
| } |
| #endif // DCHECK_IS_ON() |
| |
| switch (property_id) { |
| case CSSPropertyID::kVariable: |
| result.Append(GetCustomPropertyText(property, num_decls++)); |
| continue; |
| case CSSPropertyID::kAll: |
| result.Append(GetPropertyText(name, property.Value()->CssText(), |
| property.IsImportant(), num_decls++)); |
| continue; |
| default: |
| break; |
| } |
| if (longhand_serialized.test(GetCSSPropertyIDIndex(property_id))) { |
| 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 = GetCSSPropertyIDIndex(shorthand_property); |
| // 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(GetCSSPropertyIDIndex( |
| shorthand.properties()[i]->PropertyID()))) { |
| serialized_other_longhand = true; |
| break; |
| } |
| } |
| if (serialized_other_longhand) { |
| continue; |
| } |
| |
| String shorthand_result = SerializeShorthand(shorthand_property); |
| if (shorthand_result.empty()) { |
| continue; |
| } |
| |
| result.Append(GetPropertyText( |
| CSSProperty::Get(shorthand_property).GetCSSPropertyName(), |
| shorthand_result, property.IsImportant(), num_decls++)); |
| serialized_as_shorthand = true; |
| for (unsigned i = 0; i < shorthand.length(); i++) { |
| longhand_serialized.set( |
| GetCSSPropertyIDIndex(shorthand.properties()[i]->PropertyID())); |
| } |
| break; |
| } |
| |
| if (serialized_as_shorthand) { |
| continue; |
| } |
| |
| result.Append(GetPropertyText(name, property.Value()->CssText(), |
| property.IsImportant(), num_decls++)); |
| } |
| |
| DCHECK(!num_decls ^ !result.empty()); |
| return result.ReleaseString(); |
| } |
| |
| // 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 CSSPropertyID::kBackground: |
| case CSSPropertyID::kBorder: |
| case CSSPropertyID::kBorderTop: |
| case CSSPropertyID::kBorderRight: |
| case CSSPropertyID::kBorderBottom: |
| case CSSPropertyID::kBorderLeft: |
| case CSSPropertyID::kBorderBlockStart: |
| case CSSPropertyID::kBorderBlockEnd: |
| case CSSPropertyID::kBorderInlineStart: |
| case CSSPropertyID::kBorderInlineEnd: |
| case CSSPropertyID::kBorderBlock: |
| case CSSPropertyID::kBorderInline: |
| case CSSPropertyID::kOutline: |
| case CSSPropertyID::kColumnRule: |
| case CSSPropertyID::kColumns: |
| case CSSPropertyID::kGridColumn: |
| case CSSPropertyID::kGridRow: |
| case CSSPropertyID::kGridArea: |
| case CSSPropertyID::kGap: |
| case CSSPropertyID::kListStyle: |
| case CSSPropertyID::kOffset: |
| case CSSPropertyID::kTextDecoration: |
| case CSSPropertyID::kTextEmphasis: |
| case CSSPropertyID::kWebkitMask: |
| case CSSPropertyID::kWebkitTextStroke: |
| case CSSPropertyID::kAlternativeWhiteSpace: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| String StylePropertySerializer::CommonShorthandChecks( |
| const StylePropertyShorthand& shorthand) const { |
| unsigned longhand_count = shorthand.length(); |
| if (!longhand_count || longhand_count > kMaxShorthandExpansion) { |
| NOTREACHED(); |
| return g_empty_string; |
| } |
| |
| const CSSValue* longhands[kMaxShorthandExpansion] = {}; |
| |
| bool has_important = false; |
| bool has_non_important = false; |
| |
| for (unsigned 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 (unsigned i = 1; i < longhand_count; i++) { |
| if (!base::ValuesEquivalent(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<cssvalue::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 (unsigned i = 0; i < longhand_count; i++) { |
| const CSSValue& value = *longhands[i]; |
| if (!allow_initial && value.IsInitialValue()) { |
| return g_empty_string; |
| } |
| if ((value.IsCSSWideKeyword() && !value.IsInitialValue()) || |
| 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 CSSPropertyID::kAnimation: |
| return GetLayeredShorthandValue(animationShorthand()); |
| case CSSPropertyID::kAlternativeAnimationWithTimeline: |
| return GetLayeredShorthandValue( |
| alternativeAnimationWithTimelineShorthand()); |
| case CSSPropertyID::kAlternativeAnimationWithDelayStartEnd: |
| return GetLayeredShorthandValue( |
| alternativeAnimationWithDelayStartEndShorthand()); |
| case CSSPropertyID::kAlternativeAnimationDelay: |
| return AnimationDelayShorthandValue(); |
| case CSSPropertyID::kAnimationRange: |
| return AnimationRangeShorthandValue(); |
| case CSSPropertyID::kBorderSpacing: |
| return Get2Values(borderSpacingShorthand()); |
| case CSSPropertyID::kBackgroundPosition: |
| return GetLayeredShorthandValue(backgroundPositionShorthand()); |
| case CSSPropertyID::kBackgroundRepeat: |
| return BackgroundRepeatPropertyValue(); |
| case CSSPropertyID::kBackground: |
| return GetLayeredShorthandValue(backgroundShorthand()); |
| case CSSPropertyID::kBorder: |
| return BorderPropertyValue(borderWidthShorthand(), borderStyleShorthand(), |
| borderColorShorthand()); |
| case CSSPropertyID::kBorderImage: |
| return BorderImagePropertyValue(); |
| case CSSPropertyID::kBorderTop: |
| return GetShorthandValue(borderTopShorthand()); |
| case CSSPropertyID::kBorderRight: |
| return GetShorthandValue(borderRightShorthand()); |
| case CSSPropertyID::kBorderBottom: |
| return GetShorthandValue(borderBottomShorthand()); |
| case CSSPropertyID::kBorderLeft: |
| return GetShorthandValue(borderLeftShorthand()); |
| case CSSPropertyID::kBorderBlock: |
| return BorderPropertyValue(borderBlockWidthShorthand(), |
| borderBlockStyleShorthand(), |
| borderBlockColorShorthand()); |
| case CSSPropertyID::kBorderBlockColor: |
| return Get2Values(borderBlockColorShorthand()); |
| case CSSPropertyID::kBorderBlockStyle: |
| return Get2Values(borderBlockStyleShorthand()); |
| case CSSPropertyID::kBorderBlockWidth: |
| return Get2Values(borderBlockWidthShorthand()); |
| case CSSPropertyID::kBorderBlockStart: |
| return GetShorthandValue(borderBlockStartShorthand()); |
| case CSSPropertyID::kBorderBlockEnd: |
| return GetShorthandValue(borderBlockEndShorthand()); |
| case CSSPropertyID::kBorderInline: |
| return BorderPropertyValue(borderInlineWidthShorthand(), |
| borderInlineStyleShorthand(), |
| borderInlineColorShorthand()); |
| case CSSPropertyID::kBorderInlineColor: |
| return Get2Values(borderInlineColorShorthand()); |
| case CSSPropertyID::kBorderInlineStyle: |
| return Get2Values(borderInlineStyleShorthand()); |
| case CSSPropertyID::kBorderInlineWidth: |
| return Get2Values(borderInlineWidthShorthand()); |
| case CSSPropertyID::kBorderInlineStart: |
| return GetShorthandValue(borderInlineStartShorthand()); |
| case CSSPropertyID::kBorderInlineEnd: |
| return GetShorthandValue(borderInlineEndShorthand()); |
| case CSSPropertyID::kContainer: |
| return ContainerValue(); |
| case CSSPropertyID::kOutline: |
| return GetShorthandValue(outlineShorthand()); |
| case CSSPropertyID::kBorderColor: |
| return Get4Values(borderColorShorthand()); |
| case CSSPropertyID::kBorderWidth: |
| return Get4Values(borderWidthShorthand()); |
| case CSSPropertyID::kBorderStyle: |
| return Get4Values(borderStyleShorthand()); |
| case CSSPropertyID::kColumnRule: |
| return GetShorthandValue(columnRuleShorthand()); |
| case CSSPropertyID::kColumns: |
| return GetShorthandValueForColumns(columnsShorthand()); |
| case CSSPropertyID::kContainIntrinsicSize: |
| return ContainIntrinsicSizeValue(); |
| case CSSPropertyID::kFlex: |
| return GetShorthandValue(flexShorthand()); |
| case CSSPropertyID::kFlexFlow: |
| return GetShorthandValueForDoubleBarCombinator(flexFlowShorthand()); |
| case CSSPropertyID::kGrid: |
| return GetShorthandValueForGrid(gridShorthand()); |
| case CSSPropertyID::kGridTemplate: |
| return GetShorthandValueForGridTemplate(gridTemplateShorthand()); |
| case CSSPropertyID::kGridColumn: |
| return GetShorthandValue(gridColumnShorthand(), " / "); |
| case CSSPropertyID::kGridRow: |
| return GetShorthandValue(gridRowShorthand(), " / "); |
| case CSSPropertyID::kGridArea: |
| return GetShorthandValue(gridAreaShorthand(), " / "); |
| case CSSPropertyID::kGap: |
| return Get2Values(gapShorthand()); |
| case CSSPropertyID::kInset: |
| return Get4Values(insetShorthand()); |
| case CSSPropertyID::kInsetBlock: |
| return Get2Values(insetBlockShorthand()); |
| case CSSPropertyID::kInsetInline: |
| return Get2Values(insetInlineShorthand()); |
| case CSSPropertyID::kPlaceContent: |
| return Get2Values(placeContentShorthand()); |
| case CSSPropertyID::kPlaceItems: |
| return Get2Values(placeItemsShorthand()); |
| case CSSPropertyID::kPlaceSelf: |
| return Get2Values(placeSelfShorthand()); |
| case CSSPropertyID::kFont: |
| return FontValue(); |
| case CSSPropertyID::kFontSynthesis: |
| return FontSynthesisValue(); |
| case CSSPropertyID::kFontVariant: |
| return FontVariantValue(); |
| case CSSPropertyID::kMargin: |
| return Get4Values(marginShorthand()); |
| case CSSPropertyID::kMarginBlock: |
| return Get2Values(marginBlockShorthand()); |
| case CSSPropertyID::kMarginInline: |
| return Get2Values(marginInlineShorthand()); |
| case CSSPropertyID::kOffset: |
| return OffsetValue(); |
| case CSSPropertyID::kOverflow: |
| return Get2Values(overflowShorthand()); |
| case CSSPropertyID::kOverscrollBehavior: |
| return Get2Values(overscrollBehaviorShorthand()); |
| case CSSPropertyID::kPadding: |
| return Get4Values(paddingShorthand()); |
| case CSSPropertyID::kPaddingBlock: |
| return Get2Values(paddingBlockShorthand()); |
| case CSSPropertyID::kPaddingInline: |
| return Get2Values(paddingInlineShorthand()); |
| case CSSPropertyID::kTextDecoration: |
| return TextDecorationValue(); |
| case CSSPropertyID::kTransition: |
| return GetLayeredShorthandValue(transitionShorthand()); |
| case CSSPropertyID::kListStyle: |
| return GetShorthandValue(listStyleShorthand()); |
| case CSSPropertyID::kWebkitMaskPosition: |
| return GetLayeredShorthandValue(webkitMaskPositionShorthand()); |
| case CSSPropertyID::kWebkitMaskRepeat: |
| return GetLayeredShorthandValue(webkitMaskRepeatShorthand()); |
| case CSSPropertyID::kWebkitMask: |
| return GetLayeredShorthandValue(webkitMaskShorthand()); |
| case CSSPropertyID::kTextEmphasis: |
| return GetShorthandValue(textEmphasisShorthand()); |
| case CSSPropertyID::kWebkitTextStroke: |
| return GetShorthandValue(webkitTextStrokeShorthand()); |
| case CSSPropertyID::kMarker: { |
| if (const CSSValue* start = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerStart())) { |
| const CSSValue* mid = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerMid()); |
| const CSSValue* end = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyMarkerEnd()); |
| if (mid && end && *start == *mid && *start == *end) { |
| return start->CssText(); |
| } |
| } |
| return String(); |
| } |
| case CSSPropertyID::kBorderRadius: |
| return BorderRadiusValue(); |
| case CSSPropertyID::kScrollPadding: |
| return Get4Values(scrollPaddingShorthand()); |
| case CSSPropertyID::kScrollPaddingBlock: |
| return Get2Values(scrollPaddingBlockShorthand()); |
| case CSSPropertyID::kScrollPaddingInline: |
| return Get2Values(scrollPaddingInlineShorthand()); |
| case CSSPropertyID::kScrollMargin: |
| return Get4Values(scrollMarginShorthand()); |
| case CSSPropertyID::kScrollMarginBlock: |
| return Get2Values(scrollMarginBlockShorthand()); |
| case CSSPropertyID::kScrollMarginInline: |
| return Get2Values(scrollMarginInlineShorthand()); |
| case CSSPropertyID::kScrollTimeline: |
| return ScrollTimelineValue(); |
| case CSSPropertyID::kPageBreakAfter: |
| return PageBreakPropertyValue(pageBreakAfterShorthand()); |
| case CSSPropertyID::kPageBreakBefore: |
| return PageBreakPropertyValue(pageBreakBeforeShorthand()); |
| case CSSPropertyID::kPageBreakInside: |
| return PageBreakPropertyValue(pageBreakInsideShorthand()); |
| case CSSPropertyID::kToggle: { |
| const CSSValue* toggle_root = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyToggleRoot()); |
| DCHECK(toggle_root); |
| const CSSValue* toggle_trigger = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyToggleTrigger()); |
| DCHECK(toggle_trigger); |
| if (!IsValidToggleShorthand(toggle_root, toggle_trigger)) { |
| return g_empty_string; |
| } |
| return toggle_root->CssText(); |
| } |
| case CSSPropertyID::kViewTimeline: |
| return ViewTimelineValue(); |
| case CSSPropertyID::kAlternativeWhiteSpace: |
| return WhiteSpaceValue(); |
| case CSSPropertyID::kGridColumnGap: |
| case CSSPropertyID::kGridGap: |
| case CSSPropertyID::kGridRowGap: |
| case CSSPropertyID::kWebkitColumnBreakAfter: |
| case CSSPropertyID::kWebkitColumnBreakBefore: |
| case CSSPropertyID::kWebkitColumnBreakInside: |
| case CSSPropertyID::kWebkitMaskBoxImage: |
| // Temporary exceptions to the NOTREACHED() below. |
| // TODO(crbug.com/1316689): Write something real here. |
| return String(); |
| case CSSPropertyID::kScrollStart: |
| return ScrollStartValue(); |
| case CSSPropertyID::kScrollStartTarget: |
| return ScrollStartTargetValue(); |
| default: |
| NOTREACHED() |
| << "Shorthand property " |
| << CSSPropertyName(property_id).ToAtomicString() |
| << " must be handled in StylePropertySerializer::SerializeShorthand."; |
| 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 (IsA<CSSIdentifierValue>(font_stretch_value)) { |
| return font_stretch_value; |
| } |
| if (auto* primitive_value = |
| DynamicTo<CSSPrimitiveValue>(font_stretch_value)) { |
| double value = primitive_value->GetDoubleValue(); |
| if (value == 50) { |
| return CSSIdentifierValue::Create(CSSValueID::kUltraCondensed); |
| } |
| if (value == 62.5) { |
| return CSSIdentifierValue::Create(CSSValueID::kExtraCondensed); |
| } |
| if (value == 75) { |
| return CSSIdentifierValue::Create(CSSValueID::kCondensed); |
| } |
| if (value == 87.5) { |
| return CSSIdentifierValue::Create(CSSValueID::kSemiCondensed); |
| } |
| if (value == 100) { |
| return CSSIdentifierValue::Create(CSSValueID::kNormal); |
| } |
| if (value == 112.5) { |
| return CSSIdentifierValue::Create(CSSValueID::kSemiExpanded); |
| } |
| if (value == 125) { |
| return CSSIdentifierValue::Create(CSSValueID::kExpanded); |
| } |
| if (value == 150) { |
| return CSSIdentifierValue::Create(CSSValueID::kExtraExpanded); |
| } |
| if (value == 200) { |
| return CSSIdentifierValue::Create(CSSValueID::kUltraExpanded); |
| } |
| } |
| 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(CSSPropertyID::kFontStretch)) { |
| const CSSValue* keyword = GetFontStretchKeyword(val); |
| if (!keyword) { |
| return false; |
| } |
| val = keyword; |
| } |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(val); |
| if (identifier_value && |
| identifier_value->GetValueID() == CSSValueID::kNormal) { |
| return true; |
| } |
| |
| if (!result.empty()) { |
| switch (property.PropertyID()) { |
| case CSSPropertyID::kFontStyle: |
| break; // No prefix. |
| case CSSPropertyID::kFontFamily: |
| case CSSPropertyID::kFontStretch: |
| case CSSPropertyID::kFontVariantCaps: |
| case CSSPropertyID::kFontVariantLigatures: |
| case CSSPropertyID::kFontVariantNumeric: |
| case CSSPropertyID::kFontVariantEastAsian: |
| case CSSPropertyID::kFontVariantAlternates: |
| case CSSPropertyID::kFontVariantPosition: |
| case CSSPropertyID::kFontWeight: |
| result.Append(' '); |
| break; |
| case CSSPropertyID::kLineHeight: |
| result.Append(" / "); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| String value; |
| // In the font-variant shorthand a "none" ligatures value needs to be |
| // expanded. |
| if (property.IDEquals(CSSPropertyID::kFontVariantLigatures) && |
| identifier_value && identifier_value->GetValueID() == CSSValueID::kNone) { |
| value = |
| "no-common-ligatures no-discretionary-ligatures " |
| "no-historical-ligatures no-contextual"; |
| } else { |
| value = val->CssText(); |
| } |
| |
| result.Append(value); |
| return true; |
| } |
| |
| String StylePropertySerializer::ContainerValue() const { |
| CHECK_EQ(containerShorthand().length(), 2u); |
| CHECK_EQ(containerShorthand().properties()[0], |
| &GetCSSPropertyContainerName()); |
| CHECK_EQ(containerShorthand().properties()[1], |
| &GetCSSPropertyContainerType()); |
| |
| CSSValueList* list = CSSValueList::CreateSlashSeparated(); |
| |
| const CSSValue* name = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyContainerName()); |
| const CSSValue* type = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyContainerType()); |
| |
| DCHECK(name); |
| DCHECK(type); |
| |
| list->Append(*name); |
| |
| if (!(IsA<CSSIdentifierValue>(type) && |
| To<CSSIdentifierValue>(*type).GetValueID() == CSSValueID::kNormal)) { |
| list->Append(*type); |
| } |
| |
| return list->CssText(); |
| } |
| |
| namespace { |
| |
| CSSValue* TimelineValueItem(wtf_size_t index, |
| const CSSValueList& name_list, |
| const CSSValueList& axis_list, |
| const CSSValueList& attachment_list) { |
| DCHECK_LT(index, name_list.length()); |
| DCHECK_LT(index, axis_list.length()); |
| DCHECK_LT(index, attachment_list.length()); |
| |
| const CSSValue& name = name_list.Item(index); |
| const CSSValue& axis = axis_list.Item(index); |
| const CSSValue& attachment = attachment_list.Item(index); |
| |
| CSSValueList* list = CSSValueList::CreateSpaceSeparated(); |
| |
| // Note that the name part can never be omitted, since e.g. serializing |
| // "view-timeline:none inline" as "view-timeline:inline" doesn't roundtrip. |
| // (It would set view-timeline-name to inline). |
| list->Append(name); |
| |
| if (!(IsA<CSSIdentifierValue>(axis) && |
| To<CSSIdentifierValue>(axis).GetValueID() == CSSValueID::kBlock)) { |
| list->Append(axis); |
| } |
| if (!(IsA<CSSIdentifierValue>(attachment) && |
| To<CSSIdentifierValue>(attachment).GetValueID() == |
| CSSValueID::kLocal)) { |
| list->Append(attachment); |
| } |
| |
| return list; |
| } |
| |
| } // namespace |
| |
| String StylePropertySerializer::TimelineValue( |
| const StylePropertyShorthand& shorthand) const { |
| CHECK_EQ(shorthand.length(), 3u); |
| |
| const CSSValueList& name_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(*shorthand.properties()[0])); |
| const CSSValueList& axis_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(*shorthand.properties()[1])); |
| const CSSValueList& attachment_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(*shorthand.properties()[2])); |
| |
| // The scroll/view-timeline shorthand can not expand to longhands of two |
| // different lengths, so we can also not contract two different-longhands |
| // into a single shorthand. |
| if (name_list.length() != axis_list.length()) { |
| return ""; |
| } |
| if (name_list.length() != attachment_list.length()) { |
| return ""; |
| } |
| |
| CSSValueList* list = CSSValueList::CreateCommaSeparated(); |
| |
| for (wtf_size_t i = 0; i < name_list.length(); ++i) { |
| list->Append(*TimelineValueItem(i, name_list, axis_list, attachment_list)); |
| } |
| |
| return list->CssText(); |
| } |
| |
| String StylePropertySerializer::ScrollTimelineValue() const { |
| CHECK_EQ(scrollTimelineShorthand().length(), 3u); |
| CHECK_EQ(scrollTimelineShorthand().properties()[0], |
| &GetCSSPropertyScrollTimelineName()); |
| CHECK_EQ(scrollTimelineShorthand().properties()[1], |
| &GetCSSPropertyScrollTimelineAxis()); |
| CHECK_EQ(scrollTimelineShorthand().properties()[2], |
| &GetCSSPropertyScrollTimelineAttachment()); |
| return TimelineValue(scrollTimelineShorthand()); |
| } |
| |
| String StylePropertySerializer::ViewTimelineValue() const { |
| CHECK_EQ(viewTimelineShorthand().length(), 3u); |
| CHECK_EQ(viewTimelineShorthand().properties()[0], |
| &GetCSSPropertyViewTimelineName()); |
| CHECK_EQ(viewTimelineShorthand().properties()[1], |
| &GetCSSPropertyViewTimelineAxis()); |
| CHECK_EQ(viewTimelineShorthand().properties()[2], |
| &GetCSSPropertyViewTimelineAttachment()); |
| return TimelineValue(viewTimelineShorthand()); |
| } |
| |
| namespace { |
| |
| CSSValue* AnimationDelayShorthandValueItem(wtf_size_t index, |
| const CSSValueList& start_list, |
| const CSSValueList& end_list) { |
| DCHECK_LT(index, start_list.length()); |
| DCHECK_LT(index, end_list.length()); |
| |
| const CSSValue& start = start_list.Item(index); |
| const CSSValue& end = end_list.Item(index); |
| |
| CSSValueList* list = CSSValueList::CreateSpaceSeparated(); |
| |
| list->Append(start); |
| |
| if (const auto* primitive = DynamicTo<CSSPrimitiveValue>(end); |
| !primitive || !primitive->IsZero()) { |
| list->Append(end); |
| } |
| |
| return list; |
| } |
| |
| // Return the name and offset (in percent). This is useful for |
| // contracting '<somename> 0%' and '<somename> 100%' into just <somename>. |
| // |
| // If the offset is present, but not a <percentage>, -1 is returned as the |
| // offset. Otherwise (also in the 'normal' case), the `default_offset_percent` |
| // is returned. |
| std::pair<CSSValueID, double> GetTimelineRangePercent( |
| const CSSValue& value, |
| double default_offset_percent) { |
| const auto* list = DynamicTo<CSSValueList>(value); |
| if (!list) { |
| return {CSSValueID::kNormal, default_offset_percent}; |
| } |
| DCHECK_GE(list->length(), 1u); |
| DCHECK_LE(list->length(), 2u); |
| CSSValueID name = CSSValueID::kNormal; |
| double offset_percent = default_offset_percent; |
| |
| if (list->Item(0).IsIdentifierValue()) { |
| name = To<CSSIdentifierValue>(list->Item(0)).GetValueID(); |
| if (list->length() == 2u) { |
| const auto& offset = To<CSSPrimitiveValue>(list->Item(1)); |
| offset_percent = offset.IsPercentage() ? offset.GetValue<double>() : -1.0; |
| } |
| } else { |
| const auto& offset = To<CSSPrimitiveValue>(list->Item(0)); |
| offset_percent = offset.IsPercentage() ? offset.GetValue<double>() : -1.0; |
| } |
| |
| return {name, offset_percent}; |
| } |
| |
| CSSValue* AnimationRangeShorthandValueItem(wtf_size_t index, |
| const CSSValueList& start_list, |
| const CSSValueList& end_list) { |
| DCHECK_LT(index, start_list.length()); |
| DCHECK_LT(index, end_list.length()); |
| |
| const CSSValue& start = start_list.Item(index); |
| const CSSValue& end = end_list.Item(index); |
| |
| CSSValueList* list = CSSValueList::CreateSpaceSeparated(); |
| |
| list->Append(start); |
| |
| // The form "name X name 100%" must contract to "name X". |
| // |
| // https://github.com/w3c/csswg-drafts/issues/8438 |
| const auto& start_pair = GetTimelineRangePercent(start, 0.0); |
| const auto& end_pair = GetTimelineRangePercent(end, 100.0); |
| std::pair<CSSValueID, double> omittable_end = {start_pair.first, 100.0}; |
| if (end_pair != omittable_end) { |
| list->Append(end); |
| } |
| |
| return list; |
| } |
| |
| } // namespace |
| |
| String StylePropertySerializer::AnimationDelayShorthandValue() const { |
| CHECK_EQ(alternativeAnimationDelayShorthand().length(), 2u); |
| CHECK_EQ(alternativeAnimationDelayShorthand().properties()[0], |
| &GetCSSPropertyAnimationDelayStart()); |
| CHECK_EQ(alternativeAnimationDelayShorthand().properties()[1], |
| &GetCSSPropertyAnimationDelayEnd()); |
| |
| const CSSValueList& start_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationDelayStart())); |
| const CSSValueList& end_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationDelayEnd())); |
| |
| if (start_list.length() != end_list.length()) { |
| return ""; |
| } |
| |
| CSSValueList* list = CSSValueList::CreateCommaSeparated(); |
| |
| for (wtf_size_t i = 0; i < start_list.length(); ++i) { |
| list->Append(*AnimationDelayShorthandValueItem(i, start_list, end_list)); |
| } |
| |
| return list->CssText(); |
| } |
| |
| String StylePropertySerializer::AnimationRangeShorthandValue() const { |
| CHECK_EQ(animationRangeShorthand().length(), 2u); |
| CHECK_EQ(animationRangeShorthand().properties()[0], |
| &GetCSSPropertyAnimationRangeStart()); |
| CHECK_EQ(animationRangeShorthand().properties()[1], |
| &GetCSSPropertyAnimationRangeEnd()); |
| |
| const CSSValueList& start_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationRangeStart())); |
| const CSSValueList& end_list = To<CSSValueList>( |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyAnimationRangeEnd())); |
| |
| if (start_list.length() != end_list.length()) { |
| return ""; |
| } |
| |
| CSSValueList* list = CSSValueList::CreateCommaSeparated(); |
| |
| for (wtf_size_t i = 0; i < start_list.length(); ++i) { |
| list->Append(*AnimationRangeShorthandValueItem(i, start_list, end_list)); |
| } |
| |
| return list->CssText(); |
| } |
| |
| 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()); |
| int font_kerning_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontKerning()); |
| int font_optical_sizing_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontOpticalSizing()); |
| int font_variation_settings_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariationSettings()); |
| int font_feature_settings_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontFeatureSettings()); |
| 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); |
| DCHECK_NE(font_kerning_property_index, -1); |
| DCHECK_NE(font_optical_sizing_property_index, -1); |
| DCHECK_NE(font_variation_settings_property_index, -1); |
| DCHECK_NE(font_feature_settings_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); |
| PropertyValueForSerializer font_kerning_property = |
| property_set_.PropertyAt(font_kerning_property_index); |
| PropertyValueForSerializer font_optical_sizing_property = |
| property_set_.PropertyAt(font_optical_sizing_property_index); |
| PropertyValueForSerializer font_variation_settings_property = |
| property_set_.PropertyAt(font_variation_settings_property_index); |
| PropertyValueForSerializer font_feature_settings_property = |
| property_set_.PropertyAt(font_feature_settings_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(); |
| const CSSValue* feature_settings_value = |
| font_feature_settings_property.Value(); |
| const CSSValue* variation_settings_value = |
| font_variation_settings_property.Value(); |
| |
| auto IsPropertyNonInitial = [](const CSSValue& value, |
| const CSSValueID initial_value_id) { |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(value); |
| return (identifier_value && |
| identifier_value->GetValueID() != initial_value_id); |
| }; |
| |
| if (IsPropertyNonInitial(*ligatures_value, CSSValueID::kNormal) || |
| ligatures_value->IsValueList()) { |
| return g_empty_string; |
| } |
| |
| if (IsPropertyNonInitial(*numeric_value, CSSValueID::kNormal) || |
| numeric_value->IsValueList()) { |
| return g_empty_string; |
| } |
| |
| if (IsPropertyNonInitial(*east_asian_value, CSSValueID::kNormal) || |
| east_asian_value->IsValueList()) { |
| return g_empty_string; |
| } |
| |
| if (IsPropertyNonInitial(*font_kerning_property.Value(), CSSValueID::kAuto) || |
| IsPropertyNonInitial(*font_optical_sizing_property.Value(), |
| CSSValueID::kAuto)) { |
| return g_empty_string; |
| } |
| |
| if (IsPropertyNonInitial(*variation_settings_value, CSSValueID::kNormal) || |
| variation_settings_value->IsValueList()) { |
| return g_empty_string; |
| } |
| |
| if (IsPropertyNonInitial(*feature_settings_value, CSSValueID::kNormal) || |
| feature_settings_value->IsValueList()) { |
| return g_empty_string; |
| } |
| |
| if (RuntimeEnabledFeatures::FontVariantAlternatesEnabled()) { |
| int font_variant_alternates_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariantAlternates()); |
| DCHECK_NE(font_variant_alternates_property_index, -1); |
| PropertyValueForSerializer font_variant_alternates_property = |
| property_set_.PropertyAt(font_variant_alternates_property_index); |
| const CSSValue* alternates_value = font_variant_alternates_property.Value(); |
| if (IsPropertyNonInitial(*alternates_value, CSSValueID::kNormal) || |
| alternates_value->IsValueList()) { |
| return g_empty_string; |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::FontVariantPositionEnabled()) { |
| int font_variant_position_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontVariantPosition()); |
| DCHECK_NE(font_variant_position_property_index, -1); |
| PropertyValueForSerializer font_variant_position_property = |
| property_set_.PropertyAt(font_variant_position_property_index); |
| if (IsPropertyNonInitial(*font_variant_position_property.Value(), |
| CSSValueID::kNormal)) { |
| return g_empty_string; |
| } |
| } |
| |
| if (RuntimeEnabledFeatures::CSSFontSizeAdjustEnabled()) { |
| int font_size_adjust_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontSizeAdjust()); |
| DCHECK_NE(font_size_adjust_property_index, -1); |
| PropertyValueForSerializer font_size_adjust_property = |
| property_set_.PropertyAt(font_size_adjust_property_index); |
| const CSSValue* size_adjust_value = font_size_adjust_property.Value(); |
| if (IsPropertyNonInitial(*size_adjust_value, CSSValueID::kNone) || |
| size_adjust_value->IsNumericLiteralValue()) { |
| return g_empty_string; |
| } |
| } |
| |
| const StylePropertyShorthand& shorthand = fontShorthand(); |
| const CSSProperty** longhands = shorthand.properties(); |
| unsigned length = shorthand.length(); |
| const CSSValue* first = property_set_.GetPropertyCSSValue(*longhands[0]); |
| if (const auto* system_font = |
| DynamicTo<cssvalue::CSSPendingSystemFontValue>(first)) { |
| for (unsigned i = 1; i < length; i++) { |
| const CSSValue* value = property_set_.GetPropertyCSSValue(*longhands[i]); |
| if (!base::ValuesEquivalent(first, value)) { |
| return g_empty_string; |
| } |
| } |
| return getValueName(system_font->SystemFontId()); |
| } else { |
| for (unsigned i = 1; i < length; i++) { |
| const CSSValue* value = property_set_.GetPropertyCSSValue(*longhands[i]); |
| if (value->IsPendingSystemFontValue()) { |
| 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() != CSSValueID::kSmallCaps && |
| identifier_value->GetValueID() != CSSValueID::kNormal)) { |
| 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.empty()) { |
| result.Append(' '); |
| } |
| result.Append(font_size_property.Value()->CssText()); |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyLineHeight(), result); |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append(font_family_property.Value()->CssText()); |
| return result.ReleaseString(); |
| } |
| |
| 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); |
| if (RuntimeEnabledFeatures::FontVariantAlternatesEnabled()) { |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantAlternates(), |
| result); |
| } |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantNumeric(), |
| result); |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantEastAsian(), |
| result); |
| if (RuntimeEnabledFeatures::FontVariantPositionEnabled()) { |
| AppendFontLonghandValueIfNotNormal(GetCSSPropertyFontVariantPosition(), |
| result); |
| } |
| |
| if (result.empty()) { |
| return "normal"; |
| } |
| |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::FontSynthesisValue() const { |
| StringBuilder result; |
| |
| int font_synthesis_weight_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontSynthesisWeight()); |
| int font_synthesis_style_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontSynthesisStyle()); |
| int font_synthesis_small_caps_property_index = |
| property_set_.FindPropertyIndex(GetCSSPropertyFontSynthesisSmallCaps()); |
| DCHECK_NE(font_synthesis_weight_property_index, -1); |
| DCHECK_NE(font_synthesis_style_property_index, -1); |
| DCHECK_NE(font_synthesis_small_caps_property_index, -1); |
| |
| PropertyValueForSerializer font_synthesis_weight_property = |
| property_set_.PropertyAt(font_synthesis_weight_property_index); |
| PropertyValueForSerializer font_synthesis_style_property = |
| property_set_.PropertyAt(font_synthesis_style_property_index); |
| PropertyValueForSerializer font_synthesis_small_caps_property = |
| property_set_.PropertyAt(font_synthesis_small_caps_property_index); |
| |
| const CSSValue* font_synthesis_weight_value = |
| font_synthesis_weight_property.Value(); |
| const CSSValue* font_synthesis_style_value = |
| font_synthesis_style_property.Value(); |
| const CSSValue* font_synthesis_small_caps_value = |
| font_synthesis_small_caps_property.Value(); |
| |
| auto* font_synthesis_weight_identifier_value = |
| DynamicTo<CSSIdentifierValue>(font_synthesis_weight_value); |
| if (font_synthesis_weight_identifier_value && |
| font_synthesis_weight_identifier_value->GetValueID() == |
| CSSValueID::kAuto) { |
| result.Append("weight"); |
| } |
| |
| auto* font_synthesis_style_identifier_value = |
| DynamicTo<CSSIdentifierValue>(font_synthesis_style_value); |
| if (font_synthesis_style_identifier_value && |
| font_synthesis_style_identifier_value->GetValueID() == |
| CSSValueID::kAuto) { |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append("style"); |
| } |
| |
| auto* font_synthesis_small_caps_identifier_value = |
| DynamicTo<CSSIdentifierValue>(font_synthesis_small_caps_value); |
| if (font_synthesis_small_caps_identifier_value && |
| font_synthesis_small_caps_identifier_value->GetValueID() == |
| CSSValueID::kAuto) { |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append("small-caps"); |
| } |
| |
| if (result.empty()) { |
| return "none"; |
| } |
| |
| return result.ReleaseString(); |
| } |
| |
| 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.empty()) { |
| 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 { |
| // The longhand values cannot be serialized as a valid shorthand value. |
| // Serialize them as individual longhands instead. |
| if (!distance->IsInitialValue() || !rotate->IsInitialValue()) { |
| return String(); |
| } |
| } |
| if (RuntimeEnabledFeatures::CSSOffsetPositionAnchorEnabled()) { |
| const CSSValue* anchor = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyOffsetAnchor()); |
| if (!anchor->IsInitialValue()) { |
| result.Append(" / "); |
| result.Append(anchor->CssText()); |
| } |
| } |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::TextDecorationValue() const { |
| StringBuilder result; |
| const auto& shorthand = shorthandForProperty(CSSPropertyID::kTextDecoration); |
| 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 (shorthand.properties()[i]->PropertyID() == |
| CSSPropertyID::kTextDecorationThickness) { |
| if (auto* identifier_value = DynamicTo<CSSIdentifierValue>(value)) { |
| // Do not include initial value 'auto' for thickness. |
| // TODO(https://crbug.com/1093826): general shorthand serialization |
| // issues remain, in particular for text-decoration. |
| CSSValueID value_id = identifier_value->GetValueID(); |
| if (value_id == CSSValueID::kAuto) { |
| continue; |
| } |
| } |
| } |
| if (!result.empty()) { |
| result.Append(" "); |
| } |
| result.Append(value_text); |
| } |
| |
| if (result.empty()) { |
| return "none"; |
| } |
| return result.ReleaseString(); |
| } |
| |
| 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 = !base::ValuesEquivalent(start.Value(), end.Value()); |
| |
| StringBuilder result; |
| result.Append(start.Value()->CssText()); |
| if (show_end) { |
| result.Append(' '); |
| result.Append(end.Value()->CssText()); |
| } |
| return result.ReleaseString(); |
| } |
| |
| 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 = !base::ValuesEquivalent(right.Value(), left.Value()); |
| bool show_bottom = |
| !base::ValuesEquivalent(top.Value(), bottom.Value()) || show_left; |
| bool show_right = |
| !base::ValuesEquivalent(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.ReleaseString(); |
| } |
| |
| 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 = To<CSSValueList>(values[i].Get()); |
| 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 auto* property_values = |
| To<CSSValueList>(values[property_index].Get()); |
| // 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(CSSPropertyID::kBackgroundColor)) || |
| (layer == num_layers - 1 && |
| property->IDEquals(CSSPropertyID::kBackgroundColor))) { |
| // 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(CSSPropertyID::kBackgroundRepeatX) || |
| property->IDEquals(CSSPropertyID::kWebkitMaskRepeatX)) { |
| DCHECK(shorthand.properties()[property_index + 1]->IDEquals( |
| CSSPropertyID::kBackgroundRepeatY) || |
| shorthand.properties()[property_index + 1]->IDEquals( |
| CSSPropertyID::kWebkitMaskRepeatY)); |
| auto* value_list = |
| DynamicTo<CSSValueList>(values[property_index + 1].Get()); |
| const CSSValue& y_value = |
| value_list ? value_list->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 == CSSValueID::kRepeat && |
| y_id == CSSValueID::kNoRepeat) { |
| use_repeat_x_shorthand = true; |
| property = shorthand.properties()[++property_index]; |
| } else if (x_id == CSSValueID::kNoRepeat && |
| y_id == CSSValueID::kRepeat) { |
| use_repeat_y_shorthand = true; |
| property = shorthand.properties()[++property_index]; |
| } |
| } |
| |
| bool is_initial_value = value->IsInitialValue(); |
| |
| // The shorthand can not represent the following properties if they have |
| // non-initial values. This is because they are always reset to their |
| // initial value by the shorthand. |
| if (property->IDEquals(CSSPropertyID::kAnimationTimeline)) { |
| auto* ident = DynamicTo<CSSIdentifierValue>(value); |
| if (!ident || (ident->GetValueID() != |
| CSSAnimationData::InitialTimeline().GetKeyword())) { |
| DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled()); |
| return g_empty_string; |
| } |
| is_initial_value = true; |
| } |
| if (property->IDEquals(CSSPropertyID::kAnimationDelayEnd)) { |
| if (CSSToStyleMap::MapAnimationDelayEnd(*value) != |
| CSSTimingData::InitialDelayEnd()) { |
| return g_empty_string; |
| } |
| is_initial_value = true; |
| } |
| if (property->IDEquals(CSSPropertyID::kAnimationRangeStart)) { |
| auto* ident = DynamicTo<CSSIdentifierValue>(value); |
| if (!ident || (ident->GetValueID() != CSSValueID::kNormal)) { |
| DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled()); |
| return g_empty_string; |
| } |
| is_initial_value = true; |
| } |
| if (property->IDEquals(CSSPropertyID::kAnimationRangeEnd)) { |
| auto* ident = DynamicTo<CSSIdentifierValue>(value); |
| if (!ident || (ident->GetValueID() != CSSValueID::kNormal)) { |
| DCHECK(RuntimeEnabledFeatures::ScrollTimelineEnabled()); |
| return g_empty_string; |
| } |
| is_initial_value = true; |
| } |
| |
| if (!is_initial_value) { |
| if (property->IDEquals(CSSPropertyID::kBackgroundSize) || |
| property->IDEquals(CSSPropertyID::kWebkitMaskSize)) { |
| if (found_position_ycss_property || found_position_xcss_property) { |
| layer_result.Append(" / "); |
| } else { |
| layer_result.Append(" 0% 0% / "); |
| } |
| } else if (!layer_result.empty()) { |
| // 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(CSSValueID::kRepeatX)); |
| } else if (use_repeat_y_shorthand) { |
| use_repeat_y_shorthand = false; |
| layer_result.Append(getValueName(CSSValueID::kRepeatY)); |
| } else { |
| if (use_single_word_shorthand) { |
| use_single_word_shorthand = false; |
| } |
| layer_result.Append(value->CssText()); |
| } |
| if (property->IDEquals(CSSPropertyID::kBackgroundPositionX) || |
| property->IDEquals(CSSPropertyID::kWebkitMaskPositionX)) { |
| found_position_xcss_property = true; |
| } |
| if (property->IDEquals(CSSPropertyID::kBackgroundPositionY) || |
| property->IDEquals(CSSPropertyID::kWebkitMaskPositionY)) { |
| 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.empty()) { |
| if (!result.empty()) { |
| result.Append(", "); |
| } |
| result.Append(layer_result); |
| } |
| } |
| |
| return result.ReleaseString(); |
| } |
| |
| 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.empty()) { |
| result.Append(separator); |
| } |
| result.Append(value_text); |
| } |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::GetShorthandValueForColumns( |
| const StylePropertyShorthand& shorthand) const { |
| DCHECK_EQ(shorthand.length(), 2u); |
| |
| 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 (IsA<CSSIdentifierValue>(value) && |
| To<CSSIdentifierValue>(value)->GetValueID() == CSSValueID::kAuto) { |
| continue; |
| } |
| if (!result.empty()) { |
| result.Append(" "); |
| } |
| result.Append(value_text); |
| } |
| |
| if (result.empty()) { |
| return "auto"; |
| } |
| |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::GetShorthandValueForDoubleBarCombinator( |
| const StylePropertyShorthand& shorthand) const { |
| StringBuilder result; |
| for (unsigned i = 0; i < shorthand.length(); ++i) { |
| const Longhand* longhand = To<Longhand>(shorthand.properties()[i]); |
| DCHECK(!longhand->InitialValue()->IsInitialValue()) |
| << "Without InitialValue() implemented, 'initial' will show up in the " |
| "serialization below."; |
| const CSSValue* value = property_set_.GetPropertyCSSValue(*longhand); |
| if (*value == *longhand->InitialValue()) { |
| continue; |
| } |
| String value_text = value->CssText(); |
| if (!result.empty()) { |
| result.Append(" "); |
| } |
| result.Append(value_text); |
| } |
| |
| if (result.empty()) { |
| return To<Longhand>(shorthand.properties()[0])->InitialValue()->CssText(); |
| } |
| |
| return result.ReleaseString(); |
| } |
| |
| namespace { |
| |
| String NamedGridAreaTextForPosition(const NamedGridAreaMap& grid_area_map, |
| wtf_size_t row, |
| wtf_size_t column) { |
| for (const auto& item : grid_area_map) { |
| const GridArea& area = item.value; |
| if (row >= area.rows.StartLine() && row < area.rows.EndLine() && |
| column >= area.columns.StartLine() && column < area.columns.EndLine()) { |
| return item.key; |
| } |
| } |
| return "."; |
| } |
| |
| } // namespace |
| |
| String StylePropertySerializer::GetShorthandValueForGrid( |
| const StylePropertyShorthand& shorthand) const { |
| DCHECK_EQ(shorthand.length(), 6u); |
| |
| const CSSValue* auto_flow_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[3]); |
| const CSSValue* auto_row_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[4]); |
| const CSSValue* auto_column_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[5]); |
| |
| // 1- <'grid-template'> |
| if (IsA<CSSIdentifierValue>(auto_flow_values) && |
| To<CSSIdentifierValue>(auto_flow_values)->GetValueID() == |
| CSSValueID::kRow && |
| IsA<CSSIdentifierValue>(auto_row_values) && |
| To<CSSIdentifierValue>(auto_row_values)->GetValueID() == |
| CSSValueID::kAuto && |
| IsA<CSSIdentifierValue>(auto_column_values) && |
| To<CSSIdentifierValue>(auto_column_values)->GetValueID() == |
| CSSValueID::kAuto) { |
| return GetShorthandValueForGridTemplate(shorthand); |
| } |
| |
| // If we have grid-auto-{flow,row,column} along with named lines, we can't |
| // serialize as a "grid" shorthand, as a syntax that combines the two is not |
| // valid per the grammar. |
| const CSSValue* template_area_value = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[2]); |
| if (*template_area_value != |
| *(To<Longhand>(GetCSSPropertyGridTemplateAreas()).InitialValue())) { |
| return String(); |
| } |
| |
| const CSSValue* template_row_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[0]); |
| const CSSValue* template_column_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[1]); |
| const CSSValueList* auto_flow_value_list = |
| DynamicTo<CSSValueList>(auto_flow_values); |
| |
| // We cannot represent a grid shorthand if we have both template row and |
| // template column values along with auto-flow. |
| if (*template_row_values != |
| *(To<Longhand>(GetCSSPropertyGridTemplateRows()).InitialValue()) && |
| *template_column_values != |
| *(To<Longhand>(GetCSSPropertyGridTemplateColumns()).InitialValue())) { |
| return String(); |
| } |
| |
| StringBuilder auto_flow_text; |
| auto_flow_text.Append("auto-flow "); |
| if (auto_flow_value_list && |
| auto_flow_value_list->HasValue( |
| *CSSIdentifierValue::Create(CSSValueID::kDense))) { |
| auto_flow_text.Append("dense "); |
| } |
| |
| // 2- <'grid-template-rows'> / [ auto-flow && dense? ] <'grid-auto-columns'>? |
| // | [ auto-flow && dense? ] <'grid-auto-rows'>? / <'grid-template-columns'> |
| StringBuilder result; |
| if (auto_flow_value_list && |
| auto_flow_value_list->HasValue( |
| *CSSIdentifierValue::Create(CSSValueID::kColumn))) { |
| result.Append(template_row_values->CssText()); |
| result.Append(" / "); |
| result.Append(auto_flow_text); |
| result.Append(auto_column_values->CssText()); |
| } else { |
| result.Append(auto_flow_text); |
| result.Append(auto_row_values->CssText()); |
| result.Append(" / "); |
| result.Append(template_column_values->CssText()); |
| } |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::GetShorthandValueForGridTemplate( |
| const StylePropertyShorthand& shorthand) const { |
| const CSSValue* template_row_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[0]); |
| const CSSValue* template_column_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[1]); |
| const CSSValue* template_area_values = |
| property_set_.GetPropertyCSSValue(*shorthand.properties()[2]); |
| |
| // 1- 'none' case. |
| if (IsA<CSSIdentifierValue>(template_row_values) && |
| To<CSSIdentifierValue>(template_row_values)->GetValueID() == |
| CSSValueID::kNone && |
| IsA<CSSIdentifierValue>(template_column_values) && |
| To<CSSIdentifierValue>(template_column_values)->GetValueID() == |
| CSSValueID::kNone) { |
| return "none"; |
| } |
| |
| const auto* template_row_value_list = |
| DynamicTo<CSSValueList>(template_row_values); |
| StringBuilder result; |
| |
| // 2- <grid-template-rows> / <grid-template-columns> |
| if (!template_row_value_list || |
| (IsA<CSSIdentifierValue>(template_area_values) && |
| To<CSSIdentifierValue>(template_area_values)->GetValueID() == |
| CSSValueID::kNone)) { |
| result.Append(template_row_values->CssText()); |
| result.Append(" / "); |
| result.Append(template_column_values->CssText()); |
| return result.ReleaseString(); |
| } |
| |
| // 3- [ <line-names>? <string> <track-size>? <line-names>? ]+ |
| // [ / <track-list> ]? |
| if (template_row_value_list->length() == 1 && |
| IsA<CSSIdentifierValue>(template_row_value_list->Item(0)) && |
| To<CSSIdentifierValue>(template_row_value_list->Item(0)).GetValueID() == |
| CSSValueID::kAuto) { |
| // If the |template_row_value_list| has only one value and it is 'auto', |
| // then we append the 'grid-template-area' values. |
| result.Append(template_area_values->CssText()); |
| } else { |
| const auto* template_areas = |
| DynamicTo<cssvalue::CSSGridTemplateAreasValue>(template_area_values); |
| DCHECK(template_areas); |
| const NamedGridAreaMap& grid_area_map = template_areas->GridAreaMap(); |
| wtf_size_t grid_area_column_count = template_areas->ColumnCount(); |
| wtf_size_t grid_area_index = 0; |
| for (const auto& row_value : *template_row_value_list) { |
| const String row_value_text = row_value->CssText(); |
| if (row_value->IsGridLineNamesValue()) { |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append(row_value_text); |
| continue; |
| } |
| StringBuilder grid_area_text; |
| for (wtf_size_t column = 0; column < grid_area_column_count; ++column) { |
| grid_area_text.Append(NamedGridAreaTextForPosition( |
| grid_area_map, grid_area_index, column)); |
| if (column != grid_area_column_count - 1) { |
| grid_area_text.Append(' '); |
| } |
| } |
| if (!grid_area_text.empty()) { |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append('"'); |
| result.Append(grid_area_text); |
| result.Append('"'); |
| ++grid_area_index; |
| } |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append(row_value_text); |
| } |
| } |
| if (!(IsA<CSSIdentifierValue>(template_column_values) && |
| To<CSSIdentifierValue>(template_column_values)->GetValueID() == |
| CSSValueID::kNone)) { |
| result.Append(" / "); |
| result.Append(template_column_values->CssText()); |
| } |
| return result.ReleaseString(); |
| } |
| |
| // 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 CSSProperty* border_image_properties[] = { |
| &GetCSSPropertyBorderImageSource(), &GetCSSPropertyBorderImageSlice(), |
| &GetCSSPropertyBorderImageWidth(), &GetCSSPropertyBorderImageOutset(), |
| &GetCSSPropertyBorderImageRepeat()}; |
| |
| // If any of the border-image longhands differ from their initial |
| // specified values, we should not serialize to a border shorthand |
| // declaration. |
| for (const auto* border_image_property : border_image_properties) { |
| const CSSValue* value = |
| property_set_.GetPropertyCSSValue(*border_image_property); |
| const CSSValue* initial_specified_value = |
| To<Longhand>(*border_image_property).InitialValue(); |
| if (value && !value->IsInitialValue() && |
| *value != *initial_specified_value) { |
| return String(); |
| } |
| } |
| |
| const StylePropertyShorthand shorthand_properties[3] = {width, style, color}; |
| StringBuilder result; |
| for (const auto& shorthand_property : shorthand_properties) { |
| const String value = GetCommonValue(shorthand_property); |
| if (value.IsNull()) { |
| return String(); |
| } |
| if (value == "initial") { |
| continue; |
| } |
| if (!result.empty()) { |
| result.Append(' '); |
| } |
| result.Append(value); |
| } |
| return result.empty() ? String() : result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::BorderImagePropertyValue() const { |
| StringBuilder result; |
| const CSSProperty* properties[] = { |
| &GetCSSPropertyBorderImageSource(), &GetCSSPropertyBorderImageSlice(), |
| &GetCSSPropertyBorderImageWidth(), &GetCSSPropertyBorderImageOutset(), |
| &GetCSSPropertyBorderImageRepeat()}; |
| size_t length = std::size(properties); |
| for (size_t i = 0; i < length; ++i) { |
| const CSSValue& value = *property_set_.GetPropertyCSSValue(*properties[i]); |
| if (!result.empty()) { |
| result.Append(" "); |
| } |
| if (i == 2 || i == 3) { |
| result.Append("/ "); |
| } |
| result.Append(value.CssText()); |
| } |
| return result.ReleaseString(); |
| } |
| |
| String StylePropertySerializer::BorderRadiusValue() const { |
| auto serialize = [](const CSSValue& top_left, const CSSValue& top_right, |
| const CSSValue& bottom_right, |
| const CSSValue& bottom_left) -> String { |
| bool show_bottom_left = !(top_right == bottom_left); |
| bool show_bottom_right = !(top_left == bottom_right) || show_bottom_left; |
| bool show_top_right = !(top_left == top_right) || show_bottom_right; |
| |
| StringBuilder result; |
| result.Append(top_left.CssText()); |
| if (show_top_right) { |
| result.Append(' '); |
| result.Append(top_right.CssText()); |
| } |
| if (show_bottom_right) { |
| result.Append(' '); |
| result.Append(bottom_right.CssText()); |
| } |
| if (show_bottom_left) { |
| result.Append(' '); |
| result.Append(bottom_left.CssText()); |
| } |
| return result.ReleaseString(); |
| }; |
| |
| const CSSValuePair& top_left = To<CSSValuePair>( |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyBorderTopLeftRadius())); |
| const CSSValuePair& top_right = To<CSSValuePair>( |
| *property_set_.GetPropertyCSSValue(GetCSSPropertyBorderTopRightRadius())); |
| const CSSValuePair& bottom_right = |
| To<CSSValuePair>(*property_set_.GetPropertyCSSValue( |
| GetCSSPropertyBorderBottomRightRadius())); |
| const CSSValuePair& bottom_left = |
| To<CSSValuePair>(*property_set_.GetPropertyCSSValue( |
| GetCSSPropertyBorderBottomLeftRadius())); |
| |
| StringBuilder builder; |
| builder.Append(serialize(top_left.First(), top_right.First(), |
| bottom_right.First(), bottom_left.First())); |
| |
| if (!(top_left.First() == top_left.Second()) || |
| !(top_right.First() == top_right.Second()) || |
| !(bottom_right.First() == bottom_right.Second()) || |
| !(bottom_left.First() == bottom_left.Second())) { |
| builder.Append(" / "); |
| builder.Append(serialize(top_left.Second(), top_right.Second(), |
| bottom_right.Second(), bottom_left.Second())); |
| } |
| |
| return builder.ReleaseString(); |
| } |
| |
| 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(const Persistent<CSSIdentifierValue>, |
| initial_repeat_value, |
| (CSSIdentifierValue::Create(CSSValueID::kRepeat))); |
| 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 == CSSValueID::kNoRepeat && |
| repeat_y_value_id == CSSValueID::kRepeat) { |
| builder.Append("repeat-y"); |
| } else if (repeat_x_value_id == CSSValueID::kRepeat && |
| repeat_y_value_id == CSSValueID::kNoRepeat) { |
| 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 auto* repeat_x_list = DynamicTo<CSSValueList>(repeat_x); |
| int repeat_x_length = 1; |
| if (repeat_x_list) { |
| repeat_x_length = repeat_x_list->length(); |
| } else if (!repeat_x.IsIdentifierValue()) { |
| return String(); |
| } |
| |
| const auto* repeat_y_list = DynamicTo<CSSValueList>(repeat_y); |
| int repeat_y_length = 1; |
| if (repeat_y_list) { |
| 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.ReleaseString(); |
| } |
| |
| 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 == CSSValueID::kPage) { |
| return "always"; |
| } |
| if (value_id == CSSValueID::kAuto || value_id == CSSValueID::kLeft || |
| value_id == CSSValueID::kRight || value_id == CSSValueID::kAvoid) { |
| return value->CssText(); |
| } |
| return String(); |
| } |
| |
| String StylePropertySerializer::ContainIntrinsicSizeValue() const { |
| // If the two values are identical, we return just one. |
| String res = GetCommonValue(containIntrinsicSizeShorthand()); |
| if (!res.IsNull()) { |
| return res; |
| } |
| // Otherwise just serialize them in sequence. |
| return GetShorthandValue(containIntrinsicSizeShorthand()); |
| } |
| |
| bool StylePropertySerializer::IsValidToggleShorthand( |
| const CSSValue* toggle_root, |
| const CSSValue* toggle_trigger) { |
| if (const auto* toggle_root_ident = |
| DynamicTo<CSSIdentifierValue>(toggle_root)) { |
| DCHECK_EQ(toggle_root_ident->GetValueID(), CSSValueID::kNone); |
| if (const auto* toggle_trigger_ident = |
| DynamicTo<CSSIdentifierValue>(toggle_trigger)) { |
| DCHECK_EQ(toggle_trigger_ident->GetValueID(), CSSValueID::kNone); |
| return true; |
| } |
| return false; |
| } |
| |
| const auto* toggle_trigger_list = DynamicTo<CSSValueList>(toggle_trigger); |
| if (!toggle_trigger_list) { |
| return false; |
| } |
| const auto* toggle_root_list = To<CSSValueList>(toggle_root); |
| wtf_size_t length = toggle_trigger_list->length(); |
| if (length != toggle_root_list->length()) { |
| return false; |
| } |
| for (wtf_size_t i = 0; i < length; ++i) { |
| const auto& toggle_root_item = To<CSSValueList>(toggle_root_list->Item(i)); |
| const auto& toggle_trigger_item = |
| To<CSSValueList>(toggle_trigger_list->Item(i)); |
| if (toggle_trigger_item.length() > 1u) { |
| DCHECK_EQ(toggle_trigger_item.length(), 2u); |
| return false; |
| } |
| if (!base::ValuesEquivalent(&toggle_root_item.Item(0), |
| &toggle_trigger_item.Item(0))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| String StylePropertySerializer::WhiteSpaceValue() const { |
| DCHECK(RuntimeEnabledFeatures::CSSWhiteSpaceShorthandEnabled()); |
| |
| const CSSValue* collapse_value = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyWhiteSpaceCollapse()); |
| const CSSValue* wrap_value = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyTextWrap()); |
| if (!collapse_value || !wrap_value) { |
| // If any longhands are missing, don't serialize as a shorthand. |
| return g_empty_string; |
| } |
| |
| // Check if longhands are one of pre-defined keywords of `white-space`. |
| const WhiteSpaceCollapse collapse = ToWhiteSpaceCollapse(collapse_value); |
| const TextWrap wrap = ToTextWrap(wrap_value); |
| const EWhiteSpace whitespace = ToWhiteSpace(collapse, wrap); |
| if (IsValidWhiteSpace(whitespace)) { |
| return getValueName(PlatformEnumToCSSValueID(whitespace)); |
| } |
| |
| // Otherwise build a multi-value list. |
| StringBuilder result; |
| if (collapse != ComputedStyleInitialValues::InitialWhiteSpaceCollapse()) { |
| result.Append(getValueName(PlatformEnumToCSSValueID(collapse))); |
| } |
| if (wrap != ComputedStyleInitialValues::InitialTextWrap()) { |
| if (!result.empty()) { |
| result.Append(kSpaceCharacter); |
| } |
| result.Append(getValueName(PlatformEnumToCSSValueID(wrap))); |
| } |
| // When all longhands are initial values, it should be `normal`, covered by |
| // `IsValidWhiteSpace()` above. |
| DCHECK(!result.empty()); |
| return result.ToString(); |
| } |
| |
| String StylePropertySerializer::ScrollStartValue() const { |
| CHECK_EQ(scrollStartShorthand().length(), 2u); |
| CHECK_EQ(scrollStartShorthand().properties()[0], |
| &GetCSSPropertyScrollStartBlock()); |
| CHECK_EQ(scrollStartShorthand().properties()[1], |
| &GetCSSPropertyScrollStartInline()); |
| |
| CSSValueList* list = CSSValueList::CreateSpaceSeparated(); |
| const CSSValue* block_value = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartBlock()); |
| const CSSValue* inline_value = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartInline()); |
| |
| DCHECK(block_value); |
| DCHECK(inline_value); |
| |
| list->Append(*block_value); |
| |
| if (!(IsA<CSSIdentifierValue>(inline_value) && |
| To<CSSIdentifierValue>(*inline_value).GetValueID() == |
| CSSValueID::kStart)) { |
| list->Append(*inline_value); |
| } |
| |
| return list->CssText(); |
| } |
| |
| String StylePropertySerializer::ScrollStartTargetValue() const { |
| CHECK_EQ(scrollStartTargetShorthand().length(), 2u); |
| CHECK_EQ(scrollStartTargetShorthand().properties()[0], |
| &GetCSSPropertyScrollStartTargetBlock()); |
| CHECK_EQ(scrollStartTargetShorthand().properties()[1], |
| &GetCSSPropertyScrollStartTargetInline()); |
| |
| CSSValueList* list = CSSValueList::CreateSpaceSeparated(); |
| const CSSValue* block_value = |
| property_set_.GetPropertyCSSValue(GetCSSPropertyScrollStartTargetBlock()); |
| const CSSValue* inline_value = property_set_.GetPropertyCSSValue( |
| GetCSSPropertyScrollStartTargetInline()); |
| |
| DCHECK(block_value); |
| DCHECK(inline_value); |
| |
| list->Append(*block_value); |
| |
| if (To<CSSIdentifierValue>(*inline_value).GetValueID() != CSSValueID::kNone) { |
| list->Append(*inline_value); |
| } |
| |
| return list->CssText(); |
| } |
| |
| } // namespace blink |