| // 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/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/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()) { |
| 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 |