Bug 1162699 - Replace mochitest test synth services with global services to simplify tests. r=smaug

Also, remove ipc tests since we now can enable these tests in e10s.

Also, make utterances very long in cancel test so that we actually interrupt them.
This commit is contained in:
Eitan Isaacson 2015-05-14 16:24:14 -07:00
parent 06998bb825
commit 72e93f95e2
15 changed files with 541 additions and 474 deletions

View File

@ -1,196 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for OOP TTS</title>
<script type="application/javascript" src="../../test/common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<script type="application/javascript;version=1.7">
"use strict";
window.SimpleTest = parent.SimpleTest;
window.ok = parent.ok;
window.is = parent.is;
window.info = parent.info;
// The crash observer registration functions are stubbed out here to
// prevent the iframe test runner from breaking later crash-related tests.
function iframeScriptFirst() {
SpecialPowers.prototype.registerProcessCrashObservers = () => {};
SpecialPowers.prototype.unregisterProcessCrashObservers = () => {};
content.wrappedJSObject.RunSet.reloadAndRunAll({
preventDefault: function() { },
__exposedProps__: { preventDefault: 'r' }
});
}
function iframeScriptSecond() {
let TestRunner = content.wrappedJSObject.TestRunner;
let oldComplete = TestRunner.onComplete;
TestRunner.onComplete = function() {
TestRunner.onComplete = oldComplete;
sendAsyncMessage("test:SpeechSynthesis:ipcTestComplete", {
result: JSON.stringify(TestRunner._failedTests)
});
if (oldComplete) {
oldComplete();
}
};
TestRunner.structuredLogger._dumpMessage = function(msg) {
sendAsyncMessage("test:SpeechSynthesis:ipcTestMessage", { msg: msg });
}
}
let VALID_ACTIONS = ['suite_start', 'suite_end', 'test_start', 'test_end', 'test_status', 'process_output', 'log'];
function validStructuredMessage(message) {
return message.action !== undefined && VALID_ACTIONS.indexOf(message.action) >= 0;
}
function onTestMessage(data) {
let message = SpecialPowers.wrap(data).data.msg;
if (validStructuredMessage(message)) {
switch (message.action) {
case "test_status":
case "test_end":
let test_tokens = message.test.split("/");
let test_name = test_tokens[test_tokens.length - 1];
if (message.subtest) {
test_name += " | " + message.subtest;
}
ok(message.expected === undefined, test_name, message.message);
break;
case "log":
info(message.message);
break;
default:
// nothing
}
}
}
function onTestComplete() {
let comp = SpecialPowers.wrap(SpecialPowers.Components);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
let spObserver = comp.classes["@mozilla.org/special-powers-observer;1"]
.getService(comp.interfaces.nsIMessageListener);
mm.removeMessageListener("SPPrefService", spObserver);
mm.removeMessageListener("SPProcessCrashService", spObserver);
mm.removeMessageListener("SPPingService", spObserver);
mm.removeMessageListener("SpecialPowers.Quit", spObserver);
mm.removeMessageListener("SPPermissionManager", spObserver);
mm.removeMessageListener("test:SpeechSynthesis:ipcTestMessage", onTestMessage);
mm.removeMessageListener("test:SpeechSynthesis:ipcTestComplete", onTestComplete);
let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(SpecialPowers.Ci.nsIMessageBroadcaster);
ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthAddVoice", onSynthAddVoice);
ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthSetDefault", onSynthSetDefault);
ppmm.removeMessageListener("test:SpeechSynthesis:ipcSynthCleanup", onSynthCleanup);
SimpleTest.executeSoon(function () { SimpleTest.finish(); });
}
function onSynthAddVoice(data) {
let message = SpecialPowers.wrap(data).json;
return synthAddVoice.apply(synthAddVoice, message);
}
function onSynthSetDefault(data) {
let message = SpecialPowers.wrap(data).json;
synthSetDefault.apply(synthSetDefault, message);
}
function onSynthCleanup(data) {
synthCleanup();
}
function runTests() {
let iframe = document.createElement("iframe");
SpecialPowers.wrap(iframe).mozbrowser = true;
iframe.id = "iframe";
iframe.style.width = "100%";
iframe.style.height = "1000px";
function iframeLoadSecond() {
ok(true, "Got second iframe load event.");
iframe.removeEventListener("mozbrowserloadend", iframeLoadSecond);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
mm.loadFrameScript("data:,(" + iframeScriptSecond.toString() + ")();",
false);
}
function iframeLoadFirst() {
ok(true, "Got first iframe load event.");
iframe.removeEventListener("mozbrowserloadend", iframeLoadFirst);
iframe.addEventListener("mozbrowserloadend", iframeLoadSecond);
let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
let comp = SpecialPowers.wrap(SpecialPowers.Components);
let spObserver =
comp.classes["@mozilla.org/special-powers-observer;1"]
.getService(comp.interfaces.nsIMessageListener);
mm.addMessageListener("SPPrefService", spObserver);
mm.addMessageListener("SPProcessCrashService", spObserver);
mm.addMessageListener("SPPingService", spObserver);
mm.addMessageListener("SpecialPowers.Quit", spObserver);
mm.addMessageListener("SPPermissionManager", spObserver);
mm.addMessageListener("test:SpeechSynthesis:ipcTestMessage", onTestMessage);
mm.addMessageListener("test:SpeechSynthesis:ipcTestComplete", onTestComplete);
let specialPowersBase = "chrome://specialpowers/content/";
mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
mm.loadFrameScript("data:,(" + iframeScriptFirst.toString() + ")();", false);
let ppmm = SpecialPowers.Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(SpecialPowers.Ci.nsIMessageBroadcaster);
ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthAddVoice", onSynthAddVoice);
ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthSetDefault", onSynthSetDefault);
ppmm.addMessageListener("test:SpeechSynthesis:ipcSynthCleanup", onSynthCleanup);
}
iframe.addEventListener("mozbrowserloadend", iframeLoadFirst);
// Strip this filename and one directory level and then add "/test".
let href = window.location.href;
href = href.substring(0, href.lastIndexOf('/'));
href = href.substring(0, href.lastIndexOf('/'));
href = href.substring(0, href.lastIndexOf('/'));
iframe.src = href + "/test?consoleLevel=INFO";
document.body.appendChild(iframe);
}
addEventListener("load", function() {
SpecialPowers.addPermission("browser", true, document);
SpecialPowers.pushPrefEnv({
"set": [
// TODO: remove this as part of bug 820712
["network.disable.ipc.security", true],
["dom.ipc.browser_frames.oop_by_default", true],
["dom.mozBrowserFramesEnabled", true],
["browser.pagethumbnails.capturing_disabled", true]
]
}, runTests);
});
</script>
</body>
</html>

View File

@ -1,7 +0,0 @@
[DEFAULT]
skip-if = e10s
support-files =
file_ipc.html
[test_ipc.html]
skip-if = buildapp == 'b2g' || toolkit == 'android' #bug 857673 # b2g(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined) b2g-debug(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined) b2g-desktop(comp.classes['@mozilla.org/special-powers-observer;1'] is undefined)

View File

@ -1,17 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for OOP TTS</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<iframe id="testFrame"></iframe>
<script type="application/javascript;version=1.7">
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({ set: [['media.webspeech.synth.enabled', true]] },
function() { document.getElementById("testFrame").src = "file_ipc.html"; });
</script>
</body>
</html>

View File

@ -5,7 +5,6 @@
if CONFIG['MOZ_WEBSPEECH']:
MOCHITEST_MANIFESTS += [
'ipc/test/mochitest.ini',
'test/mochitest.ini',
]
@ -34,6 +33,8 @@ if CONFIG['MOZ_WEBSPEECH']:
'SpeechSynthesis.cpp',
'SpeechSynthesisUtterance.cpp',
'SpeechSynthesisVoice.cpp',
'test/FakeSynthModule.cpp',
'test/nsFakeSynthServices.cpp'
]
if CONFIG['MOZ_SYNTH_PICO']:

View File

@ -84,7 +84,7 @@ private:
// nsSpeechTask
NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance);
NS_IMPL_CYCLE_COLLECTION(nsSpeechTask, mSpeechSynthesis, mUtterance, mCallback);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSpeechTask)
NS_INTERFACE_MAP_ENTRY(nsISpeechTask)

View File

@ -0,0 +1,55 @@
/* 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 "mozilla/ModuleUtils.h"
#include "nsIClassInfoImpl.h"
#include "nsFakeSynthServices.h"
using namespace mozilla::dom;
#define FAKESYNTHSERVICE_CID \
{0xe7d52d9e, 0xc148, 0x47d8, {0xab, 0x2a, 0x95, 0xd7, 0xf4, 0x0e, 0xa5, 0x3d}}
#define FAKESYNTHSERVICE_CONTRACTID "@mozilla.org/fakesynth;1"
// Defines nsFakeSynthServicesConstructor
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsFakeSynthServices,
nsFakeSynthServices::GetInstanceForService)
// Defines kFAKESYNTHSERVICE_CID
NS_DEFINE_NAMED_CID(FAKESYNTHSERVICE_CID);
static const mozilla::Module::CIDEntry kCIDs[] = {
{ &kFAKESYNTHSERVICE_CID, true, nullptr, nsFakeSynthServicesConstructor },
{ nullptr }
};
static const mozilla::Module::ContractIDEntry kContracts[] = {
{ FAKESYNTHSERVICE_CONTRACTID, &kFAKESYNTHSERVICE_CID },
{ nullptr }
};
static const mozilla::Module::CategoryEntry kCategories[] = {
{ "profile-after-change", "Fake Speech Synth", FAKESYNTHSERVICE_CONTRACTID },
{ nullptr }
};
static void
UnloadFakeSynthmodule()
{
nsFakeSynthServices::Shutdown();
}
static const mozilla::Module kModule = {
mozilla::Module::kVersion,
kCIDs,
kContracts,
kCategories,
nullptr,
nullptr,
UnloadFakeSynthmodule
};
NSMODULE_DEFN(fakesynth) = &kModule;

View File

@ -1,199 +1,3 @@
var gSpeechRegistry = SpecialPowers.Cc["@mozilla.org/synth-voice-registry;1"]
.getService(SpecialPowers.Ci.nsISynthVoiceRegistry);
var gAddedVoices = [];
function SpeechTaskCallback(onpause, onresume, oncancel) {
this.onpause = onpause;
this.onresume = onresume;
this.oncancel = oncancel;
}
SpeechTaskCallback.prototype = {
QueryInterface: function(iid) {
return this;
},
getInterfaces: function(c) {},
getScriptableHelper: function() {},
onPause: function onPause() {
if (this.onpause)
this.onpause();
},
onResume: function onResume() {
if (this.onresume)
this.onresume();
},
onCancel: function onCancel() {
if (this.oncancel)
this.oncancel();
}
};
var TestSpeechServiceWithAudio = SpecialPowers.wrapCallbackObject({
CHANNELS: 1,
SAMPLE_RATE: 16000,
serviceType: SpecialPowers.Ci.nsISpeechService.SERVICETYPE_DIRECT_AUDIO,
speak: function speak(aText, aUri, aVolume, aRate, aPitch, aTask) {
var task = SpecialPowers.wrap(aTask);
window.setTimeout(
function () {
task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback()), this.CHANNELS, this.SAMPLE_RATE);
// 0.025 seconds per character.
task.sendAudio(new Int16Array((this.SAMPLE_RATE/40)*aText.length), []);
task.sendAudio(new Int16Array(0), []);
}.bind(this), 0);
},
QueryInterface: function(iid) {
return this;
},
getInterfaces: function(c) {},
getScriptableHelper: function() {}
});
var TestSpeechServiceNoAudio = SpecialPowers.wrapCallbackObject({
serviceType: SpecialPowers.Ci.nsISpeechService.SERVICETYPE_INDIRECT_AUDIO,
speak: function speak(aText, aUri, aVolume, aRate, aPitch, aTask) {
var pair = this.expectedSpeaks.shift();
if (pair) {
// XXX: These tests do not happen in OOP
var utterance = pair[0];
var expected = pair[1];
is(aText, utterance.text, "Speak text matches utterance text");
var args = {uri: aUri, rate: aRate, pitch: aPitch};
for (var attr in args) {
if (expected[attr] != undefined)
is(args[attr], expected[attr], "expected service arg " + attr);
}
}
// If the utterance contains the phrase 'callback events', we will dispatch
// an appropriate event for each callback method.
var no_events = (aText.indexOf('callback events') < 0);
// If the utterance contains the phrase 'never end', we don't immediately
// end the 'synthesis' of the utterance.
var end_utterance = (aText.indexOf('never end') < 0);
var task = SpecialPowers.wrap(aTask);
task.setup(SpecialPowers.wrapCallbackObject(new SpeechTaskCallback(
function() {
if (!no_events) {
task.dispatchPause(1, 1.23);
}
},
function() {
if (!no_events) {
task.dispatchResume(1, 1.23);
}
},
function() {
if (!no_events) {
task.dispatchEnd(1, 1.23);
}
})));
setTimeout(function () {
task.dispatchStart();
if (end_utterance) {
setTimeout(function () {
task.dispatchEnd(
aText.length / 2.0, aText.length);
}, 0);
}
}, 0);
},
QueryInterface: function(iid) {
return this;
},
getInterfaces: function(c) {},
getScriptableHelper: function() {},
expectedSpeaks: []
});
function synthAddVoice(aServiceName, aName, aLang, aIsLocal) {
if (SpecialPowers.isMainProcess()) {
var voicesBefore = speechSynthesis.getVoices().length;
var uri = "urn:moz-tts:mylittleservice:" + encodeURI(aName + '?' + aLang);
gSpeechRegistry.addVoice(window[aServiceName], uri, aName, aLang, aIsLocal);
gAddedVoices.push([window[aServiceName], uri]);
var voicesAfter = speechSynthesis.getVoices().length;
is(voicesBefore + 1, voicesAfter, "Voice added");
var voice = speechSynthesis.getVoices()[voicesAfter - 1];
is(voice.voiceURI, uri, "voice URI matches");
is(voice.name, aName, "voice name matches");
is(voice.lang, aLang, "voice lang matches");
is(voice.localService, aIsLocal, "voice localService matches");
return uri;
} else {
// XXX: It would be nice to check here that the child gets the voice
// added update, but alas, it is aynchronous.
var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(SpecialPowers.Ci.nsISyncMessageSender);
return mm.sendSyncMessage(
'test:SpeechSynthesis:ipcSynthAddVoice',
[aServiceName, aName, aLang, aIsLocal])[0];
}
}
function synthSetDefault(aUri, aIsDefault) {
if (SpecialPowers.isMainProcess()) {
gSpeechRegistry.setDefaultVoice(aUri, aIsDefault);
var voices = speechSynthesis.getVoices();
for (var i in voices) {
if (voices[i].voiceURI == aUri)
ok(voices[i]['default'], "Voice set to default");
}
} else {
// XXX: It would be nice to check here that the child gets the voice
// added update, but alas, it is aynchronous.
var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(SpecialPowers.Ci.nsISyncMessageSender);
return mm.sendSyncMessage(
'test:SpeechSynthesis:ipcSynthSetDefault', [aUri, aIsDefault])[0];
}
}
function synthCleanup() {
if (SpecialPowers.isMainProcess()) {
var voicesBefore = speechSynthesis.getVoices().length;
var toRemove = gAddedVoices.length;
var removeArgs;
while ((removeArgs = gAddedVoices.shift()))
gSpeechRegistry.removeVoice.apply(gSpeechRegistry.removeVoice, removeArgs);
var voicesAfter = speechSynthesis.getVoices().length;
is(voicesAfter, voicesBefore - toRemove, "Successfully removed test voices");
} else {
// XXX: It would be nice to check here that the child gets the voice
// removed update, but alas, it is aynchronous.
var mm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(SpecialPowers.Ci.nsISyncMessageSender);
mm.sendSyncMessage('test:SpeechSynthesis:ipcSynthCleanup');
}
}
function synthTestQueue(aTestArgs, aEndFunc) {
var utterances = [];
for (var i in aTestArgs) {
@ -220,6 +24,16 @@ function synthTestQueue(aTestArgs, aEndFunc) {
}
}
u.addEventListener('start',
(function (expectedUri) {
return function (e) {
if (expectedUri) {
var chosenVoice = SpecialPowers.wrap(e).target.chosenVoiceURI;
is(chosenVoice, expectedUri, "Incorrect URI is used");
}
};
})(aTestArgs[i][1] ? aTestArgs[i][1].uri : null));
u.addEventListener('end', onend_handler);
u.addEventListener('error', onend_handler);
@ -229,7 +43,6 @@ function synthTestQueue(aTestArgs, aEndFunc) {
});
utterances.push(u);
TestSpeechServiceNoAudio.expectedSpeaks.push([u, aTestArgs[i][1]]);
speechSynthesis.speak(u);
}

View File

@ -26,11 +26,10 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1155034
/** Test for Bug 1155034 **/
synthAddVoice('TestSpeechServiceNoAudio', 'Female 1', 'en-GB', true);
function test_with_events() {
info('test_with_events');
var utterance = new SpeechSynthesisUtterance("never end, callback events");
utterance.lang = 'it-IT-noend';
utterance.addEventListener('start', function(e) {
speechSynthesis.pause();
@ -38,20 +37,20 @@ function test_with_events() {
});
utterance.addEventListener('pause', function(e) {
ok(e.charIndex, 1, 'pause event charIndex matches service arguments');
ok(e.elapsedTime, 1.23, 'pause event elapsedTime matches service arguments');
is(e.charIndex, 1, 'pause event charIndex matches service arguments');
is(e.elapsedTime, 1.5, 'pause event elapsedTime matches service arguments');
speechSynthesis.resume();
});
utterance.addEventListener('resume', function(e) {
ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
ok(e.elapsedTime, 1.23, 'resume event elapsedTime matches service arguments');
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) {
ok(e.charIndex, 1, 'resume event charIndex matches service arguments');
ok(e.elapsedTime, 1.23, 'end event elapsedTime matches service arguments');
ok(e.elapsedTime, 1.5, 'end event elapsedTime matches service arguments');
test_no_events();
});
@ -60,12 +59,11 @@ function test_with_events() {
function test_no_events() {
var utterance = new SpeechSynthesisUtterance("never end");
utterance.lang = "it-IT-noevents-noend";
utterance.addEventListener('start', function(e) {
speechSynthesis.pause();
// Wait to see if we get some bad events we didn't expect.
setTimeout(function() {
synthCleanup();
SimpleTest.finish();
}, 1000);
});

View File

@ -25,12 +25,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=525444
/** Test for Bug 525444 **/
synthAddVoice('TestSpeechServiceNoAudio', 'Bob Marley', 'en-JM', true);
synthAddVoice('TestSpeechServiceNoAudio', 'Amy Winehouse', 'en-GB', true);
synthAddVoice('TestSpeechServiceNoAudio', 'Leonard Cohen', 'en-CA', true);
synthAddVoice('TestSpeechServiceNoAudio', 'Celine Dion', 'fr-CA', true);
synthAddVoice('TestSpeechServiceNoAudio', 'Julieta Venegas', 'es-MX', true);
ok(SpeechSynthesis, "SpeechSynthesis exists in global scope");
ok(SpeechSynthesisVoice, "SpeechSynthesisVoice exists in global scope");
ok(SpeechSynthesisEvent, "SpeechSynthesisEvent exists in global scope");
@ -75,8 +69,6 @@ for (var i in voices1) {
ok(voices1[i] == voices2[i], "Voice instance matches");
}
synthCleanup();
SimpleTest.finish();
</script>
</pre>

View File

@ -26,16 +26,35 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1150315
/** Test for Bug 1150315 **/
synthAddVoice('TestSpeechServiceWithAudio', 'Male 1', 'en-GB', true);
var gotEndEvent = false;
var utterance = new SpeechSynthesisUtterance("Hello, world!");
// 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" +
"consectetur bibendum. Integer a commodo tortor. Duis semper dolor eu" +
"facilisis facilisis. Etiam venenatis turpis est, quis tincidunt velit" +
"suscipit a. Cras semper orci in sapien rhoncus bibendum. Suspendisse" +
"eu ex lobortis, finibus enim in, condimentum quam. Maecenas eget dui" +
"ipsum. Aliquam tortor leo, interdum eget congue ut, tempor id elit.");
utterance.addEventListener('start', function(e) {
ok(true, 'start utterance 1');
speechSynthesis.cancel();
speechSynthesis.speak(utterance2);
});
var utterance2 = new SpeechSynthesisUtterance("Hello, world 2!");
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." +
"Praesent eu iaculis orci. Sed nisl diam, sodales ac purus et," +
"volutpat interdum tortor. Nullam aliquam porta elit et maximus. Cras" +
"risus lectus, elementum vel sodales vel, ultricies eget lectus." +
"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() {
speechSynthesis.cancel();
speechSynthesis.speak(utterance3);
@ -46,10 +65,9 @@ utterance2.addEventListener('end', function(e) {
var utterance3 = new SpeechSynthesisUtterance("Hello, world 3!");
utterance3.addEventListener('start', function() {
ok(gotEndEvent, "didn't get start event for this utterance")
ok(gotEndEvent, "didn't get start event for this utterance");
});
utterance3.addEventListener('end', function(e) {
synthCleanup();
SimpleTest.finish();
});

View File

@ -18,48 +18,54 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=525444
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=525444">Mozilla Bug 525444</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 525444 **/
var englishJamaican = synthAddVoice('TestSpeechServiceNoAudio',
'Bob Marley', 'en-JM', true);
var englishBritish = synthAddVoice('TestSpeechServiceNoAudio',
'Amy Winehouse', 'en-GB', true);
var englishCanadian = synthAddVoice('TestSpeechServiceNoAudio',
'Leonard Cohen', 'en-CA', true);
var frenchCanadian = synthAddVoice('TestSpeechServiceNoAudio',
'Celine Dion', 'fr-CA', true);
var spanishMexican = synthAddVoice('TestSpeechServiceNoAudio',
'Julieta Venegas', 'es-MX', true);
// XXX: Rate and pitch are not tested.
synthSetDefault(englishBritish, true);
var langUriMap = {};
for (var voice of speechSynthesis.getVoices()) {
if (voice.voiceURI.indexOf('urn:moz-tts:fake-direct') < 0) {
continue;
}
langUriMap[voice.lang] = voice.voiceURI;
ok(true, voice.lang + ' ' + voice.voiceURI + ' ' + voice.default);
is(voice.default, voice.lang == 'en-JM', 'Only Jamaican voice should be default');
}
ok(langUriMap['en-JM'], 'No English-Jamaican voice');
ok(langUriMap['en-GB'], 'No English-British voice');
ok(langUriMap['en-CA'], 'No English-Canadian voice');
ok(langUriMap['fr-CA'], 'No French-Canadian voice');
ok(langUriMap['es-MX'], 'No Spanish-Mexican voice');
synthTestQueue(
[[{text: "Hello, world."},
{ uri: englishBritish }],
{ uri: langUriMap['en-JM'] }],
[{text: "Bonjour tout le monde .", lang: "fr", rate: 0.5, pitch: 0.75},
{ uri: frenchCanadian, rate: 0.5, pitch: 0.75}],
{ uri: langUriMap['fr-CA'], rate: 0.5, pitch: 0.75}],
[{text: "How are you doing?", lang: "en-GB"},
{ rate: 1, pitch: 1, uri: englishBritish}],
[{text: "¡hasta mañana", lang: "es-ES"},
{ uri: spanishMexican }]],
{ rate: 1, pitch: 1, uri: langUriMap['en-GB']}],
[{text: "¡hasta mañana!", lang: "es-MX"},
{ uri: langUriMap['es-MX'] }]],
function () {
synthSetDefault(englishJamaican, true);
var test_data = [[{text: "I shot the sheriff."},
{ uri: englishJamaican }]];
var test_data = [];
var voices = speechSynthesis.getVoices();
for (var i in voices) {
test_data.push([{text: "Hello world", voice: voices[i]},
{uri: voices[i].voiceURI}]);
for (var voice of voices) {
if (voice.voiceURI.indexOf('urn:moz-tts:fake-direct') < 0) {
continue;
}
test_data.push([{text: "Hello world", voice: voice},
{uri: voice.voiceURI}]);
}
synthTestQueue(test_data,
function () {
synthCleanup();
SimpleTest.finish();
});
});

View File

@ -26,8 +26,6 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=650295
/** Test for Bug 525444 **/
synthAddVoice('TestSpeechServiceWithAudio', 'Male 1', 'en-GB', true);
var gotStartEvent = false;
var gotBoundaryEvent = false;
var utterance = new SpeechSynthesisUtterance("Hello, world!");
@ -42,7 +40,6 @@ utterance.addEventListener('end', function(e) {
ok(!speechSynthesis.pending, "speechSynthesis has no other utterances queued.");
ok(gotStartEvent, "Got 'start' event.");
info('end ' + e.elapsedTime);
synthCleanup();
SimpleTest.finish();
});

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = e10s
support-files =
common.js
file_setup.html
@ -10,10 +9,6 @@ support-files =
[test_setup.html]
[test_speech_queue.html]
skip-if = buildapp == 'b2g' # b2g(Test timed out)
[test_speech_simple.html]
skip-if = buildapp == 'b2g' # b2g(Test timed out)
[test_speech_cancel.html]
skip-if = toolkit == 'gonk' # b2g(Test timed out)
[test_indirect_service_events.html]
skip-if = toolkit == 'gonk' # b2g(Test timed out)

View File

@ -0,0 +1,359 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "nsISupports.h"
#include "nsFakeSynthServices.h"
#include "nsPrintfCString.h"
#include "nsIWeakReferenceUtils.h"
#include "SharedBuffer.h"
#include "nsISimpleEnumerator.h"
#include "mozilla/dom/nsSynthVoiceRegistry.h"
#include "mozilla/dom/nsSpeechTask.h"
#include "nsThreadUtils.h"
#include "prenv.h"
#include "mozilla/Preferences.h"
#include "mozilla/DebugOnly.h"
#define CHANNELS 1
#define SAMPLERATE 1600
namespace mozilla {
namespace dom {
StaticRefPtr<nsFakeSynthServices> nsFakeSynthServices::sSingleton;
enum VoiceFlags
{
eSuppressEvents = 1,
eSuppressEnd = 2
};
struct VoiceDetails
{
const char* uri;
const char* name;
const char* lang;
bool defaultVoice;
uint32_t flags;
};
static const VoiceDetails sDirectVoices[] = {
{"urn:moz-tts:fake-direct:bob", "Bob Marley", "en-JM", true, 0},
{"urn:moz-tts:fake-direct:amy", "Amy Winehouse", "en-GB", false, 0},
{"urn:moz-tts:fake-direct:lenny", "Leonard Cohen", "en-CA", false, 0},
{"urn:moz-tts:fake-direct:celine", "Celine Dion", "fr-CA", false, 0},
{"urn:moz-tts:fake-direct:julie", "Julieta Venegas", "es-MX", false, },
};
static const VoiceDetails sIndirectVoices[] = {
{"urn:moz-tts:fake-indirect:zanetta", "Zanetta Farussi", "it-IT", false, 0},
{"urn:moz-tts:fake-indirect:margherita", "Margherita Durastanti", "it-IT-noevents-noend", false, eSuppressEvents | eSuppressEnd},
{"urn:moz-tts:fake-indirect:teresa", "Teresa Cornelys", "it-IT-noend", false, eSuppressEnd},
};
// FakeSynthCallback
class FakeSynthCallback : public nsISpeechTaskCallback
{
public:
explicit FakeSynthCallback(nsISpeechTask* aTask) : mTask(aTask) { }
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(FakeSynthCallback, nsISpeechTaskCallback)
NS_IMETHOD OnPause()
{
if (mTask) {
mTask->DispatchPause(1.5, 1);
}
return NS_OK;
}
NS_IMETHOD OnResume()
{
if (mTask) {
mTask->DispatchResume(1.5, 1);
}
return NS_OK;
}
NS_IMETHOD OnCancel()
{
if (mTask) {
mTask->DispatchEnd(1.5, 1);
}
return NS_OK;
}
private:
virtual ~FakeSynthCallback() { }
nsCOMPtr<nsISpeechTask> mTask;
};
NS_IMPL_CYCLE_COLLECTION(FakeSynthCallback, mTask);
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FakeSynthCallback)
NS_INTERFACE_MAP_ENTRY(nsISpeechTaskCallback)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsISpeechTaskCallback)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(FakeSynthCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(FakeSynthCallback)
// FakeDirectAudioSynth
class FakeDirectAudioSynth : public nsISpeechService
{
public:
FakeDirectAudioSynth() { }
NS_DECL_ISUPPORTS
NS_DECL_NSISPEECHSERVICE
private:
virtual ~FakeDirectAudioSynth() { }
};
NS_IMPL_ISUPPORTS(FakeDirectAudioSynth, nsISpeechService)
NS_IMETHODIMP
FakeDirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
float aVolume, float aRate, float aPitch,
nsISpeechTask* aTask)
{
class Runnable final : public nsRunnable
{
public:
Runnable(nsISpeechTask* aTask, const nsAString& aText) :
mTask(aTask), mText(aText)
{
}
NS_IMETHOD Run() override
{
nsRefPtr<FakeSynthCallback> cb = new FakeSynthCallback(nullptr);
mTask->Setup(cb, CHANNELS, SAMPLERATE, 2);
// Just an arbitrary multiplier. Pretend that each character is
// synthesized to 40 frames.
uint32_t frames_length = 40 * mText.Length();
nsAutoArrayPtr<int16_t> frames(new int16_t[frames_length]());
mTask->SendAudioNative(frames, frames_length);
mTask->SendAudioNative(nullptr, 0);
return NS_OK;
}
private:
nsCOMPtr<nsISpeechTask> mTask;
nsString mText;
};
nsCOMPtr<nsIRunnable> runnable = new Runnable(aTask, aText);
NS_DispatchToMainThread(runnable);
return NS_OK;
}
NS_IMETHODIMP
FakeDirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
{
*aServiceType = nsISpeechService::SERVICETYPE_DIRECT_AUDIO;
return NS_OK;
}
// FakeDirectAudioSynth
class FakeIndirectAudioSynth : public nsISpeechService
{
public:
FakeIndirectAudioSynth() {}
NS_DECL_ISUPPORTS
NS_DECL_NSISPEECHSERVICE
private:
virtual ~FakeIndirectAudioSynth() { }
};
NS_IMPL_ISUPPORTS(FakeIndirectAudioSynth, nsISpeechService)
NS_IMETHODIMP
FakeIndirectAudioSynth::Speak(const nsAString& aText, const nsAString& aUri,
float aVolume, float aRate, float aPitch,
nsISpeechTask* aTask)
{
class DispatchStart final : public nsRunnable
{
public:
explicit DispatchStart(nsISpeechTask* aTask) :
mTask(aTask)
{
}
NS_IMETHOD Run() override
{
mTask->DispatchStart();
return NS_OK;
}
private:
nsCOMPtr<nsISpeechTask> mTask;
};
class DispatchEnd final : public nsRunnable
{
public:
DispatchEnd(nsISpeechTask* aTask, const nsAString& aText) :
mTask(aTask), mText(aText)
{
}
NS_IMETHOD Run() override
{
mTask->DispatchEnd(mText.Length()/2, mText.Length());
return NS_OK;
}
private:
nsCOMPtr<nsISpeechTask> mTask;
nsString mText;
};
uint32_t flags = 0;
for (uint32_t i = 0; i < ArrayLength(sIndirectVoices); i++) {
if (aUri.EqualsASCII(sIndirectVoices[i].uri)) {
flags = sIndirectVoices[i].flags;
}
}
nsRefPtr<FakeSynthCallback> cb = new FakeSynthCallback(
(flags & eSuppressEvents) ? nullptr : aTask);
aTask->Setup(cb, 0, 0, 0);
nsCOMPtr<nsIRunnable> runnable = new DispatchStart(aTask);
NS_DispatchToMainThread(runnable);
if ((flags & eSuppressEnd) == 0) {
runnable = new DispatchEnd(aTask, aText);
NS_DispatchToMainThread(runnable);
}
return NS_OK;
}
NS_IMETHODIMP
FakeIndirectAudioSynth::GetServiceType(SpeechServiceType* aServiceType)
{
*aServiceType = nsISpeechService::SERVICETYPE_INDIRECT_AUDIO;
return NS_OK;
}
// nsFakeSynthService
NS_INTERFACE_MAP_BEGIN(nsFakeSynthServices)
NS_INTERFACE_MAP_ENTRY(nsIObserver)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
NS_INTERFACE_MAP_END
NS_IMPL_ADDREF(nsFakeSynthServices)
NS_IMPL_RELEASE(nsFakeSynthServices)
nsFakeSynthServices::nsFakeSynthServices()
{
}
nsFakeSynthServices::~nsFakeSynthServices()
{
}
static void
AddVoices(nsISpeechService* aService, const VoiceDetails* aVoices, uint32_t aLength)
{
nsSynthVoiceRegistry* registry = nsSynthVoiceRegistry::GetInstance();
for (uint32_t i = 0; i < aLength; i++) {
NS_ConvertUTF8toUTF16 name(aVoices[i].name);
NS_ConvertUTF8toUTF16 uri(aVoices[i].uri);
NS_ConvertUTF8toUTF16 lang(aVoices[i].lang);
registry->AddVoice(aService, uri, name, lang, true);
if (aVoices[i].defaultVoice) {
registry->SetDefaultVoice(uri, true);
}
}
}
void
nsFakeSynthServices::Init()
{
mDirectService = new FakeDirectAudioSynth();
AddVoices(mDirectService, sDirectVoices, ArrayLength(sDirectVoices));
mIndirectService = new FakeIndirectAudioSynth();
AddVoices(mIndirectService, sIndirectVoices, ArrayLength(sIndirectVoices));
}
// nsIObserver
NS_IMETHODIMP
nsFakeSynthServices::Observe(nsISupports* aSubject, const char* aTopic,
const char16_t* aData)
{
MOZ_ASSERT(NS_IsMainThread());
NS_ENSURE_TRUE(!strcmp(aTopic, "profile-after-change"), NS_ERROR_UNEXPECTED);
if (Preferences::GetBool("media.webspeech.synth.test")) {
Init();
}
return NS_OK;
}
// static methods
nsFakeSynthServices*
nsFakeSynthServices::GetInstance()
{
MOZ_ASSERT(NS_IsMainThread());
if (XRE_GetProcessType() != GeckoProcessType_Default) {
MOZ_ASSERT(false, "nsFakeSynthServices can only be started on main gecko process");
return nullptr;
}
if (!sSingleton) {
sSingleton = new nsFakeSynthServices();
}
return sSingleton;
}
already_AddRefed<nsFakeSynthServices>
nsFakeSynthServices::GetInstanceForService()
{
nsRefPtr<nsFakeSynthServices> picoService = GetInstance();
return picoService.forget();
}
void
nsFakeSynthServices::Shutdown()
{
if (!sSingleton) {
return;
}
sSingleton = nullptr;
}
} // namespace dom
} // namespace mozilla

View File

@ -0,0 +1,53 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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/. */
#ifndef nsFakeSynthServices_h
#define nsFakeSynthServices_h
#include "nsAutoPtr.h"
#include "nsTArray.h"
#include "nsIObserver.h"
#include "nsIThread.h"
#include "nsISpeechService.h"
#include "nsRefPtrHashtable.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/Monitor.h"
namespace mozilla {
namespace dom {
class nsFakeSynthServices : public nsIObserver
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
nsFakeSynthServices();
static nsFakeSynthServices* GetInstance();
static already_AddRefed<nsFakeSynthServices> GetInstanceForService();
static void Shutdown();
private:
virtual ~nsFakeSynthServices();
void Init();
nsCOMPtr<nsISpeechService> mDirectService;
nsCOMPtr<nsISpeechService> mIndirectService;
static StaticRefPtr<nsFakeSynthServices> sSingleton;
};
} // namespace dom
} // namespace mozilla
#endif