v0.23.2 bfd5eb4

webkit_stack.go RawHistoryFolder

1package bootstrap
2
3import (
4	"context"
5	"path/filepath"
6
7	"github.com/bnema/dumber/assets"
8	"github.com/bnema/dumber/internal/application/port"
9	"github.com/bnema/dumber/internal/application/usecase"
10	"github.com/bnema/dumber/internal/infrastructure/config"
11	"github.com/bnema/dumber/internal/infrastructure/env"
12	"github.com/bnema/dumber/internal/infrastructure/filtering"
13	"github.com/bnema/dumber/internal/infrastructure/webkit"
14	"github.com/bnema/dumber/internal/ui/theme"
15	"github.com/rs/zerolog"
16)
17
18// WebKitStackInput holds the input for BuildWebKitStack.
19type WebKitStackInput struct {
20	Ctx           context.Context
21	Config        *config.Config
22	DataDir       string
23	CacheDir      string
24	ThemeManager  *theme.Manager
25	ColorResolver port.ColorSchemeResolver
26	Logger        zerolog.Logger
27}
28
29type WebKitStack struct {
30	Context       *webkit.WebKitContext
31	Settings      *webkit.SettingsManager
32	Injector      *webkit.ContentInjector
33	MessageRouter *webkit.MessageRouter
34	Pool          *webkit.WebViewPool
35	FilterManager *filtering.Manager
36}
37
38func BuildWebKitStack(input WebKitStackInput) WebKitStack {
39	ctx := input.Ctx
40	cfg := input.Config
41	dataDir := input.DataDir
42	cacheDir := input.CacheDir
43	themeManager := input.ThemeManager
44	colorResolver := input.ColorResolver
45	logger := input.Logger
46	// Detect hardware for performance profile scaling
47	hwSurveyor := env.NewHardwareSurveyor()
48	hwInfo := hwSurveyor.Survey(ctx)
49	logger.Info().
50		Int("cpu_cores", hwInfo.CPUCores).
51		Int("cpu_threads", hwInfo.CPUThreads).
52		Uint64("ram_mb", hwInfo.TotalRAM/(1024*1024)).
53		Str("gpu_vendor", string(hwInfo.GPUVendor)).
54		Uint64("vram_mb", hwInfo.VRAM/(1024*1024)).
55		Msg("hardware survey completed")
56
57	// Resolve performance profile to get actual settings
58	perfSettings := config.ResolvePerformanceProfile(&cfg.Performance, &hwInfo)
59	logger.Info().
60		Str("profile", string(cfg.Performance.Profile)).
61		Int("skia_cpu_threads", perfSettings.SkiaCPUPaintingThreads).
62		Int("skia_gpu_threads", perfSettings.SkiaGPUPaintingThreads).
63		Int("webview_pool_prewarm", perfSettings.WebViewPoolPrewarmCount).
64		Msg("resolved performance profile")
65
66	// Configure rendering environment (must happen before WebKit/GTK init)
67	configureRenderingEnvironment(ctx, cfg, &perfSettings, logger)
68
69	// Build WebKitContext options with memory pressure settings
70	wkOpts := port.WebKitContextOptions{
71		DataDir:  dataDir,
72		CacheDir: cacheDir,
73	}
74
75	// Configure web process memory pressure if any setting is configured
76	if hasWebProcessMemoryConfig(&perfSettings) {
77		wkOpts.WebProcessMemory = &port.MemoryPressureConfig{
78			MemoryLimitMB:         perfSettings.WebProcessMemoryLimitMB,
79			PollIntervalSec:       perfSettings.WebProcessMemoryPollIntervalSec,
80			ConservativeThreshold: perfSettings.WebProcessMemoryConservativeThreshold,
81			StrictThreshold:       perfSettings.WebProcessMemoryStrictThreshold,
82		}
83	}
84
85	// Configure network process memory pressure if any setting is configured
86	if hasNetworkProcessMemoryConfig(&perfSettings) {
87		wkOpts.NetworkProcessMemory = &port.MemoryPressureConfig{
88			MemoryLimitMB:         perfSettings.NetworkProcessMemoryLimitMB,
89			PollIntervalSec:       perfSettings.NetworkProcessMemoryPollIntervalSec,
90			ConservativeThreshold: perfSettings.NetworkProcessMemoryConservativeThreshold,
91			StrictThreshold:       perfSettings.NetworkProcessMemoryStrictThreshold,
92		}
93	}
94
95	wkCtx, err := webkit.NewWebKitContextWithOptions(ctx, wkOpts)
96	if err != nil {
97		logger.Fatal().Err(err).Msg("failed to initialize WebKit context")
98	}
99
100	filterStoreDir := filepath.Join(dataDir, "filters", "store")
101	filterJSONDir := filepath.Join(dataDir, "filters", "json")
102	filterManager, err := filtering.NewManager(filtering.ManagerConfig{
103		StoreDir:   filterStoreDir,
104		JSONDir:    filterJSONDir,
105		Enabled:    cfg.ContentFiltering.Enabled,
106		AutoUpdate: cfg.ContentFiltering.AutoUpdate,
107	})
108	if err != nil {
109		logger.Warn().Err(err).Msg("failed to create filter manager, continuing without content filtering")
110	} else {
111		if err := filterManager.Initialize(ctx); err != nil {
112			logger.Warn().Err(err).Msg("failed to initialize filters, will load async")
113		}
114	}
115
116	schemeHandler := webkit.NewDumbSchemeHandler(ctx)
117	schemeHandler.SetAssets(assets.WebUIAssets)
118	schemeHandler.RegisterWithContext(wkCtx)
119
120	settings := webkit.NewSettingsManager(ctx, cfg)
121	injector := webkit.NewContentInjector(colorResolver)
122
123	prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector)
124	themeCSSText := themeManager.GetWebUIThemeCSS()
125	if err := prepareThemeUC.Execute(ctx, usecase.PrepareWebUIThemeInput{CSSVars: themeCSSText}); err != nil {
126		logger.Warn().Err(err).Msg("failed to prepare WebUI theme CSS")
127	}
128
129	messageRouter := webkit.NewMessageRouter(ctx)
130	poolCfg := webkit.DefaultPoolConfig()
131	// Override prewarm count from resolved profile settings
132	if perfSettings.WebViewPoolPrewarmCount > 0 {
133		poolCfg.PrewarmCount = perfSettings.WebViewPoolPrewarmCount
134	}
135	pool := webkit.NewWebViewPool(ctx, wkCtx, settings, poolCfg, injector, messageRouter)
136	// Ensure prewarmed WebViews pick up the theme background color.
137	bgR, bgG, bgB, bgA := themeManager.GetBackgroundRGBA()
138	pool.SetBackgroundColor(bgR, bgG, bgB, bgA)
139
140	if filterManager != nil {
141		pool.SetFilterApplier(filterManager)
142	}
143
144	// Pre-create ONE WebView synchronously for instant first navigation.
145	// This is the heaviest operation but happens before window is shown,
146	// so users perceive it as part of the normal "loading" phase.
147	if err := pool.PrewarmFirst(ctx); err != nil {
148		logger.Warn().Err(err).Msg("failed to prewarm first webview, first tab may be slower")
149	}
150
151	return WebKitStack{
152		Context:       wkCtx,
153		Settings:      settings,
154		Injector:      injector,
155		MessageRouter: messageRouter,
156		Pool:          pool,
157		FilterManager: filterManager,
158	}
159}
160
161// hasWebProcessMemoryConfig returns true if any web process memory setting is configured.
162func hasWebProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool {
163	return p.WebProcessMemoryLimitMB > 0 ||
164		p.WebProcessMemoryPollIntervalSec > 0 ||
165		p.WebProcessMemoryConservativeThreshold > 0 ||
166		p.WebProcessMemoryStrictThreshold > 0
167}
168
169// hasNetworkProcessMemoryConfig returns true if any network process memory setting is configured.
170func hasNetworkProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool {
171	return p.NetworkProcessMemoryLimitMB > 0 ||
172		p.NetworkProcessMemoryPollIntervalSec > 0 ||
173		p.NetworkProcessMemoryConservativeThreshold > 0 ||
174		p.NetworkProcessMemoryStrictThreshold > 0
175}
176
177// configureRenderingEnvironment sets up the rendering environment before WebKit/GTK initialization.
178// Environment variables must be set before GTK/WebKit/GStreamer initializes.
179func configureRenderingEnvironment(
180	ctx context.Context,
181	cfg *config.Config,
182	perfSettings *config.ResolvedPerformanceSettings,
183	logger zerolog.Logger,
184) {
185	renderEnv := env.NewManager()
186	gpuVendor := renderEnv.DetectGPUVendor(ctx)
187	renderSettings := port.RenderingEnvSettings{
188		// GStreamer settings
189		ForceVSync:          cfg.Media.ForceVSync,
190		GLRenderingMode:     string(cfg.Media.GLRenderingMode),
191		GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel,
192		VideoBufferSizeMB:   cfg.Media.VideoBufferSizeMB,
193		QueueBufferTimeSec:  cfg.Media.QueueBufferTimeSec,
194
195		// WebKit compositor settings
196		DisableDMABufRenderer:  cfg.Rendering.DisableDMABufRenderer,
197		ForceCompositingMode:   cfg.Rendering.ForceCompositingMode,
198		DisableCompositingMode: cfg.Rendering.DisableCompositingMode,
199
200		// GTK/GSK settings
201		GSKRenderer:    string(cfg.Rendering.GSKRenderer),
202		DisableMipmaps: cfg.Rendering.DisableMipmaps,
203		PreferGL:       cfg.Rendering.PreferGL,
204
205		// Debug settings
206		ShowFPS:      cfg.Rendering.ShowFPS,
207		SampleMemory: cfg.Rendering.SampleMemory,
208		DebugFrames:  cfg.Rendering.DebugFrames,
209
210		// Skia rendering thread settings (from resolved profile)
211		SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads,
212		SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads,
213		SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering,
214	}
215	if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil {
216		logger.Warn().Err(err).Msg("failed to apply rendering environment")
217	}
218	logger.Info().
219		Str("gpu", string(gpuVendor)).
220		Interface("vars", renderEnv.GetAppliedVars()).
221		Msg("rendering environment configured")
222}

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