Skip to content

Commit bfbe2bf

Browse files
Support the Modern Comments side track pane in MS Word when not accessing MS Word documents via UIA (#12988)
Fixes #12982 Summary of the issue: 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. Description of how this pull request fixes the issue: 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. I.e. this UIA element is a control of a NetUI container embedded in an MS Word document. E.g. a control in the Modern Comments side track pane. Also, if there is a UIA focus event for the Word document root, even though we are not classing that window as native UIA, if the previously focused object was a control in a NetUI container embedded in the same Word document, then generate an MSAA focus event for the document, as MS Word will not do this itself. Note that allowing NVDA to track the focus with UIA for these embedded controls is enough to work around the issue, as this then means that focus never hits the Word document while the Comments side track pane should be focused, thus avoids making object model calls MS Word does not like or expect.
1 parent d7ca6c3 commit bfbe2bf

2 files changed

Lines changed: 73 additions & 1 deletion

File tree

source/_UIAHandler.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import threading
2727
import time
28+
import IAccessibleHandler.internalWinEventHandler
2829
import config
2930
import api
3031
import appModuleHandler
@@ -44,6 +45,10 @@
4445
import aria
4546

4647

48+
#: The window class name for Microsoft Word documents.
49+
# Microsoft Word's UI Automation implementation
50+
# also exposes this value as the document UIA element's classname property.
51+
MS_WORD_DOCUMENT_WINDOW_CLASS = "_WwG"
4752

4853
HorizontalTextAlignment_Left=0
4954
HorizontalTextAlignment_Centered=1
@@ -533,6 +538,15 @@ def IUIAutomationFocusChangedEventHandler_HandleFocusChangedEvent(self,sender):
533538
return
534539
self.lastFocusedUIAElement = sender
535540
if not self.isNativeUIAElement(sender):
541+
# #12982: This element may be the root of an MS Word document
542+
# for which we may be refusing to use UIA as its implementation may be incomplete.
543+
# However, there are some controls embedded in the MS Word document window
544+
# such as the Modern comments side track pane
545+
# for which we do have to use UIA.
546+
# But, if focus jumps from one of these controls back to the document (E.g. the user presses escape),
547+
# we receive no MSAA focus event, only a UIA focus event.
548+
# As we are not treating the Word doc as UIA, we need to manually fire an MSAA focus event on the document.
549+
self._emitMSAAFocusForWordDocIfNecessary(sender)
536550
if _isDebug():
537551
log.debug("HandleFocusChangedEvent: Ignoring for non native element")
538552
return
@@ -758,7 +772,7 @@ def _isUIAWindowHelper(self,hwnd):
758772
canUseOlderInProcessApproach = bool(appModule.helperLocalBindingHandle)
759773
if (
760774
# An MS Word document window
761-
windowClass=="_WwG"
775+
windowClass == MS_WORD_DOCUMENT_WINDOW_CLASS
762776
# Disabling is only useful if we can inject in-process (and use our older code)
763777
and canUseOlderInProcessApproach
764778
# Allow the user to explicitly force UIA support for MS Word documents
@@ -838,6 +852,55 @@ def getNearestWindowHandle(self,UIAElement):
838852
UIAElement._nearestWindowHandle=window
839853
return window
840854

855+
def _isNetUIEmbeddedInWordDoc(self, element: UIA.IUIAutomationElement) -> bool:
856+
"""
857+
Detects if the given UIA element represents a control in a NetUI container
858+
embedded within a MS Word document window.
859+
E.g. the Modern Comments side track pane.
860+
This method also caches the answer on the element itself
861+
to both speed up checking later and to allow checking on an already dead element
862+
E.g. a previous focus.
863+
"""
864+
if getattr(element, '_isNetUIEmbeddedInWordDoc', False):
865+
return True
866+
windowHandle = self.getNearestWindowHandle(element)
867+
if winUser.getClassName(windowHandle) != MS_WORD_DOCUMENT_WINDOW_CLASS:
868+
return False
869+
condition = UIAUtils.createUIAMultiPropertyCondition(
870+
{UIA.UIA_ClassNamePropertyId: 'NetUIHWNDElement'},
871+
{UIA.UIA_NativeWindowHandlePropertyId: windowHandle}
872+
)
873+
walker = self.clientObject.createTreeWalker(condition)
874+
cacheRequest = self.clientObject.createCacheRequest()
875+
cacheRequest.AddProperty(UIA.UIA_ClassNamePropertyId)
876+
cacheRequest.AddProperty(UIA.UIA_NativeWindowHandlePropertyId)
877+
ancestor = walker.NormalizeElementBuildCache(element, cacheRequest)
878+
# ancestor will either be the embedded NetUIElement, or just hit the root of the MS Word document window
879+
if ancestor.CachedClassName != 'NetUIHWNDElement':
880+
return False
881+
element._isNetUIEmbeddedInWordDoc = True
882+
return True
883+
884+
def _emitMSAAFocusForWordDocIfNecessary(self, element: UIA.IUIAutomationElement) -> None:
885+
"""
886+
Fires an MSAA focus event on the given UIA element
887+
if the element is the root of a Word document,
888+
and the focus was previously in a NetUI container embedded in this Word document.
889+
"""
890+
import NVDAObjects.UIA
891+
oldFocus = eventHandler.lastQueuedFocusObject
892+
if (
893+
isinstance(oldFocus, NVDAObjects.UIA.UIA)
894+
and getattr(oldFocus.UIAElement, '_isNetUIEmbeddedInWordDoc', False)
895+
and element.CachedClassName == MS_WORD_DOCUMENT_WINDOW_CLASS
896+
and element.CachedControlType == UIA.UIA_DocumentControlTypeId
897+
and self.getNearestWindowHandle(element) == oldFocus.windowHandle
898+
and not self.isUIAWindow(oldFocus.windowHandle)
899+
):
900+
IAccessibleHandler.internalWinEventHandler.winEventLimiter.addEvent(
901+
winUser.EVENT_OBJECT_FOCUS, oldFocus.windowHandle, winUser.OBJID_CLIENT, 0, oldFocus.windowThreadID
902+
)
903+
841904
def isNativeUIAElement(self,UIAElement):
842905
#Due to issues dealing with UIA elements coming from the same process, we do not class these UIA elements as usable.
843906
#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):
852915
if windowHandle:
853916
if self.isUIAWindow(windowHandle):
854917
return True
918+
# #12982: although NVDA by default may not treat this element's window as native UIA,
919+
# E.g. it is proxied from MSAA, or NVDA has specifically black listed it,
920+
# It may be an element from a NetUIcontainer embedded in a Word document,
921+
# such as the MS Word Modern Comments side track pane.
922+
# These elements are only exposed via UIA, and not MSAA,
923+
# thus we must treat these elements as native UIA.
924+
if self._isNetUIEmbeddedInWordDoc(UIAElement):
925+
return True
855926
if winUser.getClassName(windowHandle)=="DirectUIHWND" and "IEFRAME.dll" in UIAElement.cachedProviderDescription and UIAElement.currentClassName in ("DownloadBox", "accessiblebutton", "DUIToolbarButton", "PushButton"):
856927
# This is the IE 9 downloads list.
857928
# #3354: UiaHasServerSideProvider returns false for the IE 9 downloads list window,

user_docs/en/changes.t2t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ If you need this functionality please assign a gesture to the appropriate script
7373
- Fixed duplicate braille and speech when 'description' matches 'content' or 'name'. (#12888)
7474
- In MS Word with UIA enabled, more accurate playing of spelling error sounds as you type. (#12161)
7575
- In Windows 11, NVDA will no longer announce "pane" when pressing Alt+Tab to switch between programs. (#12648)
76+
- The new Modern Comments side track pane is now supported in MS Word when not accessing the document via UIA. Press alt+f12 to move between the side track pane and the document. (#12982)
7677
-
7778

7879

0 commit comments

Comments
 (0)