Skip to content

Per foreground uia property change events#8742

Merged
michaelDCurran merged 7 commits into
masterfrom
perForegroundUIAPropertyChangeEvents
Sep 14, 2018
Merged

Per foreground uia property change events#8742
michaelDCurran merged 7 commits into
masterfrom
perForegroundUIAPropertyChangeEvents

Conversation

@michaelDCurran

@michaelDCurran michaelDCurran commented Sep 13, 2018

Copy link
Copy Markdown
Member

Link to issue number:

Fixes #7345

Summary of the issue:

Sometimes UI Automation focus events stop firing across the Operating System, if a background app is currently firing a lot of MSAA events and at the same time is slow to respond to window messages. An example would be trying to use File Explorer while another app is showing a progress bar while processing audio.

Description of how this pull request fixes the issue:

NVDA now registers for UIA propertyChange events per foreground change, rather than globally at NVDA startup.

Testing performed:

Multiple users have been testing try build of this code on both Windows 7 and Windows 10. They have reported that the UIA focus events no longer disappear, and in some cases general performance has been improved.

Known issues with pull request:

None known so far.

Change log entry:

Bug fixes:
NVDA no longer fails to track focus in File Explorer and other applications using UI Automation when another app is busy (such as batch processing audio).

@LeonarddeR

Copy link
Copy Markdown
Collaborator

We need to fined a UIA app on Windows 10 to provde that NVDA is still picking up UIA propertyChange events for the foreground.

I assume you intended to say Windows 7 here?

@michaelDCurran

michaelDCurran commented Sep 13, 2018 via email

Copy link
Copy Markdown
Member Author

Comment thread source/_UIAHandler.py Outdated
# Windows 10 RS5 provides new performance features for UI Automation including event coalescing and connection recovery.
# Enable all of these where available.
if isinstance(self.clientObject,IUIAutomation6):
if False: #isinstance(self.clientObject,IUIAutomation6):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you intend this to stay? I guess a decision about coalescing has to be made here:

  1. Either use event coalescing with global property change event registration or coalescing with per foreground events. I'd like to opt in for the latter
  2. I assume this change can still considered somewhat risky. For easier debugging, could it help to add some additional config parameters to the UIA section? Like: "useEventCoalescingWhenAvailable" (defaults to True) and useGlobalUIAPropertyChangeEvents (defaults to False). This way, we can easily change defaults if one or both of the features causes problems and we don't have to necessarily revert the whole pr.

Comment thread source/_UIAHandler.py Outdated
while True:
func=self.MTAThreadQueue.get()
if func:
func()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a try/except handler help here? While you use exception handling in the only func that is currently queued, an unexpected failure can just bring down the whole thread.

Comment thread source/_UIAHandler.py
# The system should forget the old event registration itself.
pass
self.currentForegroundUIAElement=pendingForegroundUIAElement
try:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is event registration required if not handler.isNativeUIAElement(pendingForegroundUIAElement)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. UIA can be implemented per-window. Just because the foreground window does not support UIA, does not mean that one of its descendants does not.

Comment thread source/_UIAHandler.py
try:
self.clientObject.AddPropertyChangedEventHandler(self.currentForegroundUIAElement,TreeScope_Subtree,self.baseCacheRequest,self,UIAPropertyIdsToNVDAEventNames.keys())
except COMError:
log.error("Could not register for UIA property change events for new foreground")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re me previous comment, I assume this is only problematic for native UIA windows?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

problematic for any descendant native UIA windows, yes. Again, the foreground window itself may not natively support UIA. However, the bridged UIA element we get for the foreground window in this case is still enough for UIA core to register propertyChange events for the subtree.

Comment thread source/_UIAHandler.py Outdated
# The old UIAElement died as the window was closed.
# The system should forget the old event registration itself.
pass
self.currentForegroundUIAElement=pendingForegroundUIAElement

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to set pendingForegroundUIAElement to None here. My concern with the current implementation is that if onForegroundChange fails multiple times in a row to set the pendingForegroundUIAElement, the pendingForegroundUIAElement still refers to an old element for which unregistration is attempted multiple times as well.

@michaelDCurran michaelDCurran Sep 14, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Err... I would argue rather that currentForegroundUIAElement should be set to None if registration fails, not pendingForegroundUIAElement.
PendingForegroundUIAElement is always replaced with a new element before _foregroundChange is queued. CurrentForegroundUIAElement if it is left set will try to be unregistered multiple times.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you're actually correct here. Sorry for that, but it at least triggered you to do the right thing here :)

@michaelDCurran

michaelDCurran commented Sep 13, 2018 via email

Copy link
Copy Markdown
Member Author

Comment thread source/eventHandler.py Outdated
newForeground=obj
api.setForegroundObject(newForeground)
import UIAHandler
if UIAHandler.isUIAAvailable:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you want to check if UIAHandler.handler is not None. The current check will break NVDA if you call UIAHandler.terminate() for some reason.

…Handler's onForegroundChange as UIAHandler may be terminated.
…MTA thread, and ensure that propertyChange events are not unregistered from the same element multiple times if a new registration fails.
LeonarddeR
LeonarddeR previously approved these changes Sep 14, 2018

@LeonarddeR LeonarddeR left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with this, that is, if you believe that adding config parameters is not necessary at this point. I guess this just has to be tested in the wild to see whether it has any side effects we're currently not aware of.

@michaelDCurran

Copy link
Copy Markdown
Member Author

I have confirmed that UIA propertyChange events are successfully registering per foreground change on Windows 7.
Steps were:

  • Open Windows Explorer
  • Tab to the Command module tool bar
  • Right arrow to the preview checkbox. (This is a native UIA control).
  • Press space on this checkbox one or more times and make sure NVDA is announcing state and value changes.
  • Alt+tab to something else, and alt+tab back again.
  • Again press space one or more times to ensure that NVDA continuses to announce changes on this control.

@LeonarddeR

Copy link
Copy Markdown
Collaborator

I've experienced like ten complete crashes of explorer.exe while rapidly navigating into folders since this pr is merged. It would be good to know whether there are more people suffering from this. This is Windows 10.0.17134.280.

@michaelDCurran: Could we somehow check whether UIA elements died before throwing them at removePropertyChangedEventHandler or ADDPropertyChangedEventHandler?

@zstanecic

zstanecic commented Sep 14, 2018 via email

Copy link
Copy Markdown
Contributor

@beqabeqa473

beqabeqa473 commented Sep 14, 2018 via email

Copy link
Copy Markdown
Contributor

@LeonarddeR

Copy link
Copy Markdown
Collaborator

It does require some very rapid entering and backspacing as well as quick typing to utilize first letter navigation in folders.

@michaelDCurran

michaelDCurran commented Sep 14, 2018 via email

Copy link
Copy Markdown
Member Author

@LeonarddeR

LeonarddeR commented Sep 14, 2018

Copy link
Copy Markdown
Collaborator

There is no alt+tab involved at all. It mostly crashes when I open a folder and start typing pretty quickly after that:

Also, there are quite a lot of debug warnings in the log:

DEBUGWARNING - eventHandler.executeEvent (21:23:09.805):
error executing event: typedCharacter on <baseObject.Dynamic_UIItemListItemUIA object at 0x093981D0> with extra args of {'ch': u'\r'}
Traceback (most recent call last):
File "eventHandler.pyc", line 155, in executeEvent
File "eventHandler.pyc", line 92, in init
File "eventHandler.pyc", line 100, in next
File "NVDAObjects_init_.pyc", line 908, in event_typedCharacter
File "speech.pyc", line 661, in speakTypedCharacters
File "api.pyc", line 248, in isTypingProtected
File "baseObject.pyc", line 34, in get
File "baseObject.pyc", line 115, in getPropertyViaCache
File "NVDAObjects_init
.pyc", line 782, in _get_isProtected
File "baseObject.pyc", line 34, in get
File "baseObject.pyc", line 115, in getPropertyViaCache
File "NVDAObjects\UIA_init
.pyc", line 1067, in get_states
File "NVDAObjects\UIA_init
.pyc", line 734, in _prefetchUIACacheForPropertyIDs
COMError: (-2147220991, 'An event was unable to invoke any of the subscribers', (None, None, None, 0, None))

@beqabeqa473

beqabeqa473 commented Sep 17, 2018 via email

Copy link
Copy Markdown
Contributor

@michaelDCurran

Copy link
Copy Markdown
Member Author

I am reverting this pr for now. Annoyingly so far I have not been able to reproduce the crash myself.

@beqabeqa473

beqabeqa473 commented Sep 17, 2018 via email

Copy link
Copy Markdown
Contributor

@LeonarddeR

Copy link
Copy Markdown
Collaborator

Given the recent concerns about UIA performance issues, now we are at Python 3 and several Windows 10 releases ahead, would it be worthwile to reconsider this pull request? Cc @jcsteh @feerrenrut

@jcsteh

jcsteh commented Apr 20, 2020

Copy link
Copy Markdown
Contributor

While I think this is generally a good idea if it can be done reliably, note that this would not have fixed the issues with 1Password (#10508). In that case, the events were being fired by 1Password, which was in the foreground. Fixing that case generically requires NVDA to only listen to property change events for the focus, the focus ancestry and special cases like progress bars.

@codeofdusk

Copy link
Copy Markdown
Contributor

For whatever it's worth, I tried re-introducing this PR to latest master and the test case in #11002 still fails.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UIA seems to stall under certain conditions.

8 participants