pool.go RawHistoryFolder
1package cef
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "sync"
8
9 "github.com/bnema/dumber/internal/application/port"
10 "github.com/bnema/dumber/internal/logging"
11)
12
13// Compile-time interface check.
14var _ port.WebViewPool = (*WebViewPool)(nil)
15
16// errPoolClosed is returned when an operation is attempted on a closed pool.
17var errPoolClosed = errors.New("cef: webview pool is closed")
18
19// WebViewPool manages a pool of pre-created WebViews for fast tab creation.
20// In Phase 1 the pool is simple: Acquire pops or creates, Release destroys.
21type WebViewPool struct {
22 factory *WebViewFactory
23 mu sync.Mutex
24 pool []*WebView
25 closed bool
26}
27
28// newWebViewPool returns a pool backed by the given factory.
29func newWebViewPool(factory *WebViewFactory) *WebViewPool {
30 return &WebViewPool{
31 factory: factory,
32 }
33}
34
35// Acquire obtains a WebView from the pool. If a pre-warmed WebView is
36// available it is returned immediately; otherwise a new one is created.
37func (p *WebViewPool) Acquire(ctx context.Context) (port.WebView, error) {
38 p.mu.Lock()
39 if p.closed {
40 p.mu.Unlock()
41 return nil, errPoolClosed
42 }
43
44 if len(p.pool) > 0 {
45 wv := p.pool[len(p.pool)-1]
46 p.pool = p.pool[:len(p.pool)-1]
47 p.mu.Unlock()
48 wv.generation.Add(1)
49 return wv, nil
50 }
51 p.mu.Unlock()
52
53 return p.factory.Create(ctx)
54}
55
56// Release destroys the WebView. In Phase 1 there is no reuse — the browser
57// is closed and all resources are freed immediately.
58func (p *WebViewPool) Release(wv port.WebView) {
59 if wv == nil {
60 return
61 }
62 wv.Destroy()
63}
64
65// Prewarm creates count WebViews synchronously and adds them to the pool.
66func (p *WebViewPool) Prewarm(count int) {
67 if count <= 0 {
68 return
69 }
70
71 ctx := p.factory.engine.ctx
72 log := logging.FromContext(ctx)
73 views := make([]*WebView, 0, count)
74 for range count {
75 wv, err := p.factory.Create(ctx)
76 if err != nil {
77 log.Warn().Err(err).Msg("cef: pool prewarm failed to create WebView")
78 continue
79 }
80 cefWV, ok := wv.(*WebView)
81 if !ok {
82 log.Warn().Str("concrete_type", fmt.Sprintf("%T", wv)).Msg("cef: pool prewarm got unexpected WebView type")
83 wv.Destroy()
84 continue
85 }
86 views = append(views, cefWV)
87 }
88
89 p.mu.Lock()
90 defer p.mu.Unlock()
91 if p.closed {
92 for _, wv := range views {
93 wv.Destroy()
94 }
95 return
96 }
97 p.pool = append(p.pool, views...)
98}
99
100// PrewarmAsync schedules WebView creation. In Phase 1 this calls Prewarm
101// synchronously; a future phase will use the GTK idle loop to avoid blocking.
102// TODO(phase2): schedule creation via glib.IdleAdd for non-blocking startup.
103func (p *WebViewPool) PrewarmAsync(_ context.Context, count int) {
104 p.Prewarm(count)
105}
106
107func (p *WebViewPool) Size() int {
108 p.mu.Lock()
109 defer p.mu.Unlock()
110 return len(p.pool)
111}
112
113// pooledViewBrowserReady reports whether a pooled reserve holds a live
114// browser. Wrapper-only reserves (allocated struct, no OnAfterCreated yet)
115// are never reported browser-ready. Viewport validity and first blank frame
116// require native validation and stay outside this predicate.
117func pooledViewBrowserReady(wv *WebView) bool {
118 if wv == nil || wv.destroyed.Load() {
119 return false
120 }
121 wv.mu.RLock()
122 defer wv.mu.RUnlock()
123 return wv.browser != nil
124}
125
126// Close shuts down the pool and destroys all pooled WebViews.
127func (p *WebViewPool) Close() {
128 p.mu.Lock()
129 if p.closed {
130 p.mu.Unlock()
131 return
132 }
133 p.closed = true
134 views := p.pool
135 p.pool = nil
136 p.mu.Unlock()
137
138 for _, wv := range views {
139 wv.Destroy()
140 }
141}