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.
5 #include "ash/wm/system_modal_container_layout_manager.h"
7 #include "ash/root_window_controller.h"
8 #include "ash/session_state_delegate.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"
29 aura::Window
* GetModalContainer() {
30 return Shell::GetPrimaryRootWindowController()->GetContainer(
31 ash::internal::kShellWindowId_SystemModalContainer
);
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
) {
41 static_cast<internal::SystemModalContainerLayoutManager
*>(
42 (*iter
)->layout_manager())->has_modal_background();
44 return has_modal_screen
;
47 bool AllRootWindowsHaveLockedModalBackgrounds() {
48 return AllRootWindowsHaveModalBackgroundsForContainer(
49 internal::kShellWindowId_LockSystemModalContainer
);
52 bool AllRootWindowsHaveModalBackgrounds() {
53 return AllRootWindowsHaveModalBackgroundsForContainer(
54 internal::kShellWindowId_SystemModalContainer
);
57 class TestWindow
: public views::WidgetDelegateView
{
59 explicit TestWindow(bool modal
) : modal_(modal
) {}
60 virtual ~TestWindow() {}
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();
68 // Overridden from views::View:
69 virtual gfx::Size
GetPreferredSize() OVERRIDE
{
70 return gfx::Size(50, 50);
73 // Overridden from views::WidgetDelegate:
74 virtual views::View
* GetContentsView() OVERRIDE
{
77 virtual ui::ModalType
GetModalType() const OVERRIDE
{
78 return modal_
? ui::MODAL_TYPE_SYSTEM
: ui::MODAL_TYPE_NONE
;
84 DISALLOW_COPY_AND_ASSIGN(TestWindow
);
87 class EventTestWindow
: public TestWindow
{
89 explicit EventTestWindow(bool modal
) : TestWindow(modal
),
91 virtual ~EventTestWindow() {}
93 aura::Window
* OpenTestWindowWithContext(aura::RootWindow
* context
) {
94 views::Widget
* widget
=
95 views::Widget::CreateWindowWithContext(this, context
);
97 return widget
->GetNativeView();
100 aura::Window
* OpenTestWindowWithParent(aura::Window
* parent
) {
102 views::Widget
* widget
=
103 views::Widget::CreateWindowWithParent(this, parent
);
105 return widget
->GetNativeView();
108 // Overridden from views::View:
109 virtual bool OnMousePressed(const ui::MouseEvent
& event
) OVERRIDE
{
114 int mouse_presses() const { return mouse_presses_
; }
118 DISALLOW_COPY_AND_ASSIGN(EventTestWindow
);
121 class TransientWindowObserver
: public aura::WindowObserver
{
123 TransientWindowObserver() : destroyed_(false) {}
124 virtual ~TransientWindowObserver() {}
126 bool destroyed() const { return destroyed_
; }
128 // Overridden from aura::WindowObserver:
129 virtual void OnWindowDestroyed(aura::Window
* window
) OVERRIDE
{
136 DISALLOW_COPY_AND_ASSIGN(TransientWindowObserver
);
141 class SystemModalContainerLayoutManagerTest
: public AshTestBase
{
143 aura::Window
* OpenToplevelTestWindow(bool modal
) {
144 views::Widget
* widget
= views::Widget::CreateWindowWithContext(
145 new TestWindow(modal
), CurrentContext());
147 return widget
->GetNativeView();
150 aura::Window
* OpenTestWindowWithParent(aura::Window
* parent
, bool modal
) {
151 views::Widget
* widget
=
152 views::Widget::CreateWindowWithParent(new TestWindow(modal
), parent
);
154 return widget
->GetNativeView();
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
);
164 EXPECT_EQ(parent
.get(), transient
->transient_parent());
165 EXPECT_EQ(parent
->parent(), transient
->parent());
167 // The transient should be destroyed with its parent.
169 EXPECT_TRUE(destruction_observer
.destroyed());
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);
178 TransientWindowObserver do1
;
179 t1
->AddObserver(&do1
);
181 EXPECT_EQ(parent
.get(), t1
->transient_parent());
182 EXPECT_EQ(GetModalContainer(), t1
->parent());
184 // t1 should now be active.
185 EXPECT_TRUE(wm::IsActiveWindow(t1
));
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
));
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
);
197 EXPECT_TRUE(wm::IsActiveWindow(t2
));
199 EXPECT_EQ(t1
, t2
->transient_parent());
200 EXPECT_EQ(GetModalContainer(), t2
->parent());
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
));
207 // Both transients should be destroyed with parent.
209 EXPECT_TRUE(do1
.destroyed());
210 EXPECT_TRUE(do2
.destroyed());
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
);
220 EXPECT_EQ(NULL
, t1
->transient_parent());
221 EXPECT_EQ(GetModalContainer(), t1
->parent());
223 // t1 should now be active.
224 EXPECT_TRUE(wm::IsActiveWindow(t1
.get()));
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()));
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
);
237 EXPECT_TRUE(wm::IsActiveWindow(t2
));
239 EXPECT_EQ(t1
, t2
->transient_parent());
240 EXPECT_EQ(GetModalContainer(), t2
->parent());
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
));
247 // Both transients should be destroyed with parent.
249 EXPECT_TRUE(do1
.destroyed());
250 EXPECT_TRUE(do2
.destroyed());
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
258 #define MAYBE_CanActivateAfterEndModalSession CanActivateAfterEndModalSession
260 // Tests that we can activate an unrelated window after a modal window is closed
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()));
270 scoped_ptr
<aura::Window
> transient(
271 OpenTestWindowWithParent(parent
.get(), true));
272 // t1 should now be active.
273 EXPECT_TRUE(wm::IsActiveWindow(transient
.get()));
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()));
280 // Now close the transient.
282 TestWindow::CloseTestWindow(transient
.release());
284 base::RunLoop().RunUntilIdle();
286 // parent should now be active again.
287 EXPECT_TRUE(wm::IsActiveWindow(parent
.get()));
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()));
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());
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());
314 for (int block_reason
= FIRST_BLOCK_REASON
;
315 block_reason
< NUMBER_OF_BLOCK_REASONS
;
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());
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();
344 // Makes sure we don't crash if a modal window is shown while the parent window
346 TEST_F(SystemModalContainerLayoutManagerTest
, ShowModalWhileHidden
) {
347 // Hide the lock screen.
348 Shell::GetPrimaryRootWindowController()->GetContainer(
349 internal::kShellWindowId_SystemModalContainer
)->layer()->SetOpacity(0);
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));
356 modal_window
->Show();
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());
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());
380 // Verifies that the window gets moved into the visible screen area upon screen
382 TEST_F(SystemModalContainerLayoutManagerTest
, KeepVisible
) {
383 GetModalContainer()->SetBounds(gfx::Rect(0, 0, 1024, 768));
384 scoped_ptr
<aura::Window
> main(OpenTestWindowWithParent(GetModalContainer(),
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));
391 gfx::Rect bounds
= main
->bounds();
392 EXPECT_EQ(bounds
, gfx::Rect(700, 500, 100, 100));
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));
400 modal_window
->Show();
402 // Normal system modal window. Shows normal system modal background and not
404 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
405 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
407 TestWindow::CloseTestWindow(modal_window
.release());
408 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
409 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
411 for (int block_reason
= FIRST_BLOCK_REASON
;
412 block_reason
< NUMBER_OF_BLOCK_REASONS
;
414 // Normal system modal window while blocked. Shows blocked system modal
416 BlockUserSession(static_cast<UserSessionBlockReason
>(block_reason
));
417 scoped_ptr
<aura::Window
> lock_parent(OpenTestWindowWithParent(
418 Shell::GetPrimaryRootWindowController()->GetContainer(
419 ash::internal::kShellWindowId_LockScreenContainer
),
421 scoped_ptr
<aura::Window
> lock_modal_window(OpenTestWindowWithParent(
422 lock_parent
.get(), true));
424 lock_modal_window
->Show();
425 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
426 EXPECT_TRUE(AllRootWindowsHaveLockedModalBackgrounds());
427 TestWindow::CloseTestWindow(lock_modal_window
.release());
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.
445 TEST_F(SystemModalContainerLayoutManagerTest
, MultiDisplays
) {
446 if (!SupportsMultipleDisplays())
449 UpdateDisplay("500x500,500x500");
451 scoped_ptr
<aura::Window
> normal(OpenToplevelTestWindow(false));
452 normal
->SetBounds(gfx::Rect(100, 100, 50, 50));
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
);
461 scoped_ptr
<aura::Window
> modal1(
462 OpenTestWindowWithParent(container1
, true));
463 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
464 EXPECT_TRUE(wm::IsActiveWindow(modal1
.get()));
466 scoped_ptr
<aura::Window
> modal11(
467 OpenTestWindowWithParent(container1
, true));
468 EXPECT_TRUE(wm::IsActiveWindow(modal11
.get()));
470 scoped_ptr
<aura::Window
> modal2(
471 OpenTestWindowWithParent(container2
, true));
472 EXPECT_TRUE(wm::IsActiveWindow(modal2
.get()));
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());
479 TestWindow::CloseTestWindow(modal2
.release());
480 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
481 EXPECT_TRUE(wm::IsActiveWindow(modal11
.get()));
483 TestWindow::CloseTestWindow(modal11
.release());
484 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
485 EXPECT_TRUE(wm::IsActiveWindow(modal1
.get()));
487 UpdateDisplay("500x500");
488 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
489 EXPECT_TRUE(wm::IsActiveWindow(modal1
.get()));
491 UpdateDisplay("500x500,600x600");
492 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
493 EXPECT_TRUE(wm::IsActiveWindow(modal1
.get()));
495 // No more modal screen.
497 TestWindow::CloseTestWindow(modal1
.release());
498 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
499 EXPECT_TRUE(wm::IsActiveWindow(normal
.get()));