blob: 631475726149209e7ec61d2a3863a6eeec8e97ab [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/compositor_animations.h"
#include <limits>
#include <memory>
#include <utility>
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "cc/animation/animation_host.h"
#include "cc/layers/picture_layer.h"
#include "cc/trees/transform_node.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/web/web_settings.h"
#include "third_party/blink/renderer/core/animation/animation.h"
#include "third_party/blink/renderer/core/animation/css/compositor_keyframe_double.h"
#include "third_party/blink/renderer/core/animation/document_timeline.h"
#include "third_party/blink/renderer/core/animation/element_animations.h"
#include "third_party/blink/renderer/core/animation/keyframe_effect.h"
#include "third_party/blink/renderer/core/animation/pending_animations.h"
#include "third_party/blink/renderer/core/css/css_custom_ident_value.h"
#include "third_party/blink/renderer/core/css/css_paint_value.h"
#include "third_party/blink/renderer/core/css/css_syntax_definition.h"
#include "third_party/blink/renderer/core/css/css_test_helpers.h"
#include "third_party/blink/renderer/core/css/mock_css_paint_image_generator.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/paint/compositing/composited_layer_mapping.h"
#include "third_party/blink/renderer/core/paint/object_paint_properties.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
#include "third_party/blink/renderer/core/style/filter_operations.h"
#include "third_party/blink/renderer/core/style/style_generated_image.h"
#include "third_party/blink/renderer/core/svg/svg_element.h"
#include "third_party/blink/renderer/core/svg/svg_length.h"
#include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/animation/compositor_color_animation_curve.h"
#include "third_party/blink/renderer/platform/animation/compositor_float_animation_curve.h"
#include "third_party/blink/renderer/platform/animation/compositor_float_keyframe.h"
#include "third_party/blink/renderer/platform/animation/compositor_keyframe_model.h"
#include "third_party/blink/renderer/platform/geometry/float_box.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/graphics/compositing/paint_artifact_compositor.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/testing/histogram_tester.h"
#include "third_party/blink/renderer/platform/testing/paint_test_configurations.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
#include "third_party/blink/renderer/platform/transforms/transform_operations.h"
#include "third_party/blink/renderer/platform/transforms/translate_transform_operation.h"
#include "third_party/blink/renderer/platform/wtf/hash_functions.h"
#include "third_party/skia/include/core/SkColor.h"
using testing::_;
using testing::NiceMock;
using testing::Return;
using testing::ReturnRef;
using testing::Values;
namespace blink {
namespace {
// CSSPaintImageGenerator requires that CSSPaintImageGeneratorCreateFunction be
// a static method. As such, it cannot access a class member and so instead we
// store a pointer to the overriding generator globally.
MockCSSPaintImageGenerator* g_override_generator = nullptr;
CSSPaintImageGenerator* ProvideOverrideGenerator(
const String&,
const Document&,
CSSPaintImageGenerator::Observer*) {
return g_override_generator;
}
} // namespace
using css_test_helpers::RegisterProperty;
class AnimationCompositorAnimationsTest : public PaintTestConfigurations,
public RenderingTest {
protected:
scoped_refptr<TimingFunction> linear_timing_function_;
scoped_refptr<TimingFunction> cubic_ease_timing_function_;
scoped_refptr<TimingFunction> cubic_custom_timing_function_;
scoped_refptr<TimingFunction> step_timing_function_;
Timing timing_;
CompositorAnimations::CompositorTiming compositor_timing_;
Persistent<HeapVector<Member<StringKeyframe>>> keyframe_vector2_;
Persistent<StringKeyframeEffectModel> keyframe_animation_effect2_;
Persistent<HeapVector<Member<StringKeyframe>>> keyframe_vector5_;
Persistent<StringKeyframeEffectModel> keyframe_animation_effect5_;
Persistent<Element> element_;
Persistent<Element> inline_;
Persistent<DocumentTimeline> timeline_;
void SetUp() override {
EnableCompositing();
RenderingTest::SetUp();
linear_timing_function_ = LinearTimingFunction::Shared();
cubic_ease_timing_function_ = CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE);
cubic_custom_timing_function_ =
CubicBezierTimingFunction::Create(1, 2, 3, 4);
step_timing_function_ =
StepsTimingFunction::Create(1, StepsTimingFunction::StepPosition::END);
timing_ = CreateCompositableTiming();
compositor_timing_ = CompositorAnimations::CompositorTiming();
// Make sure the CompositableTiming is really compositable, otherwise
// most other tests will fail.
ASSERT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
keyframe_vector2_ = CreateCompositableFloatKeyframeVector(2);
keyframe_animation_effect2_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector2_);
keyframe_vector5_ = CreateCompositableFloatKeyframeVector(5);
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
GetAnimationClock().ResetTimeForTesting();
timeline_ = GetDocument().Timeline();
timeline_->ResetForTesting();
// Using will-change ensures that this object will need paint properties.
// Having an animation would normally ensure this but these tests don't
// explicitly construct a full animation on the element.
SetBodyInnerHTML(R"HTML(
<div id='test' style='will-change: opacity,filter,transform; height:100px; background: green;'></div>
<span id='inline' style='will-change: opacity,filter,transform;'>text</div>
)HTML");
element_ = GetDocument().getElementById("test");
inline_ = GetDocument().getElementById("inline");
helper_.Initialize(nullptr, nullptr, nullptr);
base_url_ = "http://www.test.com/";
}
public:
bool ConvertTimingForCompositor(const Timing& t,
CompositorAnimations::CompositorTiming& out,
double playback_rate = 1) {
return CompositorAnimations::ConvertTimingForCompositor(
t, base::TimeDelta(), out, playback_rate);
}
CompositorAnimations::FailureReasons CanStartEffectOnCompositor(
const Timing& timing,
const KeyframeEffectModelBase& effect) {
// TODO(crbug.com/725385): Remove once compositor uses InterpolationTypes.
auto style = GetDocument().GetStyleResolver().StyleForElement(
element_, StyleRecalcContext());
effect.SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
return CheckCanStartEffectOnCompositor(timing, *element_.Get(), nullptr,
effect);
}
CompositorAnimations::FailureReasons CheckCanStartEffectOnCompositor(
const Timing& timing,
const Element& element,
const Animation* animation,
const EffectModel& effect_model,
PropertyHandleSet* unsupported_properties = nullptr) {
const PaintArtifactCompositor* paint_artifact_compositor =
GetDocument().View()->GetPaintArtifactCompositor();
return CompositorAnimations::CheckCanStartEffectOnCompositor(
timing, element, animation, effect_model, paint_artifact_compositor, 1,
unsupported_properties);
}
CompositorAnimations::FailureReasons CheckCanStartElementOnCompositor(
const Element& element,
const EffectModel& model) {
return CompositorAnimations::CheckCanStartElementOnCompositor(element,
model);
}
void GetAnimationOnCompositor(
Timing& timing,
StringKeyframeEffectModel& effect,
Vector<std::unique_ptr<CompositorKeyframeModel>>& keyframe_models,
double animation_playback_rate) {
CompositorAnimations::GetAnimationOnCompositor(
*element_, timing, 0, base::nullopt, base::TimeDelta(), effect,
keyframe_models, animation_playback_rate);
}
CompositorAnimations::FailureReasons
DuplicateSingleKeyframeAndTestIsCandidateOnResult(StringKeyframe* frame) {
EXPECT_EQ(frame->CheckedOffset(), 0);
StringKeyframeVector frames;
Keyframe* second = frame->CloneWithOffset(1);
frames.push_back(frame);
frames.push_back(To<StringKeyframe>(second));
return CanStartEffectOnCompositor(
timing_, *MakeGarbageCollected<StringKeyframeEffectModel>(frames));
}
// -------------------------------------------------------------------
Timing CreateCompositableTiming() {
Timing timing;
timing.start_delay = 0;
timing.fill_mode = Timing::FillMode::NONE;
timing.iteration_start = 0;
timing.iteration_count = 1;
timing.iteration_duration = AnimationTimeDelta::FromSecondsD(1);
timing.direction = Timing::PlaybackDirection::NORMAL;
timing.timing_function = linear_timing_function_;
return timing;
}
StringKeyframe* CreateReplaceOpKeyframe(CSSPropertyID id,
const String& value,
double offset = 0) {
auto* keyframe = MakeGarbageCollected<StringKeyframe>();
keyframe->SetCSSPropertyValue(id, value,
SecureContextMode::kInsecureContext, nullptr);
keyframe->SetComposite(EffectModel::kCompositeReplace);
keyframe->SetOffset(offset);
keyframe->SetEasing(LinearTimingFunction::Shared());
return keyframe;
}
StringKeyframe* CreateReplaceOpKeyframe(const String& property_name,
const String& value,
double offset = 0) {
auto* keyframe = MakeGarbageCollected<StringKeyframe>();
keyframe->SetCSSPropertyValue(
AtomicString(property_name), value,
GetDocument().GetExecutionContext()->GetSecureContextMode(),
GetDocument().ElementSheet().Contents());
keyframe->SetComposite(EffectModel::kCompositeReplace);
keyframe->SetOffset(offset);
keyframe->SetEasing(LinearTimingFunction::Shared());
return keyframe;
}
StringKeyframe* CreateDefaultKeyframe(CSSPropertyID id,
EffectModel::CompositeOperation op,
double offset = 0) {
String value = "0.1";
if (id == CSSPropertyID::kTransform)
value = "none";
else if (id == CSSPropertyID::kColor)
value = "red";
StringKeyframe* keyframe = CreateReplaceOpKeyframe(id, value, offset);
keyframe->SetComposite(op);
return keyframe;
}
HeapVector<Member<StringKeyframe>>* CreateCompositableFloatKeyframeVector(
size_t n) {
Vector<double> values;
for (size_t i = 0; i < n; i++) {
values.push_back(static_cast<double>(i));
}
return CreateCompositableFloatKeyframeVector(values);
}
HeapVector<Member<StringKeyframe>>* CreateCompositableFloatKeyframeVector(
Vector<double>& values) {
HeapVector<Member<StringKeyframe>>* frames =
MakeGarbageCollected<HeapVector<Member<StringKeyframe>>>();
for (wtf_size_t i = 0; i < values.size(); i++) {
double offset = 1.0 / (values.size() - 1) * i;
String value = String::Number(values[i]);
frames->push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, value, offset));
}
return frames;
}
void SetCustomProperty(const String& name, const String& value) {
DummyExceptionStateForTesting exception_state;
element_->style()->setProperty(GetDocument().GetExecutionContext(), name,
value, g_empty_string, exception_state);
EXPECT_FALSE(exception_state.HadException());
EXPECT_TRUE(element_->style()->getPropertyValue(name));
}
// This class exists to dodge the interlock between creating compositor
// keyframe values iff we can animate them on the compositor, and hence can
// start their animations on it. i.e. two far away switch statements have
// matching non-default values, preventing us from testing the default.
class MockStringKeyframe : public StringKeyframe {
public:
static StringKeyframe* Create(double offset) {
return MakeGarbageCollected<MockStringKeyframe>(offset);
}
MockStringKeyframe(double offset)
: StringKeyframe(),
property_specific_(
MakeGarbageCollected<MockPropertySpecificStringKeyframe>(
offset)) {
SetOffset(offset);
}
Keyframe::PropertySpecificKeyframe* CreatePropertySpecificKeyframe(
const PropertyHandle&,
EffectModel::CompositeOperation,
double) const final {
return property_specific_; // We know a shortcut.
}
void Trace(Visitor* visitor) const override {
visitor->Trace(property_specific_);
StringKeyframe::Trace(visitor);
}
private:
class MockPropertySpecificStringKeyframe : public PropertySpecificKeyframe {
public:
// Pretend to have a compositor keyframe value. Pick the offset for pure
// convenience: it matters not what it is.
MockPropertySpecificStringKeyframe(double offset)
: PropertySpecificKeyframe(offset,
LinearTimingFunction::Shared(),
EffectModel::kCompositeReplace),
compositor_keyframe_value_(
MakeGarbageCollected<CompositorKeyframeDouble>(offset)) {}
bool IsNeutral() const final { return true; }
bool IsRevert() const final { return false; }
PropertySpecificKeyframe* CloneWithOffset(double) const final {
NOTREACHED();
return nullptr;
}
bool PopulateCompositorKeyframeValue(
const PropertyHandle&,
Element&,
const ComputedStyle& base_style,
const ComputedStyle* parent_style) const final {
return true;
}
const CompositorKeyframeValue* GetCompositorKeyframeValue() const final {
return compositor_keyframe_value_;
}
PropertySpecificKeyframe* NeutralKeyframe(
double,
scoped_refptr<TimingFunction>) const final {
NOTREACHED();
return nullptr;
}
void Trace(Visitor* visitor) const override {
visitor->Trace(compositor_keyframe_value_);
PropertySpecificKeyframe::Trace(visitor);
}
private:
Member<CompositorKeyframeDouble> compositor_keyframe_value_;
};
Member<PropertySpecificKeyframe> property_specific_;
};
StringKeyframe* CreateMockReplaceKeyframe(CSSPropertyID id,
const String& value,
double offset) {
StringKeyframe* keyframe = MockStringKeyframe::Create(offset);
keyframe->SetCSSPropertyValue(id, value,
SecureContextMode::kInsecureContext, nullptr);
keyframe->SetComposite(EffectModel::kCompositeReplace);
keyframe->SetEasing(LinearTimingFunction::Shared());
return keyframe;
}
StringKeyframe* CreateSVGKeyframe(const QualifiedName& name,
const String& value,
double offset) {
auto* keyframe = MakeGarbageCollected<StringKeyframe>();
keyframe->SetSVGAttributeValue(name, value);
keyframe->SetComposite(EffectModel::kCompositeReplace);
keyframe->SetOffset(offset);
keyframe->SetEasing(LinearTimingFunction::Shared());
return keyframe;
}
StringKeyframeEffectModel* CreateKeyframeEffectModel(
StringKeyframe* from,
StringKeyframe* to,
StringKeyframe* c = nullptr,
StringKeyframe* d = nullptr) {
EXPECT_EQ(from->CheckedOffset(), 0);
StringKeyframeVector frames;
frames.push_back(from);
EXPECT_LE(from->Offset(), to->Offset());
frames.push_back(to);
if (c) {
EXPECT_LE(to->Offset(), c->Offset());
frames.push_back(c);
}
if (d) {
frames.push_back(d);
EXPECT_LE(c->Offset(), d->Offset());
EXPECT_EQ(d->CheckedOffset(), 1.0);
} else {
EXPECT_EQ(to->CheckedOffset(), 1.0);
}
if (!HasFatalFailure()) {
return MakeGarbageCollected<StringKeyframeEffectModel>(frames);
}
return nullptr;
}
void SimulateFrame(double time) {
GetAnimationClock().UpdateTime(base::TimeTicks() +
base::TimeDelta::FromSecondsD(time));
GetPendingAnimations().Update(nullptr, false);
timeline_->ServiceAnimations(kTimingUpdateForAnimationFrame);
}
std::unique_ptr<CompositorKeyframeModel> ConvertToCompositorAnimation(
StringKeyframeEffectModel& effect,
double animation_playback_rate) {
// As the compositor code only understands CompositorKeyframeValues, we must
// snapshot the effect to make those available.
// TODO(crbug.com/725385): Remove once compositor uses InterpolationTypes.
auto style = GetDocument().GetStyleResolver().StyleForElement(
element_, StyleRecalcContext());
effect.SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
Vector<std::unique_ptr<CompositorKeyframeModel>> result;
GetAnimationOnCompositor(timing_, effect, result, animation_playback_rate);
DCHECK_EQ(1U, result.size());
return std::move(result[0]);
}
std::unique_ptr<CompositorKeyframeModel> ConvertToCompositorAnimation(
StringKeyframeEffectModel& effect) {
return ConvertToCompositorAnimation(effect, 1.0);
}
void ExpectKeyframeTimingFunctionCubic(
const CompositorFloatKeyframe& keyframe,
const CubicBezierTimingFunction::EaseType ease_type) {
auto keyframe_timing_function = keyframe.GetTimingFunctionForTesting();
DCHECK_EQ(keyframe_timing_function->GetType(),
TimingFunction::Type::CUBIC_BEZIER);
const auto& cubic_timing_function =
To<CubicBezierTimingFunction>(*keyframe_timing_function);
EXPECT_EQ(cubic_timing_function.GetEaseType(), ease_type);
}
void LoadTestData(const std::string& file_name) {
String testing_path =
test::BlinkRootDir() + "/renderer/core/animation/test_data/";
WebURL url = url_test_helpers::RegisterMockedURLLoadFromBase(
WebString::FromUTF8(base_url_), testing_path,
WebString::FromUTF8(file_name));
frame_test_helpers::LoadFrame(helper_.GetWebView()->MainFrameImpl(),
base_url_ + file_name);
ForceFullCompositingUpdate();
url_test_helpers::RegisterMockedURLUnregister(url);
}
LocalFrame* GetFrame() const { return helper_.LocalMainFrame()->GetFrame(); }
void BeginFrame() {
helper_.GetWebView()
->MainFrameViewWidget()
->SynchronouslyCompositeForTesting(base::TimeTicks::Now());
}
void ForceFullCompositingUpdate() {
helper_.GetWebView()->MainFrameWidget()->UpdateAllLifecyclePhases(
DocumentUpdateReason::kTest);
}
private:
frame_test_helpers::WebViewHelper helper_;
std::string base_url_;
};
class LayoutObjectProxy : public LayoutObject {
public:
static LayoutObjectProxy* Create(Node* node) {
return new LayoutObjectProxy(node);
}
static void Dispose(LayoutObjectProxy* proxy) { proxy->Destroy(); }
const char* GetName() const override { return nullptr; }
void UpdateLayout() override {}
FloatRect LocalBoundingBoxRectForAccessibility() const override {
return FloatRect();
}
void EnsureIdForTestingProxy() {
// We need Ids of proxies to be valid.
EnsureIdForTesting();
}
private:
explicit LayoutObjectProxy(Node* node) : LayoutObject(node) {}
};
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
INSTANTIATE_PAINT_TEST_SUITE_P(AnimationCompositorAnimationsTest);
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorKeyframeMultipleCSSProperties) {
StringKeyframe* keyframe_good_multiple = CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace);
keyframe_good_multiple->SetCSSPropertyValue(
CSSPropertyID::kTransform, "none", SecureContextMode::kInsecureContext,
nullptr);
EXPECT_EQ(
DuplicateSingleKeyframeAndTestIsCandidateOnResult(keyframe_good_multiple),
CompositorAnimations::kNoFailure);
StringKeyframe* keyframe_bad_multiple_id = CreateDefaultKeyframe(
CSSPropertyID::kColor, EffectModel::kCompositeReplace);
keyframe_bad_multiple_id->SetCSSPropertyValue(
CSSPropertyID::kOpacity, "0.1", SecureContextMode::kInsecureContext,
nullptr);
EXPECT_TRUE(DuplicateSingleKeyframeAndTestIsCandidateOnResult(
keyframe_bad_multiple_id) &
CompositorAnimations::kUnsupportedCSSProperty);
}
TEST_P(AnimationCompositorAnimationsTest,
IsNotCandidateForCompositorAnimationTransformDependsOnBoxSize) {
ScopedCompositeRelativeKeyframesForTest no_relative_keyframes(false);
// Absolute transforms can be animated on the compositor.
String transform = "translateX(2px) translateY(2px)";
StringKeyframe* good_keyframe =
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, transform);
EXPECT_EQ(DuplicateSingleKeyframeAndTestIsCandidateOnResult(good_keyframe),
CompositorAnimations::kNoFailure);
// Transforms that rely on the box size, such as percent calculations, cannot
// be animated on the compositor (as the box size may change).
String transform2 = "translateX(50%) translateY(2px)";
StringKeyframe* bad_keyframe =
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, transform2);
EXPECT_TRUE(DuplicateSingleKeyframeAndTestIsCandidateOnResult(bad_keyframe) &
CompositorAnimations::kTransformRelatedPropertyDependsOnBoxSize);
// Similarly, calc transforms cannot be animated on the compositor.
String transform3 = "translateX(calc(100% + (0.5 * 100px)))";
StringKeyframe* bad_keyframe2 =
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, transform3);
EXPECT_TRUE(DuplicateSingleKeyframeAndTestIsCandidateOnResult(bad_keyframe2) &
CompositorAnimations::kTransformRelatedPropertyDependsOnBoxSize);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorKeyframeEffectModel) {
StringKeyframeVector frames_same;
frames_same.push_back(CreateDefaultKeyframe(
CSSPropertyID::kColor, EffectModel::kCompositeReplace, 0.0));
frames_same.push_back(CreateDefaultKeyframe(
CSSPropertyID::kColor, EffectModel::kCompositeReplace, 1.0));
EXPECT_TRUE(CanStartEffectOnCompositor(
timing_, *MakeGarbageCollected<StringKeyframeEffectModel>(
frames_same)) &
CompositorAnimations::kUnsupportedCSSProperty);
StringKeyframeVector frames_mixed_properties;
auto* keyframe = MakeGarbageCollected<StringKeyframe>();
keyframe->SetOffset(0);
keyframe->SetCSSPropertyValue(CSSPropertyID::kColor, "red",
SecureContextMode::kInsecureContext, nullptr);
keyframe->SetCSSPropertyValue(CSSPropertyID::kOpacity, "0",
SecureContextMode::kInsecureContext, nullptr);
frames_mixed_properties.push_back(keyframe);
keyframe = MakeGarbageCollected<StringKeyframe>();
keyframe->SetOffset(1);
keyframe->SetCSSPropertyValue(CSSPropertyID::kColor, "green",
SecureContextMode::kInsecureContext, nullptr);
keyframe->SetCSSPropertyValue(CSSPropertyID::kOpacity, "1",
SecureContextMode::kInsecureContext, nullptr);
frames_mixed_properties.push_back(keyframe);
EXPECT_TRUE(CanStartEffectOnCompositor(
timing_, *MakeGarbageCollected<StringKeyframeEffectModel>(
frames_mixed_properties)) &
CompositorAnimations::kUnsupportedCSSProperty);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorCustomCssProperty) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
RegisterProperty(GetDocument(), "--foo", "<number>", "0", false);
RegisterProperty(GetDocument(), "--bar", "<length>", "10px", false);
RegisterProperty(GetDocument(), "--loo", "<color>", "rgb(0, 0, 0)", false);
RegisterProperty(GetDocument(), "--x", "<number>", "0", false);
RegisterProperty(GetDocument(), "--y", "<number>", "200", false);
RegisterProperty(GetDocument(), "--z", "<number>", "200", false);
SetCustomProperty("--foo", "10");
SetCustomProperty("--bar", "10px");
SetCustomProperty("--loo", "rgb(0, 255, 0)");
SetCustomProperty("--x", "5");
UpdateAllLifecyclePhasesForTest();
auto style = GetDocument().GetStyleResolver().StyleForElement(
element_, StyleRecalcContext());
EXPECT_TRUE(style->NonInheritedVariables());
EXPECT_TRUE(style->NonInheritedVariables()
->GetData(AtomicString("--foo"))
.value_or(nullptr));
EXPECT_TRUE(style->NonInheritedVariables()
->GetData(AtomicString("--bar"))
.value_or(nullptr));
EXPECT_TRUE(style->NonInheritedVariables()
->GetData(AtomicString("--loo"))
.value_or(nullptr));
EXPECT_TRUE(style->NonInheritedVariables()
->GetData(AtomicString("--x"))
.value_or(nullptr));
EXPECT_TRUE(style->GetVariableData("--y"));
EXPECT_TRUE(style->GetVariableData("--z"));
NiceMock<MockCSSPaintImageGenerator>* mock_generator =
MakeGarbageCollected<NiceMock<MockCSSPaintImageGenerator>>();
base::AutoReset<MockCSSPaintImageGenerator*> scoped_override_generator(
&g_override_generator, mock_generator);
base::AutoReset<CSSPaintImageGenerator::CSSPaintImageGeneratorCreateFunction>
scoped_create_function(
CSSPaintImageGenerator::GetCreateFunctionForTesting(),
ProvideOverrideGenerator);
mock_generator->AddCustomProperty("--foo");
mock_generator->AddCustomProperty("--bar");
mock_generator->AddCustomProperty("--loo");
mock_generator->AddCustomProperty("--y");
mock_generator->AddCustomProperty("--z");
auto* ident = MakeGarbageCollected<CSSCustomIdentValue>("foopainter");
CSSPaintValue* paint_value = MakeGarbageCollected<CSSPaintValue>(ident);
paint_value->CreateGeneratorForTesting(GetDocument());
StyleGeneratedImage* style_image =
MakeGarbageCollected<StyleGeneratedImage>(*paint_value);
style->AddPaintImage(style_image);
element_->GetLayoutObject()->SetStyle(style);
// The image is added for testing off-thread paint worklet supporting
// custom property animation case. The style doesn't have a real
// PaintImage, so we cannot call UpdateAllLifecyclePhasesForTest. But the
// PaintArtifactCompositor requires NeedsUpdate to be false.
// In the real world when a PaintImage does exist in the style, the life
// cycle will be updated automatically and we don't have to worry about
// this.
auto* paint_artifact_compositor =
GetDocument().View()->GetPaintArtifactCompositor();
paint_artifact_compositor->ClearNeedsUpdateForTesting();
ON_CALL(*mock_generator, IsImageGeneratorReady()).WillByDefault(Return(true));
StringKeyframe* keyframe = CreateReplaceOpKeyframe("--foo", "10");
EXPECT_EQ(DuplicateSingleKeyframeAndTestIsCandidateOnResult(keyframe),
CompositorAnimations::kNoFailure);
// Color-valued properties are supported
StringKeyframe* color_keyframe =
CreateReplaceOpKeyframe("--loo", "rgb(0, 255, 0)");
EXPECT_EQ(DuplicateSingleKeyframeAndTestIsCandidateOnResult(color_keyframe),
CompositorAnimations::kNoFailure);
// Length-valued properties are not compositable.
StringKeyframe* non_animatable_keyframe =
CreateReplaceOpKeyframe("--bar", "10px");
EXPECT_TRUE(DuplicateSingleKeyframeAndTestIsCandidateOnResult(
non_animatable_keyframe) &
CompositorAnimations::kUnsupportedCSSProperty);
// Cannot composite due to side effect.
SetCustomProperty("opacity", "var(--foo)");
EXPECT_TRUE(DuplicateSingleKeyframeAndTestIsCandidateOnResult(keyframe) &
CompositorAnimations::kUnsupportedCSSProperty);
// Cannot composite because "--x" is not used by the paint worklet.
StringKeyframe* non_used_keyframe = CreateReplaceOpKeyframe("--x", "5");
EXPECT_EQ(
DuplicateSingleKeyframeAndTestIsCandidateOnResult(non_used_keyframe),
CompositorAnimations::kUnsupportedCSSProperty);
// Implicitly initial values are supported.
StringKeyframe* y_keyframe = CreateReplaceOpKeyframe("--y", "1000");
EXPECT_EQ(DuplicateSingleKeyframeAndTestIsCandidateOnResult(y_keyframe),
CompositorAnimations::kNoFailure);
// Implicitly initial values are not supported when the property
// has been referenced.
SetCustomProperty("opacity", "var(--z)");
StringKeyframe* z_keyframe = CreateReplaceOpKeyframe("--z", "1000");
EXPECT_EQ(DuplicateSingleKeyframeAndTestIsCandidateOnResult(z_keyframe),
CompositorAnimations::kUnsupportedCSSProperty);
}
TEST_P(AnimationCompositorAnimationsTest,
ConvertTimingForCompositorStartDelay) {
const double play_forward = 1;
const double play_reverse = -1;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(20);
timing_.start_delay = 2.0;
EXPECT_TRUE(
ConvertTimingForCompositor(timing_, compositor_timing_, play_forward));
EXPECT_DOUBLE_EQ(-2.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_TRUE(
ConvertTimingForCompositor(timing_, compositor_timing_, play_reverse));
EXPECT_DOUBLE_EQ(0.0, compositor_timing_.scaled_time_offset.InSecondsF());
timing_.start_delay = -2.0;
EXPECT_TRUE(
ConvertTimingForCompositor(timing_, compositor_timing_, play_forward));
EXPECT_DOUBLE_EQ(2.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_TRUE(
ConvertTimingForCompositor(timing_, compositor_timing_, play_reverse));
EXPECT_DOUBLE_EQ(0.0, compositor_timing_.scaled_time_offset.InSecondsF());
}
TEST_P(AnimationCompositorAnimationsTest,
ConvertTimingForCompositorIterationStart) {
timing_.iteration_start = 2.2;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
}
TEST_P(AnimationCompositorAnimationsTest,
ConvertTimingForCompositorIterationCount) {
timing_.iteration_count = 5.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(5, compositor_timing_.adjusted_iteration_count);
timing_.iteration_count = 5.5;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(5.5, compositor_timing_.adjusted_iteration_count);
timing_.iteration_count = std::numeric_limits<double>::infinity();
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(std::numeric_limits<double>::infinity(),
compositor_timing_.adjusted_iteration_count);
timing_.iteration_count = std::numeric_limits<double>::infinity();
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(5);
timing_.start_delay = -6.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(6.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_EQ(std::numeric_limits<double>::infinity(),
compositor_timing_.adjusted_iteration_count);
}
TEST_P(AnimationCompositorAnimationsTest,
ConvertTimingForCompositorIterationsAndStartDelay) {
timing_.iteration_count = 4.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(5);
timing_.start_delay = 6.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(-6.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_DOUBLE_EQ(4.0, compositor_timing_.adjusted_iteration_count);
timing_.start_delay = -6.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(6.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_DOUBLE_EQ(4.0, compositor_timing_.adjusted_iteration_count);
timing_.start_delay = 21.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
}
TEST_P(AnimationCompositorAnimationsTest, ConvertTimingForCompositorDirection) {
timing_.direction = Timing::PlaybackDirection::NORMAL;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(compositor_timing_.direction, Timing::PlaybackDirection::NORMAL);
timing_.direction = Timing::PlaybackDirection::ALTERNATE_NORMAL;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(compositor_timing_.direction,
Timing::PlaybackDirection::ALTERNATE_NORMAL);
timing_.direction = Timing::PlaybackDirection::ALTERNATE_REVERSE;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(compositor_timing_.direction,
Timing::PlaybackDirection::ALTERNATE_REVERSE);
timing_.direction = Timing::PlaybackDirection::REVERSE;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_EQ(compositor_timing_.direction, Timing::PlaybackDirection::REVERSE);
}
TEST_P(AnimationCompositorAnimationsTest,
ConvertTimingForCompositorDirectionIterationsAndStartDelay) {
timing_.direction = Timing::PlaybackDirection::ALTERNATE_NORMAL;
timing_.iteration_count = 4.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(5);
timing_.start_delay = -6.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(6.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_EQ(4, compositor_timing_.adjusted_iteration_count);
EXPECT_EQ(compositor_timing_.direction,
Timing::PlaybackDirection::ALTERNATE_NORMAL);
timing_.direction = Timing::PlaybackDirection::ALTERNATE_NORMAL;
timing_.iteration_count = 4.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(5);
timing_.start_delay = -11.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(11.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_EQ(4, compositor_timing_.adjusted_iteration_count);
EXPECT_EQ(compositor_timing_.direction,
Timing::PlaybackDirection::ALTERNATE_NORMAL);
timing_.direction = Timing::PlaybackDirection::ALTERNATE_REVERSE;
timing_.iteration_count = 4.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(5);
timing_.start_delay = -6.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(6.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_EQ(4, compositor_timing_.adjusted_iteration_count);
EXPECT_EQ(compositor_timing_.direction,
Timing::PlaybackDirection::ALTERNATE_REVERSE);
timing_.direction = Timing::PlaybackDirection::ALTERNATE_REVERSE;
timing_.iteration_count = 4.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(5);
timing_.start_delay = -11.0;
EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_));
EXPECT_DOUBLE_EQ(11.0, compositor_timing_.scaled_time_offset.InSecondsF());
EXPECT_EQ(4, compositor_timing_.adjusted_iteration_count);
EXPECT_EQ(compositor_timing_.direction,
Timing::PlaybackDirection::ALTERNATE_REVERSE);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionLinear) {
timing_.timing_function = linear_timing_function_;
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionCubic) {
timing_.timing_function = cubic_ease_timing_function_;
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
timing_.timing_function = cubic_custom_timing_function_;
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionSteps) {
timing_.timing_function = step_timing_function_;
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionChainedLinear) {
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorNonLinearTimingFunctionOnFirstOrLastFrame) {
keyframe_vector2_->at(0)->SetEasing(cubic_ease_timing_function_.get());
keyframe_animation_effect2_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector2_);
keyframe_vector5_->at(3)->SetEasing(cubic_ease_timing_function_.get());
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
timing_.timing_function = cubic_ease_timing_function_;
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
timing_.timing_function = cubic_custom_timing_function_;
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartElementOnCompositorEffectOpacity) {
// Check that we got something effectively different.
StringKeyframeVector key_frames;
key_frames.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 0.0));
key_frames.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 1.0));
KeyframeEffectModelBase* animation_effect =
MakeGarbageCollected<StringKeyframeEffectModel>(key_frames);
Timing timing;
timing.iteration_duration = AnimationTimeDelta::FromSecondsD(1);
// The first animation for opacity is ok to run on compositor.
auto* keyframe_effect1 =
MakeGarbageCollected<KeyframeEffect>(element_, animation_effect, timing);
Animation* animation = timeline_->Play(keyframe_effect1);
auto style = ComputedStyle::Create();
animation_effect->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(),
*style, nullptr);
// Now we can check that we are set up correctly.
EXPECT_EQ(CheckCanStartEffectOnCompositor(timing, *element_.Get(), animation,
*animation_effect),
CompositorAnimations::kNoFailure);
// Timings have to be convertible for compositor.
EXPECT_EQ(CheckCanStartEffectOnCompositor(timing, *element_.Get(), animation,
*animation_effect),
CompositorAnimations::kNoFailure);
timing.end_delay = 1.0;
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing, *element_.Get(),
animation, *animation_effect) &
CompositorAnimations::kEffectHasUnsupportedTimingParameters);
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing, *element_.Get(),
animation, *animation_effect) &
(CompositorAnimations::kTargetHasInvalidCompositingState |
CompositorAnimations::kEffectHasUnsupportedTimingParameters));
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartElementOnCompositorEffectInvalid) {
auto style = ComputedStyle::Create();
// Check that we notice the value is not animatable correctly.
const CSSProperty& target_property1(GetCSSPropertyOutlineStyle());
PropertyHandle target_property1h(target_property1);
StringKeyframeEffectModel* effect1 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(target_property1.PropertyID(), "dotted", 0),
CreateReplaceOpKeyframe(target_property1.PropertyID(), "dashed", 1.0));
auto* keyframe_effect1 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect1, timing_);
Animation* animation1 = timeline_->Play(keyframe_effect1);
effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
const auto& keyframes1 =
*effect1->GetPropertySpecificKeyframes(target_property1h);
EXPECT_EQ(2u, keyframes1.size());
EXPECT_FALSE(keyframes1[0]->GetCompositorKeyframeValue());
EXPECT_EQ(1u, effect1->Properties().size());
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *element_.Get(),
animation1, *effect1) &
CompositorAnimations::kUnsupportedCSSProperty);
// Check that we notice transform is not animatable correctly on an inline.
const CSSProperty& target_property2(GetCSSPropertyScale());
PropertyHandle target_property2h(target_property2);
StringKeyframeEffectModel* effect2 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(target_property2.PropertyID(), "1", 0),
CreateReplaceOpKeyframe(target_property2.PropertyID(), "3", 1.0));
auto* keyframe_effect2 =
MakeGarbageCollected<KeyframeEffect>(inline_.Get(), effect2, timing_);
Animation* animation2 = timeline_->Play(keyframe_effect2);
effect2->SnapshotAllCompositorKeyframesIfNecessary(*inline_.Get(), *style,
nullptr);
const auto& keyframes2 =
*effect2->GetPropertySpecificKeyframes(target_property2h);
EXPECT_EQ(2u, keyframes2.size());
EXPECT_TRUE(keyframes2[0]->GetCompositorKeyframeValue());
EXPECT_EQ(1u, effect2->Properties().size());
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *inline_.Get(),
animation2, *effect2) &
CompositorAnimations::
kTransformRelatedPropertyCannotBeAcceleratedOnTarget);
// Check that we notice the Property is not animatable correctly.
// These ones claim to have animatable values, but we can't composite
// the property. We also don't know the ID domain.
const CSSProperty& target_property3(GetCSSPropertyWidth());
PropertyHandle target_property3h(target_property3);
StringKeyframeEffectModel* effect3 = CreateKeyframeEffectModel(
CreateMockReplaceKeyframe(target_property3.PropertyID(), "10px", 0.0),
CreateMockReplaceKeyframe(target_property3.PropertyID(), "20px", 1.0));
auto* keyframe_effect3 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect3, timing_);
Animation* animation3 = timeline_->Play(keyframe_effect3);
effect3->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
const auto& keyframes3 =
*effect3->GetPropertySpecificKeyframes(target_property3h);
EXPECT_EQ(2u, keyframes3.size());
EXPECT_TRUE(keyframes3[0]->GetCompositorKeyframeValue());
EXPECT_EQ(1u, effect3->Properties().size());
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *element_.Get(),
animation3, *effect3) &
CompositorAnimations::kUnsupportedCSSProperty);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartElementOnCompositorEffectFilter) {
// TODO(https://crbug.com/960953): Create a filter effect node when
// will-change: filter is specified so that filter effects can be tested
// without compositing changes.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
// Filter Properties use a different ID namespace
StringKeyframeEffectModel* effect1 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kFilter, "none", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kFilter, "sepia(50%)", 1.0));
auto* keyframe_effect1 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect1, timing_);
Animation* animation1 = timeline_->Play(keyframe_effect1);
auto style = ComputedStyle::Create();
effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
// Now we can check that we are set up correctly.
EXPECT_EQ(CheckCanStartEffectOnCompositor(timing_, *element_.Get(),
animation1, *effect1),
CompositorAnimations::kNoFailure);
// Filters that affect neighboring pixels can't be composited.
StringKeyframeEffectModel* effect2 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kFilter, "none", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kFilter, "blur(10px)", 1.0));
auto* keyframe_effect2 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect2, timing_);
Animation* animation2 = timeline_->Play(keyframe_effect2);
effect2->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *element_.Get(),
animation2, *effect2) &
CompositorAnimations::kFilterRelatedPropertyMayMovePixels);
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *element_.Get(),
animation2, *effect2) &
(CompositorAnimations::kFilterRelatedPropertyMayMovePixels |
CompositorAnimations::kTargetHasInvalidCompositingState));
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartElementOnCompositorEffectTransform) {
auto style = ComputedStyle::Create();
StringKeyframeEffectModel* effect1 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, "none", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, "rotate(45deg)", 1.0));
auto* keyframe_effect1 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect1, timing_);
Animation* animation1 = timeline_->Play(keyframe_effect1);
effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
// Check if our layout object is not TransformApplicable
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *inline_.Get(),
animation1, *effect1) &
CompositorAnimations::
kTransformRelatedPropertyCannotBeAcceleratedOnTarget);
StringKeyframeEffectModel* effect2 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, "translateX(-45px)",
0),
CreateReplaceOpKeyframe(CSSPropertyID::kRotate, "none", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, "translateX(45px)",
1.0),
CreateReplaceOpKeyframe(CSSPropertyID::kRotate, "45deg", 1.0));
auto* keyframe_effect2 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect2, timing_);
Animation* animation2 = timeline_->Play(keyframe_effect2);
effect2->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *element_.Get(),
animation2, *effect2) &
CompositorAnimations::kMultipleTransformAnimationsOnSameTarget);
}
TEST_P(AnimationCompositorAnimationsTest,
CheckCanStartEffectOnCompositorUnsupportedCSSProperties) {
auto style = ComputedStyle::Create();
StringKeyframeEffectModel* effect1 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "1", 1));
auto* keyframe_effect1 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect1, timing_);
Animation* animation1 = timeline_->Play(keyframe_effect1);
effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
// Make sure supported properties do not register a failure
PropertyHandleSet unsupported_properties1;
EXPECT_EQ(CheckCanStartEffectOnCompositor(timing_, *inline_.Get(), animation1,
*effect1, &unsupported_properties1),
CompositorAnimations::kNoFailure);
EXPECT_TRUE(unsupported_properties1.IsEmpty());
StringKeyframeEffectModel* effect2 = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kHeight, "100px", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kHeight, "200px", 1));
auto* keyframe_effect2 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect2, timing_);
Animation* animation2 = timeline_->Play(keyframe_effect2);
effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
// Make sure unsupported properties are reported
PropertyHandleSet unsupported_properties2;
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *inline_.Get(),
animation2, *effect2,
&unsupported_properties2) &
CompositorAnimations::kUnsupportedCSSProperty);
EXPECT_EQ(unsupported_properties2.size(), 1U);
EXPECT_EQ(
unsupported_properties2.begin()->GetCSSPropertyName().ToAtomicString(),
"height");
StringKeyframeEffectModel* effect3 =
MakeGarbageCollected<StringKeyframeEffectModel>(StringKeyframeVector({
CreateReplaceOpKeyframe(CSSPropertyID::kHeight, "100px", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, "translateY(0)",
0),
CreateReplaceOpKeyframe(CSSPropertyID::kFilter, "grayscale(50%)", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kHeight, "200px", 1),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "1", 1),
CreateReplaceOpKeyframe(CSSPropertyID::kTransform, "translateY(50px)",
1),
CreateReplaceOpKeyframe(CSSPropertyID::kFilter, "grayscale(100%)", 1),
}));
auto* keyframe_effect3 =
MakeGarbageCollected<KeyframeEffect>(element_.Get(), effect3, timing_);
Animation* animation3 = timeline_->Play(keyframe_effect3);
effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style,
nullptr);
// Make sure only the unsupported properties are reported
PropertyHandleSet unsupported_properties3;
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing_, *inline_.Get(),
animation3, *effect3,
&unsupported_properties3) &
CompositorAnimations::kUnsupportedCSSProperty);
EXPECT_EQ(unsupported_properties3.size(), 1U);
EXPECT_EQ(
unsupported_properties3.begin()->GetCSSPropertyName().ToAtomicString(),
"height");
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionChainedCubicMatchingOffsets) {
keyframe_vector2_->at(0)->SetEasing(cubic_ease_timing_function_.get());
keyframe_animation_effect2_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector2_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
keyframe_vector2_->at(0)->SetEasing(cubic_custom_timing_function_.get());
keyframe_animation_effect2_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector2_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
keyframe_vector5_->at(0)->SetEasing(cubic_ease_timing_function_.get());
keyframe_vector5_->at(1)->SetEasing(cubic_custom_timing_function_.get());
keyframe_vector5_->at(2)->SetEasing(cubic_custom_timing_function_.get());
keyframe_vector5_->at(3)->SetEasing(cubic_custom_timing_function_.get());
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionMixedGood) {
keyframe_vector5_->at(0)->SetEasing(linear_timing_function_.get());
keyframe_vector5_->at(1)->SetEasing(cubic_ease_timing_function_.get());
keyframe_vector5_->at(2)->SetEasing(cubic_ease_timing_function_.get());
keyframe_vector5_->at(3)->SetEasing(linear_timing_function_.get());
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartEffectOnCompositorTimingFunctionWithStepOrFrameOkay) {
keyframe_vector2_->at(0)->SetEasing(step_timing_function_.get());
keyframe_animation_effect2_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector2_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
keyframe_vector5_->at(0)->SetEasing(step_timing_function_.get());
keyframe_vector5_->at(1)->SetEasing(linear_timing_function_.get());
keyframe_vector5_->at(2)->SetEasing(cubic_ease_timing_function_.get());
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
keyframe_vector5_->at(1)->SetEasing(step_timing_function_.get());
keyframe_vector5_->at(2)->SetEasing(cubic_ease_timing_function_.get());
keyframe_vector5_->at(3)->SetEasing(linear_timing_function_.get());
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
keyframe_vector5_->at(0)->SetEasing(linear_timing_function_.get());
keyframe_vector5_->at(2)->SetEasing(cubic_ease_timing_function_.get());
keyframe_vector5_->at(3)->SetEasing(step_timing_function_.get());
keyframe_animation_effect5_ =
MakeGarbageCollected<StringKeyframeEffectModel>(*keyframe_vector5_);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *keyframe_animation_effect5_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest, CanStartEffectOnCompositorBasic) {
StringKeyframeVector basic_frames_vector;
basic_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 0.0));
basic_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 1.0));
StringKeyframeVector non_basic_frames_vector;
non_basic_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 0.0));
non_basic_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 0.5));
non_basic_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 1.0));
basic_frames_vector[0]->SetEasing(linear_timing_function_.get());
auto* basic_frames =
MakeGarbageCollected<StringKeyframeEffectModel>(basic_frames_vector);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *basic_frames),
CompositorAnimations::kNoFailure);
basic_frames_vector[0]->SetEasing(CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE_IN));
basic_frames =
MakeGarbageCollected<StringKeyframeEffectModel>(basic_frames_vector);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *basic_frames),
CompositorAnimations::kNoFailure);
non_basic_frames_vector[0]->SetEasing(linear_timing_function_.get());
non_basic_frames_vector[1]->SetEasing(CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE_IN));
auto* non_basic_frames =
MakeGarbageCollected<StringKeyframeEffectModel>(non_basic_frames_vector);
EXPECT_EQ(CanStartEffectOnCompositor(timing_, *non_basic_frames),
CompositorAnimations::kNoFailure);
StringKeyframeVector non_allowed_frames_vector;
non_allowed_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeAdd, 0.1));
non_allowed_frames_vector.push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeAdd, 0.25));
auto* non_allowed_frames = MakeGarbageCollected<StringKeyframeEffectModel>(
non_allowed_frames_vector);
EXPECT_TRUE(CanStartEffectOnCompositor(timing_, *non_allowed_frames) &
CompositorAnimations::kEffectHasNonReplaceCompositeMode);
// Set SVGAttribute keeps a pointer to this thing for the lifespan of
// the Keyframe. This is ugly but sufficient to work around it.
QualifiedName fake_name("prefix", "local", "uri");
StringKeyframeVector non_css_frames_vector;
non_css_frames_vector.push_back(CreateSVGKeyframe(fake_name, "cargo", 0.0));
non_css_frames_vector.push_back(CreateSVGKeyframe(fake_name, "cargo", 1.0));
auto* non_css_frames =
MakeGarbageCollected<StringKeyframeEffectModel>(non_css_frames_vector);
EXPECT_TRUE(CanStartEffectOnCompositor(timing_, *non_css_frames) &
CompositorAnimations::kAnimationAffectsNonCSSProperties);
EXPECT_TRUE(non_css_frames->RequiresPropertyNode());
// NB: Important that non_css_frames_vector goes away and cleans up
// before fake_name.
}
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
TEST_P(AnimationCompositorAnimationsTest, CreateSimpleOpacityAnimation) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(1.0, keyframe_model->Iterations());
EXPECT_EQ(0, keyframe_model->TimeOffset());
EXPECT_EQ(CompositorKeyframeModel::Direction::NORMAL,
keyframe_model->GetDirection());
EXPECT_EQ(1.0, keyframe_model->PlaybackRate());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
EXPECT_EQ(0, keyframes[0]->Time());
EXPECT_EQ(0.2f, keyframes[0]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[0]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(1.0, keyframes[1]->Time());
EXPECT_EQ(0.5f, keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleOpacityAnimationDuration) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
const AnimationTimeDelta kDuration = AnimationTimeDelta::FromSecondsD(10);
timing_.iteration_duration = kDuration;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
EXPECT_EQ(kDuration, keyframes[1]->Time() * kDuration);
}
TEST_P(AnimationCompositorAnimationsTest,
CreateMultipleKeyframeOpacityAnimationLinear) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.0", 0.25),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.25", 0.5),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
timing_.iteration_count = 5;
timing_.direction = Timing::PlaybackDirection::ALTERNATE_NORMAL;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect, 2.0);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(5.0, keyframe_model->Iterations());
EXPECT_EQ(0, keyframe_model->TimeOffset());
EXPECT_EQ(CompositorKeyframeModel::Direction::ALTERNATE_NORMAL,
keyframe_model->GetDirection());
EXPECT_EQ(2.0, keyframe_model->PlaybackRate());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(4UL, keyframes.size());
EXPECT_EQ(0, keyframes[0]->Time());
EXPECT_EQ(0.2f, keyframes[0]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[0]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(0.25, keyframes[1]->Time());
EXPECT_EQ(0, keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(0.5, keyframes[2]->Time());
EXPECT_EQ(0.25f, keyframes[2]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[2]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(1.0, keyframes[3]->Time());
EXPECT_EQ(0.5f, keyframes[3]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[3]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleOpacityAnimationStartDelay) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
const double kStartDelay = 3.25;
timing_.iteration_count = 5.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(1.75);
timing_.start_delay = kStartDelay;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(5.0, keyframe_model->Iterations());
EXPECT_EQ(-kStartDelay, keyframe_model->TimeOffset());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
EXPECT_EQ(1.75,
keyframes[1]->Time() * timing_.iteration_duration->InSecondsF());
EXPECT_EQ(0.5f, keyframes[1]->Value());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateMultipleKeyframeOpacityAnimationChained) {
// KeyframeEffect to convert
StringKeyframeVector frames;
frames.push_back(CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0));
frames.push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.0", 0.25));
frames.push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.35", 0.5));
frames.push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
frames[0]->SetEasing(cubic_ease_timing_function_.get());
frames[1]->SetEasing(linear_timing_function_.get());
frames[2]->SetEasing(cubic_custom_timing_function_.get());
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(frames);
timing_.timing_function = linear_timing_function_.get();
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(2);
timing_.iteration_count = 10;
timing_.direction = Timing::PlaybackDirection::ALTERNATE_NORMAL;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(10.0, keyframe_model->Iterations());
EXPECT_EQ(0, keyframe_model->TimeOffset());
EXPECT_EQ(CompositorKeyframeModel::Direction::ALTERNATE_NORMAL,
keyframe_model->GetDirection());
EXPECT_EQ(1.0, keyframe_model->PlaybackRate());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(4UL, keyframes.size());
EXPECT_EQ(0, keyframes[0]->Time() * timing_.iteration_duration->InSecondsF());
EXPECT_EQ(0.2f, keyframes[0]->Value());
ExpectKeyframeTimingFunctionCubic(*keyframes[0],
CubicBezierTimingFunction::EaseType::EASE);
EXPECT_EQ(0.5,
keyframes[1]->Time() * timing_.iteration_duration->InSecondsF());
EXPECT_EQ(0, keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(1.0,
keyframes[2]->Time() * timing_.iteration_duration->InSecondsF());
EXPECT_EQ(0.35f, keyframes[2]->Value());
ExpectKeyframeTimingFunctionCubic(
*keyframes[2], CubicBezierTimingFunction::EaseType::CUSTOM);
EXPECT_EQ(2.0,
keyframes[3]->Time() * timing_.iteration_duration->InSecondsF());
EXPECT_EQ(0.5f, keyframes[3]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[3]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest, CreateReversedOpacityAnimation) {
scoped_refptr<TimingFunction> cubic_easy_flip_timing_function =
CubicBezierTimingFunction::Create(0.0, 0.0, 0.0, 1.0);
// KeyframeEffect to convert
StringKeyframeVector frames;
frames.push_back(CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0));
frames.push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.0", 0.25));
frames.push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.25", 0.5));
frames.push_back(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
frames[0]->SetEasing(CubicBezierTimingFunction::Preset(
CubicBezierTimingFunction::EaseType::EASE_IN));
frames[1]->SetEasing(linear_timing_function_.get());
frames[2]->SetEasing(cubic_easy_flip_timing_function.get());
auto* effect = MakeGarbageCollected<StringKeyframeEffectModel>(frames);
timing_.timing_function = linear_timing_function_.get();
timing_.iteration_count = 10;
timing_.direction = Timing::PlaybackDirection::ALTERNATE_REVERSE;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(10.0, keyframe_model->Iterations());
EXPECT_EQ(0, keyframe_model->TimeOffset());
EXPECT_EQ(CompositorKeyframeModel::Direction::ALTERNATE_REVERSE,
keyframe_model->GetDirection());
EXPECT_EQ(1.0, keyframe_model->PlaybackRate());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(4UL, keyframes.size());
EXPECT_EQ(keyframed_float_curve->GetTimingFunctionForTesting()->GetType(),
TimingFunction::Type::LINEAR);
EXPECT_EQ(0, keyframes[0]->Time());
EXPECT_EQ(0.2f, keyframes[0]->Value());
ExpectKeyframeTimingFunctionCubic(
*keyframes[0], CubicBezierTimingFunction::EaseType::EASE_IN);
EXPECT_EQ(0.25, keyframes[1]->Time());
EXPECT_EQ(0, keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(0.5, keyframes[2]->Time());
EXPECT_EQ(0.25f, keyframes[2]->Value());
ExpectKeyframeTimingFunctionCubic(
*keyframes[2], CubicBezierTimingFunction::EaseType::CUSTOM);
EXPECT_EQ(1.0, keyframes[3]->Time());
EXPECT_EQ(0.5f, keyframes[3]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[3]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateReversedOpacityAnimationNegativeStartDelay) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
const double kNegativeStartDelay = -3;
timing_.iteration_count = 5.0;
timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(1.5);
timing_.start_delay = kNegativeStartDelay;
timing_.direction = Timing::PlaybackDirection::ALTERNATE_REVERSE;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(5.0, keyframe_model->Iterations());
EXPECT_EQ(-kNegativeStartDelay, keyframe_model->TimeOffset());
EXPECT_EQ(CompositorKeyframeModel::Direction::ALTERNATE_REVERSE,
keyframe_model->GetDirection());
EXPECT_EQ(1.0, keyframe_model->PlaybackRate());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleOpacityAnimationFillModeNone) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
timing_.fill_mode = Timing::FillMode::NONE;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(CompositorKeyframeModel::FillMode::NONE,
keyframe_model->GetFillMode());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleOpacityAnimationFillModeAuto) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
timing_.fill_mode = Timing::FillMode::AUTO;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::OPACITY,
keyframe_model->TargetProperty());
EXPECT_EQ(1.0, keyframe_model->Iterations());
EXPECT_EQ(0, keyframe_model->TimeOffset());
EXPECT_EQ(CompositorKeyframeModel::Direction::NORMAL,
keyframe_model->GetDirection());
EXPECT_EQ(1.0, keyframe_model->PlaybackRate());
EXPECT_EQ(CompositorKeyframeModel::FillMode::NONE,
keyframe_model->GetFillMode());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleOpacityAnimationWithTimingFunction) {
// KeyframeEffect to convert
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.2", 0),
CreateReplaceOpKeyframe(CSSPropertyID::kOpacity, "0.5", 1.0));
timing_.timing_function = cubic_custom_timing_function_;
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
auto curve_timing_function =
keyframed_float_curve->GetTimingFunctionForTesting();
EXPECT_EQ(curve_timing_function->GetType(),
TimingFunction::Type::CUBIC_BEZIER);
const auto& cubic_timing_function =
To<CubicBezierTimingFunction>(*curve_timing_function);
EXPECT_EQ(cubic_timing_function.GetEaseType(),
CubicBezierTimingFunction::EaseType::CUSTOM);
EXPECT_EQ(cubic_timing_function.X1(), 1.0);
EXPECT_EQ(cubic_timing_function.Y1(), 2.0);
EXPECT_EQ(cubic_timing_function.X2(), 3.0);
EXPECT_EQ(cubic_timing_function.Y2(), 4.0);
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
EXPECT_EQ(0, keyframes[0]->Time());
EXPECT_EQ(0.2f, keyframes[0]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[0]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(1.0, keyframes[1]->Time());
EXPECT_EQ(0.5f, keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateCustomFloatPropertyAnimationWithNonAsciiName) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
String property_name = "--東京都";
RegisterProperty(GetDocument(), property_name, "<number>", "0", false);
SetCustomProperty(property_name, "10");
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe(property_name, "10", 0),
CreateReplaceOpKeyframe(property_name, "20", 1.0));
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY,
keyframe_model->TargetProperty());
EXPECT_EQ(keyframe_model->GetCustomPropertyNameForTesting(),
property_name.Utf8().data());
EXPECT_FALSE(effect->RequiresPropertyNode());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleCustomFloatPropertyAnimation) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
RegisterProperty(GetDocument(), "--foo", "<number>", "0", false);
SetCustomProperty("--foo", "10");
StringKeyframeEffectModel* effect =
CreateKeyframeEffectModel(CreateReplaceOpKeyframe("--foo", "10", 0),
CreateReplaceOpKeyframe("--foo", "20", 1.0));
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY,
keyframe_model->TargetProperty());
std::unique_ptr<CompositorFloatAnimationCurve> keyframed_float_curve =
keyframe_model->FloatCurveForTesting();
CompositorFloatAnimationCurve::Keyframes keyframes =
keyframed_float_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
EXPECT_EQ(0, keyframes[0]->Time());
EXPECT_EQ(10, keyframes[0]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[0]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(1.0, keyframes[1]->Time());
EXPECT_EQ(20, keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest,
CreateSimpleCustomColorPropertyAnimation) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
RegisterProperty(GetDocument(), "--foo", "<color>", "rgb(0, 0, 0)", false);
SetCustomProperty("--foo", "rgb(0, 0, 0)");
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe("--foo", "rgb(0, 0, 0)", 0),
CreateReplaceOpKeyframe("--foo", "rgb(0, 255, 0)", 1.0));
std::unique_ptr<CompositorKeyframeModel> keyframe_model =
ConvertToCompositorAnimation(*effect);
EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY,
keyframe_model->TargetProperty());
std::unique_ptr<CompositorColorAnimationCurve> keyframed_color_curve =
keyframe_model->ColorCurveForTesting();
CompositorColorAnimationCurve::Keyframes keyframes =
keyframed_color_curve->KeyframesForTesting();
ASSERT_EQ(2UL, keyframes.size());
EXPECT_EQ(0, keyframes[0]->Time());
EXPECT_EQ(SkColorSetRGB(0, 0, 0), keyframes[0]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[0]->GetTimingFunctionForTesting()->GetType());
EXPECT_EQ(1.0, keyframes[1]->Time());
EXPECT_EQ(SkColorSetRGB(0, 0xFF, 0), keyframes[1]->Value());
EXPECT_EQ(TimingFunction::Type::LINEAR,
keyframes[1]->GetTimingFunctionForTesting()->GetType());
}
TEST_P(AnimationCompositorAnimationsTest, MixedCustomPropertyAnimation) {
ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true);
RegisterProperty(GetDocument(), "--foo", "<number> | <color>", "0", false);
SetCustomProperty("--foo", "0");
StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(
CreateReplaceOpKeyframe("--foo", "20", 0),
CreateReplaceOpKeyframe("--foo", "rgb(0, 255, 0)", 1.0));
EXPECT_TRUE(CanStartEffectOnCompositor(timing_, *effect) &
CompositorAnimations::kMixedKeyframeValueTypes);
}
TEST_P(AnimationCompositorAnimationsTest,
CancelIncompatibleCompositorAnimations) {
Persistent<HeapVector<Member<StringKeyframe>>> key_frames =
MakeGarbageCollected<HeapVector<Member<StringKeyframe>>>();
key_frames->push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 0.0));
key_frames->push_back(CreateDefaultKeyframe(
CSSPropertyID::kOpacity, EffectModel::kCompositeReplace, 1.0));
KeyframeEffectModelBase* animation_effect1 =
MakeGarbageCollected<StringKeyframeEffectModel>(*key_frames);
KeyframeEffectModelBase* animation_effect2 =
MakeGarbageCollected<StringKeyframeEffectModel>(*key_frames);
Timing timing;
timing.iteration_duration = AnimationTimeDelta::FromSecondsD(1);
// The first animation for opacity is ok to run on compositor.
auto* keyframe_effect1 = MakeGarbageCollected<KeyframeEffect>(
element_.Get(), animation_effect1, timing);
Animation* animation1 = timeline_->Play(keyframe_effect1);
auto style = ComputedStyle::Create();
animation_effect1->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(),
*style, nullptr);
EXPECT_EQ(CheckCanStartEffectOnCompositor(timing, *element_.Get(), animation1,
*animation_effect1),
CompositorAnimations::kNoFailure);
// The second animation for opacity is not ok to run on compositor.
auto* keyframe_effect2 = MakeGarbageCollected<KeyframeEffect>(
element_.Get(), animation_effect2, timing);
Animation* animation2 = timeline_->Play(keyframe_effect2);
animation_effect2->SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(),
*style, nullptr);
EXPECT_TRUE(CheckCanStartEffectOnCompositor(timing, *element_.Get(),
animation2, *animation_effect2) &
CompositorAnimations::kTargetHasIncompatibleAnimations);
EXPECT_FALSE(animation2->HasActiveAnimationsOnCompositor());
// A fallback to blink implementation needed, so cancel all compositor-side
// opacity animations for this element.
animation2->CancelIncompatibleAnimationsOnCompositor();
EXPECT_FALSE(animation1->HasActiveAnimationsOnCompositor());
EXPECT_FALSE(animation2->HasActiveAnimationsOnCompositor());
SimulateFrame(0);
EXPECT_EQ(2U, element_->GetElementAnimations()->Animations().size());
// After finishing and collecting garbage there should be no
// ElementAnimations on the element.
SimulateFrame(1.);
ThreadState::Current()->CollectAllGarbageForTesting();
EXPECT_TRUE(element_->GetElementAnimations()->Animations().IsEmpty());
}
namespace {
void UpdateDummyTransformNode(ObjectPaintProperties& properties,
CompositingReasons reasons) {
// Initialize with TransformationMatrix() to avoid 2d translation optimization
// in case of transform animation.
TransformPaintPropertyNode::State state{TransformationMatrix()};
state.direct_compositing_reasons = reasons;
properties.UpdateTransform(TransformPaintPropertyNode::Root(),
std::move(state));
}
void UpdateDummyEffectNode(ObjectPaintProperties& properties,
CompositingReasons reasons) {
EffectPaintPropertyNode::State state;
state.direct_compositing_reasons = reasons;
properties.UpdateEffect(EffectPaintPropertyNode::Root(), std::move(state));
}
} // namespace
TEST_P(AnimationCompositorAnimationsTest,
CanStartElementOnCompositorTransformBasedOnPaintProperties) {
Persistent<Element> element = GetDocument().CreateElementForBinding("shared");
LayoutObjectProxy* layout_object = LayoutObjectProxy::Create(element.Get());
layout_object->EnsureIdForTestingProxy();
element->SetLayoutObject(layout_object);
auto& properties = layout_object->GetMutableForPainting()
.FirstFragment()
.EnsurePaintProperties();
// Add a transform with a compositing reason, which should allow starting
// animation.
UpdateDummyTransformNode(properties,
CompositingReason::kActiveTransformAnimation);
EXPECT_EQ(
CheckCanStartElementOnCompositor(*element, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
// Setting to CompositingReasonNone should produce false.
UpdateDummyTransformNode(properties, CompositingReason::kNone);
EXPECT_TRUE(
CheckCanStartElementOnCompositor(*element, *keyframe_animation_effect2_) &
CompositorAnimations::kTargetHasInvalidCompositingState);
// Clearing the transform node entirely should also produce false.
properties.ClearTransform();
EXPECT_TRUE(
CheckCanStartElementOnCompositor(*element, *keyframe_animation_effect2_) &
CompositorAnimations::kTargetHasInvalidCompositingState);
element->SetLayoutObject(nullptr);
LayoutObjectProxy::Dispose(layout_object);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartElementOnCompositorEffectBasedOnPaintProperties) {
Persistent<Element> element = GetDocument().CreateElementForBinding("shared");
LayoutObjectProxy* layout_object = LayoutObjectProxy::Create(element.Get());
layout_object->EnsureIdForTestingProxy();
element->SetLayoutObject(layout_object);
auto& properties = layout_object->GetMutableForPainting()
.FirstFragment()
.EnsurePaintProperties();
// Add an effect with a compositing reason, which should allow starting
// animation.
UpdateDummyEffectNode(properties,
CompositingReason::kActiveTransformAnimation);
EXPECT_EQ(
CheckCanStartElementOnCompositor(*element, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
// Setting to CompositingReasonNone should produce false.
UpdateDummyEffectNode(properties, CompositingReason::kNone);
EXPECT_TRUE(
CheckCanStartElementOnCompositor(*element, *keyframe_animation_effect2_) &
CompositorAnimations::kTargetHasInvalidCompositingState);
// Clearing the effect node entirely should also produce false.
properties.ClearEffect();
EXPECT_TRUE(
CheckCanStartElementOnCompositor(*element, *keyframe_animation_effect2_) &
CompositorAnimations::kTargetHasInvalidCompositingState);
element->SetLayoutObject(nullptr);
LayoutObjectProxy::Dispose(layout_object);
}
TEST_P(AnimationCompositorAnimationsTest, TrackRafAnimation) {
LoadTestData("raf-countdown.html");
cc::AnimationHost* host =
GetFrame()->GetDocument()->View()->GetCompositorAnimationHost();
// The test file registers two rAF 'animations'; one which ends after 5
// iterations and the other that ends after 10.
for (int i = 0; i < 9; i++) {
BeginFrame();
EXPECT_TRUE(host->CurrentFrameHadRAF());
EXPECT_TRUE(host->NextFrameHasPendingRAF());
}
// On the 10th iteration, there should be a current rAF, but no more pending
// rAFs.
BeginFrame();
EXPECT_TRUE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
// On the 11th iteration, there should be no more rAFs firing.
BeginFrame();
EXPECT_FALSE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
}
TEST_P(AnimationCompositorAnimationsTest, TrackRafAnimationTimeout) {
LoadTestData("raf-timeout.html");
cc::AnimationHost* host =
GetFrame()->GetDocument()->View()->GetCompositorAnimationHost();
// The test file executes a rAF, which fires a setTimeout for the next rAF.
// Even with setTimeout(func, 0), the next rAF is not considered pending.
BeginFrame();
EXPECT_TRUE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
}
TEST_P(AnimationCompositorAnimationsTest, TrackRafAnimationNoneRegistered) {
SetBodyInnerHTML("<div id='box'></div>");
// Run a full frame after loading the test data so that scripted animations
// are serviced and data propagated.
BeginFrame();
// The HTML does not have any rAFs.
cc::AnimationHost* host =
GetFrame()->GetDocument()->View()->GetCompositorAnimationHost();
EXPECT_FALSE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
// And still shouldn't after another frame.
BeginFrame();
EXPECT_FALSE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
}
TEST_P(AnimationCompositorAnimationsTest, CompositedCustomProperty) {
RegisterProperty(GetDocument(), "--foo", "<number>", "0", false);
SetCustomProperty("--foo", "0");
StringKeyframeEffectModel* effect =
CreateKeyframeEffectModel(CreateReplaceOpKeyframe("--foo", "20", 0),
CreateReplaceOpKeyframe("--foo", "100", 1.0));
LoadTestData("custom-property.html");
Document* document = GetFrame()->GetDocument();
Element* target = document->getElementById("target");
// Make sure the animation is started on the compositor.
EXPECT_EQ(CheckCanStartElementOnCompositor(*target, *effect),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest, CompositedTransformAnimation) {
// TODO(wangxianzhu): Fix this test for CompositeAfterPaint.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
LoadTestData("transform-animation.html");
Document* document = GetFrame()->GetDocument();
Element* target = document->getElementById("target");
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
ASSERT_NE(nullptr, properties);
const auto* transform = properties->Transform();
ASSERT_NE(nullptr, transform);
EXPECT_TRUE(transform->HasDirectCompositingReasons());
EXPECT_TRUE(transform->HasActiveTransformAnimation());
// Make sure the animation state is initialized in paint properties.
auto* property_trees =
document->View()->RootCcLayer()->layer_tree_host()->property_trees();
auto* cc_transform = property_trees->transform_tree.Node(
property_trees->element_id_to_transform_node_index
[transform->GetCompositorElementId()]);
ASSERT_NE(nullptr, cc_transform);
EXPECT_TRUE(cc_transform->has_potential_animation);
EXPECT_TRUE(cc_transform->is_currently_animating);
EXPECT_EQ(1.f, cc_transform->maximum_animation_scale);
// Make sure the animation is started on the compositor.
EXPECT_EQ(
CheckCanStartElementOnCompositor(*target, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 1u);
}
TEST_P(AnimationCompositorAnimationsTest, CompositedScaleAnimation) {
// TODO(wangxianzhu): Fix this test for CompositeAfterPaint.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
LoadTestData("scale-animation.html");
Document* document = GetFrame()->GetDocument();
Element* target = document->getElementById("target");
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
ASSERT_NE(nullptr, properties);
const auto* transform = properties->Transform();
ASSERT_NE(nullptr, transform);
EXPECT_TRUE(transform->HasDirectCompositingReasons());
EXPECT_TRUE(transform->HasActiveTransformAnimation());
// Make sure the animation state is initialized in paint properties.
auto* property_trees =
document->View()->RootCcLayer()->layer_tree_host()->property_trees();
auto* cc_transform = property_trees->transform_tree.Node(
property_trees->element_id_to_transform_node_index
[transform->GetCompositorElementId()]);
ASSERT_NE(nullptr, cc_transform);
EXPECT_TRUE(cc_transform->has_potential_animation);
EXPECT_TRUE(cc_transform->is_currently_animating);
EXPECT_EQ(5.f, cc_transform->maximum_animation_scale);
// Make sure the animation is started on the compositor.
EXPECT_EQ(
CheckCanStartElementOnCompositor(*target, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 1u);
}
TEST_P(AnimationCompositorAnimationsTest,
NonAnimatedTransformPropertyChangeGetsUpdated) {
// TODO(wangxianzhu): Fix this test for CompositeAfterPaint.
if (RuntimeEnabledFeatures::CompositeAfterPaintEnabled())
return;
LoadTestData("transform-animation-update.html");
Document* document = GetFrame()->GetDocument();
Element* target = document->getElementById("target");
const ObjectPaintProperties* properties =
target->GetLayoutObject()->FirstFragment().PaintProperties();
ASSERT_NE(nullptr, properties);
const auto* transform = properties->Transform();
ASSERT_NE(nullptr, transform);
// Make sure composited animation is running on #target.
EXPECT_TRUE(transform->HasDirectCompositingReasons());
EXPECT_TRUE(transform->HasActiveTransformAnimation());
EXPECT_EQ(
CheckCanStartElementOnCompositor(*target, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
// Make sure the animation state is initialized in paint properties.
auto* property_trees =
document->View()->RootCcLayer()->layer_tree_host()->property_trees();
auto* cc_transform = property_trees->transform_tree.Node(
property_trees->element_id_to_transform_node_index
[transform->GetCompositorElementId()]);
ASSERT_NE(nullptr, cc_transform);
EXPECT_TRUE(cc_transform->has_potential_animation);
EXPECT_TRUE(cc_transform->is_currently_animating);
// Make sure the animation is started on the compositor.
EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 1u);
// Make sure the backface-visibility is correctly set, both in blink and on
// the cc::Layer.
EXPECT_FALSE(transform->Matrix().IsIdentity()); // Rotated
EXPECT_EQ(transform->GetBackfaceVisibilityForTesting(),
TransformPaintPropertyNode::BackfaceVisibility::kVisible);
const CompositedLayerMapping* composited_layer_mapping =
target->GetLayoutBoxModelObject()->Layer()->GetCompositedLayerMapping();
ASSERT_NE(nullptr, composited_layer_mapping);
const auto& layer = composited_layer_mapping->MainGraphicsLayer()->CcLayer();
EXPECT_FALSE(layer.should_check_backface_visibility());
// Change the backface visibility, while the compositor animation is
// happening.
target->setAttribute(html_names::kClassAttr, "backface-hidden");
ForceFullCompositingUpdate();
// Make sure the setting made it to both blink and all the way to CC.
EXPECT_EQ(transform->GetBackfaceVisibilityForTesting(),
TransformPaintPropertyNode::BackfaceVisibility::kHidden);
EXPECT_TRUE(layer.should_check_backface_visibility())
<< "Change to hidden did not get propagated to CC";
// Make sure the animation state is initialized in paint properties after
// blink pushing new paint properties without animation state change.
property_trees =
document->View()->RootCcLayer()->layer_tree_host()->property_trees();
cc_transform = property_trees->transform_tree.Node(
property_trees->element_id_to_transform_node_index
[transform->GetCompositorElementId()]);
ASSERT_NE(nullptr, cc_transform);
EXPECT_TRUE(cc_transform->has_potential_animation);
EXPECT_TRUE(cc_transform->is_currently_animating);
}
// Regression test for https://crbug.com/781305. When we have a transform
// animation on a SVG element, the effect can be started on compositor but the
// element itself cannot.
TEST_P(AnimationCompositorAnimationsTest,
CannotStartElementOnCompositorEffectSVG) {
LoadTestData("transform-animation-on-svg.html");
Document* document = GetFrame()->GetDocument();
Element* target = document->getElementById("dots");
EXPECT_TRUE(
CheckCanStartElementOnCompositor(*target, *keyframe_animation_effect2_) &
CompositorAnimations::kTargetHasInvalidCompositingState);
EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 4u);
}
// Regression test for https://crbug.com/999333. We were relying on the Document
// always having Settings, which will not be the case if it is not attached to a
// Frame.
TEST_P(AnimationCompositorAnimationsTest,
DocumentWithoutSettingShouldNotCauseCrash) {
SetBodyInnerHTML("<div id='target'></div>");
Element* target = GetElementById("target");
ASSERT_TRUE(target);
// Move the target element to another Document, that does not have a frame
// (and thus no Settings).
Document* another_document = Document::CreateForTest();
ASSERT_FALSE(another_document->GetSettings());
another_document->adoptNode(target, ASSERT_NO_EXCEPTION);
// This should not crash.
EXPECT_NE(
CheckCanStartElementOnCompositor(*target, *keyframe_animation_effect2_),
CompositorAnimations::kNoFailure);
}
TEST_P(AnimationCompositorAnimationsTest,
CanStartTransformAnimationOnCompositorForSVG) {
SetBodyInnerHTML(R"HTML(
<style>
.animate {
width: 100px;
height: 100px;
animation: wave 1s infinite;
}
@keyframes wave {
0% { transform: rotate(-5deg); }
100% { transform: rotate(5deg); }
}
</style>
<svg id="svg" class="animate">
<rect id="rect" class="animate"/>
<rect id="rect-useref" class="animate"/>
<rect id="rect-smil" class="animate">
<animateMotion dur="10s" repeatCount="indefinite"
path="M0,0 L100,100 z"/>
</rect>
<rect id="rect-effect" class="animate"
vector-effect="non-scaling-stroke"/>
<g id="g-effect" class="animate">
<rect class="animate" vector-effect="non-scaling-stroke"/>
</g>
<svg id="nested-svg" class="animate"/>
<foreignObject id="foreign" class="animate"/>
<foreignObject id="foreign-zoomed" class="animate"
style="zoom: 1.5; will-change: opacity"/>
<use id="use" href="#rect-useref" class="animate"/>
<use id="use-offset" href="#rect-useref" x="10" class="animate"/>
</svg>
<svg id="svg-zoomed" class="animate" style="zoom: 1.5">
<rect id="rect-zoomed" class="animate"/>
</svg>
)HTML");
auto CanStartAnimation = [&](const char* id) -> bool {
return CompositorAnimations::CanStartTransformAnimationOnCompositorForSVG(
To<SVGElement>(*GetDocument().getElementById(id)));
};
EXPECT_TRUE(CanStartAnimation("svg"));
EXPECT_TRUE(CanStartAnimation("rect"));
EXPECT_FALSE(CanStartAnimation("rect-useref"));
EXPECT_FALSE(CanStartAnimation("rect-smil"));
EXPECT_FALSE(CanStartAnimation("rect-effect"));
EXPECT_FALSE(CanStartAnimation("g-effect"));
EXPECT_FALSE(CanStartAnimation("nested-svg"));
EXPECT_TRUE(CanStartAnimation("foreign"));
EXPECT_FALSE(CanStartAnimation("foreign-zoomed"));
EXPECT_TRUE(CanStartAnimation("use"));
EXPECT_FALSE(CanStartAnimation("use-offset"));
EXPECT_FALSE(CanStartAnimation("svg-zoomed"));
EXPECT_FALSE(CanStartAnimation("rect-zoomed"));
To<SVGElement>(GetDocument().getElementById("rect"))
->SetWebAnimatedAttribute(
svg_names::kXAttr,
MakeGarbageCollected<SVGLength>(SVGLength::Initial::kPercent50,
SVGLengthMode::kOther));
EXPECT_FALSE(CanStartAnimation("rect"));
}
TEST_P(AnimationCompositorAnimationsTest, UnsupportedSVGCSSProperty) {
SetBodyInnerHTML(R"HTML(
<style>
@keyframes mixed {
0% { transform: rotate(-5deg); stroke-dashoffset: 0; }
100% { transform: rotate(5deg); stroke-dashoffset: 180; }
}
</style>
<svg>
<rect id="rect"
style="width: 100px; height: 100px; animation: mixed 1s infinite"/>
</svg>
)HTML");
Element* element = GetDocument().getElementById("rect");
const Animation& animation =
*element->GetElementAnimations()->Animations().begin()->key;
EXPECT_EQ(CompositorAnimations::kUnsupportedCSSProperty,
animation.CheckCanStartAnimationOnCompositor(
GetDocument().View()->GetPaintArtifactCompositor()));
}
TEST_P(AnimationCompositorAnimationsTest,
TotalAnimationCountAcrossAllDocuments) {
LoadTestData("animation-in-main-frame.html");
cc::AnimationHost* host =
GetFrame()->GetDocument()->View()->GetCompositorAnimationHost();
// We are checking that the animation count for all documents is 1 for every
// frame.
for (int i = 0; i < 9; i++) {
BeginFrame();
EXPECT_EQ(1U, host->MainThreadAnimationsCount());
}
}
TEST_P(AnimationCompositorAnimationsTest, TrackRafAnimationAcrossAllDocuments) {
LoadTestData("raf-countdown-in-main-frame.html");
cc::AnimationHost* host =
GetFrame()->GetDocument()->View()->GetCompositorAnimationHost();
// The test file registers two rAF 'animations'; one which ends after 5
// iterations and the other that ends after 10.
for (int i = 0; i < 9; i++) {
BeginFrame();
EXPECT_TRUE(host->CurrentFrameHadRAF());
EXPECT_TRUE(host->NextFrameHasPendingRAF());
}
// On the 10th iteration, there should be a current rAF, but no more pending
// rAFs.
BeginFrame();
EXPECT_TRUE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
// On the 11th iteration, there should be no more rAFs firing.
BeginFrame();
EXPECT_FALSE(host->CurrentFrameHadRAF());
EXPECT_FALSE(host->NextFrameHasPendingRAF());
}
} // namespace blink