// Copyright 2018 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/list_interpolation_functions.h" #include #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/renderer/core/animation/css_number_interpolation_type.h" #include "third_party/blink/renderer/core/animation/interpolation_value.h" #include "third_party/blink/renderer/core/animation/underlying_value.h" #include "third_party/blink/renderer/platform/wtf/functional.h" namespace blink { namespace { class TestNonInterpolableValue : public NonInterpolableValue { public: ~TestNonInterpolableValue() final = default; static scoped_refptr Create(int value) { DCHECK_GE(value, 1); return base::AdoptRef(new TestNonInterpolableValue(value)); } int GetValue() const { return value_; } DECLARE_NON_INTERPOLABLE_VALUE_TYPE(); private: explicit TestNonInterpolableValue(int value) : value_(value) {} int value_; }; DEFINE_NON_INTERPOLABLE_VALUE_TYPE(TestNonInterpolableValue); // DEFINE_NON_INTERPOLABLE_VALUE_TYPE_CASTS won't work in anonymous namespaces. inline const TestNonInterpolableValue& ToTestNonInterpolableValue( const NonInterpolableValue& value) { DCHECK_EQ(value.GetType(), TestNonInterpolableValue::static_type_); return static_cast(value); } class TestUnderlyingValue : public UnderlyingValue { STACK_ALLOCATED(); public: TestUnderlyingValue(InterpolationValue& interpolation_value) : interpolation_value_(interpolation_value) {} InterpolableValue& MutableInterpolableValue() final { return *interpolation_value_.interpolable_value; } void SetInterpolableValue( std::unique_ptr interpolable_value) final { interpolation_value_.interpolable_value = std::move(interpolable_value); } const NonInterpolableValue* GetNonInterpolableValue() const final { return interpolation_value_.non_interpolable_value.get(); } void SetNonInterpolableValue( scoped_refptr non_interpolable_value) final { interpolation_value_.non_interpolable_value = non_interpolable_value; } private: InterpolationValue& interpolation_value_; }; // Creates an InterpolationValue containing a list of interpolable and // non-interpolable values from the pairs of input. InterpolationValue CreateInterpolableList( const Vector>& values) { return ListInterpolationFunctions::CreateList( values.size(), [&values](size_t i) { return InterpolationValue( std::make_unique(values[i].first), TestNonInterpolableValue::Create(values[i].second)); }); } // Creates an InterpolationValue which contains a list of interpolable values, // but a non-interpolable list of nullptrs. InterpolationValue CreateInterpolableList(const Vector& values) { return ListInterpolationFunctions::CreateList( values.size(), [&values](size_t i) { return InterpolationValue( std::make_unique(values[i]), nullptr); }); } // Creates an InterpolationValue which contains a list of non-interpolable // values, but an interpolable list of zeroes. InterpolationValue CreateNonInterpolableList(const Vector& values) { return ListInterpolationFunctions::CreateList( values.size(), [&values](size_t i) { return InterpolationValue(std::make_unique(0), TestNonInterpolableValue::Create(values[i])); }); } // A simple helper to specify which InterpolableValues in an InterpolableList // should be considered compatible. class InterpolableValuesCompatibilityHelper { public: // The input |answers| vector must be at least as large as the // InterpolableList being tested, or |AreCompatible| will DCHECK. InterpolableValuesCompatibilityHelper(Vector answers) : answers_(answers), current_index_(0) {} // Callers should pass a reference to this function to // ListInterpolationFunctions::Composite. bool AreCompatible(const InterpolableValue*, const InterpolableValue*) { DCHECK(current_index_ < answers_.size()); return answers_.at(current_index_++); } private: Vector answers_; wtf_size_t current_index_; }; bool NonInterpolableValuesAreCompatible(const NonInterpolableValue* a, const NonInterpolableValue* b) { return (a ? ToTestNonInterpolableValue(*a).GetValue() : 0) == (b ? ToTestNonInterpolableValue(*b).GetValue() : 0); } PairwiseInterpolationValue MaybeMergeSingles(InterpolationValue&& start, InterpolationValue&& end) { if (!NonInterpolableValuesAreCompatible(start.non_interpolable_value.get(), end.non_interpolable_value.get())) { return nullptr; } return PairwiseInterpolationValue(std::move(start.interpolable_value), std::move(end.interpolable_value), nullptr); } void Composite(UnderlyingValue& underlying_value, double underlying_fraction, const InterpolableValue& interpolable_value, const NonInterpolableValue* non_interpolable_value) { DCHECK(NonInterpolableValuesAreCompatible( underlying_value.GetNonInterpolableValue(), non_interpolable_value)); underlying_value.MutableInterpolableValue().ScaleAndAdd(underlying_fraction, interpolable_value); } } // namespace TEST(ListInterpolationFunctionsTest, EqualMergeSinglesSameLengths) { auto list1 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); auto list2 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); auto pairwise = ListInterpolationFunctions::MaybeMergeSingles( std::move(list1), std::move(list2), ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating(MaybeMergeSingles)); EXPECT_TRUE(pairwise); } TEST(ListInterpolationFunctionsTest, EqualMergeSinglesDifferentLengths) { auto list1 = CreateInterpolableList({1.0, 2.0, 3.0}); auto list2 = CreateInterpolableList({1.0, 3.0}); auto pairwise = ListInterpolationFunctions::MaybeMergeSingles( std::move(list1), std::move(list2), ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating(MaybeMergeSingles)); EXPECT_FALSE(pairwise); } TEST(ListInterpolationFunctionsTest, EqualMergeSinglesIncompatibleValues) { auto list1 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); auto list2 = CreateInterpolableList({{1.0, 1}, {2.0, 4}, {3.0, 3}}); auto pairwise = ListInterpolationFunctions::MaybeMergeSingles( std::move(list1), std::move(list2), ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating(MaybeMergeSingles)); EXPECT_FALSE(pairwise); } TEST(ListInterpolationFunctionsTest, EqualMergeSinglesIncompatibleNullptrs) { auto list1 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); auto list2 = CreateInterpolableList({1, 2, 3}); auto pairwise = ListInterpolationFunctions::MaybeMergeSingles( std::move(list1), std::move(list2), ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating(MaybeMergeSingles)); EXPECT_FALSE(pairwise); } TEST(ListInterpolationFunctionsTest, EqualCompositeSameLengths) { auto list1 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); auto list2 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); PropertyHandle property_handle(GetCSSPropertyZIndex()); CSSNumberInterpolationType interpolation_type(property_handle); UnderlyingValueOwner owner; owner.Set(interpolation_type, std::move(list1)); ListInterpolationFunctions::Composite( owner, 1.0, interpolation_type, list2, ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating( ListInterpolationFunctions::InterpolableValuesKnownCompatible), WTF::BindRepeating(NonInterpolableValuesAreCompatible), WTF::BindRepeating(Composite)); const auto& result = To(*owner.Value().interpolable_value); ASSERT_EQ(result.length(), 3u); EXPECT_EQ(To(result.Get(0))->Value(), 2.0); EXPECT_EQ(To(result.Get(1))->Value(), 4.0); EXPECT_EQ(To(result.Get(2))->Value(), 6.0); } // Two lists of different lengths are not interpolable, so we expect the // underlying value to be replaced. TEST(ListInterpolationFunctionsTest, EqualCompositeDifferentLengths) { auto list1 = CreateInterpolableList({1.0, 2.0, 3.0}); auto list2 = CreateInterpolableList({4.0, 5.0}); PropertyHandle property_handle(GetCSSPropertyZIndex()); CSSNumberInterpolationType interpolation_type(property_handle); UnderlyingValueOwner owner; owner.Set(interpolation_type, std::move(list1)); ListInterpolationFunctions::Composite( owner, 1.0, interpolation_type, list2, ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating( ListInterpolationFunctions::InterpolableValuesKnownCompatible), WTF::BindRepeating(NonInterpolableValuesAreCompatible), WTF::BindRepeating(Composite)); const auto& result = To(*owner.Value().interpolable_value); ASSERT_EQ(result.length(), 2u); EXPECT_EQ(To(result.Get(0))->Value(), 4.0); EXPECT_EQ(To(result.Get(1))->Value(), 5.0); } // If one (or more) of the element pairs are incompatible, the list as a whole // is non-interpolable. We expect the underlying value to be replaced. TEST(ListInterpolationFunctionsTest, EqualCompositeIncompatibleInterpolableValues) { auto list1 = CreateInterpolableList({1.0, 2.0, 3.0}); auto list2 = CreateInterpolableList({4.0, 5.0, 6.0}); InterpolableValuesCompatibilityHelper compatibility_helper( {true, false, true}); PropertyHandle property_handle(GetCSSPropertyZIndex()); CSSNumberInterpolationType interpolation_type(property_handle); UnderlyingValueOwner owner; owner.Set(interpolation_type, std::move(list1)); ListInterpolationFunctions::Composite( owner, 1.0, interpolation_type, list2, ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating(&InterpolableValuesCompatibilityHelper::AreCompatible, WTF::Unretained(&compatibility_helper)), WTF::BindRepeating(NonInterpolableValuesAreCompatible), WTF::BindRepeating(Composite)); const auto& result = To(*owner.Value().interpolable_value); ASSERT_EQ(result.length(), 3u); EXPECT_EQ(To(result.Get(0))->Value(), 4.0); EXPECT_EQ(To(result.Get(1))->Value(), 5.0); EXPECT_EQ(To(result.Get(2))->Value(), 6.0); } // If one (or more) of the element pairs are incompatible, the list as a whole // is non-interpolable. We expect the underlying value to be replaced. TEST(ListInterpolationFunctionsTest, EqualCompositeIncompatibleNonInterpolableValues) { auto list1 = CreateInterpolableList({{1.0, 1}, {2.0, 2}, {3.0, 3}}); auto list2 = CreateInterpolableList({{4.0, 1}, {5.0, 4}, {6.0, 3}}); PropertyHandle property_handle(GetCSSPropertyZIndex()); CSSNumberInterpolationType interpolation_type(property_handle); UnderlyingValueOwner owner; owner.Set(interpolation_type, std::move(list1)); ListInterpolationFunctions::Composite( owner, 1.0, interpolation_type, list2, ListInterpolationFunctions::LengthMatchingStrategy::kEqual, WTF::BindRepeating( ListInterpolationFunctions::InterpolableValuesKnownCompatible), WTF::BindRepeating(NonInterpolableValuesAreCompatible), WTF::BindRepeating(Composite)); const auto& result = To(*owner.Value().interpolable_value); ASSERT_EQ(result.length(), 3u); EXPECT_EQ(To(result.Get(0))->Value(), 4.0); EXPECT_EQ(To(result.Get(1))->Value(), 5.0); EXPECT_EQ(To(result.Get(2))->Value(), 6.0); } TEST(ListInterpolationFunctionsTest, BuilderNoModify) { auto list = CreateNonInterpolableList({1, 2, 3}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); } auto& after = To(*list.non_interpolable_value); EXPECT_EQ(&before, &after); ASSERT_EQ(3u, before.length()); EXPECT_EQ(1, ToTestNonInterpolableValue(*before.Get(0)).GetValue()); EXPECT_EQ(2, ToTestNonInterpolableValue(*before.Get(1)).GetValue()); EXPECT_EQ(3, ToTestNonInterpolableValue(*before.Get(2)).GetValue()); } TEST(ListInterpolationFunctionsTest, BuilderModifyFirst) { auto list = CreateNonInterpolableList({1, 2, 3}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); builder.Set(0, TestNonInterpolableValue::Create(4)); } auto& after = To(*list.non_interpolable_value); EXPECT_NE(&before, &after); ASSERT_EQ(3u, after.length()); EXPECT_EQ(4, ToTestNonInterpolableValue(*after.Get(0)).GetValue()); EXPECT_EQ(2, ToTestNonInterpolableValue(*after.Get(1)).GetValue()); EXPECT_EQ(3, ToTestNonInterpolableValue(*after.Get(2)).GetValue()); } TEST(ListInterpolationFunctionsTest, BuilderModifyMiddle) { auto list = CreateNonInterpolableList({1, 2, 3}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); builder.Set(1, TestNonInterpolableValue::Create(4)); } auto& after = To(*list.non_interpolable_value); EXPECT_NE(&before, &after); ASSERT_EQ(3u, after.length()); EXPECT_EQ(1, ToTestNonInterpolableValue(*after.Get(0)).GetValue()); EXPECT_EQ(4, ToTestNonInterpolableValue(*after.Get(1)).GetValue()); EXPECT_EQ(3, ToTestNonInterpolableValue(*after.Get(2)).GetValue()); } TEST(ListInterpolationFunctionsTest, BuilderModifyLast) { auto list = CreateNonInterpolableList({1, 2, 3}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); builder.Set(2, TestNonInterpolableValue::Create(4)); } auto& after = To(*list.non_interpolable_value); EXPECT_NE(&before, &after); ASSERT_EQ(3u, after.length()); EXPECT_EQ(1, ToTestNonInterpolableValue(*after.Get(0)).GetValue()); EXPECT_EQ(2, ToTestNonInterpolableValue(*after.Get(1)).GetValue()); EXPECT_EQ(4, ToTestNonInterpolableValue(*after.Get(2)).GetValue()); } TEST(ListInterpolationFunctionsTest, BuilderModifyAll) { auto list = CreateNonInterpolableList({1, 2, 3}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); builder.Set(0, TestNonInterpolableValue::Create(4)); builder.Set(1, TestNonInterpolableValue::Create(5)); builder.Set(2, TestNonInterpolableValue::Create(6)); } auto& after = To(*list.non_interpolable_value); EXPECT_NE(&before, &after); ASSERT_EQ(3u, after.length()); EXPECT_EQ(4, ToTestNonInterpolableValue(*after.Get(0)).GetValue()); EXPECT_EQ(5, ToTestNonInterpolableValue(*after.Get(1)).GetValue()); EXPECT_EQ(6, ToTestNonInterpolableValue(*after.Get(2)).GetValue()); } TEST(ListInterpolationFunctionsTest, BuilderModifyReverse) { auto list = CreateNonInterpolableList({1, 2, 3, 4, 5}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); builder.Set(3, TestNonInterpolableValue::Create(6)); builder.Set(1, TestNonInterpolableValue::Create(7)); } auto& after = To(*list.non_interpolable_value); EXPECT_NE(&before, &after); ASSERT_EQ(5u, after.length()); EXPECT_EQ(1, ToTestNonInterpolableValue(*after.Get(0)).GetValue()); EXPECT_EQ(7, ToTestNonInterpolableValue(*after.Get(1)).GetValue()); EXPECT_EQ(3, ToTestNonInterpolableValue(*after.Get(2)).GetValue()); EXPECT_EQ(6, ToTestNonInterpolableValue(*after.Get(3)).GetValue()); EXPECT_EQ(5, ToTestNonInterpolableValue(*after.Get(4)).GetValue()); } TEST(ListInterpolationFunctionsTest, BuilderModifyListWithOneItem) { auto list = CreateNonInterpolableList({1}); auto& before = To(*list.non_interpolable_value); { TestUnderlyingValue underlying_value(list); NonInterpolableList::AutoBuilder builder(underlying_value); builder.Set(0, TestNonInterpolableValue::Create(4)); } auto& after = To(*list.non_interpolable_value); EXPECT_NE(&before, &after); EXPECT_EQ(4, ToTestNonInterpolableValue(*after.Get(0)).GetValue()); } } // namespace blink