feat(engine): wire SettingsApplier and FilterApplier into content coordinator
- commit
- 54ea5e7712409963163e1fa047df323f3e49597b
- author
- brice · · 2026-03-18 05:41 UTC
- parent
- 25cf728
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +55 -12
M internal/ui/app.go +8 -0
| @@ -1544,6 +1544,14 @@ func (a *App) initCoordinators(ctx context.Context) { | |||
| 1544 | 1544 | a.contentCoord.SetIdleInhibitor(a.deps.IdleInhibitor) | |
| 1545 | 1545 | } | |
| 1546 | 1546 | ||
| 1547 | + | // Wire engine settings and filter appliers for hot-reload and late-binding filters | |
| 1548 | + | if sa := a.deps.Engine.SettingsApplier(); sa != nil { | |
| 1549 | + | a.contentCoord.SetSettingsApplier(sa) | |
| 1550 | + | } | |
| 1551 | + | if fa := a.deps.Engine.FilterApplier(); fa != nil { | |
| 1552 | + | a.contentCoord.SetFilterApplier(fa) | |
| 1553 | + | } | |
| 1554 | + | ||
| 1547 | 1555 | // Wire external URL launcher (e.g. xdg-open for vscode://, spotify://) | |
| 1548 | 1556 | if a.deps.LaunchExternalURL != nil { | |
| 1549 | 1557 | a.contentCoord.SetOnLaunchExternalURL(a.deps.LaunchExternalURL) | |
| @@ -1544,6 +1544,14 @@ func (a *App) initCoordinators(ctx context.Context) { | |||||
| 1544 | a.contentCoord.SetIdleInhibitor(a.deps.IdleInhibitor) | 1544 | a.contentCoord.SetIdleInhibitor(a.deps.IdleInhibitor) | ||
| 1545 | } | 1545 | } | ||
| 1546 | 1546 | ||||
| 1547 | + | // Wire engine settings and filter appliers for hot-reload and late-binding filters | |||
| 1548 | + | if sa := a.deps.Engine.SettingsApplier(); sa != nil { | |||
| 1549 | + | a.contentCoord.SetSettingsApplier(sa) | |||
| 1550 | + | } | |||
| 1551 | + | if fa := a.deps.Engine.FilterApplier(); fa != nil { | |||
| 1552 | + | a.contentCoord.SetFilterApplier(fa) | |||
| 1553 | + | } | |||
| 1554 | + | ||||
| 1547 | // Wire external URL launcher (e.g. xdg-open for vscode://, spotify://) | 1555 | // Wire external URL launcher (e.g. xdg-open for vscode://, spotify://) | ||
| 1548 | if a.deps.LaunchExternalURL != nil { | 1556 | if a.deps.LaunchExternalURL != nil { | ||
| 1549 | a.contentCoord.SetOnLaunchExternalURL(a.deps.LaunchExternalURL) | 1557 | a.contentCoord.SetOnLaunchExternalURL(a.deps.LaunchExternalURL) | ||
M internal/ui/coordinator/content/bulk.go +29 -6
| @@ -10,17 +10,41 @@ import ( | |||
| 10 | 10 | ||
| 11 | 11 | // ApplyFiltersToAll applies content filters to all active webviews. | |
| 12 | 12 | // Called when filters become available after webviews were already created. | |
| 13 | - | // TODO: Accept a port.FilterApplier once engine abstraction is wired. | |
| 14 | 13 | func (c *Coordinator) ApplyFiltersToAll(ctx context.Context) { | |
| 15 | 14 | log := logging.FromContext(ctx) | |
| 16 | - | log.Debug().Msg("ApplyFiltersToAll: not yet implemented with engine abstraction") | |
| 15 | + | if c.filterApplier == nil { | |
| 16 | + | log.Debug().Msg("ApplyFiltersToAll: no filter applier configured") | |
| 17 | + | return | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | c.webViewsMu.RLock() | |
| 21 | + | snapshot := make([]port.WebView, 0, len(c.webViews)) | |
| 22 | + | for _, wv := range c.webViews { | |
| 23 | + | snapshot = append(snapshot, wv) | |
| 24 | + | } | |
| 25 | + | c.webViewsMu.RUnlock() | |
| 26 | + | ||
| 27 | + | c.filterApplier.ApplyToAll(ctx, snapshot) | |
| 28 | + | log.Debug().Int("count", len(snapshot)).Msg("applied filters to all webviews") | |
| 17 | 29 | } | |
| 18 | 30 | ||
| 19 | - | // ApplySettingsToAll reapplies WebKit settings to all active WebViews. | |
| 20 | - | // TODO: Accept engine-level settings once engine abstraction is wired. | |
| 31 | + | // ApplySettingsToAll reapplies engine settings to all active WebViews. | |
| 21 | 32 | func (c *Coordinator) ApplySettingsToAll(ctx context.Context) { | |
| 22 | 33 | log := logging.FromContext(ctx) | |
| 23 | - | log.Debug().Msg("ApplySettingsToAll: not yet implemented with engine abstraction") | |
| 34 | + | if c.settingsApplier == nil { | |
| 35 | + | log.Debug().Msg("ApplySettingsToAll: no settings applier configured") | |
| 36 | + | return | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | c.webViewsMu.RLock() | |
| 40 | + | snapshot := make([]port.WebView, 0, len(c.webViews)) | |
| 41 | + | for _, wv := range c.webViews { | |
| 42 | + | snapshot = append(snapshot, wv) | |
| 43 | + | } | |
| 44 | + | c.webViewsMu.RUnlock() | |
| 45 | + | ||
| 46 | + | c.settingsApplier.ApplyToAll(ctx, snapshot) | |
| 47 | + | log.Debug().Int("count", len(snapshot)).Msg("applied settings to all webviews") | |
| 24 | 48 | } | |
| 25 | 49 | ||
| 26 | 50 | // RefreshInjectedScriptsToAll clears and re-injects user scripts into all active WebViews. | |
| @@ -30,7 +54,6 @@ func (c *Coordinator) ApplySettingsToAll(ctx context.Context) { | |||
| 30 | 54 | // the scripts so future navigations pick up the latest values. | |
| 31 | 55 | // Script refresh is deferred for any WebView that is currently loading to avoid | |
| 32 | 56 | // removing scripts mid-navigation. | |
| 33 | - | // TODO: Accept engine-level injector once engine abstraction is wired. | |
| 34 | 57 | func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) { | |
| 35 | 58 | log := logging.FromContext(ctx) | |
| 36 | 59 | if c.injector == nil { | |
| @@ -10,17 +10,41 @@ import ( | |||||
| 10 | 10 | ||||
| 11 | // ApplyFiltersToAll applies content filters to all active webviews. | 11 | // ApplyFiltersToAll applies content filters to all active webviews. | ||
| 12 | // Called when filters become available after webviews were already created. | 12 | // Called when filters become available after webviews were already created. | ||
| 13 | - | // TODO: Accept a port.FilterApplier once engine abstraction is wired. | |||
| 14 | func (c *Coordinator) ApplyFiltersToAll(ctx context.Context) { | 13 | func (c *Coordinator) ApplyFiltersToAll(ctx context.Context) { | ||
| 15 | log := logging.FromContext(ctx) | 14 | log := logging.FromContext(ctx) | ||
| 16 | - | log.Debug().Msg("ApplyFiltersToAll: not yet implemented with engine abstraction") | 15 | + | if c.filterApplier == nil { |
| 16 | + | log.Debug().Msg("ApplyFiltersToAll: no filter applier configured") | |||
| 17 | + | return | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | c.webViewsMu.RLock() | |||
| 21 | + | snapshot := make([]port.WebView, 0, len(c.webViews)) | |||
| 22 | + | for _, wv := range c.webViews { | |||
| 23 | + | snapshot = append(snapshot, wv) | |||
| 24 | + | } | |||
| 25 | + | c.webViewsMu.RUnlock() | |||
| 26 | + | ||||
| 27 | + | c.filterApplier.ApplyToAll(ctx, snapshot) | |||
| 28 | + | log.Debug().Int("count", len(snapshot)).Msg("applied filters to all webviews") | |||
| 17 | } | 29 | } | ||
| 18 | 30 | ||||
| 19 | - | // ApplySettingsToAll reapplies WebKit settings to all active WebViews. | 31 | + | // ApplySettingsToAll reapplies engine settings to all active WebViews. |
| 20 | - | // TODO: Accept engine-level settings once engine abstraction is wired. | |||
| 21 | func (c *Coordinator) ApplySettingsToAll(ctx context.Context) { | 32 | func (c *Coordinator) ApplySettingsToAll(ctx context.Context) { | ||
| 22 | log := logging.FromContext(ctx) | 33 | log := logging.FromContext(ctx) | ||
| 23 | - | log.Debug().Msg("ApplySettingsToAll: not yet implemented with engine abstraction") | 34 | + | if c.settingsApplier == nil { |
| 35 | + | log.Debug().Msg("ApplySettingsToAll: no settings applier configured") | |||
| 36 | + | return | |||
| 37 | + | } | |||
| 38 | + | ||||
| 39 | + | c.webViewsMu.RLock() | |||
| 40 | + | snapshot := make([]port.WebView, 0, len(c.webViews)) | |||
| 41 | + | for _, wv := range c.webViews { | |||
| 42 | + | snapshot = append(snapshot, wv) | |||
| 43 | + | } | |||
| 44 | + | c.webViewsMu.RUnlock() | |||
| 45 | + | ||||
| 46 | + | c.settingsApplier.ApplyToAll(ctx, snapshot) | |||
| 47 | + | log.Debug().Int("count", len(snapshot)).Msg("applied settings to all webviews") | |||
| 24 | } | 48 | } | ||
| 25 | 49 | ||||
| 26 | // RefreshInjectedScriptsToAll clears and re-injects user scripts into all active WebViews. | 50 | // RefreshInjectedScriptsToAll clears and re-injects user scripts into all active WebViews. | ||
| @@ -30,7 +54,6 @@ func (c *Coordinator) ApplySettingsToAll(ctx context.Context) { | |||||
| 30 | // the scripts so future navigations pick up the latest values. | 54 | // the scripts so future navigations pick up the latest values. | ||
| 31 | // Script refresh is deferred for any WebView that is currently loading to avoid | 55 | // Script refresh is deferred for any WebView that is currently loading to avoid | ||
| 32 | // removing scripts mid-navigation. | 56 | // removing scripts mid-navigation. | ||
| 33 | - | // TODO: Accept engine-level injector once engine abstraction is wired. | |||
| 34 | func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) { | 57 | func (c *Coordinator) RefreshInjectedScriptsToAll(ctx context.Context) { | ||
| 35 | log := logging.FromContext(ctx) | 58 | log := logging.FromContext(ctx) | ||
| 36 | if c.injector == nil { | 59 | if c.injector == nil { | ||
M internal/ui/coordinator/content/coordinator.go +18 -6
| @@ -18,12 +18,14 @@ import ( | |||
| 18 | 18 | ||
| 19 | 19 | // Coordinator manages WebView lifecycle, title tracking, and content attachment. | |
| 20 | 20 | type Coordinator struct { | |
| 21 | - | pool port.WebViewPool | |
| 22 | - | widgetFactory layout.WidgetFactory | |
| 23 | - | faviconAdapter *adapter.FaviconAdapter | |
| 24 | - | zoomUC *usecase.ManageZoomUseCase | |
| 25 | - | permissionUC *usecase.HandlePermissionUseCase | |
| 26 | - | injector port.ContentInjector | |
| 21 | + | pool port.WebViewPool | |
| 22 | + | widgetFactory layout.WidgetFactory | |
| 23 | + | faviconAdapter *adapter.FaviconAdapter | |
| 24 | + | zoomUC *usecase.ManageZoomUseCase | |
| 25 | + | permissionUC *usecase.HandlePermissionUseCase | |
| 26 | + | injector port.ContentInjector | |
| 27 | + | settingsApplier port.SettingsApplier // optional: nil if engine doesn't support | |
| 28 | + | filterApplier port.FilterApplier // optional: nil if engine doesn't support | |
| 27 | 29 | ||
| 28 | 30 | webViews map[entity.PaneID]port.WebView | |
| 29 | 31 | webViewsMu sync.RWMutex | |
| @@ -238,6 +240,16 @@ func (c *Coordinator) SetOnLaunchExternalURL(fn func(uri string)) { | |||
| 238 | 240 | c.onLaunchExternalURL = fn | |
| 239 | 241 | } | |
| 240 | 242 | ||
| 243 | + | // SetSettingsApplier sets the engine settings applier for config hot-reload. | |
| 244 | + | func (c *Coordinator) SetSettingsApplier(sa port.SettingsApplier) { | |
| 245 | + | c.settingsApplier = sa | |
| 246 | + | } | |
| 247 | + | ||
| 248 | + | // SetFilterApplier sets the content filter applier for late-binding filters. | |
| 249 | + | func (c *Coordinator) SetFilterApplier(fa port.FilterApplier) { | |
| 250 | + | c.filterApplier = fa | |
| 251 | + | } | |
| 252 | + | ||
| 241 | 253 | // ActivePaneID returns the currently active pane ID used by navigation. | |
| 242 | 254 | func (c *Coordinator) ActivePaneID(ctx context.Context) entity.PaneID { | |
| 243 | 255 | if paneID, ok := c.activePaneOverrideID(); ok { | |
| @@ -18,12 +18,14 @@ import ( | |||||
| 18 | 18 | ||||
| 19 | // Coordinator manages WebView lifecycle, title tracking, and content attachment. | 19 | // Coordinator manages WebView lifecycle, title tracking, and content attachment. | ||
| 20 | type Coordinator struct { | 20 | type Coordinator struct { | ||
| 21 | - | pool port.WebViewPool | 21 | + | pool port.WebViewPool |
| 22 | - | widgetFactory layout.WidgetFactory | 22 | + | widgetFactory layout.WidgetFactory |
| 23 | - | faviconAdapter *adapter.FaviconAdapter | 23 | + | faviconAdapter *adapter.FaviconAdapter |
| 24 | - | zoomUC *usecase.ManageZoomUseCase | 24 | + | zoomUC *usecase.ManageZoomUseCase |
| 25 | - | permissionUC *usecase.HandlePermissionUseCase | 25 | + | permissionUC *usecase.HandlePermissionUseCase |
| 26 | - | injector port.ContentInjector | 26 | + | injector port.ContentInjector |
| 27 | + | settingsApplier port.SettingsApplier // optional: nil if engine doesn't support | |||
| 28 | + | filterApplier port.FilterApplier // optional: nil if engine doesn't support | |||
| 27 | 29 | ||||
| 28 | webViews map[entity.PaneID]port.WebView | 30 | webViews map[entity.PaneID]port.WebView | ||
| 29 | webViewsMu sync.RWMutex | 31 | webViewsMu sync.RWMutex | ||
| @@ -238,6 +240,16 @@ func (c *Coordinator) SetOnLaunchExternalURL(fn func(uri string)) { | |||||
| 238 | c.onLaunchExternalURL = fn | 240 | c.onLaunchExternalURL = fn | ||
| 239 | } | 241 | } | ||
| 240 | 242 | ||||
| 243 | + | // SetSettingsApplier sets the engine settings applier for config hot-reload. | |||
| 244 | + | func (c *Coordinator) SetSettingsApplier(sa port.SettingsApplier) { | |||
| 245 | + | c.settingsApplier = sa | |||
| 246 | + | } | |||
| 247 | + | ||||
| 248 | + | // SetFilterApplier sets the content filter applier for late-binding filters. | |||
| 249 | + | func (c *Coordinator) SetFilterApplier(fa port.FilterApplier) { | |||
| 250 | + | c.filterApplier = fa | |||
| 251 | + | } | |||
| 252 | + | ||||
| 241 | // ActivePaneID returns the currently active pane ID used by navigation. | 253 | // ActivePaneID returns the currently active pane ID used by navigation. | ||
| 242 | func (c *Coordinator) ActivePaneID(ctx context.Context) entity.PaneID { | 254 | func (c *Coordinator) ActivePaneID(ctx context.Context) entity.PaneID { | ||
| 243 | if paneID, ok := c.activePaneOverrideID(); ok { | 255 | if paneID, ok := c.activePaneOverrideID(); ok { | ||