Crash in WindowsUIAManager: game window is not an accessible window

We have a rare assertion we’re hitting after enabling accessibility (Accessibility.Enable=1) in our build:

Assertion failed: AccessibleWindow.IsValid() [File:D:\P4\<Game>\main\Engine\Source\Runtime\ApplicationCore\Private\Windows\Accessibility\WindowsUIAManager.cpp] [Line: 142] <Game> (64-bit Development PCD3D_SM6) is not an accessible window. All windows must be accessible.

We use the SlateScreenReader to enable reading aloud focused widgets, and everything seem to be working correctly aside from this occasional crash.

I haven’t been able to capture this in a debugger myself because it doesn’t happen for me. If you’ve seen something like this before or have hints on where to begin my investigation I’d appreciate it.

Hi Chad,

I haven’t seen anything similar, though the ScreenReader isn’t used too widely so we may have just not run into it yet. Are you only seeing this in development builds? That does look like the proper, top-level window, so perhaps a good first step would be to add some additional logging to FSlateAccessibleMessageHandler::GetAccessibleWindow and FSlateAccessibleWidgetCache::GetAccessibleWidgetChecked to see why it’s returning null. It looks like either the SWindow is not valid, not accessible, or the accessibility system isn’t active yet. Knowing which of those conditions is failing should help narrow down where the problem is, I wonder if it’s some race condition where a Windows message sent very early is being processed before the accessibility system is initialized.

Best,

Cody

Looks like the crash happens because at some point we call FSlateAccessibleMessageHandler::GetAccessibleWindow and it is inactive.

Interestingly, there are some successful calls first then the unsuccessful one later. You can see this in the logs I added:

`[2025.04.28-13.49.21:682][ 0]LogWindowsDesktop: Got WM_GETOBJECT and application is accessible
[2025.04.28-13.49.21:682][ 0]LogAccessibility: FWindowsUIAManager::GetWindowProvider called
[2025.04.28-13.49.21:682][ 0]LogSlateAccessibleMessageHandler: FSlateAccessibleMessageHandler::GetAccessibleWindow - ACTIVE - SlateWindow.IsValid=1
[2025.04.28-13.49.21:686][ 0]LogWindowsDesktop: Got WM_GETOBJECT and application is accessible
[2025.04.28-13.49.21:686][ 0]LogAccessibility: FWindowsUIAManager::GetWindowProvider called
[2025.04.28-13.49.21:686][ 0]LogSlateAccessibleMessageHandler: FSlateAccessibleMessageHandler::GetAccessibleWindow - ACTIVE - SlateWindow.IsValid=1

[2025.04.28-13.49.46:853][427]LogWindowsDesktop: Got WM_GETOBJECT and application is accessible
[2025.04.28-13.49.46:853][427]LogAccessibility: FWindowsUIAManager::GetWindowProvider called
[2025.04.28-13.49.46:853][427]LogSlateAccessibleMessageHandler: FSlateAccessibleMessageHandler::GetAccessibleWindow - INACTIVE
[2025.04.28-13.49.46:853][427]LogOutputDevice: Warning:

Script Stack (0 frames) :

[2025.04.28-13.49.46:861][427]LogWindows: Error: appError called: Assertion failed: AccessibleWindow.IsValid() [File:D:\P4<Game>\main\Engine\Source\Runtime\ApplicationCore\Private\Windows\Accessibility\WindowsUIAManager.cpp] [Line: 146]
(64-bit Development PCD3D_SM6) is not an accessible window. All windows must be accessible.`

I plan to add some additional logging around when it becomes active/inactive and try to trace where this deactivate happens too early.

Hi,

Sounds like we’re making some progress. I see some logging in FGenericAccessibleMessageHandler as well, so I’d recommend setting LogAccessibility to Verbose to see if it’s being explicitly deactivated. Since we’re focusing on Windows, it looks like FWindowsUIAManager will deactivate accessibility in FWindowsUIAManager::OnWidgetProviderRemoved, which suggests every FWindowsUIAWidgetProvider was destroyed:

`void FWindowsUIAManager::OnWidgetProviderRemoved(TSharedRef InWidget)
{
CachedWidgetProviders.Remove(InWidget);

if (CachedWidgetProviders.Num() == 0)
{
// If the last widget Provider is being removed, we can disable application accessibility.
// Technically an external application could still be running listening for mouse/keyboard events,
// but in practice I don’t think its realistic to do this while having no references to any Provider.
//
// Note that there could still be control Providers with valid references. In practice I don’t think
// this is possible, but if we run into problems then we can simply call AddRef and Release on the
// underlying widget Provider whenever a control Provider gets added/removed.
OnAccessibilityDisabled();
}
}`Looking back at the callstack, it sounds like that second paragraph is exactly what could be happening here. That could also explain why it’s sporadic, since it would only be a problem if a message came in while there were no accessible widgets and the system has been made inactive. If you want to test out that theory, we could have the control provider increment the RefCount on the widget provider as suggested:

`// FWindowsUIAControlProvider

FWindowsUIAControlProvider::FWindowsUIAControlProvider(FWindowsUIAManager& InManager, TSharedRef InWidget)
: FWindowsUIABaseProvider(InManager, InWidget)
{
UIAManager->GetWidgetProvider(Widget).AddRef();
}

FWindowsUIAControlProvider::~FWindowsUIAControlProvider()
{
UIAManager->GetWidgetProvider(Widget).Release();
}`Best,

Cody

We’re only seeing this on development builds. It does seem like it mostly happens early at the start of the client, so it is possible the windows message is coming through before the accessibility system has initialized.

I’ll try to add some additional logging so we can see what the order of operations here might be.

Nice, I’ll enable verbose logging and apply this change to see if it resolves the issue.

I have discovered the cause of the crash. Our code is calling SetActive() on the slate message handler to match the user settings of whether or not to enable the in-game screen reader.

Looks like what we should be doing instead is registering/unregistering the user. Let me know if that is the correct direction.

Glad you were able to track it down! You should be safe using any of the functions exposed on USlateScreenReaderEngineSubsystem. You could deactivate/unregister the user, or deactivate the full system via DeactivateScreenReader if you don’t need to support per-user settings.

In our case we have TTS options that may be enabled separately from the full Slate Screen Reader experience. We still push the TTS tasks through the Screen Reader subsystem since it handles queuing and everything for us.

That’s why we were always registering the user, and then only enabling the Slate handler if the option for full screen reading was enabled. Effectively we want the screen reader system enabled and working, but slate only be reading under certain conditions. Luckily, since our game only supports a single local player I can use a “system” user index for our TTS implementation and the “real” user index for the slate screen reader.

But it would be nice if this feature evolves to be able to support pushing TTS requests through separately from the full Slate Screen Reader being enabled.