Skip to content

fix(v3/windows): keep host-owned rasterization scale under visual hosting#5761

Merged
leaanthony merged 7 commits into
wailsapp:masterfrom
wayneforrest:fix/visual-hosting-scale-detection
Jul 7, 2026
Merged

fix(v3/windows): keep host-owned rasterization scale under visual hosting#5761
leaanthony merged 7 commits into
wailsapp:masterfrom
wayneforrest:fix/visual-hosting-scale-detection

Conversation

@wayneforrest

@wayneforrest wayneforrest commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Problem

With UseVisualHosting = true and resizing a window, from one screen to the next, and pausing midway,
and then continue to drag (move) the window to land on the seconds scree, the window would have the wrong DPI applied.

In my testing resize+dragging was done on actual hardware, Windows 11 with a 1080p screen, and dragging the window to a 4K screen, having a scaling set to 225%. The end result was a too tiny font on the 4K screen. After a window resize, it auto corrects as expected.

I tested this change using both UseVisualHosting = true in the WindowsOptions and having it set to UseVisualHosting = false.

My go.mod was pinned to this exact version:

// TEMPORARY test pin: upstream master (alpha2.114) + one commit that gates
// WebView2 monitor-scale detection on hosting mode. Under UseVisualHosting the
// host keeps ownership of the rasterization scale (fixes the tiny-font regression
// #5734 introduced); windowed hosting is unchanged. Upstream PR: wailsapp/wails#5761.
// Drop this replace once #5761 merges and we bump to an upstream tag.
replace github.com/wailsapp/wails/v3 => github.com/wayneforrest/wails/v3 v3.0.0-20260706035514-b5e22b17de34

#5734 re-enabled WebView2 automatic monitor-scale detection (ShouldDetectMonitorScaleChanges) unconditionally in setupChromium, to fix the mixed-DPI GPU-process crash (#5732). That is the right call for the default windowed (HWND-child) hosting mode.

It is incorrect under UseVisualHosting (COREWEBVIEW2_HOSTING_MODE_WINDOW_TO_VISUAL). In that mode the WebView2 content is a DirectComposition visual, decoupled from the child HWND that automatic detection tracks. Dragging across a mixed-DPI monitor boundary then makes detection read the wrong monitor and settle the visual on a stale rasterization scale — the whole UI renders shrunk (tiny fonts) until something forces a re-layout. Repro: two monitors at different scale (e.g. 150% / 100%), drag the window slowly across the boundary, pause, then continue.

Fix

Gate the detection-enable on hosting mode in setupChromium:

