Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for writing UI Automation Remote Operations natively in NVDA using Python #16214

Merged
merged 117 commits into from
May 29, 2024

Conversation

michaelDCurran
Copy link
Member

@michaelDCurran michaelDCurran commented Feb 22, 2024

Link to issue number:

None

Background

Several years ago Microsoft added a low-level API in Windows to allow the caller to execute multiple actions in a UI Automation remote provider via only one cross-process call.
Limiting the amount of cross-process calls is necessary for providing a responsive experience, especially for interacting with applications hosted in Windows Defender Application Guard (WDAG) or as a Remote Application Integrated Locally (RAIL). There are also scenarios such as searching for / listing headings in large documents, which can bennifit from limiting cross-process calls, even when the application is running on the local machine.
the Windows.UI.UIAutomation.Core.CoreAutomationRemoteOperation is a winRT interface that allows the caller to specify the actions as a string of byte code representing a specific set of instructions for a conceptual virtual machine which is executed within the remote provider. The instructions allow you to manipulate UI Automation elements, patterns and text ranges, as well as perform basic logic and collect information using ints, floats, strings, GUIDs and arrays created within the conceptual virtual machine.
Although these instructions are well-defined, public documentation is limited and programming with these raw instructions can be slow and complicated to read.
Microsoft released the Microsoft-UI-UIAutomation Remote Operations Library which provides some higher level ways of building and executing these instructions, including intermediate winRT interfaces, and high-level pure c++/winRT interfaces.
NV Access has made many prototypes with this library, and has made significant contributions to that project over the years.
Today, NVDA includes this library as a depedency, and currently uses it to access the new UI Automation Custom Extensions feature, which is required to access certain information in Microsoft Word. In theory, NVDA could make a much greater uuse of UI automation Remote Operations to improve performance across many parts of Windows and other applications.
However, there are several issues with the Microsoft UI Automation Remote Operations Library which slow down or limit greater usage in NVDA, including:

  • All remote operations logic must be written in C++ and not Python. This creates a large learning curve, if not a quite uncomfortable context switch at very least.
  • the Remote Operations Library seems to be no longer maintained by Microsoft.
  • The Remote Operations Library currently contributes an extra massive 16 minutes to NvDA's build time, as it must compile the whole of the C++/winRT API set.

Solution

This PR introduces a new remote operations library written in pure Python, which wraps the low-level CoreAutomationRemoteOperation winRT interfaces in Windows. This new library replaces the need for the older Microsoft Remote Operations Library, and therefore is no longer a dependency.

I had several initial goals in mind with this new library:

  1. To support our current usage of remote operations (namely UI Automation custom extensions: isExtension and CallExtension). If I could not achieve our current requirements, there would be no point continuing.
  2. To make a significant performance improvement to at least one extra action or scenario in NvDA (E.g. quick navigation by heading in Microsoft Word). This would prove that implementation of a fix / feature was easier with the new library, and would make it worth spending the initial time on it.

I was able to achieve both of these goals.

Description of user facing changes

  • In browse mode in Microsoft Word with UIA enabled, quick nav to headings, and listing headings in Elements list, is now up to 30 times faster. E.g. Jumping to a heading at the bottom of a 160 page document all the way from the top previously took ~4 seconds, now it takes ~0.13 seconds.

Description of development approach

  • A new UIAHandler._remoteOps package has been added, which interfaces with the low-level Windows Remote Operations API, providing higher-level APIs and types to aide in building and executing easy to read remote algorithms. See The NVDA remote operations readme for explanations and example code for all features.
  • UIAHandler.remote's msWord_getCustomAttributeValue has been re-written to use the new NVDA remote operations library.
  • Some extra functions were added to UIAHandler.remote, including findFirstHeadingInTextRange and collectAllHeadingsInTextRange, built on the NVDA remote ops library. UIA browse mode makes use of these functions on Windows 11, to majorly speed up jumping to / listing headings.

Testing strategy:

  • ~40 unit tests were added for the NVDA remote operations library, see tests/unit/test_UIARemoteOps/test_highLevel.py
  • In browse mode in Microsoft Word with UIA enabled, tested pressing h to jump to each heading in this document, also tested opening the Elements list and listing headings.

Known issues with pull request:

  • Documentation could be improved.

Code Review Checklist:

  • Documentation:
    • Change log entry
    • User Documentation
    • Developer / Technical Documentation
    • Context sensitive help for GUI changes
  • Testing:
    • Unit tests
    • System (end to end) tests
    • Manual testing
  • UX of all users considered:
    • Speech
    • Braille
    • Low Vision
    • Different web browsers
    • Localization in other languages / culture than English
  • API is compatible with existing add-ons.
  • Security precautions taken.

Summary by CodeRabbit

  • Chores

    • Removed the microsoft-ui-uiautomation submodule and its references from the project.
  • New Features

    • Introduced functionality for creating, manipulating, and retrieving results from remote operations in UI Automation.
    • Added support for building and managing remote operations within NVDA.
    • Enhanced local execution of operations with new classes and functions.
    • Implemented a function for iterating over text units in a remote text range.
  • Documentation

    • Updated readme files to reflect the removal of microsoft-ui-uiautomation and added guidelines for new remote operations features.

… we now use the lowlevel Remote Ops functionality in Windows directly.
…ext manager like IfBlock. EloseBlock must only appear directly after the closing of IfBlock.
…g an element / textRange, and may do it sown separate cross-process call. Not worth it.
…a function to execute, plus objects to import, and returns the objects included in results. The function takes a RemoteFuncAPI instance, and any remoted objects as arguments, and is epected to return any remote objects that should be included in the results.
…a 'set' method to all remote objects. Add ability for methods to cache remoted local constants to reduce amount of instructions needed.
…types to make instruction dumps a little more readable.
* Instruction commenting
* build caching
* If remoteLogging argument is different to the build cache, then the build cache is not used.
* dumpInstructions is now its own keyword argument. If true the build cache is not used, and an instruction dump is logged. and the build cache is still updated.
…ogic in highLevel.execute. Also move all the exception classes into highLevel.
…__ so it can be captured like: with rfa.catchBlock() as e: and the operation status is cleared before executing the catch body, thus now catchBlock correctly stops the error from causing the entire operation from failing.
…adding comments and meta commands. Importing of elements and textRanges are now printed as a meta command.
@AppVeyorBot
Copy link

  • Build execution time has reached the maximum allowed time for your plan (60 minutes).

See test results for failed build of commit 8e47706e76

@seanbudd seanbudd removed this from the 2024.3 milestone May 24, 2024
@michaelDCurran michaelDCurran marked this pull request as ready for review May 27, 2024 21:54
Co-authored-by: Luke Davis <8139760+XLTechie@users.noreply.github.com>
source/UIAHandler/remote.py Show resolved Hide resolved
class _TypedInstruction(InstructionBase):

@property
def params(self):
Copy link
Member

Choose a reason for hiding this comment

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

can you add a return type here

@AppVeyorBot
Copy link

See test results for failed build of commit 04d14a7bc7

@seanbudd seanbudd merged commit 7e31f30 into master May 29, 2024
3 checks passed
@seanbudd seanbudd deleted the remoteOpsLowLevel branch May 29, 2024 00:43
@cary-rowen
Copy link
Contributor

cc @mltony
This PR is merged, will it have a positive impact on style navigation in MSWord?

@dkager
Copy link
Collaborator

dkager commented May 29, 2024

It's great to see this coming to master, thank you very much!
Since on older versions of Windows the remote ops will not be available, am I correct to assume that the DLL will still be required? If so, it would be nice to also work on #16217. If not, that issue can be closed.

@mltony
Copy link
Contributor

mltony commented Jun 2, 2024

@cary-rowen - that's actually not up to me - the problem is that textInfo implementation is too slow in MSWord in current implementation. If this PR would allow faster textInfo then yes. But I don't understand low level UIA enough to tell whetehr this will allow to speed up TextInfo enough.

@michaelDCurran
Copy link
Member Author

michaelDCurran commented Jun 2, 2024 via email

@Adriani90
Copy link
Collaborator

I think this needs a changelog entry for developers. right?
And also maybe an internal design doc in the project docs? Or maybe including it here?
https://github.com/nvaccess/nvda/wiki/internals

cc: @jcsteh

@seanbudd
Copy link
Member

The docs here are pretty thorough, do you think anything is missing?
https://github.com/nvaccess/nvda/blob/master/source/UIAHandler/_remoteOps/readme.md

That wiki page is pretty out of date, we have goals of moving anything relevant into projectDocs/dev/ like the current up-to-date technical design overview

@Adriani90
Copy link
Collaborator

Adriani90 commented Jun 26, 2024 via email

@seanbudd
Copy link
Member

Would you open an issue to track any remaining work you think there is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
conceptApproved Similar 'triaged' for issues, PR accepted in theory, implementation needs review. merge-early Merge Early in a developer cycle
Projects
None yet
Development

Successfully merging this pull request may close these issues.