Home | History | Annotate | Download | only in wm
      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, &center);
    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