Repository: nvaccess/nvda

Issue:
UIA seems to stall under certain conditions.
### Steps to reproduce:
1. start a process like a batch processing job in total recorder or goldwave.
2. open any listview, explorer, etc. 
### Expected behavior:
the listview should read

### Actual behavior:
the listview doesn't read.> Tell us what happens instead.

### System configuration:
NVDA version: 2017.2


NVDA Installed or portable: installed.
Other information:


Windows version: windows 10 pro creator update. 
### Other questions:

Does the issue still occur after restarting your PC? yes

Have you tried any other versions of NVDA? reproduceable from 2016 onward





Discussion:

Resolving patch:
diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py
--- a/source/_UIAHandler.py
+++ b/source/_UIAHandler.py
@@ -4,6 +4,10 @@
 #This file is covered by the GNU General Public License.
 #See the file COPYING for more details.
 
+try:
+	from Queue import Queue # Python 2.7 import
+except ImportError:
+	from queue import Queue # Python 3 import
 from ctypes import *
 from ctypes.wintypes import *
 import comtypes.client
@@ -156,8 +160,8 @@ class UIAHandler(COMObject):
 	def __init__(self):
 		super(UIAHandler,self).__init__()
 		self.MTAThreadInitEvent=threading.Event()
-		self.MTAThreadStopEvent=threading.Event()
 		self.MTAThreadInitException=None
+		self.MTAThreadQueue=Queue()
 		self.MTAThread=threading.Thread(target=self.MTAThreadFunc)
 		self.MTAThread.daemon=True
 		self.MTAThread.start()
@@ -167,7 +171,7 @@ def __init__(self):
 
 	def terminate(self):
 		MTAThreadHandle=HANDLE(windll.kernel32.OpenThread(winKernel.SYNCHRONIZE,False,self.MTAThread.ident))
-		self.MTAThreadStopEvent.set()
+		self.MTAThreadQueue.put_nowait(None)
 		#Wait for the MTA thread to die (while still message pumping)
 		if windll.user32.MsgWaitForMultipleObjects(1,byref(MTAThreadHandle),False,200,0)!=0:
 			log.debugWarning("Timeout or error while waiting for UIAHandler MTA thread")
@@ -213,8 +217,10 @@ def MTAThreadFunc(self):
 			self.rootElement=self.clientObject.getRootElementBuildCache(self.baseCacheRequest)
 			self.reservedNotSupportedValue=self.clientObject.ReservedNotSupportedValue
 			self.ReservedMixedAttributeValue=self.clientObject.ReservedMixedAttributeValue
+			self.pendingForegroundUIAElement=None
+			self.currentForegroundUIAElement=None
 			self.clientObject.AddFocusChangedEventHandler(self.baseCacheRequest,self)
-			self.clientObject.AddPropertyChangedEventHandler(self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self,UIAPropertyIdsToNVDAEventNames.keys())
+			#self.clientObject.AddPropertyChangedEventHandler(self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self,UIAPropertyIdsToNVDAEventNames.keys())
 			for x in UIAEventIdsToNVDAEventNames.iterkeys():  
 				self.clientObject.addAutomationEventHandler(x,self.rootElement,TreeScope_Subtree,self.baseCacheRequest,self)
 			# #7984: add support for notification event (IUIAutomation5, part of Windows 10 build 16299 and later).
@@ -224,9 +230,45 @@ def MTAThreadFunc(self):
 			self.MTAThreadInitException=e
 		finally:
 			self.MTAThreadInitEvent.set()
-		self.MTAThreadStopEvent.wait()
+		while True:
+			func=self.MTAThreadQueue.get()
+			if func:
+				try:
+					func()
+				except:
+					log.error("Exception in function queued to UIA MTA thread",exc_info=True)
+			else:
+				break
 		self.clientObject.RemoveAllEventHandlers()
 
+	def _onForegroundChange(self):
+		pendingForegroundUIAElement=self.pendingForegroundUIAElement
+		if pendingForegroundUIAElement==self.currentForegroundUIAElement:
+			return
+		if self.currentForegroundUIAElement:
+			try:
+				self.clientObject.removePropertyChangedEventHandler(self.currentForegroundUIAElement,self)
+			except COMError:
+				# The old UIAElement died as the window was closed.
+				# The system should forget the old event registration itself.
+				pass
+		try:
+			self.clientObject.AddPropertyChangedEventHandler(pendingForegroundUIAElement,TreeScope_Subtree,self.baseCacheRequest,self,UIAPropertyIdsToNVDAEventNames.keys())
+		except COMError:
+			log.error("Could not register for UIA property change events for new foreground")
+			self.currentForegroundUIAElement=None
+		else:
+			self.currentForegroundUIAElement=pendingForegroundUIAElement
+
+	def onForegroundChange(self,hwnd):
+		try:
+			self.pendingForegroundUIAElement=self.clientObject.ElementFromHandle(hwnd)
+		except COMError:
+			log.debugWarning("Could not get a UIAElement from new foreground window")
+			return
+		# Event registration/unregistration must be always done from the MTA thread, otherwise deadlocks can occur with our UI.
+		self.MTAThreadQueue.put_nowait(self._onForegroundChange)
+
 	def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID):
 		if not self.MTAThreadInitEvent.isSet():
 			# UIAHandler hasn't finished initialising yet, so just ignore this event.
diff --git a/source/eventHandler.py b/source/eventHandler.py
--- a/source/eventHandler.py
+++ b/source/eventHandler.py
@@ -171,6 +171,10 @@ def doPreGainFocus(obj,sleepMode=False):
 			else:
 				newForeground=obj
 		api.setForegroundObject(newForeground)
+		import UIAHandler
+		if UIAHandler.isUIAAvailable and UIAHandler.handler:
+			# Notify UIAHandler of the new foreground so that it can deregister old events and register new ones 
+			UIAHandler.handler.onForegroundChange(newForeground.windowHandle)
 		executeEvent('foreground',newForeground)
 	if sleepMode: return True
 	#Fire focus entered events for all new ancestors of the focus if this is a gainFocus event


Test patch: