chromium/chrome/browser/ui/webui/ash/settings/pages/a11y/tts_handler.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/ash/settings/pages/a11y/tts_handler.h"

#include "ash/webui/settings/public/constants/routes.mojom.h"
#include "base/functional/bind.h"
#include "base/i18n/rtl.h"
#include "base/json/json_reader.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/speech/extension_api/tts_engine_extension_api.h"
#include "chrome/browser/speech/extension_api/tts_engine_extension_observer_chromeos.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/grit/generated_resources.h"
#include "content/public/browser/tts_controller.h"
#include "content/public/browser/web_ui.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/manifest_handlers/options_page_info.h"
#include "ui/base/l10n/l10n_util.h"

namespace ash::settings {

TtsHandler::TtsHandler() = default;

TtsHandler::~TtsHandler() = default;

void TtsHandler::HandleGetTtsExtensions(const base::Value::List& args) {
  // Ensure the built in tts engine is loaded to be able to respond to messages.
  WakeTtsEngine(base::Value::List());

  base::Value::List responses;
  Profile* profile = Profile::FromWebUI(web_ui());
  extensions::ExtensionRegistry* registry =
      extensions::ExtensionRegistry::Get(profile);

  const std::set<std::string> extensions =
      TtsEngineExtensionObserverChromeOS::GetInstance(profile)
          ->engine_extension_ids();
  std::set<std::string>::const_iterator iter;
  for (iter = extensions.begin(); iter != extensions.end(); ++iter) {
    const std::string extension_id = *iter;
    const extensions::Extension* extension =
        registry->GetInstalledExtension(extension_id);
    if (!extension) {
      // The extension is still loading from OnVoicesChange call to
      // TtsController::GetVoices(). Don't do any work, voices will
      // be updated again after extension load.
      continue;
    }
    base::Value::Dict response;
    response.Set("name", extension->name());
    response.Set("extensionId", extension_id);
    if (extensions::OptionsPageInfo::HasOptionsPage(extension)) {
      response.Set(
          "optionsPage",
          extensions::OptionsPageInfo::GetOptionsPage(extension).spec());
    }
    responses.Append(std::move(response));
  }

  FireWebUIListener("tts-extensions-updated", responses);
}

void TtsHandler::HandleGetDisplayNameForLocale(const base::Value::List& args) {
  CHECK_EQ(2U, args.size());
  const std::string callback_id = args[0].GetString();
  const std::string locale = args[1].GetString();

  const std::u16string display_name = l10n_util::GetDisplayNameForLocale(
      locale, g_browser_process->GetApplicationLocale(), true);

  AllowJavascript();
  ResolveJavascriptCallback(callback_id, base::UTF16ToUTF8(display_name));
}

void TtsHandler::HandleGetApplicationLocale(const base::Value::List& args) {
  CHECK_EQ(1U, args.size());
  const std::string callback_id = args[0].GetString();

  const std::string& application_locale =
      g_browser_process->GetApplicationLocale();

  AllowJavascript();
  ResolveJavascriptCallback(callback_id, application_locale);
}

void TtsHandler::OnVoicesChanged() {
  content::TtsController* tts_controller =
      content::TtsController::GetInstance();
  std::vector<content::VoiceData> voices;
  tts_controller->GetVoices(Profile::FromWebUI(web_ui()), GURL(), &voices);
  const std::string& app_locale = g_browser_process->GetApplicationLocale();
  base::Value::List responses;
  for (const auto& voice : voices) {
    base::Value::Dict response;
    int language_score = GetVoiceLangMatchScore(&voice, app_locale);
    std::string language_code;
    if (voice.lang.empty()) {
      language_code = "noLanguageCode";
      response.Set(
          "displayLanguage",
          l10n_util::GetStringUTF8(IDS_TEXT_TO_SPEECH_SETTINGS_NO_LANGUAGE));
    } else {
      language_code = l10n_util::GetLanguage(voice.lang);
      response.Set(
          "displayLanguage",
          l10n_util::GetDisplayNameForLocale(
              language_code, g_browser_process->GetApplicationLocale(), true));
    }
    response.Set("name", voice.name);
    response.Set("remote", voice.remote);
    response.Set("languageCode", language_code);
    response.Set("fullLanguageCode", voice.lang);
    response.Set("languageScore", language_score);
    response.Set("extensionId", voice.engine_id);
    responses.Append(std::move(response));
  }
  AllowJavascript();
  FireWebUIListener("all-voice-data-updated", responses);

  // Also refresh the TTS extensions in case they have changed.
  HandleGetTtsExtensions(base::Value::List());
}

void TtsHandler::RegisterMessages() {
  SettingsWithTtsPreviewHandler::RegisterMessages();
  web_ui()->RegisterMessageCallback(
      "getTtsExtensions",
      base::BindRepeating(&TtsHandler::HandleGetTtsExtensions,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getDisplayNameForLocale",
      base::BindRepeating(&TtsHandler::HandleGetDisplayNameForLocale,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getApplicationLocale",
      base::BindRepeating(&TtsHandler::HandleGetApplicationLocale,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "previewTtsVoice",
      base::BindRepeating(&SettingsWithTtsPreviewHandler::HandlePreviewTtsVoice,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "wakeTtsEngine",
      base::BindRepeating(&TtsHandler::WakeTtsEngine, base::Unretained(this)));
}

int TtsHandler::GetVoiceLangMatchScore(const content::VoiceData* voice,
                                       const std::string& app_locale) {
  if (voice->lang.empty() || app_locale.empty()) {
    return 0;
  }
  if (voice->lang == app_locale) {
    return 2;
  }
  return l10n_util::GetLanguage(voice->lang) ==
                 l10n_util::GetLanguage(app_locale)
             ? 1
             : 0;
}

void TtsHandler::WakeTtsEngine(const base::Value::List& args) {
  Profile* profile = Profile::FromWebUI(web_ui());
  TtsExtensionEngine::GetInstance()->LoadBuiltInTtsEngine(profile);
  extensions::ProcessManager::Get(profile)->WakeEventPage(
      extension_misc::kGoogleSpeechSynthesisExtensionId,
      base::BindOnce(&TtsHandler::OnTtsEngineAwake,
                     weak_factory_.GetWeakPtr()));
}

void TtsHandler::OnTtsEngineAwake(bool success) {
  OnVoicesChanged();
}

GURL TtsHandler::GetSourceURL() const {
  return GURL(chrome::GetOSSettingsUrl(
      chromeos::settings::mojom::kTextToSpeechSubpagePath));
}

}  // namespace ash::settings