From b5e22b17de34bef556a2b90355e951527edee30d Mon Sep 17 00:00:00 2001 From: wayne Date: Mon, 6 Jul 2026 15:55:14 +1200 Subject: [PATCH 1/2] fix(v3/windows): keep host-owned scale under visual hosting #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 #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 #5734 targets). Host-owned scale is reliable now that the by-value PutRasterizationScale bug (#5701) is fixed, and is the documented contract (WebView2Feedback #3665: detection-off is correct when the host puts the scale on every change). Co-Authored-By: Claude Opus 4.8 (1M context) --- v3/pkg/application/webview_window_windows.go | 53 +++++++++++++------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index d5173253315..8363533ff26 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -2378,26 +2378,45 @@ func (w *windowsWebviewWindow) setupChromium() { chromium.Embed(w.hwnd) - // Ensure automatic monitor-scale detection is on. Published webview2 - // module versions up to v1.0.27 disable ShouldDetectMonitorScaleChanges - // at controller creation (the in-repo module no longer does), leaving - // rasterization-scale updates entirely to the host's WM_DPICHANGED - // handling. In that - // host-managed mode, dragging the window across a mixed-DPI monitor - // boundary can make the embedded browser compute a degenerate - // scale(0,0) transform (ui/gfx/geometry/transform.cc NOTREACHED - // "is not invertible"); the resulting compositor frame is rejected by - // the viz process as a malformed Mojo message, which kills the GPU - // process, and after enough repeat kills the browser process gives up - // ("GPU process isn't usable. Goodbye.") taking the controller with - // it. Detection-on is the WebView2 default and keeps monitor-cross - // scale updates inside Edge, where that path is actually exercised; - // bounds stay raw-pixels. While detection is on, the host-side - // WM_DPICHANGED / un-minimise scale resyncs stand down (see + // Configure who owns the WebView2 rasterization scale on a monitor-DPI + // change. Two hosting modes need opposite answers: + // + // Windowed (HWND-child) hosting — the default: enable automatic + // monitor-scale detection. Published webview2 module versions up to + // v1.0.27 disabled ShouldDetectMonitorScaleChanges at controller creation + // (the in-repo module no longer does), leaving rasterization-scale updates + // entirely to the host's WM_DPICHANGED handling. In that host-managed mode, + // dragging the window across a mixed-DPI monitor boundary can make the + // embedded browser compute a degenerate scale(0,0) transform + // (ui/gfx/geometry/transform.cc NOTREACHED "is not invertible"); the + // resulting compositor frame is rejected by the viz process as a malformed + // Mojo message, which kills the GPU process, and after enough repeat kills + // the browser process gives up ("GPU process isn't usable. Goodbye.") + // taking the controller with it. Detection-on is the WebView2 default and + // keeps monitor-cross scale updates inside Edge, where that path is + // actually exercised; bounds stay raw-pixels. While detection is on, the + // host-side WM_DPICHANGED / un-minimise scale resyncs stand down (see // monitorScaleDetectionOn) so the scale has exactly one writer. + // + // Visual hosting (UseVisualHosting -> COREWEBVIEW2_HOSTING_MODE_WINDOW_TO_VISUAL): + // the content is a DirectComposition visual, decoupled from the child HWND + // that automatic detection tracks. Across a mixed-DPI boundary detection + // then reads the wrong monitor and settles the visual on a stale scale, so + // the whole UI renders shrunk. There the host must own the scale via + // resyncWebviewRasterizationScale (correct per WebView2Feedback #3665, and + // reliable now that the by-value PutRasterizationScale bug is fixed). The + // module leaves detection at its platform default (enabled), so visual + // hosting must disable it *explicitly* — merely skipping the enable would + // leave detection on AND the host resync running, i.e. two writers racing, + // which is the scale(0,0) crash above. Keeping monitorScaleDetectionOn + // false lets resyncWebviewRasterizationScale run its real body. if controller := chromium.GetController(); controller != nil { if c3 := controller.GetICoreWebView2Controller3(); c3 != nil { - if err := c3.PutShouldDetectMonitorScaleChanges(true); err != nil { + if globalApplication.options.Windows.UseVisualHosting { + if err := c3.PutShouldDetectMonitorScaleChanges(false); err != nil { + globalApplication.error("webview2: disable monitor scale detection (visual hosting): %v", err) + } + } else if err := c3.PutShouldDetectMonitorScaleChanges(true); err != nil { globalApplication.error("webview2: enable monitor scale detection: %v", err) } else { w.monitorScaleDetectionOn = true From f6cb0361d19fd72b3e65b5c37ec859b6c42fb44b Mon Sep 17 00:00:00 2001 From: taliesin-ai Date: Wed, 8 Jul 2026 07:25:27 +1000 Subject: [PATCH 2/2] fix(v3/windows): derive monitorScaleDetectionOn from actual controller state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #5761. --- v3/pkg/application/webview_window_windows.go | 28 ++++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 6b898f69077..9916913da98 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -2413,17 +2413,29 @@ func (w *windowsWebviewWindow) setupChromium() { // module leaves detection at its platform default (enabled), so visual // hosting must disable it *explicitly* — merely skipping the enable would // leave detection on AND the host resync running, i.e. two writers racing, - // which is the scale(0,0) crash above. Keeping monitorScaleDetectionOn - // false lets resyncWebviewRasterizationScale run its real body. + // which is the scale(0,0) crash above. With detection off the host owns the + // scale via resyncWebviewRasterizationScale. + // + // Whichever mode: monitorScaleDetectionOn is set from the controller's + // *actual* ShouldDetectMonitorScaleChanges after the Put, never from the + // value we asked for. A failed Put can leave detection at its platform + // default (enabled) while we intended off; keying the flag off intent would + // then run the host resync against a detecting Edge — the exact two-writer + // race this guards against. The host stands down whenever detection is on so + // the rasterization scale keeps exactly one writer. if controller := chromium.GetController(); controller != nil { if c3 := controller.GetICoreWebView2Controller3(); c3 != nil { - if globalApplication.options.Windows.UseVisualHosting { - if err := c3.PutShouldDetectMonitorScaleChanges(false); err != nil { - globalApplication.error("webview2: disable monitor scale detection (visual hosting): %v", err) - } - } else if err := c3.PutShouldDetectMonitorScaleChanges(true); err != nil { - globalApplication.error("webview2: enable monitor scale detection: %v", err) + wantDetection := !globalApplication.options.Windows.UseVisualHosting + if err := c3.PutShouldDetectMonitorScaleChanges(wantDetection); err != nil { + globalApplication.error("webview2: set monitor scale detection to %v: %v", wantDetection, err) + } + if on, err := c3.GetShouldDetectMonitorScaleChanges(); err == nil { + w.monitorScaleDetectionOn = on } else { + // Can't confirm the state: assume detection may be on and stand + // the host resync down. A cosmetic stale scale beats risking the + // two-writer GPU-process crash. + globalApplication.error("webview2: query monitor scale detection: %v", err) w.monitorScaleDetectionOn = true } }