| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/utility/layer_copy_animator.h" |
| |
| #include "base/cancelable_callback.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "base/timer/timer.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/window.h" |
| #include "ui/compositor/compositor.h" |
| #include "ui/compositor/layer.h" |
| #include "ui/compositor/layer_animation_sequence.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| #include "ui/compositor/scoped_layer_animation_settings.h" |
| #include "ui/compositor/test/test_compositor_host.h" |
| #include "ui/compositor/test/test_context_factories.h" |
| #include "ui/compositor/test/test_layer_animation_observer.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace ash { |
| namespace { |
| |
| class TestLayerCopyAnimator final : public LayerCopyAnimator { |
| public: |
| explicit TestLayerCopyAnimator(aura::Window* window) |
| : LayerCopyAnimator(window) {} |
| TestLayerCopyAnimator(const TestLayerCopyAnimator& animator) = delete; |
| TestLayerCopyAnimator& operator=(const TestLayerCopyAnimator& animator) = |
| delete; |
| ~TestLayerCopyAnimator() final = default; |
| |
| // LayerCopyAnimator: |
| void OnLayerCopied(std::unique_ptr<ui::Layer> new_layer) override { |
| DCHECK(!copied_); |
| LayerCopyAnimator::OnLayerCopied(std::move(new_layer)); |
| copied_ = true; |
| if (run_loop_.running()) |
| run_loop_.Quit(); |
| } |
| |
| ui::Layer* WaitForCopy() { |
| if (!copied_) |
| run_loop_.Run(); |
| return copied_layer_for_test(); |
| } |
| |
| private: |
| base::RunLoop run_loop_; |
| bool copied_ = false; |
| }; |
| |
| class LayerCopyAnimatorTest : public testing::Test { |
| public: |
| LayerCopyAnimatorTest() = default; |
| LayerCopyAnimatorTest(const LayerCopyAnimatorTest&) = delete; |
| LayerCopyAnimatorTest& operator=(const LayerCopyAnimatorTest&) = delete; |
| ~LayerCopyAnimatorTest() override = default; |
| |
| // testing::Test: |
| void SetUp() override { |
| context_factories_ = std::make_unique<ui::TestContextFactories>(false); |
| |
| const gfx::Rect bounds(300, 300); |
| host_.reset(ui::TestCompositorHost::Create( |
| bounds, context_factories_->GetContextFactory())); |
| host_->Show(); |
| |
| root_.Init(ui::LAYER_NOT_DRAWN); |
| root_.SetBounds(gfx::Rect(200, 200)); |
| compositor()->SetRootLayer(root_.layer()); |
| |
| anim_root_ = std::make_unique<aura::Window>(nullptr); |
| anim_root_->Init(ui::LAYER_NOT_DRAWN); |
| anim_root_->SetBounds(gfx::Rect(100, 100)); |
| |
| anim_leaf_.Init(ui::LAYER_TEXTURED); |
| anim_leaf_.SetBounds(gfx::Rect(50, 50)); |
| |
| root_.AddChild(anim_root_.get()); |
| anim_root_->AddChild(&anim_leaf_); |
| } |
| |
| void TearDown() override { |
| DeleteAnimRoot(); |
| host_.reset(); |
| context_factories_.reset(); |
| } |
| |
| void Advance(const base::TimeDelta& delta) { |
| task_environment_.FastForwardBy(delta); |
| } |
| |
| void GenerateOneFrame() { compositor()->ScheduleFullRedraw(); } |
| |
| ui::Compositor* compositor() { return host_->GetCompositor(); } |
| |
| aura::Window* root() { return &root_; } |
| aura::Window* anim_root() { return anim_root_.get(); } |
| |
| void DeleteAnimRoot() { |
| if (anim_root_) { |
| anim_root_->RemoveChild(&anim_leaf_); |
| anim_root_.reset(); |
| } |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME, |
| base::test::TaskEnvironment::MainThreadType::UI}; |
| |
| std::unique_ptr<ui::TestContextFactories> context_factories_; |
| std::unique_ptr<ui::TestCompositorHost> host_; |
| aura::Window root_{nullptr}; |
| |
| std::unique_ptr<aura::Window> anim_root_; |
| aura::Window anim_leaf_{nullptr}; |
| }; |
| |
| } // namespace |
| |
| TEST_F(LayerCopyAnimatorTest, Basic) { |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| auto* root_layer = root()->layer(); |
| auto* anim_layer = anim_root()->layer(); |
| |
| auto* animator = new TestLayerCopyAnimator(anim_root()); |
| EXPECT_FALSE(animator->animation_requested()); |
| EXPECT_EQ(2u, anim_layer->children().size()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| |
| GenerateOneFrame(); |
| auto* copied_layer = animator->WaitForCopy(); |
| EXPECT_TRUE(copied_layer); |
| |
| EXPECT_EQ(ui::LAYER_SOLID_COLOR, copied_layer->type()); |
| EXPECT_EQ(gfx::Size(100, 100), copied_layer->size()); |
| |
| ui::TestLayerAnimationObserver observer; |
| animator->MaybeStartAnimation( |
| &observer, base::BindOnce([](ui::Layer* layer, |
| ui::LayerAnimationObserver* observer) { |
| DCHECK(observer); |
| layer->SetOpacity(0.f); |
| ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); |
| settings.SetTransitionDuration(base::Milliseconds(1)); |
| layer->SetOpacity(1.f); |
| |
| ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence( |
| ui::LayerAnimationElement::CreateOpacityElement(1.0, |
| base::TimeDelta())); |
| sequence->AddObserver(observer); |
| layer->GetAnimator()->ScheduleAnimation(sequence); |
| })); |
| ASSERT_EQ(2u, root_layer->children().size()); |
| EXPECT_EQ(copied_layer, root_layer->children()[1]); |
| EXPECT_TRUE(copied_layer->GetAnimator()->is_animating()); |
| EXPECT_EQ(0.f, anim_layer->GetTargetOpacity()); |
| |
| Advance(base::Milliseconds(1000)); |
| EXPECT_EQ(3, observer.last_ended_sequence_epoch()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| EXPECT_EQ(1.f, anim_layer->GetTargetOpacity()); |
| } |
| |
| TEST_F(LayerCopyAnimatorTest, CopyAfterAnimationRequest) { |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| auto* root_layer = root()->layer(); |
| auto* anim_layer = anim_root()->layer(); |
| |
| auto* animator = new TestLayerCopyAnimator(anim_root()); |
| EXPECT_FALSE(animator->animation_requested()); |
| EXPECT_EQ(2u, anim_layer->children().size()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| |
| ui::TestLayerAnimationObserver observer; |
| |
| animator->MaybeStartAnimation( |
| &observer, base::BindOnce([](ui::Layer* layer, |
| ui::LayerAnimationObserver* observer) { |
| DCHECK(observer); |
| layer->SetOpacity(0.f); |
| ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); |
| // Longer duration so that animation doesn't end after copy. |
| settings.SetTransitionDuration(base::Milliseconds(100)); |
| layer->SetOpacity(1.f); |
| |
| ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence( |
| ui::LayerAnimationElement::CreateOpacityElement(1.0, |
| base::TimeDelta())); |
| sequence->AddObserver(observer); |
| layer->GetAnimator()->ScheduleAnimation(sequence); |
| })); |
| |
| EXPECT_FALSE(animator->copied_layer_for_test()); |
| |
| GenerateOneFrame(); |
| auto* copied_layer = animator->WaitForCopy(); |
| ASSERT_TRUE(copied_layer); |
| |
| EXPECT_EQ(ui::LAYER_SOLID_COLOR, copied_layer->type()); |
| EXPECT_EQ(gfx::Size(100, 100), copied_layer->size()); |
| ASSERT_EQ(2u, root_layer->children().size()); |
| EXPECT_EQ(copied_layer, root_layer->children()[1]); |
| EXPECT_TRUE(copied_layer->GetAnimator()->is_animating()); |
| EXPECT_EQ(0.f, anim_layer->GetTargetOpacity()); |
| |
| Advance(base::Milliseconds(1000)); |
| |
| // When animation starts before copy, it registers the observer to fake |
| // sequecne, hence become 6. |
| EXPECT_EQ(6, observer.last_ended_sequence_epoch()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| EXPECT_EQ(1.f, anim_layer->GetTargetOpacity()); |
| } |
| |
| TEST_F(LayerCopyAnimatorTest, CancelByResize) { |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| auto* root_layer = root()->layer(); |
| auto* anim_layer = anim_root()->layer(); |
| |
| auto* animator = new TestLayerCopyAnimator(anim_root()); |
| EXPECT_FALSE(animator->animation_requested()); |
| EXPECT_EQ(2u, anim_layer->children().size()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| |
| anim_layer->SetBounds(gfx::Rect(210, 210)); |
| GenerateOneFrame(); |
| auto* copied_layer = animator->WaitForCopy(); |
| ASSERT_FALSE(copied_layer); |
| |
| ui::TestLayerAnimationObserver observer; |
| EXPECT_EQ(-1, observer.last_aborted_sequence_epoch()); |
| animator->MaybeStartAnimation( |
| &observer, base::BindOnce([](ui::Layer* layer, |
| ui::LayerAnimationObserver* observer) { |
| EXPECT_FALSE(true) << "Callback should not be called"; |
| })); |
| EXPECT_EQ(1.f, anim_layer->GetTargetOpacity()); |
| EXPECT_EQ(1, observer.last_aborted_sequence_epoch()); |
| } |
| |
| TEST_F(LayerCopyAnimatorTest, CancelByDelete) { |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| auto* root_layer = root()->layer(); |
| auto* anim_layer = anim_root()->layer(); |
| |
| auto* animator = new LayerCopyAnimator(anim_root()); |
| EXPECT_FALSE(animator->animation_requested()); |
| EXPECT_EQ(2u, anim_layer->children().size()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| |
| GenerateOneFrame(); |
| DeleteAnimRoot(); |
| } |
| |
| TEST_F(LayerCopyAnimatorTest, CancelByStop) { |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| auto* root_layer = root()->layer(); |
| auto* anim_layer = anim_root()->layer(); |
| |
| auto* animator = new TestLayerCopyAnimator(anim_root()); |
| EXPECT_FALSE(animator->animation_requested()); |
| EXPECT_EQ(2u, anim_layer->children().size()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| |
| GenerateOneFrame(); |
| auto* copied_layer = animator->WaitForCopy(); |
| ASSERT_TRUE(copied_layer); |
| |
| EXPECT_EQ(ui::LAYER_SOLID_COLOR, copied_layer->type()); |
| EXPECT_EQ(gfx::Size(100, 100), copied_layer->size()); |
| |
| ui::TestLayerAnimationObserver observer; |
| |
| animator->MaybeStartAnimation( |
| &observer, base::BindOnce([](ui::Layer* layer, |
| ui::LayerAnimationObserver* observer) { |
| DCHECK(observer); |
| layer->SetOpacity(0.f); |
| ui::ScopedLayerAnimationSettings settings(layer->GetAnimator()); |
| settings.SetTransitionDuration(base::Milliseconds(100)); |
| layer->SetOpacity(1.f); |
| |
| ui::LayerAnimationSequence* sequence = new ui::LayerAnimationSequence( |
| ui::LayerAnimationElement::CreateOpacityElement(1.0, |
| base::TimeDelta())); |
| sequence->AddObserver(observer); |
| layer->GetAnimator()->ScheduleAnimation(sequence); |
| })); |
| ASSERT_EQ(2u, root_layer->children().size()); |
| EXPECT_EQ(copied_layer, root_layer->children()[1]); |
| EXPECT_TRUE(copied_layer->GetAnimator()->is_animating()); |
| EXPECT_EQ(0.f, anim_layer->GetTargetOpacity()); |
| copied_layer->GetAnimator()->StopAnimating(); |
| |
| Advance(base::Milliseconds(1000)); |
| |
| EXPECT_EQ(3, observer.last_ended_sequence_epoch()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| EXPECT_EQ(1.f, anim_layer->GetTargetOpacity()); |
| } |
| |
| TEST_F(LayerCopyAnimatorTest, NoAnimationStopImmediately) { |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); |
| auto* root_layer = root()->layer(); |
| auto* anim_layer = anim_root()->layer(); |
| |
| auto* animator = new TestLayerCopyAnimator(anim_root()); |
| EXPECT_FALSE(animator->animation_requested()); |
| EXPECT_EQ(2u, anim_layer->children().size()); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| |
| GenerateOneFrame(); |
| auto* copied_layer = animator->WaitForCopy(); |
| ASSERT_TRUE(copied_layer); |
| |
| EXPECT_EQ(ui::LAYER_SOLID_COLOR, copied_layer->type()); |
| EXPECT_EQ(gfx::Size(100, 100), copied_layer->size()); |
| |
| ui::TestLayerAnimationObserver observer; |
| |
| animator->MaybeStartAnimation( |
| &observer, base::BindOnce([](ui::Layer* layer, |
| ui::LayerAnimationObserver* observer) {})); |
| EXPECT_EQ(1u, root_layer->children().size()); |
| EXPECT_EQ(1.f, anim_layer->GetTargetOpacity()); |
| EXPECT_EQ(1, observer.last_ended_sequence_epoch()); |
| } |
| |
| } // namespace ash |