blob: e469a3c80620600520045de6a28e2e7228a11ca8 [file] [log] [blame]
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h"
6
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01007#include <set>
Ben Murdochca12bfa2013-07-23 11:17:05 +01008#include <vector>
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01009
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010010#include "ash/ash_switches.h"
11#include "ash/shell.h"
12#include "ash/wm/window_properties.h"
13#include "base/command_line.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010014#include "chrome/browser/chrome_notification_types.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010015#include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +010016#include "chrome/browser/ui/immersive_fullscreen_configuration.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010017#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010018#include "chrome/browser/ui/views/frame/top_container_view.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010019#include "content/public/browser/notification_service.h"
Ben Murdochca12bfa2013-07-23 11:17:05 +010020#include "content/public/browser/web_contents.h"
21#include "content/public/browser/web_contents_view.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010022#include "ui/aura/client/activation_client.h"
23#include "ui/aura/client/aura_constants.h"
24#include "ui/aura/client/capture_client.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010025#include "ui/aura/client/cursor_client.h"
26#include "ui/aura/client/screen_position_client.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010027#include "ui/aura/env.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010028#include "ui/aura/root_window.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010029#include "ui/aura/window.h"
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010030#include "ui/base/animation/slide_animation.h"
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010031#include "ui/views/bubble/bubble_delegate.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010032#include "ui/views/view.h"
33#include "ui/views/widget/widget.h"
34#include "ui/views/window/non_client_view.h"
35
36using views::View;
37
38namespace {
39
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010040// The slide open/closed animation looks better if it starts and ends just a
41// few pixels before the view goes completely off the screen, which reduces
42// the visual "pop" as the 2-pixel tall immersive-style tabs become visible.
43const int kAnimationOffsetY = 3;
44
45// Duration for the reveal show/hide slide animation. The slower duration is
46// used for the initial slide out to give the user more change to see what
47// happened.
48const int kRevealSlowAnimationDurationMs = 400;
49const int kRevealFastAnimationDurationMs = 200;
50
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +010051// How many pixels a gesture can start away from |top_container_| when in
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010052// closed state and still be considered near it. This is needed to overcome
53// issues with poor location values near the edge of the display.
Ben Murdochca12bfa2013-07-23 11:17:05 +010054const int kNearTopContainerDistance = 8;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010055
56// Used to multiply x value of an update in check to determine if gesture is
57// vertical. This is used to make sure that gesture is close to vertical instead
58// of just more vertical then horizontal.
59const int kSwipeVerticalThresholdMultiplier = 3;
60
Ben Murdochbb1529c2013-08-08 10:24:53 +010061// The height in pixels of the region above the top edge of the display which
62// hosts the immersive fullscreen window in which mouse events are ignored
63// (cannot reveal or unreveal the top-of-window views).
64// See ShouldIgnoreMouseEventAtLocation() for more details.
65const int kHeightOfDeadRegionAboveTopContainer = 10;
66
67// The height in pixels of the region below the top edge of the display in which
68// the mouse can trigger revealing the top-of-window views. The height must be
69// greater than 1px because the top pixel is used to trigger moving the cursor
70// between displays if the user has a vertical display layout (primary display
71// above/below secondary display).
72const int kMouseRevealBoundsHeight = 3;
73
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010074// If |hovered| is true, moves the mouse above |view|. Moves it outside of
75// |view| otherwise.
76// Should not be called outside of tests.
77void MoveMouse(views::View* view, bool hovered) {
78 gfx::Point cursor_pos;
79 if (!hovered) {
80 int bottom_edge = view->bounds().bottom();
81 cursor_pos = gfx::Point(0, bottom_edge + 100);
82 }
83 views::View::ConvertPointToScreen(view, &cursor_pos);
84 aura::Env::GetInstance()->set_last_mouse_location(cursor_pos);
85}
86
Ben Murdoch7dbb3d52013-07-17 14:55:54 +010087// Returns the BubbleDelegateView corresponding to |maybe_bubble| if
88// |maybe_bubble| is a bubble.
89views::BubbleDelegateView* AsBubbleDelegate(aura::Window* maybe_bubble) {
90 if (!maybe_bubble)
91 return NULL;
92 views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble);
93 if (!widget)
94 return NULL;
95 return widget->widget_delegate()->AsBubbleDelegate();
96}
97
98// Returns true if |maybe_transient| is a transient child of |toplevel|.
99bool IsWindowTransientChildOf(aura::Window* maybe_transient,
100 aura::Window* toplevel) {
101 if (!maybe_transient || !toplevel)
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100102 return false;
103
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100104 for (aura::Window* window = maybe_transient; window;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100105 window = window->transient_parent()) {
106 if (window == toplevel)
107 return true;
108 }
109 return false;
110}
111
Ben Murdocheb525c52013-07-10 11:40:50 +0100112// Returns the location of |event| in screen coordinates.
113gfx::Point GetEventLocationInScreen(const ui::LocatedEvent& event) {
114 gfx::Point location_in_screen = event.location();
115 aura::Window* target = static_cast<aura::Window*>(event.target());
116 aura::client::ScreenPositionClient* screen_position_client =
117 aura::client::GetScreenPositionClient(target->GetRootWindow());
118 screen_position_client->ConvertPointToScreen(target, &location_in_screen);
119 return location_in_screen;
120}
121
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100122////////////////////////////////////////////////////////////////////////////////
123
124class RevealedLockAsh : public ImmersiveRevealedLock {
125 public:
126 RevealedLockAsh(const base::WeakPtr<ImmersiveModeControllerAsh>& controller,
127 ImmersiveModeController::AnimateReveal animate_reveal)
128 : controller_(controller) {
129 DCHECK(controller_);
130 controller_->LockRevealedState(animate_reveal);
131 }
132
133 virtual ~RevealedLockAsh() {
134 if (controller_)
135 controller_->UnlockRevealedState();
136 }
137
138 private:
139 base::WeakPtr<ImmersiveModeControllerAsh> controller_;
140
141 DISALLOW_COPY_AND_ASSIGN(RevealedLockAsh);
142};
143
144} // namespace
145
146////////////////////////////////////////////////////////////////////////////////
147
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100148// Class which keeps the top-of-window views revealed as long as one of the
149// bubbles it is observing is visible. The logic to keep the top-of-window
150// views revealed based on the visibility of bubbles anchored to
151// children of |ImmersiveModeController::top_container_| is separate from
152// the logic related to |ImmersiveModeControllerAsh::focus_revealed_lock_|
153// so that bubbles which are not activatable and bubbles which do not close
154// upon deactivation also keep the top-of-window views revealed for the
155// duration of their visibility.
156class ImmersiveModeControllerAsh::BubbleManager : public aura::WindowObserver {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100157 public:
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100158 explicit BubbleManager(ImmersiveModeControllerAsh* controller);
159 virtual ~BubbleManager();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100160
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100161 // Start / stop observing changes to |bubble|'s visibility.
162 void StartObserving(aura::Window* bubble);
163 void StopObserving(aura::Window* bubble);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100164
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100165 private:
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100166 // Updates |revealed_lock_| based on whether any of |bubbles_| is visible.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100167 void UpdateRevealedLock();
168
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100169 // aura::WindowObserver overrides:
170 virtual void OnWindowVisibilityChanged(aura::Window* window,
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100171 bool visible) OVERRIDE;
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100172 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100173
174 ImmersiveModeControllerAsh* controller_;
175
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100176 std::set<aura::Window*> bubbles_;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100177
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100178 // Lock which keeps the top-of-window views revealed based on whether any of
179 // |bubbles_| is visible.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100180 scoped_ptr<ImmersiveRevealedLock> revealed_lock_;
181
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100182 DISALLOW_COPY_AND_ASSIGN(BubbleManager);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100183};
184
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100185ImmersiveModeControllerAsh::BubbleManager::BubbleManager(
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100186 ImmersiveModeControllerAsh* controller)
187 : controller_(controller) {
188}
189
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100190ImmersiveModeControllerAsh::BubbleManager::~BubbleManager() {
191 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
192 it != bubbles_.end(); ++it) {
193 (*it)->RemoveObserver(this);
194 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100195}
196
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100197void ImmersiveModeControllerAsh::BubbleManager::StartObserving(
198 aura::Window* bubble) {
199 if (bubbles_.insert(bubble).second) {
200 bubble->AddObserver(this);
201 UpdateRevealedLock();
202 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100203}
204
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100205void ImmersiveModeControllerAsh::BubbleManager::StopObserving(
206 aura::Window* bubble) {
207 if (bubbles_.erase(bubble)) {
208 bubble->RemoveObserver(this);
209 UpdateRevealedLock();
210 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100211}
212
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100213void ImmersiveModeControllerAsh::BubbleManager::UpdateRevealedLock() {
214 bool has_visible_bubble = false;
215 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
216 it != bubbles_.end(); ++it) {
217 if ((*it)->IsVisible()) {
218 has_visible_bubble = true;
219 break;
220 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100221 }
222
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100223 bool was_revealed = controller_->IsRevealed();
224 if (has_visible_bubble) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100225 if (!revealed_lock_.get()) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100226 // Reveal the top-of-window views without animating because it looks
227 // weird for the top-of-window views to animate and the bubble not to
228 // animate along with the top-of-window views.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100229 revealed_lock_.reset(controller_->GetRevealedLock(
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100230 ImmersiveModeController::ANIMATE_REVEAL_NO));
231 }
232 } else {
233 revealed_lock_.reset();
234 }
235
236 if (!was_revealed && revealed_lock_.get()) {
237 // Currently, there is no nice way for bubbles to reposition themselves
238 // whenever the anchor view moves. Tell the bubbles to reposition themselves
239 // explicitly instead. The hidden bubbles are also repositioned because
240 // BubbleDelegateView does not reposition its widget as a result of a
241 // visibility change.
242 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin();
243 it != bubbles_.end(); ++it) {
244 AsBubbleDelegate(*it)->OnAnchorViewBoundsChanged();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100245 }
246 }
247}
248
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100249void ImmersiveModeControllerAsh::BubbleManager::OnWindowVisibilityChanged(
250 aura::Window*,
251 bool visible) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100252 UpdateRevealedLock();
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100253}
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100254
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100255void ImmersiveModeControllerAsh::BubbleManager::OnWindowDestroying(
256 aura::Window* window) {
257 StopObserving(window);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100258}
259
260////////////////////////////////////////////////////////////////////////////////
261
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100262ImmersiveModeControllerAsh::ImmersiveModeControllerAsh()
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100263 : delegate_(NULL),
264 widget_(NULL),
265 top_container_(NULL),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100266 observers_enabled_(false),
267 enabled_(false),
268 reveal_state_(CLOSED),
269 revealed_lock_count_(0),
270 tab_indicator_visibility_(TAB_INDICATORS_HIDE),
Ben Murdochbb1529c2013-08-08 10:24:53 +0100271 mouse_x_when_hit_top_in_screen_(-1),
Ben Murdocheb525c52013-07-10 11:40:50 +0100272 gesture_begun_(false),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100273 native_window_(NULL),
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100274 animation_(new ui::SlideAnimation(this)),
275 animations_disabled_for_test_(false),
Ben Murdocheb525c52013-07-10 11:40:50 +0100276 weak_ptr_factory_(this) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100277}
278
279ImmersiveModeControllerAsh::~ImmersiveModeControllerAsh() {
280 // The browser view is being destroyed so there's no need to update its
281 // layout or layers, even if the top views are revealed. But the window
282 // observers still need to be removed.
283 EnableWindowObservers(false);
284}
285
286void ImmersiveModeControllerAsh::LockRevealedState(
287 AnimateReveal animate_reveal) {
288 ++revealed_lock_count_;
289 Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ?
290 ANIMATE_FAST : ANIMATE_NO;
291 MaybeStartReveal(animate);
292}
293
294void ImmersiveModeControllerAsh::UnlockRevealedState() {
295 --revealed_lock_count_;
296 DCHECK_GE(revealed_lock_count_, 0);
297 if (revealed_lock_count_ == 0) {
298 // Always animate ending the reveal fast.
299 MaybeEndReveal(ANIMATE_FAST);
300 }
301}
302
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100303void ImmersiveModeControllerAsh::MaybeExitImmersiveFullscreen() {
304 if (ShouldExitImmersiveFullscreen())
305 delegate_->FullscreenStateChanged();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100306}
307
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100308void ImmersiveModeControllerAsh::Init(
309 Delegate* delegate,
310 views::Widget* widget,
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100311 views::View* top_container) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100312 delegate_ = delegate;
313 widget_ = widget;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100314 // Browser view is detached from its widget during destruction. Cache the
315 // window pointer so |this| can stop observing during destruction.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100316 native_window_ = widget_->GetNativeWindow();
317 top_container_ = top_container;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100318
319 // Optionally allow the tab indicators to be hidden.
320 if (CommandLine::ForCurrentProcess()->
321 HasSwitch(ash::switches::kAshImmersiveHideTabIndicators)) {
322 tab_indicator_visibility_ = TAB_INDICATORS_FORCE_HIDE;
323 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100324}
325
326void ImmersiveModeControllerAsh::SetEnabled(bool enabled) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100327 DCHECK(native_window_) << "Must initialize before enabling";
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100328 if (enabled_ == enabled)
329 return;
330 enabled_ = enabled;
331
Ben Murdocheb525c52013-07-10 11:40:50 +0100332 EnableWindowObservers(enabled_);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100333
334 UpdateUseMinimalChrome(LAYOUT_NO);
335
336 if (enabled_) {
337 // Animate enabling immersive mode by sliding out the top-of-window views.
338 // No animation occurs if a lock is holding the top-of-window views open.
339
340 // Do a reveal to set the initial state for the animation. (And any
341 // required state in case the animation cannot run because of a lock holding
342 // the top-of-window views open.) This call has the side effect of relaying
343 // out |browser_view_|'s root view.
344 MaybeStartReveal(ANIMATE_NO);
345
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100346 // Reset the located event and the focus revealed locks so that they do not
347 // affect whether the top-of-window views are hidden.
348 located_event_revealed_lock_.reset();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100349 focus_revealed_lock_.reset();
350
351 // Try doing the animation.
352 MaybeEndReveal(ANIMATE_SLOW);
353
354 if (reveal_state_ == REVEALED) {
355 // Reveal was unsuccessful. Reacquire the revealed locks if appropriate.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100356 UpdateLocatedEventRevealedLock(NULL);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100357 UpdateFocusRevealedLock();
358 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100359 } else {
360 // Stop cursor-at-top tracking.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100361 top_edge_hover_timer_.Stop();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100362 // Snap immediately to the closed state.
363 reveal_state_ = CLOSED;
364 EnablePaintToLayer(false);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100365 delegate_->SetImmersiveStyle(false);
Ben Murdochca12bfa2013-07-23 11:17:05 +0100366 SetRenderWindowTopInsetsForTouch(0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100367
368 // Relayout the root view because disabling immersive fullscreen may have
369 // changed the result of NonClientFrameView::GetBoundsForClientView().
370 LayoutBrowserRootView();
371 }
372}
373
374bool ImmersiveModeControllerAsh::IsEnabled() const {
375 return enabled_;
376}
377
378bool ImmersiveModeControllerAsh::ShouldHideTabIndicators() const {
379 return tab_indicator_visibility_ != TAB_INDICATORS_SHOW;
380}
381
382bool ImmersiveModeControllerAsh::ShouldHideTopViews() const {
383 return enabled_ && reveal_state_ == CLOSED;
384}
385
386bool ImmersiveModeControllerAsh::IsRevealed() const {
387 return enabled_ && reveal_state_ != CLOSED;
388}
389
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100390int ImmersiveModeControllerAsh::GetTopContainerVerticalOffset(
391 const gfx::Size& top_container_size) const {
392 if (!enabled_ || reveal_state_ == REVEALED || reveal_state_ == CLOSED)
393 return 0;
394
395 return animation_->CurrentValueBetween(
396 -top_container_size.height() + kAnimationOffsetY, 0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100397}
398
399ImmersiveRevealedLock* ImmersiveModeControllerAsh::GetRevealedLock(
400 AnimateReveal animate_reveal) {
401 return new RevealedLockAsh(weak_ptr_factory_.GetWeakPtr(), animate_reveal);
402}
403
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100404void ImmersiveModeControllerAsh::OnFindBarVisibleBoundsChanged(
405 const gfx::Rect& new_visible_bounds_in_screen) {
406 find_bar_visible_bounds_in_screen_ = new_visible_bounds_in_screen;
407}
408
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100409////////////////////////////////////////////////////////////////////////////////
410// Observers:
411
412void ImmersiveModeControllerAsh::Observe(
413 int type,
414 const content::NotificationSource& source,
415 const content::NotificationDetails& details) {
416 DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type);
417 if (enabled_)
418 UpdateUseMinimalChrome(LAYOUT_YES);
419}
420
421void ImmersiveModeControllerAsh::OnMouseEvent(ui::MouseEvent* event) {
422 if (!enabled_)
423 return;
424
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100425 if (event->type() != ui::ET_MOUSE_MOVED &&
426 event->type() != ui::ET_MOUSE_PRESSED &&
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100427 event->type() != ui::ET_MOUSE_RELEASED &&
428 event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100429 return;
430 }
431
432 // Mouse hover should not initiate revealing the top-of-window views while
433 // |native_window_| is inactive.
434 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive())
435 return;
436
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100437 // Mouse hover should not initiate revealing the top-of-window views while
438 // a window has mouse capture.
439 if (aura::client::GetCaptureWindow(native_window_))
440 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100441
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100442 if (IsRevealed())
443 UpdateLocatedEventRevealedLock(event);
444
445 // Trigger a reveal if the cursor pauses at the top of the screen for a
446 // while.
447 if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED)
448 UpdateTopEdgeHoverTimer(event);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100449}
450
451void ImmersiveModeControllerAsh::OnTouchEvent(ui::TouchEvent* event) {
452 if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED)
453 return;
454
455 UpdateLocatedEventRevealedLock(event);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100456}
457
458void ImmersiveModeControllerAsh::OnGestureEvent(ui::GestureEvent* event) {
459 if (!enabled_)
460 return;
461
462 // Touch gestures should not initiate revealing the top-of-window views while
463 // |native_window_| is inactive.
464 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive())
465 return;
466
467 switch (event->type()) {
468 case ui::ET_GESTURE_SCROLL_BEGIN:
Ben Murdocheb525c52013-07-10 11:40:50 +0100469 if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100470 gesture_begun_ = true;
471 event->SetHandled();
472 }
473 break;
474 case ui::ET_GESTURE_SCROLL_UPDATE:
475 if (gesture_begun_) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100476 if (UpdateRevealedLocksForSwipe(GetSwipeType(event)))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100477 event->SetHandled();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100478 gesture_begun_ = false;
479 }
480 break;
481 case ui::ET_GESTURE_SCROLL_END:
482 case ui::ET_SCROLL_FLING_START:
483 gesture_begun_ = false;
484 break;
485 default:
486 break;
487 }
488}
489
490void ImmersiveModeControllerAsh::OnWillChangeFocus(views::View* focused_before,
491 views::View* focused_now) {
492}
493
494void ImmersiveModeControllerAsh::OnDidChangeFocus(views::View* focused_before,
495 views::View* focused_now) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100496 scoped_ptr<ImmersiveRevealedLock> lock;
497 if (reveal_state_ == REVEALED || reveal_state_ == SLIDING_OPEN) {
498 // Acquire a lock so that if UpdateLocatedEventRevealedLock() or
499 // UpdateFocusRevealedLock() ends the reveal, it occurs after the
500 // function terminates. This is useful in tests.
501 lock.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
502 }
503
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100504 UpdateLocatedEventRevealedLock(NULL);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100505 UpdateFocusRevealedLock();
506}
507
508void ImmersiveModeControllerAsh::OnWidgetDestroying(views::Widget* widget) {
509 EnableWindowObservers(false);
510 native_window_ = NULL;
511
512 // Set |enabled_| to false such that any calls to MaybeStartReveal() and
513 // MaybeEndReveal() have no effect.
514 enabled_ = false;
515}
516
517void ImmersiveModeControllerAsh::OnWidgetActivationChanged(
518 views::Widget* widget,
519 bool active) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100520 scoped_ptr<ImmersiveRevealedLock> lock;
521 if (reveal_state_ == REVEALED || reveal_state_ == SLIDING_OPEN) {
522 // Acquire a lock so that if UpdateLocatedEventRevealedLock() or
523 // UpdateFocusRevealedLock() ends the reveal, it occurs after the
524 // function terminates. This is useful in tests.
525 lock.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
526 }
527
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100528 // Mouse hover should not initiate revealing the top-of-window views while
529 // |native_window_| is inactive.
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100530 top_edge_hover_timer_.Stop();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100531
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100532 UpdateLocatedEventRevealedLock(NULL);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100533 UpdateFocusRevealedLock();
534}
535
536////////////////////////////////////////////////////////////////////////////////
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100537// Animation delegate:
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100538
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100539void ImmersiveModeControllerAsh::AnimationEnded(
540 const ui::Animation* animation) {
541 if (reveal_state_ == SLIDING_OPEN) {
542 // AnimationProgressed() is called immediately before AnimationEnded()
543 // and does a layout.
544 OnSlideOpenAnimationCompleted(LAYOUT_NO);
545 } else if (reveal_state_ == SLIDING_CLOSED) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100546 OnSlideClosedAnimationCompleted();
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100547 }
548}
549
550void ImmersiveModeControllerAsh::AnimationProgressed(
551 const ui::Animation* animation) {
552 // Relayout. This will also move any views whose position depends on the
553 // top container position such as the find bar.
554 // We do not call LayoutBrowserRootView() here because we are not toggling
555 // the tab strip's immersive style so relaying out the non client view is not
556 // necessary.
557 top_container_->parent()->Layout();
558}
559
560////////////////////////////////////////////////////////////////////////////////
561// aura::WindowObserver overrides:
562void ImmersiveModeControllerAsh::OnWindowPropertyChanged(aura::Window* window,
563 const void* key,
564 intptr_t old) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100565 if (!enabled_)
566 return;
567
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100568 if (key == aura::client::kShowStateKey) {
569 // Disable immersive mode when the user exits fullscreen without going
570 // through FullscreenController::ToggleFullscreenMode(). This is the case
571 // if the user exits fullscreen via the restore button.
572 if (ShouldExitImmersiveFullscreen()) {
573 // Other "property change" observers may want to animate between the
574 // current visuals and the new window state. Do not alter the current
575 // visuals yet and post a task to exit immersive fullscreen instead.
576 base::MessageLoopForUI::current()->PostTask(
577 FROM_HERE,
578 base::Bind(&ImmersiveModeControllerAsh::MaybeExitImmersiveFullscreen,
579 weak_ptr_factory_.GetWeakPtr()));
580 }
Ben Murdocheb525c52013-07-10 11:40:50 +0100581
582 ui::WindowShowState show_state = native_window_->GetProperty(
583 aura::client::kShowStateKey);
584 if (show_state == ui::SHOW_STATE_FULLSCREEN &&
585 old == ui::SHOW_STATE_MINIMIZED) {
586 // Relayout in case there was a layout while the window show state was
587 // ui::SHOW_STATE_MINIMIZED.
588 LayoutBrowserRootView();
589 }
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100590 }
591}
592
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100593void ImmersiveModeControllerAsh::OnAddTransientChild(aura::Window* window,
594 aura::Window* transient) {
595 views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient);
596 if (bubble_delegate &&
597 bubble_delegate->anchor_view() &&
598 top_container_->Contains(bubble_delegate->anchor_view())) {
599 // Observe the aura::Window because the BubbleDelegateView may not be
600 // parented to the widget's root view yet so |bubble_delegate->GetWidget()|
601 // may still return NULL.
602 bubble_manager_->StartObserving(transient);
603 }
604}
605
606void ImmersiveModeControllerAsh::OnRemoveTransientChild(
607 aura::Window* window,
608 aura::Window* transient) {
609 bubble_manager_->StopObserving(transient);
610}
611
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100612////////////////////////////////////////////////////////////////////////////////
613// Testing interface:
614
615void ImmersiveModeControllerAsh::SetForceHideTabIndicatorsForTest(bool force) {
616 if (force)
617 tab_indicator_visibility_ = TAB_INDICATORS_FORCE_HIDE;
618 else if (tab_indicator_visibility_ == TAB_INDICATORS_FORCE_HIDE)
619 tab_indicator_visibility_ = TAB_INDICATORS_HIDE;
620 UpdateUseMinimalChrome(LAYOUT_YES);
621}
622
623void ImmersiveModeControllerAsh::StartRevealForTest(bool hovered) {
624 MaybeStartReveal(ANIMATE_NO);
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100625 MoveMouse(top_container_, hovered);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100626 UpdateLocatedEventRevealedLock(NULL);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100627}
628
629void ImmersiveModeControllerAsh::SetMouseHoveredForTest(bool hovered) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100630 MoveMouse(top_container_, hovered);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100631 UpdateLocatedEventRevealedLock(NULL);
632}
633
634void ImmersiveModeControllerAsh::DisableAnimationsForTest() {
635 animations_disabled_for_test_ = true;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100636}
637
638////////////////////////////////////////////////////////////////////////////////
639// private:
640
641void ImmersiveModeControllerAsh::EnableWindowObservers(bool enable) {
642 if (observers_enabled_ == enable)
643 return;
644 observers_enabled_ = enable;
645
646 if (!native_window_) {
647 NOTREACHED() << "ImmersiveModeControllerAsh not initialized";
648 return;
649 }
650
651 views::Widget* widget =
652 views::Widget::GetWidgetForNativeWindow(native_window_);
653 views::FocusManager* focus_manager = widget->GetFocusManager();
654 if (enable) {
655 widget->AddObserver(this);
656 focus_manager->AddFocusChangeListener(this);
657 } else {
658 widget->RemoveObserver(this);
659 focus_manager->RemoveFocusChangeListener(this);
660 }
661
Ben Murdocheb525c52013-07-10 11:40:50 +0100662 if (enable)
663 ash::Shell::GetInstance()->AddPreTargetHandler(this);
664 else
665 ash::Shell::GetInstance()->RemovePreTargetHandler(this);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100666
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100667 if (enable) {
668 native_window_->AddObserver(this);
669 } else {
670 native_window_->RemoveObserver(this);
671 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100672
673 if (enable) {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100674 RecreateBubbleManager();
675 } else {
676 // We have stopped observing whether transient children are added or removed
677 // to |native_window_|. The set of bubbles that BubbleManager is observing
678 // will become stale really quickly. Destroy BubbleManager and recreate it
679 // when we start observing |native_window_| again.
680 bubble_manager_.reset();
681 }
682
683 if (enable) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100684 registrar_.Add(
685 this,
686 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
687 content::Source<FullscreenController>(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100688 delegate_->GetFullscreenController()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100689 } else {
690 registrar_.Remove(
691 this,
692 chrome::NOTIFICATION_FULLSCREEN_CHANGED,
693 content::Source<FullscreenController>(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100694 delegate_->GetFullscreenController()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100695 }
696
697 if (!enable)
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100698 animation_->Stop();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100699}
700
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100701void ImmersiveModeControllerAsh::UpdateTopEdgeHoverTimer(
702 ui::MouseEvent* event) {
703 DCHECK(enabled_);
Ben Murdocheb525c52013-07-10 11:40:50 +0100704 // Stop the timer if the top-of-window views are already revealed.
705 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) {
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100706 top_edge_hover_timer_.Stop();
707 return;
708 }
Ben Murdocheb525c52013-07-10 11:40:50 +0100709
710 gfx::Point location_in_screen = GetEventLocationInScreen(*event);
Ben Murdochbb1529c2013-08-08 10:24:53 +0100711 if (ShouldIgnoreMouseEventAtLocation(location_in_screen))
712 return;
Ben Murdocheb525c52013-07-10 11:40:50 +0100713
714 // Stop the timer if the cursor left the top edge or is on a different
Ben Murdochbb1529c2013-08-08 10:24:53 +0100715 // display. The bounds of |top_container_|'s parent are used to infer the hit
716 // bounds because |top_container_| will be partially offscreen if it is
717 // animating closed.
718 gfx::Rect hit_bounds_in_screen =
719 top_container_->parent()->GetBoundsInScreen();
720 hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight);
721 if (!hit_bounds_in_screen.Contains(location_in_screen)) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100722 top_edge_hover_timer_.Stop();
723 return;
724 }
725
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100726 // The cursor is now at the top of the screen. Consider the cursor "not
Ben Murdochbb1529c2013-08-08 10:24:53 +0100727 // moving" even if it moves a little bit because users don't have perfect
728 // pointing precision. (The y position is not tested because
729 // |hit_bounds_in_screen| is short.)
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100730 if (top_edge_hover_timer_.IsRunning() &&
Ben Murdochbb1529c2013-08-08 10:24:53 +0100731 abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <=
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100732 ImmersiveFullscreenConfiguration::
733 immersive_mode_reveal_x_threshold_pixels())
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100734 return;
735
736 // Start the reveal if the cursor doesn't move for some amount of time.
Ben Murdochbb1529c2013-08-08 10:24:53 +0100737 mouse_x_when_hit_top_in_screen_ = location_in_screen.x();
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100738 top_edge_hover_timer_.Stop();
739 // Timer is stopped when |this| is destroyed, hence Unretained() is safe.
740 top_edge_hover_timer_.Start(
741 FROM_HERE,
742 base::TimeDelta::FromMilliseconds(
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100743 ImmersiveFullscreenConfiguration::immersive_mode_reveal_delay_ms()),
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100744 base::Bind(&ImmersiveModeControllerAsh::AcquireLocatedEventRevealedLock,
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +0100745 base::Unretained(this)));
746}
747
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100748void ImmersiveModeControllerAsh::UpdateLocatedEventRevealedLock(
749 ui::LocatedEvent* event) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100750 if (!enabled_)
751 return;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100752 DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100753
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100754 // Neither the mouse nor touch can initiate a reveal when the top-of-window
755 // views are sliding closed or are closed with the following exceptions:
756 // - Hovering at y = 0 which is handled in OnMouseEvent().
757 // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent().
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100758 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED)
759 return;
760
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100761 // Neither the mouse nor touch should keep the top-of-window views revealed if
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100762 // |native_window_| is not active.
763 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100764 located_event_revealed_lock_.reset();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100765 return;
766 }
767
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100768 // Ignore all events while a window has capture. This keeps the top-of-window
769 // views revealed during a drag.
770 if (aura::client::GetCaptureWindow(native_window_))
771 return;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100772
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100773 gfx::Point location_in_screen;
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100774 if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) {
Ben Murdocheb525c52013-07-10 11:40:50 +0100775 location_in_screen = GetEventLocationInScreen(*event);
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100776 } else {
777 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient(
778 native_window_->GetRootWindow());
779 if (!cursor_client->IsMouseEventsEnabled()) {
780 // If mouse events are disabled, the user's last interaction was probably
781 // via touch. Do no do further processing in this case as there is no easy
782 // way of retrieving the position of the user's last touch.
783 return;
784 }
785 location_in_screen = aura::Env::GetInstance()->last_mouse_location();
786 }
787
Ben Murdochbb1529c2013-08-08 10:24:53 +0100788 if ((!event || event->IsMouseEvent()) &&
789 ShouldIgnoreMouseEventAtLocation(location_in_screen)) {
790 return;
791 }
792
793 gfx::Rect hit_bounds_in_top_container = top_container_->GetVisibleBounds();
794 // TODO(tdanderson): Implement View::ConvertRectToScreen();
795 gfx::Point hit_bounds_in_screen_origin = hit_bounds_in_top_container.origin();
796 views::View::ConvertPointToScreen(top_container_,
797 &hit_bounds_in_screen_origin);
798 gfx::Rect hit_bounds_in_screen(hit_bounds_in_screen_origin,
799 hit_bounds_in_top_container.size());
800
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100801 gfx::Rect find_bar_hit_bounds_in_screen = find_bar_visible_bounds_in_screen_;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100802
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100803 // Allow the cursor to move slightly off the top-of-window views before
804 // sliding closed. This helps when the user is attempting to click on the
805 // bookmark bar and overshoots slightly.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100806 if (event && event->type() == ui::ET_MOUSE_MOVED) {
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100807 const int kBoundsOffsetY = 8;
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100808 hit_bounds_in_screen.Inset(0, 0, 0, -kBoundsOffsetY);
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100809 find_bar_hit_bounds_in_screen.Inset(0, 0, 0, -kBoundsOffsetY);
Torne (Richard Coles)a93a17c2013-05-15 11:34:50 +0100810 }
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100811
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100812 if (hit_bounds_in_screen.Contains(location_in_screen) ||
813 find_bar_hit_bounds_in_screen.Contains(location_in_screen)) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100814 AcquireLocatedEventRevealedLock();
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100815 } else {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100816 located_event_revealed_lock_.reset();
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100817 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100818}
819
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100820void ImmersiveModeControllerAsh::AcquireLocatedEventRevealedLock() {
Torne (Richard Coles)7d4cd472013-06-19 11:58:07 +0100821 // CAUTION: Acquiring the lock results in a reentrant call to
822 // AcquireLocatedEventRevealedLock() when
823 // |ImmersiveModeControllerAsh::animations_disabled_for_test_| is true.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100824 if (!located_event_revealed_lock_.get())
825 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100826}
827
828void ImmersiveModeControllerAsh::UpdateFocusRevealedLock() {
829 if (!enabled_)
830 return;
831
832 bool hold_lock = false;
833 views::Widget* widget =
834 views::Widget::GetWidgetForNativeWindow(native_window_);
835 if (widget->IsActive()) {
836 views::View* focused_view = widget->GetFocusManager()->GetFocusedView();
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100837 if (top_container_->Contains(focused_view))
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100838 hold_lock = true;
839 } else {
Ben Murdoch7dbb3d52013-07-17 14:55:54 +0100840 aura::Window* active_window = aura::client::GetActivationClient(
841 native_window_->GetRootWindow())->GetActiveWindow();
842 views::BubbleDelegateView* bubble_delegate =
843 AsBubbleDelegate(active_window);
844 if (bubble_delegate && bubble_delegate->anchor_view()) {
845 // BubbleManager will already have locked the top-of-window views if the
846 // bubble is anchored to a child of |top_container_|. Don't acquire
847 // |focus_revealed_lock_| here for the sake of simplicity.
848 } else {
849 // The currently active window is not |native_window_| and it is not a
850 // bubble with an anchor view. The top-of-window views should be revealed
851 // if:
852 // 1) The active window is a transient child of |native_window_|.
853 // 2) The top-of-window views are already revealed. This restriction
854 // prevents a transient window opened by the web contents while the
855 // top-of-window views are hidden from from initiating a reveal.
856 // The top-of-window views will stay revealed till |native_window_| is
857 // reactivated.
858 if (IsRevealed() &&
859 IsWindowTransientChildOf(active_window, native_window_)) {
860 hold_lock = true;
861 }
862 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100863 }
864
865 if (hold_lock) {
866 if (!focus_revealed_lock_.get())
867 focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
868 } else {
869 focus_revealed_lock_.reset();
870 }
871}
872
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100873bool ImmersiveModeControllerAsh::UpdateRevealedLocksForSwipe(
874 SwipeType swipe_type) {
875 if (!enabled_ || swipe_type == SWIPE_NONE)
876 return false;
877
878 // Swipes while |native_window_| is inactive should have been filtered out in
879 // OnGestureEvent().
880 DCHECK(views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive());
881
882 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) {
883 if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) {
884 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES));
885 return true;
886 }
887 } else {
888 if (swipe_type == SWIPE_CLOSE) {
889 // Attempt to end the reveal. If other code is holding onto a lock, the
890 // attempt will be unsuccessful.
891 located_event_revealed_lock_.reset();
892 focus_revealed_lock_.reset();
893
894 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED)
895 return true;
896
897 // Ending the reveal was unsuccessful. Reaquire the locks if appropriate.
898 UpdateLocatedEventRevealedLock(NULL);
899 UpdateFocusRevealedLock();
900 }
901 }
902 return false;
903}
904
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100905void ImmersiveModeControllerAsh::UpdateUseMinimalChrome(Layout layout) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100906 // May be NULL in tests.
907 FullscreenController* fullscreen_controller =
908 delegate_->GetFullscreenController();
909 bool in_tab_fullscreen = fullscreen_controller ?
910 fullscreen_controller->IsFullscreenForTabOrPending() : false;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100911 bool use_minimal_chrome = !in_tab_fullscreen && enabled_;
912 native_window_->SetProperty(ash::internal::kFullscreenUsesMinimalChromeKey,
913 use_minimal_chrome);
914
915 TabIndicatorVisibility previous_tab_indicator_visibility =
916 tab_indicator_visibility_;
917 if (tab_indicator_visibility_ != TAB_INDICATORS_FORCE_HIDE) {
918 tab_indicator_visibility_ = use_minimal_chrome ?
919 TAB_INDICATORS_SHOW : TAB_INDICATORS_HIDE;
920 }
921
922 // Ash on Windows may not have a shell.
923 if (ash::Shell::HasInstance()) {
924 // When using minimal chrome, the shelf is auto-hidden. The auto-hidden
925 // shelf displays a 3px 'light bar' when it is closed.
926 ash::Shell::GetInstance()->UpdateShelfVisibility();
927 }
928
929 if (tab_indicator_visibility_ != previous_tab_indicator_visibility) {
930 // If the top-of-window views are revealed or animating, the change will
931 // take effect with the layout once the top-of-window views are closed.
932 if (layout == LAYOUT_YES && reveal_state_ == CLOSED)
933 LayoutBrowserRootView();
934 }
935}
936
937int ImmersiveModeControllerAsh::GetAnimationDuration(Animate animate) const {
938 switch (animate) {
939 case ANIMATE_NO:
940 return 0;
941 case ANIMATE_SLOW:
942 return kRevealSlowAnimationDurationMs;
943 case ANIMATE_FAST:
944 return kRevealFastAnimationDurationMs;
945 }
946 NOTREACHED();
947 return 0;
948}
949
950void ImmersiveModeControllerAsh::MaybeStartReveal(Animate animate) {
951 if (!enabled_)
952 return;
953
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100954 if (animations_disabled_for_test_)
955 animate = ANIMATE_NO;
956
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100957 // Callers with ANIMATE_NO expect this function to synchronously reveal the
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100958 // top-of-window views.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100959 if (reveal_state_ == REVEALED ||
960 (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) {
961 return;
962 }
963
964 RevealState previous_reveal_state = reveal_state_;
965 reveal_state_ = SLIDING_OPEN;
966 if (previous_reveal_state == CLOSED) {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100967 // Turn on layer painting so that we can overlap the web contents.
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100968 EnablePaintToLayer(true);
969
970 // Ensure window caption buttons are updated and the view bounds are
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100971 // computed at normal (non-immersive-style) size. The layout call moves the
972 // top-of-window views to their initial offscreen position for the
973 // animation.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +0100974 delegate_->SetImmersiveStyle(false);
Ben Murdochca12bfa2013-07-23 11:17:05 +0100975 SetRenderWindowTopInsetsForTouch(0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100976 LayoutBrowserRootView();
977
978 // Do not do any more processing if LayoutBrowserView() changed
979 // |reveal_state_|.
Ben Murdocheb525c52013-07-10 11:40:50 +0100980 if (reveal_state_ != SLIDING_OPEN) {
981 if (reveal_state_ == REVEALED)
982 FOR_EACH_OBSERVER(Observer, observers_, OnImmersiveRevealStarted());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100983 return;
Ben Murdocheb525c52013-07-10 11:40:50 +0100984 }
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100985 }
986 // Slide in the reveal view.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +0100987 if (animate == ANIMATE_NO) {
988 animation_->Reset(1);
989 OnSlideOpenAnimationCompleted(LAYOUT_YES);
990 } else {
991 animation_->SetSlideDuration(GetAnimationDuration(animate));
992 animation_->Show();
993 }
Ben Murdocheb525c52013-07-10 11:40:50 +0100994
Ben Murdochca12bfa2013-07-23 11:17:05 +0100995 if (previous_reveal_state == CLOSED)
Ben Murdocheb525c52013-07-10 11:40:50 +0100996 FOR_EACH_OBSERVER(Observer, observers_, OnImmersiveRevealStarted());
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100997}
998
999void ImmersiveModeControllerAsh::EnablePaintToLayer(bool enable) {
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001000 top_container_->SetPaintToLayer(enable);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001001
1002 // Views software compositing is not fully layer aware. If the bookmark bar
1003 // is detached while the top container layer slides on or off the screen,
1004 // the pixels that become exposed are the remnants of the last software
1005 // composite of the BrowserView, not the freshly-exposed bookmark bar.
1006 // Force the bookmark bar to paint to a layer so the views composite
1007 // properly. The infobar container does not need this treatment because
1008 // BrowserView::PaintChildren() always draws it last when it is visible.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001009 BookmarkBarView* bookmark_bar = delegate_->GetBookmarkBar();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001010 if (!bookmark_bar)
1011 return;
1012 if (enable && bookmark_bar->IsDetached())
1013 bookmark_bar->SetPaintToLayer(true);
1014 else
1015 bookmark_bar->SetPaintToLayer(false);
1016}
1017
1018void ImmersiveModeControllerAsh::LayoutBrowserRootView() {
1019 // Update the window caption buttons.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001020 widget_->non_client_view()->frame_view()->ResetWindowControls();
1021 // Layout all views, including BrowserView.
1022 widget_->GetRootView()->Layout();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001023}
1024
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001025void ImmersiveModeControllerAsh::OnSlideOpenAnimationCompleted(Layout layout) {
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001026 DCHECK_EQ(SLIDING_OPEN, reveal_state_);
1027 reveal_state_ = REVEALED;
1028
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001029 if (layout == LAYOUT_YES)
1030 top_container_->parent()->Layout();
1031
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001032 // The user may not have moved the mouse since the reveal was initiated.
1033 // Update the revealed lock to reflect the mouse's current state.
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001034 UpdateLocatedEventRevealedLock(NULL);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001035}
1036
1037void ImmersiveModeControllerAsh::MaybeEndReveal(Animate animate) {
1038 if (!enabled_ || revealed_lock_count_ != 0)
1039 return;
1040
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001041 if (animations_disabled_for_test_)
1042 animate = ANIMATE_NO;
1043
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001044 // Callers with ANIMATE_NO expect this function to synchronously close the
1045 // top-of-window views.
1046 if (reveal_state_ == CLOSED ||
1047 (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) {
1048 return;
1049 }
1050
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001051 reveal_state_ = SLIDING_CLOSED;
1052 int duration_ms = GetAnimationDuration(animate);
1053 if (duration_ms > 0) {
1054 // The bookmark bar may have become detached during the reveal so ensure
1055 // layers are available. This is a no-op for the top container.
1056 EnablePaintToLayer(true);
1057
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001058 animation_->SetSlideDuration(duration_ms);
1059 animation_->Hide();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001060 } else {
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001061 animation_->Reset(0);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001062 OnSlideClosedAnimationCompleted();
1063 }
1064}
1065
1066void ImmersiveModeControllerAsh::OnSlideClosedAnimationCompleted() {
1067 DCHECK_EQ(SLIDING_CLOSED, reveal_state_);
1068 reveal_state_ = CLOSED;
1069 // Layers aren't needed after animation completes.
1070 EnablePaintToLayer(false);
1071 // Update tabstrip for closed state.
Torne (Richard Coles)90dce4d2013-05-29 14:40:03 +01001072 delegate_->SetImmersiveStyle(true);
Ben Murdochca12bfa2013-07-23 11:17:05 +01001073 SetRenderWindowTopInsetsForTouch(kNearTopContainerDistance);
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001074 LayoutBrowserRootView();
1075}
1076
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001077bool ImmersiveModeControllerAsh::ShouldExitImmersiveFullscreen() const {
1078 if (!native_window_)
1079 return false;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001080
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001081 ui::WindowShowState show_state = static_cast<ui::WindowShowState>(
1082 native_window_->GetProperty(aura::client::kShowStateKey));
1083 return IsEnabled() &&
1084 show_state != ui::SHOW_STATE_FULLSCREEN &&
1085 show_state != ui::SHOW_STATE_MINIMIZED;
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001086}
1087
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001088ImmersiveModeControllerAsh::SwipeType ImmersiveModeControllerAsh::GetSwipeType(
1089 ui::GestureEvent* event) const {
1090 if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE)
1091 return SWIPE_NONE;
1092 // Make sure that it is a clear vertical gesture.
1093 if (abs(event->details().scroll_y()) <=
1094 kSwipeVerticalThresholdMultiplier * abs(event->details().scroll_x()))
1095 return SWIPE_NONE;
1096 if (event->details().scroll_y() < 0)
1097 return SWIPE_CLOSE;
1098 else if (event->details().scroll_y() > 0)
1099 return SWIPE_OPEN;
1100 return SWIPE_NONE;
1101}
1102
Ben Murdochbb1529c2013-08-08 10:24:53 +01001103bool ImmersiveModeControllerAsh::ShouldIgnoreMouseEventAtLocation(
1104 const gfx::Point& location) const {
1105 // Ignore mouse events in the region immediately above the top edge of the
1106 // display. This is to handle the case of a user with a vertical display
1107 // layout (primary display above/below secondary display) and the immersive
1108 // fullscreen window on the bottom display. It is really hard to trigger a
1109 // reveal in this case because:
1110 // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight|
1111 // pixels of the bottom display.
1112 // - The cursor is warped to the top display if the cursor gets to the top
1113 // edge of the bottom display.
1114 // Mouse events are ignored in the bottom few pixels of the top display
1115 // (Mouse events in this region cannot start or end a reveal). This allows a
1116 // user to overshoot the top of the bottom display and still reveal the
1117 // top-of-window views.
1118 gfx::Rect dead_region = top_container_->parent()->GetBoundsInScreen();
1119 dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer);
1120 dead_region.set_height(kHeightOfDeadRegionAboveTopContainer);
1121 return dead_region.Contains(location);
1122}
1123
Ben Murdocheb525c52013-07-10 11:40:50 +01001124bool ImmersiveModeControllerAsh::ShouldHandleGestureEvent(
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001125 const gfx::Point& location) const {
1126 // All of the gestures that are of interest start in a region with left &
1127 // right edges agreeing with |top_container_|. When CLOSED it is difficult to
1128 // hit the bounds due to small size of the tab strip, so the hit target needs
1129 // to be extended on the bottom, thus the inset call. Finally there may be a
1130 // bezel sensor off screen logically above |top_container_| thus the test
1131 // needs to include gestures starting above.
1132 gfx::Rect near_bounds = top_container_->GetBoundsInScreen();
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001133 if (reveal_state_ == CLOSED)
1134 near_bounds.Inset(gfx::Insets(0, 0, -kNearTopContainerDistance, 0));
Torne (Richard Coles)868fa2f2013-06-11 10:57:03 +01001135 return near_bounds.Contains(location) ||
1136 ((location.y() < near_bounds.y()) &&
1137 (location.x() >= near_bounds.x()) &&
Ben Murdocheb525c52013-07-10 11:40:50 +01001138 (location.x() < near_bounds.right()));
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +01001139}
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001140
Ben Murdochca12bfa2013-07-23 11:17:05 +01001141void ImmersiveModeControllerAsh::SetRenderWindowTopInsetsForTouch(
1142 int top_inset) {
1143 content::WebContents* contents = delegate_->GetWebContents();
1144 if (contents) {
1145 aura::Window* window = contents->GetView()->GetContentNativeView();
1146 gfx::Insets inset(top_inset, 0, 0, 0);
1147 window->SetHitTestBoundsOverrideOuter(
1148 window->hit_test_bounds_override_outer_mouse(),
1149 inset);
1150 }
1151}
1152
Ben Murdoch7dbb3d52013-07-17 14:55:54 +01001153void ImmersiveModeControllerAsh::RecreateBubbleManager() {
1154 bubble_manager_.reset(new BubbleManager(this));
1155 const std::vector<aura::Window*> transient_children =
1156 native_window_->transient_children();
1157 for (size_t i = 0; i < transient_children.size(); ++i) {
1158 aura::Window* transient_child = transient_children[i];
1159 views::BubbleDelegateView* bubble_delegate =
1160 AsBubbleDelegate(transient_child);
1161 if (bubble_delegate &&
1162 bubble_delegate->anchor_view() &&
1163 top_container_->Contains(bubble_delegate->anchor_view())) {
1164 bubble_manager_->StartObserving(transient_child);
1165 }
1166 }
1167}