1package webkit
2
3import (
4 "context"
5 "fmt"
6
7 "github.com/bnema/dumber/internal/application/port"
8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/infrastructure/filtering"
10 "github.com/bnema/dumber/internal/infrastructure/handlers"
11 "github.com/rs/zerolog"
12)
13
14// Engine implements port.Engine for the WebKit browser engine.
15type Engine struct {
16 ctx context.Context
17 wkCtx *WebKitContext
18 settings *SettingsManager
19 injector *ContentInjector
20 messageRouter *MessageRouter
21 pool *WebViewPool
22 factory *WebViewFactory
23 filterManager *filtering.Manager
24 schemeHandler *DumbSchemeHandler
25 schemePath string
26 logger zerolog.Logger
27 ctxMenuBuilder port.ContextMenuBuilder
28 ctxMenuExecutorFactory port.ContextMenuActionExecutorFactory
29 onClipboardCopied func(textLen int)
30 downloadPath string
31 downloadPreparer port.DownloadPreparer
32 clipboard port.Clipboard // captured during RegisterHandlers for context menu wiring
33}
34
35// Compile-time check that Engine implements port.Engine.
36var _ port.Engine = (*Engine)(nil)
37
38// Factory returns the WebViewFactory wrapped as a port.WebViewFactory.
39func (e *Engine) Factory() port.WebViewFactory {
40 return &webViewFactoryAdapter{factory: e.factory}
41}
42
43// Pool returns the WebViewPool wrapped as a port.WebViewPool.
44func (e *Engine) Pool() port.WebViewPool {
45 return &webViewPoolAdapter{pool: e.pool, ctx: e.ctx}
46}
47
48// ContentInjector returns the ContentInjector implementing port.ContentInjector.
49func (e *Engine) ContentInjector() port.ContentInjector {
50 return e.injector
51}
52
53// InternalSchemePath returns the URI scheme used for internal app resources.
54func (e *Engine) InternalSchemePath() string {
55 return e.schemePath
56}
57
58// Close releases all resources held by the engine.
59func (e *Engine) Close() error {
60 if e.pool != nil {
61 e.pool.Close(e.ctx)
62 }
63 return nil
64}
65
66// SettingsApplier returns a port.SettingsApplier adapter for the SettingsManager.
67func (e *Engine) SettingsApplier() port.SettingsApplier {
68 return &settingsApplierAdapter{settings: e.settings}
69}
70
71// FilterApplier returns a port.FilterApplier adapter, or nil if filtering is disabled.
72func (e *Engine) FilterApplier() port.FilterApplier {
73 if e.filterManager == nil {
74 return nil
75 }
76 return &filterApplierAdapter{manager: e.filterManager}
77}
78
79// FaviconDatabase returns a port.FaviconDatabase adapter for async favicon lookups.
80func (e *Engine) FaviconDatabase() port.FaviconDatabase {
81 return &faviconDatabaseAdapter{wkCtx: e.wkCtx}
82}
83
84// InternalFilterManager returns the FilterManager for content filter lifecycle.
85// This is on the concrete *Engine type (not the port.Engine interface) because
86// FilterManager is a webkit-specific concern used only during dependency wiring.
87func (e *Engine) InternalFilterManager() port.FilterManager { return e.filterManager }
88
89// SetHandlerContext sets the base context for message handler dispatch.
90func (e *Engine) SetHandlerContext(ctx context.Context) {
91 if e.messageRouter != nil {
92 e.messageRouter.SetBaseContext(ctx)
93 }
94}
95
96// RegisterHandlers registers all WebUI message bridge handlers.
97func (e *Engine) RegisterHandlers(ctx context.Context, deps port.HandlerDependencies) error {
98 if e.messageRouter == nil {
99 return fmt.Errorf("message router not initialized")
100 }
101 // Capture clipboard for context menu pipeline wiring in ConfigureDownloads.
102 e.clipboard = deps.Clipboard
103 e.onClipboardCopied = deps.OnClipboardCopied
104 return handlers.RegisterAll(ctx, e.messageRouter, deps)
105}
106
107// RegisterAccentHandlers registers accent/dead-key input handlers.
108func (e *Engine) RegisterAccentHandlers(ctx context.Context, handler port.AccentKeyHandler) error {
109 if e.messageRouter == nil {
110 return fmt.Errorf("message router not initialized")
111 }
112 return handlers.RegisterAccentHandlers(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 if preparer == nil {
124 return fmt.Errorf("download preparer is required")
125 }
126 handler := NewDownloadHandler(downloadPath, eventHandler, preparer)
127 e.wkCtx.SetDownloadHandler(ctx, handler)
128 e.downloadPath = downloadPath
129 e.downloadPreparer = preparer
130
131 // Wire the shared context menu pipeline now that all dependencies are
132 // available. RegisterHandlers (called earlier) captured the clipboard;
133 // ConfigureDownloads supplies the download preparer and path.
134 e.installContextMenuPipeline()
135
136 return nil
137}
138
139// OnToolkitReady refreshes pooled WebViews after toolkit init.
140func (e *Engine) OnToolkitReady(ctx context.Context) error {
141 if e.pool != nil {
142 e.pool.RefreshScripts(ctx)
143 }
144 return nil
145}
146
147// UpdateAppearance updates default background color for pool and factory.
148func (e *Engine) UpdateAppearance(_ context.Context, r, g, b, alpha float64) error {
149 if e.pool != nil {
150 e.pool.SetBackgroundColor(float32(r), float32(g), float32(b), float32(alpha))
151 }
152 if e.factory != nil {
153 e.factory.SetBackgroundColor(float32(r), float32(g), float32(b), float32(alpha))
154 }
155 return nil
156}
157
158// UpdateSettings applies runtime config changes to engine settings.
159func (e *Engine) UpdateSettings(ctx context.Context, update entity.EngineSettingsUpdate) error {
160 if e.settings != nil {
161 e.settings.UpdateFromPayload(ctx, update.Settings)
162 }
163 return nil
164}
165
166// installContextMenuPipeline creates the shared context menu pipeline and
167// propagates it to the factory (and pool). This is called once during startup,
168// after both RegisterHandlers (clipboard) and ConfigureDownloads (download
169// deps) have run. Kept on the concrete *Engine type because it is a
170// WebKit-specific concern.
171func (e *Engine) installContextMenuPipeline() {
172 resolver := NewContextMenuResolver()
173 saver := NewResolvedImageSaver(e.downloadPreparer, e.downloadPath)
174
175 // WebKit's context-menu signal fires on the GTK main thread, so no
176 // dispatch wrapper is needed.
177 renderer := NewRenderer(nil)
178
179 pipeline := &contextMenuPipeline{
180 builder: e.ctxMenuBuilder,
181 executorFactory: e.ctxMenuExecutorFactory,
182 clipboard: e.clipboard,
183 onCopied: e.onClipboardCopied,
184 resolver: resolver,
185 saver: saver,
186 renderer: renderer,
187 }
188 e.SetContextMenuPipeline(pipeline)
189}
190
191// SetContextMenuPipeline configures the context menu pipeline for all WebViews
192// created by the factory (and pool). Call this after the use cases and renderer
193// are available in the bootstrap layer.
194func (e *Engine) SetContextMenuPipeline(pipeline *contextMenuPipeline) {
195 if e.factory != nil {
196 e.factory.SetContextMenuPipeline(pipeline)
197 }
198}