v0.20.0 c741a39

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
23// NewWebViewFactory creates a new WebViewFactory.
24// The pool parameter is optional; if nil, WebViews are created directly.
25func NewWebViewFactory(
26	wkCtx *WebKitContext,
27	settings *SettingsManager,
28	pool *WebViewPool,
29	injector *ContentInjector,
30	router *MessageRouter,
31) *WebViewFactory {
32	return &WebViewFactory{
33		wkCtx:    wkCtx,
34		settings: settings,
35		pool:     pool,
36		injector: injector,
37		router:   router,
38	}
39}
40
41// SetFilterApplier sets the content filter applier.
42// Filters will be applied to all newly created WebViews.
43func (f *WebViewFactory) SetFilterApplier(applier FilterApplier) {
44	f.filterApplier = applier
45	// Also propagate to the pool if present
46	if f.pool != nil {
47		f.pool.SetFilterApplier(applier)
48	}
49}
50
51// Create creates a new WebView instance.
52// If a pool is configured, it will try to acquire from the pool first.
53func (f *WebViewFactory) Create(ctx context.Context) (*WebView, error) {
54	log := logging.FromContext(ctx)
55
56	// Try pool first if available
57	if f.pool != nil {
58		wv, err := f.pool.Acquire(ctx)
59		if err == nil && wv != nil {
60			log.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool")
61			return wv, nil
62		}
63		log.Debug().Err(err).Msg("pool acquire failed, creating directly")
64	}
65
66	// Create directly
67	return f.createDirect(ctx)
68}
69
70// CreateRelated creates a WebView that shares session/cookies with the parent.
71// This is required for popup windows to maintain authentication state (OAuth).
72// Related WebViews bypass the pool since they must be linked to a specific parent.
73func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebViewID) (*WebView, error) {
74	log := logging.FromContext(ctx)
75
76	// Look up parent WebView
77	parent := LookupWebView(parentID)
78	if parent == nil {
79		return nil, fmt.Errorf("parent webview %d not found", parentID)
80	}
81	if parent.IsDestroyed() {
82		return nil, fmt.Errorf("parent webview %d is destroyed", parentID)
83	}
84
85	// Create related WebView
86	wv, err := NewWebViewWithRelated(ctx, parent, f.settings)
87	if err != nil {
88		return nil, fmt.Errorf("create related webview: %w", err)
89	}
90
91	// Ensure visible
92	wv.inner.SetVisible(true)
93
94	// Attach frontend (scripts, message handler)
95	if err := wv.AttachFrontend(ctx, f.injector, f.router); err != nil {
96		log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to related webview")
97	}
98
99	// Apply content filters if configured
100	if f.filterApplier != nil {
101		f.filterApplier.ApplyTo(ctx, wv.ucm)
102	}
103
104	log.Debug().
105		Uint64("id", uint64(wv.ID())).
106		Uint64("parent_id", uint64(parentID)).
107		Msg("created related webview for popup")
108
109	return wv, nil
110}
111
112// createDirect creates a WebView without using the pool.
113func (f *WebViewFactory) createDirect(ctx context.Context) (*WebView, error) {
114	log := logging.FromContext(ctx)
115
116	wv, err := NewWebView(ctx, f.wkCtx, f.settings)
117	if err != nil {
118		return nil, err
119	}
120
121	// Ensure visible
122	wv.inner.SetVisible(true)
123
124	// Attach frontend
125	if err := wv.AttachFrontend(ctx, f.injector, f.router); err != nil {
126		log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to webview")
127	}
128
129	// Apply content filters if configured
130	if f.filterApplier != nil {
131		f.filterApplier.ApplyTo(ctx, wv.ucm)
132	}
133
134	log.Debug().Uint64("id", uint64(wv.ID())).Msg("created webview directly")
135	return wv, nil
136}
137
138// Release returns a WebView to the pool if available, otherwise destroys it.
139// Related WebViews (popups) should be destroyed directly, not released to pool.
140func (f *WebViewFactory) Release(ctx context.Context, wv *WebView) {
141	if wv == nil || wv.IsDestroyed() {
142		return
143	}
144
145	if f.pool != nil {
146		f.pool.Release(ctx, wv)
147	} else {
148		wv.Destroy()
149	}
150}

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