Commit 746e99fb authored by Anders Hartvoll Ruud's avatar Anders Hartvoll Ruud Committed by Commit Bot

Add kEquals length matching strategy to ListInterpolationFunctions.

This is needed to implement correct list behavior for interpolation of
custom property lists.

R=flackr@chromium.org

Bug: 868959
Change-Id: I04a28307e4a4a0bdcc262fd289ec12be7bf40a50
Reviewed-on: https://chromium-review.googlesource.com/1212724
Commit-Queue: Anders Ruud <andruud@chromium.org>
Reviewed-by: default avatarRobert Flack <flackr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589904}
parent 8045942e
...@@ -1710,6 +1710,7 @@ jumbo_source_set("unit_tests") { ...@@ -1710,6 +1710,7 @@ jumbo_source_set("unit_tests") {
"animation/interpolation_effect_test.cc", "animation/interpolation_effect_test.cc",
"animation/keyframe_effect_model_test.cc", "animation/keyframe_effect_model_test.cc",
"animation/keyframe_effect_test.cc", "animation/keyframe_effect_test.cc",
"animation/list_interpolation_functions_test.cc",
"animation/property_handle_test.cc", "animation/property_handle_test.cc",
"animation/scroll_timeline_test.cc", "animation/scroll_timeline_test.cc",
"animation/timing_calculations_test.cc", "animation/timing_calculations_test.cc",
......
...@@ -57,8 +57,12 @@ static wtf_size_t MatchLengths( ...@@ -57,8 +57,12 @@ static wtf_size_t MatchLengths(
ListInterpolationFunctions::LengthMatchingStrategy ListInterpolationFunctions::LengthMatchingStrategy
length_matching_strategy) { length_matching_strategy) {
if (length_matching_strategy == if (length_matching_strategy ==
ListInterpolationFunctions::LengthMatchingStrategy:: ListInterpolationFunctions::LengthMatchingStrategy::kEqual) {
kLowestCommonMultiple) { DCHECK_EQ(start_length, end_length);
return start_length;
} else if (length_matching_strategy ==
ListInterpolationFunctions::LengthMatchingStrategy::
kLowestCommonMultiple) {
// Combining the length expansion of lowestCommonMultiple with CSS // Combining the length expansion of lowestCommonMultiple with CSS
// transitions has the potential to create pathological cases where this // transitions has the potential to create pathological cases where this
// algorithm compounds upon itself as the user starts transitions on already // algorithm compounds upon itself as the user starts transitions on already
...@@ -84,6 +88,12 @@ PairwiseInterpolationValue ListInterpolationFunctions::MaybeMergeSingles( ...@@ -84,6 +88,12 @@ PairwiseInterpolationValue ListInterpolationFunctions::MaybeMergeSingles(
const wtf_size_t end_length = const wtf_size_t end_length =
ToInterpolableList(*end.interpolable_value).length(); ToInterpolableList(*end.interpolable_value).length();
if (length_matching_strategy ==
ListInterpolationFunctions::LengthMatchingStrategy::kEqual &&
(start_length != end_length)) {
return nullptr;
}
if (start_length == 0 && end_length == 0) { if (start_length == 0 && end_length == 0) {
return PairwiseInterpolationValue(std::move(start.interpolable_value), return PairwiseInterpolationValue(std::move(start.interpolable_value),
std::move(end.interpolable_value), std::move(end.interpolable_value),
...@@ -266,15 +276,24 @@ void ListInterpolationFunctions::Composite( ...@@ -266,15 +276,24 @@ void ListInterpolationFunctions::Composite(
const wtf_size_t underlying_length = const wtf_size_t underlying_length =
ToInterpolableList(*underlying_value_owner.Value().interpolable_value) ToInterpolableList(*underlying_value_owner.Value().interpolable_value)
.length(); .length();
const InterpolableList& interpolable_list =
ToInterpolableList(*value.interpolable_value);
const wtf_size_t value_length = interpolable_list.length();
if (length_matching_strategy ==
ListInterpolationFunctions::LengthMatchingStrategy::kEqual &&
(underlying_length != value_length)) {
underlying_value_owner.Set(type, value);
return;
}
if (underlying_length == 0) { if (underlying_length == 0) {
DCHECK(!underlying_value_owner.Value().non_interpolable_value); DCHECK(!underlying_value_owner.Value().non_interpolable_value);
underlying_value_owner.Set(type, value); underlying_value_owner.Set(type, value);
return; return;
} }
const InterpolableList& interpolable_list =
ToInterpolableList(*value.interpolable_value);
const wtf_size_t value_length = interpolable_list.length();
if (value_length == 0) { if (value_length == 0) {
DCHECK(!value.non_interpolable_value); DCHECK(!value.non_interpolable_value);
underlying_value_owner.MutableValue().interpolable_value->Scale( underlying_value_owner.MutableValue().interpolable_value->Scale(
...@@ -314,8 +333,11 @@ void ListInterpolationFunctions::Composite( ...@@ -314,8 +333,11 @@ void ListInterpolationFunctions::Composite(
non_interpolable_list.Get(i % value_length)); non_interpolable_list.Get(i % value_length));
} }
} else { } else {
DCHECK_EQ(length_matching_strategy, LengthMatchingStrategy::kPadToLargest); DCHECK(length_matching_strategy == LengthMatchingStrategy::kPadToLargest ||
length_matching_strategy == LengthMatchingStrategy::kEqual);
if (underlying_length < final_length) { if (underlying_length < final_length) {
DCHECK_EQ(length_matching_strategy,
LengthMatchingStrategy::kPadToLargest);
DCHECK_EQ(value_length, final_length); DCHECK_EQ(value_length, final_length);
PadToSameLength(underlying_value, value); PadToSameLength(underlying_value, value);
} }
......
...@@ -15,7 +15,7 @@ namespace blink { ...@@ -15,7 +15,7 @@ namespace blink {
class UnderlyingValueOwner; class UnderlyingValueOwner;
class InterpolationType; class InterpolationType;
class ListInterpolationFunctions { class CORE_EXPORT ListInterpolationFunctions {
public: public:
template <typename CreateItemCallback> template <typename CreateItemCallback>
static InterpolationValue CreateList(wtf_size_t length, CreateItemCallback); static InterpolationValue CreateList(wtf_size_t length, CreateItemCallback);
...@@ -23,7 +23,11 @@ class ListInterpolationFunctions { ...@@ -23,7 +23,11 @@ class ListInterpolationFunctions {
return InterpolationValue(InterpolableList::Create(0)); return InterpolationValue(InterpolableList::Create(0));
} }
enum class LengthMatchingStrategy { kLowestCommonMultiple, kPadToLargest }; enum class LengthMatchingStrategy {
kEqual,
kLowestCommonMultiple,
kPadToLargest
};
using MergeSingleItemConversionsCallback = using MergeSingleItemConversionsCallback =
base::RepeatingCallback<PairwiseInterpolationValue(InterpolationValue&&, base::RepeatingCallback<PairwiseInterpolationValue(InterpolationValue&&,
...@@ -59,7 +63,7 @@ class ListInterpolationFunctions { ...@@ -59,7 +63,7 @@ class ListInterpolationFunctions {
CompositeItemCallback); CompositeItemCallback);
}; };
class NonInterpolableList : public NonInterpolableValue { class CORE_EXPORT NonInterpolableList : public NonInterpolableValue {
public: public:
~NonInterpolableList() final = default; ~NonInterpolableList() final = default;
......
// 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 <utility>
#include <vector>
#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"
namespace blink {
namespace {
class TestNonInterpolableValue : public NonInterpolableValue {
public:
~TestNonInterpolableValue() final = default;
static scoped_refptr<TestNonInterpolableValue> 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<const TestNonInterpolableValue&>(value);
}
// Creates an InterpolationValue containing a list of interpolable and
// non-interpolable values from the pairs of input.
InterpolationValue CreateInterpolableList(
const std::vector<std::pair<double, int>>& values) {
return ListInterpolationFunctions::CreateList(
values.size(), [&values](size_t i) {
return InterpolationValue(
InterpolableNumber::Create(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 std::vector<double>& values) {
return ListInterpolationFunctions::CreateList(values.size(), [&values](
size_t i) {
return InterpolationValue(InterpolableNumber::Create(values[i]), nullptr);
});
}
bool NonInterpolableValuesAreCompatible(const NonInterpolableValue* a,
const NonInterpolableValue* b) {
// Note that '0' may never be held by TestNonInterpolableValues. See
// DCHECK in TestNonInterpolableValue::Create.
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(
std::unique_ptr<InterpolableValue>& underlying_interpolable_value,
scoped_refptr<NonInterpolableValue>& underlying_non_interpolable_value,
double underlying_fraction,
const InterpolableValue& interpolable_value,
const NonInterpolableValue* non_interpolable_value) {
DCHECK(NonInterpolableValuesAreCompatible(
underlying_non_interpolable_value.get(), non_interpolable_value));
underlying_interpolable_value->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(NonInterpolableValuesAreCompatible),
WTF::BindRepeating(Composite));
const auto& result = ToInterpolableList(*owner.Value().interpolable_value);
ASSERT_EQ(result.length(), 3u);
EXPECT_EQ(ToInterpolableNumber(result.Get(0))->Value(), 2.0);
EXPECT_EQ(ToInterpolableNumber(result.Get(1))->Value(), 4.0);
EXPECT_EQ(ToInterpolableNumber(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(NonInterpolableValuesAreCompatible),
WTF::BindRepeating(Composite));
const auto& result = ToInterpolableList(*owner.Value().interpolable_value);
ASSERT_EQ(result.length(), 2u);
EXPECT_EQ(ToInterpolableNumber(result.Get(0))->Value(), 4.0);
EXPECT_EQ(ToInterpolableNumber(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, EqualCompositeIncompatibleValues) {
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(NonInterpolableValuesAreCompatible),
WTF::BindRepeating(Composite));
const auto& result = ToInterpolableList(*owner.Value().interpolable_value);
ASSERT_EQ(result.length(), 3u);
EXPECT_EQ(ToInterpolableNumber(result.Get(0))->Value(), 4.0);
EXPECT_EQ(ToInterpolableNumber(result.Get(1))->Value(), 5.0);
EXPECT_EQ(ToInterpolableNumber(result.Get(2))->Value(), 6.0);
}
} // namespace blink
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment