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