diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 2b3594bef5e..9916913da98 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -2383,28 +2383,59 @@ 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. 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 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 } }