Re: Problem with fast browse mode - multiple NVDAObjects are created

Tony Malykh
 

Hello Michael,

Did you see my previous emails?

--Tony

On 3/25/2019 3:12 PM, Tony Malykh via Groups.Io wrote:
Hello Michael,

I was wondering if I could have any assistance with this issue? I was under impression that you offered to help me with debugging fast browse mode PR... I haven't heard from you for more than two weeks, so should I keep waiting? Or did I misunderstand you and I'm own my own with this PR?

Thanks

--Tony

On 3/20/2019 9:20 AM, Tony Malykh via Groups.Io wrote:
Moving this thread to new mailing list.

[BCC old mailing list]


--Tony

On 3/9/2019 2:49 PM, Tony Malykh wrote:
Hello NVDA devs and especially Michael,

I am trying to work on fast browse mode PR (a.k.a. focus follows browse mode off).
Just a quick recap: In v1 of this PR there were some  bugs related to the fact that some logic was asynchronous and it relied on gainFocus event being received from the operating system. However in some cases for unknown reason operating system wouldn't send this event, causing bugs that are hard to deal with, such as inability to activate form elements on web pages.
I am now working on v2:
https://github.com/nvaccess/nvda/pull/9360
The code is by no means ready to be reviewed, but it's just for the reference.
The main change in v2 is replacing asynchronous logic with synchronous logic there, and in general it works, but there is one new bugI'm facing that I would like to discuss and maybe ask for some help.

Basic description of the bug:
When unchecking a checkbox on a web page, its new state is not being announced. The bug only reproduces for unchecking - that is checking previously blank checkbox works as expected.
Here are the concrete steps to reproduce the bug.
1. Enter Fast browse mode by pressing NVDA+num row 8.
2. In Chrome or Firefox navigate to https://www.ironspider.ca/forms/checkradio.htm
3. Check the first 3 checkboxes on the page
4. Go back to the first check box
5. Press space to uncheck it. The check box will be successfully unchecked, but the new state will not be announced immediately.

I spent some time debugging this , and here are my findings so far.
Announcing state change works like this. First speech.speakObject(obj,controlTypes.REASON_ONLYCACHE) is called to cache the previous state. In traditional browse mode this function is called when user navigates to that checkbox, and therefore system focus is switched to that checkbox as well. This happens in BrowseModeDocumentTreeInterceptor.event_gainFocus.
Then, assuming that user presses space bar on a checkbox, BrowseModeTreeInterceptor._activatePosition is triggered, which activates this checkbox. Shortly, in response to that we receive a state changed event, that is processed in NVDAObject.event_stateChange(), which in turn calls speech.speakObjectProperties(self,states=True, reason=controlTypes.REASON_CHANGE).
So we make two calls to speech module: the first one to cache object properties, the second one to speak whatever properties have changed. Cached properties are stored inside NVDAObject, so it is very important that the same object is passed to both these calls.

Now back to fast browse mode v2. Since we don't update system focus on every browse mode focus change, we need to update system focus right before activating the checkbox. This is accomplished in BrowseModeTreeInterceptor.maybeSyncFocus(). And in the same function, right after I call obj.setFocus(), I also call speech.speakObject(obj,controlTypes.REASON_ONLYCACHE).
However, here is the problem. When we receive state changed event, the function NVDAObject.event_stateChange is called on a different NVDA object instance. So we completely miss the cached state.
When I say a different NVDAObject instance, I only mean a different python object. So there are apparently (at least) two NVDAObjects, I'll call them NVDAObject1 and NVDAObject2. Both represent the same checkbox in the browser, both have the same IA2UniqueID and windowHandle. Yet in the first call (cacheonly) I see NVDAObject1, and the state changed event comes in with NVDAObject2.
To illustrate this, here is the example from my logs (slightly formatted):
cacheonly from maybeSyncFocus():
    <NVDAObjects.IAccessible.mozilla.Mozilla object at 0x07A6A370>
    uniqueID = (67548, -33555216)
speakObject on stateChanged
    <NVDAObjects.IAccessible.mozilla.Mozilla object at 0x07A6A1F0>
    uniqueID = (67548L, -33555216)

So the big question I have is why there are two separate NVDAObjects created for the same checkbox in my scenario? And why they are not created Without my PR? What is logic behind creating new NVDA Objects? I did some cursory debugging of that and I saw that most of NVDAObjects are created using function getNVDAObjectFromEvent() defined in IAccessible2\__init__.py, and there doesn't seem to be any caching logic there.
It seems there are two potential approaches of tackling this issue.
1. We need to figure out why two separate NVDA objects are created.
2. Potentially another approach is to allow creating multiple python NVDAObjects for the same Checkbox (or any other real IAccessible2 object), and have a properties cache that would take this into account. Some kind of weakKeyDictionary might work. Property caching logic in speech.py might have to be redesigned though.

And also another thing worth noticing in the logs that I pasted above. UniqueID is a tuple of WindowHandle and IA2UniqueID . If you looked at both entries carefully, you would notice that WindowHandle is slightly different: even though the value is the same, in the first entry it is int, and in the second entry it is long. This looks very suspicious to me. Do you think this might be the reason why two copies of NVDAObjects are created? On the other hand it doesn't explain why two objects are not created in stock NVDA.

Anyway, this is current state of my second attempt to implement fast browse mode. Any suggestions will be appreciated!
Best
--Tony

Join nvda-devel@groups.io to automatically receive all group messages.