From 9a876debb649efc73ceb039a32a67200909cae0c Mon Sep 17 00:00:00 2001 From: Johan Appelgren Date: Mon, 7 Sep 2026 07:30:11 +0000 Subject: [PATCH] Fix undisposed PointerEvent JSObject on browser pointer move (#22112) * Fix undisposed PointerEvent JSObject on browser pointer move OnPointerMove dropped argsObj (the JSObject wrapping the native DOM PointerEvent) without disposing it, except inside a Lazy factory only evaluated when GetIntermediatePoints() is called - which doesn't happen for ordinary moves/hover. Without explicit Dispose(), release requires two steps: Mono GC must collect the abandoned JSObject wrapper to release its JS handle, then V8 can reclaim the underlying JS object. Verified with a standalone FinalizationRegistry-based repro that Mono GC does not trigger on its own under continuous pointermove-like allocation pressure, so undisposed objects pile up - not a permanent leak though. Fix by disposing argsObj in a finally block after routing, guaranteeing deterministic release instead of depending on GC timing. Coalesced-event resolution (GetCoalescedEvents) is unaffected since it only runs synchronously within the same call when a consumer needs it. * Updated comments * Update src/Browser/Avalonia.Browser/BrowserInputHandler.cs Co-authored-by: Max Katz * Return empty list in lazy if argsObj has been disposed --------- Co-authored-by: Max Katz --- .../Avalonia.Browser/BrowserInputHandler.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Browser/Avalonia.Browser/BrowserInputHandler.cs b/src/Browser/Avalonia.Browser/BrowserInputHandler.cs index ba97ad07a2..7033787939 100644 --- a/src/Browser/Avalonia.Browser/BrowserInputHandler.cs +++ b/src/Browser/Avalonia.Browser/BrowserInputHandler.cs @@ -79,10 +79,12 @@ internal class BrowserInputHandler { coalescedEvents = new Lazy?>(() => { + if (argsObj.IsDisposed) + return []; + // To minimize JS interop usage, we resolve all points properties in a single call. const int itemsPerPoint = 6; var pointsProps = InputHelper.GetCoalescedEvents(argsObj); - argsObj.Dispose(); s_intermediatePointsPooledList.Clear(); var pointsCount = pointsProps.Length / itemsPerPoint; @@ -101,8 +103,17 @@ internal class BrowserInputHandler }); } - return RawPointerEvent(type, pointerType!, point, (RawInputModifiers)modifier, pointerId, - coalescedEvents); + try + { + return RawPointerEvent(type, pointerType!, point, (RawInputModifiers)modifier, pointerId, + coalescedEvents); + } + finally + { + // Release the JS handle after processing the event. + // ImmediatePoints is only expected to be accessed synchronously during event processing. + argsObj.Dispose(); + } } public bool OnPointerDown(string pointerType, long pointerId, int buttons, double offsetX, double offsetY,