/* * 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 #include #include #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_frame_widget_base.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/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 linear_timing_function_; scoped_refptr cubic_ease_timing_function_; scoped_refptr cubic_custom_timing_function_; scoped_refptr step_timing_function_; Timing timing_; CompositorAnimations::CompositorTiming compositor_timing_; Persistent>> keyframe_vector2_; Persistent keyframe_animation_effect2_; Persistent>> keyframe_vector5_; Persistent keyframe_animation_effect5_; Persistent element_; Persistent inline_; Persistent 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(*keyframe_vector2_); keyframe_vector5_ = CreateCompositableFloatKeyframeVector(5); keyframe_animation_effect5_ = MakeGarbageCollected(*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(
text )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) { return CompositorAnimations::ConvertTimingForCompositor( t, base::TimeDelta(), out, 1); } CompositorAnimations::FailureReasons CanStartEffectOnCompositor( const Timing& timing, const KeyframeEffectModelBase& effect) { // TODO(crbug.com/725385): Remove once compositor uses InterpolationTypes. auto style = GetDocument().EnsureStyleResolver().StyleForElement(element_); 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) { const PaintArtifactCompositor* paint_artifact_compositor = GetDocument().View()->GetPaintArtifactCompositor(); return CompositorAnimations::CheckCanStartEffectOnCompositor( timing, element, animation, effect_model, paint_artifact_compositor, 1); } CompositorAnimations::FailureReasons CheckCanStartElementOnCompositor( const Element& element) { return CompositorAnimations::CheckCanStartElementOnCompositor(element); } void GetAnimationOnCompositor( Timing& timing, StringKeyframeEffectModel& effect, Vector>& 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(second)); return CanStartEffectOnCompositor( timing_, *MakeGarbageCollected(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(); 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(); keyframe->SetCSSPropertyValue(AtomicString(property_name), value, GetDocument().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>* CreateCompositableFloatKeyframeVector( size_t n) { Vector values; for (size_t i = 0; i < n; i++) { values.push_back(static_cast(i)); } return CreateCompositableFloatKeyframeVector(values); } HeapVector>* CreateCompositableFloatKeyframeVector( Vector& values) { HeapVector>* frames = MakeGarbageCollected>>(); 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(offset); } MockStringKeyframe(double offset) : StringKeyframe(), property_specific_( MakeGarbageCollected( offset)) { SetOffset(offset); } Keyframe::PropertySpecificKeyframe* CreatePropertySpecificKeyframe( const PropertyHandle&, EffectModel::CompositeOperation, double) const final { return property_specific_; // We know a shortcut. } void Trace(Visitor* visitor) 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(offset)) {} bool IsNeutral() const final { return true; } 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) const final { NOTREACHED(); return nullptr; } void Trace(Visitor* visitor) override { visitor->Trace(compositor_keyframe_value_); PropertySpecificKeyframe::Trace(visitor); } private: Member compositor_keyframe_value_; }; Member 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(); 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(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 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().EnsureStyleResolver().StyleForElement(element_); effect.SnapshotAllCompositorKeyframesIfNecessary(*element_.Get(), *style, nullptr); Vector> result; GetAnimationOnCompositor(timing_, effect, result, animation_playback_rate); DCHECK_EQ(1U, result.size()); return std::move(result[0]); } std::unique_ptr 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(*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() ->MainFrameWidgetBase() ->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) { // 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( frames_same)) & CompositorAnimations::kUnsupportedCSSProperty); StringKeyframeVector frames_mixed_properties; auto* keyframe = MakeGarbageCollected(); 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(); 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( frames_mixed_properties)) & CompositorAnimations::kUnsupportedCSSProperty); } TEST_P(AnimationCompositorAnimationsTest, CanStartEffectOnCompositorCustomCssProperty) { ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true); RegisterProperty(GetDocument(), "--foo", "", "0", false); RegisterProperty(GetDocument(), "--bar", "", "10px", false); RegisterProperty(GetDocument(), "--loo", "", "rgb(0, 0, 0)", false); RegisterProperty(GetDocument(), "--x", "", "0", false); SetCustomProperty("--foo", "10"); SetCustomProperty("--bar", "10px"); SetCustomProperty("--loo", "rgb(0, 255, 0)"); SetCustomProperty("--x", "5"); auto style = GetDocument().EnsureStyleResolver().StyleForElement(element_); 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)); NiceMock* mock_generator = MakeGarbageCollected>(); base::AutoReset scoped_override_generator( &g_override_generator, mock_generator); base::AutoReset scoped_create_function( CSSPaintImageGenerator::GetCreateFunctionForTesting(), ProvideOverrideGenerator); mock_generator->AddCustomProperty("--foo"); mock_generator->AddCustomProperty("--bar"); mock_generator->AddCustomProperty("--loo"); auto* ident = MakeGarbageCollected("foopainter"); CSSPaintValue* paint_value = MakeGarbageCollected(ident); paint_value->CreateGeneratorForTesting(GetDocument()); StyleGeneratedImage* style_image = MakeGarbageCollected(*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); } TEST_P(AnimationCompositorAnimationsTest, ConvertTimingForCompositorStartDelay) { timing_.iteration_duration = AnimationTimeDelta::FromSecondsD(20); timing_.start_delay = 2.0; EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_)); EXPECT_DOUBLE_EQ(-2.0, compositor_timing_.scaled_time_offset.InSecondsF()); timing_.start_delay = -2.0; EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_)); EXPECT_DOUBLE_EQ(2.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::infinity(); EXPECT_TRUE(ConvertTimingForCompositor(timing_, compositor_timing_)); EXPECT_EQ(std::numeric_limits::infinity(), compositor_timing_.adjusted_iteration_count); timing_.iteration_count = std::numeric_limits::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::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(*keyframe_vector2_); keyframe_vector5_->at(3)->SetEasing(cubic_ease_timing_function_.get()); keyframe_animation_effect5_ = MakeGarbageCollected(*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(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(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(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(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(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(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(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(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(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, CanStartEffectOnCompositorTimingFunctionChainedCubicMatchingOffsets) { keyframe_vector2_->at(0)->SetEasing(cubic_ease_timing_function_.get()); keyframe_animation_effect2_ = MakeGarbageCollected(*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(*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(*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(*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(*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(*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(*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(*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(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(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(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( 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(non_css_frames_vector); EXPECT_TRUE(CanStartEffectOnCompositor(timing_, *non_css_frames) & CompositorAnimations::kAnimationAffectsNonCSSProperties); // 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 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 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 keyframe_model = ConvertToCompositorAnimation(*effect); std::unique_ptr 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 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 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 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 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(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 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 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 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(frames); timing_.timing_function = linear_timing_function_.get(); timing_.iteration_count = 10; timing_.direction = Timing::PlaybackDirection::ALTERNATE_REVERSE; std::unique_ptr 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 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 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 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 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 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 keyframe_model = ConvertToCompositorAnimation(*effect); std::unique_ptr 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(*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, "", "0", false); SetCustomProperty(property_name, "10"); StringKeyframeEffectModel* effect = CreateKeyframeEffectModel( CreateReplaceOpKeyframe(property_name, "10", 0), CreateReplaceOpKeyframe(property_name, "20", 1.0)); std::unique_ptr keyframe_model = ConvertToCompositorAnimation(*effect); EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY, keyframe_model->TargetProperty()); EXPECT_EQ(keyframe_model->GetCustomPropertyNameForTesting(), property_name.Utf8().data()); } TEST_P(AnimationCompositorAnimationsTest, CreateSimpleCustomFloatPropertyAnimation) { ScopedOffMainThreadCSSPaintForTest off_main_thread_css_paint(true); RegisterProperty(GetDocument(), "--foo", "", "0", false); SetCustomProperty("--foo", "10"); StringKeyframeEffectModel* effect = CreateKeyframeEffectModel(CreateReplaceOpKeyframe("--foo", "10", 0), CreateReplaceOpKeyframe("--foo", "20", 1.0)); std::unique_ptr keyframe_model = ConvertToCompositorAnimation(*effect); EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY, keyframe_model->TargetProperty()); std::unique_ptr 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", "", "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 keyframe_model = ConvertToCompositorAnimation(*effect); EXPECT_EQ(compositor_target_property::CSS_CUSTOM_PROPERTY, keyframe_model->TargetProperty()); std::unique_ptr 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", " | ", "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>> key_frames = MakeGarbageCollected>>(); 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(*key_frames); KeyframeEffectModelBase* animation_effect2 = MakeGarbageCollected(*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( 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( 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 = 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), CompositorAnimations::kNoFailure); // Setting to CompositingReasonNone should produce false. UpdateDummyTransformNode(properties, CompositingReason::kNone); EXPECT_TRUE(CheckCanStartElementOnCompositor(*element) & CompositorAnimations::kTargetHasInvalidCompositingState); // Clearing the transform node entirely should also produce false. properties.ClearTransform(); EXPECT_TRUE(CheckCanStartElementOnCompositor(*element) & CompositorAnimations::kTargetHasInvalidCompositingState); element->SetLayoutObject(nullptr); LayoutObjectProxy::Dispose(layout_object); } TEST_P(AnimationCompositorAnimationsTest, CanStartElementOnCompositorEffectBasedOnPaintProperties) { Persistent 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), CompositorAnimations::kNoFailure); // Setting to CompositingReasonNone should produce false. UpdateDummyEffectNode(properties, CompositingReason::kNone); EXPECT_TRUE(CheckCanStartElementOnCompositor(*element) & CompositorAnimations::kTargetHasInvalidCompositingState); // Clearing the effect node entirely should also produce false. properties.ClearEffect(); EXPECT_TRUE(CheckCanStartElementOnCompositor(*element) & 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("
"); // 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, 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(cc::kNotScaled, cc_transform->starting_animation_scale); EXPECT_EQ(cc::kNotScaled, cc_transform->maximum_animation_scale); // Make sure the animation is started on the compositor. EXPECT_EQ(CheckCanStartElementOnCompositor(*target), CompositorAnimations::kNoFailure); EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 1u); cc::AnimationHost* host = document->View()->GetCompositorAnimationHost(); EXPECT_EQ(host->MainThreadAnimationsCount(), 0u); EXPECT_EQ(host->CompositedAnimationsCount(), 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(2.f, cc_transform->starting_animation_scale); EXPECT_EQ(5.f, cc_transform->maximum_animation_scale); // Make sure the animation is started on the compositor. EXPECT_EQ(CheckCanStartElementOnCompositor(*target), CompositorAnimations::kNoFailure); EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 1u); cc::AnimationHost* host = document->View()->GetCompositorAnimationHost(); EXPECT_EQ(host->MainThreadAnimationsCount(), 0u); EXPECT_EQ(host->CompositedAnimationsCount(), 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), 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); cc::AnimationHost* host = document->View()->GetCompositorAnimationHost(); EXPECT_EQ(host->MainThreadAnimationsCount(), 0u); EXPECT_EQ(host->CompositedAnimationsCount(), 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 = ToLayoutBoxModelObject(target->GetLayoutObject()) ->Layer() ->GetCompositedLayerMapping(); ASSERT_NE(nullptr, composited_layer_mapping); const cc::PictureLayer* layer = composited_layer_mapping->MainGraphicsLayer()->CcLayer(); ASSERT_NE(nullptr, layer); EXPECT_TRUE(layer->double_sided()); // 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_FALSE(layer->double_sided()) << "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) & CompositorAnimations::kTargetHasInvalidCompositingState); EXPECT_EQ(document->Timeline().AnimationsNeedingUpdateCount(), 4u); cc::AnimationHost* host = document->View()->GetCompositorAnimationHost(); EXPECT_EQ(host->MainThreadAnimationsCount(), 4u); EXPECT_EQ(host->CompositedAnimationsCount(), 0u); } // 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("
"); 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 = MakeGarbageCollected(); ASSERT_FALSE(another_document->GetSettings()); another_document->adoptNode(target, ASSERT_NO_EXCEPTION); // This should not crash. EXPECT_NE(CheckCanStartElementOnCompositor(*target), CompositorAnimations::kNoFailure); } } // namespace blink