Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / ash / wm / immersive_fullscreen_controller.cc
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 "ash/wm/immersive_fullscreen_controller.h"
6
7 #include <set>
8
9 #include "ash/ash_constants.h"
10 #include "ash/shell.h"
11 #include "ash/wm/resize_handle_window_targeter.h"
12 #include "ash/wm/window_state.h"
13 #include "base/metrics/histogram.h"
14 #include "ui/aura/client/aura_constants.h"
15 #include "ui/aura/client/capture_client.h"
16 #include "ui/aura/client/cursor_client.h"
17 #include "ui/aura/client/screen_position_client.h"
18 #include "ui/aura/env.h"
19 #include "ui/aura/window.h"
20 #include "ui/aura/window_event_dispatcher.h"
21 #include "ui/gfx/animation/slide_animation.h"
22 #include "ui/gfx/display.h"
23 #include "ui/gfx/point.h"
24 #include "ui/gfx/rect.h"
25 #include "ui/gfx/screen.h"
26 #include "ui/views/bubble/bubble_delegate.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
29 #include "ui/wm/core/transient_window_manager.h"
30 #include "ui/wm/core/window_util.h"
31 #include "ui/wm/public/activation_client.h"
32
33 using views::View;
34
35 namespace ash {
36
37 namespace {
38
39 // Duration for the reveal show/hide slide animation. The slower duration is
40 // used for the initial slide out to give the user more change to see what
41 // happened.
42 const int kRevealSlowAnimationDurationMs = 400;
43 const int kRevealFastAnimationDurationMs = 200;
44
45 // The delay in milliseconds between the mouse stopping at the top edge of the
46 // screen and the top-of-window views revealing.
47 const int kMouseRevealDelayMs = 200;
48
49 // The maximum amount of pixels that the cursor can move for the cursor to be
50 // considered "stopped". This allows the user to reveal the top-of-window views
51 // without holding the cursor completely still.
52 const int kMouseRevealXThresholdPixels = 3;
53
54 // Used to multiply x value of an update in check to determine if gesture is
55 // vertical. This is used to make sure that gesture is close to vertical instead
56 // of just more vertical then horizontal.
57 const int kSwipeVerticalThresholdMultiplier = 3;
58
59 // The height in pixels of the region above the top edge of the display which
60 // hosts the immersive fullscreen window in which mouse events are ignored
61 // (cannot reveal or unreveal the top-of-window views).
62 // See ShouldIgnoreMouseEventAtLocation() for more details.
63 const int kHeightOfDeadRegionAboveTopContainer = 10;
64
65 // Returns the BubbleDelegateView corresponding to |maybe_bubble| if
66 // |maybe_bubble| is a bubble.
67 views::BubbleDelegateView* AsBubbleDelegate(aura::Window* maybe_bubble) {
68   if (!maybe_bubble)
69     return NULL;
70   views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble);
71   if (!widget)
72     return NULL;
73   return widget->widget_delegate()->AsBubbleDelegate();
74 }
75
76 // Returns true if |maybe_transient| is a transient child of |toplevel|.
77 bool IsWindowTransientChildOf(aura::Window* maybe_transient,
78                               aura::Window* toplevel) {
79   if (!maybe_transient || !toplevel)
80     return false;
81
82   for (aura::Window* window = maybe_transient; window;
83        window = ::wm::GetTransientParent(window)) {
84     if (window == toplevel)
85       return true;
86   }
87   return false;
88 }
89
90 // Returns the location of |event| in screen coordinates.
91 gfx::Point GetEventLocationInScreen(const ui::LocatedEvent& event) {
92   gfx::Point location_in_screen = event.location();
93   aura::Window* target = static_cast<aura::Window*>(event.target());
94   aura::client::ScreenPositionClient* screen_position_client =
95       aura::client::GetScreenPositionClient(target->GetRootWindow());
96   screen_position_client->ConvertPointToScreen(target, &location_in_screen);
97   return location_in_screen;
98 }
99
100 // Returns the bounds of the display nearest to |window| in screen coordinates.
101 gfx::Rect GetDisplayBoundsInScreen(aura::Window* window) {
102   return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds();
103 }
104
105 }  // namespace
106
107 // The height in pixels of the region below the top edge of the display in which
108 // the mouse can trigger revealing the top-of-window views.
109 #if defined(OS_WIN)
110 // Windows 8 reserves some pixels at the top of the screen for the hand icon
111 // that allows you to drag a metro app off the screen, so a few additional
112 // pixels of space must be reserved for the mouse reveal.
113 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 9;
114 #else
115 // The height must be greater than 1px because the top pixel is used to trigger
116 // moving the cursor between displays if the user has a vertical display layout
117 // (primary display above/below secondary display).
118 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 3;
119 #endif
120
121 ////////////////////////////////////////////////////////////////////////////////
122
123 // Class which keeps the top-of-window views revealed as long as one of the
124 // bubbles it is observing is visible. The logic to keep the top-of-window
125 // views revealed based on the visibility of bubbles anchored to
126 // children of |ImmersiveFullscreenController::top_container_| is separate from
127 // the logic related to |ImmersiveFullscreenController::focus_revealed_lock_|
128 // so that bubbles which are not activatable and bubbles which do not close
129 // upon deactivation also keep the top-of-window views revealed for the
130 // duration of their visibility.
131 class ImmersiveFullscreenController::BubbleManager
132     : public aura::WindowObserver {
133  public:
134   explicit BubbleManager(ImmersiveFullscreenController* controller);
135   virtual ~BubbleManager();
136
137   // Start / stop observing changes to |bubble|'s visibility.
138   void StartObserving(aura::Window* bubble);
139   void StopObserving(aura::Window* bubble);
140
141  private:
142   // Updates |revealed_lock_| based on whether any of |bubbles_| is visible.
143   void UpdateRevealedLock();
144
145   // aura::WindowObserver overrides:
146   virtual void OnWindowVisibilityChanged(aura::Window* window,
147                                          bool visible) OVERRIDE;
148   virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
149
150   ImmersiveFullscreenController* controller_;
151
152   std::set<aura::Window*> bubbles_;
153
154   // Lock which keeps the top-of-window views revealed based on whether any of
155   // |bubbles_| is visible.
156   scoped_ptr<ImmersiveRevealedLock> revealed_lock_;
157
158   DISALLOW_COPY_AND_ASSIGN(BubbleManager);
159 };
160
161 ImmersiveFullscreenController::BubbleManager::BubbleManager(
162     ImmersiveFullscreenController* controller)
163     : controller_(controller) {
164 }
165
166 ImmersiveFullscreenController::BubbleManager::~BubbleManager() {
167   for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
168        it != bubbles_.end(); ++it) {
169     (*it)->RemoveObserver(this);
170   }
171 }
172
173 void ImmersiveFullscreenController::BubbleManager::StartObserving(
174     aura::Window* bubble) {
175   if (bubbles_.insert(bubble).second) {
176     bubble->AddObserver(this);
177     UpdateRevealedLock();
178   }
179 }
180
181 void ImmersiveFullscreenController::BubbleManager::StopObserving(
182     aura::Window* bubble) {
183   if (bubbles_.erase(bubble)) {
184     bubble->RemoveObserver(this);
185     UpdateRevealedLock();
186   }
187 }
188
189 void ImmersiveFullscreenController::BubbleManager::UpdateRevealedLock() {
190   bool has_visible_bubble = false;
191   for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
192        it != bubbles_.end(); ++it) {
193     if ((*it)->IsVisible()) {
194       has_visible_bubble = true;
195       break;
196     }
197   }
198
199   bool was_revealed = controller_->IsRevealed();
200   if (has_visible_bubble) {
201     if (!revealed_lock_.get()) {
202       // Reveal the top-of-window views without animating because it looks
203       // weird for the top-of-window views to animate and the bubble not to
204       // animate along with the top-of-window views.
205       revealed_lock_.reset(controller_->GetRevealedLock(
206           ImmersiveFullscreenController::ANIMATE_REVEAL_NO));
207     }
208   } else {
209     revealed_lock_.reset();
210   }
211
212   if (!was_revealed && revealed_lock_.get()) {
213     // Currently, there is no nice way for bubbles to reposition themselves
214     // whenever the anchor view moves. Tell the bubbles to reposition themselves
215     // explicitly instead. The hidden bubbles are also repositioned because
216     // BubbleDelegateView does not reposition its widget as a result of a
217     // visibility change.
218     for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
219          it != bubbles_.end(); ++it) {
220       AsBubbleDelegate(*it)->OnAnchorBoundsChanged();
221     }
222   }
223 }
224
225 void ImmersiveFullscreenController::BubbleManager::OnWindowVisibilityChanged(
226     aura::Window*,
227     bool visible) {
228   UpdateRevealedLock();
229 }
230
231 void ImmersiveFullscreenController::BubbleManager::OnWindowDestroying(
232     aura::Window* window) {
233   StopObserving(window);
234 }
235
236 ////////////////////////////////////////////////////////////////////////////////
237
238 ImmersiveFullscreenController::ImmersiveFullscreenController()
239     : delegate_(NULL),
240       top_container_(NULL),
241       widget_(NULL),
242       native_window_(NULL),
243       observers_enabled_(false),
244       enabled_(false),
245       reveal_state_(CLOSED),
246       revealed_lock_count_(0),
247       mouse_x_when_hit_top_in_screen_(-1),
248       gesture_begun_(false),
249       animation_(new gfx::SlideAnimation(this)),
250       animations_disabled_for_test_(false),
251       weak_ptr_factory_(this) {
252 }
253
254 ImmersiveFullscreenController::~ImmersiveFullscreenController() {
255   EnableWindowObservers(false);
256 }
257
258 void ImmersiveFullscreenController::Init(Delegate* delegate,
259                                          views::Widget* widget,
260                                          views::View* top_container) {
261   delegate_ = delegate;
262   top_container_ = top_container;
263   widget_ = widget;
264   native_window_ = widget_->GetNativeWindow();
265   native_window_->SetEventTargeter(scoped_ptr<ui::EventTargeter>(
266       new ResizeHandleWindowTargeter(native_window_, this)));
267 }
268
269 void ImmersiveFullscreenController::SetEnabled(WindowType window_type,
270                                                bool enabled) {
271   if (enabled_ == enabled)
272     return;
273   enabled_ = enabled;
274
275   EnableWindowObservers(enabled_);
276
277   // Auto hide the shelf in immersive fullscreen instead of hiding it.
278   wm::GetWindowState(native_window_)->set_hide_shelf_when_fullscreen(!enabled);
279   Shell::GetInstance()->UpdateShelfVisibility();
280
281   if (enabled_) {
282     // Animate enabling immersive mode by sliding out the top-of-window views.
283     // No animation occurs if a lock is holding the top-of-window views open.
284
285     // Do a reveal to set the initial state for the animation. (And any
286     // required state in case the animation cannot run because of a lock holding
287     // the top-of-window views open.)
288     MaybeStartReveal(ANIMATE_NO);
289
290     // Reset the located event and the focus revealed locks so that they do not
291     // affect whether the top-of-window views are hidden.
292     located_event_revealed_lock_.reset();
293     focus_revealed_lock_.reset();
294
295     // Try doing the animation.
296     MaybeEndReveal(ANIMATE_SLOW);
297
298     if (reveal_state_ == REVEALED) {
299       // Reveal was unsuccessful. Reacquire the revealed locks if appropriate.
300       UpdateLocatedEventRevealedLock(NULL);
301       UpdateFocusRevealedLock();
302     } else {
303       // Clearing focus is important because it closes focus-related popups like
304       // the touch selection handles.
305       widget_->GetFocusManager()->ClearFocus();
306     }
307   } else {
308     // Stop cursor-at-top tracking.
309     top_edge_hover_timer_.Stop();
310     reveal_state_ = CLOSED;
311
312     delegate_->OnImmersiveFullscreenExited();
313   }
314
315   if (enabled_) {
316     UMA_HISTOGRAM_ENUMERATION("Ash.ImmersiveFullscreen.WindowType",
317                               window_type,
318                               WINDOW_TYPE_COUNT);
319   }
320 }
321
322 bool ImmersiveFullscreenController::IsEnabled() const {
323   return enabled_;
324 }
325
326 bool ImmersiveFullscreenController::IsRevealed() const {
327   return enabled_ && reveal_state_ != CLOSED;
328 }
329
330 ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock(
331     AnimateReveal animate_reveal) {
332   return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(),
333                                    animate_reveal);
334 }
335
336 ////////////////////////////////////////////////////////////////////////////////
337 // Testing interface:
338
339 void ImmersiveFullscreenController::SetupForTest() {
340   DCHECK(!enabled_);
341   animations_disabled_for_test_ = true;
342
343   // Move the mouse off of the top-of-window views so that it does not keep the
344   // top-of-window views revealed.
345   std::vector<gfx::Rect> bounds_in_screen(
346       delegate_->GetVisibleBoundsInScreen());
347   DCHECK(!bounds_in_screen.empty());
348   int bottommost_in_screen = bounds_in_screen[0].bottom();
349   for (size_t i = 1; i < bounds_in_screen.size(); ++i) {
350     if (bounds_in_screen[i].bottom() > bottommost_in_screen)
351       bottommost_in_screen = bounds_in_screen[i].bottom();
352   }
353   gfx::Point cursor_pos(0, bottommost_in_screen + 100);
354   aura::Env::GetInstance()->set_last_mouse_location(cursor_pos);
355   UpdateLocatedEventRevealedLock(NULL);
356 }
357
358 ////////////////////////////////////////////////////////////////////////////////
359 // ui::EventHandler overrides:
360
361 void ImmersiveFullscreenController::OnMouseEvent(ui::MouseEvent* event) {
362   if (!enabled_)
363     return;
364
365   if (event->type() != ui::ET_MOUSE_MOVED &&
366       event->type() != ui::ET_MOUSE_PRESSED &&
367       event->type() != ui::ET_MOUSE_RELEASED &&
368       event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
369     return;
370   }
371
372   // Mouse hover can initiate revealing the top-of-window views while |widget_|
373   // is inactive.
374
375   if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) {
376     top_edge_hover_timer_.Stop();
377     UpdateLocatedEventRevealedLock(event);
378   } else if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
379     // Trigger a reveal if the cursor pauses at the top of the screen for a
380     // while.
381     UpdateTopEdgeHoverTimer(event);
382   }
383 }
384
385 void ImmersiveFullscreenController::OnTouchEvent(ui::TouchEvent* event) {
386   if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED)
387     return;
388
389   // Touch should not initiate revealing the top-of-window views while |widget_|
390   // is inactive.
391   if (!widget_->IsActive())
392     return;
393
394   UpdateLocatedEventRevealedLock(event);
395 }
396
397 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) {
398   if (!enabled_)
399     return;
400
401   // Touch gestures should not initiate revealing the top-of-window views while
402   // |widget_| is inactive.
403   if (!widget_->IsActive())
404     return;
405
406   switch (event->type()) {
407 #if defined(OS_WIN)
408     case ui::ET_GESTURE_WIN8_EDGE_SWIPE:
409       UpdateRevealedLocksForSwipe(GetSwipeType(event));
410       event->SetHandled();
411       break;
412 #endif
413     case ui::ET_GESTURE_SCROLL_BEGIN:
414       if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) {
415         gesture_begun_ = true;
416         // Do not consume the event. Otherwise, we end up consuming all
417         // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views
418         // when the top-of-window views are revealed.
419       }
420       break;
421     case ui::ET_GESTURE_SCROLL_UPDATE:
422       if (gesture_begun_) {
423         if (UpdateRevealedLocksForSwipe(GetSwipeType(event)))
424           event->SetHandled();
425         gesture_begun_ = false;
426       }
427       break;
428     case ui::ET_GESTURE_SCROLL_END:
429     case ui::ET_SCROLL_FLING_START:
430       gesture_begun_ = false;
431       break;
432     default:
433       break;
434   }
435 }
436
437 ////////////////////////////////////////////////////////////////////////////////
438 // views::FocusChangeListener overrides:
439
440 void ImmersiveFullscreenController::OnWillChangeFocus(
441     views::View* focused_before,
442     views::View* focused_now) {
443 }
444
445 void ImmersiveFullscreenController::OnDidChangeFocus(
446     views::View* focused_before,
447     views::View* focused_now) {
448   UpdateFocusRevealedLock();
449 }
450
451 ////////////////////////////////////////////////////////////////////////////////
452 // views::WidgetObserver overrides:
453
454 void ImmersiveFullscreenController::OnWidgetDestroying(views::Widget* widget) {
455   EnableWindowObservers(false);
456   native_window_ = NULL;
457
458   // Set |enabled_| to false such that any calls to MaybeStartReveal() and
459   // MaybeEndReveal() have no effect.
460   enabled_ = false;
461 }
462
463 void ImmersiveFullscreenController::OnWidgetActivationChanged(
464     views::Widget* widget,
465     bool active) {
466   UpdateFocusRevealedLock();
467 }
468
469 ////////////////////////////////////////////////////////////////////////////////
470 // gfx::AnimationDelegate overrides:
471
472 void ImmersiveFullscreenController::AnimationEnded(
473     const gfx::Animation* animation) {
474   if (reveal_state_ == SLIDING_OPEN) {
475     OnSlideOpenAnimationCompleted();
476   } else if (reveal_state_ == SLIDING_CLOSED) {
477     OnSlideClosedAnimationCompleted();
478   }
479 }
480
481 void ImmersiveFullscreenController::AnimationProgressed(
482     const gfx::Animation* animation) {
483   delegate_->SetVisibleFraction(animation->GetCurrentValue());
484 }
485
486 ////////////////////////////////////////////////////////////////////////////////
487 // aura::WindowObserver overrides:
488
489 void ImmersiveFullscreenController::OnTransientChildAdded(
490     aura::Window* window,
491     aura::Window* transient) {
492   views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient);
493   if (bubble_delegate &&
494       bubble_delegate->GetAnchorView() &&
495       top_container_->Contains(bubble_delegate->GetAnchorView())) {
496     // Observe the aura::Window because the BubbleDelegateView may not be
497     // parented to the widget's root view yet so |bubble_delegate->GetWidget()|
498     // may still return NULL.
499     bubble_manager_->StartObserving(transient);
500   }
501 }
502
503 void ImmersiveFullscreenController::OnTransientChildRemoved(
504     aura::Window* window,
505     aura::Window* transient) {
506   bubble_manager_->StopObserving(transient);
507 }
508
509 ////////////////////////////////////////////////////////////////////////////////
510 // ash::ImmersiveRevealedLock::Delegate overrides:
511
512 void ImmersiveFullscreenController::LockRevealedState(
513     AnimateReveal animate_reveal) {
514   ++revealed_lock_count_;
515   Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ?
516       ANIMATE_FAST : ANIMATE_NO;
517   MaybeStartReveal(animate);
518 }
519
520 void ImmersiveFullscreenController::UnlockRevealedState() {
521   --revealed_lock_count_;
522   DCHECK_GE(revealed_lock_count_, 0);
523   if (revealed_lock_count_ == 0) {
524     // Always animate ending the reveal fast.
525     MaybeEndReveal(ANIMATE_FAST);
526   }
527 }
528
529 ////////////////////////////////////////////////////////////////////////////////
530 // private:
531
532 void ImmersiveFullscreenController::EnableWindowObservers(bool enable) {
533   if (observers_enabled_ == enable)
534     return;
535   observers_enabled_ = enable;
536
537   views::FocusManager* focus_manager = widget_->GetFocusManager();
538
539   if (enable) {
540     widget_->AddObserver(this);
541     focus_manager->AddFocusChangeListener(this);
542     Shell::GetInstance()->AddPreTargetHandler(this);
543     ::wm::TransientWindowManager::Get(native_window_)->
544         AddObserver(this);
545
546     RecreateBubbleManager();
547   } else {
548     widget_->RemoveObserver(this);
549     focus_manager->RemoveFocusChangeListener(this);
550     Shell::GetInstance()->RemovePreTargetHandler(this);
551     ::wm::TransientWindowManager::Get(native_window_)->
552         RemoveObserver(this);
553
554     // We have stopped observing whether transient children are added or removed
555     // to |native_window_|. The set of bubbles that BubbleManager is observing
556     // will become stale really quickly. Destroy BubbleManager and recreate it
557     // when we start observing |native_window_| again.
558     bubble_manager_.reset();
559
560     animation_->Stop();
561   }
562 }
563
564 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer(
565     ui::MouseEvent* event) {
566   DCHECK(enabled_);
567   DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED);
568
569   // Check whether |native_window_| is the event target's parent window instead
570   // of checking for activation. This allows the timer to be started when
571   // |widget_| is inactive but prevents starting the timer if the mouse is over
572   // a portion of the top edge obscured by an unrelated widget.
573   if (!top_edge_hover_timer_.IsRunning() &&
574       !native_window_->Contains(static_cast<aura::Window*>(event->target()))) {
575     return;
576   }
577
578   // Mouse hover should not initiate revealing the top-of-window views while a
579   // window has mouse capture.
580   if (aura::client::GetCaptureWindow(native_window_))
581     return;
582
583   gfx::Point location_in_screen = GetEventLocationInScreen(*event);
584   if (ShouldIgnoreMouseEventAtLocation(location_in_screen))
585     return;
586
587   // Stop the timer if the cursor left the top edge or is on a different
588   // display.
589   gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen(native_window_);
590   hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight);
591   if (!hit_bounds_in_screen.Contains(location_in_screen)) {
592     top_edge_hover_timer_.Stop();
593     return;
594   }
595
596   // The cursor is now at the top of the screen. Consider the cursor "not
597   // moving" even if it moves a little bit because users don't have perfect
598   // pointing precision. (The y position is not tested because
599   // |hit_bounds_in_screen| is short.)
600   if (top_edge_hover_timer_.IsRunning() &&
601       abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <=
602           kMouseRevealXThresholdPixels)
603     return;
604
605   // Start the reveal if the cursor doesn't move for some amount of time.
606   mouse_x_when_hit_top_in_screen_ = location_in_screen.x();
607   top_edge_hover_timer_.Stop();
608   // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
609   top_edge_hover_timer_.Start(
610       FROM_HERE,
611       base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs),
612       base::Bind(
613           &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock,
614           base::Unretained(this)));
615 }
616
617 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock(
618     ui::LocatedEvent* event) {
619   if (!enabled_)
620     return;
621   DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent());
622
623   // Neither the mouse nor touch can initiate a reveal when the top-of-window
624   // views are sliding closed or are closed with the following exceptions:
625   // - Hovering at y = 0 which is handled in OnMouseEvent().
626   // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
627   if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED)
628     return;
629
630   // For the sake of simplicity, ignore |widget_|'s activation in computing
631   // whether the top-of-window views should stay revealed. Ideally, the
632   // top-of-window views would stay revealed only when the mouse cursor is
633   // hovered above a non-obscured portion of the top-of-window views. The
634   // top-of-window views may be partially obscured when |widget_| is inactive.
635
636   // Ignore all events while a window has capture. This keeps the top-of-window
637   // views revealed during a drag.
638   if (aura::client::GetCaptureWindow(native_window_))
639     return;
640
641   gfx::Point location_in_screen;
642   if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
643     location_in_screen = GetEventLocationInScreen(*event);
644   } else {
645     aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
646         native_window_->GetRootWindow());
647     if (!cursor_client->IsMouseEventsEnabled()) {
648       // If mouse events are disabled, the user's last interaction was probably
649       // via touch. Do no do further processing in this case as there is no easy
650       // way of retrieving the position of the user's last touch.
651       return;
652     }
653     location_in_screen = aura::Env::GetInstance()->last_mouse_location();
654   }
655
656   if ((!event || event->IsMouseEvent()) &&
657       ShouldIgnoreMouseEventAtLocation(location_in_screen)) {
658     return;
659   }
660
661   // The visible bounds of |top_container_| should be contained in
662   // |hit_bounds_in_screen|.
663   std::vector<gfx::Rect> hit_bounds_in_screen =
664       delegate_->GetVisibleBoundsInScreen();
665   bool keep_revealed = false;
666   for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
667     // Allow the cursor to move slightly off the top-of-window views before
668     // sliding closed. In the case of ImmersiveModeControllerAsh, this helps
669     // when the user is attempting to click on the bookmark bar and overshoots
670     // slightly.
671     if (event && event->type() == ui::ET_MOUSE_MOVED) {
672       const int kBoundsOffsetY = 8;
673       hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY);
674     }
675
676     if (hit_bounds_in_screen[i].Contains(location_in_screen)) {
677       keep_revealed = true;
678       break;
679     }
680   }
681
682   if (keep_revealed)
683     AcquireLocatedEventRevealedLock();
684   else
685     located_event_revealed_lock_.reset();
686 }
687
688 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() {
689   // CAUTION: Acquiring the lock results in a reentrant call to
690   // AcquireLocatedEventRevealedLock() when
691   // |ImmersiveFullscreenController::animations_disabled_for_test_| is true.
692   if (!located_event_revealed_lock_.get())
693     located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
694 }
695
696 void ImmersiveFullscreenController::UpdateFocusRevealedLock() {
697   if (!enabled_)
698     return;
699
700   bool hold_lock = false;
701   if (widget_->IsActive()) {
702     views::View* focused_view = widget_->GetFocusManager()->GetFocusedView();
703     if (top_container_->Contains(focused_view))
704       hold_lock = true;
705   } else {
706     aura::Window* active_window = aura::client::GetActivationClient(
707         native_window_->GetRootWindow())->GetActiveWindow();
708     views::BubbleDelegateView* bubble_delegate =
709         AsBubbleDelegate(active_window);
710     if (bubble_delegate && bubble_delegate->anchor_widget()) {
711       // BubbleManager will already have locked the top-of-window views if the
712       // bubble is anchored to a child of |top_container_|. Don't acquire
713       // |focus_revealed_lock_| here for the sake of simplicity.
714       // Note: Instead of checking for the existence of the |anchor_view|,
715       // the existence of the |anchor_widget| is performed to avoid the case
716       // where the view is already gone (and the widget is still running).
717     } else {
718       // The currently active window is not |native_window_| and it is not a
719       // bubble with an anchor view. The top-of-window views should be revealed
720       // if:
721       // 1) The active window is a transient child of |native_window_|.
722       // 2) The top-of-window views are already revealed. This restriction
723       //    prevents a transient window opened by the web contents while the
724       //    top-of-window views are hidden from from initiating a reveal.
725       // The top-of-window views will stay revealed till |native_window_| is
726       // reactivated.
727       if (IsRevealed() &&
728           IsWindowTransientChildOf(active_window, native_window_)) {
729         hold_lock = true;
730       }
731     }
732   }
733
734   if (hold_lock) {
735     if (!focus_revealed_lock_.get())
736       focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
737   } else {
738     focus_revealed_lock_.reset();
739   }
740 }
741
742 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe(
743     SwipeType swipe_type) {
744   if (!enabled_ || swipe_type == SWIPE_NONE)
745     return false;
746
747   // Swipes while |native_window_| is inactive should have been filtered out in
748   // OnGestureEvent().
749   DCHECK(widget_->IsActive());
750
751   if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
752     if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) {
753       located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
754       return true;
755     }
756   } else {
757     if (swipe_type == SWIPE_CLOSE) {
758       // Attempt to end the reveal. If other code is holding onto a lock, the
759       // attempt will be unsuccessful.
760       located_event_revealed_lock_.reset();
761       focus_revealed_lock_.reset();
762
763       if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
764         widget_->GetFocusManager()->ClearFocus();
765         return true;
766       }
767
768       // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
769       UpdateLocatedEventRevealedLock(NULL);
770       UpdateFocusRevealedLock();
771     }
772   }
773   return false;
774 }
775
776 int ImmersiveFullscreenController::GetAnimationDuration(Animate animate) const {
777   switch (animate) {
778     case ANIMATE_NO:
779       return 0;
780     case ANIMATE_SLOW:
781       return kRevealSlowAnimationDurationMs;
782     case ANIMATE_FAST:
783       return kRevealFastAnimationDurationMs;
784   }
785   NOTREACHED();
786   return 0;
787 }
788
789 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) {
790   if (!enabled_)
791     return;
792
793   if (animations_disabled_for_test_)
794     animate = ANIMATE_NO;
795
796   // Callers with ANIMATE_NO expect this function to synchronously reveal the
797   // top-of-window views.
798   if (reveal_state_ == REVEALED ||
799       (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) {
800     return;
801   }
802
803   RevealState previous_reveal_state = reveal_state_;
804   reveal_state_ = SLIDING_OPEN;
805   if (previous_reveal_state == CLOSED) {
806     delegate_->OnImmersiveRevealStarted();
807
808     // Do not do any more processing if OnImmersiveRevealStarted() changed
809     // |reveal_state_|.
810     if (reveal_state_ != SLIDING_OPEN)
811       return;
812   }
813   // Slide in the reveal view.
814   if (animate == ANIMATE_NO) {
815     animation_->Reset(1);
816     OnSlideOpenAnimationCompleted();
817   } else {
818     animation_->SetSlideDuration(GetAnimationDuration(animate));
819     animation_->Show();
820   }
821 }
822
823 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() {
824   DCHECK_EQ(SLIDING_OPEN, reveal_state_);
825   reveal_state_ = REVEALED;
826   delegate_->SetVisibleFraction(1);
827
828   // The user may not have moved the mouse since the reveal was initiated.
829   // Update the revealed lock to reflect the mouse's current state.
830   UpdateLocatedEventRevealedLock(NULL);
831 }
832
833 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) {
834   if (!enabled_ || revealed_lock_count_ != 0)
835     return;
836
837   if (animations_disabled_for_test_)
838     animate = ANIMATE_NO;
839
840   // Callers with ANIMATE_NO expect this function to synchronously close the
841   // top-of-window views.
842   if (reveal_state_ == CLOSED ||
843       (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) {
844     return;
845   }
846
847   reveal_state_ = SLIDING_CLOSED;
848   int duration_ms = GetAnimationDuration(animate);
849   if (duration_ms > 0) {
850     animation_->SetSlideDuration(duration_ms);
851     animation_->Hide();
852   } else {
853     animation_->Reset(0);
854     OnSlideClosedAnimationCompleted();
855   }
856 }
857
858 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
859   DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
860   reveal_state_ = CLOSED;
861   delegate_->OnImmersiveRevealEnded();
862 }
863
864 ImmersiveFullscreenController::SwipeType
865 ImmersiveFullscreenController::GetSwipeType(ui::GestureEvent* event) const {
866 #if defined(OS_WIN)
867   if (event->type() == ui::ET_GESTURE_WIN8_EDGE_SWIPE)
868     return SWIPE_OPEN;
869 #endif
870   if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE)
871     return SWIPE_NONE;
872   // Make sure that it is a clear vertical gesture.
873   if (abs(event->details().scroll_y()) <=
874       kSwipeVerticalThresholdMultiplier * abs(event->details().scroll_x()))
875     return SWIPE_NONE;
876   if (event->details().scroll_y() < 0)
877     return SWIPE_CLOSE;
878   else if (event->details().scroll_y() > 0)
879     return SWIPE_OPEN;
880   return SWIPE_NONE;
881 }
882
883 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation(
884     const gfx::Point& location) const {
885   // Ignore mouse events in the region immediately above the top edge of the
886   // display. This is to handle the case of a user with a vertical display
887   // layout (primary display above/below secondary display) and the immersive
888   // fullscreen window on the bottom display. It is really hard to trigger a
889   // reveal in this case because:
890   // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
891   //   pixels of the bottom display.
892   // - The cursor is warped to the top display if the cursor gets to the top
893   //   edge of the bottom display.
894   // Mouse events are ignored in the bottom few pixels of the top display
895   // (Mouse events in this region cannot start or end a reveal). This allows a
896   // user to overshoot the top of the bottom display and still reveal the
897   // top-of-window views.
898   gfx::Rect dead_region = GetDisplayBoundsInScreen(native_window_);
899   dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer);
900   dead_region.set_height(kHeightOfDeadRegionAboveTopContainer);
901   return dead_region.Contains(location);
902 }
903
904 bool ImmersiveFullscreenController::ShouldHandleGestureEvent(
905     const gfx::Point& location) const {
906   DCHECK(widget_->IsActive());
907   if (reveal_state_ == REVEALED) {
908     std::vector<gfx::Rect> hit_bounds_in_screen(
909         delegate_->GetVisibleBoundsInScreen());
910     for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
911       if (hit_bounds_in_screen[i].Contains(location))
912         return true;
913     }
914     return false;
915   }
916
917   // When the top-of-window views are not fully revealed, handle gestures which
918   // start in the top few pixels of the screen.
919   gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen(native_window_));
920   hit_bounds_in_screen.set_height(kImmersiveFullscreenTopEdgeInset);
921   if (hit_bounds_in_screen.Contains(location))
922     return true;
923
924   // There may be a bezel sensor off screen logically above
925   // |hit_bounds_in_screen|. The check for the event not contained by the
926   // closest screen ensures that the event is from a valid bezel (as opposed to
927   // another screen in an extended desktop).
928   gfx::Rect screen_bounds =
929       Shell::GetScreen()->GetDisplayNearestPoint(location).bounds();
930   return (!screen_bounds.Contains(location) &&
931           location.y() < hit_bounds_in_screen.y() &&
932           location.x() >= hit_bounds_in_screen.x() &&
933           location.x() < hit_bounds_in_screen.right());
934 }
935
936 void ImmersiveFullscreenController::RecreateBubbleManager() {
937   bubble_manager_.reset(new BubbleManager(this));
938   const std::vector<aura::Window*> transient_children =
939       ::wm::GetTransientChildren(native_window_);
940   for (size_t i = 0; i < transient_children.size(); ++i) {
941     aura::Window* transient_child = transient_children[i];
942     views::BubbleDelegateView* bubble_delegate =
943         AsBubbleDelegate(transient_child);
944     if (bubble_delegate &&
945         bubble_delegate->GetAnchorView() &&
946         top_container_->Contains(bubble_delegate->GetAnchorView())) {
947       bubble_manager_->StartObserving(transient_child);
948     }
949   }
950 }
951
952 }  // namespace ash