v0.20.1 d9a391f

factory.go RawHistoryFolder

1package webkit
2
3import (
4	"context"
5	"fmt"
6	"sync"
7
8	"github.com/bnema/dumber/internal/application/port"
9	"github.com/bnema/dumber/internal/logging"
10)
11
12// WebViewFactory creates WebView instances for the application.
13// It supports creating regular WebViews (optionally via pool) and
14// related WebViews that share session/cookies with a parent (for popups).
15type WebViewFactory struct {
16	wkCtx         *WebKitContext
17	settings      *SettingsManager
18	pool          *WebViewPool
19	injector      *ContentInjector
20	router        *MessageRouter
21	filterApplier FilterApplier // Optional content filter applier
22
23	// Background color for WebViews (eliminates white flash)
24	bgR, bgG, bgB, bgA float32
25	bgMu               sync.RWMutex
26}
27
28// NewWebViewFactory creates a new WebViewFactory.
29// The pool parameter is optional; if nil, WebViews are created directly.
30func NewWebViewFactory(
31	wkCtx *WebKitContext,
32	settings *SettingsManager,
33	pool *WebViewPool,
34	injector *ContentInjector,
35	router *MessageRouter,
36) *WebViewFactory {
37	return &WebViewFactory{
38		wkCtx:    wkCtx,
39		settings: settings,
40		pool:     pool,
41		injector: injector,
42		router:   router,
43	}
44}
45
46// SetFilterApplier sets the content filter applier.
47// Filters will be applied to all newly created WebViews.
48func (f *WebViewFactory) SetFilterApplier(applier FilterApplier) {
49	f.filterApplier = applier
50	// Also propagate to the pool if present
51	if f.pool != nil {
52		f.pool.SetFilterApplier(applier)
53	}
54}
55
56// SetBackgroundColor sets the background color for newly created WebViews.
57// This color is shown before content is painted, eliminating white flash.
58func (f *WebViewFactory) SetBackgroundColor(r, g, b, a float32) {
59	f.bgMu.Lock()
60	f.bgR, f.bgG, f.bgB, f.bgA = r, g, b, a
61	f.bgMu.Unlock()
62	// Also propagate to the pool if present
63	if f.pool != nil {
64		f.pool.SetBackgroundColor(r, g, b, a)
65	}
66}
67
68// Create creates a new WebView instance.
69// If a pool is configured, it will try to acquire from the pool first.
70func (f *WebViewFactory) Create(ctx context.Context) (*WebView, error) {
71	log := logging.FromContext(ctx)
72
73	// Try pool first if available
74	if f.pool != nil {
75		wv, err := f.pool.Acquire(ctx)
76		if err == nil && wv != nil {
77			log.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool")
78			return wv, nil
79		}
80		log.Debug().Err(err).Msg("pool acquire failed, creating directly")
81	}
82
83	// Create directly
84	return f.createDirect(ctx)
85}
86
87// CreateRelated creates a WebView that shares session/cookies with the parent.
88// This is required for popup windows to maintain authentication state (OAuth).
89// Related WebViews bypass the pool since they must be linked to a specific parent.
90func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebViewID) (*WebView, error) {
91	log := logging.FromContext(ctx)
92
93	// Look up parent WebView
94	parent := LookupWebView(parentID)
95	if parent == nil {
96		return nil, fmt.Errorf("parent webview %d not found", parentID)
97	}
98	if parent.IsDestroyed() {
99		return nil, fmt.Errorf("parent webview %d is destroyed", parentID)
100	}
101
102	// Create related WebView
103	wv, err := NewWebViewWithRelated(ctx, parent, f.settings)
104	if err != nil {
105		return nil, fmt.Errorf("create related webview: %w", err)
106	}
107
108	// Set background color to match theme (eliminates white flash)
109	f.bgMu.RLock()
110	r, g, b, a := f.bgR, f.bgG, f.bgB, f.bgA
111	f.bgMu.RUnlock()
112	if a > 0 {
113		wv.SetBackgroundColor(r, g, b, a)
114	}
115
116	// Keep hidden until content is painted
117	wv.inner.SetVisible(false)
118
119	// Attach frontend (scripts, message handler)
120	if err := wv.AttachFrontend(ctx, f.injector, f.router); err != nil {
121		log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to related webview")
122	}
123
124	// Apply content filters if configured
125	if f.filterApplier != nil {
126		f.filterApplier.ApplyTo(ctx, wv.ucm)
127	}
128
129	log.Debug().
130		Uint64("id", uint64(wv.ID())).
131		Uint64("parent_id", uint64(parentID)).
132		Msg("created related webview for popup")
133
134	return wv, nil
135}
136
137// createDirect creates a WebView without using the pool.
138func (f *WebViewFactory) createDirect(ctx context.Context) (*WebView, error) {
139	log := logging.FromContext(ctx)
140
141	wv, err := NewWebView(ctx, f.wkCtx, f.settings)
142	if err != nil {
143		return nil, err
144	}
145
146	// Set background color to match theme (eliminates white flash)
147	f.bgMu.RLock()
148	r, g, b, a := f.bgR, f.bgG, f.bgB, f.bgA
149	f.bgMu.RUnlock()
150	if a > 0 {
151		wv.SetBackgroundColor(r, g, b, a)
152	}
153
154	// Keep hidden until content is painted
155	wv.inner.SetVisible(false)
156
157	// Attach frontend
158	if err := wv.AttachFrontend(ctx, f.injector, f.router); err != nil {
159		log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to webview")
160	}
161
162	// Apply content filters if configured
163	if f.filterApplier != nil {
164		f.filterApplier.ApplyTo(ctx, wv.ucm)
165	}
166
167	log.Debug().Uint64("id", uint64(wv.ID())).Msg("created webview directly")
168	return wv, nil
169}
170
171// Release returns a WebView to the pool if available, otherwise destroys it.
172// Related WebViews (popups) should be destroyed directly, not released to pool.
173func (f *WebViewFactory) Release(ctx context.Context, wv *WebView) {
174	if wv == nil || wv.IsDestroyed() {
175		return
176	}
177
178	if f.pool != nil {
179		f.pool.Release(ctx, wv)
180	} else {
181		wv.Destroy()
182	}
183}

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