Skip to content

Commit 6fdabfe

Browse files
authored
Disable UIA text change events outside of Word, Windows Console, and Windows Terminal (#14067)
Mitigation for #11002. Blocking #14047. Summary of the issue: UIA textChange NVDA events are seldom (if ever) used outside of a few specific situations, but have an extreme performance impact (see #11002). Description of user facing changes Improved performance/less chance of NVDA hanging in UIA applications. Description of development approach Explicitly do not process UIA textChange events outside of Windows Console, Terminal, and Word. The eventual end goal is to remove TermControl/TermControl2 from UIAHandler.textChangeUIAClassNames in #14047, which will very greatly improve performance in Windows Terminal. (conhost will remain, as there don't seem to be any plans to add notifications, especially as wt is becoming the default). I'm very reluctant to add a mechanism by which add-ons/app modules can request textChange events unless someone requests it, especially given #11002.
1 parent a4644ef commit 6fdabfe

2 files changed

Lines changed: 77 additions & 5 deletions

File tree

source/UIAHandler/__init__.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2008-2021 NV Access Limited, Joseph Lee, Babbage B.V., Leonard de Ruijter, Bill Dengler
2+
# Copyright (C) 2008-2022 NV Access Limited, Joseph Lee, Babbage B.V., Leonard de Ruijter, Bill Dengler
33
# This file is covered by the GNU General Public License.
44
# See the file COPYING for more details.
55

@@ -99,6 +99,16 @@
9999
"Shell_SystemDialog", # Various dialogs in Windows 10 Settings app
100100
]
101101

