detached c46cae7

chore: migrate to purego-cef v0.7.1 + compile local JSON filters on cold start

purego-cef migration (v0.7.1):
- SetHandlerTraceEnabled removed (handler tracing dropped upstream)
- DefaultWindowInfo/DefaultBrowserSettings replaced with zero-value structs
- NewKeyEvent replaced with KeyEvent struct literals
- handlerSet now implements SafeClient/SafeLifeSpanHandler (typed params)
- NewScreenInfo replaced with ScreenInfo struct literal

Filtering fix:
- On cold start with empty compiled store, compile from local JSON files
  before falling back to download. Respects auto_update=false for local
  filter testing.
commit
c46cae795de7c1c94a6ea7a86c7222a6b574c9dd
author
brice · · 2026-03-28 06:57 UTC
parent
defbe8a
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +47 -26
M internal/infrastructure/cef/engine_init.go +0 -1
@@ -31,7 +31,6 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig, transcodingCfg c
3131 multiThreaded := cfg.CEFMultiThreadedMessageLoop()
3232 manualPumpMs := cfg.CEFManualPumpIntervalMs()
3333 windowlessFrameRate := cfg.CEFWindowlessFrameRate()
34- purecef.SetHandlerTraceEnabled(cfg.TraceHandlers)
3534
3635 settings := prepareCEFSettings(cfg, logger)
3736
@@ -31,7 +31,6 @@ func NewEngine(ctx context.Context, cfg config.CEFEngineConfig, transcodingCfg c
31 multiThreaded := cfg.CEFMultiThreadedMessageLoop()31 multiThreaded := cfg.CEFMultiThreadedMessageLoop()
32 manualPumpMs := cfg.CEFManualPumpIntervalMs()32 manualPumpMs := cfg.CEFManualPumpIntervalMs()
33 windowlessFrameRate := cfg.CEFWindowlessFrameRate()33 windowlessFrameRate := cfg.CEFWindowlessFrameRate()
34- purecef.SetHandlerTraceEnabled(cfg.TraceHandlers)
35 34
36 settings := prepareCEFSettings(cfg, logger)35 settings := prepareCEFSettings(cfg, logger)
37 36
M internal/infrastructure/cef/factory.go +2 -2
@@ -118,12 +118,12 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) {
118118 wv.client = client
119119
120120 // Configure WindowInfo for off-screen rendering (OSR).
121- windowInfo := purecef.DefaultWindowInfo()
121+ windowInfo := purecef.WindowInfo{}
122122 windowInfo.WindowlessRenderingEnabled = 1
123123 windowInfo.ExternalBeginFrameEnabled = 1
124124
125125 // Configure BrowserSettings.
126- settings := purecef.DefaultBrowserSettings()
126+ settings := purecef.BrowserSettings{}
127127 settings.WindowlessFrameRate = f.windowlessFrameRate
128128 settings.LocalStorage = 1 // CEF_STATE_ENABLED
129129 if bg := f.bgColor.Load(); bg != 0 {
@@ -118,12 +118,12 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) {
118 wv.client = client118 wv.client = client
119 119
120 // Configure WindowInfo for off-screen rendering (OSR).120 // Configure WindowInfo for off-screen rendering (OSR).
121- windowInfo := purecef.DefaultWindowInfo()121+ windowInfo := purecef.WindowInfo{}
122 windowInfo.WindowlessRenderingEnabled = 1122 windowInfo.WindowlessRenderingEnabled = 1
123 windowInfo.ExternalBeginFrameEnabled = 1123 windowInfo.ExternalBeginFrameEnabled = 1
124 124
125 // Configure BrowserSettings.125 // Configure BrowserSettings.
126- settings := purecef.DefaultBrowserSettings()126+ settings := purecef.BrowserSettings{}
127 settings.WindowlessFrameRate = f.windowlessFrameRate127 settings.WindowlessFrameRate = f.windowlessFrameRate
128 settings.LocalStorage = 1 // CEF_STATE_ENABLED128 settings.LocalStorage = 1 // CEF_STATE_ENABLED
129 if bg := f.bgColor.Load(); bg != 0 {129 if bg := f.bgColor.Load(); bg != 0 {
M internal/infrastructure/cef/input_bridge.go +12 -10
@@ -385,9 +385,12 @@ func (ib *inputBridge) onIMCommit(text string) {
385385 continue
386386 }
387387 ch := uint16(r)
388- evt := purecef.NewKeyEvent(purecef.KeyEventTypeKeyeventChar, int32(ch), 0, 0)
389- evt.Character = ch
390- evt.UnmodifiedCharacter = ch
388+ evt := purecef.KeyEvent{
389+ Type: purecef.KeyEventTypeKeyeventChar,
390+ WindowsKeyCode: int32(ch),
391+ Character: ch,
392+ UnmodifiedCharacter: ch,
393+ }
391394 host.SendKeyEvent(&evt)
392395 }
393396 }
@@ -487,13 +490,12 @@ func buildMouseEvent(x, y float64, gdkMods uint, scale int32) purecef.MouseEvent
487490 }
488491
489492 func buildKeyEvent(keyval, keycode, gdkMods uint, eventType purecef.KeyEventType) purecef.KeyEvent {
490- evt := purecef.NewKeyEvent(
491- eventType,
492- gdkKeyvalToWindowsVK(keyval),
493- int32(keycode),
494- translateModifiers(gdkMods),
495- )
496- return evt
493+ return purecef.KeyEvent{
494+ Type: eventType,
495+ WindowsKeyCode: gdkKeyvalToWindowsVK(keyval),
496+ NativeKeyCode: int32(keycode),
497+ Modifiers: translateModifiers(gdkMods),
498+ }
497499 }
498500
499501 // translateModifiers maps GDK modifier masks to CEF EventFlags values.
@@ -385,9 +385,12 @@ func (ib *inputBridge) onIMCommit(text string) {
385 continue385 continue
386 }386 }
387 ch := uint16(r)387 ch := uint16(r)
388- evt := purecef.NewKeyEvent(purecef.KeyEventTypeKeyeventChar, int32(ch), 0, 0)388+ evt := purecef.KeyEvent{
389- evt.Character = ch389+ Type: purecef.KeyEventTypeKeyeventChar,
390- evt.UnmodifiedCharacter = ch390+ WindowsKeyCode: int32(ch),
391+ Character: ch,
392+ UnmodifiedCharacter: ch,
393+ }
391 host.SendKeyEvent(&evt)394 host.SendKeyEvent(&evt)
392 }395 }
393 }396 }
@@ -487,13 +490,12 @@ func buildMouseEvent(x, y float64, gdkMods uint, scale int32) purecef.MouseEvent
487 }490 }
488 491
489 func buildKeyEvent(keyval, keycode, gdkMods uint, eventType purecef.KeyEventType) purecef.KeyEvent {492 func buildKeyEvent(keyval, keycode, gdkMods uint, eventType purecef.KeyEventType) purecef.KeyEvent {
490- evt := purecef.NewKeyEvent(493+ return purecef.KeyEvent{
491- eventType,494+ Type: eventType,
492- gdkKeyvalToWindowsVK(keyval),495+ WindowsKeyCode: gdkKeyvalToWindowsVK(keyval),
493- int32(keycode),496+ NativeKeyCode: int32(keycode),
494- translateModifiers(gdkMods),497+ Modifiers: translateModifiers(gdkMods),
495- )498+ }
496- return evt
497 }499 }
498 500
499 // translateModifiers maps GDK modifier masks to CEF EventFlags values.501 // translateModifiers maps GDK modifier masks to CEF EventFlags values.
M internal/infrastructure/cef/webview.go +2 -2
@@ -391,8 +391,8 @@ func (wv *WebView) OpenDevTools() {
391391 if host == nil {
392392 return
393393 }
394- windowInfo := purecef.DefaultWindowInfo()
395- settings := purecef.DefaultBrowserSettings()
394+ windowInfo := purecef.WindowInfo{}
395+ settings := purecef.BrowserSettings{}
396396 host.ShowDevTools(&windowInfo, nil, &settings, nil)
397397 }
398398
@@ -391,8 +391,8 @@ func (wv *WebView) OpenDevTools() {
391 if host == nil {391 if host == nil {
392 return392 return
393 }393 }
394- windowInfo := purecef.DefaultWindowInfo()394+ windowInfo := purecef.WindowInfo{}
395- settings := purecef.DefaultBrowserSettings()395+ settings := purecef.BrowserSettings{}
396 host.ShowDevTools(&windowInfo, nil, &settings, nil)396 host.ShowDevTools(&windowInfo, nil, &settings, nil)
397 }397 }
398 398
M internal/infrastructure/cef/webview_handlers.go +17 -11
@@ -27,15 +27,15 @@ type handlerSet struct {
2727
2828 // Compile-time interface checks.
2929 var (
30- _ purecef.Client = (*handlerSet)(nil)
31- _ purecef.RenderHandler = (*handlerSet)(nil)
32- _ purecef.DisplayHandler = (*handlerSet)(nil)
33- _ purecef.LoadHandler = (*handlerSet)(nil)
34- _ purecef.LifeSpanHandler = (*handlerSet)(nil)
35- _ purecef.RequestHandler = (*handlerSet)(nil)
36- _ purecef.AudioHandler = (*handlerSet)(nil)
37- _ purecef.ContextMenuHandler = (*handlerSet)(nil)
38- _ purecef.FindHandler = (*handlerSet)(nil)
30+ _ purecef.SafeClient = (*handlerSet)(nil)
31+ _ purecef.RenderHandler = (*handlerSet)(nil)
32+ _ purecef.DisplayHandler = (*handlerSet)(nil)
33+ _ purecef.LoadHandler = (*handlerSet)(nil)
34+ _ purecef.SafeLifeSpanHandler = (*handlerSet)(nil)
35+ _ purecef.RequestHandler = (*handlerSet)(nil)
36+ _ purecef.AudioHandler = (*handlerSet)(nil)
37+ _ purecef.ContextMenuHandler = (*handlerSet)(nil)
38+ _ purecef.FindHandler = (*handlerSet)(nil)
3939 )
4040
4141 // ===========================================================================
@@ -66,7 +66,7 @@ func (h *handlerSet) GetFrameHandler() purecef.FrameHandler { return n
6666 func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }
6767 func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
6868 func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
69-func (h *handlerSet) GetLifeSpanHandler() purecef.LifeSpanHandler { return h }
69+func (h *handlerSet) GetLifeSpanHandler() purecef.SafeLifeSpanHandler { return h }
7070 func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }
7171 func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }
7272 func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }
@@ -145,7 +145,13 @@ func (h *handlerSet) GetScreenInfo(_ purecef.Browser, info *purecef.ScreenInfo)
145145 screenDepthPerComponent = 8
146146 )
147147 r := purecef.Rect{X: 0, Y: 0, Width: w, Height: ht}
148- *info = purecef.NewScreenInfo(float32(s), screenDepth, screenDepthPerComponent, false, r, r)
148+ *info = purecef.ScreenInfo{
149+ DeviceScaleFactor: float32(s),
150+ Depth: screenDepth,
151+ DepthPerComponent: screenDepthPerComponent,
152+ Rect: r,
153+ AvailableRect: r,
154+ }
149155 if h.wv != nil && h.wv.ctx != nil {
150156 logging.FromContext(h.wv.ctx).Debug().
151157 Uint64("screen_info_seq", callSeq).
@@ -27,15 +27,15 @@ type handlerSet struct {
27 27
28 // Compile-time interface checks.28 // Compile-time interface checks.
29 var (29 var (
30- _ purecef.Client = (*handlerSet)(nil)30+ _ purecef.SafeClient = (*handlerSet)(nil)
31- _ purecef.RenderHandler = (*handlerSet)(nil)31+ _ purecef.RenderHandler = (*handlerSet)(nil)
32- _ purecef.DisplayHandler = (*handlerSet)(nil)32+ _ purecef.DisplayHandler = (*handlerSet)(nil)
33- _ purecef.LoadHandler = (*handlerSet)(nil)33+ _ purecef.LoadHandler = (*handlerSet)(nil)
34- _ purecef.LifeSpanHandler = (*handlerSet)(nil)34+ _ purecef.SafeLifeSpanHandler = (*handlerSet)(nil)
35- _ purecef.RequestHandler = (*handlerSet)(nil)35+ _ purecef.RequestHandler = (*handlerSet)(nil)
36- _ purecef.AudioHandler = (*handlerSet)(nil)36+ _ purecef.AudioHandler = (*handlerSet)(nil)
37- _ purecef.ContextMenuHandler = (*handlerSet)(nil)37+ _ purecef.ContextMenuHandler = (*handlerSet)(nil)
38- _ purecef.FindHandler = (*handlerSet)(nil)38+ _ purecef.FindHandler = (*handlerSet)(nil)
39 )39 )
40 40
41 // ===========================================================================41 // ===========================================================================
@@ -66,7 +66,7 @@ func (h *handlerSet) GetFrameHandler() purecef.FrameHandler { return n
66 func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }66 func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }
67 func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }67 func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
68 func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }68 func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
69-func (h *handlerSet) GetLifeSpanHandler() purecef.LifeSpanHandler { return h }69+func (h *handlerSet) GetLifeSpanHandler() purecef.SafeLifeSpanHandler { return h }
70 func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }70 func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }
71 func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }71 func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }
72 func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }72 func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }
@@ -145,7 +145,13 @@ func (h *handlerSet) GetScreenInfo(_ purecef.Browser, info *purecef.ScreenInfo)
145 screenDepthPerComponent = 8145 screenDepthPerComponent = 8
146 )146 )
147 r := purecef.Rect{X: 0, Y: 0, Width: w, Height: ht}147 r := purecef.Rect{X: 0, Y: 0, Width: w, Height: ht}
148- *info = purecef.NewScreenInfo(float32(s), screenDepth, screenDepthPerComponent, false, r, r)148+ *info = purecef.ScreenInfo{
149+ DeviceScaleFactor: float32(s),
150+ Depth: screenDepth,
151+ DepthPerComponent: screenDepthPerComponent,
152+ Rect: r,
153+ AvailableRect: r,
154+ }
149 if h.wv != nil && h.wv.ctx != nil {155 if h.wv != nil && h.wv.ctx != nil {
150 logging.FromContext(h.wv.ctx).Debug().156 logging.FromContext(h.wv.ctx).Debug().
151 Uint64("screen_info_seq", callSeq).157 Uint64("screen_info_seq", callSeq).
M internal/infrastructure/filtering/manager.go +14 -0
@@ -169,6 +169,20 @@ func (m *Manager) loadAsyncWorker(ctx context.Context) {
169169 return
170170 }
171171
172+ // If local JSON files exist on disk, compile from them directly instead
173+ // of downloading. This respects auto_update=false on cold start with an
174+ // empty compiled store and allows testing with patched local JSON.
175+ if paths := m.downloader.GetCachedFilterPaths(); len(paths) > 0 {
176+ log.Info().Int("files", len(paths)).Msg("compiling filters from local JSON (skipping download)")
177+ filters, err := m.compileFilterParts(ctx, paths)
178+ if err == nil {
179+ version := m.setActiveFilters(filters, "Filters active")
180+ log.Info().Str("version", version).Int("parts", len(filters)).Msg("filters compiled from local JSON")
181+ return
182+ }
183+ log.Warn().Err(err).Msg("failed to compile local JSON, falling back to download")
184+ }
185+
172186 m.downloadCompileAndActivate(ctx)
173187 }
174188
@@ -169,6 +169,20 @@ func (m *Manager) loadAsyncWorker(ctx context.Context) {
169 return169 return
170 }170 }
171 171
172+ // If local JSON files exist on disk, compile from them directly instead
173+ // of downloading. This respects auto_update=false on cold start with an
174+ // empty compiled store and allows testing with patched local JSON.
175+ if paths := m.downloader.GetCachedFilterPaths(); len(paths) > 0 {
176+ log.Info().Int("files", len(paths)).Msg("compiling filters from local JSON (skipping download)")
177+ filters, err := m.compileFilterParts(ctx, paths)
178+ if err == nil {
179+ version := m.setActiveFilters(filters, "Filters active")
180+ log.Info().Str("version", version).Int("parts", len(filters)).Msg("filters compiled from local JSON")
181+ return
182+ }
183+ log.Warn().Err(err).Msg("failed to compile local JSON, falling back to download")
184+ }
185+
172 m.downloadCompileAndActivate(ctx)186 m.downloadCompileAndActivate(ctx)
173 }187 }
174 188

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