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