#include "content/browser/speech/tts_controller_impl.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "content/browser/speech/tts_utterance_impl.h"
#include "content/public/browser/tts_platform.h"
#include "content/public/browser/visibility.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/test/test_content_browser_client.h"
#include "content/test/test_web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/speech/speech_synthesis.mojom.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "content/public/browser/tts_controller_delegate.h"
#endif
namespace content {
class MockTtsPlatformImpl : public TtsPlatform {
public:
explicit MockTtsPlatformImpl(TtsController* controller)
: controller_(controller) {}
virtual ~MockTtsPlatformImpl() = default;
void set_voices(const std::vector<VoiceData>& voices) { voices_ = voices; }
void set_is_speaking(bool value) { is_speaking_ = value; }
bool PlatformImplSupported() override { return platform_supported_; }
bool PlatformImplInitialized() override { return platform_initialized_; }
void Speak(
int utterance_id,
const std::string& utterance,
const std::string& lang,
const VoiceData& voice,
const UtteranceContinuousParameters& params,
base::OnceCallback<void(bool)> did_start_speaking_callback) override {
utterance_id_ = utterance_id;
did_start_speaking_callback_ = std::move(did_start_speaking_callback);
utterance_voice_ = voice;
}
bool IsSpeaking() override { return is_speaking_; }
bool StopSpeaking() override {
++stop_speaking_called_;
return true;
}
void Pause() override { ++pause_called_; }
void Resume() override { ++resume_called_; }
void GetVoices(std::vector<VoiceData>* out_voices) override {
for (const auto& voice : voices_)
out_voices->push_back(voice);
}
void LoadBuiltInTtsEngine(BrowserContext* browser_context) override {}
void WillSpeakUtteranceWithVoice(TtsUtterance* utterance,
const VoiceData& voice_data) override {}
void SetError(const std::string& error) override { error_ = error; }
std::string GetError() override { return error_; }
void ClearError() override { error_.clear(); }
void Shutdown() override {}
void FinalizeVoiceOrdering(std::vector<VoiceData>& voices) override {}
void RefreshVoices() override {}
content::ExternalPlatformDelegate* GetExternalPlatformDelegate() override {
return nullptr;
}
void SetPlatformImplSupported(bool state) { platform_supported_ = state; }
void SetPlatformImplInitialized(bool state) { platform_initialized_ = state; }
VoiceData get_utterance_voice() const { return utterance_voice_; }
int pause_called() const { return pause_called_; }
int resume_called() const { return resume_called_; }
int stop_speaking_called() const { return stop_speaking_called_; }
void StartSpeaking(bool result) {
is_speaking_ = true;
std::move(did_start_speaking_callback_).Run(result);
}
void FinishSpeaking() {
is_speaking_ = false;
controller_->OnTtsEvent(utterance_id_, TTS_EVENT_END, 0, 0, {});
utterance_id_ = -1;
}
private:
const raw_ptr<TtsController> controller_;
bool platform_supported_ = true;
bool platform_initialized_ = true;
std::vector<VoiceData> voices_;
int utterance_id_ = -1;
VoiceData utterance_voice_;
bool is_speaking_ = false;
int pause_called_ = 0;
int resume_called_ = 0;
int stop_speaking_called_ = 0;
std::string error_;
base::OnceCallback<void(bool)> did_start_speaking_callback_;
};
class MockTtsEngineDelegate : public TtsEngineDelegate {
public:
int utterance_id() { return utterance_id_; }
void set_is_built_in_tts_engine_initialized(bool value) {
is_built_in_tts_engine_initialized_ = value;
}
void set_voices(const std::vector<VoiceData>& voices) { voices_ = voices; }
void Speak(TtsUtterance* utterance, const VoiceData& voice) override {
utterance_id_ = utterance->GetId();
}
void LoadBuiltInTtsEngine(BrowserContext* browser_context) override {}
bool IsBuiltInTtsEngineInitialized(BrowserContext* browser_context) override {
return is_built_in_tts_engine_initialized_;
}
void GetVoices(BrowserContext* browser_context,
const GURL& source_url,
std::vector<VoiceData>* out_voices) override {
for (const auto& voice : voices_)
out_voices->push_back(voice);
}
void Stop(TtsUtterance* utterance) override { ++stop_called_; }
void Pause(TtsUtterance* utterance) override { ++pause_called_; }
void Resume(TtsUtterance* utterance) override { ++resume_called_; }
int pause_called() const { return pause_called_; }
int resume_called() const { return resume_called_; }
int stop_called() const { return stop_called_; }
private:
bool is_built_in_tts_engine_initialized_ = true;
int utterance_id_ = -1;
std::vector<VoiceData> voices_;
int pause_called_ = 0;
int resume_called_ = 0;
int stop_called_ = 0;
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
class MockTtsControllerDelegate : public TtsControllerDelegate {
public:
MockTtsControllerDelegate() = default;
~MockTtsControllerDelegate() override = default;
void SetPreferredVoiceIds(const PreferredVoiceIds& ids) { ids_ = ids; }
BrowserContext* GetLastBrowserContext() {
BrowserContext* result = last_browser_context_;
last_browser_context_ = nullptr;
return result;
}
std::unique_ptr<PreferredVoiceIds> GetPreferredVoiceIdsForUtterance(
TtsUtterance* utterance) override {
last_browser_context_ = utterance->GetBrowserContext();
auto ids = std::make_unique<PreferredVoiceIds>(ids_);
return ids;
}
void UpdateUtteranceDefaultsFromPrefs(content::TtsUtterance* utterance,
double* rate,
double* pitch,
double* volume) override {}
private:
raw_ptr<BrowserContext, ExperimentalAsh> last_browser_context_ = nullptr;
PreferredVoiceIds ids_;
};
#endif
class MockVoicesChangedDelegate : public VoicesChangedDelegate {
public:
void OnVoicesChanged() override {}
};
class TestTtsControllerImpl : public TtsControllerImpl {
public:
TestTtsControllerImpl() = default;
~TestTtsControllerImpl() override = default;
using TtsControllerImpl::FinishCurrentUtterance;
using TtsControllerImpl::GetMatchingVoice;
using TtsControllerImpl::SpeakNextUtterance;
using TtsControllerImpl::UpdateUtteranceDefaults;
#if BUILDFLAG(IS_CHROMEOS_ASH)
using TtsControllerImpl::SetTtsControllerDelegateForTesting;
#endif
using TtsControllerImpl::IsPausedForTesting;
TtsUtterance* current_utterance() { return current_utterance_.get(); }
void set_allow_remote_voices(bool value) { allow_remote_voices_ = value; }
};
class TtsControllerTest : public testing::Test {
public:
TtsControllerTest() = default;
~TtsControllerTest() override = default;
void SetUp() override {
controller_ = std::make_unique<TestTtsControllerImpl>();
platform_impl_ = std::make_unique<MockTtsPlatformImpl>(controller_.get());
browser_context_ = std::make_unique<TestBrowserContext>();
controller()->SetTtsPlatform(platform_impl_.get());
#if !BUILDFLAG(IS_ANDROID)
controller()->SetTtsEngineDelegate(&engine_delegate_);
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
controller()->SetTtsControllerDelegateForTesting(&delegate_);
#endif
controller()->AddVoicesChangedDelegate(&voices_changed_);
}
void TearDown() override {
if (controller())
controller()->RemoveVoicesChangedDelegate(&voices_changed_);
}
MockTtsPlatformImpl* platform_impl() { return platform_impl_.get(); }
TestTtsControllerImpl* controller() { return controller_.get(); }
TestBrowserContext* browser_context() { return browser_context_.get(); }
MockTtsEngineDelegate* engine_delegate() { return &engine_delegate_; }
#if BUILDFLAG(IS_CHROMEOS_ASH)
MockTtsControllerDelegate* delegate() { return &delegate_; }
#endif
void ReleaseTtsController() { controller_.reset(); }
void ReleaseBrowserContext() {
controller()->OnBrowserContextDestroyed(browser_context_.get());
browser_context_.reset();
}
std::unique_ptr<TestWebContents> CreateWebContents() {
return std::unique_ptr<TestWebContents>(
TestWebContents::Create(browser_context_.get(), nullptr));
}
std::unique_ptr<TtsUtteranceImpl> CreateUtteranceImpl(
WebContents* web_contents = nullptr) {
return std::make_unique<TtsUtteranceImpl>(browser_context_.get(),
web_contents);
}
TtsUtterance* TtsControllerCurrentUtterance() {
return controller()->current_utterance();
}
bool IsUtteranceListEmpty() { return controller()->QueueSize() == 0; }
private:
content::BrowserTaskEnvironment task_environment_;
RenderViewHostTestEnabler rvh_enabler_;
std::unique_ptr<TestTtsControllerImpl> controller_;
std::unique_ptr<MockTtsPlatformImpl> platform_impl_;
std::unique_ptr<TestBrowserContext> browser_context_;
MockTtsEngineDelegate engine_delegate_;
#if BUILDFLAG(IS_CHROMEOS_ASH)
MockTtsControllerDelegate delegate_;
#endif
MockVoicesChangedDelegate voices_changed_;
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(TtsControllerTest, TestBrowserContextRemoved) {
std::vector<VoiceData> voices;
VoiceData voice_data;
voice_data.engine_id = "x";
voice_data.events.insert(TTS_EVENT_END);
voices.push_back(voice_data);
platform_impl()->set_voices(voices);
std::unique_ptr<TtsUtterance> utterance1 =
TtsUtterance::Create(browser_context());
utterance1->SetEngineId("x");
utterance1->SetShouldClearQueue(false);
utterance1->SetSrcId(1);
controller()->SpeakOrEnqueue(std::move(utterance1));
ASSERT_EQ(browser_context(), delegate()->GetLastBrowserContext());
std::unique_ptr<TtsUtterance> utterance2 =
TtsUtterance::Create(browser_context());
utterance2->SetEngineId("x");
utterance2->SetShouldClearQueue(false);
utterance2->SetSrcId(2);
controller()->SpeakOrEnqueue(std::move(utterance2));
ReleaseBrowserContext();
controller()->FinishCurrentUtterance();
controller()->SpeakNextUtterance();
ASSERT_EQ(nullptr, delegate()->GetLastBrowserContext());
}
#else
TEST_F(TtsControllerTest, TestTtsControllerUtteranceDefaults) {
std::unique_ptr<TtsUtterance> utterance1 = content::TtsUtterance::Create();
EXPECT_EQ(blink::mojom::kSpeechSynthesisDoublePrefNotSet,
utterance1->GetContinuousParameters().rate);
EXPECT_EQ(blink::mojom::kSpeechSynthesisDoublePrefNotSet,
utterance1->GetContinuousParameters().pitch);
EXPECT_EQ(blink::mojom::kSpeechSynthesisDoublePrefNotSet,
utterance1->GetContinuousParameters().volume);
controller()->UpdateUtteranceDefaults(utterance1.get());
EXPECT_EQ(blink::mojom::kSpeechSynthesisDefaultRate,
utterance1->GetContinuousParameters().rate);
EXPECT_EQ(blink::mojom::kSpeechSynthesisDefaultPitch,
utterance1->GetContinuousParameters().pitch);
EXPECT_EQ(blink::mojom::kSpeechSynthesisDefaultVolume,
utterance1->GetContinuousParameters().volume);
}
#endif
TEST_F(TtsControllerTest, TestGetMatchingVoice) {
TestContentBrowserClient::GetInstance()->set_application_locale("en");
{
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
std::vector<VoiceData> voices;
EXPECT_EQ(-1, controller()->GetMatchingVoice(utterance.get(), voices));
}
{
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
std::vector<VoiceData> voices(2);
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
}
{
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
std::vector<VoiceData> voices;
VoiceData fr_voice;
fr_voice.lang = "fr";
voices.push_back(fr_voice);
VoiceData en_voice;
en_voice.lang = "en";
voices.push_back(en_voice);
VoiceData de_voice;
de_voice.lang = "de";
voices.push_back(de_voice);
EXPECT_EQ(1, controller()->GetMatchingVoice(utterance.get(), voices));
}
{
std::vector<VoiceData> voices;
VoiceData voice0;
voices.push_back(voice0);
VoiceData voice1;
voice1.events.insert(TTS_EVENT_WORD);
voices.push_back(voice1);
VoiceData voice2;
voice2.lang = "de-DE";
voices.push_back(voice2);
VoiceData voice3;
voice3.lang = "fr-CA";
voices.push_back(voice3);
VoiceData voice4;
voice4.name = "Voice4";
voices.push_back(voice4);
VoiceData voice5;
voice5.engine_id = "id5";
voices.push_back(voice5);
VoiceData voice6;
voice6.engine_id = "id7";
voice6.name = "Voice6";
voice6.lang = "es-es";
voices.push_back(voice6);
VoiceData voice7;
voice7.engine_id = "id7";
voice7.name = "Voice7";
voice7.lang = "es-mx";
voices.push_back(voice7);
VoiceData voice8;
voice8.engine_id = "";
voice8.name = "Android";
voice8.lang = "";
voice8.native = true;
voices.push_back(voice8);
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
std::set<TtsEventType> types;
types.insert(TTS_EVENT_WORD);
utterance->SetRequiredEventTypes(types);
EXPECT_EQ(1, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetLang("de-DE");
EXPECT_EQ(2, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetLang("fr-FR");
EXPECT_EQ(3, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetVoiceName("Voice4");
EXPECT_EQ(4, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetVoiceName("");
utterance->SetEngineId("id5");
EXPECT_EQ(5, controller()->GetMatchingVoice(utterance.get(), voices));
#if BUILDFLAG(IS_CHROMEOS_ASH)
TtsControllerDelegate::PreferredVoiceIds preferred_voice_ids;
preferred_voice_ids.locale_voice_id.emplace("Voice7", "id7");
preferred_voice_ids.any_locale_voice_id.emplace("Android", "");
delegate()->SetPreferredVoiceIds(preferred_voice_ids);
utterance->SetEngineId("");
utterance->SetLang("es-es");
EXPECT_EQ(6, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetEngineId("");
utterance->SetLang("es-ar");
EXPECT_EQ(7, controller()->GetMatchingVoice(utterance.get(), voices));
preferred_voice_ids.locale_voice_id.reset();
delegate()->SetPreferredVoiceIds(preferred_voice_ids);
utterance->SetVoiceName("Android");
utterance->SetEngineId("");
utterance->SetLang("");
EXPECT_EQ(8, controller()->GetMatchingVoice(utterance.get(), voices));
delegate()->SetPreferredVoiceIds({});
#endif
}
{
std::vector<VoiceData> voices;
VoiceData voice0;
voice0.engine_id = "id0";
voice0.name = "voice0";
voice0.lang = "en-GB";
voices.push_back(voice0);
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
TestContentBrowserClient::GetInstance()->set_application_locale("en-US");
utterance->SetLang("en-US");
utterance->SetEngineId("test_engine");
EXPECT_EQ(-1, controller()->GetMatchingVoice(utterance.get(), voices));
}
{
std::vector<VoiceData> voices;
VoiceData voice0;
voice0.engine_id = "id0";
voice0.name = "voice0";
voice0.lang = "en-GB";
voices.push_back(voice0);
VoiceData voice1;
voice1.engine_id = "id1";
voice1.name = "voice1";
voice1.lang = "en-US";
voices.push_back(voice1);
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
TestContentBrowserClient::GetInstance()->set_application_locale("en-US");
utterance->SetLang("");
EXPECT_EQ(1, controller()->GetMatchingVoice(utterance.get(), voices));
#if BUILDFLAG(IS_CHROMEOS_ASH)
TestContentBrowserClient::GetInstance()->set_application_locale("en");
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
TtsControllerDelegate::PreferredVoiceIds preferred_voice_ids2;
preferred_voice_ids2.locale_voice_id.emplace("voice0", "id0");
delegate()->SetPreferredVoiceIds(preferred_voice_ids2);
TestContentBrowserClient::GetInstance()->set_application_locale("en-US");
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
#endif
}
{
std::vector<VoiceData> voices;
VoiceData voice0;
voice0.engine_id = "id0";
voice0.name = "English voice";
voice0.lang = "en-US";
voices.push_back(voice0);
VoiceData voice1;
voice1.engine_id = "id0";
voice1.name = "French voice";
voice1.lang = "fr";
voices.push_back(voice1);
std::unique_ptr<TtsUtterance> utterance(TtsUtterance::Create());
utterance->SetLang("en-us");
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetLang("en-US");
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
utterance->SetLang("EN-US");
EXPECT_EQ(0, controller()->GetMatchingVoice(utterance.get(), voices));
#if BUILDFLAG(IS_CHROMEOS_ASH)
VoiceData voice2;
voice2.engine_id = "id1";
voice2.name = "Another English voice";
voice2.lang = "en-us";
voices.push_back(voice2);
TtsControllerDelegate::PreferredVoiceIds preferred_voice_ids;
preferred_voice_ids.lang_voice_id.emplace(voice2.name, voice2.engine_id);
delegate()->SetPreferredVoiceIds(preferred_voice_ids);
EXPECT_EQ(2, controller()->GetMatchingVoice(utterance.get(), voices));
#endif
}
}
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_F(TtsControllerTest, TestTtsControllerShutdown) {
std::unique_ptr<TtsUtterance> utterance1 = TtsUtterance::Create();
utterance1->SetShouldClearQueue(false);
utterance1->SetSrcId(1);
controller()->SpeakOrEnqueue(std::move(utterance1));
std::unique_ptr<TtsUtterance> utterance2 = TtsUtterance::Create();
utterance2->SetShouldClearQueue(false);
utterance2->SetSrcId(2);
controller()->SpeakOrEnqueue(std::move(utterance2));
ReleaseTtsController();
}
TEST_F(TtsControllerTest, StopsWhenWebContentsDestroyed) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_TRUE(controller()->IsSpeaking());
EXPECT_TRUE(TtsControllerCurrentUtterance());
web_contents.reset();
EXPECT_FALSE(TtsControllerCurrentUtterance());
}
TEST_F(TtsControllerTest, StartsQueuedUtteranceWhenWebContentsDestroyed) {
std::unique_ptr<WebContents> web_contents1 = CreateWebContents();
std::unique_ptr<WebContents> web_contents2 = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance1 =
CreateUtteranceImpl(web_contents1.get());
void* raw_utterance1 = utterance1.get();
std::unique_ptr<TtsUtteranceImpl> utterance2 =
CreateUtteranceImpl(web_contents2.get());
utterance2->SetShouldClearQueue(false);
void* raw_utterance2 = utterance2.get();
controller()->SpeakOrEnqueue(std::move(utterance1));
EXPECT_TRUE(controller()->IsSpeaking());
EXPECT_TRUE(TtsControllerCurrentUtterance());
controller()->SpeakOrEnqueue(std::move(utterance2));
EXPECT_EQ(raw_utterance1, TtsControllerCurrentUtterance());
web_contents1.reset();
EXPECT_TRUE(TtsControllerCurrentUtterance());
EXPECT_EQ(raw_utterance2, TtsControllerCurrentUtterance());
}
TEST_F(TtsControllerTest, StartsQueuedUtteranceWhenWebContentsDestroyed2) {
std::unique_ptr<WebContents> web_contents1 = CreateWebContents();
std::unique_ptr<WebContents> web_contents2 = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance1 =
CreateUtteranceImpl(web_contents1.get());
void* raw_utterance1 = utterance1.get();
std::unique_ptr<TtsUtteranceImpl> utterance2 =
CreateUtteranceImpl(web_contents1.get());
std::unique_ptr<TtsUtteranceImpl> utterance3 =
CreateUtteranceImpl(web_contents2.get());
void* raw_utterance3 = utterance3.get();
utterance2->SetShouldClearQueue(false);
utterance3->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance1));
controller()->SpeakOrEnqueue(std::move(utterance2));
controller()->SpeakOrEnqueue(std::move(utterance3));
EXPECT_TRUE(controller()->IsSpeaking());
EXPECT_EQ(raw_utterance1, TtsControllerCurrentUtterance());
web_contents1.reset();
EXPECT_EQ(raw_utterance3, TtsControllerCurrentUtterance());
EXPECT_TRUE(IsUtteranceListEmpty());
web_contents2.reset();
EXPECT_EQ(nullptr, TtsControllerCurrentUtterance());
}
TEST_F(TtsControllerTest, StartsUtteranceWhenWebContentsHidden) {
std::unique_ptr<TestWebContents> web_contents = CreateWebContents();
web_contents->SetVisibilityAndNotifyObservers(Visibility::HIDDEN);
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_TRUE(controller()->IsSpeaking());
}
TEST_F(TtsControllerTest,
DoesNotStartUtteranceWhenWebContentsHiddenAndStopSpeakingWhenHiddenSet) {
std::unique_ptr<TestWebContents> web_contents = CreateWebContents();
web_contents->SetVisibilityAndNotifyObservers(Visibility::HIDDEN);
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
controller()->SetStopSpeakingWhenHidden(true);
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_EQ(nullptr, TtsControllerCurrentUtterance());
EXPECT_TRUE(IsUtteranceListEmpty());
}
TEST_F(TtsControllerTest, SkipsQueuedUtteranceFromHiddenWebContents) {
controller()->SetStopSpeakingWhenHidden(true);
std::unique_ptr<WebContents> web_contents1 = CreateWebContents();
std::unique_ptr<TestWebContents> web_contents2 = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance1 =
CreateUtteranceImpl(web_contents1.get());
const int utterance1_id = utterance1->GetId();
std::unique_ptr<TtsUtteranceImpl> utterance2 =
CreateUtteranceImpl(web_contents2.get());
utterance2->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance1));
EXPECT_TRUE(TtsControllerCurrentUtterance());
EXPECT_TRUE(IsUtteranceListEmpty());
controller()->SpeakOrEnqueue(std::move(utterance2));
EXPECT_FALSE(IsUtteranceListEmpty());
web_contents2->SetVisibilityAndNotifyObservers(Visibility::HIDDEN);
EXPECT_FALSE(IsUtteranceListEmpty());
controller()->OnTtsEvent(utterance1_id, TTS_EVENT_END, 0, 0, {});
EXPECT_EQ(nullptr, TtsControllerCurrentUtterance());
EXPECT_TRUE(IsUtteranceListEmpty());
}
TEST_F(TtsControllerTest, SpeakPauseResume) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance));
platform_impl()->StartSpeaking(true);
controller()->Pause();
EXPECT_TRUE(controller()->IsPausedForTesting());
EXPECT_EQ(1, platform_impl()->pause_called());
controller()->Pause();
EXPECT_EQ(1, platform_impl()->pause_called());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
controller()->Resume();
EXPECT_FALSE(controller()->IsPausedForTesting());
EXPECT_EQ(1, platform_impl()->resume_called());
controller()->Resume();
EXPECT_EQ(1, platform_impl()->resume_called());
EXPECT_TRUE(controller()->IsSpeaking());
platform_impl()->FinishSpeaking();
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_FALSE(controller()->IsSpeaking());
}
TEST_F(TtsControllerTest, SpeakWhenPaused) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
controller()->Pause();
EXPECT_TRUE(controller()->IsPausedForTesting());
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
controller()->Resume();
EXPECT_FALSE(controller()->IsPausedForTesting());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
platform_impl()->StartSpeaking(true);
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
EXPECT_TRUE(controller()->IsSpeaking());
platform_impl()->FinishSpeaking();
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_FALSE(controller()->IsSpeaking());
}
TEST_F(TtsControllerTest, SpeakWhenPausedAndCannotEnqueueUtterance) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance1 =
CreateUtteranceImpl(web_contents.get());
utterance1->SetShouldClearQueue(true);
controller()->Pause();
EXPECT_TRUE(controller()->IsPausedForTesting());
controller()->SpeakOrEnqueue(std::move(utterance1));
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_EQ(1, controller()->QueueSize());
EXPECT_FALSE(TtsControllerCurrentUtterance());
std::unique_ptr<TtsUtteranceImpl> utterance2 =
CreateUtteranceImpl(web_contents.get());
utterance2->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance2));
EXPECT_TRUE(controller()->IsPausedForTesting());
EXPECT_EQ(2, controller()->QueueSize());
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
std::unique_ptr<TtsUtteranceImpl> utterance3 =
CreateUtteranceImpl(web_contents.get());
utterance3->SetShouldClearQueue(true);
controller()->SpeakOrEnqueue(std::move(utterance3));
EXPECT_TRUE(controller()->IsPausedForTesting());
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_EQ(1, controller()->QueueSize());
EXPECT_FALSE(TtsControllerCurrentUtterance());
controller()->Resume();
EXPECT_FALSE(controller()->IsPausedForTesting());
}
TEST_F(TtsControllerTest, StopMustResumeController) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance));
platform_impl()->StartSpeaking(true);
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
platform_impl()->SetError("dummy");
controller()->Stop();
platform_impl()->FinishSpeaking();
EXPECT_EQ(2, platform_impl()->stop_speaking_called());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_TRUE(platform_impl()->GetError().empty());
EXPECT_FALSE(controller()->IsSpeaking());
}
TEST_F(TtsControllerTest, PauseAndStopMustResumeController) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
controller()->Pause();
EXPECT_TRUE(controller()->IsPausedForTesting());
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
platform_impl()->SetError("dummy");
controller()->Stop();
EXPECT_FALSE(controller()->IsPausedForTesting());
EXPECT_EQ(1, platform_impl()->stop_speaking_called());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_TRUE(platform_impl()->GetError().empty());
}
TEST_F(TtsControllerTest, EngineIdSetNoDelegateSpeakPauseResumeStop) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
utterance->SetLang("es-US");
utterance->SetEngineId("test_engine_id");
utterance->SetVoiceName("test_name");
std::vector<VoiceData> voices;
VoiceData voice_data0;
voice_data0.name = "voice0";
voice_data0.lang = "es-US";
voices.push_back(std::move(voice_data0));
VoiceData voice_data1;
voice_data1.name = "voice1";
voice_data1.lang = "es-ES";
voices.push_back(std::move(voice_data1));
platform_impl()->set_voices(voices);
controller()->SpeakOrEnqueue(std::move(utterance));
platform_impl()->StartSpeaking(true);
EXPECT_TRUE(platform_impl()->get_utterance_voice().native);
EXPECT_EQ("es-US", platform_impl()->get_utterance_voice().lang);
EXPECT_EQ("test_engine_id", platform_impl()->get_utterance_voice().engine_id);
EXPECT_EQ("test_name", platform_impl()->get_utterance_voice().name);
EXPECT_EQ(1, platform_impl()->stop_speaking_called());
controller()->Pause();
EXPECT_TRUE(controller()->IsPausedForTesting());
if (controller()->GetTtsEngineDelegate() == nullptr) {
EXPECT_EQ(1, platform_impl()->pause_called());
} else {
EXPECT_EQ(1, engine_delegate()->pause_called());
}
controller()->Pause();
if (controller()->GetTtsEngineDelegate() == nullptr) {
EXPECT_EQ(1, platform_impl()->pause_called());
} else {
EXPECT_EQ(1, engine_delegate()->pause_called());
}
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
controller()->Resume();
EXPECT_FALSE(controller()->IsPausedForTesting());
EXPECT_TRUE(controller()->IsSpeaking());
if (controller()->GetTtsEngineDelegate() == nullptr) {
EXPECT_EQ(1, platform_impl()->resume_called());
} else {
EXPECT_EQ(1, engine_delegate()->resume_called());
}
controller()->Resume();
EXPECT_TRUE(controller()->IsSpeaking());
if (controller()->GetTtsEngineDelegate() == nullptr) {
EXPECT_EQ(1, platform_impl()->resume_called());
} else {
EXPECT_EQ(1, engine_delegate()->resume_called());
}
controller()->Stop();
if (controller()->GetTtsEngineDelegate() == nullptr) {
EXPECT_EQ(2, platform_impl()->stop_speaking_called());
} else {
EXPECT_EQ(1, engine_delegate()->stop_called());
}
platform_impl()->FinishSpeaking();
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_FALSE(controller()->IsSpeaking());
}
#endif
TEST_F(TtsControllerTest, PauseResumeNoUtterance) {
controller()->Pause();
controller()->Resume();
EXPECT_EQ(0, platform_impl()->pause_called());
EXPECT_EQ(0, platform_impl()->resume_called());
}
TEST_F(TtsControllerTest, PlatformNotSupported) {
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
platform_impl()->SetPlatformImplSupported(false);
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_TRUE(IsUtteranceListEmpty());
controller()->Pause();
controller()->Resume();
controller()->Stop();
EXPECT_EQ(0, platform_impl()->pause_called());
EXPECT_EQ(0, platform_impl()->resume_called());
EXPECT_EQ(0, platform_impl()->stop_speaking_called());
}
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
TEST_F(TtsControllerTest, SpeakWhenLoadingPlatformImpl) {
platform_impl()->SetPlatformImplInitialized(false);
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
platform_impl()->SetPlatformImplInitialized(true);
controller()->VoicesChanged();
platform_impl()->StartSpeaking(true);
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
EXPECT_TRUE(controller()->IsSpeaking());
platform_impl()->FinishSpeaking();
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_FALSE(controller()->IsSpeaking());
}
TEST_F(TtsControllerTest, GetVoicesOnlineOffline) {
std::vector<VoiceData> voices;
VoiceData voice_data0;
voice_data0.name = "offline";
voices.push_back(std::move(voice_data0));
VoiceData voice_data1;
voice_data1.name = "online";
voice_data1.remote = true;
voices.push_back(std::move(voice_data1));
platform_impl()->set_voices(voices);
controller()->set_allow_remote_voices(true);
std::vector<VoiceData> controller_voices;
controller()->GetVoices(browser_context(), GURL(), &controller_voices);
EXPECT_EQ(2U, controller_voices.size());
controller_voices.clear();
controller()->set_allow_remote_voices(false);
controller()->GetVoices(browser_context(), GURL(), &controller_voices);
EXPECT_EQ(1U, controller_voices.size());
EXPECT_EQ("offline", controller_voices[0].name);
}
#endif
#if !BUILDFLAG(IS_ANDROID)
TEST_F(TtsControllerTest, SpeakWhenLoadingBuiltInEngine) {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
platform_impl()->SetPlatformImplSupported(false);
#endif
engine_delegate()->set_is_built_in_tts_engine_initialized(false);
std::vector<VoiceData> voices;
VoiceData voice_data;
voice_data.engine_id = "x";
voice_data.events.insert(TTS_EVENT_START);
voice_data.events.insert(TTS_EVENT_END);
voices.push_back(voice_data);
engine_delegate()->set_voices(voices);
std::unique_ptr<WebContents> web_contents = CreateWebContents();
std::unique_ptr<TtsUtteranceImpl> utterance =
CreateUtteranceImpl(web_contents.get());
utterance->SetShouldClearQueue(false);
controller()->SpeakOrEnqueue(std::move(utterance));
EXPECT_FALSE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
engine_delegate()->set_is_built_in_tts_engine_initialized(true);
controller()->VoicesChanged();
int utterance_id = engine_delegate()->utterance_id();
controller()->OnTtsEvent(utterance_id, content::TTS_EVENT_START, 0, 0,
std::string());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_TRUE(TtsControllerCurrentUtterance());
EXPECT_TRUE(controller()->IsSpeaking());
controller()->OnTtsEvent(utterance_id, content::TTS_EVENT_END, 0, 0,
std::string());
EXPECT_TRUE(IsUtteranceListEmpty());
EXPECT_FALSE(TtsControllerCurrentUtterance());
EXPECT_FALSE(controller()->IsSpeaking());
}
#endif
}