Repository: electron/electron
Pull request: fix: emit `context-menu` event in Windows draggable regions

#### Description of Change

Refs https://github.com/electron/electron/issues/45301
Follow-up to https://github.com/electron/electron/pull/45813

Fixes an issue where `context-menu` event weren't emitted as expected on Windows in draggable regions.

cc @bpasero 

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Fixed an issue where `context-menu` event weren't emitted as expected on Windows in draggable regions.


Changed files:
filename: shell/browser/native_window_views_win.cc
patch: @@ -10,6 +10,7 @@
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_version.h"
 #include "content/public/browser/browser_accessibility_state.h"
+#include "shell/browser/api/electron_api_web_contents.h"
 #include "shell/browser/browser.h"
 #include "shell/browser/native_window_views.h"
 #include "shell/browser/ui/views/root_view.h"
@@ -288,6 +289,11 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
 
       return false;
     }
+    case WM_RBUTTONUP: {
+      if (!has_frame())
+        electron::api::WebContents::SetDisableDraggableRegions(false);
+      return false;
+    }
     case WM_GETMINMAXINFO: {
       WINDOWPLACEMENT wp;
       wp.length = sizeof(WINDOWPLACEMENT);
@@ -411,10 +417,16 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
       return false;
     }
     case WM_CONTEXTMENU: {
-      bool prevent_default = false;
-      NotifyWindowSystemContextMenu(GET_X_LPARAM(l_param),
-                                    GET_Y_LPARAM(l_param), &prevent_default);
-      return prevent_default;
+      // We don't want to trigger system-context-menu here if we have a
+      // frameless window as it'll already be emitted in
+      // ElectronDesktopWindowTreeHostWin::HandleMouseEvent.
+      if (has_frame()) {
+        bool prevent_default = false;
+        NotifyWindowSystemContextMenu(GET_X_LPARAM(l_param),
+                                      GET_Y_LPARAM(l_param), &prevent_default);
+        return prevent_default;
+      }
+      return false;
     }
     case WM_SYSCOMMAND: {
       // Mask is needed to account for double clicking title bar to maximize

filename: shell/browser/ui/win/electron_desktop_window_tree_host_win.cc
patch: @@ -6,6 +6,7 @@
 
 #include "base/win/windows_version.h"
 #include "electron/buildflags/buildflags.h"
+#include "shell/browser/api/electron_api_web_contents.h"
 #include "shell/browser/native_window_views.h"
 #include "shell/browser/ui/views/win_frame_view.h"
 #include "shell/browser/win/dark_mode.h"
@@ -113,6 +114,12 @@ bool ElectronDesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
     bool prevent_default = false;
     native_window_view_->NotifyWindowSystemContextMenu(event->x(), event->y(),
                                                        &prevent_default);
+    // If the user prevents default behavior, emit contextmenu event to
+    // allow bringing up the custom menu.
+    if (prevent_default) {
+      electron::api::WebContents::SetDisableDraggableRegions(true);
+      views::DesktopWindowTreeHostWin::HandleMouseEvent(event);
+    }
     return prevent_default;
   }
 

filename: spec/api-web-contents-spec.ts
patch: @@ -2896,8 +2896,13 @@ describe('webContents module', () => {
       expect(contextMenuEmitCount).to.equal(1);
     });
 
-    ifit(process.platform !== 'win32')('emits when right-clicked in page in a draggable region', async () => {
+    it('emits when right-clicked in page in a draggable region', async () => {
       const w = new BrowserWindow({ show: false });
+
+      if (process.platform === 'win32') {
+        w.on('system-context-menu', (event) => { event.preventDefault(); });
+      }
+
       await w.loadFile(path.join(fixturesPath, 'pages', 'draggable-page.html'));
 
       const promise = once(w.webContents, 'context-menu') as Promise<[any, Electron.ContextMenuParams]>;