1 // Copyright 2017 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 "ash/display/display_move_window_util.h"
6
7 #include "ash/accelerators/accelerator_controller_impl.h"
8 #include "ash/accelerators/accelerator_table.h"
9 #include "ash/accessibility/test_accessibility_controller_client.h"
10 #include "ash/public/cpp/shelf_config.h"
11 #include "ash/root_window_controller.h"
12 #include "ash/screen_util.h"
13 #include "ash/shell.h"
14 #include "ash/test/ash_test_base.h"
15 #include "ash/wm/mru_window_tracker.h"
16 #include "ash/wm/window_state.h"
17 #include "ash/wm/window_util.h"
18 #include "ash/wm/wm_event.h"
19 #include "base/command_line.h"
20 #include "base/macros.h"
21 #include "ui/aura/test/test_windows.h"
22 #include "ui/display/display.h"
23 #include "ui/display/display_layout.h"
24 #include "ui/display/display_layout_builder.h"
25 #include "ui/display/manager/display_layout_store.h"
26 #include "ui/display/manager/display_manager.h"
27 #include "ui/display/screen.h"
28 #include "ui/display/test/display_manager_test_api.h"
29 #include "ui/views/widget/widget.h"
30 #include "ui/wm/core/window_util.h"
31
32 namespace ash {
33
34 namespace display_move_window_util {
35
36 namespace {
37
38 // Get the default left snapped window bounds which has snapped width ratio 0.5.
GetDefaultLeftSnappedBoundsInDisplay(const display::Display & display)39 gfx::Rect GetDefaultLeftSnappedBoundsInDisplay(
40 const display::Display& display) {
41 auto work_area = display.work_area();
42 work_area.set_width(work_area.width() / 2);
43 return work_area;
44 }
45
CreateTestWidgetWithParent(views::Widget::InitParams::Type type,gfx::NativeView parent,const gfx::Rect & bounds,bool child)46 views::Widget* CreateTestWidgetWithParent(views::Widget::InitParams::Type type,
47 gfx::NativeView parent,
48 const gfx::Rect& bounds,
49 bool child) {
50 views::Widget::InitParams params(type);
51 params.delegate = nullptr;
52 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
53 params.parent = parent;
54 params.bounds = bounds;
55 params.child = child;
56 views::Widget* widget = new views::Widget;
57 widget->Init(std::move(params));
58 widget->Show();
59 return widget;
60 }
61
PerformMoveWindowAccel()62 void PerformMoveWindowAccel() {
63 Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
64 MOVE_ACTIVE_WINDOW_BETWEEN_DISPLAYS, {});
65 }
66
67 } // namespace
68
69 using DisplayMoveWindowUtilTest = AshTestBase;
70
TEST_F(DisplayMoveWindowUtilTest,SingleDisplay)71 TEST_F(DisplayMoveWindowUtilTest, SingleDisplay) {
72 aura::Window* window =
73 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
74 wm::ActivateWindow(window);
75 EXPECT_FALSE(CanHandleMoveActiveWindowBetweenDisplays());
76 }
77
78 // Tests that window bounds are not changing after moving to another display. It
79 // is added as a child window to another root window with the same origin.
TEST_F(DisplayMoveWindowUtilTest,WindowBounds)80 TEST_F(DisplayMoveWindowUtilTest, WindowBounds) {
81 // Layout: [p][1]
82 UpdateDisplay("400x300,400x300");
83 aura::Window* window =
84 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
85 wm::ActivateWindow(window);
86 PerformMoveWindowAccel();
87 EXPECT_EQ(gfx::Rect(410, 20, 200, 100), window->GetBoundsInScreen());
88 }
89
90 // Tests window state (maximized/fullscreen/snapped) and its bounds.
TEST_F(DisplayMoveWindowUtilTest,WindowState)91 TEST_F(DisplayMoveWindowUtilTest, WindowState) {
92 // Layout: [p][ 1 ]
93 UpdateDisplay("400x300,800x300");
94
95 aura::Window* window =
96 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
97 wm::ActivateWindow(window);
98 display::Screen* screen = display::Screen::GetScreen();
99 ASSERT_EQ(display_manager()->GetDisplayAt(0).id(),
100 screen->GetDisplayNearestWindow(window).id());
101 WindowState* window_state = WindowState::Get(window);
102 // Set window to maximized state.
103 window_state->Maximize();
104 EXPECT_TRUE(window_state->IsMaximized());
105 EXPECT_EQ(screen_util::GetMaximizedWindowBoundsInParent(window),
106 window->bounds());
107 PerformMoveWindowAccel();
108 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
109 screen->GetDisplayNearestWindow(window).id());
110 // Check that window state is maximized and has updated maximized bounds.
111 EXPECT_TRUE(window_state->IsMaximized());
112 EXPECT_EQ(screen_util::GetMaximizedWindowBoundsInParent(window),
113 window->bounds());
114
115 // Set window to fullscreen state.
116 PerformMoveWindowAccel();
117 const WMEvent fullscreen(WM_EVENT_TOGGLE_FULLSCREEN);
118 window_state->OnWMEvent(&fullscreen);
119 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
120 screen->GetDisplayNearestWindow(window).id());
121 EXPECT_TRUE(window_state->IsFullscreen());
122 EXPECT_EQ(display_manager()->GetDisplayAt(0).bounds(),
123 window->GetBoundsInScreen());
124 PerformMoveWindowAccel();
125 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
126 screen->GetDisplayNearestWindow(window).id());
127 // Check that window state is fullscreen and has updated fullscreen bounds.
128 EXPECT_TRUE(window_state->IsFullscreen());
129 EXPECT_EQ(display_manager()->GetDisplayAt(1).bounds(),
130 window->GetBoundsInScreen());
131
132 // Set window to left snapped state.
133 PerformMoveWindowAccel();
134 const WMEvent snap_left(WM_EVENT_SNAP_LEFT);
135 window_state->OnWMEvent(&snap_left);
136 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
137 screen->GetDisplayNearestWindow(window).id());
138 EXPECT_TRUE(window_state->IsSnapped());
139 EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay(
140 screen->GetDisplayNearestWindow(window)),
141 window->GetBoundsInScreen());
142 EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
143 PerformMoveWindowAccel();
144 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
145 screen->GetDisplayNearestWindow(window).id());
146 // Check that window state is snapped and has updated snapped bounds.
147 EXPECT_TRUE(window_state->IsSnapped());
148 EXPECT_EQ(GetDefaultLeftSnappedBoundsInDisplay(
149 screen->GetDisplayNearestWindow(window)),
150 window->GetBoundsInScreen());
151 EXPECT_EQ(0.5f, *window_state->snapped_width_ratio());
152 }
153
154 // Tests that movement follows cycling through sorted display id list.
TEST_F(DisplayMoveWindowUtilTest,FourDisplays)155 TEST_F(DisplayMoveWindowUtilTest, FourDisplays) {
156 display::Screen* screen = display::Screen::GetScreen();
157 int64_t primary_id = screen->GetPrimaryDisplay().id();
158 // Layout:
159 // [3][2]
160 // [1][p]
161 display::DisplayIdList list =
162 display::test::CreateDisplayIdListN(primary_id, 4);
163 display::DisplayLayoutBuilder builder(primary_id);
164 builder.AddDisplayPlacement(list[1], primary_id,
165 display::DisplayPlacement::LEFT, 0);
166 builder.AddDisplayPlacement(list[2], primary_id,
167 display::DisplayPlacement::TOP, 0);
168 builder.AddDisplayPlacement(list[3], list[2], display::DisplayPlacement::LEFT,
169 0);
170 display_manager()->layout_store()->RegisterLayoutForDisplayIdList(
171 list, builder.Build());
172 UpdateDisplay("400x300,400x300,400x300,400x300");
173 EXPECT_EQ(4U, display_manager()->GetNumDisplays());
174 EXPECT_EQ(gfx::Rect(0, 0, 400, 300),
175 display_manager()->GetDisplayAt(0).bounds());
176 EXPECT_EQ(gfx::Rect(-400, 0, 400, 300),
177 display_manager()->GetDisplayAt(1).bounds());
178 EXPECT_EQ(gfx::Rect(0, -300, 400, 300),
179 display_manager()->GetDisplayAt(2).bounds());
180 EXPECT_EQ(gfx::Rect(-400, -300, 400, 300),
181 display_manager()->GetDisplayAt(3).bounds());
182
183 aura::Window* window =
184 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
185 wm::ActivateWindow(window);
186 ASSERT_EQ(list[0], screen->GetDisplayNearestWindow(window).id());
187
188 PerformMoveWindowAccel();
189 EXPECT_EQ(list[1], screen->GetDisplayNearestWindow(window).id());
190 PerformMoveWindowAccel();
191 EXPECT_EQ(list[2], screen->GetDisplayNearestWindow(window).id());
192 PerformMoveWindowAccel();
193 EXPECT_EQ(list[3], screen->GetDisplayNearestWindow(window).id());
194 PerformMoveWindowAccel();
195 EXPECT_EQ(list[0], screen->GetDisplayNearestWindow(window).id());
196 }
197
198 // Tests that a11y alert is sent on handling move window to display.
TEST_F(DisplayMoveWindowUtilTest,A11yAlert)199 TEST_F(DisplayMoveWindowUtilTest, A11yAlert) {
200 // Layout: [p][1]
201 UpdateDisplay("400x300,400x300");
202 TestAccessibilityControllerClient client;
203
204 aura::Window* window =
205 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
206 wm::ActivateWindow(window);
207 PerformMoveWindowAccel();
208 EXPECT_EQ(AccessibilityAlert::WINDOW_MOVED_TO_ANOTHER_DISPLAY,
209 client.last_a11y_alert());
210 }
211
212 // Tests that moving window between displays is no-op if active window is not in
213 // window cycle list.
TEST_F(DisplayMoveWindowUtilTest,NoMovementIfNotInCycleWindowList)214 TEST_F(DisplayMoveWindowUtilTest, NoMovementIfNotInCycleWindowList) {
215 // Layout: [p][1]
216 UpdateDisplay("400x300,400x300");
217 // Create a window in app list container, which would be excluded in cycle
218 // window list.
219 std::unique_ptr<aura::Window> window = CreateChildWindow(
220 Shell::GetPrimaryRootWindow(), gfx::Rect(10, 20, 200, 100),
221 kShellWindowId_AppListContainer);
222 wm::ActivateWindow(window.get());
223 display::Screen* screen = display::Screen::GetScreen();
224 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
225 screen->GetDisplayNearestWindow(window.get()).id());
226
227 MruWindowTracker::WindowList cycle_window_list =
228 Shell::Get()->mru_window_tracker()->BuildWindowForCycleList(kActiveDesk);
229 EXPECT_TRUE(cycle_window_list.empty());
230 PerformMoveWindowAccel();
231 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
232 screen->GetDisplayNearestWindow(window.get()).id());
233 }
234
235 // Tests that window bounds should be kept if it is not changed by user, i.e.
236 // if it is changed by display area difference, it should restore to the
237 // original bounds when it is moved between displays and there is enough work
238 // area to show this window.
TEST_F(DisplayMoveWindowUtilTest,KeepWindowBoundsIfNotChangedByUser)239 TEST_F(DisplayMoveWindowUtilTest, KeepWindowBoundsIfNotChangedByUser) {
240 // Layout:
241 // +---+---+
242 // | p | |
243 // +---+ 1 |
244 // | |
245 // +---+
246 UpdateDisplay("400x300,400x600");
247 const int shelf_inset = 300 - ShelfConfig::Get()->shelf_size();
248 // Create and activate window on display [1].
249 aura::Window* window =
250 CreateTestWindowInShellWithBounds(gfx::Rect(410, 20, 200, 400));
251 wm::ActivateWindow(window);
252 display::Screen* screen = display::Screen::GetScreen();
253 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
254 screen->GetDisplayNearestWindow(window).id());
255 // Move window to display [p]. Its window bounds is adjusted by available work
256 // area.
257 PerformMoveWindowAccel();
258 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
259 screen->GetDisplayNearestWindow(window).id());
260 EXPECT_EQ(gfx::Rect(10, 20, 200, shelf_inset), window->GetBoundsInScreen());
261 // Move window back to display [1]. Its window bounds should be restored.
262 PerformMoveWindowAccel();
263 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
264 screen->GetDisplayNearestWindow(window).id());
265 EXPECT_EQ(gfx::Rect(410, 20, 200, 400), window->GetBoundsInScreen());
266
267 // Move window to display [p] and set that its bounds is changed by user.
268 WindowState* window_state = WindowState::Get(window);
269 PerformMoveWindowAccel();
270 window_state->set_bounds_changed_by_user(true);
271 // Move window back to display [1], but its bounds has been changed by user.
272 // Then window bounds should be kept the same as that in display [p].
273 PerformMoveWindowAccel();
274 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
275 screen->GetDisplayNearestWindow(window).id());
276 EXPECT_EQ(gfx::Rect(410, 20, 200, shelf_inset), window->GetBoundsInScreen());
277 }
278
279 // Tests auto window management on moving window between displays.
TEST_F(DisplayMoveWindowUtilTest,AutoManaged)280 TEST_F(DisplayMoveWindowUtilTest, AutoManaged) {
281 // Layout: [p][1]
282 UpdateDisplay("400x300,400x300");
283 // Create and show window on display [p]. Enable auto window position managed,
284 // which will center the window on display [p].
285 aura::Window* window1 =
286 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
287 WindowState* window1_state = WindowState::Get(window1);
288 window1_state->SetWindowPositionManaged(true);
289 window1->Hide();
290 window1->Show();
291 EXPECT_EQ(gfx::Rect(100, 20, 200, 100), window1->GetBoundsInScreen());
292 display::Screen* screen = display::Screen::GetScreen();
293 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
294 screen->GetDisplayNearestWindow(window1).id());
295
296 // Create and show window on display [p]. Enable auto window position managed,
297 // which will do auto window management (pushing the other window to side).
298 aura::Window* window2 =
299 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
300 WindowState* window2_state = WindowState::Get(window2);
301 window2_state->SetWindowPositionManaged(true);
302 window2->Hide();
303 window2->Show();
304 EXPECT_EQ(gfx::Rect(0, 20, 200, 100), window1->GetBoundsInScreen());
305 EXPECT_EQ(gfx::Rect(200, 20, 200, 100), window2->GetBoundsInScreen());
306 // Activate window2.
307 wm::ActivateWindow(window2);
308 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
309 screen->GetDisplayNearestWindow(window2).id());
310 EXPECT_TRUE(window2_state->pre_auto_manage_window_bounds());
311 EXPECT_EQ(gfx::Rect(10, 20, 200, 100),
312 *window2_state->pre_auto_manage_window_bounds());
313
314 // Move window2 to display [1] and check auto window management.
315 PerformMoveWindowAccel();
316 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
317 screen->GetDisplayNearestWindow(window2).id());
318 // Check |pre_added_to_workspace_window_bounds()|, which should be equal to
319 // |pre_auto_manage_window_bounds()| in this case.
320 EXPECT_EQ(*window2_state->pre_auto_manage_window_bounds(),
321 *window2_state->pre_added_to_workspace_window_bounds());
322 // Window 1 centers on display [p] again.
323 EXPECT_EQ(gfx::Rect(100, 20, 200, 100), window1->GetBoundsInScreen());
324 // Window 2 is positioned by |pre_added_to_workspace_window_bounds()|.
325 EXPECT_EQ(gfx::Rect(410, 20, 200, 100), window2->GetBoundsInScreen());
326 }
327
TEST_F(DisplayMoveWindowUtilTest,WindowWithTransientChild)328 TEST_F(DisplayMoveWindowUtilTest, WindowWithTransientChild) {
329 UpdateDisplay("400x300,400x300");
330 aura::Window* window =
331 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
332 wm::ActivateWindow(window);
333
334 // Create a |child| window and make it a transient child of |window|.
335 std::unique_ptr<aura::Window> child =
336 CreateChildWindow(window, gfx::Rect(20, 30, 40, 50));
337 ::wm::AddTransientChild(window, child.get());
338 display::Screen* screen = display::Screen::GetScreen();
339 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
340 screen->GetDisplayNearestWindow(window).id());
341 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
342 screen->GetDisplayNearestWindow(child.get()).id());
343
344 // Operate moving window to right display. Check display and bounds.
345 PerformMoveWindowAccel();
346 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
347 screen->GetDisplayNearestWindow(window).id());
348 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
349 screen->GetDisplayNearestWindow(child.get()).id());
350 EXPECT_EQ(gfx::Rect(410, 20, 200, 100), window->GetBoundsInScreen());
351 EXPECT_EQ(gfx::Rect(430, 50, 40, 50), child->GetBoundsInScreen());
352 }
353
354 // Test that when operating move window between displays on activated transient
355 // child window, its first non-transient transient-parent window should be the
356 // target instead.
TEST_F(DisplayMoveWindowUtilTest,ActiveTransientChildWindow)357 TEST_F(DisplayMoveWindowUtilTest, ActiveTransientChildWindow) {
358 UpdateDisplay("400x300,400x300");
359 std::unique_ptr<views::Widget> window = CreateTestWidget();
360 window->SetBounds(gfx::Rect(10, 20, 200, 100));
361
362 // Create a |child| transient widget of |window|. When |child| is shown, it is
363 // activated.
364 std::unique_ptr<views::Widget> child(CreateTestWidgetWithParent(
365 views::Widget::InitParams::TYPE_WINDOW, window->GetNativeView(),
366 gfx::Rect(20, 30, 40, 50), false));
367 display::Screen* screen = display::Screen::GetScreen();
368 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
369 screen->GetDisplayNearestWindow(window->GetNativeWindow()).id());
370 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
371 screen->GetDisplayNearestWindow(child->GetNativeWindow()).id());
372 // Ensure |child| window is activated.
373 EXPECT_FALSE(wm::IsActiveWindow(window->GetNativeWindow()));
374 EXPECT_TRUE(wm::IsActiveWindow(child->GetNativeWindow()));
375
376 // Operate moving window to right display. Check display and bounds.
377 PerformMoveWindowAccel();
378 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
379 screen->GetDisplayNearestWindow(window->GetNativeWindow()).id());
380 EXPECT_EQ(display_manager()->GetDisplayAt(1).id(),
381 screen->GetDisplayNearestWindow(child->GetNativeWindow()).id());
382 EXPECT_EQ(gfx::Rect(410, 20, 200, 100),
383 window->GetNativeWindow()->GetBoundsInScreen());
384 EXPECT_EQ(gfx::Rect(420, 30, 40, 50),
385 child->GetNativeWindow()->GetBoundsInScreen());
386 }
387
388 // Test that when active window is transient child window, no movement if its
389 // first non-transient transient-parent window is not in window cycle list.
TEST_F(DisplayMoveWindowUtilTest,TransientParentNotInCycleWindowList)390 TEST_F(DisplayMoveWindowUtilTest, TransientParentNotInCycleWindowList) {
391 UpdateDisplay("400x300,400x300");
392 aura::Window* w1 =
393 CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 50, 50));
394 wm::ActivateWindow(w1);
395
396 // Create a window |w2| in non-switchable window container.
397 aura::Window* setting_bubble_container =
398 Shell::GetPrimaryRootWindowController()->GetContainer(
399 kShellWindowId_SettingBubbleContainer);
400 std::unique_ptr<aura::Window> w2 = CreateChildWindow(
401 setting_bubble_container, gfx::Rect(10, 20, 200, 100), -1);
402 wm::ActivateWindow(w2.get());
403
404 // Create a |child| transient widget of |w2|. When |child| is shown, it is
405 // activated.
406 std::unique_ptr<views::Widget> child(
407 CreateTestWidgetWithParent(views::Widget::InitParams::TYPE_WINDOW,
408 w2.get(), gfx::Rect(20, 30, 40, 50), false));
409 display::Screen* screen = display::Screen::GetScreen();
410 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
411 screen->GetDisplayNearestWindow(w1).id());
412 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
413 screen->GetDisplayNearestWindow(w2.get()).id());
414 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
415 screen->GetDisplayNearestWindow(child->GetNativeWindow()).id());
416 // Ensure |child| window is activated.
417 EXPECT_FALSE(wm::IsActiveWindow(w1));
418 EXPECT_FALSE(wm::IsActiveWindow(w2.get()));
419 EXPECT_TRUE(wm::IsActiveWindow(child->GetNativeWindow()));
420
421 PerformMoveWindowAccel();
422 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
423 screen->GetDisplayNearestWindow(w1).id());
424 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
425 screen->GetDisplayNearestWindow(w2.get()).id());
426 EXPECT_EQ(display_manager()->GetDisplayAt(0).id(),
427 screen->GetDisplayNearestWindow(child->GetNativeWindow()).id());
428 }
429
430 // Tests that restore bounds is updated with window movement to another display.
TEST_F(DisplayMoveWindowUtilTest,RestoreMaximizedWindowAfterMovement)431 TEST_F(DisplayMoveWindowUtilTest, RestoreMaximizedWindowAfterMovement) {
432 UpdateDisplay("400x300,400x300");
433 aura::Window* w =
434 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 200, 100));
435 wm::ActivateWindow(w);
436
437 WindowState* window_state = WindowState::Get(w);
438 window_state->Maximize();
439 EXPECT_EQ(gfx::Rect(0, 0, 400, 300 - ShelfConfig::Get()->shelf_size()),
440 w->GetBoundsInScreen());
441
442 PerformMoveWindowAccel();
443 EXPECT_EQ(gfx::Rect(400, 0, 400, 300 - ShelfConfig::Get()->shelf_size()),
444 w->GetBoundsInScreen());
445 window_state->Restore();
446 EXPECT_EQ(gfx::Rect(410, 20, 200, 100), w->GetBoundsInScreen());
447 }
448
449 } // namespace display_move_window_util
450
451 } // namespace ash
452