chromium/chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_mouse_tracker.mm

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_mouse_tracker.h"

#include <memory>

#import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_controller.h"
#include "chrome/browser/ui/cocoa/scoped_menu_bar_lock.h"
#import "ui/base/cocoa/tracking_area.h"

namespace {

// Additional height threshold added at the toolbar's bottom. This is to mimic
// threshold the mouse position needs to be at before the menubar automatically
// hides.
const CGFloat kTrackingAreaAdditionalThreshold = 50;

}  // namespace

@implementation FullscreenToolbarMouseTracker {
  // The frame for the tracking area. The value is the toolbar's frame with
  // additional height added at the bottom.
  NSRect _trackingAreaFrame;

  // The tracking area associated with the toolbar. This tracking area is used
  // to keep the toolbar active if the menubar had animated out but the mouse
  // is still on the toolbar.
  CrTrackingArea* __strong _trackingArea;

  // Keeps the menu bar from hiding until the mouse exits the tracking area.
  std::unique_ptr<ScopedMenuBarLock> _menuBarLock;

  // The content view for the window.
  // TODO(lgrey): Instead of retaining this, we should refrain from trying to
  // remove the tracking area when the content view has already dealloced.
  // Unfortunately, until we have a repro for https://crbug.com/1064911, we
  // can't verify more targeted fixes (for example, only removing the tracking
  // area if |_controller| has a window).
  NSView* __strong _contentView;

  FullscreenToolbarController* _controller;  // weak
}

- (instancetype)initWithFullscreenToolbarController:
    (FullscreenToolbarController*)controller {
  if ((self = [super init])) {
    _controller = controller;
  }

  return self;
}

- (void)dealloc {
  [self removeTrackingArea];
}

- (void)updateTrackingArea {
  // Remove the tracking area if the toolbar and menu bar aren't both visible.
  if ([_controller toolbarFraction] == 0 || ![NSMenu menuBarVisible]) {
    [self removeTrackingArea];
    _menuBarLock.reset();
    return;
  }

  if (_trackingArea) {
    // If |trackingArea_|'s rect matches |trackingAreaFrame_|, quit early.
    if (NSEqualRects(_trackingAreaFrame, [_trackingArea rect]))
      return;

    [self removeTrackingArea];
  }

  _contentView = [[_controller window] contentView];

  _trackingArea = [[CrTrackingArea alloc]
      initWithRect:_trackingAreaFrame
           options:NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow
             owner:self
          userInfo:nil];

  [_contentView addTrackingArea:_trackingArea];
}

- (void)updateToolbarFrame:(NSRect)frame {
  NSRect contentBounds = [[[_controller window] contentView] bounds];
  _trackingAreaFrame = frame;
  _trackingAreaFrame.origin.y -= kTrackingAreaAdditionalThreshold;
  _trackingAreaFrame.size.height =
      NSMaxY(contentBounds) - _trackingAreaFrame.origin.y;

  [self updateTrackingArea];
}

- (void)removeTrackingArea {
  if (!_trackingArea)
    return;

  // TODO(https://crbug.com/1063417, https://crbug.com/1064911): This DCHECK
  // is hit when closing a fullscreen window using the traffic lights. This is
  // because removeTrackingArea should be removed in response to the window
  // closing, but isn't.
  // DCHECK(_contentView);
  [_contentView removeTrackingArea:_trackingArea];
  _trackingArea = nil;
  _contentView = nil;
}

- (void)mouseEntered:(NSEvent*)event {
  _menuBarLock = std::make_unique<ScopedMenuBarLock>();
}

- (void)mouseExited:(NSEvent*)event {
  _menuBarLock = nil;
}

@end