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