Repository: nvaccess/nvda

Issue:
when activating document review and review cursor isn't following focus review cursor always ends up at the top of the document.
### Is your feature request related to a problem? Please describe.
When pressing NVDA+numpadMinus to move review cursor and navigator object to the position of the caret/focus respectively and the caret is inside the browse mode document navigator object/review cursor ends up at the position of the last focused object not at the caret position. 
This is mainly problematic in two cases:
1. In long pdf's when there is no focusable objects - just a big amount of text - in this case review cursor ends up at the top of the document.
2. After "focus focusable elements in browse mode" has been set to disabled the focused element is a browser window hence moving navigator object to your position in browse mode is not possible.
### Describe the solution you'd like
When moving navigator object to focus take into account position of the browse mode caret and set review cursor there - this would be consistent with other cases e.g. setting position of the review cursor in other editable text fields.
### Describe alternatives you've considered
None
### Additional context
This is especially annoying for people who have set their review cursor not to follow caret - in this case it is impossible to set the mouse to your browse mode position without navigating to it with object nav from the top of the webpage.


Discussion:
The current description for this command is:
> Sets the navigator object to the current focus, and the review cursor to the position of the caret inside it, if possible

I agree that the user intent that you highlight is likely to be more common than moving to focus. Moving to virtual caret was effectively how this worked previously. However, since there may be cases where the focus is of interest specifically, this should be introduced as a new command. 

Given that this use case has regressed with the introduction of #11190, I will accept this into the 2020.3 release unless #11190 gets reverted for some reason.

- Please start with a PR introducing a new command with no default gesture.
- As a follow-up PR we can this new command becoming the default for for the `NVDA+numpad minus` gesture.
@feerrenrut wrote:

> The current description for this command is:
> 
> > Sets the navigator object to the current focus, and the review cursor to the position of the caret inside it, if possible
> 
> I agree that the user intent that you highlight is likely to be more common than moving to focus. Moving to virtual caret was effectively how this worked previously.

No, this command never moved review cursor to the position of the browse mode caret.


> However, since there may be cases where the focus is of interest specifically, this should be introduced as a new command.

I'm afraid I don't follow here:
- Outside of browse mode this command always moves to focus and if the object with focus has caret sets review cursor to its position.
- In browse mode after #11190 either a browser window has focus because user navigates with the browse mode caret only (I really don't see any use case for moving review cursor to it) or some random object which was focused previously - again what is the point of moving to it specifically. 

The regression here is the fact that for people for whom review cursor doesn't follow browse mode caret it is impossible to move object nav to the current position in browse mode. In all other places this is accomplished by NVDA+Numminus and I'm afraid introducing  new command just for browse mode would be extremely confusing.

Resolving patch:
diff --git a/source/globalCommands.py b/source/globalCommands.py
--- a/source/globalCommands.py
+++ b/source/globalCommands.py
@@ -898,19 +898,23 @@ def script_navigatorObject_currentDimensions(self,gesture):
 	script_navigatorObject_currentDimensions.__doc__=_("Reports information about the location of the text or object at the review cursor. Pressing twice may provide further detail.") 
 	script_navigatorObject_currentDimensions.category=SCRCAT_OBJECTNAVIGATION
 
+	@script(
+		description=_(
+			# Translators: Input help mode message for move navigator object to current focus command.
+			"Sets the navigator object to the current focus,"
+			"and the review cursor to the position of the caret inside it, if possible."
+		),
+		category=SCRCAT_OBJECTNAVIGATION,
+		gestures=("kb:NVDA+numpadMinus", "kb(laptop):NVDA+backspace"),
+	)
 	def script_navigatorObject_toFocus(self,gesture):
-		obj=api.getFocusObject()
-		try:
-			pos=obj.makeTextInfo(textInfos.POSITION_CARET)
-		except (NotImplementedError,RuntimeError):
-			pos=obj.makeTextInfo(textInfos.POSITION_FIRST)
-		api.setReviewPosition(pos)
+		tIAtCaret = self._getTIAtCaret(True)
+		focusedObj = api.getFocusObject()
+		api.setNavigatorObject(focusedObj)
+		api.setReviewPosition(tIAtCaret)
 		# Translators: Reported when attempting to move the navigator object to focus.
 		speech.speakMessage(_("Move to focus"))
-		speech.speakObject(obj,reason=controlTypes.REASON_FOCUS)
-	# Translators: Input help mode message for move navigator object to current focus command.
-	script_navigatorObject_toFocus.__doc__=_("Sets the navigator object to the current focus, and the review cursor to the position of the caret inside it, if possible.")
-	script_navigatorObject_toFocus.category=SCRCAT_OBJECTNAVIGATION
+		speech.speakObject(api.getNavigatorObject(), reason=controlTypes.OutputReason.FOCUS)
 
 	def script_navigatorObject_moveFocus(self,gesture):
 		obj=api.getNavigatorObject()
@@ -1477,7 +1481,8 @@ def _reportFormattingHelper(self, info, browseable=False):
 				_("Formatting")
 			)
 
-	def _getTIAtCaret(self):
+	@staticmethod
+	def _getTIAtCaret(fallbackToPOSITION_FIRST=False):
 		# Returns text info at the caret position if there is a caret in the current control, None otherwise.
 		# Note that if there is  no caret this fact is announced  in speech and braille.
 		obj = api.getFocusObject()
@@ -1488,12 +1493,13 @@ def _getTIAtCaret(self):
 		):
 			obj = treeInterceptor
 		try:
-			info = obj.makeTextInfo(textInfos.POSITION_CARET)
-			return info
+			return obj.makeTextInfo(textInfos.POSITION_CARET)
 		except (NotImplementedError, RuntimeError):
-			# Translators: Reported when there is no caret.
-			ui.message(_("No caret"))
-			return
+			if fallbackToPOSITION_FIRST:
+				return obj.makeTextInfo(textInfos.POSITION_FIRST)
+			else:
+				# Translators: Reported when there is no caret.
+				ui.message(_("No caret"))
 
 	@script(
 		# Translators: Input help mode message for report formatting command.
@@ -2753,8 +2759,6 @@ def _enableScreenCurtain(doEnable: bool = True):
 		"kb:NVDA+numpad2": "navigatorObject_firstChild",
 		"kb(laptop):NVDA+shift+downArrow": "navigatorObject_firstChild",
 		"ts(object):flickdown":"navigatorObject_firstChild",
-		"kb:NVDA+numpadMinus": "navigatorObject_toFocus",
-		"kb(laptop):NVDA+backspace": "navigatorObject_toFocus",
 		"kb:NVDA+numpadEnter": "review_activate",
 		"kb(laptop):NVDA+enter": "review_activate",
 		"ts:double_tap": "review_activate",


Test patch: