Home | History | Annotate | Download | only in cocoa
      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