Handle tree interceptors in objectBelowLockScreenAndWindowsIsLocked (#20678)
Fixes #18861
Note that #18861 was closed as "fixed / can't reproduce", but the underlying bug was never fixed.
Reproducing it reliably requires the virtual buffer to keep changing while Windows is locked, which is why it appeared to go away.
Summary of the issue:
While Windows is locked, NVDA logs the following roughly once a second if a browse mode document keeps updating in the background, for example a Firefox tab playing a YouTube video:
Traceback (most recent call last):
File "utils\security.pyc", line 176, in objectBelowLockScreenAndWindowsIsLocked
AttributeError: 'Gecko_ia2' object has no attribute 'isBelowLockScreen'
Gecko_ia2 here is the virtual buffer, i.e. a TreeInterceptor, not an NVDAObject.
Whenever the document changes, VirtualBuffer.changeNotify queues _handleUpdate, which calls braille.handler.handleUpdate(self), passing the tree interceptor itself.
handleUpdate then calls objectBelowLockScreenAndWindowsIsLocked, which reads obj.isBelowLockScreen.
That property is only implemented by NVDAObject, so an AttributeError is raised, caught, and logged.
The error only appears while Windows is locked because isLockScreenModeActive() and obj.isBelowLockScreen short circuits when unlocked.
braille.handler.handleCaretMove has the same problem, as cursorManager passes self to it.
Description of user facing changes:
The log is no longer flooded with these errors while Windows is locked.
Description of developer facing changes:
utils.security.objectBelowLockScreenAndWindowsIsLocked now accepts a TreeInterceptor as well as an NVDAObject, and checks the tree interceptor's rootNVDAObject.
Description of development approach:
Tree interceptors are resolved to their root object inside objectBelowLockScreenAndWindowsIsLocked, rather than changing what _handleUpdate passes to braille.
Braille regions store the tree interceptor in region.obj, so passing the root object from _handleUpdate instead would break the region.obj == obj match and stop braille updates.
Fixing it in utils.security also covers the handleCaretMove path.
An object which is neither an NVDAObject nor has a rootNVDAObject is logged at debug level and treated as safe, consistent with the existing handling in api.setReviewPosition.
Note that this changes behaviour beyond silencing the log: previously the AttributeError was handled as if the object were above the lock screen, so the check did not apply to tree interceptors at all.
The isLockScreenModeActive() check has been moved to the top of the function, so the common unlocked case returns before the added isinstance check and import.
The NVDAObjects import is inline because NVDAObjects imports utils.security at module level, matching the existing inline import in _isObjectBelowLockScreen.