| // 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_filter_list_interpolation_type.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/ptr_util.h" |
| #include "third_party/blink/renderer/core/animation/interpolable_filter.h" |
| #include "third_party/blink/renderer/core/animation/list_interpolation_functions.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_value_list.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 { |
| |
| const FilterOperations& GetFilterList(const CSSProperty& property, |
| const ComputedStyle& style) { |
| switch (property.PropertyID()) { |
| default: |
| NOTREACHED(); |
| FALLTHROUGH; |
| case CSSPropertyID::kBackdropFilter: |
| return style.BackdropFilter(); |
| case CSSPropertyID::kFilter: |
| return style.Filter(); |
| } |
| } |
| |
| void SetFilterList(const CSSProperty& property, |
| ComputedStyle& style, |
| const FilterOperations& filter_operations) { |
| switch (property.PropertyID()) { |
| case CSSPropertyID::kBackdropFilter: |
| style.SetBackdropFilter(filter_operations); |
| break; |
| case CSSPropertyID::kFilter: |
| style.SetFilter(filter_operations); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| class UnderlyingFilterListChecker |
| : public CSSInterpolationType::CSSConversionChecker { |
| public: |
| UnderlyingFilterListChecker(const InterpolableList* interpolable_list) { |
| wtf_size_t length = interpolable_list->length(); |
| types_.ReserveInitialCapacity(length); |
| for (wtf_size_t i = 0; i < length; i++) { |
| types_.push_back( |
| To<InterpolableFilter>(interpolable_list->Get(i))->GetType()); |
| } |
| } |
| |
| bool IsValid(const StyleResolverState&, |
| const InterpolationValue& underlying) const final { |
| const auto& underlying_list = |
| To<InterpolableList>(*underlying.interpolable_value); |
| if (underlying_list.length() != types_.size()) |
| return false; |
| for (wtf_size_t i = 0; i < types_.size(); i++) { |
| FilterOperation::OperationType other_type = |
| To<InterpolableFilter>(underlying_list.Get(i))->GetType(); |
| if (types_[i] != other_type) |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| Vector<FilterOperation::OperationType> types_; |
| }; |
| |
| class InheritedFilterListChecker |
| : public CSSInterpolationType::CSSConversionChecker { |
| public: |
| InheritedFilterListChecker(const CSSProperty& property, |
| const FilterOperations& filter_operations) |
| : property_(property), |
| filter_operations_wrapper_( |
| MakeGarbageCollected<FilterOperationsWrapper>(filter_operations)) {} |
| |
| bool IsValid(const StyleResolverState& state, |
| const InterpolationValue&) const final { |
| const FilterOperations& filter_operations = |
| filter_operations_wrapper_->Operations(); |
| return filter_operations == GetFilterList(property_, *state.ParentStyle()); |
| } |
| |
| private: |
| const CSSProperty& property_; |
| Persistent<FilterOperationsWrapper> filter_operations_wrapper_; |
| }; |
| |
| InterpolationValue ConvertFilterList(const FilterOperations& filter_operations, |
| double zoom) { |
| wtf_size_t length = filter_operations.size(); |
| auto interpolable_list = std::make_unique<InterpolableList>(length); |
| for (wtf_size_t i = 0; i < length; i++) { |
| std::unique_ptr<InterpolableFilter> result = |
| InterpolableFilter::MaybeCreate(*filter_operations.Operations()[i], |
| zoom); |
| if (!result) |
| return nullptr; |
| interpolable_list->Set(i, std::move(result)); |
| } |
| return InterpolationValue(std::move(interpolable_list)); |
| } |
| |
| class AlwaysInvalidateChecker |
| : public CSSInterpolationType::CSSConversionChecker { |
| public: |
| bool IsValid(const StyleResolverState& state, |
| const InterpolationValue& underlying) const final { |
| return false; |
| } |
| }; |
| } // namespace |
| |
| InterpolationValue CSSFilterListInterpolationType::MaybeConvertNeutral( |
| const InterpolationValue& underlying, |
| ConversionCheckers& conversion_checkers) const { |
| const auto* interpolable_list = |
| To<InterpolableList>(underlying.interpolable_value.get()); |
| conversion_checkers.push_back( |
| std::make_unique<UnderlyingFilterListChecker>(interpolable_list)); |
| // The neutral value for composition for a filter list is the empty list, as |
| // the additive operator is concatenation, so concat(underlying, []) == |
| // underlying. |
| return InterpolationValue(std::make_unique<InterpolableList>(0)); |
| } |
| |
| InterpolationValue CSSFilterListInterpolationType::MaybeConvertInitial( |
| const StyleResolverState&, |
| ConversionCheckers& conversion_checkers) const { |
| return ConvertFilterList( |
| GetFilterList(CssProperty(), ComputedStyle::InitialStyle()), 1); |
| } |
| |
| InterpolationValue CSSFilterListInterpolationType::MaybeConvertInherit( |
| const StyleResolverState& state, |
| ConversionCheckers& conversion_checkers) const { |
| const FilterOperations& inherited_filter_operations = |
| GetFilterList(CssProperty(), *state.ParentStyle()); |
| conversion_checkers.push_back(std::make_unique<InheritedFilterListChecker>( |
| CssProperty(), inherited_filter_operations)); |
| return ConvertFilterList(inherited_filter_operations, |
| state.Style()->EffectiveZoom()); |
| } |
| |
| InterpolationValue CSSFilterListInterpolationType::MaybeConvertValue( |
| const CSSValue& value, |
| const StyleResolverState*, |
| ConversionCheckers&) const { |
| auto* identifier_value = DynamicTo<CSSIdentifierValue>(value); |
| if (identifier_value && identifier_value->GetValueID() == CSSValueID::kNone) |
| return InterpolationValue(std::make_unique<InterpolableList>(0)); |
| |
| if (!value.IsBaseValueList()) |
| return nullptr; |
| |
| const auto& list = To<CSSValueList>(value); |
| wtf_size_t length = list.length(); |
| auto interpolable_list = std::make_unique<InterpolableList>(length); |
| for (wtf_size_t i = 0; i < length; i++) { |
| std::unique_ptr<InterpolableFilter> result = |
| InterpolableFilter::MaybeConvertCSSValue(list.Item(i)); |
| if (!result) |
| return nullptr; |
| interpolable_list->Set(i, std::move(result)); |
| } |
| return InterpolationValue(std::move(interpolable_list)); |
| } |
| |
| InterpolationValue |
| CSSFilterListInterpolationType::MaybeConvertStandardPropertyUnderlyingValue( |
| const ComputedStyle& style) const { |
| return ConvertFilterList(GetFilterList(CssProperty(), style), |
| style.EffectiveZoom()); |
| } |
| |
| PairwiseInterpolationValue CSSFilterListInterpolationType::MaybeMergeSingles( |
| InterpolationValue&& start, |
| InterpolationValue&& end) const { |
| auto& start_interpolable_list = |
| To<InterpolableList>(*start.interpolable_value); |
| auto& end_interpolable_list = To<InterpolableList>(*end.interpolable_value); |
| wtf_size_t start_length = start_interpolable_list.length(); |
| wtf_size_t end_length = end_interpolable_list.length(); |
| |
| for (wtf_size_t i = 0; i < start_length && i < end_length; i++) { |
| if (To<InterpolableFilter>(start_interpolable_list.Get(i))->GetType() != |
| To<InterpolableFilter>(end_interpolable_list.Get(i))->GetType()) |
| return nullptr; |
| } |
| |
| if (start_length == end_length) { |
| return PairwiseInterpolationValue(std::move(start.interpolable_value), |
| std::move(end.interpolable_value)); |
| } |
| |
| // Extend the shorter InterpolableList with neutral values that are compatible |
| // with corresponding filters in the longer list. |
| InterpolationValue& shorter = start_length < end_length ? start : end; |
| wtf_size_t shorter_length = std::min(start_length, end_length); |
| wtf_size_t longer_length = std::max(start_length, end_length); |
| InterpolableList& shorter_interpolable_list = start_length < end_length |
| ? start_interpolable_list |
| : end_interpolable_list; |
| const InterpolableList& longer_interpolable_list = |
| start_length < end_length ? end_interpolable_list |
| : start_interpolable_list; |
| auto extended_interpolable_list = |
| std::make_unique<InterpolableList>(longer_length); |
| for (wtf_size_t i = 0; i < longer_length; i++) { |
| if (i < shorter_length) |
| extended_interpolable_list->Set( |
| i, std::move(shorter_interpolable_list.GetMutable(i))); |
| else |
| extended_interpolable_list->Set( |
| i, InterpolableFilter::CreateInitialValue( |
| To<InterpolableFilter>(longer_interpolable_list.Get(i)) |
| ->GetType())); |
| } |
| shorter.interpolable_value = std::move(extended_interpolable_list); |
| |
| return PairwiseInterpolationValue(std::move(start.interpolable_value), |
| std::move(end.interpolable_value)); |
| } |
| |
| void CSSFilterListInterpolationType::Composite( |
| UnderlyingValueOwner& underlying_value_owner, |
| double underlying_fraction, |
| const InterpolationValue& value, |
| double interpolation_fraction) const { |
| // We do our compositing behavior in |PreInterpolationCompositeIfNeeded|; see |
| // the documentation on that method. |
| underlying_value_owner.Set(*this, value); |
| } |
| |
| void CSSFilterListInterpolationType::ApplyStandardPropertyValue( |
| const InterpolableValue& interpolable_value, |
| const NonInterpolableValue* non_interpolable_value, |
| StyleResolverState& state) const { |
| const auto& interpolable_list = To<InterpolableList>(interpolable_value); |
| wtf_size_t length = interpolable_list.length(); |
| |
| FilterOperations filter_operations; |
| filter_operations.Operations().ReserveCapacity(length); |
| for (wtf_size_t i = 0; i < length; i++) { |
| filter_operations.Operations().push_back( |
| To<InterpolableFilter>(interpolable_list.Get(i)) |
| ->CreateFilterOperation(state)); |
| } |
| SetFilterList(CssProperty(), *state.Style(), std::move(filter_operations)); |
| } |
| |
| InterpolationValue |
| CSSFilterListInterpolationType::PreInterpolationCompositeIfNeeded( |
| InterpolationValue value, |
| const InterpolationValue& underlying, |
| EffectModel::CompositeOperation composite, |
| ConversionCheckers& conversion_checkers) const { |
| DCHECK(!value.non_interpolable_value); |
| DCHECK(!underlying.non_interpolable_value); |
| |
| // Due to the post-interpolation composite optimization, the interpolation |
| // stack aggressively caches interpolated values. When we are doing |
| // pre-interpolation compositing, this can cause us to bake-in the composited |
| // result even when the underlying value is changing. This checker is a hack |
| // to disable that caching in this case. |
| // TODO(crbug.com/1009230): Remove this once our interpolation code isn't |
| // caching composited values. |
| conversion_checkers.push_back(std::make_unique<AlwaysInvalidateChecker>()); |
| |
| // The underlying value can be nullptr, most commonly if it contains a url(). |
| // TODO(crbug.com/1009229): Properly handle url() in filter composite. |
| if (!underlying.interpolable_value) |
| return nullptr; |
| |
| auto interpolable_list = std::unique_ptr<InterpolableList>( |
| To<InterpolableList>(value.interpolable_value.release())); |
| const auto& underlying_list = |
| To<InterpolableList>(*underlying.interpolable_value); |
| |
| if (composite == EffectModel::CompositeOperation::kCompositeAdd) { |
| return PerformAdditiveComposition(std::move(interpolable_list), |
| underlying_list); |
| } |
| DCHECK_EQ(composite, EffectModel::CompositeOperation::kCompositeAccumulate); |
| return PerformAccumulativeComposition(std::move(interpolable_list), |
| underlying_list); |
| } |
| |
| InterpolationValue CSSFilterListInterpolationType::PerformAdditiveComposition( |
| std::unique_ptr<InterpolableList> interpolable_list, |
| const InterpolableList& underlying_list) const { |
| // Per the spec, addition of filter lists is defined as concatenation. |
| // https://drafts.fxtf.org/filter-effects-1/#addition |
| auto composited_list = std::make_unique<InterpolableList>( |
| underlying_list.length() + interpolable_list->length()); |
| for (wtf_size_t i = 0; i < composited_list->length(); i++) { |
| if (i < underlying_list.length()) { |
| composited_list->Set(i, underlying_list.Get(i)->Clone()); |
| } else { |
| composited_list->Set( |
| i, interpolable_list->Get(i - underlying_list.length())->Clone()); |
| } |
| } |
| return InterpolationValue(std::move(composited_list)); |
| } |
| |
| InterpolationValue |
| CSSFilterListInterpolationType::PerformAccumulativeComposition( |
| std::unique_ptr<InterpolableList> interpolable_list, |
| const InterpolableList& underlying_list) const { |
| // Per the spec, accumulation of filter lists operates on pairwise addition of |
| // the underlying components. |
| // https://drafts.fxtf.org/filter-effects-1/#accumulation |
| wtf_size_t length = interpolable_list->length(); |
| wtf_size_t underlying_length = underlying_list.length(); |
| |
| // If any of the types don't match, fallback to replace behavior. |
| for (wtf_size_t i = 0; i < underlying_length && i < length; i++) { |
| if (To<InterpolableFilter>(underlying_list.Get(i))->GetType() != |
| To<InterpolableFilter>(interpolable_list->Get(i))->GetType()) |
| return InterpolationValue(std::move(interpolable_list)); |
| } |
| |
| // Otherwise, arithmetically combine the matching prefix of the lists then |
| // concatenate the remainder of the longer one. |
| wtf_size_t max_length = std::max(length, underlying_length); |
| auto composited_list = std::make_unique<InterpolableList>(max_length); |
| for (wtf_size_t i = 0; i < max_length; i++) { |
| if (i < underlying_length) { |
| composited_list->Set(i, underlying_list.Get(i)->Clone()); |
| if (i < length) |
| composited_list->GetMutable(i)->Add(*interpolable_list->Get(i)); |
| } else { |
| composited_list->Set(i, interpolable_list->Get(i)->Clone()); |
| } |
| } |
| |
| return InterpolationValue(std::move(composited_list)); |
| } |
| |
| } // namespace blink |