1 // Copyright (c) 2012 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.
5 #include "ash/wm/workspace/workspace_layout_manager.h"
7 #include "ash/display/display_layout.h"
8 #include "ash/display/display_manager.h"
9 #include "ash/root_window_controller.h"
10 #include "ash/screen_ash.h"
11 #include "ash/shelf/shelf_layout_manager.h"
12 #include "ash/shelf/shelf_widget.h"
13 #include "ash/shell.h"
14 #include "ash/test/ash_test_base.h"
15 #include "ash/wm/window_state.h"
16 #include "ash/wm/window_util.h"
17 #include "ui/aura/client/aura_constants.h"
18 #include "ui/aura/root_window.h"
19 #include "ui/aura/test/test_windows.h"
20 #include "ui/aura/window.h"
21 #include "ui/gfx/insets.h"
22 #include "ui/views/widget/widget.h"
23 #include "ui/views/widget/widget_delegate.h"
28 class MaximizeDelegateView : public views::WidgetDelegateView {
30 MaximizeDelegateView(const gfx::Rect& initial_bounds)
31 : initial_bounds_(initial_bounds) {
33 virtual ~MaximizeDelegateView() {}
35 virtual bool GetSavedWindowPlacement(
36 const views::Widget* widget,
38 ui::WindowShowState* show_state) const OVERRIDE {
39 *bounds = initial_bounds_;
40 *show_state = ui::SHOW_STATE_MAXIMIZED;
45 const gfx::Rect initial_bounds_;
47 DISALLOW_COPY_AND_ASSIGN(MaximizeDelegateView);
52 typedef test::AshTestBase WorkspaceLayoutManagerTest;
54 // Verifies that a window containing a restore coordinate will be restored to
55 // to the size prior to minimize, keeping the restore rectangle in tact (if
57 TEST_F(WorkspaceLayoutManagerTest, RestoreFromMinimizeKeepsRestore) {
58 scoped_ptr<aura::Window> window(
59 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 3, 4)));
60 gfx::Rect bounds(10, 15, 25, 35);
61 window->SetBounds(bounds);
63 wm::WindowState* window_state = wm::GetWindowState(window.get());
65 // This will not be used for un-minimizing window.
66 window_state->SetRestoreBoundsInScreen(gfx::Rect(0, 0, 100, 100));
67 window_state->Minimize();
68 window_state->Restore();
69 EXPECT_EQ("0,0 100x100", window_state->GetRestoreBoundsInScreen().ToString());
70 EXPECT_EQ("10,15 25x35", window.get()->bounds().ToString());
72 if (!SupportsMultipleDisplays())
75 UpdateDisplay("400x300,500x400");
76 window->SetBoundsInScreen(gfx::Rect(600, 0, 100, 100),
77 ScreenAsh::GetSecondaryDisplay());
78 EXPECT_EQ(Shell::GetAllRootWindows()[1], window->GetRootWindow());
79 window_state->Minimize();
80 // This will not be used for un-minimizing window.
81 window_state->SetRestoreBoundsInScreen(gfx::Rect(0, 0, 100, 100));
82 window_state->Restore();
83 EXPECT_EQ("600,0 100x100", window->GetBoundsInScreen().ToString());
85 // Make sure the unminimized window moves inside the display when
86 // 2nd display is disconnected.
87 window_state->Minimize();
88 UpdateDisplay("400x300");
89 window_state->Restore();
90 EXPECT_EQ(Shell::GetPrimaryRootWindow(), window->GetRootWindow());
92 Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
95 TEST_F(WorkspaceLayoutManagerTest, KeepMinimumVisibilityInDisplays) {
96 if (!SupportsMultipleDisplays())
99 UpdateDisplay("300x400,400x500");
100 Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
102 DisplayLayout layout(DisplayLayout::TOP, 0);
103 Shell::GetInstance()->display_manager()->
104 SetLayoutForCurrentDisplays(layout);
105 EXPECT_EQ("0,-500 400x500", root_windows[1]->GetBoundsInScreen().ToString());
107 scoped_ptr<aura::Window> window1(
108 CreateTestWindowInShellWithBounds(gfx::Rect(10, -400, 200, 200)));
109 EXPECT_EQ("10,-400 200x200", window1->GetBoundsInScreen().ToString());
111 // Make sure the caption is visible.
112 scoped_ptr<aura::Window> window2(
113 CreateTestWindowInShellWithBounds(gfx::Rect(10, -600, 200, 200)));
114 EXPECT_EQ("10,-500 200x200", window2->GetBoundsInScreen().ToString());
117 TEST_F(WorkspaceLayoutManagerTest, KeepRestoredWindowInDisplay) {
118 if (!SupportsHostWindowResize())
120 scoped_ptr<aura::Window> window(
121 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 30, 40)));
122 wm::WindowState* window_state = wm::GetWindowState(window.get());
124 // Maximized -> Normal transition.
125 window_state->Maximize();
126 window_state->SetRestoreBoundsInScreen(gfx::Rect(-100, -100, 30, 40));
127 window_state->Restore();
129 Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
130 // Y bounds should not be negative.
131 EXPECT_EQ("-20,0 30x40", window->bounds().ToString());
133 // Minimized -> Normal transition.
134 window->SetBounds(gfx::Rect(-100, -100, 30, 40));
135 window_state->Minimize();
137 Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
138 EXPECT_EQ("-100,-100 30x40", window->bounds().ToString());
141 Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
142 // Y bounds should not be negative.
143 EXPECT_EQ("-20,0 30x40", window->bounds().ToString());
145 // Fullscreen -> Normal transition.
146 window->SetBounds(gfx::Rect(0, 0, 30, 40)); // reset bounds.
147 ASSERT_EQ("0,0 30x40", window->bounds().ToString());
148 window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
149 EXPECT_EQ(window->bounds(), window->GetRootWindow()->bounds());
150 window_state->SetRestoreBoundsInScreen(gfx::Rect(-100, -100, 30, 40));
151 window_state->Restore();
153 Shell::GetPrimaryRootWindow()->bounds().Intersects(window->bounds()));
154 // Y bounds should not be negative.
155 EXPECT_EQ("-20,0 30x40", window->bounds().ToString());
158 TEST_F(WorkspaceLayoutManagerTest, MaximizeInDisplayToBeRestored) {
159 if (!SupportsMultipleDisplays())
161 UpdateDisplay("300x400,400x500");
163 Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
165 scoped_ptr<aura::Window> window(
166 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 30, 40)));
167 EXPECT_EQ(root_windows[0], window->GetRootWindow());
169 wm::WindowState* window_state = wm::GetWindowState(window.get());
170 window_state->SetRestoreBoundsInScreen(gfx::Rect(400, 0, 30, 40));
171 // Maximize the window in 2nd display as the restore bounds
172 // is inside 2nd display.
173 window_state->Maximize();
174 EXPECT_EQ(root_windows[1], window->GetRootWindow());
175 EXPECT_EQ("300,0 400x453", window->GetBoundsInScreen().ToString());
177 window_state->Restore();
178 EXPECT_EQ(root_windows[1], window->GetRootWindow());
179 EXPECT_EQ("400,0 30x40", window->GetBoundsInScreen().ToString());
181 // If the restore bounds intersects with the current display,
183 window_state->SetRestoreBoundsInScreen(gfx::Rect(280, 0, 30, 40));
184 window_state->Maximize();
185 EXPECT_EQ(root_windows[1], window->GetRootWindow());
186 EXPECT_EQ("300,0 400x453", window->GetBoundsInScreen().ToString());
188 window_state->Restore();
189 EXPECT_EQ(root_windows[1], window->GetRootWindow());
190 EXPECT_EQ("280,0 30x40", window->GetBoundsInScreen().ToString());
192 // Restoring widget state.
193 scoped_ptr<views::Widget> w1(new views::Widget);
194 views::Widget::InitParams params;
195 params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
196 params.delegate = new MaximizeDelegateView(gfx::Rect(400, 0, 30, 40));
197 params.context = root_windows[0];
200 EXPECT_TRUE(w1->IsMaximized());
201 EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow());
202 EXPECT_EQ("300,0 400x453", w1->GetWindowBoundsInScreen().ToString());
204 EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow());
205 EXPECT_EQ("400,0 30x40", w1->GetWindowBoundsInScreen().ToString());
208 TEST_F(WorkspaceLayoutManagerTest, FullscreenInDisplayToBeRestored) {
209 if (!SupportsMultipleDisplays())
211 UpdateDisplay("300x400,400x500");
213 Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
215 scoped_ptr<aura::Window> window(
216 CreateTestWindowInShellWithBounds(gfx::Rect(1, 2, 30, 40)));
217 EXPECT_EQ(root_windows[0], window->GetRootWindow());
219 wm::WindowState* window_state = wm::GetWindowState(window.get());
220 window_state->SetRestoreBoundsInScreen(gfx::Rect(400, 0, 30, 40));
221 // Maximize the window in 2nd display as the restore bounds
222 // is inside 2nd display.
223 window->SetProperty(aura::client::kShowStateKey,
224 ui::SHOW_STATE_FULLSCREEN);
225 EXPECT_EQ(root_windows[1], window->GetRootWindow());
226 EXPECT_EQ("300,0 400x500", window->GetBoundsInScreen().ToString());
228 window_state->Restore();
229 EXPECT_EQ(root_windows[1], window->GetRootWindow());
230 EXPECT_EQ("400,0 30x40", window->GetBoundsInScreen().ToString());
232 // If the restore bounds intersects with the current display,
234 window_state->SetRestoreBoundsInScreen(gfx::Rect(280, 0, 30, 40));
235 window->SetProperty(aura::client::kShowStateKey,
236 ui::SHOW_STATE_FULLSCREEN);
237 EXPECT_EQ(root_windows[1], window->GetRootWindow());
238 EXPECT_EQ("300,0 400x500", window->GetBoundsInScreen().ToString());
240 window_state->Restore();
241 EXPECT_EQ(root_windows[1], window->GetRootWindow());
242 EXPECT_EQ("280,0 30x40", window->GetBoundsInScreen().ToString());
245 // WindowObserver implementation used by DontClobberRestoreBoundsWindowObserver.
246 // This code mirrors what BrowserFrameAsh does. In particular when this code
247 // sees the window was maximized it changes the bounds of a secondary
248 // window. The secondary window mirrors the status window.
249 class DontClobberRestoreBoundsWindowObserver : public aura::WindowObserver {
251 DontClobberRestoreBoundsWindowObserver() : window_(NULL) {}
253 void set_window(aura::Window* window) { window_ = window; }
255 virtual void OnWindowPropertyChanged(aura::Window* window,
257 intptr_t old) OVERRIDE {
261 if (wm::GetWindowState(window)->IsMaximized()) {
262 aura::Window* w = window_;
265 gfx::Rect shelf_bounds(Shell::GetPrimaryRootWindowController()->
266 GetShelfLayoutManager()->GetIdealBounds());
267 const gfx::Rect& window_bounds(w->bounds());
268 w->SetBounds(gfx::Rect(window_bounds.x(), shelf_bounds.y() - 1,
269 window_bounds.width(), window_bounds.height()));
274 aura::Window* window_;
276 DISALLOW_COPY_AND_ASSIGN(DontClobberRestoreBoundsWindowObserver);
279 // Creates a window, maximized the window and from within the maximized
280 // notification sets the bounds of a window to overlap the shelf. Verifies this
281 // doesn't effect the restore bounds.
282 TEST_F(WorkspaceLayoutManagerTest, DontClobberRestoreBounds) {
283 DontClobberRestoreBoundsWindowObserver window_observer;
284 scoped_ptr<aura::Window> window(new aura::Window(NULL));
285 window->SetType(aura::client::WINDOW_TYPE_NORMAL);
286 window->Init(ui::LAYER_TEXTURED);
287 window->SetBounds(gfx::Rect(10, 20, 30, 40));
288 // NOTE: for this test to exercise the failure the observer needs to be added
289 // before the parent set. This mimics what BrowserFrameAsh does.
290 window->AddObserver(&window_observer);
291 ParentWindowInPrimaryRootWindow(window.get());
294 wm::WindowState* window_state = wm::GetWindowState(window.get());
295 window_state->Activate();
297 scoped_ptr<aura::Window> window2(
298 CreateTestWindowInShellWithBounds(gfx::Rect(12, 20, 30, 40)));
299 window->AddTransientChild(window2.get());
302 window_observer.set_window(window2.get());
303 window_state->Maximize();
304 EXPECT_EQ("10,20 30x40",
305 window_state->GetRestoreBoundsInScreen().ToString());
306 window->RemoveObserver(&window_observer);
309 // Verifies when a window is maximized all descendant windows have a size.
310 TEST_F(WorkspaceLayoutManagerTest, ChildBoundsResetOnMaximize) {
311 scoped_ptr<aura::Window> window(
312 CreateTestWindowInShellWithBounds(gfx::Rect(10, 20, 30, 40)));
314 wm::WindowState* window_state = wm::GetWindowState(window.get());
315 window_state->Activate();
316 scoped_ptr<aura::Window> child_window(
317 aura::test::CreateTestWindowWithBounds(gfx::Rect(5, 6, 7, 8),
319 child_window->Show();
320 window_state->Maximize();
321 EXPECT_EQ("5,6 7x8", child_window->bounds().ToString());
324 TEST_F(WorkspaceLayoutManagerTest, WindowShouldBeOnScreenWhenAdded) {
325 // Normal window bounds shouldn't be changed.
326 gfx::Rect window_bounds(100, 100, 200, 200);
327 scoped_ptr<aura::Window> window(
328 CreateTestWindowInShellWithBounds(window_bounds));
329 EXPECT_EQ(window_bounds, window->bounds());
331 // If the window is out of the workspace, it would be moved on screen.
332 gfx::Rect root_window_bounds =
333 Shell::GetInstance()->GetPrimaryRootWindow()->bounds();
334 window_bounds.Offset(root_window_bounds.width(), root_window_bounds.height());
335 ASSERT_FALSE(window_bounds.Intersects(root_window_bounds));
336 scoped_ptr<aura::Window> out_window(
337 CreateTestWindowInShellWithBounds(window_bounds));
338 EXPECT_EQ(window_bounds.size(), out_window->bounds().size());
339 gfx::Rect bounds = out_window->bounds();
340 bounds.Intersect(root_window_bounds);
342 // 30% of the window edge must be visible.
343 EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
344 EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
346 aura::Window* parent = out_window->parent();
347 parent->RemoveChild(out_window.get());
348 out_window->SetBounds(gfx::Rect(-200, -200, 200, 200));
349 // UserHasChangedWindowPositionOrSize flag shouldn't turn off this behavior.
350 wm::GetWindowState(window.get())->set_bounds_changed_by_user(true);
351 parent->AddChild(out_window.get());
352 EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
353 EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
355 // Make sure we always make more than 1/3 of the window edge visible even
356 // if the initial bounds intersects with display.
357 window_bounds.SetRect(-150, -150, 200, 200);
358 bounds = window_bounds;
359 bounds.Intersect(root_window_bounds);
361 // Make sure that the initial bounds' visible area is less than 26%
362 // so that the auto adjustment logic kicks in.
363 ASSERT_LT(bounds.width(), out_window->bounds().width() * 0.26);
364 ASSERT_LT(bounds.height(), out_window->bounds().height() * 0.26);
365 ASSERT_TRUE(window_bounds.Intersects(root_window_bounds));
367 scoped_ptr<aura::Window> partially_out_window(
368 CreateTestWindowInShellWithBounds(window_bounds));
369 EXPECT_EQ(window_bounds.size(), partially_out_window->bounds().size());
370 bounds = partially_out_window->bounds();
371 bounds.Intersect(root_window_bounds);
372 EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
373 EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
375 // Make sure the window whose 30% width/height is bigger than display
376 // will be placed correctly.
377 window_bounds.SetRect(-1900, -1900, 3000, 3000);
378 scoped_ptr<aura::Window> window_bigger_than_display(
379 CreateTestWindowInShellWithBounds(window_bounds));
380 EXPECT_GE(root_window_bounds.width(),
381 window_bigger_than_display->bounds().width());
382 EXPECT_GE(root_window_bounds.height(),
383 window_bigger_than_display->bounds().height());
385 bounds = window_bigger_than_display->bounds();
386 bounds.Intersect(root_window_bounds);
387 EXPECT_GT(bounds.width(), out_window->bounds().width() * 0.29);
388 EXPECT_GT(bounds.height(), out_window->bounds().height() * 0.29);
391 // Verifies the size of a window is enforced to be smaller than the work area.
392 TEST_F(WorkspaceLayoutManagerTest, SizeToWorkArea) {
393 // Normal window bounds shouldn't be changed.
395 Shell::GetScreen()->GetPrimaryDisplay().work_area().size());
396 const gfx::Rect window_bounds(
397 100, 101, work_area.width() + 1, work_area.height() + 2);
398 scoped_ptr<aura::Window> window(
399 CreateTestWindowInShellWithBounds(window_bounds));
400 EXPECT_EQ(gfx::Rect(gfx::Point(100, 101), work_area).ToString(),
401 window->bounds().ToString());
403 // Directly setting the bounds triggers a slightly different code path. Verify
405 window->SetBounds(window_bounds);
406 EXPECT_EQ(gfx::Rect(gfx::Point(100, 101), work_area).ToString(),
407 window->bounds().ToString());