diff options
Diffstat (limited to 'dom/media/webspeech/recognition/test')
25 files changed, 1324 insertions, 0 deletions
diff --git a/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp b/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp new file mode 100644 index 0000000000..cf14cb3750 --- /dev/null +++ b/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.cpp @@ -0,0 +1,118 @@ +/* -*- 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 "nsThreadUtils.h" + +#include "FakeSpeechRecognitionService.h" + +#include "SpeechRecognition.h" +#include "SpeechRecognitionAlternative.h" +#include "SpeechRecognitionResult.h" +#include "SpeechRecognitionResultList.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_media.h" + +namespace mozilla { + +using namespace dom; + +NS_IMPL_ISUPPORTS(FakeSpeechRecognitionService, nsISpeechRecognitionService, + nsIObserver) + +FakeSpeechRecognitionService::FakeSpeechRecognitionService() = default; + +FakeSpeechRecognitionService::~FakeSpeechRecognitionService() = default; + +NS_IMETHODIMP +FakeSpeechRecognitionService::Initialize( + WeakPtr<SpeechRecognition> aSpeechRecognition) { + MOZ_ASSERT(NS_IsMainThread()); + mRecognition = aSpeechRecognition; + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + obs->AddObserver(this, SPEECH_RECOGNITION_TEST_EVENT_REQUEST_TOPIC, false); + obs->AddObserver(this, SPEECH_RECOGNITION_TEST_END_TOPIC, false); + return NS_OK; +} + +NS_IMETHODIMP +FakeSpeechRecognitionService::ProcessAudioSegment(AudioSegment* aAudioSegment, + int32_t aSampleRate) { + MOZ_ASSERT(!NS_IsMainThread()); + return NS_OK; +} + +NS_IMETHODIMP +FakeSpeechRecognitionService::SoundEnd() { + MOZ_ASSERT(NS_IsMainThread()); + return NS_OK; +} + +NS_IMETHODIMP +FakeSpeechRecognitionService::ValidateAndSetGrammarList( + mozilla::dom::SpeechGrammar*, nsISpeechGrammarCompilationCallback*) { + return NS_OK; +} + +NS_IMETHODIMP +FakeSpeechRecognitionService::Abort() { + MOZ_ASSERT(NS_IsMainThread()); + return NS_OK; +} + +NS_IMETHODIMP +FakeSpeechRecognitionService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(StaticPrefs::media_webspeech_test_fake_recognition_service(), + "Got request to fake recognition service event, but " + "media.webspeech.test.fake_recognition_service is not set"); + + if (!strcmp(aTopic, SPEECH_RECOGNITION_TEST_END_TOPIC)) { + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + obs->RemoveObserver(this, SPEECH_RECOGNITION_TEST_EVENT_REQUEST_TOPIC); + obs->RemoveObserver(this, SPEECH_RECOGNITION_TEST_END_TOPIC); + + return NS_OK; + } + + const nsDependentString eventName = nsDependentString(aData); + + if (eventName.EqualsLiteral("EVENT_RECOGNITIONSERVICE_ERROR")) { + mRecognition->DispatchError( + SpeechRecognition::EVENT_RECOGNITIONSERVICE_ERROR, + SpeechRecognitionErrorCode::Network, // TODO different codes? + "RECOGNITIONSERVICE_ERROR test event"); + + } else if (eventName.EqualsLiteral("EVENT_RECOGNITIONSERVICE_FINAL_RESULT")) { + RefPtr<SpeechEvent> event = new SpeechEvent( + mRecognition, SpeechRecognition::EVENT_RECOGNITIONSERVICE_FINAL_RESULT); + + event->mRecognitionResultList = BuildMockResultList(); + NS_DispatchToMainThread(event); + } + return NS_OK; +} + +SpeechRecognitionResultList* +FakeSpeechRecognitionService::BuildMockResultList() { + SpeechRecognitionResultList* resultList = + new SpeechRecognitionResultList(mRecognition); + SpeechRecognitionResult* result = new SpeechRecognitionResult(mRecognition); + if (0 < mRecognition->MaxAlternatives()) { + SpeechRecognitionAlternative* alternative = + new SpeechRecognitionAlternative(mRecognition); + + alternative->mTranscript = u"Mock final result"_ns; + alternative->mConfidence = 0.0f; + + result->mItems.AppendElement(alternative); + } + resultList->mItems.AppendElement(result); + + return resultList; +} + +} // namespace mozilla diff --git a/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.h b/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.h new file mode 100644 index 0000000000..69e2786b76 --- /dev/null +++ b/dom/media/webspeech/recognition/test/FakeSpeechRecognitionService.h @@ -0,0 +1,40 @@ +/* -*- 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 mozilla_dom_FakeSpeechRecognitionService_h +#define mozilla_dom_FakeSpeechRecognitionService_h + +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsISpeechRecognitionService.h" + +#define NS_FAKE_SPEECH_RECOGNITION_SERVICE_CID \ + {0x48c345e7, \ + 0x9929, \ + 0x4f9a, \ + {0xa5, 0x63, 0xf4, 0x78, 0x22, 0x2d, 0xab, 0xcd}}; + +namespace mozilla { + +class FakeSpeechRecognitionService : public nsISpeechRecognitionService, + public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISPEECHRECOGNITIONSERVICE + NS_DECL_NSIOBSERVER + + FakeSpeechRecognitionService(); + + private: + virtual ~FakeSpeechRecognitionService(); + + WeakPtr<dom::SpeechRecognition> mRecognition; + dom::SpeechRecognitionResultList* BuildMockResultList(); +}; + +} // namespace mozilla + +#endif diff --git a/dom/media/webspeech/recognition/test/head.js b/dom/media/webspeech/recognition/test/head.js new file mode 100644 index 0000000000..c77a7ee926 --- /dev/null +++ b/dom/media/webspeech/recognition/test/head.js @@ -0,0 +1,200 @@ +"use strict"; + +const DEFAULT_AUDIO_SAMPLE_FILE = "hello.ogg"; +const SPEECH_RECOGNITION_TEST_REQUEST_EVENT_TOPIC = + "SpeechRecognitionTest:RequestEvent"; +const SPEECH_RECOGNITION_TEST_END_TOPIC = "SpeechRecognitionTest:End"; + +var errorCodes = { + NO_SPEECH: "no-speech", + ABORTED: "aborted", + AUDIO_CAPTURE: "audio-capture", + NETWORK: "network", + NOT_ALLOWED: "not-allowed", + SERVICE_NOT_ALLOWED: "service-not-allowed", + BAD_GRAMMAR: "bad-grammar", + LANGUAGE_NOT_SUPPORTED: "language-not-supported", +}; + +var Services = SpecialPowers.Services; + +function EventManager(sr) { + var self = this; + var nEventsExpected = 0; + self.eventsReceived = []; + + var allEvents = [ + "audiostart", + "soundstart", + "speechstart", + "speechend", + "soundend", + "audioend", + "result", + "nomatch", + "error", + "start", + "end", + ]; + + var eventDependencies = { + speechend: "speechstart", + soundend: "soundstart", + audioend: "audiostart", + }; + + var isDone = false; + + // set up grammar + var sgl = new SpeechGrammarList(); + sgl.addFromString("#JSGF V1.0; grammar test; public <simple> = hello ;", 1); + sr.grammars = sgl; + + // AUDIO_DATA events are asynchronous, + // so we queue events requested while they are being + // issued to make them seem synchronous + var isSendingAudioData = false; + var queuedEventRequests = []; + + // register default handlers + for (var i = 0; i < allEvents.length; i++) { + (function (eventName) { + sr["on" + eventName] = function (evt) { + var message = "unexpected event: " + eventName; + if (eventName == "error") { + message += " -- " + evt.message; + } + + ok(false, message); + if (self.doneFunc && !isDone) { + isDone = true; + self.doneFunc(); + } + }; + })(allEvents[i]); + } + + self.expect = function EventManager_expect(eventName, cb) { + nEventsExpected++; + + sr["on" + eventName] = function (evt) { + self.eventsReceived.push(eventName); + ok(true, "received event " + eventName); + + var dep = eventDependencies[eventName]; + if (dep) { + ok( + self.eventsReceived.includes(dep), + eventName + " must come after " + dep + ); + } + + cb && cb(evt, sr); + if ( + self.doneFunc && + !isDone && + nEventsExpected === self.eventsReceived.length + ) { + isDone = true; + self.doneFunc(); + } + }; + }; + + self.start = function EventManager_start() { + isSendingAudioData = true; + var audioTag = document.createElement("audio"); + audioTag.src = self.audioSampleFile; + + var stream = audioTag.mozCaptureStreamUntilEnded(); + audioTag.addEventListener("ended", function () { + info("Sample stream ended, requesting queued events"); + isSendingAudioData = false; + while (queuedEventRequests.length) { + self.requestFSMEvent(queuedEventRequests.shift()); + } + }); + + audioTag.play(); + sr.start(stream); + }; + + self.requestFSMEvent = function EventManager_requestFSMEvent(eventName) { + if (isSendingAudioData) { + info( + "Queuing event " + eventName + " until we're done sending audio data" + ); + queuedEventRequests.push(eventName); + return; + } + + info("requesting " + eventName); + Services.obs.notifyObservers( + null, + SPEECH_RECOGNITION_TEST_REQUEST_EVENT_TOPIC, + eventName + ); + }; + + self.requestTestEnd = function EventManager_requestTestEnd() { + Services.obs.notifyObservers(null, SPEECH_RECOGNITION_TEST_END_TOPIC); + }; +} + +function buildResultCallback(transcript) { + return function (evt) { + is(evt.results[0][0].transcript, transcript, "expect correct transcript"); + }; +} + +function buildErrorCallback(errcode) { + return function (err) { + is(err.error, errcode, "expect correct error code"); + }; +} + +function performTest(options) { + var prefs = options.prefs; + + prefs.unshift( + ["media.webspeech.recognition.enable", true], + ["media.webspeech.test.enable", true] + ); + + SpecialPowers.pushPrefEnv({ set: prefs }, function () { + var sr; + if (!options.webkit) { + sr = new SpeechRecognition(); + } else { + sr = new webkitSpeechRecognition(); + var grammar = new webkitSpeechGrammar(); + var speechrecognitionlist = new webkitSpeechGrammarList(); + speechrecognitionlist.addFromString("", 1); + sr.grammars = speechrecognitionlist; + } + var em = new EventManager(sr); + + for (var eventName in options.expectedEvents) { + var cb = options.expectedEvents[eventName]; + em.expect(eventName, cb); + } + + em.doneFunc = function () { + em.requestTestEnd(); + if (options.doneFunc) { + options.doneFunc(); + } + }; + + em.audioSampleFile = DEFAULT_AUDIO_SAMPLE_FILE; + if (options.audioSampleFile) { + em.audioSampleFile = options.audioSampleFile; + } + + em.start(); + + for (var i = 0; i < options.eventsToRequest.length; i++) { + em.requestFSMEvent(options.eventsToRequest[i]); + } + }); +} diff --git a/dom/media/webspeech/recognition/test/hello.ogg b/dom/media/webspeech/recognition/test/hello.ogg Binary files differnew file mode 100644 index 0000000000..7a80926065 --- /dev/null +++ b/dom/media/webspeech/recognition/test/hello.ogg diff --git a/dom/media/webspeech/recognition/test/hello.ogg^headers^ b/dom/media/webspeech/recognition/test/hello.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/webspeech/recognition/test/hello.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/webspeech/recognition/test/http_requesthandler.sjs b/dom/media/webspeech/recognition/test/http_requesthandler.sjs new file mode 100644 index 0000000000..3400df50ec --- /dev/null +++ b/dom/media/webspeech/recognition/test/http_requesthandler.sjs @@ -0,0 +1,85 @@ +const CC = Components.Constructor; + +// Context structure - we need to set this up properly to pass to setObjectState +const ctx = { + QueryInterface(iid) { + if (iid.equals(Components.interfaces.nsISupports)) { + return this; + } + throw Components.Exception("", Components.results.NS_ERROR_NO_INTERFACE); + }, +}; + +function setRequest(request) { + setObjectState(key, request); +} +function getRequest() { + let request; + getObjectState(v => { + request = v; + }); + return request; +} + +function handleRequest(request, response) { + response.processAsync(); + if (request.queryString == "save") { + // Get the context structure and finish the old request + getObjectState("context", function (obj) { + savedCtx = obj.wrappedJSObject; + request = savedCtx.request; + + response.setHeader("Content-Type", "application/octet-stream", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + + const input = request.bodyInputStream; + const output = response.bodyOutputStream; + let bodyAvail; + while ((bodyAvail = input.available()) > 0) { + output.writeFrom(input, bodyAvail); + } + response.finish(); + }); + return; + } else if ( + request.queryString == "malformedresult=1" || + request.queryString == "emptyresult=1" + ) { + jsonOK = + request.queryString == "malformedresult=1" + ? '{"status":"ok","dat' + : '{"status":"ok","data":[]}'; + response.setHeader("Content-Length", String(jsonOK.length), false); + response.setHeader("Content-Type", "application/json", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(jsonOK, jsonOK.length); + response.finish(); + } else if (request.queryString == "hangup=1") { + response.finish(); + } else if (request.queryString == "return400=1") { + jsonOK = "{'message':'Bad header:accept-language-stt'}"; + response.setHeader("Content-Length", String(jsonOK.length), false); + response.setHeader("Content-Type", "application/json", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 400, "Bad Request"); + response.write(jsonOK, jsonOK.length); + response.finish(); + } else { + ctx.wrappedJSObject = ctx; + ctx.request = request; + setObjectState("context", ctx); + jsonOK = '{"status":"ok","data":[{"confidence":0.9085610,"text":"hello"}]}'; + response.setHeader("Content-Length", String(jsonOK.length), false); + response.setHeader("Content-Type", "application/json", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.write(jsonOK, jsonOK.length); + response.finish(); + } +} diff --git a/dom/media/webspeech/recognition/test/mochitest.ini b/dom/media/webspeech/recognition/test/mochitest.ini new file mode 100644 index 0000000000..6af13b906c --- /dev/null +++ b/dom/media/webspeech/recognition/test/mochitest.ini @@ -0,0 +1,35 @@ +[DEFAULT] +tags=mtg +subsuite = media +support-files = + head.js + hello.ogg + hello.ogg^headers^ + http_requesthandler.sjs + sinoid+hello.ogg + sinoid+hello.ogg^headers^ + silence.ogg + silence.ogg^headers^ +[test_abort.html] +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538363 +[test_audio_capture_error.html] +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538360 +[test_call_start_from_end_handler.html] +tags=capturestream +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538363 +[test_nested_eventloop.html] +skip-if = toolkit == 'android' +[test_online_400_response.html] +[test_online_hangup.html] +[test_online_http.html] +[test_online_http_webkit.html] +[test_online_malformed_result_handling.html] +[test_online_empty_result_handling.html] +[test_preference_enable.html] +[test_recognition_service_error.html] +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538360 +[test_success_without_recognition_service.html] +skip-if = (os == "win" && processor == "aarch64") # aarch64 due to 1538360 +[test_timeout.html] +skip-if = + os == "linux" # Bug 1307991 - low frequency on try pushes diff --git a/dom/media/webspeech/recognition/test/silence.ogg b/dom/media/webspeech/recognition/test/silence.ogg Binary files differnew file mode 100644 index 0000000000..e6da3a5022 --- /dev/null +++ b/dom/media/webspeech/recognition/test/silence.ogg diff --git a/dom/media/webspeech/recognition/test/silence.ogg^headers^ b/dom/media/webspeech/recognition/test/silence.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/webspeech/recognition/test/silence.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/webspeech/recognition/test/sinoid+hello.ogg b/dom/media/webspeech/recognition/test/sinoid+hello.ogg Binary files differnew file mode 100644 index 0000000000..7092e82f30 --- /dev/null +++ b/dom/media/webspeech/recognition/test/sinoid+hello.ogg diff --git a/dom/media/webspeech/recognition/test/sinoid+hello.ogg^headers^ b/dom/media/webspeech/recognition/test/sinoid+hello.ogg^headers^ new file mode 100644 index 0000000000..4030ea1d3d --- /dev/null +++ b/dom/media/webspeech/recognition/test/sinoid+hello.ogg^headers^ @@ -0,0 +1 @@ +Cache-Control: no-store diff --git a/dom/media/webspeech/recognition/test/test_abort.html b/dom/media/webspeech/recognition/test/test_abort.html new file mode 100644 index 0000000000..0f22770cc7 --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_abort.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- Call abort from inside handlers</title> + <script 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(); + + // Abort inside event handlers, should't get a + // result after that + + var nextEventIdx = 0; + var eventsToAbortOn = [ + "start", + "audiostart", + "speechstart", + "speechend", + "audioend" + ]; + + function doNextTest() { + var nextEvent = eventsToAbortOn[nextEventIdx]; + var expectedEvents = { + "start": null, + "audiostart": null, + "audioend": null, + "end": null + }; + + if (nextEventIdx >= eventsToAbortOn.indexOf("speechstart")) { + expectedEvents.speechstart = null; + } + + if (nextEventIdx >= eventsToAbortOn.indexOf("speechend")) { + expectedEvents.speechend = null; + } + + info("Aborting on " + nextEvent); + expectedEvents[nextEvent] = function(evt, sr) { + sr.abort(); + }; + + nextEventIdx++; + + performTest({ + eventsToRequest: [], + expectedEvents, + doneFunc: (nextEventIdx < eventsToAbortOn.length) ? doNextTest : SimpleTest.finish, + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 100000]] + }); + } + + doNextTest(); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_audio_capture_error.html b/dom/media/webspeech/recognition/test/test_audio_capture_error.html new file mode 100644 index 0000000000..0c054dbf0b --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_audio_capture_error.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- Behavior on audio error</title> + <script 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(); + + performTest({ + eventsToRequest: ['EVENT_AUDIO_ERROR'], + expectedEvents: { + 'start': null, + 'audiostart': null, + 'speechstart': null, + 'speechend': null, + 'audioend': null, + 'error': buildErrorCallback(errorCodes.AUDIO_CAPTURE), + 'end': null + }, + doneFunc: SimpleTest.finish, + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 100000]] + }); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_call_start_from_end_handler.html b/dom/media/webspeech/recognition/test/test_call_start_from_end_handler.html new file mode 100644 index 0000000000..895648ad9e --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_call_start_from_end_handler.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- Restart recognition from end handler</title> + <script 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(); + + function createAudioStream() { + var audioTag = document.createElement("audio"); + audioTag.src = DEFAULT_AUDIO_SAMPLE_FILE; + + var stream = audioTag.mozCaptureStreamUntilEnded(); + audioTag.play(); + + return stream; + } + + var done = false; + function endHandler(evt, sr) { + if (done) { + SimpleTest.finish(); + return; + } + + try { + var stream = createAudioStream(); + sr.start(stream); // shouldn't fail + } catch (err) { + ok(false, "Failed to start() from end() callback"); + } + + // calling start() may cause some callbacks to fire, but we're + // no longer interested in them, except for onend, which is where + // we'll conclude the test. + sr.onstart = null; + sr.onaudiostart = null; + sr.onspeechstart = null; + sr.onspeechend = null; + sr.onaudioend = null; + sr.onresult = null; + + // FIXME(ggp) the state transition caused by start() is async, + // but abort() is sync (see bug 1055093). until we normalize + // state transitions, we need to setTimeout here to make sure + // abort() finds the speech recognition object in the correct + // state (namely, STATE_STARTING). + setTimeout(function() { + sr.abort(); + done = true; + }); + + info("Successfully start() from end() callback"); + } + + function expectExceptionHandler(evt, sr) { + try { + sr.start(createAudioStream()); + } catch (err) { + is(err.name, "InvalidStateError"); + return; + } + + ok(false, "Calling start() didn't raise InvalidStateError"); + } + + performTest({ + eventsToRequest: [ + 'EVENT_RECOGNITIONSERVICE_FINAL_RESULT' + ], + expectedEvents: { + 'start': expectExceptionHandler, + 'audiostart': expectExceptionHandler, + 'speechstart': expectExceptionHandler, + 'speechend': expectExceptionHandler, + 'audioend': expectExceptionHandler, + 'result': buildResultCallback("Mock final result"), + 'end': endHandler, + }, + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_nested_eventloop.html b/dom/media/webspeech/recognition/test/test_nested_eventloop.html new file mode 100644 index 0000000000..4924766b44 --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_nested_eventloop.html @@ -0,0 +1,82 @@ +<!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 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(); + + /* + * SpecialPowers.spinEventLoop 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. + */ + function abortAndSpinEventLoop(evt, sr) { + sr.abort(); + SpecialPowers.spinEventLoop(window); + } + function doneFunc() { + // Trigger gc now and wait some time to make sure this test gets the blame + // for any assertions caused by spinning the event loop. + // + // NB - The assertions should be gone, but this looks too scary to touch + // during batch cleanup. + 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: [], + expectedEvents: { + "audiostart": abortAndSpinEventLoop, + "audioend": abortAndSpinEventLoop, + "end": null + }, + doneFunc, + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_online_400_response.html b/dom/media/webspeech/recognition/test/test_online_400_response.html new file mode 100644 index 0000000000..1a7d0ed452 --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_online_400_response.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248897 +The intent of this file is to test the speech recognition service behavior +whenever the server returns a 400 error +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1248897 -- Online speech service</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=1248897">Mozilla Bug 1248897</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + performTest({ + eventsToRequest: [], + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "end": null, + 'error': buildErrorCallback(errorCodes.NETWORK), + "speechstart": null, + "speechend": null + }, + doneFunc: SimpleTest.finish, + prefs: [["media.webspeech.recognition.enable", true], + ["media.webspeech.recognition.force_enable", true], + ["media.webspeech.service.endpoint", + "http://mochi.test:8888/tests/dom/media/webspeech/recognition/test/http_requesthandler.sjs?return400=1"], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_online_empty_result_handling.html b/dom/media/webspeech/recognition/test/test_online_empty_result_handling.html new file mode 100644 index 0000000000..46f1e7e0cb --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_online_empty_result_handling.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248897 +The intent of this file is to test the speech recognition service behavior +whenever the server returns a valid json object, but without any transcription +results on it, for example: `{"status":"ok","data":[]}` +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1248897 -- Online speech service</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=1248897">Mozilla Bug 1248897</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + performTest({ + eventsToRequest: [], + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "end": null, + 'error': buildErrorCallback(errorCodes.NETWORK), + "speechstart": null, + "speechend": null + }, + doneFunc: SimpleTest.finish, + prefs: [["media.webspeech.recognition.enable", true], + ["media.webspeech.recognition.force_enable", true], + ["media.webspeech.service.endpoint", + "http://mochi.test:8888/tests/dom/media/webspeech/recognition/test/http_requesthandler.sjs?emptyresult=1"], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_online_hangup.html b/dom/media/webspeech/recognition/test/test_online_hangup.html new file mode 100644 index 0000000000..4a46f80f8f --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_online_hangup.html @@ -0,0 +1,47 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248897 +The intent of this file is to test the speech recognition service behavior +whenever the server hangups the connection without sending any response +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1248897 -- Online speech service</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=1248897">Mozilla Bug 1248897</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + performTest({ + eventsToRequest: [], + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "end": null, + 'error': buildErrorCallback(errorCodes.NETWORK), + "speechstart": null, + "speechend": null + }, + doneFunc: SimpleTest.finish, + prefs: [["media.webspeech.recognition.enable", true], + ["media.webspeech.recognition.force_enable", true], + ["media.webspeech.service.endpoint", + "http://mochi.test:8888/tests/dom/media/webspeech/recognition/test/http_requesthandler.sjs?hangup=1"], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_online_http.html b/dom/media/webspeech/recognition/test/test_online_http.html new file mode 100644 index 0000000000..43be7a656a --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_online_http.html @@ -0,0 +1,89 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248897 +The intent of this file is to test a successfull speech recognition request and +that audio is being properly encoded +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1248897 -- Online speech service</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=1248897">Mozilla Bug 1248897</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + async function validateRawAudio(buffer) { + const ac = new AudioContext(); + const decodedData = await ac.decodeAudioData(buffer); + const source = ac.createBufferSource(); + source.buffer = decodedData; + source.loop = true; + const analyser = ac.createAnalyser(); + analyser.smoothingTimeConstant = 0.2; + analyser.fftSize = 1024; + source.connect(analyser); + const binIndexForFrequency = frequency => + 1 + Math.round(frequency * analyser.fftSize / ac.sampleRate); + source.start(); + const data = new Uint8Array(analyser.frequencyBinCount); + const start = performance.now(); + while (true) { + if (performance.now() - start > 10000) { + return false; + break; + } + analyser.getByteFrequencyData(data); + if (data[binIndexForFrequency(200)] < 50 && + data[binIndexForFrequency(440)] > 180 && + data[binIndexForFrequency(1000)] < 50) { + return true; + break; + } + await new Promise(r => requestAnimationFrame(r)); + } + } + + async function verifyEncodedAudio(requestUrl) { + try { + const response = await fetch(requestUrl); + const buffer = await response.arrayBuffer(); + ok(await validateRawAudio(buffer), "Audio encoding is valid"); + } catch(e) { + ok(false, e); + } finally { + SimpleTest.finish(); + } + } + + performTest({ + eventsToRequest: {}, + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "end": null, + "result": () => verifyEncodedAudio("http_requesthandler.sjs?save"), + "speechstart": null, + "speechend": null + }, + audioSampleFile: "sinoid+hello.ogg", + prefs: [["media.webspeech.recognition.enable", true], + ["media.webspeech.recognition.force_enable", true], + ["media.webspeech.service.endpoint", + "http://mochi.test:8888/tests/dom/media/webspeech/recognition/test/http_requesthandler.sjs"], + ["media.webspeech.recognition.timeout", 100000]] + }); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_online_http_webkit.html b/dom/media/webspeech/recognition/test/test_online_http_webkit.html new file mode 100644 index 0000000000..7f6c7e6d7d --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_online_http_webkit.html @@ -0,0 +1,90 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248897 +The intent of this file is to test a successfull speech recognition request and +that audio is being properly encoded +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1248897 -- Online speech service</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=1248897">Mozilla Bug 1248897</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + async function validateRawAudio(buffer) { + const ac = new AudioContext(); + const decodedData = await ac.decodeAudioData(buffer); + const source = ac.createBufferSource(); + source.buffer = decodedData; + source.loop = true; + const analyser = ac.createAnalyser(); + analyser.smoothingTimeConstant = 0.2; + analyser.fftSize = 1024; + source.connect(analyser); + const binIndexForFrequency = frequency => + 1 + Math.round(frequency * analyser.fftSize / ac.sampleRate); + source.start(); + const data = new Uint8Array(analyser.frequencyBinCount); + const start = performance.now(); + while (true) { + if (performance.now() - start > 10000) { + return false; + break; + } + analyser.getByteFrequencyData(data); + if (data[binIndexForFrequency(200)] < 50 && + data[binIndexForFrequency(440)] > 180 && + data[binIndexForFrequency(1000)] < 50) { + return true; + break; + } + await new Promise(r => requestAnimationFrame(r)); + } + } + + async function verifyEncodedAudio(requestUrl) { + try { + const response = await fetch(requestUrl); + const buffer = await response.arrayBuffer(); + ok(await validateRawAudio(buffer), "Audio encoding is valid"); + } catch(e) { + ok(false, e); + } finally { + SimpleTest.finish(); + } + } + + performTest({ + eventsToRequest: {}, + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "end": null, + "result": () => verifyEncodedAudio("http_requesthandler.sjs?save"), + "speechstart": null, + "speechend": null + }, + audioSampleFile: "sinoid+hello.ogg", + prefs: [["media.webspeech.recognition.enable", true], + ["media.webspeech.recognition.force_enable", true], + ["media.webspeech.service.endpoint", + "http://mochi.test:8888/tests/dom/media/webspeech/recognition/test/http_requesthandler.sjs"], + ["media.webspeech.recognition.timeout", 100000]], + webkit: true + }); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_online_malformed_result_handling.html b/dom/media/webspeech/recognition/test/test_online_malformed_result_handling.html new file mode 100644 index 0000000000..b071a46ea3 --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_online_malformed_result_handling.html @@ -0,0 +1,48 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1248897 +The intent of this file is to test the speech recognition service behavior +whenever the server returns an invalid/corrupted json object, for example: +`{"status":"ok","dat` +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1248897 -- Online speech service</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=1248897">Mozilla Bug 1248897</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +<script type="text/javascript"> + SimpleTest.waitForExplicitFinish(); + + performTest({ + eventsToRequest: [], + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "end": null, + 'error': buildErrorCallback(errorCodes.NETWORK), + "speechstart": null, + "speechend": null + }, + doneFunc: SimpleTest.finish, + prefs: [["media.webspeech.recognition.enable", true], + ["media.webspeech.recognition.force_enable", true], + ["media.webspeech.service.endpoint", + "http://mochi.test:8888/tests/dom/media/webspeech/recognition/test/http_requesthandler.sjs?malformedresult=1"], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_preference_enable.html b/dom/media/webspeech/recognition/test/test_preference_enable.html new file mode 100644 index 0000000000..2b56f82e2c --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_preference_enable.html @@ -0,0 +1,43 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- No objects should be visible with preference disabled</title> + <script src="/tests/SimpleTest/SimpleTest.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=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(); + + SpecialPowers.pushPrefEnv({ + set: [["media.webspeech.recognition.enable", false]] + }, function() { + var objects = [ + "SpeechRecognition", + "SpeechGrammar", + "SpeechRecognitionResult", + "SpeechRecognitionResultList", + "SpeechRecognitionAlternative" + ]; + + for (var i = 0; i < objects.length; i++) { + is(window[objects[i]], undefined, + objects[i] + " should be undefined with pref off"); + } + + SimpleTest.finish(); + }); +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_recognition_service_error.html b/dom/media/webspeech/recognition/test/test_recognition_service_error.html new file mode 100644 index 0000000000..e8e59e2afc --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_recognition_service_error.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- Behavior on recognition service error</title> + <script 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(); + + performTest({ + eventsToRequest: [ + 'EVENT_RECOGNITIONSERVICE_ERROR' + ], + expectedEvents: { + 'start': null, + 'audiostart': null, + 'speechstart': null, + 'speechend': null, + 'audioend': null, + 'error': buildErrorCallback(errorCodes.NETWORK), + 'end': null + }, + doneFunc: SimpleTest.finish, + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_success_without_recognition_service.html b/dom/media/webspeech/recognition/test/test_success_without_recognition_service.html new file mode 100644 index 0000000000..38748ed5cb --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_success_without_recognition_service.html @@ -0,0 +1,45 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- Success with fake recognition service</title> + <script 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(); + + performTest({ + eventsToRequest: [ + 'EVENT_RECOGNITIONSERVICE_FINAL_RESULT' + ], + expectedEvents: { + 'start': null, + 'audiostart': null, + 'speechstart': null, + 'speechend': null, + 'audioend': null, + 'result': buildResultCallback("Mock final result"), + 'end': null + }, + doneFunc:SimpleTest.finish, + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 100000]] + }); + +</script> +</pre> +</body> +</html> diff --git a/dom/media/webspeech/recognition/test/test_timeout.html b/dom/media/webspeech/recognition/test/test_timeout.html new file mode 100644 index 0000000000..8334c9e779 --- /dev/null +++ b/dom/media/webspeech/recognition/test/test_timeout.html @@ -0,0 +1,42 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=650295 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 650295 -- Timeout for user speech</title> + <script 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(); + + performTest({ + eventsToRequest: [], + expectedEvents: { + "start": null, + "audiostart": null, + "audioend": null, + "error": buildErrorCallback(errorCodes.NO_SPEECH), + "end": null + }, + doneFunc: SimpleTest.finish, + audioSampleFile: "silence.ogg", + prefs: [["media.webspeech.test.fake_fsm_events", true], + ["media.webspeech.test.fake_recognition_service", true], + ["media.webspeech.recognition.timeout", 1000]] + }); + +</script> +</pre> +</body> +</html> |