Host-owned scale is reliable now that the by-value PutRasterizationScale bug (#5701) is fixed, and it is the documented contract per WebView2Feedback #3665 (detection-off is correct when the host puts the scale on every change).

Testing

  • CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build ./pkg/application/ and go vet both clean.
  • The equivalent "host owns the scale" configuration (native detection off) resolves the shrunk-UI regression on a mixed-DPI Windows 11 setup; on-device validation of this exact gating is in progress.

🤖 Generated with Claude Code

Summary by CodeRabbit

Summary by CodeRabbit

  • Bug Fixes
    • Improved Windows mixed-DPI scaling behavior in WebView-based rendering.
    • Reduced visual glitches during monitor/DPI changes, especially when visual hosting is enabled.
    • Updated monitor-scale change detection so scaling updates are applied by the correct rendering side for the active hosting mode, avoiding conflicting resizes/rasterization updates.

wailsapp#5734 re-enabled WebView2 automatic monitor-scale detection
(ShouldDetectMonitorScaleChanges) unconditionally to fix the mixed-DPI
GPU-process crash. That is correct for the default windowed (HWND-child)
hosting mode, but wrong under UseVisualHosting
(COREWEBVIEW2_HOSTING_MODE_WINDOW_TO_VISUAL): the content is a
DirectComposition visual decoupled from the child HWND that automatic
detection tracks, so on a mixed-DPI monitor cross detection reads the
wrong monitor and settles the visual on a stale rasterization scale --
the whole UI renders shrunk (small fonts) until something forces a
re-layout.

Gate the detection-enable on hosting mode:
- windowed: enable detection (unchanged; keeps wailsapp#5734's crash fix)
- visual hosting: explicitly disable detection so the host owns the
  scale via resyncWebviewRasterizationScale (monitorScaleDetectionOn
  stays false). The module leaves detection at its platform default
  (enabled), so visual hosting must disable it explicitly -- merely
  skipping the enable would leave both Edge's detection and the host
  resync writing the scale (the two-writer race wailsapp#5734 targets).

Host-owned scale is reliable now that the by-value PutRasterizationScale
bug (wailsapp#5701) is fixed, and is the documented contract (WebView2Feedback
wailsapp#3665: detection-off is correct when the host puts the scale on every
change).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85f42a7e-15e3-4c9e-82e1-576dbb2f48c1

📥 Commits

Reviewing files that changed from the base of the PR and between 698e0ec and f6cb036.

📒 Files selected for processing (1)
  • v3/pkg/application/webview_window_windows.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • v3/pkg/application/webview_window_windows.go

Walkthrough

setupChromium now documents hosting-mode scale ownership and configures WebView2 monitor-scale detection from UseVisualHosting, then records the controller’s resulting state with updated logging.

Changes

WebView2 Monitor-Scale Detection

Layer / File(s) Summary
Hosting-mode scale ownership
v3/pkg/application/webview_window_windows.go
The setupChromium comment block now describes windowed hosting as the WebView2 scale-detection owner and visual hosting as the host-side resync path.
Conditional controller detection
v3/pkg/application/webview_window_windows.go
setupChromium now computes wantDetection from UseVisualHosting, applies it with PutShouldDetectMonitorScaleChanges, reads back the controller state with GetShouldDetectMonitorScaleChanges, and logs query failure before leaving host resync disabled.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • wailsapp/wails#5380: Introduces UseVisualHosting, which this change uses to switch WebView2 scale-detection behavior.
  • wailsapp/wails#5572: Adds rasterization-scale resync behavior that complements disabled monitor-scale auto-detection.
  • wailsapp/wails#5734: Also updates the WebView2 monitor-scale detection/resync flow in webview_window_windows.go.

Suggested reviewers: leaanthony, atterpac

Poem

A bunny twitched its cotton nose,
And watched the scale-detective close.
Windowed said “yes,” visual said “nay,”
Then read the state and hopped away. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: gating rasterization-scale ownership by visual hosting mode.
Description check ✅ Passed The description covers the problem, fix, issue context, and testing, but it omits the template's checkbox and test-configuration sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@wayneforrest wayneforrest marked this pull request as draft July 6, 2026 04:14

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
v3/pkg/application/webview_window_windows.go (1)

2413-2425: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Visual-hosting disable failure leaves the system in the documented two-writers crash state.

When UseVisualHosting is true and PutShouldDetectMonitorScaleChanges(false) fails, the code only logs and moves on. Per the comment above (Lines 2408-2411), the module leaves detection at its platform default (enabled), so a failed disable leaves automatic detection on while monitorScaleDetectionOn remains false — which means resyncWebviewRasterizationScale runs its full body (Lines 2055-2057). That is exactly the "two writers racing → scale(0,0)" GPU-crash scenario this change is meant to prevent.

Set monitorScaleDetectionOn = true on disable failure so the host resync stands down and Edge remains the single writer (degraded shrunk-UI is preferable to the GPU-process crash).

🛡️ Proposed defensive fix
 			if globalApplication.options.Windows.UseVisualHosting {
 				if err := c3.PutShouldDetectMonitorScaleChanges(false); err != nil {
 					globalApplication.error("webview2: disable monitor scale detection (visual hosting): %v", err)
+					// Disable failed: detection stays at its enabled default.
+					// Let the host resync stand down so Edge remains the sole
+					// scale writer, avoiding the two-writers scale(0,0) crash.
+					w.monitorScaleDetectionOn = true
 				}
 			} else if err := c3.PutShouldDetectMonitorScaleChanges(true); err != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/pkg/application/webview_window_windows.go` around lines 2413 - 2425, In
the UseVisualHosting path inside webview window setup, a failed
PutShouldDetectMonitorScaleChanges(false) currently only logs and leaves
monitorScaleDetectionOn false, which can still allow
resyncWebviewRasterizationScale to run against the platform’s default enabled
detection. Update the failure branch in the
controller.GetICoreWebView2Controller3 block so monitorScaleDetectionOn is set
to true when disabling scale detection fails, keeping the host resync logic
suppressed and preserving the single-writer behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@v3/pkg/application/webview_window_windows.go`:
- Around line 2413-2425: In the UseVisualHosting path inside webview window
setup, a failed PutShouldDetectMonitorScaleChanges(false) currently only logs
and leaves monitorScaleDetectionOn false, which can still allow
resyncWebviewRasterizationScale to run against the platform’s default enabled
detection. Update the failure branch in the
controller.GetICoreWebView2Controller3 block so monitorScaleDetectionOn is set
to true when disabling scale detection fails, keeping the host resync logic
suppressed and preserving the single-writer behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 00135368-915e-4efd-be3d-eaff61f660e1

📥 Commits

Reviewing files that changed from the base of the PR and between 0ea5f3a and b5e22b1.

📒 Files selected for processing (1)
  • v3/pkg/application/webview_window_windows.go

@wayneforrest wayneforrest marked this pull request as ready for review July 6, 2026 04:46
@wayneforrest

Copy link
Copy Markdown
Contributor Author

cc @@taliesin-ai @leaanthony

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
v3/pkg/application/webview_window_windows.go (1)

1669-1678: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead GetClientRect nil branch.

w32.GetClientRect panics on failure and otherwise returns a non-nil *RECT, so this guard does not prevent the described FillRect(nil, ...) crash path. If a real failure path needs hardening, use an OK-style wrapper instead.

Based on learnings, the repo’s Win32 GetClientRect wrapper panics on failure and otherwise returns non-nil; minimize/restore crash claims need a repro or stack trace.

Proposed cleanup
-			// GetClientRect can legitimately return nil for a window in a
-			// transient state (minimise/restore transitions are especially
-			// prone to triggering it). FillRect with a nil rect crashes, so it
-			// must be checked before painting.
-			if rc := w32.GetClientRect(w.hwnd); rc != nil {
-				colorRef := w32.COLORREF(uint32(col.Red) | uint32(col.Green)<<8 | uint32(col.Blue)<<16)
-				hbrush := w32.CreateSolidBrush(colorRef)
-				w32.FillRect(hdc, rc, hbrush)
-				w32.DeleteObject(w32.HGDIOBJ(hbrush))
-			}
+			rc := w32.GetClientRect(w.hwnd)
+			colorRef := w32.COLORREF(uint32(col.Red) | uint32(col.Green)<<8 | uint32(col.Blue)<<16)
+			hbrush := w32.CreateSolidBrush(colorRef)
+			w32.FillRect(hdc, rc, hbrush)
+			w32.DeleteObject(w32.HGDIOBJ(hbrush))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/pkg/application/webview_window_windows.go` around lines 1669 - 1678, The
nil-check around w32.GetClientRect in the painting path is dead code because
this wrapper panics on failure and otherwise returns a non-nil RECT, so remove
the unreachable branch in the window paint logic. If you need to harden the
resize/minimize path, do it by switching the GetClientRect call site to an
OK-style wrapper or another explicit error-handling path in the same painting
method rather than guarding against nil here.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@v3/pkg/application/webview_window_windows.go`:
- Around line 1669-1678: The nil-check around w32.GetClientRect in the painting
path is dead code because this wrapper panics on failure and otherwise returns a
non-nil RECT, so remove the unreachable branch in the window paint logic. If you
need to harden the resize/minimize path, do it by switching the GetClientRect
call site to an OK-style wrapper or another explicit error-handling path in the
same painting method rather than guarding against nil here.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0d0a6f16-8dc4-4745-968e-0f9d026298cc

📥 Commits

Reviewing files that changed from the base of the PR and between fc9ab2a and 698e0ec.

📒 Files selected for processing (1)
  • v3/pkg/application/webview_window_windows.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts WebView2 monitor-scale detection behavior on Windows so DPI/rasterization scale ownership matches the selected WebView2 hosting mode, preventing incorrect scaling under visual hosting while preserving the mixed-DPI GPU-process crash fix for windowed hosting.

Changes:

  • Gate ShouldDetectMonitorScaleChanges enabling/disabling based on UseVisualHosting.
  • Expand inline documentation in setupChromium() to describe the rationale for scale ownership per hosting mode.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread v3/pkg/application/webview_window_windows.go Outdated
…r state

Query GetShouldDetectMonitorScaleChanges after the Put and set the
single-writer flag from the controller's real state, not from the value
requested. A failed PutShouldDetectMonitorScaleChanges could leave
detection at its platform default (enabled) while monitorScaleDetectionOn
stayed false, so resyncWebviewRasterizationScale would keep writing
PutRasterizationScale concurrently with Edge — the two-writer scale(0,0)
race this change removes. If the query itself fails, assume detection may
be on and stand the host resync down.

Addresses review feedback on wailsapp#5761.
@leaanthony leaanthony merged commit 5cd2be6 into wailsapp:master Jul 7, 2026
32 of 33 checks passed
@leaanthony

Copy link
Copy Markdown
Member

Thanks @wayneforrest 🙏

leaanthony pushed a commit that referenced this pull request Jul 7, 2026
… host-owned rasterization scale under visual hosting
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants