immersive_mode_controller_ash_unittest.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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/display/display_manager.h"
8#include "ash/shell.h"
9#include "ash/test/ash_test_base.h"
10#include "chrome/app/chrome_command_ids.h"
11#include "chrome/browser/ui/browser_commands.h"
12#include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
13#include "chrome/browser/ui/fullscreen/fullscreen_controller_test.h"
14#include "chrome/browser/ui/immersive_fullscreen_configuration.h"
15#include "chrome/browser/ui/views/frame/browser_view.h"
16#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
17#include "chrome/browser/ui/views/frame/top_container_view.h"
18#include "chrome/browser/ui/views/tabs/tab_strip.h"
19#include "chrome/browser/ui/views/toolbar_view.h"
20#include "ui/aura/client/cursor_client.h"
21#include "ui/aura/env.h"
22#include "ui/aura/root_window.h"
23#include "ui/aura/test/event_generator.h"
24#include "ui/aura/window.h"
25#include "ui/gfx/animation/slide_animation.h"
26#include "ui/views/bubble/bubble_delegate.h"
27#include "ui/views/controls/webview/webview.h"
28
29// For now, immersive fullscreen is Chrome OS only.
30#if defined(OS_CHROMEOS)
31
32/////////////////////////////////////////////////////////////////////////////
33
34class MockImmersiveModeControllerDelegate
35    : public ImmersiveModeController::Delegate {
36 public:
37  MockImmersiveModeControllerDelegate() : immersive_style_(false) {}
38  virtual ~MockImmersiveModeControllerDelegate() {}
39
40  bool immersive_style() const { return immersive_style_; }
41
42  // ImmersiveModeController::Delegate overrides:
43  virtual BookmarkBarView* GetBookmarkBar() OVERRIDE { return NULL; }
44  virtual FullscreenController* GetFullscreenController() OVERRIDE {
45    return NULL;
46  }
47  virtual void FullscreenStateChanged() OVERRIDE {}
48  virtual void SetImmersiveStyle(bool immersive) OVERRIDE {
49    immersive_style_ = immersive;
50  }
51  virtual content::WebContents* GetWebContents() OVERRIDE {
52    return NULL;
53  }
54
55 private:
56  bool immersive_style_;
57
58  DISALLOW_COPY_AND_ASSIGN(MockImmersiveModeControllerDelegate);
59};
60
61/////////////////////////////////////////////////////////////////////////////
62
63class ImmersiveModeControllerAshTest : public ash::test::AshTestBase {
64 public:
65  enum Modality {
66    MODALITY_MOUSE,
67    MODALITY_TOUCH,
68    MODALITY_GESTURE
69  };
70
71  ImmersiveModeControllerAshTest() : widget_(NULL), top_container_(NULL) {}
72  virtual ~ImmersiveModeControllerAshTest() {}
73
74  ImmersiveModeControllerAsh* controller() { return controller_.get(); }
75  views::View* top_container() { return top_container_; }
76  MockImmersiveModeControllerDelegate* delegate() { return delegate_.get(); }
77
78  // Access to private data from the controller.
79  bool top_edge_hover_timer_running() const {
80    return controller_->top_edge_hover_timer_.IsRunning();
81  }
82  int mouse_x_when_hit_top() const {
83    return controller_->mouse_x_when_hit_top_in_screen_;
84  }
85
86  // ash::test::AshTestBase overrides:
87  virtual void SetUp() OVERRIDE {
88    ash::test::AshTestBase::SetUp();
89
90    ImmersiveFullscreenConfiguration::EnableImmersiveFullscreenForTest();
91    ASSERT_TRUE(ImmersiveFullscreenConfiguration::UseImmersiveFullscreen());
92
93    controller_.reset(new ImmersiveModeControllerAsh);
94    delegate_.reset(new MockImmersiveModeControllerDelegate);
95
96    widget_ = new views::Widget();
97    views::Widget::InitParams params;
98    params.context = CurrentContext();
99    params.bounds = gfx::Rect(0, 0, 500, 500);
100    widget_->Init(params);
101    widget_->Show();
102
103    top_container_ = new views::View();
104    top_container_->SetBounds(0, 0, 500, 100);
105    top_container_->set_focusable(true);
106
107    widget_->GetContentsView()->AddChildView(top_container_);
108
109    controller_->Init(delegate_.get(), widget_, top_container_);
110    SetAnimationsDisabled(true);
111
112    // The mouse is moved so that it is not over |top_container_| by
113    // AshTestBase.
114  }
115
116  // Enable or disable the immersive mode controller's animations. When the
117  // immersive mode controller's animations are disabled, some behavior is
118  // slightly different. In particular, the behavior is different when there
119  // is a transfer in which lock keeps the top-of-window views revealed (eg
120  // bubble keeps top-of-window views revealed -> mouse keeps top-of-window
121  // views revealed). It is necessary to temparily enable the immersive
122  // controller's animations to get the correct behavior in tests.
123  void SetAnimationsDisabled(bool disabled) {
124    controller_->animations_disabled_for_test_ = disabled;
125    // Force any in progress animations to finish.
126    if (disabled)
127      controller_->animation_->End();
128  }
129
130  // Attempt to reveal the top-of-window views via |modality|.
131  // The top-of-window views can only be revealed via mouse hover or a gesture.
132  void AttemptReveal(Modality modality) {
133    ASSERT_NE(modality, MODALITY_TOUCH);
134    AttemptRevealStateChange(true, modality);
135  }
136
137  // Attempt to unreveal the top-of-window views via |modality|. The
138  // top-of-window views can be unrevealed via any modality.
139  void AttemptUnreveal(Modality modality) {
140    AttemptRevealStateChange(false, modality);
141  }
142
143  // Sets whether the mouse is hovered above |top_container_|.
144  // SetHovered(true) moves the mouse over the |top_container_| but does not
145  // move it to the top of the screen so will not initiate a reveal.
146  void SetHovered(bool is_mouse_hovered) {
147    MoveMouse(0, is_mouse_hovered ? 1 : top_container_->height() + 100);
148  }
149
150  // Move the mouse to the given coordinates. The coordinates should be in
151  // |top_container_| coordinates.
152  void MoveMouse(int x, int y) {
153    gfx::Point screen_position(x, y);
154    views::View::ConvertPointToScreen(top_container_, &screen_position);
155    GetEventGenerator().MoveMouseTo(screen_position.x(), screen_position.y());
156
157    // If the top edge timer started running as a result of the mouse move, run
158    // the task which occurs after the timer delay. This reveals the
159    // top-of-window views synchronously if the mouse is hovered at the top of
160    // the screen.
161    if (controller()->top_edge_hover_timer_.IsRunning()) {
162      controller()->top_edge_hover_timer_.user_task().Run();
163      controller()->top_edge_hover_timer_.Stop();
164    }
165  }
166
167 private:
168  // Attempt to change the revealed state to |revealed| via |modality|.
169  void AttemptRevealStateChange(bool revealed, Modality modality) {
170    // Compute the event position in |top_container_| coordinates.
171    gfx::Point event_position(0, revealed ? 0 : top_container_->height() + 100);
172    switch (modality) {
173      case MODALITY_MOUSE: {
174        MoveMouse(event_position.x(), event_position.y());
175        break;
176      }
177      case MODALITY_TOUCH: {
178        gfx::Point screen_position = event_position;
179        views::View::ConvertPointToScreen(top_container_, &screen_position);
180
181        aura::test::EventGenerator& event_generator(GetEventGenerator());
182        event_generator.MoveTouch(event_position);
183        event_generator.PressTouch();
184        event_generator.ReleaseTouch();
185        break;
186      }
187      case MODALITY_GESTURE: {
188        aura::client::GetCursorClient(CurrentContext())->DisableMouseEvents();
189        ImmersiveModeControllerAsh::SwipeType swipe_type = revealed ?
190            ImmersiveModeControllerAsh::SWIPE_OPEN :
191            ImmersiveModeControllerAsh::SWIPE_CLOSE;
192        controller_->UpdateRevealedLocksForSwipe(swipe_type);
193        break;
194      }
195    }
196  }
197
198  scoped_ptr<ImmersiveModeControllerAsh> controller_;
199  scoped_ptr<MockImmersiveModeControllerDelegate> delegate_;
200  views::Widget* widget_;  // Owned by the native widget.
201  views::View* top_container_;  // Owned by |root_view_|.
202  scoped_ptr<aura::test::EventGenerator> event_generator_;
203
204  DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerAshTest);
205};
206
207// Test of initial state and basic functionality.
208TEST_F(ImmersiveModeControllerAshTest, ImmersiveModeControllerAsh) {
209  // Initial state.
210  EXPECT_FALSE(controller()->IsEnabled());
211  EXPECT_FALSE(controller()->ShouldHideTopViews());
212  EXPECT_FALSE(controller()->IsRevealed());
213  EXPECT_FALSE(delegate()->immersive_style());
214
215  // Enabling hides the top views.
216  controller()->SetEnabled(true);
217  EXPECT_TRUE(controller()->IsEnabled());
218  EXPECT_FALSE(controller()->IsRevealed());
219  EXPECT_TRUE(controller()->ShouldHideTopViews());
220  EXPECT_FALSE(controller()->ShouldHideTabIndicators());
221  EXPECT_TRUE(delegate()->immersive_style());
222
223  // Revealing shows the top views.
224  AttemptReveal(MODALITY_MOUSE);
225  EXPECT_TRUE(controller()->IsRevealed());
226  EXPECT_FALSE(controller()->ShouldHideTopViews());
227  // Tabs are painting in the normal style during a reveal.
228  EXPECT_FALSE(delegate()->immersive_style());
229
230  // Disabling immersive fullscreen keeps the top views shown.
231  controller()->SetEnabled(false);
232  EXPECT_FALSE(controller()->IsEnabled());
233  EXPECT_FALSE(controller()->IsRevealed());
234  EXPECT_FALSE(controller()->ShouldHideTopViews());
235  EXPECT_FALSE(delegate()->immersive_style());
236
237  // Test disabling immersive fullscreen when the top views are hidden.
238  controller()->SetEnabled(true);
239  EXPECT_TRUE(controller()->IsEnabled());
240  EXPECT_FALSE(controller()->IsRevealed());
241  EXPECT_TRUE(controller()->ShouldHideTopViews());
242  EXPECT_TRUE(delegate()->immersive_style());
243
244  controller()->SetEnabled(false);
245  EXPECT_FALSE(controller()->IsEnabled());
246  EXPECT_FALSE(controller()->IsRevealed());
247  EXPECT_FALSE(controller()->ShouldHideTopViews());
248  EXPECT_FALSE(delegate()->immersive_style());
249}
250
251// Test mouse event processing for top-of-screen reveal triggering.
252TEST_F(ImmersiveModeControllerAshTest, OnMouseEvent) {
253  // Set up initial state.
254  controller()->SetEnabled(true);
255  ASSERT_TRUE(controller()->IsEnabled());
256  ASSERT_FALSE(controller()->IsRevealed());
257
258  aura::test::EventGenerator& event_generator(GetEventGenerator());
259
260  gfx::Rect top_container_bounds_in_screen =
261      top_container()->GetBoundsInScreen();
262  // A position along the top edge of TopContainerView in screen coordinates.
263  gfx::Point top_edge_pos(top_container_bounds_in_screen.x() + 100,
264                          top_container_bounds_in_screen.y());
265
266  // Mouse wheel event does nothing.
267  ui::MouseEvent wheel(
268      ui::ET_MOUSEWHEEL, top_edge_pos, top_edge_pos, ui::EF_NONE);
269  event_generator.Dispatch(&wheel);
270  EXPECT_FALSE(top_edge_hover_timer_running());
271
272  // Move to top edge of screen starts hover timer running. We cannot use
273  // MoveMouse() because MoveMouse() stops the timer if it started running.
274  event_generator.MoveMouseTo(top_edge_pos);
275  EXPECT_TRUE(top_edge_hover_timer_running());
276  EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top());
277
278  // Moving |ImmersiveModeControllerAsh::kMouseRevealBoundsHeight| down from
279  // the top edge stops it.
280  event_generator.MoveMouseBy(0, 3);
281  EXPECT_FALSE(top_edge_hover_timer_running());
282
283  // Moving back to the top starts the timer again.
284  event_generator.MoveMouseTo(top_edge_pos);
285  EXPECT_TRUE(top_edge_hover_timer_running());
286  EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top());
287
288  // Slight move to the right keeps the timer running for the same hit point.
289  event_generator.MoveMouseBy(1, 0);
290  EXPECT_TRUE(top_edge_hover_timer_running());
291  EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top());
292
293  // Moving back to the left also keeps the timer running.
294  event_generator.MoveMouseBy(-1, 0);
295  EXPECT_TRUE(top_edge_hover_timer_running());
296  EXPECT_EQ(top_edge_pos.x(), mouse_x_when_hit_top());
297
298  // Large move right restarts the timer (so it is still running) and considers
299  // this a new hit at the top.
300  event_generator.MoveMouseTo(top_edge_pos.x() + 100, top_edge_pos.y());
301  EXPECT_TRUE(top_edge_hover_timer_running());
302  EXPECT_EQ(top_edge_pos.x() + 100, mouse_x_when_hit_top());
303
304  // Moving off the top edge horizontally stops the timer.
305  EXPECT_GT(CurrentContext()->bounds().width(), top_container()->width());
306  event_generator.MoveMouseTo(top_container_bounds_in_screen.right(),
307                              top_container_bounds_in_screen.y());
308  EXPECT_FALSE(top_edge_hover_timer_running());
309
310  // Once revealed, a move just a little below the top container doesn't end a
311  // reveal.
312  AttemptReveal(MODALITY_MOUSE);
313  event_generator.MoveMouseTo(top_container_bounds_in_screen.x(),
314                              top_container_bounds_in_screen.bottom() + 1);
315  EXPECT_TRUE(controller()->IsRevealed());
316
317  // Once revealed, clicking just below the top container ends the reveal.
318  event_generator.ClickLeftButton();
319  EXPECT_FALSE(controller()->IsRevealed());
320
321  // Moving a lot below the top container ends a reveal.
322  AttemptReveal(MODALITY_MOUSE);
323  EXPECT_TRUE(controller()->IsRevealed());
324  event_generator.MoveMouseTo(top_container_bounds_in_screen.x(),
325                              top_container_bounds_in_screen.bottom() + 50);
326  EXPECT_FALSE(controller()->IsRevealed());
327
328  // The mouse position cannot cause a reveal when TopContainerView's widget
329  // has capture.
330  views::Widget* widget = top_container()->GetWidget();
331  widget->SetCapture(top_container());
332  AttemptReveal(MODALITY_MOUSE);
333  EXPECT_FALSE(controller()->IsRevealed());
334  widget->ReleaseCapture();
335
336  // The mouse position cannot end the reveal while TopContainerView's widget
337  // has capture.
338  AttemptReveal(MODALITY_MOUSE);
339  EXPECT_TRUE(controller()->IsRevealed());
340  widget->SetCapture(top_container());
341  event_generator.MoveMouseTo(top_container_bounds_in_screen.x(),
342                              top_container_bounds_in_screen.bottom() + 51);
343  EXPECT_TRUE(controller()->IsRevealed());
344
345  // Releasing capture should end the reveal.
346  widget->ReleaseCapture();
347  EXPECT_FALSE(controller()->IsRevealed());
348}
349
350// Test mouse event processing for top-of-screen reveal triggering when the user
351// has a vertical display layout (primary display above/below secondary display)
352// and the immersive fullscreen window is on the bottom display.
353TEST_F(ImmersiveModeControllerAshTest, MouseEventsVerticalDisplayLayout) {
354  if (!SupportsMultipleDisplays())
355    return;
356
357  // Set up initial state.
358  UpdateDisplay("800x600,800x600");
359  ash::DisplayLayout display_layout(ash::DisplayLayout::TOP, 0);
360  ash::Shell::GetInstance()->display_manager()->SetLayoutForCurrentDisplays(
361      display_layout);
362
363  controller()->SetEnabled(true);
364  ASSERT_TRUE(controller()->IsEnabled());
365  ASSERT_FALSE(controller()->IsRevealed());
366
367  ash::Shell::RootWindowList root_windows = ash::Shell::GetAllRootWindows();
368  ASSERT_EQ(root_windows[0],
369            top_container()->GetWidget()->GetNativeWindow()->GetRootWindow());
370
371  gfx::Rect primary_root_window_bounds_in_screen =
372      root_windows[0]->GetBoundsInScreen();
373  // Do not set |x| to the root window's x position because the display's
374  // corners have special behavior.
375  int x = primary_root_window_bounds_in_screen.x() + 10;
376  // The y position of the top edge of the primary display.
377  int y_top_edge = primary_root_window_bounds_in_screen.y();
378
379  aura::test::EventGenerator& event_generator(GetEventGenerator());
380
381  // Moving right below the top edge starts the hover timer running. We
382  // cannot use MoveMouse() because MoveMouse() stops the timer if it started
383  // running.
384  event_generator.MoveMouseTo(x, y_top_edge + 1);
385  EXPECT_TRUE(top_edge_hover_timer_running());
386  EXPECT_EQ(y_top_edge + 1,
387            aura::Env::GetInstance()->last_mouse_location().y());
388
389  // The timer should continue running if the user moves the mouse to the top
390  // edge even though the mouse is warped to the secondary display.
391  event_generator.MoveMouseTo(x, y_top_edge);
392  EXPECT_TRUE(top_edge_hover_timer_running());
393  EXPECT_NE(y_top_edge,
394            aura::Env::GetInstance()->last_mouse_location().y());
395
396  // The timer should continue running if the user overshoots the top edge
397  // a bit.
398  event_generator.MoveMouseTo(x, y_top_edge - 2);
399  EXPECT_TRUE(top_edge_hover_timer_running());
400
401  // The timer should stop running if the user overshoots the top edge by
402  // a lot.
403  event_generator.MoveMouseTo(x, y_top_edge - 20);
404  EXPECT_FALSE(top_edge_hover_timer_running());
405
406  // The timer should not start if the user moves the mouse to the bottom of the
407  // secondary display without crossing the top edge first.
408  event_generator.MoveMouseTo(x, y_top_edge - 2);
409
410  // Reveal the top-of-window views by overshooting the top edge slightly.
411  event_generator.MoveMouseTo(x, y_top_edge + 1);
412  // MoveMouse() runs the timer task.
413  MoveMouse(x, y_top_edge - 2);
414  EXPECT_TRUE(controller()->IsRevealed());
415
416  // The top-of-window views should stay revealed if the user moves the mouse
417  // around in the bottom region of the secondary display.
418  event_generator.MoveMouseTo(x + 10, y_top_edge - 3);
419  EXPECT_TRUE(controller()->IsRevealed());
420
421  // The top-of-window views should hide if the user moves the mouse away from
422  // the bottom region of the secondary display.
423  event_generator.MoveMouseTo(x, y_top_edge - 20);
424  EXPECT_FALSE(controller()->IsRevealed());
425}
426
427// Test that hovering the mouse over the find bar does not end a reveal.
428TEST_F(ImmersiveModeControllerAshTest, FindBar) {
429  // Set up initial state.
430  controller()->SetEnabled(true);
431  ASSERT_TRUE(controller()->IsEnabled());
432  ASSERT_FALSE(controller()->IsRevealed());
433
434  // Compute the find bar bounds relative to TopContainerView. The find
435  // bar is aligned with the bottom right of the TopContainerView.
436  gfx::Rect find_bar_bounds(top_container()->bounds().right() - 100,
437                            top_container()->bounds().bottom(),
438                            100,
439                            50);
440
441  gfx::Point find_bar_position_in_screen = find_bar_bounds.origin();
442  views::View::ConvertPointToScreen(top_container(),
443      &find_bar_position_in_screen);
444  gfx::Rect find_bar_bounds_in_screen(find_bar_position_in_screen,
445      find_bar_bounds.size());
446  controller()->OnFindBarVisibleBoundsChanged(find_bar_bounds_in_screen);
447
448  // Moving the mouse over the find bar does not end the reveal.
449  gfx::Point over_find_bar(find_bar_bounds.x() + 25, find_bar_bounds.y() + 25);
450  AttemptReveal(MODALITY_MOUSE);
451  EXPECT_TRUE(controller()->IsRevealed());
452  MoveMouse(over_find_bar.x(), over_find_bar.y());
453  EXPECT_TRUE(controller()->IsRevealed());
454
455  // Moving the mouse off of the find bar horizontally ends the reveal.
456  MoveMouse(find_bar_bounds.x() - 25, find_bar_bounds.y() + 25);
457  EXPECT_FALSE(controller()->IsRevealed());
458
459  // Moving the mouse off of the find bar vertically ends the reveal.
460  AttemptReveal(MODALITY_MOUSE);
461  EXPECT_TRUE(controller()->IsRevealed());
462  MoveMouse(find_bar_bounds.x() + 25, find_bar_bounds.bottom() + 25);
463
464  // Similar to the TopContainerView, moving the mouse slightly off vertically
465  // of the find bar does not end the reveal.
466  AttemptReveal(MODALITY_MOUSE);
467  MoveMouse(find_bar_bounds.x() + 25, find_bar_bounds.bottom() + 1);
468  EXPECT_TRUE(controller()->IsRevealed());
469
470  // Similar to the TopContainerView, clicking the mouse even slightly off of
471  // the find bar ends the reveal.
472  GetEventGenerator().ClickLeftButton();
473  EXPECT_FALSE(controller()->IsRevealed());
474
475  // Set the find bar bounds to empty. Hovering over the position previously
476  // occupied by the find bar, |over_find_bar|, should end the reveal.
477  controller()->OnFindBarVisibleBoundsChanged(gfx::Rect());
478  AttemptReveal(MODALITY_MOUSE);
479  MoveMouse(over_find_bar.x(), over_find_bar.y());
480  EXPECT_FALSE(controller()->IsRevealed());
481}
482
483// Test revealing the top-of-window views using one modality and ending
484// the reveal via another. For instance, initiating the reveal via a SWIPE_OPEN
485// edge gesture, switching to using the mouse and ending the reveal by moving
486// the mouse off of the top-of-window views.
487TEST_F(ImmersiveModeControllerAshTest, DifferentModalityEnterExit) {
488  controller()->SetEnabled(true);
489  EXPECT_TRUE(controller()->IsEnabled());
490  EXPECT_FALSE(controller()->IsRevealed());
491
492  // Initiate reveal via gesture, end reveal via mouse.
493  AttemptReveal(MODALITY_GESTURE);
494  EXPECT_TRUE(controller()->IsRevealed());
495  MoveMouse(1, 1);
496  EXPECT_TRUE(controller()->IsRevealed());
497  AttemptUnreveal(MODALITY_MOUSE);
498  EXPECT_FALSE(controller()->IsRevealed());
499
500  // Initiate reveal via gesture, end reveal via touch.
501  AttemptReveal(MODALITY_GESTURE);
502  EXPECT_TRUE(controller()->IsRevealed());
503  AttemptUnreveal(MODALITY_TOUCH);
504  EXPECT_FALSE(controller()->IsRevealed());
505
506  // Initiate reveal via mouse, end reveal via gesture.
507  AttemptReveal(MODALITY_MOUSE);
508  EXPECT_TRUE(controller()->IsRevealed());
509  AttemptUnreveal(MODALITY_GESTURE);
510  EXPECT_FALSE(controller()->IsRevealed());
511
512  // Initiate reveal via mouse, end reveal via touch.
513  AttemptReveal(MODALITY_MOUSE);
514  EXPECT_TRUE(controller()->IsRevealed());
515  AttemptUnreveal(MODALITY_TOUCH);
516  EXPECT_FALSE(controller()->IsRevealed());
517}
518
519// Test when the SWIPE_CLOSE edge gesture closes the top-of-window views.
520TEST_F(ImmersiveModeControllerAshTest, EndRevealViaGesture) {
521  controller()->SetEnabled(true);
522  EXPECT_TRUE(controller()->IsEnabled());
523  EXPECT_FALSE(controller()->IsRevealed());
524
525  // A gesture should be able to close the top-of-window views when
526  // top-of-window views have focus.
527  AttemptReveal(MODALITY_MOUSE);
528  top_container()->RequestFocus();
529  EXPECT_TRUE(controller()->IsRevealed());
530  AttemptUnreveal(MODALITY_GESTURE);
531  EXPECT_FALSE(controller()->IsRevealed());
532  top_container()->GetFocusManager()->ClearFocus();
533
534  // If some other code is holding onto a lock, a gesture should not be able to
535  // end the reveal.
536  AttemptReveal(MODALITY_MOUSE);
537  scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock(
538      ImmersiveModeController::ANIMATE_REVEAL_NO));
539  EXPECT_TRUE(controller()->IsRevealed());
540  AttemptUnreveal(MODALITY_GESTURE);
541  EXPECT_TRUE(controller()->IsRevealed());
542  lock.reset();
543  EXPECT_FALSE(controller()->IsRevealed());
544}
545
546// Do not test under windows because focus testing is not reliable on
547// Windows. (crbug.com/79493)
548#if !defined(OS_WIN)
549
550// Test how focus and activation affects whether the top-of-window views are
551// revealed.
552TEST_F(ImmersiveModeControllerAshTest, Focus) {
553  // Add views to the view hierarchy which we will focus and unfocus during the
554  // test.
555  views::View* child_view = new views::View();
556  child_view->SetBounds(0, 0, 10, 10);
557  child_view->set_focusable(true);
558  top_container()->AddChildView(child_view);
559  views::View* unrelated_view = new views::View();
560  unrelated_view->SetBounds(0, 100, 10, 10);
561  unrelated_view->set_focusable(true);
562  top_container()->parent()->AddChildView(unrelated_view);
563  views::FocusManager* focus_manager =
564      top_container()->GetWidget()->GetFocusManager();
565
566  controller()->SetEnabled(true);
567
568  // 1) Test that the top-of-window views stay revealed as long as either a
569  // |child_view| has focus or the mouse is hovered above the top-of-window
570  // views.
571  AttemptReveal(MODALITY_MOUSE);
572  child_view->RequestFocus();
573  focus_manager->ClearFocus();
574  EXPECT_TRUE(controller()->IsRevealed());
575  child_view->RequestFocus();
576  SetHovered(false);
577  EXPECT_TRUE(controller()->IsRevealed());
578  focus_manager->ClearFocus();
579  EXPECT_FALSE(controller()->IsRevealed());
580
581  // 2) Test that focusing |unrelated_view| hides the top-of-window views.
582  // Note: In this test we can cheat and trigger a reveal via focus because
583  // the top container does not hide when the top-of-window views are not
584  // revealed.
585  child_view->RequestFocus();
586  EXPECT_TRUE(controller()->IsRevealed());
587  unrelated_view->RequestFocus();
588  EXPECT_FALSE(controller()->IsRevealed());
589
590  // 3) Test that a loss of focus of |child_view| to |unrelated_view|
591  // while immersive mode is disabled is properly registered.
592  child_view->RequestFocus();
593  EXPECT_TRUE(controller()->IsRevealed());
594  controller()->SetEnabled(false);
595  EXPECT_FALSE(controller()->IsRevealed());
596  unrelated_view->RequestFocus();
597  controller()->SetEnabled(true);
598  EXPECT_FALSE(controller()->IsRevealed());
599
600  // Repeat test but with a revealed lock acquired when immersive mode is
601  // disabled because the code path is different.
602  child_view->RequestFocus();
603  EXPECT_TRUE(controller()->IsRevealed());
604  controller()->SetEnabled(false);
605  scoped_ptr<ImmersiveRevealedLock> lock(controller()->GetRevealedLock(
606      ImmersiveModeController::ANIMATE_REVEAL_NO));
607  EXPECT_FALSE(controller()->IsRevealed());
608  unrelated_view->RequestFocus();
609  controller()->SetEnabled(true);
610  EXPECT_TRUE(controller()->IsRevealed());
611  lock.reset();
612  EXPECT_FALSE(controller()->IsRevealed());
613}
614
615// Test how activation affects whether the top-of-window views are revealed.
616// The behavior when a bubble is activated is tested in
617// ImmersiveModeControllerAshTest.Bubbles.
618TEST_F(ImmersiveModeControllerAshTest, Activation) {
619  views::Widget* top_container_widget = top_container()->GetWidget();
620
621  controller()->SetEnabled(true);
622  ASSERT_FALSE(controller()->IsRevealed());
623
624  // 1) Test that a transient window which is not a bubble does not trigger a
625  // reveal but does keep the top-of-window views revealed if they are already
626  // revealed.
627  views::Widget::InitParams transient_params;
628  transient_params.ownership =
629      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
630  transient_params.parent = top_container_widget->GetNativeView();
631  transient_params.bounds = gfx::Rect(0, 0, 100, 100);
632  scoped_ptr<views::Widget> transient_widget(new views::Widget());
633  transient_widget->Init(transient_params);
634  transient_widget->Show();
635
636  EXPECT_FALSE(controller()->IsRevealed());
637  top_container_widget->Activate();
638  AttemptReveal(MODALITY_MOUSE);
639  EXPECT_TRUE(controller()->IsRevealed());
640  transient_widget->Activate();
641  SetHovered(false);
642  EXPECT_TRUE(controller()->IsRevealed());
643  transient_widget.reset();
644  EXPECT_FALSE(controller()->IsRevealed());
645
646  // 2) Test that activating a non-transient window ends the reveal if any.
647  views::Widget::InitParams non_transient_params;
648  non_transient_params.ownership =
649      views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
650  non_transient_params.context = top_container_widget->GetNativeView();
651  non_transient_params.bounds = gfx::Rect(0, 0, 100, 100);
652  scoped_ptr<views::Widget> non_transient_widget(new views::Widget());
653  non_transient_widget->Init(non_transient_params);
654  non_transient_widget->Show();
655
656  EXPECT_FALSE(controller()->IsRevealed());
657  top_container_widget->Activate();
658  AttemptReveal(MODALITY_MOUSE);
659  EXPECT_TRUE(controller()->IsRevealed());
660  non_transient_widget->Activate();
661  EXPECT_FALSE(controller()->IsRevealed());
662}
663
664// Test how bubbles affect whether the top-of-window views are revealed.
665TEST_F(ImmersiveModeControllerAshTest, Bubbles) {
666  scoped_ptr<ImmersiveRevealedLock> revealed_lock;
667  views::Widget* top_container_widget = top_container()->GetWidget();
668
669  // Add views to the view hierarchy to which we will anchor bubbles.
670  views::View* child_view = new views::View();
671  child_view->SetBounds(0, 0, 10, 10);
672  top_container()->AddChildView(child_view);
673  views::View* unrelated_view = new views::View();
674  unrelated_view->SetBounds(0, 100, 10, 10);
675  top_container()->parent()->AddChildView(unrelated_view);
676
677  controller()->SetEnabled(true);
678  ASSERT_FALSE(controller()->IsRevealed());
679
680  // 1) Test that a bubble anchored to a child of the top container triggers
681  // a reveal and keeps the top-of-window views revealed for the duration of
682  // its visibility.
683  views::Widget* bubble_widget1(views::BubbleDelegateView::CreateBubble(
684      new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE)));
685  bubble_widget1->Show();
686  EXPECT_TRUE(controller()->IsRevealed());
687
688  // Activating |top_container_widget| will close |bubble_widget1|.
689  top_container_widget->Activate();
690  AttemptReveal(MODALITY_MOUSE);
691  revealed_lock.reset(controller()->GetRevealedLock(
692      ImmersiveModeController::ANIMATE_REVEAL_NO));
693  EXPECT_TRUE(controller()->IsRevealed());
694
695  views::Widget* bubble_widget2 = views::BubbleDelegateView::CreateBubble(
696      new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE));
697  bubble_widget2->Show();
698  EXPECT_TRUE(controller()->IsRevealed());
699  revealed_lock.reset();
700  SetHovered(false);
701  EXPECT_TRUE(controller()->IsRevealed());
702  bubble_widget2->Close();
703  EXPECT_FALSE(controller()->IsRevealed());
704
705  // 2) Test that transitioning from keeping the top-of-window views revealed
706  // because of a bubble to keeping the top-of-window views revealed because of
707  // mouse hover by activating |top_container_widget| works.
708  views::Widget* bubble_widget3 = views::BubbleDelegateView::CreateBubble(
709      new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE));
710  bubble_widget3->Show();
711  SetHovered(true);
712  EXPECT_TRUE(controller()->IsRevealed());
713
714  SetAnimationsDisabled(false);
715  // Activating |top_container_widget| will close |bubble_widget3|.
716  top_container_widget->Activate();
717  SetAnimationsDisabled(true);
718  EXPECT_TRUE(controller()->IsRevealed());
719
720  // 3) Test that the top-of-window views stay revealed as long as at least one
721  // bubble anchored to a child of the top container is visible.
722  SetHovered(false);
723  EXPECT_FALSE(controller()->IsRevealed());
724
725  views::BubbleDelegateView* bubble_delegate4(new views::BubbleDelegateView(
726      child_view, views::BubbleBorder::NONE));
727  bubble_delegate4->set_use_focusless(true);
728  views::Widget* bubble_widget4(views::BubbleDelegateView::CreateBubble(
729      bubble_delegate4));
730  bubble_widget4->Show();
731
732  views::BubbleDelegateView* bubble_delegate5(new views::BubbleDelegateView(
733      child_view, views::BubbleBorder::NONE));
734  bubble_delegate5->set_use_focusless(true);
735  views::Widget* bubble_widget5(views::BubbleDelegateView::CreateBubble(
736      bubble_delegate5));
737  bubble_widget5->Show();
738
739  EXPECT_TRUE(controller()->IsRevealed());
740  bubble_widget4->Hide();
741  EXPECT_TRUE(controller()->IsRevealed());
742  bubble_widget5->Hide();
743  EXPECT_FALSE(controller()->IsRevealed());
744  bubble_widget5->Show();
745  EXPECT_TRUE(controller()->IsRevealed());
746
747  // 4) Test that visibility changes which occur while immersive fullscreen is
748  // disabled are handled upon reenabling immersive fullscreen.
749  controller()->SetEnabled(false);
750  bubble_widget5->Hide();
751  controller()->SetEnabled(true);
752  EXPECT_FALSE(controller()->IsRevealed());
753
754  // We do not need |bubble_widget4| or |bubble_widget5| anymore, close them.
755  bubble_widget4->Close();
756  bubble_widget5->Close();
757
758  // 5) Test that a bubble added while immersive fullscreen is disabled is
759  // handled upon reenabling immersive fullscreen.
760  controller()->SetEnabled(false);
761
762  views::Widget* bubble_widget6 = views::BubbleDelegateView::CreateBubble(
763      new views::BubbleDelegateView(child_view, views::BubbleBorder::NONE));
764  bubble_widget6->Show();
765
766  controller()->SetEnabled(true);
767  EXPECT_TRUE(controller()->IsRevealed());
768
769  bubble_widget6->Close();
770
771  // 6) Test that a bubble which is not anchored to a child of the
772  // TopContainerView does not trigger a reveal or keep the
773  // top-of-window views revealed if they are already revealed.
774  views::Widget* bubble_widget7 = views::BubbleDelegateView::CreateBubble(
775      new views::BubbleDelegateView(unrelated_view, views::BubbleBorder::NONE));
776  bubble_widget7->Show();
777  EXPECT_FALSE(controller()->IsRevealed());
778
779  // Activating |top_container_widget| will close |bubble_widget6|.
780  top_container_widget->Activate();
781  AttemptReveal(MODALITY_MOUSE);
782  EXPECT_TRUE(controller()->IsRevealed());
783
784  views::Widget* bubble_widget8 = views::BubbleDelegateView::CreateBubble(
785      new views::BubbleDelegateView(unrelated_view, views::BubbleBorder::NONE));
786  bubble_widget8->Show();
787  SetHovered(false);
788  EXPECT_FALSE(controller()->IsRevealed());
789  bubble_widget8->Close();
790}
791
792#endif  // defined(OS_WIN)
793
794class ImmersiveModeControllerAshTestWithBrowserView
795    : public TestWithBrowserView {
796 public:
797  ImmersiveModeControllerAshTestWithBrowserView() {}
798  virtual ~ImmersiveModeControllerAshTestWithBrowserView() {}
799
800  // TestWithBrowserView override:
801  virtual void SetUp() OVERRIDE {
802    TestWithBrowserView::SetUp();
803
804    browser()->window()->Show();
805
806    ImmersiveFullscreenConfiguration::EnableImmersiveFullscreenForTest();
807    ASSERT_TRUE(ImmersiveFullscreenConfiguration::UseImmersiveFullscreen());
808
809    controller_ = static_cast<ImmersiveModeControllerAsh*>(
810        browser_view()->immersive_mode_controller());
811    controller_->DisableAnimationsForTest();
812
813    // Move the mouse so that it is not over the top-of-window views. The mouse
814    // position matters because entering immersive fullscreen causes synthesized
815    // mouse moves. (If the mouse is at the very top of the screen when entering
816    // immersive fullscreen, the top-of-window views will hide, then reveal as
817    // a result of the synthesized mouse moves).
818    controller()->SetMouseHoveredForTest(false);
819  }
820
821  // Returns the bounds of |view| in widget coordinates.
822  gfx::Rect GetBoundsInWidget(views::View* view) {
823    return view->ConvertRectToWidget(view->GetLocalBounds());
824  }
825
826  ImmersiveModeControllerAsh* controller() { return controller_; }
827
828 private:
829  // Not owned.
830  ImmersiveModeControllerAsh* controller_;
831
832  DISALLOW_COPY_AND_ASSIGN(ImmersiveModeControllerAshTestWithBrowserView);
833};
834
835// Test the layout and visibility of the tabstrip, toolbar and TopContainerView
836// in immersive fullscreen.
837TEST_F(ImmersiveModeControllerAshTestWithBrowserView, Layout) {
838  AddTab(browser(), GURL("about:blank"));
839
840  TabStrip* tabstrip = browser_view()->tabstrip();
841  ToolbarView* toolbar = browser_view()->toolbar();
842  views::WebView* contents_web_view =
843      browser_view()->GetContentsWebViewForTest();
844
845  // Immersive fullscreen starts out disabled.
846  ASSERT_FALSE(browser_view()->GetWidget()->IsFullscreen());
847  ASSERT_FALSE(controller()->IsEnabled());
848
849  // By default, the tabstrip and toolbar should be visible.
850  EXPECT_TRUE(tabstrip->visible());
851  EXPECT_TRUE(toolbar->visible());
852
853  // Enter immersive fullscreen.
854  {
855    // NOTIFICATION_FULLSCREEN_CHANGED is sent asynchronously.
856    scoped_ptr<FullscreenNotificationObserver> waiter(
857        new FullscreenNotificationObserver());
858    chrome::ToggleFullscreenMode(browser());
859    waiter->Wait();
860  }
861  EXPECT_TRUE(browser_view()->GetWidget()->IsFullscreen());
862  EXPECT_TRUE(controller()->IsEnabled());
863  EXPECT_FALSE(controller()->IsRevealed());
864
865  // Entering immersive fullscreen should make the tab strip use the immersive
866  // style and hide the toolbar.
867  EXPECT_TRUE(tabstrip->visible());
868  EXPECT_TRUE(tabstrip->IsImmersiveStyle());
869  EXPECT_FALSE(toolbar->visible());
870
871  // The tab indicators should be flush with the top of the widget.
872  EXPECT_EQ(0, GetBoundsInWidget(tabstrip).y());
873
874  // The web contents should be immediately below the tab indicators.
875  EXPECT_EQ(Tab::GetImmersiveHeight(),
876            GetBoundsInWidget(contents_web_view).y());
877
878  // Revealing the top-of-window views should set the tab strip back to the
879  // normal style and show the toolbar.
880  controller()->StartRevealForTest(true);
881  EXPECT_TRUE(controller()->IsRevealed());
882  EXPECT_TRUE(tabstrip->visible());
883  EXPECT_FALSE(tabstrip->IsImmersiveStyle());
884  EXPECT_TRUE(toolbar->visible());
885
886  // The TopContainerView should be flush with the top edge of the widget. If
887  // it is not flush with the top edge the immersive reveal animation looks
888  // wonky.
889  EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).y());
890
891  // The web contents should be at the same y position as they were when the
892  // top-of-window views were hidden.
893  EXPECT_EQ(Tab::GetImmersiveHeight(),
894            GetBoundsInWidget(contents_web_view).y());
895
896  // Repeat the test for when in both immersive fullscreen and tab fullscreen.
897  {
898    scoped_ptr<FullscreenNotificationObserver> waiter(
899        new FullscreenNotificationObserver());
900    browser()->fullscreen_controller()->ToggleFullscreenModeForTab(
901        contents_web_view->GetWebContents(), true);
902    waiter->Wait();
903  }
904  // Hide and reveal the top-of-window views so that they get relain out.
905  controller()->SetMouseHoveredForTest(false);
906  controller()->StartRevealForTest(true);
907
908  // The tab strip and toolbar should still be visible and the TopContainerView
909  // should still be flush with the top edge of the widget.
910  EXPECT_TRUE(controller()->IsRevealed());
911  EXPECT_TRUE(tabstrip->visible());
912  EXPECT_FALSE(tabstrip->IsImmersiveStyle());
913  EXPECT_TRUE(toolbar->visible());
914  EXPECT_EQ(0, GetBoundsInWidget(browser_view()->top_container()).y());
915
916  // The web contents should be flush with the top edge of the widget when in
917  // both immersive and tab fullscreen.
918  EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());
919
920  // Hide the top-of-window views. Both the tab strip and the toolbar should
921  // hide when in both immersive and tab fullscreen.
922  controller()->SetMouseHoveredForTest(false);
923  EXPECT_FALSE(controller()->IsRevealed());
924  EXPECT_FALSE(tabstrip->visible());
925  EXPECT_FALSE(toolbar->visible());
926
927  // The web contents should still be flush with the edge of the widget.
928  EXPECT_EQ(0, GetBoundsInWidget(contents_web_view).y());
929
930  // Exiting both immersive and tab fullscreen should show the tab strip and
931  // toolbar.
932  {
933    scoped_ptr<FullscreenNotificationObserver> waiter(
934        new FullscreenNotificationObserver());
935    chrome::ToggleFullscreenMode(browser());
936    waiter->Wait();
937  }
938  EXPECT_FALSE(browser_view()->GetWidget()->IsFullscreen());
939  EXPECT_FALSE(controller()->IsEnabled());
940  EXPECT_FALSE(controller()->IsRevealed());
941  EXPECT_TRUE(tabstrip->visible());
942  EXPECT_FALSE(tabstrip->IsImmersiveStyle());
943  EXPECT_TRUE(toolbar->visible());
944}
945
946// Test that the browser commands which are usually disabled in fullscreen are
947// are enabled in immersive fullscreen.
948TEST_F(ImmersiveModeControllerAshTestWithBrowserView, EnabledCommands) {
949  ASSERT_FALSE(controller()->IsEnabled());
950  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL));
951  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT));
952  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FOCUS_LOCATION));
953
954  chrome::ToggleFullscreenMode(browser());
955  EXPECT_TRUE(controller()->IsEnabled());
956  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_OPEN_CURRENT_URL));
957  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_ABOUT));
958  EXPECT_TRUE(chrome::IsCommandEnabled(browser(), IDC_FOCUS_LOCATION));
959}
960
961// Test that restoring a window properly exits immersive fullscreen.
962TEST_F(ImmersiveModeControllerAshTestWithBrowserView, ExitUponRestore) {
963  ASSERT_FALSE(controller()->IsEnabled());
964  chrome::ToggleFullscreenMode(browser());
965  controller()->StartRevealForTest(true);
966  ASSERT_TRUE(controller()->IsEnabled());
967  ASSERT_TRUE(controller()->IsRevealed());
968  ASSERT_TRUE(browser_view()->GetWidget()->IsFullscreen());
969
970  browser_view()->GetWidget()->Restore();
971  // Exiting immersive fullscreen occurs as a result of a task posted to the
972  // message loop.
973  content::RunAllPendingInMessageLoop();
974
975  EXPECT_FALSE(controller()->IsEnabled());
976}
977
978#endif  // defined(OS_CHROMEOS)
979