summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/cros/cros_mock.cc12
-rw-r--r--chrome/browser/extensions/extension_tts_api.cc196
-rw-r--r--chrome/browser/extensions/extension_tts_api.h96
-rw-r--r--chrome/browser/extensions/extension_tts_api_linux.cc2
-rw-r--r--chrome/browser/extensions/extension_tts_api_util.h1
-rw-r--r--chrome/browser/extensions/extension_tts_apitest.cc158
-rw-r--r--chrome/chrome_tests.gypi4
-rw-r--r--chrome/common/extensions/api/extension_api.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/chromeos/test.js34
-rw-r--r--chrome/test/data/extensions/api_test/tts/enqueue/manifest.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/enqueue/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts/enqueue/test.js25
-rw-r--r--chrome/test/data/extensions/api_test/tts/interrupt/manifest.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/interrupt/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts/interrupt/test.js25
-rw-r--r--chrome/test/data/extensions/api_test/tts/queue_interrupt/manifest.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/queue_interrupt/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js29
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_error/manifest.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_error/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_error/test.js29
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_once/manifest.json7
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_once/test.html1
-rw-r--r--chrome/test/data/extensions/api_test/tts/speak_once/test.js15
24 files changed, 607 insertions, 66 deletions
diff --git a/chrome/browser/chromeos/cros/cros_mock.cc b/chrome/browser/chromeos/cros/cros_mock.cc
index ab33dc0..6e7844f 100644
--- a/chrome/browser/chromeos/cros/cros_mock.cc
+++ b/chrome/browser/chromeos/cros/cros_mock.cc
@@ -29,6 +29,7 @@
namespace chromeos {
using ::testing::AnyNumber;
+using ::testing::InSequence;
using ::testing::InvokeWithoutArgs;
using ::testing::Return;
using ::testing::ReturnRef;
@@ -378,17 +379,20 @@ void CrosMock::SetPowerLibraryExpectations() {
}
void CrosMock::SetSpeechSynthesisLibraryExpectations() {
+ InSequence s;
+ EXPECT_CALL(*mock_speech_synthesis_library_, StopSpeaking())
+ .WillOnce(Return(true))
+ .RetiresOnSaturation();
EXPECT_CALL(*mock_speech_synthesis_library_, Speak(_))
- .Times(1)
.WillOnce(Return(true))
.RetiresOnSaturation();
EXPECT_CALL(*mock_speech_synthesis_library_, StopSpeaking())
- .Times(1)
.WillOnce(Return(true))
.RetiresOnSaturation();
- EXPECT_CALL(*mock_speech_synthesis_library_, IsSpeaking())
- .Times(4)
+ EXPECT_CALL(*mock_speech_synthesis_library_, Speak(_))
.WillOnce(Return(true))
+ .RetiresOnSaturation();
+ EXPECT_CALL(*mock_speech_synthesis_library_, IsSpeaking())
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(false))
diff --git a/chrome/browser/extensions/extension_tts_api.cc b/chrome/browser/extensions/extension_tts_api.cc
index b022ec0..1654b071 100644
--- a/chrome/browser/extensions/extension_tts_api.cc
+++ b/chrome/browser/extensions/extension_tts_api.cc
@@ -7,68 +7,206 @@
#include <string>
#include "base/float_util.h"
+#include "base/message_loop.h"
#include "base/values.h"
namespace util = extension_tts_api_util;
namespace {
- const char kCrosLibraryNotLoadedError[] =
- "Cros shared library not loaded.";
+const char kCrosLibraryNotLoadedError[] =
+ "Cros shared library not loaded.";
+const int kSpeechCheckDelayIntervalMs = 100;
};
+// static
+ExtensionTtsController* ExtensionTtsController::GetInstance() {
+ return Singleton<ExtensionTtsController>::get();
+}
+
+ExtensionTtsController::ExtensionTtsController()
+ : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
+ current_utterance_(NULL),
+ platform_impl_(NULL) {
+}
+
+void ExtensionTtsController::SpeakOrEnqueue(
+ Utterance* utterance, bool can_enqueue) {
+ if (IsSpeaking() && can_enqueue) {
+ utterance_queue_.push(utterance);
+ } else {
+ Stop();
+ SpeakNow(utterance);
+ }
+}
+
+void ExtensionTtsController::SpeakNow(Utterance* utterance) {
+ GetPlatformImpl()->clear_error();
+ bool success = GetPlatformImpl()->Speak(
+ utterance->text,
+ utterance->language,
+ utterance->gender,
+ utterance->rate,
+ utterance->pitch,
+ utterance->volume);
+ if (!success) {
+ utterance->error = GetPlatformImpl()->error();
+ utterance->failure_task->Run();
+ delete utterance->success_task;
+ delete utterance;
+ return;
+ }
+ current_utterance_ = utterance;
+
+ // Post a task to check if this utterance has completed after a delay.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &ExtensionTtsController::CheckSpeechStatus),
+ kSpeechCheckDelayIntervalMs);
+}
+
+void ExtensionTtsController::Stop() {
+ GetPlatformImpl()->clear_error();
+ GetPlatformImpl()->StopSpeaking();
+
+ FinishCurrentUtterance();
+ ClearUtteranceQueue();
+}
+
+bool ExtensionTtsController::IsSpeaking() const {
+ return current_utterance_ != NULL;
+}
+
+void ExtensionTtsController::FinishCurrentUtterance() {
+ if (current_utterance_) {
+ current_utterance_->success_task->Run();
+ delete current_utterance_->failure_task;
+ delete current_utterance_;
+ current_utterance_ = NULL;
+ }
+}
+
+void ExtensionTtsController::ClearUtteranceQueue() {
+ while (!utterance_queue_.empty()) {
+ Utterance* utterance = utterance_queue_.front();
+ utterance_queue_.pop();
+ utterance->success_task->Run();
+ delete utterance->failure_task;
+ delete utterance;
+ }
+}
+
+void ExtensionTtsController::CheckSpeechStatus() {
+ if (!current_utterance_)
+ return;
+
+ if (GetPlatformImpl()->IsSpeaking() == false) {
+ FinishCurrentUtterance();
+
+ // Start speaking the next utterance in the queue. Keep trying in case
+ // one fails but there are still more in the queue to try.
+ while (!utterance_queue_.empty() && !current_utterance_) {
+ Utterance* utterance = utterance_queue_.front();
+ utterance_queue_.pop();
+ SpeakNow(utterance);
+ }
+ }
+
+ // If we're still speaking something (either the prevoius utterance or
+ // a new utterance), keep calling this method after another delay.
+ if (current_utterance_) {
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE, method_factory_.NewRunnableMethod(
+ &ExtensionTtsController::CheckSpeechStatus),
+ kSpeechCheckDelayIntervalMs);
+ }
+}
+
+void ExtensionTtsController::SetPlatformImpl(
+ ExtensionTtsPlatformImpl* platform_impl) {
+ platform_impl_ = platform_impl;
+}
+
+ExtensionTtsPlatformImpl* ExtensionTtsController::GetPlatformImpl() {
+ if (!platform_impl_)
+ platform_impl_ = ExtensionTtsPlatformImpl::GetInstance();
+ return platform_impl_;
+}
+
+//
+// Extension API functions
+//
+
bool ExtensionTtsSpeakFunction::RunImpl() {
- std::string utterance;
- std::string language;
- std::string gender;
- double rate = -1.0;
- double pitch = -1.0;
- double volume = -1.0;
+ utterance_ = new ExtensionTtsController::Utterance();
+ bool can_enqueue = false;
DictionaryValue* speak_options = NULL;
- EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &utterance));
+ EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &utterance_->text));
if (args_->GetDictionary(1, &speak_options)) {
if (speak_options->HasKey(util::kLanguageNameKey)) {
- speak_options->GetString(util::kLanguageNameKey, &language);
+ speak_options->GetString(util::kLanguageNameKey, &utterance_->language);
}
if (speak_options->HasKey(util::kGenderKey)) {
- speak_options->GetString(util::kGenderKey, &gender);
+ speak_options->GetString(util::kGenderKey, &utterance_->gender);
+ }
+
+ if (speak_options->HasKey(util::kEnqueueKey)) {
+ speak_options->GetBoolean(util::kEnqueueKey, &can_enqueue);
}
- if (util::ReadNumberByKey(speak_options, util::kRateKey, &rate)) {
- if (!base::IsFinite(rate) || rate < 0.0 || rate > 1.0) {
- rate = -1.0;
+ if (util::ReadNumberByKey(
+ speak_options, util::kRateKey, &utterance_->rate)) {
+ if (!base::IsFinite(utterance_->rate) ||
+ utterance_->rate < 0.0 ||
+ utterance_->rate > 1.0) {
+ utterance_->rate = -1.0;
}
}
- if (util::ReadNumberByKey(speak_options, util::kPitchKey, &pitch)) {
- if (!base::IsFinite(pitch) || pitch < 0.0 || pitch > 1.0) {
- pitch = -1.0;
+ if (util::ReadNumberByKey(
+ speak_options, util::kPitchKey, &utterance_->pitch)) {
+ if (!base::IsFinite(utterance_->pitch) ||
+ utterance_->pitch < 0.0 ||
+ utterance_->pitch > 1.0) {
+ utterance_->pitch = -1.0;
}
}
- if (util::ReadNumberByKey(speak_options, util::kVolumeKey, &volume)) {
- if (!base::IsFinite(volume) || volume < 0.0 || volume > 1.0) {
- volume = -1.0;
+ if (util::ReadNumberByKey(
+ speak_options, util::kVolumeKey, &utterance_->volume)) {
+ if (!base::IsFinite(utterance_->volume) ||
+ utterance_->volume < 0.0 ||
+ utterance_->volume > 1.0) {
+ utterance_->volume = -1.0;
}
}
}
- ExtensionTtsPlatformImpl* impl = ExtensionTtsPlatformImpl::GetInstance();
- impl->clear_error();
- return impl->Speak(utterance, language, gender, rate, pitch, volume);
+ AddRef(); // Balanced in SpeechFinished().
+ utterance_->success_task = NewRunnableMethod(
+ this, &ExtensionTtsSpeakFunction::SpeechFinished, true);
+ utterance_->failure_task = NewRunnableMethod(
+ this, &ExtensionTtsSpeakFunction::SpeechFinished, false);
+ ExtensionTtsController::GetInstance()->SpeakOrEnqueue(
+ utterance_, can_enqueue);
+ return true;
+}
+
+void ExtensionTtsSpeakFunction::SpeechFinished(bool success) {
+ error_ = utterance_->error;
+ SendResponse(success);
+ Release(); // Balanced in Speak().
}
bool ExtensionTtsStopSpeakingFunction::RunImpl() {
- ExtensionTtsPlatformImpl* impl = ExtensionTtsPlatformImpl::GetInstance();
- impl->clear_error();
- return impl->StopSpeaking();
+ ExtensionTtsController::GetInstance()->Stop();
+ return true;
}
bool ExtensionTtsIsSpeakingFunction::RunImpl() {
- ExtensionTtsPlatformImpl* impl = ExtensionTtsPlatformImpl::GetInstance();
- impl->clear_error();
- result_.reset(Value::CreateBooleanValue(impl->IsSpeaking()));
+ result_.reset(Value::CreateBooleanValue(
+ ExtensionTtsController::GetInstance()->IsSpeaking()));
return true;
}
diff --git a/chrome/browser/extensions/extension_tts_api.h b/chrome/browser/extensions/extension_tts_api.h
index 73fc887..0eedcc2 100644
--- a/chrome/browser/extensions/extension_tts_api.h
+++ b/chrome/browser/extensions/extension_tts_api.h
@@ -5,6 +5,10 @@
#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_
#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TTS_API_H_
+#include <queue>
+
+#include "base/singleton.h"
+#include "base/task.h"
#include "chrome/browser/extensions/extension_function.h"
#include "chrome/browser/extensions/extension_tts_api_util.h"
@@ -17,6 +21,11 @@ class ExtensionTtsPlatformImpl {
// and return true on success. Utterance will always be nonempty.
// If the user does not specify the other values, language and gender
// will be empty strings, and rate, pitch, and volume will be -1.0.
+ //
+ // The ExtensionTtsController will only try to speak one utterance at
+ // a time. If it wants to intterupt speech, it will always call Stop
+ // before speaking again, otherwise it will wait until IsSpeaking
+ // returns false before calling Speak again.
virtual bool Speak(
const std::string& utterance,
const std::string& language,
@@ -44,23 +53,108 @@ class ExtensionTtsPlatformImpl {
DISALLOW_COPY_AND_ASSIGN(ExtensionTtsPlatformImpl);
};
+// Singleton class that manages text-to-speech.
+class ExtensionTtsController {
+ public:
+ // Get the single instance of this class.
+ static ExtensionTtsController* GetInstance();
+
+ struct Utterance {
+ Utterance()
+ : rate(-1.0),
+ pitch(-1.0),
+ volume(-1.0),
+ success_task(NULL),
+ failure_task(NULL) {
+ }
+
+ std::string text;
+ std::string language;
+ std::string gender;
+ double rate;
+ double pitch;
+ double volume;
+
+ Task* success_task;
+ Task* failure_task;
+
+ std::string error;
+ };
+
+ // Returns true if we're currently speaking an utterance.
+ bool IsSpeaking() const;
+
+ // Speak the given utterance. If |can_enqueue| is true and another
+ // utterance is in progress, adds it to the end of the queue. Otherwise,
+ // interrupts any current utterance and speaks this one immediately.
+ void SpeakOrEnqueue(Utterance* utterance, bool can_enqueue);
+
+ // Stop all utterances and flush the queue.
+ void Stop();
+
+ // For unit testing.
+ void SetPlatformImpl(ExtensionTtsPlatformImpl* platform_impl);
+
+ private:
+ ExtensionTtsController();
+ virtual ~ExtensionTtsController() {}
+
+ // Get the platform TTS implementation (or injected mock).
+ ExtensionTtsPlatformImpl* GetPlatformImpl();
+
+ // Start speaking the given utterance. Will either take ownership of
+ // |utterance| or delete it if there's an error.
+ void SpeakNow(Utterance* utterance);
+
+ // Called periodically when speech is ongoing. Checks to see if the
+ // underlying platform speech system has finished the current utterance,
+ // and if so finishes it and pops the next utterance off the queue.
+ void CheckSpeechStatus();
+
+ // Clear the utterance queue.
+ void ClearUtteranceQueue();
+
+ // Finalize and delete the current utterance.
+ void FinishCurrentUtterance();
+
+ ScopedRunnableMethodFactory<ExtensionTtsController> method_factory_;
+ friend struct DefaultSingletonTraits<ExtensionTtsController>;
+
+ // The current utterance being spoken.
+ Utterance* current_utterance_;
+
+ // A queue of utterances to speak after the current one finishes.
+ std::queue<Utterance*> utterance_queue_;
+
+ // A pointer to the platform implementation of text-to-speech, for
+ // dependency injection.
+ ExtensionTtsPlatformImpl* platform_impl_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionTtsController);
+};
+
//
// Extension API function definitions
//
-class ExtensionTtsSpeakFunction : public SyncExtensionFunction {
+class ExtensionTtsSpeakFunction : public AsyncExtensionFunction {
+ private:
~ExtensionTtsSpeakFunction() {}
virtual bool RunImpl();
+ void SpeechFinished(bool success);
+ ExtensionTtsController::Utterance* utterance_;
DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.speak")
};
class ExtensionTtsStopSpeakingFunction : public SyncExtensionFunction {
+ private:
~ExtensionTtsStopSpeakingFunction() {}
virtual bool RunImpl();
DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.stop")
};
class ExtensionTtsIsSpeakingFunction : public SyncExtensionFunction {
+ private:
~ExtensionTtsIsSpeakingFunction() {}
virtual bool RunImpl();
DECLARE_EXTENSION_FUNCTION_NAME("experimental.tts.isSpeaking")
diff --git a/chrome/browser/extensions/extension_tts_api_linux.cc b/chrome/browser/extensions/extension_tts_api_linux.cc
index 8ee228b..7b89799 100644
--- a/chrome/browser/extensions/extension_tts_api_linux.cc
+++ b/chrome/browser/extensions/extension_tts_api_linux.cc
@@ -38,7 +38,7 @@ class ExtensionTtsPlatformImplLinux : public ExtensionTtsPlatformImpl {
// Get the single instance of this class.
static ExtensionTtsPlatformImplLinux* GetInstance() {
- return ExtensionTtsPlatformImplLinux::GetInstance();
+ return Singleton<ExtensionTtsPlatformImplLinux>::get();
}
private:
diff --git a/chrome/browser/extensions/extension_tts_api_util.h b/chrome/browser/extensions/extension_tts_api_util.h
index 4b41871..cf0d702 100644
--- a/chrome/browser/extensions/extension_tts_api_util.h
+++ b/chrome/browser/extensions/extension_tts_api_util.h
@@ -17,6 +17,7 @@ const char kGenderKey[] = "gender";
const char kRateKey[] = "rate";
const char kPitchKey[] = "pitch";
const char kVolumeKey[] = "volume";
+const char kEnqueueKey[] = "enqueue";
const char kEqualStr[] = "=";
const char kDelimiter[] = ";";
diff --git a/chrome/browser/extensions/extension_tts_apitest.cc b/chrome/browser/extensions/extension_tts_apitest.cc
index dcdfb9d5..6f37404 100644
--- a/chrome/browser/extensions/extension_tts_apitest.cc
+++ b/chrome/browser/extensions/extension_tts_apitest.cc
@@ -2,19 +2,164 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/command_line.h"
-#include "chrome/browser/chromeos/cros/cros_mock.h"
#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_tts_api.h"
#include "chrome/common/chrome_switches.h"
#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+// Needed for CreateFunctor.
+#define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING
+#include "testing/gmock_mutant.h"
-// This extension API is currently only supported on Chrome OS.
#if defined(OS_CHROMEOS)
-#define MAYBE_Tts Tts
-#else
-#define MAYBE_Tts DISABLED_Tts
+#include "chrome/browser/chromeos/cros/cros_mock.h"
#endif
-IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_Tts) {
+using ::testing::CreateFunctor;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::InvokeWithoutArgs;
+using ::testing::Return;
+using ::testing::StrictMock;
+using ::testing::_;
+
+class MockExtensionTtsPlatformImpl : public ExtensionTtsPlatformImpl {
+ public:
+ MOCK_METHOD6(Speak,
+ bool(const std::string& utterance,
+ const std::string& language,
+ const std::string& gender,
+ double rate,
+ double pitch,
+ double volume));
+ MOCK_METHOD0(StopSpeaking, bool(void));
+ MOCK_METHOD0(IsSpeaking, bool(void));
+
+ void SetErrorToEpicFail() {
+ set_error("epic fail");
+ }
+};
+
+class TtsApiTest : public ExtensionApiTest {
+ public:
+ virtual void SetUpCommandLine(CommandLine* command_line) {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis);
+ }
+
+ virtual void SetUpInProcessBrowserTestFixture() {
+ ExtensionApiTest::SetUpInProcessBrowserTestFixture();
+ ExtensionTtsController::GetInstance()->SetPlatformImpl(
+ &mock_platform_impl_);
+ }
+
+ protected:
+ StrictMock<MockExtensionTtsPlatformImpl> mock_platform_impl_;
+};
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakFinishesImmediately) {
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(false));
+ ASSERT_TRUE(RunExtensionTest("tts/speak_once")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakKeepsSpeakingTwice) {
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(true))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+ ASSERT_TRUE(RunExtensionTest("tts/speak_once")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakInterrupt) {
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak("text 1", _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak("text 2", _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(true))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+ ASSERT_TRUE(RunExtensionTest("tts/interrupt")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakQueueInterrupt) {
+ // In this test, two utterances are queued, and then a third
+ // interrupts. Speak() never gets called on the second utterance.
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak("text 1", _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak("text 3", _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(true))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+ ASSERT_TRUE(RunExtensionTest("tts/queue_interrupt")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakEnqueue) {
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak("text 1", _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(true))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak("text 2", _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(true))
+ .WillOnce(Return(true))
+ .WillOnce(Return(false));
+ ASSERT_TRUE(RunExtensionTest("tts/enqueue")) << message_;
+}
+
+IN_PROC_BROWSER_TEST_F(TtsApiTest, PlatformSpeakError) {
+ InSequence s;
+ EXPECT_CALL(mock_platform_impl_, StopSpeaking())
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(false));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
+ .WillOnce(DoAll(
+ InvokeWithoutArgs(
+ CreateFunctor(&mock_platform_impl_,
+ &MockExtensionTtsPlatformImpl::SetErrorToEpicFail)),
+ Return(false)));
+ EXPECT_CALL(mock_platform_impl_, Speak(_, _, _, _, _, _))
+ .WillOnce(Return(true));
+ EXPECT_CALL(mock_platform_impl_, IsSpeaking())
+ .WillOnce(Return(false));
+ ASSERT_TRUE(RunExtensionTest("tts/speak_error")) << message_;
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, TtsChromeOs) {
CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableExperimentalExtensionApis);
@@ -24,3 +169,4 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, MAYBE_Tts) {
ASSERT_TRUE(RunExtensionTest("tts/chromeos")) << message_;
}
+#endif
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 61bf901..7bf15f7 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2031,10 +2031,6 @@
['chromeos==0', {
'sources/': [
['exclude', '^browser/chromeos'],
- # Currently TTS extension API is supported only for chromeos.
- # Remove the following exclude when support for other platforms
- # is added.
- ['exclude', 'browser/extensions/extension_tts_apitest.cc'],
],
'sources!': [
'browser/dom_ui/mediaplayer_browsertest.cc',
diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json
index 3fdd1ce..5936a3e 100644
--- a/chrome/common/extensions/api/extension_api.json
+++ b/chrome/common/extensions/api/extension_api.json
@@ -503,7 +503,12 @@
"optional": true,
"description": "The speak options. This parameter is currently ignored.",
"properties": {
- "languageName": {
+ "enqueue": {
+ "type": "boolean",
+ "optional": true,
+ "description": "If true, enqueues this utterance if TTS is already in progress. If false (the default), interrupts any current speech and flushes the speech queue before speaking this new utterance."
+ },
+ "languageName": {
"type": "string",
"optional": true,
"description": "The language name for synthesis specified in the form <language>-<locale>, e.g. en-US, en-GB, fr-CA, zh-CN, etc."
diff --git a/chrome/test/data/extensions/api_test/tts/chromeos/test.js b/chrome/test/data/extensions/api_test/tts/chromeos/test.js
index 0cf73bc0..9db8d96 100644
--- a/chrome/test/data/extensions/api_test/tts/chromeos/test.js
+++ b/chrome/test/data/extensions/api_test/tts/chromeos/test.js
@@ -3,29 +3,23 @@
// found in the LICENSE file.
// TTS api test for Chrome on ChromeOS.
-// browser_tests.exe --gtest_filter=ExtensionApiTest.TtsOnChromeOs
+// browser_tests.exe --gtest_filter="ExtensionApiTest.TtsChromeOs"
chrome.test.runTests([
- function testSpeak() {
- chrome.experimental.tts.speak('hello world', {}, function() {
+ function testChromeOsSpeech() {
+ var callbacks = 0;
+ chrome.experimental.tts.speak('text 1', {}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('text 2', {}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ if (callbacks == 2) {
chrome.test.succeed();
- });
- },
-
- function testStop() {
- chrome.experimental.tts.stop();
- chrome.test.succeed();
- },
-
- function testIsSpeaking() {
- for (var i = 0; i < 3; i++) {
- chrome.experimental.tts.isSpeaking(function(speaking) {
- chrome.test.assertTrue(speaking);
- });
- }
- chrome.experimental.tts.isSpeaking(function(speaking) {
- chrome.test.assertFalse(speaking);
- chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
});
}
diff --git a/chrome/test/data/extensions/api_test/tts/enqueue/manifest.json b/chrome/test/data/extensions/api_test/tts/enqueue/manifest.json
new file mode 100644
index 0000000..a43bc3d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/enqueue/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts/enqueue/test.html b/chrome/test/data/extensions/api_test/tts/enqueue/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/enqueue/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts/enqueue/test.js b/chrome/test/data/extensions/api_test/tts/enqueue/test.js
new file mode 100644
index 0000000..a903ab3
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/enqueue/test.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+chrome.test.runTests([
+ function testAllSpeakCallbackFunctionsAreCalled() {
+ var callbacks = 0;
+ chrome.experimental.tts.speak('text 1', {'enqueue': true}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('text 2', {'enqueue': true}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ if (callbacks == 2) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts/interrupt/manifest.json b/chrome/test/data/extensions/api_test/tts/interrupt/manifest.json
new file mode 100644
index 0000000..a43bc3d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/interrupt/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts/interrupt/test.html b/chrome/test/data/extensions/api_test/tts/interrupt/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/interrupt/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts/interrupt/test.js b/chrome/test/data/extensions/api_test/tts/interrupt/test.js
new file mode 100644
index 0000000..51759b2
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/interrupt/test.js
@@ -0,0 +1,25 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+chrome.test.runTests([
+ function testAllSpeakCallbackFunctionsAreCalled() {
+ var callbacks = 0;
+ chrome.experimental.tts.speak('text 1', {'enqueue': false}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('text 2', {'enqueue': false}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ if (callbacks == 2) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts/queue_interrupt/manifest.json b/chrome/test/data/extensions/api_test/tts/queue_interrupt/manifest.json
new file mode 100644
index 0000000..a43bc3d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/queue_interrupt/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.html b/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js b/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js
new file mode 100644
index 0000000..c8137cf
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/queue_interrupt/test.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+chrome.test.runTests([
+ function testAllSpeakCallbackFunctionsAreCalled() {
+ var callbacks = 0;
+ chrome.experimental.tts.speak('text 1', {'enqueue': true}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('text 2', {'enqueue': true}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('text 3', {'enqueue': false}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ if (callbacks == 3) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts/speak_error/manifest.json b/chrome/test/data/extensions/api_test/tts/speak_error/manifest.json
new file mode 100644
index 0000000..a43bc3d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/speak_error/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts/speak_error/test.html b/chrome/test/data/extensions/api_test/tts/speak_error/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/speak_error/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts/speak_error/test.js b/chrome/test/data/extensions/api_test/tts/speak_error/test.js
new file mode 100644
index 0000000..637ee46
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/speak_error/test.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+chrome.test.runTests([
+ function testSpeakCallbackFunctionIsCalled() {
+ var callbacks = 0;
+ chrome.experimental.tts.speak('first try', {'enqueue': true}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('second try', {'enqueue': true}, function() {
+ chrome.test.assertEq('epic fail', chrome.extension.lastError.message);
+ callbacks++;
+ });
+ chrome.experimental.tts.speak('third try', {'enqueue': true}, function() {
+ chrome.test.assertNoLastError();
+ callbacks++;
+ if (callbacks == 3) {
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail();
+ }
+ });
+ }
+]);
diff --git a/chrome/test/data/extensions/api_test/tts/speak_once/manifest.json b/chrome/test/data/extensions/api_test/tts/speak_once/manifest.json
new file mode 100644
index 0000000..a43bc3d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/speak_once/manifest.json
@@ -0,0 +1,7 @@
+{
+ "name": "chrome.experimental.tts",
+ "version": "0.1",
+ "description": "browser test for chrome.experimental.tts API",
+ "background_page": "test.html",
+ "permissions": ["experimental"]
+}
diff --git a/chrome/test/data/extensions/api_test/tts/speak_once/test.html b/chrome/test/data/extensions/api_test/tts/speak_once/test.html
new file mode 100644
index 0000000..46f4d74
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/speak_once/test.html
@@ -0,0 +1 @@
+<script src="test.js"></script>
diff --git a/chrome/test/data/extensions/api_test/tts/speak_once/test.js b/chrome/test/data/extensions/api_test/tts/speak_once/test.js
new file mode 100644
index 0000000..33fc8f5
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/tts/speak_once/test.js
@@ -0,0 +1,15 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TTS api test for Chrome on ChromeOS.
+// browser_tests.exe --gtest_filter="TtsApiTest.*"
+
+chrome.test.runTests([
+ function testSpeakCallbackFunctionIsCalled() {
+ chrome.experimental.tts.speak('hello world', {}, function() {
+ chrome.test.assertNoLastError();
+ chrome.test.succeed();
+ });
+ }
+]);