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