detached 4f0e3ae

refactor(bootstrap): replace WebKitStack with Engine interface

- Add temporary internal accessors to webkit.Engine (InternalContext,
  InternalPool, InternalSettings, InternalInjector, InternalMessageRouter,
  InternalFilterManager) as M3->M4 bridge until ui.Dependencies is updated
- Create BuildEngine() dispatch function in bootstrap/engine.go
- Rename ParallelDBWebKit* -> ParallelDBEngine* in gui.go and wire to BuildEngine
- Update main.go: initStackAndRepos returns port.Engine, buildAndConfigureApp
  and buildUIDependencies take port.Engine, type-assert to *webkit.Engine for
  concrete field access
- Delete webkit_stack.go
commit
4f0e3ae5f1f50f0a34d3506c17ece4e2485e0296
author
brice · · 2026-03-15 16:31 UTC
parent
324f8f3
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +110 -333
M cmd/dumber/main.go +31 -21
@@ -25,6 +25,7 @@ import (
2525 "github.com/bnema/dumber/internal/infrastructure/idle"
2626 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
2727 "github.com/bnema/dumber/internal/infrastructure/updater"
28+ "github.com/bnema/dumber/internal/infrastructure/webkit"
2829 "github.com/bnema/dumber/internal/infrastructure/xdg"
2930 "github.com/bnema/dumber/internal/logging"
3031 "github.com/bnema/dumber/internal/ui"
@@ -94,7 +95,7 @@ func runGUI() int {
9495
9596 needsEagerDB := restoreSessionID != "" || cfg.Session.AutoRestore
9697
97- stack, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, needsEagerDB)
98+ engine, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, needsEagerDB)
9899 if err != nil {
99100 bootstrapLog.Fatal().Err(err).Msg("failed to initialize database/webkit")
100101 }
@@ -111,8 +112,10 @@ func runGUI() int {
111112 logging.Trace().UpdateLogger(log)
112113 logCoreDumpLimits(ctx)
113114
114- if stack.MessageRouter != nil {
115- stack.MessageRouter.SetBaseContext(ctx)
115+ if wkEngine, ok := engine.(*webkit.Engine); ok {
116+ if mr := wkEngine.InternalMessageRouter(); mr != nil {
117+ mr.SetBaseContext(ctx)
118+ }
116119 }
117120
118121 useCases := createUseCases(repos, cfg)
@@ -124,7 +127,7 @@ func runGUI() int {
124127 defer closeIdleInhibitor(idleInhibitor)
125128 timer.Mark("use_cases")
126129
127- app, err := buildAndConfigureApp(ctx, cfg, initResult, &stack, repos, useCases, idleInhibitor, browserSession)
130+ app, err := buildAndConfigureApp(ctx, cfg, initResult, engine, repos, useCases, idleInhibitor, browserSession)
128131 if err != nil {
129132 log.Error().Err(err).Msg("failed to create application")
130133 return 1
@@ -194,7 +197,7 @@ func buildAndConfigureApp(
194197 ctx context.Context,
195198 cfg *config.Config,
196199 initResult *bootstrap.ParallelInitResult,
197- stack *bootstrap.WebKitStack,
200+ engine port.Engine,
198201 repos *repositories,
199202 useCases *useCases,
200203 idleInhibitor port.IdleInhibitor,
@@ -203,7 +206,7 @@ func buildAndConfigureApp(
203206 uiDeps := buildUIDependencies(
204207 ctx, cfg, initResult.ThemeManager,
205208 initResult.ColorResolver, initResult.AdwaitaDetector,
206- stack, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.UnexpectedCloseReports(),
209+ engine, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.UnexpectedCloseReports(),
207210 )
208211 configureDeferredInit(uiDeps, cfg, browserSession)
209212 return ui.New(uiDeps)
@@ -230,10 +233,10 @@ func initStackAndRepos(
230233 cfg *config.Config,
231234 initResult *bootstrap.ParallelInitResult,
232235 needsEagerDB bool,
233-) (bootstrap.WebKitStack, *repositories, func(), error) {
236+) (port.Engine, *repositories, func(), error) {
234237 if needsEagerDB {
235- // Parallel phase 2: Database + WebKit stack initialize concurrently
236- dbWebKit, err := bootstrap.RunParallelDBWebKit(bootstrap.ParallelDBWebKitInput{
238+ // Parallel phase 2: Database + engine initialize concurrently
239+ dbEngine, err := bootstrap.RunParallelDBEngine(bootstrap.ParallelDBEngineInput{
237240 Ctx: ctx,
238241 Config: cfg,
239242 DataDir: initResult.DataDir,
@@ -242,13 +245,13 @@ func initStackAndRepos(
242245 ColorResolver: initResult.ColorResolver,
243246 })
244247 if err != nil {
245- return bootstrap.WebKitStack{}, nil, nil, err
248+ return nil, nil, nil, err
246249 }
247- return dbWebKit.Stack, createRepositories(dbWebKit.DB), dbWebKit.DBCleanup, nil
250+ return dbEngine.Engine, createRepositories(dbEngine.DB), dbEngine.DBCleanup, nil
248251 }
249252
250253 log := logging.FromContext(ctx)
251- stack := bootstrap.BuildWebKitStack(bootstrap.WebKitStackInput{
254+ engine, err := bootstrap.BuildEngine(bootstrap.EngineInput{
252255 Ctx: ctx,
253256 Config: cfg,
254257 DataDir: initResult.DataDir,
@@ -257,13 +260,16 @@ func initStackAndRepos(
257260 ColorResolver: initResult.ColorResolver,
258261 Logger: *log,
259262 })
263+ if err != nil {
264+ return nil, nil, nil, err
265+ }
260266
261267 lazyDB, err := bootstrap.CreateLazyDatabase()
262268 if err != nil {
263- return stack, nil, nil, err
269+ return nil, nil, nil, err
264270 }
265271 dbCleanup := func() { _ = lazyDB.Close() }
266- return stack, createLazyRepositories(lazyDB), dbCleanup, nil
272+ return engine, createLazyRepositories(lazyDB), dbCleanup, nil
267273 }
268274
269275 func configureDeferredInit(
@@ -501,13 +507,17 @@ func buildUIDependencies(
501507 themeManager *theme.Manager,
502508 colorResolver port.ColorSchemeResolver,
503509 adwaitaDetector *colorscheme.AdwaitaDetector,
504- stack *bootstrap.WebKitStack,
510+ engine port.Engine,
505511 repos *repositories,
506512 uc *useCases,
507513 idleInhibitor port.IdleInhibitor,
508514 currentSessionID entity.SessionID,
509515 startupCrashReports []string,
510516 ) *ui.Dependencies {
517+ // Type-assert to *webkit.Engine to access concrete types for ui.Dependencies.
518+ // This bridge will be removed when ui.Dependencies is updated to use port.Engine (M4).
519+ wkEngine := engine.(*webkit.Engine)
520+
511521 return &ui.Dependencies{
512522 Ctx: ctx,
513523 Config: cfg,
@@ -518,12 +528,12 @@ func buildUIDependencies(
518528 ColorResolver: colorResolver,
519529 AdwaitaDetector: adwaitaDetector,
520530 XDG: xdg.New(),
521- WebContext: stack.Context,
522- Pool: stack.Pool,
523- Settings: stack.Settings,
524- Injector: stack.Injector,
525- MessageRouter: stack.MessageRouter,
526- FilterManager: stack.FilterManager,
531+ WebContext: wkEngine.InternalContext(),
532+ Pool: wkEngine.InternalPool(),
533+ Settings: wkEngine.InternalSettings(),
534+ Injector: wkEngine.InternalInjector(),
535+ MessageRouter: wkEngine.InternalMessageRouter(),
536+ FilterManager: wkEngine.InternalFilterManager(),
527537 HistoryRepo: repos.history,
528538 FavoriteRepo: repos.favorite,
529539 ZoomRepo: repos.zoom,
@@ -25,6 +25,7 @@ import (
25 "github.com/bnema/dumber/internal/infrastructure/idle"25 "github.com/bnema/dumber/internal/infrastructure/idle"
26 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"26 "github.com/bnema/dumber/internal/infrastructure/persistence/sqlite"
27 "github.com/bnema/dumber/internal/infrastructure/updater"27 "github.com/bnema/dumber/internal/infrastructure/updater"
28+ "github.com/bnema/dumber/internal/infrastructure/webkit"
28 "github.com/bnema/dumber/internal/infrastructure/xdg"29 "github.com/bnema/dumber/internal/infrastructure/xdg"
29 "github.com/bnema/dumber/internal/logging"30 "github.com/bnema/dumber/internal/logging"
30 "github.com/bnema/dumber/internal/ui"31 "github.com/bnema/dumber/internal/ui"
@@ -94,7 +95,7 @@ func runGUI() int {
94 95
95 needsEagerDB := restoreSessionID != "" || cfg.Session.AutoRestore96 needsEagerDB := restoreSessionID != "" || cfg.Session.AutoRestore
96 97
97- stack, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, needsEagerDB)98+ engine, repos, dbCleanup, err := initStackAndRepos(ctx, cfg, initResult, needsEagerDB)
98 if err != nil {99 if err != nil {
99 bootstrapLog.Fatal().Err(err).Msg("failed to initialize database/webkit")100 bootstrapLog.Fatal().Err(err).Msg("failed to initialize database/webkit")
100 }101 }
@@ -111,8 +112,10 @@ func runGUI() int {
111 logging.Trace().UpdateLogger(log)112 logging.Trace().UpdateLogger(log)
112 logCoreDumpLimits(ctx)113 logCoreDumpLimits(ctx)
113 114
114- if stack.MessageRouter != nil {115+ if wkEngine, ok := engine.(*webkit.Engine); ok {
115- stack.MessageRouter.SetBaseContext(ctx)116+ if mr := wkEngine.InternalMessageRouter(); mr != nil {
117+ mr.SetBaseContext(ctx)
118+ }
116 }119 }
117 120
118 useCases := createUseCases(repos, cfg)121 useCases := createUseCases(repos, cfg)
@@ -124,7 +127,7 @@ func runGUI() int {
124 defer closeIdleInhibitor(idleInhibitor)127 defer closeIdleInhibitor(idleInhibitor)
125 timer.Mark("use_cases")128 timer.Mark("use_cases")
126 129
127- app, err := buildAndConfigureApp(ctx, cfg, initResult, &stack, repos, useCases, idleInhibitor, browserSession)130+ app, err := buildAndConfigureApp(ctx, cfg, initResult, engine, repos, useCases, idleInhibitor, browserSession)
128 if err != nil {131 if err != nil {
129 log.Error().Err(err).Msg("failed to create application")132 log.Error().Err(err).Msg("failed to create application")
130 return 1133 return 1
@@ -194,7 +197,7 @@ func buildAndConfigureApp(
194 ctx context.Context,197 ctx context.Context,
195 cfg *config.Config,198 cfg *config.Config,
196 initResult *bootstrap.ParallelInitResult,199 initResult *bootstrap.ParallelInitResult,
197- stack *bootstrap.WebKitStack,200+ engine port.Engine,
198 repos *repositories,201 repos *repositories,
199 useCases *useCases,202 useCases *useCases,
200 idleInhibitor port.IdleInhibitor,203 idleInhibitor port.IdleInhibitor,
@@ -203,7 +206,7 @@ func buildAndConfigureApp(
203 uiDeps := buildUIDependencies(206 uiDeps := buildUIDependencies(
204 ctx, cfg, initResult.ThemeManager,207 ctx, cfg, initResult.ThemeManager,
205 initResult.ColorResolver, initResult.AdwaitaDetector,208 initResult.ColorResolver, initResult.AdwaitaDetector,
206- stack, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.UnexpectedCloseReports(),209+ engine, repos, useCases, idleInhibitor, browserSession.Session.ID, browserSession.UnexpectedCloseReports(),
207 )210 )
208 configureDeferredInit(uiDeps, cfg, browserSession)211 configureDeferredInit(uiDeps, cfg, browserSession)
209 return ui.New(uiDeps)212 return ui.New(uiDeps)
@@ -230,10 +233,10 @@ func initStackAndRepos(
230 cfg *config.Config,233 cfg *config.Config,
231 initResult *bootstrap.ParallelInitResult,234 initResult *bootstrap.ParallelInitResult,
232 needsEagerDB bool,235 needsEagerDB bool,
233-) (bootstrap.WebKitStack, *repositories, func(), error) {236+) (port.Engine, *repositories, func(), error) {
234 if needsEagerDB {237 if needsEagerDB {
235- // Parallel phase 2: Database + WebKit stack initialize concurrently238+ // Parallel phase 2: Database + engine initialize concurrently
236- dbWebKit, err := bootstrap.RunParallelDBWebKit(bootstrap.ParallelDBWebKitInput{239+ dbEngine, err := bootstrap.RunParallelDBEngine(bootstrap.ParallelDBEngineInput{
237 Ctx: ctx,240 Ctx: ctx,
238 Config: cfg,241 Config: cfg,
239 DataDir: initResult.DataDir,242 DataDir: initResult.DataDir,
@@ -242,13 +245,13 @@ func initStackAndRepos(
242 ColorResolver: initResult.ColorResolver,245 ColorResolver: initResult.ColorResolver,
243 })246 })
244 if err != nil {247 if err != nil {
245- return bootstrap.WebKitStack{}, nil, nil, err248+ return nil, nil, nil, err
246 }249 }
247- return dbWebKit.Stack, createRepositories(dbWebKit.DB), dbWebKit.DBCleanup, nil250+ return dbEngine.Engine, createRepositories(dbEngine.DB), dbEngine.DBCleanup, nil
248 }251 }
249 252
250 log := logging.FromContext(ctx)253 log := logging.FromContext(ctx)
251- stack := bootstrap.BuildWebKitStack(bootstrap.WebKitStackInput{254+ engine, err := bootstrap.BuildEngine(bootstrap.EngineInput{
252 Ctx: ctx,255 Ctx: ctx,
253 Config: cfg,256 Config: cfg,
254 DataDir: initResult.DataDir,257 DataDir: initResult.DataDir,
@@ -257,13 +260,16 @@ func initStackAndRepos(
257 ColorResolver: initResult.ColorResolver,260 ColorResolver: initResult.ColorResolver,
258 Logger: *log,261 Logger: *log,
259 })262 })
263+ if err != nil {
264+ return nil, nil, nil, err
265+ }
260 266
261 lazyDB, err := bootstrap.CreateLazyDatabase()267 lazyDB, err := bootstrap.CreateLazyDatabase()
262 if err != nil {268 if err != nil {
263- return stack, nil, nil, err269+ return nil, nil, nil, err
264 }270 }
265 dbCleanup := func() { _ = lazyDB.Close() }271 dbCleanup := func() { _ = lazyDB.Close() }
266- return stack, createLazyRepositories(lazyDB), dbCleanup, nil272+ return engine, createLazyRepositories(lazyDB), dbCleanup, nil
267 }273 }
268 274
269 func configureDeferredInit(275 func configureDeferredInit(
@@ -501,13 +507,17 @@ func buildUIDependencies(
501 themeManager *theme.Manager,507 themeManager *theme.Manager,
502 colorResolver port.ColorSchemeResolver,508 colorResolver port.ColorSchemeResolver,
503 adwaitaDetector *colorscheme.AdwaitaDetector,509 adwaitaDetector *colorscheme.AdwaitaDetector,
504- stack *bootstrap.WebKitStack,510+ engine port.Engine,
505 repos *repositories,511 repos *repositories,
506 uc *useCases,512 uc *useCases,
507 idleInhibitor port.IdleInhibitor,513 idleInhibitor port.IdleInhibitor,
508 currentSessionID entity.SessionID,514 currentSessionID entity.SessionID,
509 startupCrashReports []string,515 startupCrashReports []string,
510 ) *ui.Dependencies {516 ) *ui.Dependencies {
517+ // Type-assert to *webkit.Engine to access concrete types for ui.Dependencies.
518+ // This bridge will be removed when ui.Dependencies is updated to use port.Engine (M4).
519+ wkEngine := engine.(*webkit.Engine)
520+
511 return &ui.Dependencies{521 return &ui.Dependencies{
512 Ctx: ctx,522 Ctx: ctx,
513 Config: cfg,523 Config: cfg,
@@ -518,12 +528,12 @@ func buildUIDependencies(
518 ColorResolver: colorResolver,528 ColorResolver: colorResolver,
519 AdwaitaDetector: adwaitaDetector,529 AdwaitaDetector: adwaitaDetector,
520 XDG: xdg.New(),530 XDG: xdg.New(),
521- WebContext: stack.Context,531+ WebContext: wkEngine.InternalContext(),
522- Pool: stack.Pool,532+ Pool: wkEngine.InternalPool(),
523- Settings: stack.Settings,533+ Settings: wkEngine.InternalSettings(),
524- Injector: stack.Injector,534+ Injector: wkEngine.InternalInjector(),
525- MessageRouter: stack.MessageRouter,535+ MessageRouter: wkEngine.InternalMessageRouter(),
526- FilterManager: stack.FilterManager,536+ FilterManager: wkEngine.InternalFilterManager(),
527 HistoryRepo: repos.history,537 HistoryRepo: repos.history,
528 FavoriteRepo: repos.favorite,538 FavoriteRepo: repos.favorite,
529 ZoomRepo: repos.zoom,539 ZoomRepo: repos.zoom,
A internal/bootstrap/engine.go +50 -0
@@ -0,0 +1,50 @@
1+package bootstrap
2+
3+import (
4+ "context"
5+ "fmt"
6+
7+ "github.com/bnema/dumber/internal/application/port"
8+ "github.com/bnema/dumber/internal/infrastructure/config"
9+ "github.com/bnema/dumber/internal/infrastructure/webkit"
10+ "github.com/bnema/dumber/internal/ui/theme"
11+ "github.com/rs/zerolog"
12+)
13+
14+// EngineInput holds the input for BuildEngine.
15+type EngineInput struct {
16+ Ctx context.Context
17+ Config *config.Config
18+ DataDir string
19+ CacheDir string
20+ ThemeManager *theme.Manager
21+ ColorResolver port.ColorSchemeResolver
22+ Logger zerolog.Logger
23+}
24+
25+// BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type.
26+// Currently only "webkit" is supported; "cef" and other types return an error.
27+func BuildEngine(input EngineInput) (port.Engine, error) {
28+ cfg := input.Config
29+ engineType := cfg.Engine.Type
30+ if engineType == "" {
31+ engineType = "webkit"
32+ }
33+ switch engineType {
34+ case "webkit":
35+ opts := port.EngineOptions{
36+ DataDir: input.DataDir,
37+ CacheDir: input.CacheDir,
38+ CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
39+ }
40+ wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)
41+ return webkit.NewEngine(
42+ input.Ctx, cfg, opts, wkCfg,
43+ input.ThemeManager, input.ColorResolver, input.Logger,
44+ )
45+ case "cef":
46+ return nil, fmt.Errorf("CEF engine not yet implemented")
47+ default:
48+ return nil, fmt.Errorf("unknown engine type: %q", engineType)
49+ }
50+}
@@ -0,0 +1,50 @@
1+package bootstrap
2+
3+import (
4+ "context"
5+ "fmt"
6+
7+ "github.com/bnema/dumber/internal/application/port"
8+ "github.com/bnema/dumber/internal/infrastructure/config"
9+ "github.com/bnema/dumber/internal/infrastructure/webkit"
10+ "github.com/bnema/dumber/internal/ui/theme"
11+ "github.com/rs/zerolog"
12+)
13+
14+// EngineInput holds the input for BuildEngine.
15+type EngineInput struct {
16+ Ctx context.Context
17+ Config *config.Config
18+ DataDir string
19+ CacheDir string
20+ ThemeManager *theme.Manager
21+ ColorResolver port.ColorSchemeResolver
22+ Logger zerolog.Logger
23+}
24+
25+// BuildEngine constructs a port.Engine for the engine type specified in cfg.Engine.Type.
26+// Currently only "webkit" is supported; "cef" and other types return an error.
27+func BuildEngine(input EngineInput) (port.Engine, error) {
28+ cfg := input.Config
29+ engineType := cfg.Engine.Type
30+ if engineType == "" {
31+ engineType = "webkit"
32+ }
33+ switch engineType {
34+ case "webkit":
35+ opts := port.EngineOptions{
36+ DataDir: input.DataDir,
37+ CacheDir: input.CacheDir,
38+ CookiePolicy: port.CookiePolicy(cfg.Engine.CookiePolicy),
39+ }
40+ wkCfg := webkit.EngineConfigFromConfig(cfg.Engine.WebKit)
41+ return webkit.NewEngine(
42+ input.Ctx, cfg, opts, wkCfg,
43+ input.ThemeManager, input.ColorResolver, input.Logger,
44+ )
45+ case "cef":
46+ return nil, fmt.Errorf("CEF engine not yet implemented")
47+ default:
48+ return nil, fmt.Errorf("unknown engine type: %q", engineType)
49+ }
50+}
M internal/bootstrap/gui.go +19 -16
@@ -263,7 +263,7 @@ func OpenDatabase(ctx context.Context) (*DatabaseResult, error) {
263263 // CreateLazyDatabase creates a lazy database provider that defers initialization.
264264 // The database is initialized on first access, allowing the UI to render faster.
265265 //
266-// Currently unused: the application uses OpenDatabase with RunParallelDBWebKit for
266+// Currently unused: the application uses OpenDatabase with RunParallelDBEngine for
267267 // eager initialization. This function is kept for potential future use when lazy
268268 // initialization past first paint becomes beneficial.
269269 func CreateLazyDatabase() (*sqlite.LazyDB, error) {
@@ -274,19 +274,19 @@ func CreateLazyDatabase() (*sqlite.LazyDB, error) {
274274 return sqlite.NewLazyDB(dbPath), nil
275275 }
276276
277-// ParallelDBWebKitResult holds results from parallel DB and WebKit initialization.
278-type ParallelDBWebKitResult struct {
277+// ParallelDBEngineResult holds results from parallel DB and engine initialization.
278+type ParallelDBEngineResult struct {
279279 DB *sql.DB
280280 DBCleanup func()
281- Stack WebKitStack
281+ Engine port.Engine
282282 }
283283
284-// ParallelDBWebKitInput holds inputs for parallel DB and WebKit initialization.
285-type ParallelDBWebKitInput struct {
284+// ParallelDBEngineInput holds inputs for parallel DB and engine initialization.
285+type ParallelDBEngineInput struct {
286286 Ctx context.Context
287287 Config *config.Config
288- DataDir string // For WebKit context
289- CacheDir string // For WebKit cache
288+ DataDir string // For engine context
289+ CacheDir string // For engine cache
290290 ThemeManager *theme.Manager
291291 ColorResolver port.ColorSchemeResolver
292292 }
@@ -300,10 +300,10 @@ type dbInitResult struct {
300300 err error
301301 }
302302
303-// RunParallelDBWebKit initializes database in background while WebKit runs on main thread.
304-// Database init happens in goroutine (pure Go/WASM), WebKit must stay on main thread (GTK).
305-// This saves ~150ms by overlapping DB migrations with WebKit context creation.
306-func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult, error) {
303+// RunParallelDBEngine initializes database in background while the engine runs on main thread.
304+// Database init happens in goroutine (pure Go/WASM), engine must stay on main thread (GTK).
305+// This saves ~150ms by overlapping DB migrations with engine context creation.
306+func RunParallelDBEngine(input ParallelDBEngineInput) (*ParallelDBEngineResult, error) {
307307 log := logging.FromContext(input.Ctx)
308308
309309 dbCh := make(chan dbInitResult, 1)
@@ -321,8 +321,8 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult,
321321 }
322322 }()
323323
324- // WebKit stack on main thread (GTK requirement)
325- stack := BuildWebKitStack(WebKitStackInput{
324+ // Engine on main thread (GTK requirement)
325+ engine, err := BuildEngine(EngineInput{
326326 Ctx: input.Ctx,
327327 Config: input.Config,
328328 DataDir: input.DataDir,
@@ -331,6 +331,9 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult,
331331 ColorResolver: input.ColorResolver,
332332 Logger: *log,
333333 })
334+ if err != nil {
335+ return nil, fmt.Errorf("engine initialization: %w", err)
336+ }
334337
335338 // Wait for database
336339 dbRes := <-dbCh
@@ -339,9 +342,9 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult,
339342 return nil, fmt.Errorf("database initialization: %w", dbRes.err)
340343 }
341344
342- return &ParallelDBWebKitResult{
345+ return &ParallelDBEngineResult{
343346 DB: dbRes.db,
344347 DBCleanup: dbRes.cleanup,
345- Stack: stack,
348+ Engine: engine,
346349 }, nil
347350 }
@@ -263,7 +263,7 @@ func OpenDatabase(ctx context.Context) (*DatabaseResult, error) {
263 // CreateLazyDatabase creates a lazy database provider that defers initialization.263 // CreateLazyDatabase creates a lazy database provider that defers initialization.
264 // The database is initialized on first access, allowing the UI to render faster.264 // The database is initialized on first access, allowing the UI to render faster.
265 //265 //
266-// Currently unused: the application uses OpenDatabase with RunParallelDBWebKit for266+// Currently unused: the application uses OpenDatabase with RunParallelDBEngine for
267 // eager initialization. This function is kept for potential future use when lazy267 // eager initialization. This function is kept for potential future use when lazy
268 // initialization past first paint becomes beneficial.268 // initialization past first paint becomes beneficial.
269 func CreateLazyDatabase() (*sqlite.LazyDB, error) {269 func CreateLazyDatabase() (*sqlite.LazyDB, error) {
@@ -274,19 +274,19 @@ func CreateLazyDatabase() (*sqlite.LazyDB, error) {
274 return sqlite.NewLazyDB(dbPath), nil274 return sqlite.NewLazyDB(dbPath), nil
275 }275 }
276 276
277-// ParallelDBWebKitResult holds results from parallel DB and WebKit initialization.277+// ParallelDBEngineResult holds results from parallel DB and engine initialization.
278-type ParallelDBWebKitResult struct {278+type ParallelDBEngineResult struct {
279 DB *sql.DB279 DB *sql.DB
280 DBCleanup func()280 DBCleanup func()
281- Stack WebKitStack281+ Engine port.Engine
282 }282 }
283 283
284-// ParallelDBWebKitInput holds inputs for parallel DB and WebKit initialization.284+// ParallelDBEngineInput holds inputs for parallel DB and engine initialization.
285-type ParallelDBWebKitInput struct {285+type ParallelDBEngineInput struct {
286 Ctx context.Context286 Ctx context.Context
287 Config *config.Config287 Config *config.Config
288- DataDir string // For WebKit context288+ DataDir string // For engine context
289- CacheDir string // For WebKit cache289+ CacheDir string // For engine cache
290 ThemeManager *theme.Manager290 ThemeManager *theme.Manager
291 ColorResolver port.ColorSchemeResolver291 ColorResolver port.ColorSchemeResolver
292 }292 }
@@ -300,10 +300,10 @@ type dbInitResult struct {
300 err error300 err error
301 }301 }
302 302
303-// RunParallelDBWebKit initializes database in background while WebKit runs on main thread.303+// RunParallelDBEngine initializes database in background while the engine runs on main thread.
304-// Database init happens in goroutine (pure Go/WASM), WebKit must stay on main thread (GTK).304+// Database init happens in goroutine (pure Go/WASM), engine must stay on main thread (GTK).
305-// This saves ~150ms by overlapping DB migrations with WebKit context creation.305+// This saves ~150ms by overlapping DB migrations with engine context creation.
306-func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult, error) {306+func RunParallelDBEngine(input ParallelDBEngineInput) (*ParallelDBEngineResult, error) {
307 log := logging.FromContext(input.Ctx)307 log := logging.FromContext(input.Ctx)
308 308
309 dbCh := make(chan dbInitResult, 1)309 dbCh := make(chan dbInitResult, 1)
@@ -321,8 +321,8 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult,
321 }321 }
322 }()322 }()
323 323
324- // WebKit stack on main thread (GTK requirement)324+ // Engine on main thread (GTK requirement)
325- stack := BuildWebKitStack(WebKitStackInput{325+ engine, err := BuildEngine(EngineInput{
326 Ctx: input.Ctx,326 Ctx: input.Ctx,
327 Config: input.Config,327 Config: input.Config,
328 DataDir: input.DataDir,328 DataDir: input.DataDir,
@@ -331,6 +331,9 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult,
331 ColorResolver: input.ColorResolver,331 ColorResolver: input.ColorResolver,
332 Logger: *log,332 Logger: *log,
333 })333 })
334+ if err != nil {
335+ return nil, fmt.Errorf("engine initialization: %w", err)
336+ }
334 337
335 // Wait for database338 // Wait for database
336 dbRes := <-dbCh339 dbRes := <-dbCh
@@ -339,9 +342,9 @@ func RunParallelDBWebKit(input ParallelDBWebKitInput) (*ParallelDBWebKitResult,
339 return nil, fmt.Errorf("database initialization: %w", dbRes.err)342 return nil, fmt.Errorf("database initialization: %w", dbRes.err)
340 }343 }
341 344
342- return &ParallelDBWebKitResult{345+ return &ParallelDBEngineResult{
343 DB: dbRes.db,346 DB: dbRes.db,
344 DBCleanup: dbRes.cleanup,347 DBCleanup: dbRes.cleanup,
345- Stack: stack,348+ Engine: engine,
346 }, nil349 }, nil
347 }350 }
D internal/bootstrap/webkit_stack.go +0 -296
@@ -1,296 +0,0 @@
1-package bootstrap
2-
3-import (
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/logging"
15- "github.com/bnema/dumber/internal/ui/theme"
16- "github.com/jwijenbergh/puregotk/v4/gdk"
17- "github.com/rs/zerolog"
18-)
19-
20-// WebKitStackInput holds the input for BuildWebKitStack.
21-type WebKitStackInput struct {
22- Ctx context.Context
23- Config *config.Config
24- DataDir string
25- CacheDir string
26- ThemeManager *theme.Manager
27- ColorResolver port.ColorSchemeResolver
28- Logger zerolog.Logger
29-}
30-
31-type WebKitStack struct {
32- Context *webkit.WebKitContext
33- Settings *webkit.SettingsManager
34- Injector *webkit.ContentInjector
35- MessageRouter *webkit.MessageRouter
36- Pool *webkit.WebViewPool
37- FilterManager *filtering.Manager
38-}
39-
40-func BuildWebKitStack(input WebKitStackInput) WebKitStack {
41- ctx := input.Ctx
42- cfg := input.Config
43- logger := input.Logger
44-
45- perfSettings := surveyHardwareAndResolveProfile(ctx, cfg, logger)
46-
47- configureRenderingEnvironment(ctx, cfg, &perfSettings, logger)
48- logging.Trace().Mark("render_env")
49-
50- wkOpts := buildWebKitContextOptions(cfg, input.DataDir, input.CacheDir, &perfSettings)
51- logger.Info().
52- Str("cookie_policy", string(wkOpts.CookiePolicy)).
53- Bool("itp_enabled", wkOpts.ITPEnabled).
54- Msg("webkit privacy configuration")
55- wkCtx, err := webkit.NewWebKitContextWithOptions(ctx, wkOpts)
56- if err != nil {
57- logger.Fatal().Err(err).Msg("failed to initialize WebKit context")
58- }
59- logging.Trace().Mark("webkit_context")
60-
61- filterManager := initFilterManager(ctx, cfg, input.DataDir, logger)
62-
63- schemeHandler := webkit.NewDumbSchemeHandler(ctx)
64- schemeHandler.SetAssets(assets.WebUIAssets)
65- schemeHandler.RegisterWithContext(wkCtx)
66-
67- settings, injector, messageRouter := initWebKitComponents(ctx, cfg, input.ThemeManager, input.ColorResolver, logger)
68-
69- pool := initWebViewPool(ctx, wkCtx, settings, injector, messageRouter, input.ThemeManager, filterManager, &perfSettings, logger)
70-
71- return WebKitStack{
72- Context: wkCtx,
73- Settings: settings,
74- Injector: injector,
75- MessageRouter: messageRouter,
76- Pool: pool,
77- FilterManager: filterManager,
78- }
79-}
80-
81-func surveyHardwareAndResolveProfile(ctx context.Context, cfg *config.Config, logger zerolog.Logger) config.ResolvedPerformanceSettings {
82- hwSurveyor := env.NewHardwareSurveyor()
83- hwInfo := hwSurveyor.Survey(ctx)
84- logging.Trace().Mark("hardware_survey")
85- logger.Info().
86- Int("cpu_cores", hwInfo.CPUCores).
87- Int("cpu_threads", hwInfo.CPUThreads).
88- Uint64("ram_mb", hwInfo.TotalRAM/(1024*1024)).
89- Str("gpu_vendor", string(hwInfo.GPUVendor)).
90- Uint64("vram_mb", hwInfo.VRAM/(1024*1024)).
91- Msg("hardware survey completed")
92-
93- perfSettings := config.ResolvePerformanceProfile(&cfg.Performance, &hwInfo)
94- logging.Trace().Mark("performance_profile")
95- logger.Info().
96- Str("profile", string(cfg.Performance.Profile)).
97- Int("skia_cpu_threads", perfSettings.SkiaCPUPaintingThreads).
98- Int("skia_gpu_threads", perfSettings.SkiaGPUPaintingThreads).
99- Int("webview_pool_prewarm", perfSettings.WebViewPoolPrewarmCount).
100- Msg("resolved performance profile")
101-
102- return perfSettings
103-}
104-
105-func buildWebKitContextOptions(
106- cfg *config.Config,
107- dataDir, cacheDir string,
108- perfSettings *config.ResolvedPerformanceSettings,
109-) port.WebKitContextOptions {
110- cookiePolicy := port.WebKitCookiePolicyNoThirdParty
111- itpEnabled := true
112- if cfg != nil {
113- cookiePolicy = port.WebKitCookiePolicy(cfg.Privacy.CookiePolicy)
114- itpEnabled = cfg.Privacy.ITPEnabled
115- }
116-
117- wkOpts := port.WebKitContextOptions{
118- DataDir: dataDir,
119- CacheDir: cacheDir,
120- CookiePolicy: cookiePolicy,
121- ITPEnabled: itpEnabled,
122- }
123-
124- if hasWebProcessMemoryConfig(perfSettings) {
125- wkOpts.WebProcessMemory = &port.MemoryPressureConfig{
126- MemoryLimitMB: perfSettings.WebProcessMemoryLimitMB,
127- PollIntervalSec: perfSettings.WebProcessMemoryPollIntervalSec,
128- ConservativeThreshold: perfSettings.WebProcessMemoryConservativeThreshold,
129- StrictThreshold: perfSettings.WebProcessMemoryStrictThreshold,
130- }
131- }
132-
133- if hasNetworkProcessMemoryConfig(perfSettings) {
134- wkOpts.NetworkProcessMemory = &port.MemoryPressureConfig{
135- MemoryLimitMB: perfSettings.NetworkProcessMemoryLimitMB,
136- PollIntervalSec: perfSettings.NetworkProcessMemoryPollIntervalSec,
137- ConservativeThreshold: perfSettings.NetworkProcessMemoryConservativeThreshold,
138- StrictThreshold: perfSettings.NetworkProcessMemoryStrictThreshold,
139- }
140- }
141-
142- return wkOpts
143-}
144-
145-func initFilterManager(ctx context.Context, cfg *config.Config, dataDir string, logger zerolog.Logger) *filtering.Manager {
146- filterStoreDir := filepath.Join(dataDir, "filters", "store")
147- filterJSONDir := filepath.Join(dataDir, "filters", "json")
148- filterManager, err := filtering.NewManager(filtering.ManagerConfig{
149- StoreDir: filterStoreDir,
150- JSONDir: filterJSONDir,
151- Enabled: cfg.ContentFiltering.Enabled,
152- AutoUpdate: cfg.ContentFiltering.AutoUpdate,
153- })
154- if err != nil {
155- logger.Warn().Err(err).Msg("failed to create filter manager, continuing without content filtering")
156- return nil
157- }
158- if err := filterManager.Initialize(ctx); err != nil {
159- logger.Warn().Err(err).Msg("failed to initialize filters, will load async")
160- }
161- logging.Trace().Mark("filter_manager")
162- return filterManager
163-}
164-
165-func initWebKitComponents(
166- ctx context.Context,
167- cfg *config.Config,
168- themeManager *theme.Manager,
169- colorResolver port.ColorSchemeResolver,
170- logger zerolog.Logger,
171-) (*webkit.SettingsManager, *webkit.ContentInjector, *webkit.MessageRouter) {
172- settings := webkit.NewSettingsManager(ctx, cfg)
173- injector := webkit.NewContentInjector(colorResolver)
174-
175- injector.SetAutoCopyConfigGetter(func() bool {
176- return config.Get().Clipboard.AutoCopyOnSelection
177- })
178-
179- prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector)
180- themeCSSText := themeManager.GetWebUIThemeCSS()
181- if err := prepareThemeUC.Execute(ctx, usecase.PrepareWebUIThemeInput{CSSVars: themeCSSText}); err != nil {
182- logger.Warn().Err(err).Msg("failed to prepare WebUI theme CSS")
183- }
184-
185- messageRouter := webkit.NewMessageRouter(ctx)
186- logging.Trace().Mark("settings_manager")
187-
188- return settings, injector, messageRouter
189-}
190-
191-func initWebViewPool(
192- ctx context.Context,
193- wkCtx *webkit.WebKitContext,
194- settings *webkit.SettingsManager,
195- injector *webkit.ContentInjector,
196- messageRouter *webkit.MessageRouter,
197- themeManager *theme.Manager,
198- filterManager *filtering.Manager,
199- perfSettings *config.ResolvedPerformanceSettings,
200- logger zerolog.Logger,
201-) *webkit.WebViewPool {
202- poolCfg := webkit.DefaultPoolConfig()
203- if perfSettings.WebViewPoolPrewarmCount > 0 {
204- poolCfg.PrewarmCount = perfSettings.WebViewPoolPrewarmCount
205- }
206- pool := webkit.NewWebViewPool(ctx, wkCtx, settings, poolCfg, injector, messageRouter)
207-
208- bgR, bgG, bgB, bgA := themeManager.GetBackgroundRGBA()
209- pool.SetBackgroundColor(bgR, bgG, bgB, bgA)
210-
211- if filterManager != nil {
212- pool.SetFilterApplier(filterManager)
213- }
214-
215- if shouldPrewarmFirstWebView(gdk.DisplayGetDefault()) {
216- if err := pool.PrewarmFirst(ctx); err != nil {
217- logger.Warn().Err(err).Msg("failed to prewarm first webview, first tab may be slower")
218- }
219- } else {
220- logger.Warn().Msg("skipping first webview prewarm: no GDK display available yet")
221- }
222- logging.Trace().Mark("pool_prewarm_first")
223-
224- return pool
225-}
226-
227-func shouldPrewarmFirstWebView(display *gdk.Display) bool {
228- return display != nil
229-}
230-
231-// hasWebProcessMemoryConfig returns true if any web process memory setting is configured.
232-func hasWebProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool {
233- return p.WebProcessMemoryLimitMB > 0 ||
234- p.WebProcessMemoryPollIntervalSec > 0 ||
235- p.WebProcessMemoryConservativeThreshold > 0 ||
236- p.WebProcessMemoryStrictThreshold > 0
237-}
238-
239-// hasNetworkProcessMemoryConfig returns true if any network process memory setting is configured.
240-func hasNetworkProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool {
241- return p.NetworkProcessMemoryLimitMB > 0 ||
242- p.NetworkProcessMemoryPollIntervalSec > 0 ||
243- p.NetworkProcessMemoryConservativeThreshold > 0 ||
244- p.NetworkProcessMemoryStrictThreshold > 0
245-}
246-
247-// configureRenderingEnvironment sets up the rendering environment before WebKit/GTK initialization.
248-// Environment variables must be set before GTK/WebKit/GStreamer initializes.
249-func configureRenderingEnvironment(
250- ctx context.Context,
251- cfg *config.Config,
252- perfSettings *config.ResolvedPerformanceSettings,
253- logger zerolog.Logger,
254-) {
255- // Install GLib log handler FIRST if configured (must be before any GTK/GLib calls)
256- if cfg.Logging.CaptureGTKLogs {
257- enableDebug := cfg.Logging.Level == "debug" || cfg.Logging.Level == "trace"
258- logging.InstallGLibLogHandler(ctx, logger, enableDebug)
259- }
260-
261- renderEnv := env.NewManager()
262- gpuVendor := renderEnv.DetectGPUVendor(ctx)
263- renderSettings := env.RenderingSettings{
264- // GStreamer settings
265- ForceVSync: cfg.Media.ForceVSync,
266- GLRenderingMode: string(cfg.Media.GLRenderingMode),
267- GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel,
268-
269- // WebKit compositor settings
270- DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer,
271- ForceCompositingMode: cfg.Rendering.ForceCompositingMode,
272- DisableCompositingMode: cfg.Rendering.DisableCompositingMode,
273-
274- // GTK/GSK settings
275- GSKRenderer: string(cfg.Rendering.GSKRenderer),
276- DisableMipmaps: cfg.Rendering.DisableMipmaps,
277- PreferGL: cfg.Rendering.PreferGL,
278-
279- // Debug settings
280- ShowFPS: cfg.Rendering.ShowFPS,
281- SampleMemory: cfg.Rendering.SampleMemory,
282- DebugFrames: cfg.Rendering.DebugFrames,
283-
284- // Skia rendering thread settings (from resolved profile)
285- SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads,
286- SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads,
287- SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering,
288- }
289- if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil {
290- logger.Warn().Err(err).Msg("failed to apply rendering environment")
291- }
292- logger.Info().
293- Str("gpu", string(gpuVendor)).
294- Interface("vars", renderEnv.GetAppliedVars()).
295- Msg("rendering environment configured")
296-}
@@ -1,296 +0,0 @@
1-package bootstrap
2-
3-import (
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/logging"
15- "github.com/bnema/dumber/internal/ui/theme"
16- "github.com/jwijenbergh/puregotk/v4/gdk"
17- "github.com/rs/zerolog"
18-)
19-
20-// WebKitStackInput holds the input for BuildWebKitStack.
21-type WebKitStackInput struct {
22- Ctx context.Context
23- Config *config.Config
24- DataDir string
25- CacheDir string
26- ThemeManager *theme.Manager
27- ColorResolver port.ColorSchemeResolver
28- Logger zerolog.Logger
29-}
30-
31-type WebKitStack struct {
32- Context *webkit.WebKitContext
33- Settings *webkit.SettingsManager
34- Injector *webkit.ContentInjector
35- MessageRouter *webkit.MessageRouter
36- Pool *webkit.WebViewPool
37- FilterManager *filtering.Manager
38-}
39-
40-func BuildWebKitStack(input WebKitStackInput) WebKitStack {
41- ctx := input.Ctx
42- cfg := input.Config
43- logger := input.Logger
44-
45- perfSettings := surveyHardwareAndResolveProfile(ctx, cfg, logger)
46-
47- configureRenderingEnvironment(ctx, cfg, &perfSettings, logger)
48- logging.Trace().Mark("render_env")
49-
50- wkOpts := buildWebKitContextOptions(cfg, input.DataDir, input.CacheDir, &perfSettings)
51- logger.Info().
52- Str("cookie_policy", string(wkOpts.CookiePolicy)).
53- Bool("itp_enabled", wkOpts.ITPEnabled).
54- Msg("webkit privacy configuration")
55- wkCtx, err := webkit.NewWebKitContextWithOptions(ctx, wkOpts)
56- if err != nil {
57- logger.Fatal().Err(err).Msg("failed to initialize WebKit context")
58- }
59- logging.Trace().Mark("webkit_context")
60-
61- filterManager := initFilterManager(ctx, cfg, input.DataDir, logger)
62-
63- schemeHandler := webkit.NewDumbSchemeHandler(ctx)
64- schemeHandler.SetAssets(assets.WebUIAssets)
65- schemeHandler.RegisterWithContext(wkCtx)
66-
67- settings, injector, messageRouter := initWebKitComponents(ctx, cfg, input.ThemeManager, input.ColorResolver, logger)
68-
69- pool := initWebViewPool(ctx, wkCtx, settings, injector, messageRouter, input.ThemeManager, filterManager, &perfSettings, logger)
70-
71- return WebKitStack{
72- Context: wkCtx,
73- Settings: settings,
74- Injector: injector,
75- MessageRouter: messageRouter,
76- Pool: pool,
77- FilterManager: filterManager,
78- }
79-}
80-
81-func surveyHardwareAndResolveProfile(ctx context.Context, cfg *config.Config, logger zerolog.Logger) config.ResolvedPerformanceSettings {
82- hwSurveyor := env.NewHardwareSurveyor()
83- hwInfo := hwSurveyor.Survey(ctx)
84- logging.Trace().Mark("hardware_survey")
85- logger.Info().
86- Int("cpu_cores", hwInfo.CPUCores).
87- Int("cpu_threads", hwInfo.CPUThreads).
88- Uint64("ram_mb", hwInfo.TotalRAM/(1024*1024)).
89- Str("gpu_vendor", string(hwInfo.GPUVendor)).
90- Uint64("vram_mb", hwInfo.VRAM/(1024*1024)).
91- Msg("hardware survey completed")
92-
93- perfSettings := config.ResolvePerformanceProfile(&cfg.Performance, &hwInfo)
94- logging.Trace().Mark("performance_profile")
95- logger.Info().
96- Str("profile", string(cfg.Performance.Profile)).
97- Int("skia_cpu_threads", perfSettings.SkiaCPUPaintingThreads).
98- Int("skia_gpu_threads", perfSettings.SkiaGPUPaintingThreads).
99- Int("webview_pool_prewarm", perfSettings.WebViewPoolPrewarmCount).
100- Msg("resolved performance profile")
101-
102- return perfSettings
103-}
104-
105-func buildWebKitContextOptions(
106- cfg *config.Config,
107- dataDir, cacheDir string,
108- perfSettings *config.ResolvedPerformanceSettings,
109-) port.WebKitContextOptions {
110- cookiePolicy := port.WebKitCookiePolicyNoThirdParty
111- itpEnabled := true
112- if cfg != nil {
113- cookiePolicy = port.WebKitCookiePolicy(cfg.Privacy.CookiePolicy)
114- itpEnabled = cfg.Privacy.ITPEnabled
115- }
116-
117- wkOpts := port.WebKitContextOptions{
118- DataDir: dataDir,
119- CacheDir: cacheDir,
120- CookiePolicy: cookiePolicy,
121- ITPEnabled: itpEnabled,
122- }
123-
124- if hasWebProcessMemoryConfig(perfSettings) {
125- wkOpts.WebProcessMemory = &port.MemoryPressureConfig{
126- MemoryLimitMB: perfSettings.WebProcessMemoryLimitMB,
127- PollIntervalSec: perfSettings.WebProcessMemoryPollIntervalSec,
128- ConservativeThreshold: perfSettings.WebProcessMemoryConservativeThreshold,
129- StrictThreshold: perfSettings.WebProcessMemoryStrictThreshold,
130- }
131- }
132-
133- if hasNetworkProcessMemoryConfig(perfSettings) {
134- wkOpts.NetworkProcessMemory = &port.MemoryPressureConfig{
135- MemoryLimitMB: perfSettings.NetworkProcessMemoryLimitMB,
136- PollIntervalSec: perfSettings.NetworkProcessMemoryPollIntervalSec,
137- ConservativeThreshold: perfSettings.NetworkProcessMemoryConservativeThreshold,
138- StrictThreshold: perfSettings.NetworkProcessMemoryStrictThreshold,
139- }
140- }
141-
142- return wkOpts
143-}
144-
145-func initFilterManager(ctx context.Context, cfg *config.Config, dataDir string, logger zerolog.Logger) *filtering.Manager {
146- filterStoreDir := filepath.Join(dataDir, "filters", "store")
147- filterJSONDir := filepath.Join(dataDir, "filters", "json")
148- filterManager, err := filtering.NewManager(filtering.ManagerConfig{
149- StoreDir: filterStoreDir,
150- JSONDir: filterJSONDir,
151- Enabled: cfg.ContentFiltering.Enabled,
152- AutoUpdate: cfg.ContentFiltering.AutoUpdate,
153- })
154- if err != nil {
155- logger.Warn().Err(err).Msg("failed to create filter manager, continuing without content filtering")
156- return nil
157- }
158- if err := filterManager.Initialize(ctx); err != nil {
159- logger.Warn().Err(err).Msg("failed to initialize filters, will load async")
160- }
161- logging.Trace().Mark("filter_manager")
162- return filterManager
163-}
164-
165-func initWebKitComponents(
166- ctx context.Context,
167- cfg *config.Config,
168- themeManager *theme.Manager,
169- colorResolver port.ColorSchemeResolver,
170- logger zerolog.Logger,
171-) (*webkit.SettingsManager, *webkit.ContentInjector, *webkit.MessageRouter) {
172- settings := webkit.NewSettingsManager(ctx, cfg)
173- injector := webkit.NewContentInjector(colorResolver)
174-
175- injector.SetAutoCopyConfigGetter(func() bool {
176- return config.Get().Clipboard.AutoCopyOnSelection
177- })
178-
179- prepareThemeUC := usecase.NewPrepareWebUIThemeUseCase(injector)
180- themeCSSText := themeManager.GetWebUIThemeCSS()
181- if err := prepareThemeUC.Execute(ctx, usecase.PrepareWebUIThemeInput{CSSVars: themeCSSText}); err != nil {
182- logger.Warn().Err(err).Msg("failed to prepare WebUI theme CSS")
183- }
184-
185- messageRouter := webkit.NewMessageRouter(ctx)
186- logging.Trace().Mark("settings_manager")
187-
188- return settings, injector, messageRouter
189-}
190-
191-func initWebViewPool(
192- ctx context.Context,
193- wkCtx *webkit.WebKitContext,
194- settings *webkit.SettingsManager,
195- injector *webkit.ContentInjector,
196- messageRouter *webkit.MessageRouter,
197- themeManager *theme.Manager,
198- filterManager *filtering.Manager,
199- perfSettings *config.ResolvedPerformanceSettings,
200- logger zerolog.Logger,
201-) *webkit.WebViewPool {
202- poolCfg := webkit.DefaultPoolConfig()
203- if perfSettings.WebViewPoolPrewarmCount > 0 {
204- poolCfg.PrewarmCount = perfSettings.WebViewPoolPrewarmCount
205- }
206- pool := webkit.NewWebViewPool(ctx, wkCtx, settings, poolCfg, injector, messageRouter)
207-
208- bgR, bgG, bgB, bgA := themeManager.GetBackgroundRGBA()
209- pool.SetBackgroundColor(bgR, bgG, bgB, bgA)
210-
211- if filterManager != nil {
212- pool.SetFilterApplier(filterManager)
213- }
214-
215- if shouldPrewarmFirstWebView(gdk.DisplayGetDefault()) {
216- if err := pool.PrewarmFirst(ctx); err != nil {
217- logger.Warn().Err(err).Msg("failed to prewarm first webview, first tab may be slower")
218- }
219- } else {
220- logger.Warn().Msg("skipping first webview prewarm: no GDK display available yet")
221- }
222- logging.Trace().Mark("pool_prewarm_first")
223-
224- return pool
225-}
226-
227-func shouldPrewarmFirstWebView(display *gdk.Display) bool {
228- return display != nil
229-}
230-
231-// hasWebProcessMemoryConfig returns true if any web process memory setting is configured.
232-func hasWebProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool {
233- return p.WebProcessMemoryLimitMB > 0 ||
234- p.WebProcessMemoryPollIntervalSec > 0 ||
235- p.WebProcessMemoryConservativeThreshold > 0 ||
236- p.WebProcessMemoryStrictThreshold > 0
237-}
238-
239-// hasNetworkProcessMemoryConfig returns true if any network process memory setting is configured.
240-func hasNetworkProcessMemoryConfig(p *config.ResolvedPerformanceSettings) bool {
241- return p.NetworkProcessMemoryLimitMB > 0 ||
242- p.NetworkProcessMemoryPollIntervalSec > 0 ||
243- p.NetworkProcessMemoryConservativeThreshold > 0 ||
244- p.NetworkProcessMemoryStrictThreshold > 0
245-}
246-
247-// configureRenderingEnvironment sets up the rendering environment before WebKit/GTK initialization.
248-// Environment variables must be set before GTK/WebKit/GStreamer initializes.
249-func configureRenderingEnvironment(
250- ctx context.Context,
251- cfg *config.Config,
252- perfSettings *config.ResolvedPerformanceSettings,
253- logger zerolog.Logger,
254-) {
255- // Install GLib log handler FIRST if configured (must be before any GTK/GLib calls)
256- if cfg.Logging.CaptureGTKLogs {
257- enableDebug := cfg.Logging.Level == "debug" || cfg.Logging.Level == "trace"
258- logging.InstallGLibLogHandler(ctx, logger, enableDebug)
259- }
260-
261- renderEnv := env.NewManager()
262- gpuVendor := renderEnv.DetectGPUVendor(ctx)
263- renderSettings := env.RenderingSettings{
264- // GStreamer settings
265- ForceVSync: cfg.Media.ForceVSync,
266- GLRenderingMode: string(cfg.Media.GLRenderingMode),
267- GStreamerDebugLevel: cfg.Media.GStreamerDebugLevel,
268-
269- // WebKit compositor settings
270- DisableDMABufRenderer: cfg.Rendering.DisableDMABufRenderer,
271- ForceCompositingMode: cfg.Rendering.ForceCompositingMode,
272- DisableCompositingMode: cfg.Rendering.DisableCompositingMode,
273-
274- // GTK/GSK settings
275- GSKRenderer: string(cfg.Rendering.GSKRenderer),
276- DisableMipmaps: cfg.Rendering.DisableMipmaps,
277- PreferGL: cfg.Rendering.PreferGL,
278-
279- // Debug settings
280- ShowFPS: cfg.Rendering.ShowFPS,
281- SampleMemory: cfg.Rendering.SampleMemory,
282- DebugFrames: cfg.Rendering.DebugFrames,
283-
284- // Skia rendering thread settings (from resolved profile)
285- SkiaCPUPaintingThreads: perfSettings.SkiaCPUPaintingThreads,
286- SkiaGPUPaintingThreads: perfSettings.SkiaGPUPaintingThreads,
287- SkiaEnableCPURendering: perfSettings.SkiaEnableCPURendering,
288- }
289- if err := renderEnv.ApplyEnvironment(ctx, renderSettings); err != nil {
290- logger.Warn().Err(err).Msg("failed to apply rendering environment")
291- }
292- logger.Info().
293- Str("gpu", string(gpuVendor)).
294- Interface("vars", renderEnv.GetAppliedVars()).
295- Msg("rendering environment configured")
296-}
M internal/infrastructure/webkit/engine.go +10 -0
@@ -83,3 +83,13 @@ func (e *Engine) FilterApplier() port.FilterApplier {
8383 func (e *Engine) FaviconDatabase() port.FaviconDatabase {
8484 return &faviconDatabaseAdapter{wkCtx: e.wkCtx}
8585 }
86+
87+// --- Temporary accessors for migration (M3->M4 bridge) ---
88+// These will be removed when ui.Dependencies is updated to use port.Engine.
89+
90+func (e *Engine) InternalContext() *WebKitContext { return e.wkCtx }
91+func (e *Engine) InternalSettings() *SettingsManager { return e.settings }
92+func (e *Engine) InternalInjector() *ContentInjector { return e.injector }
93+func (e *Engine) InternalMessageRouter() *MessageRouter { return e.messageRouter }
94+func (e *Engine) InternalPool() *WebViewPool { return e.pool }
95+func (e *Engine) InternalFilterManager() *filtering.Manager { return e.filterManager }
@@ -83,3 +83,13 @@ func (e *Engine) FilterApplier() port.FilterApplier {
83 func (e *Engine) FaviconDatabase() port.FaviconDatabase {83 func (e *Engine) FaviconDatabase() port.FaviconDatabase {
84 return &faviconDatabaseAdapter{wkCtx: e.wkCtx}84 return &faviconDatabaseAdapter{wkCtx: e.wkCtx}
85 }85 }
86+
87+// --- Temporary accessors for migration (M3->M4 bridge) ---
88+// These will be removed when ui.Dependencies is updated to use port.Engine.
89+
90+func (e *Engine) InternalContext() *WebKitContext { return e.wkCtx }
91+func (e *Engine) InternalSettings() *SettingsManager { return e.settings }
92+func (e *Engine) InternalInjector() *ContentInjector { return e.injector }
93+func (e *Engine) InternalMessageRouter() *MessageRouter { return e.messageRouter }
94+func (e *Engine) InternalPool() *WebViewPool { return e.pool }
95+func (e *Engine) InternalFilterManager() *filtering.Manager { return e.filterManager }

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