Address migration review feedback
- commit
- 7297a701a0071fb4a11fd59beb89d2ce7975b6b6
- author
- brice · · 2026-04-29 20:17 UTC
- parent
- e528b97
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +88 -37
M internal/infrastructure/cef/cef2gtk_adapter.go +18 -17
| @@ -2,6 +2,7 @@ package cef | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "errors" | |
| 5 | + | "sync/atomic" | |
| 5 | 6 | ||
| 6 | 7 | purecef "github.com/bnema/purego-cef/cef" | |
| 7 | 8 | cef2gtk "github.com/bnema/purego-cef2gtk" | |
| @@ -24,7 +25,7 @@ var ErrAdapterDestroyed = errors.New("cef2gtk_adapter: adapter is destroyed") | |||
| 24 | 25 | // controller implementation — those belong to purego-cef2gtk. | |
| 25 | 26 | type Cef2gtkAdapter struct { | |
| 26 | 27 | view *cef2gtk.View | |
| 27 | - | destroyed bool | |
| 28 | + | destroyed atomic.Bool | |
| 28 | 29 | } | |
| 29 | 30 | ||
| 30 | 31 | // NewCef2gtkAdapter creates a GtkGLArea-backed accelerated CEF view and wraps | |
| @@ -40,7 +41,7 @@ func NewCef2gtkAdapter() *Cef2gtkAdapter { | |||
| 40 | 41 | // Widget returns the native GTK widget for embedding into containers. | |
| 41 | 42 | // The returned pointer must be used from the GTK main thread for packing. | |
| 42 | 43 | func (a *Cef2gtkAdapter) Widget() *gtk.Widget { | |
| 43 | - | if a == nil || a.view == nil { | |
| 44 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 44 | 45 | return nil | |
| 45 | 46 | } | |
| 46 | 47 | return a.view.Widget() | |
| @@ -49,7 +50,7 @@ func (a *Cef2gtkAdapter) Widget() *gtk.Widget { | |||
| 49 | 50 | // GLArea returns the underlying GtkGLArea for signal connections (resize, | |
| 50 | 51 | // tick, etc.). Signal connection/disconnection must happen on the GTK thread. | |
| 51 | 52 | func (a *Cef2gtkAdapter) GLArea() *gtk.GLArea { | |
| 52 | - | if a == nil || a.view == nil { | |
| 53 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 53 | 54 | return nil | |
| 54 | 55 | } | |
| 55 | 56 | return a.view.GLArea() | |
| @@ -68,7 +69,7 @@ func (a *Cef2gtkAdapter) NativeWidget() uintptr { | |||
| 68 | 69 | // Size returns the bridge's last observed positive widget size, or 1x1 before | |
| 69 | 70 | // the GTK widget has been allocated. It is safe to call off the GTK thread. | |
| 70 | 71 | func (a *Cef2gtkAdapter) Size() (int32, int32) { | |
| 71 | - | if a == nil || a.view == nil { | |
| 72 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 72 | 73 | return 1, 1 | |
| 73 | 74 | } | |
| 74 | 75 | return a.view.Size() | |
| @@ -77,7 +78,7 @@ func (a *Cef2gtkAdapter) Size() (int32, int32) { | |||
| 77 | 78 | // DeviceScaleFactor returns the bridge's last observed GTK scale factor, or 1 | |
| 78 | 79 | // before the widget has reported a scale. It is safe to call off the GTK thread. | |
| 79 | 80 | func (a *Cef2gtkAdapter) DeviceScaleFactor() float32 { | |
| 80 | - | if a == nil || a.view == nil { | |
| 81 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 81 | 82 | return 1 | |
| 82 | 83 | } | |
| 83 | 84 | return a.view.DeviceScaleFactor() | |
| @@ -87,7 +88,7 @@ func (a *Cef2gtkAdapter) DeviceScaleFactor() float32 { | |||
| 87 | 88 | // unregister from the GTK main thread; callbacks are invoked by the bridge from | |
| 88 | 89 | // GTK size notifications. | |
| 89 | 90 | func (a *Cef2gtkAdapter) AddSizeObserver(fn func(width, height int32)) func() { | |
| 90 | - | if a == nil || a.view == nil { | |
| 91 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 91 | 92 | return func() {} | |
| 92 | 93 | } | |
| 93 | 94 | return a.view.AddSizeObserver(fn) | |
| @@ -96,7 +97,7 @@ func (a *Cef2gtkAdapter) AddSizeObserver(fn func(width, height int32)) func() { | |||
| 96 | 97 | // HasFocus reports whether the bridge widget currently has GTK focus. Must be | |
| 97 | 98 | // called on the GTK main thread. | |
| 98 | 99 | func (a *Cef2gtkAdapter) HasFocus() bool { | |
| 99 | - | if a == nil || a.view == nil { | |
| 100 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 100 | 101 | return false | |
| 101 | 102 | } | |
| 102 | 103 | return a.view.HasFocus() | |
| @@ -105,7 +106,7 @@ func (a *Cef2gtkAdapter) HasFocus() bool { | |||
| 105 | 106 | // SetCursorFromName applies a named cursor to the bridge widget. Must be called | |
| 106 | 107 | // on the GTK main thread. | |
| 107 | 108 | func (a *Cef2gtkAdapter) SetCursorFromName(name string) { | |
| 108 | - | if a == nil || a.view == nil { | |
| 109 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 109 | 110 | return | |
| 110 | 111 | } | |
| 111 | 112 | a.view.SetCursorFromName(name) | |
| @@ -114,7 +115,7 @@ func (a *Cef2gtkAdapter) SetCursorFromName(name string) { | |||
| 114 | 115 | // PrepareOnGTKThread initializes GL/EGL resources owned by purego-cef2gtk. | |
| 115 | 116 | // Must be called on the GTK main thread. | |
| 116 | 117 | func (a *Cef2gtkAdapter) PrepareOnGTKThread() error { | |
| 117 | - | if a == nil || a.view == nil { | |
| 118 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 118 | 119 | return ErrAdapterDestroyed | |
| 119 | 120 | } | |
| 120 | 121 | return a.view.PrepareOnGTKThread() | |
| @@ -126,7 +127,7 @@ func (a *Cef2gtkAdapter) PrepareOnGTKThread() error { | |||
| 126 | 127 | // | |
| 127 | 128 | // hooks may be a zero-value cef2gtk.Hooks if no custom callbacks are needed. | |
| 128 | 129 | func (a *Cef2gtkAdapter) RenderHandler(hooks cef2gtk.Hooks) purecef.RenderHandler { | |
| 129 | - | if a == nil || a.view == nil { | |
| 130 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 130 | 131 | return nil | |
| 131 | 132 | } | |
| 132 | 133 | return a.view.RenderHandler(hooks) | |
| @@ -137,7 +138,7 @@ func (a *Cef2gtkAdapter) RenderHandler(hooks cef2gtk.Hooks) purecef.RenderHandle | |||
| 137 | 138 | // | |
| 138 | 139 | // opts.Scale configures HiDPI scale for pointer coordinate translation. | |
| 139 | 140 | func (a *Cef2gtkAdapter) AttachInput(host purecef.BrowserHost, opts cef2gtk.InputOptions) error { | |
| 140 | - | if a == nil || a.view == nil { | |
| 141 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 141 | 142 | return ErrAdapterDestroyed | |
| 142 | 143 | } | |
| 143 | 144 | return a.view.AttachInput(host, opts) | |
| @@ -147,7 +148,7 @@ func (a *Cef2gtkAdapter) AttachInput(host purecef.BrowserHost, opts cef2gtk.Inpu | |||
| 147 | 148 | // Must be called on the GTK main thread. Callers must call AttachInput first or | |
| 148 | 149 | // this returns an error from the underlying bridge. | |
| 149 | 150 | func (a *Cef2gtkAdapter) SetInputHost(host purecef.BrowserHost) error { | |
| 150 | - | if a == nil || a.view == nil { | |
| 151 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 151 | 152 | return ErrAdapterDestroyed | |
| 152 | 153 | } | |
| 153 | 154 | return a.view.SetInputHost(host) | |
| @@ -156,7 +157,7 @@ func (a *Cef2gtkAdapter) SetInputHost(host purecef.BrowserHost) error { | |||
| 156 | 157 | // DetachInput removes GTK input controllers attached by AttachInput. | |
| 157 | 158 | // Must be called on the GTK main thread. | |
| 158 | 159 | func (a *Cef2gtkAdapter) DetachInput() error { | |
| 159 | - | if a == nil || a.view == nil { | |
| 160 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 160 | 161 | return ErrAdapterDestroyed | |
| 161 | 162 | } | |
| 162 | 163 | return a.view.DetachInput() | |
| @@ -165,7 +166,7 @@ func (a *Cef2gtkAdapter) DetachInput() error { | |||
| 165 | 166 | // Diagnostics returns a point-in-time snapshot of bridge rendering diagnostics | |
| 166 | 167 | // including accelerated paint counts, import/render failures, and events. | |
| 167 | 168 | func (a *Cef2gtkAdapter) Diagnostics() cef2gtk.Diagnostics { | |
| 168 | - | if a == nil || a.view == nil { | |
| 169 | + | if a == nil || a.destroyed.Load() || a.view == nil { | |
| 169 | 170 | return cef2gtk.Diagnostics{} | |
| 170 | 171 | } | |
| 171 | 172 | return a.view.Diagnostics() | |
| @@ -181,16 +182,16 @@ func (a *Cef2gtkAdapter) Destroy() error { | |||
| 181 | 182 | return ErrAdapterDestroyed | |
| 182 | 183 | } | |
| 183 | 184 | if a.view == nil { | |
| 184 | - | a.destroyed = true | |
| 185 | + | a.destroyed.Store(true) | |
| 185 | 186 | return ErrAdapterDestroyed | |
| 186 | 187 | } | |
| 187 | 188 | err := a.view.Destroy() | |
| 188 | 189 | a.view = nil | |
| 189 | - | a.destroyed = true | |
| 190 | + | a.destroyed.Store(true) | |
| 190 | 191 | return err | |
| 191 | 192 | } | |
| 192 | 193 | ||
| 193 | 194 | // IsDestroyed returns true if the adapter has been destroyed. | |
| 194 | 195 | func (a *Cef2gtkAdapter) IsDestroyed() bool { | |
| 195 | - | return a == nil || a.destroyed | |
| 196 | + | return a == nil || a.destroyed.Load() | |
| 196 | 197 | } | |
| @@ -2,6 +2,7 @@ package cef | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "errors" | 4 | "errors" | ||
| 5 | + | "sync/atomic" | |||
| 5 | 6 | ||||
| 6 | purecef "github.com/bnema/purego-cef/cef" | 7 | purecef "github.com/bnema/purego-cef/cef" | ||
| 7 | cef2gtk "github.com/bnema/purego-cef2gtk" | 8 | cef2gtk "github.com/bnema/purego-cef2gtk" | ||
| @@ -24,7 +25,7 @@ var ErrAdapterDestroyed = errors.New("cef2gtk_adapter: adapter is destroyed") | |||||
| 24 | // controller implementation — those belong to purego-cef2gtk. | 25 | // controller implementation — those belong to purego-cef2gtk. | ||
| 25 | type Cef2gtkAdapter struct { | 26 | type Cef2gtkAdapter struct { | ||
| 26 | view *cef2gtk.View | 27 | view *cef2gtk.View | ||
| 27 | - | destroyed bool | 28 | + | destroyed atomic.Bool |
| 28 | } | 29 | } | ||
| 29 | 30 | ||||
| 30 | // NewCef2gtkAdapter creates a GtkGLArea-backed accelerated CEF view and wraps | 31 | // NewCef2gtkAdapter creates a GtkGLArea-backed accelerated CEF view and wraps | ||
| @@ -40,7 +41,7 @@ func NewCef2gtkAdapter() *Cef2gtkAdapter { | |||||
| 40 | // Widget returns the native GTK widget for embedding into containers. | 41 | // Widget returns the native GTK widget for embedding into containers. | ||
| 41 | // The returned pointer must be used from the GTK main thread for packing. | 42 | // The returned pointer must be used from the GTK main thread for packing. | ||
| 42 | func (a *Cef2gtkAdapter) Widget() *gtk.Widget { | 43 | func (a *Cef2gtkAdapter) Widget() *gtk.Widget { | ||
| 43 | - | if a == nil || a.view == nil { | 44 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 44 | return nil | 45 | return nil | ||
| 45 | } | 46 | } | ||
| 46 | return a.view.Widget() | 47 | return a.view.Widget() | ||
| @@ -49,7 +50,7 @@ func (a *Cef2gtkAdapter) Widget() *gtk.Widget { | |||||
| 49 | // GLArea returns the underlying GtkGLArea for signal connections (resize, | 50 | // GLArea returns the underlying GtkGLArea for signal connections (resize, | ||
| 50 | // tick, etc.). Signal connection/disconnection must happen on the GTK thread. | 51 | // tick, etc.). Signal connection/disconnection must happen on the GTK thread. | ||
| 51 | func (a *Cef2gtkAdapter) GLArea() *gtk.GLArea { | 52 | func (a *Cef2gtkAdapter) GLArea() *gtk.GLArea { | ||
| 52 | - | if a == nil || a.view == nil { | 53 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 53 | return nil | 54 | return nil | ||
| 54 | } | 55 | } | ||
| 55 | return a.view.GLArea() | 56 | return a.view.GLArea() | ||
| @@ -68,7 +69,7 @@ func (a *Cef2gtkAdapter) NativeWidget() uintptr { | |||||
| 68 | // Size returns the bridge's last observed positive widget size, or 1x1 before | 69 | // Size returns the bridge's last observed positive widget size, or 1x1 before | ||
| 69 | // the GTK widget has been allocated. It is safe to call off the GTK thread. | 70 | // the GTK widget has been allocated. It is safe to call off the GTK thread. | ||
| 70 | func (a *Cef2gtkAdapter) Size() (int32, int32) { | 71 | func (a *Cef2gtkAdapter) Size() (int32, int32) { | ||
| 71 | - | if a == nil || a.view == nil { | 72 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 72 | return 1, 1 | 73 | return 1, 1 | ||
| 73 | } | 74 | } | ||
| 74 | return a.view.Size() | 75 | return a.view.Size() | ||
| @@ -77,7 +78,7 @@ func (a *Cef2gtkAdapter) Size() (int32, int32) { | |||||
| 77 | // DeviceScaleFactor returns the bridge's last observed GTK scale factor, or 1 | 78 | // DeviceScaleFactor returns the bridge's last observed GTK scale factor, or 1 | ||
| 78 | // before the widget has reported a scale. It is safe to call off the GTK thread. | 79 | // before the widget has reported a scale. It is safe to call off the GTK thread. | ||
| 79 | func (a *Cef2gtkAdapter) DeviceScaleFactor() float32 { | 80 | func (a *Cef2gtkAdapter) DeviceScaleFactor() float32 { | ||
| 80 | - | if a == nil || a.view == nil { | 81 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 81 | return 1 | 82 | return 1 | ||
| 82 | } | 83 | } | ||
| 83 | return a.view.DeviceScaleFactor() | 84 | return a.view.DeviceScaleFactor() | ||
| @@ -87,7 +88,7 @@ func (a *Cef2gtkAdapter) DeviceScaleFactor() float32 { | |||||
| 87 | // unregister from the GTK main thread; callbacks are invoked by the bridge from | 88 | // unregister from the GTK main thread; callbacks are invoked by the bridge from | ||
| 88 | // GTK size notifications. | 89 | // GTK size notifications. | ||
| 89 | func (a *Cef2gtkAdapter) AddSizeObserver(fn func(width, height int32)) func() { | 90 | func (a *Cef2gtkAdapter) AddSizeObserver(fn func(width, height int32)) func() { | ||
| 90 | - | if a == nil || a.view == nil { | 91 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 91 | return func() {} | 92 | return func() {} | ||
| 92 | } | 93 | } | ||
| 93 | return a.view.AddSizeObserver(fn) | 94 | return a.view.AddSizeObserver(fn) | ||
| @@ -96,7 +97,7 @@ func (a *Cef2gtkAdapter) AddSizeObserver(fn func(width, height int32)) func() { | |||||
| 96 | // HasFocus reports whether the bridge widget currently has GTK focus. Must be | 97 | // HasFocus reports whether the bridge widget currently has GTK focus. Must be | ||
| 97 | // called on the GTK main thread. | 98 | // called on the GTK main thread. | ||
| 98 | func (a *Cef2gtkAdapter) HasFocus() bool { | 99 | func (a *Cef2gtkAdapter) HasFocus() bool { | ||
| 99 | - | if a == nil || a.view == nil { | 100 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 100 | return false | 101 | return false | ||
| 101 | } | 102 | } | ||
| 102 | return a.view.HasFocus() | 103 | return a.view.HasFocus() | ||
| @@ -105,7 +106,7 @@ func (a *Cef2gtkAdapter) HasFocus() bool { | |||||
| 105 | // SetCursorFromName applies a named cursor to the bridge widget. Must be called | 106 | // SetCursorFromName applies a named cursor to the bridge widget. Must be called | ||
| 106 | // on the GTK main thread. | 107 | // on the GTK main thread. | ||
| 107 | func (a *Cef2gtkAdapter) SetCursorFromName(name string) { | 108 | func (a *Cef2gtkAdapter) SetCursorFromName(name string) { | ||
| 108 | - | if a == nil || a.view == nil { | 109 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 109 | return | 110 | return | ||
| 110 | } | 111 | } | ||
| 111 | a.view.SetCursorFromName(name) | 112 | a.view.SetCursorFromName(name) | ||
| @@ -114,7 +115,7 @@ func (a *Cef2gtkAdapter) SetCursorFromName(name string) { | |||||
| 114 | // PrepareOnGTKThread initializes GL/EGL resources owned by purego-cef2gtk. | 115 | // PrepareOnGTKThread initializes GL/EGL resources owned by purego-cef2gtk. | ||
| 115 | // Must be called on the GTK main thread. | 116 | // Must be called on the GTK main thread. | ||
| 116 | func (a *Cef2gtkAdapter) PrepareOnGTKThread() error { | 117 | func (a *Cef2gtkAdapter) PrepareOnGTKThread() error { | ||
| 117 | - | if a == nil || a.view == nil { | 118 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 118 | return ErrAdapterDestroyed | 119 | return ErrAdapterDestroyed | ||
| 119 | } | 120 | } | ||
| 120 | return a.view.PrepareOnGTKThread() | 121 | return a.view.PrepareOnGTKThread() | ||
| @@ -126,7 +127,7 @@ func (a *Cef2gtkAdapter) PrepareOnGTKThread() error { | |||||
| 126 | // | 127 | // | ||
| 127 | // hooks may be a zero-value cef2gtk.Hooks if no custom callbacks are needed. | 128 | // hooks may be a zero-value cef2gtk.Hooks if no custom callbacks are needed. | ||
| 128 | func (a *Cef2gtkAdapter) RenderHandler(hooks cef2gtk.Hooks) purecef.RenderHandler { | 129 | func (a *Cef2gtkAdapter) RenderHandler(hooks cef2gtk.Hooks) purecef.RenderHandler { | ||
| 129 | - | if a == nil || a.view == nil { | 130 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 130 | return nil | 131 | return nil | ||
| 131 | } | 132 | } | ||
| 132 | return a.view.RenderHandler(hooks) | 133 | return a.view.RenderHandler(hooks) | ||
| @@ -137,7 +138,7 @@ func (a *Cef2gtkAdapter) RenderHandler(hooks cef2gtk.Hooks) purecef.RenderHandle | |||||
| 137 | // | 138 | // | ||
| 138 | // opts.Scale configures HiDPI scale for pointer coordinate translation. | 139 | // opts.Scale configures HiDPI scale for pointer coordinate translation. | ||
| 139 | func (a *Cef2gtkAdapter) AttachInput(host purecef.BrowserHost, opts cef2gtk.InputOptions) error { | 140 | func (a *Cef2gtkAdapter) AttachInput(host purecef.BrowserHost, opts cef2gtk.InputOptions) error { | ||
| 140 | - | if a == nil || a.view == nil { | 141 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 141 | return ErrAdapterDestroyed | 142 | return ErrAdapterDestroyed | ||
| 142 | } | 143 | } | ||
| 143 | return a.view.AttachInput(host, opts) | 144 | return a.view.AttachInput(host, opts) | ||
| @@ -147,7 +148,7 @@ func (a *Cef2gtkAdapter) AttachInput(host purecef.BrowserHost, opts cef2gtk.Inpu | |||||
| 147 | // Must be called on the GTK main thread. Callers must call AttachInput first or | 148 | // Must be called on the GTK main thread. Callers must call AttachInput first or | ||
| 148 | // this returns an error from the underlying bridge. | 149 | // this returns an error from the underlying bridge. | ||
| 149 | func (a *Cef2gtkAdapter) SetInputHost(host purecef.BrowserHost) error { | 150 | func (a *Cef2gtkAdapter) SetInputHost(host purecef.BrowserHost) error { | ||
| 150 | - | if a == nil || a.view == nil { | 151 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 151 | return ErrAdapterDestroyed | 152 | return ErrAdapterDestroyed | ||
| 152 | } | 153 | } | ||
| 153 | return a.view.SetInputHost(host) | 154 | return a.view.SetInputHost(host) | ||
| @@ -156,7 +157,7 @@ func (a *Cef2gtkAdapter) SetInputHost(host purecef.BrowserHost) error { | |||||
| 156 | // DetachInput removes GTK input controllers attached by AttachInput. | 157 | // DetachInput removes GTK input controllers attached by AttachInput. | ||
| 157 | // Must be called on the GTK main thread. | 158 | // Must be called on the GTK main thread. | ||
| 158 | func (a *Cef2gtkAdapter) DetachInput() error { | 159 | func (a *Cef2gtkAdapter) DetachInput() error { | ||
| 159 | - | if a == nil || a.view == nil { | 160 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 160 | return ErrAdapterDestroyed | 161 | return ErrAdapterDestroyed | ||
| 161 | } | 162 | } | ||
| 162 | return a.view.DetachInput() | 163 | return a.view.DetachInput() | ||
| @@ -165,7 +166,7 @@ func (a *Cef2gtkAdapter) DetachInput() error { | |||||
| 165 | // Diagnostics returns a point-in-time snapshot of bridge rendering diagnostics | 166 | // Diagnostics returns a point-in-time snapshot of bridge rendering diagnostics | ||
| 166 | // including accelerated paint counts, import/render failures, and events. | 167 | // including accelerated paint counts, import/render failures, and events. | ||
| 167 | func (a *Cef2gtkAdapter) Diagnostics() cef2gtk.Diagnostics { | 168 | func (a *Cef2gtkAdapter) Diagnostics() cef2gtk.Diagnostics { | ||
| 168 | - | if a == nil || a.view == nil { | 169 | + | if a == nil || a.destroyed.Load() || a.view == nil { |
| 169 | return cef2gtk.Diagnostics{} | 170 | return cef2gtk.Diagnostics{} | ||
| 170 | } | 171 | } | ||
| 171 | return a.view.Diagnostics() | 172 | return a.view.Diagnostics() | ||
| @@ -181,16 +182,16 @@ func (a *Cef2gtkAdapter) Destroy() error { | |||||
| 181 | return ErrAdapterDestroyed | 182 | return ErrAdapterDestroyed | ||
| 182 | } | 183 | } | ||
| 183 | if a.view == nil { | 184 | if a.view == nil { | ||
| 184 | - | a.destroyed = true | 185 | + | a.destroyed.Store(true) |
| 185 | return ErrAdapterDestroyed | 186 | return ErrAdapterDestroyed | ||
| 186 | } | 187 | } | ||
| 187 | err := a.view.Destroy() | 188 | err := a.view.Destroy() | ||
| 188 | a.view = nil | 189 | a.view = nil | ||
| 189 | - | a.destroyed = true | 190 | + | a.destroyed.Store(true) |
| 190 | return err | 191 | return err | ||
| 191 | } | 192 | } | ||
| 192 | 193 | ||||
| 193 | // IsDestroyed returns true if the adapter has been destroyed. | 194 | // IsDestroyed returns true if the adapter has been destroyed. | ||
| 194 | func (a *Cef2gtkAdapter) IsDestroyed() bool { | 195 | func (a *Cef2gtkAdapter) IsDestroyed() bool { | ||
| 195 | - | return a == nil || a.destroyed | 196 | + | return a == nil || a.destroyed.Load() |
| 196 | } | 197 | } | ||
M internal/infrastructure/cef/cef2gtk_boundary_test.go +39 -6
| @@ -1,14 +1,18 @@ | |||
| 1 | 1 | package cef | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "go/parser" | |
| 5 | + | "go/token" | |
| 4 | 6 | "os" | |
| 5 | 7 | "path/filepath" | |
| 8 | + | "runtime" | |
| 9 | + | "strconv" | |
| 6 | 10 | "strings" | |
| 7 | 11 | "testing" | |
| 8 | 12 | ) | |
| 9 | 13 | ||
| 10 | 14 | func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | |
| 11 | - | root := filepath.Clean("../../..") | |
| 15 | + | root := repoRoot(t) | |
| 12 | 16 | if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { | |
| 13 | 17 | if err != nil { | |
| 14 | 18 | return err | |
| @@ -23,11 +27,7 @@ func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | |||
| 23 | 27 | if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") { | |
| 24 | 28 | return nil | |
| 25 | 29 | } | |
| 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") { | |
| 30 | + | if !importsCEF2GTK(path) { | |
| 31 | 31 | return nil | |
| 32 | 32 | } | |
| 33 | 33 | rel, err := filepath.Rel(root, path) | |
| @@ -43,3 +43,36 @@ func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | |||
| 43 | 43 | t.Fatalf("scan imports: %v", err) | |
| 44 | 44 | } | |
| 45 | 45 | } | |
| 46 | + | ||
| 47 | + | func importsCEF2GTK(path string) bool { | |
| 48 | + | file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly) | |
| 49 | + | if err != nil { | |
| 50 | + | return false | |
| 51 | + | } | |
| 52 | + | for _, spec := range file.Imports { | |
| 53 | + | importPath, err := strconv.Unquote(spec.Path.Value) | |
| 54 | + | if err == nil && importPath == "github.com/bnema/purego-cef2gtk" { | |
| 55 | + | return true | |
| 56 | + | } | |
| 57 | + | } | |
| 58 | + | return false | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | func repoRoot(t *testing.T) string { | |
| 62 | + | t.Helper() | |
| 63 | + | _, filename, _, ok := runtime.Caller(0) | |
| 64 | + | if !ok { | |
| 65 | + | t.Fatal("resolve caller path") | |
| 66 | + | } | |
| 67 | + | dir := filepath.Dir(filename) | |
| 68 | + | for { | |
| 69 | + | if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { | |
| 70 | + | return dir | |
| 71 | + | } | |
| 72 | + | parent := filepath.Dir(dir) | |
| 73 | + | if parent == dir { | |
| 74 | + | t.Fatalf("go.mod not found from %s", filename) | |
| 75 | + | } | |
| 76 | + | dir = parent | |
| 77 | + | } | |
| 78 | + | } | |
| @@ -1,14 +1,18 @@ | |||||
| 1 | package cef | 1 | package cef | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "go/parser" | |||
| 5 | + | "go/token" | |||
| 4 | "os" | 6 | "os" | ||
| 5 | "path/filepath" | 7 | "path/filepath" | ||
| 8 | + | "runtime" | |||
| 9 | + | "strconv" | |||
| 6 | "strings" | 10 | "strings" | ||
| 7 | "testing" | 11 | "testing" | ||
| 8 | ) | 12 | ) | ||
| 9 | 13 | ||||
| 10 | func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | 14 | func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | ||
| 11 | - | root := filepath.Clean("../../..") | 15 | + | root := repoRoot(t) |
| 12 | if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { | 16 | if err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { | ||
| 13 | if err != nil { | 17 | if err != nil { | ||
| 14 | return err | 18 | return err | ||
| @@ -23,11 +27,7 @@ func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | |||||
| 23 | if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") { | 27 | if filepath.Ext(path) != ".go" || strings.HasSuffix(path, "_test.go") { | ||
| 24 | return nil | 28 | return nil | ||
| 25 | } | 29 | } | ||
| 26 | - | body, err := os.ReadFile(path) | 30 | + | if !importsCEF2GTK(path) { |
| 27 | - | if err != nil { | |||
| 28 | - | return err | |||
| 29 | - | } | |||
| 30 | - | if !strings.Contains(string(body), "github.com/bnema/purego-cef2gtk") { | |||
| 31 | return nil | 31 | return nil | ||
| 32 | } | 32 | } | ||
| 33 | rel, err := filepath.Rel(root, path) | 33 | rel, err := filepath.Rel(root, path) | ||
| @@ -43,3 +43,36 @@ func TestCEF2GTKImportStaysInInfrastructureCEF(t *testing.T) { | |||||
| 43 | t.Fatalf("scan imports: %v", err) | 43 | t.Fatalf("scan imports: %v", err) | ||
| 44 | } | 44 | } | ||
| 45 | } | 45 | } | ||
| 46 | + | ||||
| 47 | + | func importsCEF2GTK(path string) bool { | |||
| 48 | + | file, err := parser.ParseFile(token.NewFileSet(), path, nil, parser.ImportsOnly) | |||
| 49 | + | if err != nil { | |||
| 50 | + | return false | |||
| 51 | + | } | |||
| 52 | + | for _, spec := range file.Imports { | |||
| 53 | + | importPath, err := strconv.Unquote(spec.Path.Value) | |||
| 54 | + | if err == nil && importPath == "github.com/bnema/purego-cef2gtk" { | |||
| 55 | + | return true | |||
| 56 | + | } | |||
| 57 | + | } | |||
| 58 | + | return false | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | func repoRoot(t *testing.T) string { | |||
| 62 | + | t.Helper() | |||
| 63 | + | _, filename, _, ok := runtime.Caller(0) | |||
| 64 | + | if !ok { | |||
| 65 | + | t.Fatal("resolve caller path") | |||
| 66 | + | } | |||
| 67 | + | dir := filepath.Dir(filename) | |||
| 68 | + | for { | |||
| 69 | + | if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { | |||
| 70 | + | return dir | |||
| 71 | + | } | |||
| 72 | + | parent := filepath.Dir(dir) | |||
| 73 | + | if parent == dir { | |||
| 74 | + | t.Fatalf("go.mod not found from %s", filename) | |||
| 75 | + | } | |||
| 76 | + | dir = parent | |||
| 77 | + | } | |||
| 78 | + | } | |||
M internal/infrastructure/cef/factory.go +22 -10
| @@ -74,7 +74,10 @@ func (f *WebViewFactory) setDefaultBackgroundColor(r, g, b, a float64) { | |||
| 74 | 74 | // asynchronously; the returned WebView is usable immediately but navigation | |
| 75 | 75 | // will fail with errNoBrowser until OnAfterCreated fires. | |
| 76 | 76 | func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |
| 77 | - | wv := f.newWebView(ctx) | |
| 77 | + | wv, err := f.newWebView(ctx) | |
| 78 | + | if err != nil { | |
| 79 | + | return nil, err | |
| 80 | + | } | |
| 78 | 81 | ||
| 79 | 82 | // Delegate windowless/shared-texture setup to the GTK bridge. | |
| 80 | 83 | windowInfo := purecef.NewWindowInfo() | |
| @@ -99,9 +102,12 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||
| 99 | 102 | return wv, nil | |
| 100 | 103 | } | |
| 101 | 104 | ||
| 102 | - | func (f *WebViewFactory) newWebView(ctx context.Context) *WebView { | |
| 105 | + | func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { | |
| 103 | 106 | id := port.WebViewID(f.nextID.Add(1)) | |
| 104 | 107 | viewBridge := NewCef2gtkAdapter() | |
| 108 | + | if viewBridge == nil { | |
| 109 | + | return nil, fmt.Errorf("create cef2gtk view") | |
| 110 | + | } | |
| 105 | 111 | ||
| 106 | 112 | wv := &WebView{ | |
| 107 | 113 | id: id, | |
| @@ -117,18 +123,21 @@ func (f *WebViewFactory) newWebView(ctx context.Context) *WebView { | |||
| 117 | 123 | wv.handlers = handlers | |
| 118 | 124 | wv.findCtrl = newFindController() | |
| 119 | 125 | ||
| 120 | - | if viewBridge != nil { | |
| 121 | - | wv.runOnGTKSync(func() { | |
| 122 | - | if err := viewBridge.PrepareOnGTKThread(); err != nil && ctx != nil { | |
| 123 | - | logging.FromContext(ctx).Warn().Err(err).Uint64("webview_id", uint64(id)).Msg("cef: failed to prepare cef2gtk view") | |
| 124 | - | } | |
| 125 | - | }) | |
| 126 | + | var prepareErr error | |
| 127 | + | wv.runOnGTKSync(func() { | |
| 128 | + | prepareErr = viewBridge.PrepareOnGTKThread() | |
| 129 | + | }) | |
| 130 | + | if prepareErr != nil { | |
| 131 | + | if ctx != nil { | |
| 132 | + | logging.FromContext(ctx).Error().Err(prepareErr).Uint64("webview_id", uint64(id)).Msg("cef: failed to prepare cef2gtk view") | |
| 133 | + | } | |
| 134 | + | return nil, fmt.Errorf("prepare cef2gtk view: %w", prepareErr) | |
| 126 | 135 | } | |
| 127 | 136 | ||
| 128 | 137 | // Build a CEF client backed by our handlerSet. | |
| 129 | 138 | // Store on WebView to prevent GC collection before CEF AddRef's it. | |
| 130 | 139 | wv.client = purecef.NewClient(wv.handlers) | |
| 131 | - | return wv | |
| 140 | + | return wv, nil | |
| 132 | 141 | } | |
| 133 | 142 | ||
| 134 | 143 | func (f *WebViewFactory) configureInitialBrowserCreation( | |
| @@ -292,7 +301,10 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||
| 292 | 301 | return nil, fmt.Errorf("parent webview %d is destroyed", parentID) | |
| 293 | 302 | } | |
| 294 | 303 | ||
| 295 | - | popupWV := f.newWebView(ctx) | |
| 304 | + | popupWV, err := f.newWebView(ctx) | |
| 305 | + | if err != nil { | |
| 306 | + | return nil, err | |
| 307 | + | } | |
| 296 | 308 | popupWV.markNativePopupCandidate(parent) | |
| 297 | 309 | ||
| 298 | 310 | windowInfo := purecef.NewWindowInfo() | |
| @@ -74,7 +74,10 @@ func (f *WebViewFactory) setDefaultBackgroundColor(r, g, b, a float64) { | |||||
| 74 | // asynchronously; the returned WebView is usable immediately but navigation | 74 | // asynchronously; the returned WebView is usable immediately but navigation | ||
| 75 | // will fail with errNoBrowser until OnAfterCreated fires. | 75 | // will fail with errNoBrowser until OnAfterCreated fires. | ||
| 76 | func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | 76 | func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | ||
| 77 | - | wv := f.newWebView(ctx) | 77 | + | wv, err := f.newWebView(ctx) |
| 78 | + | if err != nil { | |||
| 79 | + | return nil, err | |||
| 80 | + | } | |||
| 78 | 81 | ||||
| 79 | // Delegate windowless/shared-texture setup to the GTK bridge. | 82 | // Delegate windowless/shared-texture setup to the GTK bridge. | ||
| 80 | windowInfo := purecef.NewWindowInfo() | 83 | windowInfo := purecef.NewWindowInfo() | ||
| @@ -99,9 +102,12 @@ func (f *WebViewFactory) Create(ctx context.Context) (port.WebView, error) { | |||||
| 99 | return wv, nil | 102 | return wv, nil | ||
| 100 | } | 103 | } | ||
| 101 | 104 | ||||
| 102 | - | func (f *WebViewFactory) newWebView(ctx context.Context) *WebView { | 105 | + | func (f *WebViewFactory) newWebView(ctx context.Context) (*WebView, error) { |
| 103 | id := port.WebViewID(f.nextID.Add(1)) | 106 | id := port.WebViewID(f.nextID.Add(1)) | ||
| 104 | viewBridge := NewCef2gtkAdapter() | 107 | viewBridge := NewCef2gtkAdapter() | ||
| 108 | + | if viewBridge == nil { | |||
| 109 | + | return nil, fmt.Errorf("create cef2gtk view") | |||
| 110 | + | } | |||
| 105 | 111 | ||||
| 106 | wv := &WebView{ | 112 | wv := &WebView{ | ||
| 107 | id: id, | 113 | id: id, | ||
| @@ -117,18 +123,21 @@ func (f *WebViewFactory) newWebView(ctx context.Context) *WebView { | |||||
| 117 | wv.handlers = handlers | 123 | wv.handlers = handlers | ||
| 118 | wv.findCtrl = newFindController() | 124 | wv.findCtrl = newFindController() | ||
| 119 | 125 | ||||
| 120 | - | if viewBridge != nil { | 126 | + | var prepareErr error |
| 121 | - | wv.runOnGTKSync(func() { | 127 | + | wv.runOnGTKSync(func() { |
| 122 | - | if err := viewBridge.PrepareOnGTKThread(); err != nil && ctx != nil { | 128 | + | prepareErr = viewBridge.PrepareOnGTKThread() |
| 123 | - | logging.FromContext(ctx).Warn().Err(err).Uint64("webview_id", uint64(id)).Msg("cef: failed to prepare cef2gtk view") | 129 | + | }) |
| 124 | - | } | 130 | + | if prepareErr != nil { |
| 125 | - | }) | 131 | + | if ctx != nil { |
| 132 | + | logging.FromContext(ctx).Error().Err(prepareErr).Uint64("webview_id", uint64(id)).Msg("cef: failed to prepare cef2gtk view") | |||
| 133 | + | } | |||
| 134 | + | return nil, fmt.Errorf("prepare cef2gtk view: %w", prepareErr) | |||
| 126 | } | 135 | } | ||
| 127 | 136 | ||||
| 128 | // Build a CEF client backed by our handlerSet. | 137 | // Build a CEF client backed by our handlerSet. | ||
| 129 | // Store on WebView to prevent GC collection before CEF AddRef's it. | 138 | // Store on WebView to prevent GC collection before CEF AddRef's it. | ||
| 130 | wv.client = purecef.NewClient(wv.handlers) | 139 | wv.client = purecef.NewClient(wv.handlers) | ||
| 131 | - | return wv | 140 | + | return wv, nil |
| 132 | } | 141 | } | ||
| 133 | 142 | ||||
| 134 | func (f *WebViewFactory) configureInitialBrowserCreation( | 143 | func (f *WebViewFactory) configureInitialBrowserCreation( | ||
| @@ -292,7 +301,10 @@ func (f *WebViewFactory) CreateRelated(ctx context.Context, parentID port.WebVie | |||||
| 292 | return nil, fmt.Errorf("parent webview %d is destroyed", parentID) | 301 | return nil, fmt.Errorf("parent webview %d is destroyed", parentID) | ||
| 293 | } | 302 | } | ||
| 294 | 303 | ||||
| 295 | - | popupWV := f.newWebView(ctx) | 304 | + | popupWV, err := f.newWebView(ctx) |
| 305 | + | if err != nil { | |||
| 306 | + | return nil, err | |||
| 307 | + | } | |||
| 296 | popupWV.markNativePopupCandidate(parent) | 308 | popupWV.markNativePopupCandidate(parent) | ||
| 297 | 309 | ||||
| 298 | windowInfo := purecef.NewWindowInfo() | 310 | windowInfo := purecef.NewWindowInfo() | ||
M internal/infrastructure/cef/webview_handlers.go +9 -4
| @@ -548,7 +548,7 @@ func (h *handlerSet) attachAfterCreatedBrowser( | |||
| 548 | 548 | if wv.viewBridge == nil { | |
| 549 | 549 | return | |
| 550 | 550 | } | |
| 551 | - | _ = wv.viewBridge.AttachInput(host, cef2gtk.InputOptions{ | |
| 551 | + | if err := wv.viewBridge.AttachInput(host, cef2gtk.InputOptions{ | |
| 552 | 552 | Scale: wv.viewBridgeScale(), | |
| 553 | 553 | OnMiddleClick: func(_, _ float64) bool { | |
| 554 | 554 | return wv.handleMiddleClickFromBridge() | |
| @@ -559,7 +559,9 @@ func (h *handlerSet) attachAfterCreatedBrowser( | |||
| 559 | 559 | wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text) | |
| 560 | 560 | } | |
| 561 | 561 | }, | |
| 562 | - | }) | |
| 562 | + | }); err != nil && wv.ctx != nil { | |
| 563 | + | logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: failed to attach input to cef2gtk bridge") | |
| 564 | + | } | |
| 563 | 565 | }) | |
| 564 | 566 | } | |
| 565 | 567 | ||
| @@ -643,8 +645,11 @@ func (h *handlerSet) OnBeforeClose(browser purecef.Browser) { | |||
| 643 | 645 | if h.wv.viewBridge != nil { | |
| 644 | 646 | wv := h.wv | |
| 645 | 647 | wv.runOnGTK(func() { | |
| 646 | - | if wv.viewBridge != nil { | |
| 647 | - | _ = wv.viewBridge.SetInputHost(nil) | |
| 648 | + | if wv.viewBridge == nil { | |
| 649 | + | return | |
| 650 | + | } | |
| 651 | + | if err := wv.viewBridge.SetInputHost(nil); err != nil && wv.ctx != nil { | |
| 652 | + | logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: failed to clear cef2gtk input host") | |
| 648 | 653 | } | |
| 649 | 654 | }) | |
| 650 | 655 | } | |
| @@ -548,7 +548,7 @@ func (h *handlerSet) attachAfterCreatedBrowser( | |||||
| 548 | if wv.viewBridge == nil { | 548 | if wv.viewBridge == nil { | ||
| 549 | return | 549 | return | ||
| 550 | } | 550 | } | ||
| 551 | - | _ = wv.viewBridge.AttachInput(host, cef2gtk.InputOptions{ | 551 | + | if err := wv.viewBridge.AttachInput(host, cef2gtk.InputOptions{ |
| 552 | Scale: wv.viewBridgeScale(), | 552 | Scale: wv.viewBridgeScale(), | ||
| 553 | OnMiddleClick: func(_, _ float64) bool { | 553 | OnMiddleClick: func(_, _ float64) bool { | ||
| 554 | return wv.handleMiddleClickFromBridge() | 554 | return wv.handleMiddleClickFromBridge() | ||
| @@ -559,7 +559,9 @@ func (h *handlerSet) attachAfterCreatedBrowser( | |||||
| 559 | wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text) | 559 | wv.engine.handleExplicitClipboardBridgeText(wv.id, action, text) | ||
| 560 | } | 560 | } | ||
| 561 | }, | 561 | }, | ||
| 562 | - | }) | 562 | + | }); err != nil && wv.ctx != nil { |
| 563 | + | logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: failed to attach input to cef2gtk bridge") | |||
| 564 | + | } | |||
| 563 | }) | 565 | }) | ||
| 564 | } | 566 | } | ||
| 565 | 567 | ||||
| @@ -643,8 +645,11 @@ func (h *handlerSet) OnBeforeClose(browser purecef.Browser) { | |||||
| 643 | if h.wv.viewBridge != nil { | 645 | if h.wv.viewBridge != nil { | ||
| 644 | wv := h.wv | 646 | wv := h.wv | ||
| 645 | wv.runOnGTK(func() { | 647 | wv.runOnGTK(func() { | ||
| 646 | - | if wv.viewBridge != nil { | 648 | + | if wv.viewBridge == nil { |
| 647 | - | _ = wv.viewBridge.SetInputHost(nil) | 649 | + | return |
| 650 | + | } | |||
| 651 | + | if err := wv.viewBridge.SetInputHost(nil); err != nil && wv.ctx != nil { | |||
| 652 | + | logging.FromContext(wv.ctx).Warn().Err(err).Msg("cef: failed to clear cef2gtk input host") | |||
| 648 | } | 653 | } | ||
| 649 | }) | 654 | }) | ||
| 650 | } | 655 | } | ||