Repository: nvaccess/nvda

Issue:
UIA/Edge: no aria-roledscription and other properties are rendered in browse mode
Hi,

At the moment NVDA does not render aria-roledescription and friends via Microsoft Edge.

### Steps to reproduce:

1. With multiple web browsers open (including Edge), visit http://www.ets-research.org/ia11ylab/aria11spec/.
2. Navigate through examples. The key example to look at will be 29 and 30.
3. Use browse mode commands (including arrow keys) to read examples (in this case, buttons).

### Actual behavior:
NVDA does not announce "attachment button".

### Expected behavior:
NVDA announces "attachment button" along with button label if present.

### System configuration:

#### NVDA Installed/portable/running from source:
Installed

#### NVDA version:
Next.15391

#### Windows version:
Windows 10 Version 1803

#### Name and version of other software in use when reproducing the issue:
Microsoft Edge, Firefox 61.0

#### Other information about your system:
Windows 10 App Essentials add-on is installed, but the result is the same no matter if add-ons are enabled or disabled.

### Other questions:

#### Does the issue still occur after restarting your PC?
Yes

#### Have you tried any other versions of NVDA?
Yes (latest master, 2018.2.1)

### Technical details

When using the following procedure, roledescriptoin and friends are returned:

1. With Edge opened to the website described above, focus on the attachment button.
2. Open Python Console (Control+NvDA+Z) and do:

import UIAHandler
nav._getUIACacheablePropertyValue(30102)

Note: 30102 is UIA_AriaPropertiesPropertyId constant.

If properties exist, they will appear as a string value of the form:

property=str;property2=str; ...

Which should then allow str.split(";") to return a list of property=str collection, which can be coerced into a properties dictionary. This dictionary should then be used to work with these properties in various places, chiefly in UIA browse mode module.

Thanks.


Discussion:
Hi,

The rationale for this are threefold:

1. Updating assumptions and attitudes about EdgeHTML browse mode: currently NVDA Core assumes Edge objects as being useful for basic web navigation. However, as this area is seeing rapid development, our old assumptions are quickly becoming a liability, including not being updated to support ARIA 1.1 standards that UIA/Edge exposes. Thus, as a form of updating our assumptions, I propose looking into Edge objects collection in hopes of adding roledescription and other goodies from ARIA.
2. Consistency: a subtle strength of keeping an eye on standards compliance is providing consistent user experience for many cases. This includes recent advancements in supporting ARIA in various browse mode implementations (MSHTML, Gecko, etc.). However, as Windows 10 ships with Edge and with more and more people using it for web accessibility testing purposes, I would imagine someone to ask NV Access and others regarding how nVDA is doing in supporting certain standards via Edge. Thus, I propose adding roledescription support for consistency reasons.
3. Windows 10 in S Mode: Microsoft does not allow third-party web rendering engines on its Store, meaning web browsers are asked to use EdgeHTML rendering engine (if I read the documentation correctly). Because Edge is set as default web browser in Windows 10 in S Mode (formerly Windows 10 S), some would elect to use it for various tasks, including web accessibility as noted in item 2. Thus, I propose that we look into supporting roledescription for sake of opening up more opportunities for folks who would be using Windows 10 in S Mode/Edge.

Thanks.

Resolving patch:
diff --git a/source/NVDAObjects/UIA/edge.py b/source/NVDAObjects/UIA/edge.py
--- a/source/NVDAObjects/UIA/edge.py
+++ b/source/NVDAObjects/UIA/edge.py
@@ -133,6 +133,13 @@ def move(self,unit,direction,endPoint=None,skipReplacedContent=True):
 	def _getControlFieldForObject(self,obj,isEmbedded=False,startOfNode=False,endOfNode=False):
 		field=super(EdgeTextInfo,self)._getControlFieldForObject(obj,isEmbedded=isEmbedded,startOfNode=startOfNode,endOfNode=endOfNode)
 		field['embedded']=isEmbedded
+		role=field.get('role')
+		# Fields should be treated as block for certain roles.
+		# This can affect whether the field is presented as a container (e.g.  announcing entering and exiting) 
+		if role in (controlTypes.ROLE_GROUPING,controlTypes.ROLE_SECTION,controlTypes.ROLE_PARAGRAPH):
+			field['isBlock']=True
+		# ARIA roledescription
+		field['roleText']=obj.roleText
 		# report landmarks
 		field['landmark']=obj.landmark
 		# Combo boxes with a text pattern are editable
