Repository: electron/electron (language: cpp)

Issue:
[Bug]: Frameless window is not draggable when running MAS build
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a bug report that matches the one I want to file, without success.

### Electron Version

23.1.1

### What operating system are you using?

macOS

### Operating System Version

macOS Big Sur

### What arch are you using?

arm64 (including Apple Silicon)

### Last Known Working Electron version

22.3.1

### Expected Behavior

Window should be draggable.

### Actual Behavior

Window is not draggable.

### Testcase Gist URL

https://gist.github.com/1387d3e19cbf2717cd21bbf3befaf159

### Additional Information

To be noted, dragging works as expected on standard Electron builds but consistently fails when using Mac Apple Store builds (>= v23).

@nornagon I understand you've been working on some related issues recently, please let me know if this is a known/fixed issue and I'll close this one, thanks!


Discussion / hints:
Thanks for logging this issue!

I tried reproducing it (albeit with ventura vs big sur) and couldn't reproduce the behaviour, using the test case in the description. Could you let me know if it's still reproducible on your end, and if so, attach a video? Thanks!! 
@georgexu99 Thanks for having a look. I had at least one report from a more recent macOS version but need to confirm whether that was Monterey or Ventura. 

Also it actually seems like the window is draggable from very specific regions (top right and left corners) but clearly not working consistently as I'd expect.

https://user-images.githubusercontent.com/1832946/221997197-8595b899-2d0b-4293-96cb-9ea353f07447.mp4


You might be running up against this: https://www.electronjs.org/docs/latest/breaking-changes#behavior-changed-draggable-regions-on-macos
I thought about that originally but if that was the case I'd expect it to work consistently between builds of the same Electron version but that's not the case. Also the reproducer is a dead simple draggable body element with nothing else in the page, no overlap or anything of the kind. 

Honestly I don't even know what's the actual difference between a MAS build and a standard MacOS build for the same version and architecture but from what I could test that's making a big difference because it's working just fine using any other build type.
Yeah, I can repro this on v23.1.1-mas-arm64 on macOS Monterey 12.6.3.
Looks like it's this patch causing the issue: https://github.com/electron/electron/blob/main/patches/chromium/mas_disable_custom_window_frame.patch, introduced here: https://github.com/electron/electron/pull/21573.

This is triggered by #35603, which changed our draggable region impl to depend on this custom window frame view.

Fix patch:
diff --git a/shell/browser/ui/cocoa/electron_ns_window.mm b/shell/browser/ui/cocoa/electron_ns_window.mm
index 0ad17da24e967..8a0c7767613c5 100644
--- a/shell/browser/ui/cocoa/electron_ns_window.mm
+++ b/shell/browser/ui/cocoa/electron_ns_window.mm
@@ -11,6 +11,9 @@
 #include "shell/browser/ui/cocoa/root_view_mac.h"
 #include "ui/base/cocoa/window_size_constants.h"
 
+#import <objc/message.h>
+#import <objc/runtime.h>
+
 namespace electron {
 
 int ScopedDisableResize::disable_resize_ = 0;
@@ -19,8 +22,65 @@
 
 @interface NSWindow (PrivateAPI)
 - (NSImage*)_cornerMask;
+- (int64_t)_resizeDirectionForMouseLocation:(CGPoint)location;
+@end
+
+#if IS_MAS_BUILD()
+// See components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm
+@interface NSView (CRFrameViewAdditions)
+- (void)cr_mouseDownOnFrameView:(NSEvent*)event;
+@end
+
+typedef void (*MouseDownImpl)(id, SEL, NSEvent*);
+
+namespace {
+MouseDownImpl g_nsthemeframe_mousedown;
+MouseDownImpl g_nsnextstepframe_mousedown;
+}  // namespace
+
+// This class is never instantiated, it's just a container for our swizzled
+// mouseDown method.
+@interface SwizzledMouseDownHolderClass : NSView
+@end
+
+@implementation SwizzledMouseDownHolderClass
+- (void)swiz_nsthemeframe_mouseDown:(NSEvent*)event {
+  if ([self.window respondsToSelector:@selector(shell)]) {
+    electron::NativeWindowMac* shell =
+        (electron::NativeWindowMac*)[(id)self.window shell];
+    if (shell && !shell->has_frame())
+      [self cr_mouseDownOnFrameView:event];
+    g_nsthemeframe_mousedown(self, @selector(mouseDown:), event);
+  }
+}
+
+- (void)swiz_nsnextstepframe_mouseDown:(NSEvent*)event {
+  if ([self.window respondsToSelector:@selector(shell)]) {
+    electron::NativeWindowMac* shell =
+        (electron::NativeWindowMac*)[(id)self.window shell];
+    if (shell && !shell->has_frame()) {
+      [self cr_mouseDownOnFrameView:event];
+    }
+    g_nsnextstepframe_mousedown(self, @selector(mouseDown:), event);
+  }
+}
 @end
 
+namespace {
+void SwizzleMouseDown(NSView* frame_view,
+                      SEL swiz_selector,
+                      MouseDownImpl* orig_impl) {
+  Method original_mousedown =
+      class_getInstanceMethod([frame_view class], @selector(mouseDown:));
+  *orig_impl = (MouseDownImpl)method_getImplementation(original_mousedown);
+  Method new_mousedown = class_getInstanceMethod(
+      [SwizzledMouseDownHolderClass class], swiz_selector);
+  method_setImplementation(original_mousedown,
+                           method_getImplementation(new_mousedown));
+}
+}  // namespace
+#endif  // IS_MAS_BUILD
+
 @implementation ElectronNSWindow
 
 @synthesize acceptsFirstMouse;
@@ -36,6 +96,37 @@ - (id)initWithShell:(electron::NativeWindowMac*)shell
                                styleMask:styleMask
                                  backing:NSBackingStoreBuffered
                                    defer:NO])) {
+#if IS_MAS_BUILD()
+    // The first time we create a frameless window, we swizzle the
+    // implementation of -[NSNextStepFrame mouseDown:], replacing it with our
+    // own.
+    // This is only necessary on MAS where we can't directly refer to
+    // NSNextStepFrame or NSThemeFrame, as they are private APIs.
+    // See components/remote_cocoa/app_shim/native_widget_mac_nswindow.mm for
+    // the non-MAS-compatible way of doing this.
+    if (styleMask & NSWindowStyleMaskTitled) {
+      if (!g_nsthemeframe_mousedown) {
+        NSView* theme_frame = [[self contentView] superview];
+        DCHECK(strcmp(class_getName([theme_frame class]), "NSThemeFrame") == 0)
+            << "Expected NSThemeFrame but was "
+            << class_getName([theme_frame class]);
+        SwizzleMouseDown(theme_frame, @selector(swiz_nsthemeframe_mouseDown:),
+                         &g_nsthemeframe_mousedown);
+      }
+    } else {
+      if (!g_nsnextstepframe_mousedown) {
+        NSView* nextstep_frame = [[self contentView] superview];
+        DCHECK(strcmp(class_getName([nextstep_frame class]),
+                      "NSNextStepFrame") == 0)
+            << "Expected NSNextStepFrame but was "
+            << class_getName([nextstep_frame class]);
+        SwizzleMouseDown(nextstep_frame,
+                         @selector(swiz_nsnextstepframe_mouseDown:),
+                         &g_nsnextstepframe_mousedown);
+      }
+    }
+#endif  // IS_MAS_BUILD
+
     shell_ = shell;
   }
   return self;


Test patch: