fix(interop): resolve native resource leaks in GTK and WebKit interop
All checks were successful
CI / Build (Linux) (push) Successful in 21s

Fix critical memory leaks identified in architecture review: Add signal handler disconnection in WebKitNative (load-changed and script-dialog signals now properly cleaned up), implement GTK idle callback cleanup with automatic removal on completion, add dlclose() calls for WebKit library handles, track GTK signal IDs in GtkSkiaSurfaceWidget for proper disposal. Replace empty catch blocks in GestureManager with logged exception handling. Add WebKitNative.Cleanup() and GtkNative.ClearCallbacks() methods for application shutdown.
This commit is contained in:
2026-03-06 23:14:53 -05:00
parent c5221ba580
commit 3412cb982e
22 changed files with 1208 additions and 50 deletions

View File

@@ -92,6 +92,11 @@ public sealed class GtkHostWindow : IDisposable
private readonly ButtonEventDelegate _buttonPressHandler;
private readonly ButtonEventDelegate _buttonReleaseHandler;
private readonly MotionEventDelegate _motionHandler;
private ulong _deleteSignalId;
private ulong _configureSignalId;
private ulong _buttonPressSignalId;
private ulong _buttonReleaseSignalId;
private ulong _motionSignalId;
public IntPtr Window => _window;
public IntPtr Overlay => _overlay;
@@ -155,23 +160,18 @@ public sealed class GtkHostWindow : IDisposable
_motionHandler = OnMotion;
// Connect event handlers
ConnectSignal(_window, "delete-event", Marshal.GetFunctionPointerForDelegate(_deleteEventHandler));
ConnectSignal(_window, "configure-event", Marshal.GetFunctionPointerForDelegate(_configureEventHandler));
_deleteSignalId = GtkNative.g_signal_connect_data(_window, "delete-event", Marshal.GetFunctionPointerForDelegate(_deleteEventHandler), IntPtr.Zero, IntPtr.Zero, 0);
_configureSignalId = GtkNative.g_signal_connect_data(_window, "configure-event", Marshal.GetFunctionPointerForDelegate(_configureEventHandler), IntPtr.Zero, IntPtr.Zero, 0);
// Add pointer event masks
GtkNative.gtk_widget_add_events(_window, 772);
ConnectSignal(_window, "button-press-event", Marshal.GetFunctionPointerForDelegate(_buttonPressHandler));
ConnectSignal(_window, "button-release-event", Marshal.GetFunctionPointerForDelegate(_buttonReleaseHandler));
ConnectSignal(_window, "motion-notify-event", Marshal.GetFunctionPointerForDelegate(_motionHandler));
_buttonPressSignalId = GtkNative.g_signal_connect_data(_window, "button-press-event", Marshal.GetFunctionPointerForDelegate(_buttonPressHandler), IntPtr.Zero, IntPtr.Zero, 0);
_buttonReleaseSignalId = GtkNative.g_signal_connect_data(_window, "button-release-event", Marshal.GetFunctionPointerForDelegate(_buttonReleaseHandler), IntPtr.Zero, IntPtr.Zero, 0);
_motionSignalId = GtkNative.g_signal_connect_data(_window, "motion-notify-event", Marshal.GetFunctionPointerForDelegate(_motionHandler), IntPtr.Zero, IntPtr.Zero, 0);
DiagnosticLog.Debug("GtkHostWindow", $"Created GTK window on X11: {width}x{height}");
}
private void ConnectSignal(IntPtr widget, string signal, IntPtr handler)
{
GtkNative.g_signal_connect_data(widget, signal, handler, IntPtr.Zero, IntPtr.Zero, 0);
}
private bool OnDeleteEvent(IntPtr widget, IntPtr eventData, IntPtr userData)
{
CloseRequested?.Invoke(this, EventArgs.Empty);
@@ -333,6 +333,17 @@ public sealed class GtkHostWindow : IDisposable
if (!_disposed)
{
_disposed = true;
// Disconnect signal handlers before destroying the widget
if (_window != IntPtr.Zero)
{
if (_deleteSignalId != 0) GtkNative.g_signal_handler_disconnect(_window, _deleteSignalId);
if (_configureSignalId != 0) GtkNative.g_signal_handler_disconnect(_window, _configureSignalId);
if (_buttonPressSignalId != 0) GtkNative.g_signal_handler_disconnect(_window, _buttonPressSignalId);
if (_buttonReleaseSignalId != 0) GtkNative.g_signal_handler_disconnect(_window, _buttonReleaseSignalId);
if (_motionSignalId != 0) GtkNative.g_signal_handler_disconnect(_window, _motionSignalId);
}
_skiaSurface?.Dispose();
if (_window != IntPtr.Zero)
{

View File

@@ -631,6 +631,17 @@ public class X11Window : IDisposable
{
if (!_disposed)
{
// Free cursor resources before closing the display
if (_display != IntPtr.Zero)
{
if (_arrowCursor != IntPtr.Zero) X11.XFreeCursor(_display, _arrowCursor);
if (_handCursor != IntPtr.Zero) X11.XFreeCursor(_display, _handCursor);
if (_textCursor != IntPtr.Zero) X11.XFreeCursor(_display, _textCursor);
_arrowCursor = IntPtr.Zero;
_handCursor = IntPtr.Zero;
_textCursor = IntPtr.Zero;
}
if (_window != IntPtr.Zero)
{
X11.XDestroyWindow(_display, _window);