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 "chromeos/ui/frame/immersive/immersive_fullscreen_controller.h"
6 
7 #include <set>
8 
9 #include "base/bind.h"
10 #include "chromeos/ui/base/window_properties.h"
11 #include "chromeos/ui/frame/immersive/immersive_context.h"
12 #include "chromeos/ui/frame/immersive/immersive_focus_watcher.h"
13 #include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_delegate.h"
14 #include "ui/aura/client/aura_constants.h"
15 #include "ui/aura/client/cursor_client.h"
16 #include "ui/aura/env.h"
17 #include "ui/aura/window.h"
18 #include "ui/aura/window_targeter.h"
19 #include "ui/display/display.h"
20 #include "ui/display/screen.h"
21 #include "ui/events/base_event_utils.h"
22 #include "ui/gfx/animation/animation_delegate_notifier.h"
23 #include "ui/gfx/geometry/point.h"
24 #include "ui/gfx/geometry/rect.h"
25 #include "ui/views/animation/animation_delegate_views.h"
26 #include "ui/views/bubble/bubble_dialog_delegate_view.h"
27 #include "ui/views/view.h"
28 #include "ui/views/widget/widget.h"
29 
30 DEFINE_UI_CLASS_PROPERTY_TYPE(chromeos::ImmersiveFullscreenController*)
31 
32 namespace chromeos {
33 
34 DEFINE_UI_CLASS_PROPERTY_KEY(ImmersiveFullscreenController*,
35                              kImmersiveFullscreenControllerKey,
36                              nullptr)
37 
38 namespace {
39 
40 // A window targeter installed on a Widget's window when it's in immersive mode.
41 // This targeter insets the touch area for direct children of the window it's
42 // installed on (see calls to SetInsets) so that gestures at the top of the
43 // screen will be directed to the Widget window for triggering immersive reveal.
44 // The insets are disabled while the top view is revealed.
45 class ImmersiveWindowTargeter : public aura::WindowTargeter {
46  public:
47   ImmersiveWindowTargeter() = default;
48   ~ImmersiveWindowTargeter() override = default;
49 
ShouldUseExtendedBounds(const aura::Window * target) const50   bool ShouldUseExtendedBounds(const aura::Window* target) const override {
51     return target->parent() == window();
52   }
53 
54  private:
55   DISALLOW_COPY_AND_ASSIGN(ImmersiveWindowTargeter);
56 };
57 
58 // The delay in milliseconds between the mouse stopping at the top edge of the
59 // screen and the top-of-window views revealing.
60 const int kMouseRevealDelayMs = 200;
61 
62 // The maximum amount of pixels that the cursor can move for the cursor to be
63 // considered "stopped". This allows the user to reveal the top-of-window views
64 // without holding the cursor completely still.
65 const int kMouseRevealXThresholdPixels = 3;
66 
67 // Used to multiply x value of an update in check to determine if gesture is
68 // vertical. This is used to make sure that gesture is close to vertical instead
69 // of just more vertical then horizontal.
70 const int kSwipeVerticalThresholdMultiplier = 3;
71 
72 // The height in pixels of the region above the top edge of the display which
73 // hosts the immersive fullscreen window in which mouse events are ignored
74 // (cannot reveal or unreveal the top-of-window views).
75 // See ShouldIgnoreMouseEventAtLocation() for more details.
76 const int kHeightOfDeadRegionAboveTopContainer = 10;
77 
78 }  // namespace
79 
80 // static
81 const int ImmersiveFullscreenController::kImmersiveFullscreenTopEdgeInset = 8;
82 
83 // static
84 const int ImmersiveFullscreenController::kMouseRevealBoundsHeight = 3;
85 
86 // static
87 bool ImmersiveFullscreenController::value_for_animations_disabled_for_test_ =
88     false;
89 
90 ////////////////////////////////////////////////////////////////////////////////
91 
ImmersiveFullscreenController()92 ImmersiveFullscreenController::ImmersiveFullscreenController()
93     : animations_disabled_for_test_(value_for_animations_disabled_for_test_) {}
94 
~ImmersiveFullscreenController()95 ImmersiveFullscreenController::~ImmersiveFullscreenController() {
96   EnableEventObservers(false);
97   EnableWindowObservers(false);
98 }
99 
Init(ImmersiveFullscreenControllerDelegate * delegate,views::Widget * widget,views::View * top_container)100 void ImmersiveFullscreenController::Init(
101     ImmersiveFullscreenControllerDelegate* delegate,
102     views::Widget* widget,
103     views::View* top_container) {
104   // This function may be called more than once (e.g. by
105   // ClientControlledShellSurface).
106   EnableWindowObservers(false);
107 
108   delegate_ = delegate;
109   top_container_ = top_container;
110   animation_notifier_ = std::make_unique<
111       gfx::AnimationDelegateNotifier<views::AnimationDelegateViews>>(
112       this, top_container);
113   animation_ = std::make_unique<gfx::SlideAnimation>(animation_notifier_.get());
114   widget_ = widget;
115 
116   // A widget can have more than one ImmersiveFullscreenController
117   // (WideFrameView does this), so this key only tracks the first
118   // ImmersiveFullscreenController.
119   if (nullptr == widget->GetNativeWindow()->GetProperty(
120                      kImmersiveFullscreenControllerKey)) {
121     widget->GetNativeWindow()->SetProperty(kImmersiveFullscreenControllerKey,
122                                            this);
123   }
124 
125   EnableWindowObservers(true);
126 }
127 
IsEnabled() const128 bool ImmersiveFullscreenController::IsEnabled() const {
129   return enabled_;
130 }
131 
IsRevealed() const132 bool ImmersiveFullscreenController::IsRevealed() const {
133   return enabled_ && reveal_state_ != CLOSED;
134 }
135 
GetRevealedLock(AnimateReveal animate_reveal)136 ImmersiveRevealedLock* ImmersiveFullscreenController::GetRevealedLock(
137     AnimateReveal animate_reveal) {
138   return new ImmersiveRevealedLock(weak_ptr_factory_.GetWeakPtr(),
139                                    animate_reveal);
140 }
141 
142 ///////////////////////////////////////////////////////////////////////////////
143 // ui::EventObserver overrides:
144 
OnEvent(const ui::Event & event)145 void ImmersiveFullscreenController::OnEvent(const ui::Event& event) {
146   if (!event.IsLocatedEvent())
147     return;
148 
149   const ui::LocatedEvent* located_event = event.AsLocatedEvent();
150   aura::Window* target = static_cast<aura::Window*>(event.target());
151   if (event.IsMouseEvent()) {
152     HandleMouseEvent(*event.AsMouseEvent(), located_event->root_location(),
153                      views::Widget::GetTopLevelWidgetForNativeView(target));
154   } else if (event.IsTouchEvent()) {
155     HandleTouchEvent(*event.AsTouchEvent(), located_event->root_location());
156   }
157 }
158 
159 ////////////////////////////////////////////////////////////////////////////////
160 // ui::EventHandler overrides:
161 
OnEvent(ui::Event * event)162 void ImmersiveFullscreenController::OnEvent(ui::Event* event) {
163   ui::EventHandler::OnEvent(event);
164 }
165 
OnGestureEvent(ui::GestureEvent * event)166 void ImmersiveFullscreenController::OnGestureEvent(ui::GestureEvent* event) {
167   if (!enabled_)
168     return;
169 
170   // Touch gestures should not initiate revealing the top-of-window views while
171   // |widget_| is inactive.
172   if (!widget_->IsActive())
173     return;
174 
175   switch (event->type()) {
176     case ui::ET_GESTURE_SCROLL_BEGIN:
177       if (ShouldHandleGestureEvent(
178               event->target()->GetScreenLocation(*event))) {
179         gesture_begun_ = true;
180         // Do not consume the event. Otherwise, we end up consuming all
181         // ui::ET_GESTURE_SCROLL_BEGIN events in the top-of-window views
182         // when the top-of-window views are revealed.
183       }
184       break;
185     case ui::ET_GESTURE_SCROLL_UPDATE:
186       if (gesture_begun_) {
187         if (UpdateRevealedLocksForSwipe(GetSwipeType(*event)))
188           event->SetHandled();
189         gesture_begun_ = false;
190       }
191       break;
192     case ui::ET_GESTURE_SCROLL_END:
193     case ui::ET_SCROLL_FLING_START:
194       gesture_begun_ = false;
195       break;
196     default:
197       break;
198   }
199 }
200 
201 ////////////////////////////////////////////////////////////////////////////////
202 // aura::WindowObserver overrides:
203 
OnWindowPropertyChanged(aura::Window * window,const void * key,intptr_t old)204 void ImmersiveFullscreenController::OnWindowPropertyChanged(
205     aura::Window* window,
206     const void* key,
207     intptr_t old) {
208   if (key == kImmersiveIsActive)
209     UpdateEnabled();
210 }
211 
OnWindowDestroying(aura::Window * window)212 void ImmersiveFullscreenController::OnWindowDestroying(aura::Window* window) {
213   EnableEventObservers(false);
214   EnableWindowObservers(false);
215 
216   // Set |enabled_| to false such that any calls to MaybeStartReveal() and
217   // MaybeEndReveal() have no effect.
218   enabled_ = false;
219   widget_ = nullptr;
220 }
221 
222 ////////////////////////////////////////////////////////////////////////////////
223 // views::Observer overrides:
224 
OnViewBoundsChanged(views::View * observed_view)225 void ImmersiveFullscreenController::OnViewBoundsChanged(
226     views::View* observed_view) {
227   DCHECK_EQ(top_container_, observed_view);
228   widget()->GetNativeWindow()->SetProperty(
229       kImmersiveTopContainerBoundsInScreen,
230       new gfx::Rect(top_container_->GetBoundsInScreen()));
231 }
232 
OnViewIsDeleting(views::View * observed_view)233 void ImmersiveFullscreenController::OnViewIsDeleting(
234     views::View* observed_view) {
235   DCHECK_EQ(observed_view, top_container_);
236   top_container_ = nullptr;
237 }
238 
239 ////////////////////////////////////////////////////////////////////////////////
240 // gfx::AnimationDelegate overrides:
241 
AnimationEnded(const gfx::Animation * animation)242 void ImmersiveFullscreenController::AnimationEnded(
243     const gfx::Animation* animation) {
244   if (reveal_state_ == SLIDING_OPEN) {
245     OnSlideOpenAnimationCompleted();
246   } else if (reveal_state_ == SLIDING_CLOSED) {
247     OnSlideClosedAnimationCompleted();
248   }
249 }
250 
AnimationProgressed(const gfx::Animation * animation)251 void ImmersiveFullscreenController::AnimationProgressed(
252     const gfx::Animation* animation) {
253   delegate_->SetVisibleFraction(animation->GetCurrentValue());
254 }
255 
256 ////////////////////////////////////////////////////////////////////////////////
257 // ImmersiveRevealedLock::Delegate overrides:
258 
LockRevealedState(AnimateReveal animate_reveal)259 void ImmersiveFullscreenController::LockRevealedState(
260     AnimateReveal animate_reveal) {
261   ++revealed_lock_count_;
262   Animate animate =
263       (animate_reveal == ANIMATE_REVEAL_YES) ? ANIMATE_FAST : ANIMATE_NO;
264   MaybeStartReveal(animate);
265 }
266 
UnlockRevealedState()267 void ImmersiveFullscreenController::UnlockRevealedState() {
268   --revealed_lock_count_;
269   DCHECK_GE(revealed_lock_count_, 0);
270   if (revealed_lock_count_ == 0) {
271     // Always animate ending the reveal fast.
272     MaybeEndReveal(ANIMATE_FAST);
273   }
274 }
275 
276 ////////////////////////////////////////////////////////////////////////////////
277 // public:
278 
279 // static
EnableForWidget(views::Widget * widget,bool enabled)280 void ImmersiveFullscreenController::EnableForWidget(views::Widget* widget,
281                                                     bool enabled) {
282   widget->GetNativeWindow()->SetProperty(kImmersiveIsActive, enabled);
283 }
284 
285 // static
Get(views::Widget * widget)286 ImmersiveFullscreenController* ImmersiveFullscreenController::Get(
287     views::Widget* widget) {
288   return widget->GetNativeWindow()->GetProperty(
289       kImmersiveFullscreenControllerKey);
290 }
291 
292 ////////////////////////////////////////////////////////////////////////////////
293 // private:
294 
EnableWindowObservers(bool enable)295 void ImmersiveFullscreenController::EnableWindowObservers(bool enable) {
296   if (enable) {
297     top_container_->AddObserver(this);
298     widget_->GetNativeWindow()->AddObserver(this);
299   } else {
300     if (top_container_) {
301       top_container_->RemoveObserver(this);
302       top_container_ = nullptr;
303     }
304     if (widget_) {
305       widget_->GetNativeWindow()->RemoveObserver(this);
306       widget_ = nullptr;
307     }
308 
309     animation_.reset();
310     animation_notifier_.reset();
311   }
312 }
313 
EnableEventObservers(bool enable)314 void ImmersiveFullscreenController::EnableEventObservers(bool enable) {
315   if (event_observers_enabled_ == enable)
316     return;
317   event_observers_enabled_ = enable;
318 
319   aura::Window* window = widget_->GetNativeWindow();
320   aura::Env* env = aura::Env::GetInstance();
321   if (enable) {
322     immersive_focus_watcher_ = std::make_unique<ImmersiveFocusWatcher>(this);
323     std::set<ui::EventType> types = {
324         ui::ET_MOUSE_MOVED, ui::ET_MOUSE_PRESSED,         ui::ET_MOUSE_RELEASED,
325         ui::ET_MOUSEWHEEL,  ui::ET_MOUSE_CAPTURE_CHANGED, ui::ET_TOUCH_PRESSED};
326     env->AddEventObserver(this, env, types);
327     window->AddPreTargetHandler(this);
328   } else {
329     window->RemovePreTargetHandler(this);
330     env->RemoveEventObserver(this);
331     immersive_focus_watcher_.reset();
332 
333     animation_->Stop();
334   }
335 }
336 
HandleMouseEvent(const ui::MouseEvent & event,const gfx::Point & location_in_screen,views::Widget * target)337 void ImmersiveFullscreenController::HandleMouseEvent(
338     const ui::MouseEvent& event,
339     const gfx::Point& location_in_screen,
340     views::Widget* target) {
341   if (!enabled_)
342     return;
343 
344   if (event.type() != ui::ET_MOUSE_MOVED &&
345       event.type() != ui::ET_MOUSE_PRESSED &&
346       event.type() != ui::ET_MOUSE_RELEASED &&
347       event.type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
348     return;
349   }
350 
351   // Mouse hover can initiate revealing the top-of-window views while |widget_|
352   // is inactive.
353   if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) {
354     top_edge_hover_timer_.Stop();
355     UpdateLocatedEventRevealedLock(&event, location_in_screen);
356   } else if (event.type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
357     // Trigger reveal if the cursor pauses at the top of the screen for a while.
358     UpdateTopEdgeHoverTimer(event, location_in_screen, target);
359   }
360 }
361 
HandleTouchEvent(const ui::TouchEvent & event,const gfx::Point & location_in_screen)362 void ImmersiveFullscreenController::HandleTouchEvent(
363     const ui::TouchEvent& event,
364     const gfx::Point& location_in_screen) {
365   if (!enabled_ || event.type() != ui::ET_TOUCH_PRESSED)
366     return;
367 
368   // Touch should not initiate revealing the top-of-window views while |widget_|
369   // is inactive.
370   if (!widget_->IsActive())
371     return;
372 
373   UpdateLocatedEventRevealedLock(&event, location_in_screen);
374 }
375 
UpdateTopEdgeHoverTimer(const ui::MouseEvent & event,const gfx::Point & location_in_screen,views::Widget * target)376 void ImmersiveFullscreenController::UpdateTopEdgeHoverTimer(
377     const ui::MouseEvent& event,
378     const gfx::Point& location_in_screen,
379     views::Widget* target) {
380   DCHECK(enabled_);
381   DCHECK(reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED);
382 
383   // Check whether |widget_| is the event target instead of checking for
384   // activation. This allows the timer to be started when |widget_| is inactive
385   // but prevents starting the timer if the mouse is over a portion of the top
386   // edge obscured by an unrelated widget.
387   if (!top_edge_hover_timer_.IsRunning() && !IsTargetForWidget(target))
388     return;
389 
390   // Mouse hover should not initiate revealing the top-of-window views while a
391   // window has mouse capture.
392   if (ImmersiveContext::Get()->DoesAnyWindowHaveCapture())
393     return;
394 
395   if (ShouldIgnoreMouseEventAtLocation(location_in_screen))
396     return;
397 
398   // Stop the timer if the cursor left the top edge or is on a different
399   // display.
400   gfx::Rect hit_bounds_in_screen = GetDisplayBoundsInScreen();
401   hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight);
402   if (!hit_bounds_in_screen.Contains(location_in_screen)) {
403     top_edge_hover_timer_.Stop();
404     return;
405   }
406 
407   // The cursor is now at the top of the screen. Consider the cursor "not
408   // moving" even if it moves a little bit because users don't have perfect
409   // pointing precision. (The y position is not tested because
410   // |hit_bounds_in_screen| is short.)
411   if (top_edge_hover_timer_.IsRunning() &&
412       abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <=
413           kMouseRevealXThresholdPixels)
414     return;
415 
416   // Start the reveal if the cursor doesn't move for some amount of time.
417   mouse_x_when_hit_top_in_screen_ = location_in_screen.x();
418   top_edge_hover_timer_.Stop();
419   // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
420   top_edge_hover_timer_.Start(
421       FROM_HERE, base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs),
422       base::BindOnce(
423           &ImmersiveFullscreenController::AcquireLocatedEventRevealedLock,
424           base::Unretained(this)));
425 }
426 
UpdateLocatedEventRevealedLock(const ui::LocatedEvent * event,const gfx::Point & location_in_screen)427 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock(
428     const ui::LocatedEvent* event,
429     const gfx::Point& location_in_screen) {
430   if (!enabled_)
431     return;
432   DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent());
433 
434   // Neither the mouse nor touch can initiate a reveal when the top-of-window
435   // views are sliding closed or are closed with the following exceptions:
436   // - Hovering at y = 0 which is handled in HandleMouseEvent().
437   // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
438   if (reveal_state_ == CLOSED || reveal_state_ == SLIDING_CLOSED)
439     return;
440 
441   // For the sake of simplicity, ignore |widget_|'s activation in computing
442   // whether the top-of-window views should stay revealed. Ideally, the
443   // top-of-window views would stay revealed only when the mouse cursor is
444   // hovered above a non-obscured portion of the top-of-window views. The
445   // top-of-window views may be partially obscured when |widget_| is inactive.
446 
447   // Ignore all events while a window has capture. This keeps the top-of-window
448   // views revealed during a drag.
449   if (ImmersiveContext::Get()->DoesAnyWindowHaveCapture())
450     return;
451 
452   if ((!event || event->IsMouseEvent()) &&
453       ShouldIgnoreMouseEventAtLocation(location_in_screen)) {
454     return;
455   }
456 
457   // The visible bounds of |top_container_| should be contained in
458   // |hit_bounds_in_screen|.
459   std::vector<gfx::Rect> hit_bounds_in_screen =
460       delegate_->GetVisibleBoundsInScreen();
461   bool keep_revealed = false;
462   for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
463     // Allow the cursor to move slightly off the top-of-window views before
464     // sliding closed. In the case of ImmersiveModeControllerAsh, this helps
465     // when the user is attempting to click on the bookmark bar and
466     // overshoots slightly.
467     if (event && event->type() == ui::ET_MOUSE_MOVED) {
468       const int kBoundsOffsetY = 8;
469       hit_bounds_in_screen[i].Inset(0, 0, 0, -kBoundsOffsetY);
470     }
471 
472     if (hit_bounds_in_screen[i].Contains(location_in_screen)) {
473       keep_revealed = true;
474       break;
475     }
476   }
477 
478   if (keep_revealed)
479     AcquireLocatedEventRevealedLock();
480   else
481     located_event_revealed_lock_.reset();
482 }
483 
UpdateLocatedEventRevealedLock()484 void ImmersiveFullscreenController::UpdateLocatedEventRevealedLock() {
485   if (!aura::client::GetCursorClient(
486            widget_->GetNativeWindow()->GetRootWindow())
487            ->IsMouseEventsEnabled()) {
488     // If mouse events are disabled, the user's last interaction was probably
489     // via touch. Do no further processing in this case as there is no easy
490     // way of retrieving the position of the user's last touch.
491     return;
492   }
493   UpdateLocatedEventRevealedLock(
494       nullptr, display::Screen::GetScreen()->GetCursorScreenPoint());
495 }
496 
AcquireLocatedEventRevealedLock()497 void ImmersiveFullscreenController::AcquireLocatedEventRevealedLock() {
498   // CAUTION: Acquiring the lock results in a reentrant call to
499   // AcquireLocatedEventRevealedLock() when
500   // |ImmersiveFullscreenController::animations_disabled_for_test_| is true.
501   if (!located_event_revealed_lock_.get())
502     located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
503 }
504 
UpdateRevealedLocksForSwipe(SwipeType swipe_type)505 bool ImmersiveFullscreenController::UpdateRevealedLocksForSwipe(
506     SwipeType swipe_type) {
507   if (!enabled_ || swipe_type == SWIPE_NONE)
508     return false;
509 
510   // Swipes while |widget_| is inactive should have been filtered out in
511   // OnGestureEvent().
512   DCHECK(widget_->IsActive());
513 
514   if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
515     if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) {
516       located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
517       return true;
518     }
519   } else {
520     if (swipe_type == SWIPE_CLOSE) {
521       // Attempt to end the reveal. If other code is holding onto a lock, the
522       // attempt will be unsuccessful.
523       located_event_revealed_lock_.reset();
524       if (immersive_focus_watcher_)
525         immersive_focus_watcher_->ReleaseLock();
526 
527       if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
528         widget_->GetFocusManager()->ClearFocus();
529         return true;
530       }
531 
532       // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
533       UpdateLocatedEventRevealedLock();
534       if (immersive_focus_watcher_)
535         immersive_focus_watcher_->UpdateFocusRevealedLock();
536     }
537   }
538   return false;
539 }
540 
GetAnimationDuration(Animate animate) const541 base::TimeDelta ImmersiveFullscreenController::GetAnimationDuration(
542     Animate animate) const {
543   switch (animate) {
544     case ANIMATE_NO:
545       return base::TimeDelta();
546     case ANIMATE_SLOW:
547       return base::TimeDelta::FromMilliseconds(400);
548     case ANIMATE_FAST:
549       return base::TimeDelta::FromMilliseconds(200);
550   }
551   NOTREACHED();
552   return base::TimeDelta();
553 }
554 
MaybeStartReveal(Animate animate)555 void ImmersiveFullscreenController::MaybeStartReveal(Animate animate) {
556   if (!enabled_)
557     return;
558 
559   if (animations_disabled_for_test_)
560     animate = ANIMATE_NO;
561 
562   // Callers with ANIMATE_NO expect this function to synchronously reveal the
563   // top-of-window views.
564   if (reveal_state_ == REVEALED ||
565       (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) {
566     return;
567   }
568 
569   RevealState previous_reveal_state = reveal_state_;
570   reveal_state_ = SLIDING_OPEN;
571   if (previous_reveal_state == CLOSED) {
572     EnableTouchInsets(false);
573 
574     delegate_->OnImmersiveRevealStarted();
575 
576     // Do not do any more processing if OnImmersiveRevealStarted() changed
577     // |reveal_state_|.
578     if (reveal_state_ != SLIDING_OPEN)
579       return;
580   }
581   // Slide in the reveal view.
582   if (animate == ANIMATE_NO) {
583     animation_->Reset(1);
584     OnSlideOpenAnimationCompleted();
585   } else {
586     animation_->SetSlideDuration(GetAnimationDuration(animate));
587     animation_->Show();
588   }
589 }
590 
OnSlideOpenAnimationCompleted()591 void ImmersiveFullscreenController::OnSlideOpenAnimationCompleted() {
592   DCHECK_EQ(SLIDING_OPEN, reveal_state_);
593   reveal_state_ = REVEALED;
594   delegate_->SetVisibleFraction(1);
595 
596   // The user may not have moved the mouse since the reveal was initiated.
597   // Update the revealed lock to reflect the mouse's current state.
598   UpdateLocatedEventRevealedLock();
599 }
600 
MaybeEndReveal(Animate animate)601 void ImmersiveFullscreenController::MaybeEndReveal(Animate animate) {
602   if (!enabled_ || revealed_lock_count_ != 0)
603     return;
604 
605   if (animations_disabled_for_test_)
606     animate = ANIMATE_NO;
607 
608   // Callers with ANIMATE_NO expect this function to synchronously close the
609   // top-of-window views.
610   if (reveal_state_ == CLOSED ||
611       (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) {
612     return;
613   }
614 
615   reveal_state_ = SLIDING_CLOSED;
616   base::TimeDelta duration = GetAnimationDuration(animate);
617   if (duration > base::TimeDelta()) {
618     animation_->SetSlideDuration(duration);
619     animation_->Hide();
620   } else {
621     animation_->Reset(0);
622     OnSlideClosedAnimationCompleted();
623   }
624 }
625 
OnSlideClosedAnimationCompleted()626 void ImmersiveFullscreenController::OnSlideClosedAnimationCompleted() {
627   DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
628   reveal_state_ = CLOSED;
629 
630   EnableTouchInsets(true);
631   delegate_->OnImmersiveRevealEnded();
632 }
633 
634 ImmersiveFullscreenController::SwipeType
GetSwipeType(const ui::GestureEvent & event) const635 ImmersiveFullscreenController::GetSwipeType(
636     const ui::GestureEvent& event) const {
637   if (event.type() != ui::ET_GESTURE_SCROLL_UPDATE)
638     return SWIPE_NONE;
639   // Make sure that it is a clear vertical gesture.
640   if (std::abs(event.details().scroll_y()) <=
641       kSwipeVerticalThresholdMultiplier * std::abs(event.details().scroll_x()))
642     return SWIPE_NONE;
643   if (event.details().scroll_y() < 0)
644     return SWIPE_CLOSE;
645   if (event.details().scroll_y() > 0)
646     return SWIPE_OPEN;
647   return SWIPE_NONE;
648 }
649 
ShouldIgnoreMouseEventAtLocation(const gfx::Point & location) const650 bool ImmersiveFullscreenController::ShouldIgnoreMouseEventAtLocation(
651     const gfx::Point& location) const {
652   // Ignore mouse events in the region immediately above the top edge of the
653   // display. This is to handle the case of a user with a vertical display
654   // layout (primary display above/below secondary display) and the immersive
655   // fullscreen window on the bottom display. It is really hard to trigger a
656   // reveal in this case because:
657   // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
658   //   pixels of the bottom display.
659   // - The cursor is warped to the top display if the cursor gets to the top
660   //   edge of the bottom display.
661   // Mouse events are ignored in the bottom few pixels of the top display
662   // (Mouse events in this region cannot start or end a reveal). This allows a
663   // user to overshoot the top of the bottom display and still reveal the
664   // top-of-window views.
665   gfx::Rect dead_region = GetDisplayBoundsInScreen();
666   dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer);
667   dead_region.set_height(kHeightOfDeadRegionAboveTopContainer);
668   return dead_region.Contains(location);
669 }
670 
ShouldHandleGestureEvent(const gfx::Point & location) const671 bool ImmersiveFullscreenController::ShouldHandleGestureEvent(
672     const gfx::Point& location) const {
673   DCHECK(widget_->IsActive());
674   if (reveal_state_ == REVEALED) {
675     std::vector<gfx::Rect> hit_bounds_in_screen(
676         delegate_->GetVisibleBoundsInScreen());
677     for (size_t i = 0; i < hit_bounds_in_screen.size(); ++i) {
678       if (hit_bounds_in_screen[i].Contains(location))
679         return true;
680     }
681     return false;
682   }
683 
684   // When the top-of-window views are not fully revealed, handle gestures which
685   // start in the top few pixels of the screen.
686   gfx::Rect hit_bounds_in_screen(GetDisplayBoundsInScreen());
687   hit_bounds_in_screen.set_height(kImmersiveFullscreenTopEdgeInset);
688   if (hit_bounds_in_screen.Contains(location))
689     return true;
690 
691   // There may be a bezel sensor off screen logically above
692   // |hit_bounds_in_screen|. The check for the event not contained by the
693   // closest screen ensures that the event is from a valid bezel (as opposed to
694   // another screen in an extended desktop).
695   gfx::Rect screen_bounds =
696       display::Screen::GetScreen()->GetDisplayNearestPoint(location).bounds();
697   return (!screen_bounds.Contains(location) &&
698           location.y() < hit_bounds_in_screen.y() &&
699           location.x() >= hit_bounds_in_screen.x() &&
700           location.x() < hit_bounds_in_screen.right());
701 }
702 
GetDisplayBoundsInScreen() const703 gfx::Rect ImmersiveFullscreenController::GetDisplayBoundsInScreen() const {
704   return ImmersiveContext::Get()->GetDisplayBoundsInScreen(widget_);
705 }
706 
IsTargetForWidget(views::Widget * target) const707 bool ImmersiveFullscreenController::IsTargetForWidget(
708     views::Widget* target) const {
709   return target == widget_ || target == top_container_->GetWidget();
710 }
711 
UpdateEnabled()712 void ImmersiveFullscreenController::UpdateEnabled() {
713   if (!widget_)
714     return;
715 
716   const bool enabled =
717       widget_->GetNativeWindow()->GetProperty(kImmersiveIsActive);
718 
719   if (enabled_ == enabled) {
720     // Frame layout depends on the window's state and size,
721     // which can happen asynchronously and/or independently,
722     // from the timing when the immersive state change.
723     delegate_->Relayout();
724     return;
725   }
726   enabled_ = enabled;
727 
728   EnableEventObservers(enabled_);
729 
730   ImmersiveContext::Get()->OnEnteringOrExitingImmersive(this, enabled);
731 
732   if (enabled_) {
733     // Animate enabling immersive mode by sliding out the top-of-window views.
734     // No animation occurs if a lock is holding the top-of-window views open.
735 
736     normal_targeter_ = widget_->GetNativeWindow()->SetEventTargeter(
737         std::make_unique<ImmersiveWindowTargeter>());
738 
739     // Do a reveal to set the initial state for the animation. (And any
740     // required state in case the animation cannot run because of a lock holding
741     // the top-of-window views open.)
742     MaybeStartReveal(ANIMATE_NO);
743 
744     // Reset the located event so that it does not affect whether the
745     // top-of-window views are hidden.
746     located_event_revealed_lock_.reset();
747 
748     // Try doing the animation.
749     MaybeEndReveal(ANIMATE_SLOW);
750 
751     if (reveal_state_ == REVEALED) {
752       // Reveal was unsuccessful. Reacquire the revealed locks if appropriate.
753       UpdateLocatedEventRevealedLock();
754       if (immersive_focus_watcher_)
755         immersive_focus_watcher_->UpdateFocusRevealedLock();
756     }
757 
758     delegate_->OnImmersiveFullscreenEntered();
759   } else {
760     // Stop cursor-at-top tracking.
761     top_edge_hover_timer_.Stop();
762     reveal_state_ = CLOSED;
763 
764     widget_->GetNativeWindow()->SetEventTargeter(std::move(normal_targeter_));
765 
766     delegate_->OnImmersiveFullscreenExited();
767   }
768 }
769 
EnableTouchInsets(bool enable)770 void ImmersiveFullscreenController::EnableTouchInsets(bool enable) {
771   if (!widget_->GetNativeWindow()->targeter())
772     return;
773 
774   widget_->GetNativeWindow()->targeter()->SetInsets(
775       {}, gfx::Insets(enable ? kImmersiveFullscreenTopEdgeInset : 0, 0, 0, 0));
776 }
777 
778 }  // namespace chromeos
779