Repository: nvaccess/nvda

Issue:
Wrong text chunks during say all in browsers
### Steps to reproduce:

1. open a web page with Firefox or Chrome (browsers I tested);
2. start sayAll;
3. after some words or lines, stop it with control or shift;
4. change line with arrows or open a new page,;
5. relaunch sayAll.

### Actual behavior:

SayAll reads a text chunk of previous reading before the new text.

### Expected behavior:

SayAll forgot the old text and starts from new one.

### System configuration

#### NVDA installed/portable/running from source:

Installed.

#### NVDA version:

NVDA version alpha-22126,ff79f518
(but it happens in last two-three alphas)

#### Windows version:

Windows 10 20H2 build 19042.870.

#### Name and version of other software in use when reproducing the issue:

Firefox 86.0.1, various voices/synthesizers (including OneCore).



Discussion:
I've introduced this regression in #12195 I'll submit a fix later today.
Thanks, I wait pr merging to close this issue.

Resolving patch:
diff --git a/source/sayAllHandler.py b/source/sayAllHandler.py
--- a/source/sayAllHandler.py
+++ b/source/sayAllHandler.py
@@ -3,6 +3,7 @@
 # This file may be used under the terms of the GNU General Public License, version 2 or later.
 # For more details see: https://www.gnu.org/licenses/gpl-2.0.html
 
+from typing import Optional
 import weakref
 import garbageHandler
 import speech
@@ -17,9 +18,6 @@
 from speech.commands import CallbackCommand, EndUtteranceCommand
 
 
-speakWithoutPauses = speech.SpeechWithoutPauses(speakFunc=speech.speak).speakWithoutPauses
-
-
 CURSOR_CARET = 0
 CURSOR_REVIEW = 1
 
@@ -28,6 +26,18 @@
 #: This is a weakref because the manager should be allowed to die once say all is complete.
 _activeSayAll = lambda: None # Return None when called like a dead weakref.
 
+
+def getSpeechWithoutPauses() -> "speech.SpeechWithoutPauses":
+	"""Returns an instance of `speech.SpeechWithoutPauses` which should be used for say all
+	creating it if necessary."""
+	if getSpeechWithoutPauses.speechWithoutPausesInstance is None:
+		getSpeechWithoutPauses.speechWithoutPausesInstance = speech.SpeechWithoutPauses(speakFunc=speech.speak)
+	return getSpeechWithoutPauses.speechWithoutPausesInstance
+
+
+getSpeechWithoutPauses.speechWithoutPausesInstance: Optional["speech.SpeechWithoutPauses"] = None
+
+
 def stop():
 	active = _activeSayAll()
 	if active:
@@ -154,7 +164,7 @@ def nextLine(self):
 			if isinstance(self.reader.obj, textInfos.DocumentWithPageTurns):
 				# Once the last line finishes reading, try turning the page.
 				cb = CallbackCommand(self.turnPage, name="say-all:turnPage")
-				speakWithoutPauses([cb, EndUtteranceCommand()])
+				getSpeechWithoutPauses().speakWithoutPauses([cb, EndUtteranceCommand()])
 			else:
 				self.finish()
 			return
@@ -186,7 +196,7 @@ def _onLineReached(obj=self.reader.obj, state=state):
 		seq = list(speech._flattenNestedSequences(speechGen))
 		seq.insert(0, cb)
 		# Speak the speech sequence.
-		spoke = speakWithoutPauses(seq)
+		spoke = getSpeechWithoutPauses().speakWithoutPauses(seq)
 		# Update the textInfo state ready for when speaking the next line.
 		self.speakTextInfoState = state.copy()
 
@@ -208,7 +218,7 @@ def _onLineReached(obj=self.reader.obj, state=state):
 			else:
 				# We don't want to buffer too much.
 				# Force speech. lineReached will resume things when speech catches up.
-				speakWithoutPauses(None)
+				getSpeechWithoutPauses().speakWithoutPauses(None)
 				# The first buffered line has now started speaking.
 				self.numBufferedLines -= 1
 
@@ -245,7 +255,7 @@ def finish(self):
 		# we might switch synths too early and truncate the final speech.
 		# We do this by putting a CallbackCommand at the start of a new utterance.
 		cb = CallbackCommand(self.stop, name="say-all:stop")
-		speakWithoutPauses([
+		getSpeechWithoutPauses().speakWithoutPauses([
 			EndUtteranceCommand(),
 			cb,
 			EndUtteranceCommand()
diff --git a/source/speech/__init__.py b/source/speech/__init__.py
--- a/source/speech/__init__.py
+++ b/source/speech/__init__.py
@@ -117,7 +117,7 @@ def cancelSpeech():
 	# Import only for this function to avoid circular import.
 	import sayAllHandler
 	sayAllHandler.stop()
-	_speakWithoutPauses.reset()
+	sayAllHandler.getSpeechWithoutPauses().reset()
 	if beenCanceled:
 		return
 	elif speechMode==speechMode_off:
@@ -2534,9 +2534,6 @@ def _getSpeech(
 		return finalSpeechSequence
 
 
-_speakWithoutPauses = SpeechWithoutPauses(speakFunc=speak)
-
-
 #: The singleton _SpeechManager instance used for speech functions.
 #: @type: L{manager.SpeechManager}
 _manager = manager.SpeechManager()


Test patch: