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.
4
5#include "ash/wm/system_modal_container_layout_manager.h"
6
7#include "ash/root_window_controller.h"
8#include "ash/session_state_delegate.h"
9#include "ash/shell.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"
23
24namespace ash {
25namespace test {
26
27namespace {
28
29aura::Window* GetModalContainer() {
30  return Shell::GetPrimaryRootWindowController()->GetContainer(
31      ash::internal::kShellWindowId_SystemModalContainer);
32}
33
34bool 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) {
40    has_modal_screen &=
41        static_cast<internal::SystemModalContainerLayoutManager*>(
42            (*iter)->layout_manager())->has_modal_background();
43  }
44  return has_modal_screen;
45}
46
47bool AllRootWindowsHaveLockedModalBackgrounds() {
48  return AllRootWindowsHaveModalBackgroundsForContainer(
49      internal::kShellWindowId_LockSystemModalContainer);
50}
51
52bool AllRootWindowsHaveModalBackgrounds() {
53  return AllRootWindowsHaveModalBackgroundsForContainer(
54      internal::kShellWindowId_SystemModalContainer);
55}
56
57class TestWindow : public views::WidgetDelegateView {
58 public:
59  explicit TestWindow(bool modal) : modal_(modal) {}
60  virtual ~TestWindow() {}
61
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();
66  }
67
68  // Overridden from views::View:
69  virtual gfx::Size GetPreferredSize() OVERRIDE {
70    return gfx::Size(50, 50);
71  }
72
73  // Overridden from views::WidgetDelegate:
74  virtual views::View* GetContentsView() OVERRIDE {
75    return this;
76  }
77  virtual ui::ModalType GetModalType() const OVERRIDE {
78    return modal_ ? ui::MODAL_TYPE_SYSTEM : ui::MODAL_TYPE_NONE;
79  }
80
81 private:
82  bool modal_;
83
84  DISALLOW_COPY_AND_ASSIGN(TestWindow);
85};
86
87class EventTestWindow : public TestWindow {
88 public:
89  explicit EventTestWindow(bool modal) : TestWindow(modal),
90                                         mouse_presses_(0) {}
91  virtual ~EventTestWindow() {}
92
93  aura::Window* OpenTestWindowWithContext(aura::RootWindow* context) {
94    views::Widget* widget =
95        views::Widget::CreateWindowWithContext(this, context);
96    widget->Show();
97    return widget->GetNativeView();
98  }
99
100  aura::Window* OpenTestWindowWithParent(aura::Window* parent) {
101    DCHECK(parent);
102    views::Widget* widget =
103        views::Widget::CreateWindowWithParent(this, parent);
104    widget->Show();
105    return widget->GetNativeView();
106  }
107
108  // Overridden from views::View:
109  virtual bool OnMousePressed(const ui::MouseEvent& event) OVERRIDE {
110    mouse_presses_++;
111    return false;
112  }
113
114  int mouse_presses() const { return mouse_presses_; }
115 private:
116  int mouse_presses_;
117
118  DISALLOW_COPY_AND_ASSIGN(EventTestWindow);
119};
120
121class TransientWindowObserver : public aura::WindowObserver {
122 public:
123  TransientWindowObserver() : destroyed_(false) {}
124  virtual ~TransientWindowObserver() {}
125
126  bool destroyed() const { return destroyed_; }
127
128  // Overridden from aura::WindowObserver:
129  virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {
130    destroyed_ = true;
131  }
132
133 private:
134  bool destroyed_;
135
136  DISALLOW_COPY_AND_ASSIGN(TransientWindowObserver);
137};
138
139}  // namespace
140
141class SystemModalContainerLayoutManagerTest : public AshTestBase {
142 public:
143  aura::Window* OpenToplevelTestWindow(bool modal) {
144    views::Widget* widget = views::Widget::CreateWindowWithContext(
145        new TestWindow(modal), CurrentContext());
146    widget->Show();
147    return widget->GetNativeView();
148  }
149
150  aura::Window* OpenTestWindowWithParent(aura::Window* parent, bool modal) {
151    views::Widget* widget =
152        views::Widget::CreateWindowWithParent(new TestWindow(modal), parent);
153    widget->Show();
154    return widget->GetNativeView();
155  }
156};
157
158TEST_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);
163
164  EXPECT_EQ(parent.get(), transient->transient_parent());
165  EXPECT_EQ(parent->parent(), transient->parent());
166
167  // The transient should be destroyed with its parent.
168  parent.reset();
169  EXPECT_TRUE(destruction_observer.destroyed());
170}
171
172TEST_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);
177
178  TransientWindowObserver do1;
179  t1->AddObserver(&do1);
180
181  EXPECT_EQ(parent.get(), t1->transient_parent());
182  EXPECT_EQ(GetModalContainer(), t1->parent());
183
184  // t1 should now be active.
185  EXPECT_TRUE(wm::IsActiveWindow(t1));
186
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));
191
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);
196
197  EXPECT_TRUE(wm::IsActiveWindow(t2));
198
199  EXPECT_EQ(t1, t2->transient_parent());
200  EXPECT_EQ(GetModalContainer(), t2->parent());
201
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));
206
207  // Both transients should be destroyed with parent.
208  parent.reset();
209  EXPECT_TRUE(do1.destroyed());
210  EXPECT_TRUE(do2.destroyed());
211}
212
213TEST_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);
219
220  EXPECT_EQ(NULL, t1->transient_parent());
221  EXPECT_EQ(GetModalContainer(), t1->parent());
222
223  // t1 should now be active.
224  EXPECT_TRUE(wm::IsActiveWindow(t1.get()));
225
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()));
231
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);
236
237  EXPECT_TRUE(wm::IsActiveWindow(t2));
238
239  EXPECT_EQ(t1, t2->transient_parent());
240  EXPECT_EQ(GetModalContainer(), t2->parent());
241
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));
246
247  // Both transients should be destroyed with parent.
248  t1.reset();
249  EXPECT_TRUE(do1.destroyed());
250  EXPECT_TRUE(do2.destroyed());
251}
252
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
257#else
258#define MAYBE_CanActivateAfterEndModalSession CanActivateAfterEndModalSession
259#endif
260// Tests that we can activate an unrelated window after a modal window is closed
261// for a window.
262TEST_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()));
269
270  scoped_ptr<aura::Window> transient(
271      OpenTestWindowWithParent(parent.get(), true));
272  // t1 should now be active.
273  EXPECT_TRUE(wm::IsActiveWindow(transient.get()));
274
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()));
279
280  // Now close the transient.
281  transient->Hide();
282  TestWindow::CloseTestWindow(transient.release());
283
284  base::RunLoop().RunUntilIdle();
285
286  // parent should now be active again.
287  EXPECT_TRUE(wm::IsActiveWindow(parent.get()));
288
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()));
293}
294
295TEST_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());
304
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());
313
314  for (int block_reason = FIRST_BLOCK_REASON;
315       block_reason < NUMBER_OF_BLOCK_REASONS;
316       ++block_reason) {
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());
327
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();
341  }
342}
343
344// Makes sure we don't crash if a modal window is shown while the parent window
345// is hidden.
346TEST_F(SystemModalContainerLayoutManagerTest, ShowModalWhileHidden) {
347  // Hide the lock screen.
348  Shell::GetPrimaryRootWindowController()->GetContainer(
349      internal::kShellWindowId_SystemModalContainer)->layer()->SetOpacity(0);
350
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));
355  parent->Show();
356  modal_window->Show();
357}
358
359// Verifies we generate a capture lost when showing a modal window.
360TEST_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());
367  widget->Show();
368
369  gfx::Point center(view->width() / 2, view->height() / 2);
370  views::View::ConvertPointToScreen(view, &center);
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());
378}
379
380// Verifies that the window gets moved into the visible screen area upon screen
381// resize.
382TEST_F(SystemModalContainerLayoutManagerTest, KeepVisible) {
383  GetModalContainer()->SetBounds(gfx::Rect(0, 0, 1024, 768));
384  scoped_ptr<aura::Window> main(OpenTestWindowWithParent(GetModalContainer(),
385                                                         true));
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));
390
391  gfx::Rect bounds = main->bounds();
392  EXPECT_EQ(bounds, gfx::Rect(700, 500, 100, 100));
393}
394
395TEST_F(SystemModalContainerLayoutManagerTest, ShowNormalBackgroundOrLocked) {
396  scoped_ptr<aura::Window> parent(OpenToplevelTestWindow(false));
397  scoped_ptr<aura::Window> modal_window(
398      OpenTestWindowWithParent(parent.get(), true));
399  parent->Show();
400  modal_window->Show();
401
402  // Normal system modal window.  Shows normal system modal background and not
403  // locked.
404  EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
405  EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
406
407  TestWindow::CloseTestWindow(modal_window.release());
408  EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
409  EXPECT_FALSE(AllRootWindowsHaveLockedModalBackgrounds());
410
411  for (int block_reason = FIRST_BLOCK_REASON;
412       block_reason < NUMBER_OF_BLOCK_REASONS;
413       ++block_reason) {
414    // Normal system modal window while blocked.  Shows blocked system modal
415    // background.
416    BlockUserSession(static_cast<UserSessionBlockReason>(block_reason));
417    scoped_ptr<aura::Window> lock_parent(OpenTestWindowWithParent(
418        Shell::GetPrimaryRootWindowController()->GetContainer(
419            ash::internal::kShellWindowId_LockScreenContainer),
420        false));
421    scoped_ptr<aura::Window> lock_modal_window(OpenTestWindowWithParent(
422        lock_parent.get(), true));
423    lock_parent->Show();
424    lock_modal_window->Show();
425    EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
426    EXPECT_TRUE(AllRootWindowsHaveLockedModalBackgrounds());
427    TestWindow::CloseTestWindow(lock_modal_window.release());
428
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.
442  }
443}
444
445TEST_F(SystemModalContainerLayoutManagerTest, MultiDisplays) {
446  if (!SupportsMultipleDisplays())
447    return;
448
449  UpdateDisplay("500x500,500x500");
450
451  scoped_ptr<aura::Window> normal(OpenToplevelTestWindow(false));
452  normal->SetBounds(gfx::Rect(100, 100, 50, 50));
453
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);
460
461  scoped_ptr<aura::Window> modal1(
462      OpenTestWindowWithParent(container1, true));
463  EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
464  EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
465
466  scoped_ptr<aura::Window> modal11(
467      OpenTestWindowWithParent(container1, true));
468  EXPECT_TRUE(wm::IsActiveWindow(modal11.get()));
469
470  scoped_ptr<aura::Window> modal2(
471      OpenTestWindowWithParent(container2, true));
472  EXPECT_TRUE(wm::IsActiveWindow(modal2.get()));
473
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());
478
479  TestWindow::CloseTestWindow(modal2.release());
480  EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
481  EXPECT_TRUE(wm::IsActiveWindow(modal11.get()));
482
483  TestWindow::CloseTestWindow(modal11.release());
484  EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
485  EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
486
487  UpdateDisplay("500x500");
488  EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
489  EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
490
491  UpdateDisplay("500x500,600x600");
492  EXPECT_TRUE(AllRootWindowsHaveModalBackgrounds());
493  EXPECT_TRUE(wm::IsActiveWindow(modal1.get()));
494
495  // No more modal screen.
496  modal1->Hide();
497  TestWindow::CloseTestWindow(modal1.release());
498  EXPECT_FALSE(AllRootWindowsHaveModalBackgrounds());
499  EXPECT_TRUE(wm::IsActiveWindow(normal.get()));
500}
501
502}  // namespace test
503}  // namespace ash
504