1package webkit
2
3import (
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/filtering"
10 "github.com/rs/zerolog"
11)
12
13// Engine implements port.Engine for the WebKit browser engine.
14type Engine struct {
15 ctx context.Context
16 wkCtx *WebKitContext
17 settings *SettingsManager
18 injector *ContentInjector
19 messageRouter *MessageRouter
20 pool *WebViewPool
21 factory *WebViewFactory
22 filterManager *filtering.Manager
23 schemeHandler *DumbSchemeHandler
24 schemePath string
25 logger zerolog.Logger
26
27 // Handler registrars - injected at construction to avoid import cycles.
28 handlerRegistrar func(ctx context.Context, router *MessageRouter, deps port.HandlerDependencies) error
29 accentHandlerRegistrar func(ctx context.Context, router *MessageRouter, handler port.AccentKeyHandler) error
30}
31
32// Compile-time check that Engine implements port.Engine.
33var _ port.Engine = (*Engine)(nil)
34
35// Factory returns the WebViewFactory wrapped as a port.WebViewFactory.
36func (e *Engine) Factory() port.WebViewFactory {
37 return &webViewFactoryAdapter{factory: e.factory}
38}
39
40// Pool returns the WebViewPool wrapped as a port.WebViewPool.
41func (e *Engine) Pool() port.WebViewPool {
42 return &webViewPoolAdapter{pool: e.pool, ctx: e.ctx}
43}
44
45// ContentInjector returns the ContentInjector implementing port.ContentInjector.
46func (e *Engine) ContentInjector() port.ContentInjector {
47 return e.injector
48}
49
50// InternalSchemePath returns the URI scheme used for internal app resources.
51func (e *Engine) InternalSchemePath() string {
52 return e.schemePath
53}
54
55// Close releases all resources held by the engine.
56func (e *Engine) Close() error {
57 if e.pool != nil {
58 e.pool.Close(e.ctx)
59 }
60 return nil
61}
62
63// SettingsApplier returns a port.SettingsApplier adapter for the SettingsManager.
64func (e *Engine) SettingsApplier() port.SettingsApplier {
65 return &settingsApplierAdapter{settings: e.settings}
66}
67
68// FilterApplier returns a port.FilterApplier adapter, or nil if filtering is disabled.
69func (e *Engine) FilterApplier() port.FilterApplier {
70 if e.filterManager == nil {
71 return nil
72 }
73 return &filterApplierAdapter{manager: e.filterManager}
74}
75
76// FaviconDatabase returns a port.FaviconDatabase adapter for async favicon lookups.
77func (e *Engine) FaviconDatabase() port.FaviconDatabase {
78 return &faviconDatabaseAdapter{wkCtx: e.wkCtx}
79}
80
81// InternalFilterManager returns the FilterManager for content filter lifecycle.
82// This is on the concrete *Engine type (not the port.Engine interface) because
83// FilterManager is a webkit-specific concern used only during dependency wiring.
84func (e *Engine) InternalFilterManager() port.FilterManager { return e.filterManager }
85
86// SetHandlerContext sets the base context for message handler dispatch.
87func (e *Engine) SetHandlerContext(ctx context.Context) {
88 if e.messageRouter != nil {
89 e.messageRouter.SetBaseContext(ctx)
90 }
91}
92
93// RegisterHandlers registers all WebUI message bridge handlers.
94func (e *Engine) RegisterHandlers(ctx context.Context, deps port.HandlerDependencies) error {
95 if e.messageRouter == nil {
96 return fmt.Errorf("message router not initialized")
97 }
98 if e.handlerRegistrar == nil {
99 return fmt.Errorf("handler registrar not configured")
100 }
101 return e.handlerRegistrar(ctx, e.messageRouter, deps)
102}
103
104// RegisterAccentHandlers registers accent/dead-key input handlers.
105func (e *Engine) RegisterAccentHandlers(ctx context.Context, handler port.AccentKeyHandler) error {
106 if e.messageRouter == nil {
107 return fmt.Errorf("message router not initialized")
108 }
109 if e.accentHandlerRegistrar == nil {
110 return fmt.Errorf("accent handler registrar not configured")
111 }
112 return e.accentHandlerRegistrar(ctx, e.messageRouter, handler)
113}
114
115// ConfigureDownloads sets up download handling.
116func (e *Engine) ConfigureDownloads(
117 ctx context.Context, downloadPath string,
118 eventHandler port.DownloadEventHandler, preparer port.DownloadPreparer,
119) error {
120 if e.wkCtx == nil {
121 return fmt.Errorf("webkit context not initialized")
122 }
123 handler := NewDownloadHandler(downloadPath, eventHandler, preparer)
124 e.wkCtx.SetDownloadHandler(ctx, handler)
125 return nil
126}
127
128// OnToolkitReady refreshes pooled WebViews after toolkit init.
129func (e *Engine) OnToolkitReady(ctx context.Context) error {
130 if e.pool != nil {
131 e.pool.RefreshScripts(ctx)
132 }
133 return nil
134}
135
136// UpdateAppearance updates default background color for pool and factory.
137func (e *Engine) UpdateAppearance(_ context.Context, r, g, b, alpha float64) error {
138 if e.pool != nil {
139 e.pool.SetBackgroundColor(float32(r), float32(g), float32(b), float32(alpha))
140 }
141 if e.factory != nil {
142 e.factory.SetBackgroundColor(float32(r), float32(g), float32(b), float32(alpha))
143 }
144 return nil
145}
146
147// UpdateSettings applies runtime config changes to engine settings.
148func (e *Engine) UpdateSettings(ctx context.Context, update port.EngineSettingsUpdate) error {
149 cfg, ok := update.Raw.(*config.Config)
150 if !ok {
151 return fmt.Errorf("UpdateSettings: expected *config.Config, got %T", update.Raw)
152 }
153 if e.settings != nil {
154 e.settings.UpdateFromConfig(ctx, cfg)
155 }
156 return nil
157}