Skip to content

Commit

Permalink
Use UIA in MS Word by default on MS Word version 16.0.15000 or higher (
Browse files Browse the repository at this point in the history
…#13437)

Microsoft Word 2016 exposes a rich UI Automation implementation. For some time now, users have been able to optionally turn this on with an advanced setting. NVDA's support for MS Word via UIA has major performance advantages over the older object model support, so NVDA should use the UIA support by default where available. However, as the UIA implementation improved throughout Office 2016's lifetime, we should only enable our support for recent builds of Office 2016, specifically for build 15000 and higher, and when only on Windows 11.
This was previously tried in pr #12770 but reverted in pr #12989
The main argument for reverting was the lack of math support, and ability to report line column and section numbers.
These have all been since addressed.

Description of how this pull request fixes the issue:
NVDA now uses UI Automation to access Microsoft Word document controls by default when on Windows 11, for Microsoft Word version 16.0.15000 and higher.
The Use UI Automation to access Microsoft Word document controls when available checkbox has been replaced with a combo box with the following values:
• Default (where suitable)
• Only where necessary: where the Microsoft Word object model is not available at all
• Where suitable: Windows 11 / Microsoft Word version 16.0.15000 or higher, or where the Microsoft Word object model is unavailable
• Always: where ever UI automation is available in Microsoft word (no matter how complete).
If the older checkbox was previously checked, the setting will be set to Always.
  • Loading branch information
michaelDCurran committed Mar 8, 2022
1 parent aa7049d commit 0d9a280
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 49 deletions.
73 changes: 42 additions & 31 deletions source/UIAHandler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
import time
import IAccessibleHandler.internalWinEventHandler
import config
from config import (
AllowUiaInChromium,
AllowUiaInMSWord,
)
import api
import appModuleHandler
import controlTypes
Expand Down Expand Up @@ -222,18 +226,35 @@
ignoreWinEventsMap[id] = [0]


class AllowUiaInChromium(Enum):
_DEFAULT = 0 # maps to 'when necessary'
WHEN_NECESSARY = 1 # the current default
YES = 2
NO = 3

@staticmethod
def getConfig() -> 'AllowUiaInChromium':
allow = AllowUiaInChromium(config.conf['UIA']['allowInChromium'])
if allow == AllowUiaInChromium._DEFAULT:
return AllowUiaInChromium.WHEN_NECESSARY
return allow
def shouldUseUIAInMSWord(appModule: appModuleHandler.AppModule) -> bool:
allow = AllowUiaInMSWord.getConfig()
if allow == AllowUiaInMSWord.ALWAYS:
log.debug("User has requested UIA in MS Word always")
return True
canUseOlderInProcessApproach = bool(appModule.helperLocalBindingHandle)
if not canUseOlderInProcessApproach:
log.debug("Using UIA in MS Word as no alternative object model available")
return True
if winVersion.getWinVer() < winVersion.WIN11:
log.debug("Not using UIA in MS Word on pre Windows 11 OS due to missing custom extensions")
return False
if allow != AllowUiaInMSWord.WHERE_SUITABLE:
log.debug("User does not want UIA in MS Word unless necessary")
return False
isOfficeApp = appModule.productName.startswith(("Microsoft Office", "Microsoft Outlook"))
if not isOfficeApp:
log.debug(f"Unknown Office app: {appModule.productName}")
return False
try:
officeVersion = tuple(int(x) for x in appModule.productVersion.split('.')[:3])
except Exception:
log.debugWarning(f"Unable to parse office version: {appModule.productVersion}", exc_info=True)
return False
if officeVersion < (16, 0, 15000):
log.debug(f"MS word too old for suitable UIA, Office version: {officeVersion}")
return False
log.debug(f"Using UIA due to suitable Office version: {officeVersion}")
return True


class UIAHandler(COMObject):
Expand Down Expand Up @@ -765,25 +786,15 @@ def _isUIAWindowHelper(self,hwnd):
# Ask the window if it supports UIA natively
res=windll.UIAutomationCore.UiaHasServerSideProvider(hwnd)
if res:
# The window does support UIA natively, but MS Word documents now
# have a fairly usable UI Automation implementation.
# However, builds of MS Office 2016 before build 9000 or so had bugs which
# we cannot work around.
# And even current builds of Office 2016 are still missing enough info from
# UIA that it is still impossible to switch to UIA completely.
# Therefore, if we can inject in-process, refuse to use UIA and instead
# fall back to the MS Word object model.
canUseOlderInProcessApproach = bool(appModule.helperLocalBindingHandle)
if (
# An MS Word document window
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
# no matter the Office version
and not config.conf['UIA']['useInMSWordWhenAvailable']
):
return False
if windowClass == MS_WORD_DOCUMENT_WINDOW_CLASS:
# The window does support UIA natively, but MS Word documents now
# have a fairly usable UI Automation implementation.
# However, builds of MS Office 2016 before build 15000 or so had bugs which
# we cannot work around.
# Therefore, if we can inject in-process, refuse to use UIA and instead
# fall back to the MS Word object model.
if not shouldUseUIAInMSWord(appModule):
return False
# MS Excel spreadsheets now have a fairly usable UI Automation implementation.
# However, builds of MS Office 2016 before build 9000 or so had bugs which we
# cannot work around.
Expand Down
28 changes: 28 additions & 0 deletions source/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1327,3 +1327,31 @@ def exit(self):

def __exit__(self, excType, excVal, traceback):
self.exit()


class AllowUiaInChromium(Enum):
_DEFAULT = 0 # maps to 'when necessary'
WHEN_NECESSARY = 1 # the current default
YES = 2
NO = 3

@staticmethod
def getConfig() -> 'AllowUiaInChromium':
allow = AllowUiaInChromium(conf['UIA']['allowInChromium'])
if allow == AllowUiaInChromium._DEFAULT:
return AllowUiaInChromium.WHEN_NECESSARY
return allow


class AllowUiaInMSWord(Enum):
_DEFAULT = 0 # maps to 'where suitable'
WHEN_NECESSARY = 1
WHERE_SUITABLE = 2
ALWAYS = 3

@staticmethod
def getConfig() -> 'AllowUiaInMSWord':
allow = AllowUiaInMSWord(conf['UIA']['allowInMSWord'])
if allow == AllowUiaInMSWord._DEFAULT:
return AllowUiaInMSWord.WHERE_SUITABLE
return allow
5 changes: 3 additions & 2 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#: provide an upgrade step (@see profileUpgradeSteps.py). An upgrade step does not need to be added when
#: just adding a new element to (or removing from) the schema, only when old versions of the config
#: (conforming to old schema versions) will not work correctly with the new schema.
latestSchemaVersion = 5
latestSchemaVersion = 6

#: The configuration specification string
#: @type: String
Expand Down Expand Up @@ -229,12 +229,13 @@
[UIA]
enabled = boolean(default=true)
useInMSWordWhenAvailable = boolean(default=false)
useInMSExcelWhenAvailable = boolean(default=false)
winConsoleImplementation= option("auto", "legacy", "UIA", default="auto")
selectiveEventRegistration = boolean(default=false)
# 0:default, 1:Only when necessary, 2:yes, 3:no
allowInChromium = integer(0, 3, default=0)
# 0:default (where suitable), 1:Only when necessary, 2: where suitable, 3: always
allowInMSWord = integer(0, 3, default=0)
[annotations]
reportDetails = boolean(default=true)
Expand Down
15 changes: 15 additions & 0 deletions source/config/profileUpgradeSteps.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from logHandler import log


def upgradeConfigFrom_0_to_1(profile):
# Schema has been modified to set a new minimum blink rate
# The blink rate could previously be set to zero to disable blinking (while still
Expand Down Expand Up @@ -84,3 +85,17 @@ def upgradeConfigFrom_4_to_5(profile):
except KeyError:
# Setting does not exist, no need for upgrade of this setting
log.debug("reportDetails not present, no action taken.")


def upgradeConfigFrom_5_to_6(profile: dict):
"""
useInMSWordWhenAvailable in UIA section has been replaced with allowInMSWord multichoice.
"""
try:
useInMSWord = profile['UIA']['useInMSWordWhenAvailable']
del profile['UIA']['useInMSWordWhenAvailable']
except KeyError:
useInMSWord = False
if useInMSWord:
from . import AllowUiaInMSWord
profile['UIA']['allowInMSWord'] = AllowUiaInMSWord.ALWAYS.value
33 changes: 23 additions & 10 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2652,13 +2652,26 @@ def __init__(self, parent):
self._getDefaultValue(["UIA", "selectiveEventRegistration"])
)

