diff --git a/source/NVDAObjects/IAccessible/ia2Web.py b/source/NVDAObjects/IAccessible/ia2Web.py index 98312c92e59..8f68fbb2fb6 100644 --- a/source/NVDAObjects/IAccessible/ia2Web.py +++ b/source/NVDAObjects/IAccessible/ia2Web.py @@ -104,6 +104,10 @@ def _rolesGenerator(self) -> Generator[Optional[controlTypes.Role], None, None]: class Ia2Web(IAccessible): IAccessibleTableUsesTableCellIndexAttrib = True + # The IAccessibleText implementation in web browsers exposes embedded object + # characters which need to be traversed to read the content. That isn't useful + # to users. + _shouldUseTextInfoForReading = False def isDescendantOf(self, obj: "NVDAObjects.NVDAObject") -> bool: if obj.windowHandle != self.windowHandle: @@ -284,6 +288,9 @@ class Figure(Ia2Web): class Editor(Ia2Web, DocumentWithTableNavigation): TextInfo = MozillaCompoundTextInfo + # MozillaCompoundTextInfo traverses embedded objects and is suitable for + # presentation to users. + _shouldUseTextInfoForReading = True def _getTableCellAt(self, tableID, startPos, destRow, destCol): # Locate the table in the object ancestry of the given document position. diff --git a/source/NVDAObjects/__init__.py b/source/NVDAObjects/__init__.py index d741c51196c..4ef45093c48 100644 --- a/source/NVDAObjects/__init__.py +++ b/source/NVDAObjects/__init__.py @@ -1605,6 +1605,12 @@ def _get__hasNavigableText(self): else: return False + #: Whether the TextInfo should be used for the review cursor, the read current + #: line command, etc. This should be False where the TextInfo is only used + #: internally and doesn't provide text that is suitable for presentation to the + #: user; e.g. it includes raw embedded object characters. + _shouldUseTextInfoForReading: bool = True + def _get_hasIrrelevantLocation(self): """Returns whether the location of this object is irrelevant for mouse or magnification tracking or highlighting, either because it is programatically hidden (State.INVISIBLE), off screen or the object has no location.""" diff --git a/source/globalCommands.py b/source/globalCommands.py index 57490c715cb..a95c2d5e867 100755 --- a/source/globalCommands.py +++ b/source/globalCommands.py @@ -262,15 +262,22 @@ def script_toggleCurrentAppSleepMode(self, gesture): def script_reportCurrentLine(self, gesture): obj = api.getFocusObject() treeInterceptor = obj.treeInterceptor + useTextInfo: bool = False if ( isinstance(treeInterceptor, treeInterceptorHandler.DocumentTreeInterceptor) and not treeInterceptor.passThrough ): obj = treeInterceptor - try: - info = obj.makeTextInfo(textInfos.POSITION_CARET) - except (NotImplementedError, RuntimeError): - info = obj.makeTextInfo(textInfos.POSITION_FIRST) + useTextInfo = True + else: + useTextInfo = obj._shouldUseTextInfoForReading + if useTextInfo: + try: + info = obj.makeTextInfo(textInfos.POSITION_CARET) + except (NotImplementedError, RuntimeError): + info = obj.makeTextInfo(textInfos.POSITION_FIRST) + else: + info = NVDAObjectTextInfo(obj, textInfos.POSITION_FIRST) info.expand(textInfos.UNIT_LINE) scriptCount = getLastScriptRepeatCount() if scriptCount == 0: diff --git a/source/review.py b/source/review.py index 636fe28d3a7..a9cf90dda61 100644 --- a/source/review.py +++ b/source/review.py @@ -20,26 +20,28 @@ import config -def getObjectPosition(obj): +def getObjectPosition(obj: NVDAObject) -> tuple[textInfos.TextInfo, ScriptableObject] | None: """ Fetches a TextInfo instance suitable for reviewing the text in the given object. - @param obj: the NVDAObject to review - @type obj: L{NVDAObject} - @return: the TextInfo instance and the Scriptable object the TextInfo instance is referencing, or None on error. - @rtype: (L{TextInfo},L{ScriptableObject}) + :param obj: the NVDAObject to review + :return: the TextInfo instance and the Scriptable object the TextInfo instance is referencing, or None on error. """ - try: - pos = obj.makeTextInfo(textInfos.POSITION_CARET) - except (NotImplementedError, RuntimeError): - # No caret supported, try first position instead + useTextInfo: bool = obj._shouldUseTextInfoForReading + if useTextInfo: try: - pos = obj.makeTextInfo(textInfos.POSITION_FIRST) + pos = obj.makeTextInfo(textInfos.POSITION_CARET) except (NotImplementedError, RuntimeError): - log.debugWarning( - "%s does not support POSITION_FIRST, falling back to NVDAObjectTextInfo" % obj.TextInfo, - ) - # First position not supported either, return first position from a generic NVDAObjectTextInfo - return NVDAObjectTextInfo(obj, textInfos.POSITION_FIRST), obj + # No caret supported, try first position instead + try: + pos = obj.makeTextInfo(textInfos.POSITION_FIRST) + except (NotImplementedError, RuntimeError): + log.debugWarning( + f"{obj.TextInfo} does not support POSITION_FIRST, falling back to NVDAObjectTextInfo", + ) + # First position not supported either, return first position from a generic NVDAObjectTextInfo + useTextInfo = False + if not useTextInfo: + return NVDAObjectTextInfo(obj, textInfos.POSITION_FIRST), obj return pos, pos.obj diff --git a/user_docs/en/changes.md b/user_docs/en/changes.md index 4bf6d8b13dd..1803ae7fc63 100644 --- a/user_docs/en/changes.md +++ b/user_docs/en/changes.md @@ -73,6 +73,7 @@ The setting is disabled by default. (#20013, @LeonarddeR) * Fixed NVDA freezing when navigating in JetBrains IDEs. (#16741, @christopherpross) * Speech dictionary entries of type Whole word now correctly handle words containing Unicode combining marks (e.g. Hebrew niqqud, Arabic harakat). (#20013, @LeonarddeR) * In particular, Whole word entries no longer incorrectly match inside larger words when those words contain combining marks. +* In focus mode in web browsers, it is now possible to review and spell the labels of controls when those labels are specifically provided for accessibility; e.g. via aria-label or aria-labelledby. (#15159, @jcsteh) ### Changes for Developers