blob: d63b0d824555e05054c8197d917c90f815cfa4b6 [file] [log] [blame]
// 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