detached aa84390

fix(cef): implement context menu on handlerSet to avoid SIGSEGV

Move ContextMenuHandler methods onto handlerSet (returned as `h`)
instead of a separate struct. CEF resolves handler raw pointers via
the Client wrapper — returning a standalone Go struct crashes because
CEF cannot map it to a valid C callback table.
commit
aa84390da51b3b0051f326dcff027ffa554da43f
author
brice · · 2026-03-21 07:14 UTC
committer
brice · · 2026-03-28 04:37 UTC
parent
349d988
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +40 -47
M internal/infrastructure/cef/context_menu.go +14 -20
@@ -6,15 +6,6 @@ import (
66 "github.com/bnema/puregotk/v4/gtk"
77 )
88
9-// Compile-time interface check.
10-var _ purecef.ContextMenuHandler = (*contextMenuHandler)(nil)
11-
12-// contextMenuHandler builds a native GTK4 popover from CEF's menu model.
13-// In OSR mode CEF cannot render its own context menu.
14-type contextMenuHandler struct {
15- wv *WebView
16-}
17-
189 // menuEntry holds a snapshot of one CEF menu item.
1910 type menuEntry struct {
2011 label string
@@ -22,10 +13,14 @@ type menuEntry struct {
2213 isSep bool
2314 }
2415
25-func (h *contextMenuHandler) OnBeforeContextMenu(_ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams, _ purecef.MenuModel) {
16+// ===========================================================================
17+// ContextMenuHandler (implemented on handlerSet)
18+// ===========================================================================
19+
20+func (h *handlerSet) OnBeforeContextMenu(_ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams, _ purecef.MenuModel) {
2621 }
2722
28-func (h *contextMenuHandler) RunContextMenu(
23+func (h *handlerSet) RunContextMenu(
2924 _ purecef.Browser, _ purecef.Frame,
3025 params purecef.ContextMenuParams, model purecef.MenuModel,
3126 callback purecef.RunContextMenuCallback,
@@ -60,19 +55,18 @@ func (h *contextMenuHandler) RunContextMenu(
6055 y := params.GetYcoord()
6156
6257 h.wv.runOnGTK(func() {
63- h.showMenu(items, x, y, callback)
58+ showContextMenu(h.wv, items, x, y, callback)
6459 })
6560 return 1
6661 }
6762
68-func (h *contextMenuHandler) showMenu(items []menuEntry, x, y int32, callback purecef.RunContextMenuCallback) {
69- glArea := h.wv.pipeline.glArea
63+func showContextMenu(wv *WebView, items []menuEntry, x, y int32, callback purecef.RunContextMenuCallback) {
64+ glArea := wv.pipeline.glArea
7065 if glArea == nil {
7166 callback.Cancel()
7267 return
7368 }
7469
75- // Build a vertical box of buttons inside a popover.
7670 box := gtk.NewBox(gtk.OrientationVerticalValue, 0)
7771 box.AddCssClass("context-menu")
7872
@@ -122,24 +116,24 @@ func (h *contextMenuHandler) showMenu(items []menuEntry, x, y int32, callback pu
122116 popover.Popup()
123117 }
124118
125-func (h *contextMenuHandler) OnContextMenuCommand(
119+func (h *handlerSet) OnContextMenuCommand(
126120 _ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams,
127121 _ int32, _ purecef.EventFlags,
128122 ) int32 {
129123 return 0
130124 }
131125
132-func (h *contextMenuHandler) OnContextMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}
126+func (h *handlerSet) OnContextMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}
133127
134-func (h *contextMenuHandler) RunQuickMenu(
128+func (h *handlerSet) RunQuickMenu(
135129 _ purecef.Browser, _ purecef.Frame, _ *purecef.Point, _ *purecef.Size,
136130 _ purecef.QuickMenuEditStateFlags, _ purecef.RunQuickMenuCallback,
137131 ) int32 {
138132 return 0
139133 }
140134
141-func (h *contextMenuHandler) OnQuickMenuCommand(_ purecef.Browser, _ purecef.Frame, _ int32, _ purecef.EventFlags) int32 {
135+func (h *handlerSet) OnQuickMenuCommand(_ purecef.Browser, _ purecef.Frame, _ int32, _ purecef.EventFlags) int32 {
142136 return 0
143137 }
144138
145-func (h *contextMenuHandler) OnQuickMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}
139+func (h *handlerSet) OnQuickMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}
@@ -6,15 +6,6 @@ import (
6 "github.com/bnema/puregotk/v4/gtk"6 "github.com/bnema/puregotk/v4/gtk"
7 )7 )
8 8
9-// Compile-time interface check.
10-var _ purecef.ContextMenuHandler = (*contextMenuHandler)(nil)
11-
12-// contextMenuHandler builds a native GTK4 popover from CEF's menu model.
13-// In OSR mode CEF cannot render its own context menu.
14-type contextMenuHandler struct {
15- wv *WebView
16-}
17-
18 // menuEntry holds a snapshot of one CEF menu item.9 // menuEntry holds a snapshot of one CEF menu item.
19 type menuEntry struct {10 type menuEntry struct {
20 label string11 label string
@@ -22,10 +13,14 @@ type menuEntry struct {
22 isSep bool13 isSep bool
23 }14 }
24 15
25-func (h *contextMenuHandler) OnBeforeContextMenu(_ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams, _ purecef.MenuModel) {16+// ===========================================================================
17+// ContextMenuHandler (implemented on handlerSet)
18+// ===========================================================================
19+
20+func (h *handlerSet) OnBeforeContextMenu(_ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams, _ purecef.MenuModel) {
26 }21 }
27 22
28-func (h *contextMenuHandler) RunContextMenu(23+func (h *handlerSet) RunContextMenu(
29 _ purecef.Browser, _ purecef.Frame,24 _ purecef.Browser, _ purecef.Frame,
30 params purecef.ContextMenuParams, model purecef.MenuModel,25 params purecef.ContextMenuParams, model purecef.MenuModel,
31 callback purecef.RunContextMenuCallback,26 callback purecef.RunContextMenuCallback,
@@ -60,19 +55,18 @@ func (h *contextMenuHandler) RunContextMenu(
60 y := params.GetYcoord()55 y := params.GetYcoord()
61 56
62 h.wv.runOnGTK(func() {57 h.wv.runOnGTK(func() {
63- h.showMenu(items, x, y, callback)58+ showContextMenu(h.wv, items, x, y, callback)
64 })59 })
65 return 160 return 1
66 }61 }
67 62
68-func (h *contextMenuHandler) showMenu(items []menuEntry, x, y int32, callback purecef.RunContextMenuCallback) {63+func showContextMenu(wv *WebView, items []menuEntry, x, y int32, callback purecef.RunContextMenuCallback) {
69- glArea := h.wv.pipeline.glArea64+ glArea := wv.pipeline.glArea
70 if glArea == nil {65 if glArea == nil {
71 callback.Cancel()66 callback.Cancel()
72 return67 return
73 }68 }
74 69
75- // Build a vertical box of buttons inside a popover.
76 box := gtk.NewBox(gtk.OrientationVerticalValue, 0)70 box := gtk.NewBox(gtk.OrientationVerticalValue, 0)
77 box.AddCssClass("context-menu")71 box.AddCssClass("context-menu")
78 72
@@ -122,24 +116,24 @@ func (h *contextMenuHandler) showMenu(items []menuEntry, x, y int32, callback pu
122 popover.Popup()116 popover.Popup()
123 }117 }
124 118
125-func (h *contextMenuHandler) OnContextMenuCommand(119+func (h *handlerSet) OnContextMenuCommand(
126 _ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams,120 _ purecef.Browser, _ purecef.Frame, _ purecef.ContextMenuParams,
127 _ int32, _ purecef.EventFlags,121 _ int32, _ purecef.EventFlags,
128 ) int32 {122 ) int32 {
129 return 0123 return 0
130 }124 }
131 125
132-func (h *contextMenuHandler) OnContextMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}126+func (h *handlerSet) OnContextMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}
133 127
134-func (h *contextMenuHandler) RunQuickMenu(128+func (h *handlerSet) RunQuickMenu(
135 _ purecef.Browser, _ purecef.Frame, _ *purecef.Point, _ *purecef.Size,129 _ purecef.Browser, _ purecef.Frame, _ *purecef.Point, _ *purecef.Size,
136 _ purecef.QuickMenuEditStateFlags, _ purecef.RunQuickMenuCallback,130 _ purecef.QuickMenuEditStateFlags, _ purecef.RunQuickMenuCallback,
137 ) int32 {131 ) int32 {
138 return 0132 return 0
139 }133 }
140 134
141-func (h *contextMenuHandler) OnQuickMenuCommand(_ purecef.Browser, _ purecef.Frame, _ int32, _ purecef.EventFlags) int32 {135+func (h *handlerSet) OnQuickMenuCommand(_ purecef.Browser, _ purecef.Frame, _ int32, _ purecef.EventFlags) int32 {
142 return 0136 return 0
143 }137 }
144 138
145-func (h *contextMenuHandler) OnQuickMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}139+func (h *handlerSet) OnQuickMenuDismissed(_ purecef.Browser, _ purecef.Frame) {}
M internal/infrastructure/cef/webview_handlers.go +26 -27
@@ -16,39 +16,38 @@ type handlerSet struct {
1616
1717 // Compile-time interface checks.
1818 var (
19- _ purecef.Client = (*handlerSet)(nil)
20- _ purecef.RenderHandler = (*handlerSet)(nil)
21- _ purecef.DisplayHandler = (*handlerSet)(nil)
22- _ purecef.LoadHandler = (*handlerSet)(nil)
23- _ purecef.LifeSpanHandler = (*handlerSet)(nil)
24- _ purecef.RequestHandler = (*handlerSet)(nil)
25- _ purecef.AudioHandler = (*handlerSet)(nil)
19+ _ purecef.Client = (*handlerSet)(nil)
20+ _ purecef.RenderHandler = (*handlerSet)(nil)
21+ _ purecef.DisplayHandler = (*handlerSet)(nil)
22+ _ purecef.LoadHandler = (*handlerSet)(nil)
23+ _ purecef.LifeSpanHandler = (*handlerSet)(nil)
24+ _ purecef.RequestHandler = (*handlerSet)(nil)
25+ _ purecef.AudioHandler = (*handlerSet)(nil)
26+ _ purecef.ContextMenuHandler = (*handlerSet)(nil)
2627 )
2728
2829 // ===========================================================================
2930 // Client
3031 // ===========================================================================
3132
32-func (h *handlerSet) GetAudioHandler() purecef.AudioHandler { return h }
33-func (h *handlerSet) GetCommandHandler() purecef.CommandHandler { return nil }
34-func (h *handlerSet) GetContextMenuHandler() purecef.ContextMenuHandler {
35- return &contextMenuHandler{wv: h.wv}
36-}
37-func (h *handlerSet) GetDialogHandler() purecef.DialogHandler { return nil }
38-func (h *handlerSet) GetDisplayHandler() purecef.DisplayHandler { return h }
39-func (h *handlerSet) GetDownloadHandler() purecef.DownloadHandler { return nil }
40-func (h *handlerSet) GetDragHandler() purecef.DragHandler { return nil }
41-func (h *handlerSet) GetFindHandler() purecef.FindHandler { return nil }
42-func (h *handlerSet) GetFocusHandler() purecef.FocusHandler { return nil }
43-func (h *handlerSet) GetFrameHandler() purecef.FrameHandler { return nil }
44-func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }
45-func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
46-func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
47-func (h *handlerSet) GetLifeSpanHandler() purecef.LifeSpanHandler { return h }
48-func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }
49-func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }
50-func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }
51-func (h *handlerSet) GetRequestHandler() purecef.RequestHandler { return h }
33+func (h *handlerSet) GetAudioHandler() purecef.AudioHandler { return h }
34+func (h *handlerSet) GetCommandHandler() purecef.CommandHandler { return nil }
35+func (h *handlerSet) GetContextMenuHandler() purecef.ContextMenuHandler { return h }
36+func (h *handlerSet) GetDialogHandler() purecef.DialogHandler { return nil }
37+func (h *handlerSet) GetDisplayHandler() purecef.DisplayHandler { return h }
38+func (h *handlerSet) GetDownloadHandler() purecef.DownloadHandler { return nil }
39+func (h *handlerSet) GetDragHandler() purecef.DragHandler { return nil }
40+func (h *handlerSet) GetFindHandler() purecef.FindHandler { return nil }
41+func (h *handlerSet) GetFocusHandler() purecef.FocusHandler { return nil }
42+func (h *handlerSet) GetFrameHandler() purecef.FrameHandler { return nil }
43+func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }
44+func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
45+func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
46+func (h *handlerSet) GetLifeSpanHandler() purecef.LifeSpanHandler { return h }
47+func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }
48+func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }
49+func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }
50+func (h *handlerSet) GetRequestHandler() purecef.RequestHandler { return h }
5251
5352 func (h *handlerSet) OnProcessMessageReceived(_ purecef.Browser, _ purecef.Frame, _ purecef.ProcessID, _ purecef.ProcessMessage) int32 {
5453 return 0
@@ -16,39 +16,38 @@ type handlerSet struct {
16 16
17 // Compile-time interface checks.17 // Compile-time interface checks.
18 var (18 var (
19- _ purecef.Client = (*handlerSet)(nil)19+ _ purecef.Client = (*handlerSet)(nil)
20- _ purecef.RenderHandler = (*handlerSet)(nil)20+ _ purecef.RenderHandler = (*handlerSet)(nil)
21- _ purecef.DisplayHandler = (*handlerSet)(nil)21+ _ purecef.DisplayHandler = (*handlerSet)(nil)
22- _ purecef.LoadHandler = (*handlerSet)(nil)22+ _ purecef.LoadHandler = (*handlerSet)(nil)
23- _ purecef.LifeSpanHandler = (*handlerSet)(nil)23+ _ purecef.LifeSpanHandler = (*handlerSet)(nil)
24- _ purecef.RequestHandler = (*handlerSet)(nil)24+ _ purecef.RequestHandler = (*handlerSet)(nil)
25- _ purecef.AudioHandler = (*handlerSet)(nil)25+ _ purecef.AudioHandler = (*handlerSet)(nil)
26+ _ purecef.ContextMenuHandler = (*handlerSet)(nil)
26 )27 )
27 28
28 // ===========================================================================29 // ===========================================================================
29 // Client30 // Client
30 // ===========================================================================31 // ===========================================================================
31 32
32-func (h *handlerSet) GetAudioHandler() purecef.AudioHandler { return h }33+func (h *handlerSet) GetAudioHandler() purecef.AudioHandler { return h }
33-func (h *handlerSet) GetCommandHandler() purecef.CommandHandler { return nil }34+func (h *handlerSet) GetCommandHandler() purecef.CommandHandler { return nil }
34-func (h *handlerSet) GetContextMenuHandler() purecef.ContextMenuHandler {35+func (h *handlerSet) GetContextMenuHandler() purecef.ContextMenuHandler { return h }
35- return &contextMenuHandler{wv: h.wv}36+func (h *handlerSet) GetDialogHandler() purecef.DialogHandler { return nil }
36-}37+func (h *handlerSet) GetDisplayHandler() purecef.DisplayHandler { return h }
37-func (h *handlerSet) GetDialogHandler() purecef.DialogHandler { return nil }38+func (h *handlerSet) GetDownloadHandler() purecef.DownloadHandler { return nil }
38-func (h *handlerSet) GetDisplayHandler() purecef.DisplayHandler { return h }39+func (h *handlerSet) GetDragHandler() purecef.DragHandler { return nil }
39-func (h *handlerSet) GetDownloadHandler() purecef.DownloadHandler { return nil }40+func (h *handlerSet) GetFindHandler() purecef.FindHandler { return nil }
40-func (h *handlerSet) GetDragHandler() purecef.DragHandler { return nil }41+func (h *handlerSet) GetFocusHandler() purecef.FocusHandler { return nil }
41-func (h *handlerSet) GetFindHandler() purecef.FindHandler { return nil }42+func (h *handlerSet) GetFrameHandler() purecef.FrameHandler { return nil }
42-func (h *handlerSet) GetFocusHandler() purecef.FocusHandler { return nil }43+func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }
43-func (h *handlerSet) GetFrameHandler() purecef.FrameHandler { return nil }44+func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }
44-func (h *handlerSet) GetPermissionHandler() purecef.PermissionHandler { return nil }45+func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }
45-func (h *handlerSet) GetJsdialogHandler() purecef.JsdialogHandler { return nil }46+func (h *handlerSet) GetLifeSpanHandler() purecef.LifeSpanHandler { return h }
46-func (h *handlerSet) GetKeyboardHandler() purecef.KeyboardHandler { return nil }47+func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }
47-func (h *handlerSet) GetLifeSpanHandler() purecef.LifeSpanHandler { return h }48+func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }
48-func (h *handlerSet) GetLoadHandler() purecef.LoadHandler { return h }49+func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }
49-func (h *handlerSet) GetPrintHandler() purecef.PrintHandler { return nil }50+func (h *handlerSet) GetRequestHandler() purecef.RequestHandler { return h }
50-func (h *handlerSet) GetRenderHandler() purecef.RenderHandler { return h }
51-func (h *handlerSet) GetRequestHandler() purecef.RequestHandler { return h }
52 51
53 func (h *handlerSet) OnProcessMessageReceived(_ purecef.Browser, _ purecef.Frame, _ purecef.ProcessID, _ purecef.ProcessMessage) int32 {52 func (h *handlerSet) OnProcessMessageReceived(_ purecef.Browser, _ purecef.Frame, _ purecef.ProcessID, _ purecef.ProcessMessage) int32 {
54 return 053 return 0

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