detached 4539669

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 (
66 "sync/atomic"
77 "time"
88
9+ "github.com/bnema/dumber/internal/logging"
910 "github.com/rs/zerolog"
1011 )
1112
@@ -37,15 +38,18 @@ type WebViewPool struct {
3738 settings *SettingsManager
3839 config PoolConfig
3940
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
4246
4347 closed atomic.Bool
4448 wg sync.WaitGroup
4549 }
4650
4751 // 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 {
4953 if cfg.MaxSize <= 0 {
5054 cfg.MaxSize = 8
5155 }
@@ -55,6 +59,9 @@ func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolCon
5559 if cfg.MinSize > cfg.MaxSize {
5660 cfg.MinSize = cfg.MaxSize
5761 }
62+ if ctx == nil {
63+ ctx = context.Background()
64+ }
5865
5966 p := &WebViewPool{
6067 wkCtx: wkCtx,
@@ -62,6 +69,9 @@ func NewWebViewPool(wkCtx *WebKitContext, settings *SettingsManager, cfg PoolCon
6269 config: cfg,
6370 pool: make(chan *WebView, cfg.MaxSize),
6471 logger: logger.With().Str("component", "webview-pool").Logger(),
72+ ctx: ctx,
73+ injector: injector,
74+ router: router,
6575 }
6676
6777 return p
@@ -77,6 +87,8 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) {
7787 select {
7888 case wv := <-p.pool:
7989 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)
8092 p.logger.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool")
8193 return wv, nil
8294 }
@@ -87,7 +99,23 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) {
8799
88100 // Pool empty or pooled view was destroyed, create new
89101 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
91119 }
92120
93121 // Release returns a WebView to the pool.
@@ -101,7 +129,7 @@ func (p *WebViewPool) Release(wv *WebView) {
101129 }
102130
103131 // Reset to blank state
104- _ = wv.LoadURI("about:blank")
132+ _ = wv.LoadURI(p.ctx, "about:blank")
105133
106134 // Clear callbacks
107135 wv.OnLoadChanged = nil
@@ -121,7 +149,8 @@ func (p *WebViewPool) Release(wv *WebView) {
121149 }
122150 }
123151
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).
125154 func (p *WebViewPool) Prewarm(count int) {
126155 if count <= 0 {
127156 count = p.config.PrewarmCount
@@ -129,12 +158,7 @@ func (p *WebViewPool) Prewarm(count int) {
129158 if count <= 0 {
130159 return
131160 }
132-
133- p.wg.Add(1)
134- go func() {
135- defer p.wg.Done()
136- p.prewarmSync(count)
137- }()
161+ p.prewarmSync(count)
138162 }
139163
140164 // prewarmSync creates WebViews synchronously.
@@ -145,7 +169,7 @@ func (p *WebViewPool) prewarmSync(count int) {
145169 break
146170 }
147171
148- wv, err := NewWebView(p.wkCtx, p.settings, p.logger)
172+ wv, err := p.createWebView()
149173 if err != nil {
150174 p.logger.Warn().Err(err).Msg("failed to prewarm webview")
151175 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 *SettingsManager38 settings *SettingsManager
38 config PoolConfig39 config PoolConfig
39 40
40- pool chan *WebView41+ pool chan *WebView
41- logger zerolog.Logger42+ logger zerolog.Logger
43+ ctx context.Context
44+ injector *ContentInjector
45+ router *MessageRouter
42 46
43 closed atomic.Bool47 closed atomic.Bool
44 wg sync.WaitGroup48 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 = 854 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.MaxSize60 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 p77 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, nil93 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 new100 // 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 state131 // Reset to blank state
104- _ = wv.LoadURI("about:blank")132+ _ = wv.LoadURI(p.ctx, "about:blank")
105 133
106 // Clear callbacks134 // Clear callbacks
107 wv.OnLoadChanged = nil135 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.PrewarmCount156 count = p.config.PrewarmCount
@@ -129,12 +158,7 @@ func (p *WebViewPool) Prewarm(count int) {
129 if count <= 0 {158 if count <= 0 {
130 return159 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 break169 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 continue175 continue

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close