blob: 4a75261a456e7af0aca0bf8df0781265f32ed8ee [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/animation/keyframe_effect_model.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/core/animation/animation_test_helpers.h"
#include "third_party/blink/renderer/core/animation/css/compositor_keyframe_color.h"
#include "third_party/blink/renderer/core/animation/css/compositor_keyframe_double.h"
#include "third_party/blink/renderer/core/animation/css/compositor_keyframe_value_factory.h"
#include "third_party/blink/renderer/core/animation/css_default_interpolation_type.h"
#include "third_party/blink/renderer/core/animation/interpolable_length.h"
#include "third_party/blink/renderer/core/animation/invalidatable_interpolation.h"
#include "third_party/blink/renderer/core/animation/string_keyframe.h"
#include "third_party/blink/renderer/core/css/css_primitive_value.h"
#include "third_party/blink/renderer/core/css/css_test_helpers.h"
#include "third_party/blink/renderer/core/css/property_registry.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
#include "third_party/skia/include/core/SkColor.h"
namespace blink {
using animation_test_helpers::EnsureInterpolatedValueCached;
class AnimationKeyframeEffectModel : public PageTestBase {
protected:
void SetUp() override {
PageTestBase::SetUp(IntSize());
GetDocument().UpdateStyleAndLayoutTree();
element = GetDocument().CreateElementForBinding("foo");
GetDocument().body()->appendChild(element);
}
void ExpectLengthValue(double expected_value,
Interpolation* interpolation_value) {
ActiveInterpolations* interpolations =
MakeGarbageCollected<ActiveInterpolations>();
interpolations->push_back(interpolation_value);
EnsureInterpolatedValueCached(interpolations, GetDocument(), element);
const auto* typed_value =
To<InvalidatableInterpolation>(interpolation_value)
->GetCachedValueForTesting();
// Length values are stored as an |InterpolableLength|; here we assume
// pixels.
ASSERT_TRUE(typed_value->GetInterpolableValue().IsLength());
const InterpolableLength& length =
To<InterpolableLength>(typed_value->GetInterpolableValue());
// Lengths are computed in logical units, which are quantized to 64ths of
// a pixel.
EXPECT_NEAR(expected_value,
length.CreateCSSValue(kValueRangeAll)->GetDoubleValue(),
/*abs_error=*/0.02);
}
void ExpectNonInterpolableValue(const String& expected_value,
Interpolation* interpolation_value) {
ActiveInterpolations* interpolations =
MakeGarbageCollected<ActiveInterpolations>();
interpolations->push_back(interpolation_value);
EnsureInterpolatedValueCached(interpolations, GetDocument(), element);
const auto* typed_value =
To<InvalidatableInterpolation>(interpolation_value)
->GetCachedValueForTesting();
const NonInterpolableValue* non_interpolable_value =
typed_value->GetNonInterpolableValue();
ASSERT_TRUE(IsA<CSSDefaultNonInterpolableValue>(non_interpolable_value));
const CSSValue* css_value =
To<CSSDefaultNonInterpolableValue>(non_interpolable_value)->CssValue();
EXPECT_EQ(expected_value, css_value->CssText());
}
Persistent<Element> element;
};
const AnimationTimeDelta kDuration = AnimationTimeDelta::FromSecondsD(1);
StringKeyframeVector KeyframesAtZeroAndOne(CSSPropertyID property,
const String& zero_value,
const String& one_value) {
StringKeyframeVector keyframes(2);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(
property, zero_value, SecureContextMode::kInsecureContext, nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(1.0);
keyframes[1]->SetCSSPropertyValue(
property, one_value, SecureContextMode::kInsecureContext, nullptr);
return keyframes;
}
StringKeyframeVector KeyframesAtZeroAndOne(
AtomicString property_name,
const String& zero_value,
const String& one_value) {
StringKeyframeVector keyframes(2);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(
property_name, zero_value, SecureContextMode::kInsecureContext, nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(1.0);
keyframes[1]->SetCSSPropertyValue(
property_name, one_value, SecureContextMode::kInsecureContext, nullptr);
return keyframes;
}
const PropertySpecificKeyframeVector& ConstructEffectAndGetKeyframes(
const AtomicString& property_name,
const AtomicString& type,
Document* document,
Element* element,
const String& zero_value,
const String& one_value,
ExceptionState& exception_state) {
css_test_helpers::RegisterProperty(*document, property_name, type, zero_value,
false);
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(property_name, zero_value, one_value);
element->style()->setProperty(document->GetExecutionContext(), property_name,
zero_value, g_empty_string, exception_state);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
auto style = document->GetStyleResolver().StyleForElement(
element, StyleRecalcContext());
// Snapshot should update first time after construction
EXPECT_TRUE(effect->SnapshotAllCompositorKeyframesIfNecessary(
*element, *style, nullptr));
return *effect->GetPropertySpecificKeyframes(PropertyHandle(property_name));
}
void ExpectProperty(CSSPropertyID property,
Interpolation* interpolation_value) {
auto* interpolation = To<InvalidatableInterpolation>(interpolation_value);
const PropertyHandle& property_handle = interpolation->GetProperty();
ASSERT_TRUE(property_handle.IsCSSProperty());
ASSERT_EQ(property, property_handle.GetCSSProperty().PropertyID());
}
Interpolation* FindValue(HeapVector<Member<Interpolation>>& values,
CSSPropertyID id) {
for (auto& value : values) {
const auto& property =
To<InvalidatableInterpolation>(value.Get())->GetProperty();
if (property.IsCSSProperty() &&
property.GetCSSProperty().PropertyID() == id)
return value;
}
return nullptr;
}
TEST_F(AnimationKeyframeEffectModel, BasicOperation) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kFontFamily, "serif", "cursive");
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ASSERT_EQ(1UL, values.size());
ExpectProperty(CSSPropertyID::kFontFamily, values.at(0));
ExpectNonInterpolableValue("cursive", values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, CompositeReplaceNonInterpolable) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kFontFamily, "serif", "cursive");
keyframes[0]->SetComposite(EffectModel::kCompositeReplace);
keyframes[1]->SetComposite(EffectModel::kCompositeReplace);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectNonInterpolableValue("cursive", values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, CompositeReplace) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
keyframes[0]->SetComposite(EffectModel::kCompositeReplace);
keyframes[1]->SetComposite(EffectModel::kCompositeReplace);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue(3.0 * 0.4 + 5.0 * 0.6, values.at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_CompositeAdd) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
keyframes[0]->SetComposite(EffectModel::kCompositeAdd);
keyframes[1]->SetComposite(EffectModel::kCompositeAdd);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue((7.0 + 3.0) * 0.4 + (7.0 + 5.0) * 0.6, values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, CompositeEaseIn) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
keyframes[0]->SetComposite(EffectModel::kCompositeReplace);
keyframes[0]->SetEasing(CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE_IN));
keyframes[1]->SetComposite(EffectModel::kCompositeReplace);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
// CubicBezier(0.42, 0, 1, 1)(0.6) = 0.4291197695757142.
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue(3.85824, values.at(0));
effect->Sample(0, 0.6, kDuration * 100, values);
ExpectLengthValue(3.85824, values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, CompositeCubicBezier) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
keyframes[0]->SetComposite(EffectModel::kCompositeReplace);
keyframes[0]->SetEasing(CubicBezierTimingFunction::Create(0.42, 0, 0.58, 1));
keyframes[1]->SetComposite(EffectModel::kCompositeReplace);
// CubicBezier(0.42, 0, 0.58, 1)(0.6) = 0.6681161300485039.
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue(4.336232, values.at(0));
effect->Sample(0, 0.6, kDuration * 1000, values);
ExpectLengthValue(4.336232, values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, ExtrapolateReplaceNonInterpolable) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kFontFamily, "serif", "cursive");
keyframes[0]->SetComposite(EffectModel::kCompositeReplace);
keyframes[1]->SetComposite(EffectModel::kCompositeReplace);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 1.6, kDuration, values);
ExpectNonInterpolableValue("cursive", values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, ExtrapolateReplace) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
keyframes[0]->SetComposite(EffectModel::kCompositeReplace);
keyframes[1]->SetComposite(EffectModel::kCompositeReplace);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 1.6, kDuration, values);
ExpectLengthValue(3.0 * -0.6 + 5.0 * 1.6, values.at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_ExtrapolateAdd) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
keyframes[0]->SetComposite(EffectModel::kCompositeAdd);
keyframes[1]->SetComposite(EffectModel::kCompositeAdd);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 1.6, kDuration, values);
ExpectLengthValue((7.0 + 3.0) * -0.6 + (7.0 + 5.0) * 1.6, values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, ZeroKeyframes) {
auto* effect =
MakeGarbageCollected<StringKeyframeEffectModel>(StringKeyframeVector());
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.5, kDuration, values);
EXPECT_TRUE(values.IsEmpty());
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_SingleKeyframeAtOffsetZero) {
StringKeyframeVector keyframes(1);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_SingleKeyframeAtOffsetOne) {
StringKeyframeVector keyframes(1);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(1.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kLeft, "5px",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue(7.0 * 0.4 + 5.0 * 0.6, values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, MoreThanTwoKeyframes) {
StringKeyframeVector keyframes(3);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(0.5);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "sans-serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[2]->SetOffset(1.0);
keyframes[2]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "cursive",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.3, kDuration, values);
ExpectNonInterpolableValue("sans-serif", values.at(0));
effect->Sample(0, 0.8, kDuration, values);
ExpectNonInterpolableValue("cursive", values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, EndKeyframeOffsetsUnspecified) {
StringKeyframeVector keyframes(3);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(0.5);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "cursive",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[2]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.1, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
effect->Sample(0, 0.6, kDuration, values);
ExpectNonInterpolableValue("cursive", values.at(0));
effect->Sample(0, 0.9, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, SampleOnKeyframe) {
StringKeyframeVector keyframes(3);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(0.5);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "cursive",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[2]->SetOffset(1.0);
keyframes[2]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.0, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
effect->Sample(0, 0.5, kDuration, values);
ExpectNonInterpolableValue("cursive", values.at(0));
effect->Sample(0, 1.0, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, MultipleKeyframesWithSameOffset) {
StringKeyframeVector keyframes(9);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(0.1);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "sans-serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[2]->SetOffset(0.1);
keyframes[2]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "monospace",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[3] = MakeGarbageCollected<StringKeyframe>();
keyframes[3]->SetOffset(0.5);
keyframes[3]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "cursive",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[4] = MakeGarbageCollected<StringKeyframe>();
keyframes[4]->SetOffset(0.5);
keyframes[4]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "fantasy",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[5] = MakeGarbageCollected<StringKeyframe>();
keyframes[5]->SetOffset(0.5);
keyframes[5]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "system-ui",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[6] = MakeGarbageCollected<StringKeyframe>();
keyframes[6]->SetOffset(0.9);
keyframes[6]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[7] = MakeGarbageCollected<StringKeyframe>();
keyframes[7]->SetOffset(0.9);
keyframes[7]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "sans-serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[8] = MakeGarbageCollected<StringKeyframe>();
keyframes[8]->SetOffset(1.0);
keyframes[8]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "monospace",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.0, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
effect->Sample(0, 0.2, kDuration, values);
ExpectNonInterpolableValue("monospace", values.at(0));
effect->Sample(0, 0.4, kDuration, values);
ExpectNonInterpolableValue("cursive", values.at(0));
effect->Sample(0, 0.5, kDuration, values);
ExpectNonInterpolableValue("system-ui", values.at(0));
effect->Sample(0, 0.6, kDuration, values);
ExpectNonInterpolableValue("system-ui", values.at(0));
effect->Sample(0, 0.8, kDuration, values);
ExpectNonInterpolableValue("serif", values.at(0));
effect->Sample(0, 1.0, kDuration, values);
ExpectNonInterpolableValue("monospace", values.at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_PerKeyframeComposite) {
StringKeyframeVector keyframes(2);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kLeft, "3px",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(1.0);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kLeft, "5px",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1]->SetComposite(EffectModel::kCompositeAdd);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue(3.0 * 0.4 + (7.0 + 5.0) * 0.6, values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, MultipleProperties) {
StringKeyframeVector keyframes(2);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "serif",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kFontStyle, "normal",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(1.0);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kFontFamily, "cursive",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kFontStyle, "oblique",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
EXPECT_EQ(2UL, values.size());
Interpolation* left_value = FindValue(values, CSSPropertyID::kFontFamily);
ASSERT_TRUE(left_value);
ExpectNonInterpolableValue("cursive", left_value);
Interpolation* right_value = FindValue(values, CSSPropertyID::kFontStyle);
ASSERT_TRUE(right_value);
ExpectNonInterpolableValue("oblique", right_value);
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_RecompositeCompositableValue) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "3px", "5px");
keyframes[0]->SetComposite(EffectModel::kCompositeAdd);
keyframes[1]->SetComposite(EffectModel::kCompositeAdd);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.6, kDuration, values);
ExpectLengthValue((7.0 + 3.0) * 0.4 + (7.0 + 5.0) * 0.6, values.at(0));
ExpectLengthValue((9.0 + 3.0) * 0.4 + (9.0 + 5.0) * 0.6, values.at(1));
}
TEST_F(AnimationKeyframeEffectModel, MultipleIterations) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kLeft, "1px", "3px");
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0.5, kDuration, values);
ExpectLengthValue(2.0, values.at(0));
effect->Sample(1, 0.5, kDuration, values);
ExpectLengthValue(2.0, values.at(0));
effect->Sample(2, 0.5, kDuration, values);
ExpectLengthValue(2.0, values.at(0));
}
// FIXME: Re-enable this test once compositing of CompositeAdd is supported.
TEST_F(AnimationKeyframeEffectModel, DISABLED_DependsOnUnderlyingValue) {
StringKeyframeVector keyframes(3);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.0);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kLeft, "1px",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[0]->SetComposite(EffectModel::kCompositeAdd);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[1]->SetOffset(0.5);
keyframes[1]->SetCSSPropertyValue(CSSPropertyID::kLeft, "1px",
SecureContextMode::kInsecureContext,
nullptr);
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[2]->SetOffset(1.0);
keyframes[2]->SetCSSPropertyValue(CSSPropertyID::kLeft, "1px",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
HeapVector<Member<Interpolation>> values;
effect->Sample(0, 0, kDuration, values);
EXPECT_TRUE(values.at(0));
effect->Sample(0, 0.1, kDuration, values);
EXPECT_TRUE(values.at(0));
effect->Sample(0, 0.25, kDuration, values);
EXPECT_TRUE(values.at(0));
effect->Sample(0, 0.4, kDuration, values);
EXPECT_TRUE(values.at(0));
effect->Sample(0, 0.5, kDuration, values);
EXPECT_FALSE(values.at(0));
effect->Sample(0, 0.6, kDuration, values);
EXPECT_FALSE(values.at(0));
effect->Sample(0, 0.75, kDuration, values);
EXPECT_FALSE(values.at(0));
effect->Sample(0, 0.8, kDuration, values);
EXPECT_FALSE(values.at(0));
effect->Sample(0, 1, kDuration, values);
EXPECT_FALSE(values.at(0));
}
TEST_F(AnimationKeyframeEffectModel, AddSyntheticKeyframes) {
StringKeyframeVector keyframes(1);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.5);
keyframes[0]->SetCSSPropertyValue(CSSPropertyID::kLeft, "4px",
SecureContextMode::kInsecureContext,
nullptr);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
const StringPropertySpecificKeyframeVector& property_specific_keyframes =
*effect->GetPropertySpecificKeyframes(
PropertyHandle(GetCSSPropertyLeft()));
EXPECT_EQ(3U, property_specific_keyframes.size());
EXPECT_DOUBLE_EQ(0.0, property_specific_keyframes[0]->Offset());
EXPECT_DOUBLE_EQ(0.5, property_specific_keyframes[1]->Offset());
EXPECT_DOUBLE_EQ(1.0, property_specific_keyframes[2]->Offset());
}
TEST_F(AnimationKeyframeEffectModel, ToKeyframeEffectModel) {
StringKeyframeVector keyframes(0);
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
EffectModel* base_effect = effect;
EXPECT_TRUE(ToStringKeyframeEffectModel(base_effect));
}
TEST_F(AnimationKeyframeEffectModel, CompositorSnapshotUpdateBasic) {
StringKeyframeVector keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kOpacity, "0", "1");
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes);
auto style = GetDocument().GetStyleResolver().StyleForElement(
element, StyleRecalcContext());
const CompositorKeyframeValue* value;
// Compositor keyframe value should be empty before snapshot
const auto& empty_keyframes = *effect->GetPropertySpecificKeyframes(
PropertyHandle(GetCSSPropertyOpacity()));
value = empty_keyframes[0]->GetCompositorKeyframeValue();
EXPECT_FALSE(value);
// Snapshot should update first time after construction
EXPECT_TRUE(effect->SnapshotAllCompositorKeyframesIfNecessary(
*element, *style, nullptr));
// Snapshot should not update on second call
EXPECT_FALSE(effect->SnapshotAllCompositorKeyframesIfNecessary(
*element, *style, nullptr));
// Snapshot should update after an explicit invalidation
effect->InvalidateCompositorKeyframesSnapshot();
EXPECT_TRUE(effect->SnapshotAllCompositorKeyframesIfNecessary(
*element, *style, nullptr));
// Compositor keyframe value should be available after snapshot
const auto& available_keyframes = *effect->GetPropertySpecificKeyframes(
PropertyHandle(GetCSSPropertyOpacity()));
value = available_keyframes[0]->GetCompositorKeyframeValue();
EXPECT_TRUE(value);
EXPECT_TRUE(value->IsDouble());
}
TEST_F(AnimationKeyframeEffectModel,
CompositorSnapshotUpdateAfterKeyframeChange) {
StringKeyframeVector opacity_keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kOpacity, "0", "1");
auto* effect =
MakeGarbageCollected<StringKeyframeEffectModel>(opacity_keyframes);
auto style = GetDocument().GetStyleResolver().StyleForElement(
element, StyleRecalcContext());
EXPECT_TRUE(effect->SnapshotAllCompositorKeyframesIfNecessary(
*element, *style, nullptr));
const CompositorKeyframeValue* value;
const auto& keyframes = *effect->GetPropertySpecificKeyframes(
PropertyHandle(GetCSSPropertyOpacity()));
value = keyframes[0]->GetCompositorKeyframeValue();
EXPECT_TRUE(value);
EXPECT_TRUE(value->IsDouble());
StringKeyframeVector filter_keyframes =
KeyframesAtZeroAndOne(CSSPropertyID::kFilter, "blur(1px)", "blur(10px)");
effect->SetFrames(filter_keyframes);
// Snapshot should update after changing keyframes
EXPECT_TRUE(effect->SnapshotAllCompositorKeyframesIfNecessary(
*element, *style, nullptr));
const auto& updated_keyframes = *effect->GetPropertySpecificKeyframes(
PropertyHandle(GetCSSPropertyFilter()));
value = updated_keyframes[0]->GetCompositorKeyframeValue();
EXPECT_TRUE(value);
EXPECT_TRUE(value->IsFilterOperations());
}
TEST_F(AnimationKeyframeEffectModel, CompositorSnapshotUpdateCustomProperty) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
DummyExceptionStateForTesting exception_state;
// Compositor keyframe value available after snapshot
const CompositorKeyframeValue* value =
ConstructEffectAndGetKeyframes("--foo", "<number>", &GetDocument(),
element, "0", "100", exception_state)[1]
->GetCompositorKeyframeValue();
ASSERT_FALSE(exception_state.HadException());
// Test value holds the correct number type
EXPECT_TRUE(value);
EXPECT_TRUE(value->IsDouble());
EXPECT_EQ(To<CompositorKeyframeDouble>(value)->ToDouble(), 100);
}
TEST_F(AnimationKeyframeEffectModel, CompositorUpdateColorProperty) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
DummyExceptionStateForTesting exception_state;
element->style()->setProperty(GetDocument().GetExecutionContext(), "color",
"rgb(0, 255, 0)", g_empty_string,
exception_state);
// Compositor keyframe value available after snapshot
const CompositorKeyframeValue* value_rgb =
ConstructEffectAndGetKeyframes("--rgb", "<color>", &GetDocument(),
element, "rgb(0, 0, 0)", "rgb(0, 255, 0)",
exception_state)[1]
->GetCompositorKeyframeValue();
const CompositorKeyframeValue* value_hsl =
ConstructEffectAndGetKeyframes("--hsl", "<color>", &GetDocument(),
element, "hsl(0, 0%, 0%)",
"hsl(120, 100%, 50%)", exception_state)[1]
->GetCompositorKeyframeValue();
const CompositorKeyframeValue* value_name =
ConstructEffectAndGetKeyframes("--named", "<color>", &GetDocument(),
element, "black", "lime",
exception_state)[1]
->GetCompositorKeyframeValue();
const CompositorKeyframeValue* value_hex =
ConstructEffectAndGetKeyframes("--hex", "<color>", &GetDocument(),
element, "#000000", "#00FF00",
exception_state)[1]
->GetCompositorKeyframeValue();
const CompositorKeyframeValue* value_curr =
ConstructEffectAndGetKeyframes("--curr", "<color>", &GetDocument(),
element, "#000000", "currentcolor",
exception_state)[1]
->GetCompositorKeyframeValue();
const PropertySpecificKeyframeVector& values_mixed =
ConstructEffectAndGetKeyframes("--mixed", "<color>", &GetDocument(),
element, "#000000", "lime",
exception_state);
ASSERT_FALSE(exception_state.HadException());
// Test rgb color input
EXPECT_TRUE(value_rgb);
EXPECT_TRUE(value_rgb->IsColor());
EXPECT_EQ(To<CompositorKeyframeColor>(value_rgb)->ToColor(), SK_ColorGREEN);
// Test hsl color input
EXPECT_TRUE(value_hsl);
EXPECT_TRUE(value_hsl->IsColor());
EXPECT_EQ(To<CompositorKeyframeColor>(value_hsl)->ToColor(), SK_ColorGREEN);
// Test named color input
EXPECT_TRUE(value_name);
EXPECT_TRUE(value_name->IsColor());
EXPECT_EQ(To<CompositorKeyframeColor>(value_name)->ToColor(), SK_ColorGREEN);
// Test hex color input
EXPECT_TRUE(value_hex);
EXPECT_TRUE(value_hex->IsColor());
EXPECT_EQ(To<CompositorKeyframeColor>(value_hex)->ToColor(), SK_ColorGREEN);
// currentcolor is a CSSIdentifierValue not a color
EXPECT_FALSE(value_curr);
// Ensure both frames are consistent when values are mixed
const CompositorKeyframeValue* value_mixed0 =
values_mixed[0]->GetCompositorKeyframeValue();
const CompositorKeyframeValue* value_mixed1 =
values_mixed[1]->GetCompositorKeyframeValue();
EXPECT_TRUE(value_mixed0);
EXPECT_TRUE(value_mixed0->IsColor());
EXPECT_EQ(To<CompositorKeyframeColor>(value_mixed0)->ToColor(),
SK_ColorBLACK);
EXPECT_TRUE(value_mixed1);
EXPECT_TRUE(value_mixed1->IsColor());
EXPECT_EQ(To<CompositorKeyframeColor>(value_mixed1)->ToColor(),
SK_ColorGREEN);
}
} // namespace blink
namespace blink {
class KeyframeEffectModelTest : public testing::Test {
public:
static Vector<double> GetComputedOffsets(const KeyframeVector& keyframes) {
return KeyframeEffectModelBase::GetComputedOffsets(keyframes);
}
};
TEST_F(KeyframeEffectModelTest, EvenlyDistributed1) {
KeyframeVector keyframes(5);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0.125);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[3] = MakeGarbageCollected<StringKeyframe>();
keyframes[4] = MakeGarbageCollected<StringKeyframe>();
keyframes[4]->SetOffset(0.625);
const Vector<double> result = GetComputedOffsets(keyframes);
EXPECT_EQ(5U, result.size());
EXPECT_DOUBLE_EQ(0.125, result[0]);
EXPECT_DOUBLE_EQ(0.25, result[1]);
EXPECT_DOUBLE_EQ(0.375, result[2]);
EXPECT_DOUBLE_EQ(0.5, result[3]);
EXPECT_DOUBLE_EQ(0.625, result[4]);
}
TEST_F(KeyframeEffectModelTest, EvenlyDistributed2) {
KeyframeVector keyframes(6);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[3] = MakeGarbageCollected<StringKeyframe>();
keyframes[3]->SetOffset(0.75);
keyframes[4] = MakeGarbageCollected<StringKeyframe>();
keyframes[5] = MakeGarbageCollected<StringKeyframe>();
const Vector<double> result = GetComputedOffsets(keyframes);
EXPECT_EQ(6U, result.size());
EXPECT_DOUBLE_EQ(0.0, result[0]);
EXPECT_DOUBLE_EQ(0.25, result[1]);
EXPECT_DOUBLE_EQ(0.5, result[2]);
EXPECT_DOUBLE_EQ(0.75, result[3]);
EXPECT_DOUBLE_EQ(0.875, result[4]);
EXPECT_DOUBLE_EQ(1.0, result[5]);
}
TEST_F(KeyframeEffectModelTest, EvenlyDistributed3) {
KeyframeVector keyframes(12);
keyframes[0] = MakeGarbageCollected<StringKeyframe>();
keyframes[0]->SetOffset(0);
keyframes[1] = MakeGarbageCollected<StringKeyframe>();
keyframes[2] = MakeGarbageCollected<StringKeyframe>();
keyframes[3] = MakeGarbageCollected<StringKeyframe>();
keyframes[4] = MakeGarbageCollected<StringKeyframe>();
keyframes[4]->SetOffset(0.5);
keyframes[5] = MakeGarbageCollected<StringKeyframe>();
keyframes[6] = MakeGarbageCollected<StringKeyframe>();
keyframes[7] = MakeGarbageCollected<StringKeyframe>();
keyframes[7]->SetOffset(0.8);
keyframes[8] = MakeGarbageCollected<StringKeyframe>();
keyframes[9] = MakeGarbageCollected<StringKeyframe>();
keyframes[10] = MakeGarbageCollected<StringKeyframe>();
keyframes[11] = MakeGarbageCollected<StringKeyframe>();
const Vector<double> result = GetComputedOffsets(keyframes);
EXPECT_EQ(12U, result.size());
EXPECT_DOUBLE_EQ(0.0, result[0]);
EXPECT_DOUBLE_EQ(0.125, result[1]);
EXPECT_DOUBLE_EQ(0.25, result[2]);
EXPECT_DOUBLE_EQ(0.375, result[3]);
EXPECT_DOUBLE_EQ(0.5, result[4]);
EXPECT_DOUBLE_EQ(0.6, result[5]);
EXPECT_DOUBLE_EQ(0.7, result[6]);
EXPECT_DOUBLE_EQ(0.8, result[7]);
EXPECT_DOUBLE_EQ(0.85, result[8]);
EXPECT_DOUBLE_EQ(0.9, result[9]);
EXPECT_DOUBLE_EQ(0.95, result[10]);
EXPECT_DOUBLE_EQ(1.0, result[11]);
}
TEST_F(KeyframeEffectModelTest, RejectInvalidPropertyValue) {
StringKeyframe* keyframe = MakeGarbageCollected<StringKeyframe>();
keyframe->SetCSSPropertyValue(CSSPropertyID::kBackgroundColor,
"not a valid color",
SecureContextMode::kInsecureContext, nullptr);
// Verifty that property is quietly rejected.
EXPECT_EQ(0U, keyframe->Properties().size());
// Verify that a valid property value is accepted.
keyframe->SetCSSPropertyValue(CSSPropertyID::kBackgroundColor, "blue",
SecureContextMode::kInsecureContext, nullptr);
EXPECT_EQ(1U, keyframe->Properties().size());
}
} // namespace blink