Repository: nvaccess/nvda

Issue:
NVDA not reading tabs in Instantbird
<!-- Please read the text in this edit field before filling it in.
Please thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.
Issues may be closed if the required information is not present.
https://github.com/nvaccess/nvda/blob/master/devDocs/githubIssueTemplateExplanationAndExamples.md
Please also note that the NVDA project has a Citizen and Contributor Code of Conduct which can be found at https://github.com/nvaccess/nvda/blob/master/CODE_OF_CONDUCT.md. NV Access expects that all contributors and other community members read and abide by the rules set out in this document while participating or contributing to this project. This includes creating or commenting on issues and pull requests. 

Each of the questions and sections below start with multiple hash symbols (#). Place your answers and information on the blank line below each question.
-->

### Steps to reproduce:
Open Instantbird and try to read the tabs or the windows title.
### Actual behavior:
You just get the pings for a error. 
### Expected behavior:
It should read the windows title and the name of the chats that are the tabs you have open.
### System configuration
#### NVDA installed/portable/running from source:
Installed
#### NVDA version:
NVDA Version: alpha-24876,2325be89
#### Windows version:
Windows 11
#### Name and version of other software in use when reproducing the issue:
Instantbird 1.5
#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your computer?

#### Have you tried any other versions of NVDA? If so, please report their behaviors.

#### If NVDA add-ons are disabled, is your problem still occurring?

#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
Here is a bit from my log. 

ERROR - eventHandler.executeEvent (23:02:18.998) - MainThread (784):
error executing event: gainFocus on <NVDAObjects.Dynamic_DocumentMozillaIAccessible object at 0x06CDBD10> with extra args of {}
Traceback (most recent call last):
  File "eventHandler.pyc", line 279, in executeEvent
  File "eventHandler.pyc", line 327, in doPreGainFocus
  File "browseMode.pyc", line 1321, in event_treeInterceptor_gainFocus
  File "speech\speech.pyc", line 558, in speakObject
  File "speech\speech.pyc", line 601, in getObjectSpeech
  File "speech\speech.pyc", line 433, in getObjectPropertiesSpeech
  File "NVDAObjects\IAccessible\mozilla.pyc", line 82, in hasDetails
  File "baseObject.pyc", line 42, in __get__
  File "baseObject.pyc", line 146, in _getPropertyViaCache
  File "NVDAObjects\IAccessible\__init__.pyc", line 1554, in _get_detailsRelations
  File "NVDAObjects\IAccessible\__init__.pyc", line 1497, in _getIA2TargetsForRelationsOfType
  File "comtypes\__init__.pyc", line 1152, in QueryInterface
  File "monkeyPatches\comtypesMonkeyPatches.pyc", line 32, in __call__
_ctypes.COMError: (-2147467262, 'No such interface supported', (None, None, None, 0, None))



Discussion:
Hi @tapper82 ,
try to run COM utility from NVDA tools menu. But consider that Instantbird is an abandoned software (from 2013 or so), build over a very old graphical system of Mozilla products. So, if you have a strong necessity to use it and solve this problem, I suggest you to close this issue and ask in community for an add-on or similar that acts as workaround.
Given that Instantbird is Gecko based this is almost certainly caused by #13399 cc @feerrenrut  
A similar problem has been reported on the users list fol older version of Thunderbird.
Hi I do know that Instantbird  is old. I would like to use something newer. I just use Instantbird for IRC does any one know anything that is as simple to use for IRC with NVDA?

Resolving patch:
diff --git a/source/IAccessibleHandler/__init__.py b/source/IAccessibleHandler/__init__.py
--- a/source/IAccessibleHandler/__init__.py
+++ b/source/IAccessibleHandler/__init__.py
@@ -240,7 +240,10 @@
 }
 
 
-def normalizeIAccessible(pacc, childID=0):
+def normalizeIAccessible(
+		pacc: Union[IUnknown, IA.IAccessible, IA2.IAccessible2],
+		childID: int = 0
+) -> Union[IA.IAccessible, IA2.IAccessible2]:
 	if not isinstance(pacc, IA.IAccessible):
 		try:
 			pacc = pacc.QueryInterface(IA.IAccessible)
diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py
--- a/source/NVDAObjects/IAccessible/__init__.py
+++ b/source/NVDAObjects/IAccessible/__init__.py
@@ -3,6 +3,10 @@
 # This file is covered by the GNU General Public License.
 # See the file COPYING for more details.
 import typing
+from typing import (
+	Optional,
+	Union,
+)
 
 from comtypes.automation import IEnumVARIANT, VARIANT
 from comtypes import (
@@ -20,6 +24,7 @@
 import itertools
 import importlib
 from comInterfaces.tom import ITextDocument
+from comInterfaces import Accessibility as IA
 from comInterfaces import IAccessible2Lib as IA2
 import tones
 import languageHandler
@@ -608,17 +613,20 @@ def findOverlayClasses(self,clsList):
 			if clsList[0]==IAccessible and len(clsList)==3 and self.IAccessibleRole==oleacc.ROLE_SYSTEM_CLIENT and self.childCount==0:
 				clsList.insert(0,ContentGenericClient)
 
-	def __init__(self,windowHandle=None,IAccessibleObject=None,IAccessibleChildID=None,event_windowHandle=None,event_objectID=None,event_childID=None):
+	# C901: 'IAccessible.__init__' is too complex
+	def __init__(  # noqa: C901
+			self,
+			windowHandle: Optional[int] = None,
+			IAccessibleObject: Optional[Union[IUnknown, IA.IAccessible, IA2.IAccessible2]] = None,
+			IAccessibleChildID: Optional[int] = None,
+			event_windowHandle: Optional = None,
+			event_objectID: Optional = None,
+			event_childID: Optional = None
+	):
+		"""
+		@param windowHandle: the window handle, if known
+		@param IAccessibleChildID: A child ID that will be used on all methods of the IAccessible pointer
 		"""
-@param pacc: a pointer to an IAccessible object
-@type pacc: ctypes.POINTER(IAccessible)
-@param child: A child ID that will be used on all methods of the IAccessible pointer
-@type child: int
-@param hwnd: the window handle, if known
-@type hwnd: int
-@param objectID: the objectID for the IAccessible Object, if known
-@type objectID: int
-"""
 		self.IAccessibleObject=IAccessibleObject
 		self.IAccessibleChildID=IAccessibleChildID
 
@@ -1462,12 +1470,14 @@ def _get_indexInParent(self):
 
 	def _get__IA2Relations(self) -> typing.List[IA2.IAccessibleRelation]:
 		if not isinstance(self.IAccessibleObject, IA2.IAccessible2):
+			log.debug("Not an IA2.IAccessible2")
 			raise NotImplementedError
 		import ctypes
 		import comtypes.hresult
 		try:
 			size = self.IAccessibleObject.nRelations
 		except COMError:
+			log.debug("Unable to get nRelations")
 			raise NotImplementedError
 		if size <= 0:
 			return list()
@@ -1476,6 +1486,7 @@ def _get__IA2Relations(self) -> typing.List[IA2.IAccessibleRelation]:
 		# The client allocated relations array is an [out] parameter instead of [in, out], so we need to use the raw COM method.
 		res = self.IAccessibleObject._IAccessible2__com__get_relations(size, relations, ctypes.byref(count))
 		if res != comtypes.hresult.S_OK:
+			log.debug("Unable to get relations")
 			raise NotImplementedError
 		return list(relations)
 
@@ -1483,9 +1494,13 @@ def _getIA2TargetsForRelationsOfType(
 			self,
 			relationType: "IAccessibleHandler.RelationType",
 			maxRelations: int = 1,
-	) -> typing.List[ctypes.POINTER(IUnknown)]:
+	) -> typing.List[IUnknown]:
 		"""Gets the target IAccessible (actually IUnknown; use QueryInterface or
-		normalizeIAccessible to resolve) for the relations with given type."""
+		normalizeIAccessible to resolve) for the relations with given type.
+		Allows escape of exception: COMError(-2147417836, 'Requested object does not exist.'),
+		callers should handle this, for this reason consider using _getIA2RelationFirstTarget
+		if only the first target is required, and you wish the target to be converted to an IAccessible
+		"""
 		acc = self.IAccessibleObject
 		if not isinstance(acc, IA2.IAccessible2):
 			raise NotImplementedError
@@ -1526,13 +1541,17 @@ def _getIA2RelationFirstTarget(
 			# rather than fetch all the relations and querying the type, do that in process for performance reasons
 			targets = self._getIA2TargetsForRelationsOfType(relationType, maxRelations=1)
 			if targets:
-				return targets[0]
+				ia2Object = IAccessibleHandler.normalizeIAccessible(targets[0])
+				return IAccessible(
+					IAccessibleObject=ia2Object,
+					IAccessibleChildID=0
+				)
 		except (NotImplementedError, COMError):
 			log.debugWarning("Unable to use _getIA2TargetsForRelationsOfType, fallback to _IA2Relations.")
 
 		# eg IA2_2 is not available, fall back to old approach
-		for relation in self._IA2Relations:
-			try:
+		try:
+			for relation in self._IA2Relations:
 				if relation.relationType == relationType:
 					# Take the first of 'relation.nTargets' see IAccessibleRelation._methods_
 					target = relation.target(0)
@@ -1541,24 +1560,18 @@ def _getIA2RelationFirstTarget(
 						IAccessibleObject=ia2Object,
 						IAccessibleChildID=0
 					)
-			except COMError:
-				pass
+		except (NotImplementedError, COMError):
+			log.debug("Unable to fetch _IA2Relations", exc_info=True)
+			pass
 		return None
 
 	#: Type definition for auto prop '_get_detailsRelations'
 	detailsRelations: typing.Iterable["IAccessible"]
 
 	def _get_detailsRelations(self) -> typing.Iterable["IAccessible"]:
-		relations = self._getIA2TargetsForRelationsOfType(
-			IAccessibleHandler.RelationType.DETAILS,
-			maxRelations=1
-		)
-		if not relations:
+		relationTarget = self._getIA2RelationFirstTarget(IAccessibleHandler.RelationType.DETAILS)
+		if not relationTarget:
 			return ()
-		relationTarget = IAccessible(
-			IAccessibleObject=IAccessibleHandler.normalizeIAccessible(relations[0]),
-			IAccessibleChildID=0
-		)
 		return (relationTarget, )
 
 	#: Type definition for auto prop '_get_flowsTo'
diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py
--- a/source/NVDAObjects/IAccessible/mozilla.py
+++ b/source/NVDAObjects/IAccessible/mozilla.py
@@ -68,7 +68,6 @@ def _get_detailsSummary(self) -> Optional[str]:
 		# Although slower, we have to fetch the details relations instead.
 		detailsRelations = self.detailsRelations
 		if not detailsRelations:
-			log.error("should be able to fetch detailsRelations")
 			return None
 		for target in detailsRelations:
 			# just take the first for now.


Test patch: