fix(ticks): release raw callback slots
- commit
- 6c6c1d8593ee32e5a84a52dd65024f15ba11c568
- author
- brice · · 2026-07-14 06:23 UTC
- committer
- brice · · 2026-07-14 07:06 UTC
- parent
- d80cc8e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +40 -16
M internal/infrastructure/cef/webview.go +16 -6
| @@ -2071,12 +2071,14 @@ func (wv *WebView) startBeginFrameLoop() { | |||
| 2071 | 2071 | cb := new(gtk.TickCallback) | |
| 2072 | 2072 | *cb = func(_, _, _ uintptr) bool { | |
| 2073 | 2073 | if wv.destroyed.Load() { | |
| 2074 | + | wv.releaseBeginFrameTickCallback() | |
| 2074 | 2075 | return false | |
| 2075 | 2076 | } | |
| 2076 | 2077 | wv.mu.RLock() | |
| 2077 | 2078 | host := wv.host | |
| 2078 | 2079 | wv.mu.RUnlock() | |
| 2079 | 2080 | if host == nil { | |
| 2081 | + | wv.releaseBeginFrameTickCallback() | |
| 2080 | 2082 | return false | |
| 2081 | 2083 | } | |
| 2082 | 2084 | host.SendExternalBeginFrame() | |
| @@ -2099,22 +2101,30 @@ func (wv *WebView) startBeginFrameLoop() { | |||
| 2099 | 2101 | } | |
| 2100 | 2102 | ||
| 2101 | 2103 | func (wv *WebView) stopBeginFrameLoop() { | |
| 2102 | - | if wv.viewBridge == nil || wv.viewBridge.Widget() == nil { | |
| 2103 | - | return | |
| 2104 | - | } | |
| 2105 | - | ||
| 2106 | 2104 | wv.mu.Lock() | |
| 2107 | 2105 | tickID := wv.beginFrameTickID | |
| 2108 | 2106 | wv.beginFrameTickID = 0 | |
| 2109 | 2107 | wv.beginFrameTick = nil | |
| 2110 | - | widget := wv.viewBridge.Widget() | |
| 2108 | + | bridge := wv.viewBridge | |
| 2111 | 2109 | wv.mu.Unlock() | |
| 2112 | 2110 | ||
| 2113 | - | if tickID != 0 { | |
| 2111 | + | if tickID == 0 || bridge == nil { | |
| 2112 | + | return | |
| 2113 | + | } | |
| 2114 | + | if widget := bridge.Widget(); widget != nil { | |
| 2114 | 2115 | widget.RemoveTickCallback(tickID) | |
| 2115 | 2116 | } | |
| 2116 | 2117 | } | |
| 2117 | 2118 | ||
| 2119 | + | // releaseBeginFrameTickCallback releases the Go callback reference after GTK | |
| 2120 | + | // removes a source because its callback returned false. | |
| 2121 | + | func (wv *WebView) releaseBeginFrameTickCallback() { | |
| 2122 | + | wv.mu.Lock() | |
| 2123 | + | wv.beginFrameTickID = 0 | |
| 2124 | + | wv.beginFrameTick = nil | |
| 2125 | + | wv.mu.Unlock() | |
| 2126 | + | } | |
| 2127 | + | ||
| 2118 | 2128 | func (wv *WebView) runOnGTK(fn func()) { | |
| 2119 | 2129 | if fn == nil { | |
| 2120 | 2130 | return | |
| @@ -2071,12 +2071,14 @@ func (wv *WebView) startBeginFrameLoop() { | |||||
| 2071 | cb := new(gtk.TickCallback) | 2071 | cb := new(gtk.TickCallback) | ||
| 2072 | *cb = func(_, _, _ uintptr) bool { | 2072 | *cb = func(_, _, _ uintptr) bool { | ||
| 2073 | if wv.destroyed.Load() { | 2073 | if wv.destroyed.Load() { | ||
| 2074 | + | wv.releaseBeginFrameTickCallback() | |||
| 2074 | return false | 2075 | return false | ||
| 2075 | } | 2076 | } | ||
| 2076 | wv.mu.RLock() | 2077 | wv.mu.RLock() | ||
| 2077 | host := wv.host | 2078 | host := wv.host | ||
| 2078 | wv.mu.RUnlock() | 2079 | wv.mu.RUnlock() | ||
| 2079 | if host == nil { | 2080 | if host == nil { | ||
| 2081 | + | wv.releaseBeginFrameTickCallback() | |||
| 2080 | return false | 2082 | return false | ||
| 2081 | } | 2083 | } | ||
| 2082 | host.SendExternalBeginFrame() | 2084 | host.SendExternalBeginFrame() | ||
| @@ -2099,22 +2101,30 @@ func (wv *WebView) startBeginFrameLoop() { | |||||
| 2099 | } | 2101 | } | ||
| 2100 | 2102 | ||||
| 2101 | func (wv *WebView) stopBeginFrameLoop() { | 2103 | func (wv *WebView) stopBeginFrameLoop() { | ||
| 2102 | - | if wv.viewBridge == nil || wv.viewBridge.Widget() == nil { | |||
| 2103 | - | return | |||
| 2104 | - | } | |||
| 2105 | - | ||||
| 2106 | wv.mu.Lock() | 2104 | wv.mu.Lock() | ||
| 2107 | tickID := wv.beginFrameTickID | 2105 | tickID := wv.beginFrameTickID | ||
| 2108 | wv.beginFrameTickID = 0 | 2106 | wv.beginFrameTickID = 0 | ||
| 2109 | wv.beginFrameTick = nil | 2107 | wv.beginFrameTick = nil | ||
| 2110 | - | widget := wv.viewBridge.Widget() | 2108 | + | bridge := wv.viewBridge |
| 2111 | wv.mu.Unlock() | 2109 | wv.mu.Unlock() | ||
| 2112 | 2110 | ||||
| 2113 | - | if tickID != 0 { | 2111 | + | if tickID == 0 || bridge == nil { |
| 2112 | + | return | |||
| 2113 | + | } | |||
| 2114 | + | if widget := bridge.Widget(); widget != nil { | |||
| 2114 | widget.RemoveTickCallback(tickID) | 2115 | widget.RemoveTickCallback(tickID) | ||
| 2115 | } | 2116 | } | ||
| 2116 | } | 2117 | } | ||
| 2117 | 2118 | ||||
| 2119 | + | // releaseBeginFrameTickCallback releases the Go callback reference after GTK | |||
| 2120 | + | // removes a source because its callback returned false. | |||
| 2121 | + | func (wv *WebView) releaseBeginFrameTickCallback() { | |||
| 2122 | + | wv.mu.Lock() | |||
| 2123 | + | wv.beginFrameTickID = 0 | |||
| 2124 | + | wv.beginFrameTick = nil | |||
| 2125 | + | wv.mu.Unlock() | |||
| 2126 | + | } | |||
| 2127 | + | ||||
| 2118 | func (wv *WebView) runOnGTK(fn func()) { | 2128 | func (wv *WebView) runOnGTK(fn func()) { | ||
| 2119 | if fn == nil { | 2129 | if fn == nil { | ||
| 2120 | return | 2130 | return | ||
M internal/ui/app.go +24 -10
| @@ -167,7 +167,10 @@ type floatingWorkspaceSession struct { | |||
| 167 | 167 | omnibox *component.Omnibox | |
| 168 | 168 | omniboxWidget layout.Widget | |
| 169 | 169 | resizeWatcherActive bool | |
| 170 | - | resizeTickID uint | |
| 170 | + | // resizeTickCallback keeps the purego callback live until its GTK source is | |
| 171 | + | // removed. It is cleared exactly once with resizeTickID. | |
| 172 | + | resizeTickCallback *gtk.TickCallback | |
| 173 | + | resizeTickID uint | |
| 171 | 174 | appliedWidth int | |
| 172 | 175 | appliedHeight int | |
| 173 | 176 | } | |
| @@ -4594,22 +4597,22 @@ func (a *App) startFloatingResizeWatcher(session *floatingWorkspaceSession) { | |||
| 4594 | 4597 | ||
| 4595 | 4598 | session.resizeWatcherActive = true | |
| 4596 | 4599 | paneID := session.paneID | |
| 4597 | - | tickCallback := gtk.TickCallback(func(_ uintptr, _ uintptr, _ uintptr) bool { | |
| 4600 | + | callback := new(gtk.TickCallback) | |
| 4601 | + | *callback = func(_ uintptr, _ uintptr, _ uintptr) bool { | |
| 4598 | 4602 | liveSession := a.floatingSessionByPaneID(paneID) | |
| 4599 | 4603 | if liveSession == nil { | |
| 4600 | - | session.resizeWatcherActive = false | |
| 4601 | - | session.resizeTickID = 0 | |
| 4604 | + | session.releaseResizeTickCallback() | |
| 4602 | 4605 | return false | |
| 4603 | 4606 | } | |
| 4604 | 4607 | ||
| 4605 | 4608 | keepRunning := a.handleFloatingViewportTick(liveSession) | |
| 4606 | 4609 | if !keepRunning { | |
| 4607 | - | liveSession.resizeWatcherActive = false | |
| 4608 | - | liveSession.resizeTickID = 0 | |
| 4610 | + | liveSession.releaseResizeTickCallback() | |
| 4609 | 4611 | } | |
| 4610 | 4612 | return keepRunning | |
| 4611 | - | }) | |
| 4612 | - | session.resizeTickID = overlayWidget.AddTickCallback(&tickCallback, 0, nil) | |
| 4613 | + | } | |
| 4614 | + | session.resizeTickCallback = callback | |
| 4615 | + | session.resizeTickID = overlayWidget.AddTickCallback(callback, 0, nil) | |
| 4613 | 4616 | } | |
| 4614 | 4617 | ||
| 4615 | 4618 | func (a *App) stopFloatingResizeWatcher(session *floatingWorkspaceSession) { | |
| @@ -4617,12 +4620,23 @@ func (a *App) stopFloatingResizeWatcher(session *floatingWorkspaceSession) { | |||
| 4617 | 4620 | return | |
| 4618 | 4621 | } | |
| 4619 | 4622 | ||
| 4620 | - | if session.resizeTickID != 0 && session.overlay != nil { | |
| 4623 | + | tickID := session.resizeTickID | |
| 4624 | + | session.releaseResizeTickCallback() | |
| 4625 | + | if tickID != 0 && session.overlay != nil { | |
| 4621 | 4626 | if overlayWidget := session.overlay.GtkWidget(); overlayWidget != nil { | |
| 4622 | - | overlayWidget.RemoveTickCallback(session.resizeTickID) | |
| 4627 | + | overlayWidget.RemoveTickCallback(tickID) | |
| 4623 | 4628 | } | |
| 4624 | 4629 | } | |
| 4630 | + | } | |
| 4631 | + | ||
| 4632 | + | // releaseResizeTickCallback releases the raw purego callback slot. It is safe | |
| 4633 | + | // to call after GTK has already removed a callback that returned false. | |
| 4634 | + | func (session *floatingWorkspaceSession) releaseResizeTickCallback() { | |
| 4635 | + | if session == nil { | |
| 4636 | + | return | |
| 4637 | + | } | |
| 4625 | 4638 | session.resizeTickID = 0 | |
| 4639 | + | session.resizeTickCallback = nil | |
| 4626 | 4640 | session.resizeWatcherActive = false | |
| 4627 | 4641 | } | |
| 4628 | 4642 | ||
| @@ -167,7 +167,10 @@ type floatingWorkspaceSession struct { | |||||
| 167 | omnibox *component.Omnibox | 167 | omnibox *component.Omnibox | ||
| 168 | omniboxWidget layout.Widget | 168 | omniboxWidget layout.Widget | ||
| 169 | resizeWatcherActive bool | 169 | resizeWatcherActive bool | ||
| 170 | - | resizeTickID uint | 170 | + | // resizeTickCallback keeps the purego callback live until its GTK source is |
| 171 | + | // removed. It is cleared exactly once with resizeTickID. | |||
| 172 | + | resizeTickCallback *gtk.TickCallback | |||
| 173 | + | resizeTickID uint | |||
| 171 | appliedWidth int | 174 | appliedWidth int | ||
| 172 | appliedHeight int | 175 | appliedHeight int | ||
| 173 | } | 176 | } | ||
| @@ -4594,22 +4597,22 @@ func (a *App) startFloatingResizeWatcher(session *floatingWorkspaceSession) { | |||||
| 4594 | 4597 | ||||
| 4595 | session.resizeWatcherActive = true | 4598 | session.resizeWatcherActive = true | ||
| 4596 | paneID := session.paneID | 4599 | paneID := session.paneID | ||
| 4597 | - | tickCallback := gtk.TickCallback(func(_ uintptr, _ uintptr, _ uintptr) bool { | 4600 | + | callback := new(gtk.TickCallback) |
| 4601 | + | *callback = func(_ uintptr, _ uintptr, _ uintptr) bool { | |||
| 4598 | liveSession := a.floatingSessionByPaneID(paneID) | 4602 | liveSession := a.floatingSessionByPaneID(paneID) | ||
| 4599 | if liveSession == nil { | 4603 | if liveSession == nil { | ||
| 4600 | - | session.resizeWatcherActive = false | 4604 | + | session.releaseResizeTickCallback() |
| 4601 | - | session.resizeTickID = 0 | |||
| 4602 | return false | 4605 | return false | ||
| 4603 | } | 4606 | } | ||
| 4604 | 4607 | ||||
| 4605 | keepRunning := a.handleFloatingViewportTick(liveSession) | 4608 | keepRunning := a.handleFloatingViewportTick(liveSession) | ||
| 4606 | if !keepRunning { | 4609 | if !keepRunning { | ||
| 4607 | - | liveSession.resizeWatcherActive = false | 4610 | + | liveSession.releaseResizeTickCallback() |
| 4608 | - | liveSession.resizeTickID = 0 | |||
| 4609 | } | 4611 | } | ||
| 4610 | return keepRunning | 4612 | return keepRunning | ||
| 4611 | - | }) | 4613 | + | } |
| 4612 | - | session.resizeTickID = overlayWidget.AddTickCallback(&tickCallback, 0, nil) | 4614 | + | session.resizeTickCallback = callback |
| 4615 | + | session.resizeTickID = overlayWidget.AddTickCallback(callback, 0, nil) | |||
| 4613 | } | 4616 | } | ||
| 4614 | 4617 | ||||
| 4615 | func (a *App) stopFloatingResizeWatcher(session *floatingWorkspaceSession) { | 4618 | func (a *App) stopFloatingResizeWatcher(session *floatingWorkspaceSession) { | ||
| @@ -4617,12 +4620,23 @@ func (a *App) stopFloatingResizeWatcher(session *floatingWorkspaceSession) { | |||||
| 4617 | return | 4620 | return | ||
| 4618 | } | 4621 | } | ||
| 4619 | 4622 | ||||
| 4620 | - | if session.resizeTickID != 0 && session.overlay != nil { | 4623 | + | tickID := session.resizeTickID |
| 4624 | + | session.releaseResizeTickCallback() | |||
| 4625 | + | if tickID != 0 && session.overlay != nil { | |||
| 4621 | if overlayWidget := session.overlay.GtkWidget(); overlayWidget != nil { | 4626 | if overlayWidget := session.overlay.GtkWidget(); overlayWidget != nil { | ||
| 4622 | - | overlayWidget.RemoveTickCallback(session.resizeTickID) | 4627 | + | overlayWidget.RemoveTickCallback(tickID) |
| 4623 | } | 4628 | } | ||
| 4624 | } | 4629 | } | ||
| 4630 | + | } | |||
| 4631 | + | ||||
| 4632 | + | // releaseResizeTickCallback releases the raw purego callback slot. It is safe | |||
| 4633 | + | // to call after GTK has already removed a callback that returned false. | |||
| 4634 | + | func (session *floatingWorkspaceSession) releaseResizeTickCallback() { | |||
| 4635 | + | if session == nil { | |||
| 4636 | + | return | |||
| 4637 | + | } | |||
| 4625 | session.resizeTickID = 0 | 4638 | session.resizeTickID = 0 | ||
| 4639 | + | session.resizeTickCallback = nil | |||
| 4626 | session.resizeWatcherActive = false | 4640 | session.resizeWatcherActive = false | ||
| 4627 | } | 4641 | } | ||
| 4628 | 4642 | ||||