Browse Source

fix: clean up implementations of titleBarStyle (#27489)

* Rewrite titleBarStyle impls with WindowButtonsView

* Remove fullscreenWindowTitle option

* Make buttons show correctly under RTL

* Fix docs about traffic lights position

* Fix test on fullscreen resizable

* Fix button states with closabe/minimizable/fullscreenable

* Fix typo

* Deprecate the fullscreenWindowTitle option
Cheng Zhao 4 years ago
parent
commit
8bf66f8974

+ 10 - 13
docs/api/browser-window.md

@@ -222,16 +222,14 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
       the top left.
     * `hiddenInset` - Results in a hidden title bar with an alternative look
       where the traffic light buttons are slightly more inset from the window edge.
-    * `customButtonsOnHover` Boolean (optional) - Draw custom close,
-      and minimize buttons on macOS frameless windows. These buttons will not display
-      unless hovered over in the top left of the window. These custom buttons prevent
-      issues with mouse events that occur with the standard window toolbar buttons.
-      **Note:** This option is currently experimental.
+    * `customButtonsOnHover` - Results in a hidden title bar and a full size
+      content window, the traffic light buttons will display when being hovered
+      over in the top left of the window.  **Note:** This option is currently
+      experimental.
   * `trafficLightPosition` [Point](structures/point.md) (optional) - Set a
-    custom position for the traffic light buttons. Can only be used with
-    `titleBarStyle` set to `hidden` or `customButtonsOnHover`.
-  * `fullscreenWindowTitle` Boolean (optional) - Shows the title in the
-    title bar in full screen mode on macOS for all `titleBarStyle` options.
+    custom position for the traffic light buttons in frameless windows.
+  * `fullscreenWindowTitle` Boolean (optional) _Deprecated_ - Shows the title in
+    the title bar in full screen mode on macOS for `hiddenInset` titleBarStyle.
     Default is `false`.
   * `thickFrame` Boolean (optional) - Use `WS_THICKFRAME` style for frameless windows on
     Windows, which adds standard window frame. Setting it to `false` will remove
@@ -1740,13 +1738,12 @@ deprecated and will be removed in an upcoming version of macOS.
 
 * `position` [Point](structures/point.md)
 
-Set a custom position for the traffic light buttons. Can only be used with
-`titleBarStyle` set to `hidden` or `customButtonsOnHover`.
+Set a custom position for the traffic light buttons in frameless window.
 
 #### `win.getTrafficLightPosition()` _macOS_
 
-Returns `Point` - The current position for the traffic light buttons. Can only
-be used with `titleBarStyle` set to `hidden` or `customButtonsOnHover`.
+Returns `Point` - The custom position for the traffic light buttons in
+frameless window.
 
 #### `win.setTouchBar(touchBar)` _macOS_
 

+ 2 - 0
filenames.gni

@@ -180,6 +180,8 @@ filenames = {
     "shell/browser/ui/cocoa/root_view_mac.mm",
     "shell/browser/ui/cocoa/views_delegate_mac.h",
     "shell/browser/ui/cocoa/views_delegate_mac.mm",
+    "shell/browser/ui/cocoa/window_buttons_view.h",
+    "shell/browser/ui/cocoa/window_buttons_view.mm",
     "shell/browser/ui/drag_util_mac.mm",
     "shell/browser/ui/file_dialog_mac.mm",
     "shell/browser/ui/inspectable_web_contents_view_mac.h",

+ 2 - 2
shell/browser/native_window.h

@@ -280,8 +280,8 @@ class NativeWindow : public base::SupportsUserData,
   void NotifyWindowRotateGesture(float rotation);
   void NotifyWindowSheetBegin();
   void NotifyWindowSheetEnd();
-  void NotifyWindowEnterFullScreen();
-  void NotifyWindowLeaveFullScreen();
+  virtual void NotifyWindowEnterFullScreen();
+  virtual void NotifyWindowLeaveFullScreen();
   void NotifyWindowEnterHtmlFullScreen();
   void NotifyWindowLeaveHtmlFullScreen();
   void NotifyWindowAlwaysOnTopChanged();

+ 9 - 7
shell/browser/native_window_mac.h

@@ -21,7 +21,7 @@
 @class ElectronNSWindowDelegate;
 @class ElectronPreviewItem;
 @class ElectronTouchBar;
-@class CustomWindowButtonView;
+@class WindowButtonsView;
 
 namespace electron {
 
@@ -139,6 +139,11 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver {
   void CloseFilePreview() override;
   gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const override;
   gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override;
+  void NotifyWindowEnterFullScreen() override;
+  void NotifyWindowLeaveFullScreen() override;
+
+  void NotifyWindowWillEnterFullScreen();
+  void NotifyWindowWillLeaveFullScreen();
 
   // Cleanup observers when window is getting closed. Note that the destructor
   // can be called much later after window gets closed, so we should not do
@@ -153,8 +158,6 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver {
   void SetCollectionBehavior(bool on, NSUInteger flag);
   void SetWindowLevel(int level);
 
-  void SetExitingFullScreen(bool flag);
-
   enum class VisualEffectState {
     kFollowWindow,
     kActive,
@@ -172,7 +175,6 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver {
   ElectronPreviewItem* preview_item() const { return preview_item_.get(); }
   ElectronTouchBar* touch_bar() const { return touch_bar_.get(); }
   bool zoom_to_page_width() const { return zoom_to_page_width_; }
-  bool fullscreen_window_title() const { return fullscreen_window_title_; }
   bool always_simple_fullscreen() const { return always_simple_fullscreen_; }
   bool exiting_fullscreen() const { return exiting_fullscreen_; }
 
@@ -186,9 +188,10 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver {
 
  private:
   // Add custom layers to the content view.
-  void AddContentViewLayers(bool minimizable, bool closable);
+  void AddContentViewLayers();
 
   void InternalSetWindowButtonVisibility(bool visible);
+  void InternalSetStandardButtonsVisibility(bool visible);
   void InternalSetParentWindow(NativeWindow* parent, bool attach);
   void SetForwardMouseMessages(bool forward);
 
@@ -197,7 +200,7 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver {
   base::scoped_nsobject<ElectronNSWindowDelegate> window_delegate_;
   base::scoped_nsobject<ElectronPreviewItem> preview_item_;
   base::scoped_nsobject<ElectronTouchBar> touch_bar_;
-  base::scoped_nsobject<CustomWindowButtonView> buttons_view_;
+  base::scoped_nsobject<WindowButtonsView> buttons_view_;
 
   // Event monitor for scroll wheel event.
   id wheel_event_monitor_;
@@ -213,7 +216,6 @@ class NativeWindowMac : public NativeWindow, public ui::NativeThemeObserver {
   bool is_kiosk_ = false;
   bool was_fullscreen_ = false;
   bool zoom_to_page_width_ = false;
-  bool fullscreen_window_title_ = false;
   bool resizable_ = true;
   bool exiting_fullscreen_ = false;
   base::Optional<gfx::Point> traffic_light_position_;

+ 83 - 225
shell/browser/native_window_mac.mm

@@ -29,6 +29,7 @@
 #include "shell/browser/ui/cocoa/electron_preview_item.h"
 #include "shell/browser/ui/cocoa/electron_touch_bar.h"
 #include "shell/browser/ui/cocoa/root_view_mac.h"
+#include "shell/browser/ui/cocoa/window_buttons_view.h"
 #include "shell/browser/ui/inspectable_web_contents.h"
 #include "shell/browser/ui/inspectable_web_contents_view.h"
 #include "shell/browser/window_list.h"
@@ -119,106 +120,6 @@
 
 @end
 
-// Custom Quit, Minimize and Full Screen button container for frameless
-// windows.
-@interface CustomWindowButtonView : NSView {
- @private
-  BOOL mouse_inside_;
-  gfx::Point margin_;
-}
-
-- (id)initWithMargin:(const base::Optional<gfx::Point>&)margin;
-- (void)setMargin:(const base::Optional<gfx::Point>&)margin;
-@end
-
-@implementation CustomWindowButtonView
-
-- (id)initWithMargin:(const base::Optional<gfx::Point>&)margin {
-  self = [super initWithFrame:NSZeroRect];
-  [self setMargin:margin];
-
-  NSButton* close_button =
-      [NSWindow standardWindowButton:NSWindowCloseButton
-                        forStyleMask:NSWindowStyleMaskTitled];
-  [close_button setTag:1];
-  NSButton* miniaturize_button =
-      [NSWindow standardWindowButton:NSWindowMiniaturizeButton
-                        forStyleMask:NSWindowStyleMaskTitled];
-  [miniaturize_button setTag:2];
-
-  CGFloat x = 0;
-  const CGFloat space_between = 20;
-
-  [close_button setFrameOrigin:NSMakePoint(x, 0)];
-  x += space_between;
-  [self addSubview:close_button];
-
-  [miniaturize_button setFrameOrigin:NSMakePoint(x, 0)];
-  x += space_between;
-  [self addSubview:miniaturize_button];
-
-  const auto last_button_frame = miniaturize_button.frame;
-  [self setFrameSize:NSMakeSize(last_button_frame.origin.x +
-                                    last_button_frame.size.width,
-                                last_button_frame.size.height)];
-
-  mouse_inside_ = NO;
-  [self setNeedsDisplayForButtons];
-
-  return self;
-}
-
-- (void)setMargin:(const base::Optional<gfx::Point>&)margin {
-  margin_ = margin.value_or(gfx::Point(7, 3));
-}
-
-- (void)viewDidMoveToWindow {
-  if (!self.window) {
-    return;
-  }
-
-  // Stay in upper left corner.
-  [self setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
-  [self setFrameOrigin:NSMakePoint(margin_.x(), self.window.frame.size.height -
-                                                    self.frame.size.height -
-                                                    margin_.y())];
-}
-
-- (BOOL)_mouseInGroup:(NSButton*)button {
-  return mouse_inside_;
-}
-
-- (void)updateTrackingAreas {
-  auto tracking_area = [[[NSTrackingArea alloc]
-      initWithRect:NSZeroRect
-           options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
-                   NSTrackingInVisibleRect
-             owner:self
-          userInfo:nil] autorelease];
-  [self addTrackingArea:tracking_area];
-}
-
-- (void)mouseEntered:(NSEvent*)event {
-  [super mouseEntered:event];
-  mouse_inside_ = YES;
-  [self setNeedsDisplayForButtons];
-}
-
-- (void)mouseExited:(NSEvent*)event {
-  [super mouseExited:event];
-  mouse_inside_ = NO;
-  [self setNeedsDisplayForButtons];
-}
-
-- (void)setNeedsDisplayForButtons {
-  for (NSView* subview in self.subviews) {
-    [subview setHidden:!mouse_inside_];
-    [subview setNeedsDisplay:YES];
-  }
-}
-
-@end
-
 @interface ElectronProgressBar : NSProgressIndicator
 @end
 
@@ -370,11 +271,17 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
   options.Get(options::kResizable, &resizable_);
   options.Get(options::kTitleBarStyle, &title_bar_style_);
   options.Get(options::kZoomToPageWidth, &zoom_to_page_width_);
-  options.Get(options::kFullscreenWindowTitle, &fullscreen_window_title_);
   options.Get(options::kSimpleFullScreen, &always_simple_fullscreen_);
   options.GetOptional(options::kTrafficLightPosition, &traffic_light_position_);
   options.Get(options::kVisualEffectState, &visual_effect_state_);
 
+  if (options.Has(options::kFullscreenWindowTitle)) {
+    EmitWarning(node::Environment::GetCurrent(v8::Isolate::GetCurrent()),
+                "\"fullscreenWindowTitle\" option has been deprecated and is "
+                "no-op now.",
+                "electron");
+  }
+
   bool minimizable = true;
   options.Get(options::kMinimizable, &minimizable);
 
@@ -457,6 +364,8 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
     [window_ setTitleVisibility:NSWindowTitleHidden];
     // Remove non-transparent corners, see http://git.io/vfonD.
     [window_ setOpaque:NO];
+    // Hide the window buttons.
+    InternalSetStandardButtonsVisibility(false);
   }
 
   // Create a tab only if tabbing identifier is specified and window has
@@ -471,14 +380,6 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
     }
   }
 
-  // Hide the title bar.
-  if (title_bar_style_ == TitleBarStyle::kHiddenInset) {
-    base::scoped_nsobject<NSToolbar> toolbar(
-        [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
-    [toolbar setShowsBaselineSeparator:NO];
-    [window_ setToolbar:toolbar];
-  }
-
   // Resize to content bounds.
   bool use_content_size = false;
   options.Get(options::kUseContentSize, &use_content_size);
@@ -527,7 +428,7 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
 
   // Default content view.
   SetContentView(new views::View());
-  AddContentViewLayers(minimizable, closable);
+  AddContentViewLayers();
 
   original_frame_ = [window_ frame];
   original_level_ = [window_ level];
@@ -830,6 +731,8 @@ bool NativeWindowMac::IsMovable() {
 
 void NativeWindowMac::SetMinimizable(bool minimizable) {
   SetStyleMask(minimizable, NSMiniaturizableWindowMask);
+  if (buttons_view_)
+    [[buttons_view_ viewWithTag:1] setEnabled:minimizable];
 }
 
 bool NativeWindowMac::IsMinimizable() {
@@ -851,6 +754,8 @@ void NativeWindowMac::SetFullScreenable(bool fullscreenable) {
   // On EL Capitan this flag is required to hide fullscreen button.
   SetCollectionBehavior(!fullscreenable,
                         NSWindowCollectionBehaviorFullScreenAuxiliary);
+  if (buttons_view_)
+    [[buttons_view_ viewWithTag:2] setEnabled:fullscreenable];
 }
 
 bool NativeWindowMac::IsFullScreenable() {
@@ -860,6 +765,8 @@ bool NativeWindowMac::IsFullScreenable() {
 
 void NativeWindowMac::SetClosable(bool closable) {
   SetStyleMask(closable, NSWindowStyleMaskClosable);
+  if (buttons_view_)
+    [[buttons_view_ viewWithTag:0] setEnabled:closable];
 }
 
 bool NativeWindowMac::IsClosable() {
@@ -940,9 +847,6 @@ void NativeWindowMac::Invalidate() {
 
 void NativeWindowMac::SetTitle(const std::string& title) {
   [window_ setTitle:base::SysUTF8ToNSString(title)];
-  if (title_bar_style_ == TitleBarStyle::kHidden) {
-    RedrawTrafficLights();
-  }
 }
 
 std::string NativeWindowMac::GetTitle() {
@@ -1004,14 +908,7 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
       window.level = NSPopUpMenuWindowLevel;
     }
 
-    if (!fullscreen_window_title()) {
-      // Hide the titlebar
-      SetStyleMask(false, NSWindowStyleMaskTitled);
-
-      // Resize the window to accommodate the _entire_ screen size
-      fullscreenFrame.size.height -=
-          [[[NSApplication sharedApplication] mainMenu] menuBarHeight];
-    } else if (!window_button_visibility_.has_value()) {
+    if (!window_button_visibility_.has_value()) {
       // Lets keep previous behaviour - hide window controls in titled
       // fullscreen mode when not specified otherwise.
       InternalSetWindowButtonVisibility(false);
@@ -1027,11 +924,6 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
   } else if (!simple_fullscreen && is_simple_fullscreen_) {
     is_simple_fullscreen_ = false;
 
-    if (!fullscreen_window_title()) {
-      // Restore the titlebar
-      SetStyleMask(true, NSWindowStyleMaskTitled);
-    }
-
     // Restore default window controls visibility state.
     if (!window_button_visibility_.has_value()) {
       bool visibility;
@@ -1450,9 +1342,7 @@ bool NativeWindowMac::GetWindowButtonVisibility() const {
 void NativeWindowMac::SetTrafficLightPosition(
     base::Optional<gfx::Point> position) {
   traffic_light_position_ = std::move(position);
-  if (title_bar_style_ == TitleBarStyle::kHidden) {
-    RedrawTrafficLights();
-  } else if (title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover) {
+  if (buttons_view_) {
     [buttons_view_ setMargin:position];
     [buttons_view_ viewDidMoveToWindow];
   }
@@ -1463,64 +1353,8 @@ base::Optional<gfx::Point> NativeWindowMac::GetTrafficLightPosition() const {
 }
 
 void NativeWindowMac::RedrawTrafficLights() {
-  // Ensure maximizable options retain pre-existing state.
-  SetMaximizable(maximizable_);
-
-  // Changing system titlebar is only allowed for "hidden" titleBarStyle.
-  if (!traffic_light_position_ || title_bar_style_ != TitleBarStyle::kHidden)
-    return;
-
-  if (IsFullscreen())
-    return;
-
-  NSWindow* window = window_;
-  NSButton* close = [window standardWindowButton:NSWindowCloseButton];
-  NSButton* miniaturize =
-      [window standardWindowButton:NSWindowMiniaturizeButton];
-  NSButton* zoom = [window standardWindowButton:NSWindowZoomButton];
-  // Safety check just in case apple changes the view structure in a macOS
-  // update
-  DCHECK(close.superview);
-  DCHECK(close.superview.superview);
-  if (!close.superview || !close.superview.superview)
-    return;
-  NSView* titleBarContainerView = close.superview.superview;
-
-  // Hide the container when exiting fullscreen, otherwise traffic light buttons
-  // jump
-  if (exiting_fullscreen_) {
-    [titleBarContainerView setHidden:YES];
-    return;
-  }
-
-  [titleBarContainerView setHidden:NO];
-  CGFloat buttonHeight = [close frame].size.height;
-  CGFloat titleBarFrameHeight = buttonHeight + traffic_light_position_->y();
-  CGRect titleBarRect = titleBarContainerView.frame;
-  CGFloat titleBarWidth = NSWidth(titleBarRect);
-  titleBarRect.size.height = titleBarFrameHeight;
-  titleBarRect.origin.y = window.frame.size.height - titleBarFrameHeight;
-  [titleBarContainerView setFrame:titleBarRect];
-
-  BOOL isRTL = [titleBarContainerView userInterfaceLayoutDirection] ==
-               NSUserInterfaceLayoutDirectionRightToLeft;
-  NSArray* windowButtons = @[ close, miniaturize, zoom ];
-  const CGFloat space_between =
-      [miniaturize frame].origin.x - [close frame].origin.x;
-  for (NSUInteger i = 0; i < windowButtons.count; i++) {
-    NSView* view = [windowButtons objectAtIndex:i];
-    CGRect rect = [view frame];
-    if (isRTL) {
-      CGFloat buttonWidth = NSWidth(rect);
-      // origin is always top-left, even in RTL
-      rect.origin.x = titleBarWidth - traffic_light_position_->x() +
-                      (i * space_between) - buttonWidth;
-    } else {
-      rect.origin.x = traffic_light_position_->x() + (i * space_between);
-    }
-    rect.origin.y = (titleBarFrameHeight - rect.size.height) / 2;
-    [view setFrameOrigin:rect.origin];
-  }
+  if (buttons_view_)
+    [buttons_view_ setNeedsDisplayForButtons];
 }
 
 void NativeWindowMac::SetTouchBar(
@@ -1642,6 +1476,41 @@ gfx::Rect NativeWindowMac::WindowBoundsToContentBounds(
   }
 }
 
+void NativeWindowMac::NotifyWindowEnterFullScreen() {
+  NativeWindow::NotifyWindowEnterFullScreen();
+  // Restore the window title under fullscreen mode.
+  if (buttons_view_)
+    [window_ setTitleVisibility:NSWindowTitleVisible];
+  RedrawTrafficLights();
+}
+
+void NativeWindowMac::NotifyWindowLeaveFullScreen() {
+  NativeWindow::NotifyWindowLeaveFullScreen();
+  exiting_fullscreen_ = false;
+  // Add back buttonsView after leaving fullscreen mode.
+  if (buttons_view_) {
+    InternalSetStandardButtonsVisibility(false);
+    [[window_ contentView] addSubview:buttons_view_];
+  }
+}
+
+void NativeWindowMac::NotifyWindowWillEnterFullScreen() {
+  // Remove the buttonsView otherwise window buttons won't show under
+  // fullscreen mode.
+  if (buttons_view_) {
+    [buttons_view_ removeFromSuperview];
+    InternalSetStandardButtonsVisibility(true);
+  }
+}
+
+void NativeWindowMac::NotifyWindowWillLeaveFullScreen() {
+  // Hide window title after leaving fullscreen.
+  if (buttons_view_)
+    [window_ setTitleVisibility:NSWindowTitleHidden];
+  exiting_fullscreen_ = true;
+  RedrawTrafficLights();
+}
+
 void NativeWindowMac::Cleanup() {
   DCHECK(!IsClosed());
   ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
@@ -1663,7 +1532,7 @@ void NativeWindowMac::OverrideNSWindowContentView() {
   [container_view_
       setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
   [window_ setContentView:container_view_];
-  AddContentViewLayers(IsMinimizable(), IsClosable());
+  AddContentViewLayers();
 }
 
 void NativeWindowMac::SetStyleMask(bool on, NSUInteger flag) {
@@ -1692,10 +1561,6 @@ void NativeWindowMac::SetCollectionBehavior(bool on, NSUInteger flag) {
   SetMaximizable(was_maximizable);
 }
 
-void NativeWindowMac::SetExitingFullScreen(bool flag) {
-  exiting_fullscreen_ = flag;
-}
-
 bool NativeWindowMac::CanResize() const {
   return resizable_;
 }
@@ -1710,7 +1575,7 @@ void NativeWindowMac::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) {
       base::BindOnce(&NativeWindow::RedrawTrafficLights, GetWeakPtr()));
 }
 
-void NativeWindowMac::AddContentViewLayers(bool minimizable, bool closable) {
+void NativeWindowMac::AddContentViewLayers() {
   // Make sure the bottom corner is rounded for non-modal windows:
   // http://crbug.com/396264.
   if (!is_modal()) {
@@ -1742,46 +1607,39 @@ void NativeWindowMac::AddContentViewLayers(bool minimizable, bool closable) {
       [[window_ contentView] viewDidMoveToWindow];
     }
 
-    // The fullscreen button should always be hidden for frameless window.
-    [[window_ standardWindowButton:NSWindowFullScreenButton] setHidden:YES];
-
-    // Create a custom window buttons view for kCustomButtonsOnHover.
-    if (title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover) {
-      buttons_view_.reset([[CustomWindowButtonView alloc]
-          initWithMargin:traffic_light_position_]);
-
-      if (!minimizable)
-        [[buttons_view_ viewWithTag:2] removeFromSuperview];
-      if (!closable)
-        [[buttons_view_ viewWithTag:1] removeFromSuperview];
+    // Create a custom window buttons view.
+    if (title_bar_style_ != TitleBarStyle::kNormal) {
+      buttons_view_.reset(
+          [[WindowButtonsView alloc] initWithMargin:traffic_light_position_]);
+      if (title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover)
+        [buttons_view_ setShowOnHover:YES];
+      if (title_bar_style_ == TitleBarStyle::kHiddenInset &&
+          !traffic_light_position_)
+        [buttons_view_ setMargin:gfx::Point(12, 11)];
+
+      if (!IsClosable())
+        [[buttons_view_ viewWithTag:0] setEnabled:NO];
+      if (!IsMinimizable())
+        [[buttons_view_ viewWithTag:1] setEnabled:NO];
+      if (!IsFullScreenable())
+        [[buttons_view_ viewWithTag:2] setEnabled:NO];
 
       [[window_ contentView] addSubview:buttons_view_];
     }
-
-    // Hide the window buttons except for kHidden and kHiddenInset.
-    if (title_bar_style_ == TitleBarStyle::kNormal ||
-        title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover) {
-      [[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES];
-      [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
-      [[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES];
-
-      // Some third-party macOS utilities check the zoom button's enabled state
-      // to determine whether to show custom UI on hover, so we disable it here
-      // to prevent them from doing so in a frameless app window.
-      SetMaximizable(false);
-    }
   }
 }
 
 void NativeWindowMac::InternalSetWindowButtonVisibility(bool visible) {
-  if (buttons_view_) {
+  if (buttons_view_)
     [buttons_view_ setHidden:!visible];
-  } else {
-    [[window_ standardWindowButton:NSWindowCloseButton] setHidden:!visible];
-    [[window_ standardWindowButton:NSWindowMiniaturizeButton]
-        setHidden:!visible];
-    [[window_ standardWindowButton:NSWindowZoomButton] setHidden:!visible];
-  }
+  else
+    InternalSetStandardButtonsVisibility(visible);
+}
+
+void NativeWindowMac::InternalSetStandardButtonsVisibility(bool visible) {
+  [[window_ standardWindowButton:NSWindowCloseButton] setHidden:!visible];
+  [[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:!visible];
+  [[window_ standardWindowButton:NSWindowZoomButton] setHidden:!visible];
 }
 
 void NativeWindowMac::InternalSetParentWindow(NativeWindow* parent,

+ 7 - 58
shell/browser/ui/cocoa/electron_ns_window_delegate.mm

@@ -91,14 +91,17 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
 
 - (void)windowDidBecomeMain:(NSNotification*)notification {
   shell_->NotifyWindowFocus();
+  shell_->RedrawTrafficLights();
 }
 
 - (void)windowDidResignMain:(NSNotification*)notification {
   shell_->NotifyWindowBlur();
+  shell_->RedrawTrafficLights();
 }
 
 - (void)windowDidBecomeKey:(NSNotification*)notification {
   shell_->NotifyWindowIsKeyChanged(true);
+  shell_->RedrawTrafficLights();
 }
 
 - (void)windowDidResignKey:(NSNotification*)notification {
@@ -110,6 +113,7 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
     return;
 
   shell_->NotifyWindowIsKeyChanged(false);
+  shell_->RedrawTrafficLights();
 }
 
 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
@@ -151,9 +155,6 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
 - (void)windowDidResize:(NSNotification*)notification {
   [super windowDidResize:notification];
   shell_->NotifyWindowResize();
-  if (shell_->title_bar_style() == TitleBarStyle::kHidden) {
-    shell_->RedrawTrafficLights();
-  }
 }
 
 - (void)windowWillMove:(NSNotification*)notification {
@@ -212,75 +213,23 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
 }
 
 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
-  // Setting resizable to true before entering fullscreen
+  shell_->NotifyWindowWillEnterFullScreen();
+  // Setting resizable to true before entering fullscreen.
   is_resizable_ = shell_->IsResizable();
   shell_->SetResizable(true);
-  // Hide the native toolbar before entering fullscreen, so there is no visual
-  // artifacts.
-  if (shell_->title_bar_style() == TitleBarStyle::kHiddenInset) {
-    NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
-    [window setToolbar:nil];
-  }
 }
 
 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
   shell_->NotifyWindowEnterFullScreen();
-
-  // For frameless window we don't show set title for normal mode since the
-  // titlebar is expected to be empty, but after entering fullscreen mode we
-  // have to set one, because title bar is visible here.
-  NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
-  if ((shell_->transparent() || !shell_->has_frame()) &&
-      // FIXME(zcbenz): Showing titlebar for hiddenInset window is weird under
-      // fullscreen mode.
-      // Show title if fullscreen_window_title flag is set
-      (shell_->title_bar_style() != TitleBarStyle::kHiddenInset ||
-       shell_->fullscreen_window_title())) {
-    [window setTitleVisibility:NSWindowTitleVisible];
-  }
-
-  // Restore the native toolbar immediately after entering fullscreen, if we
-  // do this before leaving fullscreen, traffic light buttons will be jumping.
-  if (shell_->title_bar_style() == TitleBarStyle::kHiddenInset) {
-    base::scoped_nsobject<NSToolbar> toolbar(
-        [[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
-    [toolbar setShowsBaselineSeparator:NO];
-    [window setToolbar:toolbar];
-
-    // Set window style to hide the toolbar, otherwise the toolbar will show
-    // in fullscreen mode.
-    [window setTitlebarAppearsTransparent:NO];
-    shell_->SetStyleMask(true, NSWindowStyleMaskFullSizeContentView);
-  }
 }
 
 - (void)windowWillExitFullScreen:(NSNotification*)notification {
-  // Restore the titlebar visibility.
-  NSWindow* window = shell_->GetNativeWindow().GetNativeNSWindow();
-  if ((shell_->transparent() || !shell_->has_frame()) &&
-      (shell_->title_bar_style() != TitleBarStyle::kHiddenInset ||
-       shell_->fullscreen_window_title())) {
-    [window setTitleVisibility:NSWindowTitleHidden];
-  }
-
-  // Turn off the style for toolbar.
-  if (shell_->title_bar_style() == TitleBarStyle::kHiddenInset) {
-    shell_->SetStyleMask(false, NSWindowStyleMaskFullSizeContentView);
-    [window setTitlebarAppearsTransparent:YES];
-  }
-  shell_->SetExitingFullScreen(true);
-  if (shell_->title_bar_style() == TitleBarStyle::kHidden) {
-    shell_->RedrawTrafficLights();
-  }
+  shell_->NotifyWindowWillLeaveFullScreen();
 }
 
 - (void)windowDidExitFullScreen:(NSNotification*)notification {
   shell_->SetResizable(is_resizable_);
   shell_->NotifyWindowLeaveFullScreen();
-  shell_->SetExitingFullScreen(false);
-  if (shell_->title_bar_style() == TitleBarStyle::kHidden) {
-    shell_->RedrawTrafficLights();
-  }
 }
 
 - (void)windowWillClose:(NSNotification*)notification {

+ 31 - 0
shell/browser/ui/cocoa/window_buttons_view.h

@@ -0,0 +1,31 @@
+// Copyright (c) 2021 Microsoft, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_VIEW_H_
+#define SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_VIEW_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/mac/scoped_nsobject.h"
+#include "base/optional.h"
+#include "ui/gfx/geometry/point.h"
+
+// Custom Quit, Minimize and Full Screen button container for frameless
+// windows.
+@interface WindowButtonsView : NSView {
+ @private
+  BOOL mouse_inside_;
+  BOOL show_on_hover_;
+  BOOL is_rtl_;
+  gfx::Point margin_;
+  base::scoped_nsobject<NSTrackingArea> tracking_area_;
+}
+
+- (id)initWithMargin:(const base::Optional<gfx::Point>&)margin;
+- (void)setMargin:(const base::Optional<gfx::Point>&)margin;
+- (void)setShowOnHover:(BOOL)yes;
+- (void)setNeedsDisplayForButtons;
+@end
+
+#endif  // SHELL_BROWSER_UI_COCOA_WINDOW_BUTTONS_VIEW_H_

+ 119 - 0
shell/browser/ui/cocoa/window_buttons_view.mm

@@ -0,0 +1,119 @@
+// Copyright (c) 2021 Microsoft, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "shell/browser/ui/cocoa/window_buttons_view.h"
+
+#include "base/i18n/rtl.h"
+#include "base/logging.h"
+#include "base/stl_util.h"
+#include "ui/gfx/mac/coordinate_conversion.h"
+
+namespace {
+
+const CGFloat kButtonPadding = 20.;
+
+const NSWindowButton kButtonTypes[] = {
+    NSWindowCloseButton,
+    NSWindowMiniaturizeButton,
+    NSWindowZoomButton,
+};
+
+}  // namespace
+
+@implementation WindowButtonsView
+
+- (id)initWithMargin:(const base::Optional<gfx::Point>&)margin {
+  self = [super initWithFrame:NSZeroRect];
+  [self setMargin:margin];
+
+  mouse_inside_ = false;
+  show_on_hover_ = false;
+  is_rtl_ = base::i18n::IsRTL();
+
+  for (size_t i = 0; i < base::size(kButtonTypes); ++i) {
+    NSButton* button = [NSWindow standardWindowButton:kButtonTypes[i]
+                                         forStyleMask:NSWindowStyleMaskTitled];
+    [button setTag:i];
+    int left_index = is_rtl_ ? base::size(kButtonTypes) - i - 1 : i;
+    [button setFrameOrigin:NSMakePoint(left_index * kButtonPadding, 0)];
+    [self addSubview:button];
+  }
+
+  NSView* last_button =
+      is_rtl_ ? [[self subviews] firstObject] : [[self subviews] lastObject];
+  [self setFrameSize:NSMakeSize(last_button.frame.origin.x +
+                                    last_button.frame.size.width,
+                                last_button.frame.size.height)];
+  [self setNeedsDisplayForButtons];
+
+  return self;
+}
+
+- (void)setMargin:(const base::Optional<gfx::Point>&)margin {
+  margin_ = margin.value_or(gfx::Point(7, 3));
+}
+
+- (void)setShowOnHover:(BOOL)yes {
+  show_on_hover_ = yes;
+  [self setNeedsDisplayForButtons];
+}
+
+- (void)setNeedsDisplayForButtons {
+  for (NSView* subview in self.subviews) {
+    [subview setHidden:(show_on_hover_ && !mouse_inside_)];
+    [subview setNeedsDisplay:YES];
+  }
+}
+
+- (void)removeFromSuperview {
+  [super removeFromSuperview];
+  mouse_inside_ = NO;
+}
+
+- (void)viewDidMoveToWindow {
+  // Stay in upper left corner.
+  CGFloat y =
+      self.superview.frame.size.height - self.frame.size.height - margin_.y();
+  if (is_rtl_) {
+    CGFloat x =
+        self.superview.frame.size.width - self.frame.size.width - margin_.x();
+    [self setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
+    [self setFrameOrigin:NSMakePoint(x, y)];
+  } else {
+    [self setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
+    [self setFrameOrigin:NSMakePoint(margin_.x(), y)];
+  }
+}
+
+- (BOOL)_mouseInGroup:(NSButton*)button {
+  return mouse_inside_;
+}
+
+- (void)updateTrackingAreas {
+  [super updateTrackingAreas];
+  if (tracking_area_)
+    [self removeTrackingArea:tracking_area_.get()];
+
+  tracking_area_.reset([[NSTrackingArea alloc]
+      initWithRect:NSZeroRect
+           options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
+                   NSTrackingInVisibleRect
+             owner:self
+          userInfo:nil]);
+  [self addTrackingArea:tracking_area_.get()];
+}
+
+- (void)mouseEntered:(NSEvent*)event {
+  [super mouseEntered:event];
+  mouse_inside_ = YES;
+  [self setNeedsDisplayForButtons];
+}
+
+- (void)mouseExited:(NSEvent*)event {
+  [super mouseExited:event];
+  mouse_inside_ = NO;
+  [self setNeedsDisplayForButtons];
+}
+
+@end