fix(webkit): skip TerminateWebProcess for related popup WebViews
- commit
- ee7cb2fef0f628a076cb18244a37f8d4dafad948
- author
- bnema · · 2025-12-27 17:07 UTC
- committer
- bnema · · 2025-12-27 17:12 UTC
- parent
- e5a8cf8
- signature
- GPG Unverified
2B82207D370127A0The key is unknown
2 files · +11 -2
M CHANGELOG.md +1 -0
| @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. | |||
| 11 | 11 | - New config: `workspace.switch_to_tab_on_move` (default: true). | |
| 12 | 12 | ||
| 13 | 13 | ### Fixed | |
| 14 | + | - **OAuth popup login**: Fixed parent page going blank after OAuth popup closes (e.g., Google login on claude.ai, notion.com). Related WebViews share a web process with their parent; destroying the popup was terminating the shared process, killing the parent. Now skips `TerminateWebProcess()` for popup WebViews. | |
| 14 | 15 | - Startup: defer WebView pool prewarm until after initial tab creation to reduce cold-start navigation latency. | |
| 15 | 16 | - README CLI examples: updated to match current commands/flags (`purge --force` only, `logs` usage, `sessions list --limit`, removed deprecated `--dmenu` root flag). | |
| 16 | 17 | ||
| @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. | |||||
| 11 | - New config: `workspace.switch_to_tab_on_move` (default: true). | 11 | - New config: `workspace.switch_to_tab_on_move` (default: true). | ||
| 12 | 12 | ||||
| 13 | ### Fixed | 13 | ### Fixed | ||
| 14 | + | - **OAuth popup login**: Fixed parent page going blank after OAuth popup closes (e.g., Google login on claude.ai, notion.com). Related WebViews share a web process with their parent; destroying the popup was terminating the shared process, killing the parent. Now skips `TerminateWebProcess()` for popup WebViews. | |||
| 14 | - Startup: defer WebView pool prewarm until after initial tab creation to reduce cold-start navigation latency. | 15 | - Startup: defer WebView pool prewarm until after initial tab creation to reduce cold-start navigation latency. | ||
| 15 | - README CLI examples: updated to match current commands/flags (`purge --force` only, `logs` usage, `sessions list --limit`, removed deprecated `--dmenu` root flag). | 16 | - README CLI examples: updated to match current commands/flags (`purge --force` only, `logs` usage, `sessions list --limit`, removed deprecated `--dmenu` root flag). | ||
| 16 | 17 | ||||
M internal/infrastructure/webkit/webview.go +10 -2
| @@ -88,6 +88,7 @@ type WebView struct { | |||
| 88 | 88 | ||
| 89 | 89 | // State (protected by mutex) | |
| 90 | 90 | destroyed atomic.Bool | |
| 91 | + | isRelated bool // true if created via NewWebViewWithRelated (shares web process with parent) | |
| 91 | 92 | uri string | |
| 92 | 93 | title string | |
| 93 | 94 | progress float64 | |
| @@ -294,6 +295,7 @@ func NewWebViewWithRelated(ctx context.Context, parent *WebView, settings *Setti | |||
| 294 | 295 | ||
| 295 | 296 | wv := &WebView{ | |
| 296 | 297 | inner: inner, | |
| 298 | + | isRelated: true, // Shares web process with parent - must not terminate process on destroy | |
| 297 | 299 | ucm: inner.GetUserContentManager(), | |
| 298 | 300 | logger: log.With().Str("component", "webview-popup").Logger(), | |
| 299 | 301 | signalIDs: make([]uint32, 0, 6), | |
| @@ -981,7 +983,9 @@ func (wv *WebView) Destroy() { | |||
| 981 | 983 | ||
| 982 | 984 | // 5. Terminate the web process to free GPU resources (VA-API, DMA-BUF, GL contexts) | |
| 983 | 985 | // This is critical to prevent zombie processes that hold video decoder resources. | |
| 984 | - | if wv.inner != nil { | |
| 986 | + | // IMPORTANT: Skip for related views (popups) - they share the web process with their | |
| 987 | + | // parent. Terminating the shared process would kill the parent WebView too! | |
| 988 | + | if !wv.isRelated && wv.inner != nil { | |
| 985 | 989 | wv.inner.TerminateWebProcess() | |
| 986 | 990 | } | |
| 987 | 991 | ||
| @@ -993,7 +997,11 @@ func (wv *WebView) Destroy() { | |||
| 993 | 997 | wv.ucm = nil | |
| 994 | 998 | wv.findController = nil | |
| 995 | 999 | ||
| 996 | - | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("webview destroyed and web process terminated") | |
| 1000 | + | if wv.isRelated { | |
| 1001 | + | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("related webview destroyed (process shared with parent)") | |
| 1002 | + | } else { | |
| 1003 | + | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("webview destroyed and web process terminated") | |
| 1004 | + | } | |
| 997 | 1005 | } | |
| 998 | 1006 | ||
| 999 | 1007 | // RunJavaScript executes script in the specified world (empty for main world). | |
| @@ -88,6 +88,7 @@ type WebView struct { | |||||
| 88 | 88 | ||||
| 89 | // State (protected by mutex) | 89 | // State (protected by mutex) | ||
| 90 | destroyed atomic.Bool | 90 | destroyed atomic.Bool | ||
| 91 | + | isRelated bool // true if created via NewWebViewWithRelated (shares web process with parent) | |||
| 91 | uri string | 92 | uri string | ||
| 92 | title string | 93 | title string | ||
| 93 | progress float64 | 94 | progress float64 | ||
| @@ -294,6 +295,7 @@ func NewWebViewWithRelated(ctx context.Context, parent *WebView, settings *Setti | |||||
| 294 | 295 | ||||
| 295 | wv := &WebView{ | 296 | wv := &WebView{ | ||
| 296 | inner: inner, | 297 | inner: inner, | ||
| 298 | + | isRelated: true, // Shares web process with parent - must not terminate process on destroy | |||
| 297 | ucm: inner.GetUserContentManager(), | 299 | ucm: inner.GetUserContentManager(), | ||
| 298 | logger: log.With().Str("component", "webview-popup").Logger(), | 300 | logger: log.With().Str("component", "webview-popup").Logger(), | ||
| 299 | signalIDs: make([]uint32, 0, 6), | 301 | signalIDs: make([]uint32, 0, 6), | ||
| @@ -981,7 +983,9 @@ func (wv *WebView) Destroy() { | |||||
| 981 | 983 | ||||
| 982 | // 5. Terminate the web process to free GPU resources (VA-API, DMA-BUF, GL contexts) | 984 | // 5. Terminate the web process to free GPU resources (VA-API, DMA-BUF, GL contexts) | ||
| 983 | // This is critical to prevent zombie processes that hold video decoder resources. | 985 | // This is critical to prevent zombie processes that hold video decoder resources. | ||
| 984 | - | if wv.inner != nil { | 986 | + | // IMPORTANT: Skip for related views (popups) - they share the web process with their |
| 987 | + | // parent. Terminating the shared process would kill the parent WebView too! | |||
| 988 | + | if !wv.isRelated && wv.inner != nil { | |||
| 985 | wv.inner.TerminateWebProcess() | 989 | wv.inner.TerminateWebProcess() | ||
| 986 | } | 990 | } | ||
| 987 | 991 | ||||
| @@ -993,7 +997,11 @@ func (wv *WebView) Destroy() { | |||||
| 993 | wv.ucm = nil | 997 | wv.ucm = nil | ||
| 994 | wv.findController = nil | 998 | wv.findController = nil | ||
| 995 | 999 | ||||
| 996 | - | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("webview destroyed and web process terminated") | 1000 | + | if wv.isRelated { |
| 1001 | + | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("related webview destroyed (process shared with parent)") | |||
| 1002 | + | } else { | |||
| 1003 | + | wv.logger.Debug().Uint64("id", uint64(wv.id)).Msg("webview destroyed and web process terminated") | |||
| 1004 | + | } | |||
| 997 | } | 1005 | } | ||
| 998 | 1006 | ||||
| 999 | // RunJavaScript executes script in the specified world (empty for main world). | 1007 | // RunJavaScript executes script in the specified world (empty for main world). | ||