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. 4 5 #include "ash/wm/system_modal_container_layout_manager.h" 6 7 #include "ash/root_window_controller.h" 8 #include "ash/session_state_delegate.h" 9 #include "ash/shell.h" 10 #include "ash/shell_window_ids.h" 11 #include "ash/test/ash_test_base.h" 12 #include "ash/wm/window_util.h" 13 #include "base/compiler_specific.h" 14 #include "base/run_loop.h" 15 #include "ui/aura/root_window.h" 16 #include "ui/aura/test/event_generator.h" 17 #include "ui/aura/window.h" 18 #include "ui/compositor/layer.h" 19 #include "ui/gfx/screen.h" 20 #include "ui/views/test/capture_tracking_view.h" 21 #include "ui/views/widget/widget.h" 22 #include "ui/views/widget/widget_delegate.h" 23 24 namespace ash { 25 namespace test { 26 27 namespace { 28 29 aura::Window* GetModalContainer() { 30 return Shell::GetPrimaryRootWindowController()->GetContainer( 31 ash::internal::kShellWindowId_SystemModalContainer); 32 } 33 34 bool AllRootWindowsHaveModalBackgroundsForContainer(int container_id) { 35 std::vector<aura::Window*> containers = 36 Shell::GetContainersFromAllRootWindows(container_id, NULL); 37 bool has_modal_screen = !containers.empty(); 38 for (std::vector<aura::Window*>::iterator iter = containers.begin(); 39 iter != containers.end(); ++iter) { 40 has_modal_screen &= 41 static_cast<internal::SystemModalContainerLayoutManager*>( 42 (*iter)->layout_manager())->has_modal_background(); 43 } 44 return has_modal_screen; 45 } 46 47 bool AllRootWindowsHaveLockedModalBackgrounds() { 48 return AllRootWindowsHaveModalBackgroundsForContainer( 49 internal::kShellWindowId_LockSystemModalContainer); 50 } 51 52 bool AllRootWindowsHaveModalBackgrounds() { 53 return AllRootWindowsHaveModalBackgroundsForContainer( 54 internal::kShellWindowId_SystemModalContainer); 55 } 56 57 class TestWindow : public views::WidgetDelegateView { 58 public: 59 explicit TestWindow(bool modal) : modal_(modal) {} 60 virtual ~TestWindow() {} 61 62 // The window needs be closed from widget in order for 63 // aura::client::kModalKey property to be reset. 64 static void CloseTestWindow(aura::Window* window) { 65 views::Widget::GetWidgetForNativeWindow(window)->Close(); 66 } 67 68 // Overridden from views::View: 69 virtual gfx::Size GetPreferredSize() OVERRIDE { 70 return gfx::Size(50, 50); 71 } 72 73 // Overridden from views::WidgetDelegate: 74 virtual views::View* GetContentsView() OVERRIDE { 75 return this; 76 } 77 virtual ui::ModalType GetModalType() const OVERRIDE { 78 return modal_ ? ui::MODAL_TYPE_SYSTEM : ui::MODAL_TYPE_NONE; 79 } 80 81 private: 82 bool modal_; 83 84 DISALLOW_COPY_AND_ASSIGN(TestWindow); 85 }; 86 87 class EventTestWindow : public TestWindow { 88 public: 89 explicit EventTestWindow(bool modal) : TestWindow(modal), 90 mouse_presses_(0) {} 91 virtual ~EventTestWindow() {} 92 93 aura::Window* OpenTestWindowWithContext(aura::RootWindow* context) { 94 views::Widget* widget = 95 views::Widget::CreateWindowWithContext(this, context); 96 widget->Show(); 97 return widget->GetNativeView(); 98 } 99 100 aura::Window* OpenTestWindowWithParent(aura::Window* parent) { 101 DCHECK(parent); 102 views::Widget* widget = 103 views::Widget::CreateWindowWithParent(this, parent); 104 widget->Show(); 105 return widget->GetNativeView(); 106 } 107 108 // Overridden from views::View: 109 virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE { 110 mouse_presses_++; 111 return false; 112 } 113 114 int mouse_presses() const { return mouse_presses_; } 115 private: 116 int mouse_presses_; 117 118 DISALLOW_COPY_AND_ASSIGN(EventTestWindow); 119 }; 120 121 class TransientWindowObserver : public aura::WindowObserver { 122 public: 123 TransientWindowObserver() : destroyed_(false) {} 124 virtual ~TransientWindowObserver() {} 125 126 bool destroyed() const { return destroyed_; } 127 128 // Overridden from aura::WindowObserver: 129 virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE { 130 destroyed_ = true; 131 } 132 133 private: 134 bool destroyed_; 135 136 DISALLOW_COPY_AND_ASSIGN(TransientWindowObserver); 137 }; 138 139 } // namespace 140 141 class SystemModalContainerLayoutManagerTest : public AshTestBase { 142 public: 143 aura::Window* OpenToplevelTestWindow(bool modal) { 144 views::Widget* widget = views::Widget::CreateWindowWithContext( 145 new TestWindow(modal), CurrentContext()); 146 widget->Show(); 147 return widget->GetNativeView(); 148 } 149 150 aura::Window* OpenTestWindowWithParent(aura::Window* parent, bool modal) { 151 views::Widget* widget = 152 views::Widget::CreateWindowWithParent(new TestWindow(modal), parent); 153 widget->Show(); 154 return widget->GetNativeView(); 155 } 156 }; 157 158 TEST_F(SystemModalContainerLayoutManagerTest, NonModalTransient) { 159 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false)); 160 aura::Window* transient = OpenTestWindowWithParent(parent.get(), false); 161 TransientWindowObserver destruction_observer; 162 transient->AddObserver(&destruction_observer); 163 164 EXPECT_EQ(parent.get(), transient->transient_parent()); 165 EXPECT_EQ(parent->parent(), transient->parent()); 166 167 // The transient should be destroyed with its parent. 168 parent.reset(); 169 EXPECT_TRUE(destruction_observer.destroyed()); 170 } 171 172 TEST_F(SystemModalContainerLayoutManagerTest, ModalTransient) { 173 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false)); 174 // parent should be active. 175 EXPECT_TRUE(wm::IsActiveWindow(parent.get())); 176 aura::Window* t1 = OpenTestWindowWithParent(parent.get(), true); 177 178 TransientWindowObserver do1; 179 t1->AddObserver(&do1); 180 181 EXPECT_EQ(parent.get(), t1->transient_parent()); 182 EXPECT_EQ(GetModalContainer(), t1->parent()); 183 184 // t1 should now be active. 185 EXPECT_TRUE(wm::IsActiveWindow(t1)); 186 187 // Attempting to click the parent should result in no activation change. 188 aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), parent.get()); 189 e1.ClickLeftButton(); 190 EXPECT_TRUE(wm::IsActiveWindow(t1)); 191 192 // Now open another modal transient parented to the original modal transient. 193 aura::Window* t2 = OpenTestWindowWithParent(t1, true); 194 TransientWindowObserver do2; 195 t2->AddObserver(&do2); 196 197 EXPECT_TRUE(wm::IsActiveWindow(t2)); 198 199 EXPECT_EQ(t1, t2->transient_parent()); 200 EXPECT_EQ(GetModalContainer(), t2->parent()); 201 202 // t2 should still be active, even after clicking on t1. 203 aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), t1); 204 e2.ClickLeftButton(); 205 EXPECT_TRUE(wm::IsActiveWindow(t2)); 206 207 // Both transients should be destroyed with parent. 208 parent.reset(); 209 EXPECT_TRUE(do1.destroyed()); 210 EXPECT_TRUE(do2.destroyed()); 211 } 212 213 TEST_F(SystemModalContainerLayoutManagerTest, ModalNonTransient) { 214 scoped_ptr<aura::Window> t1(OpenToplevelTestWindow(true)); 215 // parent should be active. 216 EXPECT_TRUE(wm::IsActiveWindow(t1.get())); 217 TransientWindowObserver do1; 218 t1->AddObserver(&do1); 219 220 EXPECT_EQ(NULL, t1->transient_parent()); 221 EXPECT_EQ(GetModalContainer(), t1->parent()); 222 223 // t1 should now be active. 224 EXPECT_TRUE(wm::IsActiveWindow(t1.get())); 225 226 // Attempting to click the parent should result in no activation change. 227 aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), 228 Shell::GetPrimaryRootWindow()); 229 e1.ClickLeftButton(); 230 EXPECT_TRUE(wm::IsActiveWindow(t1.get())); 231 232 // Now open another modal transient parented to the original modal transient. 233 aura::Window* t2 = OpenTestWindowWithParent(t1.get(), true); 234 TransientWindowObserver do2; 235 t2->AddObserver(&do2); 236 237 EXPECT_TRUE(wm::IsActiveWindow(t2)); 238 239 EXPECT_EQ(t1, t2->transient_parent()); 240 EXPECT_EQ(GetModalContainer(), t2->parent()); 241 242 // t2 should still be active, even after clicking on t1. 243 aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), t1.get()); 244 e2.ClickLeftButton(); 245 EXPECT_TRUE(wm::IsActiveWindow(t2)); 246 247 // Both transients should be destroyed with parent. 248 t1.reset(); 249 EXPECT_TRUE(do1.destroyed()); 250 EXPECT_TRUE(do2.destroyed()); 251 } 252 253 // Fails on Mac only. Needs to be implemented. http://crbug.com/111279. 254 #if defined(OS_MACOSX) 255 #define MAYBE_CanActivateAfterEndModalSession \ 256 DISABLED_CanActivateAfterEndModalSession 257 #else 258 #define MAYBE_CanActivateAfterEndModalSession CanActivateAfterEndModalSession 259 #endif 260 // Tests that we can activate an unrelated window after a modal window is closed 261 // for a window. 262 TEST_F(SystemModalContainerLayoutManagerTest, 263 MAYBE_CanActivateAfterEndModalSession) { 264 scoped_ptr<aura::Window> unrelated(OpenToplevelTestWindow(false)); 265 unrelated->SetBounds(gfx::Rect(100, 100, 50, 50)); 266 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false)); 267 // parent should be active. 268 EXPECT_TRUE(wm::IsActiveWindow(parent.get())); 269 270 scoped_ptr<aura::Window> transient( 271 OpenTestWindowWithParent(parent.get(), true)); 272 // t1 should now be active. 273 EXPECT_TRUE(wm::IsActiveWindow(transient.get())); 274 275 // Attempting to click the parent should result in no activation change. 276 aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), parent.get()); 277 e1.ClickLeftButton(); 278 EXPECT_TRUE(wm::IsActiveWindow(transient.get())); 279 280 // Now close the transient. 281 transient->Hide(); 282 TestWindow::CloseTestWindow(transient.release()); 283 284 base::RunLoop().RunUntilIdle(); 285 286 // parent should now be active again. 287 EXPECT_TRUE(wm::IsActiveWindow(parent.get())); 288 289 // Attempting to click unrelated should activate it. 290 aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), unrelated.get()); 291 e2.ClickLeftButton(); 292 EXPECT_TRUE(wm::IsActiveWindow(unrelated.get())); 293 } 294 295 TEST_F(SystemModalContainerLayoutManagerTest, EventFocusContainers) { 296 // Create a normal window and attempt to receive a click event. 297 EventTestWindow* main_delegate = new EventTestWindow(false); 298 scoped_ptr<aura::Window> main( 299 main_delegate->OpenTestWindowWithContext(CurrentContext())); 300 EXPECT_TRUE(wm::IsActiveWindow(main.get())); 301 aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), main.get()); 302 e1.ClickLeftButton(); 303 EXPECT_EQ(1, main_delegate->mouse_presses()); 304 305 // Create a modal window for the main window and verify that the main window 306 // no longer receives mouse events. 307 EventTestWindow* transient_delegate = new EventTestWindow(true); 308 aura::Window* transient = 309 transient_delegate->OpenTestWindowWithParent(main.get()); 310 EXPECT_TRUE(wm::IsActiveWindow(transient)); 311 e1.ClickLeftButton(); 312 EXPECT_EQ(1, transient_delegate->mouse_presses()); 313 314 for (int block_reason = FIRST_BLOCK_REASON; 315 block_reason < NUMBER_OF_BLOCK_REASONS; 316 ++block_reason) { 317 // Create a window in the lock screen container and ensure that it receives 318 // the mouse event instead of the modal window (crbug.com/110920). 319 BlockUserSession(static_cast<UserSessionBlockReason>(block_reason)); 320 EventTestWindow* lock_delegate = new EventTestWindow(false); 321 scoped_ptr<aura::Window> lock(lock_delegate->OpenTestWindowWithParent( 322 Shell::GetPrimaryRootWindowController()->GetContainer( 323 ash::internal::kShellWindowId_LockScreenContainer))); 324 EXPECT_TRUE(wm::IsActiveWindow(lock.get())); 325 e1.ClickLeftButton(); 326 EXPECT_EQ(1, lock_delegate->mouse_presses()); 327 328 // Make sure that a modal container created by the lock screen can still 329 // receive mouse events. 330 EventTestWindow* lock_modal_delegate = new EventTestWindow(true); 331 aura::Window* lock_modal = 332 lock_modal_delegate->OpenTestWindowWithParent(lock.get()); 333 EXPECT_TRUE(wm::IsActiveWindow(lock_modal)); 334 e1.ClickLeftButton(); 335 // Verify that none of the other containers received any more mouse presses. 336 EXPECT_EQ(1, lock_modal_delegate->mouse_presses()); 337 EXPECT_EQ(1, lock_delegate->mouse_presses()); 338 EXPECT_EQ(1, main_delegate->mouse_presses()); 339 EXPECT_EQ(1, transient_delegate->mouse_presses()); 340 UnblockUserSession(); 341 } 342 } 343 344 // Makes sure we don't crash if a modal window is shown while the parent window 345 // is hidden. 346 TEST_F(SystemModalContainerLayoutManagerTest, ShowModalWhileHidden) { 347 // Hide the lock screen. 348 Shell::GetPrimaryRootWindowController()->GetContainer( 349 internal::kShellWindowId_SystemModalContainer)->layer()->SetOpacity(0); 350 351 // Create a modal window. 352 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false)); 353 scoped_ptr<aura::Window> modal_window( 354 OpenTestWindowWithParent(parent.get(), true)); 355 parent->Show(); 356 modal_window->Show(); 357 } 358 359 // Verifies we generate a capture lost when showing a modal window. 360 TEST_F(SystemModalContainerLayoutManagerTest, ChangeCapture) { 361 views::Widget* widget = views::Widget::CreateWindowWithContext( 362 new TestWindow(false), CurrentContext()); 363 scoped_ptr<aura::Window> widget_window(widget->GetNativeView()); 364 views::test::CaptureTrackingView* view = new views::test::CaptureTrackingView; 365 widget->GetContentsView()->AddChildView(view); 366 view->SetBoundsRect(widget->GetContentsView()->bounds()); 367 widget->Show(); 368 369 gfx::Point center(view->width() / 2, view->height() / 2); 370 views::View::ConvertPointToScreen(view, ¢er); 371 aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), center); 372 generator.PressLeftButton(); 373 EXPECT_TRUE(view->got_press()); 374 scoped_ptr<aura::Window> modal_window( 375 OpenTestWindowWithParent(widget->GetNativeView(), true)); 376 modal_window->Show(); 377 EXPECT_TRUE(view->got_capture_lost()); 378 } 379 380 // Verifies that the window gets moved into the visible screen area upon screen 381 // resize. 382 TEST_F(SystemModalContainerLayoutManagerTest, KeepVisible) { 383 GetModalContainer()->SetBounds(gfx::Rect(0, 0, 1024, 768)); 384 scoped_ptr<aura::Window> main(OpenTestWindowWithParent(GetModalContainer(), 385 true)); 386 main->SetBounds(gfx::Rect(924, 668, 100, 100)); 387 // We set now the bounds of the root window to something new which will 388 // Then trigger the repos operation. 389 GetModalContainer()->SetBounds(gfx::Rect(0, 0, 800, 600)); 390 391 gfx::Rect bounds = main->bounds(); 392 EXPECT_EQ(bounds, gfx::Rect(700, 500, 100, 100)); 393 } 394 395 TEST_F(SystemModalContainerLayoutManagerTest, ShowNormalBackgroundOrLocked) { 396 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false)); 397 scoped_ptr<aura::Window> modal_window( 398 OpenTestWindowWithParent(parent.get(), true)); 399 parent->Show(); 400 modal_window->Show(); 401 402 // Normal system modal window. Shows normal system modal background and not 403 // locked. 404 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 405 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds()); 406 407 TestWindow::CloseTestWindow(modal_window.release()); 408 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds()); 409 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds()); 410 411 for (int block_reason = FIRST_BLOCK_REASON; 412 block_reason < NUMBER_OF_BLOCK_REASONS; 413 ++block_reason) { 414 // Normal system modal window while blocked. Shows blocked system modal 415 // background. 416 BlockUserSession(static_cast<UserSessionBlockReason>(block_reason)); 417 scoped_ptr<aura::Window> lock_parent(OpenTestWindowWithParent( 418 Shell::GetPrimaryRootWindowController()->GetContainer( 419 ash::internal::kShellWindowId_LockScreenContainer), 420 false)); 421 scoped_ptr<aura::Window> lock_modal_window(OpenTestWindowWithParent( 422 lock_parent.get(), true)); 423 lock_parent->Show(); 424 lock_modal_window->Show(); 425 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds()); 426 EXPECT_TRUE(AllRootWindowsHaveLockedModalBackgrounds()); 427 TestWindow::CloseTestWindow(lock_modal_window.release()); 428 429 // Normal system modal window while blocked, but it belongs to the normal 430 // window. Shouldn't show blocked system modal background, but normal. 431 scoped_ptr<aura::Window> modal_window( 432 OpenTestWindowWithParent(parent.get(), true)); 433 modal_window->Show(); 434 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 435 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds()); 436 TestWindow::CloseTestWindow(modal_window.release()); 437 UnblockUserSession(); 438 // Here we should check the behavior of the locked system modal dialog when 439 // unlocked, but such case isn't handled very well right now. 440 // See crbug.com/157660 441 // TODO(mukai): add the test case when the bug is fixed. 442 } 443 } 444 445 TEST_F(SystemModalContainerLayoutManagerTest, MultiDisplays) { 446 if (!SupportsMultipleDisplays()) 447 return; 448 449 UpdateDisplay("500x500,500x500"); 450 451 scoped_ptr<aura::Window> normal(OpenToplevelTestWindow(false)); 452 normal->SetBounds(gfx::Rect(100, 100, 50, 50)); 453 454 Shell::RootWindowList root_windows = Shell::GetAllRootWindows(); 455 EXPECT_EQ(2U, root_windows.size()); 456 aura::Window* container1 = Shell::GetContainer( 457 root_windows[0], ash::internal::kShellWindowId_SystemModalContainer); 458 aura::Window* container2 = Shell::GetContainer( 459 root_windows[1], ash::internal::kShellWindowId_SystemModalContainer); 460 461 scoped_ptr<aura::Window> modal1( 462 OpenTestWindowWithParent(container1, true)); 463 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 464 EXPECT_TRUE(wm::IsActiveWindow(modal1.get())); 465 466 scoped_ptr<aura::Window> modal11( 467 OpenTestWindowWithParent(container1, true)); 468 EXPECT_TRUE(wm::IsActiveWindow(modal11.get())); 469 470 scoped_ptr<aura::Window> modal2( 471 OpenTestWindowWithParent(container2, true)); 472 EXPECT_TRUE(wm::IsActiveWindow(modal2.get())); 473 474 // Sanity check if they're on the correct containers. 475 EXPECT_EQ(container1, modal1->parent()); 476 EXPECT_EQ(container1, modal11->parent()); 477 EXPECT_EQ(container2, modal2->parent()); 478 479 TestWindow::CloseTestWindow(modal2.release()); 480 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 481 EXPECT_TRUE(wm::IsActiveWindow(modal11.get())); 482 483 TestWindow::CloseTestWindow(modal11.release()); 484 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 485 EXPECT_TRUE(wm::IsActiveWindow(modal1.get())); 486 487 UpdateDisplay("500x500"); 488 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 489 EXPECT_TRUE(wm::IsActiveWindow(modal1.get())); 490 491 UpdateDisplay("500x500,600x600"); 492 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds()); 493 EXPECT_TRUE(wm::IsActiveWindow(modal1.get())); 494 495 // No more modal screen. 496 modal1->Hide(); 497 TestWindow::CloseTestWindow(modal1.release()); 498 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds()); 499 EXPECT_TRUE(wm::IsActiveWindow(normal.get())); 500 } 501 502 } // namespace test 503 } // namespace ash 504