зеркало из https://github.com/mozilla/gecko-dev.git
Bug 863852 - Part 2 - Handle reentrance in the state machine. r=smaug
This commit is contained in:
Родитель
7f1573cedd
Коммит
597f33c342
|
@ -38,10 +38,6 @@ static const uint32_t kSPEECH_DETECTION_TIMEOUT_MS = 10000;
|
|||
// kSAMPLE_RATE frames = 1s, kESTIMATION_FRAMES frames = 300ms
|
||||
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
|
||||
PRLogModuleInfo*
|
||||
GetSpeechRecognitionLog()
|
||||
|
@ -68,8 +64,7 @@ NS_IMPL_RELEASE_INHERITED(SpeechRecognition, nsDOMEventTargetHelper)
|
|||
struct SpeechRecognition::TestConfig SpeechRecognition::mTestConfig;
|
||||
|
||||
SpeechRecognition::SpeechRecognition()
|
||||
: mProcessingEvent(false)
|
||||
, mEndpointer(kSAMPLE_RATE)
|
||||
: mEndpointer(kSAMPLE_RATE)
|
||||
, mAudioSamplesPerChunk(mEndpointer.FrameSize())
|
||||
, mSpeechDetectionTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
|
||||
{
|
||||
|
@ -89,7 +84,21 @@ SpeechRecognition::SpeechRecognition()
|
|||
Preferences::GetInt(PREFERENCE_ENDPOINTER_LONG_SILENCE_LENGTH, 1000000));
|
||||
mEndpointer.set_long_speech_length(
|
||||
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*
|
||||
|
@ -125,17 +134,22 @@ SpeechRecognition::ProcessEvent(SpeechEvent* aEvent)
|
|||
GetName(aEvent),
|
||||
GetName(mCurrentState));
|
||||
|
||||
MOZ_ASSERT(!mProcessingEvent, "Event dispatch should be sequential!");
|
||||
mProcessingEvent = true;
|
||||
// Run priority events first
|
||||
for (uint32_t i = 0; i < mPriorityEvents.Length(); ++i) {
|
||||
nsRefPtr<SpeechEvent> event = mPriorityEvents[i];
|
||||
|
||||
mCurrentState = TransitionAndGetNextState(aEvent);
|
||||
SR_LOG("Transitioned to state: %s", GetName(mCurrentState));
|
||||
SR_LOG("Processing priority %s", GetName(event));
|
||||
Transition(event);
|
||||
}
|
||||
|
||||
mProcessingEvent = false;
|
||||
mPriorityEvents.Clear();
|
||||
|
||||
SR_LOG("Processing %s received as argument", GetName(aEvent));
|
||||
Transition(aEvent);
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
|
||||
void
|
||||
SpeechRecognition::Transition(SpeechEvent* aEvent)
|
||||
{
|
||||
switch (mCurrentState) {
|
||||
case STATE_IDLE:
|
||||
|
@ -143,112 +157,145 @@ SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
|
|||
case EVENT_START:
|
||||
// TODO: may want to time out if we wait too long
|
||||
// for user to approve
|
||||
return STATE_STARTING;
|
||||
WaitForAudioData(aEvent);
|
||||
break;
|
||||
case EVENT_STOP:
|
||||
case EVENT_ABORT:
|
||||
case EVENT_AUDIO_DATA:
|
||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_ERROR:
|
||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||
return AbortError(aEvent);
|
||||
AbortError(aEvent);
|
||||
break;
|
||||
case EVENT_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||
}
|
||||
break;
|
||||
case STATE_STARTING:
|
||||
switch (aEvent->mType) {
|
||||
case EVENT_AUDIO_DATA:
|
||||
return StartedAudioCapture(aEvent);
|
||||
StartedAudioCapture(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_ERROR:
|
||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||
return AbortError(aEvent);
|
||||
AbortError(aEvent);
|
||||
break;
|
||||
case EVENT_ABORT:
|
||||
return AbortSilently(aEvent);
|
||||
AbortSilently(aEvent);
|
||||
break;
|
||||
case EVENT_STOP:
|
||||
return Reset();
|
||||
Reset();
|
||||
break;
|
||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_START:
|
||||
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
|
||||
MOZ_NOT_REACHED("");
|
||||
case EVENT_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||
}
|
||||
break;
|
||||
case STATE_ESTIMATING:
|
||||
switch (aEvent->mType) {
|
||||
case EVENT_AUDIO_DATA:
|
||||
return WaitForEstimation(aEvent);
|
||||
WaitForEstimation(aEvent);
|
||||
break;
|
||||
case EVENT_STOP:
|
||||
return StopRecordingAndRecognize(aEvent);
|
||||
StopRecordingAndRecognize(aEvent);
|
||||
break;
|
||||
case EVENT_ABORT:
|
||||
return AbortSilently(aEvent);
|
||||
AbortSilently(aEvent);
|
||||
break;
|
||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_ERROR:
|
||||
return AbortError(aEvent);
|
||||
AbortError(aEvent);
|
||||
break;
|
||||
case EVENT_START:
|
||||
SR_LOG("STATE_ESTIMATING: Unhandled event %d", aEvent->mType);
|
||||
MOZ_NOT_REACHED("");
|
||||
case EVENT_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||
}
|
||||
break;
|
||||
case STATE_WAITING_FOR_SPEECH:
|
||||
switch (aEvent->mType) {
|
||||
case EVENT_AUDIO_DATA:
|
||||
return DetectSpeech(aEvent);
|
||||
DetectSpeech(aEvent);
|
||||
break;
|
||||
case EVENT_STOP:
|
||||
return StopRecordingAndRecognize(aEvent);
|
||||
StopRecordingAndRecognize(aEvent);
|
||||
break;
|
||||
case EVENT_ABORT:
|
||||
return AbortSilently(aEvent);
|
||||
AbortSilently(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_ERROR:
|
||||
return AbortError(aEvent);
|
||||
AbortError(aEvent);
|
||||
break;
|
||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_START:
|
||||
SR_LOG("STATE_STARTING: Unhandled event %s", GetName(aEvent));
|
||||
MOZ_NOT_REACHED("");
|
||||
case EVENT_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||
}
|
||||
break;
|
||||
case STATE_RECOGNIZING:
|
||||
switch (aEvent->mType) {
|
||||
case EVENT_AUDIO_DATA:
|
||||
return WaitForSpeechEnd(aEvent);
|
||||
WaitForSpeechEnd(aEvent);
|
||||
break;
|
||||
case EVENT_STOP:
|
||||
return StopRecordingAndRecognize(aEvent);
|
||||
StopRecordingAndRecognize(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_ERROR:
|
||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||
return AbortError(aEvent);
|
||||
AbortError(aEvent);
|
||||
break;
|
||||
case EVENT_ABORT:
|
||||
return AbortSilently(aEvent);
|
||||
AbortSilently(aEvent);
|
||||
break;
|
||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_START:
|
||||
SR_LOG("STATE_RECOGNIZING: Unhandled aEvent %s", GetName(aEvent));
|
||||
MOZ_NOT_REACHED("");
|
||||
case EVENT_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||
}
|
||||
break;
|
||||
case STATE_WAITING_FOR_RESULT:
|
||||
switch (aEvent->mType) {
|
||||
case EVENT_STOP:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_ERROR:
|
||||
case EVENT_RECOGNITIONSERVICE_ERROR:
|
||||
return AbortError(aEvent);
|
||||
AbortError(aEvent);
|
||||
break;
|
||||
case EVENT_RECOGNITIONSERVICE_FINAL_RESULT:
|
||||
return NotifyFinalResult(aEvent);
|
||||
NotifyFinalResult(aEvent);
|
||||
break;
|
||||
case EVENT_AUDIO_DATA:
|
||||
return DoNothing(aEvent);
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case EVENT_ABORT:
|
||||
return AbortSilently(aEvent);
|
||||
AbortSilently(aEvent);
|
||||
break;
|
||||
case EVENT_START:
|
||||
case EVENT_RECOGNITIONSERVICE_INTERMEDIATE_RESULT:
|
||||
SR_LOG("STATE_WAITING_FOR_RESULT: Unhandled aEvent %s", GetName(aEvent));
|
||||
|
@ -256,12 +303,15 @@ SpeechRecognition::TransitionAndGetNextState(SpeechEvent* aEvent)
|
|||
case EVENT_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid event EVENT_COUNT");
|
||||
}
|
||||
break;
|
||||
case STATE_ABORTING:
|
||||
DoNothing(aEvent);
|
||||
break;
|
||||
case STATE_COUNT:
|
||||
MOZ_NOT_REACHED("Invalid state STATE_COUNT");
|
||||
}
|
||||
SR_LOG("Unhandled state %s", GetName(mCurrentState));
|
||||
MOZ_NOT_REACHED("");
|
||||
return mCurrentState;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -311,101 +361,116 @@ SpeechRecognition::GetRecognitionServiceCID(nsACString& aResultCID)
|
|||
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()
|
||||
{
|
||||
SetState(STATE_IDLE);
|
||||
mRecognitionService = nullptr;
|
||||
mEstimationSamples = 0;
|
||||
mBufferedSamples = 0;
|
||||
mSpeechDetectionTimer->Cancel();
|
||||
|
||||
return STATE_IDLE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Since the handler for "end" may call
|
||||
* start(), we want to fully reset before dispatching
|
||||
* the event.
|
||||
*/
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::ResetAndEnd()
|
||||
{
|
||||
mCurrentState = Reset();
|
||||
Reset();
|
||||
DispatchTrustedEvent(NS_LITERAL_STRING("end"));
|
||||
return mCurrentState;
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::WaitForAudioData(SpeechEvent* aEvent)
|
||||
{
|
||||
SetState(STATE_STARTING);
|
||||
}
|
||||
|
||||
void
|
||||
SpeechRecognition::StartedAudioCapture(SpeechEvent* aEvent)
|
||||
{
|
||||
SetState(STATE_ESTIMATING);
|
||||
|
||||
mEndpointer.SetEnvironmentEstimationMode();
|
||||
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
||||
|
||||
DispatchTrustedEvent(NS_LITERAL_STRING("start"));
|
||||
DispatchTrustedEvent(NS_LITERAL_STRING("audiostart"));
|
||||
|
||||
return STATE_ESTIMATING;
|
||||
if (mCurrentState == STATE_ESTIMATING) {
|
||||
DispatchTrustedEvent(NS_LITERAL_STRING("start"));
|
||||
}
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::StopRecordingAndRecognize(SpeechEvent* aEvent)
|
||||
{
|
||||
StopRecording();
|
||||
SetState(STATE_WAITING_FOR_RESULT);
|
||||
|
||||
MOZ_ASSERT(mRecognitionService, "Service deleted before recording done");
|
||||
mRecognitionService->SoundEnd();
|
||||
|
||||
return STATE_WAITING_FOR_RESULT;
|
||||
StopRecording();
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::WaitForEstimation(SpeechEvent* aEvent)
|
||||
{
|
||||
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
||||
SetState(STATE_ESTIMATING);
|
||||
|
||||
mEstimationSamples += ProcessAudioSegment(aEvent->mAudioSegment);
|
||||
if (mEstimationSamples > kESTIMATION_SAMPLES) {
|
||||
mEndpointer.SetUserInputMode();
|
||||
return STATE_WAITING_FOR_SPEECH;
|
||||
SetState(STATE_WAITING_FOR_SPEECH);
|
||||
}
|
||||
|
||||
return STATE_ESTIMATING;
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::DetectSpeech(SpeechEvent* aEvent)
|
||||
{
|
||||
ProcessAudioSegment(aEvent->mAudioSegment);
|
||||
SetState(STATE_WAITING_FOR_SPEECH);
|
||||
|
||||
ProcessAudioSegment(aEvent->mAudioSegment);
|
||||
if (mEndpointer.DidStartReceivingSpeech()) {
|
||||
mSpeechDetectionTimer->Cancel();
|
||||
SetState(STATE_RECOGNIZING);
|
||||
DispatchTrustedEvent(NS_LITERAL_STRING("speechstart"));
|
||||
return STATE_RECOGNIZING;
|
||||
}
|
||||
|
||||
return STATE_WAITING_FOR_SPEECH;
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::WaitForSpeechEnd(SpeechEvent* aEvent)
|
||||
{
|
||||
SetState(STATE_RECOGNIZING);
|
||||
|
||||
ProcessAudioSegment(aEvent->mAudioSegment);
|
||||
|
||||
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"));
|
||||
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)
|
||||
{
|
||||
ResetAndEnd();
|
||||
|
||||
nsCOMPtr<nsIDOMEvent> domEvent;
|
||||
NS_NewDOMSpeechRecognitionEvent(getter_AddRefs(domEvent), nullptr, nullptr, nullptr);
|
||||
|
||||
|
@ -420,35 +485,37 @@ SpeechRecognition::NotifyFinalResult(SpeechEvent* aEvent)
|
|||
|
||||
bool defaultActionEnabled;
|
||||
this->DispatchEvent(domEvent, &defaultActionEnabled);
|
||||
return ResetAndEnd();
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::DoNothing(SpeechEvent* aEvent)
|
||||
{
|
||||
return mCurrentState;
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::AbortSilently(SpeechEvent* aEvent)
|
||||
{
|
||||
bool stopRecording = StateBetween(STATE_ESTIMATING, STATE_RECOGNIZING);
|
||||
|
||||
// prevent reentrancy from DOM events
|
||||
SetState(STATE_ABORTING);
|
||||
|
||||
if (mRecognitionService) {
|
||||
mRecognitionService->Abort();
|
||||
}
|
||||
|
||||
if (STATE_BETWEEN(STATE_ESTIMATING, STATE_RECOGNIZING)) {
|
||||
if (stopRecording) {
|
||||
StopRecording();
|
||||
}
|
||||
|
||||
return ResetAndEnd();
|
||||
ResetAndEnd();
|
||||
}
|
||||
|
||||
SpeechRecognition::FSMState
|
||||
void
|
||||
SpeechRecognition::AbortError(SpeechEvent* aEvent)
|
||||
{
|
||||
FSMState nextState = AbortSilently(aEvent);
|
||||
AbortSilently(aEvent);
|
||||
NotifyError(aEvent);
|
||||
return nextState;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -506,7 +573,7 @@ SpeechRecognition::Observe(nsISupports* aSubject, const char* aTopic,
|
|||
MOZ_ASSERT(NS_IsMainThread(), "Observer invoked off the main thread");
|
||||
|
||||
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,
|
||||
nsIDOMSpeechRecognitionError::NO_SPEECH,
|
||||
|
@ -638,7 +705,7 @@ SpeechRecognition::SetServiceURI(const nsAString& aArg, ErrorResult& aRv)
|
|||
void
|
||||
SpeechRecognition::Start(ErrorResult& aRv)
|
||||
{
|
||||
if (!STATE_EQUALS(STATE_IDLE)) {
|
||||
if (!mCurrentState == STATE_IDLE) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
@ -830,7 +897,8 @@ SpeechRecognition::GetName(FSMState aId)
|
|||
"STATE_ESTIMATING",
|
||||
"STATE_WAITING_FOR_SPEECH",
|
||||
"STATE_RECOGNIZING",
|
||||
"STATE_WAITING_FOR_RESULT"
|
||||
"STATE_WAITING_FOR_RESULT",
|
||||
"STATE_ABORTING"
|
||||
};
|
||||
|
||||
MOZ_ASSERT(aId < STATE_COUNT);
|
||||
|
|
|
@ -167,9 +167,13 @@ private:
|
|||
STATE_WAITING_FOR_SPEECH,
|
||||
STATE_RECOGNIZING,
|
||||
STATE_WAITING_FOR_RESULT,
|
||||
STATE_ABORTING,
|
||||
STATE_COUNT
|
||||
};
|
||||
|
||||
void SetState(FSMState state);
|
||||
bool StateBetween(FSMState begin, FSMState end);
|
||||
|
||||
class GetUserMediaStreamOptions : public nsIMediaStreamOptions
|
||||
{
|
||||
public:
|
||||
|
@ -219,19 +223,20 @@ private:
|
|||
void NotifyError(SpeechEvent* aEvent);
|
||||
|
||||
void ProcessEvent(SpeechEvent* aEvent);
|
||||
FSMState TransitionAndGetNextState(SpeechEvent* aEvent);
|
||||
void Transition(SpeechEvent* aEvent);
|
||||
|
||||
FSMState Reset();
|
||||
FSMState ResetAndEnd();
|
||||
FSMState StartedAudioCapture(SpeechEvent* aEvent);
|
||||
FSMState StopRecordingAndRecognize(SpeechEvent* aEvent);
|
||||
FSMState WaitForEstimation(SpeechEvent* aEvent);
|
||||
FSMState DetectSpeech(SpeechEvent* aEvent);
|
||||
FSMState WaitForSpeechEnd(SpeechEvent* aEvent);
|
||||
FSMState NotifyFinalResult(SpeechEvent* aEvent);
|
||||
FSMState DoNothing(SpeechEvent* aEvent);
|
||||
FSMState AbortSilently(SpeechEvent* aEvent);
|
||||
FSMState AbortError(SpeechEvent* aEvent);
|
||||
void Reset();
|
||||
void ResetAndEnd();
|
||||
void WaitForAudioData(SpeechEvent* aEvent);
|
||||
void StartedAudioCapture(SpeechEvent* aEvent);
|
||||
void StopRecordingAndRecognize(SpeechEvent* aEvent);
|
||||
void WaitForEstimation(SpeechEvent* aEvent);
|
||||
void DetectSpeech(SpeechEvent* aEvent);
|
||||
void WaitForSpeechEnd(SpeechEvent* aEvent);
|
||||
void NotifyFinalResult(SpeechEvent* aEvent);
|
||||
void DoNothing(SpeechEvent* aEvent);
|
||||
void AbortSilently(SpeechEvent* aEvent);
|
||||
void AbortError(SpeechEvent* aEvent);
|
||||
|
||||
nsRefPtr<DOMMediaStream> mDOMStream;
|
||||
nsRefPtr<SpeechStreamListener> mSpeechListener;
|
||||
|
@ -240,7 +245,7 @@ private:
|
|||
void GetRecognitionServiceCID(nsACString& aResultCID);
|
||||
|
||||
FSMState mCurrentState;
|
||||
bool mProcessingEvent;
|
||||
nsTArray<nsRefPtr<SpeechEvent> > mPriorityEvents;
|
||||
|
||||
Endpointer mEndpointer;
|
||||
uint32_t mEstimationSamples;
|
||||
|
|
|
@ -23,6 +23,7 @@ MOCHITEST_FILES := \
|
|||
test_abort.html \
|
||||
test_call_start_from_end_handler.html \
|
||||
test_preference_enable.html \
|
||||
test_nested_eventloop.html \
|
||||
hello.ogg \
|
||||
silence.ogg \
|
||||
$(NULL)
|
||||
|
|
|
@ -159,7 +159,9 @@ function performTest(options) {
|
|||
|
||||
em.doneFunc = function() {
|
||||
em.requestTestEnd();
|
||||
options.doneFunc();
|
||||
if (options.doneFunc) {
|
||||
options.doneFunc();
|
||||
}
|
||||
}
|
||||
|
||||
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_u8.html": "TIMED_OUT",
|
||||
"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_smilExtDoc.xhtml": "",
|
||||
"content/xul/content/test/test_bug486990.xul": "TIMED_OUT",
|
||||
|
|
Загрузка…
Ссылка в новой задаче