1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
ash / wm / overview / cleanup_animation_observer_unittest.cc [blame]
// Copyright 2016 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/overview/cleanup_animation_observer.h"
#include <utility>
#include <vector>
#include "ash/test/ash_test_base.h"
#include "ash/wm/overview/overview_delegate.h"
#include "base/containers/unique_ptr_adapters.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace ash {
namespace {
class TestOverviewDelegate : public OverviewDelegate {
public:
TestOverviewDelegate() = default;
TestOverviewDelegate(const TestOverviewDelegate&) = delete;
TestOverviewDelegate& operator=(const TestOverviewDelegate&) = delete;
~TestOverviewDelegate() override {
// Destroy widgets that may be still animating if shell shuts down soon
// after exiting overview mode.
for (std::unique_ptr<DelayedAnimationObserver>& observer : observers_)
observer->Shutdown();
}
// OverviewDelegate:
void AddExitAnimationObserver(
std::unique_ptr<DelayedAnimationObserver> animation_observer) override {
animation_observer->SetOwner(this);
observers_.push_back(std::move(animation_observer));
}
void RemoveAndDestroyExitAnimationObserver(
DelayedAnimationObserver* animation_observer) override {
std::erase_if(observers_, base::MatchesUniquePtr(animation_observer));
}
void AddEnterAnimationObserver(
std::unique_ptr<DelayedAnimationObserver> animation_observer) override {}
void RemoveAndDestroyEnterAnimationObserver(
DelayedAnimationObserver* animation_observer) override {}
private:
std::vector<std::unique_ptr<DelayedAnimationObserver>> observers_;
};
class CleanupAnimationObserverTest : public AshTestBase,
public views::WidgetObserver {
public:
CleanupAnimationObserverTest() = default;
CleanupAnimationObserverTest(const CleanupAnimationObserverTest&) = delete;
CleanupAnimationObserverTest& operator=(const CleanupAnimationObserverTest&) =
delete;
~CleanupAnimationObserverTest() override {
if (widget_)
widget_->RemoveObserver(this);
}
// Creates a Widget containing a Window with the given |bounds|. This should
// be used when the test requires a Widget. For example any test that will
// cause a window to be closed via
// views::Widget::GetWidgetForNativeView(window)->Close().
std::unique_ptr<views::Widget> CreateWindowWidget(const gfx::Rect& bounds) {
auto widget = std::make_unique<views::Widget>();
views::Widget::InitParams params(
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
views::Widget::InitParams::TYPE_WINDOW);
params.bounds = bounds;
params.context = GetContext();
widget->Init(std::move(params));
widget->Show();
widget->AddObserver(this);
widget_ = widget.get();
return widget;
}
protected:
bool widget_destroyed() { return !widget_; }
private:
void OnWidgetDestroyed(views::Widget* widget) override {
if (widget_ == widget)
widget_ = nullptr;
}
raw_ptr<views::Widget> widget_ = nullptr;
};
} // namespace
// Tests that basic create-destroy sequence does not crash.
TEST_F(CleanupAnimationObserverTest, CreateDestroy) {
TestOverviewDelegate delegate;
std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
auto observer = std::make_unique<CleanupAnimationObserver>(std::move(widget));
delegate.AddExitAnimationObserver(std::move(observer));
}
// Tests that completing animation deletes the animation observer and the
// test widget and that deleting the OverviewDelegate instance which
// owns the observer does not crash.
TEST_F(CleanupAnimationObserverTest, CreateAnimateComplete) {
TestOverviewDelegate delegate;
std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
aura::Window* widget_window = widget->GetNativeWindow();
{
ui::ScopedLayerAnimationSettings animation_settings(
widget_window->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::Milliseconds(1000));
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
auto observer =
std::make_unique<CleanupAnimationObserver>(std::move(widget));
animation_settings.AddObserver(observer.get());
delegate.AddExitAnimationObserver(std::move(observer));
widget_window->SetBounds(gfx::Rect(50, 50, 60, 60));
}
// The widget should be destroyed when |animation_settings| gets out of scope
// which in absence of NON_ZERO_DURATION animation duration mode completes
// the animation and calls OnImplicitAnimationsCompleted() on the cleanup
// observer and auto-deletes the owned widget.
EXPECT_TRUE(widget_destroyed());
// TestOverviewDelegate going out of scope should not crash.
}
// Tests that starting an animation and exiting doesn't crash. If not for
// TestOverviewDelegate calling Shutdown() on a CleanupAnimationObserver
// instance in destructor, this test would have crashed.
TEST_F(CleanupAnimationObserverTest, CreateAnimateShutdown) {
TestOverviewDelegate delegate;
std::unique_ptr<views::Widget> widget = CreateWindowWidget(gfx::Rect(40, 40));
aura::Window* widget_window = widget->GetNativeWindow();
{
// Normal animations for tests have ZERO_DURATION, make sure we are actually
// animating the movement.
ui::ScopedAnimationDurationScaleMode animation_scale_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
ui::ScopedLayerAnimationSettings animation_settings(
widget_window->layer()->GetAnimator());
animation_settings.SetTransitionDuration(base::Milliseconds(1000));
animation_settings.SetPreemptionStrategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
auto observer =
std::make_unique<CleanupAnimationObserver>(std::move(widget));
animation_settings.AddObserver(observer.get());
delegate.AddExitAnimationObserver(std::move(observer));
widget_window->SetBounds(gfx::Rect(50, 50, 60, 60));
}
// The widget still exists.
EXPECT_FALSE(widget_destroyed());
// The test widget is auto-deleted when |delegate| that owns it goes out of
// scope. The animation is still active when this happens which should not
// crash.
}
} // namespace ash