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

UI Automation in Windows Console: improve reliability of visible range checks #9957

Merged
merged 14 commits into from Aug 1, 2019
Merged
48 changes: 33 additions & 15 deletions source/NVDAObjects/UIA/winConsoleUIA.py
Expand Up @@ -12,7 +12,9 @@
import textInfos
import UIAHandler

from comtypes import COMError
from scriptHandler import script
from UIAUtils import isTextRangeOffscreen
from winVersion import isWin10
from . import UIATextInfo
from ..behaviors import Terminal
Expand All @@ -26,6 +28,21 @@ class consoleUIATextInfo(UIATextInfo):
#: to do much good either.
_expandCollapseBeforeReview = False

def __init__(self,obj,position,_rangeObj=None):
super(consoleUIATextInfo, self).__init__(obj, position, _rangeObj)
# Re-implement POSITION_FIRST and POSITION_LAST in terms of
feerrenrut marked this conversation as resolved.
Show resolved Hide resolved
# visible ranges to fix review top/bottom scripts.
if position==textInfos.POSITION_FIRST:
visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
firstVisiRange = visiRanges.GetElement(0)
self._rangeObj = firstVisiRange
self.collapse()
elif position==textInfos.POSITION_LAST:
visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
lastVisiRange = visiRanges.GetElement(visiRanges.length - 1)
self._rangeObj = lastVisiRange
self.collapse(True)

def collapse(self,end=False):
"""Works around a UIA bug on Windows 10 1903 and later."""
if not isWin10(1903):
Expand All @@ -50,8 +67,6 @@ def move(self, unit, direction, endPoint=None):
visiRanges = self.obj.UIATextPattern.GetVisibleRanges()
visiLength = visiRanges.length
if visiLength > 0:
firstVisiRange = visiRanges.GetElement(0)
lastVisiRange = visiRanges.GetElement(visiLength - 1)
oldRange = self._rangeObj.clone()
if unit == textInfos.UNIT_WORD and direction != 0:
# UIA doesn't implement word movement, so we need to do it manually.
Expand Down Expand Up @@ -94,7 +109,6 @@ def move(self, unit, direction, endPoint=None):
lineInfo.expand(textInfos.UNIT_LINE)
offset = self._getCurrentOffsetInThisLine(lineInfo)
# Finally using the new offset,

# Calculate the current word offsets and move to the start of
# this word if we are not already there.
start, end = self._getWordOffsetsInThisLine(offset, lineInfo)
Expand All @@ -108,15 +122,16 @@ def move(self, unit, direction, endPoint=None):
else: # moving by a unit other than word
res = super(consoleUIATextInfo, self).move(unit, direction,
endPoint)
if oldRange and (
self._rangeObj.CompareEndPoints(
UIAHandler.TextPatternRangeEndpoint_Start, firstVisiRange,
UIAHandler.TextPatternRangeEndpoint_Start) < 0
or self._rangeObj.CompareEndPoints(
UIAHandler.TextPatternRangeEndpoint_Start, lastVisiRange,
UIAHandler.TextPatternRangeEndpoint_End) >= 0):
self._rangeObj = oldRange
return 0
try:
if (
oldRange
and isTextRangeOffscreen(self._rangeObj, visiRanges)
and not isTextRangeOffscreen(oldRange, visiRanges)
):
self._rangeObj = oldRange
return 0
except COMError, RuntimeError:
pass
return res

def expand(self, unit):
Expand Down Expand Up @@ -292,9 +307,12 @@ def script_flush_queuedChars(self, gesture):

def _getTextLines(self):
# Filter out extraneous empty lines from UIA
ptr = self.UIATextPattern.GetVisibleRanges()
res = [ptr.GetElement(i).GetText(-1) for i in range(ptr.length)]
return res
return (
self.makeTextInfo(textInfos.POSITION_ALL)
._rangeObj.getText(-1)
.rstrip()
.split("\r\n")
)

def _calculateNewText(self, newLines, oldLines):
self._hasNewLines = (
Expand Down
17 changes: 17 additions & 0 deletions source/UIAUtils.py
Expand Up @@ -172,6 +172,23 @@ def getChildrenWithCacheFromUIATextRange(textRange,cacheRequest):
c=CacheableUIAElementArray(c)
return c

def isTextRangeOffscreen(range, visiRanges):
Copy link
Contributor

Choose a reason for hiding this comment

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

Please change the range argument so it does not shadow the builtin function

"""Given a UIA text range and a visible ranges array (returned from obj.UIATextPattern.GetVisibleRanges), determines if the given range is not within the visible ranges."""
visiLength = visiRanges.length
if visiLength > 0:
firstVisiRange = visiRanges.GetElement(0)
lastVisiRange = visiRanges.GetElement(visiLength - 1)
return range.CompareEndPoints(
UIAHandler.TextPatternRangeEndpoint_Start, firstVisiRange,
UIAHandler.TextPatternRangeEndpoint_Start
) < 0 or range.CompareEndPoints(
UIAHandler.TextPatternRangeEndpoint_Start, lastVisiRange,
UIAHandler.TextPatternRangeEndpoint_End) >= 0
else:
# Visible ranges not available.
raise RuntimeError("Visible ranges array is empty or invalid.")


class UIATextRangeAttributeValueFetcher(object):

def __init__(self,textRange):
Expand Down