Repository: electron/electron (language: cpp)

Issue:
[Bug]: Windows Control Overlay button hover effect not working
### 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

14.0.0

### What operating system are you using?

Windows

### Operating System Version

Windows 11

### What arch are you using?

x64

### Last Known Working Electron version

_No response_

### Expected Behavior

See https://github.com/electron/electron/issues/30764 for specific behavior

### Actual Behavior

See https://github.com/electron/electron/issues/30764 for specific behavior

### Testcase Gist URL

_No response_

### Additional Information

I am creating this issue based on https://github.com/electron/electron/issues/30764 and the information I have gathered while trying to triage it. Though this not a breaking bug, it does create a visual inconsistency for users, as a window control button would typically change color when hovered over. For this reason, I think it should still be addressed at some point. Consolidating all current information and future questions/comments here should help in finding a solution.

[Here](https://bugs.chromium.org/p/chromium/issues/detail?id=1218921) is a link to the bug that led to the original fix implemented in Chromium

And [here](https://bugs.chromium.org/p/chromium/issues/detail?id=1271293#c1) is the link to the follow-up bug report created noting issues with the implemented fix. This was then labeled as `WontFix` 

The chromium fix artificially creates the correct tooltips and allows for the button color to change on hover. However, this results in a response to the `WM_NCHITTEST` [message](https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-nchittest) that does not correspond with the function of the button. For example, `HTMAXIMIZE` is not returned, thus Snap Layout on Windows 11 does not work.

If someone else wants to take a look at this, the relevant Electron files can be found in [this PR](https://github.com/electron/electron/pull/29600) adding WCO to Electron. For this bug in particular, I believe the related files are: `shell/browser/ui/views/win_caption_button.cc`, `shell/browser/ui/views/win_caption_button_container.cc`, and `shell/browser/ui/views/win_frame_view.cc` but changes to other files may provide a solution as well.


Discussion / hints:
any update here?
any workarounds? hope this is fixed soon

👀
Just noting this observation here while I look into this: if you create a BrowserWindow but don't load anything (no `loadURL` or `loadFile`) then the window controls work perfectly fine (note: this is of a build without the [first fix in chromium that breaks Snap Assist](https://bugs.chromium.org/p/chromium/issues/detail?id=1218921)). So something about the actual web view is breaking the event flow described in the chromium bugs.
I've been diving deep into this one and I've managed to find out a lot. Take a look:

This bug seems to be triggered due to [`LegacyRenderWidgetHostHWND::OnMouseRange`](https://source.chromium.org/chromium/chromium/src/+/main:content/browser/renderer_host/legacy_render_widget_host_win.cc;l=287-332;drc=4aaea83384df67f75d0a361cd9b47dff888ea8ad) calling the view event handler twice in a very roundabout way. The first is when it calls `HandleMouseMessage` which eventually ends up correctly being the mouse enter event. However, the second call is when it calls the Win API function `DefWindowProc`, essentially causing the OS to send a `WM_NCMOUSELEAVE` message which is what causes the mouse exit event.

I think the only fix here is to somehow mark the original call to `HandleMouseMessage` as handled, which prevents `LegacyRenderWidgetHostHWND::OnMouseRange` from passing the message on to `DefWindowProc`. I can verify that this is a valid solution by manually changing `Widget::OnMouseEvent` to set the event as handled for any non-client event, and it does solve this bug! However, I'm not really sure if this is the right solution or not and if this could affect anything else.

```patch
# void Widget::OnMouseEvent(ui::MouseEvent* event) {
#   ...
#   case ui::ET_MOUSE_MOVED: {
#     ...
         last_mouse_event_was_move_ = true;
         if (root_view)
           root_view->OnMouseMoved(*event);
+        if ((event->flags() & ui::EF_IS_NON_CLIENT) == 0)
+          event->SetHandled();
       }
       return;
#   }
#   ...
# }
```

Fix patch:
diff --git a/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc b/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc
index 03d0846c7f8f5..16e71fac12b39 100644
--- a/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc
+++ b/shell/browser/ui/win/electron_desktop_window_tree_host_win.cc
@@ -99,4 +99,21 @@ bool ElectronDesktopWindowTreeHostWin::GetClientAreaInsets(
   return false;
 }
 
+bool ElectronDesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
+  // Call the default implementation of this method to get the event to its
+  // proper handler.
+  bool handled = views::DesktopWindowTreeHostWin::HandleMouseEvent(event);
+
+  // On WCO-enabled windows, we need to mark non-client mouse moved events as
+  // handled so they don't incorrectly propogate back to the OS.
+  if (native_window_view_->IsWindowControlsOverlayEnabled() &&
+      event->type() == ui::ET_MOUSE_MOVED &&
+      (event->flags() & ui::EF_IS_NON_CLIENT) != 0) {
+    event->SetHandled();
+    handled = true;
+  }
+
+  return handled;
+}
+
 }  // namespace electron
diff --git a/shell/browser/ui/win/electron_desktop_window_tree_host_win.h b/shell/browser/ui/win/electron_desktop_window_tree_host_win.h
index 3fd831aae1ed0..8c7be1175a193 100644
--- a/shell/browser/ui/win/electron_desktop_window_tree_host_win.h
+++ b/shell/browser/ui/win/electron_desktop_window_tree_host_win.h
@@ -36,6 +36,7 @@ class ElectronDesktopWindowTreeHostWin
   bool GetDwmFrameInsetsInPixels(gfx::Insets* insets) const override;
   bool GetClientAreaInsets(gfx::Insets* insets,
                            HMONITOR monitor) const override;
+  bool HandleMouseEvent(ui::MouseEvent* event) override;
 
  private:
   NativeWindowViews* native_window_view_;  // weak ref


Test patch: