зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
c57d35080a
Коммит
d49063d60c
|
@ -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>
|
Загрузка…
Ссылка в новой задаче