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