# Translators: This is the label for a checkbox in the
# Advanced settings panel.
label = _("Use UI Automation to access Microsoft &Word document controls when available")
self.UIAInMSWordCheckBox = UIAGroup.addItem(wx.CheckBox(UIABox, label=label))
self.bindHelpEvent("AdvancedSettingsUseUiaForWord", self.UIAInMSWordCheckBox)
self.UIAInMSWordCheckBox.SetValue(config.conf["UIA"]["useInMSWordWhenAvailable"])
self.UIAInMSWordCheckBox.defaultValue = self._getDefaultValue(["UIA", "useInMSWordWhenAvailable"])
label = pgettext(
"advanced.uiaWithMSWord",
# Translators: Label for the Use UIA with MS Word combobox, in the Advanced settings panel.
"Use UI Automation to access Microsoft Word document controls"
)
wordChoices = (
# Translators: Label for the default value of the Use UIA with MS Word combobox,
# in the Advanced settings panel.
pgettext("advanced.uiaWithMSWord", "Default (Where suitable)"),
# Translators: Label for a value in the Use UIA with MS Word combobox, in the Advanced settings panel.
pgettext("advanced.uiaWithMSWord", "Only when necessary"),
# Translators: Label for a value in the Use UIA with MS Word combobox, in the Advanced settings panel.
pgettext("advanced.uiaWithMSWord", "Where suitable"),
# Translators: Label for a value in the Use UIA with MS Word combobox, in the Advanced settings panel.
pgettext("advanced.uiaWithMSWord", "Always"),
)
self.UIAInMSWordCombo = UIAGroup.addLabeledControl(label, wx.Choice, choices=wordChoices)
self.bindHelpEvent("MSWordUIA", self.UIAInMSWordCombo)
self.UIAInMSWordCombo.SetSelection(config.conf["UIA"]["allowInMSWord"])
self.UIAInMSWordCombo.defaultValue = self._getDefaultValue(["UIA", "allowInMSWord"])

