refactor(webkit): enhance pool with frontend attachment and context injection
Updates WebViewPool to accept and manage ContentInjector and MessageRouter instances. Ensures all WebViews acquired from the pool have the frontend properly attached with injected scripts and messaging handlers configured.
- commit
- 4539669e04e5106e88d9d0b493e5a107f4c583ec
- author
- brice · · 2025-12-11 21:52 UTC
- committer
- brice · · 2025-12-11 19:49 UTC
- parent
- 88a2080
- signature
- unsigned
1 file · +37 -13
M internal/infrastructure/webkit/pool.go +37 -13
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | 6 | "sync/atomic" | |
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | + | "github.com/bnema/dumber/internal/logging" | |
| 9 | 10 | "github.com/rs/zerolog" | |
| 10 | 11 | ) | |
| 11 | 12 | ||
| @@ -37,15 +38,18 @@ type WebViewPool struct { | |||
| 37 | 38 | settings *SettingsManager | |
| 38 | 39 | config PoolConfig | |
| 39 | 40 | ||
| 40 | - | pool chan *WebView | |
| 41 | - | logger zerolog.Logger | |
| 41 | + | pool chan *WebView | |
| 42 | + | logger zerolog.Logger | |
| 43 | + | ctx context.Context | |
| 44 | + | injector *ContentInjector | |
| 45 | + | router *MessageRouter | |
| 42 | 46 | ||
| 43 | 47 | closed atomic.Bool | |
| 44 | 48 | wg sync.WaitGroup | |
| 45 | 49 | } | |
| 46 | 50 | ||
| 47 | 51 | // NewWebViewPool creates a new WebView pool. | |
| 48 | - | func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolConfig, logger zerolog.Logger) *WebViewPool { | |
| 52 | + | func NewWebViewPool(ctx context.Context, wkCtx *WebKitContext, settings *SettingsManager, cfg PoolConfig, injector *ContentInjector, router *MessageRouter, logger zerolog.Logger) *WebViewPool { | |
| 49 | 53 | if cfg.MaxSize <= 0 { | |
| 50 | 54 | cfg.MaxSize = 8 | |
| 51 | 55 | } | |
| @@ -55,6 +59,9 @@ func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolCon | |||
| 55 | 59 | if cfg.MinSize > cfg.MaxSize { | |
| 56 | 60 | cfg.MinSize = cfg.MaxSize | |
| 57 | 61 | } | |
| 62 | + | if ctx == nil { | |
| 63 | + | ctx = context.Background() | |
| 64 | + | } | |
| 58 | 65 | ||
| 59 | 66 | p := &WebViewPool{ | |
| 60 | 67 | wkCtx: wkCtx, | |
| @@ -62,6 +69,9 @@ func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolCon | |||
| 62 | 69 | config: cfg, | |
| 63 | 70 | pool: make(chan *WebView, cfg.MaxSize), | |
| 64 | 71 | logger: logger.With().Str("component", "webview-pool").Logger(), | |
| 72 | + | ctx: ctx, | |
| 73 | + | injector: injector, | |
| 74 | + | router: router, | |
| 65 | 75 | } | |
| 66 | 76 | ||
| 67 | 77 | return p | |
| @@ -77,6 +87,8 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |||
| 77 | 87 | select { | |
| 78 | 88 | case wv := <-p.pool: | |
| 79 | 89 | if wv != nil && !wv.IsDestroyed() { | |
| 90 | + | // Ensure frontend is attached even if this WebView was pooled before injection was configured. | |
| 91 | + | _ = wv.AttachFrontend(p.ctx, p.injector, p.router) | |
| 80 | 92 | p.logger.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool") | |
| 81 | 93 | return wv, nil | |
| 82 | 94 | } | |
| @@ -87,7 +99,23 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |||
| 87 | 99 | ||
| 88 | 100 | // Pool empty or pooled view was destroyed, create new | |
| 89 | 101 | p.logger.Debug().Msg("pool empty, creating new webview") | |
| 90 | - | return NewWebView(p.wkCtx, p.settings, p.logger) | |
| 102 | + | return p.createWebView() | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | func (p *WebViewPool) createWebView() (*WebView, error) { | |
| 106 | + | wv, err := NewWebView(p.wkCtx, p.settings, p.logger) | |
| 107 | + | if err != nil { | |
| 108 | + | return nil, err | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | // Ensure WebView is visible when used | |
| 112 | + | wv.inner.SetVisible(true) | |
| 113 | + | ||
| 114 | + | if err := wv.AttachFrontend(p.ctx, p.injector, p.router); err != nil { | |
| 115 | + | logging.FromContext(p.ctx).Warn().Err(err).Msg("failed to attach frontend to new webview") | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | return wv, nil | |
| 91 | 119 | } | |
| 92 | 120 | ||
| 93 | 121 | // Release returns a WebView to the pool. | |
| @@ -101,7 +129,7 @@ func (p *WebViewPool) Release(wv *WebView) { | |||
| 101 | 129 | } | |
| 102 | 130 | ||
| 103 | 131 | // Reset to blank state | |
| 104 | - | _ = wv.LoadURI("about:blank") | |
| 132 | + | _ = wv.LoadURI(p.ctx, "about:blank") | |
| 105 | 133 | ||
| 106 | 134 | // Clear callbacks | |
| 107 | 135 | wv.OnLoadChanged = nil | |
| @@ -121,7 +149,8 @@ func (p *WebViewPool) Release(wv *WebView) { | |||
| 121 | 149 | } | |
| 122 | 150 | } | |
| 123 | 151 | ||
| 124 | - | // Prewarm creates WebViews in the background to populate the pool. | |
| 152 | + | // Prewarm creates WebViews synchronously to populate the pool. | |
| 153 | + | // Must be called from the GTK main thread (after GTK application is initialized). | |
| 125 | 154 | func (p *WebViewPool) Prewarm(count int) { | |
| 126 | 155 | if count <= 0 { | |
| 127 | 156 | count = p.config.PrewarmCount | |
| @@ -129,12 +158,7 @@ func (p *WebViewPool) Prewarm(count int) { | |||
| 129 | 158 | if count <= 0 { | |
| 130 | 159 | return | |
| 131 | 160 | } | |
| 132 | - | ||
| 133 | - | p.wg.Add(1) | |
| 134 | - | go func() { | |
| 135 | - | defer p.wg.Done() | |
| 136 | - | p.prewarmSync(count) | |
| 137 | - | }() | |
| 161 | + | p.prewarmSync(count) | |
| 138 | 162 | } | |
| 139 | 163 | ||
| 140 | 164 | // prewarmSync creates WebViews synchronously. | |
| @@ -145,7 +169,7 @@ func (p *WebViewPool) prewarmSync(count int) { | |||
| 145 | 169 | break | |
| 146 | 170 | } | |
| 147 | 171 | ||
| 148 | - | wv, err := NewWebView(p.wkCtx, p.settings, p.logger) | |
| 172 | + | wv, err := p.createWebView() | |
| 149 | 173 | if err != nil { | |
| 150 | 174 | p.logger.Warn().Err(err).Msg("failed to prewarm webview") | |
| 151 | 175 | continue | |
| @@ -6,6 +6,7 @@ import ( | |||||
| 6 | "sync/atomic" | 6 | "sync/atomic" | ||
| 7 | "time" | 7 | "time" | ||
| 8 | 8 | ||||
| 9 | + | "github.com/bnema/dumber/internal/logging" | |||
| 9 | "github.com/rs/zerolog" | 10 | "github.com/rs/zerolog" | ||
| 10 | ) | 11 | ) | ||
| 11 | 12 | ||||
| @@ -37,15 +38,18 @@ type WebViewPool struct { | |||||
| 37 | settings *SettingsManager | 38 | settings *SettingsManager | ||
| 38 | config PoolConfig | 39 | config PoolConfig | ||
| 39 | 40 | ||||
| 40 | - | pool chan *WebView | 41 | + | pool chan *WebView |
| 41 | - | logger zerolog.Logger | 42 | + | logger zerolog.Logger |
| 43 | + | ctx context.Context | |||
| 44 | + | injector *ContentInjector | |||
| 45 | + | router *MessageRouter | |||
| 42 | 46 | ||||
| 43 | closed atomic.Bool | 47 | closed atomic.Bool | ||
| 44 | wg sync.WaitGroup | 48 | wg sync.WaitGroup | ||
| 45 | } | 49 | } | ||
| 46 | 50 | ||||
| 47 | // NewWebViewPool creates a new WebView pool. | 51 | // NewWebViewPool creates a new WebView pool. | ||
| 48 | - | func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolConfig, logger zerolog.Logger) *WebViewPool { | 52 | + | func NewWebViewPool(ctx context.Context, wkCtx *WebKitContext, settings *SettingsManager, cfg PoolConfig, injector *ContentInjector, router *MessageRouter, logger zerolog.Logger) *WebViewPool { |
| 49 | if cfg.MaxSize <= 0 { | 53 | if cfg.MaxSize <= 0 { | ||
| 50 | cfg.MaxSize = 8 | 54 | cfg.MaxSize = 8 | ||
| 51 | } | 55 | } | ||
| @@ -55,6 +59,9 @@ func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolCon | |||||
| 55 | if cfg.MinSize > cfg.MaxSize { | 59 | if cfg.MinSize > cfg.MaxSize { | ||
| 56 | cfg.MinSize = cfg.MaxSize | 60 | cfg.MinSize = cfg.MaxSize | ||
| 57 | } | 61 | } | ||
| 62 | + | if ctx == nil { | |||
| 63 | + | ctx = context.Background() | |||
| 64 | + | } | |||
| 58 | 65 | ||||
| 59 | p := &WebViewPool{ | 66 | p := &WebViewPool{ | ||
| 60 | wkCtx: wkCtx, | 67 | wkCtx: wkCtx, | ||
| @@ -62,6 +69,9 @@ func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolCon | |||||
| 62 | config: cfg, | 69 | config: cfg, | ||
| 63 | pool: make(chan *WebView, cfg.MaxSize), | 70 | pool: make(chan *WebView, cfg.MaxSize), | ||
| 64 | logger: logger.With().Str("component", "webview-pool").Logger(), | 71 | logger: logger.With().Str("component", "webview-pool").Logger(), | ||
| 72 | + | ctx: ctx, | |||
| 73 | + | injector: injector, | |||
| 74 | + | router: router, | |||
| 65 | } | 75 | } | ||
| 66 | 76 | ||||
| 67 | return p | 77 | return p | ||
| @@ -77,6 +87,8 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |||||
| 77 | select { | 87 | select { | ||
| 78 | case wv := <-p.pool: | 88 | case wv := <-p.pool: | ||
| 79 | if wv != nil && !wv.IsDestroyed() { | 89 | if wv != nil && !wv.IsDestroyed() { | ||
| 90 | + | // Ensure frontend is attached even if this WebView was pooled before injection was configured. | |||
| 91 | + | _ = wv.AttachFrontend(p.ctx, p.injector, p.router) | |||
| 80 | p.logger.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool") | 92 | p.logger.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool") | ||
| 81 | return wv, nil | 93 | return wv, nil | ||
| 82 | } | 94 | } | ||
| @@ -87,7 +99,23 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |||||
| 87 | 99 | ||||
| 88 | // Pool empty or pooled view was destroyed, create new | 100 | // Pool empty or pooled view was destroyed, create new | ||
| 89 | p.logger.Debug().Msg("pool empty, creating new webview") | 101 | p.logger.Debug().Msg("pool empty, creating new webview") | ||
| 90 | - | return NewWebView(p.wkCtx, p.settings, p.logger) | 102 | + | return p.createWebView() |
| 103 | + | } | |||
| 104 | + | ||||
| 105 | + | func (p *WebViewPool) createWebView() (*WebView, error) { | |||
| 106 | + | wv, err := NewWebView(p.wkCtx, p.settings, p.logger) | |||
| 107 | + | if err != nil { | |||
| 108 | + | return nil, err | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | // Ensure WebView is visible when used | |||
| 112 | + | wv.inner.SetVisible(true) | |||
| 113 | + | ||||
| 114 | + | if err := wv.AttachFrontend(p.ctx, p.injector, p.router); err != nil { | |||
| 115 | + | logging.FromContext(p.ctx).Warn().Err(err).Msg("failed to attach frontend to new webview") | |||
| 116 | + | } | |||
| 117 | + | ||||
| 118 | + | return wv, nil | |||
| 91 | } | 119 | } | ||
| 92 | 120 | ||||
| 93 | // Release returns a WebView to the pool. | 121 | // Release returns a WebView to the pool. | ||
| @@ -101,7 +129,7 @@ func (p *WebViewPool) Release(wv *WebView) { | |||||
| 101 | } | 129 | } | ||
| 102 | 130 | ||||
| 103 | // Reset to blank state | 131 | // Reset to blank state | ||
| 104 | - | _ = wv.LoadURI("about:blank") | 132 | + | _ = wv.LoadURI(p.ctx, "about:blank") |
| 105 | 133 | ||||
| 106 | // Clear callbacks | 134 | // Clear callbacks | ||
| 107 | wv.OnLoadChanged = nil | 135 | wv.OnLoadChanged = nil | ||
| @@ -121,7 +149,8 @@ func (p *WebViewPool) Release(wv *WebView) { | |||||
| 121 | } | 149 | } | ||
| 122 | } | 150 | } | ||
| 123 | 151 | ||||
| 124 | - | // Prewarm creates WebViews in the background to populate the pool. | 152 | + | // Prewarm creates WebViews synchronously to populate the pool. |
| 153 | + | // Must be called from the GTK main thread (after GTK application is initialized). | |||
| 125 | func (p *WebViewPool) Prewarm(count int) { | 154 | func (p *WebViewPool) Prewarm(count int) { | ||
| 126 | if count <= 0 { | 155 | if count <= 0 { | ||
| 127 | count = p.config.PrewarmCount | 156 | count = p.config.PrewarmCount | ||
| @@ -129,12 +158,7 @@ func (p *WebViewPool) Prewarm(count int) { | |||||
| 129 | if count <= 0 { | 158 | if count <= 0 { | ||
| 130 | return | 159 | return | ||
| 131 | } | 160 | } | ||
| 132 | - | 161 | + | p.prewarmSync(count) | |
| 133 | - | p.wg.Add(1) | |||
| 134 | - | go func() { | |||
| 135 | - | defer p.wg.Done() | |||
| 136 | - | p.prewarmSync(count) | |||
| 137 | - | }() | |||
| 138 | } | 162 | } | ||
| 139 | 163 | ||||
| 140 | // prewarmSync creates WebViews synchronously. | 164 | // prewarmSync creates WebViews synchronously. | ||
| @@ -145,7 +169,7 @@ func (p *WebViewPool) prewarmSync(count int) { | |||||
| 145 | break | 169 | break | ||
| 146 | } | 170 | } | ||
| 147 | 171 | ||||
| 148 | - | wv, err := NewWebView(p.wkCtx, p.settings, p.logger) | 172 | + | wv, err := p.createWebView() |
| 149 | if err != nil { | 173 | if err != nil { | ||
| 150 | p.logger.Warn().Err(err).Msg("failed to prewarm webview") | 174 | p.logger.Warn().Err(err).Msg("failed to prewarm webview") | ||
| 151 | continue | 175 | continue | ||