@@ -471,6 +478,9 @@ def _get_isCurrent(self):
 			return valueOfAriaCurrent
 		return None
 
+	def _get_roleText(self):
+		return self.ariaProperties.get('roledescription', None)
+
 	def _get_placeholder(self):
 		ariaPlaceholder = self.ariaProperties.get('placeholder', None)
 		return ariaPlaceholder
diff --git a/source/braille.py b/source/braille.py
--- a/source/braille.py
+++ b/source/braille.py
@@ -622,6 +622,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
 	value=field.get('value',None)
 	current=field.get('current', None)
 	placeholder=field.get('placeholder', None)
+	roleText=field.get('roleText')
 
 	if presCat == field.PRESCAT_LAYOUT:
 		text = []
@@ -654,7 +655,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
 			# Don't report the role for math here.
 			# However, we still need to pass it (hence "_role").
 			"_role" if role == controlTypes.ROLE_MATH else "role": role,
-			"states": states,"value":value, "current":current, "placeholder":placeholder}
+			"states": states,"value":value, "current":current, "placeholder":placeholder,"roleText":roleText}
 		if config.conf["presentation"]["reportKeyboardShortcuts"]:
 			kbShortcut = field.get("keyboardShortcut")
 			if kbShortcut:
@@ -684,7 +685,7 @@ def getControlFieldBraille(info, field, ancestors, reportStart, formatConfig):
 		# Translators: Displayed in braille at the end of a control field such as a list or table.
 		# %s is replaced with the control's role.
 		return (_("%s end") %
-			getBrailleTextForProperties(role=role))
+			getBrailleTextForProperties(role=role,roleText=roleText))
 
 def getFormatFieldBraille(field, fieldCache, isAtStart, formatConfig):
 	"""Generates the braille text for the given format field.
diff --git a/source/browseMode.py b/source/browseMode.py
--- a/source/browseMode.py
+++ b/source/browseMode.py
@@ -1046,14 +1046,14 @@ def getControlFieldSpeech(self, attrs, ancestorAttrs, fieldType, formatConfig=No
 		textList = []
 		landmark = attrs.get("landmark")
 		if formatConfig["reportLandmarks"] and fieldType == "start_addedToControlFieldStack" and landmark:
-			try:
-				textList.append(attrs["name"])
-			except KeyError:
-				pass
-			if landmark == "region":
-				# The word landmark is superfluous for regions.
-				textList.append(aria.landmarkRoles[landmark])
-			else:
+			# Ensure that the name of the field gets presented even if normally it wouldn't. 
+			name=attrs.get('name')
+			if name and attrs.getPresentationCategory(ancestorAttrs,formatConfig,reason) is None:
+				textList.append(name)
+				if landmark == "region":
+					# The word landmark is superfluous for regions.
+					textList.append(aria.landmarkRoles[landmark])
+			if landmark != "region":
 				textList.append(_("%s landmark") % aria.landmarkRoles[landmark])
 		textList.append(super(BrowseModeDocumentTextInfo, self).getControlFieldSpeech(attrs, ancestorAttrs, fieldType, formatConfig, extraDetail, reason))
 		return " ".join(textList)
@@ -1062,14 +1062,14 @@ def getControlFieldBraille(self, field, ancestors, reportStart, formatConfig):
 		textList = []
 		landmark = field.get("landmark")
 		if formatConfig["reportLandmarks"] and reportStart and landmark and field.get("_startOfNode"):
-			try:
-				textList.append(field["name"])
-			except KeyError:
-				pass
-			if landmark == "region":
-				# The word landmark is superfluous for regions.
-				textList.append(braille.landmarkLabels[landmark])
-			else:
+			# Ensure that the name of the field gets presented even if normally it wouldn't. 
+			name=field.get('name')
+			if name and field.getPresentationCategory(ancestors,formatConfig) is None:
+				textList.append(name)
+				if landmark == "region":
+					# The word landmark is superfluous for regions.
+					textList.append(braille.landmarkLabels[landmark])
+			if landmark != "region":
 				# Translators: This is brailled to indicate a landmark (example output: lmk main).
 				textList.append(_("lmk %s") % braille.landmarkLabels[landmark])
 		text = super(BrowseModeDocumentTextInfo, self).getControlFieldBraille(field, ancestors, reportStart, formatConfig)


Test patch: