Skip to content

fix: handle COMError in UIA selectionContainer to prevent silent focus#20255

Merged
seanbudd merged 2 commits into
nvaccess:masterfrom
rezabakhshilaktasaraei:fix/uia-selection-container-comerror
Jun 1, 2026
Merged

fix: handle COMError in UIA selectionContainer to prevent silent focus#20255
seanbudd merged 2 commits into
nvaccess:masterfrom
rezabakhshilaktasaraei:fix/uia-selection-container-comerror

Conversation

@rezabakhshilaktasaraei

Copy link
Copy Markdown
Contributor

Link to issue number:

No GitHub issue filed — happy to open one if preferred. The bug is diagnosable from a UIA debug log and the fix is a defensive try/except matching an existing pattern in the same file three lines above.

Summary of the issue:

When NVDA focuses a UIA list item whose provider exposes the SelectionItem pattern but errors when asked for the SelectionContainer, NVDAObjects.UIA.UIA._get_selectionContainer raises an uncaught COMError. The exception propagates up through reportFocusspeakObjectgetObjectPropertiesSpeech, aborting speech for the entire focus event — the user hears nothing on focus.

The trigger is the SELECTABLE | FOCUSABLE | SELECTED branch in speech/speech.py:801-818 (the code that suppresses redundant "selected" announcements when only one item is selected). That branch queries obj.selectionContainer, which fails.

Reproduced in Telegram Desktop built against Qt 5.15.19 with the UIA backend (qwindowsuiaaccessibility). The chat list (Dialogs::InnerWidget) exposes ListItem-role virtual children with a single selected row. UIA debug log of the crash:

DEBUGWARNING - eventHandler.executeEvent ...
error executing event: gainFocus on <NVDAObjects.UIA.ListItem ...>
Traceback (most recent call last):
  File "eventHandler.pyc", line 354, in executeEvent
  File "NVDAObjects/UIA/__init__.pyc", line 1573, in event_gainFocus
  File "NVDAObjects/__init__.pyc", line 1208, in reportFocus
  File "speech/speech.pyc", line 816, in speakObject
  File "speech/speech.pyc", line 771, in getObjectPropertiesSpeech
  File "baseObject.pyc", line 167, in _getPropertyViaCache
  File "NVDAObjects/UIA/__init__.pyc", line 1677, in _get_selectionContainer
  File "comtypes/_post_coinit/_cominterface_meta_patcher.pyc", line 21, in __getattr__
_ctypes.COMError: (-2147220991, 'An event was unable to invoke any of the subscribers', ...)

Qt's QWindowsUiaSelectionItemProvider::get_SelectionContainer requires actionInterface() to be non-null even though it never uses the action interface (see qwindowsuiaselectionitemprovider.cpp:183-185). When the accessible interface has no action interface — as is the case for Telegram Desktop's chat row items, which only expose name/role/state/rect — Qt returns UIA_E_ELEMENTNOTAVAILABLE (0x80040201), surfaced as a COMError. This is arguably an upstream Qt bug too, but Qt 5.15 LTS is closed for community fixes, and any UIA provider that errors on this property has the same effect on NVDA today.

The bug is invisible until a list item with SELECTED state is encountered. With state.selected not set, the speech.py guard is skipped, selectionContainer is never fetched, and the same focus path completes normally — which is why the symptom looks like "NVDA only goes silent on selected items".

Description of user facing changes:

Focus is no longer silent on list items in Qt-based UIA applications (such as Telegram Desktop) when the item exposes SelectionItemPattern but its provider errors on SelectionContainer. NVDA treats the error the same as a NULL selection container — focus speech proceeds normally.

Description of developer facing changes:

NVDAObjects.UIA.UIA._get_selectionContainer now catches COMError from IUIAutomationSelectionItemPattern::CurrentSelectionContainer and returns None. This matches the defensive pattern already used by _get_UIASelectionPattern2 three lines above.

Description of development approach:

Diagnosed from a UIA debug log captured in Telegram Desktop (see traceback above). The log shows gainFocus correctly queued for the new list item, then reportFocus failing inside getObjectPropertiesSpeech at the p.currentSelectionContainer COM call. Wrapping that single property access in try / except COMError (and returning None on failure) fixes the crash. The rest of the method already handles the NULL case via the existing if not e: return None branch, so no other changes are needed.

Testing strategy:

Manual reproduction in Telegram Desktop with NVDA running from source:

  • Before patch: focus the chat list, arrow up/down — no speech (UIA log shows the COMError traceback above).
  • After patch: focus the chat list, arrow up/down — each row is spoken normally.

A unit test would mock IUIAutomationSelectionItemPattern so that CurrentSelectionContainer raises COMError and assert _get_selectionContainer returns None. Happy to add one if requested — it was not added in this PR because the bulk of _get_selectionContainer is bare COM access without much else to test in isolation.

Known issues with pull request:

None.

Code Review Checklist:

  • Documentation:
    • Change log entry
  • Testing:
    • Unit tests — see Testing strategy above
    • Manual testing — Telegram Desktop chat list (Qt 5.15.19 UIA)
  • UX of all users considered:
    • Speech
  • API is compatible with existing add-ons.
  • Security precautions taken.

When a UIA SelectionItemPattern provider errors on get_CurrentSelectionContainer,
NVDA's _get_selectionContainer raised an uncaught COMError. The exception
propagated up through reportFocus -> speakObject -> getObjectPropertiesSpeech
(triggered by the SELECTABLE|FOCUSABLE|SELECTED branch in speech.py), aborting
speech for the entire focus event.

Reproduced in Telegram Desktop (Qt 5.15.19 with UIA backend) where
QWindowsUiaSelectionItemProvider::get_SelectionContainer returns
UIA_E_ELEMENTNOTAVAILABLE if the QAccessibleInterface has no actionInterface.

Catch COMError on the property access and treat it as no selection container,
matching the defensive pattern already used by _get_UIASelectionPattern2
three lines above.
@rezabakhshilaktasaraei rezabakhshilaktasaraei marked this pull request as ready for review May 31, 2026 22:25
@rezabakhshilaktasaraei rezabakhshilaktasaraei requested a review from a team as a code owner May 31, 2026 22:25

@seanbudd seanbudd left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@seanbudd seanbudd merged commit 1c29cf3 into nvaccess:master Jun 1, 2026
1 check passed
@github-actions github-actions Bot added this to the 2026.3 milestone Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants