Support IAccessible2 labelled-by relation (#17437)
* Support IAccessible2 labelled-by relation
### Link to issue number:
Fixes #17436
### Summary of the issue:
IAccessible2 has an IA2_RELATION_LABELLED_BY relation type, see
https://accessibility.linuxfoundation.org/a11yspecs/ia2/docs/html/group__grp_relations.html#ga7bbace7ffb57476b75d621af2e27d1ff .
However, that one was not taken into account by NVDA's "labeledBy"
property for objects. This could could be seen for example with LibreOffice that
implements handling of that IAccessible2 relation.
Additionally, a `None` return value from
IAccessibleHandler.accNavigate wasn't handled
in `IAccessible._get_labeledBy`, triggering an error, e.g.:
>>> focus.labeledBy
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\tools\cygwin\home\user\development\git\nvda\source\baseObject.py", line 59, in __get__
return instance._getPropertyViaCache(self.fget)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\tools\cygwin\home\user\development\git\nvda\source\baseObject.py", line 167, in _getPropertyViaCache
val = getterMethod(self)
^^^^^^^^^^^^^^^^^^
File "C:\tools\cygwin\home\user\development\git\nvda\source\NVDAObjects\IAccessible\__init__.py", line 1167, in _get_labeledBy
(pacc, accChild) = IAccessibleHandler.accNavigate(
^^^^^^^^^^^^^^^^
TypeError: cannot unpack non-iterable NoneType object
### Description of user facing changes
In NVDA's Python console, retrieving the "labeledBy" property now works for objects in applications implementing the "labelled-by" IAccessible2 relation and no longer triggers an error.
### Description of development approach
* Extend `IAccessibleHandler.RelationType` with the `LABELLED_BY` relation
as defined in the IAccessible2 spec.
* Use this relation in `IAccessible._get_labeledBy` before
falling back to trying a custom Mozilla/Gecko specific NAVRELATION.
* Add handling for the case where `IAccessibleHandler.accNavigate`
for the custom Mozilla/Geck approach returns `None` to fix
the error triggered otherwise when no relation is set.
### Testing strategy:
1. start NVDA
2. Start LibreOffice Writer
3. open the options dialog: "Tools" -> "Options", go to the "User Data" page
4. move focus to the "Company" text edit
5. open NVDA's Python console (Ctrl+Insert+Z)
6. print the object that the currently focused edit is labelled by,
and it's accessible name and role:
>>> focus.labeledBy
<NVDAObjects.IAccessible.IAccessible object at 0x0BC55A70>
>>> focus.labeledBy.name
'Company:'
>>> focus.labeledBy.role
<Role.STATICTEXT: 7>
### Known issues with pull request:
None
### Code Review Checklist:
- [x] Documentation:
- Change log entry
- User Documentation
- Developer / Technical Documentation
- Context sensitive help for GUI changes
- [x] Testing:
- Unit tests
- System (end to end) tests
- Manual testing
- [x] UX of all users considered:
- Speech
- Braille
- Low Vision
- Different web browsers
- Localization in other languages / culture than English
- [x] API is compatible with existing add-ons.
- [x] Security precautions taken.
<!-- Please keep the following -->
@coderabbitai summary
* Add type annotation
* Move changelog entry to "Changes for Developers" section
* Avoid type clash due to typing.List import
Use qualified name.
Otherwise the newly introduced
from __future__ import annotations
in
commit feea4aa0aa0a6b12c33ee8dd70f22d76a2cc6d83
Author: Michael Weghorn <m.weghorn@posteo.de>
Date: Wed Nov 27 09:12:38 2024 +0000
Add type annotation
causes
source/NVDAObjects/IAccessible/__init__.py:2391:7: F811 Redefinition of unused `List` from line 13
|
2391 | class List(IAccessible):
| ^^^^ F811
2392 | def _get_role(self):
2393 | return controlTypes.Role.LIST
|
= help: Remove definition: `List`
Found 1 error.
* Apply suggestions from code review
* Add comment why import is needed
Without deferred evaluation (s. PEPs mentioned
in the newly added comment):
$ ./runlint.bat
Ensuring NVDA Python virtual environment
call ruff check --fix
source\NVDAObjects\IAccessible\__init__.py:1165:30: F821 Undefined name `IAccessible`
|
1163 | return True
1164 |
1165 | def _get_labeledBy(self) -> IAccessible | None:
| ^^^^^^^^^^^ F821
1166 | label = self._getIA2RelationFirstTarget(IAccessibleHandler.RelationType.LABELLED_BY)
1167 | if label:
|
Found 1 error.
Deactivating NVDA Python virtual environment
* Apply suggestions from code review
* Update source/NVDAObjects/IAccessible/__init__.py
* Update source/NVDAObjects/IAccessible/__init__.py
---------
Co-authored-by: Sean Budd <seanbudd123@gmail.com>
Co-authored-by: Sean Budd <sean@nvaccess.org>