immersive_mode_controller_ash_unittest.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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 "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h"
6
7#include "ash/test/ash_test_base.h"
8#include "chrome/browser/ui/immersive_fullscreen_configuration.h"
9#include "ui/aura/client/cursor_client.h"
10#include "ui/aura/root_window.h"
11#include "ui/aura/test/event_generator.h"
12#include "ui/aura/window.h"
13
14// For now, immersive fullscreen is Chrome OS only.
15#if defined(OS_CHROMEOS)
16
17/////////////////////////////////////////////////////////////////////////////
18
19class MockImmersiveModeControllerDelegate
20    : public ImmersiveModeController::Delegate {
21 public:
22  MockImmersiveModeControllerDelegate() : immersive_style_(false) {}
23  virtual ~MockImmersiveModeControllerDelegate() {}
24
25  bool immersive_style() const { return immersive_style_; }
26
27  // ImmersiveModeController::Delegate overrides:
28  virtual BookmarkBarView* GetBookmarkBar() OVERRIDE { return NULL; }
29  virtual FullscreenController* GetFullscreenController() OVERRIDE {
30    return NULL;
31  }
32  virtual void FullscreenStateChanged() OVERRIDE {}
33  virtual void SetImmersiveStyle(bool immersive) OVERRIDE {
34    immersive_style_ = immersive;
35  }
36
37 private:
38  bool immersive_style_;
39
40  DISALLOW_COPY_AND_ASSIGN(MockImmersiveModeControllerDelegate);
41};
42
43/////////////////////////////////////////////////////////////////////////////
44
45class ImmersiveModeControllerAshTest : public ash::test::AshTestBase {
46 public:
47  enum Modality {
48    MODALITY_MOUSE,
49    MODALITY_TOUCH,
50    MODALITY_GESTURE
51  };
52
53  ImmersiveModeControllerAshTest() : widget_(NULL), top_container_(NULL) {}
54  virtual ~ImmersiveModeControllerAshTest() {}
55
56  ImmersiveModeControllerAsh* controller() { return controller_.get(); }
57  views::View* top_container() { return top_container_; }
58  MockImmersiveModeControllerDelegate* delegate() { return delegate_.get(); }
59
60  aura::test::EventGenerator* event_generator() {
61    return event_generator_.get();
62  }
63
64  // Access to private data from the controller.
65  bool top_edge_hover_timer_running() const {
66    return controller_->top_edge_hover_timer_.IsRunning();
67  }
68  int mouse_x_when_hit_top() const {
69    return controller_->mouse_x_when_hit_top_;
70  }
71
72  // ash::test::AshTestBase overrides:
73  virtual void SetUp() OVERRIDE {
74    ash::test::AshTestBase::SetUp();
75
76    ImmersiveFullscreenConfiguration::EnableImmersiveFullscreenForTest();
77    ASSERT_TRUE(ImmersiveFullscreenConfiguration::UseImmersiveFullscreen());
78
79    controller_.reset(new ImmersiveModeControllerAsh);
80    delegate_.reset(new MockImmersiveModeControllerDelegate);
81
82    event_generator_.reset(new aura::test::EventGenerator(CurrentContext()));
83
84    widget_ = new views::Widget();
85    views::Widget::InitParams params;
86    params.context = CurrentContext();
87    params.bounds = gfx::Rect(0, 0, 500, 500);
88    widget_->Init(params);
89    widget_->Show();
90
91    top_container_ = new views::View();
92    top_container_->SetBounds(0, 0, 500, 100);
93    top_container_->set_focusable(true);
94
95    widget_->GetContentsView()->AddChildView(top_container_);
96
97    controller_->Init(delegate_.get(), widget_, top_container_);
98    controller_->DisableAnimationsForTest();
99  }
100
101  // Attempt to reveal the top-of-window views via |modality|.
102  // The top-of-window views can only be revealed via mouse hover or a gesture.
103  void AttemptReveal(Modality modality) {
104    ASSERT_NE(modality, MODALITY_TOUCH);
105    AttemptRevealStateChange(true, modality);
106  }
107
108  // Attempt to unreveal the top-of-window views via |modality|. The
109  // top-of-window views can be unrevealed via any modality.
110  void AttemptUnreveal(Modality modality) {
111    AttemptRevealStateChange(false, modality);
112  }
113
114  // Move the mouse to the given coordinates. The coordinates should be in
115  // |top_container_| coordinates.
116  void MoveMouse(int x, int y) {
117    // Luckily, |top_container_| is at the top left of the root window so the
118    // provided coordinates are already in the coordinates of the root window.
119    event_generator_->MoveMouseTo(x, y);
120
121    // If the top edge timer started running as a result of the mouse move, run
122    // the task which occurs after the timer delay. This reveals the
123    // top-of-window views synchronously if the mouse is hovered at the top of
124    // the screen.
125    if (controller()->top_edge_hover_timer_.IsRunning()) {
126      controller()->top_edge_hover_timer_.user_task().Run();
127      controller()->top_edge_hover_timer_.Stop();
128    }
129  }
130
131 private:
132  // Attempt to change the revealed state to |revealed| via |modality|.
133  void AttemptRevealStateChange(bool revealed, Modality modality) {
134    // Compute the event position in |top_container_| coordinates.
135    gfx::Point event_position(0, revealed ? 0 : top_container_->height() + 100);
136    switch (modality) {
137      case MODALITY_MOUSE: {
138        MoveMouse(event_position.x(), event_position.y());
139        break;
140      }
141      case MODALITY_TOUCH: {
142        // Luckily, |top_container_| is at the top left of the root window so
143        // |event_position| is already in the coordinates of the root window.
144        event_generator_->MoveTouch(event_position);
145        event_generator_->PressTouch();
146        event_generator_->ReleaseTouch();
147        break;
148      }
149      case MODALITY_GESTURE: {
150        aura::client::GetCursorClient(CurrentContext())->DisableMouseEvents();
151        ImmersiveModeControllerAsh::SwipeType swipe_type = revealed ?
152            ImmersiveModeControllerAsh::SWIPE_OPEN :
153            ImmersiveModeControllerAsh::SWIPE_CLOSE;
154        controller_->UpdateRevealedLocksForSwipe(swipe_type);
155        break;
156      }
157    }
158  }
159
160  scoped_ptr<ImmersiveModeControllerAsh> controller_;
161  scoped_ptr<MockImmersiveModeControllerDelegate> delegate_;
162  views::Widget* widget_;  // Owned by the native widget.
163  views::View* top_container_;  // Owned by |root_view_|.
164  scoped_ptr<aura::test::EventGenerator> event_generator_;
165
166  DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerAshTest);
167};
168
169// Test of initial state and basic functionality.
170TEST_F(ImmersiveModeControllerAshTest, ImmersiveModeControllerAsh) {
171  // Initial state.
172  EXPECT_FALSE(controller()->IsEnabled());
173  EXPECT_FALSE(controller()->ShouldHideTopViews());
174  EXPECT_FALSE(controller()->IsRevealed());
175  EXPECT_FALSE(delegate()->immersive_style());
176
177  // Enabling hides the top views.
178  controller()->SetEnabled(true);
179  EXPECT_TRUE(controller()->IsEnabled());
180  EXPECT_FALSE(controller()->IsRevealed());
181  EXPECT_TRUE(controller()->ShouldHideTopViews());
182  EXPECT_FALSE(controller()->ShouldHideTabIndicators());
183  EXPECT_TRUE(delegate()->immersive_style());
184
185  // Revealing shows the top views.
186  AttemptReveal(MODALITY_MOUSE);
187  EXPECT_TRUE(controller()->IsRevealed());
188  EXPECT_FALSE(controller()->ShouldHideTopViews());
189  // Tabs are painting in the normal style during a reveal.
190  EXPECT_FALSE(delegate()->immersive_style());
191}
192
193// Test mouse event processing for top-of-screen reveal triggering.
194TEST_F(ImmersiveModeControllerAshTest, OnMouseEvent) {
195  // Set up initial state.
196  controller()->SetEnabled(true);
197  ASSERT_TRUE(controller()->IsEnabled());
198  ASSERT_FALSE(controller()->IsRevealed());
199
200  // Mouse wheel event does nothing.
201  ui::MouseEvent wheel(
202      ui::ET_MOUSEWHEEL, gfx::Point(), gfx::Point(), ui::EF_NONE);
203  event_generator()->Dispatch(&wheel);
204  EXPECT_FALSE(top_edge_hover_timer_running());
205
206  // Move to top edge of screen starts hover timer running. We cannot use
207  // MoveMouse() because MoveMouse() stops the timer if it started running.
208  event_generator()->MoveMouseTo(100, 0);
209  EXPECT_TRUE(top_edge_hover_timer_running());
210  EXPECT_EQ(100, mouse_x_when_hit_top());
211
212  // Moving off the top edge stops it.
213  event_generator()->MoveMouseTo(100, 1);
214  EXPECT_FALSE(top_edge_hover_timer_running());
215
216  // Moving back to the top starts the timer again.
217  event_generator()->MoveMouseTo(100, 0);
218  EXPECT_TRUE(top_edge_hover_timer_running());
219  EXPECT_EQ(100, mouse_x_when_hit_top());
220
221  // Slight move to the right keeps the timer running for the same hit point.
222  event_generator()->MoveMouseTo(101, 0);
223  EXPECT_TRUE(top_edge_hover_timer_running());
224  EXPECT_EQ(100, mouse_x_when_hit_top());
225
226  // Moving back to the left also keeps the timer running.
227  event_generator()->MoveMouseTo(100, 0);
228  EXPECT_TRUE(top_edge_hover_timer_running());
229  EXPECT_EQ(100, mouse_x_when_hit_top());
230
231  // Large move right restarts the timer (so it is still running) and considers
232  // this a new hit at the top.
233  event_generator()->MoveMouseTo(499, 0);
234  EXPECT_TRUE(top_edge_hover_timer_running());
235  EXPECT_EQ(499, mouse_x_when_hit_top());
236
237  // Moving off the top edge horizontally stops the timer.
238  EXPECT_GT(CurrentContext()->bounds().width(), top_container()->width());
239  EXPECT_EQ(500, top_container()->width());
240  event_generator()->MoveMouseTo(500, 0);
241  EXPECT_FALSE(top_edge_hover_timer_running());
242
243  // Once revealed, a move just a little below the top container doesn't end a
244  // reveal.
245  AttemptReveal(MODALITY_MOUSE);
246  event_generator()->MoveMouseTo(0, top_container()->height() + 1);
247  EXPECT_TRUE(controller()->IsRevealed());
248
249  // Once revealed, clicking just below the top container ends the reveal.
250  event_generator()->ClickLeftButton();
251  EXPECT_FALSE(controller()->IsRevealed());
252
253  // Moving a lot below the top container ends a reveal.
254  AttemptReveal(MODALITY_MOUSE);
255  EXPECT_TRUE(controller()->IsRevealed());
256  event_generator()->MoveMouseTo(0, top_container()->height() + 50);
257  EXPECT_FALSE(controller()->IsRevealed());
258
259  // The mouse position cannot cause a reveal when TopContainerView's widget
260  // has capture.
261  views::Widget* widget = top_container()->GetWidget();
262  widget->SetCapture(top_container());
263  AttemptReveal(MODALITY_MOUSE);
264  EXPECT_FALSE(controller()->IsRevealed());
265  widget->ReleaseCapture();
266
267  // The mouse position cannot end the reveal while TopContainerView's widget
268  // has capture.
269  AttemptReveal(MODALITY_MOUSE);
270  EXPECT_TRUE(controller()->IsRevealed());
271  widget->SetCapture(top_container());
272  event_generator()->MoveMouseTo(0, top_container()->height() + 51);
273  EXPECT_TRUE(controller()->IsRevealed());
274
275  // Releasing capture should end the reveal.
276  widget->ReleaseCapture();
277  EXPECT_FALSE(controller()->IsRevealed());
278}
279
280// Test that hovering the mouse over the find bar does not end a reveal.
281TEST_F(ImmersiveModeControllerAshTest, FindBar) {
282  // Set up initial state.
283  controller()->SetEnabled(true);
284  ASSERT_TRUE(controller()->IsEnabled());
285  ASSERT_FALSE(controller()->IsRevealed());
286
287  // Compute the find bar bounds relative to TopContainerView. The find
288  // bar is aligned with the bottom right of the TopContainerView.
289  gfx::Rect find_bar_bounds(top_container()->bounds().right() - 100,
290                            top_container()->bounds().bottom(),
291                            100,
292                            50);
293
294  gfx::Point find_bar_position_in_screen = find_bar_bounds.origin();
295  views::View::ConvertPointToScreen(top_container(),
296      &find_bar_position_in_screen);
297  gfx::Rect find_bar_bounds_in_screen(find_bar_position_in_screen,
298      find_bar_bounds.size());
299  controller()->OnFindBarVisibleBoundsChanged(find_bar_bounds_in_screen);
300
301  // Moving the mouse over the find bar does not end the reveal.
302  gfx::Point over_find_bar(find_bar_bounds.x() + 25, find_bar_bounds.y() + 25);
303  AttemptReveal(MODALITY_MOUSE);
304  EXPECT_TRUE(controller()->IsRevealed());
305  MoveMouse(over_find_bar.x(), over_find_bar.y());
306  EXPECT_TRUE(controller()->IsRevealed());
307
308  // Moving the mouse off of the find bar horizontally ends the reveal.
309  MoveMouse(find_bar_bounds.x() - 25, find_bar_bounds.y() + 25);
310  EXPECT_FALSE(controller()->IsRevealed());
311
312  // Moving the mouse off of the find bar vertically ends the reveal.
313  AttemptReveal(MODALITY_MOUSE);
314  EXPECT_TRUE(controller()->IsRevealed());
315  MoveMouse(find_bar_bounds.x() + 25, find_bar_bounds.bottom() + 25);
316
317  // Similar to the TopContainerView, moving the mouse slightly off vertically
318  // of the find bar does not end the reveal.
319  AttemptReveal(MODALITY_MOUSE);
320  MoveMouse(find_bar_bounds.x() + 25, find_bar_bounds.bottom() + 1);
321  EXPECT_TRUE(controller()->IsRevealed());
322
323  // Similar to the TopContainerView, clicking the mouse even slightly off of
324  // the find bar ends the reveal.
325  event_generator()->ClickLeftButton();
326  EXPECT_FALSE(controller()->IsRevealed());
327
328  // Set the find bar bounds to empty. Hovering over the position previously
329  // occupied by the find bar, |over_find_bar|, should end the reveal.
330  controller()->OnFindBarVisibleBoundsChanged(gfx::Rect());
331  AttemptReveal(MODALITY_MOUSE);
332  MoveMouse(over_find_bar.x(), over_find_bar.y());
333  EXPECT_FALSE(controller()->IsRevealed());
334}
335
336// Test revealing the top-of-window views using one modality and ending
337// the reveal via another. For instance, initiating the reveal via a SWIPE_OPEN
338// edge gesture, switching to using the mouse and ending the reveal by moving
339// the mouse off of the top-of-window views.
340TEST_F(ImmersiveModeControllerAshTest, DifferentModalityEnterExit) {
341  controller()->SetEnabled(true);
342  EXPECT_TRUE(controller()->IsEnabled());
343  EXPECT_FALSE(controller()->IsRevealed());
344
345  // Initiate reveal via gesture, end reveal via mouse.
346  AttemptReveal(MODALITY_GESTURE);
347  EXPECT_TRUE(controller()->IsRevealed());
348  MoveMouse(1, 1);
349  EXPECT_TRUE(controller()->IsRevealed());
350  AttemptUnreveal(MODALITY_MOUSE);
351  EXPECT_FALSE(controller()->IsRevealed());
352
353  // Initiate reveal via gesture, end reveal via touch.
354  AttemptReveal(MODALITY_GESTURE);
355  EXPECT_TRUE(controller()->IsRevealed());
356  AttemptUnreveal(MODALITY_TOUCH);
357  EXPECT_FALSE(controller()->IsRevealed());
358
359  // Initiate reveal via mouse, end reveal via gesture.
360  AttemptReveal(MODALITY_MOUSE);
361  EXPECT_TRUE(controller()->IsRevealed());
362  AttemptUnreveal(MODALITY_GESTURE);
363  EXPECT_FALSE(controller()->IsRevealed());
364
365  // Initiate reveal via mouse, end reveal via touch.
366  AttemptReveal(MODALITY_MOUSE);
367  EXPECT_TRUE(controller()->IsRevealed());
368  AttemptUnreveal(MODALITY_TOUCH);
369  EXPECT_FALSE(controller()->IsRevealed());
370}
371
372// Test when the SWIPE_CLOSE edge gesture closes the top-of-window views.
373TEST_F(ImmersiveModeControllerAshTest, EndRevealViaGesture) {
374  controller()->SetEnabled(true);
375  EXPECT_TRUE(controller()->IsEnabled());
376  EXPECT_FALSE(controller()->IsRevealed());
377
378  // A gesture should be able to close the top-of-window views when
379  // top-of-window views have focus.
380  AttemptReveal(MODALITY_MOUSE);
381  top_container()->RequestFocus();
382  EXPECT_TRUE(controller()->IsRevealed());
383  AttemptUnreveal(MODALITY_GESTURE);
384  EXPECT_FALSE(controller()->IsRevealed());
385  top_container()->GetFocusManager()->ClearFocus();
386
387  // If some other code is holding onto a lock, a gesture should not be able to
388  // end the reveal.
389  AttemptReveal(MODALITY_MOUSE);
390  scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock(
391      ImmersiveModeController::ANIMATE_REVEAL_NO));
392  EXPECT_TRUE(controller()->IsRevealed());
393  AttemptUnreveal(MODALITY_GESTURE);
394  EXPECT_TRUE(controller()->IsRevealed());
395  lock.reset();
396  EXPECT_FALSE(controller()->IsRevealed());
397}
398
399#endif  // defined(OS_CHROMEOS)
400