Simplify cef2gtk migration
- commit
- e528b972c45216591be55b9b4384de5c9e8a5468
- author
- brice · · 2026-04-29 20:10 UTC
- parent
- 5eb26e1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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 @@ | |||
| 1 | 1 | package cef | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "os/exec" | |
| 4 | + | "os" | |
| 5 | + | "path/filepath" | |
| 5 | 6 | "strings" | |
| 6 | 7 | "testing" | |
| 7 | 8 | ) | |
| 8 | 9 | ||
| 9 | 10 | 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 | |
| 21 | 36 | } | |
| 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 | |
| 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 | } | |
| @@ -1,27 +1,45 @@ | |||||
| 1 | package cef | 1 | 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 the | 12 | + | 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 | - | continue | 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 | |||
| 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 | - | continue | 38 | + | 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 ( | |||
| 8 | 8 | purecef "github.com/bnema/purego-cef/cef" | |
| 9 | 9 | ||
| 10 | 10 | "github.com/bnema/dumber/internal/application/port" | |
| 11 | + | "github.com/bnema/dumber/internal/infrastructure/gtkmenu" | |
| 11 | 12 | "github.com/bnema/dumber/internal/logging" | |
| 12 | 13 | "github.com/bnema/puregotk/v4/gtk" | |
| 13 | 14 | ) | |
| @@ -81,7 +82,7 @@ func (h *handlerSet) RunContextMenu( | |||
| 81 | 82 | } | |
| 82 | 83 | glArea := wv.viewBridge.GLArea() | |
| 83 | 84 | logContextMenuPopupRequest(h, glArea, params, x, y) | |
| 84 | - | NewRenderer(nil).Show( | |
| 85 | + | gtkmenu.NewRenderer(nil).Show( | |
| 85 | 86 | items, | |
| 86 | 87 | &glArea.Widget, | |
| 87 | 88 | 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( | |||
| 159 | 159 | return | |
| 160 | 160 | } | |
| 161 | 161 | ||
| 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. | |
| 164 | 164 | wv.mu.RLock() | |
| 165 | 165 | host := wv.host | |
| 166 | 166 | wv.mu.RUnlock() | |
| 167 | 167 | if host == nil { | |
| 168 | - | log.Debug(). | |
| 168 | + | logging.FromContext(ctx).Debug(). | |
| 169 | 169 | Uint64("webview_id", uint64(wv.id)). | |
| 170 | 170 | Int32("resize_width", w). | |
| 171 | 171 | Int32("resize_height", h). | |
| @@ -174,15 +174,8 @@ func (f *WebViewFactory) configureInitialBrowserCreation( | |||
| 174 | 174 | Msg("cef: resize observed before browser host existed") | |
| 175 | 175 | 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 | 177 | notifyBrowserResize(host) | |
| 185 | - | log.Debug(). | |
| 178 | + | logging.FromContext(ctx).Debug(). | |
| 186 | 179 | Uint64("webview_id", uint64(wv.id)). | |
| 187 | 180 | Int32("resize_width", w). | |
| 188 | 181 | Int32("resize_height", h). | |
| @@ -159,13 +159,13 @@ func (f *WebViewFactory) configureInitialBrowserCreation( | |||||
| 159 | return | 159 | 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.host | 165 | 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 | return | 175 | 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 ( | |||
| 7 | 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 | 10 | 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{ | |
| 23 | 12 | OnUnsupportedPaint: func() { | |
| 24 | - | if wv.ctx != nil { | |
| 13 | + | if wv != nil && wv.ctx != nil { | |
| 25 | 14 | logging.FromContext(wv.ctx).Warn().Msg("cef: unsupported CPU paint from accelerated bridge") | |
| 26 | 15 | } | |
| 27 | 16 | }, | |
| 28 | 17 | OnError: func(err error) { | |
| 29 | - | if wv.ctx != nil { | |
| 18 | + | if wv != nil && wv.ctx != nil { | |
| 30 | 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) { | |
| 34 | - | h.OnTextSelectionChanged(nil, selectedText, selectedRange) | |
| 22 | + | OnTextSelectionChanged: func(selectedText string, _ *purecef.Range) { | |
| 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 | - | } | |
| 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) | |
| 122 | 29 | } | |
| 30 | + | return wv.viewBridge.RenderHandler(hooks) | |
| 123 | 31 | } | |
| 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 { | |
| 126 | 35 | return | |
| 127 | 36 | } | |
| 128 | - | previous, changed := h.wv.setSelectedText(selectedText) | |
| 37 | + | previous, changed := wv.setSelectedText(selectedText) | |
| 129 | 38 | if !changed { | |
| 130 | 39 | return | |
| 131 | 40 | } | |
| 132 | - | if h.wv.ctx != nil && selectedText == "" { | |
| 41 | + | if wv.ctx != nil && selectedText == "" { | |
| 133 | 42 | if previous != "" { | |
| 134 | - | logging.FromContext(h.wv.ctx).Debug(). | |
| 43 | + | logging.FromContext(wv.ctx).Debug(). | |
| 135 | 44 | Int("prev_text_len", len(previous)). | |
| 136 | 45 | Msg("cef: text selection cleared") | |
| 137 | 46 | } | |
| 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(). | |
| 140 | 49 | Int("text_len", len(selectedText)). | |
| 141 | 50 | Msg("cef: text selection changed") | |
| 142 | 51 | } | |
| 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) | |
| 150 | 54 | } | |
| 151 | 55 | } | |
| @@ -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 | return | 35 | return | ||
| 127 | } | 36 | } | ||
| 128 | - | previous, changed := h.wv.setSelectedText(selectedText) | 37 | + | previous, changed := wv.setSelectedText(selectedText) |
| 129 | if !changed { | 38 | if !changed { | ||
| 130 | return | 39 | 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 | |||
| 302 | 302 | clipboardTextOrchestrator: orchestrator, | |
| 303 | 303 | }, | |
| 304 | 304 | } | |
| 305 | - | h := &dumberRenderHandler{wv: wv} | |
| 305 | + | h := &selectionRenderHandlerForTest{wv: wv} | |
| 306 | 306 | ||
| 307 | 307 | h.OnTextSelectionChanged(nil, "selected text", nil) | |
| 308 | 308 | ||
| @@ -332,7 +332,7 @@ func TestOnTextSelectionChanged_DebouncesAndCollapsesRapidUpdates(t *testing.T) | |||
| 332 | 332 | clipboardTextOrchestrator: orchestrator, | |
| 333 | 333 | }, | |
| 334 | 334 | } | |
| 335 | - | h := &dumberRenderHandler{wv: wv} | |
| 335 | + | h := &selectionRenderHandlerForTest{wv: wv} | |
| 336 | 336 | ||
| 337 | 337 | h.OnTextSelectionChanged(nil, "first selection", nil) | |
| 338 | 338 | h.OnTextSelectionChanged(nil, "second selection", nil) | |
| @@ -375,7 +375,7 @@ func TestOnTextSelectionChanged_SuppressesAutoCopyWhenFocusedNodeEditableAndResu | |||
| 375 | 375 | clipboardTextOrchestrator: orchestrator, | |
| 376 | 376 | }, | |
| 377 | 377 | } | |
| 378 | - | h := &dumberRenderHandler{wv: wv} | |
| 378 | + | h := &selectionRenderHandlerForTest{wv: wv} | |
| 379 | 379 | clientHandlers := &handlerSet{wv: wv} | |
| 380 | 380 | frame := cefmocks.NewMockFrame(t) | |
| 381 | 381 | oldFactory := newRendererBridgeProcessMessage | |
| @@ -449,7 +449,7 @@ func TestOnTextSelectionChanged_DoesNotEmitLateDebouncedUpdateAfterDestroy(t *te | |||
| 449 | 449 | clipboardTextOrchestrator: orchestrator, | |
| 450 | 450 | }, | |
| 451 | 451 | } | |
| 452 | - | h := &dumberRenderHandler{wv: wv} | |
| 452 | + | h := &selectionRenderHandlerForTest{wv: wv} | |
| 453 | 453 | ||
| 454 | 454 | h.OnTextSelectionChanged(nil, "selected text", nil) | |
| 455 | 455 | require.Len(t, scheduler.callbacks, 1) | |
| @@ -500,7 +500,7 @@ func TestOptionalHandlersAreAlwaysEnabled(t *testing.T) { | |||
| 500 | 500 | // AudioHandler is always enabled (required for media decoding). | |
| 501 | 501 | require.Same(t, h, h.GetAudioHandler()) | |
| 502 | 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 | 506 | func TestGetRenderHandlerPreservesTextSelectionCallback(t *testing.T) { | |
| @@ -526,3 +526,9 @@ func TestGetDownloadHandler_EnabledWhenEngineConfigured(t *testing.T) { | |||
| 526 | 526 | ||
| 527 | 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 | + | } | |
| @@ -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 := newRendererBridgeProcessMessage | 381 | 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 | + | } | |||