| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/modules/csspaint/background_color_paint_worklet.h" |
| |
| #include "testing/gmock/include/gmock/gmock.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/inert_effect.h" |
| #include "third_party/blink/renderer/core/animation/keyframe_effect.h" |
| #include "third_party/blink/renderer/core/animation/keyframe_effect_model.h" |
| #include "third_party/blink/renderer/core/animation/string_keyframe.h" |
| #include "third_party/blink/renderer/core/animation/timing.h" |
| #include "third_party/blink/renderer/core/dom/element.h" |
| #include "third_party/blink/renderer/core/testing/page_test_base.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/graphics/color.h" |
| #include "third_party/blink/renderer/platform/graphics/platform_paint_worklet_layer_painter.h" |
| |
| namespace blink { |
| |
| using BackgroundColorPaintWorkletTest = PageTestBase; |
| |
| // Test the case when there is no animation attached to the element. |
| TEST_F(BackgroundColorPaintWorkletTest, FallbackToMainNoAnimation) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| SetBodyInnerHTML(R"HTML( |
| <div id ="target" style="width: 100px; height: 100px"> |
| </div> |
| )HTML"); |
| Element* element = GetElementById("target"); |
| EXPECT_FALSE(element->GetElementAnimations()); |
| Vector<Color> animated_colors; |
| Vector<double> offsets; |
| EXPECT_FALSE(BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams( |
| element, &animated_colors, &offsets)); |
| } |
| |
| // Test that when an element has other animations but no background color |
| // animation, then we fall back to the main thread. Also testing that calling |
| // BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams do not crash. |
| TEST_F(BackgroundColorPaintWorkletTest, NoBGColorAnimationFallback) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| SetBodyInnerHTML(R"HTML( |
| <div id ="target" style="width: 100px; height: 100px"> |
| </div> |
| )HTML"); |
| |
| Timing timing; |
| timing.iteration_duration = AnimationTimeDelta::FromSecondsD(30); |
| |
| CSSPropertyID property_id = CSSPropertyID::kColor; |
| Persistent<StringKeyframe> start_keyframe = |
| MakeGarbageCollected<StringKeyframe>(); |
| start_keyframe->SetCSSPropertyValue( |
| property_id, "red", SecureContextMode::kInsecureContext, nullptr); |
| Persistent<StringKeyframe> end_keyframe = |
| MakeGarbageCollected<StringKeyframe>(); |
| end_keyframe->SetCSSPropertyValue( |
| property_id, "green", SecureContextMode::kInsecureContext, nullptr); |
| |
| StringKeyframeVector keyframes; |
| keyframes.push_back(start_keyframe); |
| keyframes.push_back(end_keyframe); |
| |
| auto* model = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes); |
| model->SetComposite(EffectModel::kCompositeAccumulate); |
| |
| Element* element = GetElementById("target"); |
| NonThrowableExceptionState exception_state; |
| DocumentTimeline* timeline = |
| MakeGarbageCollected<DocumentTimeline>(&GetDocument()); |
| Animation* animation = Animation::Create( |
| MakeGarbageCollected<KeyframeEffect>(element, model, timing), timeline, |
| exception_state); |
| UpdateAllLifecyclePhasesForTest(); |
| animation->play(); |
| |
| EXPECT_TRUE(element->GetElementAnimations()); |
| EXPECT_EQ(element->GetElementAnimations()->Animations().size(), 1u); |
| Vector<Color> animated_colors; |
| Vector<double> offsets; |
| EXPECT_FALSE(BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams( |
| element, &animated_colors, &offsets)); |
| EXPECT_TRUE(animated_colors.IsEmpty()); |
| EXPECT_TRUE(offsets.IsEmpty()); |
| } |
| |
| // Test the case where the composite mode is not replace. |
| TEST_F(BackgroundColorPaintWorkletTest, FallbackToMainCompositeAccumulate) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| SetBodyInnerHTML(R"HTML( |
| <div id ="target" style="width: 100px; height: 100px"> |
| </div> |
| )HTML"); |
| |
| Timing timing; |
| timing.iteration_duration = AnimationTimeDelta::FromSecondsD(30); |
| |
| CSSPropertyID property_id = CSSPropertyID::kBackgroundColor; |
| Persistent<StringKeyframe> start_keyframe = |
| MakeGarbageCollected<StringKeyframe>(); |
| start_keyframe->SetCSSPropertyValue( |
| property_id, "red", SecureContextMode::kInsecureContext, nullptr); |
| Persistent<StringKeyframe> end_keyframe = |
| MakeGarbageCollected<StringKeyframe>(); |
| end_keyframe->SetCSSPropertyValue( |
| property_id, "green", SecureContextMode::kInsecureContext, nullptr); |
| |
| StringKeyframeVector keyframes; |
| keyframes.push_back(start_keyframe); |
| keyframes.push_back(end_keyframe); |
| |
| auto* model = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes); |
| model->SetComposite(EffectModel::kCompositeAccumulate); |
| |
| Element* element = GetElementById("target"); |
| NonThrowableExceptionState exception_state; |
| DocumentTimeline* timeline = |
| MakeGarbageCollected<DocumentTimeline>(&GetDocument()); |
| Animation* animation = Animation::Create( |
| MakeGarbageCollected<KeyframeEffect>(element, model, timing), timeline, |
| exception_state); |
| UpdateAllLifecyclePhasesForTest(); |
| animation->play(); |
| EXPECT_FALSE(animation->CanCompositeBGColorAnim()); |
| |
| EXPECT_TRUE(element->GetElementAnimations()); |
| EXPECT_EQ(element->GetElementAnimations()->Animations().size(), 1u); |
| Vector<Color> animated_colors; |
| Vector<double> offsets; |
| EXPECT_FALSE(BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams( |
| element, &animated_colors, &offsets)); |
| EXPECT_FALSE(animation->CanCompositeBGColorAnim()); |
| } |
| |
| // Test that when there are multiple bgcolor animations on an Element, we |
| // composite the animation with the highest compositing order. |
| TEST_F(BackgroundColorPaintWorkletTest, MultipleAnimationsNotFallback) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| SetBodyInnerHTML(R"HTML( |
| <div id ="target" style="width: 100px; height: 100px"> |
| </div> |
| )HTML"); |
| |
| Timing timing; |
| timing.iteration_duration = AnimationTimeDelta::FromSecondsD(30); |
| |
| CSSPropertyID property_id = CSSPropertyID::kBackgroundColor; |
| Persistent<StringKeyframe> start_keyframe = |
| MakeGarbageCollected<StringKeyframe>(); |
| start_keyframe->SetCSSPropertyValue( |
| property_id, "red", SecureContextMode::kInsecureContext, nullptr); |
| Persistent<StringKeyframe> end_keyframe = |
| MakeGarbageCollected<StringKeyframe>(); |
| end_keyframe->SetCSSPropertyValue( |
| property_id, "green", SecureContextMode::kInsecureContext, nullptr); |
| |
| StringKeyframeVector keyframes; |
| keyframes.push_back(start_keyframe); |
| keyframes.push_back(end_keyframe); |
| auto* model1 = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes); |
| |
| Element* element = GetElementById("target"); |
| NonThrowableExceptionState exception_state; |
| DocumentTimeline* timeline = |
| MakeGarbageCollected<DocumentTimeline>(&GetDocument()); |
| Animation* animation1 = Animation::Create( |
| MakeGarbageCollected<KeyframeEffect>(element, model1, timing), timeline, |
| exception_state); |
| |
| start_keyframe->SetCSSPropertyValue( |
| property_id, "blue", SecureContextMode::kInsecureContext, nullptr); |
| end_keyframe->SetCSSPropertyValue( |
| property_id, "yellow", SecureContextMode::kInsecureContext, nullptr); |
| keyframes.clear(); |
| keyframes.push_back(start_keyframe); |
| keyframes.push_back(end_keyframe); |
| auto* model2 = MakeGarbageCollected<StringKeyframeEffectModel>(keyframes); |
| Animation* animation2 = Animation::Create( |
| MakeGarbageCollected<KeyframeEffect>(element, model2, timing), timeline, |
| exception_state); |
| UpdateAllLifecyclePhasesForTest(); |
| animation1->play(); |
| animation2->play(); |
| EXPECT_FALSE(animation1->CanCompositeBGColorAnim()); |
| EXPECT_FALSE(animation2->CanCompositeBGColorAnim()); |
| |
| // Two active background-color animations, fall back to main. |
| EXPECT_TRUE(element->GetElementAnimations()); |
| EXPECT_EQ(element->GetElementAnimations()->Animations().size(), 2u); |
| Vector<Color> animated_colors; |
| Vector<double> offsets; |
| EXPECT_TRUE(BackgroundColorPaintWorklet::GetBGColorPaintWorkletParams( |
| element, &animated_colors, &offsets)); |
| EXPECT_FALSE(animation1->CanCompositeBGColorAnim()); |
| EXPECT_TRUE(animation2->CanCompositeBGColorAnim()); |
| EXPECT_EQ(animated_colors.size(), 2u); |
| // The animated_colors should be blue and yellow. |
| EXPECT_EQ(animated_colors[0].Red(), 0); |
| EXPECT_EQ(animated_colors[0].Green(), 0); |
| EXPECT_EQ(animated_colors[0].Blue(), 255); |
| EXPECT_EQ(animated_colors[1].Red(), 255); |
| EXPECT_EQ(animated_colors[1].Green(), 255); |
| EXPECT_EQ(animated_colors[1].Blue(), 0); |
| } |
| |
| // Test that calling BackgroundColorPaintWorkletProxyClient::Paint won't crash |
| // when the animated property value is empty. |
| TEST_F(BackgroundColorPaintWorkletTest, ProxyClientPaintWithNoPropertyValue) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| Vector<Color> animated_colors = {Color(0, 255, 0), Color(255, 0, 0)}; |
| Vector<double> offsets = {0, 1}; |
| CompositorPaintWorkletJob::AnimatedPropertyValues property_values; |
| BackgroundColorPaintWorklet::ProxyClientPaintForTest(animated_colors, offsets, |
| property_values); |
| } |
| |
| // Test that BackgroundColorPaintWorkletProxyClient::Paint won't crash if the |
| // progress of the animation is a negative number. |
| TEST_F(BackgroundColorPaintWorkletTest, ProxyClientPaintWithNegativeProgress) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| Vector<Color> animated_colors = {Color(0, 255, 0), Color(255, 0, 0)}; |
| Vector<double> offsets = {0, 1}; |
| CompositorPaintWorkletJob::AnimatedPropertyValues property_values; |
| CompositorPaintWorkletInput::PropertyKey property_key( |
| CompositorPaintWorkletInput::NativePropertyType::kBackgroundColor, |
| CompositorElementId(1u)); |
| CompositorPaintWorkletInput::PropertyValue property_value(-0.0f); |
| property_values.insert(std::make_pair(property_key, property_value)); |
| BackgroundColorPaintWorklet::ProxyClientPaintForTest(animated_colors, offsets, |
| property_values); |
| } |
| |
| // Test that BackgroundColorPaintWorkletProxyClient::Paint won't crash if the |
| // progress of the animation is > 1. |
| TEST_F(BackgroundColorPaintWorkletTest, |
| ProxyClientPaintWithLargerThanOneProgress) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| Vector<Color> animated_colors = {Color(0, 255, 0), Color(255, 0, 0)}; |
| Vector<double> offsets = {0, 1}; |
| CompositorPaintWorkletJob::AnimatedPropertyValues property_values; |
| CompositorPaintWorkletInput::PropertyKey property_key( |
| CompositorPaintWorkletInput::NativePropertyType::kBackgroundColor, |
| CompositorElementId(1u)); |
| float progress = 1 + std::numeric_limits<float>::epsilon(); |
| CompositorPaintWorkletInput::PropertyValue property_value(progress); |
| property_values.insert(std::make_pair(property_key, property_value)); |
| BackgroundColorPaintWorklet::ProxyClientPaintForTest(animated_colors, offsets, |
| property_values); |
| } |
| |
| // Test that BackgroundColorPaintWorkletProxyClient::Paint won't crash when the |
| // largest offset is not exactly one. |
| TEST_F(BackgroundColorPaintWorkletTest, ProxyClientPaintWithCloseToOneOffset) { |
| ScopedCompositeBGColorAnimationForTest composite_bgcolor_animation(true); |
| Vector<Color> animated_colors = {Color(0, 255, 0), Color(0, 255, 255), |
| Color(255, 0, 0)}; |
| Vector<double> offsets = {0, 0.6, 0.99999}; |
| CompositorPaintWorkletJob::AnimatedPropertyValues property_values; |
| CompositorPaintWorkletInput::PropertyKey property_key( |
| CompositorPaintWorkletInput::NativePropertyType::kBackgroundColor, |
| CompositorElementId(1u)); |
| float progress = 1 - std::numeric_limits<float>::epsilon(); |
| CompositorPaintWorkletInput::PropertyValue property_value(progress); |
| property_values.insert(std::make_pair(property_key, property_value)); |
| BackgroundColorPaintWorklet::ProxyClientPaintForTest(animated_colors, offsets, |
| property_values); |
| } |
| |
| } // namespace blink |