# Translators: This is the label for a checkbox in the
# Advanced settings panel.
Expand Down Expand Up @@ -2965,7 +2978,7 @@ def haveConfigDefaultsBeenRestored(self):
self.selectiveUIAEventRegistrationCheckBox.IsChecked()
== self.selectiveUIAEventRegistrationCheckBox.defaultValue
)
and self.UIAInMSWordCheckBox.IsChecked() == self.UIAInMSWordCheckBox.defaultValue
and self.UIAInMSWordCombo.GetSelection() == self.UIAInMSWordCombo.defaultValue
and self.UIAInMSExcelCheckBox.IsChecked() == self.UIAInMSExcelCheckBox.defaultValue
and self.ConsoleUIACheckBox.IsChecked() == (self.ConsoleUIACheckBox.defaultValue == 'UIA')
and self.cancelExpiredFocusSpeechCombo.GetSelection() == self.cancelExpiredFocusSpeechCombo.defaultValue
Expand All @@ -2985,7 +2998,7 @@ def haveConfigDefaultsBeenRestored(self):
def restoreToDefaults(self):
self.scratchpadCheckBox.SetValue(self.scratchpadCheckBox.defaultValue)
self.selectiveUIAEventRegistrationCheckBox.SetValue(self.selectiveUIAEventRegistrationCheckBox.defaultValue)
self.UIAInMSWordCheckBox.SetValue(self.UIAInMSWordCheckBox.defaultValue)
self.UIAInMSWordCombo.SetSelection(self.UIAInMSWordCombo.defaultValue)
self.UIAInMSExcelCheckBox.SetValue(self.UIAInMSExcelCheckBox.defaultValue)
self.ConsoleUIACheckBox.SetValue(self.ConsoleUIACheckBox.defaultValue == 'UIA')
self.UIAInChromiumCombo.SetSelection(self.UIAInChromiumCombo.defaultValue)
Expand All @@ -3005,7 +3018,7 @@ def onSave(self):
log.debug("Saving advanced config")
config.conf["development"]["enableScratchpadDir"]=self.scratchpadCheckBox.IsChecked()
config.conf["UIA"]["selectiveEventRegistration"] = self.selectiveUIAEventRegistrationCheckBox.IsChecked()
config.conf["UIA"]["useInMSWordWhenAvailable"]=self.UIAInMSWordCheckBox.IsChecked()
config.conf["UIA"]["allowInMSWord"] = self.UIAInMSWordCombo.GetSelection()
config.conf["UIA"]["useInMSExcelWhenAvailable"] = self.UIAInMSExcelCheckBox.IsChecked()
if self.ConsoleUIACheckBox.IsChecked():
config.conf['UIA']['winConsoleImplementation'] = "UIA"
Expand Down
3 changes: 3 additions & 0 deletions user_docs/en/changes.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ What's New in NVDA
- The Speech Dictionary dialog now features a "Remove all" button to help clear a whole dictionary. (#11802)
- Added support for Windows 11 Calculator. (#13212)
- In Microsoft Word with UI Automation enabled on Windows 11, line numbers, section numbers and layout column numbers can now be reported. (#13283)
- For Microsoft Office 16.0.15000 and higher on Windows 11, NVDA will use UI Automation to access Microsoft Word documents by default, providing a significant performance improvement over the old Object model access. (#13437)
- This includes documents in Microsoft Word itself, and also the message reader and composer in Microsoft Outlook.
-
-


Expand Down
15 changes: 9 additions & 6 deletions user_docs/en/userGuide.t2t
Original file line number Diff line number Diff line change
Expand Up @@ -1864,12 +1864,15 @@ This has a major negative impact on performance, especially in applications like
Therefore, when this option is enabled, NVDA will limit event registration to the system focus for most events.
If you suffer from performance issues in one or more applications, We recommend you to try this functionality to see whether performance improves.

==== Use UI automation to access Microsoft Word document controls when available ====[AdvancedSettingsUseUiaForWord]
When this option is enabled, NVDA will try to use the Microsoft UI Automation accessibility API in order to fetch information from Microsoft Word document controls.
This includes Microsoft Word itself, and also the Microsoft Outlook message viewer and composer.
For the most recent versions of Microsoft Office 2016/365 running on Windows 10 and later, UI Automation support is complete enough to provide access to Microsoft Word documents almost equal to NVDA's existing Microsoft Word support, with the added advantage that responsiveness is majorly increased.
However, There may be some information which is either not exposed, or exposed incorrectly in some versions of Microsoft Office, which means this UI automation support cannot always be relied upon.
We still do not recommend that the majority of users turn this on by default, though we do welcome users of Office 2016/365 to test this feature and provide feedback.
==== Use UI automation to access Microsoft Word document controls ====[MSWordUIA]
Configures whether or not NVDA should use the UI Automation accessibility API to access Microsoft Word documents, rather than the older Microsoft Word object model.
This applies to documents in Microsoft word itself, plus messages in Microsoft Outlook.
This setting contains the following values:
- Default (where suitable)
- Only where necessary: where the Microsoft Word object model is not available at all
- Where suitable: Microsoft Word version 16.0.15000 or higher, or where the Microsoft Word object model is unavailable
- Always: where ever UI automation is available in Microsoft word (no matter how complete).
-

==== Use UI Automation to access the Windows Console when available ====[AdvancedSettingsConsoleUIA]
When this option is enabled, NVDA will use a new, work in progress version of its support for Windows Console which takes advantage of [accessibility improvements made by Microsoft https://devblogs.microsoft.com/commandline/whats-new-in-windows-console-in-windows-10-fall-creators-update/]. This feature is highly experimental and is still incomplete, so its use is not yet recommended. However, once completed, it is anticipated that this new support will become the default, improving NVDA's performance and stability in Windows command consoles.
Expand Down

0 comments on commit 0d9a280

Please sign in to comment.