Repository: nvaccess/nvda

Issue:
Not checked reported for toggle buttons in Chrome 
_Originally posted by @leonardder in https://github.com/nvaccess/nvda/pull/11377#issuecomment-662282688_

### Steps to reproduce:
Open the following test case in Chrome:

```data:text/html, <button aria-pressed="true">hello</button>```

### Actual behavior:
NVDA reports ```Hello toggle button pressed not checked```. Braille has two patterns, one pressed pattern for toggle buttons and one unchecked pattern for check boxes

### Expected behavior:
NVDA reports ```Hello toggle button pressed```. Braille has only one pattern for the pressed state of the toggle button.



Discussion:
Can you expand on this? Just to clarify the speech / braille you expect vs what you actually get. It could be argued that "checked" is a confusing term for a toggle button. 
I expanded the initial description with expected/actual behavior.
Thanks for the update @leonardder. A discussion similar to what I am talking about was had at: #11310
I think this should be fixed in Chrome, it does not reproduce in Firefox. 

See below for dev info for this object. Note the difference is the inclusion in Chrome of IAccessible2 state `IA2_STATE_CHECKABLE (263168)` and IAccessible2 attribute `checkable:true`

In Chrome:
```
Developer info for navigator object:
name: 'hello'
role: ROLE_TOGGLEBUTTON
roleText: None
states: STATE_PRESSED, STATE_FOCUSABLE, STATE_FOCUSED, STATE_CHECKABLE
isFocusable: True
hasFocus: True
Python object: <NVDAObjects.IAccessible.ia2Web.Ia2Web object at 0x007D16F0>
Python class mro: (<class 'NVDAObjects.IAccessible.ia2Web.Ia2Web'>, <class 'NVDAObjects.IAccessible.IAccessible'>, <class 'NVDAObjects.window.Window'>, <class 'NVDAObjects.NVDAObject'>, <class 'documentBase.TextContainerObject'>, <class 'baseObject.ScriptableObject'>, <class 'baseObject.AutoPropertyObject'>, <class 'object'>)
description: None
location: RectLTWH(left=8, top=79, width=45, height=21)
value: ''
appModule: <'appModuleHandler' (appName 'chrome', process ID 2248) at address 3590c90>
appModule.productName: 'Google Chrome'
appModule.productVersion: '84.0.4147.89'
TextInfo: <class 'NVDAObjects.IAccessible.IA2TextTextInfo'>
windowHandle: 658476
windowClassName: 'Chrome_RenderWidgetHostHWND'
windowControlID: 151411856
windowStyle: 1445986304
extendedWindowStyle: 32
windowThreadID: 5212
windowText: 'Chrome Legacy Window'
displayText: ''
IAccessibleObject: <POINTER(IAccessible2) ptr=0x522ee0c at 6dfff80>
IAccessibleChildID: 0
IAccessible event parameters: windowHandle=658476, objectID=-4, childID=-309
IAccessible accName: 'hello'
IAccessible accRole: ROLE_SYSTEM_PUSHBUTTON
IAccessible accState: STATE_SYSTEM_FOCUSED, STATE_SYSTEM_PRESSED, STATE_SYSTEM_FOCUSABLE, STATE_SYSTEM_VALID (1048588)
IAccessible accDescription: None
IAccessible accValue: ''
IAccessible2 windowHandle: 658476
IAccessible2 uniqueID: -309
IAccessible2 role: IA2_ROLE_TOGGLE_BUTTON
IAccessible2 states: IA2_STATE_OPAQUE, IA2_STATE_CHECKABLE (263168)
IAccessible2 attributes: 'display:inline-block;tag:button;checkable:true;'
```
In Firefox
```
Developer info for navigator object:
name: 'hello'
role: ROLE_TOGGLEBUTTON
roleText: None
states: STATE_PRESSED, STATE_FOCUSABLE, STATE_FOCUSED
isFocusable: True
hasFocus: True
Python object: <NVDAObjects.IAccessible.mozilla.Mozilla object at 0x06D3B470>
Python class mro: (<class 'NVDAObjects.IAccessible.mozilla.Mozilla'>, <class 'NVDAObjects.IAccessible.ia2Web.Ia2Web'>, <class 'NVDAObjects.IAccessible.IAccessible'>, <class 'NVDAObjects.window.Window'>, <class 'NVDAObjects.NVDAObject'>, <class 'documentBase.TextContainerObject'>, <class 'baseObject.ScriptableObject'>, <class 'baseObject.AutoPropertyObject'>, <class 'object'>)
description: ''
location: RectLTWH(left=9, top=77, width=63, height=29)
value: None
appModule: <'firefox' (appName 'firefox', process ID 10132) at address 4f98c30>
appModule.productName: 'Firefox'
appModule.productVersion: '78.0.2'
TextInfo: <class 'NVDAObjects.IAccessible.IA2TextTextInfo'>
windowHandle: 132294
windowClassName: 'MozillaWindowClass'
windowControlID: 0
windowStyle: 399441920
extendedWindowStyle: 256
windowThreadID: 14148
windowText: 'Mozilla Firefox'
displayText: ''
IAccessibleObject: <POINTER(IAccessible2) ptr=0x5217954 at 6dffb70>
IAccessibleChildID: 0
IAccessible event parameters: windowHandle=132294, objectID=-4, childID=-117440520
IAccessible accName: 'hello'
IAccessible accRole: ROLE_SYSTEM_PUSHBUTTON
IAccessible accState: STATE_SYSTEM_FOCUSED, STATE_SYSTEM_PRESSED, STATE_SYSTEM_FOCUSABLE, STATE_SYSTEM_VALID (1048588)
IAccessible accDescription: ''
IAccessible accValue: None
IAccessible2 windowHandle: 132294
IAccessible2 uniqueID: -117440520
IAccessible2 role: IA2_ROLE_TOGGLE_BUTTON
IAccessible2 states: IA2_STATE_OPAQUE, IA2_STATE_SELECTABLE_TEXT (5120)
IAccessible2 attributes: 'margin-left:0px;text-align:center;text-indent:0px;margin-right:0px;tag:button;margin-top:0px;margin-bottom:0px;display:inline-block;'
```
Yes, this is a bug in Chrome, but I think we should consider working 
around it, at least temporarily.

