feat(webkit): apply content filters to webviews on creation and acquire
- commit
- 8987eeccfd5ac3a89d8458f797ef80b6f5a70389
- author
- brice · · 2025-12-18 18:21 UTC
- committer
- brice · · 2025-12-18 19:27 UTC
- parent
- 92431e4
- signature
- unsigned
2 files · +52 -8
M internal/infrastructure/webkit/factory.go +26 -5
| @@ -12,11 +12,12 @@ import ( | |||
| 12 | 12 | // It supports creating regular WebViews (optionally via pool) and | |
| 13 | 13 | // related WebViews that share session/cookies with a parent (for popups). | |
| 14 | 14 | type WebViewFactory struct { | |
| 15 | - | wkCtx *WebKitContext | |
| 16 | - | settings *SettingsManager | |
| 17 | - | pool *WebViewPool | |
| 18 | - | injector *ContentInjector | |
| 19 | - | router *MessageRouter | |
| 15 | + | wkCtx *WebKitContext | |
| 16 | + | settings *SettingsManager | |
| 17 | + | pool *WebViewPool | |
| 18 | + | injector *ContentInjector | |
| 19 | + | router *MessageRouter | |
| 20 | + | filterApplier FilterApplier // Optional content filter applier | |
| 20 | 21 | } | |
| 21 | 22 | ||
| 22 | 23 | // NewWebViewFactory creates a new WebViewFactory. | |
| @@ -37,6 +38,16 @@ func NewWebViewFactory( | |||
| 37 | 38 | } | |
| 38 | 39 | } | |
| 39 | 40 | ||
| 41 | + | // SetFilterApplier sets the content filter applier. | |
| 42 | + | // Filters will be applied to all newly created WebViews. | |
| 43 | + | func (f *WebViewFactory) SetFilterApplier(applier FilterApplier) { | |
| 44 | + | f.filterApplier = applier | |
| 45 | + | // Also propagate to the pool if present | |
| 46 | + | if f.pool != nil { | |
| 47 | + | f.pool.SetFilterApplier(applier) | |
| 48 | + | } | |
| 49 | + | } | |
| 50 | + | ||
| 40 | 51 | // Create creates a new WebView instance. | |
| 41 | 52 | // If a pool is configured, it will try to acquire from the pool first. | |
| 42 | 53 | func (f *WebViewFactory) Create(ctx context.Context) (*WebView, error) { | |
| @@ -85,6 +96,11 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||
| 85 | 96 | log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to related webview") | |
| 86 | 97 | } | |
| 87 | 98 | ||
| 99 | + | // Apply content filters if configured | |
| 100 | + | if f.filterApplier != nil { | |
| 101 | + | f.filterApplier.ApplyTo(ctx, wv.ucm) | |
| 102 | + | } | |
| 103 | + | ||
| 88 | 104 | log.Debug(). | |
| 89 | 105 | Uint64("id", uint64(wv.ID())). | |
| 90 | 106 | Uint64("parent_id", uint64(parentID)). | |
| @@ -110,6 +126,11 @@ func (f *WebViewFactory) createDirect(ctx context.Context) (*WebView, error) { | |||
| 110 | 126 | log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to webview") | |
| 111 | 127 | } | |
| 112 | 128 | ||
| 129 | + | // Apply content filters if configured | |
| 130 | + | if f.filterApplier != nil { | |
| 131 | + | f.filterApplier.ApplyTo(ctx, wv.ucm) | |
| 132 | + | } | |
| 133 | + | ||
| 113 | 134 | log.Debug().Uint64("id", uint64(wv.ID())).Msg("created webview directly") | |
| 114 | 135 | return wv, nil | |
| 115 | 136 | } | |
| @@ -12,11 +12,12 @@ import ( | |||||
| 12 | // It supports creating regular WebViews (optionally via pool) and | 12 | // It supports creating regular WebViews (optionally via pool) and | ||
| 13 | // related WebViews that share session/cookies with a parent (for popups). | 13 | // related WebViews that share session/cookies with a parent (for popups). | ||
| 14 | type WebViewFactory struct { | 14 | type WebViewFactory struct { | ||
| 15 | - | wkCtx *WebKitContext | 15 | + | wkCtx *WebKitContext |
| 16 | - | settings *SettingsManager | 16 | + | settings *SettingsManager |
| 17 | - | pool *WebViewPool | 17 | + | pool *WebViewPool |
| 18 | - | injector *ContentInjector | 18 | + | injector *ContentInjector |
| 19 | - | router *MessageRouter | 19 | + | router *MessageRouter |
| 20 | + | filterApplier FilterApplier // Optional content filter applier | |||
| 20 | } | 21 | } | ||
| 21 | 22 | ||||
| 22 | // NewWebViewFactory creates a new WebViewFactory. | 23 | // NewWebViewFactory creates a new WebViewFactory. | ||
| @@ -37,6 +38,16 @@ func NewWebViewFactory( | |||||
| 37 | } | 38 | } | ||
| 38 | } | 39 | } | ||
| 39 | 40 | ||||
| 41 | + | // SetFilterApplier sets the content filter applier. | |||
| 42 | + | // Filters will be applied to all newly created WebViews. | |||
| 43 | + | func (f *WebViewFactory) SetFilterApplier(applier FilterApplier) { | |||
| 44 | + | f.filterApplier = applier | |||
| 45 | + | // Also propagate to the pool if present | |||
| 46 | + | if f.pool != nil { | |||
| 47 | + | f.pool.SetFilterApplier(applier) | |||
| 48 | + | } | |||
| 49 | + | } | |||
| 50 | + | ||||
| 40 | // Create creates a new WebView instance. | 51 | // Create creates a new WebView instance. | ||
| 41 | // If a pool is configured, it will try to acquire from the pool first. | 52 | // If a pool is configured, it will try to acquire from the pool first. | ||
| 42 | func (f *WebViewFactory) Create(ctx context.Context) (*WebView, error) { | 53 | func (f *WebViewFactory) Create(ctx context.Context) (*WebView, error) { | ||
| @@ -85,6 +96,11 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||||
| 85 | log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to related webview") | 96 | log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to related webview") | ||
| 86 | } | 97 | } | ||
| 87 | 98 | ||||
| 99 | + | // Apply content filters if configured | |||
| 100 | + | if f.filterApplier != nil { | |||
| 101 | + | f.filterApplier.ApplyTo(ctx, wv.ucm) | |||
| 102 | + | } | |||
| 103 | + | ||||
| 88 | log.Debug(). | 104 | log.Debug(). | ||
| 89 | Uint64("id", uint64(wv.ID())). | 105 | Uint64("id", uint64(wv.ID())). | ||
| 90 | Uint64("parent_id", uint64(parentID)). | 106 | Uint64("parent_id", uint64(parentID)). | ||
| @@ -110,6 +126,11 @@ func (f *WebViewFactory) createDirect(ctx context.Context) (*WebView, error) { | |||||
| 110 | log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to webview") | 126 | log.Warn().Err(err).Uint64("id", uint64(wv.ID())).Msg("failed to attach frontend to webview") | ||
| 111 | } | 127 | } | ||
| 112 | 128 | ||||
| 129 | + | // Apply content filters if configured | |||
| 130 | + | if f.filterApplier != nil { | |||
| 131 | + | f.filterApplier.ApplyTo(ctx, wv.ucm) | |||
| 132 | + | } | |||
| 133 | + | ||||
| 113 | log.Debug().Uint64("id", uint64(wv.ID())).Msg("created webview directly") | 134 | log.Debug().Uint64("id", uint64(wv.ID())).Msg("created webview directly") | ||
| 114 | return wv, nil | 135 | return wv, nil | ||
| 115 | } | 136 | } | ||
M internal/infrastructure/webkit/pool.go +26 -3
| @@ -7,8 +7,15 @@ import ( | |||
| 7 | 7 | "time" | |
| 8 | 8 | ||
| 9 | 9 | "github.com/bnema/dumber/internal/logging" | |
| 10 | + | "github.com/bnema/puregotk-webkit/webkit" | |
| 10 | 11 | ) | |
| 11 | 12 | ||
| 13 | + | // FilterApplier applies content filters to a UserContentManager. | |
| 14 | + | // This interface decouples the pool from the filtering package. | |
| 15 | + | type FilterApplier interface { | |
| 16 | + | ApplyTo(ctx context.Context, ucm *webkit.UserContentManager) | |
| 17 | + | } | |
| 18 | + | ||
| 12 | 19 | // PoolConfig configures the WebView pool behavior. | |
| 13 | 20 | type PoolConfig struct { | |
| 14 | 21 | // MinSize is the minimum number of warm WebViews to maintain. | |
| @@ -37,9 +44,10 @@ type WebViewPool struct { | |||
| 37 | 44 | settings *SettingsManager | |
| 38 | 45 | config PoolConfig | |
| 39 | 46 | ||
| 40 | - | pool chan *WebView | |
| 41 | - | injector *ContentInjector | |
| 42 | - | router *MessageRouter | |
| 47 | + | pool chan *WebView | |
| 48 | + | injector *ContentInjector | |
| 49 | + | router *MessageRouter | |
| 50 | + | filterApplier FilterApplier // Optional content filter applier | |
| 43 | 51 | ||
| 44 | 52 | closed atomic.Bool | |
| 45 | 53 | wg sync.WaitGroup | |
| @@ -75,6 +83,12 @@ func NewWebViewPool(ctx context.Context, wkCtx *WebKitContext, settings *Setting | |||
| 75 | 83 | return p | |
| 76 | 84 | } | |
| 77 | 85 | ||
| 86 | + | // SetFilterApplier sets the content filter applier. | |
| 87 | + | // Filters will be applied to all newly created WebViews. | |
| 88 | + | func (p *WebViewPool) SetFilterApplier(applier FilterApplier) { | |
| 89 | + | p.filterApplier = applier | |
| 90 | + | } | |
| 91 | + | ||
| 78 | 92 | // Acquire gets a WebView from the pool or creates a new one. | |
| 79 | 93 | func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |
| 80 | 94 | log := logging.FromContext(ctx) | |
| @@ -89,6 +103,10 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |||
| 89 | 103 | if wv != nil && !wv.IsDestroyed() { | |
| 90 | 104 | // Ensure frontend is attached even if this WebView was pooled before injection was configured. | |
| 91 | 105 | _ = wv.AttachFrontend(ctx, p.injector, p.router) | |
| 106 | + | // Apply filters if available (may not have been applied during prewarm) | |
| 107 | + | if p.filterApplier != nil { | |
| 108 | + | p.filterApplier.ApplyTo(ctx, wv.ucm) | |
| 109 | + | } | |
| 92 | 110 | log.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool") | |
| 93 | 111 | return wv, nil | |
| 94 | 112 | } | |
| @@ -117,6 +135,11 @@ func (p *WebViewPool) createWebView(ctx context.Context) (*WebView, error) { | |||
| 117 | 135 | log.Warn().Err(err).Msg("failed to attach frontend to new webview") | |
| 118 | 136 | } | |
| 119 | 137 | ||
| 138 | + | // Apply content filters if configured | |
| 139 | + | if p.filterApplier != nil { | |
| 140 | + | p.filterApplier.ApplyTo(ctx, wv.ucm) | |
| 141 | + | } | |
| 142 | + | ||
| 120 | 143 | return wv, nil | |
| 121 | 144 | } | |
| 122 | 145 | ||
| @@ -7,8 +7,15 @@ import ( | |||||
| 7 | "time" | 7 | "time" | ||
| 8 | 8 | ||||
| 9 | "github.com/bnema/dumber/internal/logging" | 9 | "github.com/bnema/dumber/internal/logging" | ||
| 10 | + | "github.com/bnema/puregotk-webkit/webkit" | |||
| 10 | ) | 11 | ) | ||
| 11 | 12 | ||||
| 13 | + | // FilterApplier applies content filters to a UserContentManager. | |||
| 14 | + | // This interface decouples the pool from the filtering package. | |||
| 15 | + | type FilterApplier interface { | |||
| 16 | + | ApplyTo(ctx context.Context, ucm *webkit.UserContentManager) | |||
| 17 | + | } | |||
| 18 | + | ||||
| 12 | // PoolConfig configures the WebView pool behavior. | 19 | // PoolConfig configures the WebView pool behavior. | ||
| 13 | type PoolConfig struct { | 20 | type PoolConfig struct { | ||
| 14 | // MinSize is the minimum number of warm WebViews to maintain. | 21 | // MinSize is the minimum number of warm WebViews to maintain. | ||
| @@ -37,9 +44,10 @@ type WebViewPool struct { | |||||
| 37 | settings *SettingsManager | 44 | settings *SettingsManager | ||
| 38 | config PoolConfig | 45 | config PoolConfig | ||
| 39 | 46 | ||||
| 40 | - | pool chan *WebView | 47 | + | pool chan *WebView |
| 41 | - | injector *ContentInjector | 48 | + | injector *ContentInjector |
| 42 | - | router *MessageRouter | 49 | + | router *MessageRouter |
| 50 | + | filterApplier FilterApplier // Optional content filter applier | |||
| 43 | 51 | ||||
| 44 | closed atomic.Bool | 52 | closed atomic.Bool | ||
| 45 | wg sync.WaitGroup | 53 | wg sync.WaitGroup | ||
| @@ -75,6 +83,12 @@ func NewWebViewPool(ctx context.Context, wkCtx *WebKitContext, settings *Setting | |||||
| 75 | return p | 83 | return p | ||
| 76 | } | 84 | } | ||
| 77 | 85 | ||||
| 86 | + | // SetFilterApplier sets the content filter applier. | |||
| 87 | + | // Filters will be applied to all newly created WebViews. | |||
| 88 | + | func (p *WebViewPool) SetFilterApplier(applier FilterApplier) { | |||
| 89 | + | p.filterApplier = applier | |||
| 90 | + | } | |||
| 91 | + | ||||
| 78 | // Acquire gets a WebView from the pool or creates a new one. | 92 | // Acquire gets a WebView from the pool or creates a new one. | ||
| 79 | func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | 93 | func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | ||
| 80 | log := logging.FromContext(ctx) | 94 | log := logging.FromContext(ctx) | ||
| @@ -89,6 +103,10 @@ func (p *WebViewPool) Acquire(ctx context.Context) (*WebView, error) { | |||||
| 89 | if wv != nil && !wv.IsDestroyed() { | 103 | if wv != nil && !wv.IsDestroyed() { | ||
| 90 | // Ensure frontend is attached even if this WebView was pooled before injection was configured. | 104 | // Ensure frontend is attached even if this WebView was pooled before injection was configured. | ||
| 91 | _ = wv.AttachFrontend(ctx, p.injector, p.router) | 105 | _ = wv.AttachFrontend(ctx, p.injector, p.router) | ||
| 106 | + | // Apply filters if available (may not have been applied during prewarm) | |||
| 107 | + | if p.filterApplier != nil { | |||
| 108 | + | p.filterApplier.ApplyTo(ctx, wv.ucm) | |||
| 109 | + | } | |||
| 92 | log.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool") | 110 | log.Debug().Uint64("id", uint64(wv.ID())).Msg("acquired webview from pool") | ||
| 93 | return wv, nil | 111 | return wv, nil | ||
| 94 | } | 112 | } | ||
| @@ -117,6 +135,11 @@ func (p *WebViewPool) createWebView(ctx context.Context) (*WebView, error) { | |||||
| 117 | log.Warn().Err(err).Msg("failed to attach frontend to new webview") | 135 | log.Warn().Err(err).Msg("failed to attach frontend to new webview") | ||
| 118 | } | 136 | } | ||
| 119 | 137 | ||||
| 138 | + | // Apply content filters if configured | |||
| 139 | + | if p.filterApplier != nil { | |||
| 140 | + | p.filterApplier.ApplyTo(ctx, wv.ucm) | |||
| 141 | + | } | |||
| 142 | + | ||||
| 120 | return wv, nil | 143 | return wv, nil | ||
| 121 | } | 144 | } | ||
| 122 | 145 | ||||