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.
5 #include "ash/wm/solo_window_tracker.h"
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"
29 class WindowRepaintChecker
: public aura::WindowObserver
{
31 explicit WindowRepaintChecker(aura::Window
* window
)
33 is_paint_scheduled_(false) {
34 window_
->AddObserver(this);
37 virtual ~WindowRepaintChecker() {
39 window_
->RemoveObserver(this);
42 bool IsPaintScheduledAndReset() {
43 bool result
= is_paint_scheduled_
;
44 is_paint_scheduled_
= false;
49 // aura::WindowObserver overrides:
50 virtual void OnWindowPaintScheduled(aura::Window
* window
,
51 const gfx::Rect
& region
) OVERRIDE
{
52 is_paint_scheduled_
= true;
54 virtual void OnWindowDestroyed(aura::Window
* window
) OVERRIDE
{
55 DCHECK_EQ(window_
, window
);
59 aura::Window
* window_
;
60 bool is_paint_scheduled_
;
62 DISALLOW_COPY_AND_ASSIGN(WindowRepaintChecker
);
67 class SoloWindowTrackerTest
: public test::AshTestBase
{
69 SoloWindowTrackerTest() {
71 virtual ~SoloWindowTrackerTest() {
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(ui::wm::WINDOW_TYPE_NORMAL
);
78 window
->Init(aura::WINDOW_LAYER_TEXTURED
);
79 window
->SetBounds(gfx::Rect(100, 100));
80 ParentWindowInPrimaryRootWindow(window
);
83 aura::Window
* CreateAlwaysOnTopWindowInPrimary() {
84 aura::Window
* window
= new aura::Window(NULL
);
85 window
->SetType(ui::wm::WINDOW_TYPE_NORMAL
);
86 window
->Init(aura::WINDOW_LAYER_TEXTURED
);
87 window
->SetBounds(gfx::Rect(100, 100));
88 window
->SetProperty(aura::client::kAlwaysOnTopKey
, true);
89 ParentWindowInPrimaryRootWindow(window
);
92 aura::Window
* CreatePanelWindowInPrimary() {
93 aura::Window
* window
= new aura::Window(NULL
);
94 window
->SetType(ui::wm::WINDOW_TYPE_PANEL
);
95 window
->Init(aura::WINDOW_LAYER_TEXTURED
);
96 window
->SetBounds(gfx::Rect(100, 100));
97 ParentWindowInPrimaryRootWindow(window
);
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.
106 ash::ScreenUtil::GetDisplayBoundsInParent(window
).top_right();
107 scoped_ptr
<WindowResizer
> resizer(CreateWindowResizer(
109 window
->bounds().origin(),
111 aura::client::WINDOW_MOVE_SOURCE_MOUSE
));
112 resizer
->Drag(drag_to
, 0);
113 resizer
->CompleteDrag();
114 EXPECT_EQ(internal::kShellWindowId_DockedContainer
,
115 window
->parent()->id());
118 // Drag |window| out of the dock.
119 void UndockWindow(aura::Window
* window
) {
121 ash::ScreenUtil::GetDisplayWorkAreaBoundsInParent(window
).top_right() -
122 gfx::Vector2d(10, 0);
123 scoped_ptr
<WindowResizer
> resizer(CreateWindowResizer(
125 window
->bounds().origin(),
127 aura::client::WINDOW_MOVE_SOURCE_MOUSE
));
128 resizer
->Drag(drag_to
, 0);
129 resizer
->CompleteDrag();
130 EXPECT_NE(internal::kShellWindowId_DockedContainer
,
131 window
->parent()->id());
134 // Returns the primary display.
135 gfx::Display
GetPrimaryDisplay() {
136 return ash::Shell::GetInstance()->GetScreen()->GetPrimaryDisplay();
139 // Returns the secondary display.
140 gfx::Display
GetSecondaryDisplay() {
141 return ScreenUtil::GetSecondaryDisplay();
144 // Returns the window which uses the solo header, if any, on the primary
146 aura::Window
* GetWindowWithSoloHeaderInPrimary() {
147 return GetWindowWithSoloHeader(Shell::GetPrimaryRootWindow());
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
;
159 DISALLOW_COPY_AND_ASSIGN(SoloWindowTrackerTest
);
162 TEST_F(SoloWindowTrackerTest
, Basic
) {
163 scoped_ptr
<aura::Window
> w1(CreateWindowInPrimary());
166 // We only have one window, so it should use a solo header.
167 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
169 // Create a second window.
170 scoped_ptr
<aura::Window
> w2(CreateWindowInPrimary());
173 // Now there are two windows, so we should not use solo headers.
174 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
176 // Hide one window. Solo should be enabled.
178 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
180 // Show that window. Solo should be disabled.
182 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
184 // Minimize the first window. Solo should be enabled.
185 wm::GetWindowState(w1
.get())->Minimize();
186 EXPECT_EQ(w2
.get(), GetWindowWithSoloHeaderInPrimary());
188 // Close the minimized window.
190 EXPECT_EQ(w2
.get(), GetWindowWithSoloHeaderInPrimary());
192 // Open an always-on-top window (which lives in a different container).
193 scoped_ptr
<aura::Window
> w3(CreateAlwaysOnTopWindowInPrimary());
195 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
197 // Close the always-on-top window.
199 EXPECT_EQ(w2
.get(), GetWindowWithSoloHeaderInPrimary());
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
205 TEST_F(SoloWindowTrackerTest
, DockedWindow
) {
206 if (!switches::UseDockedWindows() || !SupportsHostWindowResize())
209 scoped_ptr
<aura::Window
> w1(CreateWindowInPrimary());
211 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
213 DockWindow(w1
.get());
214 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
216 UndockWindow(w1
.get());
217 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
219 scoped_ptr
<aura::Window
> w2(CreateWindowInPrimary());
221 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
223 DockWindow(w2
.get());
224 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
226 wm::GetWindowState(w2
.get())->Minimize();
227 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
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());
236 // We only have one window, so it should use a solo header.
237 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
239 // Create a panel window.
240 scoped_ptr
<aura::Window
> w2(CreatePanelWindowInPrimary());
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());
247 // Even after closing the first window, the panel is still not considered
250 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
253 // Modal dialogs should not use solo headers.
254 TEST_F(SoloWindowTrackerTest
, Modal
) {
255 scoped_ptr
<aura::Window
> w1(CreateWindowInPrimary());
258 // We only have one window, so it should use a solo header.
259 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
261 // Create a fake modal window.
262 scoped_ptr
<aura::Window
> w2(CreateWindowInPrimary());
263 w2
->SetProperty(aura::client::kModalKey
, ui::MODAL_TYPE_WINDOW
);
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());
271 // Constrained windows should not use solo headers.
272 TEST_F(SoloWindowTrackerTest
, Constrained
) {
273 scoped_ptr
<aura::Window
> w1(CreateWindowInPrimary());
276 // We only have one window, so it should use a solo header.
277 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
279 // Create a fake constrained window.
280 scoped_ptr
<aura::Window
> w2(CreateWindowInPrimary());
281 w2
->SetProperty(aura::client::kConstrainedWindowKey
, true);
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());
289 // Non-drawing windows should not affect the solo computation.
290 TEST_F(SoloWindowTrackerTest
, NotDrawn
) {
291 aura::Window
* w
= CreateWindowInPrimary();
294 // We only have one window, so it should use a solo header.
295 EXPECT_EQ(w
, GetWindowWithSoloHeaderInPrimary());
297 // Create non-drawing window similar to DragDropTracker.
298 aura::Window
* not_drawn
= new aura::Window(NULL
);
299 not_drawn
->SetType(ui::wm::WINDOW_TYPE_NORMAL
);
300 not_drawn
->Init(aura::WINDOW_LAYER_NOT_DRAWN
);
301 ParentWindowInPrimaryRootWindow(not_drawn
);
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());
309 TEST_F(SoloWindowTrackerTest
, MultiDisplay
) {
310 if (!SupportsMultipleDisplays())
313 UpdateDisplay("1000x600,600x400");
315 scoped_ptr
<aura::Window
> w1(CreateWindowInPrimary());
316 w1
->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
318 WindowRepaintChecker
checker1(w1
.get());
319 scoped_ptr
<aura::Window
> w2(CreateWindowInPrimary());
320 w2
->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
322 WindowRepaintChecker
checker2(w2
.get());
324 // Now there are two windows in the same display, so we should not use solo
326 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
327 EXPECT_TRUE(checker1
.IsPaintScheduledAndReset());
329 // Moves the second window to the secondary display. Both w1/w2 should be
331 w2
->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100),
332 ScreenUtil::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());
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());
342 scoped_ptr
<aura::Window
> w4(CreateWindowInPrimary());
343 w4
->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay());
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());
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());
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());
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()));
372 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
373 EXPECT_EQ(w2
.get(), GetWindowWithSoloHeader(w2
->GetRootWindow()));
374 EXPECT_TRUE(checker2
.IsPaintScheduledAndReset());
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());
385 EXPECT_EQ(w1
.get(), GetWindowWithSoloHeaderInPrimary());
386 EXPECT_TRUE(checker1
.IsPaintScheduledAndReset());
389 TEST_F(SoloWindowTrackerTest
, ChildWindowVisibility
) {
390 aura::Window
* w
= CreateWindowInPrimary();
393 // We only have one window, so it should use a solo header.
394 EXPECT_EQ(w
, GetWindowWithSoloHeaderInPrimary());
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(ui::wm::WINDOW_TYPE_CONTROL
);
399 child
->Init(aura::WINDOW_LAYER_TEXTURED
);
400 child
->SetBounds(gfx::Rect(100, 100));
403 EXPECT_EQ(w
, GetWindowWithSoloHeaderInPrimary());
405 // Changing the visibility of |child| should not affect the solo-ness of |w1|.
407 EXPECT_EQ(w
, GetWindowWithSoloHeaderInPrimary());
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());
416 // We only have one window, so it should use a solo header.
417 EXPECT_EQ(w
.get(), GetWindowWithSoloHeaderInPrimary());
421 EXPECT_EQ(NULL
, GetWindowWithSoloHeaderInPrimary());
423 // Recreate another window again.
424 w
.reset(CreateWindowInPrimary());
426 EXPECT_EQ(w
.get(), GetWindowWithSoloHeaderInPrimary());