Repository: nvaccess/nvda

Issue:
MS Word: comments inaccessible to NVDA via object model from build 13901
### Background
In build 13901, MS Word introduced "Modern comments" which allows comment threads (I.e. an initial comment plus replies) and the ability to resolve or reopen threads. The new UI to do this is exposed as a new comments side pane which shows the comments and replies in a treeview. This UI is only exposed via UI Automation.
 This UI is supposed to completely replace the older Comments story in a document.
As this new UI is imbedded within the document, it shares the same window handle. If NvDA is using the object model to access the document (I.e. the UIA provider of the document's window is ignored) then NVDA never sees the modern comments UI at all. It seems that MS Word does not ever proxy this to MSAA either.
 Also, if MS Word detects that something is trying to access the comments via the object model in the old way (E.g. calls comment.range) then the Modern comments UI is disabled (hidden) and MS Word ends up in a state where querying the range at the selection of the object model is no longer in the comments story, yet the UI is not shown either. This MS Word bug is known to Microsoft however they have no plans or willingness to fix it. Their only recommended solution is to stop using the object model and just use UIA.

### Steps to reproduce:
Using NVDA 2021.2 with the Use UIA in MS Word documents NVDA setting disabled,
Open MS Word build 13901 or above.
Create a new blank document.
Type some text.
Select 1 or more characters, and choose New Comment from the context menu.

### Actual behavior:
After the context menu closes, NVDA reports the focus is back in the document at the position of the selected text.
Pressing alt+f12 to get to the Comments side pane does not work.
There is no way to get into the Comments story of the document either.

### Expected behavior:
After leaving the context menu, one of the following is expected:
* Focus should be in the comments side pane, allowing the user to type the comment and post or cancel it, or
* Focus should be on the document but the selection should be in the older Comments story, allowing you to type the comment.

### ideas for a fix
If MS Word build 13901 or higher:
1. `NVDAObjects.window.winword.WordDocumentTextInfo` (I.e. text info for MS Word object model): Don't fetch text or manipulate the selection in any way if selection.storyType is wdCommentsStory.
2. in UIAHandler.handler.isUIAElement: return true if the element's windowHandle is _WwG but an ancestor of the element has a UIA className of NetUIHWNDElement.

If we were able to successfully fix this issue for NVDA 2021.3, then we could push back UIA by default, and also the new support for math in UIA to 2022.1, ensuring that they get more testing time.

Note also that we can only support math from build 14326 onwards, yet modern comments were introduced in build 13901. Thus builds within this period still require this fix.
 
### System configuration
#### NVDA installed/portable/running from source:
Installed
#### NVDA version:
2021.2
#### Windows version:
Windows 11 build 22483. 
#### Name and version of other software in use when reproducing the issue:
Microsoft® Word for Microsoft 365 MSO (Version 2111 Build 16.0.14613.20000) 64-bit 
#### Other information about your system:

### Other questions
#### Does the issue still occur after restarting your computer?
yes.
#### Have you tried any other versions of NVDA? If so, please report their behaviors.
Recent builds of NVDA alpha (approaching 2021.3) use UIA by defauitl in MS Word from build 13901 upwards to avoid this issue. However, there are still some other issues relating to UIA support which may block UIA by default in NVDA 2021.3.

#### If add-ons are disabled, is your problem still occurring?
Yes.
#### Does the issue still occur after you run the COM Registration Fixing Tool in NVDA's tools menu?
Yes.



Discussion:

Resolving patch:
diff --git a/source/_UIAHandler.py b/source/_UIAHandler.py
--- a/source/_UIAHandler.py
+++ b/source/_UIAHandler.py
@@ -25,6 +25,7 @@
 
 import threading
 import time
+import IAccessibleHandler.internalWinEventHandler
 import config
 import api
 import appModuleHandler
@@ -44,6 +45,10 @@
 import aria
 
 
+#: The window class name for Microsoft Word documents.
+# Microsoft Word's UI Automation implementation
+# also exposes this value as the document UIA element's classname property.
+MS_WORD_DOCUMENT_WINDOW_CLASS = "_WwG"
 
 HorizontalTextAlignment_Left=0
 HorizontalTextAlignment_Centered=1
@@ -533,6 +538,15 @@ def IUIAutomationFocusChangedEventHandler_HandleFocusChangedEvent(self,sender):
 			return
 		self.lastFocusedUIAElement = sender
 		if not self.isNativeUIAElement(sender):
+			# #12982: This element may be the root of an MS Word document
+			# for which we may be refusing to use UIA as its implementation may be incomplete.
+			# However, there are some controls embedded in the MS Word document window
+			# such as the Modern comments side track pane
+			# for which we do have to use UIA.
+			# But, if focus jumps from one of these controls back to the document (E.g. the user presses escape),
+			# we receive no MSAA focus event, only a UIA focus event.
+			# As we are not treating the Word doc as UIA, we need to manually fire an MSAA focus event on the document.
+			self._emitMSAAFocusForWordDocIfNecessary(sender)
 			if _isDebug():
 				log.debug("HandleFocusChangedEvent: Ignoring for non native element")
 			return
@@ -758,7 +772,7 @@ def _isUIAWindowHelper(self,hwnd):
 			canUseOlderInProcessApproach = bool(appModule.helperLocalBindingHandle)
 			if (
 				# An MS Word document window 
-				windowClass=="_WwG" 
+				windowClass == MS_WORD_DOCUMENT_WINDOW_CLASS
 				# Disabling is only useful if we can inject in-process (and use our older code)
 				and canUseOlderInProcessApproach
 				# Allow the user to explicitly force UIA support for MS Word documents
@@ -838,6 +852,55 @@ def getNearestWindowHandle(self,UIAElement):
 		UIAElement._nearestWindowHandle=window
 		return window
 
+	def _isNetUIEmbeddedInWordDoc(self, element: UIA.IUIAutomationElement) -> bool:
+		"""
+		Detects if the given UIA element represents a control in a NetUI container
+		embedded within a MS Word document window.
+		E.g. the Modern Comments side track pane.
+		This method also caches the answer on the element itself
+		to both speed up checking later and to allow checking on an already dead element
+		E.g. a previous focus.
+		"""
+		if getattr(element, '_isNetUIEmbeddedInWordDoc', False):
+			return True
+		windowHandle = self.getNearestWindowHandle(element)
+		if winUser.getClassName(windowHandle) != MS_WORD_DOCUMENT_WINDOW_CLASS:
+			return False
+		condition = UIAUtils.createUIAMultiPropertyCondition(
+			{UIA.UIA_ClassNamePropertyId: 'NetUIHWNDElement'},
+			{UIA.UIA_NativeWindowHandlePropertyId: windowHandle}
+		)
+		walker = self.clientObject.createTreeWalker(condition)
+		cacheRequest = self.clientObject.createCacheRequest()
+		cacheRequest.AddProperty(UIA.UIA_ClassNamePropertyId)
+		cacheRequest.AddProperty(UIA.UIA_NativeWindowHandlePropertyId)
+		ancestor = walker.NormalizeElementBuildCache(element, cacheRequest)
+		# ancestor will either be the embedded NetUIElement, or just hit the root of the MS Word document window
+		if ancestor.CachedClassName != 'NetUIHWNDElement':
+			return False
+		element._isNetUIEmbeddedInWordDoc = True
+		return True
+
+	def _emitMSAAFocusForWordDocIfNecessary(self, element: UIA.IUIAutomationElement) -> None:
+		"""
+		Fires an MSAA focus event on the given UIA element
+		if the element is the root of a Word document,
+		and the focus was previously in a NetUI container embedded in this Word document.
+		"""
+		import NVDAObjects.UIA
+		oldFocus = eventHandler.lastQueuedFocusObject
+		if (
+			isinstance(oldFocus, NVDAObjects.UIA.UIA)
+			and getattr(oldFocus.UIAElement, '_isNetUIEmbeddedInWordDoc', False)
+			and element.CachedClassName == MS_WORD_DOCUMENT_WINDOW_CLASS
+			and element.CachedControlType == UIA.UIA_DocumentControlTypeId
+			and self.getNearestWindowHandle(element) == oldFocus.windowHandle
+			and not self.isUIAWindow(oldFocus.windowHandle)
+		):
+			IAccessibleHandler.internalWinEventHandler.winEventLimiter.addEvent(
+				winUser.EVENT_OBJECT_FOCUS, oldFocus.windowHandle, winUser.OBJID_CLIENT, 0, oldFocus.windowThreadID
+			)
+
 	def isNativeUIAElement(self,UIAElement):
 		#Due to issues dealing with UIA elements coming from the same process, we do not class these UIA elements as usable.
 		#It seems to be safe enough to retreave the cached processID, but using tree walkers or fetching other properties causes a freeze.
@@ -852,6 +915,14 @@ def isNativeUIAElement(self,UIAElement):
 		if windowHandle:
 			if self.isUIAWindow(windowHandle):
 				return True
+			# #12982: although NVDA by default may not treat this element's window as native UIA,
+			# E.g. it is proxied from MSAA, or NVDA has specifically black listed it,
+			# It may be an element from a NetUIcontainer embedded in a Word document,
+			# such as the MS Word Modern Comments side track pane.
+			# These elements are only exposed via UIA, and not MSAA,
+			# thus we must treat these elements as native UIA.
+			if self._isNetUIEmbeddedInWordDoc(UIAElement):
+				return True
 			if winUser.getClassName(windowHandle)=="DirectUIHWND" and "IEFRAME.dll" in UIAElement.cachedProviderDescription and UIAElement.currentClassName in ("DownloadBox", "accessiblebutton", "DUIToolbarButton", "PushButton"):
 				# This is the IE 9 downloads list.
 				# #3354: UiaHasServerSideProvider returns false for the IE 9 downloads list window,


Test patch: