detached b6ebf7a

feat(filtering): extend content filtering service with additional rule handling

Add support for extended filtering operations, codec improvements for data
serialization, and browser service enhancements for content filtering
integration.
commit
b6ebf7a6d345c527b87082bfbe1aeb583c67fab8
author
bnema · · 2025-12-06 20:52 UTC
parent
81a156d
signature
unsigned
3 files · +65 -7
M internal/app/environment/codec.go +9 -0
@@ -73,6 +73,15 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
7373 logging.Info(fmt.Sprintf("[codec] Blocked VP8 decoders"))
7474 }
7575
76+ // Promote hardware JPEG decoders for faster image loading
77+ jpegRanks := []string{
78+ "vaapijpegdec:512", // VA-API JPEG decoder (highest priority)
79+ "nvjpegdec:384", // NVDEC JPEG decoder
80+ "jpegdec:256", // Software fallback
81+ }
82+ rankSettings = append(rankSettings, jpegRanks...)
83+ logging.Info("[codec] Promoted hardware JPEG decoders")
84+
7685 // Apply the complete ranking system
7786 if len(rankSettings) > 0 {
7887 finalRank := strings.Join(rankSettings, ",")
@@ -73,6 +73,15 @@ func ApplyCodecConfiguration(codecPrefs config.CodecConfig) {
73 logging.Info(fmt.Sprintf("[codec] Blocked VP8 decoders"))73 logging.Info(fmt.Sprintf("[codec] Blocked VP8 decoders"))
74 }74 }
75 75
76+ // Promote hardware JPEG decoders for faster image loading
77+ jpegRanks := []string{
78+ "vaapijpegdec:512", // VA-API JPEG decoder (highest priority)
79+ "nvjpegdec:384", // NVDEC JPEG decoder
80+ "jpegdec:256", // Software fallback
81+ }
82+ rankSettings = append(rankSettings, jpegRanks...)
83+ logging.Info("[codec] Promoted hardware JPEG decoders")
84+
76 // Apply the complete ranking system85 // Apply the complete ranking system
77 if len(rankSettings) > 0 {86 if len(rankSettings) > 0 {
78 finalRank := strings.Join(rankSettings, ",")87 finalRank := strings.Join(rankSettings, ",")
M internal/filtering/service.go +33 -7
@@ -80,6 +80,9 @@ func (s *ContentBlockingService) SetFiltersReady(networkJSON []byte) {
8080 for _, wv := range webViews {
8181 webView := wv // capture for closure
8282 pkgwebkit.RunOnMainThread(func() {
83+ if webView.IsDestroyed() {
84+ return
85+ }
8386 s.injectCosmeticBaseScript(webView)
8487 if uri := webView.GetCurrentURL(); uri != "" {
8588 s.injectCosmeticRules(webView, uri)
@@ -115,6 +118,9 @@ func (s *ContentBlockingService) SetFiltersReady(networkJSON []byte) {
115118 for _, wv := range currentWebViews {
116119 webView := wv // capture for closure
117120 pkgwebkit.RunOnMainThread(func() {
121+ if webView.IsDestroyed() {
122+ return
123+ }
118124 s.applyFiltersToWebView(webView)
119125 })
120126 }
@@ -150,8 +156,12 @@ func (s *ContentBlockingService) RegisterWebView(wv *pkgwebkit.WebView) {
150156
151157 // Apply network filters if already compiled (instant)
152158 if s.contentBlockingMgr.IsFilterCompiled() {
159+ webView := wv // capture for closure
153160 pkgwebkit.RunOnMainThread(func() {
154- s.applyFiltersToWebView(wv)
161+ if webView.IsDestroyed() {
162+ return
163+ }
164+ s.applyFiltersToWebView(webView)
155165 })
156166 }
157167 }
@@ -159,7 +169,7 @@ func (s *ContentBlockingService) RegisterWebView(wv *pkgwebkit.WebView) {
159169 // injectCosmeticBaseScript injects the base cosmetic filtering script into a WebView.
160170 // This script sets up the cosmetic filtering infrastructure (MutationObserver, hide functions, etc.)
161171 func (s *ContentBlockingService) injectCosmeticBaseScript(wv *pkgwebkit.WebView) {
162- if wv == nil {
172+ if wv == nil || wv.IsDestroyed() {
163173 return
164174 }
165175
@@ -217,6 +227,10 @@ func (s *ContentBlockingService) UnregisterWebView(wv *pkgwebkit.WebView) {
217227
218228 // applyFiltersToWebView applies both network and cosmetic filters to a WebView.
219229 func (s *ContentBlockingService) applyFiltersToWebView(wv *pkgwebkit.WebView) {
230+ if wv == nil || wv.IsDestroyed() {
231+ return
232+ }
233+
220234 s.mu.RLock()
221235 networkJSON := s.networkFilterJSON
222236 state := s.webViews[wv]
@@ -224,15 +238,19 @@ func (s *ContentBlockingService) applyFiltersToWebView(wv *pkgwebkit.WebView) {
224238
225239 // Apply network filters if not already applied
226240 if !state.networkApplied && len(networkJSON) > 0 {
241+ webView := wv // capture for closure
227242 pkgwebkit.RunOnMainThread(func() {
228- s.applyNetworkFilters(wv, networkJSON)
243+ if webView.IsDestroyed() {
244+ return
245+ }
246+ s.applyNetworkFilters(webView, networkJSON)
229247 })
230248 }
231249 }
232250
233251 // applyNetworkFilters applies network blocking rules to a WebView.
234252 func (s *ContentBlockingService) applyNetworkFilters(wv *pkgwebkit.WebView, filterJSON []byte) {
235- if wv == nil {
253+ if wv == nil || wv.IsDestroyed() {
236254 return
237255 }
238256
@@ -284,14 +302,18 @@ func (s *ContentBlockingService) setupCosmeticHooks(wv *pkgwebkit.WebView) {
284302 }
285303
286304 // Register load-committed handler to inject domain-specific cosmetic rules
305+ webView := wv // capture for closure
287306 wv.RegisterLoadCommittedHandler(func(uri string) {
288- s.injectCosmeticRules(wv, uri)
307+ if webView.IsDestroyed() {
308+ return
309+ }
310+ s.injectCosmeticRules(webView, uri)
289311 })
290312 }
291313
292314 // injectCosmeticRules injects cosmetic filtering rules for the current page.
293315 func (s *ContentBlockingService) injectCosmeticRules(wv *pkgwebkit.WebView, uri string) {
294- if wv == nil || uri == "" {
316+ if wv == nil || wv.IsDestroyed() || uri == "" {
295317 return
296318 }
297319
@@ -319,8 +341,12 @@ func (s *ContentBlockingService) injectCosmeticRules(wv *pkgwebkit.WebView, uri
319341
320342 // Inject the script
321343 logging.Debug(fmt.Sprintf("[filtering] Injecting cosmetic rules for domain: %s (%d bytes)", domain, len(script)))
344+ webView := wv // capture for closure
322345 pkgwebkit.RunOnMainThread(func() {
323- if err := wv.InjectScript(script); err != nil {
346+ if webView.IsDestroyed() {
347+ return
348+ }
349+ if err := webView.InjectScript(script); err != nil {
324350 logging.Error(fmt.Sprintf("[filtering] Failed to inject cosmetic rules for %s: %v", domain, err))
325351 } else {
326352 logging.Debug(fmt.Sprintf("[filtering] Successfully injected cosmetic rules for domain: %s", domain))
@@ -80,6 +80,9 @@ func (s *ContentBlockingService) SetFiltersReady(networkJSON []byte) {
80 for _, wv := range webViews {80 for _, wv := range webViews {
81 webView := wv // capture for closure81 webView := wv // capture for closure
82 pkgwebkit.RunOnMainThread(func() {82 pkgwebkit.RunOnMainThread(func() {
83+ if webView.IsDestroyed() {
84+ return
85+ }
83 s.injectCosmeticBaseScript(webView)86 s.injectCosmeticBaseScript(webView)
84 if uri := webView.GetCurrentURL(); uri != "" {87 if uri := webView.GetCurrentURL(); uri != "" {
85 s.injectCosmeticRules(webView, uri)88 s.injectCosmeticRules(webView, uri)
@@ -115,6 +118,9 @@ func (s *ContentBlockingService) SetFiltersReady(networkJSON []byte) {
115 for _, wv := range currentWebViews {118 for _, wv := range currentWebViews {
116 webView := wv // capture for closure119 webView := wv // capture for closure
117 pkgwebkit.RunOnMainThread(func() {120 pkgwebkit.RunOnMainThread(func() {
121+ if webView.IsDestroyed() {
122+ return
123+ }
118 s.applyFiltersToWebView(webView)124 s.applyFiltersToWebView(webView)
119 })125 })
120 }126 }
@@ -150,8 +156,12 @@ func (s *ContentBlockingService) RegisterWebView(wv *pkgwebkit.WebView) {
150 156
151 // Apply network filters if already compiled (instant)157 // Apply network filters if already compiled (instant)
152 if s.contentBlockingMgr.IsFilterCompiled() {158 if s.contentBlockingMgr.IsFilterCompiled() {
159+ webView := wv // capture for closure
153 pkgwebkit.RunOnMainThread(func() {160 pkgwebkit.RunOnMainThread(func() {
154- s.applyFiltersToWebView(wv)161+ if webView.IsDestroyed() {
162+ return
163+ }
164+ s.applyFiltersToWebView(webView)
155 })165 })
156 }166 }
157 }167 }
@@ -159,7 +169,7 @@ func (s *ContentBlockingService) RegisterWebView(wv *pkgwebkit.WebView) {
159 // injectCosmeticBaseScript injects the base cosmetic filtering script into a WebView.169 // injectCosmeticBaseScript injects the base cosmetic filtering script into a WebView.
160 // This script sets up the cosmetic filtering infrastructure (MutationObserver, hide functions, etc.)170 // This script sets up the cosmetic filtering infrastructure (MutationObserver, hide functions, etc.)
161 func (s *ContentBlockingService) injectCosmeticBaseScript(wv *pkgwebkit.WebView) {171 func (s *ContentBlockingService) injectCosmeticBaseScript(wv *pkgwebkit.WebView) {
162- if wv == nil {172+ if wv == nil || wv.IsDestroyed() {
163 return173 return
164 }174 }
165 175
@@ -217,6 +227,10 @@ func (s *ContentBlockingService) UnregisterWebView(wv *pkgwebkit.WebView) {
217 227
218 // applyFiltersToWebView applies both network and cosmetic filters to a WebView.228 // applyFiltersToWebView applies both network and cosmetic filters to a WebView.
219 func (s *ContentBlockingService) applyFiltersToWebView(wv *pkgwebkit.WebView) {229 func (s *ContentBlockingService) applyFiltersToWebView(wv *pkgwebkit.WebView) {
230+ if wv == nil || wv.IsDestroyed() {
231+ return
232+ }
233+
220 s.mu.RLock()234 s.mu.RLock()
221 networkJSON := s.networkFilterJSON235 networkJSON := s.networkFilterJSON
222 state := s.webViews[wv]236 state := s.webViews[wv]
@@ -224,15 +238,19 @@ func (s *ContentBlockingService) applyFiltersToWebView(wv *pkgwebkit.WebView) {
224 238
225 // Apply network filters if not already applied239 // Apply network filters if not already applied
226 if !state.networkApplied && len(networkJSON) > 0 {240 if !state.networkApplied && len(networkJSON) > 0 {
241+ webView := wv // capture for closure
227 pkgwebkit.RunOnMainThread(func() {242 pkgwebkit.RunOnMainThread(func() {
228- s.applyNetworkFilters(wv, networkJSON)243+ if webView.IsDestroyed() {
244+ return
245+ }
246+ s.applyNetworkFilters(webView, networkJSON)
229 })247 })
230 }248 }
231 }249 }
232 250
233 // applyNetworkFilters applies network blocking rules to a WebView.251 // applyNetworkFilters applies network blocking rules to a WebView.
234 func (s *ContentBlockingService) applyNetworkFilters(wv *pkgwebkit.WebView, filterJSON []byte) {252 func (s *ContentBlockingService) applyNetworkFilters(wv *pkgwebkit.WebView, filterJSON []byte) {
235- if wv == nil {253+ if wv == nil || wv.IsDestroyed() {
236 return254 return
237 }255 }
238 256
@@ -284,14 +302,18 @@ func (s *ContentBlockingService) setupCosmeticHooks(wv *pkgwebkit.WebView) {
284 }302 }
285 303
286 // Register load-committed handler to inject domain-specific cosmetic rules304 // Register load-committed handler to inject domain-specific cosmetic rules
305+ webView := wv // capture for closure
287 wv.RegisterLoadCommittedHandler(func(uri string) {306 wv.RegisterLoadCommittedHandler(func(uri string) {
288- s.injectCosmeticRules(wv, uri)307+ if webView.IsDestroyed() {
308+ return
309+ }
310+ s.injectCosmeticRules(webView, uri)
289 })311 })
290 }312 }
291 313
292 // injectCosmeticRules injects cosmetic filtering rules for the current page.314 // injectCosmeticRules injects cosmetic filtering rules for the current page.
293 func (s *ContentBlockingService) injectCosmeticRules(wv *pkgwebkit.WebView, uri string) {315 func (s *ContentBlockingService) injectCosmeticRules(wv *pkgwebkit.WebView, uri string) {
294- if wv == nil || uri == "" {316+ if wv == nil || wv.IsDestroyed() || uri == "" {
295 return317 return
296 }318 }
297 319
@@ -319,8 +341,12 @@ func (s *ContentBlockingService) injectCosmeticRules(wv *pkgwebkit.WebView, uri
319 341
320 // Inject the script342 // Inject the script
321 logging.Debug(fmt.Sprintf("[filtering] Injecting cosmetic rules for domain: %s (%d bytes)", domain, len(script)))343 logging.Debug(fmt.Sprintf("[filtering] Injecting cosmetic rules for domain: %s (%d bytes)", domain, len(script)))
344+ webView := wv // capture for closure
322 pkgwebkit.RunOnMainThread(func() {345 pkgwebkit.RunOnMainThread(func() {
323- if err := wv.InjectScript(script); err != nil {346+ if webView.IsDestroyed() {
347+ return
348+ }
349+ if err := webView.InjectScript(script); err != nil {
324 logging.Error(fmt.Sprintf("[filtering] Failed to inject cosmetic rules for %s: %v", domain, err))350 logging.Error(fmt.Sprintf("[filtering] Failed to inject cosmetic rules for %s: %v", domain, err))
325 } else {351 } else {
326 logging.Debug(fmt.Sprintf("[filtering] Successfully injected cosmetic rules for domain: %s", domain))352 logging.Debug(fmt.Sprintf("[filtering] Successfully injected cosmetic rules for domain: %s", domain))
M internal/services/browser.go +23 -0
@@ -1227,9 +1227,17 @@ type HistoryAnalytics struct {
12271227 TotalVisits float64 `json:"total_visits"`
12281228 UniqueDays int64 `json:"unique_days"`
12291229 TopDomains []DomainStat `json:"top_domains"`
1230+ DailyVisits []DailyVisitCount `json:"daily_visits"`
12301231 HourlyDist []HourlyVisitCount `json:"hourly_distribution"`
12311232 }
12321233
1234+// DailyVisitCount represents visit counts by day.
1235+type DailyVisitCount struct {
1236+ Day string `json:"day"`
1237+ Entries int64 `json:"entries"`
1238+ Visits float64 `json:"visits"`
1239+}
1240+
12331241 // HourlyVisitCount represents visit counts by hour.
12341242 type HourlyVisitCount struct {
12351243 Hour int `json:"hour"`
@@ -1248,6 +1256,20 @@ func (s *BrowserService) GetHistoryAnalytics(ctx context.Context) (*HistoryAnaly
12481256 topDomains = []DomainStat{}
12491257 }
12501258
1259+ // Get daily visit counts for last 30 days
1260+ dailyRows, err := s.dbQueries.GetDailyVisitCount(ctx, "-30 days")
1261+ if err != nil {
1262+ dailyRows = []db.GetDailyVisitCountRow{}
1263+ }
1264+ dailyVisits := make([]DailyVisitCount, len(dailyRows))
1265+ for i, d := range dailyRows {
1266+ dailyVisits[i] = DailyVisitCount{
1267+ Day: d.Day.(string),
1268+ Entries: d.Entries,
1269+ Visits: d.Visits.Float64,
1270+ }
1271+ }
1272+
12511273 hourlyDist, err := s.dbQueries.GetHourlyDistribution(ctx)
12521274 if err != nil {
12531275 hourlyDist = []db.GetHourlyDistributionRow{}
@@ -1266,6 +1288,7 @@ func (s *BrowserService) GetHistoryAnalytics(ctx context.Context) (*HistoryAnaly
12661288 TotalVisits: stats.TotalVisits.Float64,
12671289 UniqueDays: stats.UniqueDays,
12681290 TopDomains: topDomains,
1291+ DailyVisits: dailyVisits,
12691292 HourlyDist: hourly,
12701293 }, nil
12711294 }
@@ -1227,9 +1227,17 @@ type HistoryAnalytics struct {
1227 TotalVisits float64 `json:"total_visits"`1227 TotalVisits float64 `json:"total_visits"`
1228 UniqueDays int64 `json:"unique_days"`1228 UniqueDays int64 `json:"unique_days"`
1229 TopDomains []DomainStat `json:"top_domains"`1229 TopDomains []DomainStat `json:"top_domains"`
1230+ DailyVisits []DailyVisitCount `json:"daily_visits"`
1230 HourlyDist []HourlyVisitCount `json:"hourly_distribution"`1231 HourlyDist []HourlyVisitCount `json:"hourly_distribution"`
1231 }1232 }
1232 1233
1234+// DailyVisitCount represents visit counts by day.
1235+type DailyVisitCount struct {
1236+ Day string `json:"day"`
1237+ Entries int64 `json:"entries"`
1238+ Visits float64 `json:"visits"`
1239+}
1240+
1233 // HourlyVisitCount represents visit counts by hour.1241 // HourlyVisitCount represents visit counts by hour.
1234 type HourlyVisitCount struct {1242 type HourlyVisitCount struct {
1235 Hour int `json:"hour"`1243 Hour int `json:"hour"`
@@ -1248,6 +1256,20 @@ func (s *BrowserService) GetHistoryAnalytics(ctx context.Context) (*HistoryAnaly
1248 topDomains = []DomainStat{}1256 topDomains = []DomainStat{}
1249 }1257 }
1250 1258
1259+ // Get daily visit counts for last 30 days
1260+ dailyRows, err := s.dbQueries.GetDailyVisitCount(ctx, "-30 days")
1261+ if err != nil {
1262+ dailyRows = []db.GetDailyVisitCountRow{}
1263+ }
1264+ dailyVisits := make([]DailyVisitCount, len(dailyRows))
1265+ for i, d := range dailyRows {
1266+ dailyVisits[i] = DailyVisitCount{
1267+ Day: d.Day.(string),
1268+ Entries: d.Entries,
1269+ Visits: d.Visits.Float64,
1270+ }
1271+ }
1272+
1251 hourlyDist, err := s.dbQueries.GetHourlyDistribution(ctx)1273 hourlyDist, err := s.dbQueries.GetHourlyDistribution(ctx)
1252 if err != nil {1274 if err != nil {
1253 hourlyDist = []db.GetHourlyDistributionRow{}1275 hourlyDist = []db.GetHourlyDistributionRow{}
@@ -1266,6 +1288,7 @@ func (s *BrowserService) GetHistoryAnalytics(ctx context.Context) (*HistoryAnaly
1266 TotalVisits: stats.TotalVisits.Float64,1288 TotalVisits: stats.TotalVisits.Float64,
1267 UniqueDays: stats.UniqueDays,1289 UniqueDays: stats.UniqueDays,
1268 TopDomains: topDomains,1290 TopDomains: topDomains,
1291+ DailyVisits: dailyVisits,
1269 HourlyDist: hourly,1292 HourlyDist: hourly,
1270 }, nil1293 }, nil
1271 }1294 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close