Bug 1178738 - Have utterances dispatch "canceled" error when they never start. r=smaug

MozReview-Commit-ID: KfJurwcS7mw

--HG--
extra : rebase_source : d190e091aa4ebb8711a77b14f99c75cbfadaf98a
This commit is contained in:
Eitan Isaacson 2016-05-18 11:13:45 -07:00
Родитель c57d35080a
Коммит d49063d60c
9 изменённых файлов: 195 добавлений и 11 удалений

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

@ -12,6 +12,7 @@
#include "mozilla/dom/Element.h"
#include "mozilla/dom/SpeechSynthesisBinding.h"
#include "mozilla/dom/SpeechSynthesisErrorEvent.h"
#include "SpeechSynthesis.h"
#include "nsSynthVoiceRegistry.h"
#include "nsIDocument.h"
@ -193,13 +194,34 @@ SpeechSynthesis::AdvanceQueue()
return;
}
void
SpeechSynthesis::DispatchToCanceledQueue()
{
while (mCanceledQueue.Length()) {
RefPtr<SpeechSynthesisUtterance> utterance = mCanceledQueue.ElementAt(0);
mCanceledQueue.RemoveElementAt(0);
utterance->DispatchSpeechSynthesisErrorEvent(
0, 0, SpeechSynthesisErrorCode::Canceled);
}
}
void
SpeechSynthesis::Cancel()
{
mSpeechQueue.Clear();
if (mCanceledQueue.IsEmpty()) {
mCanceledQueue.SwapElements(mSpeechQueue);
} else {
while (mSpeechQueue.Length()) {
mCanceledQueue.AppendElement(mSpeechQueue.ElementAt(0));
mSpeechQueue.RemoveElementAt(0);
}
}
if (mCurrentTask) {
mCurrentTask->Cancel();
} else {
DispatchToCanceledQueue();
}
}
@ -239,6 +261,7 @@ SpeechSynthesis::OnEnd(const nsSpeechTask* aTask)
MOZ_ASSERT(mCurrentTask == aTask);
mCurrentTask = nullptr;
DispatchToCanceledQueue();
AdvanceQueue();
}

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

@ -70,10 +70,14 @@ private:
void AdvanceQueue();
void DispatchToCanceledQueue();
bool HasVoices() const;
nsTArray<RefPtr<SpeechSynthesisUtterance> > mSpeechQueue;
nsTArray<RefPtr<SpeechSynthesisUtterance> > mCanceledQueue;
RefPtr<nsSpeechTask> mCurrentTask;
nsRefPtrHashtable<nsStringHashKey, SpeechSynthesisVoice> mVoiceCache;

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

@ -535,10 +535,19 @@ nsSpeechTask::DispatchErrorImpl(float aElapsedTime, uint32_t aCharIndex, uint32_
}
RefPtr<SpeechSynthesisUtterance> utterance = mUtterance;
utterance->mState = (utterance->mState == SpeechSynthesisUtterance::STATE_SPEAKING) ?
SpeechSynthesisUtterance::STATE_ENDED : SpeechSynthesisUtterance::STATE_NONE;
utterance->DispatchSpeechSynthesisErrorEvent(aCharIndex, aElapsedTime,
SpeechSynthesisErrorCode(aError));
SpeechSynthesisErrorCode err;
if (utterance->mState == SpeechSynthesisUtterance::STATE_PENDING &&
aError == uint32_t(SpeechSynthesisErrorCode::Interrupted)) {
// If utterance never started the error should be "canceled" instead of
// "interrupted".
err = SpeechSynthesisErrorCode::Canceled;
utterance->mState = SpeechSynthesisUtterance::STATE_NONE;
} else {
err = SpeechSynthesisErrorCode(aError);
utterance->mState = SpeechSynthesisUtterance::STATE_ENDED;
}
utterance->DispatchSpeechSynthesisErrorEvent(aCharIndex, aElapsedTime, err);
return NS_OK;
}

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

@ -89,4 +89,10 @@ function testSynthState(win, expectedState) {
is(win.speechSynthesis[attr], expectedState[attr],
win.document.title + ": '" + attr + '" does not match');
}
}
function synthEventPromise(utterance, eventType) {
return new Promise(resolve => {
utterance.addEventListener(eventType, resolve);
});
}

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