102+
textChangeUIAAutomationIDs = (
103+
"Text Area", # Windows Console Host
104+
)
105+
106+
textChangeUIAClassNames = (
107+
"_WwG", # Microsoft Word
108+
"TermControl",
109+
"TermControl2"
110+
)
111+
102112
NVDAUnitsToUIAUnits: Dict[str, int] = {
103113
textInfos.UNIT_CHARACTER: UIA.TextUnit_Character,
104114
textInfos.UNIT_WORD: UIA.TextUnit_Word,
@@ -206,11 +216,9 @@
206216
autoSelectDetectionAvailable = False
207217
if winVersion.getWinVer() >= winVersion.WIN10:
208218
UIAEventIdsToNVDAEventNames.update({
209-
UIA.UIA_Text_TextChangedEventId: "textChange",
210219
UIA.UIA_Text_TextSelectionChangedEventId: "caret",
211220
})
212221
localEventHandlerGroupUIAEventIds.update({
213-
UIA.UIA_Text_TextChangedEventId,
214222
UIA.UIA_Text_TextSelectionChangedEventId,
215223
})
216224
autoSelectDetectionAvailable = True
@@ -268,6 +276,7 @@ def __init__(self):
268276
super(UIAHandler,self).__init__()
269277
self.globalEventHandlerGroup = None
270278
self.localEventHandlerGroup = None
279+
self.localEventHandlerGroupWithTextChanges = None
271280
self._localEventHandlerGroupElements = set()
272281
self.MTAThreadInitEvent=threading.Event()
273282
self.MTAThreadQueue = Queue()
@@ -403,6 +412,17 @@ def _registerGlobalEventHandlers(self):
403412
self.baseCacheRequest,
404413
self
405414
)
415+
if (
416+
not utils._shouldSelectivelyRegister()
417+
and winVersion.getWinVer() >= winVersion.WIN10
418+
):
419+
# #14067: Due to poor performance, textChange requires special handling
420+
self.globalEventHandlerGroup.AddAutomationEventHandler(
421+
UIA.UIA_Text_TextChangedEventId,
422+
UIA.TreeScope_Subtree,
423+
self.baseCacheRequest,
424+
self
425+
)
406426
# #7984: add support for notification event (IUIAutomation5, part of Windows 10 build 16299 and later).
407427
if isinstance(self.clientObject, UIA.IUIAutomation5):
408428
self.globalEventHandlerGroup.AddNotificationEventHandler(
@@ -421,21 +441,41 @@ def _registerGlobalEventHandlers(self):
421441
def _createLocalEventHandlerGroup(self):
422442
if isinstance(self.clientObject, UIA.IUIAutomation6):
423443
self.localEventHandlerGroup = self.clientObject.CreateEventHandlerGroup()
444+
self.localEventHandlerGroupWithTextChanges = self.clientObject.CreateEventHandlerGroup()
424445
else:
425446
self.localEventHandlerGroup = utils.FakeEventHandlerGroup(self.clientObject)
447+
self.localEventHandlerGroupWithTextChanges = utils.FakeEventHandlerGroup(self.clientObject)
426448
self.localEventHandlerGroup.AddPropertyChangedEventHandler(
427449
UIA.TreeScope_Ancestors | UIA.TreeScope_Element,
428450
self.baseCacheRequest,
429451
self,
430452
*self.clientObject.IntSafeArrayToNativeArray(localEventHandlerGroupUIAPropertyIds)
431453
)
454+
self.localEventHandlerGroupWithTextChanges.AddPropertyChangedEventHandler(
455+
UIA.TreeScope_Ancestors | UIA.TreeScope_Element,
456+
self.baseCacheRequest,
457+
self,
458+
*self.clientObject.IntSafeArrayToNativeArray(localEventHandlerGroupUIAPropertyIds)
459+
)
432460
for eventId in localEventHandlerGroupUIAEventIds:
433461
self.localEventHandlerGroup.AddAutomationEventHandler(
434462
eventId,
435463
UIA.TreeScope_Ancestors | UIA.TreeScope_Element,
436464
self.baseCacheRequest,
437465
self
438466
)
467+
self.localEventHandlerGroupWithTextChanges.AddAutomationEventHandler(
468+
eventId,
469+
UIA.TreeScope_Ancestors | UIA.TreeScope_Element,
470+
self.baseCacheRequest,
471+
self
472+
)
473+
self.localEventHandlerGroupWithTextChanges.AddAutomationEventHandler(
474+
UIA.UIA_Text_TextChangedEventId,
475+
UIA.TreeScope_Ancestors | UIA.TreeScope_Element,
476+
self.baseCacheRequest,
477+
self
478+
)
439479

440480
def addEventHandlerGroup(self, element, eventHandlerGroup):
441481
if isinstance(eventHandlerGroup, UIA.IUIAutomationEventHandlerGroup):
@@ -466,7 +506,23 @@ def func():
466506
if not isStillFocus:
467507
return
468508
try:
469-
self.addEventHandlerGroup(element, self.localEventHandlerGroup)
509+
if (
510+
element.currentClassName in textChangeUIAClassNames
511+
or element.CachedAutomationID in textChangeUIAAutomationIDs
512+
):
513+
group = self.localEventHandlerGroupWithTextChanges
514+
logPrefix = "Explicitly"
515+
else:
516+
group = self.localEventHandlerGroup
517+
logPrefix = "Not"
518+
519+
if _isDebug():
520+
log.debugWarning(
521+
f"{logPrefix} registering for textChange events from UIA element "
522+
f"with class name {repr(element.currentClassName)} "
523+
f"and automation ID {repr(element.CachedAutomationID)}"
524+
)
525+
self.addEventHandlerGroup(element, group)
470526
except COMError:
471527
log.error("Could not register for UIA events for element", exc_info=True)
472528
else:
@@ -500,7 +556,22 @@ def IUIAutomationEventHandler_HandleAutomationEvent(self,sender,eventID):
500556
if _isDebug():
501557
log.debug("HandleAutomationEvent: Ignored MenuOpenedEvent while focus event pending")
502558
return
503-
NVDAEventName=UIAEventIdsToNVDAEventNames.get(eventID,None)
559+
if eventID == UIA.UIA_Text_TextChangedEventId:
560+
if (
561+
sender.currentClassName in textChangeUIAClassNames
562+
or sender.CachedAutomationID in textChangeUIAAutomationIDs
563+
):
564+
NVDAEventName = "textChange"
565+
else:
566+
if _isDebug():
567+
log.debugWarning(
568+
"HandleAutomationEvent: Dropping textChange event from element "
569+
f"with class name {repr(sender.currentClassName)} "
570+
f"and automation ID {repr(sender.CachedAutomationID)}"
571+
)
572+
return
573+
else:
574+
NVDAEventName = UIAEventIdsToNVDAEventNames.get(eventID, None)
504575
if not NVDAEventName:
505576
if _isDebug():
506577
log.debugWarning(f"HandleAutomationEvent: Don't know how to handle event {eventID}")

user_docs/en/changes.t2t

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ What's New in NVDA
4747
Please refer to [the developer guide https://www.nvaccess.org/files/nvda/documentation/developerGuide.html#API] for information on NVDA's API deprecation and removal process.
4848

4949
- The [NVDA API Announcement mailing list https://groups.google.com/a/nvaccess.org/g/nvda-api/about] was created. (#13999)
50+
- NVDA no longer processes ``textChange`` events for most UI Automation applications due to their extreme negative performance impact. (#11002, #14067)
5051
-
5152

5253

0 commit comments

Comments
 (0)