fix(webkit): restore content blocking after gotk4 migration
During the gotk4 migration, content blocking was left as empty TODO stubs.
This commit restores full functionality using WebKit's native content
filtering API.
Changes:
1. New file: pkg/webkit/content_blocking.go
- ContentBlockingManager: Manages WebKit UserContentFilterStore
- ApplyFiltersFromJSON(): Compiles WebKit JSON rules and applies to UserContentManager
- ApplyFiltersToWebView(): Convenience wrapper for filter application
2. Modified: pkg/webkit/webview.go
- InitializeContentBlocking(): Now properly implements filter application
- Signature changed from []string to []byte for WebKit JSON rules
- UpdateContentFilters(): Marked as deprecated with warning
3. Modified: internal/app/browser/webview.go
- setupContentBlocking(): Wired up to fetch filters from FilterManager
- Added callback to apply filters when they become ready
- Filters now properly applied via UserContentManager.AddFilter()
How It Works:
```
FilterManager.GetNetworkFilters() → WebKit JSON []byte
↓
UserContentFilterStore.Save() → Compile to native format
↓
UserContentManager.AddFilter() → ✅ Content blocking active
```
WebKit Integration:
- Uses webkit.UserContentFilterStore for efficient filter compilation
- Compiles JSON rules asynchronously to avoid blocking startup
- Filters applied via webkit.UserContentManager.AddFilter()
- Follows WebKit content blocker spec: https://webkit.org/blog/3476/content-blockers-first-look/
Testing:
- Verified on lesnumeriques.com: ads and trackers successfully blocked
- Filter loading: 117K+ network rules successfully compiled and applied
- Minor WebKit preconnect assertion errors are harmless (WebKit bug with blockers)
Before: Content blocking was non-functional (empty TODO stubs)
After: Full ad/tracker blocking working via WebKit native API- commit
- 788f19a30144262fda9005f88dec346ff676cac9
- author
- bamen · · 2025-10-15 05:32 UTC
- committer
- bamen · · 2025-10-15 05:33 UTC
- parent
- 0e199d7
- signature
- unsigned
3 files · +168 -41
M internal/app/browser/webview.go +30 -37
| @@ -336,53 +336,46 @@ func (app *BrowserApp) setupContentBlocking() error { | |||
| 336 | 336 | // Store reference to filter manager | |
| 337 | 337 | app.filterManager = filterManager | |
| 338 | 338 | ||
| 339 | - | // Set up callback to re-apply network filters when they become ready | |
| 339 | + | // Set up callback to apply network filters when they become ready | |
| 340 | 340 | // This callback will be called from the async filter loading process | |
| 341 | 341 | filterManager.SetFiltersReadyCallback(func() { | |
| 342 | - | // Add small delay to avoid race conditions with WebView initialization | |
| 343 | - | go func() { | |
| 344 | - | time.Sleep(300 * time.Millisecond) | |
| 345 | - | if app.webView != nil { | |
| 346 | - | // Note: Content filtering is now handled via WebKit's UserContentManager | |
| 347 | - | // UpdateContentFilters would need to be implemented if needed | |
| 348 | - | log.Printf("Filters loaded - content blocking via UserContentManager") | |
| 349 | - | } | |
| 350 | - | }() | |
| 342 | + | log.Printf("[filtering] Filters ready, applying to WebView...") | |
| 343 | + | ||
| 344 | + | // Get the WebKit JSON rules from filter manager | |
| 345 | + | filterJSON, err := filterManager.GetNetworkFilters() | |
| 346 | + | if err != nil { | |
| 347 | + | log.Printf("[filtering] Failed to get network filters: %v", err) | |
| 348 | + | return | |
| 349 | + | } | |
| 350 | + | ||
| 351 | + | if len(filterJSON) == 0 { | |
| 352 | + | log.Printf("[filtering] No network filters to apply") | |
| 353 | + | return | |
| 354 | + | } | |
| 355 | + | ||
| 356 | + | log.Printf("[filtering] Got %d bytes of WebKit JSON rules", len(filterJSON)) | |
| 357 | + | ||
| 358 | + | // Apply filters to the WebView | |
| 359 | + | // This must be done on the main thread since it touches GTK/WebKit | |
| 360 | + | if app.webView != nil { | |
| 361 | + | app.webView.RunOnMainThread(func() { | |
| 362 | + | if err := app.webView.InitializeContentBlocking(filterJSON); err != nil { | |
| 363 | + | log.Printf("[filtering] Failed to apply content filters: %v", err) | |
| 364 | + | } else { | |
| 365 | + | log.Printf("[filtering] ✅ Content blocking enabled successfully") | |
| 366 | + | } | |
| 367 | + | }) | |
| 368 | + | } | |
| 351 | 369 | }) | |
| 352 | 370 | ||
| 353 | - | // Start async filter loading early (before WebView content blocking initialization) | |
| 354 | - | // This allows filters to be ready when the WebView needs them | |
| 371 | + | // Start async filter loading | |
| 372 | + | // This allows filters to compile in the background while the browser starts | |
| 355 | 373 | go func() { | |
| 356 | 374 | if err := filtering.InitializeFiltersAsync(filterManager); err != nil { | |
| 357 | 375 | log.Printf("Warning: failed to initialize filters asynchronously: %v", err) | |
| 358 | 376 | } | |
| 359 | 377 | }() | |
| 360 | 378 | ||
| 361 | - | // Initialize content blocking in WebView with delay | |
| 362 | - | // Wait for WebView to be fully loaded before setting up content blocking | |
| 363 | - | go func() { | |
| 364 | - | // Wait extra time for the first navigation to avoid preconnect interference | |
| 365 | - | log.Printf("Waiting for WebView to complete initial load before enabling content blocking...") | |
| 366 | - | time.Sleep(3000 * time.Millisecond) // 3 seconds for first load stability | |
| 367 | - | ||
| 368 | - | // Note: Content blocking is now handled via WebKit's UserContentManager | |
| 369 | - | // The InitializeContentBlocking API would need to be implemented if needed | |
| 370 | - | log.Printf("Content blocking enabled via UserContentManager") | |
| 371 | - | ||
| 372 | - | // Register navigation handler for GUI injection | |
| 373 | - | // Content filtering is handled by UserContentManager automatically | |
| 374 | - | app.webView.RegisterURIChangedHandler(func(uri string) { | |
| 375 | - | // Add small delay to avoid conflicts with page load | |
| 376 | - | go func() { | |
| 377 | - | time.Sleep(200 * time.Millisecond) | |
| 378 | - | ||
| 379 | - | // Note: Content filtering is automatic via UserContentManager | |
| 380 | - | // GUI bundle (controls and toast) is injected as User Script | |
| 381 | - | log.Printf("[webview] Navigated to: %s", uri) | |
| 382 | - | }() | |
| 383 | - | }) | |
| 384 | - | }() | |
| 385 | - | ||
| 386 | 379 | log.Printf("Content blocking system initialization started") | |
| 387 | 380 | return nil | |
| 388 | 381 | } | |
| @@ -336,53 +336,46 @@ func (app *BrowserApp) setupContentBlocking() error { | |||||
| 336 | // Store reference to filter manager | 336 | // Store reference to filter manager | ||
| 337 | app.filterManager = filterManager | 337 | app.filterManager = filterManager | ||
| 338 | 338 | ||||
| 339 | - | // Set up callback to re-apply network filters when they become ready | 339 | + | // Set up callback to apply network filters when they become ready |
| 340 | // This callback will be called from the async filter loading process | 340 | // This callback will be called from the async filter loading process | ||
| 341 | filterManager.SetFiltersReadyCallback(func() { | 341 | filterManager.SetFiltersReadyCallback(func() { | ||
| 342 | - | // Add small delay to avoid race conditions with WebView initialization | 342 | + | log.Printf("[filtering] Filters ready, applying to WebView...") |
| 343 | - | go func() { | 343 | + | |
| 344 | - | time.Sleep(300 * time.Millisecond) | 344 | + | // Get the WebKit JSON rules from filter manager |
| 345 | - | if app.webView != nil { | 345 | + | filterJSON, err := filterManager.GetNetworkFilters() |
| 346 | - | // Note: Content filtering is now handled via WebKit's UserContentManager | 346 | + | if err != nil { |
| 347 | - | // UpdateContentFilters would need to be implemented if needed | 347 | + | log.Printf("[filtering] Failed to get network filters: %v", err) |
| 348 | - | log.Printf("Filters loaded - content blocking via UserContentManager") | 348 | + | return |
| 349 | - | } | 349 | + | } |
| 350 | - | }() | 350 | + | |
| 351 | + | if len(filterJSON) == 0 { | |||
| 352 | + | log.Printf("[filtering] No network filters to apply") | |||
| 353 | + | return | |||
| 354 | + | } | |||
| 355 | + | ||||
| 356 | + | log.Printf("[filtering] Got %d bytes of WebKit JSON rules", len(filterJSON)) | |||
| 357 | + | ||||
| 358 | + | // Apply filters to the WebView | |||
| 359 | + | // This must be done on the main thread since it touches GTK/WebKit | |||
| 360 | + | if app.webView != nil { | |||
| 361 | + | app.webView.RunOnMainThread(func() { | |||
| 362 | + | if err := app.webView.InitializeContentBlocking(filterJSON); err != nil { | |||
| 363 | + | log.Printf("[filtering] Failed to apply content filters: %v", err) | |||
| 364 | + | } else { | |||
| 365 | + | log.Printf("[filtering] ✅ Content blocking enabled successfully") | |||
| 366 | + | } | |||
| 367 | + | }) | |||
| 368 | + | } | |||
| 351 | }) | 369 | }) | ||
| 352 | 370 | ||||
| 353 | - | // Start async filter loading early (before WebView content blocking initialization) | 371 | + | // Start async filter loading |
| 354 | - | // This allows filters to be ready when the WebView needs them | 372 | + | // This allows filters to compile in the background while the browser starts |
| 355 | go func() { | 373 | go func() { | ||
| 356 | if err := filtering.InitializeFiltersAsync(filterManager); err != nil { | 374 | if err := filtering.InitializeFiltersAsync(filterManager); err != nil { | ||
| 357 | log.Printf("Warning: failed to initialize filters asynchronously: %v", err) | 375 | log.Printf("Warning: failed to initialize filters asynchronously: %v", err) | ||
| 358 | } | 376 | } | ||
| 359 | }() | 377 | }() | ||
| 360 | 378 | ||||
| 361 | - | // Initialize content blocking in WebView with delay | |||
| 362 | - | // Wait for WebView to be fully loaded before setting up content blocking | |||
| 363 | - | go func() { | |||
| 364 | - | // Wait extra time for the first navigation to avoid preconnect interference | |||
| 365 | - | log.Printf("Waiting for WebView to complete initial load before enabling content blocking...") | |||
| 366 | - | time.Sleep(3000 * time.Millisecond) // 3 seconds for first load stability | |||
| 367 | - | ||||
| 368 | - | // Note: Content blocking is now handled via WebKit's UserContentManager | |||
| 369 | - | // The InitializeContentBlocking API would need to be implemented if needed | |||
| 370 | - | log.Printf("Content blocking enabled via UserContentManager") | |||
| 371 | - | ||||
| 372 | - | // Register navigation handler for GUI injection | |||
| 373 | - | // Content filtering is handled by UserContentManager automatically | |||
| 374 | - | app.webView.RegisterURIChangedHandler(func(uri string) { | |||
| 375 | - | // Add small delay to avoid conflicts with page load | |||
| 376 | - | go func() { | |||
| 377 | - | time.Sleep(200 * time.Millisecond) | |||
| 378 | - | ||||
| 379 | - | // Note: Content filtering is automatic via UserContentManager | |||
| 380 | - | // GUI bundle (controls and toast) is injected as User Script | |||
| 381 | - | log.Printf("[webview] Navigated to: %s", uri) | |||
| 382 | - | }() | |||
| 383 | - | }) | |||
| 384 | - | }() | |||
| 385 | - | ||||
| 386 | log.Printf("Content blocking system initialization started") | 379 | log.Printf("Content blocking system initialization started") | ||
| 387 | return nil | 380 | return nil | ||
| 388 | } | 381 | } | ||
A pkg/webkit/content_blocking.go +128 -0
| @@ -0,0 +1,128 @@ | |||
| 1 | + | package webkit | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "context" | |
| 5 | + | "fmt" | |
| 6 | + | "log" | |
| 7 | + | "path/filepath" | |
| 8 | + | ||
| 9 | + | webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6" | |
| 10 | + | "github.com/diamondburned/gotk4/pkg/gio/v2" | |
| 11 | + | "github.com/diamondburned/gotk4/pkg/glib/v2" | |
| 12 | + | ) | |
| 13 | + | ||
| 14 | + | // ContentBlockingManager handles WebKit content filtering | |
| 15 | + | type ContentBlockingManager struct { | |
| 16 | + | filterStore *webkit.UserContentFilterStore | |
| 17 | + | storagePath string | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // NewContentBlockingManager creates a new content blocking manager | |
| 21 | + | func NewContentBlockingManager(storagePath string) (*ContentBlockingManager, error) { | |
| 22 | + | // Create the filter store | |
| 23 | + | filterStore := webkit.NewUserContentFilterStore(storagePath) | |
| 24 | + | if filterStore == nil { | |
| 25 | + | return nil, fmt.Errorf("failed to create UserContentFilterStore") | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | log.Printf("[webkit] Created UserContentFilterStore at: %s", storagePath) | |
| 29 | + | ||
| 30 | + | return &ContentBlockingManager{ | |
| 31 | + | filterStore: filterStore, | |
| 32 | + | storagePath: storagePath, | |
| 33 | + | }, nil | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // ApplyFiltersFromJSON compiles and applies WebKit JSON filter rules to a UserContentManager | |
| 37 | + | // The jsonRules parameter should be a JSON array of WebKit content blocker rules | |
| 38 | + | func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentManager, identifier string, jsonRules []byte) error { | |
| 39 | + | if ucm == nil { | |
| 40 | + | return fmt.Errorf("UserContentManager is nil") | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | if len(jsonRules) == 0 { | |
| 44 | + | return fmt.Errorf("empty filter rules") | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | log.Printf("[webkit] Applying content filters (identifier: %s, rules size: %d bytes)", identifier, len(jsonRules)) | |
| 48 | + | ||
| 49 | + | // Convert []byte to *glib.Bytes for WebKit API | |
| 50 | + | // NewBytesWithGo keeps the Go slice alive for the GBytes lifetime | |
| 51 | + | gBytes := glib.NewBytesWithGo(jsonRules) | |
| 52 | + | ||
| 53 | + | // Save filters to store asynchronously | |
| 54 | + | // This compiles the JSON rules into WebKit's internal format | |
| 55 | + | ctx := context.Background() | |
| 56 | + | ||
| 57 | + | // Use a channel to wait for the async operation | |
| 58 | + | done := make(chan error, 1) | |
| 59 | + | ||
| 60 | + | cbm.filterStore.Save(ctx, identifier, gBytes, func(result gio.AsyncResulter) { | |
| 61 | + | // This callback runs when compilation is complete | |
| 62 | + | filter, err := cbm.filterStore.SaveFinish(result) | |
| 63 | + | if err != nil { | |
| 64 | + | log.Printf("[webkit] Failed to compile content filter: %v", err) | |
| 65 | + | done <- fmt.Errorf("failed to compile filter: %w", err) | |
| 66 | + | return | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | if filter == nil { | |
| 70 | + | log.Printf("[webkit] Filter compilation returned nil") | |
| 71 | + | done <- fmt.Errorf("filter compilation returned nil") | |
| 72 | + | return | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // Add the compiled filter to the UserContentManager | |
| 76 | + | // This must be done on the main GTK thread | |
| 77 | + | RunOnMainThread(func() { | |
| 78 | + | ucm.AddFilter(filter) | |
| 79 | + | log.Printf("[webkit] Successfully added content filter: %s", identifier) | |
| 80 | + | done <- nil | |
| 81 | + | }) | |
| 82 | + | }) | |
| 83 | + | ||
| 84 | + | // Wait for completion | |
| 85 | + | return <-done | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | // RemoveAllFilters removes all content filters from a UserContentManager | |
| 89 | + | func (cbm *ContentBlockingManager) RemoveAllFilters(ucm *webkit.UserContentManager) { | |
| 90 | + | if ucm != nil { | |
| 91 | + | ucm.RemoveAllFilters() | |
| 92 | + | log.Printf("[webkit] Removed all content filters") | |
| 93 | + | } | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | // GetFilterStore returns the underlying UserContentFilterStore | |
| 97 | + | func (cbm *ContentBlockingManager) GetFilterStore() *webkit.UserContentFilterStore { | |
| 98 | + | return cbm.filterStore | |
| 99 | + | } | |
| 100 | + | ||
| 101 | + | // ApplyFiltersToWebView applies content blocking filters to a WebView | |
| 102 | + | func ApplyFiltersToWebView(view *WebView, filterJSON []byte) error { | |
| 103 | + | if view == nil || view.view == nil { | |
| 104 | + | return fmt.Errorf("invalid WebView") | |
| 105 | + | } | |
| 106 | + | ||
| 107 | + | // Get the UserContentManager | |
| 108 | + | ucm := view.view.UserContentManager() | |
| 109 | + | if ucm == nil { | |
| 110 | + | return fmt.Errorf("UserContentManager is nil") | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | // Create content blocking manager | |
| 114 | + | // Use a subdirectory of the cache dir for filter storage | |
| 115 | + | dataDir := view.config.DataDir | |
| 116 | + | if dataDir == "" { | |
| 117 | + | return fmt.Errorf("WebView has no data directory configured") | |
| 118 | + | } | |
| 119 | + | ||
| 120 | + | filterStorePath := filepath.Join(dataDir, "content-filters") | |
| 121 | + | cbm, err := NewContentBlockingManager(filterStorePath) | |
| 122 | + | if err != nil { | |
| 123 | + | return fmt.Errorf("failed to create content blocking manager: %w", err) | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | // Apply the filters | |
| 127 | + | return cbm.ApplyFiltersFromJSON(ucm, "adblock-filters", filterJSON) | |
| 128 | + | } | |
| @@ -0,0 +1,128 @@ | |||||
| 1 | + | package webkit | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "context" | |||
| 5 | + | "fmt" | |||
| 6 | + | "log" | |||
| 7 | + | "path/filepath" | |||
| 8 | + | ||||
| 9 | + | webkit "github.com/diamondburned/gotk4-webkitgtk/pkg/webkit/v6" | |||
| 10 | + | "github.com/diamondburned/gotk4/pkg/gio/v2" | |||
| 11 | + | "github.com/diamondburned/gotk4/pkg/glib/v2" | |||
| 12 | + | ) | |||
| 13 | + | ||||
| 14 | + | // ContentBlockingManager handles WebKit content filtering | |||
| 15 | + | type ContentBlockingManager struct { | |||
| 16 | + | filterStore *webkit.UserContentFilterStore | |||
| 17 | + | storagePath string | |||
| 18 | + | } | |||
| 19 | + | ||||
| 20 | + | // NewContentBlockingManager creates a new content blocking manager | |||
| 21 | + | func NewContentBlockingManager(storagePath string) (*ContentBlockingManager, error) { | |||
| 22 | + | // Create the filter store | |||
| 23 | + | filterStore := webkit.NewUserContentFilterStore(storagePath) | |||
| 24 | + | if filterStore == nil { | |||
| 25 | + | return nil, fmt.Errorf("failed to create UserContentFilterStore") | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | log.Printf("[webkit] Created UserContentFilterStore at: %s", storagePath) | |||
| 29 | + | ||||
| 30 | + | return &ContentBlockingManager{ | |||
| 31 | + | filterStore: filterStore, | |||
| 32 | + | storagePath: storagePath, | |||
| 33 | + | }, nil | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | // ApplyFiltersFromJSON compiles and applies WebKit JSON filter rules to a UserContentManager | |||
| 37 | + | // The jsonRules parameter should be a JSON array of WebKit content blocker rules | |||
| 38 | + | func (cbm *ContentBlockingManager) ApplyFiltersFromJSON(ucm *webkit.UserContentManager, identifier string, jsonRules []byte) error { | |||
| 39 | + | if ucm == nil { | |||
| 40 | + | return fmt.Errorf("UserContentManager is nil") | |||
| 41 | + | } | |||
| 42 | + | ||||
| 43 | + | if len(jsonRules) == 0 { | |||
| 44 | + | return fmt.Errorf("empty filter rules") | |||
| 45 | + | } | |||
| 46 | + | ||||
| 47 | + | log.Printf("[webkit] Applying content filters (identifier: %s, rules size: %d bytes)", identifier, len(jsonRules)) | |||
| 48 | + | ||||
| 49 | + | // Convert []byte to *glib.Bytes for WebKit API | |||
| 50 | + | // NewBytesWithGo keeps the Go slice alive for the GBytes lifetime | |||
| 51 | + | gBytes := glib.NewBytesWithGo(jsonRules) | |||
| 52 | + | ||||
| 53 | + | // Save filters to store asynchronously | |||
| 54 | + | // This compiles the JSON rules into WebKit's internal format | |||
| 55 | + | ctx := context.Background() | |||
| 56 | + | ||||
| 57 | + | // Use a channel to wait for the async operation | |||
| 58 | + | done := make(chan error, 1) | |||
| 59 | + | ||||
| 60 | + | cbm.filterStore.Save(ctx, identifier, gBytes, func(result gio.AsyncResulter) { | |||
| 61 | + | // This callback runs when compilation is complete | |||
| 62 | + | filter, err := cbm.filterStore.SaveFinish(result) | |||
| 63 | + | if err != nil { | |||
| 64 | + | log.Printf("[webkit] Failed to compile content filter: %v", err) | |||
| 65 | + | done <- fmt.Errorf("failed to compile filter: %w", err) | |||
| 66 | + | return | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | if filter == nil { | |||
| 70 | + | log.Printf("[webkit] Filter compilation returned nil") | |||
| 71 | + | done <- fmt.Errorf("filter compilation returned nil") | |||
| 72 | + | return | |||
| 73 | + | } | |||
| 74 | + | ||||
| 75 | + | // Add the compiled filter to the UserContentManager | |||
| 76 | + | // This must be done on the main GTK thread | |||
| 77 | + | RunOnMainThread(func() { | |||
| 78 | + | ucm.AddFilter(filter) | |||
| 79 | + | log.Printf("[webkit] Successfully added content filter: %s", identifier) | |||
| 80 | + | done <- nil | |||
| 81 | + | }) | |||
| 82 | + | }) | |||
| 83 | + | ||||
| 84 | + | // Wait for completion | |||
| 85 | + | return <-done | |||
| 86 | + | } | |||
| 87 | + | ||||
| 88 | + | // RemoveAllFilters removes all content filters from a UserContentManager | |||
| 89 | + | func (cbm *ContentBlockingManager) RemoveAllFilters(ucm *webkit.UserContentManager) { | |||
| 90 | + | if ucm != nil { | |||
| 91 | + | ucm.RemoveAllFilters() | |||
| 92 | + | log.Printf("[webkit] Removed all content filters") | |||
| 93 | + | } | |||
| 94 | + | } | |||
| 95 | + | ||||
| 96 | + | // GetFilterStore returns the underlying UserContentFilterStore | |||
| 97 | + | func (cbm *ContentBlockingManager) GetFilterStore() *webkit.UserContentFilterStore { | |||
| 98 | + | return cbm.filterStore | |||
| 99 | + | } | |||
| 100 | + | ||||
| 101 | + | // ApplyFiltersToWebView applies content blocking filters to a WebView | |||
| 102 | + | func ApplyFiltersToWebView(view *WebView, filterJSON []byte) error { | |||
| 103 | + | if view == nil || view.view == nil { | |||
| 104 | + | return fmt.Errorf("invalid WebView") | |||
| 105 | + | } | |||
| 106 | + | ||||
| 107 | + | // Get the UserContentManager | |||
| 108 | + | ucm := view.view.UserContentManager() | |||
| 109 | + | if ucm == nil { | |||
| 110 | + | return fmt.Errorf("UserContentManager is nil") | |||
| 111 | + | } | |||
| 112 | + | ||||
| 113 | + | // Create content blocking manager | |||
| 114 | + | // Use a subdirectory of the cache dir for filter storage | |||
| 115 | + | dataDir := view.config.DataDir | |||
| 116 | + | if dataDir == "" { | |||
| 117 | + | return fmt.Errorf("WebView has no data directory configured") | |||
| 118 | + | } | |||
| 119 | + | ||||
| 120 | + | filterStorePath := filepath.Join(dataDir, "content-filters") | |||
| 121 | + | cbm, err := NewContentBlockingManager(filterStorePath) | |||
| 122 | + | if err != nil { | |||
| 123 | + | return fmt.Errorf("failed to create content blocking manager: %w", err) | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | // Apply the filters | |||
| 127 | + | return cbm.ApplyFiltersFromJSON(ucm, "adblock-filters", filterJSON) | |||
| 128 | + | } | |||
M pkg/webkit/webview.go +10 -4
| @@ -713,12 +713,14 @@ func (w *WebView) UpdateContentFilters(rules string) error { | |||
| 713 | 713 | return ErrWebViewDestroyed | |
| 714 | 714 | } | |
| 715 | 715 | ||
| 716 | - | // TODO: Implement content filtering using WebKit's UserContentManager | |
| 716 | + | // Note: Content filtering is handled via InitializeContentBlocking() | |
| 717 | + | // This method is kept for backward compatibility but is deprecated | |
| 718 | + | log.Printf("[webkit] UpdateContentFilters called but is deprecated - use InitializeContentBlocking instead") | |
| 717 | 719 | return nil | |
| 718 | 720 | } | |
| 719 | 721 | ||
| 720 | 722 | // InitializeContentBlocking initializes content blocking with filter lists | |
| 721 | - | func (w *WebView) InitializeContentBlocking(filterLists []string) error { | |
| 723 | + | func (w *WebView) InitializeContentBlocking(filterJSON []byte) error { | |
| 722 | 724 | w.mu.RLock() | |
| 723 | 725 | defer w.mu.RUnlock() | |
| 724 | 726 | ||
| @@ -726,8 +728,12 @@ func (w *WebView) InitializeContentBlocking(filterLists []string) error { | |||
| 726 | 728 | return ErrWebViewDestroyed | |
| 727 | 729 | } | |
| 728 | 730 | ||
| 729 | - | // TODO: Implement content blocking initialization | |
| 730 | - | return nil | |
| 731 | + | if len(filterJSON) == 0 { | |
| 732 | + | return fmt.Errorf("no filter rules provided") | |
| 733 | + | } | |
| 734 | + | ||
| 735 | + | // Apply filters using the content blocking API | |
| 736 | + | return ApplyFiltersToWebView(w, filterJSON) | |
| 731 | 737 | } | |
| 732 | 738 | ||
| 733 | 739 | // OnNavigate registers a navigation handler | |
| @@ -713,12 +713,14 @@ func (w *WebView) UpdateContentFilters(rules string) error { | |||||
| 713 | return ErrWebViewDestroyed | 713 | return ErrWebViewDestroyed | ||
| 714 | } | 714 | } | ||
| 715 | 715 | ||||
| 716 | - | // TODO: Implement content filtering using WebKit's UserContentManager | 716 | + | // Note: Content filtering is handled via InitializeContentBlocking() |
| 717 | + | // This method is kept for backward compatibility but is deprecated | |||
| 718 | + | log.Printf("[webkit] UpdateContentFilters called but is deprecated - use InitializeContentBlocking instead") | |||
| 717 | return nil | 719 | return nil | ||
| 718 | } | 720 | } | ||
| 719 | 721 | ||||
| 720 | // InitializeContentBlocking initializes content blocking with filter lists | 722 | // InitializeContentBlocking initializes content blocking with filter lists | ||
| 721 | - | func (w *WebView) InitializeContentBlocking(filterLists []string) error { | 723 | + | func (w *WebView) InitializeContentBlocking(filterJSON []byte) error { |
| 722 | w.mu.RLock() | 724 | w.mu.RLock() | ||
| 723 | defer w.mu.RUnlock() | 725 | defer w.mu.RUnlock() | ||
| 724 | 726 | ||||
| @@ -726,8 +728,12 @@ func (w *WebView) InitializeContentBlocking(filterLists []string) error { | |||||
| 726 | return ErrWebViewDestroyed | 728 | return ErrWebViewDestroyed | ||
| 727 | } | 729 | } | ||
| 728 | 730 | ||||
| 729 | - | // TODO: Implement content blocking initialization | 731 | + | if len(filterJSON) == 0 { |
| 730 | - | return nil | 732 | + | return fmt.Errorf("no filter rules provided") |
| 733 | + | } | |||
| 734 | + | ||||
| 735 | + | // Apply filters using the content blocking API | |||
| 736 | + | return ApplyFiltersToWebView(w, filterJSON) | |||
| 731 | } | 737 | } | ||
| 732 | 738 | ||||
| 733 | // OnNavigate registers a navigation handler | 739 | // OnNavigate registers a navigation handler | ||