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