1 // Copyright 2014 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/wm/window_state.h" 6 7 #include "ash/screen_util.h" 8 #include "ash/shell.h" 9 #include "ash/test/ash_test_base.h" 10 #include "ash/wm/window_state.h" 11 #include "ash/wm/wm_event.h" 12 #include "ui/aura/client/aura_constants.h" 13 #include "ui/aura/test/test_window_delegate.h" 14 #include "ui/aura/window.h" 15 16 namespace ash { 17 namespace wm { 18 namespace { 19 20 class AlwaysMaximizeTestState : public WindowState::State { 21 public: 22 explicit AlwaysMaximizeTestState(WindowStateType initial_state_type) 23 : state_type_(initial_state_type) {} 24 virtual ~AlwaysMaximizeTestState() {} 25 26 // WindowState::State overrides: 27 virtual void OnWMEvent(WindowState* window_state, 28 const WMEvent* event) OVERRIDE { 29 // We don't do anything here. 30 } 31 virtual WindowStateType GetType() const OVERRIDE { 32 return state_type_; 33 } 34 virtual void AttachState( 35 WindowState* window_state, 36 WindowState::State* previous_state) OVERRIDE { 37 // We always maximize. 38 if (state_type_ != WINDOW_STATE_TYPE_MAXIMIZED) { 39 window_state->Maximize(); 40 state_type_ = WINDOW_STATE_TYPE_MAXIMIZED; 41 } 42 } 43 virtual void DetachState(WindowState* window_state) OVERRIDE {} 44 45 private: 46 WindowStateType state_type_; 47 48 DISALLOW_COPY_AND_ASSIGN(AlwaysMaximizeTestState); 49 }; 50 51 } // namespace 52 53 typedef test::AshTestBase WindowStateTest; 54 55 // Test that a window gets properly snapped to the display's edges in a 56 // multi monitor environment. 57 TEST_F(WindowStateTest, SnapWindowBasic) { 58 if (!SupportsMultipleDisplays()) 59 return; 60 61 UpdateDisplay("0+0-500x400, 0+500-600x400"); 62 const gfx::Rect kPrimaryDisplayWorkAreaBounds = 63 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 64 const gfx::Rect kSecondaryDisplayWorkAreaBounds = 65 ScreenUtil::GetSecondaryDisplay().work_area(); 66 67 scoped_ptr<aura::Window> window( 68 CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100))); 69 WindowState* window_state = GetWindowState(window.get()); 70 const WMEvent snap_left(WM_EVENT_SNAP_LEFT); 71 window_state->OnWMEvent(&snap_left); 72 gfx::Rect expected = gfx::Rect( 73 kPrimaryDisplayWorkAreaBounds.x(), 74 kPrimaryDisplayWorkAreaBounds.y(), 75 kPrimaryDisplayWorkAreaBounds.width() / 2, 76 kPrimaryDisplayWorkAreaBounds.height()); 77 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 78 79 const WMEvent snap_right(WM_EVENT_SNAP_RIGHT); 80 window_state->OnWMEvent(&snap_right); 81 expected.set_x(kPrimaryDisplayWorkAreaBounds.right() - expected.width()); 82 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 83 84 // Move the window to the secondary display. 85 window->SetBoundsInScreen(gfx::Rect(600, 0, 100, 100), 86 ScreenUtil::GetSecondaryDisplay()); 87 88 window_state->OnWMEvent(&snap_right); 89 expected = gfx::Rect( 90 kSecondaryDisplayWorkAreaBounds.x() + 91 kSecondaryDisplayWorkAreaBounds.width() / 2, 92 kSecondaryDisplayWorkAreaBounds.y(), 93 kSecondaryDisplayWorkAreaBounds.width() / 2, 94 kSecondaryDisplayWorkAreaBounds.height()); 95 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 96 97 window_state->OnWMEvent(&snap_left); 98 expected.set_x(kSecondaryDisplayWorkAreaBounds.x()); 99 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 100 } 101 102 // Test how the minimum and maximum size specified by the aura::WindowDelegate 103 // affect snapping. 104 TEST_F(WindowStateTest, SnapWindowMinimumSize) { 105 if (!SupportsHostWindowResize()) 106 return; 107 108 UpdateDisplay("0+0-600x900"); 109 const gfx::Rect kWorkAreaBounds = 110 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 111 112 aura::test::TestWindowDelegate delegate; 113 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate( 114 &delegate, -1, gfx::Rect(0, 100, kWorkAreaBounds.width() - 1, 100))); 115 116 // It should be possible to snap a window with a minimum size. 117 delegate.set_minimum_size(gfx::Size(kWorkAreaBounds.width() - 1, 0)); 118 WindowState* window_state = GetWindowState(window.get()); 119 EXPECT_TRUE(window_state->CanSnap()); 120 const WMEvent snap_right(WM_EVENT_SNAP_RIGHT); 121 window_state->OnWMEvent(&snap_right); 122 gfx::Rect expected = gfx::Rect(kWorkAreaBounds.x() + 1, 123 kWorkAreaBounds.y(), 124 kWorkAreaBounds.width() - 1, 125 kWorkAreaBounds.height()); 126 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 127 128 // It should not be possible to snap a window with a maximum size, or if it 129 // cannot be maximized. 130 delegate.set_maximum_size(gfx::Size(kWorkAreaBounds.width() - 1, 0)); 131 EXPECT_FALSE(window_state->CanSnap()); 132 delegate.set_maximum_size(gfx::Size(0, kWorkAreaBounds.height() - 1)); 133 EXPECT_FALSE(window_state->CanSnap()); 134 delegate.set_maximum_size(gfx::Size()); 135 window->SetProperty(aura::client::kCanMaximizeKey, false); 136 EXPECT_FALSE(window_state->CanSnap()); 137 } 138 139 // Test that the minimum size specified by aura::WindowDelegate gets respected. 140 TEST_F(WindowStateTest, TestRespectMinimumSize) { 141 if (!SupportsHostWindowResize()) 142 return; 143 144 UpdateDisplay("0+0-1024x768"); 145 146 aura::test::TestWindowDelegate delegate; 147 const gfx::Size minimum_size(gfx::Size(500, 300)); 148 delegate.set_minimum_size(minimum_size); 149 150 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate( 151 &delegate, -1, gfx::Rect(0, 100, 100, 100))); 152 153 // Check that the window has the correct minimum size. 154 EXPECT_EQ(minimum_size.ToString(), window->bounds().size().ToString()); 155 156 // Set the size to something bigger - that should work. 157 gfx::Rect bigger_bounds(700, 500, 700, 500); 158 window->SetBounds(bigger_bounds); 159 EXPECT_EQ(bigger_bounds.ToString(), window->bounds().ToString()); 160 161 // Set the size to something smaller - that should only resize to the smallest 162 // possible size. 163 gfx::Rect smaller_bounds(700, 500, 100, 100); 164 window->SetBounds(smaller_bounds); 165 EXPECT_EQ(minimum_size.ToString(), window->bounds().size().ToString()); 166 } 167 168 // Test that the minimum window size specified by aura::WindowDelegate does not 169 // exceed the screen size. 170 TEST_F(WindowStateTest, TestIgnoreTooBigMinimumSize) { 171 if (!SupportsHostWindowResize()) 172 return; 173 174 UpdateDisplay("0+0-1024x768"); 175 const gfx::Size work_area_size = 176 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area().size(); 177 const gfx::Size illegal_size(1280, 960); 178 const gfx::Rect illegal_bounds(gfx::Point(0, 0), illegal_size); 179 180 aura::test::TestWindowDelegate delegate; 181 const gfx::Size minimum_size(illegal_size); 182 delegate.set_minimum_size(minimum_size); 183 184 // The creation should force the window to respect the screen size. 185 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithDelegate( 186 &delegate, -1, illegal_bounds)); 187 EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString()); 188 189 // Trying to set the size to something bigger then the screen size should be 190 // ignored. 191 window->SetBounds(illegal_bounds); 192 EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString()); 193 194 // Maximizing the window should not allow it to go bigger than that either. 195 WindowState* window_state = GetWindowState(window.get()); 196 window_state->Maximize(); 197 EXPECT_EQ(work_area_size.ToString(), window->bounds().size().ToString()); 198 } 199 200 // Test that setting the bounds of a snapped window keeps its snapped. 201 TEST_F(WindowStateTest, SnapWindowSetBounds) { 202 if (!SupportsHostWindowResize()) 203 return; 204 205 UpdateDisplay("0+0-900x600"); 206 const gfx::Rect kWorkAreaBounds = 207 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 208 209 scoped_ptr<aura::Window> window( 210 CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100))); 211 WindowState* window_state = GetWindowState(window.get()); 212 const WMEvent snap_left(WM_EVENT_SNAP_LEFT); 213 window_state->OnWMEvent(&snap_left); 214 EXPECT_EQ(WINDOW_STATE_TYPE_LEFT_SNAPPED, window_state->GetStateType()); 215 gfx::Rect expected = gfx::Rect(kWorkAreaBounds.x(), 216 kWorkAreaBounds.y(), 217 kWorkAreaBounds.width() / 2, 218 kWorkAreaBounds.height()); 219 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 220 221 // Snapped windows can have any width. 222 expected.set_width(500); 223 window->SetBounds(gfx::Rect(10, 10, 500, 300)); 224 EXPECT_EQ(expected.ToString(), window->GetBoundsInScreen().ToString()); 225 EXPECT_EQ(WINDOW_STATE_TYPE_LEFT_SNAPPED, window_state->GetStateType()); 226 } 227 228 // Test that snapping left/right preserves the restore bounds. 229 TEST_F(WindowStateTest, RestoreBounds) { 230 scoped_ptr<aura::Window> window( 231 CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100))); 232 WindowState* window_state = GetWindowState(window.get()); 233 234 EXPECT_TRUE(window_state->IsNormalStateType()); 235 236 // 1) Start with restored window with restore bounds set. 237 gfx::Rect restore_bounds = window->GetBoundsInScreen(); 238 restore_bounds.set_width(restore_bounds.width() + 1); 239 window_state->SetRestoreBoundsInScreen(restore_bounds); 240 const WMEvent snap_left(WM_EVENT_SNAP_LEFT); 241 window_state->OnWMEvent(&snap_left); 242 const WMEvent snap_right(WM_EVENT_SNAP_RIGHT); 243 window_state->OnWMEvent(&snap_right); 244 EXPECT_NE(restore_bounds.ToString(), window->GetBoundsInScreen().ToString()); 245 EXPECT_EQ(restore_bounds.ToString(), 246 window_state->GetRestoreBoundsInScreen().ToString()); 247 window_state->Restore(); 248 EXPECT_EQ(restore_bounds.ToString(), window->GetBoundsInScreen().ToString()); 249 250 // 2) Start with restored bounds set as a result of maximizing the window. 251 window_state->Maximize(); 252 gfx::Rect maximized_bounds = window->GetBoundsInScreen(); 253 EXPECT_NE(maximized_bounds.ToString(), restore_bounds.ToString()); 254 EXPECT_EQ(restore_bounds.ToString(), 255 window_state->GetRestoreBoundsInScreen().ToString()); 256 257 window_state->OnWMEvent(&snap_left); 258 EXPECT_NE(restore_bounds.ToString(), window->GetBoundsInScreen().ToString()); 259 EXPECT_NE(maximized_bounds.ToString(), 260 window->GetBoundsInScreen().ToString()); 261 EXPECT_EQ(restore_bounds.ToString(), 262 window_state->GetRestoreBoundsInScreen().ToString()); 263 264 window_state->Restore(); 265 EXPECT_EQ(restore_bounds.ToString(), window->GetBoundsInScreen().ToString()); 266 } 267 268 // Test that maximizing an auto managed window, then snapping it puts the window 269 // at the snapped bounds and not at the auto-managed (centered) bounds. 270 TEST_F(WindowStateTest, AutoManaged) { 271 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0)); 272 WindowState* window_state = GetWindowState(window.get()); 273 window_state->set_window_position_managed(true); 274 window->Hide(); 275 window->SetBounds(gfx::Rect(100, 100, 100, 100)); 276 window->Show(); 277 278 window_state->Maximize(); 279 const WMEvent snap_right(WM_EVENT_SNAP_RIGHT); 280 window_state->OnWMEvent(&snap_right); 281 282 const gfx::Rect kWorkAreaBounds = 283 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 284 gfx::Rect expected_snapped_bounds( 285 kWorkAreaBounds.x() + kWorkAreaBounds.width() / 2, 286 kWorkAreaBounds.y(), 287 kWorkAreaBounds.width() / 2, 288 kWorkAreaBounds.height()); 289 EXPECT_EQ(expected_snapped_bounds.ToString(), 290 window->GetBoundsInScreen().ToString()); 291 292 // The window should still be auto managed despite being right maximized. 293 EXPECT_TRUE(window_state->window_position_managed()); 294 } 295 296 // Test that the replacement of a State object works as expected. 297 TEST_F(WindowStateTest, SimpleStateSwap) { 298 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0)); 299 WindowState* window_state = GetWindowState(window.get()); 300 EXPECT_FALSE(window_state->IsMaximized()); 301 window_state->SetStateObject( 302 scoped_ptr<WindowState::State> (new AlwaysMaximizeTestState( 303 window_state->GetStateType()))); 304 EXPECT_TRUE(window_state->IsMaximized()); 305 } 306 307 // Test that the replacement of a state object, following a restore with the 308 // original one restores the window to its original state. 309 TEST_F(WindowStateTest, StateSwapRestore) { 310 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0)); 311 WindowState* window_state = GetWindowState(window.get()); 312 EXPECT_FALSE(window_state->IsMaximized()); 313 scoped_ptr<WindowState::State> old(window_state->SetStateObject( 314 scoped_ptr<WindowState::State> (new AlwaysMaximizeTestState( 315 window_state->GetStateType()))).Pass()); 316 EXPECT_TRUE(window_state->IsMaximized()); 317 window_state->SetStateObject(old.Pass()); 318 EXPECT_FALSE(window_state->IsMaximized()); 319 } 320 321 // Tests that a window that had same bounds as the work area shrinks after the 322 // window is maximized and then restored. 323 TEST_F(WindowStateTest, RestoredWindowBoundsShrink) { 324 scoped_ptr<aura::Window> window(CreateTestWindowInShellWithId(0)); 325 WindowState* window_state = GetWindowState(window.get()); 326 EXPECT_FALSE(window_state->IsMaximized()); 327 gfx::Rect work_area = 328 ash::Shell::GetScreen()->GetPrimaryDisplay().work_area(); 329 330 window->SetBounds(work_area); 331 window_state->Maximize(); 332 EXPECT_TRUE(window_state->IsMaximized()); 333 EXPECT_EQ(work_area.ToString(), window->bounds().ToString()); 334 335 window_state->Restore(); 336 EXPECT_FALSE(window_state->IsMaximized()); 337 EXPECT_NE(work_area.ToString(), window->bounds().ToString()); 338 EXPECT_TRUE(work_area.Contains(window->bounds())); 339 } 340 341 // TODO(skuhne): Add more unit test to verify the correctness for the restore 342 // operation. 343 344 } // namespace wm 345 } // namespace ash 346