// Copyright (c) 2012 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 "ui/wm/core/window_animations.h" #include "base/macros.h" #include "base/time/time.h" #include "ui/aura/test/aura_test_base.h" #include "ui/aura/test/test_windows.h" #include "ui/aura/window.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animator.h" #include "ui/compositor/scoped_animation_duration_scale_mode.h" #include "ui/gfx/animation/animation_container_element.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/wm/core/transient_window_manager.h" #include "ui/wm/core/transient_window_stacking_client.h" #include "ui/wm/core/window_util.h" #include "ui/wm/public/animation_host.h" using aura::Window; using ui::Layer; namespace wm { namespace { templateint GetZPosition(const T* child) { const T* parent = child->parent(); const std::vector children = parent->children(); typename std::vector::const_iterator iter = std::find(children.begin(), children.end(), child); DCHECK(iter != children.end()); return iter - children.begin(); } int GetWindowZPosition(const aura::Window* child) { return GetZPosition(child); } int GetLayerZPosition(const ui::Layer* child) { return GetZPosition(child); } } // namespace class WindowAnimationsTest : public aura::test::AuraTestBase { public: WindowAnimationsTest() {} void TearDown() override { AuraTestBase::TearDown(); } private: DISALLOW_COPY_AND_ASSIGN(WindowAnimationsTest); }; TEST_F(WindowAnimationsTest, LayerTargetVisibility) { scoped_ptr window( aura::test::CreateTestWindowWithId(0, NULL)); // Layer target visibility changes according to Show/Hide. window->Show(); EXPECT_TRUE(window->layer()->GetTargetVisibility()); window->Hide(); EXPECT_FALSE(window->layer()->GetTargetVisibility()); window->Show(); EXPECT_TRUE(window->layer()->GetTargetVisibility()); } TEST_F(WindowAnimationsTest, LayerTargetVisibility_AnimateShow) { // Tests if opacity and transform are reset when only show animation is // enabled. See also LayerTargetVisibility_AnimateHide. // Since the window is not visible after Hide() is called, opacity and // transform shouldn't matter in case of ANIMATE_SHOW, but we reset them // to keep consistency. scoped_ptr window(aura::test::CreateTestWindowWithId(0, NULL)); SetWindowVisibilityAnimationTransition(window.get(), ANIMATE_SHOW); // Layer target visibility and opacity change according to Show/Hide. window->Show(); AnimateOnChildWindowVisibilityChanged(window.get(), true); EXPECT_TRUE(window->layer()->GetTargetVisibility()); EXPECT_EQ(1, window->layer()->opacity()); window->Hide(); AnimateOnChildWindowVisibilityChanged(window.get(), false); EXPECT_FALSE(window->layer()->GetTargetVisibility()); EXPECT_EQ(0, window->layer()->opacity()); EXPECT_EQ(gfx::Transform(), window->layer()->transform()); window->Show(); AnimateOnChildWindowVisibilityChanged(window.get(), true); EXPECT_TRUE(window->layer()->GetTargetVisibility()); EXPECT_EQ(1, window->layer()->opacity()); } TEST_F(WindowAnimationsTest, LayerTargetVisibility_AnimateHide) { // Tests if opacity and transform are reset when only hide animation is // enabled. Hide animation changes opacity and transform in addition to // visibility, so we need to reset not only visibility but also opacity // and transform to show the window. scoped_ptr window(aura::test::CreateTestWindowWithId(0, NULL)); SetWindowVisibilityAnimationTransition(window.get(), ANIMATE_HIDE); // Layer target visibility and opacity change according to Show/Hide. window->Show(); AnimateOnChildWindowVisibilityChanged(window.get(), true); EXPECT_TRUE(window->layer()->GetTargetVisibility()); EXPECT_EQ(1, window->layer()->opacity()); EXPECT_EQ(gfx::Transform(), window->layer()->transform()); window->Hide(); AnimateOnChildWindowVisibilityChanged(window.get(), false); EXPECT_FALSE(window->layer()->GetTargetVisibility()); EXPECT_EQ(0, window->layer()->opacity()); window->Show(); AnimateOnChildWindowVisibilityChanged(window.get(), true); EXPECT_TRUE(window->layer()->GetTargetVisibility()); EXPECT_EQ(1, window->layer()->opacity()); EXPECT_EQ(gfx::Transform(), window->layer()->transform()); } TEST_F(WindowAnimationsTest, HideAnimationDetachLayers) { scoped_ptr parent(aura::test::CreateTestWindowWithId(0, NULL)); scoped_ptr other( aura::test::CreateTestWindowWithId(1, parent.get())); scoped_ptr animating_window( aura::test::CreateTestWindowWithId(2, parent.get())); SetWindowVisibilityAnimationTransition(animating_window.get(), ANIMATE_HIDE); EXPECT_EQ(0, GetWindowZPosition(other.get())); EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); EXPECT_EQ(0, GetLayerZPosition(other->layer())); EXPECT_EQ(1, GetLayerZPosition(animating_window->layer())); { ui::ScopedAnimationDurationScaleMode scale_mode( ui::ScopedAnimationDurationScaleMode::FAST_DURATION); ui::Layer* animating_layer = animating_window->layer(); animating_window->Hide(); EXPECT_TRUE(AnimateOnChildWindowVisibilityChanged( animating_window.get(), false)); EXPECT_TRUE(animating_layer->GetAnimator()->is_animating()); EXPECT_FALSE(animating_layer->delegate()); // Make sure the Hide animation create another layer, and both are in // the parent layer. EXPECT_NE(animating_window->layer(), animating_layer); EXPECT_TRUE( std::find(parent->layer()->children().begin(), parent->layer()->children().end(), animating_layer) != parent->layer()->children().end()); EXPECT_TRUE( std::find(parent->layer()->children().begin(), parent->layer()->children().end(), animating_window->layer()) != parent->layer()->children().end()); // Current layer must be already hidden. EXPECT_FALSE(animating_window->layer()->visible()); EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); EXPECT_EQ(1, GetLayerZPosition(animating_window->layer())); EXPECT_EQ(2, GetLayerZPosition(animating_layer)); parent->StackChildAtTop(other.get()); EXPECT_EQ(0, GetWindowZPosition(animating_window.get())); EXPECT_EQ(1, GetWindowZPosition(other.get())); EXPECT_EQ(0, GetLayerZPosition(animating_window->layer())); EXPECT_EQ(1, GetLayerZPosition(other->layer())); // Make sure the animating layer is on top. EXPECT_EQ(2, GetLayerZPosition(animating_layer)); // Animating layer must be gone animating_layer->GetAnimator()->StopAnimating(); EXPECT_TRUE( std::find(parent->layer()->children().begin(), parent->layer()->children().end(), animating_layer) == parent->layer()->children().end()); } } TEST_F(WindowAnimationsTest, HideAnimationDetachLayersWithTransientChildren) { TransientWindowStackingClient transient_stacking_client; scoped_ptr parent(aura::test::CreateTestWindowWithId(0, NULL)); scoped_ptr other( aura::test::CreateTestWindowWithId(1, parent.get())); scoped_ptr animating_window( aura::test::CreateTestWindowWithId(2, parent.get())); SetWindowVisibilityAnimationTransition(animating_window.get(), ANIMATE_HIDE); scoped_ptr transient1( aura::test::CreateTestWindowWithId(3, parent.get())); scoped_ptr transient2( aura::test::CreateTestWindowWithId(4, parent.get())); TransientWindowManager::Get(animating_window.get()); AddTransientChild(animating_window.get(), transient1.get()); AddTransientChild(animating_window.get(), transient2.get()); EXPECT_EQ(0, GetWindowZPosition(other.get())); EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); EXPECT_EQ(2, GetWindowZPosition(transient1.get())); EXPECT_EQ(3, GetWindowZPosition(transient2.get())); { ui::ScopedAnimationDurationScaleMode scale_mode( ui::ScopedAnimationDurationScaleMode::FAST_DURATION); ui::Layer* animating_layer = animating_window->layer(); animating_window->Hide(); EXPECT_TRUE(AnimateOnChildWindowVisibilityChanged( animating_window.get(), false)); EXPECT_TRUE(animating_layer->GetAnimator()->is_animating()); EXPECT_FALSE(animating_layer->delegate()); EXPECT_EQ(1, GetWindowZPosition(animating_window.get())); EXPECT_EQ(2, GetWindowZPosition(transient1.get())); EXPECT_EQ(3, GetWindowZPosition(transient2.get())); EXPECT_EQ(1, GetLayerZPosition(animating_window->layer())); EXPECT_EQ(2, GetLayerZPosition(transient1->layer())); EXPECT_EQ(3, GetLayerZPosition(transient2->layer())); EXPECT_EQ(4, GetLayerZPosition(animating_layer)); parent->StackChildAtTop(other.get()); EXPECT_EQ(0, GetWindowZPosition(animating_window.get())); EXPECT_EQ(1, GetWindowZPosition(transient1.get())); EXPECT_EQ(2, GetWindowZPosition(transient2.get())); EXPECT_EQ(3, GetWindowZPosition(other.get())); EXPECT_EQ(0, GetLayerZPosition(animating_window->layer())); EXPECT_EQ(1, GetLayerZPosition(transient1->layer())); EXPECT_EQ(2, GetLayerZPosition(transient2->layer())); EXPECT_EQ(3, GetLayerZPosition(other->layer())); // Make sure the animating layer is on top of all windows. EXPECT_EQ(4, GetLayerZPosition(animating_layer)); } } // A simple AnimationHost implementation for the NotifyHideCompleted test. class NotifyHideCompletedAnimationHost : public aura::client::AnimationHost { public: NotifyHideCompletedAnimationHost() : hide_completed_(false) {} ~NotifyHideCompletedAnimationHost() override {} // Overridden from TestWindowDelegate: void OnWindowHidingAnimationCompleted() override { hide_completed_ = true; } void SetHostTransitionOffsets(const gfx::Vector2d& top_left, const gfx::Vector2d& bottom_right) override {} bool hide_completed() const { return hide_completed_; } private: bool hide_completed_; DISALLOW_COPY_AND_ASSIGN(NotifyHideCompletedAnimationHost); }; TEST_F(WindowAnimationsTest, NotifyHideCompleted) { NotifyHideCompletedAnimationHost animation_host; scoped_ptr window(aura::test::CreateTestWindowWithId(0, NULL)); aura::client::SetAnimationHost(window.get(), &animation_host); wm::SetWindowVisibilityAnimationType( window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_FADE); AnimateOnChildWindowVisibilityChanged(window.get(), true); EXPECT_TRUE(window->layer()->visible()); EXPECT_FALSE(animation_host.hide_completed()); AnimateOnChildWindowVisibilityChanged(window.get(), false); EXPECT_TRUE(animation_host.hide_completed()); } // The rotation animation for hiding a window should not leak the animation // observer. TEST_F(WindowAnimationsTest, RotateHideNoLeak) { ui::ScopedAnimationDurationScaleMode scale_mode( ui::ScopedAnimationDurationScaleMode::FAST_DURATION); scoped_ptr window(aura::test::CreateTestWindowWithId(0, NULL)); ui::Layer* animating_layer = window->layer(); wm::SetWindowVisibilityAnimationType(window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE); AnimateOnChildWindowVisibilityChanged(window.get(), true); AnimateOnChildWindowVisibilityChanged(window.get(), false); animating_layer->GetAnimator()->StopAnimating(); } // The rotation animation for hiding a window should not crash when terminated // by LayerAnimator::StopAnimating(). TEST_F(WindowAnimationsTest, RotateHideNoCrash) { ui::ScopedAnimationDurationScaleMode scale_mode( ui::ScopedAnimationDurationScaleMode::FAST_DURATION); scoped_ptr window(aura::test::CreateTestWindowWithId(0, NULL)); ui::Layer* animating_layer = window->layer(); wm::SetWindowVisibilityAnimationType(window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_ROTATE); AnimateOnChildWindowVisibilityChanged(window.get(), true); window->layer()->GetAnimator()->Step(base::TimeTicks::Now() + base::TimeDelta::FromSeconds(5)); AnimateOnChildWindowVisibilityChanged(window.get(), false); animating_layer->GetAnimator()->StopAnimating(); } } // namespace wm