| // 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/extensions/api/input_ime/input_ime_api.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/lazy_instance.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/common/extensions/api/input_ime.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_service.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "ui/base/ime/ime_bridge.h" |
| #include "ui/keyboard/keyboard_util.h" |
| |
| namespace input_ime = extensions::api::input_ime; |
| namespace KeyEventHandled = extensions::api::input_ime::KeyEventHandled; |
| namespace SetComposition = extensions::api::input_ime::SetComposition; |
| namespace CommitText = extensions::api::input_ime::CommitText; |
| namespace SendKeyEvents = extensions::api::input_ime::SendKeyEvents; |
| using ui::IMEEngineHandlerInterface; |
| using input_method::InputMethodEngineBase; |
| |
| namespace { |
| const char kErrorEngineNotAvailable[] = "Engine is not available"; |
| const char kErrorSetKeyEventsFail[] = "Could not send key events"; |
| } |
| namespace ui { |
| |
| ImeObserver::ImeObserver(const std::string& extension_id, Profile* profile) |
| : extension_id_(extension_id), profile_(profile) {} |
| |
| void ImeObserver::OnActivate(const std::string& component_id) { |
| // Don't check whether the extension listens on onActivate event here. |
| // Send onActivate event to give the IME a chance to add their listeners. |
| if (extension_id_.empty()) |
| return; |
| |
| std::unique_ptr<base::ListValue> args(input_ime::OnActivate::Create( |
| component_id, input_ime::ParseScreenType(GetCurrentScreenType()))); |
| |
| DispatchEventToExtension(extensions::events::INPUT_IME_ON_ACTIVATE, |
| input_ime::OnActivate::kEventName, |
| std::move(args)); |
| } |
| |
| void ImeObserver::OnFocus( |
| const IMEEngineHandlerInterface::InputContext& context) { |
| if (extension_id_.empty() || !HasListener(input_ime::OnFocus::kEventName)) |
| return; |
| |
| input_ime::InputContext context_value; |
| context_value.context_id = context.id; |
| context_value.type = |
| input_ime::ParseInputContextType(ConvertInputContextType(context)); |
| context_value.auto_correct = ConvertInputContextAutoCorrect(context); |
| context_value.auto_complete = ConvertInputContextAutoComplete(context); |
| context_value.spell_check = ConvertInputContextSpellCheck(context); |
| context_value.should_do_learning = context.should_do_learning; |
| |
| std::unique_ptr<base::ListValue> args( |
| input_ime::OnFocus::Create(context_value)); |
| |
| DispatchEventToExtension(extensions::events::INPUT_IME_ON_FOCUS, |
| input_ime::OnFocus::kEventName, std::move(args)); |
| } |
| |
| void ImeObserver::OnBlur(int context_id) { |
| if (extension_id_.empty() || !HasListener(input_ime::OnBlur::kEventName)) |
| return; |
| |
| std::unique_ptr<base::ListValue> args(input_ime::OnBlur::Create(context_id)); |
| |
| DispatchEventToExtension(extensions::events::INPUT_IME_ON_BLUR, |
| input_ime::OnBlur::kEventName, std::move(args)); |
| } |
| |
| void ImeObserver::OnKeyEvent( |
| const std::string& component_id, |
| const InputMethodEngineBase::KeyboardEvent& event, |
| IMEEngineHandlerInterface::KeyEventDoneCallback key_data) { |
| if (extension_id_.empty()) |
| return; |
| |
| // If there is no listener for the event, no need to dispatch the event to |
| // extension. Instead, releases the key event for default system behavior. |
| if (!ShouldForwardKeyEvent()) { |
| // Continue processing the key event so that the physical keyboard can |
| // still work. |
| std::move(key_data).Run(false); |
| return; |
| } |
| |
| extensions::InputImeEventRouter* event_router = |
| extensions::GetInputImeEventRouter(profile_); |
| if (!event_router || !event_router->GetActiveEngine(extension_id_)) |
| return; |
| const std::string request_id = |
| event_router->GetActiveEngine(extension_id_) |
| ->AddRequest(component_id, std::move(key_data)); |
| |
| input_ime::KeyboardEvent key_data_value; |
| key_data_value.type = input_ime::ParseKeyboardEventType(event.type); |
| key_data_value.request_id = request_id; |
| if (!event.extension_id.empty()) |
| key_data_value.extension_id.reset(new std::string(event.extension_id)); |
| key_data_value.key = event.key; |
| key_data_value.code = event.code; |
| key_data_value.alt_key.reset(new bool(event.alt_key)); |
| key_data_value.ctrl_key.reset(new bool(event.ctrl_key)); |
| key_data_value.shift_key.reset(new bool(event.shift_key)); |
| key_data_value.caps_lock.reset(new bool(event.caps_lock)); |
| |
| std::unique_ptr<base::ListValue> args( |
| input_ime::OnKeyEvent::Create(component_id, key_data_value)); |
| |
| DispatchEventToExtension(extensions::events::INPUT_IME_ON_KEY_EVENT, |
| input_ime::OnKeyEvent::kEventName, std::move(args)); |
| } |
| |
| void ImeObserver::OnReset(const std::string& component_id) { |
| if (extension_id_.empty() || !HasListener(input_ime::OnReset::kEventName)) |
| return; |
| |
| std::unique_ptr<base::ListValue> args( |
| input_ime::OnReset::Create(component_id)); |
| |
| DispatchEventToExtension(extensions::events::INPUT_IME_ON_RESET, |
| input_ime::OnReset::kEventName, std::move(args)); |
| } |
| |
| void ImeObserver::OnDeactivated(const std::string& component_id) { |
| if (extension_id_.empty() || |
| !HasListener(input_ime::OnDeactivated::kEventName)) |
| return; |
| |
| std::unique_ptr<base::ListValue> args( |
| input_ime::OnDeactivated::Create(component_id)); |
| |
| DispatchEventToExtension(extensions::events::INPUT_IME_ON_DEACTIVATED, |
| input_ime::OnDeactivated::kEventName, |
| std::move(args)); |
| } |
| |
| // TODO(azurewei): This function implementation should be shared on all |
| // platforms, while with some changing on the current code on ChromeOS. |
| void ImeObserver::OnCompositionBoundsChanged( |
| const std::vector<gfx::Rect>& bounds) {} |
| |
| bool ImeObserver::IsInterestedInKeyEvent() const { |
| return ShouldForwardKeyEvent(); |
| } |
| |
| void ImeObserver::OnSurroundingTextChanged(const std::string& component_id, |
| const std::string& text, |
| int cursor_pos, |
| int anchor_pos, |
| int offset_pos) { |
| if (extension_id_.empty() || |
| !HasListener(input_ime::OnSurroundingTextChanged::kEventName)) |
| return; |
| |
| input_ime::OnSurroundingTextChanged::SurroundingInfo info; |
| info.text = text; |
| info.focus = cursor_pos; |
| info.anchor = anchor_pos; |
| info.offset = offset_pos; |
| std::unique_ptr<base::ListValue> args( |
| input_ime::OnSurroundingTextChanged::Create(component_id, info)); |
| |
| DispatchEventToExtension( |
| extensions::events::INPUT_IME_ON_SURROUNDING_TEXT_CHANGED, |
| input_ime::OnSurroundingTextChanged::kEventName, std::move(args)); |
| } |
| |
| bool ImeObserver::ShouldForwardKeyEvent() const { |
| // Only forward key events to extension if there are non-lazy listeners |
| // for onKeyEvent. Because if something wrong with the lazy background |
| // page which doesn't register listener for onKeyEvent, it will not handle |
| // the key events, and therefore, all key events will be eaten. |
| // This is for error-tolerance, and it means that onKeyEvent will never wake |
| // up lazy background page. |
| const extensions::EventListenerMap::ListenerList& listeners = |
| extensions::EventRouter::Get(profile_) |
| ->listeners() |
| .GetEventListenersByName(input_ime::OnKeyEvent::kEventName); |
| for (const std::unique_ptr<extensions::EventListener>& listener : listeners) { |
| if (listener->extension_id() == extension_id_ && !listener->IsLazy()) |
| return true; |
| } |
| return false; |
| } |
| |
| bool ImeObserver::HasListener(const std::string& event_name) const { |
| return extensions::EventRouter::Get(profile_)->HasEventListener(event_name); |
| } |
| |
| std::string ImeObserver::ConvertInputContextType( |
| ui::IMEEngineHandlerInterface::InputContext input_context) { |
| std::string input_context_type = "text"; |
| switch (input_context.type) { |
| case ui::TEXT_INPUT_TYPE_SEARCH: |
| input_context_type = "search"; |
| break; |
| case ui::TEXT_INPUT_TYPE_TELEPHONE: |
| input_context_type = "tel"; |
| break; |
| case ui::TEXT_INPUT_TYPE_URL: |
| input_context_type = "url"; |
| break; |
| case ui::TEXT_INPUT_TYPE_EMAIL: |
| input_context_type = "email"; |
| break; |
| case ui::TEXT_INPUT_TYPE_NUMBER: |
| input_context_type = "number"; |
| break; |
| case ui::TEXT_INPUT_TYPE_PASSWORD: |
| input_context_type = "password"; |
| break; |
| default: |
| input_context_type = "text"; |
| break; |
| } |
| return input_context_type; |
| } |
| |
| bool ImeObserver::ConvertInputContextAutoCorrect( |
| ui::IMEEngineHandlerInterface::InputContext input_context) { |
| return keyboard::GetKeyboardConfig().auto_correct && |
| !(input_context.flags & ui::TEXT_INPUT_FLAG_AUTOCORRECT_OFF); |
| } |
| |
| bool ImeObserver::ConvertInputContextAutoComplete( |
| ui::IMEEngineHandlerInterface::InputContext input_context) { |
| return keyboard::GetKeyboardConfig().auto_complete && |
| !(input_context.flags & ui::TEXT_INPUT_FLAG_AUTOCOMPLETE_OFF); |
| } |
| |
| bool ImeObserver::ConvertInputContextSpellCheck( |
| ui::IMEEngineHandlerInterface::InputContext input_context) { |
| return keyboard::GetKeyboardConfig().spell_check && |
| !(input_context.flags & ui::TEXT_INPUT_FLAG_SPELLCHECK_OFF); |
| } |
| |
| } // namespace ui |
| |
| namespace extensions { |
| |
| InputImeEventRouterFactory* InputImeEventRouterFactory::GetInstance() { |
| return base::Singleton<InputImeEventRouterFactory>::get(); |
| } |
| |
| InputImeEventRouterFactory::InputImeEventRouterFactory() { |
| } |
| |
| InputImeEventRouterFactory::~InputImeEventRouterFactory() { |
| } |
| |
| InputImeEventRouter* InputImeEventRouterFactory::GetRouter(Profile* profile) { |
| if (!profile) |
| return nullptr; |
| InputImeEventRouter* router = router_map_[profile]; |
| if (!router) { |
| router = new InputImeEventRouter(profile); |
| router_map_[profile] = router; |
| } |
| return router; |
| } |
| |
| void InputImeEventRouterFactory::RemoveProfile(Profile* profile) { |
| if (!profile || router_map_.empty()) |
| return; |
| auto it = router_map_.find(profile); |
| if (it != router_map_.end() && it->first == profile) { |
| delete it->second; |
| router_map_.erase(it); |
| } |
| } |
| |
| ExtensionFunction::ResponseAction InputImeKeyEventHandledFunction::Run() { |
| std::unique_ptr<KeyEventHandled::Params> params( |
| KeyEventHandled::Params::Create(*args_)); |
| InputImeEventRouter* event_router = |
| GetInputImeEventRouter(Profile::FromBrowserContext(browser_context())); |
| InputMethodEngineBase* engine = |
| event_router ? event_router->GetActiveEngine(extension_id()) : nullptr; |
| if (engine) { |
| engine->KeyEventHandled(extension_id(), params->request_id, |
| params->response); |
| } |
| return RespondNow(NoArguments()); |
| } |
| |
| ExtensionFunction::ResponseAction InputImeSetCompositionFunction::Run() { |
| InputImeEventRouter* event_router = |
| GetInputImeEventRouter(Profile::FromBrowserContext(browser_context())); |
| InputMethodEngineBase* engine = |
| event_router ? event_router->GetActiveEngine(extension_id()) : nullptr; |
| if (engine) { |
| std::unique_ptr<SetComposition::Params> parent_params( |
| SetComposition::Params::Create(*args_)); |
| const SetComposition::Params::Parameters& params = |
| parent_params->parameters; |
| std::vector<InputMethodEngineBase::SegmentInfo> segments; |
| if (params.segments) { |
| for (const auto& segments_arg : *params.segments) { |
| EXTENSION_FUNCTION_VALIDATE(segments_arg.style != |
| input_ime::UNDERLINE_STYLE_NONE); |
| InputMethodEngineBase::SegmentInfo segment_info; |
| segment_info.start = segments_arg.start; |
| segment_info.end = segments_arg.end; |
| if (segments_arg.style == input_ime::UNDERLINE_STYLE_UNDERLINE) { |
| segment_info.style = InputMethodEngineBase::SEGMENT_STYLE_UNDERLINE; |
| } else if (segments_arg.style == |
| input_ime::UNDERLINE_STYLE_DOUBLEUNDERLINE) { |
| segment_info.style = |
| InputMethodEngineBase::SEGMENT_STYLE_DOUBLE_UNDERLINE; |
| } else { |
| segment_info.style = |
| InputMethodEngineBase::SEGMENT_STYLE_NO_UNDERLINE; |
| } |
| segments.push_back(segment_info); |
| } |
| } |
| int selection_start = |
| params.selection_start ? *params.selection_start : params.cursor; |
| int selection_end = |
| params.selection_end ? *params.selection_end : params.cursor; |
| std::string error; |
| if (!engine->SetComposition(params.context_id, params.text.c_str(), |
| selection_start, selection_end, params.cursor, |
| segments, &error)) { |
| std::unique_ptr<base::ListValue> results = |
| std::make_unique<base::ListValue>(); |
| results->Append(std::make_unique<base::Value>(false)); |
| return RespondNow(ErrorWithArguments(std::move(results), error)); |
| } |
| } |
| return RespondNow(OneArgument(std::make_unique<base::Value>(true))); |
| } |
| |
| ExtensionFunction::ResponseAction InputImeCommitTextFunction::Run() { |
| InputImeEventRouter* event_router = |
| GetInputImeEventRouter(Profile::FromBrowserContext(browser_context())); |
| InputMethodEngineBase* engine = |
| event_router ? event_router->GetActiveEngine(extension_id()) : nullptr; |
| if (engine) { |
| std::unique_ptr<CommitText::Params> parent_params( |
| CommitText::Params::Create(*args_)); |
| const CommitText::Params::Parameters& params = parent_params->parameters; |
| std::string error; |
| if (!engine->CommitText(params.context_id, params.text.c_str(), &error)) { |
| std::unique_ptr<base::ListValue> results = |
| std::make_unique<base::ListValue>(); |
| results->Append(std::make_unique<base::Value>(false)); |
| return RespondNow(ErrorWithArguments(std::move(results), error)); |
| } |
| } |
| return RespondNow(OneArgument(std::make_unique<base::Value>(true))); |
| } |
| |
| ExtensionFunction::ResponseAction InputImeSendKeyEventsFunction::Run() { |
| InputImeEventRouter* event_router = |
| GetInputImeEventRouter(Profile::FromBrowserContext(browser_context())); |
| InputMethodEngineBase* engine = |
| event_router ? event_router->GetActiveEngine(extension_id()) : nullptr; |
| if (!engine) |
| return RespondNow(Error(kErrorEngineNotAvailable)); |
| |
| std::unique_ptr<SendKeyEvents::Params> parent_params( |
| SendKeyEvents::Params::Create(*args_)); |
| EXTENSION_FUNCTION_VALIDATE(parent_params); |
| const SendKeyEvents::Params::Parameters& params = parent_params->parameters; |
| std::vector<InputMethodEngineBase::KeyboardEvent> key_data_out; |
| |
| for (const auto& key_event : params.key_data) { |
| key_data_out.push_back(InputMethodEngineBase::KeyboardEvent()); |
| InputMethodEngineBase::KeyboardEvent& event = key_data_out.back(); |
| event.type = input_ime::ToString(key_event.type); |
| event.key = key_event.key; |
| event.code = key_event.code; |
| event.key_code = key_event.key_code.get() ? *(key_event.key_code) : 0; |
| event.alt_key = key_event.alt_key ? *(key_event.alt_key) : false; |
| event.ctrl_key = key_event.ctrl_key ? *(key_event.ctrl_key) : false; |
| event.shift_key = key_event.shift_key ? *(key_event.shift_key) : false; |
| event.caps_lock = key_event.caps_lock ? *(key_event.caps_lock) : false; |
| } |
| if (!engine->SendKeyEvents(params.context_id, key_data_out)) |
| return RespondNow(Error(kErrorSetKeyEventsFail)); |
| return RespondNow(NoArguments()); |
| } |
| |
| InputImeAPI::InputImeAPI(content::BrowserContext* context) |
| : browser_context_(context), extension_registry_observer_(this) { |
| extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); |
| |
| EventRouter* event_router = EventRouter::Get(browser_context_); |
| event_router->RegisterObserver(this, input_ime::OnFocus::kEventName); |
| registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED, |
| content::NotificationService::AllSources()); |
| } |
| |
| void InputImeAPI::Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK_EQ(chrome::NOTIFICATION_PROFILE_DESTROYED, type); |
| extensions::InputImeEventRouterFactory::GetInstance()->RemoveProfile( |
| content::Source<Profile>(source).ptr()); |
| } |
| |
| InputImeAPI::~InputImeAPI() = default; |
| |
| void InputImeAPI::Shutdown() { |
| EventRouter::Get(browser_context_)->UnregisterObserver(this); |
| registrar_.RemoveAll(); |
| if (observer_ && ui::IMEBridge::Get()) { |
| ui::IMEBridge::Get()->SetObserver(nullptr); |
| } |
| } |
| |
| static base::LazyInstance<BrowserContextKeyedAPIFactory<InputImeAPI>>:: |
| DestructorAtExit g_input_ime_factory = LAZY_INSTANCE_INITIALIZER; |
| |
| // static |
| BrowserContextKeyedAPIFactory<InputImeAPI>* InputImeAPI::GetFactoryInstance() { |
| return g_input_ime_factory.Pointer(); |
| } |
| |
| InputImeEventRouter* GetInputImeEventRouter(Profile* profile) { |
| if (!profile) |
| return nullptr; |
| if (profile->HasOffTheRecordProfile()) |
| profile = profile->GetOffTheRecordProfile(); |
| return extensions::InputImeEventRouterFactory::GetInstance()->GetRouter( |
| profile); |
| } |
| |
| } // namespace extensions |