// Copyright 2012 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/wm/window_animations.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/cpp/window_animation_types.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/window_state.h"
#include "ash/wm/wm_event.h"
#include "ash/wm/workspace_controller.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_util.h"
using aura::Window;
using ui::Layer;
namespace ash {
namespace {
void WaitForMilliseconds(int ms) {
base::RunLoop loop;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, loop.QuitClosure(), base::Milliseconds(ms));
loop.Run();
}
} // namespace
class WindowAnimationsTest : public AshTestBase {
public:
WindowAnimationsTest() = default;
WindowAnimationsTest(const WindowAnimationsTest&) = delete;
WindowAnimationsTest& operator=(const WindowAnimationsTest&) = delete;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
keyboard::switches::kEnableVirtualKeyboard);
AshTestBase::SetUp();
}
};
// Listens to animation scheduled notifications. Remembers the transition
// duration of the first sequence.
class MinimizeAnimationObserver : public ui::LayerAnimationObserver {
public:
explicit MinimizeAnimationObserver(ui::LayerAnimator* animator)
: animator_(animator) {
animator_->AddObserver(this);
// RemoveObserver is called when the first animation is scheduled and so
// there should be no need for now to remove it in destructor.
}
MinimizeAnimationObserver(const MinimizeAnimationObserver&) = delete;
MinimizeAnimationObserver& operator=(const MinimizeAnimationObserver&) =
delete;
base::TimeDelta duration() { return duration_; }
protected:
// ui::LayerAnimationObserver:
void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override {
duration_ = animator_->GetTransitionDuration();
animator_->RemoveObserver(this);
}
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {}
private:
raw_ptr<ui::LayerAnimator, DanglingUntriaged> animator_;
base::TimeDelta duration_;
};
// This is the class that simulates the behavior of
// `FrameHeader::FrameAnimatorView` which may recreate the window layer in the
// middle of setting the animation of the old and new layer.
class FrameAnimator : public ui::ImplicitAnimationObserver {
public:
explicit FrameAnimator(aura::Window* window) : window_(window) {
// Set up an animation which will be stopped before the old layer animation.
SetOpacityAnimation(window_->layer());
}
FrameAnimator(const FrameAnimator&) = delete;
FrameAnimator& operator=(const FrameAnimator&) = delete;
~FrameAnimator() override = default;
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
// Once the initial animation is stopped by the old layer, start a new
// opacity animation and recreate the window layer at the same time. The
// opacity animation will be stopped when the layer set opacity and the
// layer is destroyed.
if (!animation_started_)
StartAnimation();
else
layer_owner_.reset();
}
private:
// Set an opacity animation which should last longer than the cross fade
// animation.
void SetOpacityAnimation(ui::Layer* layer) {
layer->SetOpacity(1.f);
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.AddObserver(this);
settings.SetTransitionDuration(base::Milliseconds(1000));
layer->SetOpacity(0.f);
}
// Recreate the window layer and start a new opacity animation.
void StartAnimation() {
layer_owner_ =
std::make_unique<ui::LayerTreeOwner>(window_->RecreateLayer());
SetOpacityAnimation(layer_owner_->root());
animation_started_ = true;
}
raw_ptr<aura::Window> window_;
std::unique_ptr<ui::LayerTreeOwner> layer_owner_;
bool animation_started_ = false;
};
TEST_F(WindowAnimationsTest, HideShowBrightnessGrayscaleAnimation) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
window->Show();
EXPECT_TRUE(window->layer()->visible());
// Hiding.
wm::SetWindowVisibilityAnimationType(
window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE);
AnimateOnChildWindowVisibilityChanged(window.get(), false);
EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
EXPECT_FALSE(window->layer()->GetTargetVisibility());
EXPECT_FALSE(window->layer()->visible());
// Showing.
wm::SetWindowVisibilityAnimationType(
window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_BRIGHTNESS_GRAYSCALE);
AnimateOnChildWindowVisibilityChanged(window.get(), true);
EXPECT_EQ(0.0f, window->layer()->GetTargetBrightness());
EXPECT_EQ(0.0f, window->layer()->GetTargetGrayscale());
EXPECT_TRUE(window->layer()->visible());
// Stays shown.
window->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
base::Seconds(5));
EXPECT_EQ(0.0f, window->layer()->GetTargetBrightness());
EXPECT_EQ(0.0f, window->layer()->GetTargetGrayscale());
EXPECT_TRUE(window->layer()->visible());
}
TEST_F(WindowAnimationsTest, LayerTargetVisibility) {
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
// 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, CrossFadeToBounds) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
std::unique_ptr<Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(5, 10, 320, 240));
window->Show();
Layer* old_layer = window->layer();
EXPECT_EQ(1.0f, old_layer->GetTargetOpacity());
// Cross fade to a larger size, as in a maximize animation.
WindowState::Get(window.get())
->SetBoundsDirectCrossFade(gfx::Rect(0, 0, 640, 480));
// Window's layer has been replaced.
EXPECT_NE(old_layer, window->layer());
// Original layer stays opaque and stretches to new size.
EXPECT_EQ(1.0f, old_layer->GetTargetOpacity());
EXPECT_EQ("5,10 320x240", old_layer->bounds().ToString());
gfx::Transform grow_transform;
grow_transform.Translate(-5.f, -10.f);
grow_transform.Scale(640.f / 320.f, 480.f / 240.f);
EXPECT_EQ(grow_transform, old_layer->GetTargetTransform());
// New layer animates in to the identity transform.
EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
EXPECT_EQ(gfx::Transform(), window->layer()->GetTargetTransform());
// Run the animations to completion.
old_layer->GetAnimator()->Step(base::TimeTicks::Now() + base::Seconds(1));
window->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
base::Seconds(1));
// Cross fade to a smaller size, as in a restore animation.
old_layer = window->layer();
WindowState::Get(window.get())
->SetBoundsDirectCrossFade(gfx::Rect(5, 10, 320, 240));
// Again, window layer has been replaced.
EXPECT_NE(old_layer, window->layer());
// Original layer fades out and stretches down to new size.
EXPECT_EQ(0.0f, old_layer->GetTargetOpacity());
EXPECT_EQ("0,0 640x480", old_layer->bounds().ToString());
gfx::Transform shrink_transform;
shrink_transform.Translate(5.f, 10.f);
shrink_transform.Scale(320.f / 640.f, 240.f / 480.f);
EXPECT_EQ(shrink_transform, old_layer->GetTargetTransform());
// New layer animates in to the identity transform.
EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
EXPECT_EQ(gfx::Transform(), window->layer()->GetTargetTransform());
old_layer->GetAnimator()->Step(base::TimeTicks::Now() + base::Seconds(1));
window->layer()->GetAnimator()->Step(base::TimeTicks::Now() +
base::Seconds(1));
}
// Tests that when crossfading from a window which has a transform, the cross
// fading animation should be ignored and the window should set to its desired
// bounds directly.
TEST_F(WindowAnimationsTest, CrossFadeToBoundsFromTransform) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
std::unique_ptr<Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(10, 10, 320, 240));
gfx::Transform half_size;
half_size.Translate(10, 10);
half_size.Scale(0.5f, 0.5f);
window->SetTransform(half_size);
window->Show();
Layer* old_layer = window->layer();
EXPECT_EQ(1.0f, old_layer->GetTargetOpacity());
// Cross fade to a larger size, as in a maximize animation.
WindowState::Get(window.get())
->SetBoundsDirectCrossFade(gfx::Rect(0, 0, 640, 480));
// Window's layer has not been replaced.
EXPECT_EQ(old_layer, window->layer());
// Original layer stays opaque and set to new size directly.
EXPECT_EQ(1.0f, old_layer->GetTargetOpacity());
EXPECT_EQ("0,0 640x480", old_layer->bounds().ToString());
// Window still has its old transform before crossfading animation.
EXPECT_EQ(half_size, old_layer->transform());
}
// Tests that if we recreate the window layers during a cross fade animation,
// there is no crash.
// Regression test for https://crbug.com/1088169.
TEST_F(WindowAnimationsTest, CrossFadeThenRecreate) {
auto window = CreateTestWindow(gfx::Rect(100, 100));
// Use a bit more time than NON_ZERO_DURATION as its possible with non zero we
// finish the animation instantly.
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
WindowState* window_state = WindowState::Get(window.get());
window_state->Maximize();
ASSERT_TRUE(window->layer()->GetAnimator()->is_animating());
// Recreate the layers and then delete |window|. There should be no crash when
// stopping the old layers animation.
std::unique_ptr<ui::LayerTreeOwner> tree = wm::RecreateLayers(window.get());
window.reset();
tree->root()->GetAnimator()->StopAnimating();
}
namespace {
// Defines an observer that would recreate the window's layer tree when the
// opacity is set for the first time on it since the start of the observation.
class WindowOpacityObserver : public aura::WindowObserver {
public:
explicit WindowOpacityObserver(aura::Window* window) {
observation_.Observe(window);
}
WindowOpacityObserver(const WindowOpacityObserver&) = delete;
WindowOpacityObserver& operator=(const WindowOpacityObserver&) = delete;
~WindowOpacityObserver() override = default;
// aura::WindowObserver:
void OnWindowOpacitySet(aura::Window* window,
ui::PropertyChangeReason reason) override {
// In a cross-fade animation for maximizing, the window's opacity is set to
// 0 first, at which point we recreate the layers, and then it's set to
// animate to 1, at which point we destroy the old layer tree to simulate
// the crash in http://b/333095196.
if (owner_) {
owner_.reset();
} else {
owner_ = wm::RecreateLayers(window);
}
}
private:
base::ScopedObservation<aura::Window, aura::WindowObserver> observation_{
this};
std::unique_ptr<ui::LayerTreeOwner> owner_;
};
} // namespace
// Regression test for http://b/333095196 where the window's layer tree is
// recreated while in the middle of a cross fade animation.
TEST_F(WindowAnimationsTest, RecreateLayersDuringCrossFade) {
auto window = CreateTestWindow(gfx::Rect(100, 100));
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
WindowState* window_state = WindowState::Get(window.get());
WindowOpacityObserver observer{window.get()};
window_state->Maximize();
}
// Tests that if the window layer is recreated after setting the old layer's
// animation (e.g., by `FrameHeader::FrameAnimatorView::StartAnimation`). There
// should be no crash. Regression test for https://crbug.com/1313977.
TEST_F(WindowAnimationsTest, RecreateWhenSettingCrossFade) {
auto window = CreateTestWindow(gfx::Rect(100, 100));
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
auto frame_animator = std::make_unique<FrameAnimator>(window.get());
WindowState::Get(window.get())->Maximize();
}
TEST_F(WindowAnimationsTest, LockAnimationDuration) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
std::unique_ptr<Window> window(CreateTestWindowInShellWithId(0));
Layer* layer = window->layer();
window->SetBounds(gfx::Rect(5, 10, 320, 240));
window->Show();
// Test that it is possible to override transition duration when it is not
// locked.
{
ui::ScopedLayerAnimationSettings settings1(layer->GetAnimator());
settings1.SetTransitionDuration(base::Milliseconds(1000));
{
ui::ScopedLayerAnimationSettings settings2(layer->GetAnimator());
// Duration is not locked so it gets overridden.
settings2.SetTransitionDuration(base::Milliseconds(50));
WindowState::Get(window.get())->Minimize();
EXPECT_TRUE(layer->GetAnimator()->is_animating());
// Expect duration from the inner scope
EXPECT_EQ(50,
layer->GetAnimator()->GetTransitionDuration().InMilliseconds());
}
window->Show();
layer->GetAnimator()->StopAnimating();
}
// Test that it is possible to lock transition duration
{
// Update layer as minimizing will replace the window's layer.
layer = window->layer();
ui::ScopedLayerAnimationSettings settings1(layer->GetAnimator());
settings1.SetTransitionDuration(base::Milliseconds(1000));
// Duration is locked in outer scope.
settings1.LockTransitionDuration();
{
ui::ScopedLayerAnimationSettings settings2(layer->GetAnimator());
// Transition duration setting is ignored.
settings2.SetTransitionDuration(base::Milliseconds(50));
WindowState::Get(window.get())->Minimize();
EXPECT_TRUE(layer->GetAnimator()->is_animating());
// Expect duration from the outer scope
EXPECT_EQ(1000,
layer->GetAnimator()->GetTransitionDuration().InMilliseconds());
}
window->Show();
layer->GetAnimator()->StopAnimating();
}
// Test that duration respects default.
{
layer = window->layer();
// Query default duration.
MinimizeAnimationObserver observer(layer->GetAnimator());
WindowState::Get(window.get())->Minimize();
EXPECT_TRUE(layer->GetAnimator()->is_animating());
base::TimeDelta default_duration(observer.duration());
window->Show();
layer->GetAnimator()->StopAnimating();
layer = window->layer();
ui::ScopedLayerAnimationSettings settings(layer->GetAnimator());
settings.LockTransitionDuration();
// Setting transition duration is ignored since duration is locked
settings.SetTransitionDuration(base::Milliseconds(1000));
WindowState::Get(window.get())->Minimize();
EXPECT_TRUE(layer->GetAnimator()->is_animating());
// Expect default duration (200ms for stock ash minimizing animation).
EXPECT_EQ(default_duration.InMilliseconds(),
layer->GetAnimator()->GetTransitionDuration().InMilliseconds());
window->Show();
layer->GetAnimator()->StopAnimating();
}
}
// Test that a slide out animation slides the window off the screen while
// modifying the opacity.
TEST_F(WindowAnimationsTest, SlideOutAnimation) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(0, 0, 100, 100));
window->Show();
EXPECT_TRUE(window->layer()->visible());
::wm::SetWindowVisibilityAnimationType(
window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT);
AnimateOnChildWindowVisibilityChanged(window.get(), false);
EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
EXPECT_FALSE(window->layer()->GetTargetVisibility());
EXPECT_FALSE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(-150, 0, 100, 100), window->layer()->GetTargetBounds());
}
// Test that a fade in slide out animation fades in.
TEST_F(WindowAnimationsTest, FadeInAnimation) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(0, 0, 100, 100));
window->Hide();
EXPECT_FALSE(window->layer()->visible());
::wm::SetWindowVisibilityAnimationType(
window.get(), WINDOW_VISIBILITY_ANIMATION_TYPE_FADE_IN_SLIDE_OUT);
AnimateOnChildWindowVisibilityChanged(window.get(), true);
EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
EXPECT_TRUE(window->layer()->GetTargetVisibility());
EXPECT_TRUE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(0, 0, 100, 100), window->layer()->GetTargetBounds());
}
TEST_F(WindowAnimationsTest, SlideOutAnimationPlaysTwiceForPipWindow) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(8, 8, 100, 100));
WindowState* window_state = WindowState::Get(window.get());
const WMEvent enter_pip(WM_EVENT_PIP);
window_state->OnWMEvent(&enter_pip);
EXPECT_TRUE(window_state->IsPip());
window->Show();
EXPECT_TRUE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(8, 8, 100, 100), window->layer()->GetTargetBounds());
window->Hide();
EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
EXPECT_FALSE(window->layer()->GetTargetVisibility());
EXPECT_FALSE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(-142, 8, 100, 100), window->layer()->GetTargetBounds());
// Reset the position and try again.
window->Show();
window->SetBounds(gfx::Rect(8, 8, 100, 100));
EXPECT_TRUE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(8, 8, 100, 100), window->layer()->GetTargetBounds());
window->Hide();
EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
EXPECT_FALSE(window->layer()->GetTargetVisibility());
EXPECT_FALSE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(-142, 8, 100, 100), window->layer()->GetTargetBounds());
}
TEST_F(WindowAnimationsTest, ResetAnimationAfterDismissingArcPip) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::ZERO_DURATION);
std::unique_ptr<aura::Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(8, 8, 100, 100));
WindowState* window_state = WindowState::Get(window.get());
const WMEvent enter_pip(WM_EVENT_PIP);
window_state->OnWMEvent(&enter_pip);
EXPECT_TRUE(window_state->IsPip());
window->Show();
EXPECT_TRUE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(8, 8, 100, 100), window->layer()->GetTargetBounds());
// Ensure the window is slided out.
WindowState::Get(window.get())->Minimize();
EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
EXPECT_FALSE(window->layer()->GetTargetVisibility());
EXPECT_FALSE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(-142, 8, 100, 100), window->layer()->GetTargetBounds());
WindowState::Get(window.get())->Maximize();
EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
EXPECT_TRUE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(0, 0, 800, 600 - ShelfConfig::Get()->shelf_size()),
window->layer()->GetTargetBounds());
// Ensure the window is not slided out.
window->Hide();
EXPECT_EQ(0.0f, window->layer()->GetTargetOpacity());
EXPECT_FALSE(window->layer()->GetTargetVisibility());
EXPECT_FALSE(window->layer()->visible());
EXPECT_EQ(gfx::Rect(0, 0, 800, 600 - ShelfConfig::Get()->shelf_size()),
window->layer()->GetTargetBounds());
}
// Tests a version of the cross fade animation which animates the transform and
// opacity of the new layer, but only the opacity of the old layer. The old
// layer transform is updated manually when the animation ticks so that it
// has the same visible bounds as the new layer.
// Flaky on Chrome OS. https://crbug.com/1113901
TEST_F(WindowAnimationsTest, DISABLED_CrossFadeAnimateNewLayerOnly) {
ui::ScopedAnimationDurationScaleMode test_duration_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
std::unique_ptr<Window> window(CreateTestWindowInShellWithId(0));
window->SetBounds(gfx::Rect(10, 10, 200, 200));
window->Show();
window->layer()->GetAnimator()->StopAnimating();
Layer* old_layer = window->layer();
EXPECT_EQ(1.f, old_layer->GetTargetOpacity());
const gfx::Rect target_bounds(40, 40, 400, 400);
CrossFadeAnimationAnimateNewLayerOnly(
window.get(), target_bounds, base::Milliseconds(200), gfx::Tween::LINEAR,
"test-histogram-name");
// Window's layer has been replaced.
EXPECT_NE(old_layer, window->layer());
// Original layer fades away. Transform is updated as the animation steps.
EXPECT_EQ(0.f, old_layer->GetTargetOpacity());
EXPECT_EQ(gfx::Rect(10, 10, 200, 200), old_layer->bounds());
EXPECT_EQ(gfx::Transform(), old_layer->GetTargetTransform());
// New layer animates in to the identity transform.
EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
EXPECT_EQ(gfx::Transform(), window->layer()->GetTargetTransform());
// Start the animations, then set the bounds of the new window during the
// animation.
WaitForMilliseconds(10);
// Set the bounds halfway through the animation. The bounds of the old layer
// remain the same, but the transform has updated to match the bounds of the
// new layer.
window->SetBounds(gfx::Rect(80, 80, 200, 200));
WaitForMilliseconds(100);
EXPECT_EQ(gfx::Rect(10, 10, 200, 200), old_layer->bounds());
EXPECT_NE(gfx::Transform(), old_layer->GetTargetTransform());
// New layer targets remain the same.
EXPECT_EQ(1.0f, window->layer()->GetTargetOpacity());
EXPECT_EQ(gfx::Transform(), window->layer()->GetTargetTransform());
WaitForMilliseconds(300);
EXPECT_FALSE(window->layer()->GetAnimator()->is_animating());
}
// Tests that widgets that are created minimized have the correct restore
// bounds.
TEST_F(WindowAnimationsTest, NoMinimizedShowAnimation) {
ui::ScopedAnimationDurationScaleMode animation_scale_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
views::UniqueWidgetPtr widget = std::make_unique<views::Widget>();
views::Widget::InitParams params(
views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
views::Widget::InitParams::TYPE_WINDOW);
params.show_state = ui::SHOW_STATE_MINIMIZED;
params.bounds = gfx::Rect(600, 400);
widget->Init(std::move(params));
auto* layer = widget->GetNativeWindow()->layer();
widget->Show();
// The window should have the same layer because layer animation will recreate
// layer.
EXPECT_EQ(layer, widget->GetNativeWindow()->layer());
}
} // namespace ash