1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/exo/ui_lock_controller.h"
6
7 #include "ash/public/cpp/app_types.h"
8 #include "ash/shell.h"
9 #include "ash/wm/window_state.h"
10 #include "components/exo/buffer.h"
11 #include "components/exo/display.h"
12 #include "components/exo/shell_surface.h"
13 #include "components/exo/surface.h"
14 #include "components/exo/test/exo_test_base.h"
15 #include "components/exo/test/exo_test_helper.h"
16 #include "testing/gtest/include/gtest/gtest.h"
17 #include "ui/aura/client/aura_constants.h"
18 #include "ui/wm/core/window_util.h"
19
20 namespace exo {
21 namespace {
22
23 struct SurfaceTriplet {
24 std::unique_ptr<Surface> surface;
25 std::unique_ptr<ShellSurface> shell_surface;
26 std::unique_ptr<Buffer> buffer;
27
GetTopLevelWindowexo::__anon71ea4dd10111::SurfaceTriplet28 aura::Window* GetTopLevelWindow() {
29 auto* top_level_widget = views::Widget::GetTopLevelWidgetForNativeView(
30 shell_surface->host_window());
31 assert(top_level_widget);
32 return top_level_widget->GetNativeWindow();
33 }
34
GetTopLevelWindowStateexo::__anon71ea4dd10111::SurfaceTriplet35 ash::WindowState* GetTopLevelWindowState() {
36 return ash::WindowState::Get(GetTopLevelWindow());
37 }
38 };
39
40 class UILockControllerTest : public test::ExoTestBase {
41 public:
UILockControllerTest()42 UILockControllerTest()
43 : test::ExoTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
44 ~UILockControllerTest() override = default;
45
46 UILockControllerTest(const UILockControllerTest&) = delete;
47 UILockControllerTest& operator=(const UILockControllerTest&) = delete;
48
49 protected:
50 // test::ExoTestBase:
SetUp()51 void SetUp() override {
52 test::ExoTestBase::SetUp();
53 seat_ = std::make_unique<Seat>();
54 }
55
TearDown()56 void TearDown() override {
57 seat_.reset();
58 test::ExoTestBase::TearDown();
59 }
60
BuildSurface(int w,int h)61 SurfaceTriplet BuildSurface(int w, int h) {
62 auto surface = std::make_unique<Surface>();
63 auto shell_surface = std::make_unique<ShellSurface>(
64 surface.get(), gfx::Point{0, 0},
65 /*activatable=*/true,
66 /*can_minimize=*/true, ash::desks_util::GetActiveDeskContainerId());
67 auto buffer = std::make_unique<Buffer>(
68 exo_test_helper()->CreateGpuMemoryBuffer({w, h}));
69 surface->Attach(buffer.get());
70
71 return {std::move(surface), std::move(shell_surface), std::move(buffer)};
72 }
73
74 std::unique_ptr<Seat> seat_;
75 };
76
SetAppType(SurfaceTriplet & surface,ash::AppType appType)77 void SetAppType(SurfaceTriplet& surface, ash::AppType appType) {
78 surface.GetTopLevelWindow()->SetProperty(aura::client::kAppType,
79 static_cast<int>(appType));
80 }
81
TEST_F(UILockControllerTest,HoldingEscapeExitsFullscreen)82 TEST_F(UILockControllerTest, HoldingEscapeExitsFullscreen) {
83 SurfaceTriplet test_surface = BuildSurface(1024, 768);
84 test_surface.shell_surface->SetUseImmersiveForFullscreen(false);
85 test_surface.shell_surface->SetFullscreen(true);
86 test_surface.surface->Commit();
87 SetAppType(test_surface, ash::AppType::CROSTINI_APP);
88 auto* window_state = test_surface.GetTopLevelWindowState();
89 EXPECT_TRUE(window_state->IsFullscreen());
90
91 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
92 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
93 EXPECT_TRUE(window_state->IsFullscreen()); // no change yet
94
95 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
96 EXPECT_FALSE(window_state->IsFullscreen());
97 }
98
TEST_F(UILockControllerTest,HoldingCtrlEscapeDoesNotExitFullscreen)99 TEST_F(UILockControllerTest, HoldingCtrlEscapeDoesNotExitFullscreen) {
100 SurfaceTriplet test_surface = BuildSurface(1024, 768);
101 test_surface.shell_surface->SetUseImmersiveForFullscreen(false);
102 test_surface.shell_surface->SetFullscreen(true);
103 test_surface.surface->Commit();
104 SetAppType(test_surface, ash::AppType::CROSTINI_APP);
105 auto* window_state = test_surface.GetTopLevelWindowState();
106 EXPECT_TRUE(window_state->IsFullscreen());
107
108 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_CONTROL_DOWN);
109 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
110 EXPECT_TRUE(window_state->IsFullscreen());
111 }
112
TEST_F(UILockControllerTest,HoldingEscapeOnlyAffectsCrostiniApps)113 TEST_F(UILockControllerTest, HoldingEscapeOnlyAffectsCrostiniApps) {
114 SurfaceTriplet test_surface = BuildSurface(1024, 768);
115 test_surface.shell_surface->SetUseImmersiveForFullscreen(false);
116 test_surface.shell_surface->SetFullscreen(true);
117 test_surface.surface->Commit();
118 SetAppType(test_surface, ash::AppType::ARC_APP);
119 auto* window_state = test_surface.GetTopLevelWindowState();
120 EXPECT_TRUE(window_state->IsFullscreen());
121
122 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
123 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
124 EXPECT_TRUE(window_state->IsFullscreen());
125 }
126
TEST_F(UILockControllerTest,HoldingEscapeOnlyExitsFocusedFullscreen)127 TEST_F(UILockControllerTest, HoldingEscapeOnlyExitsFocusedFullscreen) {
128 SurfaceTriplet test_surface1 = BuildSurface(1024, 768);
129 test_surface1.shell_surface->SetUseImmersiveForFullscreen(false);
130 test_surface1.shell_surface->SetFullscreen(true);
131 test_surface1.surface->Commit();
132 SetAppType(test_surface1, ash::AppType::CROSTINI_APP);
133
134 SurfaceTriplet test_surface2 = BuildSurface(1024, 768);
135 test_surface2.shell_surface->SetUseImmersiveForFullscreen(false);
136 test_surface2.shell_surface->SetFullscreen(true);
137 test_surface2.surface->Commit();
138 SetAppType(test_surface2, ash::AppType::CROSTINI_APP);
139
140 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
141 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
142
143 EXPECT_TRUE(test_surface1.GetTopLevelWindowState()->IsFullscreen());
144 EXPECT_FALSE(test_surface2.GetTopLevelWindowState()->IsFullscreen());
145 }
146
TEST_F(UILockControllerTest,DestroyingWindowCancels)147 TEST_F(UILockControllerTest, DestroyingWindowCancels) {
148 std::unique_ptr<SurfaceTriplet> test_surface =
149 std::make_unique<SurfaceTriplet>(BuildSurface(1024, 768));
150 test_surface->shell_surface->SetUseImmersiveForFullscreen(false);
151 test_surface->shell_surface->SetFullscreen(true);
152 test_surface->surface->Commit();
153 SetAppType(*test_surface, ash::AppType::CROSTINI_APP);
154 auto* window_state = test_surface->GetTopLevelWindowState();
155 EXPECT_TRUE(window_state->IsFullscreen());
156
157 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
158 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
159
160 test_surface.reset(); // Destroying the Surface destroys the Window
161
162 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(3));
163
164 // The implicit assertion is that the code doesn't crash.
165 }
166
TEST_F(UILockControllerTest,FocusChangeCancels)167 TEST_F(UILockControllerTest, FocusChangeCancels) {
168 // Arrange: two windows, one is fullscreen and focused
169 SurfaceTriplet other_surface = BuildSurface(1024, 768);
170 other_surface.surface->Commit();
171 SetAppType(other_surface, ash::AppType::CROSTINI_APP);
172
173 SurfaceTriplet fullscreen_surface = BuildSurface(1024, 768);
174 fullscreen_surface.shell_surface->SetUseImmersiveForFullscreen(false);
175 fullscreen_surface.shell_surface->SetFullscreen(true);
176 fullscreen_surface.surface->Commit();
177 SetAppType(fullscreen_surface, ash::AppType::CROSTINI_APP);
178
179 EXPECT_EQ(fullscreen_surface.surface.get(), seat_->GetFocusedSurface());
180 EXPECT_FALSE(fullscreen_surface.GetTopLevelWindowState()->IsMinimized());
181
182 // Act: Press escape, then toggle focus back and forth
183 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
184 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
185
186 wm::ActivateWindow(other_surface.surface->window());
187 wm::ActivateWindow(fullscreen_surface.surface->window());
188
189 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
190
191 // Assert: Fullscreen window was not minimized, despite regaining focus.
192 EXPECT_FALSE(fullscreen_surface.GetTopLevelWindowState()->IsMinimized());
193 EXPECT_EQ(fullscreen_surface.surface.get(), seat_->GetFocusedSurface());
194 }
195
TEST_F(UILockControllerTest,EscapeDoesNotExitImmersiveFullscreen)196 TEST_F(UILockControllerTest, EscapeDoesNotExitImmersiveFullscreen) {
197 SurfaceTriplet test_surface = BuildSurface(1024, 768);
198 test_surface.shell_surface->SetFullscreen(true);
199 test_surface.surface->Commit();
200 SetAppType(test_surface, ash::AppType::CROSTINI_APP);
201 auto* window_state = test_surface.GetTopLevelWindowState();
202
203 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
204 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
205
206 EXPECT_TRUE(window_state->IsFullscreen());
207 }
208
TEST_F(UILockControllerTest,ShortHoldEscapeDoesNotExitFullscreen)209 TEST_F(UILockControllerTest, ShortHoldEscapeDoesNotExitFullscreen) {
210 SurfaceTriplet test_surface = BuildSurface(1024, 768);
211 test_surface.shell_surface->SetUseImmersiveForFullscreen(false);
212 test_surface.shell_surface->SetFullscreen(true);
213 test_surface.surface->Commit();
214 SetAppType(test_surface, ash::AppType::CROSTINI_APP);
215 auto* window_state = test_surface.GetTopLevelWindowState();
216
217 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
218 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
219 GetEventGenerator()->ReleaseKey(ui::VKEY_ESCAPE, ui::EF_NONE);
220 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
221
222 EXPECT_TRUE(window_state->IsFullscreen());
223 }
224
TEST_F(UILockControllerTest,HoldingEscapeDoesNotMinimizeIfWindowed)225 TEST_F(UILockControllerTest, HoldingEscapeDoesNotMinimizeIfWindowed) {
226 SurfaceTriplet test_surface = BuildSurface(1024, 768);
227 test_surface.shell_surface->SetUseImmersiveForFullscreen(false);
228 test_surface.surface->Commit();
229 SetAppType(test_surface, ash::AppType::CROSTINI_APP);
230 auto* window_state = test_surface.GetTopLevelWindowState();
231
232 GetEventGenerator()->PressKey(ui::VKEY_ESCAPE, ui::EF_NONE);
233 task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(2));
234
235 EXPECT_FALSE(window_state->IsMinimized());
236 }
237
238 } // namespace
239 } // namespace exo
240