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::Window* 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 // Tests that we can activate an unrelated window after a modal window is closed
255 TEST_F(SystemModalContainerLayoutManagerTest, CanActivateAfterEndModalSession) {
256 scoped_ptr<aura::Window> unrelated(OpenToplevelTestWindow(false));
257 unrelated->SetBounds(gfx::Rect(100, 100, 50, 50));
258 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
259 // parent should be active.
260 EXPECT_TRUE(wm::IsActiveWindow(parent.get()));
262 scoped_ptr<aura::Window> transient(
263 OpenTestWindowWithParent(parent.get(), true));
264 // t1 should now be active.
265 EXPECT_TRUE(wm::IsActiveWindow(transient.get()));
267 // Attempting to click the parent should result in no activation change.
268 aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), parent.get());
269 e1.ClickLeftButton();
270 EXPECT_TRUE(wm::IsActiveWindow(transient.get()));
272 // Now close the transient.
274 TestWindow::CloseTestWindow(transient.release());
276 base::RunLoop().RunUntilIdle();
278 // parent should now be active again.
279 EXPECT_TRUE(wm::IsActiveWindow(parent.get()));
281 // Attempting to click unrelated should activate it.
282 aura::test::EventGenerator e2(Shell::GetPrimaryRootWindow(), unrelated.get());
283 e2.ClickLeftButton();
284 EXPECT_TRUE(wm::IsActiveWindow(unrelated.get()));
287 TEST_F(SystemModalContainerLayoutManagerTest, EventFocusContainers) {
288 // Create a normal window and attempt to receive a click event.
289 EventTestWindow* main_delegate = new EventTestWindow(false);
290 scoped_ptr<aura::Window> main(
291 main_delegate->OpenTestWindowWithContext(CurrentContext()));
292 EXPECT_TRUE(wm::IsActiveWindow(main.get()));
293 aura::test::EventGenerator e1(Shell::GetPrimaryRootWindow(), main.get());
294 e1.ClickLeftButton();
295 EXPECT_EQ(1, main_delegate->mouse_presses());
297 // Create a modal window for the main window and verify that the main window
298 // no longer receives mouse events.
299 EventTestWindow* transient_delegate = new EventTestWindow(true);
300 aura::Window* transient =
301 transient_delegate->OpenTestWindowWithParent(main.get());
302 EXPECT_TRUE(wm::IsActiveWindow(transient));
303 e1.ClickLeftButton();
304 EXPECT_EQ(1, transient_delegate->mouse_presses());
306 for (int block_reason = FIRST_BLOCK_REASON;
307 block_reason < NUMBER_OF_BLOCK_REASONS;
309 // Create a window in the lock screen container and ensure that it receives
310 // the mouse event instead of the modal window (crbug.com/110920).
311 BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
312 EventTestWindow* lock_delegate = new EventTestWindow(false);
313 scoped_ptr<aura::Window> lock(lock_delegate->OpenTestWindowWithParent(
314 Shell::GetPrimaryRootWindowController()->GetContainer(
315 ash::internal::kShellWindowId_LockScreenContainer)));
316 EXPECT_TRUE(wm::IsActiveWindow(lock.get()));
317 e1.ClickLeftButton();
318 EXPECT_EQ(1, lock_delegate->mouse_presses());
320 // Make sure that a modal container created by the lock screen can still
321 // receive mouse events.
322 EventTestWindow* lock_modal_delegate = new EventTestWindow(true);
323 aura::Window* lock_modal =
324 lock_modal_delegate->OpenTestWindowWithParent(lock.get());
325 EXPECT_TRUE(wm::IsActiveWindow(lock_modal));
326 e1.ClickLeftButton();
327 // Verify that none of the other containers received any more mouse presses.
328 EXPECT_EQ(1, lock_modal_delegate->mouse_presses());
329 EXPECT_EQ(1, lock_delegate->mouse_presses());
330 EXPECT_EQ(1, main_delegate->mouse_presses());
331 EXPECT_EQ(1, transient_delegate->mouse_presses());
332 UnblockUserSession();
336 // Makes sure we don't crash if a modal window is shown while the parent window
338 TEST_F(SystemModalContainerLayoutManagerTest, ShowModalWhileHidden) {
339 // Hide the lock screen.
340 Shell::GetPrimaryRootWindowController()->GetContainer(
341 internal::kShellWindowId_SystemModalContainer)->layer()->SetOpacity(0);
343 // Create a modal window.
344 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
345 scoped_ptr<aura::Window> modal_window(
346 OpenTestWindowWithParent(parent.get(), true));
348 modal_window->Show();
351 // Verifies we generate a capture lost when showing a modal window.
352 TEST_F(SystemModalContainerLayoutManagerTest, ChangeCapture) {
353 views::Widget* widget = views::Widget::CreateWindowWithContext(
354 new TestWindow(false), CurrentContext());
355 scoped_ptr<aura::Window> widget_window(widget->GetNativeView());
356 views::test::CaptureTrackingView* view = new views::test::CaptureTrackingView;
357 widget->GetContentsView()->AddChildView(view);
358 view->SetBoundsRect(widget->GetContentsView()->bounds());
361 gfx::Point center(view->width() / 2, view->height() / 2);
362 views::View::ConvertPointToScreen(view, ¢er);
363 aura::test::EventGenerator generator(Shell::GetPrimaryRootWindow(), center);
364 generator.PressLeftButton();
365 EXPECT_TRUE(view->got_press());
366 scoped_ptr<aura::Window> modal_window(
367 OpenTestWindowWithParent(widget->GetNativeView(), true));
368 modal_window->Show();
369 EXPECT_TRUE(view->got_capture_lost());
372 // Verifies that the window gets moved into the visible screen area upon screen
374 TEST_F(SystemModalContainerLayoutManagerTest, KeepVisible) {
375 GetModalContainer()->SetBounds(gfx::Rect(0, 0, 1024, 768));
376 scoped_ptr<aura::Window> main(OpenTestWindowWithParent(GetModalContainer(),
378 main->SetBounds(gfx::Rect(924, 668, 100, 100));
379 // We set now the bounds of the root window to something new which will
380 // Then trigger the repos operation.
381 GetModalContainer()->SetBounds(gfx::Rect(0, 0, 800, 600));
383 gfx::Rect bounds = main->bounds();
384 EXPECT_EQ(bounds, gfx::Rect(700, 500, 100, 100));
387 TEST_F(SystemModalContainerLayoutManagerTest, ShowNormalBackgroundOrLocked) {
388 scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
389 scoped_ptr<aura::Window> modal_window(
390 OpenTestWindowWithParent(parent.get(), true));
392 modal_window->Show();
394 // Normal system modal window. Shows normal system modal background and not
396 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
397 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
399 TestWindow::CloseTestWindow(modal_window.release());
400 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
401 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
403 for (int block_reason = FIRST_BLOCK_REASON;
404 block_reason < NUMBER_OF_BLOCK_REASONS;
406 // Normal system modal window while blocked. Shows blocked system modal
408 BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
409 scoped_ptr<aura::Window> lock_parent(OpenTestWindowWithParent(
410 Shell::GetPrimaryRootWindowController()->GetContainer(
411 ash::internal::kShellWindowId_LockScreenContainer),
413 scoped_ptr<aura::Window> lock_modal_window(OpenTestWindowWithParent(
414 lock_parent.get(), true));
416 lock_modal_window->Show();
417 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
418 EXPECT_TRUE(AllRootWindowsHaveLockedModalBackgrounds());
419 TestWindow::CloseTestWindow(lock_modal_window.release());
421 // Normal system modal window while blocked, but it belongs to the normal
422 // window. Shouldn't show blocked system modal background, but normal.
423 scoped_ptr<aura::Window> modal_window(
424 OpenTestWindowWithParent(parent.get(), true));
425 modal_window->Show();
426 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
427 EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
428 TestWindow::CloseTestWindow(modal_window.release());
429 UnblockUserSession();
430 // Here we should check the behavior of the locked system modal dialog when
431 // unlocked, but such case isn't handled very well right now.
432 // See crbug.com/157660
433 // TODO(mukai): add the test case when the bug is fixed.
437 TEST_F(SystemModalContainerLayoutManagerTest, MultiDisplays) {
438 if (!SupportsMultipleDisplays())
441 UpdateDisplay("500x500,500x500");
443 scoped_ptr<aura::Window> normal(OpenToplevelTestWindow(false));
444 normal->SetBounds(gfx::Rect(100, 100, 50, 50));
446 Shell::RootWindowList root_windows = Shell::GetAllRootWindows();
447 EXPECT_EQ(2U, root_windows.size());
448 aura::Window* container1 = Shell::GetContainer(
449 root_windows[0], ash::internal::kShellWindowId_SystemModalContainer);
450 aura::Window* container2 = Shell::GetContainer(
451 root_windows[1], ash::internal::kShellWindowId_SystemModalContainer);
453 scoped_ptr<aura::Window> modal1(
454 OpenTestWindowWithParent(container1, true));
455 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
456 EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
458 scoped_ptr<aura::Window> modal11(
459 OpenTestWindowWithParent(container1, true));
460 EXPECT_TRUE(wm::IsActiveWindow(modal11.get()));
462 scoped_ptr<aura::Window> modal2(
463 OpenTestWindowWithParent(container2, true));
464 EXPECT_TRUE(wm::IsActiveWindow(modal2.get()));
466 // Sanity check if they're on the correct containers.
467 EXPECT_EQ(container1, modal1->parent());
468 EXPECT_EQ(container1, modal11->parent());
469 EXPECT_EQ(container2, modal2->parent());
471 TestWindow::CloseTestWindow(modal2.release());
472 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
473 EXPECT_TRUE(wm::IsActiveWindow(modal11.get()));
475 TestWindow::CloseTestWindow(modal11.release());
476 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
477 EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
479 UpdateDisplay("500x500");
480 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
481 EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
483 UpdateDisplay("500x500,600x600");
484 EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
485 EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
487 // No more modal screen.
489 TestWindow::CloseTestWindow(modal1.release());
490 EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
491 EXPECT_TRUE(wm::IsActiveWindow(normal.get()));