| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/extensions/input_method_api.h" |
| |
| #include <stddef.h> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/shell.h" |
| #include "base/command_line.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/chromeos/extensions/dictionary_event_router.h" |
| #include "chrome/browser/chromeos/extensions/ime_menu_event_router.h" |
| #include "chrome/browser/chromeos/extensions/input_method_event_router.h" |
| #include "chrome/browser/chromeos/input_method/input_method_util.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/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/extensions/api/input_method_private.h" |
| #include "chrome/common/pref_names.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "components/browser_sync/profile_sync_service.h" |
| #include "components/prefs/pref_service.h" |
| #include "extensions/browser/extension_function_registry.h" |
| #include "extensions/browser/extension_system.h" |
| #include "ui/base/ime/chromeos/extension_ime_util.h" |
| #include "ui/base/ime/chromeos/ime_keyboard.h" |
| #include "ui/base/ime/chromeos/input_method_descriptor.h" |
| #include "ui/base/ime/chromeos/input_method_manager.h" |
| #include "ui/keyboard/keyboard_controller.h" |
| #include "ui/keyboard/keyboard_util.h" |
| |
| namespace AddWordToDictionary = |
| extensions::api::input_method_private::AddWordToDictionary; |
| namespace SetCurrentInputMethod = |
| extensions::api::input_method_private::SetCurrentInputMethod; |
| 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 { |
| |
| // Prefix, which is used by XKB. |
| const char kXkbPrefix[] = "xkb:"; |
| const char kErrorFailToShowInputView[] = |
| "Unable to show the input view window."; |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateGetInputMethodConfigFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| std::unique_ptr<base::DictionaryValue> output(new base::DictionaryValue()); |
| output->SetBoolean( |
| "isPhysicalKeyboardAutocorrectEnabled", |
| !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kDisablePhysicalKeyboardAutocorrect)); |
| output->SetBoolean("isImeMenuActivated", |
| base::FeatureList::IsEnabled(features::kOptInImeMenu) && |
| Profile::FromBrowserContext(browser_context()) |
| ->GetPrefs() |
| ->GetBoolean(prefs::kLanguageImeMenuActivated)); |
| return RespondNow(OneArgument(std::move(output))); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateGetCurrentInputMethodFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| chromeos::input_method::InputMethodManager* manager = |
| chromeos::input_method::InputMethodManager::Get(); |
| return RespondNow(OneArgument(base::MakeUnique<base::StringValue>( |
| manager->GetActiveIMEState()->GetCurrentInputMethod().id()))); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateSetCurrentInputMethodFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| std::unique_ptr<SetCurrentInputMethod::Params> params( |
| SetCurrentInputMethod::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| scoped_refptr<chromeos::input_method::InputMethodManager::State> ime_state = |
| chromeos::input_method::InputMethodManager::Get()->GetActiveIMEState(); |
| const std::vector<std::string>& input_methods = |
| ime_state->GetActiveInputMethodIds(); |
| for (size_t i = 0; i < input_methods.size(); ++i) { |
| const std::string& input_method = input_methods[i]; |
| if (input_method == params->input_method_id) { |
| ime_state->ChangeInputMethod(params->input_method_id, |
| false /* show_message */); |
| return RespondNow(NoArguments()); |
| } |
| } |
| return RespondNow(Error("Invalid input method id.")); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateGetInputMethodsFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| std::unique_ptr<base::ListValue> output(new base::ListValue()); |
| chromeos::input_method::InputMethodManager* manager = |
| chromeos::input_method::InputMethodManager::Get(); |
| chromeos::input_method::InputMethodUtil* util = manager->GetInputMethodUtil(); |
| scoped_refptr<chromeos::input_method::InputMethodManager::State> ime_state = |
| manager->GetActiveIMEState(); |
| std::unique_ptr<chromeos::input_method::InputMethodDescriptors> |
| input_methods = ime_state->GetActiveInputMethods(); |
| for (size_t i = 0; i < input_methods->size(); ++i) { |
| const chromeos::input_method::InputMethodDescriptor& input_method = |
| (*input_methods)[i]; |
| auto val = base::MakeUnique<base::DictionaryValue>(); |
| val->SetString("id", input_method.id()); |
| val->SetString("name", util->GetInputMethodLongName(input_method)); |
| val->SetString("indicator", util->GetInputMethodShortName(input_method)); |
| output->Append(std::move(val)); |
| } |
| return RespondNow(OneArgument(std::move(output))); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateFetchAllDictionaryWordsFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| SpellcheckService* spellcheck = SpellcheckServiceFactory::GetForContext( |
| context_); |
| if (!spellcheck) { |
| return RespondNow(Error("Spellcheck service not available.")); |
| } |
| SpellcheckCustomDictionary* dictionary = spellcheck->GetCustomDictionary(); |
| if (!dictionary->IsLoaded()) { |
| return RespondNow(Error("Custom dictionary not loaded yet.")); |
| } |
| |
| const std::set<std::string>& words = dictionary->GetWords(); |
| std::unique_ptr<base::ListValue> output(new base::ListValue()); |
| for (auto it = words.begin(); it != words.end(); ++it) { |
| output->AppendString(*it); |
| } |
| return RespondNow(OneArgument(std::move(output))); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateAddWordToDictionaryFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| std::unique_ptr<AddWordToDictionary::Params> params( |
| AddWordToDictionary::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| SpellcheckService* spellcheck = SpellcheckServiceFactory::GetForContext( |
| context_); |
| if (!spellcheck) { |
| return RespondNow(Error("Spellcheck service not available.")); |
| } |
| SpellcheckCustomDictionary* dictionary = spellcheck->GetCustomDictionary(); |
| if (!dictionary->IsLoaded()) { |
| return RespondNow(Error("Custom dictionary not loaded yet.")); |
| } |
| |
| if (dictionary->AddWord(params->word)) |
| return RespondNow(NoArguments()); |
| // Invalid words: |
| // - Already in the dictionary. |
| // - Not a UTF8 string. |
| // - Longer than 99 bytes (MAX_CUSTOM_DICTIONARY_WORD_BYTES). |
| // - Leading/trailing whitespace. |
| // - Empty. |
| return RespondNow(Error("Unable to add invalid word to dictionary.")); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateGetEncryptSyncEnabledFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| browser_sync::ProfileSyncService* profile_sync_service = |
| ProfileSyncServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser_context())); |
| if (!profile_sync_service) |
| return RespondNow(Error("Sync service is not ready for current profile.")); |
| std::unique_ptr<base::Value> ret(new base::FundamentalValue( |
| profile_sync_service->IsEncryptEverythingEnabled())); |
| return RespondNow(OneArgument(std::move(ret))); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateSetXkbLayoutFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| std::unique_ptr<SetXkbLayout::Params> params( |
| SetXkbLayout::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| chromeos::input_method::InputMethodManager* manager = |
| chromeos::input_method::InputMethodManager::Get(); |
| chromeos::input_method::ImeKeyboard* keyboard = manager->GetImeKeyboard(); |
| keyboard->SetCurrentKeyboardLayoutByName(params->xkb_name); |
| return RespondNow(NoArguments()); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateShowInputViewFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| keyboard::KeyboardController* keyboard_controller = |
| keyboard::KeyboardController::GetInstance(); |
| if (keyboard_controller) { |
| keyboard_controller->ShowKeyboard(false); |
| return RespondNow(NoArguments()); |
| } |
| |
| if (keyboard::IsKeyboardEnabled()) |
| return RespondNow(Error(kErrorFailToShowInputView)); |
| |
| // Forcibly enables the a11y onscreen keyboard if there is on keyboard enabled |
| // for now. And re-disables it after showing once. |
| keyboard::SetAccessibilityKeyboardEnabled(true); |
| ash::Shell::GetInstance()->CreateKeyboard(); |
| keyboard_controller = keyboard::KeyboardController::GetInstance(); |
| if (!keyboard_controller) { |
| keyboard::SetAccessibilityKeyboardEnabled(false); |
| return RespondNow(Error(kErrorFailToShowInputView)); |
| } |
| keyboard_controller->ShowKeyboard(false); |
| keyboard::SetAccessibilityKeyboardEnabled(false); |
| return RespondNow(NoArguments()); |
| #endif |
| } |
| |
| ExtensionFunction::ResponseAction |
| InputMethodPrivateOpenOptionsPageFunction::Run() { |
| #if !defined(OS_CHROMEOS) |
| EXTENSION_FUNCTION_VALIDATE(false); |
| #else |
| std::unique_ptr<OpenOptionsPage::Params> params( |
| OpenOptionsPage::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(params.get()); |
| scoped_refptr<chromeos::input_method::InputMethodManager::State> ime_state = |
| chromeos::input_method::InputMethodManager::Get()->GetActiveIMEState(); |
| const chromeos::input_method::InputMethodDescriptor* ime = |
| ime_state->GetInputMethodFromId(params->input_method_id); |
| if (!ime) |
| return RespondNow(Error("IME not found: *", params->input_method_id)); |
| |
| content::WebContents* web_contents = GetSenderWebContents(); |
| if (web_contents) { |
| const GURL& options_page_url = ime->options_page_url(); |
| if (!options_page_url.is_empty()) { |
| Browser* browser = chrome::FindBrowserWithWebContents(web_contents); |
| content::OpenURLParams url_params(options_page_url, content::Referrer(), |
| WindowOpenDisposition::SINGLETON_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| browser->OpenURL(url_params); |
| } |
| } |
| return RespondNow(NoArguments()); |
| #endif |
| } |
| |
| 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); |
| ExtensionFunctionRegistry* registry = |
| ExtensionFunctionRegistry::GetInstance(); |
| registry->RegisterFunction<InputMethodPrivateGetInputMethodConfigFunction>(); |
| registry->RegisterFunction<InputMethodPrivateGetCurrentInputMethodFunction>(); |
| registry->RegisterFunction<InputMethodPrivateSetCurrentInputMethodFunction>(); |
| registry->RegisterFunction<InputMethodPrivateGetInputMethodsFunction>(); |
| registry |
| ->RegisterFunction<InputMethodPrivateFetchAllDictionaryWordsFunction>(); |
| registry->RegisterFunction<InputMethodPrivateAddWordToDictionaryFunction>(); |
| registry->RegisterFunction<InputMethodPrivateGetEncryptSyncEnabledFunction>(); |
| registry->RegisterFunction< |
| InputMethodPrivateNotifyImeMenuItemActivatedFunction>(); |
| registry->RegisterFunction<InputMethodPrivateOpenOptionsPageFunction>(); |
| } |
| |
| InputMethodAPI::~InputMethodAPI() { |
| } |
| |
| // static |
| std::string InputMethodAPI::GetInputMethodForXkb(const std::string& xkb_id) { |
| std::string xkb_prefix = |
| chromeos::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_.reset( |
| new chromeos::ExtensionInputMethodEventRouter(context_)); |
| } else if (details.event_name == OnDictionaryChanged::kEventName || |
| details.event_name == OnDictionaryLoaded::kEventName) { |
| if (!dictionary_event_router_.get()) { |
| dictionary_event_router_.reset( |
| new 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_.reset( |
| new chromeos::ExtensionImeMenuEventRouter(context_)); |
| } |
| } |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<InputMethodAPI> > |
| g_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<InputMethodAPI>* |
| InputMethodAPI::GetFactoryInstance() { |
| return g_factory.Pointer(); |
| } |
| |
| } // namespace extensions |