Repository: nvaccess/nvda

Issue:
Chrome: "list" is reported on every line of a list in rich text editors
### Steps to reproduce:
1. Open this URL in Chrome:
    `data:text/html,<div contentEditable="true" role="textbox" aria-multiline="true">Before<ul><li>a</li><li>b</li></ul>After</div>`
2. Focus the text box and ensure you are in focus mode.
3. Press control+home.
4. Read through the content line by line using the down arrow key.

### Expected behavior:
```
Before
list  bullet  a
bullet  b
out of list  After
```

### Actual behavior:
```
Before
list  bullet  a
list  bullet  b
After
```

Note: Whether you hear "bullet" depends on your symbol level; I have mine set to "all".

### System configuration:
NVDA version: next-14373,6bbe5915
NVDA Installed or portable: installed
Windows version: Windows 10 Version 1703 (OS Build 16251.0)
Name and version of other software in use when reproducing the issue: Chrome Version 62.0.3201.2 (Official Build) canary (64-bit)

### Technical info:
This happens because a contentEditable list (the `ul` tag) does not get the read-only state. Lists and list boxes both get the same role (list), but they're normally differentiated by the read-only state; a `<ul>` has read-only, whereas a `<select size="2">` doesn't. However, in this case, I can kinda understand why Chrome doesn't set read-only; after all, it does have the editable state.

I think we should probably just tweak `TextInfo.getPresentationCategory` to treat editable liss as being containers; i.e. allow for the editable state as well as the read-only state in the rule for `PRESCAT_CONTAINER`. Alternatively, we could file a bug against Chrome requesting this get fixed on their side.

P2 because this is quite annoying when dealing with rich text editors in Chrome, including the Gmail composer.


Discussion:
I was trying to understand the `getPresentationCategory` function and was wondering what this is currently returning among 
```
PRESCAT_LAYOUT
PRESCAT_CONTAINER
PRESCAT_CELL
PRESCAT_SINGLELINE
PRESCAT_MARKER
```

when we are focusing on a list as pointed out in the issue.
Nevermind I think it is returning `SINGLELINE`
Filed https://crbug.com/1087551 to track this.

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
@@ -74,6 +74,22 @@ def _get_states(self):
 		return states
 
 
+class PresentationalList(ia2Web.Ia2Web):
+	"""
+	Ensures that lists like UL, DL and OL always have the readonly state.
+	A work-around for issue #7562
+	allowing us to differentiate presentational lists from interactive lists
+	(such as of size greater 1 and ARIA list boxes).
+	In firefox, this is possible by the presence of a read-only state,
+	even in a content editable.
+	"""
+
+	def _get_states(self):
+		states = super().states
+		states.add(controlTypes.STATE_READONLY)
+		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.
@@ -82,5 +98,7 @@ def findExtraOverlayClasses(obj, clsList):
 		clsList.append(ComboboxListItem)
 	elif obj.role == controlTypes.ROLE_TOGGLEBUTTON:
 		clsList.append(ToggleButton)
+	elif obj.role == controlTypes.ROLE_LIST and obj.IA2Attributes.get('tag') in ('ul', 'dl', 'ol'):
+		clsList.append(PresentationalList)
 	ia2Web.findExtraOverlayClasses(obj, clsList,
 		documentClass=Document)


Test patch:
diff --git a/tests/system/libraries/ChromeLib.py b/tests/system/libraries/ChromeLib.py
--- a/tests/system/libraries/ChromeLib.py
+++ b/tests/system/libraries/ChromeLib.py
@@ -71,9 +71,9 @@ def _writeTestFile(testCase) -> str:
 				<title>{ChromeLib._testCaseTitle}</title>
 			</head>
 			<body>
-				<button>{ChromeLib._beforeMarker}</button>
+				<p>{ChromeLib._beforeMarker}</p>
 				{testCase}
-				<button>{ChromeLib._afterMarker}</button>
+				<p>{ChromeLib._afterMarker}</p>
 			</body>
 		""")
 		with open(file=filePath, mode='w', encoding='UTF-8') as f:
diff --git a/tests/system/nvdaSettingsFiles/standard-doShowWelcomeDialog.ini b/tests/system/nvdaSettingsFiles/standard-doShowWelcomeDialog.ini
--- a/tests/system/nvdaSettingsFiles/standard-doShowWelcomeDialog.ini
+++ b/tests/system/nvdaSettingsFiles/standard-doShowWelcomeDialog.ini
@@ -12,3 +12,4 @@ schemaVersion = 2
 	enableScratchpadDir = True
 [virtualBuffers]
 	autoSayAllOnPageLoad = False
+	passThroughAudioIndication = False
diff --git a/tests/system/nvdaSettingsFiles/standard-dontShowWelcomeDialog.ini b/tests/system/nvdaSettingsFiles/standard-dontShowWelcomeDialog.ini
--- a/tests/system/nvdaSettingsFiles/standard-dontShowWelcomeDialog.ini
+++ b/tests/system/nvdaSettingsFiles/standard-dontShowWelcomeDialog.ini
@@ -12,3 +12,4 @@ schemaVersion = 2
 	enableScratchpadDir = True
 [virtualBuffers]
 	autoSayAllOnPageLoad = False
+	passThroughAudioIndication = False
diff --git a/tests/system/robot/chromeTests.py b/tests/system/robot/chromeTests.py
--- a/tests/system/robot/chromeTests.py
+++ b/tests/system/robot/chromeTests.py
@@ -39,3 +39,48 @@ def checkbox_labelled_by_inner_element():
 		# Instead this should be spoken as:
 		"Simulate evil cat  check box  not checked"
 	)
+
+
+def test_i7562():
+	""" List should not be announced on every line of a ul in a contenteditable """
+	_chrome.prepareChrome(
+		r"""
+			<div contenteditable="true">
+				<p>before</p>
+				<ul>
+					<li>frogs</li>
+					<li>birds</li>
+				</ul>
+				<p>after</p>
+			</div>
+		"""
+	)
+	actualSpeech = _chrome.getSpeechAfterKey("NVDA+space")
+	_asserts.strings_match(
+		actualSpeech,
+		"Focus mode"
+	)
+	# Tab into the contenteditable
+	actualSpeech = _chrome.getSpeechAfterKey("tab")
+	_asserts.strings_match(
+		actualSpeech,
+		"section  multi line  editable  before"
+	)
+	# DownArow into the list. 'list' should be announced when entering.
+	actualSpeech = _chrome.getSpeechAfterKey("downArrow")
+	_asserts.strings_match(
+		actualSpeech,
+		"list  bullet  frogs"
+	)
+	# DownArrow to the second list item. 'list' should not be announced.
+	actualSpeech = _chrome.getSpeechAfterKey("downArrow")
+	_asserts.strings_match(
+		actualSpeech,
+		"bullet  birds"
+	)
+	# DownArrow out of the list. 'out of list' should be announced.
+	actualSpeech = _chrome.getSpeechAfterKey("downArrow")
+	_asserts.strings_match(
+		actualSpeech,
+		"out of list  after",
+	)
diff --git a/tests/system/robot/chromeTests.robot b/tests/system/robot/chromeTests.robot
--- a/tests/system/robot/chromeTests.robot
+++ b/tests/system/robot/chromeTests.robot
@@ -26,3 +26,6 @@ default teardown
 checkbox labelled by inner element
 	[Documentation]	A checkbox labelled by an inner element should not read the label element twice.
 	checkbox_labelled_by_inner_element
+i7562
+	[Documentation]	List should not be announced on every line of a ul in a contenteditable
+	test_i7562