Browse Source

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>
pull/22177/head
Johan Appelgren 3 weeks ago
committed by GitHub
parent
commit
9a876debb6
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 17
      src/Browser/Avalonia.Browser/BrowserInputHandler.cs

17
src/Browser/Avalonia.Browser/BrowserInputHandler.cs

@ -79,10 +79,12 @@ internal class BrowserInputHandler
{
coalescedEvents = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
{
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,

Loading…
Cancel
Save