| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/animation/css_border_image_length_box_interpolation_type.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "third_party/blink/renderer/core/animation/interpolable_length.h" |
| #include "third_party/blink/renderer/core/animation/list_interpolation_functions.h" |
| #include "third_party/blink/renderer/core/animation/side_index.h" |
| #include "third_party/blink/renderer/core/css/css_identifier_value.h" |
| #include "third_party/blink/renderer/core/css/css_property_names.h" |
| #include "third_party/blink/renderer/core/css/css_quad_value.h" |
| #include "third_party/blink/renderer/core/css/resolver/style_resolver_state.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| enum class SideType { |
| kNumber, |
| kAuto, |
| kLength, |
| }; |
| |
| const BorderImageLengthBox& GetBorderImageLengthBox( |
| const CSSProperty& property, |
| const ComputedStyle& style) { |
| switch (property.PropertyID()) { |
| case CSSPropertyID::kBorderImageOutset: |
| return style.BorderImageOutset(); |
| case CSSPropertyID::kBorderImageWidth: |
| return style.BorderImageWidth(); |
| case CSSPropertyID::kWebkitMaskBoxImageOutset: |
| return style.MaskBoxImageOutset(); |
| case CSSPropertyID::kWebkitMaskBoxImageWidth: |
| return style.MaskBoxImageWidth(); |
| default: |
| NOTREACHED(); |
| return GetBorderImageLengthBox( |
| CSSProperty::Get(CSSPropertyID::kBorderImageOutset), |
| ComputedStyle::InitialStyle()); |
| } |
| } |
| |
| void SetBorderImageLengthBox(const CSSProperty& property, |
| ComputedStyle& style, |
| const BorderImageLengthBox& box) { |
| switch (property.PropertyID()) { |
| case CSSPropertyID::kBorderImageOutset: |
| style.SetBorderImageOutset(box); |
| break; |
| case CSSPropertyID::kWebkitMaskBoxImageOutset: |
| style.SetMaskBoxImageOutset(box); |
| break; |
| case CSSPropertyID::kBorderImageWidth: |
| style.SetBorderImageWidth(box); |
| break; |
| case CSSPropertyID::kWebkitMaskBoxImageWidth: |
| style.SetMaskBoxImageWidth(box); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| } // namespace |
| |
| // The NonInterpolableValue for the CSSBorderImageLengthBoxInterpolationType |
| // as a whole is a NonInterpolableList with kSideIndexCount items. Each entry |
| // in that list is either an instance of this class, or it's the |
| // NonInterpolableValue returned by LengthInterpolationFunctions. |
| class CSSBorderImageLengthBoxSideNonInterpolableValue |
| : public NonInterpolableValue { |
| public: |
| static scoped_refptr<CSSBorderImageLengthBoxSideNonInterpolableValue> Create( |
| SideType side_type) { |
| DCHECK_NE(SideType::kLength, side_type); |
| return base::AdoptRef( |
| new CSSBorderImageLengthBoxSideNonInterpolableValue(side_type)); |
| } |
| |
| SideType GetSideType() const { return side_type_; } |
| |
| DECLARE_NON_INTERPOLABLE_VALUE_TYPE(); |
| |
| private: |
| CSSBorderImageLengthBoxSideNonInterpolableValue(const SideType side_type) |
| : side_type_(side_type) {} |
| |
| const SideType side_type_; |
| }; |
| |
| DEFINE_NON_INTERPOLABLE_VALUE_TYPE( |
| CSSBorderImageLengthBoxSideNonInterpolableValue); |
| template <> |
| struct DowncastTraits<CSSBorderImageLengthBoxSideNonInterpolableValue> { |
| static bool AllowFrom(const NonInterpolableValue* value) { |
| return value && AllowFrom(*value); |
| } |
| static bool AllowFrom(const NonInterpolableValue& value) { |
| return value.GetType() == |
| CSSBorderImageLengthBoxSideNonInterpolableValue::static_type_; |
| } |
| }; |
| |
| namespace { |
| |
| SideType GetSideType(const BorderImageLength& side) { |
| if (side.IsNumber()) { |
| return SideType::kNumber; |
| } |
| if (side.length().IsAuto()) { |
| return SideType::kAuto; |
| } |
| DCHECK(side.length().IsSpecified()); |
| return SideType::kLength; |
| } |
| |
| SideType GetSideType(const CSSValue& side) { |
| auto* side_primitive_value = DynamicTo<CSSPrimitiveValue>(side); |
| if (side_primitive_value && side_primitive_value->IsNumber()) { |
| return SideType::kNumber; |
| } |
| auto* side_identifier_value = DynamicTo<CSSIdentifierValue>(side); |
| if (side_identifier_value && |
| side_identifier_value->GetValueID() == CSSValueID::kAuto) { |
| return SideType::kAuto; |
| } |
| return SideType::kLength; |
| } |
| |
| SideType GetSideType(const NonInterpolableValue* side) { |
| // We interpret nullptr as kLength, because LengthInterpolationFunctions |
| // returns a nullptr NonInterpolableValue if there is no percent unit. |
| // |
| // In cases where LengthInterpolationFunctions is not used to convert the |
| // value (kAuto, kNumber), we will always have a non-interpolable value of |
| // type CSSBorderImageLengthBoxSideNonInterpolableValue. |
| auto* non_interpolable = |
| DynamicTo<CSSBorderImageLengthBoxSideNonInterpolableValue>(side); |
| if (!side || !non_interpolable) |
| return SideType::kLength; |
| return non_interpolable->GetSideType(); |
| } |
| |
| struct SideTypes { |
| explicit SideTypes(const BorderImageLengthBox& box) { |
| type[kSideTop] = GetSideType(box.Top()); |
| type[kSideRight] = GetSideType(box.Right()); |
| type[kSideBottom] = GetSideType(box.Bottom()); |
| type[kSideLeft] = GetSideType(box.Left()); |
| } |
| explicit SideTypes(const CSSQuadValue& quad) { |
| type[kSideTop] = GetSideType(*quad.Top()); |
| type[kSideRight] = GetSideType(*quad.Right()); |
| type[kSideBottom] = GetSideType(*quad.Bottom()); |
| type[kSideLeft] = GetSideType(*quad.Left()); |
| } |
| explicit SideTypes(const InterpolationValue& underlying) { |
| const auto& non_interpolable_list = |
| To<NonInterpolableList>(*underlying.non_interpolable_value); |
| DCHECK_EQ(kSideIndexCount, non_interpolable_list.length()); |
| type[kSideTop] = GetSideType(non_interpolable_list.Get(0)); |
| type[kSideRight] = GetSideType(non_interpolable_list.Get(1)); |
| type[kSideBottom] = GetSideType(non_interpolable_list.Get(2)); |
| type[kSideLeft] = GetSideType(non_interpolable_list.Get(3)); |
| } |
| |
| bool operator==(const SideTypes& other) const { |
| for (size_t i = 0; i < kSideIndexCount; i++) { |
| if (type[i] != other.type[i]) |
| return false; |
| } |
| return true; |
| } |
| bool operator!=(const SideTypes& other) const { return !(*this == other); } |
| |
| SideType type[kSideIndexCount]; |
| }; |
| |
| class UnderlyingSideTypesChecker |
| : public CSSInterpolationType::CSSConversionChecker { |
| public: |
| |
| explicit UnderlyingSideTypesChecker(const SideTypes& underlying_side_types) |
| : underlying_side_types_(underlying_side_types) {} |
| |
| private: |
| bool IsValid(const StyleResolverState&, |
| const InterpolationValue& underlying) const final { |
| return underlying_side_types_ == SideTypes(underlying); |
| } |
| |
| const SideTypes underlying_side_types_; |
| }; |
| |
| class InheritedSideTypesChecker |
| : public CSSInterpolationType::CSSConversionChecker { |
| public: |
| InheritedSideTypesChecker(const CSSProperty& property, |
| const SideTypes& inherited_side_types) |
| : property_(property), inherited_side_types_(inherited_side_types) {} |
| |
| private: |
| bool IsValid(const StyleResolverState& state, |
| const InterpolationValue& underlying) const final { |
| return inherited_side_types_ == |
| SideTypes(GetBorderImageLengthBox(property_, *state.ParentStyle())); |
| } |
| |
| const CSSProperty& property_; |
| const SideTypes inherited_side_types_; |
| }; |
| |
| InterpolationValue ConvertBorderImageNumberSide(double number) { |
| return InterpolationValue( |
| std::make_unique<InterpolableNumber>(number), |
| CSSBorderImageLengthBoxSideNonInterpolableValue::Create( |
| SideType::kNumber)); |
| } |
| |
| InterpolationValue ConvertBorderImageAutoSide() { |
| return InterpolationValue( |
| std::make_unique<InterpolableList>(0), |
| CSSBorderImageLengthBoxSideNonInterpolableValue::Create(SideType::kAuto)); |
| } |
| |
| InterpolationValue ConvertBorderImageLengthBox(const BorderImageLengthBox& box, |
| double zoom) { |
| auto list = std::make_unique<InterpolableList>(kSideIndexCount); |
| Vector<scoped_refptr<const NonInterpolableValue>> non_interpolable_values( |
| kSideIndexCount); |
| const BorderImageLength* sides[kSideIndexCount] = {}; |
| sides[kSideTop] = &box.Top(); |
| sides[kSideRight] = &box.Right(); |
| sides[kSideBottom] = &box.Bottom(); |
| sides[kSideLeft] = &box.Left(); |
| |
| return ListInterpolationFunctions::CreateList( |
| kSideIndexCount, [&sides, zoom](wtf_size_t index) { |
| const BorderImageLength& side = *sides[index]; |
| if (side.IsNumber()) |
| return ConvertBorderImageNumberSide(side.Number()); |
| if (side.length().IsAuto()) |
| return ConvertBorderImageAutoSide(); |
| return InterpolationValue( |
| InterpolableLength::MaybeConvertLength(side.length(), zoom)); |
| }); |
| } |
| |
| void CompositeSide(UnderlyingValue& underlying_value, |
| double underlying_fraction, |
| const InterpolableValue& interpolable_value, |
| const NonInterpolableValue* non_interpolable_value) { |
| switch (GetSideType(non_interpolable_value)) { |
| case SideType::kNumber: |
| case SideType::kLength: |
| underlying_value.MutableInterpolableValue().ScaleAndAdd( |
| underlying_fraction, interpolable_value); |
| break; |
| case SideType::kAuto: |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| bool NonInterpolableSidesAreCompatible(const NonInterpolableValue* a, |
| const NonInterpolableValue* b) { |
| return GetSideType(a) == GetSideType(b); |
| } |
| |
| } // namespace |
| |
| InterpolationValue |
| CSSBorderImageLengthBoxInterpolationType::MaybeConvertNeutral( |
| const InterpolationValue& underlying, |
| ConversionCheckers& conversion_checkers) const { |
| SideTypes underlying_side_types(underlying); |
| conversion_checkers.push_back( |
| std::make_unique<UnderlyingSideTypesChecker>(underlying_side_types)); |
| return InterpolationValue(underlying.interpolable_value->CloneAndZero(), |
| underlying.non_interpolable_value); |
| } |
| |
| InterpolationValue |
| CSSBorderImageLengthBoxInterpolationType::MaybeConvertInitial( |
| const StyleResolverState&, |
| ConversionCheckers&) const { |
| return ConvertBorderImageLengthBox( |
| GetBorderImageLengthBox(CssProperty(), ComputedStyle::InitialStyle()), 1); |
| } |
| |
| InterpolationValue |
| CSSBorderImageLengthBoxInterpolationType::MaybeConvertInherit( |
| const StyleResolverState& state, |
| ConversionCheckers& conversion_checkers) const { |
| const BorderImageLengthBox& inherited = |
| GetBorderImageLengthBox(CssProperty(), *state.ParentStyle()); |
| conversion_checkers.push_back(std::make_unique<InheritedSideTypesChecker>( |
| CssProperty(), SideTypes(inherited))); |
| return ConvertBorderImageLengthBox(inherited, |
| state.ParentStyle()->EffectiveZoom()); |
| } |
| |
| InterpolationValue CSSBorderImageLengthBoxInterpolationType::MaybeConvertValue( |
| const CSSValue& value, |
| const StyleResolverState*, |
| ConversionCheckers&) const { |
| const auto* quad = DynamicTo<CSSQuadValue>(value); |
| if (!quad) |
| return nullptr; |
| |
| auto list = std::make_unique<InterpolableList>(kSideIndexCount); |
| Vector<scoped_refptr<const NonInterpolableValue>> non_interpolable_values( |
| kSideIndexCount); |
| const CSSValue* sides[kSideIndexCount] = {}; |
| sides[kSideTop] = quad->Top(); |
| sides[kSideRight] = quad->Right(); |
| sides[kSideBottom] = quad->Bottom(); |
| sides[kSideLeft] = quad->Left(); |
| |
| return ListInterpolationFunctions::CreateList( |
| kSideIndexCount, [&sides](wtf_size_t index) { |
| const CSSValue& side = *sides[index]; |
| |
| auto* side_primitive_value = DynamicTo<CSSPrimitiveValue>(side); |
| if (side_primitive_value && side_primitive_value->IsNumber()) { |
| return ConvertBorderImageNumberSide( |
| side_primitive_value->GetDoubleValue()); |
| } |
| |
| auto* side_identifier_value = DynamicTo<CSSIdentifierValue>(side); |
| if (side_identifier_value && |
| side_identifier_value->GetValueID() == CSSValueID::kAuto) { |
| return ConvertBorderImageAutoSide(); |
| } |
| |
| return InterpolationValue( |
| InterpolableLength::MaybeConvertCSSValue(side)); |
| }); |
| } |
| |
| InterpolationValue CSSBorderImageLengthBoxInterpolationType:: |
| MaybeConvertStandardPropertyUnderlyingValue( |
| const ComputedStyle& style) const { |
| return ConvertBorderImageLengthBox( |
| GetBorderImageLengthBox(CssProperty(), style), style.EffectiveZoom()); |
| } |
| |
| PairwiseInterpolationValue |
| CSSBorderImageLengthBoxInterpolationType::MaybeMergeSingles( |
| InterpolationValue&& start, |
| InterpolationValue&& end) const { |
| if (SideTypes(start) != SideTypes(end)) |
| return nullptr; |
| |
| return PairwiseInterpolationValue(std::move(start.interpolable_value), |
| std::move(end.interpolable_value), |
| std::move(start.non_interpolable_value)); |
| } |
| |
| void CSSBorderImageLengthBoxInterpolationType::Composite( |
| UnderlyingValueOwner& underlying_value_owner, |
| double underlying_fraction, |
| const InterpolationValue& value, |
| double interpolation_fraction) const { |
| ListInterpolationFunctions::Composite( |
| underlying_value_owner, underlying_fraction, *this, value, |
| ListInterpolationFunctions::LengthMatchingStrategy::kEqual, |
| WTF::BindRepeating( |
| ListInterpolationFunctions::InterpolableValuesKnownCompatible), |
| WTF::BindRepeating(NonInterpolableSidesAreCompatible), |
| WTF::BindRepeating(CompositeSide)); |
| } |
| |
| void CSSBorderImageLengthBoxInterpolationType::ApplyStandardPropertyValue( |
| const InterpolableValue& interpolable_value, |
| const NonInterpolableValue* non_interpolable_value, |
| StyleResolverState& state) const { |
| const auto& list = To<InterpolableList>(interpolable_value); |
| const auto& non_interpolable_list = |
| To<NonInterpolableList>(*non_interpolable_value); |
| const auto& convert_side = [&list, &non_interpolable_list, |
| &state](wtf_size_t index) -> BorderImageLength { |
| switch (GetSideType(non_interpolable_list.Get(index))) { |
| case SideType::kNumber: |
| return clampTo<double>(To<InterpolableNumber>(list.Get(index))->Value(), |
| 0); |
| case SideType::kAuto: |
| return Length::Auto(); |
| case SideType::kLength: |
| return To<InterpolableLength>(*list.Get(index)) |
| .CreateLength(state.CssToLengthConversionData(), |
| kValueRangeNonNegative); |
| default: |
| NOTREACHED(); |
| return Length::Auto(); |
| } |
| }; |
| BorderImageLengthBox box(convert_side(kSideTop), convert_side(kSideRight), |
| convert_side(kSideBottom), convert_side(kSideLeft)); |
| SetBorderImageLengthBox(CssProperty(), *state.Style(), box); |
| } |
| |
| } // namespace blink |