From 9e56ed7e107c5e44bbafe11497ef956170a20b4d Mon Sep 17 00:00:00 2001 From: wayne Date: Sat, 4 Jul 2026 13:57:17 +1200 Subject: [PATCH 1/3] fix(windows): hide the WebView2 controller on minimise/hide (visual-hosting dead zone) --- v3/pkg/application/webview_window_windows.go | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 4c43fee8677..73d9cd0433f 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1274,6 +1274,14 @@ func (w *windowsWebviewWindow) hide() { w.windowShown = false w.showRequested = false + // Symmetric with show()'s chromium.Show(): under UseVisualHosting a bare + // SW_HIDE leaves the DirectComposition input surface hit-testing where the + // window was (a desktop right-click "dead zone"). Restore is always + // programmatic via Show() -> show() -> chromium.Show(). + if w.chromium != nil { + _ = w.chromium.Hide() + } + // Cancel any pending visibility timeout if w.visibilityTimeout != nil { w.visibilityTimeout.Stop() @@ -1694,6 +1702,11 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // here (not at SIZE_RESTORED), and needs the same DPI // resync as the restore path below (#5544). w.resyncWebviewDPIAfterUnminimiseIfDPIChanged() + // Re-assert controller visibility hidden on SIZE_MINIMIZED so the + // (visual-hosted) window does not restore blank. + if w.chromium != nil { + _ = w.chromium.Show() + } w.parent.emit(events.Windows.WindowUnMinimise) } w.isMinimizing = false @@ -1716,6 +1729,11 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // rasterization scale on restore, so window.devicePixelRatio // keeps the wrong monitor's value until a manual resize (#5544). w.resyncWebviewDPIAfterUnminimiseIfDPIChanged() + // Re-assert controller visibility hidden on SIZE_MINIMIZED so the + // (visual-hosted) window does not restore blank. + if w.chromium != nil { + _ = w.chromium.Show() + } w.parent.emit(events.Windows.WindowUnMinimise) } w.isMinimizing = false @@ -1731,6 +1749,15 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp case w32.SIZE_MINIMIZED: w.isMinimizing = true w.parent.emit(events.Windows.WindowMinimise) + // Under UseVisualHosting (WINDOW_TO_VISUAL) the WebView2 content is a + // DirectComposition visual whose input surface keeps hit-testing at the + // window's last on-screen rectangle after a bare SW_MINIMIZE — leaving a + // desktop right-click "dead zone". Tell the controller to become + // invisible (also the WebView2-recommended action on minimize); the + // SIZE_RESTORED/SIZE_MAXIMIZED un-minimize branches re-assert it. + if w.chromium != nil { + _ = w.chromium.Hide() + } } w.lastSizeWParam = wparam From 09cce7a609021325048ac0cb0603ea8945c86d3a Mon Sep 17 00:00:00 2001 From: taliesin-ai Date: Sun, 5 Jul 2026 09:38:41 +1000 Subject: [PATCH 2/3] fix(windows): harden the controller visibility calls from review - Guard the new chromium.Hide()/Show() sites with GetController() != nil, matching the file's other controller call sites: w.chromium exists from window construction but the controller is nil until WebView2's async creation completes, and a background-goroutine Hide() dispatched inside Embed's nested message pump would otherwise nil-panic. - Gate the un-minimise Show() on windowShown || showRequested so restoring a window that was hidden while minimised does not resurrect the invisible input surface the minimise-time Hide() removed. - Reword the un-minimise comments (they described the minimise branch). --- v3/pkg/application/webview_window_windows.go | 27 ++++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 146207cc542..8e193ef4a40 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1288,7 +1288,10 @@ func (w *windowsWebviewWindow) hide() { // SW_HIDE leaves the DirectComposition input surface hit-testing where the // window was (a desktop right-click "dead zone"). Restore is always // programmatic via Show() -> show() -> chromium.Show(). - if w.chromium != nil { + // The controller can still be nil while WebView2 creation is in flight + // (a background-goroutine Hide() is dispatched inside Embed's nested + // message pump), so guard like the other controller call sites do. + if w.chromium != nil && w.chromium.GetController() != nil { _ = w.chromium.Hide() } @@ -1712,9 +1715,12 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // here (not at SIZE_RESTORED), and needs the same DPI // resync as the restore path below (#5544). w.resyncWebviewDPIAfterUnminimiseIfDPIChanged() - // Re-assert controller visibility hidden on SIZE_MINIMIZED so the - // (visual-hosted) window does not restore blank. - if w.chromium != nil { + // Undo the SIZE_MINIMIZED chromium.Hide() so the + // (visual-hosted) window does not restore blank. Skip it for + // logically hidden windows (Hide() called while minimised): + // re-showing their controller would resurrect the invisible + // input surface ("dead zone") that hiding it avoids. + if (w.windowShown || w.showRequested) && w.chromium != nil && w.chromium.GetController() != nil { _ = w.chromium.Show() } w.parent.emit(events.Windows.WindowUnMinimise) @@ -1739,9 +1745,12 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // rasterization scale on restore, so window.devicePixelRatio // keeps the wrong monitor's value until a manual resize (#5544). w.resyncWebviewDPIAfterUnminimiseIfDPIChanged() - // Re-assert controller visibility hidden on SIZE_MINIMIZED so the - // (visual-hosted) window does not restore blank. - if w.chromium != nil { + // Undo the SIZE_MINIMIZED chromium.Hide() so the + // (visual-hosted) window does not restore blank. Skip it for + // logically hidden windows (Hide() called while minimised): + // re-showing their controller would resurrect the invisible + // input surface ("dead zone") that hiding it avoids. + if (w.windowShown || w.showRequested) && w.chromium != nil && w.chromium.GetController() != nil { _ = w.chromium.Show() } w.parent.emit(events.Windows.WindowUnMinimise) @@ -1765,7 +1774,9 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // desktop right-click "dead zone". Tell the controller to become // invisible (also the WebView2-recommended action on minimize); the // SIZE_RESTORED/SIZE_MAXIMIZED un-minimize branches re-assert it. - if w.chromium != nil { + // The controller can be nil while creation is in flight; guard like + // the other controller call sites. + if w.chromium != nil && w.chromium.GetController() != nil { _ = w.chromium.Hide() } } From f7b7cd9e012f7694d1e6e355d9176609ad5fe817 Mon Sep 17 00:00:00 2001 From: taliesin-ai Date: Sun, 5 Jul 2026 11:03:16 +1000 Subject: [PATCH 3/3] fix(windows): gate un-minimise controller Show on webviewNavigationCompleted Matches show()'s gating (review feedback from atterpac): before first NavigationCompleted the controller is deliberately left for the first-paint Hide/Show nudge; re-showing it early from an un-minimise would flash unpainted content. The Hide() sites stay ungated on purpose: hiding pre-navigation is harmless and prevents the invisible input surface from existing in the pre-first-paint window. --- v3/pkg/application/webview_window_windows.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 8e193ef4a40..9b831d0689e 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -1720,7 +1720,8 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // logically hidden windows (Hide() called while minimised): // re-showing their controller would resurrect the invisible // input surface ("dead zone") that hiding it avoids. - if (w.windowShown || w.showRequested) && w.chromium != nil && w.chromium.GetController() != nil { + if (w.windowShown || w.showRequested) && w.webviewNavigationCompleted && + w.chromium != nil && w.chromium.GetController() != nil { _ = w.chromium.Show() } w.parent.emit(events.Windows.WindowUnMinimise) @@ -1750,7 +1751,8 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp // logically hidden windows (Hide() called while minimised): // re-showing their controller would resurrect the invisible // input surface ("dead zone") that hiding it avoids. - if (w.windowShown || w.showRequested) && w.chromium != nil && w.chromium.GetController() != nil { + if (w.windowShown || w.showRequested) && w.webviewNavigationCompleted && + w.chromium != nil && w.chromium.GetController() != nil { _ = w.chromium.Show() } w.parent.emit(events.Windows.WindowUnMinimise)