immersive_mode_controller_ash.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
1// 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 7#include <set> 8#include <vector> 9 10#include "ash/shell.h" 11#include "ash/wm/window_state.h" 12#include "chrome/browser/chrome_notification_types.h" 13#include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 14#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h" 15#include "chrome/browser/ui/views/frame/top_container_view.h" 16#include "content/public/browser/notification_service.h" 17#include "content/public/browser/web_contents.h" 18#include "content/public/browser/web_contents_view.h" 19#include "ui/aura/client/activation_client.h" 20#include "ui/aura/client/aura_constants.h" 21#include "ui/aura/client/capture_client.h" 22#include "ui/aura/client/cursor_client.h" 23#include "ui/aura/client/screen_position_client.h" 24#include "ui/aura/env.h" 25#include "ui/aura/root_window.h" 26#include "ui/aura/window.h" 27#include "ui/gfx/animation/slide_animation.h" 28#include "ui/views/bubble/bubble_delegate.h" 29#include "ui/views/view.h" 30#include "ui/views/widget/widget.h" 31#include "ui/views/window/non_client_view.h" 32 33using views::View; 34 35namespace { 36 37// The slide open/closed animation looks better if it starts and ends just a 38// few pixels before the view goes completely off the screen, which reduces 39// the visual "pop" as the 2-pixel tall immersive-style tabs become visible. 40const int kAnimationOffsetY = 3; 41 42// Duration for the reveal show/hide slide animation. The slower duration is 43// used for the initial slide out to give the user more change to see what 44// happened. 45const int kRevealSlowAnimationDurationMs = 400; 46const int kRevealFastAnimationDurationMs = 200; 47 48// The delay in milliseconds between the mouse stopping at the top edge of the 49// screen and the top-of-window views revealing. 50const int kMouseRevealDelayMs = 200; 51 52// The maximum amount of pixels that the cursor can move for the cursor to be 53// considered "stopped". This allows the user to reveal the top-of-window views 54// without holding the cursor completely still. 55const int kMouseRevealXThresholdPixels = 3; 56 57// How many pixels a gesture can start away from |top_container_| when in 58// closed state and still be considered near it. This is needed to overcome 59// issues with poor location values near the edge of the display. 60const int kNearTopContainerDistance = 8; 61 62// Used to multiply x value of an update in check to determine if gesture is 63// vertical. This is used to make sure that gesture is close to vertical instead 64// of just more vertical then horizontal. 65const int kSwipeVerticalThresholdMultiplier = 3; 66 67// The height in pixels of the region above the top edge of the display which 68// hosts the immersive fullscreen window in which mouse events are ignored 69// (cannot reveal or unreveal the top-of-window views). 70// See ShouldIgnoreMouseEventAtLocation() for more details. 71const int kHeightOfDeadRegionAboveTopContainer = 10; 72 73// The height in pixels of the region below the top edge of the display in which 74// the mouse can trigger revealing the top-of-window views. The height must be 75// greater than 1px because the top pixel is used to trigger moving the cursor 76// between displays if the user has a vertical display layout (primary display 77// above/below secondary display). 78const int kMouseRevealBoundsHeight = 3; 79 80// Returns the BubbleDelegateView corresponding to |maybe_bubble| if 81// |maybe_bubble| is a bubble. 82views::BubbleDelegateView* AsBubbleDelegate(aura::Window* maybe_bubble) { 83 if (!maybe_bubble) 84 return NULL; 85 views::Widget* widget = views::Widget::GetWidgetForNativeView(maybe_bubble); 86 if (!widget) 87 return NULL; 88 return widget->widget_delegate()->AsBubbleDelegate(); 89} 90 91// Returns true if |maybe_transient| is a transient child of |toplevel|. 92bool IsWindowTransientChildOf(aura::Window* maybe_transient, 93 aura::Window* toplevel) { 94 if (!maybe_transient || !toplevel) 95 return false; 96 97 for (aura::Window* window = maybe_transient; window; 98 window = window->transient_parent()) { 99 if (window == toplevel) 100 return true; 101 } 102 return false; 103} 104 105// Returns the location of |event| in screen coordinates. 106gfx::Point GetEventLocationInScreen(const ui::LocatedEvent& event) { 107 gfx::Point location_in_screen = event.location(); 108 aura::Window* target = static_cast<aura::Window*>(event.target()); 109 aura::client::ScreenPositionClient* screen_position_client = 110 aura::client::GetScreenPositionClient(target->GetRootWindow()); 111 screen_position_client->ConvertPointToScreen(target, &location_in_screen); 112 return location_in_screen; 113} 114 115//////////////////////////////////////////////////////////////////////////////// 116 117class RevealedLockAsh : public ImmersiveRevealedLock { 118 public: 119 RevealedLockAsh(const base::WeakPtr<ImmersiveModeControllerAsh>& controller, 120 ImmersiveModeController::AnimateReveal animate_reveal) 121 : controller_(controller) { 122 DCHECK(controller_); 123 controller_->LockRevealedState(animate_reveal); 124 } 125 126 virtual ~RevealedLockAsh() { 127 if (controller_) 128 controller_->UnlockRevealedState(); 129 } 130 131 private: 132 base::WeakPtr<ImmersiveModeControllerAsh> controller_; 133 134 DISALLOW_COPY_AND_ASSIGN(RevealedLockAsh); 135}; 136 137} // namespace 138 139//////////////////////////////////////////////////////////////////////////////// 140 141// Class which keeps the top-of-window views revealed as long as one of the 142// bubbles it is observing is visible. The logic to keep the top-of-window 143// views revealed based on the visibility of bubbles anchored to 144// children of |ImmersiveModeController::top_container_| is separate from 145// the logic related to |ImmersiveModeControllerAsh::focus_revealed_lock_| 146// so that bubbles which are not activatable and bubbles which do not close 147// upon deactivation also keep the top-of-window views revealed for the 148// duration of their visibility. 149class ImmersiveModeControllerAsh::BubbleManager : public aura::WindowObserver { 150 public: 151 explicit BubbleManager(ImmersiveModeControllerAsh* controller); 152 virtual ~BubbleManager(); 153 154 // Start / stop observing changes to |bubble|'s visibility. 155 void StartObserving(aura::Window* bubble); 156 void StopObserving(aura::Window* bubble); 157 158 private: 159 // Updates |revealed_lock_| based on whether any of |bubbles_| is visible. 160 void UpdateRevealedLock(); 161 162 // aura::WindowObserver overrides: 163 virtual void OnWindowVisibilityChanged(aura::Window* window, 164 bool visible) OVERRIDE; 165 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; 166 167 ImmersiveModeControllerAsh* controller_; 168 169 std::set<aura::Window*> bubbles_; 170 171 // Lock which keeps the top-of-window views revealed based on whether any of 172 // |bubbles_| is visible. 173 scoped_ptr<ImmersiveRevealedLock> revealed_lock_; 174 175 DISALLOW_COPY_AND_ASSIGN(BubbleManager); 176}; 177 178ImmersiveModeControllerAsh::BubbleManager::BubbleManager( 179 ImmersiveModeControllerAsh* controller) 180 : controller_(controller) { 181} 182 183ImmersiveModeControllerAsh::BubbleManager::~BubbleManager() { 184 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin(); 185 it != bubbles_.end(); ++it) { 186 (*it)->RemoveObserver(this); 187 } 188} 189 190void ImmersiveModeControllerAsh::BubbleManager::StartObserving( 191 aura::Window* bubble) { 192 if (bubbles_.insert(bubble).second) { 193 bubble->AddObserver(this); 194 UpdateRevealedLock(); 195 } 196} 197 198void ImmersiveModeControllerAsh::BubbleManager::StopObserving( 199 aura::Window* bubble) { 200 if (bubbles_.erase(bubble)) { 201 bubble->RemoveObserver(this); 202 UpdateRevealedLock(); 203 } 204} 205 206void ImmersiveModeControllerAsh::BubbleManager::UpdateRevealedLock() { 207 bool has_visible_bubble = false; 208 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin(); 209 it != bubbles_.end(); ++it) { 210 if ((*it)->IsVisible()) { 211 has_visible_bubble = true; 212 break; 213 } 214 } 215 216 bool was_revealed = controller_->IsRevealed(); 217 if (has_visible_bubble) { 218 if (!revealed_lock_.get()) { 219 // Reveal the top-of-window views without animating because it looks 220 // weird for the top-of-window views to animate and the bubble not to 221 // animate along with the top-of-window views. 222 revealed_lock_.reset(controller_->GetRevealedLock( 223 ImmersiveModeController::ANIMATE_REVEAL_NO)); 224 } 225 } else { 226 revealed_lock_.reset(); 227 } 228 229 if (!was_revealed && revealed_lock_.get()) { 230 // Currently, there is no nice way for bubbles to reposition themselves 231 // whenever the anchor view moves. Tell the bubbles to reposition themselves 232 // explicitly instead. The hidden bubbles are also repositioned because 233 // BubbleDelegateView does not reposition its widget as a result of a 234 // visibility change. 235 for (std::set<aura::Window*>::const_iterator it = bubbles_.begin(); 236 it != bubbles_.end(); ++it) { 237 AsBubbleDelegate(*it)->OnAnchorViewBoundsChanged(); 238 } 239 } 240} 241 242void ImmersiveModeControllerAsh::BubbleManager::OnWindowVisibilityChanged( 243 aura::Window*, 244 bool visible) { 245 UpdateRevealedLock(); 246} 247 248void ImmersiveModeControllerAsh::BubbleManager::OnWindowDestroying( 249 aura::Window* window) { 250 StopObserving(window); 251} 252 253//////////////////////////////////////////////////////////////////////////////// 254 255ImmersiveModeControllerAsh::ImmersiveModeControllerAsh() 256 : delegate_(NULL), 257 widget_(NULL), 258 top_container_(NULL), 259 observers_enabled_(false), 260 enabled_(false), 261 reveal_state_(CLOSED), 262 revealed_lock_count_(0), 263 tab_indicator_visibility_(TAB_INDICATORS_HIDE), 264 mouse_x_when_hit_top_in_screen_(-1), 265 gesture_begun_(false), 266 native_window_(NULL), 267 animation_(new gfx::SlideAnimation(this)), 268 animations_disabled_for_test_(false), 269 weak_ptr_factory_(this) { 270} 271 272ImmersiveModeControllerAsh::~ImmersiveModeControllerAsh() { 273 // The browser view is being destroyed so there's no need to update its 274 // layout or layers, even if the top views are revealed. But the window 275 // observers still need to be removed. 276 EnableWindowObservers(false); 277} 278 279void ImmersiveModeControllerAsh::LockRevealedState( 280 AnimateReveal animate_reveal) { 281 ++revealed_lock_count_; 282 Animate animate = (animate_reveal == ANIMATE_REVEAL_YES) ? 283 ANIMATE_FAST : ANIMATE_NO; 284 MaybeStartReveal(animate); 285} 286 287void ImmersiveModeControllerAsh::UnlockRevealedState() { 288 --revealed_lock_count_; 289 DCHECK_GE(revealed_lock_count_, 0); 290 if (revealed_lock_count_ == 0) { 291 // Always animate ending the reveal fast. 292 MaybeEndReveal(ANIMATE_FAST); 293 } 294} 295 296void ImmersiveModeControllerAsh::Init( 297 Delegate* delegate, 298 views::Widget* widget, 299 views::View* top_container) { 300 delegate_ = delegate; 301 widget_ = widget; 302 // Browser view is detached from its widget during destruction. Cache the 303 // window pointer so |this| can stop observing during destruction. 304 native_window_ = widget_->GetNativeWindow(); 305 top_container_ = top_container; 306} 307 308void ImmersiveModeControllerAsh::SetEnabled(bool enabled) { 309 DCHECK(native_window_) << "Must initialize before enabling"; 310 if (enabled_ == enabled) 311 return; 312 enabled_ = enabled; 313 314 EnableWindowObservers(enabled_); 315 316 UpdateUseMinimalChrome(LAYOUT_NO); 317 318 if (enabled_) { 319 // Animate enabling immersive mode by sliding out the top-of-window views. 320 // No animation occurs if a lock is holding the top-of-window views open. 321 322 // Do a reveal to set the initial state for the animation. (And any 323 // required state in case the animation cannot run because of a lock holding 324 // the top-of-window views open.) This call has the side effect of relaying 325 // out |browser_view_|'s root view. 326 MaybeStartReveal(ANIMATE_NO); 327 328 // Reset the located event and the focus revealed locks so that they do not 329 // affect whether the top-of-window views are hidden. 330 located_event_revealed_lock_.reset(); 331 focus_revealed_lock_.reset(); 332 333 // Try doing the animation. 334 MaybeEndReveal(ANIMATE_SLOW); 335 336 if (reveal_state_ == REVEALED) { 337 // Reveal was unsuccessful. Reacquire the revealed locks if appropriate. 338 UpdateLocatedEventRevealedLock(NULL, ALLOW_REVEAL_WHILE_CLOSING_NO); 339 UpdateFocusRevealedLock(); 340 } 341 } else { 342 // Stop cursor-at-top tracking. 343 top_edge_hover_timer_.Stop(); 344 // Snap immediately to the closed state. 345 reveal_state_ = CLOSED; 346 top_container_->SetPaintToLayer(false); 347 delegate_->SetImmersiveStyle(false); 348 SetRenderWindowTopInsetsForTouch(0); 349 350 // Layout the root view so that incognito avatar icon, if any, gets laid 351 // out. 352 LayoutBrowserRootView(); 353 } 354} 355 356bool ImmersiveModeControllerAsh::IsEnabled() const { 357 return enabled_; 358} 359 360bool ImmersiveModeControllerAsh::ShouldHideTabIndicators() const { 361 return tab_indicator_visibility_ != TAB_INDICATORS_SHOW; 362} 363 364bool ImmersiveModeControllerAsh::ShouldHideTopViews() const { 365 return enabled_ && reveal_state_ == CLOSED; 366} 367 368bool ImmersiveModeControllerAsh::IsRevealed() const { 369 return enabled_ && reveal_state_ != CLOSED; 370} 371 372int ImmersiveModeControllerAsh::GetTopContainerVerticalOffset( 373 const gfx::Size& top_container_size) const { 374 if (!enabled_ || reveal_state_ == REVEALED || reveal_state_ == CLOSED) 375 return 0; 376 377 return animation_->CurrentValueBetween( 378 -top_container_size.height() + kAnimationOffsetY, 0); 379} 380 381ImmersiveRevealedLock* ImmersiveModeControllerAsh::GetRevealedLock( 382 AnimateReveal animate_reveal) { 383 return new RevealedLockAsh(weak_ptr_factory_.GetWeakPtr(), animate_reveal); 384} 385 386void ImmersiveModeControllerAsh::OnFindBarVisibleBoundsChanged( 387 const gfx::Rect& new_visible_bounds_in_screen) { 388 find_bar_visible_bounds_in_screen_ = new_visible_bounds_in_screen; 389} 390 391void ImmersiveModeControllerAsh::SetupForTest() { 392 DCHECK(!enabled_); 393 animations_disabled_for_test_ = true; 394 395 // Move the mouse off of the top-of-window views so that it does not keep 396 // the top-of-window views revealed. 397 gfx::Point cursor_pos(0, top_container_->bounds().bottom() + 100); 398 views::View::ConvertPointToScreen(top_container_, &cursor_pos); 399 aura::Env::GetInstance()->set_last_mouse_location(cursor_pos); 400 UpdateLocatedEventRevealedLock(NULL, ALLOW_REVEAL_WHILE_CLOSING_NO); 401} 402 403//////////////////////////////////////////////////////////////////////////////// 404// Observers: 405 406void ImmersiveModeControllerAsh::Observe( 407 int type, 408 const content::NotificationSource& source, 409 const content::NotificationDetails& details) { 410 DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type); 411 if (enabled_) 412 UpdateUseMinimalChrome(LAYOUT_YES); 413} 414 415void ImmersiveModeControllerAsh::OnMouseEvent(ui::MouseEvent* event) { 416 if (!enabled_) 417 return; 418 419 if (event->type() != ui::ET_MOUSE_MOVED && 420 event->type() != ui::ET_MOUSE_PRESSED && 421 event->type() != ui::ET_MOUSE_RELEASED && 422 event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { 423 return; 424 } 425 426 // Mouse hover should not initiate revealing the top-of-window views while 427 // |native_window_| is inactive. 428 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) 429 return; 430 431 // Mouse hover should not initiate revealing the top-of-window views while 432 // a window has mouse capture. 433 if (aura::client::GetCaptureWindow(native_window_)) 434 return; 435 436 if (IsRevealed()) 437 UpdateLocatedEventRevealedLock(event, ALLOW_REVEAL_WHILE_CLOSING_NO); 438 439 // Trigger a reveal if the cursor pauses at the top of the screen for a 440 // while. 441 if (event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) 442 UpdateTopEdgeHoverTimer(event); 443} 444 445void ImmersiveModeControllerAsh::OnTouchEvent(ui::TouchEvent* event) { 446 if (!enabled_ || event->type() != ui::ET_TOUCH_PRESSED) 447 return; 448 449 UpdateLocatedEventRevealedLock(event, ALLOW_REVEAL_WHILE_CLOSING_NO); 450} 451 452void ImmersiveModeControllerAsh::OnGestureEvent(ui::GestureEvent* event) { 453 if (!enabled_) 454 return; 455 456 // Touch gestures should not initiate revealing the top-of-window views while 457 // |native_window_| is inactive. 458 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) 459 return; 460 461 switch (event->type()) { 462 case ui::ET_GESTURE_SCROLL_BEGIN: 463 if (ShouldHandleGestureEvent(GetEventLocationInScreen(*event))) { 464 gesture_begun_ = true; 465 event->SetHandled(); 466 } 467 break; 468 case ui::ET_GESTURE_SCROLL_UPDATE: 469 if (gesture_begun_) { 470 if (UpdateRevealedLocksForSwipe(GetSwipeType(event))) 471 event->SetHandled(); 472 gesture_begun_ = false; 473 } 474 break; 475 case ui::ET_GESTURE_SCROLL_END: 476 case ui::ET_SCROLL_FLING_START: 477 gesture_begun_ = false; 478 break; 479 default: 480 break; 481 } 482} 483 484void ImmersiveModeControllerAsh::OnWillChangeFocus(views::View* focused_before, 485 views::View* focused_now) { 486} 487 488void ImmersiveModeControllerAsh::OnDidChangeFocus(views::View* focused_before, 489 views::View* focused_now) { 490 UpdateFocusRevealedLock(); 491} 492 493void ImmersiveModeControllerAsh::OnWidgetDestroying(views::Widget* widget) { 494 EnableWindowObservers(false); 495 native_window_ = NULL; 496 497 // Set |enabled_| to false such that any calls to MaybeStartReveal() and 498 // MaybeEndReveal() have no effect. 499 enabled_ = false; 500} 501 502void ImmersiveModeControllerAsh::OnWidgetActivationChanged( 503 views::Widget* widget, 504 bool active) { 505 // Mouse hover should not initiate revealing the top-of-window views while 506 // |native_window_| is inactive. 507 top_edge_hover_timer_.Stop(); 508 509 UpdateFocusRevealedLock(); 510 511 // Allow the top-of-window views to stay revealed if all of the revealed locks 512 // were released in the process of activating |widget| but the mouse is still 513 // hovered above the top-of-window views. For instance, if the bubble which 514 // has been keeping the top-of-window views revealed is hidden but the mouse 515 // is hovered above the top-of-window views, the top-of-window views should 516 // stay revealed. We cannot call UpdateLocatedEventRevealedLock() from 517 // BubbleManager::UpdateRevealedLock() because |widget| is not yet active 518 // at that time. 519 UpdateLocatedEventRevealedLock(NULL, ALLOW_REVEAL_WHILE_CLOSING_YES); 520} 521 522//////////////////////////////////////////////////////////////////////////////// 523// Animation delegate: 524 525void ImmersiveModeControllerAsh::AnimationEnded( 526 const gfx::Animation* animation) { 527 if (reveal_state_ == SLIDING_OPEN) { 528 // AnimationProgressed() is called immediately before AnimationEnded() 529 // and does a layout. 530 OnSlideOpenAnimationCompleted(LAYOUT_NO); 531 } else if (reveal_state_ == SLIDING_CLOSED) { 532 OnSlideClosedAnimationCompleted(); 533 } 534} 535 536void ImmersiveModeControllerAsh::AnimationProgressed( 537 const gfx::Animation* animation) { 538 // Relayout. This will also move any views whose position depends on the 539 // top container position such as the find bar. 540 // We do not call LayoutBrowserRootView() here because we are not toggling 541 // the tab strip's immersive style so relaying out the non client view is not 542 // necessary. 543 top_container_->parent()->Layout(); 544} 545 546//////////////////////////////////////////////////////////////////////////////// 547// aura::WindowObserver overrides: 548 549void ImmersiveModeControllerAsh::OnWindowPropertyChanged(aura::Window* window, 550 const void* key, 551 intptr_t old) { 552 if (!enabled_) 553 return; 554 555 if (key == aura::client::kShowStateKey) { 556 // Disable immersive mode when the user exits fullscreen without going 557 // through FullscreenController::ToggleFullscreenMode(). This is the case 558 // if the user exits fullscreen via the restore button. 559 ui::WindowShowState show_state = static_cast<ui::WindowShowState>( 560 native_window_->GetProperty(aura::client::kShowStateKey)); 561 if (show_state != ui::SHOW_STATE_FULLSCREEN && 562 show_state != ui::SHOW_STATE_MINIMIZED) { 563 delegate_->FullscreenStateChanged(); 564 } 565 } 566} 567 568void ImmersiveModeControllerAsh::OnAddTransientChild(aura::Window* window, 569 aura::Window* transient) { 570 views::BubbleDelegateView* bubble_delegate = AsBubbleDelegate(transient); 571 if (bubble_delegate && 572 bubble_delegate->GetAnchorView() && 573 top_container_->Contains(bubble_delegate->GetAnchorView())) { 574 // Observe the aura::Window because the BubbleDelegateView may not be 575 // parented to the widget's root view yet so |bubble_delegate->GetWidget()| 576 // may still return NULL. 577 bubble_manager_->StartObserving(transient); 578 } 579} 580 581void ImmersiveModeControllerAsh::OnRemoveTransientChild( 582 aura::Window* window, 583 aura::Window* transient) { 584 bubble_manager_->StopObserving(transient); 585} 586 587//////////////////////////////////////////////////////////////////////////////// 588// private: 589 590void ImmersiveModeControllerAsh::EnableWindowObservers(bool enable) { 591 if (observers_enabled_ == enable) 592 return; 593 observers_enabled_ = enable; 594 595 if (!native_window_) { 596 NOTREACHED() << "ImmersiveModeControllerAsh not initialized"; 597 return; 598 } 599 600 views::Widget* widget = 601 views::Widget::GetWidgetForNativeWindow(native_window_); 602 views::FocusManager* focus_manager = widget->GetFocusManager(); 603 if (enable) { 604 widget->AddObserver(this); 605 focus_manager->AddFocusChangeListener(this); 606 } else { 607 widget->RemoveObserver(this); 608 focus_manager->RemoveFocusChangeListener(this); 609 } 610 611 if (enable) 612 ash::Shell::GetInstance()->AddPreTargetHandler(this); 613 else 614 ash::Shell::GetInstance()->RemovePreTargetHandler(this); 615 616 if (enable) { 617 native_window_->AddObserver(this); 618 } else { 619 native_window_->RemoveObserver(this); 620 } 621 622 if (enable) { 623 RecreateBubbleManager(); 624 } else { 625 // We have stopped observing whether transient children are added or removed 626 // to |native_window_|. The set of bubbles that BubbleManager is observing 627 // will become stale really quickly. Destroy BubbleManager and recreate it 628 // when we start observing |native_window_| again. 629 bubble_manager_.reset(); 630 } 631 632 if (enable) { 633 registrar_.Add( 634 this, 635 chrome::NOTIFICATION_FULLSCREEN_CHANGED, 636 content::Source<FullscreenController>( 637 delegate_->GetFullscreenController())); 638 } else { 639 registrar_.Remove( 640 this, 641 chrome::NOTIFICATION_FULLSCREEN_CHANGED, 642 content::Source<FullscreenController>( 643 delegate_->GetFullscreenController())); 644 } 645 646 if (!enable) 647 animation_->Stop(); 648} 649 650void ImmersiveModeControllerAsh::UpdateTopEdgeHoverTimer( 651 ui::MouseEvent* event) { 652 DCHECK(enabled_); 653 // Stop the timer if the top-of-window views are already revealed. 654 if (reveal_state_ == SLIDING_OPEN || reveal_state_ == REVEALED) { 655 top_edge_hover_timer_.Stop(); 656 return; 657 } 658 659 gfx::Point location_in_screen = GetEventLocationInScreen(*event); 660 if (ShouldIgnoreMouseEventAtLocation(location_in_screen)) 661 return; 662 663 // Stop the timer if the cursor left the top edge or is on a different 664 // display. The bounds of |top_container_|'s parent are used to infer the hit 665 // bounds because |top_container_| will be partially offscreen if it is 666 // animating closed. 667 gfx::Rect hit_bounds_in_screen = 668 top_container_->parent()->GetBoundsInScreen(); 669 hit_bounds_in_screen.set_height(kMouseRevealBoundsHeight); 670 if (!hit_bounds_in_screen.Contains(location_in_screen)) { 671 top_edge_hover_timer_.Stop(); 672 return; 673 } 674 675 // The cursor is now at the top of the screen. Consider the cursor "not 676 // moving" even if it moves a little bit because users don't have perfect 677 // pointing precision. (The y position is not tested because 678 // |hit_bounds_in_screen| is short.) 679 if (top_edge_hover_timer_.IsRunning() && 680 abs(location_in_screen.x() - mouse_x_when_hit_top_in_screen_) <= 681 kMouseRevealXThresholdPixels) 682 return; 683 684 // Start the reveal if the cursor doesn't move for some amount of time. 685 mouse_x_when_hit_top_in_screen_ = location_in_screen.x(); 686 top_edge_hover_timer_.Stop(); 687 // Timer is stopped when |this| is destroyed, hence Unretained() is safe. 688 top_edge_hover_timer_.Start( 689 FROM_HERE, 690 base::TimeDelta::FromMilliseconds(kMouseRevealDelayMs), 691 base::Bind(&ImmersiveModeControllerAsh::AcquireLocatedEventRevealedLock, 692 base::Unretained(this))); 693} 694 695void ImmersiveModeControllerAsh::UpdateLocatedEventRevealedLock( 696 ui::LocatedEvent* event, 697 AllowRevealWhileClosing allow_reveal_while_closing) { 698 if (!enabled_) 699 return; 700 DCHECK(!event || event->IsMouseEvent() || event->IsTouchEvent()); 701 702 // Neither the mouse nor touch can initiate a reveal when the top-of-window 703 // views are sliding closed or are closed with the following exceptions: 704 // - Hovering at y = 0 which is handled in OnMouseEvent(). 705 // - Doing a SWIPE_OPEN edge gesture which is handled in OnGestureEvent(). 706 if (reveal_state_ == CLOSED || 707 (reveal_state_ == SLIDING_CLOSED && 708 allow_reveal_while_closing == ALLOW_REVEAL_WHILE_CLOSING_NO)) { 709 return; 710 } 711 712 // Neither the mouse nor touch should keep the top-of-window views revealed if 713 // |native_window_| is not active. 714 if (!views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()) { 715 located_event_revealed_lock_.reset(); 716 return; 717 } 718 719 // Ignore all events while a window has capture. This keeps the top-of-window 720 // views revealed during a drag. 721 if (aura::client::GetCaptureWindow(native_window_)) 722 return; 723 724 gfx::Point location_in_screen; 725 if (event && event->type() != ui::ET_MOUSE_CAPTURE_CHANGED) { 726 location_in_screen = GetEventLocationInScreen(*event); 727 } else { 728 aura::client::CursorClient* cursor_client = aura::client::GetCursorClient( 729 native_window_->GetRootWindow()); 730 if (!cursor_client->IsMouseEventsEnabled()) { 731 // If mouse events are disabled, the user's last interaction was probably 732 // via touch. Do no do further processing in this case as there is no easy 733 // way of retrieving the position of the user's last touch. 734 return; 735 } 736 location_in_screen = aura::Env::GetInstance()->last_mouse_location(); 737 } 738 739 if ((!event || event->IsMouseEvent()) && 740 ShouldIgnoreMouseEventAtLocation(location_in_screen)) { 741 return; 742 } 743 744 gfx::Rect hit_bounds_in_top_container = top_container_->GetVisibleBounds(); 745 // TODO(tdanderson): Implement View::ConvertRectToScreen(); 746 gfx::Point hit_bounds_in_screen_origin = hit_bounds_in_top_container.origin(); 747 views::View::ConvertPointToScreen(top_container_, 748 &hit_bounds_in_screen_origin); 749 gfx::Rect hit_bounds_in_screen(hit_bounds_in_screen_origin, 750 hit_bounds_in_top_container.size()); 751 752 gfx::Rect find_bar_hit_bounds_in_screen = find_bar_visible_bounds_in_screen_; 753 754 // Allow the cursor to move slightly off the top-of-window views before 755 // sliding closed. This helps when the user is attempting to click on the 756 // bookmark bar and overshoots slightly. 757 if (event && event->type() == ui::ET_MOUSE_MOVED) { 758 const int kBoundsOffsetY = 8; 759 hit_bounds_in_screen.Inset(0, 0, 0, -kBoundsOffsetY); 760 find_bar_hit_bounds_in_screen.Inset(0, 0, 0, -kBoundsOffsetY); 761 } 762 763 if (hit_bounds_in_screen.Contains(location_in_screen) || 764 find_bar_hit_bounds_in_screen.Contains(location_in_screen)) { 765 AcquireLocatedEventRevealedLock(); 766 } else { 767 located_event_revealed_lock_.reset(); 768 } 769} 770 771void ImmersiveModeControllerAsh::AcquireLocatedEventRevealedLock() { 772 // CAUTION: Acquiring the lock results in a reentrant call to 773 // AcquireLocatedEventRevealedLock() when 774 // |ImmersiveModeControllerAsh::animations_disabled_for_test_| is true. 775 if (!located_event_revealed_lock_.get()) 776 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 777} 778 779void ImmersiveModeControllerAsh::UpdateFocusRevealedLock() { 780 if (!enabled_) 781 return; 782 783 bool hold_lock = false; 784 views::Widget* widget = 785 views::Widget::GetWidgetForNativeWindow(native_window_); 786 if (widget->IsActive()) { 787 views::View* focused_view = widget->GetFocusManager()->GetFocusedView(); 788 if (top_container_->Contains(focused_view)) 789 hold_lock = true; 790 } else { 791 aura::Window* active_window = aura::client::GetActivationClient( 792 native_window_->GetRootWindow())->GetActiveWindow(); 793 views::BubbleDelegateView* bubble_delegate = 794 AsBubbleDelegate(active_window); 795 if (bubble_delegate && bubble_delegate->anchor_widget()) { 796 // BubbleManager will already have locked the top-of-window views if the 797 // bubble is anchored to a child of |top_container_|. Don't acquire 798 // |focus_revealed_lock_| here for the sake of simplicity. 799 // Note: Instead of checking for the existence of the |anchor_view|, 800 // the existence of the |anchor_widget| is performed to avoid the case 801 // where the view is already gone (and the widget is still running). 802 } else { 803 // The currently active window is not |native_window_| and it is not a 804 // bubble with an anchor view. The top-of-window views should be revealed 805 // if: 806 // 1) The active window is a transient child of |native_window_|. 807 // 2) The top-of-window views are already revealed. This restriction 808 // prevents a transient window opened by the web contents while the 809 // top-of-window views are hidden from from initiating a reveal. 810 // The top-of-window views will stay revealed till |native_window_| is 811 // reactivated. 812 if (IsRevealed() && 813 IsWindowTransientChildOf(active_window, native_window_)) { 814 hold_lock = true; 815 } 816 } 817 } 818 819 if (hold_lock) { 820 if (!focus_revealed_lock_.get()) 821 focus_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 822 } else { 823 focus_revealed_lock_.reset(); 824 } 825} 826 827bool ImmersiveModeControllerAsh::UpdateRevealedLocksForSwipe( 828 SwipeType swipe_type) { 829 if (!enabled_ || swipe_type == SWIPE_NONE) 830 return false; 831 832 // Swipes while |native_window_| is inactive should have been filtered out in 833 // OnGestureEvent(). 834 DCHECK(views::Widget::GetWidgetForNativeWindow(native_window_)->IsActive()); 835 836 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) { 837 if (swipe_type == SWIPE_OPEN && !located_event_revealed_lock_.get()) { 838 located_event_revealed_lock_.reset(GetRevealedLock(ANIMATE_REVEAL_YES)); 839 return true; 840 } 841 } else { 842 if (swipe_type == SWIPE_CLOSE) { 843 // Attempt to end the reveal. If other code is holding onto a lock, the 844 // attempt will be unsuccessful. 845 located_event_revealed_lock_.reset(); 846 focus_revealed_lock_.reset(); 847 848 if (reveal_state_ == SLIDING_CLOSED || reveal_state_ == CLOSED) 849 return true; 850 851 // Ending the reveal was unsuccessful. Reaquire the locks if appropriate. 852 UpdateLocatedEventRevealedLock(NULL, ALLOW_REVEAL_WHILE_CLOSING_NO); 853 UpdateFocusRevealedLock(); 854 } 855 } 856 return false; 857} 858 859void ImmersiveModeControllerAsh::UpdateUseMinimalChrome(Layout layout) { 860 // May be NULL in tests. 861 FullscreenController* fullscreen_controller = 862 delegate_->GetFullscreenController(); 863 bool in_tab_fullscreen = fullscreen_controller ? 864 fullscreen_controller->IsFullscreenForTabOrPending() : false; 865 bool use_minimal_chrome = !in_tab_fullscreen && enabled_; 866 867 // When using minimal chrome, the shelf is auto-hidden. The auto-hidden shelf 868 // displays a 3px 'light bar' when it is closed. Otherwise, the shelf is 869 // hidden completely and cannot be revealed. 870 ash::wm::GetWindowState(native_window_)->set_hide_shelf_when_fullscreen( 871 !use_minimal_chrome); 872 873 TabIndicatorVisibility previous_tab_indicator_visibility = 874 tab_indicator_visibility_; 875 if (tab_indicator_visibility_ != TAB_INDICATORS_FORCE_HIDE) { 876 tab_indicator_visibility_ = use_minimal_chrome ? 877 TAB_INDICATORS_SHOW : TAB_INDICATORS_HIDE; 878 } 879 880 ash::Shell::GetInstance()->UpdateShelfVisibility(); 881 882 if (tab_indicator_visibility_ != previous_tab_indicator_visibility) { 883 // If the top-of-window views are revealed or animating, the change will 884 // take effect with the layout once the top-of-window views are closed. 885 if (layout == LAYOUT_YES && reveal_state_ == CLOSED) 886 LayoutBrowserRootView(); 887 } 888} 889 890int ImmersiveModeControllerAsh::GetAnimationDuration(Animate animate) const { 891 switch (animate) { 892 case ANIMATE_NO: 893 return 0; 894 case ANIMATE_SLOW: 895 return kRevealSlowAnimationDurationMs; 896 case ANIMATE_FAST: 897 return kRevealFastAnimationDurationMs; 898 } 899 NOTREACHED(); 900 return 0; 901} 902 903void ImmersiveModeControllerAsh::MaybeStartReveal(Animate animate) { 904 if (!enabled_) 905 return; 906 907 if (animations_disabled_for_test_) 908 animate = ANIMATE_NO; 909 910 // Callers with ANIMATE_NO expect this function to synchronously reveal the 911 // top-of-window views. 912 if (reveal_state_ == REVEALED || 913 (reveal_state_ == SLIDING_OPEN && animate != ANIMATE_NO)) { 914 return; 915 } 916 917 RevealState previous_reveal_state = reveal_state_; 918 reveal_state_ = SLIDING_OPEN; 919 if (previous_reveal_state == CLOSED) { 920 // Turn on layer painting so that we can overlap the web contents. 921 top_container_->SetPaintToLayer(true); 922 923 // Ensure window caption buttons are updated and the view bounds are 924 // computed at normal (non-immersive-style) size. The layout call moves the 925 // top-of-window views to their initial offscreen position for the 926 // animation. 927 delegate_->SetImmersiveStyle(false); 928 SetRenderWindowTopInsetsForTouch(0); 929 LayoutBrowserRootView(); 930 931 // Do not do any more processing if LayoutBrowserView() changed 932 // |reveal_state_|. 933 if (reveal_state_ != SLIDING_OPEN) { 934 if (reveal_state_ == REVEALED) 935 FOR_EACH_OBSERVER(Observer, observers_, OnImmersiveRevealStarted()); 936 return; 937 } 938 } 939 // Slide in the reveal view. 940 if (animate == ANIMATE_NO) { 941 animation_->Reset(1); 942 OnSlideOpenAnimationCompleted(LAYOUT_YES); 943 } else { 944 animation_->SetSlideDuration(GetAnimationDuration(animate)); 945 animation_->Show(); 946 } 947 948 if (previous_reveal_state == CLOSED) 949 FOR_EACH_OBSERVER(Observer, observers_, OnImmersiveRevealStarted()); 950} 951 952void ImmersiveModeControllerAsh::LayoutBrowserRootView() { 953 // Update the window caption buttons. 954 widget_->non_client_view()->frame_view()->ResetWindowControls(); 955 // Layout all views, including BrowserView. 956 widget_->GetRootView()->Layout(); 957} 958 959void ImmersiveModeControllerAsh::OnSlideOpenAnimationCompleted(Layout layout) { 960 DCHECK_EQ(SLIDING_OPEN, reveal_state_); 961 reveal_state_ = REVEALED; 962 963 if (layout == LAYOUT_YES) 964 top_container_->parent()->Layout(); 965 966 // The user may not have moved the mouse since the reveal was initiated. 967 // Update the revealed lock to reflect the mouse's current state. 968 UpdateLocatedEventRevealedLock(NULL, ALLOW_REVEAL_WHILE_CLOSING_NO); 969} 970 971void ImmersiveModeControllerAsh::MaybeEndReveal(Animate animate) { 972 if (!enabled_ || revealed_lock_count_ != 0) 973 return; 974 975 if (animations_disabled_for_test_) 976 animate = ANIMATE_NO; 977 978 // Callers with ANIMATE_NO expect this function to synchronously close the 979 // top-of-window views. 980 if (reveal_state_ == CLOSED || 981 (reveal_state_ == SLIDING_CLOSED && animate != ANIMATE_NO)) { 982 return; 983 } 984 985 reveal_state_ = SLIDING_CLOSED; 986 int duration_ms = GetAnimationDuration(animate); 987 if (duration_ms > 0) { 988 animation_->SetSlideDuration(duration_ms); 989 animation_->Hide(); 990 } else { 991 animation_->Reset(0); 992 OnSlideClosedAnimationCompleted(); 993 } 994} 995 996void ImmersiveModeControllerAsh::OnSlideClosedAnimationCompleted() { 997 DCHECK_EQ(SLIDING_CLOSED, reveal_state_); 998 reveal_state_ = CLOSED; 999 // Layers aren't needed after animation completes. 1000 top_container_->SetPaintToLayer(false); 1001 // Update tabstrip for closed state. 1002 delegate_->SetImmersiveStyle(true); 1003 SetRenderWindowTopInsetsForTouch(kNearTopContainerDistance); 1004 LayoutBrowserRootView(); 1005} 1006 1007ImmersiveModeControllerAsh::SwipeType ImmersiveModeControllerAsh::GetSwipeType( 1008 ui::GestureEvent* event) const { 1009 if (event->type() != ui::ET_GESTURE_SCROLL_UPDATE) 1010 return SWIPE_NONE; 1011 // Make sure that it is a clear vertical gesture. 1012 if (abs(event->details().scroll_y()) <= 1013 kSwipeVerticalThresholdMultiplier * abs(event->details().scroll_x())) 1014 return SWIPE_NONE; 1015 if (event->details().scroll_y() < 0) 1016 return SWIPE_CLOSE; 1017 else if (event->details().scroll_y() > 0) 1018 return SWIPE_OPEN; 1019 return SWIPE_NONE; 1020} 1021 1022bool ImmersiveModeControllerAsh::ShouldIgnoreMouseEventAtLocation( 1023 const gfx::Point& location) const { 1024 // Ignore mouse events in the region immediately above the top edge of the 1025 // display. This is to handle the case of a user with a vertical display 1026 // layout (primary display above/below secondary display) and the immersive 1027 // fullscreen window on the bottom display. It is really hard to trigger a 1028 // reveal in this case because: 1029 // - It is hard to stop the cursor in the top |kMouseRevealBoundsHeight| 1030 // pixels of the bottom display. 1031 // - The cursor is warped to the top display if the cursor gets to the top 1032 // edge of the bottom display. 1033 // Mouse events are ignored in the bottom few pixels of the top display 1034 // (Mouse events in this region cannot start or end a reveal). This allows a 1035 // user to overshoot the top of the bottom display and still reveal the 1036 // top-of-window views. 1037 gfx::Rect dead_region = top_container_->parent()->GetBoundsInScreen(); 1038 dead_region.set_y(dead_region.y() - kHeightOfDeadRegionAboveTopContainer); 1039 dead_region.set_height(kHeightOfDeadRegionAboveTopContainer); 1040 return dead_region.Contains(location); 1041} 1042 1043bool ImmersiveModeControllerAsh::ShouldHandleGestureEvent( 1044 const gfx::Point& location) const { 1045 gfx::Rect top_container_bounds_in_screen = 1046 top_container_->GetBoundsInScreen(); 1047 1048 // All of the gestures that are of interest start in a region with left & 1049 // right edges agreeing with |top_container_|. When CLOSED it is difficult to 1050 // hit the bounds due to small size of the tab strip, so the hit target needs 1051 // to be extended on the bottom, thus the inset call. 1052 gfx::Rect near_bounds = top_container_bounds_in_screen; 1053 if (reveal_state_ == CLOSED) 1054 near_bounds.Inset(gfx::Insets(0, 0, -kNearTopContainerDistance, 0)); 1055 if (near_bounds.Contains(location)) 1056 return true; 1057 1058 // There may be a bezel sensor off screen logically above |top_container_| 1059 // thus the test needs to include gestures starting above, but this needs to 1060 // be distinguished from events originating on another screen from 1061 // (potentially) an extended desktop. The check for the event not contained by 1062 // the closest screen ensures that the event is from a valid bezel and can be 1063 // interpreted as such. 1064 gfx::Rect screen_bounds = 1065 ash::Shell::GetScreen()->GetDisplayNearestPoint(location).bounds(); 1066 return (!screen_bounds.Contains(location) && 1067 location.y() < top_container_bounds_in_screen.y() && 1068 location.x() >= top_container_bounds_in_screen.x() && 1069 location.x() < top_container_bounds_in_screen.right()); 1070} 1071 1072void ImmersiveModeControllerAsh::SetRenderWindowTopInsetsForTouch( 1073 int top_inset) { 1074 content::WebContents* contents = delegate_->GetWebContents(); 1075 if (contents) { 1076 aura::Window* window = contents->GetView()->GetContentNativeView(); 1077 // |window| is NULL if the renderer crashed. 1078 if (window) { 1079 gfx::Insets inset(top_inset, 0, 0, 0); 1080 window->SetHitTestBoundsOverrideOuter( 1081 window->hit_test_bounds_override_outer_mouse(), 1082 inset); 1083 } 1084 } 1085} 1086 1087void ImmersiveModeControllerAsh::RecreateBubbleManager() { 1088 bubble_manager_.reset(new BubbleManager(this)); 1089 const std::vector<aura::Window*> transient_children = 1090 native_window_->transient_children(); 1091 for (size_t i = 0; i < transient_children.size(); ++i) { 1092 aura::Window* transient_child = transient_children[i]; 1093 views::BubbleDelegateView* bubble_delegate = 1094 AsBubbleDelegate(transient_child); 1095 if (bubble_delegate && 1096 bubble_delegate->GetAnchorView() && 1097 top_container_->Contains(bubble_delegate->GetAnchorView())) { 1098 bubble_manager_->StartObserving(transient_child); 1099 } 1100 } 1101} 1102