v0.23.2 bfd5eb4

context.go RawHistoryFolder

1package webkit
2
3import (
4	"context"
5	"fmt"
6	"path/filepath"
7	"sync"
8
9	"github.com/bnema/dumber/internal/application/port"
10	"github.com/bnema/dumber/internal/logging"
11	"github.com/bnema/puregotk-webkit/webkit"
12	"github.com/rs/zerolog"
13)
14
15// FaviconDatabase is a type alias for webkit.FaviconDatabase.
16// Re-exported for use by UI layer without direct puregotk-webkit import.
17type FaviconDatabase = webkit.FaviconDatabase
18
19// WebKitContext manages the shared WebContext and persistent NetworkSession.
20// IMPORTANT: This MUST be initialized before creating any WebViews.
21type WebKitContext struct {
22	webContext     *webkit.WebContext
23	networkSession *webkit.NetworkSession
24	faviconDB      *webkit.FaviconDatabase
25
26	dataDir  string
27	cacheDir string
28
29	logger      zerolog.Logger
30	mu          sync.RWMutex
31	initialized bool
32}
33
34// NewWebKitContext creates and initializes a WebKitContext with a persistent NetworkSession.
35// The dataDir and cacheDir are used for cookie storage, cache, and other persistent data.
36// This MUST be called before creating any WebViews to ensure they use persistent storage.
37func NewWebKitContext(ctx context.Context, dataDir, cacheDir string) (*WebKitContext, error) {
38	return NewWebKitContextWithOptions(ctx, port.WebKitContextOptions{
39		DataDir:  dataDir,
40		CacheDir: cacheDir,
41	})
42}
43
44// NewWebKitContextWithOptions creates and initializes a WebKitContext with the given options.
45// This allows configuring memory pressure settings for web and network processes.
46func NewWebKitContextWithOptions(ctx context.Context, opts port.WebKitContextOptions) (*WebKitContext, error) {
47	log := logging.FromContext(ctx).With().Str("component", "webkit-context").Logger()
48
49	if opts.DataDir == "" {
50		return nil, fmt.Errorf("data directory cannot be empty")
51	}
52	if opts.CacheDir == "" {
53		return nil, fmt.Errorf("cache directory cannot be empty")
54	}
55
56	wkCtx := &WebKitContext{
57		dataDir:  opts.DataDir,
58		cacheDir: opts.CacheDir,
59		logger:   log,
60	}
61
62	// Apply network process memory pressure settings BEFORE creating session
63	// Per WebKitGTK docs: must be called before creating any NetworkSession
64	if opts.IsNetworkProcessMemoryConfigured() {
65		applier := NewMemoryPressureApplier()
66		if err := applier.ApplyNetworkProcessSettings(ctx, opts.NetworkProcessMemory); err != nil {
67			log.Warn().Err(err).Msg("failed to apply network process memory pressure settings")
68		}
69	}
70
71	// Create persistent network session FIRST
72	// Per WebKitGTK 6.0 docs: "The first WebKitNetworkSession created becomes the default"
73	if err := wkCtx.initNetworkSession(); err != nil {
74		return nil, fmt.Errorf("failed to init network session: %w", err)
75	}
76
77	// Create WebContext - use custom constructor if memory pressure settings are configured
78	if opts.IsWebProcessMemoryConfigured() {
79		memSettings := buildMemoryPressureSettings(opts.WebProcessMemory)
80		if memSettings != nil {
81			wkCtx.webContext = webkit.NewWebContextWithMemoryPressureSettings(memSettings)
82			if wkCtx.webContext != nil {
83				log.Info().
84					Int("limit_mb", opts.WebProcessMemory.MemoryLimitMB).
85					Float64("poll_sec", opts.WebProcessMemory.PollIntervalSec).
86					Float64("conservative", opts.WebProcessMemory.ConservativeThreshold).
87					Float64("strict", opts.WebProcessMemory.StrictThreshold).
88					Msg("created WebContext with memory pressure settings")
89			}
90		}
91	}
92
93	// Fall back to default WebContext if custom creation failed or wasn't needed
94	if wkCtx.webContext == nil {
95		wkCtx.webContext = webkit.WebContextGetDefault()
96	}
97	if wkCtx.webContext == nil {
98		return nil, fmt.Errorf("failed to create or get WebContext")
99	}
100
101	// Set cache model for browser-style caching
102	wkCtx.webContext.SetCacheModel(webkit.CacheModelWebBrowserValue)
103
104	wkCtx.initialized = true
105	log.Info().
106		Str("data_dir", opts.DataDir).
107		Str("cache_dir", opts.CacheDir).
108		Msg("webkit context initialized")
109
110	return wkCtx, nil
111}
112
113// initNetworkSession creates and configures the persistent network session.
114func (c *WebKitContext) initNetworkSession() error {
115	// Create persistent network session
116	session := webkit.NewNetworkSession(&c.dataDir, &c.cacheDir)
117	if session == nil {
118		return fmt.Errorf("failed to create network session")
119	}
120
121	// Verify session is not ephemeral
122	if session.IsEphemeral() {
123		return fmt.Errorf("network session is ephemeral despite providing data directories")
124	}
125
126	// Verify WebsiteDataManager is not ephemeral
127	dataManager := session.GetWebsiteDataManager()
128	if dataManager == nil {
129		return fmt.Errorf("failed to get website data manager")
130	}
131	if dataManager.IsEphemeral() {
132		return fmt.Errorf("website data manager is ephemeral")
133	}
134
135	// Debug: verify the directories WebKit is actually using
136	actualDataDir := dataManager.GetBaseDataDirectory()
137	actualCacheDir := dataManager.GetBaseCacheDirectory()
138	c.logger.Info().
139		Str("expected_data_dir", c.dataDir).
140		Str("actual_data_dir", actualDataDir).
141		Str("expected_cache_dir", c.cacheDir).
142		Str("actual_cache_dir", actualCacheDir).
143		Msg("webkit directories configured")
144
145	// Enable favicon collection and get database reference
146	dataManager.SetFaviconsEnabled(true)
147	c.faviconDB = dataManager.GetFaviconDatabase()
148	if c.faviconDB != nil {
149		c.logger.Debug().Msg("favicon database enabled")
150	}
151
152	// Configure cookie storage
153	cookieManager := session.GetCookieManager()
154	if cookieManager == nil {
155		return fmt.Errorf("failed to get cookie manager")
156	}
157
158	cookiePath := filepath.Join(c.dataDir, "cookies.db")
159	cookieManager.SetPersistentStorage(cookiePath, webkit.CookiePersistentStorageSqliteValue)
160	cookieManager.SetAcceptPolicy(webkit.CookiePolicyAcceptNoThirdPartyValue)
161
162	c.logger.Debug().
163		Str("cookie_path", cookiePath).
164		Msg("cookie storage configured")
165
166	// Enable persistent credential storage
167	session.SetPersistentCredentialStorageEnabled(true)
168
169	// Configure TLS errors to emit signals (enables load-failed-with-tls-errors)
170	session.SetTlsErrorsPolicy(webkit.TlsErrorsPolicyFailValue)
171
172	// Verify default session
173	defaultSession := webkit.NetworkSessionGetDefault()
174	if defaultSession == nil || defaultSession.IsEphemeral() {
175		c.logger.Warn().Msg("default session may not be persistent - created session might not be used by default")
176	}
177
178	c.networkSession = session
179	c.logger.Debug().Msg("network session initialized as persistent")
180
181	return nil
182}
183
184// Context returns the shared WebContext.
185func (c *WebKitContext) Context() *webkit.WebContext {
186	c.mu.RLock()
187	defer c.mu.RUnlock()
188	return c.webContext
189}
190
191// NetworkSession returns the persistent NetworkSession.
192func (c *WebKitContext) NetworkSession() *webkit.NetworkSession {
193	c.mu.RLock()
194	defer c.mu.RUnlock()
195	return c.networkSession
196}
197
198// FaviconDatabase returns the favicon database for persistent favicon storage.
199func (c *WebKitContext) FaviconDatabase() *webkit.FaviconDatabase {
200	c.mu.RLock()
201	defer c.mu.RUnlock()
202	return c.faviconDB
203}
204
205// DataDir returns the data directory path.
206func (c *WebKitContext) DataDir() string {
207	return c.dataDir
208}
209
210// CacheDir returns the cache directory path.
211func (c *WebKitContext) CacheDir() string {
212	return c.cacheDir
213}
214
215// IsInitialized returns true if the context has been successfully initialized.
216func (c *WebKitContext) IsInitialized() bool {
217	c.mu.RLock()
218	defer c.mu.RUnlock()
219	return c.initialized
220}
221
222// PrefetchDNS prefetches DNS for the given hostname to speed up future requests.
223func (c *WebKitContext) PrefetchDNS(hostname string) {
224	if hostname == "" || c.networkSession == nil {
225		return
226	}
227	c.networkSession.PrefetchDns(hostname)
228}
229
230// Close performs cleanup. Currently a no-op as WebKit handles cleanup internally.
231func (c *WebKitContext) Close() error {
232	c.mu.Lock()
233	defer c.mu.Unlock()
234
235	c.initialized = false
236	c.logger.Debug().Msg("webkit context closed")
237	return nil
238}

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