electron / electron/electron

[Breaking Change] 41.3 + 42: frameless+thickFrame HWND grows 16×8 DIP, no longer matches getBounds()

Open
#51,679 12 comments 5 reactions 0 assignees View on GitHub
42-x-y bug :beetle: component/BrowserWindow has-repro-gist platform/windows
Dominant language
C++
Stars
123k
Forks
17.5k
Avg merge
14h 22m
Merged PRs (30d)
873

Description

### 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

42.1.0

### What operating system(s) are you using?

Windows

### Operating System Version

Windows 11 (verified on 10.0.22631 and 10.0.26200)

### What arch are you using?

x64

### Last Known Working Electron version

41.2.0

### Does the issue also appear in Chromium / Google Chrome?

I don't know how to test

### Expected Behavior

For `BrowserWindow({ frame: false, thickFrame: true, resizable: true })` on Windows, the on-screen HWND rect (read via `user32!GetWindowRect`) equals `screen.dipToScreenRect(win.getBounds())`. `setBounds({ x, y, width: W, height: H })` produces a visible window of `W × H` DIP at `(x, y)`. This held through Electron `41.3.0`.

### Actual Behavior

Starting in `41.3.0` (backport of #50706 via #50863) and `42.0.0` (via #50864), the HWND extends outward from `getBounds()` by `SM_CXSIZEFRAME + SM_CXPADDEDBORDER` DIP on **left, right, and bottom**, and by `0` on **top**. `setBounds({ x, y, width: W, height: H })` produces a visible window of `(W + 16) × (H + 8)` DIP at `(x - 8, y)` at 100% DPI (scales with DPI). `getBounds()`, `getNormalBounds()`, and `getContentBounds()` continue to return the original logical (inset-subtracted) rect, so the `getBounds() ↔ HWND` identity is broken on this window class. No constructor option, runtime flag, or JS API to query the inset or restore pre-41.3 behavior.

### Testcase Gist URL

https://gist.github.com/gdavidkov/c8c466109c009ff195f1abdcb68362a9

### Additional Information

### How the break looks in numbers

Same `BrowserWindow` options, same display, **measured locally** with the repro at the bottom of this issue:

| Observation | Electron 41.2.0 (last working) | Electron 41.3.0 / 42.1.0 (same input) | Delta |
|---|---|---|---|
| `asymmetricInsetPhysical` @ 100% DPI | `{0, 0, 0, 0}` | `{left:8, top:0, right:8, bottom:8}` | new inset |
| `asymmetricInsetPhysical` @ 137.5% DPI | `{0, 0, 0, 0}` | `{left:12, top:0, right:12, bottom:11}` | new inset (scales with DPI) |
| Visible window width for `setBounds(w:500)` | 500 DIP | 516 DIP | **+16 DIP** |
| Visible window height for `setBounds(h:400)` | 400 DIP | 408 DIP | **+8 DIP** |
| `getBounds()` vs HWND rect | identical | HWND = bounds outset by `TLBR(0, T, T, T)` | identity broken |
| Center of `getBounds()` vs HWND center on Y | identical | logical center is `T/2` above HWND center | **−4 DIP** |
| `setBounds(display.workArea)` fills `workArea` | yes | overflows `workArea` by `T` DIP on L / R / B | window leaves work area |

### Source of the change

Introduced by **#50706** *"fix: external resize hit targets for frameless windows on Windows"* (shipped as #50864), with follow-up regression fix in **#51252 / #51296**. The relevant code in [`shell/browser/ui/views/win_frame_view.cc@v42.1.0`](https://github.com/electron/electron/blob/v42.1.0/shell/browser/ui/views/win_frame_view.cc):

```cpp
gfx::Insets WinFrameView::RestoredFrameBorderInsets() const {
if (window_->has_frame() || !window_->has_thick_frame() ||
!window_->IsResizable())
return {};

const int thickness =
display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXSIZEFRAME) +
display::win::GetScreenWin()->GetSystemMetricsInDIP(SM_CXPADDEDBORDER);
return gfx::Insets::TLBR(0, thickness, thickness, thickness);
}
```

### Related

- [#40505](https://github.com/electron/electron/issues/40505) — original Win11 frameless-resize bug
- [#50706](https://github.com/electron/electron/pull/50706) — the fix on `main`
- [#50863](https://github.com/electron/electron/pull/50863) — backport to `41-x-y` (shipped in `41.3.0`)
- [#50864](https://github.com/electron/electron/pull/50864) — backport to `42-x-y` (shipped in `42.0.0`)
- [#51244](https://github.com/electron/electron/issues/51244) — regression introduced by #50706
- [#51252](https://github.com/electron/electron/pull/51252) — regression fix on `main`
- [#51296](https://github.com/electron/electron/pull/51296) — regression-fix backport
- [#51287](https://github.com/electron/electron/pull/51287) — `fix: remove insets on fullscreen windows on Windows` (related follow-up)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.