Repository navigation
Stop review position errors from crashing the magnifier - #20992
ChristosBouronikos wants to merge 7 commits into
Conversation
FocusManager._getReviewPosition called api.getReviewPosition() outside its error handling. When fetching the review position raised (for example a COMError from an MSHTML browseable message or a UIA text control), the error reached Magnifier._updateMagnifier, which counted it as a magnification failure. After three such errors the magnifier attempted recovery, which flashes the screen, and then stopped. Fetching the review position now uses the same handling as computing its point, so a failure falls back to the last valid review position and the magnified view keeps working. Fixes nvaccess#20860
|
Can this please be retargetted to master? |
@seanbudd changed target to master branch. |
|
According to its description, this PR definitely seems to bring an improvement with regards to situations where review cursor retrieval raises errors: instead of crashing the magnifier, tracking is just temporarily not honoured. So if it works as advertised, I have not tested myself, it definitely deserves to be merged. Though, as I understand it, the root tracking issue still stands, for magnifier, but maybe also for visual highlighter of browse mode cursor. |
|
Maybe ideally, all possible / known errors when retrieving review cursor should be catched in |
Address review feedback on nvaccess#20992: api.getReviewPosition now raises exceptions.LocationUnavailableError (a LookupError subclass) when the review position cannot be retrieved, keeping the original error as the cause. vision.util.getContextRect does the same for highlight locations. The magnifier and NVDA Highlighter now only handle this error type. Fix the two retrieval failures from the issue logs: - MSHTML: for a non-editable BODY, use the document selection range directly instead of moving a new range to its bookmark, which fails for some text such as "𝔓". - UIA: a COMError when collapsing the selection to the caret (seen in Windows Terminal) is now raised as RuntimeError, like other selection failures, so callers fall back as they already do. Focus handling no longer aborts when the review position cannot be retrieved: setCurrentMode no longer fetches a position when updateReviewPosition is False, and setNavigatorObject tolerates caret retrieval failures. In the magnifier, tracking is separated from the native update so only Magnification API errors count towards recovery, and the update timer is always rescheduled.
|
@CyrilleB79 thanks for the review. I've updated the PR to address both of your comments. The description has the full details, but in short: A single error type
The underlying tracking errors I tried to fix the two failures from the logs at their source:
There's no log for the Firefox tab case, so I don't know its underlying cause. Magnifier should no longer flicker or stop there either way. If you still see an error in the log when closing tabs, a log would be very helpful, and it could be tracked in a new issue. Please note: I haven't tested this extensively yet, only unit tests and some quick manual checks. In particular, I haven't properly tested the Windows Terminal and Firefox cases. If you could try your original steps to reproduce, that would be much appreciated. |
The fallback in FocusManager._getPointAtStart was reworked to report failures as LocationUnavailableError, but had no tests. Cover the previous character fallbacks and the errors raised when they fail.
|
@ChristosBouronikos regarding Firefox issue or the other one when alt+tabbing, I have no reproducible STR. It only happened from time to time (or even only once. |
|
marking as draft until testing is performed |
Link to issue number:
Fixes #20860
Follow-up to #20897.
Note
This PR has been updated following review feedback. I have only done limited testing so far: unit tests and some quick manual checks. It has not been tested extensively on Windows, especially the Windows Terminal and Firefox cases below. Testing and feedback are very welcome.
Relationship to #20897:
#20897 was merged as a partial fix for this issue. It changed
_MAX_RECOVERY_ATTEMPTSinfullscreenMagnifier.pyfrom 3 to 1, which reduces the number of screen flashes during recovery. It does not touch the error that starts the recovery.This PR addresses the error itself. A failure to get a tracked position no longer reaches the recovery path at all, so there is no recovery, no flash, and Magnifier stays active. Genuine Magnification API failures still go through the recovery path, still limited to one attempt by #20897.
Summary of the issue:
Magnifier flickers and then stops itself in situations unrelated to the Magnification API, for example when opening some browseable messages (
ui.browseableMessage("<p>𝔓 power set</p>", isHtml=True)), when closing a Firefox tab, or when switching to Windows Terminal.In the logs on the issue, getting the review position raises a
COMError.Magnifier._updateMagnifiertreated this as a Magnification API failure, and after three such errors attempted recovery (the screen flash) and then stopped.The logs show two different underlying retrieval failures:
MSHTMLTextInfo.__init__fails inMoveToBookmarkwhen moving a new BODY text range to the bookmark of the current selection (COMError: Paramètre incorrect). The same error also breaks thecaretevent for that object.UIATextInfo.__init__fails incollapse()when collapsing the selection to the caret (COMError: Erreur non spécifiée). This happened insideapi.setNavigatorObject→review.setCurrentMode, so it also aborted thegainFocusevent.There is no log for the Firefox tab case.
Description of user facing changes:
Description of developer facing changes:
exceptions.LocationUnavailableError, a subclass ofLookupError, raised when a cursor position or its screen location cannot currently be retrieved. The underlying error is available as__cause__.api.getReviewPositionnow raisesLocationUnavailableErrorwhen the review position cannot be retrieved, instead of the underlying error (COMError,RuntimeError, etc.). A failed retrieval is not cached, so a later call retries. This is documented in its docstring.vision.util.getContextRectnow raisesLocationUnavailableErrorfor retrieval failures instead of the underlying error.review.setCurrentMode(..., updateReviewPosition=False)no longer fetches a review position, so the mode is set without checking that a position is available. Internally this is only used during focus and caret handling, where the fetched position was discarded anyway.Existing callers that catch
LookupErroraroundgetReviewPosition(for example inNVDAObjects/__init__.pyandglobalCommands.py) keep working, since the new exception is aLookupError.Description of development approach:
Following @CyrilleB79's suggestion, review position errors are now reported with a single error type, and both visual consumers handle only that type:
api.getReviewPosition: known retrieval errors (COMError,LookupError,OSError,RuntimeError) are raised asLocationUnavailableErrorwith the original error as the cause. Programming errors andCallCancelledstill propagate unchanged.FocusManager._getReviewPositionnow only catchesLocationUnavailableError._getPointAtStartalso raises it when no screen point can be found.Magnifier._updateMagnifier, tracking is now separated from_doUpdate. Only Magnification API errors from_doUpdatecount towards recovery, so no tracking error can trigger the recovery flash, including ones we don't know about yet (such as the Firefox case).CallCancelledduring an NVDA freeze) stopped the timer and tracking never resumed.getContextRectraisesLocationUnavailableErrorfor retrieval failures, andupdateContextRectcatches it instead ofLookupError/RuntimeError. It still also catchesValueError/TypeError, which come fromlocationHelperrejecting invalid rectangles rather than from retrieval. Previously aCOMErrorfrom a caret or review lookup was not handled at all.The two retrieval failures from the logs are fixed at their source:
COMErrorfromcollapse()is now raised asRuntimeError, the same as the existing handling for a failingGetSelection(). Callers already handle that, for example object review falls back to the first position.GetSelection()fails. I preferred this to a new exception type, because many existing callers only catchRuntimeErrorhere (focus speech, caret movement detection, several global commands), and would otherwise start raising errors.review.setCurrentModeno longer fetches a position it discards, andapi.setNavigatorObjectno longer aborts the focus event if getting the caret for the review position fails. The review position is then fetched again when needed.Testing strategy:
Unit tests:
test_api.py:getReviewPositionraisesLocationUnavailableErrorwith the original cause, does not cache a failure, and recovers on the next call. Unexpected errors andCallCancelledstill propagate.setNavigatorObjectstill completes when getting the caret fails.test_review.py:setCurrentMode(..., updateReviewPosition=False)does not fetch a position. The default still checks availability.test_NVDAObjects_IAccessible_MSHTML.py: the BODY caret and selection use the selection range withoutMoveToBookmark. Inactive elements are still rejected, and other elements still use the bookmark.test_NVDAObjects_UIA.py: aCOMErrorfromcollapse()raisesRuntimeError, and object review falls back to the first position.test_visionUtil.pyandtest_visionEnhancementProviders/test_NVDAHighlighter.py: retrieval errors fromgetContextRectare raised asLocationUnavailableError, and the highlighter clears the highlight instead of raising.test_magnifier/test_magnifier.pyandtest_magnifier/test_focusManager.py: an unavailable tracked location keeps the current view without counting as an error. Tracking errors never trigger recovery. The timer keeps running.The new tests for each fix fail without it.
Manual testing has been limited so far. Not yet tested extensively. Suggested manual tests:
import ui;ui.browseableMessage("<p>𝔓 power set</p>", isHtml=True).caretevent.Known issues with pull request:
vision.util.getRectFromTextInfoexpands the passedTextInfoin place, which can modify the cached review position. This is a pre-existing issue, left for a separate PR.Code Review Checklist: