Home | History | Annotate | Download | only in wm
      1 // Copyright 2013 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/solo_window_tracker.h"
      6 
      7 #include "ash/ash_constants.h"
      8 #include "ash/ash_switches.h"
      9 #include "ash/root_window_controller.h"
     10 #include "ash/screen_ash.h"
     11 #include "ash/shell.h"
     12 #include "ash/shell_window_ids.h"
     13 #include "ash/test/ash_test_base.h"
     14 #include "ash/wm/window_resizer.h"
     15 #include "ash/wm/window_state.h"
     16 #include "base/memory/scoped_ptr.h"
     17 #include "ui/aura/client/aura_constants.h"
     18 #include "ui/aura/root_window.h"
     19 #include "ui/aura/test/event_generator.h"
     20 #include "ui/aura/window.h"
     21 #include "ui/aura/window_observer.h"
     22 #include "ui/base/hit_test.h"
     23 #include "ui/gfx/screen.h"
     24 
     25 namespace ash {
     26 
     27 namespace {
     28 
     29 class WindowRepaintChecker : public aura::WindowObserver {
     30  public:
     31   explicit WindowRepaintChecker(aura::Window* window)
     32       : window_(window),
     33         is_paint_scheduled_(false) {
     34     window_->AddObserver(this);
     35   }
     36 
     37   virtual ~WindowRepaintChecker() {
     38     if (window_)
     39       window_->RemoveObserver(this);
     40   }
     41 
     42   bool IsPaintScheduledAndReset() {
     43     bool result = is_paint_scheduled_;
     44     is_paint_scheduled_ = false;
     45     return result;
     46   }
     47 
     48  private:
     49   // aura::WindowObserver overrides:
     50   virtual void OnWindowPaintScheduled(aura::Window* window,
     51                                       const gfx::Rect& region) OVERRIDE {
     52     is_paint_scheduled_ = true;
     53   }
     54   virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {
     55     DCHECK_EQ(window_, window);
     56     window_ = NULL;
     57   }
     58 
     59   aura::Window* window_;
     60   bool is_paint_scheduled_;
     61 
     62   DISALLOW_COPY_AND_ASSIGN(WindowRepaintChecker);
     63 };
     64 
     65 }  // namespace
     66 
     67 class SoloWindowTrackerTest : public test::AshTestBase {
     68  public:
     69   SoloWindowTrackerTest() {
     70   }
     71   virtual ~SoloWindowTrackerTest() {
     72   }
     73 
     74   // Helpers methods to create test windows in the primary root window.
     75   aura::Window* CreateWindowInPrimary() {
     76     aura::Window* window = new aura::Window(NULL);
     77     window->SetType(aura::client::WINDOW_TYPE_NORMAL);
     78     window->Init(ui::LAYER_TEXTURED);
     79     window->SetBounds(gfx::Rect(100, 100));
     80     ParentWindowInPrimaryRootWindow(window);
     81     return window;
     82   }
     83   aura::Window* CreateAlwaysOnTopWindowInPrimary() {
     84     aura::Window* window = new aura::Window(NULL);
     85     window->SetType(aura::client::WINDOW_TYPE_NORMAL);
     86     window->Init(ui::LAYER_TEXTURED);
     87     window->SetBounds(gfx::Rect(100, 100));
     88     window->SetProperty(aura::client::kAlwaysOnTopKey, true);
     89     ParentWindowInPrimaryRootWindow(window);
     90     return window;
     91   }
     92   aura::Window* CreatePanelWindowInPrimary() {
     93     aura::Window* window = new aura::Window(NULL);
     94     window->SetType(aura::client::WINDOW_TYPE_PANEL);
     95     window->Init(ui::LAYER_TEXTURED);
     96     window->SetBounds(gfx::Rect(100, 100));
     97     ParentWindowInPrimaryRootWindow(window);
     98     return window;
     99   }
    100 
    101   // Drag |window| to the dock.
    102   void DockWindow(aura::Window* window) {
    103     // Because the tests use windows without delegates,
    104     // aura::test::EventGenerator cannot be used.
    105     gfx::Point drag_to =
    106         ash::ScreenAsh::GetDisplayBoundsInParent(window).top_right();
    107     scoped_ptr<WindowResizer> resizer(CreateWindowResizer(
    108         window,
    109         window->bounds().origin(),
    110         HTCAPTION,
    111         aura::client::WINDOW_MOVE_SOURCE_MOUSE));
    112     resizer->Drag(drag_to, 0);
    113     resizer->CompleteDrag(0);
    114     EXPECT_EQ(internal::kShellWindowId_DockedContainer,
    115               window->parent()->id());
    116   }
    117 
    118   // Drag |window| out of the dock.
    119   void UndockWindow(aura::Window* window) {
    120     gfx::Point drag_to =
    121         ash::ScreenAsh::GetDisplayWorkAreaBoundsInParent(window).top_right() -
    122         gfx::Vector2d(10, 0);
    123     scoped_ptr<WindowResizer> resizer(CreateWindowResizer(
    124         window,
    125         window->bounds().origin(),
    126         HTCAPTION,
    127         aura::client::WINDOW_MOVE_SOURCE_MOUSE));
    128     resizer->Drag(drag_to, 0);
    129     resizer->CompleteDrag(0);
    130     EXPECT_NE(internal::kShellWindowId_DockedContainer,
    131               window->parent()->id());
    132   }
    133 
    134   // Returns the primary display.
    135   gfx::Display GetPrimaryDisplay() {
    136     return ash::Shell::GetInstance()->GetScreen()->GetPrimaryDisplay();
    137   }
    138 
    139   // Returns the secondary display.
    140   gfx::Display GetSecondaryDisplay() {
    141     return ScreenAsh::GetSecondaryDisplay();
    142   }
    143 
    144   // Returns the window which uses the solo header, if any, on the primary
    145   // display.
    146   aura::Window* GetWindowWithSoloHeaderInPrimary() {
    147     return GetWindowWithSoloHeader(Shell::GetPrimaryRootWindow());
    148   }
    149 
    150   // Returns the window which uses the solo header, if any, in |root|.
    151   aura::Window* GetWindowWithSoloHeader(aura::Window* root) {
    152     SoloWindowTracker* solo_window_tracker =
    153         internal::GetRootWindowController(root)->solo_window_tracker();
    154     return solo_window_tracker ?
    155         solo_window_tracker->GetWindowWithSoloHeader() : NULL;
    156   }
    157 
    158  private:
    159   DISALLOW_COPY_AND_ASSIGN(SoloWindowTrackerTest);
    160 };
    161 
    162 TEST_F(SoloWindowTrackerTest, Basic) {
    163   scoped_ptr<aura::Window> w1(CreateWindowInPrimary());
    164   w1->Show();
    165 
    166   // We only have one window, so it should use a solo header.
    167   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    168 
    169   // Create a second window.
    170   scoped_ptr<aura::Window> w2(CreateWindowInPrimary());
    171   w2->Show();
    172 
    173   // Now there are two windows, so we should not use solo headers.
    174   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    175 
    176   // Hide one window.  Solo should be enabled.
    177   w2->Hide();
    178   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    179 
    180   // Show that window.  Solo should be disabled.
    181   w2->Show();
    182   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    183 
    184   // Minimize the first window.  Solo should be enabled.
    185   wm::GetWindowState(w1.get())->Minimize();
    186   EXPECT_EQ(w2.get(), GetWindowWithSoloHeaderInPrimary());
    187 
    188   // Close the minimized window.
    189   w1.reset();
    190   EXPECT_EQ(w2.get(), GetWindowWithSoloHeaderInPrimary());
    191 
    192   // Open an always-on-top window (which lives in a different container).
    193   scoped_ptr<aura::Window> w3(CreateAlwaysOnTopWindowInPrimary());
    194   w3->Show();
    195   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    196 
    197   // Close the always-on-top window.
    198   w3.reset();
    199   EXPECT_EQ(w2.get(), GetWindowWithSoloHeaderInPrimary());
    200 }
    201 
    202 // Test that docked windows never use the solo header and that the presence of a
    203 // docked window prevents all other windows from the using the solo window
    204 // header.
    205 TEST_F(SoloWindowTrackerTest, DockedWindow) {
    206   if (!switches::UseDockedWindows() || !SupportsHostWindowResize())
    207     return;
    208 
    209   scoped_ptr<aura::Window> w1(CreateWindowInPrimary());
    210   w1->Show();
    211   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    212 
    213   DockWindow(w1.get());
    214   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    215 
    216   UndockWindow(w1.get());
    217   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    218 
    219   scoped_ptr<aura::Window> w2(CreateWindowInPrimary());
    220   w2->Show();
    221   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    222 
    223   DockWindow(w2.get());
    224   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    225 
    226   wm::GetWindowState(w2.get())->Minimize();
    227   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    228 }
    229 
    230 // Panels should not "count" for computing solo window headers, and the panel
    231 // itself should never use the solo header.
    232 TEST_F(SoloWindowTrackerTest, Panel) {
    233   scoped_ptr<aura::Window> w1(CreateWindowInPrimary());
    234   w1->Show();
    235 
    236   // We only have one window, so it should use a solo header.
    237   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    238 
    239   // Create a panel window.
    240   scoped_ptr<aura::Window> w2(CreatePanelWindowInPrimary());
    241   w2->Show();
    242 
    243   // Despite two windows, the first window should still be considered "solo"
    244   // because panels aren't included in the computation.
    245   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    246 
    247   // Even after closing the first window, the panel is still not considered
    248   // solo.
    249   w1.reset();
    250   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    251 }
    252 
    253 // Modal dialogs should not use solo headers.
    254 TEST_F(SoloWindowTrackerTest, Modal) {
    255   scoped_ptr<aura::Window> w1(CreateWindowInPrimary());
    256   w1->Show();
    257 
    258   // We only have one window, so it should use a solo header.
    259   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    260 
    261   // Create a fake modal window.
    262   scoped_ptr<aura::Window> w2(CreateWindowInPrimary());
    263   w2->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW);
    264   w2->Show();
    265 
    266   // Despite two windows, the first window should still be considered "solo"
    267   // because modal windows aren't included in the computation.
    268   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    269 }
    270 
    271 // Constrained windows should not use solo headers.
    272 TEST_F(SoloWindowTrackerTest, Constrained) {
    273   scoped_ptr<aura::Window> w1(CreateWindowInPrimary());
    274   w1->Show();
    275 
    276   // We only have one window, so it should use a solo header.
    277   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    278 
    279   // Create a fake constrained window.
    280   scoped_ptr<aura::Window> w2(CreateWindowInPrimary());
    281   w2->SetProperty(aura::client::kConstrainedWindowKey, true);
    282   w2->Show();
    283 
    284   // Despite two windows, the first window should still be considered "solo"
    285   // because constrained windows aren't included in the computation.
    286   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    287 }
    288 
    289 // Non-drawing windows should not affect the solo computation.
    290 TEST_F(SoloWindowTrackerTest, NotDrawn) {
    291   aura::Window* w = CreateWindowInPrimary();
    292   w->Show();
    293 
    294   // We only have one window, so it should use a solo header.
    295   EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary());
    296 
    297   // Create non-drawing window similar to DragDropTracker.
    298   aura::Window* not_drawn = new aura::Window(NULL);
    299   not_drawn->SetType(aura::client::WINDOW_TYPE_NORMAL);
    300   not_drawn->Init(ui::LAYER_NOT_DRAWN);
    301   ParentWindowInPrimaryRootWindow(not_drawn);
    302   not_drawn->Show();
    303 
    304   // Despite two windows, the first window should still be considered "solo"
    305   // because non-drawing windows aren't included in the computation.
    306   EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary());
    307 }
    308 
    309 TEST_F(SoloWindowTrackerTest, MultiDisplay) {
    310   if (!SupportsMultipleDisplays())
    311     return;
    312 
    313   UpdateDisplay("1000x600,600x400");
    314 
    315   scoped_ptr<aura::Window> w1(CreateWindowInPrimary());
    316   w1->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
    317   w1->Show();
    318   WindowRepaintChecker checker1(w1.get());
    319   scoped_ptr<aura::Window> w2(CreateWindowInPrimary());
    320   w2->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
    321   w2->Show();
    322   WindowRepaintChecker checker2(w2.get());
    323 
    324   // Now there are two windows in the same display, so we should not use solo
    325   // headers.
    326   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    327   EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
    328 
    329   // Moves the second window to the secondary display.  Both w1/w2 should be
    330   // solo.
    331   w2->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100),
    332                         ScreenAsh::GetSecondaryDisplay());
    333   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    334   EXPECT_EQ(w2.get(), GetWindowWithSoloHeader(w2->GetRootWindow()));
    335   EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
    336   EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
    337 
    338   // Open two more windows in the primary display.
    339   scoped_ptr<aura::Window> w3(CreateWindowInPrimary());
    340   w3->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
    341   w3->Show();
    342   scoped_ptr<aura::Window> w4(CreateWindowInPrimary());
    343   w4->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
    344   w4->Show();
    345 
    346   // Because the primary display has three windows w1, w3, and w4, they
    347   // shouldn't be solo.  w2 should be solo.
    348   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    349   EXPECT_EQ(w2.get(), GetWindowWithSoloHeader(w2->GetRootWindow()));
    350   EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
    351 
    352   // Move w4 to the secondary display.  Now w2 shouldn't be solo anymore.
    353   w4->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100), GetSecondaryDisplay());
    354   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    355   EXPECT_EQ(NULL, GetWindowWithSoloHeader(w2->GetRootWindow()));
    356   EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
    357 
    358   // Moves w3 to the secondary display too.  Now w1 should be solo again.
    359   w3->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100), GetSecondaryDisplay());
    360   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    361   EXPECT_EQ(NULL, GetWindowWithSoloHeader(w2->GetRootWindow()));
    362   EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
    363 
    364   // Change w3's state to maximize.  Doesn't affect w1.
    365   wm::GetWindowState(w3.get())->Maximize();
    366   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    367   EXPECT_EQ(NULL, GetWindowWithSoloHeader(w2->GetRootWindow()));
    368 
    369   // Close w3 and w4.
    370   w3.reset();
    371   w4.reset();
    372   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    373   EXPECT_EQ(w2.get(), GetWindowWithSoloHeader(w2->GetRootWindow()));
    374   EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
    375 
    376   // Move w2 back to the primary display.
    377   w2->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
    378   EXPECT_EQ(w1->GetRootWindow(), w2->GetRootWindow());
    379   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    380   EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
    381   EXPECT_TRUE(checker2.IsPaintScheduledAndReset());
    382 
    383   // Close w2.
    384   w2.reset();
    385   EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary());
    386   EXPECT_TRUE(checker1.IsPaintScheduledAndReset());
    387 }
    388 
    389 TEST_F(SoloWindowTrackerTest, ChildWindowVisibility) {
    390   aura::Window* w = CreateWindowInPrimary();
    391   w->Show();
    392 
    393   // We only have one window, so it should use a solo header.
    394   EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary());
    395 
    396   // Create a child window. This should not affect the solo-ness of |w1|.
    397   aura::Window* child = new aura::Window(NULL);
    398   child->SetType(aura::client::WINDOW_TYPE_CONTROL);
    399   child->Init(ui::LAYER_TEXTURED);
    400   child->SetBounds(gfx::Rect(100, 100));
    401   w->AddChild(child);
    402   child->Show();
    403   EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary());
    404 
    405   // Changing the visibility of |child| should not affect the solo-ness of |w1|.
    406   child->Hide();
    407   EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary());
    408 }
    409 
    410 TEST_F(SoloWindowTrackerTest, CreateAndDeleteSingleWindow) {
    411   // Ensure that creating/deleting a window works well and doesn't cause
    412   // crashes.  See crbug.com/155634
    413   scoped_ptr<aura::Window> w(CreateWindowInPrimary());
    414   w->Show();
    415 
    416   // We only have one window, so it should use a solo header.
    417   EXPECT_EQ(w.get(), GetWindowWithSoloHeaderInPrimary());
    418 
    419   // Close the window.
    420   w.reset();
    421   EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary());
    422 
    423   // Recreate another window again.
    424   w.reset(CreateWindowInPrimary());
    425   w->Show();
    426   EXPECT_EQ(w.get(), GetWindowWithSoloHeaderInPrimary());
    427 }
    428 
    429 }  // namespace ash
    430