fix: resize cursor behavior for frameless windows on Windows#46703
fix: resize cursor behavior for frameless windows on Windows#46703hotdogee wants to merge 6 commits into
Conversation
|
💖 Thanks for opening this pull request! 💖 Semantic PR titlesWe use semantic commit messages to streamline the release process. Before your pull request can be merged, you should update your pull request title to start with a semantic prefix. Examples of commit messages with semantic prefixes:
Commit signingThis repo enforces commit signatures for all incoming PRs. PR tipsThings that will help get your PR across the finish line:
We get a lot of pull requests on this repo, so please be patient and we will get back to you as soon as we can. |
a82cf89 to
34269d0
Compare
This aligns the resizing behavior of frameless windows with system standards and improves usability. On Windows, frameless windows (titleBarStyle: 'hidden' or frame: false) currently only trigger resize cursors and actions when the mouse is exactly on or slightly inside the border. This differs from standard framed windows and other applications where resizing is also possible when the cursor is slightly outside the border, leading to a less intuitive user experience. This commit modifies ElectronDesktopWindowTreeHostWin::GetClientAreaInsets to apply standard system border insets (SM_CXSIZEFRAME + SM_CXPADDEDBORDER) to the left, right, and bottom of the non-client area via WM_NCCALCSIZE when the window is not maximized. This allows the default Windows message handling (WM_NCHITTEST) to correctly identify resize regions (HTLEFT, HTRIGHT, HTBOTTOM, etc.) in the area just outside the client rectangle. Internal Views hit-testing in FramelessView::ResizingBorderHitTest is also adjusted to prevent conflicts with the OS-level hit-testing for these outer borders. The caption button layout in WinFrameView is slightly adjusted for cleaner alignment. Fixes electron#40505. Related to microsoft/vscode#185249. Signed-off-by: Han Lin <hotdogee@gmail.com>
34269d0 to
f6872f7
Compare
felixrieseberg
left a comment
There was a problem hiding this comment.
This is a really good PR, we can put this one up on the fridge.
I appreciate your detailed explanation and the simplicity of the code. I think we can mark this one up as a pure bugfix, I don't think it needs to be semver/minor.
Wrap the custom non-client insets and hit‑test adjustments in OS_WIN guards so that Ubuntu and macOS still use the original inside‑bounds resize logic. Other platforms (Linux/macOS) will continue to apply the full kResizeInsideBoundsSize inset, avoiding any unintended UX changes outside of Windows.
|
@hotdogee it looks like some of the failing tests might be relevant - can you validate those locally? |
|
The failing tests look relevant to this PR: |
ckerr
left a comment
There was a problem hiding this comment.
The failing tests need to be resolved, but LGTM in principle
This resolves test failures caused by the updated resize hit areas for frameless windows. The changes in GetClientAreaInsets() modified how content and window sizes relate to each other by adding system border insets to the non-client area. - Updates ContentBoundsToWindowBounds and WindowBoundsToContentBounds to properly account for these insets - Only applies insets in GetClientAreaInsets for resizable windows - Modifies useContentSize handling for frameless windows on Windows - Updates test expectations for Windows platform tests Fixes failing tests while maintaining the improved resize experience.
|
Thanks for the feedback on the failing tests! 🙏 Changes Made:
These updates resolve the test failures by ensuring accurate bounds calculations on Windows, while maintaining the expected behavior on Linux and macOS. Please let me know if additional tweaks are needed 🙂 |
| const size = w.getSize(); | ||
| expect(size).to.deep.equal([400, 400]); | ||
| if (process.platform === 'win32') { | ||
| expect(size).to.deep.equal([416, 408]); |
There was a problem hiding this comment.
@hotdogee this PR caught my eye as I'm looking into similar issues with decorations and resize insets on Linux.
I think it's important for Electron to preserve logical bounds: if a caller sets window or content bounds, they should get back the same values back that they set. (And the window/content should visually have the correct measurements, even if there are invisible dimensions that extend further out.) Can you think of a way to keep the resize handle math hidden from the public APIs?
|
@hotdogee do you wish to pursue this? |
|
We're experiencing the same issue, and although we're not able to confirm this PR fixes our issue (as our app is dependent on castLabs' electron), we're patiently awaiting the release of this fix. Thank you for all the effort thus far 🙏 |

Description of Change
Closes: #40505
Related: microsoft/vscode#185249
Problem Description
Currently, on Windows, frameless Electron windows (
titleBarStyle: 'hidden'orframe: false) only show the resize cursor and allow resizing when the mouse cursor is exactly on the window border or slightly inside it. This differs from standard framed windows (including Electron's default framed windows) and other applications like Chrome or Edge, where the resize cursor also appears when the cursor is slightly outside the window border. This discrepancy makes resizing frameless windows less intuitive and user-friendly, grabbing the top-right resize handle is especially hard as there is only 1 pixel to grab.This PR aligns the resizing behavior of frameless windows on Windows with that of standard windows by enabling resizing when the cursor is positioned slightly outside the left, right, and bottom edges.
Current and Expected Behavior
Root Cause Analysis
The issue stems from how Windows handles hit-testing for window resizing and how Electron's frameless window implementation interacts with it:
WM_NCHITTEST): Standard window resizing relies on Windows processing theWM_NCHITTESTmessage within the non-client area (the window frame). When the cursor is slightly outside the client area but within the frame's designated resize borders, Windows returns hit-test codes likeHTLEFT,HTRIGHT,HTBOTTOM, etc., triggering the resize cursor and behavior.WM_NCCALCSIZEmessage. The previous implementation inElectronDesktopWindowTreeHostWin::GetClientAreaInsetsonly applied system-defined border thicknesses (SM_CXSIZEFRAME+SM_CXPADDEDBORDER) when the window was maximized. In the normal (non-maximized) state, no such non-client border insets were defined for the left, right, and bottom edges. Consequently, Windows did not receive the necessary geometry information to perform hit-testing just outside the client rectangle for these edges.FramelessViewperforms its own hit-testing (ResizingBorderHitTestImplusingGetHTComponentForFrame). This logic used a fixed internal inset (kResizeInsideBoundsSize) to detect resize handles inside the window bounds.Proposed Changes
This pull request modifies the way non-client area calculations and hit-testing are handled for frameless windows on Windows:
electron_desktop_window_tree_host_win.cc:GetClientAreaInsets. Previously, insets (usingGetSystemMetrics(SM_CXSIZEFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER)) were only applied when the frameless window was maximized.gfx::Insets::TLBR(0, thickness, thickness, thickness)). The top inset remains 0 to align with the frameless window behavior.gfx::Insets::TLBR(thickness, thickness, thickness, thickness)). This prevents the maximized window from bleeding onto other monitors.WM_NCCALCSIZEmessage processing. By defining a non-client area border around the client area (even if visually transparent), Windows' defaultWM_NCHITTESTprocessing can correctly identifyHTLEFT,HTRIGHT,HTBOTTOM, etc., when the cursor is in these border regions slightly outside the client rectangle.frameless_view.cc:ResizingBorderHitTestImplwithinResizingBorderHitTestis modified. Instead of passinggfx::Insets(kResizeInsideBoundsSize)(which applied the inset uniformly), it now passesgfx::Insets::TLBR(kResizeInsideBoundsSize, 0, 0, 0).win_frame_view.cc:Testing
1.99.3.2025-04-21.09-58-03.mp4
Benefits
Checklist
npm testpassesRelease Notes
Notes: Aligned the resizing behavior of frameless windows with system standards to improve usability on Windows.