I have added the 2020.3 milestone, we should try to ensure this is fixed prior to the release.
cc: @ObjectInSpace or @aleventhal, is there a bug on Chromium for this?
No bug number, but addressing it on Chromium side.

Resolving patch:
diff --git a/source/NVDAObjects/IAccessible/chromium.py b/source/NVDAObjects/IAccessible/chromium.py
--- a/source/NVDAObjects/IAccessible/chromium.py
+++ b/source/NVDAObjects/IAccessible/chromium.py
@@ -12,10 +12,22 @@
 import controlTypes
 import IAccessibleHandler
 from NVDAObjects.IAccessible import IAccessible
-from virtualBuffers.gecko_ia2 import Gecko_ia2 as GeckoVBuf
+from virtualBuffers.gecko_ia2 import Gecko_ia2 as GeckoVBuf, Gecko_ia2_TextInfo as GeckoVBufTextInfo
 from . import ia2Web
 
+
+class ChromeVBufTextInfo(GeckoVBufTextInfo):
+
+	def _normalizeControlField(self, attrs):
+		attrs = super()._normalizeControlField(attrs)
+		if attrs['role'] == controlTypes.ROLE_TOGGLEBUTTON and controlTypes.STATE_CHECKABLE in attrs['states']:
+			# In Chromium, the checkable state is exposed erroneously on toggle buttons.
+			attrs['states'].discard(controlTypes.STATE_CHECKABLE)
+		return attrs
+
+
 class ChromeVBuf(GeckoVBuf):
+	TextInfo = ChromeVBufTextInfo
 
 	def __contains__(self, obj):
 		if obj.windowHandle != self.rootNVDAObject.windowHandle:
@@ -32,6 +44,7 @@ def __contains__(self, obj):
 			return False
 		return not self._isNVDAObjectInApplication(obj)
 
+
 class Document(ia2Web.Document):
 
 	def _get_treeInterceptorClass(self):
@@ -51,11 +64,23 @@ def _get_focusRedirect(self):
 		if self.parent and controlTypes.STATE_INVISIBLE in self.parent.states:
 			return self.parent.parent
 
+
+class ToggleButton(ia2Web.Ia2Web):
+
+	def _get_states(self):
+		# In Chromium, the checkable state is exposed erroneously on toggle buttons.
+		states = super().states
+		states.discard(controlTypes.STATE_CHECKABLE)
+		return states
+
+
 def findExtraOverlayClasses(obj, clsList):
 	"""Determine the most appropriate class(es) for Chromium objects.
 	This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method.
 	"""
 	if obj.role==controlTypes.ROLE_LISTITEM and obj.parent and obj.parent.parent and obj.parent.parent.role==controlTypes.ROLE_COMBOBOX:
 		clsList.append(ComboboxListItem)
+	elif obj.role == controlTypes.ROLE_TOGGLEBUTTON:
+		clsList.append(ToggleButton)
 	ia2Web.findExtraOverlayClasses(obj, clsList,
 		documentClass=Document)


Test patch: