detached e528b97

Simplify cef2gtk migration

commit
e528b972c45216591be55b9b4384de5c9e8a5468
author
brice · · 2026-04-29 20:10 UTC
parent
5eb26e1
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +69 -147
M internal/infrastructure/cef/cef2gtk_boundary_test.go +33 -15
@@ -1,27 +1,45 @@
11 package cef
22
33 import (
4- "os/exec"
4+ "os"
5+ "path/filepath"
56 "strings"
67 "testing"
78 )
89
910 func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) {
10- cmd := exec.Command("go", "list", "-mod=mod", "-f", "{{.ImportPath}} {{join .Imports \" \"}}", "./...")
11- // Tests in this package run from internal/infrastructure/cef; ../../.. is the
12- // repository root for go list ./....
13- cmd.Dir = "../../.."
14- out, err := cmd.Output()
15- if err != nil {
16- t.Fatalf("go list imports: %v", err)
17- }
18- for _, line := range strings.Split(string(out), "\n") {
19- if !strings.Contains(line, "github.com/bnema/purego-cef2gtk") {
20- continue
11+ root := filepath.Clean("../../..")
12+ if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
13+ if err != nil {
14+ return err
15+ }
16+ if d.IsDir() {
17+ switch d.Name() {
18+ case ".git", "vendor", "dist", "tmp":
19+ return filepath.SkipDir
20+ }
21+ return nil
22+ }
23+ if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
24+ return nil
25+ }
26+ body, err := os.ReadFile(path)
27+ if err != nil {
28+ return err
29+ }
30+ if !strings.Contains(string(body), "github.com/bnema/purego-cef2gtk") {
31+ return nil
32+ }
33+ rel, err := filepath.Rel(root, path)
34+ if err != nil {
35+ return err
2136 }
22- if strings.HasPrefix(line, "github.com/bnema/dumber/internal/infrastructure/cef ") {
23- continue
37+ if strings.HasPrefix(filepath.ToSlash(rel), "internal/infrastructure/cef/") {
38+ return nil
2439 }
25- t.Fatalf("purego-cef2gtk imported outside cef infrastructure: %s", line)
40+ t.Fatalf("purego-cef2gtk imported outside cef infrastructure: %s", rel)
41+ return nil
42+ }); err != nil {
43+ t.Fatalf("scan imports: %v", err)
2644 }
2745 }
@@ -1,27 +1,45 @@
1 package cef1 package cef
2 2
3 import (3 import (
4- "os/exec"4+ "os"
5+ "path/filepath"
5 "strings"6 "strings"
6 "testing"7 "testing"
7 )8 )
8 9
9 func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) {10 func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) {
10- cmd := exec.Command("go", "list", "-mod=mod", "-f", "{{.ImportPath}} {{join .Imports \" \"}}", "./...")11+ root := filepath.Clean("../../..")
11- // Tests in this package run from internal/infrastructure/cef; ../../.. is the12+ if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
12- // repository root for go list ./....13+ if err != nil {
13- cmd.Dir = "../../.."14+ return err
14- out, err := cmd.Output()15+ }
15- if err != nil {16+ if d.IsDir() {
16- t.Fatalf("go list imports: %v", err)17+ switch d.Name() {
17- }18+ case ".git", "vendor", "dist", "tmp":
18- for _, line := range strings.Split(string(out), "\n") {19+ return filepath.SkipDir
19- if !strings.Contains(line, "github.com/bnema/purego-cef2gtk") {20+ }
20- continue21+ return nil
22+ }
23+ if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") {
24+ return nil
25+ }
26+ body, err := os.ReadFile(path)
27+ if err != nil {
28+ return err
29+ }
30+ if !strings.Contains(string(body), "github.com/bnema/purego-cef2gtk") {
31+ return nil
32+ }
33+ rel, err := filepath.Rel(root, path)
34+ if err != nil {
35+ return err
21 }36 }
22- if strings.HasPrefix(line, "github.com/bnema/dumber/internal/infrastructure/cef ") {37+ if strings.HasPrefix(filepath.ToSlash(rel), "internal/infrastructure/cef/") {
23- continue38+ return nil
24 }39 }
25- t.Fatalf("purego-cef2gtk imported outside cef infrastructure: %s", line)40+ t.Fatalf("purego-cef2gtk imported outside cef infrastructure: %s", rel)
41+ return nil
42+ }); err != nil {
43+ t.Fatalf("scan imports: %v", err)
26 }44 }
27 }45 }
M internal/infrastructure/cef/context_menu.go +2 -1
@@ -8,6 +8,7 @@ import (
88 purecef "github.com/bnema/purego-cef/cef"
99
1010 "github.com/bnema/dumber/internal/application/port"
11+ "github.com/bnema/dumber/internal/infrastructure/gtkmenu"
1112 "github.com/bnema/dumber/internal/logging"
1213 "github.com/bnema/puregotk/v4/gtk"
1314 )
@@ -81,7 +82,7 @@ func (h *handlerSet) RunContextMenu(
8182 }
8283 glArea := wv.viewBridge.GLArea()
8384 logContextMenuPopupRequest(h, glArea, params, x, y)
84- NewRenderer(nil).Show(
85+ gtkmenu.NewRenderer(nil).Show(
8586 items,
8687 &glArea.Widget,
8788 x,
@@ -8,6 +8,7 @@ import (
8 purecef "github.com/bnema/purego-cef/cef"8 purecef "github.com/bnema/purego-cef/cef"
9 9
10 "github.com/bnema/dumber/internal/application/port"10 "github.com/bnema/dumber/internal/application/port"
11+ "github.com/bnema/dumber/internal/infrastructure/gtkmenu"
11 "github.com/bnema/dumber/internal/logging"12 "github.com/bnema/dumber/internal/logging"
12 "github.com/bnema/puregotk/v4/gtk"13 "github.com/bnema/puregotk/v4/gtk"
13 )14 )
@@ -81,7 +82,7 @@ func (h *handlerSet) RunContextMenu(
81 }82 }
82 glArea := wv.viewBridge.GLArea()83 glArea := wv.viewBridge.GLArea()
83 logContextMenuPopupRequest(h, glArea, params, x, y)84 logContextMenuPopupRequest(h, glArea, params, x, y)
84- NewRenderer(nil).Show(85+ gtkmenu.NewRenderer(nil).Show(
85 items,86 items,
86 &glArea.Widget,87 &glArea.Widget,
87 x,88 x,
M internal/infrastructure/cef/factory.go +4 -11
@@ -159,13 +159,13 @@ func (f *WebViewFactory) configureInitialBrowserCreation(
159159 return
160160 }
161161
162- // On subsequent resizes, notify CEF so it re-queries GetViewRect.
163- log := logging.FromContext(ctx)
162+ // Size observers run on the GTK thread; the captured firstResize state is
163+ // therefore single-threaded.
164164 wv.mu.RLock()
165165 host := wv.host
166166 wv.mu.RUnlock()
167167 if host == nil {
168- log.Debug().
168+ logging.FromContext(ctx).Debug().
169169 Uint64("webview_id", uint64(wv.id)).
170170 Int32("resize_width", w).
171171 Int32("resize_height", h).
@@ -174,15 +174,8 @@ func (f *WebViewFactory) configureInitialBrowserCreation(
174174 Msg("cef: resize observed before browser host existed")
175175 return
176176 }
177- log.Debug().
178- Uint64("webview_id", uint64(wv.id)).
179- Int32("resize_width", w).
180- Int32("resize_height", h).
181- Bool("host_nil", false).
182- Bool("browser_ready", true).
183- Msg("cef: browser resize observed before WasResized")
184177 notifyBrowserResize(host)
185- log.Debug().
178+ logging.FromContext(ctx).Debug().
186179 Uint64("webview_id", uint64(wv.id)).
187180 Int32("resize_width", w).
188181 Int32("resize_height", h).
@@ -159,13 +159,13 @@ func (f *WebViewFactory) configureInitialBrowserCreation(
159 return159 return
160 }160 }
161 161
162- // On subsequent resizes, notify CEF so it re-queries GetViewRect.162+ // Size observers run on the GTK thread; the captured firstResize state is
163- log := logging.FromContext(ctx)163+ // therefore single-threaded.
164 wv.mu.RLock()164 wv.mu.RLock()
165 host := wv.host165 host := wv.host
166 wv.mu.RUnlock()166 wv.mu.RUnlock()
167 if host == nil {167 if host == nil {
168- log.Debug().168+ logging.FromContext(ctx).Debug().
169 Uint64("webview_id", uint64(wv.id)).169 Uint64("webview_id", uint64(wv.id)).
170 Int32("resize_width", w).170 Int32("resize_width", w).
171 Int32("resize_height", h).171 Int32("resize_height", h).
@@ -174,15 +174,8 @@ func (f *WebViewFactory) configureInitialBrowserCreation(
174 Msg("cef: resize observed before browser host existed")174 Msg("cef: resize observed before browser host existed")
175 return175 return
176 }176 }
177- log.Debug().
178- Uint64("webview_id", uint64(wv.id)).
179- Int32("resize_width", w).
180- Int32("resize_height", h).
181- Bool("host_nil", false).
182- Bool("browser_ready", true).
183- Msg("cef: browser resize observed before WasResized")
184 notifyBrowserResize(host)177 notifyBrowserResize(host)
185- log.Debug().178+ logging.FromContext(ctx).Debug().
186 Uint64("webview_id", uint64(wv.id)).179 Uint64("webview_id", uint64(wv.id)).
187 Int32("resize_width", w).180 Int32("resize_width", w).
188 Int32("resize_height", h).181 Int32("resize_height", h).
M internal/infrastructure/cef/render_handler_adapter.go +19 -115
@@ -7,145 +7,49 @@ import (
77 "github.com/bnema/dumber/internal/logging"
88 )
99
10-type dumberRenderHandler struct {
11- wv *WebView
12- delegate purecef.RenderHandler
13-}
14-
15-var _ purecef.RenderHandler = (*dumberRenderHandler)(nil)
16-
1710 func newDumberRenderHandler(wv *WebView) purecef.RenderHandler {
18- h := &dumberRenderHandler{wv: wv}
19- if wv == nil || wv.viewBridge == nil {
20- return h
21- }
22- h.delegate = wv.viewBridge.RenderHandler(cef2gtk.Hooks{
11+ hooks := cef2gtk.Hooks{
2312 OnUnsupportedPaint: func() {
24- if wv.ctx != nil {
13+ if wv != nil && wv.ctx != nil {
2514 logging.FromContext(wv.ctx).Warn().Msg("cef: unsupported CPU paint from accelerated bridge")
2615 }
2716 },
2817 OnError: func(err error) {
29- if wv.ctx != nil {
18+ if wv != nil && wv.ctx != nil {
3019 logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: accelerated render bridge error")
3120 }
3221 },
33- OnTextSelectionChanged: func(selectedText string, selectedRange *purecef.Range) {
34- h.OnTextSelectionChanged(nil, selectedText, selectedRange)
22+ OnTextSelectionChanged: func(selectedText string, _ *purecef.Range) {
23+ handleRenderTextSelectionChanged(wv, selectedText)
3524 },
36- })
37- return h
38-}
39-
40-func (h *dumberRenderHandler) GetAccessibilityHandler() purecef.AccessibilityHandler {
41- if h.delegate != nil {
42- return h.delegate.GetAccessibilityHandler()
43- }
44- return nil
45-}
46-func (h *dumberRenderHandler) GetRootScreenRect(browser purecef.Browser, rect *purecef.Rect) int32 {
47- if h.delegate != nil {
48- return h.delegate.GetRootScreenRect(browser, rect)
49- }
50- return 0
51-}
52-func (h *dumberRenderHandler) GetViewRect(browser purecef.Browser, rect *purecef.Rect) {
53- if h.delegate != nil {
54- h.delegate.GetViewRect(browser, rect)
55- return
56- }
57- if rect != nil {
58- rect.X, rect.Y, rect.Width, rect.Height = 0, 0, 1, 1
59- }
60-}
61-func (h *dumberRenderHandler) GetScreenPoint(browser purecef.Browser, viewX, viewY int32, screenX, screenY *int32) int32 {
62- if h.delegate != nil {
63- return h.delegate.GetScreenPoint(browser, viewX, viewY, screenX, screenY)
64- }
65- return 0
66-}
67-func (h *dumberRenderHandler) GetScreenInfo(browser purecef.Browser, info *purecef.ScreenInfo) int32 {
68- if h.delegate != nil {
69- return h.delegate.GetScreenInfo(browser, info)
70- }
71- return 0
72-}
73-func (h *dumberRenderHandler) OnPopupShow(browser purecef.Browser, show int32) {
74- if h.delegate != nil {
75- h.delegate.OnPopupShow(browser, show)
76- }
77-}
78-func (h *dumberRenderHandler) OnPopupSize(browser purecef.Browser, rect *purecef.Rect) {
79- if h.delegate != nil {
80- h.delegate.OnPopupSize(browser, rect)
81- }
82-}
83-func (h *dumberRenderHandler) OnPaint(browser purecef.Browser, elementType purecef.PaintElementType, dirtyRects []purecef.Rect, buffer []byte, width, height int32) {
84- if h.delegate != nil {
85- h.delegate.OnPaint(browser, elementType, dirtyRects, buffer, width, height)
86- }
87-}
88-func (h *dumberRenderHandler) OnAcceleratedPaint(browser purecef.Browser, elementType purecef.PaintElementType, dirtyRects []purecef.Rect, info *purecef.AcceleratedPaintInfo) {
89- if h.delegate != nil {
90- h.delegate.OnAcceleratedPaint(browser, elementType, dirtyRects, info)
91- }
92-}
93-func (h *dumberRenderHandler) GetTouchHandleSize(browser purecef.Browser, orientation purecef.HorizontalAlignment, size *purecef.Size) {
94- if h.delegate != nil {
95- h.delegate.GetTouchHandleSize(browser, orientation, size)
96- }
97-}
98-func (h *dumberRenderHandler) OnTouchHandleStateChanged(browser purecef.Browser, state *purecef.TouchHandleState) {
99- if h.delegate != nil {
100- h.delegate.OnTouchHandleStateChanged(browser, state)
101- }
102-}
103-func (h *dumberRenderHandler) StartDragging(browser purecef.Browser, dragData purecef.DragData, allowedOps purecef.DragOperationsMask, x, y int32) int32 {
104- if h.delegate != nil {
105- return h.delegate.StartDragging(browser, dragData, allowedOps, x, y)
106- }
107- return 0
108-}
109-func (h *dumberRenderHandler) UpdateDragCursor(browser purecef.Browser, operation purecef.DragOperationsMask) {
110- if h.delegate != nil {
111- h.delegate.UpdateDragCursor(browser, operation)
112- }
113-}
114-func (h *dumberRenderHandler) OnScrollOffsetChanged(browser purecef.Browser, x, y float64) {
115- if h.delegate != nil {
116- h.delegate.OnScrollOffsetChanged(browser, x, y)
11725 }
118-}
119-func (h *dumberRenderHandler) OnImeCompositionRangeChanged(browser purecef.Browser, selectedRange *purecef.Range, characterBounds []purecef.Rect) {
120- if h.delegate != nil {
121- h.delegate.OnImeCompositionRangeChanged(browser, selectedRange, characterBounds)
26+ if wv == nil || wv.viewBridge == nil {
27+ var view *cef2gtk.View
28+ return view.RenderHandler(hooks)
12229 }
30+ return wv.viewBridge.RenderHandler(hooks)
12331 }
124-func (h *dumberRenderHandler) OnTextSelectionChanged(_ purecef.Browser, selectedText string, _ *purecef.Range) {
125- if h == nil || h.wv == nil {
32+
33+func handleRenderTextSelectionChanged(wv *WebView, selectedText string) {
34+ if wv == nil {
12635 return
12736 }
128- previous, changed := h.wv.setSelectedText(selectedText)
37+ previous, changed := wv.setSelectedText(selectedText)
12938 if !changed {
13039 return
13140 }
132- if h.wv.ctx != nil && selectedText == "" {
41+ if wv.ctx != nil && selectedText == "" {
13342 if previous != "" {
134- logging.FromContext(h.wv.ctx).Debug().
43+ logging.FromContext(wv.ctx).Debug().
13544 Int("prev_text_len", len(previous)).
13645 Msg("cef: text selection cleared")
13746 }
138- } else if h.wv.ctx != nil {
139- logging.FromContext(h.wv.ctx).Debug().
47+ } else if wv.ctx != nil {
48+ logging.FromContext(wv.ctx).Debug().
14049 Int("text_len", len(selectedText)).
14150 Msg("cef: text selection changed")
14251 }
143- if h.wv.engine != nil {
144- h.wv.scheduleSelectionUpdate(selectedText)
145- }
146-}
147-func (h *dumberRenderHandler) OnVirtualKeyboardRequested(browser purecef.Browser, mode purecef.TextInputMode) {
148- if h.delegate != nil {
149- h.delegate.OnVirtualKeyboardRequested(browser, mode)
52+ if wv.engine != nil {
53+ wv.scheduleSelectionUpdate(selectedText)
15054 }
15155 }
@@ -7,145 +7,49 @@ import (
7 "github.com/bnema/dumber/internal/logging"7 "github.com/bnema/dumber/internal/logging"
8 )8 )
9 9
10-type dumberRenderHandler struct {
11- wv *WebView
12- delegate purecef.RenderHandler
13-}
14-
15-var _ purecef.RenderHandler = (*dumberRenderHandler)(nil)
16-
17 func newDumberRenderHandler(wv *WebView) purecef.RenderHandler {10 func newDumberRenderHandler(wv *WebView) purecef.RenderHandler {
18- h := &dumberRenderHandler{wv: wv}11+ hooks := cef2gtk.Hooks{
19- if wv == nil || wv.viewBridge == nil {
20- return h
21- }
22- h.delegate = wv.viewBridge.RenderHandler(cef2gtk.Hooks{
23 OnUnsupportedPaint: func() {12 OnUnsupportedPaint: func() {
24- if wv.ctx != nil {13+ if wv != nil && wv.ctx != nil {
25 logging.FromContext(wv.ctx).Warn().Msg("cef: unsupported CPU paint from accelerated bridge")14 logging.FromContext(wv.ctx).Warn().Msg("cef: unsupported CPU paint from accelerated bridge")
26 }15 }
27 },16 },
28 OnError: func(err error) {17 OnError: func(err error) {
29- if wv.ctx != nil {18+ if wv != nil && wv.ctx != nil {
30 logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: accelerated render bridge error")19 logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: accelerated render bridge error")
31 }20 }
32 },21 },
33- OnTextSelectionChanged: func(selectedText string, selectedRange *purecef.Range) {22+ OnTextSelectionChanged: func(selectedText string, _ *purecef.Range) {
34- h.OnTextSelectionChanged(nil, selectedText, selectedRange)23+ handleRenderTextSelectionChanged(wv, selectedText)
35 },24 },
36- })
37- return h
38-}
39-
40-func (h *dumberRenderHandler) GetAccessibilityHandler() purecef.AccessibilityHandler {
41- if h.delegate != nil {
42- return h.delegate.GetAccessibilityHandler()
43- }
44- return nil
45-}
46-func (h *dumberRenderHandler) GetRootScreenRect(browser purecef.Browser, rect *purecef.Rect) int32 {
47- if h.delegate != nil {
48- return h.delegate.GetRootScreenRect(browser, rect)
49- }
50- return 0
51-}
52-func (h *dumberRenderHandler) GetViewRect(browser purecef.Browser, rect *purecef.Rect) {
53- if h.delegate != nil {
54- h.delegate.GetViewRect(browser, rect)
55- return
56- }
57- if rect != nil {
58- rect.X, rect.Y, rect.Width, rect.Height = 0, 0, 1, 1
59- }
60-}
61-func (h *dumberRenderHandler) GetScreenPoint(browser purecef.Browser, viewX, viewY int32, screenX, screenY *int32) int32 {
62- if h.delegate != nil {
63- return h.delegate.GetScreenPoint(browser, viewX, viewY, screenX, screenY)
64- }
65- return 0
66-}
67-func (h *dumberRenderHandler) GetScreenInfo(browser purecef.Browser, info *purecef.ScreenInfo) int32 {
68- if h.delegate != nil {
69- return h.delegate.GetScreenInfo(browser, info)
70- }
71- return 0
72-}
73-func (h *dumberRenderHandler) OnPopupShow(browser purecef.Browser, show int32) {
74- if h.delegate != nil {
75- h.delegate.OnPopupShow(browser, show)
76- }
77-}
78-func (h *dumberRenderHandler) OnPopupSize(browser purecef.Browser, rect *purecef.Rect) {
79- if h.delegate != nil {
80- h.delegate.OnPopupSize(browser, rect)
81- }
82-}
83-func (h *dumberRenderHandler) OnPaint(browser purecef.Browser, elementType purecef.PaintElementType, dirtyRects []purecef.Rect, buffer []byte, width, height int32) {
84- if h.delegate != nil {
85- h.delegate.OnPaint(browser, elementType, dirtyRects, buffer, width, height)
86- }
87-}
88-func (h *dumberRenderHandler) OnAcceleratedPaint(browser purecef.Browser, elementType purecef.PaintElementType, dirtyRects []purecef.Rect, info *purecef.AcceleratedPaintInfo) {
89- if h.delegate != nil {
90- h.delegate.OnAcceleratedPaint(browser, elementType, dirtyRects, info)
91- }
92-}
93-func (h *dumberRenderHandler) GetTouchHandleSize(browser purecef.Browser, orientation purecef.HorizontalAlignment, size *purecef.Size) {
94- if h.delegate != nil {
95- h.delegate.GetTouchHandleSize(browser, orientation, size)
96- }
97-}
98-func (h *dumberRenderHandler) OnTouchHandleStateChanged(browser purecef.Browser, state *purecef.TouchHandleState) {
99- if h.delegate != nil {
100- h.delegate.OnTouchHandleStateChanged(browser, state)
101- }
102-}
103-func (h *dumberRenderHandler) StartDragging(browser purecef.Browser, dragData purecef.DragData, allowedOps purecef.DragOperationsMask, x, y int32) int32 {
104- if h.delegate != nil {
105- return h.delegate.StartDragging(browser, dragData, allowedOps, x, y)
106- }
107- return 0
108-}
109-func (h *dumberRenderHandler) UpdateDragCursor(browser purecef.Browser, operation purecef.DragOperationsMask) {
110- if h.delegate != nil {
111- h.delegate.UpdateDragCursor(browser, operation)
112- }
113-}
114-func (h *dumberRenderHandler) OnScrollOffsetChanged(browser purecef.Browser, x, y float64) {
115- if h.delegate != nil {
116- h.delegate.OnScrollOffsetChanged(browser, x, y)
117 }25 }
118-}26+ if wv == nil || wv.viewBridge == nil {
119-func (h *dumberRenderHandler) OnImeCompositionRangeChanged(browser purecef.Browser, selectedRange *purecef.Range, characterBounds []purecef.Rect) {27+ var view *cef2gtk.View
120- if h.delegate != nil {28+ return view.RenderHandler(hooks)
121- h.delegate.OnImeCompositionRangeChanged(browser, selectedRange, characterBounds)
122 }29 }
30+ return wv.viewBridge.RenderHandler(hooks)
123 }31 }
124-func (h *dumberRenderHandler) OnTextSelectionChanged(_ purecef.Browser, selectedText string, _ *purecef.Range) {32+
125- if h == nil || h.wv == nil {33+func handleRenderTextSelectionChanged(wv *WebView, selectedText string) {
34+ if wv == nil {
126 return35 return
127 }36 }
128- previous, changed := h.wv.setSelectedText(selectedText)37+ previous, changed := wv.setSelectedText(selectedText)
129 if !changed {38 if !changed {
130 return39 return
131 }40 }
132- if h.wv.ctx != nil && selectedText == "" {41+ if wv.ctx != nil && selectedText == "" {
133 if previous != "" {42 if previous != "" {
134- logging.FromContext(h.wv.ctx).Debug().43+ logging.FromContext(wv.ctx).Debug().
135 Int("prev_text_len", len(previous)).44 Int("prev_text_len", len(previous)).
136 Msg("cef: text selection cleared")45 Msg("cef: text selection cleared")
137 }46 }
138- } else if h.wv.ctx != nil {47+ } else if wv.ctx != nil {
139- logging.FromContext(h.wv.ctx).Debug().48+ logging.FromContext(wv.ctx).Debug().
140 Int("text_len", len(selectedText)).49 Int("text_len", len(selectedText)).
141 Msg("cef: text selection changed")50 Msg("cef: text selection changed")
142 }51 }
143- if h.wv.engine != nil {52+ if wv.engine != nil {
144- h.wv.scheduleSelectionUpdate(selectedText)53+ wv.scheduleSelectionUpdate(selectedText)
145- }
146-}
147-func (h *dumberRenderHandler) OnVirtualKeyboardRequested(browser purecef.Browser, mode purecef.TextInputMode) {
148- if h.delegate != nil {
149- h.delegate.OnVirtualKeyboardRequested(browser, mode)
150 }54 }
151 }55 }
M internal/infrastructure/cef/webview_handlers_test.go +11 -5
@@ -302,7 +302,7 @@ func TestOnTextSelectionChanged_ForwardsSelectionToClipboardOrchestrator(t *test
302302 clipboardTextOrchestrator: orchestrator,
303303 },
304304 }
305- h := &dumberRenderHandler{wv: wv}
305+ h := &selectionRenderHandlerForTest{wv: wv}
306306
307307 h.OnTextSelectionChanged(nil, "selected text", nil)
308308
@@ -332,7 +332,7 @@ func TestOnTextSelectionChanged_DebouncesAndCollapsesRapidUpdates(t *testing.T)
332332 clipboardTextOrchestrator: orchestrator,
333333 },
334334 }
335- h := &dumberRenderHandler{wv: wv}
335+ h := &selectionRenderHandlerForTest{wv: wv}
336336
337337 h.OnTextSelectionChanged(nil, "first selection", nil)
338338 h.OnTextSelectionChanged(nil, "second selection", nil)
@@ -375,7 +375,7 @@ func TestOnTextSelectionChanged_SuppressesAutoCopyWhenFocusedNodeEditableAndResu
375375 clipboardTextOrchestrator: orchestrator,
376376 },
377377 }
378- h := &dumberRenderHandler{wv: wv}
378+ h := &selectionRenderHandlerForTest{wv: wv}
379379 clientHandlers := &handlerSet{wv: wv}
380380 frame := cefmocks.NewMockFrame(t)
381381 oldFactory := newRendererBridgeProcessMessage
@@ -449,7 +449,7 @@ func TestOnTextSelectionChanged_DoesNotEmitLateDebouncedUpdateAfterDestroy(t *te
449449 clipboardTextOrchestrator: orchestrator,
450450 },
451451 }
452- h := &dumberRenderHandler{wv: wv}
452+ h := &selectionRenderHandlerForTest{wv: wv}
453453
454454 h.OnTextSelectionChanged(nil, "selected text", nil)
455455 require.Len(t, scheduler.callbacks, 1)
@@ -500,7 +500,7 @@ func TestOptionalHandlersAreAlwaysEnabled(t *testing.T) {
500500 // AudioHandler is always enabled (required for media decoding).
501501 require.Same(t, h, h.GetAudioHandler())
502502 require.Same(t, h, h.GetContextMenuHandler())
503- require.IsType(t, &dumberRenderHandler{}, h.GetRenderHandler())
503+ require.NotSame(t, h, h.GetRenderHandler())
504504 }
505505
506506 func TestGetRenderHandlerPreservesTextSelectionCallback(t *testing.T) {
@@ -526,3 +526,9 @@ func TestGetDownloadHandler_EnabledWhenEngineConfigured(t *testing.T) {
526526
527527 require.Same(t, h, h.GetDownloadHandler())
528528 }
529+
530+type selectionRenderHandlerForTest struct{ wv *WebView }
531+
532+func (h *selectionRenderHandlerForTest) OnTextSelectionChanged(_ purecef.Browser, selectedText string, _ *purecef.Range) {
533+ handleRenderTextSelectionChanged(h.wv, selectedText)
534+}
@@ -302,7 +302,7 @@ func TestOnTextSelectionChanged_ForwardsSelectionToClipboardOrchestrator(t *test
302 clipboardTextOrchestrator: orchestrator,302 clipboardTextOrchestrator: orchestrator,
303 },303 },
304 }304 }
305- h := &dumberRenderHandler{wv: wv}305+ h := &selectionRenderHandlerForTest{wv: wv}
306 306
307 h.OnTextSelectionChanged(nil, "selected text", nil)307 h.OnTextSelectionChanged(nil, "selected text", nil)
308 308
@@ -332,7 +332,7 @@ func TestOnTextSelectionChanged_DebouncesAndCollapsesRapidUpdates(t *testing.T)
332 clipboardTextOrchestrator: orchestrator,332 clipboardTextOrchestrator: orchestrator,
333 },333 },
334 }334 }
335- h := &dumberRenderHandler{wv: wv}335+ h := &selectionRenderHandlerForTest{wv: wv}
336 336
337 h.OnTextSelectionChanged(nil, "first selection", nil)337 h.OnTextSelectionChanged(nil, "first selection", nil)
338 h.OnTextSelectionChanged(nil, "second selection", nil)338 h.OnTextSelectionChanged(nil, "second selection", nil)
@@ -375,7 +375,7 @@ func TestOnTextSelectionChanged_SuppressesAutoCopyWhenFocusedNodeEditableAndResu
375 clipboardTextOrchestrator: orchestrator,375 clipboardTextOrchestrator: orchestrator,
376 },376 },
377 }377 }
378- h := &dumberRenderHandler{wv: wv}378+ h := &selectionRenderHandlerForTest{wv: wv}
379 clientHandlers := &handlerSet{wv: wv}379 clientHandlers := &handlerSet{wv: wv}
380 frame := cefmocks.NewMockFrame(t)380 frame := cefmocks.NewMockFrame(t)
381 oldFactory := newRendererBridgeProcessMessage381 oldFactory := newRendererBridgeProcessMessage
@@ -449,7 +449,7 @@ func TestOnTextSelectionChanged_DoesNotEmitLateDebouncedUpdateAfterDestroy(t *te
449 clipboardTextOrchestrator: orchestrator,449 clipboardTextOrchestrator: orchestrator,
450 },450 },
451 }451 }
452- h := &dumberRenderHandler{wv: wv}452+ h := &selectionRenderHandlerForTest{wv: wv}
453 453
454 h.OnTextSelectionChanged(nil, "selected text", nil)454 h.OnTextSelectionChanged(nil, "selected text", nil)
455 require.Len(t, scheduler.callbacks, 1)455 require.Len(t, scheduler.callbacks, 1)
@@ -500,7 +500,7 @@ func TestOptionalHandlersAreAlwaysEnabled(t *testing.T) {
500 // AudioHandler is always enabled (required for media decoding).500 // AudioHandler is always enabled (required for media decoding).
501 require.Same(t, h, h.GetAudioHandler())501 require.Same(t, h, h.GetAudioHandler())
502 require.Same(t, h, h.GetContextMenuHandler())502 require.Same(t, h, h.GetContextMenuHandler())
503- require.IsType(t, &dumberRenderHandler{}, h.GetRenderHandler())503+ require.NotSame(t, h, h.GetRenderHandler())
504 }504 }
505 505
506 func TestGetRenderHandlerPreservesTextSelectionCallback(t *testing.T) {506 func TestGetRenderHandlerPreservesTextSelectionCallback(t *testing.T) {
@@ -526,3 +526,9 @@ func TestGetDownloadHandler_EnabledWhenEngineConfigured(t *testing.T) {
526 526
527 require.Same(t, h, h.GetDownloadHandler())527 require.Same(t, h, h.GetDownloadHandler())
528 }528 }
529+
530+type selectionRenderHandlerForTest struct{ wv *WebView }
531+
532+func (h *selectionRenderHandlerForTest) OnTextSelectionChanged(_ purecef.Browser, selectedText string, _ *purecef.Range) {
533+ handleRenderTextSelectionChanged(h.wv, selectedText)
534+}

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