Bug 863852 - Part 2 - Handle reentrance in the state machine. r=smaug

This commit is contained in:
Guilherme Gonçalves 2013-04-24 10:40:55 -07:00 коммит произвёл John Schoenick
Родитель 7f1573cedd
Коммит 597f33c342
6 изменённых файлов: 276 добавлений и 110 удалений

Просмотреть файл

@ -38,10 +38,6 @@ static const uint32_t kSPEECH_DETECTION_TIMEOUT_MS = 10000;
// kSAMPLE_RATE frames = 1s, kESTIMATION_FRAMES frames = 300ms // kSAMPLE_RATE frames = 1s, kESTIMATION_FRAMES frames = 300ms
static const uint32_t kESTIMATION_SAMPLES = 300 * kSAMPLE_RATE / 1000; static const uint32_t kESTIMATION_SAMPLES = 300 * kSAMPLE_RATE / 1000;
#define STATE_EQUALS(state) (mCurrentState == state)
#define STATE_BETWEEN(state1, state2) \
(mCurrentState >= (state1) && mCurrentState <= (state2))
#ifdef PR_LOGGING #ifdef PR_LOGGING
PRLogModuleInfo* PRLogModuleInfo*
GetSpeechRecognitionLog() GetSpeechRecognitionLog()
@ -68,8 +64,7 @@ NS_IMPL_RELEASE_INHERITED(SpeechRecognition, nsDOMEventTargetHelper)
struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig; struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
SpeechRecognition::SpeechRecognition() SpeechRecognition::SpeechRecognition()
: mProcessingEvent(false) : mEndpointer(kSAMPLE_RATE)
, mEndpointer(kSAMPLE_RATE)
, mAudioSamplesPerChunk(mEndpointer.FrameSize()) , mAudioSamplesPerChunk(mEndpointer.FrameSize())
, mSpeechDetectionTimer(do_CreateInstance(NS_TIMER_CONTRACTID)) , mSpeechDetectionTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
{ {
@ -89,7 +84,21 @@ SpeechRecognition::SpeechRecognition()
Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000)); Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000));
mEndpointer.set_long_speech_length( mEndpointer.set_long_speech_length(
Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 3 * 1000000)); Preferences::GetInt(PREFERENCE_ENDPOINTER_SILENCE_LENGTH, 3 * 1000000));
mCurrentState = Reset(); Reset();
}
bool
SpeechRecognition::StateBetween(FSMState begin, FSMState end)
{
return mCurrentState >= begin && mCurrentState <= end;
}
void
SpeechRecognition::SetState(FSMState state)
{
mCurrentState = state;
SR_LOG("Transitioned to state %s", GetName(mCurrentState));
return;
} }
JSObject* JSObject*
@ -125,17 +134,22 @@ SpeechRecognition::ProcessEvent(SpeechEvent* aEvent)
GetName(aEvent), GetName(aEvent),
GetName(mCurrentState)); GetName(mCurrentState));
MOZ_ASSERT(!mProcessingEvent, "Event dispatch should be sequential!"); // Run priority events first
mProcessingEvent = true; for (uint32_t i = 0; i < mPriorityEvents.Length(); ++i) {
nsRefPtr<SpeechEvent> event = mPriorityEvents[i];
mCurrentState = TransitionAndGetNextState(aEvent); SR_LOG("Processing priority %s", GetName(event));
SR_LOG("Transitioned to state: %s", GetName(mCurrentState)); Transition(event);
}
mProcessingEvent = false; mPriorityEvents.Clear();
SR_LOG("Processing %s received as argument", GetName(aEvent));
Transition(aEvent);
} }
SpeechRecognition::FSMState void
SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent) SpeechRecognition::Transition(SpeechEvent* aEvent)
{ {
switch (mCurrentState) { switch (mCurrentState) {
case STATE_IDLE: case STATE_IDLE:
@ -143,112 +157,145 @@ SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
case EVENT_START: case EVENT_START:
// TODO: may want to time out if we wait too long // TODO: may want to time out if we wait too long
// for user to approve // for user to approve
return STATE_STARTING; WaitForAudioData(aEvent);
break;
case EVENT_STOP: case EVENT_STOP:
case EVENT_ABORT: case EVENT_ABORT:
case EVENT_AUDIO_DATA: case EVENT_AUDIO_DATA:
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT: case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT: case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_AUDIO_ERROR: case EVENT_AUDIO_ERROR:
case EVENT_RECOGNITIONSERVICE_ERROR: case EVENT_RECOGNITIONSERVICE_ERROR:
return AbortError(aEvent); AbortError(aEvent);
break;
case EVENT_COUNT: case EVENT_COUNT:
MOZ_NOT_REACHED("Invalid event EVENT_COUNT"); MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
} }
break;
case STATE_STARTING: case STATE_STARTING:
switch (aEvent->mType) { switch (aEvent->mType) {
case EVENT_AUDIO_DATA: case EVENT_AUDIO_DATA:
return StartedAudioCapture(aEvent); StartedAudioCapture(aEvent);
break;
case EVENT_AUDIO_ERROR: case EVENT_AUDIO_ERROR:
case EVENT_RECOGNITIONSERVICE_ERROR: case EVENT_RECOGNITIONSERVICE_ERROR:
return AbortError(aEvent); AbortError(aEvent);
break;
case EVENT_ABORT: case EVENT_ABORT:
return AbortSilently(aEvent); AbortSilently(aEvent);
break;
case EVENT_STOP: case EVENT_STOP:
return Reset(); Reset();
break;
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT: case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT: case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_START: case EVENT_START:
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent)); SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
MOZ_NOT_REACHED(""); MOZ_NOT_REACHED("");
case EVENT_COUNT: case EVENT_COUNT:
MOZ_NOT_REACHED("Invalid event EVENT_COUNT"); MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
} }
break;
case STATE_ESTIMATING: case STATE_ESTIMATING:
switch (aEvent->mType) { switch (aEvent->mType) {
case EVENT_AUDIO_DATA: case EVENT_AUDIO_DATA:
return WaitForEstimation(aEvent); WaitForEstimation(aEvent);
break;
case EVENT_STOP: case EVENT_STOP:
return StopRecordingAndRecognize(aEvent); StopRecordingAndRecognize(aEvent);
break;
case EVENT_ABORT: case EVENT_ABORT:
return AbortSilently(aEvent); AbortSilently(aEvent);
break;
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT: case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT: case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
case EVENT_RECOGNITIONSERVICE_ERROR: case EVENT_RECOGNITIONSERVICE_ERROR:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_AUDIO_ERROR: case EVENT_AUDIO_ERROR:
return AbortError(aEvent); AbortError(aEvent);
break;
case EVENT_START: case EVENT_START:
SR_LOG("STATE_ESTIMATING: Unhandled event %d", aEvent->mType); SR_LOG("STATE_ESTIMATING: Unhandled event %d", aEvent->mType);
MOZ_NOT_REACHED(""); MOZ_NOT_REACHED("");
case EVENT_COUNT: case EVENT_COUNT:
MOZ_NOT_REACHED("Invalid event EVENT_COUNT"); MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
} }
break;
case STATE_WAITING_FOR_SPEECH: case STATE_WAITING_FOR_SPEECH:
switch (aEvent->mType) { switch (aEvent->mType) {
case EVENT_AUDIO_DATA: case EVENT_AUDIO_DATA:
return DetectSpeech(aEvent); DetectSpeech(aEvent);
break;
case EVENT_STOP: case EVENT_STOP:
return StopRecordingAndRecognize(aEvent); StopRecordingAndRecognize(aEvent);
break;
case EVENT_ABORT: case EVENT_ABORT:
return AbortSilently(aEvent); AbortSilently(aEvent);
break;
case EVENT_AUDIO_ERROR: case EVENT_AUDIO_ERROR:
return AbortError(aEvent); AbortError(aEvent);
break;
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT: case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT: case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
case EVENT_RECOGNITIONSERVICE_ERROR: case EVENT_RECOGNITIONSERVICE_ERROR:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_START: case EVENT_START:
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent)); SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
MOZ_NOT_REACHED(""); MOZ_NOT_REACHED("");
case EVENT_COUNT: case EVENT_COUNT:
MOZ_NOT_REACHED("Invalid event EVENT_COUNT"); MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
} }
break;
case STATE_RECOGNIZING: case STATE_RECOGNIZING:
switch (aEvent->mType) { switch (aEvent->mType) {
case EVENT_AUDIO_DATA: case EVENT_AUDIO_DATA:
return WaitForSpeechEnd(aEvent); WaitForSpeechEnd(aEvent);
break;
case EVENT_STOP: case EVENT_STOP:
return StopRecordingAndRecognize(aEvent); StopRecordingAndRecognize(aEvent);
break;
case EVENT_AUDIO_ERROR: case EVENT_AUDIO_ERROR:
case EVENT_RECOGNITIONSERVICE_ERROR: case EVENT_RECOGNITIONSERVICE_ERROR:
return AbortError(aEvent); AbortError(aEvent);
break;
case EVENT_ABORT: case EVENT_ABORT:
return AbortSilently(aEvent); AbortSilently(aEvent);
break;
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT: case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT: case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_START: case EVENT_START:
SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %s", GetName(aEvent)); SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %s", GetName(aEvent));
MOZ_NOT_REACHED(""); MOZ_NOT_REACHED("");
case EVENT_COUNT: case EVENT_COUNT:
MOZ_NOT_REACHED("Invalid event EVENT_COUNT"); MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
} }
break;
case STATE_WAITING_FOR_RESULT: case STATE_WAITING_FOR_RESULT:
switch (aEvent->mType) { switch (aEvent->mType) {
case EVENT_STOP: case EVENT_STOP:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_AUDIO_ERROR: case EVENT_AUDIO_ERROR:
case EVENT_RECOGNITIONSERVICE_ERROR: case EVENT_RECOGNITIONSERVICE_ERROR:
return AbortError(aEvent); AbortError(aEvent);
break;
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT: case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
return NotifyFinalResult(aEvent); NotifyFinalResult(aEvent);
break;
case EVENT_AUDIO_DATA: case EVENT_AUDIO_DATA:
return DoNothing(aEvent); DoNothing(aEvent);
break;
case EVENT_ABORT: case EVENT_ABORT:
return AbortSilently(aEvent); AbortSilently(aEvent);
break;
case EVENT_START: case EVENT_START:
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT: case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %s", GetName(aEvent)); SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %s", GetName(aEvent));
@ -256,12 +303,15 @@ SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
case EVENT_COUNT: case EVENT_COUNT:
MOZ_NOT_REACHED("Invalid event EVENT_COUNT"); MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
} }
break;
case STATE_ABORTING:
DoNothing(aEvent);
break;
case STATE_COUNT: case STATE_COUNT:
MOZ_NOT_REACHED("Invalid state STATE_COUNT"); MOZ_NOT_REACHED("Invalid state STATE_COUNT");
} }
SR_LOG("Unhandled state %s", GetName(mCurrentState));
MOZ_NOT_REACHED(""); return;
return mCurrentState;
} }
/* /*
@ -311,101 +361,116 @@ SpeechRecognition::GetRecognitionServiceCID(nsACString& aResultCID)
return; return;
} }
/**************************** /****************************************************************************
* FSM Transition functions * * FSM Transition functions
****************************/ *
* If a transition function may cause a DOM event to be fired,
* it may also be re-entered, since the event handler may cause the
* event loop to spin and new SpeechEvents to be processed.
*
* Rules:
* 1) These methods should call SetState as soon as possible.
* 2) If these methods dispatch DOM events, or call methods that dispatch
* DOM events, that should be done as late as possible.
* 3) If anything must happen after dispatching a DOM event, make sure
* the state is still what the method expected it to be.
****************************************************************************/
SpeechRecognition::FSMState void
SpeechRecognition::Reset() SpeechRecognition::Reset()
{ {
SetState(STATE_IDLE);
mRecognitionService = nullptr; mRecognitionService = nullptr;
mEstimationSamples = 0; mEstimationSamples = 0;
mBufferedSamples = 0; mBufferedSamples = 0;
mSpeechDetectionTimer->Cancel(); mSpeechDetectionTimer->Cancel();
return STATE_IDLE;
} }
/* void
* Since the handler for "end" may call
* start(), we want to fully reset before dispatching
* the event.
*/
SpeechRecognition::FSMState
SpeechRecognition::ResetAndEnd() SpeechRecognition::ResetAndEnd()
{ {
mCurrentState = Reset(); Reset();
DispatchTrustedEvent(NS_LITERAL_STRING("end")); DispatchTrustedEvent(NS_LITERAL_STRING("end"));
return mCurrentState;
} }
SpeechRecognition::FSMState void
SpeechRecognition::WaitForAudioData(SpeechEvent* aEvent)
{
SetState(STATE_STARTING);
}
void
SpeechRecognition::StartedAudioCapture(SpeechEvent* aEvent) SpeechRecognition::StartedAudioCapture(SpeechEvent* aEvent)
{ {
SetState(STATE_ESTIMATING);
mEndpointer.SetEnvironmentEstimationMode(); mEndpointer.SetEnvironmentEstimationMode();
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment); mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
DispatchTrustedEvent(NS_LITERAL_STRING("start"));
DispatchTrustedEvent(NS_LITERAL_STRING("audiostart")); DispatchTrustedEvent(NS_LITERAL_STRING("audiostart"));
if (mCurrentState == STATE_ESTIMATING) {
return STATE_ESTIMATING; DispatchTrustedEvent(NS_LITERAL_STRING("start"));
}
} }
SpeechRecognition::FSMState void
SpeechRecognition::StopRecordingAndRecognize(SpeechEvent* aEvent) SpeechRecognition::StopRecordingAndRecognize(SpeechEvent* aEvent)
{ {
StopRecording(); SetState(STATE_WAITING_FOR_RESULT);
MOZ_ASSERT(mRecognitionService, "Service deleted before recording done"); MOZ_ASSERT(mRecognitionService, "Service deleted before recording done");
mRecognitionService->SoundEnd(); mRecognitionService->SoundEnd();
return STATE_WAITING_FOR_RESULT; StopRecording();
} }
SpeechRecognition::FSMState void
SpeechRecognition::WaitForEstimation(SpeechEvent* aEvent) SpeechRecognition::WaitForEstimation(SpeechEvent* aEvent)
{ {
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment); SetState(STATE_ESTIMATING);
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
if (mEstimationSamples > kESTIMATION_SAMPLES) { if (mEstimationSamples > kESTIMATION_SAMPLES) {
mEndpointer.SetUserInputMode(); mEndpointer.SetUserInputMode();
return STATE_WAITING_FOR_SPEECH; SetState(STATE_WAITING_FOR_SPEECH);
} }
return STATE_ESTIMATING;
} }
SpeechRecognition::FSMState void
SpeechRecognition::DetectSpeech(SpeechEvent* aEvent) SpeechRecognition::DetectSpeech(SpeechEvent* aEvent)
{ {
ProcessAudioSegment(aEvent->mAudioSegment); SetState(STATE_WAITING_FOR_SPEECH);
ProcessAudioSegment(aEvent->mAudioSegment);
if (mEndpointer.DidStartReceivingSpeech()) { if (mEndpointer.DidStartReceivingSpeech()) {
mSpeechDetectionTimer->Cancel(); mSpeechDetectionTimer->Cancel();
SetState(STATE_RECOGNIZING);
DispatchTrustedEvent(NS_LITERAL_STRING("speechstart")); DispatchTrustedEvent(NS_LITERAL_STRING("speechstart"));
return STATE_RECOGNIZING;
} }
return STATE_WAITING_FOR_SPEECH;
} }
SpeechRecognition::FSMState void
SpeechRecognition::WaitForSpeechEnd(SpeechEvent* aEvent) SpeechRecognition::WaitForSpeechEnd(SpeechEvent* aEvent)
{ {
SetState(STATE_RECOGNIZING);
ProcessAudioSegment(aEvent->mAudioSegment); ProcessAudioSegment(aEvent->mAudioSegment);
if (mEndpointer.speech_input_complete()) { if (mEndpointer.speech_input_complete()) {
// FIXME: StopRecordingAndRecognize should only be called for single
// shot services for continous we should just inform the service
DispatchTrustedEvent(NS_LITERAL_STRING("speechend")); DispatchTrustedEvent(NS_LITERAL_STRING("speechend"));
return StopRecordingAndRecognize(aEvent);
}
return STATE_RECOGNIZING; if (mCurrentState == STATE_RECOGNIZING) {
// FIXME: StopRecordingAndRecognize should only be called for single
// shot services for continuous we should just inform the service
StopRecordingAndRecognize(aEvent);
}
}
} }
SpeechRecognition::FSMState void
SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent) SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
{ {
ResetAndEnd();
nsCOMPtr<nsIDOMEvent> domEvent; nsCOMPtr<nsIDOMEvent> domEvent;
NS_NewDOMSpeechRecognitionEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr); NS_NewDOMSpeechRecognitionEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
@ -420,35 +485,37 @@ SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
bool defaultActionEnabled; bool defaultActionEnabled;
this->DispatchEvent(domEvent, &defaultActionEnabled); this->DispatchEvent(domEvent, &defaultActionEnabled);
return ResetAndEnd();
} }
SpeechRecognition::FSMState void
SpeechRecognition::DoNothing(SpeechEvent* aEvent) SpeechRecognition::DoNothing(SpeechEvent* aEvent)
{ {
return mCurrentState;
} }
SpeechRecognition::FSMState void
SpeechRecognition::AbortSilently(SpeechEvent* aEvent) SpeechRecognition::AbortSilently(SpeechEvent* aEvent)
{ {
bool stopRecording = StateBetween(STATE_ESTIMATING, STATE_RECOGNIZING);
// prevent reentrancy from DOM events
SetState(STATE_ABORTING);
if (mRecognitionService) { if (mRecognitionService) {
mRecognitionService->Abort(); mRecognitionService->Abort();
} }
if (STATE_BETWEEN(STATE_ESTIMATING, STATE_RECOGNIZING)) { if (stopRecording) {
StopRecording(); StopRecording();
} }
return ResetAndEnd(); ResetAndEnd();
} }
SpeechRecognition::FSMState void
SpeechRecognition::AbortError(SpeechEvent* aEvent) SpeechRecognition::AbortError(SpeechEvent* aEvent)
{ {
FSMState nextState = AbortSilently(aEvent); AbortSilently(aEvent);
NotifyError(aEvent); NotifyError(aEvent);
return nextState;
} }
void void
@ -506,7 +573,7 @@ SpeechRecognition::Observe(nsISupports* aSubject, const char* aTopic,
MOZ_ASSERT(NS_IsMainThread(), "Observer invoked off the main thread"); MOZ_ASSERT(NS_IsMainThread(), "Observer invoked off the main thread");
if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) && if (!strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) &&
STATE_BETWEEN(STATE_IDLE, STATE_WAITING_FOR_SPEECH)) { StateBetween(STATE_IDLE, STATE_WAITING_FOR_SPEECH)) {
DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR, DispatchError(SpeechRecognition::EVENT_AUDIO_ERROR,
nsIDOMSpeechRecognitionError::NO_SPEECH, nsIDOMSpeechRecognitionError::NO_SPEECH,
@ -638,7 +705,7 @@ SpeechRecognition::SetServiceURI(const nsAString& aArg, ErrorResult& aRv)
void void
SpeechRecognition::Start(ErrorResult& aRv) SpeechRecognition::Start(ErrorResult& aRv)
{ {
if (!STATE_EQUALS(STATE_IDLE)) { if (!mCurrentState == STATE_IDLE) {
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return; return;
} }
@ -830,7 +897,8 @@ SpeechRecognition::GetName(FSMState aId)
"STATE_ESTIMATING", "STATE_ESTIMATING",
"STATE_WAITING_FOR_SPEECH", "STATE_WAITING_FOR_SPEECH",
"STATE_RECOGNIZING", "STATE_RECOGNIZING",
"STATE_WAITING_FOR_RESULT" "STATE_WAITING_FOR_RESULT",
"STATE_ABORTING"
}; };
MOZ_ASSERT(aId < STATE_COUNT); MOZ_ASSERT(aId < STATE_COUNT);

Просмотреть файл

@ -167,9 +167,13 @@ private:
STATE_WAITING_FOR_SPEECH, STATE_WAITING_FOR_SPEECH,
STATE_RECOGNIZING, STATE_RECOGNIZING,
STATE_WAITING_FOR_RESULT, STATE_WAITING_FOR_RESULT,
STATE_ABORTING,
STATE_COUNT STATE_COUNT
}; };
void SetState(FSMState state);
bool StateBetween(FSMState begin, FSMState end);
class GetUserMediaStreamOptions : public nsIMediaStreamOptions class GetUserMediaStreamOptions : public nsIMediaStreamOptions
{ {
public: public:
@ -219,19 +223,20 @@ private:
void NotifyError(SpeechEvent* aEvent); void NotifyError(SpeechEvent* aEvent);
void ProcessEvent(SpeechEvent* aEvent); void ProcessEvent(SpeechEvent* aEvent);
FSMState TransitionAndGetNextState(SpeechEvent* aEvent); void Transition(SpeechEvent* aEvent);
FSMState Reset(); void Reset();
FSMState ResetAndEnd(); void ResetAndEnd();
FSMState StartedAudioCapture(SpeechEvent* aEvent); void WaitForAudioData(SpeechEvent* aEvent);
FSMState StopRecordingAndRecognize(SpeechEvent* aEvent); void StartedAudioCapture(SpeechEvent* aEvent);
FSMState WaitForEstimation(SpeechEvent* aEvent); void StopRecordingAndRecognize(SpeechEvent* aEvent);
FSMState DetectSpeech(SpeechEvent* aEvent); void WaitForEstimation(SpeechEvent* aEvent);
FSMState WaitForSpeechEnd(SpeechEvent* aEvent); void DetectSpeech(SpeechEvent* aEvent);
FSMState NotifyFinalResult(SpeechEvent* aEvent); void WaitForSpeechEnd(SpeechEvent* aEvent);
FSMState DoNothing(SpeechEvent* aEvent); void NotifyFinalResult(SpeechEvent* aEvent);
FSMState AbortSilently(SpeechEvent* aEvent); void DoNothing(SpeechEvent* aEvent);
FSMState AbortError(SpeechEvent* aEvent); void AbortSilently(SpeechEvent* aEvent);
void AbortError(SpeechEvent* aEvent);
nsRefPtr<DOMMediaStream> mDOMStream; nsRefPtr<DOMMediaStream> mDOMStream;
nsRefPtr<SpeechStreamListener> mSpeechListener; nsRefPtr<SpeechStreamListener> mSpeechListener;
@ -240,7 +245,7 @@ private:
void GetRecognitionServiceCID(nsACString& aResultCID); void GetRecognitionServiceCID(nsACString& aResultCID);
FSMState mCurrentState; FSMState mCurrentState;
bool mProcessingEvent; nsTArray<nsRefPtr<SpeechEvent> > mPriorityEvents;
Endpointer mEndpointer; Endpointer mEndpointer;
uint32_t mEstimationSamples; uint32_t mEstimationSamples;

Просмотреть файл

@ -23,6 +23,7 @@ MOCHITEST_FILES := \
test_abort.html \ test_abort.html \
test_call_start_from_end_handler.html \ test_call_start_from_end_handler.html \
test_preference_enable.html \ test_preference_enable.html \
test_nested_eventloop.html \
hello.ogg \ hello.ogg \
silence.ogg \ silence.ogg \
$(NULL) $(NULL)

Просмотреть файл

@ -159,7 +159,9 @@ function performTest(options) {
em.doneFunc = function() { em.doneFunc = function() {
em.requestTestEnd(); em.requestTestEnd();
options.doneFunc(); if (options.doneFunc) {
options.doneFunc();
}
} }
em.audioSampleFile = DEFAULT_AUDIO_SAMPLE_FILE; em.audioSampleFile = DEFAULT_AUDIO_SAMPLE_FILE;

Просмотреть файл

@ -0,0 +1,89 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=650295
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 650295 -- Spin the event loop from inside a callback</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
<script type="application/javascript" src="head.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=650295">Mozilla Bug 650295</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="text/javascript">
SimpleTest.waitForExplicitFinish();
/*
* window.showModalDialog() can be used to spin the event loop, causing
* queued SpeechEvents (such as those created by calls to start(), stop()
* or abort()) to be processed immediately.
* When this is done from inside DOM event handlers, it is possible to
* cause reentrancy in our C++ code, which we should be able to withstand.
*/
// Garbage collecting the windows created in this test can
// cause assertions (Bug 600703).
if (!navigator.platform.startsWith("Win")) {
SimpleTest.expectAssertions(2);
}
function abortAndSpinEventLoop(evt, sr) {
sr.abort();
window.showModalDialog("javascript:window.close()");
}
function doneFunc() {
// Trigger gc now and wait some time to make sure this test gets the blame
// for any assertions caused by showModalDialog
var count = 0, GC_COUNT = 4;
function triggerGCOrFinish() {
SpecialPowers.gc();
count++;
if (count == GC_COUNT) {
SimpleTest.finish();
}
}
for (var i = 0; i < GC_COUNT; i++) {
setTimeout(triggerGCOrFinish, 0);
}
}
/*
* We start by performing a normal start, then abort from the audiostart
* callback and force the EVENT_ABORT to be processed while still inside
* the event handler. This causes the recording to stop, which raises
* the audioend and (later on) end events.
* Then, we abort (once again spinning the event loop) from the audioend
* handler, attempting to cause a re-entry into the abort code. This second
* call should be ignored, and we get the end callback and finish.
*/
performTest({
eventsToRequest: [
"EVENT_START",
"EVENT_AUDIO_DATA",
],
expectedEvents: {
"audiostart": abortAndSpinEventLoop,
"audioend": abortAndSpinEventLoop,
"end": null
},
doneFunc: doneFunc,
prefs: [["media.webspeech.test.fake_fsm_events", true],
["media.webspeech.test.fake_recognition_service", true]]
});
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -119,6 +119,7 @@
"content/media/test/test_wave_data_s16.html": "TIMED_OUT", "content/media/test/test_wave_data_s16.html": "TIMED_OUT",
"content/media/test/test_wave_data_u8.html": "TIMED_OUT", "content/media/test/test_wave_data_u8.html": "TIMED_OUT",
"content/media/webspeech/synth/ipc/test/test_ipc.html": "bug 857673", "content/media/webspeech/synth/ipc/test/test_ipc.html": "bug 857673",
"content/media/webspeech/recognition/test/test_nested_eventloop.html": "",
"content/smil/test/test_smilRepeatTiming.xhtml": "TIMED_OUT", "content/smil/test/test_smilRepeatTiming.xhtml": "TIMED_OUT",
"content/smil/test/test_smilExtDoc.xhtml": "", "content/smil/test/test_smilExtDoc.xhtml": "",
"content/xul/content/test/test_bug486990.xul": "TIMED_OUT", "content/xul/content/test/test_bug486990.xul": "TIMED_OUT",