Repository: nvaccess/nvda

Issue:
Automatically set system focus to focusable lements off: pressing enter on a spin button moves the cursor to the task bar or to the address bar
### Steps to reproduce:
1. In Firefox, visit this website
https://www.dinnerkrimi.de/spielstaette/karlsruhe/badisch-brauhaus_005895/details.html
2. After the website is loaded, press nvda+8 to turn off automatically set system focus to focusable elements
3. In browse mode, press b to jump to the spin buttons
4. press enter on one of them to adjust the value in the spin edit

### Actual behavior:
Focus jumps to the task bar or to the address bar. The value in the spin edit does not change.

### Expected behavior:
The number in the spin edit should change and the focus should actually not change its position.

### System configuration
#### NVDA installed/portable/running from source:
Installed

#### NVDA version:
alpha-17928,9dfbd2ee

#### Windows version:
Windows 10 1809 update

#### Name and version of other software in use when reproducing the issue:
Firefox 67.0.4.

### Other questions
#### Does the issue still occur after restarting your PC?
yes

#### Have you tried any other versions of NVDA? If so, please report their behaviors.
no


Discussion:
Unfortunately there is not much logged when this happens. The only relevant part is this one directly after pressing enter:

```
IO - inputCore.InputManager.executeGesture (13:03:51.795):
Input: kb(laptop):enter
DEBUGWARNING - virtualBuffers.gecko_ia2.Gecko_ia2._activateNVDAObject (13:03:51.818):
doAction failed
DEBUGWARNING - virtualBuffers.gecko_ia2.Gecko_ia2._activateNVDAObject (13:03:51.819):
Clicking with mouse
```

cc: @leonardder, @mltony, @jcsteh
Perhaps NVDA isn't able to figure out the exact position of these elements. Thus it isn't also possible to move the mouse cursor onto it by using its NVDA command.  Maximize the Firefox window with WIN+ArrowUp and try again.

Well, and please throw the stupid Cookie notification out of the way, if it appears. You find it near the bottom of the html structure.
I tried it, maximizing the window does not solve this problem. However, if system focus is automatically set to focusable elements, the problem is not occuring. Thus, the problem appears wehn using the feature introduced by PR #9511. 

Resolving patch:
diff --git a/source/browseMode.py b/source/browseMode.py
--- a/source/browseMode.py
+++ b/source/browseMode.py
@@ -266,6 +266,9 @@ class BrowseModeTreeInterceptor(treeInterceptorHandler.TreeInterceptor):
 	def _get_currentNVDAObject(self):
 		raise NotImplementedError
 
+	def _get_currentFocusableNVDAObject(self):
+		return self.makeTextInfo(textInfos.POSITION_CARET).focusableNVDAObjectAtStart
+
 	def event_treeInterceptor_gainFocus(self):
 		"""Triggered when this browse mode interceptor gains focus.
 		This event is only fired upon entering this treeInterceptor when it was not the current treeInterceptor before.
@@ -517,14 +520,18 @@ def script_activatePosition(self,gesture):
 	script_activatePosition.__doc__ = _("Activates the current object in the document")
 
 	def _focusLastFocusableObject(self, activatePosition=False):
-		obj = self._lastFocusableObj
-		if not obj:
-			return
+		obj = self.currentFocusableNVDAObject
 		if obj!=self.rootNVDAObject and self._shouldSetFocusToObj(obj) and obj!= api.getFocusObject():
 			obj.setFocus()
-			speech.speakObject(obj,controlTypes.REASON_ONLYCACHE)
+			# We might be about to activate or pass through a key which will cause
+			# this object to change (e.g. checking a check box). However, we won't
+			# actually get the focus event until after the change has occurred.
+			# Therefore, we must cache properties for speech before the change occurs.
+			speech.speakObject(obj, controlTypes.REASON_ONLYCACHE)
+			self._objPendingFocusBeforeActivate = obj
 		if activatePosition:
-			self._activatePosition(obj=obj)
+			# Make sure we activate the object at the caret, which is not necessarily focusable.
+			self._activatePosition()
 
 	def script_passThrough(self,gesture):
 		if not config.conf["virtualBuffers"]["autoFocusFocusableElements"]:
@@ -1178,7 +1185,7 @@ def __init__(self,obj):
 		self._lastProgrammaticScrollTime = None
 		self.documentConstantIdentifier = self.documentConstantIdentifier
 		self._lastFocusObj = None
-		self._lastFocusableObj = None
+		self._objPendingFocusBeforeActivate = None
 		self._hadFirstGainFocus = False
 		self._enteringFromOutside = True
 		# We need to cache this because it will be unavailable once the document dies.
@@ -1281,7 +1288,6 @@ def _set_selection(self, info, reason=controlTypes.REASON_CARET):
 		if reason == controlTypes.REASON_FOCUS:
 			self._lastCaretMoveWasFocus = True
 			focusObj = api.getFocusObject()
-			self._lastFocusableObj = None
 			if focusObj==self.rootNVDAObject:
 				return
 		else:
@@ -1297,8 +1303,6 @@ def _set_selection(self, info, reason=controlTypes.REASON_CARET):
 			if followBrowseModeFocus:
 				if focusObj and not eventHandler.isPendingEvents("gainFocus") and focusObj!=self.rootNVDAObject and focusObj != api.getFocusObject() and self._shouldSetFocusToObj(focusObj):
 					focusObj.setFocus()
-			else:
-				self._lastFocusableObj = focusObj
 			obj.scrollIntoView()
 			if self.programmaticScrollMayFireEvent:
 				self._lastProgrammaticScrollTime = time.time()
@@ -1547,11 +1551,23 @@ def event_gainFocus(self, obj, nextHandler):
 					# Note: this is usually called after the caret movement.
 					vision.handler.handleGainFocus(obj)
 				elif (
-					self._lastFocusableObj
-					and obj == self._lastFocusableObj
-					and obj is not self._lastFocusableObj
+					self._objPendingFocusBeforeActivate
+					and obj == self._objPendingFocusBeforeActivate
+					and obj is not self._objPendingFocusBeforeActivate
 				):
-					speech.speakObject(self._lastFocusableObj,controlTypes.REASON_CHANGE)
+					# With auto focus focusable elements disabled, when the user activates
+					# an element (e.g. by pressing enter) or presses a key which we pass
+					# through (e.g. control+enter), we call _focusLastFocusableObject.
+					# However, the activation/key press might cause a property change
+					# before we get the focus event, so NVDA's normal reporting of
+					# changes to the focus won't pick it up.
+					# The speech property cache on _objPendingFocusBeforeActivate reflects
+					# the properties before the activation/key, so use that to speak any
+					# changes.
+					speech.speakObject(
+						self._objPendingFocusBeforeActivate, controlTypes.REASON_CHANGE
+					)
+					self._objPendingFocusBeforeActivate = None
 			else:
 				self._replayFocusEnteredEvents()
 				return nextHandler()


Test patch: