Repository: nvaccess/nvda

Issue:
Add ability to report destination URL of a link
<!-- Please read the text in this edit field before filling it in.
Please thoroughly read NVDA's wiki article on how to fill in this template, including how to provide the required files.
Issues may be closed if the required information is not present.
https://github.com/nvaccess/nvda/blob/master/devDocs/githubIssueTemplateExplanationAndExamples.md
Please also note that the NVDA project has a Citizen and Contributor Code of Conduct which can be found at https://github.com/nvaccess/nvda/blob/master/CODE_OF_CONDUCT.md. NV Access expects that all contributors and other community members read and abide by the rules set out in this document while participating or contributing to this project. This includes creating or commenting on issues and pull requests. 

Each of the questions and sections below start with multiple hash symbols (#). Place your answers and information on the blank line below each question.
-->

### Is your feature request related to a problem? Please describe.

Links on web pages and in emails are often denoted with human-friendly text.  Particularly in an era where users need to be aware of and careful to avoid malicious sites and phishing attempts, it is important to check suspicious URLs before activating them.

### Describe the solution you'd like

I envisage the workflow as:
1. Moving focus to a link in an email or on a web page
2. Press a keystroke.
3. NVDA reports something like "Destination is <url> <title>", eg "Destination is https://www.nvaccess.org/download/ NV Access | Download NVDA"

### Describe alternatives you've considered

It used to be possible to query the status bar of the browser for information like this but this is generally not possible now.

The main workaround I can think of is:
1) Move to the link
2) Press the applications key to open the context menu
3) Activate the "Copy link", "Copy link address" or similar item.
4) Press NVDA+c to read the clipboard.

This is an additional step, and also overwrites the clipboard which isn't always ideal.

### Additional context



Discussion:
A few months ago, I didn't know that you could open a link in another tab, (CTRL+Enter( and open in a new window (Shift+Enter), so I created a script that copied the link's address by looking at its value. The link value is exposed, but it has been programmed not to be spoken in NVDA, since it is a lot of text.
If you press NVDA+f1 with the navigator object on the link, you can see its address.
I have implemented this in what I think is a reasonable way. However, I do not want to do a PR for it, until I get an answer to the following question:
What should be the braille experience? I am not a braille user, so have very little idea what would be best here.

Separate commands for braille and speech?

Presumably, it would take longer to read a long URL in braille. So a vanishing message wouldn't be appropriate there, while a ui.message might be fine for speech users.
Would a ui.browseable message be preferable for braille users?
@josephsl @Qchristensen, others . . .
Hi,

Perhaps recycle NVDA+D (announce details) command to announcing anchors? This raises the question about what to do if the link contains annotations.

Thanks.
For me it will be good a browseable message .
Should it be a different command for braille, or the same command with 
configurable behavior?

As it stands, I have it done in such a way that if you press the key sequence 
once, it will speak an ephemeral message with the URL and link title.
But if you press it twice, it shows in a browseable message.

For braille, you would have it jump straight to the browseable message.

If it's the same command, it would either need something to configure it, or 
perhaps I would need a second un-mapped command that went to the browseable 
message only, and braille users could re-map the key according to their needs, 
while speech users could retain the two-press functionality.

Hi, I think it would be best to keep the commands the same with both speech and braille presenting the message o the first press and a browsable message on the second press. Thanks.



Resolving patch:
diff --git a/source/globalCommands.py b/source/globalCommands.py
--- a/source/globalCommands.py
+++ b/source/globalCommands.py
@@ -2,9 +2,9 @@
 # A part of NonVisual Desktop Access (NVDA)
 # This file is covered by the GNU General Public License.
 # See the file COPYING for more details.
-# Copyright (C) 2006-2022 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Rui Batista, Joseph Lee,
+# Copyright (C) 2006-2023 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Rui Batista, Joseph Lee,
 # Leonard de Ruijter, Derek Riemer, Babbage B.V., Davy Kager, Ethan Holliger, Łukasz Golonka, Accessolutions,
-# Julien Cochuyt, Jakub Lukowicz, Bill Dengler, Cyrille Bougot, Rob Meredith
+# Julien Cochuyt, Jakub Lukowicz, Bill Dengler, Cyrille Bougot, Rob Meredith, Luke Davis
 
 import itertools
 from typing import (
@@ -3628,6 +3628,57 @@ def script_reloadPlugins(self, gesture):
 		# Translators: Presented when plugins (app modules and global plugins) are reloaded.
 		ui.message(_("Plugins reloaded"))
 
+	@script(
+		description=_(
+			# Translators: input help mode message for Report destination URL of navigator link command
+			"Report the destination URL of the link in the navigator object. "
+			"If pressed twice, shows the URL in a window for easier review."
+		),
+		gesture="kb:NVDA+k",
+		category=SCRCAT_TOOLS
+	)
+	def script_reportLinkDestination(
+			self, gesture: inputCore.InputGesture, forceBrowseable: bool = False
+	) -> None:
+		"""Generates a ui.message or ui.browseableMessage of a link's destination, if the navigator
+		object is a link, or an element with an included link such as a graphic.
+		@param forceBrowseable: skips the press once check, and displays the browseableMessage version.
+		"""
+		obj = api.getNavigatorObject()
+		presses = scriptHandler.getLastScriptRepeatCount()
+		if (
+			obj.role == controlTypes.role.Role.LINK  # If it's a link, or
+			or controlTypes.state.State.LINKED in obj.states  # if it isn't a link but contains one
+		):
+			if (
+				presses == 1  # If pressed twice, or
+				or forceBrowseable  # if a browseable message is preferred unconditionally
+			):
+				# Translators: Informs the user that the window contains the destination of the
+				# link with given title
+				ui.browseableMessage(obj.value, title=_("Destination of: {name}").format(name=obj.name))
+			elif presses == 0:  # One press
+				ui.message(obj.value)  # Speak the link
+			else:  # Some other number of presses
+				return  # Do nothing
+		else:
+			# Translators: Tell user that the command has been run on something that is not a link
+			ui.message(_("Not a link."))
+
+	@script(
+		description=_(
+			# Translators: input help mode message for Report URL of navigator link in a window command
+			"Reports the destination URL of the link in the navigator object in a window, "
+			"instead of just speaking it. May be preferred by braille users."
+		),
+		category=SCRCAT_TOOLS
+	)
+	def script_reportLinkDestinationInWindow(self, gesture: inputCore.InputGesture) -> None:
+		"""Uses the forceBrowseable flag of script_reportLinkDestination, to generate a
+		ui.browseableMessage of a link's destination.
+		"""
+		self.script_reportLinkDestination(gesture, True)
+
 	@script(
 		# Translators: Input help mode message for a touchscreen gesture.
 		description=_("Moves to the next object in a flattened view of the object navigation hierarchy"),


Test patch: