// Copyright 2012 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/ash/extensions/input_method_api.h"
#include <stddef.h>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/lazy_instance.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/extensions/dictionary_event_router.h"
#include "chrome/browser/ash/extensions/ime_menu_event_router.h"
#include "chrome/browser/ash/extensions/input_method_event_router.h"
#include "chrome/browser/ash/extensions/language_packs/language_pack_event_router.h"
#include "chrome/browser/ash/extensions/language_packs/language_packs_extensions_util.h"
#include "chrome/browser/ash/input_method/autocorrect_manager.h"
#include "chrome/browser/ash/input_method/native_input_method_engine.h"
#include "chrome/browser/ash/url_handler/os_url_handler.h"
#include "chrome/browser/extensions/api/input_ime/input_ime_api.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/spellchecker/spellcheck_factory.h"
#include "chrome/browser/spellchecker/spellcheck_service.h"
#include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/extensions/api/input_method_private.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/language_packs/handwriting.h"
#include "chromeos/ash/components/language_packs/language_pack_manager.h"
#include "chromeos/components/kiosk/kiosk_utils.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "extensions/browser/extension_function_registry.h"
#include "extensions/browser/extension_system.h"
#include "ui/base/ime/ash/extension_ime_util.h"
#include "ui/base/ime/ash/ime_bridge.h"
#include "ui/base/ime/ash/ime_keyboard.h"
#include "ui/base/ime/ash/input_method_descriptor.h"
#include "ui/base/ime/ash/input_method_manager.h"
#include "ui/base/ime/ash/input_method_util.h"
#include "ui/base/window_open_disposition.h"
namespace {
namespace input_method_private = extensions::api::input_method_private;
namespace AddWordToDictionary =
extensions::api::input_method_private::AddWordToDictionary;
namespace SetCurrentInputMethod =
extensions::api::input_method_private::SetCurrentInputMethod;
namespace SwitchToLastUsedInputMethod =
extensions::api::input_method_private::SwitchToLastUsedInputMethod;
namespace SetXkbLayout = extensions::api::input_method_private::SetXkbLayout;
namespace OpenOptionsPage =
extensions::api::input_method_private::OpenOptionsPage;
namespace OnChanged = extensions::api::input_method_private::OnChanged;
namespace OnDictionaryChanged =
extensions::api::input_method_private::OnDictionaryChanged;
namespace OnDictionaryLoaded =
extensions::api::input_method_private::OnDictionaryLoaded;
namespace OnImeMenuActivationChanged =
extensions::api::input_method_private::OnImeMenuActivationChanged;
namespace OnImeMenuListChanged =
extensions::api::input_method_private::OnImeMenuListChanged;
namespace OnImeMenuItemsChanged =
extensions::api::input_method_private::OnImeMenuItemsChanged;
namespace GetSurroundingText =
extensions::api::input_method_private::GetSurroundingText;
namespace GetSettings = extensions::api::input_method_private::GetSettings;
namespace SetSettings = extensions::api::input_method_private::SetSettings;
namespace SetCompositionRange =
extensions::api::input_method_private::SetCompositionRange;
namespace OnInputMethodOptionsChanged =
extensions::api::input_method_private::OnInputMethodOptionsChanged;
namespace OnAutocorrect = extensions::api::input_method_private::OnAutocorrect;
namespace GetLanguagePackStatus =
extensions::api::input_method_private::GetLanguagePackStatus;
namespace OnLanguagePackStatusChanged =
extensions::api::input_method_private::OnLanguagePackStatusChanged;
using ::ash::input_method::InputMethodEngine;
// Prefix, which is used by XKB.
const char kXkbPrefix[] = "xkb:";
const char kErrorFailToShowInputView[] =
"Unable to show the input view window because the keyboard is not enabled.";
const char kErrorFailToHideInputView[] =
"Unable to hide the input view window because the keyboard is not enabled.";
const char kErrorRouterNotAvailable[] = "The router is not available.";
const char kErrorInvalidInputMethod[] = "Input method not found.";
const char kErrorSpellCheckNotAvailable[] =
"Spellcheck service is not available.";
const char kErrorCustomDictionaryNotLoaded[] =
"Custom dictionary is not loaded yet.";
const char kErrorInvalidWord[] = "Unable to add invalid word to dictionary.";
const char kErrorInputContextHandlerNotAvailable[] =
"Input context handler is not available.";
const char kErrorInvalidParametersForGetSurroundingText[] =
"Invalid negative parameters for GetSurroundingText.";
InputMethodEngine* GetEngineIfActive(content::BrowserContext* browser_context,
const std::string& extension_id,
std::string* error) {
Profile* profile = Profile::FromBrowserContext(browser_context);
extensions::InputImeEventRouter* event_router =
extensions::GetInputImeEventRouter(profile);
DCHECK(event_router) << kErrorRouterNotAvailable;
InputMethodEngine* engine =
event_router->GetEngineIfActive(extension_id, error);
return engine;
}
} // namespace
namespace extensions {
ExtensionFunction::ResponseAction
InputMethodPrivateGetInputMethodConfigFunction::Run() {
base::Value::Dict output;
output.Set("isPhysicalKeyboardAutocorrectEnabled", true);
output.Set("isImeMenuActivated",
Profile::FromBrowserContext(browser_context())
->GetPrefs()
->GetBoolean(prefs::kLanguageImeMenuActivated));
return RespondNow(WithArguments(std::move(output)));
}
ExtensionFunction::ResponseAction
InputMethodPrivateGetCurrentInputMethodFunction::Run() {
auto* manager = ash::input_method::InputMethodManager::Get();
return RespondNow(WithArguments(
manager->GetActiveIMEState()->GetCurrentInputMethod().id()));
}
ExtensionFunction::ResponseAction
InputMethodPrivateSetCurrentInputMethodFunction::Run() {
std::optional<SetCurrentInputMethod::Params> params =
SetCurrentInputMethod::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
scoped_refptr<ash::input_method::InputMethodManager::State> ime_state =
ash::input_method::InputMethodManager::Get()->GetActiveIMEState();
const std::vector<std::string>& input_methods =
ime_state->GetEnabledInputMethodIds();
for (const auto& input_method : input_methods) {
if (input_method == params->input_method_id) {
ime_state->ChangeInputMethod(params->input_method_id,
false /* show_message */);
return RespondNow(NoArguments());
}
}
return RespondNow(Error(InformativeError(
base::StringPrintf("%s Input Method: %s", kErrorInvalidInputMethod,
params->input_method_id.c_str()),
static_function_name())));
}
ExtensionFunction::ResponseAction
InputMethodPrivateSwitchToLastUsedInputMethodFunction::Run() {
scoped_refptr<ash::input_method::InputMethodManager::State> ime_state =
ash::input_method::InputMethodManager::Get()->GetActiveIMEState();
ime_state->SwitchToLastUsedInputMethod();
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateGetInputMethodsFunction::Run() {
base::Value::List output;
auto* manager = ash::input_method::InputMethodManager::Get();
ash::input_method::InputMethodUtil* util = manager->GetInputMethodUtil();
scoped_refptr<ash::input_method::InputMethodManager::State> ime_state =
manager->GetActiveIMEState();
ash::input_method::InputMethodDescriptors input_methods =
ime_state->GetEnabledInputMethodsSortedByLocalizedDisplayNames();
for (size_t i = 0; i < input_methods.size(); ++i) {
const ash::input_method::InputMethodDescriptor& input_method =
input_methods[i];
base::Value::Dict val;
val.Set("id", input_method.id());
val.Set("name", util->GetInputMethodLongName(input_method));
val.Set("indicator", input_method.GetIndicator());
output.Append(std::move(val));
}
return RespondNow(WithArguments(std::move(output)));
}
ExtensionFunction::ResponseAction
InputMethodPrivateFetchAllDictionaryWordsFunction::Run() {
SpellcheckService* spellcheck =
SpellcheckServiceFactory::GetForContext(browser_context());
if (!spellcheck) {
return RespondNow(Error(InformativeError(kErrorSpellCheckNotAvailable,
static_function_name())));
}
SpellcheckCustomDictionary* dictionary = spellcheck->GetCustomDictionary();
if (!dictionary->IsLoaded()) {
return RespondNow(Error(InformativeError(kErrorCustomDictionaryNotLoaded,
static_function_name())));
}
const std::set<std::string>& words = dictionary->GetWords();
base::Value::List output;
for (const auto& word : words) {
output.Append(word);
}
return RespondNow(WithArguments(std::move(output)));
}
ExtensionFunction::ResponseAction
InputMethodPrivateAddWordToDictionaryFunction::Run() {
std::optional<AddWordToDictionary::Params> params =
AddWordToDictionary::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
SpellcheckService* spellcheck =
SpellcheckServiceFactory::GetForContext(browser_context());
if (!spellcheck) {
return RespondNow(Error(InformativeError(kErrorSpellCheckNotAvailable,
static_function_name())));
}
SpellcheckCustomDictionary* dictionary = spellcheck->GetCustomDictionary();
if (!dictionary->IsLoaded()) {
return RespondNow(Error(InformativeError(kErrorCustomDictionaryNotLoaded,
static_function_name())));
}
if (dictionary->AddWord(params->word))
return RespondNow(NoArguments());
// Invalid words:
// - Already in the dictionary.
// - Not a UTF8 string.
// - Longer than 99 bytes (kMaxCustomDictionaryWordBytes).
// - Leading/trailing whitespace.
// - Empty.
return RespondNow(Error(
InformativeError(base::StringPrintf("%s. Word: %s", kErrorInvalidWord,
params->word.c_str()),
static_function_name())));
}
ExtensionFunction::ResponseAction
InputMethodPrivateSetXkbLayoutFunction::Run() {
std::optional<SetXkbLayout::Params> params =
SetXkbLayout::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
auto* manager = ash::input_method::InputMethodManager::Get();
ash::input_method::ImeKeyboard* keyboard = manager->GetImeKeyboard();
keyboard->SetCurrentKeyboardLayoutByName(params->xkb_name, base::DoNothing());
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateShowInputViewFunction::Run() {
auto* keyboard_client = ChromeKeyboardControllerClient::Get();
if (!keyboard_client->is_keyboard_enabled()) {
return RespondNow(Error(kErrorFailToShowInputView));
}
keyboard_client->ShowKeyboard();
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateHideInputViewFunction::Run() {
auto* keyboard_client = ChromeKeyboardControllerClient::Get();
if (!keyboard_client->is_keyboard_enabled()) {
return RespondNow(Error(kErrorFailToHideInputView));
}
keyboard_client->HideKeyboard(ash::HideReason::kUser);
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateOpenOptionsPageFunction::Run() {
std::optional<OpenOptionsPage::Params> params =
OpenOptionsPage::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
scoped_refptr<ash::input_method::InputMethodManager::State> ime_state =
ash::input_method::InputMethodManager::Get()->GetActiveIMEState();
const ash::input_method::InputMethodDescriptor* ime =
ime_state->GetInputMethodFromId(params->input_method_id);
if (!ime)
return RespondNow(Error(InformativeError(
base::StringPrintf("%s Input Method: %s", kErrorInvalidInputMethod,
params->input_method_id.c_str()),
static_function_name())));
const GURL& options_page_url = ime->options_page_url();
if (!options_page_url.is_empty()) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// If Lacros is the only browser, open the options page in an Ash app window
// instead of a regular Ash browser window.
if (!crosapi::browser_util::IsAshWebBrowserEnabled() &&
!chromeos::IsKioskSession()) {
bool launched = ash::TryLaunchOsUrlHandler(options_page_url);
DCHECK(launched);
return RespondNow(NoArguments());
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
content::WebContents* web_contents = GetSenderWebContents();
if (web_contents) {
Browser* browser = chrome::FindBrowserWithTab(web_contents);
content::OpenURLParams url_params(options_page_url, content::Referrer(),
WindowOpenDisposition::SINGLETON_TAB,
ui::PAGE_TRANSITION_LINK, false);
browser->OpenURL(url_params, /*navigation_handle_callback=*/{});
}
}
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateGetSurroundingTextFunction::Run() {
ash::TextInputTarget* input_context =
ash::IMEBridge::Get()->GetInputContextHandler();
if (!input_context)
return RespondNow(Error(InformativeError(
kErrorInputContextHandlerNotAvailable, static_function_name())));
std::optional<GetSurroundingText::Params> params =
GetSurroundingText::Params::Create(args());
if (params->before_length < 0 || params->after_length < 0)
return RespondNow(Error(InformativeError(
base::StringPrintf("%s before_length = %d, after_length = %d.",
kErrorInvalidParametersForGetSurroundingText,
params->before_length, params->after_length),
static_function_name())));
uint32_t param_before_length = (uint32_t)params->before_length;
uint32_t param_after_length = (uint32_t)params->after_length;
ash::SurroundingTextInfo info = input_context->GetSurroundingTextInfo();
if (!info.selection_range.IsValid())
return RespondNow(WithArguments(base::Value()));
base::Value::Dict ret;
uint32_t selection_start = info.selection_range.start();
uint32_t selection_end = info.selection_range.end();
// Makes sure |selection_start| is less or equals to |selection_end|.
if (selection_start > selection_end)
std::swap(selection_start, selection_end);
uint32_t text_before_end = selection_start;
uint32_t text_before_start = text_before_end > param_before_length
? text_before_end - param_before_length
: 0;
uint32_t text_after_start = selection_end;
uint32_t text_after_end =
text_after_start + param_after_length < info.surrounding_text.length()
? text_after_start + param_after_length
: info.surrounding_text.length();
ret.Set("before",
info.surrounding_text.substr(text_before_start,
text_before_end - text_before_start));
ret.Set("selected", info.surrounding_text.substr(
text_before_end, text_after_start - text_before_end));
ret.Set("after", info.surrounding_text.substr(
text_after_start, text_after_end - text_after_start));
return RespondNow(WithArguments(std::move(ret)));
}
ExtensionFunction::ResponseAction InputMethodPrivateGetSettingsFunction::Run() {
const auto params = GetSettings::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
const base::Value::Dict& input_methods =
Profile::FromBrowserContext(browser_context())
->GetPrefs()
->GetDict(prefs::kLanguageInputMethodSpecificSettings);
const base::Value* engine_result =
input_methods.FindByDottedPath(params->engine_id);
base::Value result;
if (engine_result)
result = engine_result->Clone();
return RespondNow(WithArguments(std::move(result)));
}
ExtensionFunction::ResponseAction InputMethodPrivateSetSettingsFunction::Run() {
const auto params = SetSettings::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
ScopedDictPrefUpdate update(
Profile::FromBrowserContext(browser_context())->GetPrefs(),
prefs::kLanguageInputMethodSpecificSettings);
update->SetByDottedPath(params->engine_id, params->settings.ToValue());
// The router will only send the event to extensions that are listening.
extensions::EventRouter* router =
extensions::EventRouter::Get(browser_context());
if (router->HasEventListener(OnInputMethodOptionsChanged::kEventName)) {
auto event = std::make_unique<extensions::Event>(
extensions::events::INPUT_IME_ON_INPUT_METHOD_OPTIONS_CHANGED,
OnInputMethodOptionsChanged::kEventName,
OnInputMethodOptionsChanged::Create(params->engine_id),
browser_context());
router->BroadcastEvent(std::move(event));
}
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateSetCompositionRangeFunction::Run() {
std::string error;
InputMethodEngine* engine =
GetEngineIfActive(browser_context(), extension_id(), &error);
if (!engine)
return RespondNow(Error(InformativeError(error, static_function_name())));
const auto parent_params = SetCompositionRange::Params::Create(args());
const auto& params = parent_params->parameters;
std::vector<InputMethodEngine::SegmentInfo> segments;
if (params.segments) {
for (const auto& segments_arg : *params.segments) {
InputMethodEngine::SegmentInfo segment_info;
segment_info.start = segments_arg.start;
segment_info.end = segments_arg.end;
switch (segments_arg.style) {
case input_method_private::UnderlineStyle::kUnderline:
segment_info.style = InputMethodEngine::SEGMENT_STYLE_UNDERLINE;
break;
case input_method_private::UnderlineStyle::kDoubleUnderline:
segment_info.style =
InputMethodEngine::SEGMENT_STYLE_DOUBLE_UNDERLINE;
break;
case input_method_private::UnderlineStyle::kNoUnderline:
segment_info.style = InputMethodEngine::SEGMENT_STYLE_NO_UNDERLINE;
break;
case input_method_private::UnderlineStyle::kNone:
EXTENSION_FUNCTION_VALIDATE(false);
break;
}
segments.push_back(segment_info);
}
}
if (!engine->InputMethodEngine::SetCompositionRange(
params.context_id, params.selection_before, params.selection_after,
segments, &error)) {
return RespondNow(Error(InformativeError(error, static_function_name())));
}
return RespondNow(WithArguments(base::Value(true)));
}
ExtensionFunction::ResponseAction InputMethodPrivateResetFunction::Run() {
std::string error;
InputMethodEngine* engine =
GetEngineIfActive(browser_context(), extension_id(), &error);
if (!engine)
return RespondNow(Error(InformativeError(error, static_function_name())));
engine->Reset();
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateOnAutocorrectFunction::Run() {
std::optional<OnAutocorrect::Params> parent_params =
OnAutocorrect::Params::Create(args());
const OnAutocorrect::Params::Parameters& params = parent_params->parameters;
std::string error;
ash::input_method::NativeInputMethodEngine* engine =
static_cast<ash::input_method::NativeInputMethodEngine*>(
GetEngineIfActive(Profile::FromBrowserContext(browser_context()),
extension_id(), &error));
if (!engine)
return RespondNow(Error(InformativeError(error, static_function_name())));
// `typed_word` and `corrected_word` are both originally encoded in UTF-16 by
// JavaScript, but the extensions bindings will convert them to UTF-8 without
// changing `start_index` (which is in UTF-16 code units). Hence, convert the
// two strngs back without changing `start_index`.
engine->OnAutocorrect(base::UTF8ToUTF16(params.typed_word),
base::UTF8ToUTF16(params.corrected_word),
params.start_index);
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateNotifyInputMethodReadyForTestingFunction::Run() {
std::string error;
ash::input_method::InputMethodEngine* engine = GetEngineIfActive(
Profile::FromBrowserContext(browser_context()), extension_id(), &error);
if (!engine)
return RespondNow(Error(InformativeError(error, static_function_name())));
engine->NotifyInputMethodExtensionReadyForTesting(); // IN-TEST
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
InputMethodPrivateGetLanguagePackStatusFunction::Run() {
std::optional<GetLanguagePackStatus::Params> params =
GetLanguagePackStatus::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params);
// This currently only handles handwriting, but this should (in theory)
// handle a collection of language packs once input methods depend on multiple
// language packs.
auto* manager = ash::input_method::InputMethodManager::Get();
std::optional<std::string> handwriting_locale =
ash::language_packs::MapInputMethodIdToHandwritingLocale(
manager->GetInputMethodUtil(), params->input_method_id);
// If there are no language packs associated with an input method, installed
// is returned.
if (!handwriting_locale.has_value()) {
return RespondNow(WithArguments(
ToString(input_method_private::LanguagePackStatus::kInstalled)));
}
if (!ash::language_packs::HandwritingLocaleToDlc(*handwriting_locale)
.has_value()) {
// We obtained a handwriting locale, but it doesn't have an associated
// language pack. This means that there are no language packs associated
// with this input method.
//
// "en" is the only handwriting locale which does not have an associated
// language pack (as of writing).
if (*handwriting_locale != "en") {
LOG(DFATAL) << "Got non-English handwriting locale from manifest which "
"does not have DLC: "
<< *handwriting_locale;
}
return RespondNow(WithArguments(
ToString(input_method_private::LanguagePackStatus::kInstalled)));
}
ash::language_packs::LanguagePackManager::GetPackState(
ash::language_packs::kHandwritingFeatureId, *handwriting_locale,
// This `BindOnce` into a `.Then` is required to avoid having a method on
// this class which has a language pack type in its function signature,
// which would cause language packs to be included in this file's headers,
// which would cause a slew of dependency issues.
base::BindOnce(&chromeos::LanguagePackResultToExtensionStatus)
.Then(
base::BindOnce(&InputMethodPrivateGetLanguagePackStatusFunction::
OnGetLanguagePackStatusComplete,
this)));
return RespondLater();
}
void InputMethodPrivateGetLanguagePackStatusFunction::
OnGetLanguagePackStatusComplete(
const input_method_private::LanguagePackStatus status) {
base::Value::List results =
input_method_private::GetLanguagePackStatus::Results::Create(status);
Respond(ArgumentList(std::move(results)));
}
InputMethodAPI::InputMethodAPI(content::BrowserContext* context)
: context_(context) {
EventRouter::Get(context_)->RegisterObserver(this, OnChanged::kEventName);
EventRouter::Get(context_)
->RegisterObserver(this, OnDictionaryChanged::kEventName);
EventRouter::Get(context_)
->RegisterObserver(this, OnDictionaryLoaded::kEventName);
EventRouter::Get(context_)
->RegisterObserver(this, OnImeMenuActivationChanged::kEventName);
EventRouter::Get(context_)
->RegisterObserver(this, OnImeMenuListChanged::kEventName);
EventRouter::Get(context_)
->RegisterObserver(this, OnImeMenuItemsChanged::kEventName);
EventRouter::Get(context_)->RegisterObserver(
this, OnLanguagePackStatusChanged::kEventName);
ExtensionFunctionRegistry& registry =
ExtensionFunctionRegistry::GetInstance();
registry.RegisterFunction<InputMethodPrivateGetInputMethodConfigFunction>();
registry.RegisterFunction<InputMethodPrivateGetCurrentInputMethodFunction>();
registry.RegisterFunction<InputMethodPrivateSetCurrentInputMethodFunction>();
registry.RegisterFunction<InputMethodPrivateGetInputMethodsFunction>();
registry
.RegisterFunction<InputMethodPrivateFetchAllDictionaryWordsFunction>();
registry.RegisterFunction<InputMethodPrivateAddWordToDictionaryFunction>();
registry.RegisterFunction<InputMethodPrivateOpenOptionsPageFunction>();
}
InputMethodAPI::~InputMethodAPI() = default;
// static
std::string InputMethodAPI::GetInputMethodForXkb(const std::string& xkb_id) {
std::string xkb_prefix =
ash::extension_ime_util::GetInputMethodIDByEngineID(kXkbPrefix);
size_t prefix_length = xkb_prefix.length();
DCHECK(xkb_id.substr(0, prefix_length) == xkb_prefix);
return xkb_id.substr(prefix_length);
}
void InputMethodAPI::Shutdown() {
EventRouter::Get(context_)->UnregisterObserver(this);
}
void InputMethodAPI::OnListenerAdded(
const extensions::EventListenerInfo& details) {
if (details.event_name == OnChanged::kEventName &&
!input_method_event_router_.get()) {
input_method_event_router_ =
std::make_unique<chromeos::ExtensionInputMethodEventRouter>(context_);
} else if (details.event_name == OnDictionaryChanged::kEventName ||
details.event_name == OnDictionaryLoaded::kEventName) {
if (!dictionary_event_router_.get()) {
dictionary_event_router_ =
std::make_unique<chromeos::ExtensionDictionaryEventRouter>(context_);
}
if (details.event_name == OnDictionaryLoaded::kEventName) {
dictionary_event_router_->DispatchLoadedEventIfLoaded();
}
} else if ((details.event_name == OnImeMenuActivationChanged::kEventName ||
details.event_name == OnImeMenuListChanged::kEventName ||
details.event_name == OnImeMenuItemsChanged::kEventName) &&
!ime_menu_event_router_.get()) {
ime_menu_event_router_ =
std::make_unique<chromeos::ExtensionImeMenuEventRouter>(context_);
} else if (details.event_name == OnLanguagePackStatusChanged::kEventName &&
!language_pack_event_router_.get()) {
language_pack_event_router_ =
std::make_unique<chromeos::LanguagePackEventRouter>(context_);
}
}
static base::LazyInstance<
BrowserContextKeyedAPIFactory<InputMethodAPI>>::DestructorAtExit g_factory =
LAZY_INSTANCE_INITIALIZER;
// static
BrowserContextKeyedAPIFactory<InputMethodAPI>*
InputMethodAPI::GetFactoryInstance() {
return g_factory.Pointer();
}
} // namespace extensions