1 // Copyright (c) 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 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h" 6 7 #include <algorithm> 8 9 #include "base/command_line.h" 10 #import "base/mac/mac_util.h" 11 #include "base/mac/sdk_forward_declarations.h" 12 #include "chrome/browser/fullscreen.h" 13 #import "chrome/browser/ui/cocoa/browser_window_controller.h" 14 #include "chrome/common/chrome_switches.h" 15 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h" 16 #import "ui/base/cocoa/nsview_additions.h" 17 18 NSString* const kWillEnterFullscreenNotification = 19 @"WillEnterFullscreenNotification"; 20 NSString* const kWillLeaveFullscreenNotification = 21 @"WillLeaveFullscreenNotification"; 22 23 namespace { 24 25 // The activation zone for the main menu is 4 pixels high; if we make it any 26 // smaller, then the menu can be made to appear without the bar sliding down. 27 const CGFloat kDropdownActivationZoneHeight = 4; 28 const NSTimeInterval kDropdownAnimationDuration = 0.12; 29 const NSTimeInterval kMouseExitCheckDelay = 0.1; 30 // This show delay attempts to match the delay for the main menu. 31 const NSTimeInterval kDropdownShowDelay = 0.3; 32 const NSTimeInterval kDropdownHideDelay = 0.2; 33 34 // The amount by which the floating bar is offset downwards (to avoid the menu) 35 // in presentation mode. (We can't use |-[NSMenu menuBarHeight]| since it 36 // returns 0 when the menu bar is hidden.) 37 const CGFloat kFloatingBarVerticalOffset = 22; 38 39 OSStatus MenuBarRevealHandler(EventHandlerCallRef handler, 40 EventRef event, 41 void* context) { 42 PresentationModeController* self = 43 static_cast<PresentationModeController*>(context); 44 CGFloat revealFraction = 0; 45 GetEventParameter(event, 46 FOUR_CHAR_CODE('rvlf'), 47 typeCGFloat, 48 NULL, 49 sizeof(CGFloat), 50 NULL, 51 &revealFraction); 52 [self setMenuBarRevealProgress:revealFraction]; 53 return CallNextEventHandler(handler, event); 54 } 55 56 } // end namespace 57 58 // Helper class to manage animations for the dropdown bar. Calls 59 // [PresentationModeController changeToolbarFraction] once per 60 // animation step. 61 @interface DropdownAnimation : NSAnimation { 62 @private 63 PresentationModeController* controller_; 64 CGFloat startFraction_; 65 CGFloat endFraction_; 66 } 67 68 @property(readonly, nonatomic) CGFloat startFraction; 69 @property(readonly, nonatomic) CGFloat endFraction; 70 71 // Designated initializer. Asks |controller| for the current shown fraction, so 72 // if the bar is already partially shown or partially hidden, the animation 73 // duration may be less than |fullDuration|. 74 - (id)initWithFraction:(CGFloat)fromFraction 75 fullDuration:(CGFloat)fullDuration 76 animationCurve:(NSAnimationCurve)animationCurve 77 controller:(PresentationModeController*)controller; 78 79 @end 80 81 @implementation DropdownAnimation 82 83 @synthesize startFraction = startFraction_; 84 @synthesize endFraction = endFraction_; 85 86 - (id)initWithFraction:(CGFloat)toFraction 87 fullDuration:(CGFloat)fullDuration 88 animationCurve:(NSAnimationCurve)animationCurve 89 controller:(PresentationModeController*)controller { 90 // Calculate the effective duration, based on the current shown fraction. 91 DCHECK(controller); 92 CGFloat fromFraction = controller.toolbarFraction; 93 CGFloat effectiveDuration = fabs(fullDuration * (fromFraction - toFraction)); 94 95 if ((self = [super gtm_initWithDuration:effectiveDuration 96 eventMask:NSLeftMouseDownMask 97 animationCurve:animationCurve])) { 98 startFraction_ = fromFraction; 99 endFraction_ = toFraction; 100 controller_ = controller; 101 } 102 return self; 103 } 104 105 // Called once per animation step. Overridden to change the floating bar's 106 // position based on the animation's progress. 107 - (void)setCurrentProgress:(NSAnimationProgress)progress { 108 CGFloat fraction = 109 startFraction_ + (progress * (endFraction_ - startFraction_)); 110 [controller_ changeToolbarFraction:fraction]; 111 } 112 113 @end 114 115 116 @interface PresentationModeController (PrivateMethods) 117 118 // Updates the visibility of the menu bar and the dock. 119 - (void)updateMenuBarAndDockVisibility; 120 121 // Whether the current screen is expected to have a menu bar, regardless of 122 // current visibility of the menu bar. 123 - (BOOL)doesScreenHaveMenuBar; 124 125 // Returns YES if the window is on the primary screen. 126 - (BOOL)isWindowOnPrimaryScreen; 127 128 // Returns |kFullScreenModeHideAll| when the overlay is hidden and 129 // |kFullScreenModeHideDock| when the overlay is shown. 130 - (base::mac::FullScreenMode)desiredSystemFullscreenMode; 131 132 // Change the overlay to the given fraction, with or without animation. Only 133 // guaranteed to work properly with |fraction == 0| or |fraction == 1|. This 134 // performs the show/hide (animation) immediately. It does not touch the timers. 135 - (void)changeOverlayToFraction:(CGFloat)fraction 136 withAnimation:(BOOL)animate; 137 138 // Schedule the floating bar to be shown/hidden because of mouse position. 139 - (void)scheduleShowForMouse; 140 - (void)scheduleHideForMouse; 141 142 // Set up the tracking area used to activate the sliding bar or keep it active 143 // using with the rectangle in |trackingAreaBounds_|, or remove the tracking 144 // area if one was previously set up. 145 - (void)setupTrackingArea; 146 - (void)removeTrackingAreaIfNecessary; 147 148 // Returns YES if the mouse is currently in any current tracking rectangle, NO 149 // otherwise. 150 - (BOOL)mouseInsideTrackingRect; 151 152 // The tracking area can "falsely" report exits when the menu slides down over 153 // it. In that case, we have to monitor for a "real" mouse exit on a timer. 154 // |-setupMouseExitCheck| schedules a check; |-cancelMouseExitCheck| cancels any 155 // scheduled check. 156 - (void)setupMouseExitCheck; 157 - (void)cancelMouseExitCheck; 158 159 // Called (after a delay) by |-setupMouseExitCheck|, to check whether the mouse 160 // has exited or not; if it hasn't, it will schedule another check. 161 - (void)checkForMouseExit; 162 163 // Start timers for showing/hiding the floating bar. 164 - (void)startShowTimer; 165 - (void)startHideTimer; 166 - (void)cancelShowTimer; 167 - (void)cancelHideTimer; 168 - (void)cancelAllTimers; 169 170 // Methods called when the show/hide timers fire. Do not call directly. 171 - (void)showTimerFire:(NSTimer*)timer; 172 - (void)hideTimerFire:(NSTimer*)timer; 173 174 // Stops any running animations, removes tracking areas, etc. 175 - (void)cleanup; 176 177 // Shows and hides the UI associated with this window being active (having main 178 // status). This includes hiding the menu bar. These functions are called when 179 // the window gains or loses main status as well as in |-cleanup|. 180 - (void)showActiveWindowUI; 181 - (void)hideActiveWindowUI; 182 183 // In Immersive Fullscreen, the menubar is visible iff. toolbarFraction_ >= 184 // 1.0. 185 - (BOOL)shouldShowMenubarInImmersiveFullscreen; 186 187 @end 188 189 190 @implementation PresentationModeController 191 192 @synthesize inPresentationMode = inPresentationMode_; 193 @synthesize slidingStyle = slidingStyle_; 194 @synthesize toolbarFraction = toolbarFraction_; 195 196 - (id)initWithBrowserController:(BrowserWindowController*)controller 197 style:(fullscreen_mac::SlidingStyle)style { 198 if ((self = [super init])) { 199 browserController_ = controller; 200 systemFullscreenMode_ = base::mac::kFullScreenModeNormal; 201 slidingStyle_ = style; 202 } 203 204 // Let the world know what we're up to. 205 [[NSNotificationCenter defaultCenter] 206 postNotificationName:kWillEnterFullscreenNotification 207 object:nil]; 208 209 // Install the Carbon event handler for the undocumented menu bar show/hide 210 // event. 211 EventTypeSpec eventSpec = {kEventClassMenu, 2004}; 212 InstallApplicationEventHandler(NewEventHandlerUPP(&MenuBarRevealHandler), 213 1, 214 &eventSpec, 215 self, 216 &menuBarTrackingHandler_); 217 return self; 218 } 219 220 - (void)dealloc { 221 RemoveEventHandler(menuBarTrackingHandler_); 222 DCHECK(!inPresentationMode_); 223 DCHECK(!trackingArea_); 224 [super dealloc]; 225 } 226 227 - (void)enterPresentationModeForContentView:(NSView*)contentView 228 showDropdown:(BOOL)showDropdown { 229 DCHECK(!inPresentationMode_); 230 enteringPresentationMode_ = YES; 231 inPresentationMode_ = YES; 232 contentView_ = contentView; 233 [self changeToolbarFraction:(showDropdown ? 1 : 0)]; 234 [self updateMenuBarAndDockVisibility]; 235 236 // Register for notifications. Self is removed as an observer in |-cleanup|. 237 NSNotificationCenter* nc = [NSNotificationCenter defaultCenter]; 238 NSWindow* window = [browserController_ window]; 239 240 // Disable these notifications on Lion as they cause crashes. 241 // TODO(rohitrao): Figure out what happens if a fullscreen window changes 242 // monitors on Lion. 243 if (base::mac::IsOSSnowLeopard()) { 244 [nc addObserver:self 245 selector:@selector(windowDidChangeScreen:) 246 name:NSWindowDidChangeScreenNotification 247 object:window]; 248 249 [nc addObserver:self 250 selector:@selector(windowDidMove:) 251 name:NSWindowDidMoveNotification 252 object:window]; 253 } 254 255 [nc addObserver:self 256 selector:@selector(windowDidBecomeMain:) 257 name:NSWindowDidBecomeMainNotification 258 object:window]; 259 260 [nc addObserver:self 261 selector:@selector(windowDidResignMain:) 262 name:NSWindowDidResignMainNotification 263 object:window]; 264 265 enteringPresentationMode_ = NO; 266 } 267 268 - (void)exitPresentationMode { 269 [[NSNotificationCenter defaultCenter] 270 postNotificationName:kWillLeaveFullscreenNotification 271 object:nil]; 272 DCHECK(inPresentationMode_); 273 inPresentationMode_ = NO; 274 275 [self cleanup]; 276 } 277 278 - (void)windowDidChangeScreen:(NSNotification*)notification { 279 [browserController_ resizeFullscreenWindow]; 280 } 281 282 - (void)windowDidMove:(NSNotification*)notification { 283 [browserController_ resizeFullscreenWindow]; 284 } 285 286 - (void)windowDidBecomeMain:(NSNotification*)notification { 287 [self showActiveWindowUI]; 288 } 289 290 - (void)windowDidResignMain:(NSNotification*)notification { 291 [self hideActiveWindowUI]; 292 } 293 294 // On OSX 10.8+, the menu bar shows on the secondary screen in fullscreen. 295 // On OSX 10.7, fullscreen never fills the secondary screen. 296 // On OSX 10.6, the menu bar never shows on the secondary screen in fullscreen. 297 // See http://crbug.com/388906 for full details. 298 - (CGFloat)floatingBarVerticalOffset { 299 if (base::mac::IsOSMountainLionOrLater()) 300 return kFloatingBarVerticalOffset; 301 return [self isWindowOnPrimaryScreen] ? kFloatingBarVerticalOffset : 0; 302 } 303 304 - (void)overlayFrameChanged:(NSRect)frame { 305 if (!inPresentationMode_) 306 return; 307 308 // Make sure |trackingAreaBounds_| always reflects either the tracking area or 309 // the desired tracking area. 310 trackingAreaBounds_ = frame; 311 // The tracking area should always be at least the height of activation zone. 312 NSRect contentBounds = [contentView_ bounds]; 313 trackingAreaBounds_.origin.y = 314 std::min(trackingAreaBounds_.origin.y, 315 NSMaxY(contentBounds) - kDropdownActivationZoneHeight); 316 trackingAreaBounds_.size.height = 317 NSMaxY(contentBounds) - trackingAreaBounds_.origin.y + 1; 318 319 // If an animation is currently running, do not set up a tracking area now. 320 // Instead, leave it to be created it in |-animationDidEnd:|. 321 if (currentAnimation_) 322 return; 323 324 // If this is part of the initial setup, lock bar visibility if the mouse is 325 // within the tracking area bounds. 326 if (enteringPresentationMode_ && [self mouseInsideTrackingRect]) 327 [browserController_ lockBarVisibilityForOwner:self 328 withAnimation:NO 329 delay:NO]; 330 [self setupTrackingArea]; 331 } 332 333 - (void)ensureOverlayShownWithAnimation:(BOOL)animate delay:(BOOL)delay { 334 if (!inPresentationMode_) 335 return; 336 337 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode)) 338 return; 339 340 if (self.slidingStyle == fullscreen_mac::OMNIBOX_TABS_PRESENT) 341 return; 342 343 if (animate) { 344 if (delay) { 345 [self startShowTimer]; 346 } else { 347 [self cancelAllTimers]; 348 [self changeOverlayToFraction:1 withAnimation:YES]; 349 } 350 } else { 351 DCHECK(!delay); 352 [self cancelAllTimers]; 353 [self changeOverlayToFraction:1 withAnimation:NO]; 354 } 355 } 356 357 - (void)ensureOverlayHiddenWithAnimation:(BOOL)animate delay:(BOOL)delay { 358 if (!inPresentationMode_) 359 return; 360 361 if (self.slidingStyle == fullscreen_mac::OMNIBOX_TABS_PRESENT) 362 return; 363 364 if (animate) { 365 if (delay) { 366 [self startHideTimer]; 367 } else { 368 [self cancelAllTimers]; 369 [self changeOverlayToFraction:0 withAnimation:YES]; 370 } 371 } else { 372 DCHECK(!delay); 373 [self cancelAllTimers]; 374 [self changeOverlayToFraction:0 withAnimation:NO]; 375 } 376 } 377 378 - (void)cancelAnimationAndTimers { 379 [self cancelAllTimers]; 380 [currentAnimation_ stopAnimation]; 381 currentAnimation_.reset(); 382 } 383 384 - (void)setSystemFullscreenModeTo:(base::mac::FullScreenMode)mode { 385 if (mode == systemFullscreenMode_) 386 return; 387 if (systemFullscreenMode_ == base::mac::kFullScreenModeNormal) 388 base::mac::RequestFullScreen(mode); 389 else if (mode == base::mac::kFullScreenModeNormal) 390 base::mac::ReleaseFullScreen(systemFullscreenMode_); 391 else 392 base::mac::SwitchFullScreenModes(systemFullscreenMode_, mode); 393 systemFullscreenMode_ = mode; 394 } 395 396 - (void)changeToolbarFraction:(CGFloat)fraction { 397 toolbarFraction_ = fraction; 398 [browserController_ layoutSubviews]; 399 400 // In AppKit fullscreen, moving the mouse to the top of the screen toggles 401 // menu visibility. Replicate the same effect for immersive fullscreen. 402 if ([browserController_ isInImmersiveFullscreen]) 403 [self updateMenuBarAndDockVisibility]; 404 } 405 406 // This method works, but is fragile. 407 // 408 // It gets used during view layout, which sometimes needs to be done at the 409 // beginning of an animation. As such, this method needs to reflect the 410 // menubarOffset expected at the end of the animation. This information is not 411 // readily available. (The layout logic needs a refactor). 412 // 413 // For AppKit Fullscreen, the menubar always starts hidden, and 414 // menubarFraction_ always starts at 0, so the logic happens to work. For 415 // Immersive Fullscreen, this class controls the visibility of the menu bar, so 416 // the logic is correct and not fragile. 417 - (CGFloat)menubarOffset { 418 if ([browserController_ isInAppKitFullscreen]) 419 return -std::floor(menubarFraction_ * [self floatingBarVerticalOffset]); 420 421 return [self shouldShowMenubarInImmersiveFullscreen] 422 ? -[self floatingBarVerticalOffset] 423 : 0; 424 } 425 426 // Used to activate the floating bar in presentation mode. 427 - (void)mouseEntered:(NSEvent*)event { 428 DCHECK(inPresentationMode_); 429 430 // Having gotten a mouse entered, we no longer need to do exit checks. 431 [self cancelMouseExitCheck]; 432 433 NSTrackingArea* trackingArea = [event trackingArea]; 434 if (trackingArea == trackingArea_) { 435 // The tracking area shouldn't be active during animation. 436 DCHECK(!currentAnimation_); 437 [self scheduleShowForMouse]; 438 } 439 } 440 441 // Used to deactivate the floating bar in presentation mode. 442 - (void)mouseExited:(NSEvent*)event { 443 DCHECK(inPresentationMode_); 444 445 NSTrackingArea* trackingArea = [event trackingArea]; 446 if (trackingArea == trackingArea_) { 447 // The tracking area shouldn't be active during animation. 448 DCHECK(!currentAnimation_); 449 450 // We can get a false mouse exit when the menu slides down, so if the mouse 451 // is still actually over the tracking area, we ignore the mouse exit, but 452 // we set up to check the mouse position again after a delay. 453 if ([self mouseInsideTrackingRect]) { 454 [self setupMouseExitCheck]; 455 return; 456 } 457 458 [self scheduleHideForMouse]; 459 } 460 } 461 462 - (void)animationDidStop:(NSAnimation*)animation { 463 // Reset the |currentAnimation_| pointer now that the animation is over. 464 currentAnimation_.reset(); 465 466 // Invariant says that the tracking area is not installed while animations are 467 // in progress. Ensure this is true. 468 DCHECK(!trackingArea_); 469 [self removeTrackingAreaIfNecessary]; // For paranoia. 470 471 // Don't automatically set up a new tracking area. When explicitly stopped, 472 // either another animation is going to start immediately or the state will be 473 // changed immediately. 474 } 475 476 - (void)animationDidEnd:(NSAnimation*)animation { 477 [self animationDidStop:animation]; 478 479 // |trackingAreaBounds_| contains the correct tracking area bounds, including 480 // |any updates that may have come while the animation was running. Install a 481 // new tracking area with these bounds. 482 [self setupTrackingArea]; 483 484 // TODO(viettrungluu): Better would be to check during the animation; doing it 485 // here means that the timing is slightly off. 486 if (![self mouseInsideTrackingRect]) 487 [self scheduleHideForMouse]; 488 } 489 490 - (void)setMenuBarRevealProgress:(CGFloat)progress { 491 menubarFraction_ = progress; 492 493 // If an animation is not running, then -layoutSubviews will not be called 494 // for each tick of the menu bar reveal. Do that manually. 495 // TODO(erikchen): The animation is janky. layoutSubviews need a refactor so 496 // that it calls setFrameOffset: instead of setFrame: if the frame's size has 497 // not changed. 498 if (!currentAnimation_.get()) 499 [browserController_ layoutSubviews]; 500 } 501 502 @end 503 504 505 @implementation PresentationModeController (PrivateMethods) 506 507 - (void)updateMenuBarAndDockVisibility { 508 if (![[browserController_ window] isMainWindow] || 509 ![browserController_ isInImmersiveFullscreen]) { 510 [self setSystemFullscreenModeTo:base::mac::kFullScreenModeNormal]; 511 return; 512 } 513 514 // The screen does not have a menu bar, so there's no need to hide it. 515 if (![self doesScreenHaveMenuBar]) { 516 [self setSystemFullscreenModeTo:base::mac::kFullScreenModeHideDock]; 517 return; 518 } 519 520 [self setSystemFullscreenModeTo:[self desiredSystemFullscreenMode]]; 521 } 522 523 - (BOOL)doesScreenHaveMenuBar { 524 if (![[NSScreen class] 525 respondsToSelector:@selector(screensHaveSeparateSpaces)]) 526 return [self isWindowOnPrimaryScreen]; 527 528 BOOL eachScreenShouldHaveMenuBar = [NSScreen screensHaveSeparateSpaces]; 529 return eachScreenShouldHaveMenuBar ?: [self isWindowOnPrimaryScreen]; 530 } 531 532 - (BOOL)isWindowOnPrimaryScreen { 533 NSScreen* screen = [[browserController_ window] screen]; 534 NSScreen* primaryScreen = [[NSScreen screens] objectAtIndex:0]; 535 return (screen == primaryScreen); 536 } 537 538 - (base::mac::FullScreenMode)desiredSystemFullscreenMode { 539 if ([self shouldShowMenubarInImmersiveFullscreen]) 540 return base::mac::kFullScreenModeHideDock; 541 return base::mac::kFullScreenModeHideAll; 542 } 543 544 - (void)changeOverlayToFraction:(CGFloat)fraction 545 withAnimation:(BOOL)animate { 546 // The non-animated case is really simple, so do it and return. 547 if (!animate) { 548 [currentAnimation_ stopAnimation]; 549 [self changeToolbarFraction:fraction]; 550 return; 551 } 552 553 // If we're already animating to the given fraction, then there's nothing more 554 // to do. 555 if (currentAnimation_ && [currentAnimation_ endFraction] == fraction) 556 return; 557 558 // In all other cases, we want to cancel any running animation (which may be 559 // to show or to hide). 560 [currentAnimation_ stopAnimation]; 561 562 // Create the animation and set it up. 563 currentAnimation_.reset( 564 [[DropdownAnimation alloc] initWithFraction:fraction 565 fullDuration:kDropdownAnimationDuration 566 animationCurve:NSAnimationEaseOut 567 controller:self]); 568 DCHECK(currentAnimation_); 569 [currentAnimation_ setAnimationBlockingMode:NSAnimationNonblocking]; 570 [currentAnimation_ setDelegate:self]; 571 572 // If there is an existing tracking area, remove it. We do not track mouse 573 // movements during animations (see class comment in the header file). 574 [self removeTrackingAreaIfNecessary]; 575 576 [currentAnimation_ startAnimation]; 577 } 578 579 - (void)scheduleShowForMouse { 580 [browserController_ lockBarVisibilityForOwner:self 581 withAnimation:YES 582 delay:YES]; 583 } 584 585 - (void)scheduleHideForMouse { 586 [browserController_ releaseBarVisibilityForOwner:self 587 withAnimation:YES 588 delay:YES]; 589 } 590 591 - (void)setupTrackingArea { 592 if (trackingArea_) { 593 // If the tracking rectangle is already |trackingAreaBounds_|, quit early. 594 NSRect oldRect = [trackingArea_ rect]; 595 if (NSEqualRects(trackingAreaBounds_, oldRect)) 596 return; 597 598 // Otherwise, remove it. 599 [self removeTrackingAreaIfNecessary]; 600 } 601 602 // Create and add a new tracking area for |frame|. 603 trackingArea_.reset( 604 [[NSTrackingArea alloc] initWithRect:trackingAreaBounds_ 605 options:NSTrackingMouseEnteredAndExited | 606 NSTrackingActiveInKeyWindow 607 owner:self 608 userInfo:nil]); 609 DCHECK(contentView_); 610 [contentView_ addTrackingArea:trackingArea_]; 611 } 612 613 - (void)removeTrackingAreaIfNecessary { 614 if (trackingArea_) { 615 DCHECK(contentView_); // |contentView_| better be valid. 616 [contentView_ removeTrackingArea:trackingArea_]; 617 trackingArea_.reset(); 618 } 619 } 620 621 - (BOOL)mouseInsideTrackingRect { 622 NSWindow* window = [browserController_ window]; 623 NSPoint mouseLoc = [window mouseLocationOutsideOfEventStream]; 624 NSPoint mousePos = [contentView_ convertPoint:mouseLoc fromView:nil]; 625 return NSMouseInRect(mousePos, trackingAreaBounds_, [contentView_ isFlipped]); 626 } 627 628 - (void)setupMouseExitCheck { 629 [self performSelector:@selector(checkForMouseExit) 630 withObject:nil 631 afterDelay:kMouseExitCheckDelay]; 632 } 633 634 - (void)cancelMouseExitCheck { 635 [NSObject cancelPreviousPerformRequestsWithTarget:self 636 selector:@selector(checkForMouseExit) object:nil]; 637 } 638 639 - (void)checkForMouseExit { 640 if ([self mouseInsideTrackingRect]) 641 [self setupMouseExitCheck]; 642 else 643 [self scheduleHideForMouse]; 644 } 645 646 - (void)startShowTimer { 647 // If there's already a show timer going, just keep it. 648 if (showTimer_) { 649 DCHECK([showTimer_ isValid]); 650 DCHECK(!hideTimer_); 651 return; 652 } 653 654 // Cancel the hide timer (if necessary) and set up the new show timer. 655 [self cancelHideTimer]; 656 showTimer_.reset( 657 [[NSTimer scheduledTimerWithTimeInterval:kDropdownShowDelay 658 target:self 659 selector:@selector(showTimerFire:) 660 userInfo:nil 661 repeats:NO] retain]); 662 DCHECK([showTimer_ isValid]); // This also checks that |showTimer_ != nil|. 663 } 664 665 - (void)startHideTimer { 666 // If there's already a hide timer going, just keep it. 667 if (hideTimer_) { 668 DCHECK([hideTimer_ isValid]); 669 DCHECK(!showTimer_); 670 return; 671 } 672 673 // Cancel the show timer (if necessary) and set up the new hide timer. 674 [self cancelShowTimer]; 675 hideTimer_.reset( 676 [[NSTimer scheduledTimerWithTimeInterval:kDropdownHideDelay 677 target:self 678 selector:@selector(hideTimerFire:) 679 userInfo:nil 680 repeats:NO] retain]); 681 DCHECK([hideTimer_ isValid]); // This also checks that |hideTimer_ != nil|. 682 } 683 684 - (void)cancelShowTimer { 685 [showTimer_ invalidate]; 686 showTimer_.reset(); 687 } 688 689 - (void)cancelHideTimer { 690 [hideTimer_ invalidate]; 691 hideTimer_.reset(); 692 } 693 694 - (void)cancelAllTimers { 695 [self cancelShowTimer]; 696 [self cancelHideTimer]; 697 } 698 699 - (void)showTimerFire:(NSTimer*)timer { 700 DCHECK_EQ(showTimer_, timer); // This better be our show timer. 701 [showTimer_ invalidate]; // Make sure it doesn't repeat. 702 showTimer_.reset(); // And get rid of it. 703 [self changeOverlayToFraction:1 withAnimation:YES]; 704 } 705 706 - (void)hideTimerFire:(NSTimer*)timer { 707 DCHECK_EQ(hideTimer_, timer); // This better be our hide timer. 708 [hideTimer_ invalidate]; // Make sure it doesn't repeat. 709 hideTimer_.reset(); // And get rid of it. 710 [self changeOverlayToFraction:0 withAnimation:YES]; 711 } 712 713 - (void)cleanup { 714 [self cancelMouseExitCheck]; 715 [self cancelAnimationAndTimers]; 716 [[NSNotificationCenter defaultCenter] removeObserver:self]; 717 718 [self removeTrackingAreaIfNecessary]; 719 contentView_ = nil; 720 721 // This isn't tracked when not in presentation mode. 722 [browserController_ releaseBarVisibilityForOwner:self 723 withAnimation:NO 724 delay:NO]; 725 726 // Call the main status resignation code to perform the associated cleanup, 727 // since we will no longer be receiving actual status resignation 728 // notifications. 729 [self setSystemFullscreenModeTo:base::mac::kFullScreenModeNormal]; 730 731 // No more calls back up to the BWC. 732 browserController_ = nil; 733 } 734 735 - (void)showActiveWindowUI { 736 [self updateMenuBarAndDockVisibility]; 737 738 // TODO(rohitrao): Insert the Exit Fullscreen button. http://crbug.com/35956 739 } 740 741 - (void)hideActiveWindowUI { 742 [self updateMenuBarAndDockVisibility]; 743 744 // TODO(rohitrao): Remove the Exit Fullscreen button. http://crbug.com/35956 745 } 746 747 - (BOOL)shouldShowMenubarInImmersiveFullscreen { 748 return toolbarFraction_ >= 1.0; 749 } 750 751 @end 752