Describe the bug
After a Window with an extended client area has closed, its HWND can still receive non-client mouse messages (WM_NCHITTEST, WM_NCMOUSEMOVE, WM_NCPOINTER*, ...). WindowImpl.CustomCaptionProc routes them to HitTestVisual, which calls IInputRoot.HitTestChromeElement, and that throws because PresentationSource.RootVisual is already null:
System.ArgumentNullException: Value cannot be null. (Parameter 'visual')
at Avalonia.Utilities.ThrowHelper.<ThrowIfNull>g__ThrowArgumentNullException|0_0(String paramName)
at Avalonia.VisualTree.VisualExtensions.GetVisualAt(Visual visual, Point p, Func`2 filter)
at Avalonia.Controls.PresentationSource.Avalonia.Input.IInputRoot.HitTestChromeElement(Point point)
at Avalonia.Win32.WindowImpl.HitTestVisual(IntPtr lParam)
at Avalonia.Win32.WindowImpl.CustomCaptionProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam, Boolean& callDwp)
at Avalonia.Win32.WindowImpl.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
at Avalonia.Win32.WindowImpl.WndProcMessageHandler(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
Because this throws inside the window procedure, Dispatcher.UIThread.UnhandledException never sees it and the process dies. We see it in production telemetry (56 events from 14 users across our releases on 12.1.x). Those events have no app frames: the message comes from the OS while a window is closing.
Cause
TopLevel.HandleClosed() sets _source.RootVisual = null (and PlatformImpl = null) before raising Closed.
- The Win32
WindowImpl._owner input root is set once in SetInputRoot and never cleared, so HitTestVisual still calls into the PresentationSource.
PresentationSource.HitTestChromeElement dereferences RootVisual without a null check:
WindowDecorationsElementRole? IInputRoot.HitTestChromeElement(Point point)
{
var visual = RootVisual.GetVisualAt(point, ChromeHitTestFilter);
return GetChromeRoleFromVisual(visual);
}
This is unchanged on master.
X11 and Wayland call the same HitTestChromeElement, so they are probably affected too (not verified).
To Reproduce
Minimal app (Avalonia 12.1.2, Windows 11). The explicit SendMessage stands in for the OS delivering WM_NCHITTEST while the mouse is over a closing window:
var w = new Window { Width = 400, Height = 300, ExtendClientAreaToDecorationsHint = true };
nint h = 0;
w.Opened += (_, _) =>
{
h = w.TryGetPlatformHandle()!.Handle;
DispatcherTimer.RunOnce(() => w.Close(), TimeSpan.FromMilliseconds(500));
};
w.Closed += (_, _) =>
{
GetWindowRect(h, out var r);
SendMessage(h, 0x0084 /* WM_NCHITTEST */, 0, (nint)(((r.Top + 10) << 16) | ((r.Left + 100) & 0xFFFF)));
// -> ArgumentNullException above
};
repro.zip
Expected behavior
No exception. A closed window has nothing to hit-test, so HitTestChromeElement should return null when RootVisual is null:
WindowDecorationsElementRole? IInputRoot.HitTestChromeElement(Point point)
=> RootVisual is { } root ? GetChromeRoleFromVisual(root.GetVisualAt(point, ChromeHitTestFilter)) : null;
Alternatively, WindowImpl could stop hit-testing (or clear _owner) once the top level has closed.
Avalonia version
12.1.2, still present on master
OS
Windows
Additional context
Workaround
A Win32Properties.AddWndProcHookCallback hook on each window that claims the non-client mouse messages (returning HTNOWHERE for WM_NCHITTEST) once TopLevel.PlatformImpl is null. The hook runs before WndProc.
Describe the bug
After a
Windowwith an extended client area has closed, its HWND can still receive non-client mouse messages (WM_NCHITTEST,WM_NCMOUSEMOVE,WM_NCPOINTER*, ...).WindowImpl.CustomCaptionProcroutes them toHitTestVisual, which callsIInputRoot.HitTestChromeElement, and that throws becausePresentationSource.RootVisualis already null:Because this throws inside the window procedure,
Dispatcher.UIThread.UnhandledExceptionnever sees it and the process dies. We see it in production telemetry (56 events from 14 users across our releases on 12.1.x). Those events have no app frames: the message comes from the OS while a window is closing.Cause
TopLevel.HandleClosed()sets_source.RootVisual = null(andPlatformImpl = null) before raisingClosed.WindowImpl._ownerinput root is set once inSetInputRootand never cleared, soHitTestVisualstill calls into thePresentationSource.PresentationSource.HitTestChromeElementdereferencesRootVisualwithout a null check:master.X11 and Wayland call the same
HitTestChromeElement, so they are probably affected too (not verified).To Reproduce
Minimal app (Avalonia 12.1.2, Windows 11). The explicit
SendMessagestands in for the OS deliveringWM_NCHITTESTwhile the mouse is over a closing window:repro.zip
Expected behavior
No exception. A closed window has nothing to hit-test, so
HitTestChromeElementshould returnnullwhenRootVisualis null:Alternatively,
WindowImplcould stop hit-testing (or clear_owner) once the top level has closed.Avalonia version
12.1.2, still present on master
OS
Windows
Additional context
Workaround
A
Win32Properties.AddWndProcHookCallbackhook on each window that claims the non-client mouse messages (returningHTNOWHEREforWM_NCHITTEST) onceTopLevel.PlatformImplis null. The hook runs beforeWndProc.