1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/webui/settings/tts_handler.h"
6
7 #include "base/bind.h"
8 #include "base/json/json_reader.h"
9 #include "base/values.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
13 #include "chrome/browser/speech/extension_api/tts_engine_extension_observer.h"
14 #include "chrome/browser/ui/chrome_pages.h"
15 #include "chrome/common/extensions/extension_constants.h"
16 #include "chrome/grit/generated_resources.h"
17 #include "content/public/browser/tts_controller.h"
18 #include "content/public/browser/web_ui.h"
19 #include "extensions/browser/event_router.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/process_manager.h"
22 #include "extensions/common/extension.h"
23 #include "extensions/common/extension_set.h"
24 #include "extensions/common/manifest_handlers/options_page_info.h"
25 #include "ui/base/l10n/l10n_util.h"
26
27 namespace settings {
TtsHandler()28 TtsHandler::TtsHandler() {}
29
~TtsHandler()30 TtsHandler::~TtsHandler() {
31 content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
32 }
33
HandleGetAllTtsVoiceData(const base::ListValue * args)34 void TtsHandler::HandleGetAllTtsVoiceData(const base::ListValue* args) {
35 OnVoicesChanged();
36 }
37
HandleGetTtsExtensions(const base::ListValue * args)38 void TtsHandler::HandleGetTtsExtensions(const base::ListValue* args) {
39 // Ensure the built in tts engine is loaded to be able to respond to messages.
40 WakeTtsEngine(nullptr);
41
42 base::ListValue responses;
43 Profile* profile = Profile::FromWebUI(web_ui());
44 extensions::ExtensionRegistry* registry =
45 extensions::ExtensionRegistry::Get(profile);
46
47 const std::set<std::string> extensions =
48 TtsEngineExtensionObserver::GetInstance(profile)->GetTtsExtensions();
49 std::set<std::string>::const_iterator iter;
50 for (iter = extensions.begin(); iter != extensions.end(); ++iter) {
51 const std::string extension_id = *iter;
52 const extensions::Extension* extension =
53 registry->GetInstalledExtension(extension_id);
54 if (!extension) {
55 // The extension is still loading from OnVoicesChange call to
56 // TtsController::GetVoices(). Don't do any work, voices will
57 // be updated again after extension load.
58 continue;
59 }
60 base::DictionaryValue response;
61 response.SetString("name", extension->name());
62 response.SetString("extensionId", extension_id);
63 if (extensions::OptionsPageInfo::HasOptionsPage(extension)) {
64 response.SetString(
65 "optionsPage",
66
67 extensions::OptionsPageInfo::GetOptionsPage(extension).spec());
68 }
69 responses.Append(std::move(response));
70 }
71
72 FireWebUIListener("tts-extensions-updated", responses);
73 }
74
OnVoicesChanged()75 void TtsHandler::OnVoicesChanged() {
76 content::TtsController* tts_controller =
77 content::TtsController::GetInstance();
78 std::vector<content::VoiceData> voices;
79 tts_controller->GetVoices(Profile::FromWebUI(web_ui()), &voices);
80 const std::string& app_locale = g_browser_process->GetApplicationLocale();
81 base::ListValue responses;
82 for (const auto& voice : voices) {
83 base::DictionaryValue response;
84 int language_score = GetVoiceLangMatchScore(&voice, app_locale);
85 std::string language_code;
86 if (voice.lang.empty()) {
87 language_code = "noLanguageCode";
88 response.SetString(
89 "displayLanguage",
90 l10n_util::GetStringUTF8(IDS_TEXT_TO_SPEECH_SETTINGS_NO_LANGUAGE));
91 } else {
92 language_code = l10n_util::GetLanguage(voice.lang);
93 response.SetString(
94 "displayLanguage",
95 l10n_util::GetDisplayNameForLocale(
96 language_code, g_browser_process->GetApplicationLocale(), true));
97 }
98 response.SetString("name", voice.name);
99 response.SetString("languageCode", language_code);
100 response.SetString("fullLanguageCode", voice.lang);
101 response.SetInteger("languageScore", language_score);
102 response.SetString("extensionId", voice.engine_id);
103 responses.Append(std::move(response));
104 }
105 AllowJavascript();
106 FireWebUIListener("all-voice-data-updated", responses);
107
108 // Also refresh the TTS extensions in case they have changed.
109 HandleGetTtsExtensions(nullptr);
110 }
111
OnTtsEvent(content::TtsUtterance * utterance,content::TtsEventType event_type,int char_index,int length,const std::string & error_message)112 void TtsHandler::OnTtsEvent(content::TtsUtterance* utterance,
113 content::TtsEventType event_type,
114 int char_index,
115 int length,
116 const std::string& error_message) {
117 if (event_type == content::TTS_EVENT_END ||
118 event_type == content::TTS_EVENT_INTERRUPTED ||
119 event_type == content::TTS_EVENT_ERROR) {
120 base::Value result(false /* preview stopped */);
121 FireWebUIListener("tts-preview-state-changed", result);
122 }
123 }
124
HandlePreviewTtsVoice(const base::ListValue * args)125 void TtsHandler::HandlePreviewTtsVoice(const base::ListValue* args) {
126 DCHECK_EQ(2U, args->GetSize());
127 std::string text;
128 std::string voice_id;
129 args->GetString(0, &text);
130 args->GetString(1, &voice_id);
131
132 if (text.empty() || voice_id.empty())
133 return;
134
135 std::unique_ptr<base::DictionaryValue> json =
136 base::DictionaryValue::From(base::JSONReader::ReadDeprecated(voice_id));
137 std::string name;
138 std::string extension_id;
139 json->GetString("name", &name);
140 json->GetString("extension", &extension_id);
141
142 std::unique_ptr<content::TtsUtterance> utterance =
143 content::TtsUtterance::Create((Profile::FromWebUI(web_ui())));
144 utterance->SetText(text);
145 utterance->SetVoiceName(name);
146 utterance->SetEngineId(extension_id);
147 utterance->SetSrcUrl(
148 GURL(chrome::GetOSSettingsUrl("manageAccessibility/tts")));
149 utterance->SetEventDelegate(this);
150 content::TtsController::GetInstance()->Stop();
151
152 base::Value result(true /* preview started */);
153 FireWebUIListener("tts-preview-state-changed", result);
154 content::TtsController::GetInstance()->SpeakOrEnqueue(std::move(utterance));
155 }
156
RegisterMessages()157 void TtsHandler::RegisterMessages() {
158 web_ui()->RegisterMessageCallback(
159 "getAllTtsVoiceData",
160 base::BindRepeating(&TtsHandler::HandleGetAllTtsVoiceData,
161 base::Unretained(this)));
162 web_ui()->RegisterMessageCallback(
163 "getTtsExtensions",
164 base::BindRepeating(&TtsHandler::HandleGetTtsExtensions,
165 base::Unretained(this)));
166 web_ui()->RegisterMessageCallback(
167 "previewTtsVoice", base::BindRepeating(&TtsHandler::HandlePreviewTtsVoice,
168 base::Unretained(this)));
169 web_ui()->RegisterMessageCallback(
170 "wakeTtsEngine",
171 base::BindRepeating(&TtsHandler::WakeTtsEngine, base::Unretained(this)));
172 }
173
OnJavascriptAllowed()174 void TtsHandler::OnJavascriptAllowed() {
175 content::TtsController::GetInstance()->AddVoicesChangedDelegate(this);
176 }
177
OnJavascriptDisallowed()178 void TtsHandler::OnJavascriptDisallowed() {
179 content::TtsController::GetInstance()->RemoveVoicesChangedDelegate(this);
180 content::TtsController::GetInstance()->RemoveUtteranceEventDelegate(this);
181 }
182
GetVoiceLangMatchScore(const content::VoiceData * voice,const std::string & app_locale)183 int TtsHandler::GetVoiceLangMatchScore(const content::VoiceData* voice,
184 const std::string& app_locale) {
185 if (voice->lang.empty() || app_locale.empty())
186 return 0;
187 if (voice->lang == app_locale)
188 return 2;
189 return l10n_util::GetLanguage(voice->lang) ==
190 l10n_util::GetLanguage(app_locale)
191 ? 1
192 : 0;
193 }
194
WakeTtsEngine(const base::ListValue * args)195 void TtsHandler::WakeTtsEngine(const base::ListValue* args) {
196 Profile* profile = Profile::FromWebUI(web_ui());
197 TtsExtensionEngine::GetInstance()->LoadBuiltInTtsEngine(profile);
198 extensions::ProcessManager::Get(profile)->WakeEventPage(
199 extension_misc::kGoogleSpeechSynthesisExtensionId,
200 base::BindOnce(&TtsHandler::OnTtsEngineAwake,
201 weak_factory_.GetWeakPtr()));
202 }
203
OnTtsEngineAwake(bool success)204 void TtsHandler::OnTtsEngineAwake(bool success) {
205 OnVoicesChanged();
206 }
207
208 } // namespace settings
209