Repository: nvaccess/nvda

Issue:
NVDA announces a button with role="switch" and aria-checked="true" as "pressed checked"
### Steps to reproduce:
1. Turn on NVDA and navigate to the button in the page(https://codepen.io/iamrafan/pen/NoPrBP?editors=1000) using NVDA
2. Observe how the toggle button is announced

### Actual behavior:
NVDA announces the toggle as "Improved dialog toggle button pressed  checked"

### Expected behavior:
The toggle is expected  to be announced as "Improved dialog toggle button checked" or " Improved dialog toggle button pressed"

### System configuration
#### NVDA installed

#### NVDA version: 2018.4

#### Windows version: Windows 10 Enterprise 1803

#### Does the issue still occur after restarting your PC? Yes

#### Have you tried any other versions of NVDA? Yes
 


Discussion:
@iamrafan: Please list all browsers (including version) where this issue occurs. Thanks.
Comfirmed with Firefox 64.0.2 and Chrome. Firefox just ignores the aria-checked attribute with regard to the pressed state, Chrome seems to add both the checked and pressed states when aria-checked is true.

@jcsteh and  @MarcoZehe: could one of you spread some light on this?

It looks like there are two ways to implement toggle buttons, both which are considered valid implementation strategies according to the Mozilla Developer Network.

* role="switch", the pressed/unpressed state is covered with aria-checked
* role="button", the pressed/unpressed state is covered with aria-pressed
Ug. Yet another piece of ARIA spec silliness.

1. In terms of authoring, you're correct: for role="switch", you use aria-checked; for role="button", you use aria-pressed.
2. The mapping spec maps both [role="button" with aria-pressed](https://www.w3.org/TR/core-aam-1.2/#role-map-button-pressed) and [role="switch"](https://www.w3.org/TR/core-aam-1.2/#role-map-switch) to IA2_ROLE_TOGGLE_BUTTON.
3. [aria-pressed is mapped to the pressed state](https://www.w3.org/TR/core-aam-1.2/#ariaPressedTrue) and [aria-checked is mapped to the checked state](https://www.w3.org/TR/core-aam-1.2/#ariaCheckedTrue), regardless of the role.
4. Points 2 and 3 combined are somewhat problematic, as a client can't use the IA2 role to determine whether it should be looking for the pressed or checked state.
5. Firefox chooses checked as per strict interpretation of the spec. Chrome seems to hedge its bets and expose both. IMO, it's hard to argue that either are "wrong" given the spec's weirdness on this matter.
6. As per the spec, the object attribute xml-roles:switch is exposed for role="switch". IMO, we shouldn't have to use this to figure out what states to look for, but alsdkjfals.
7. The [spec says that switch](https://www.w3.org/TR/wai-aria-1.2/#switch) is "a type of checkbox". That would suggest we should prefer to report "checked"/"not checked" to the user, though we may wish to create a new NVDA role for switch and report "on"/"off".
8. In order to properly report these to the user, we're going to have to use xml-roles as per point 6. Alternatively, we could try to get the ARIA spec changed to expose ROLE_SYSTEM_CHECKBUTTON for role="switch" instead of IA2_ROLE_TOGGLE_BUTTON. However, getting that change into the spec and browsers and then to users will take months. Given that screen readers already have to look at IA2 attributes for various cases, it's probably not worth the effort at this point.
@DrSooom This specific issue occurs in both Chrome and Firefox


  | AT/Browser | Version | Announced as
-- | -- | -- | --
1 | Edge/NVDA | Microsoft Edge 42.17134.1.0 Microsoft EdgeHTML 17.17134 | toggle button   pressed
2 | Chrome/NVDA | 71.0.3578.98 | toggle button   pressed checked
3 | IE/NVDA | 11.345.17134.0 | button
4 | Firefox/NVDA | 64.0 | toggle button  not pressed checked



Do you want me to file separate issues for:
1) NVDA not announcing the state in IE when aria-checked="true" ?
2) NVDA announcing aria-checked="true" as not pressed in Firefox ?
@iamrafan, thanks for your report. I think the issue is clear to us, especially thanks to the clarification by @jcsteh in https://github.com/nvaccess/nvda/issues/9187#issuecomment-457001861. It looks like the browser vendors do not yet agree and the ARIA spec is pretty messy in this. I would advice against using the switch role altogether and use the button role with aria-pressed instead.
I honestly think trying to get the spec changed isn't worth the effort at this point. The simplest solution for now is to implement an overlay in NVDAObjects.IAccessible.ia2Web for xml-roles:switch, expose ROLE_CHECKBOX and remove STATE_PRESSED if present.
@jcsteh wrote:
> The simplest solution for now is to implement an overlay in NVDAObjects.IAccessible.ia2Web for xml-roles:switch, expose ROLE_CHECKBOX and remove STATE_PRESSED if present.

What do you think about the alternative: Replace STATE_CHECKED with STATE_PRESSED? I think I prefer this approach:

1. It respects the aria role. A switch behaves like a checkbox, but it is supposed to be a toggle button
2. It will/can be consistent across browsers. It seems that Edge maps aria-checked to pressed in the case of switch, see https://github.com/nvaccess/nvda/issues/9187#issuecomment-458672152
3. While it does not respect the ARIA spec, changing the role to CHECKBOX wouldn't follow the spec either. The only way to follow the spec would be speaking the negative CHECKED state if appropriate, but that would be really hacky and is definitely not the way I'd go. Using STATE_PRESSED consistently actually implements the spec as we would have intended it to be (i.e. having aria-pressed for switch instead of aria-checked).
Your arguments are based on the premise that a switch is "supposed" to be a toggle button according to the spec. However, that is debatable. The spec says that a switch is "a type of checkbox". It doesn't say "a type of button". If you map it to checkbox, then speaking the negative checked state will "just happen" and is not hacky at all.

The fact that Edge treats it as a toggle button might be a convincing argument except that Edge will no longer be a real concern for much longer. :(

I don't really have a strong opinion on whether it should be a toggle button or a checkbox. My point is that I don't think you can convincingly use the spec to argue either way.
> The spec says that a switch is "a type of checkbox". It doesn't say "a type of button". If you map it to checkbox, then speaking the negative checked state will "just happen" and is not hacky at all. 

You're correct.

> The fact that Edge treats it as a toggle button might be a convincing argument except that Edge will no longer be a real concern for much longer. :(

True. However, there is the UIA proxy for IA2, which I think will use the pressed state as expose by Chrome. I have to check that, though

Re #9219: the switches in Skype 8 really look like toggle buttons visually.

@michaelDCurran, may be you can cut the knot for us?
[**@leonardder**](https://github.com/leonardder) commented on [31 Jan 2019, 14:33 CET](https://github.com/nvaccess/nvda/issues/9187#issuecomment-459345187 "2019-01-31T13:33:27Z - Replied by Github Reply Comments"):
> There is the UIA proxy for IA2, which I think will use the pressed state as exposed by Chrome.

Nope, it looks like this is not yet complete. Both switches and buttons are treated as buttons, however there is currently no implementation for the relevant states. Tested with Windows build 18323
I just ran into this again with Riot. They're using the switch role and aria-checked, which in Firefox (and Chrome) sounds really messy. I am trying to change it to aria-pressed instead, but was pointed to various conflicting views on this, including this issue. Because the AAM says to map this to a toggle button, I am inclined to argue for aria-pressed, and maybe file spec bugs just for posterity. That, for Riot, is at least a fix that works and stops NVDA from saying stupid things like "not pressed checked".

Resolving patch:
diff --git a/source/NVDAObjects/IAccessible/ia2Web.py b/source/NVDAObjects/IAccessible/ia2Web.py
--- a/source/NVDAObjects/IAccessible/ia2Web.py
+++ b/source/NVDAObjects/IAccessible/ia2Web.py
@@ -179,6 +179,19 @@ def _get_mathMl(self):
 				"Not supported in this browser or ISimpleDOM COM proxy not registered.", exc_info=True)
 			raise LookupError
 
+
+class Switch(Ia2Web):
+	# role="switch" gets mapped to IA2_ROLE_TOGGLE_BUTTON, but it uses the
+	# checked state instead of pressed. The simplest way to deal with this
+	# identity crisis is to map it to a check box.
+	role = controlTypes.ROLE_CHECKBOX
+
+	def _get_states(self):
+		states = super().states
+		states.discard(controlTypes.STATE_PRESSED)
+		return states
+
+
 def findExtraOverlayClasses(obj, clsList, baseClass=Ia2Web, documentClass=None):
 	"""Determine the most appropriate class if this is an IA2 web object.
 	This should be called after finding any classes for the specific web implementation.
@@ -204,6 +217,8 @@ def findExtraOverlayClasses(obj, clsList, baseClass=Ia2Web, documentClass=None):
 		clsList.append(Dialog)
 	elif iaRole == oleacc.ROLE_SYSTEM_EQUATION:
 		clsList.append(Math)
+	elif xmlRoles[0] == "switch":
+		clsList.append(Switch)
 
 	if iaRole==oleacc.ROLE_SYSTEM_APPLICATION:
 		clsList.append(Application)
diff --git a/source/virtualBuffers/gecko_ia2.py b/source/virtualBuffers/gecko_ia2.py
--- a/source/virtualBuffers/gecko_ia2.py
+++ b/source/virtualBuffers/gecko_ia2.py
@@ -103,6 +103,12 @@ def _normalizeControlField(self,attrs):
 			role = controlTypes.ROLE_FIGURE
 		elif role in (controlTypes.ROLE_LANDMARK, controlTypes.ROLE_SECTION) and xmlRoles[0] == "region":
 			role = controlTypes.ROLE_REGION
+		elif xmlRoles[0] == "switch":
+			# role="switch" gets mapped to IA2_ROLE_TOGGLE_BUTTON, but it uses the
+			# checked state instead of pressed. The simplest way to deal with this
+			# identity crisis is to map it to a check box.
+			role = controlTypes.ROLE_CHECKBOX
+			states.discard(controlTypes.STATE_PRESSED)
 		attrs['role']=role
 		attrs['states']=states
 		if level is not "" and level is not None:
@@ -278,7 +284,10 @@ def _searchableAttribsForNodeType(self,nodeType):
 		elif nodeType=="comboBox":
 			attrs={"IAccessible::role":[oleacc.ROLE_SYSTEM_COMBOBOX]}
 		elif nodeType=="checkBox":
-			attrs={"IAccessible::role":[oleacc.ROLE_SYSTEM_CHECKBUTTON]}
+			attrs = [
+				{"IAccessible::role": [oleacc.ROLE_SYSTEM_CHECKBUTTON]},
+				{"IAccessible2::attribute_xml-roles": [VBufStorage_findMatch_word("switch")]},
+			]
 		elif nodeType=="graphic":
 			attrs={"IAccessible::role":[oleacc.ROLE_SYSTEM_GRAPHIC]}
 		elif nodeType=="blockQuote":


Test patch: