AccessKit / accesskit

Accessibility infrastructure for UI toolkits
https://accesskit.dev
BSD 3-Clause "New" or "Revised" License
1.05k stars 53 forks source link

fix: Make the transition from placeholder to real tree more robust #405

Closed mwcampbell closed 4 months ago

mwcampbell commented 5 months ago

On Windows, when running the winit simple example, which uses the placeholder tree, I noticed that with both NVDA and Narrator, the screen reader would sometimes not announce the new window, and if you queried the current focus, it would announce whatever had focus before. The cause seems to be that when transitioning from the placeholder tree to the real tree, a new context is created, and the placeholder context is discarded. That means that if the screen reader previously had a reference to the root node in the placeholder tree, that object would start returning UIA_E_ELEMENTNOTAVAILABLE.

I had thought that using a completely separate context for the placeholder tree was the cleanest solution. After all, we don't know what the root node ID of the real tree would be. So I figured, we might end up invalidating the root anyway, so, leaving nothing to chance, we should always invalidate the root when transitioning from the placeholder to the real tree. But I was wrong. It turns out that the screen readers, or UIA itself, didn't handle this situation as gracefully as I had hoped, even though we immediately send a focus event from the real tree.

So now, we update the context in place rather than creating a new one. To avoid the complication of updating the action handler in place, the placeholder context now uses the real action handler, but a new is_placeholder flag prevents the action handler from being run while the adapter is in the placeholder state.

The tricky part is that now PlatformNode needs to have a new configuration where it always refers to the root node, even if the ID of the root node changes when we go from the placeholder tree to the real tree. To accomplish this, I made the node_id field of PlatformNode an Option.

Also, I decided that while the adapter is in the placeholder state, the tree should reflect the real window focus state, rather than having its focus state always set to false. This is a precaution in case a mismatch between the actual window focus state and the UIA focus state of the window is ever a problem. I had thought that by not telling UIA that the window had focus until we had the real tree, we could prevent the AT from seeing the placeholder tree. But now I think it's not so bad if the AT initially sees an empty tree with just the window object. Assuming something within the window has focus, we do immediately raise a UIA focus event when we switch to the real tree.