@ -50,17 +50,25 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1188099
SpecialPowers.wrap(win1.speechSynthesis).forceEnd();
});
utterance1.addEventListener('end', function(e) {
var error4 = synthEventPromise(utterance4, 'error').then(isCanceled);
var error5 = synthEventPromise(utterance5, 'error').then(isCanceled);
var end1 = synthEventPromise(utterance1, 'end').then(() => {
is(eventOrder.shift(), 'end1', 'end1');
testSynthState(win1, { pending: true });
testSynthState(win2, { pending: false });
});
utterance2.addEventListener('start', function(e) {
var start2 = synthEventPromise(utterance2, 'start').then(() => {
is(eventOrder.shift(), 'start2', 'start2');
testSynthState(win1, { speaking: true, pending: true });
testSynthState(win2, { speaking: true, pending: false });
win1.speechSynthesis.cancel();
});
Promise.all([error4, error5, end1, start2]).then(
() => win1.speechSynthesis.cancel());
utterance2.addEventListener('error', function(e) {
is(eventOrder.shift(), 'error2', 'error2');
testSynthState(win1, { speaking: false, pending: false });
@ -72,6 +80,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1188099
ok(false, 'This shall not be uttered: "' + e.target.text + '"');
}
function isCanceled(e) {
is(e.error, "canceled", "Utterance was canceled: " + e.target.text);
}
utterance3.addEventListener('start', wrongUtterance);
utterance4.addEventListener('start', wrongUtterance);
utterance5.addEventListener('start', wrongUtterance);

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

@ -44,11 +44,16 @@ function testFunc(done_cb) {
utterance.addEventListener('start', function(e) {
ok(true, 'start utterance 1');
speechSynthesis.cancel();
info('cancel!');
speechSynthesis.speak(utterance2);
info('speak??');
});
var utterance_never = new SpeechSynthesisUtterance("I never get uttered");
var promise_cancelled = synthEventPromise(utterance_never, "error").then(
e => is(e.error, "canceled", "utterance was canceled"));
var promise_interrupted = synthEventPromise(utterance, "error").then(
e => is(e.error, "interrupted", "utterance was interrupted"));
Promise.all([promise_cancelled, promise_interrupted]).then(
() => speechSynthesis.speak(utterance2));
var utterance2 = new SpeechSynthesisUtterance("Proin ornare neque vitae " +
"risus mattis rutrum. Suspendisse a velit ut est convallis aliquet." +
"Nullam ante elit, malesuada vel luctus rutrum, ultricies nec libero." +
@ -83,6 +88,7 @@ function testFunc(done_cb) {
speechSynthesis.resume();
speechSynthesis.speak(utterance);
speechSynthesis.speak(utterance_never);
ok(!speechSynthesis.speaking, "speechSynthesis is not speaking yet.");
ok(speechSynthesis.pending, "speechSynthesis has an utterance queued.");
}

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

@ -0,0 +1,87 @@
<!DOCTYPE HTML>
<html lang="it-IT-noend">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1178738
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1178738: Make sure cancel is reentrant</title>
<script type="application/javascript">
window.SimpleTest = parent.SimpleTest;
window.info = parent.info;
window.is = parent.is;
window.isnot = parent.isnot;
window.ok = parent.ok;
</script>
<script type="application/javascript" src="common.js"></script>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1178738">Mozilla Bug 1178738</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 1178738 **/
function testFunc(done_cb) {
var utterance = new SpeechSynthesisUtterance("utterance 1");
var utterance2 = new SpeechSynthesisUtterance("utterance 2");
var utterance3 = new SpeechSynthesisUtterance("utterance 3");
var utterance4 = new SpeechSynthesisUtterance("utterance 4");
var utterance5 = new SpeechSynthesisUtterance("utterance 5");
utterance5.lang = "en-GB";
utterance.addEventListener('start', function(e) {
ok(true, 'start utterance 1');
speechSynthesis.cancel();
});
utterance.addEventListener('error', function(e) {
is(e.error, "interrupted", "utterance was interrupted");
});
utterance2.addEventListener("error", function(e) {
is(e.error, "canceled", "utterance was canceled");
speechSynthesis.cancel();
speechSynthesis.speak(utterance3);
});
utterance2.addEventListener("start", function(e) {
ok(false, "Utterance should never be spoken")
});
utterance3.addEventListener('start', function(e) {
ok(true, 'start utterance 3');
speechSynthesis.speak(utterance4);
speechSynthesis.cancel();
});
utterance3.addEventListener('error', function(e) {
is(e.error, "interrupted", "utterance was interrupted");
speechSynthesis.cancel();
speechSynthesis.speak(utterance5);
});
var utterance4_error = synthEventPromise(utterance4, "error").then(
e => is(e.error, "canceled", "utterance 4 was canceled"));
var utterance5_end = synthEventPromise(utterance5, "end").then(
e => ok(true, "utterance 5 ended"));
Promise.all([utterance4_error, utterance5_end]).then(done_cb);
speechSynthesis.speak(utterance);
speechSynthesis.speak(utterance2);
}
// Run test with no global queue, and then run it with a global queue.
testFunc(SimpleTest.finish);
</script>
</pre>
</body>
</html>

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

@ -8,6 +8,7 @@ support-files =
file_speech_queue.html
file_speech_simple.html
file_speech_cancel.html
file_speech_cancel_reentry.html
file_speech_error.html
file_indirect_service_events.html
file_global_queue.html
@ -18,6 +19,7 @@ support-files =
[test_speech_queue.html]
[test_speech_simple.html]
[test_speech_cancel.html]
[test_speech_cancel_reentry.html]
[test_speech_error.html]
[test_indirect_service_events.html]
[test_global_queue.html]

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

@ -0,0 +1,35 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1178738
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 1178738: Make sure cancel is reentrant</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript" src="common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1178738">Mozilla Bug 1178738</a>
<p id="display"></p>
<iframe id="testFrame"></iframe>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 1178738 **/
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv(
{ set: [['media.webspeech.synth.enabled', true],
['media.webspeech.synth.force_global_queue', false]] },
function() { loadSpeechTest("file_speech_cancel_reentry.html"); });
</script>
</pre>
</body>
</html>