Skip to content

Commit 9a876de

Browse files
appel1maxkatz6
andauthored
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 <maxkatz6@outlook.com> * Return empty list in lazy if argsObj has been disposed --------- Co-authored-by: Max Katz <maxkatz6@outlook.com>
1 parent 5846116 commit 9a876de

1 file changed

Lines changed: 14 additions & 3 deletions

File tree

src/Browser/Avalonia.Browser/BrowserInputHandler.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,12 @@ public bool OnPointerMove(string pointerType, long pointerId, double offsetX, do
7979
{
8080
coalescedEvents = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
8181
{
82+
if (argsObj.IsDisposed)
83+
return [];
84+
8285
// To minimize JS interop usage, we resolve all points properties in a single call.
8386
const int itemsPerPoint = 6;
8487
var pointsProps = InputHelper.GetCoalescedEvents(argsObj);
85-
argsObj.Dispose();
8688
s_intermediatePointsPooledList.Clear();
8789

8890
var pointsCount = pointsProps.Length / itemsPerPoint;
@@ -101,8 +103,17 @@ public bool OnPointerMove(string pointerType, long pointerId, double offsetX, do
101103
});
102104
}
103105

104-
return RawPointerEvent(type, pointerType!, point, (RawInputModifiers)modifier, pointerId,
105-
coalescedEvents);
106+
try
107+
{
108+
return RawPointerEvent(type, pointerType!, point, (RawInputModifiers)modifier, pointerId,
109+
coalescedEvents);
110+
}
111+
finally
112+
{
113+
// Release the JS handle after processing the event.
114+
// ImmediatePoints is only expected to be accessed synchronously during event processing.
115+
argsObj.Dispose();
116+
}
106117
}
107118

108119
public bool OnPointerDown(string pointerType, long pointerId, int buttons, double offsetX, double offsetY,

0 commit comments

Comments
 (0)