fix: preserve DIP size on programmatic cross-DPI setBounds on Windows#51645
Open
gdavidkov wants to merge 1 commit into
Open
fix: preserve DIP size on programmatic cross-DPI setBounds on Windows#51645gdavidkov wants to merge 1 commit into
gdavidkov wants to merge 1 commit into
Conversation
When BrowserWindow.setBounds() targets coordinates on a monitor with a different display scale factor than the window's current monitor, the final outer size ends up off by source/destination — e.g., a 400 DIP width requested on a 1.375x/1.1x mixed-DPI setup gets reported back as ~366 DIP. Pre-fix on the dpi-fiddle harness, an 800 DIP outer requested on the secondary returns 734 DIP; post-fix it returns 802 DIP (within ScaleToEnclosingRect rounding at non-integer DSFs). Adds a Chromium patch that caches the destination-DPI pixel rect in DesktopWindowTreeHostWin::SetBoundsInDIP and substitutes its size in HWNDMessageHandler::OnDpiChanged for the OS-suggested size, sidestepping Windows' double-rescale of WM_DPICHANGED's suggested rect. See the patch commit message for the full root-cause analysis. Adds a regression spec under BrowserWindow.setBounds that exercises the cross-DPI move when a secondary monitor at a different DSF is available (skips otherwise). Notes: Fixed an issue where BrowserWindow.setBounds() returned the wrong size when targeting a monitor with a different DPI scaling than the window's current monitor on Windows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description of Change
On Windows with multiple monitors at different DPI scaling, calling
BrowserWindow.setBounds()with coordinates on a different-DPI monitorreturns the wrong outer size. Requesting an 800×600 DIP window on a
1.375×/1.1× mixed-DPI setup currently returns ~734×550 DIP from
getBounds().Before fix — window visibly shrinks on the cross-DPI move:
After fix — outer matches the request within ±2 DIP (

ScaleToEnclosingRectrounding at non-integer DSFs):
Root cause
DesktopWindowTreeHostWin::SetBoundsInDIPconverts the requested DIP rect topixels using the destination display's DSF, then
::SetWindowPosmoves theHWND. Windows fires
WM_DPICHANGEDsynchronously during the move, andHWNDMessageHandler::OnDpiChangedapplies the OS-suggestedlParamrectverbatim — but Windows computed that rect as
current_rect * new_dpi / old_dpi, so the already-destination-DPI-sized pixels we just sent getrescaled by that ratio again, leaving the final outer off.
The mismatch is amplified by Windows 10+ text-scale: Chromium's
display::Display::device_scale_factor()folds the text-scale multiplier in(
1.25 × 1.10 = 1.375), butWM_DPICHANGED'swParamreports only thedisplay-DPI bucket (
120, i.e. 1.25×). AnyMulDiv-style cancellationagainst Windows' DPI is therefore unsound.
Fix
Cache the destination-DPI pixel rect in
SetBoundsInDIPjust before::SetWindowPos.OnDpiChangedsubstitutes the cached size for theOS-suggested size; position still comes from
lParamso the window landswhere Windows placed it. The cache is cleared right after
SetBoundsInPixelsreturns (WM_DPICHANGEDis dispatched synchronously),so it cannot leak into a later unrelated DPI change. No DPI math is
involved on purpose — passing through the already-correct pixel size
sidesteps the Chromium-DSF vs. Windows-DPI mismatch entirely.
See the patch commit message for the full analysis.
Checklist
npm testpassesRelease Notes
Notes: Fixed an issue where
BrowserWindow.setBounds()returned the wrong size when targeting a monitor with a different DPI scaling than the window's current monitor on Windows.