blob: 06caf9e7dec35d00a340cd62dccc4c6ba72992bd [file] [log] [blame]
// 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