v0.22.0 a85da87

factory.go RawHistoryFolder

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

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