v0.26.1 75cd777

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

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