Bug 1178738 - Have cancel() dispatch an "interrupted" error on spoken utterance. r?smaug
MozReview-Commit-ID: DtcEWQFQYEt
--- a/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
+++ b/dom/media/webspeech/synth/cocoa/OSXSpeechSynthesizerService.mm
@@ -30,16 +30,17 @@ class SpeechTaskCallback final : public
{
public:
SpeechTaskCallback(nsISpeechTask* aTask,
NSSpeechSynthesizer* aSynth,
const nsTArray<size_t>& aOffsets)
: mTask(aTask)
, mSpeechSynthesizer(aSynth)
, mOffsets(aOffsets)
+ , mCanceled(false)
{
mStartingTime = TimeStamp::Now();
}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(SpeechTaskCallback, nsISpeechTaskCallback)
NS_DECL_NSISPEECHTASKCALLBACK
@@ -56,16 +57,17 @@ private:
float GetTimeDurationFromStart();
nsCOMPtr<nsISpeechTask> mTask;
NSSpeechSynthesizer* mSpeechSynthesizer;
TimeStamp mStartingTime;
uint32_t mCurrentIndex;
nsTArray<size_t> mOffsets;
+ bool mCanceled;
};
NS_IMPL_CYCLE_COLLECTION(SpeechTaskCallback, mTask);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SpeechTaskCallback)
NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
NS_INTERFACE_MAP_END
@@ -73,16 +75,17 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(SpeechTaskCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(SpeechTaskCallback)
NS_IMETHODIMP
SpeechTaskCallback::OnCancel()
{
NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+ mCanceled = true;
[mSpeechSynthesizer stopSpeaking];
return NS_OK;
NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
}
NS_IMETHODIMP
SpeechTaskCallback::OnPause()
@@ -158,17 +161,22 @@ SpeechTaskCallback::OnError(uint32_t aIn
// XXX: Provide more specific error messages
mTask->DispatchError(GetTimeDurationFromStart(), aIndex,
uint32_t(dom::SpeechSynthesisErrorCode::Synthesis_failed));
}
void
SpeechTaskCallback::OnDidFinishSpeaking()
{
- mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
+ if (mCanceled) {
+ mTask->DispatchError(GetTimeDurationFromStart(), mCurrentIndex,
+ uint32_t(dom::SpeechSynthesisErrorCode::Interrupted));
+ } else {
+ mTask->DispatchEnd(GetTimeDurationFromStart(), mCurrentIndex);
+ }
// no longer needed
[mSpeechSynthesizer setDelegate:nil];
mTask = nullptr;
}
@interface SpeechDelegate : NSObject<NSSpeechSynthesizerDelegate>
{
@private
--- a/dom/media/webspeech/synth/nsSpeechTask.cpp
+++ b/dom/media/webspeech/synth/nsSpeechTask.cpp
@@ -406,39 +406,35 @@ nsSpeechTask::DispatchEndInner(float aEl
nsresult
nsSpeechTask::DispatchEndImpl(float aElapsedTime, uint32_t aCharIndex)
{
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchEnd\n"));
DestroyAudioChannelAgent();
MOZ_ASSERT(mUtterance);
- if(NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
+ if(NS_WARN_IF(mUtterance->mState != SpeechSynthesisUtterance::STATE_SPEAKING)) {
return NS_ERROR_NOT_AVAILABLE;
}
// XXX: This should not be here, but it prevents a crash in MSG.
if (mStream) {
mStream->Destroy();
}
RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
if (mSpeechSynthesis) {
mSpeechSynthesis->OnEnd(this);
}
- if (utterance->mState == SpeechSynthesisUtterance::STATE_PENDING) {
- utterance->mState = SpeechSynthesisUtterance::STATE_NONE;
- } else {
- utterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
- utterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"),
- aCharIndex, aElapsedTime,
- EmptyString());
- }
+ utterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
+ utterance->DispatchSpeechSynthesisEvent(NS_LITERAL_STRING("end"),
+ aCharIndex, aElapsedTime,
+ EmptyString());
return NS_OK;
}
NS_IMETHODIMP
nsSpeechTask::DispatchPause(float aElapsedTime, uint32_t aCharIndex)
{
if (!mIndirectAudio) {
@@ -508,16 +504,22 @@ nsSpeechTask::DispatchError(float aElaps
{
LOG(LogLevel::Debug, ("nsSpeechTask::DispatchError"));
if (!mIndirectAudio) {
NS_WARNING("Can't call DispatchError() from a direct audio speech service");
return NS_ERROR_FAILURE;
}
+ return DispatchErrorInner(aElapsedTime, aCharIndex, aError);
+}
+
+nsresult
+nsSpeechTask::DispatchErrorInner(float aElapsedTime, uint32_t aCharIndex, uint32_t aError)
+{
if (!mPreCanceled) {
nsSynthVoiceRegistry::GetInstance()->SpeakNext();
}
return DispatchErrorImpl(aElapsedTime, aCharIndex, aError);
}
nsresult
@@ -527,19 +529,22 @@ nsSpeechTask::DispatchErrorImpl(float aE
if(NS_WARN_IF(mUtterance->mState == SpeechSynthesisUtterance::STATE_ENDED)) {
return NS_ERROR_NOT_AVAILABLE;
}
if (mSpeechSynthesis) {
mSpeechSynthesis->OnEnd(this);
}
- mUtterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
- mUtterance->DispatchSpeechSynthesisErrorEvent(aCharIndex, aElapsedTime,
- SpeechSynthesisErrorCode(aError));
+ RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
+ utterance->mState = (utterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING) ?
+ SpeechSynthesisUtterance::STATE_ENDED : SpeechSynthesisUtterance::STATE_NONE;
+ utterance->DispatchSpeechSynthesisErrorEvent(aCharIndex, aElapsedTime,
+ SpeechSynthesisErrorCode(aError));
+
return NS_OK;
}
NS_IMETHODIMP
nsSpeechTask::DispatchBoundary(const nsAString& aName,
float aElapsedTime, uint32_t aCharIndex)
{
if (!mIndirectAudio) {
@@ -655,17 +660,18 @@ nsSpeechTask::Cancel()
mStream->Suspend();
}
if (!mInited) {
mPreCanceled = true;
}
if (!mIndirectAudio) {
- DispatchEndInner(GetCurrentTime(), GetCurrentCharOffset());
+ DispatchErrorInner(GetCurrentTime(), GetCurrentCharOffset(),
+ uint32_t(SpeechSynthesisErrorCode::Interrupted));
}
}
void
nsSpeechTask::ForceEnd()
{
if (mStream) {
mStream->Suspend();
--- a/dom/media/webspeech/synth/nsSpeechTask.h
+++ b/dom/media/webspeech/synth/nsSpeechTask.h
@@ -104,16 +104,18 @@ private:
void End();
void SendAudioImpl(RefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen);
nsresult DispatchStartInner();
nsresult DispatchEndInner(float aElapsedTime, uint32_t aCharIndex);
+ nsresult DispatchErrorInner(float aElapsedTime, uint32_t aCharIndex, uint32_t aError);
+
void CreateAudioChannelAgent();
void DestroyAudioChannelAgent();
RefPtr<SourceMediaStream> mStream;
RefPtr<MediaInputPort> mPort;
--- a/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
+++ b/dom/media/webspeech/synth/speechd/SpeechDispatcherService.cpp
@@ -3,16 +3,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "SpeechDispatcherService.h"
#include "mozilla/dom/nsSpeechTask.h"
#include "mozilla/dom/nsSynthVoiceRegistry.h"
+#include "mozilla/dom/SpeechSynthesisErrorEvent.h"
#include "mozilla/Preferences.h"
#include "nsEscape.h"
#include "nsISupports.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsServiceManagerUtils.h"
#include "nsThreadUtils.h"
#include "prlink.h"
@@ -246,16 +247,21 @@ SpeechDispatcherCallback::OnSpeechEvent(
mTask->DispatchPause((TimeStamp::Now() - mStartTime).ToSeconds(), 0);
break;
case SPD_EVENT_RESUME:
mTask->DispatchResume((TimeStamp::Now() - mStartTime).ToSeconds(), 0);
break;
case SPD_EVENT_CANCEL:
+ mTask->DispatchError((TimeStamp::Now() - mStartTime).ToSeconds(), 0,
+ uint32_t(SpeechSynthesisErrorCode::Interrupted));
+ remove = true;
+ break;
+
case SPD_EVENT_END:
mTask->DispatchEnd((TimeStamp::Now() - mStartTime).ToSeconds(), 0);
remove = true;
break;
case SPD_EVENT_INDEX_MARK:
// Not yet supported
break;
--- a/dom/media/webspeech/synth/test/file_global_queue_cancel.html
+++ b/dom/media/webspeech/synth/test/file_global_queue_cancel.html
@@ -36,17 +36,17 @@ https://bugzilla.mozilla.org/show_bug.cg
utterance2.lang = 'it-IT-noend';
var utterance3 = new win1.SpeechSynthesisUtterance("u3: hello, losers three");
var utterance4 = new win2.SpeechSynthesisUtterance("u4: hello, losers same!");
utterance4.lang = 'it-IT-noend';
var utterance5 = new win2.SpeechSynthesisUtterance("u5: hello, losers too");
utterance5.lang = 'it-IT-noend';
- var eventOrder = ['start1', 'end1', 'start2', 'end2'];
+ var eventOrder = ['start1', 'end1', 'start2', 'error2'];
utterance1.addEventListener('start', function(e) {
is(eventOrder.shift(), 'start1', 'start1');
testSynthState(win1, { speaking: true, pending: true });
testSynthState(win2, { speaking: true, pending: true });
win2.speechSynthesis.cancel();
SpecialPowers.wrap(win1.speechSynthesis).forceEnd();
});
@@ -56,18 +56,18 @@ https://bugzilla.mozilla.org/show_bug.cg
testSynthState(win2, { pending: false });
});
utterance2.addEventListener('start', function(e) {
is(eventOrder.shift(), 'start2', 'start2');
testSynthState(win1, { speaking: true, pending: true });
testSynthState(win2, { speaking: true, pending: false });
win1.speechSynthesis.cancel();
});
- utterance2.addEventListener('end', function(e) {
- is(eventOrder.shift(), 'end2', 'end2');
+ utterance2.addEventListener('error', function(e) {
+ is(eventOrder.shift(), 'error2', 'error2');
testSynthState(win1, { speaking: false, pending: false });
testSynthState(win2, { speaking: false, pending: false });
SimpleTest.finish();
});
function wrongUtterance(e) {
ok(false, 'This shall not be uttered: "' + e.target.text + '"');
}
--- a/dom/media/webspeech/synth/test/file_indirect_service_events.html
+++ b/dom/media/webspeech/synth/test/file_indirect_service_events.html
@@ -45,17 +45,17 @@ function testFunc(done_cb) {
});
utterance.addEventListener('resume', function(e) {
is(e.charIndex, 1, 'resume event charIndex matches service arguments');
is(e.elapsedTime, 1.5, 'resume event elapsedTime matches service arguments');
speechSynthesis.cancel();
});
- utterance.addEventListener('end', function(e) {
+ utterance.addEventListener('error', function(e) {
ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
ok(e.elapsedTime, 1.5, 'end event elapsedTime matches service arguments');
test_no_events();
});
info('start speak');
speechSynthesis.speak(utterance);
}
--- a/dom/media/webspeech/synth/test/file_speech_cancel.html
+++ b/dom/media/webspeech/synth/test/file_speech_cancel.html
@@ -22,17 +22,17 @@ https://bugzilla.mozilla.org/show_bug.cg
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 1150315 **/
function testFunc(done_cb) {
- var gotEndEvent = false;
+ var gotErrorEvent = false;
// A long utterance that we will interrupt.
var utterance = new SpeechSynthesisUtterance("Donec ac nunc feugiat, posuere " +
"mauris id, pharetra velit. Donec fermentum orci nunc, sit amet maximus" +
"dui tincidunt ut. Sed ultricies ac nisi a laoreet. Proin interdum," +
"libero maximus hendrerit posuere, lorem risus egestas nisl, a" +
"ultricies massa justo eu nisi. Duis mattis nibh a ligula tincidunt" +
"tincidunt non eu erat. Sed bibendum varius vulputate. Cras leo magna," +
"ornare ac posuere vel, luctus id metus. Mauris nec quam ac augue" +
@@ -58,23 +58,24 @@ function testFunc(done_cb) {
"Curabitur velit lacus, mollis vel finibus et, molestie sit amet" +
"sapien. Proin vitae dolor ac augue posuere efficitur ac scelerisque" +
"diam. Nulla sed odio elit.");
utterance2.addEventListener('start', function() {
info('start');
speechSynthesis.cancel();
speechSynthesis.speak(utterance3);
});
- utterance2.addEventListener('end', function(e) {
- gotEndEvent = true;
+ utterance2.addEventListener('error', function(e) {
+ gotErrorEvent = true;
+ is(e.error, "interrupted", "Error event is has right error.")
});
var utterance3 = new SpeechSynthesisUtterance("Hello, world 3!");
utterance3.addEventListener('start', function() {
- ok(gotEndEvent, "didn't get start event for this utterance");
+ ok(gotErrorEvent, "didn't get error event for previous utterance");
});
utterance3.addEventListener('end', done_cb);
// Speak/cancel while paused (Bug 1187105)
speechSynthesis.pause();
speechSynthesis.speak(new SpeechSynthesisUtterance("hello."));
ok(speechSynthesis.pending, "paused speechSynthesis has an utterance queued.");
speechSynthesis.cancel();
--- a/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
+++ b/dom/media/webspeech/synth/test/nsFakeSynthServices.cpp
@@ -85,17 +85,18 @@ public:
}
return NS_OK;
}
NS_IMETHOD OnCancel() override
{
if (mTask) {
- mTask->DispatchEnd(1.5, 1);
+ mTask->DispatchError(1.5, 1,
+ uint32_t(SpeechSynthesisErrorCode::Interrupted));
}
return NS_OK;
}
NS_IMETHOD OnVolumeChanged(float aVolume) override
{
return NS_OK;
--- a/dom/media/webspeech/synth/windows/SapiService.cpp
+++ b/dom/media/webspeech/synth/windows/SapiService.cpp
@@ -8,16 +8,17 @@
#include "SapiService.h"
#include "nsServiceManagerUtils.h"
#include "nsWin32Locale.h"
#include "GeckoProfiler.h"
#include "nsEscape.h"
#include "mozilla/dom/nsSynthVoiceRegistry.h"
#include "mozilla/dom/nsSpeechTask.h"
+#include "mozilla/dom/SpeechSynthesisErrorEvent.h"
#include "mozilla/Preferences.h"
namespace mozilla {
namespace dom {
StaticRefPtr<SapiService> SapiService::sSingleton;
class SapiCallback final : public nsISpeechTaskCallback
@@ -126,19 +127,23 @@ void
SapiCallback::OnSpeechEvent(const SPEVENT& speechEvent)
{
switch (speechEvent.eEventId) {
case SPEI_START_INPUT_STREAM:
mTask->DispatchStart();
break;
case SPEI_END_INPUT_STREAM:
if (mSpeakTextLen) {
+ // mSpeakTextLen will be > 0 on any utterance except a cancel utterance.
mCurrentIndex = mSpeakTextLen;
+ mTask->DispatchEnd(GetTickCount() - mStartingTime, mCurrentIndex);
+ } else {
+ mTask->DispatchError(GetTickCount() - mStartingTime, mCurrentIndex,
+ uint32_t(SpeechSynthesisErrorCode::Interrupted));
}
- mTask->DispatchEnd(GetTickCount() - mStartingTime, mCurrentIndex);
mTask = nullptr;
break;
case SPEI_TTS_BOOKMARK:
mCurrentIndex = static_cast<ULONG>(speechEvent.lParam) - mTextOffset;
mTask->DispatchBoundary(NS_LITERAL_STRING("mark"),
GetTickCount() - mStartingTime, mCurrentIndex);
break;
case SPEI_WORD_BOUNDARY: