From 3e1a4de2d3c8b781b8c12b0029ed667311b4b374 Mon Sep 17 00:00:00 2001 From: Tomasz Wroblewski Date: Wed, 10 Apr 2024 14:11:47 +0200 Subject: [PATCH] HP Secure Browser support (#16377): Detect & prevent infinite loop in iterUIARangeByUnit Summary of the issue: We're experiencing NVDA locking up trying to infinitely split some text ranges, for example on www.bat.org when it tries to split the range representing "Submit" button into individual characters. NVDA hangs completely in this scenario and has to be forcibly killed. If the offending split is attempted, the hang always happens. Secure Browser seems to be provoking this split more, but we've also seen that in normal chromium-based browsers when using UIA instead of IAccessible2. This could be due to significant timing differences etc, as the virtualized browser has higher overhead. Typical repro scenario involves navigating to www.bat.org then duplicating the tab & closing some of the dupes, until NVDA attempts the split and hangs. Description of user facing changes: Detect & prevent the infinite loop so that NVDA remains responsive Description of development approach: This is likely a deficiency in UIA text range implementation on the chromium side, as I don't see anything obviously wrong with the algorithm in iterRangeByUnit which is doing the split. Rewriting this UIA algorithm in C++ and running against offending website also hangs, always, in regular Google Chrome, Edge, and Secure Browser. At the cost of extra Compare COM call we can detect that chromium is yielding same range infinitely, and therefore detect & shortcircuit the infinite loop on the NVDA side. --- source/UIAHandler/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/source/UIAHandler/utils.py b/source/UIAHandler/utils.py index f400399194b..1a81843472d 100644 --- a/source/UIAHandler/utils.py +++ b/source/UIAHandler/utils.py @@ -124,13 +124,19 @@ def iterUIARangeByUnit(rangeObj,unit,reverse=False): tempRange.MoveEndpointByRange(Endpoint_relativeEnd,rangeObj,Endpoint_relativeStart) endRange=tempRange.Clone() loopCount = 0 + yieldRange = None while relativeGTOperator(endRange.Move(unit,minRelativeDistance),0): loopCount += 1 tempRange.MoveEndpointByRange(Endpoint_relativeEnd,endRange,Endpoint_relativeStart) pastEnd=relativeGTOperator(tempRange.CompareEndpoints(Endpoint_relativeEnd,rangeObj,Endpoint_relativeEnd),0) if pastEnd: tempRange.MoveEndpointByRange(Endpoint_relativeEnd,rangeObj,Endpoint_relativeEnd) - yield tempRange.clone() + if yieldRange and bool(yieldRange.compare(tempRange)): + # we've looped onto range we've already yielded previously - shortcircuit to prevent + # infinite loop + return + yieldRange = tempRange.clone() + yield yieldRange if pastEnd: return tempRange.MoveEndpointByRange(Endpoint_relativeStart,tempRange,Endpoint_relativeEnd)