// 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 "base/strings/string_number_conversions.h" #include "base/values.h" #include "chrome/browser/chromeos/profiles/profile_helper.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/common/extensions/api/input_ime.h" #include "chrome/common/extensions/api/input_ime/input_components_handler.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_function_registry.h" #include "extensions/browser/extension_registry.h" #if defined(USE_X11) #include "chrome/browser/chromeos/input_method/input_method_engine.h" #endif namespace input_ime = extensions::api::input_ime; namespace KeyEventHandled = extensions::api::input_ime::KeyEventHandled; namespace DeleteSurroundingText = extensions::api::input_ime::DeleteSurroundingText; namespace UpdateMenuItems = extensions::api::input_ime::UpdateMenuItems; namespace SendKeyEvents = extensions::api::input_ime::SendKeyEvents; namespace HideInputView = extensions::api::input_ime::HideInputView; namespace SetMenuItems = extensions::api::input_ime::SetMenuItems; namespace SetCursorPosition = extensions::api::input_ime::SetCursorPosition; namespace SetCandidates = extensions::api::input_ime::SetCandidates; namespace SetCandidateWindowProperties = extensions::api::input_ime::SetCandidateWindowProperties; namespace CommitText = extensions::api::input_ime::CommitText; namespace ClearComposition = extensions::api::input_ime::ClearComposition; namespace SetComposition = extensions::api::input_ime::SetComposition; using chromeos::InputMethodEngineInterface; namespace { const char kErrorEngineNotAvailable[] = "Engine is not available"; const char kErrorSetMenuItemsFail[] = "Could not create menu Items"; const char kErrorUpdateMenuItemsFail[] = "Could not update menu Items"; void SetMenuItemToMenu(const input_ime::MenuItem& input, InputMethodEngineInterface::MenuItem* out) { out->modified = 0; out->id = input.id; if (input.label) { out->modified |= InputMethodEngineInterface::MENU_ITEM_MODIFIED_LABEL; out->label = *input.label; } if (input.style != input_ime::MenuItem::STYLE_NONE) { out->modified |= InputMethodEngineInterface::MENU_ITEM_MODIFIED_STYLE; out->style = static_cast( input.style); } if (input.visible) out->modified |= InputMethodEngineInterface::MENU_ITEM_MODIFIED_VISIBLE; out->visible = input.visible ? *input.visible : true; if (input.checked) out->modified |= InputMethodEngineInterface::MENU_ITEM_MODIFIED_CHECKED; out->checked = input.checked ? *input.checked : false; if (input.enabled) out->modified |= InputMethodEngineInterface::MENU_ITEM_MODIFIED_ENABLED; out->enabled = input.enabled ? *input.enabled : true; } static void DispatchEventToExtension(Profile* profile, const std::string& extension_id, const std::string& event_name, scoped_ptr args) { scoped_ptr event(new extensions::Event( event_name, args.Pass())); event->restrict_to_browser_context = profile; extensions::EventRouter::Get(profile) ->DispatchEventToExtension(extension_id, event.Pass()); } } // namespace namespace chromeos { class ImeObserver : public InputMethodEngineInterface::Observer { public: ImeObserver(Profile* profile, const std::string& extension_id) : profile_(profile), extension_id_(extension_id) {} virtual ~ImeObserver() {} virtual void OnActivate(const std::string& engine_id) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; scoped_ptr args(input_ime::OnActivate::Create(engine_id)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnActivate::kEventName, args.Pass()); } virtual void OnDeactivated(const std::string& engine_id) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; scoped_ptr args( input_ime::OnDeactivated::Create(engine_id)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnDeactivated::kEventName, args.Pass()); } virtual void OnFocus( const InputMethodEngineInterface::InputContext& context) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; input_ime::InputContext context_value; context_value.context_id = context.id; context_value.type = input_ime::InputContext::ParseType(context.type); scoped_ptr args(input_ime::OnFocus::Create(context_value)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnFocus::kEventName, args.Pass()); } virtual void OnBlur(int context_id) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; scoped_ptr args(input_ime::OnBlur::Create(context_id)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnBlur::kEventName, args.Pass()); } virtual void OnInputContextUpdate( const InputMethodEngineInterface::InputContext& context) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; input_ime::InputContext context_value; context_value.context_id = context.id; context_value.type = input_ime::InputContext::ParseType(context.type); scoped_ptr args( input_ime::OnInputContextUpdate::Create(context_value)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnInputContextUpdate::kEventName, args.Pass()); } virtual void OnKeyEvent( const std::string& engine_id, const InputMethodEngineInterface::KeyboardEvent& event, chromeos::input_method::KeyEventHandle* key_data) OVERRIDE { if (profile_ == NULL || 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 (!HasKeyEventListener()) { // Continue processing the key event so that the physical keyboard can // still work. base::Callback* callback = reinterpret_cast*>(key_data); callback->Run(false); delete callback; return; } extensions::InputImeEventRouter* ime_event_router = extensions::InputImeEventRouter::GetInstance(); const std::string request_id = ime_event_router->AddRequest(engine_id, key_data); input_ime::KeyboardEvent key_data_value; key_data_value.type = input_ime::KeyboardEvent::ParseType(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)); scoped_ptr args( input_ime::OnKeyEvent::Create(engine_id, key_data_value)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnKeyEvent::kEventName, args.Pass()); } virtual void OnCandidateClicked( const std::string& engine_id, int candidate_id, InputMethodEngineInterface::MouseButtonEvent button) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; input_ime::OnCandidateClicked::Button button_enum = input_ime::OnCandidateClicked::BUTTON_NONE; switch (button) { case InputMethodEngineInterface::MOUSE_BUTTON_MIDDLE: button_enum = input_ime::OnCandidateClicked::BUTTON_MIDDLE; break; case InputMethodEngineInterface::MOUSE_BUTTON_RIGHT: button_enum = input_ime::OnCandidateClicked::BUTTON_RIGHT; break; case InputMethodEngineInterface::MOUSE_BUTTON_LEFT: // Default to left. default: button_enum = input_ime::OnCandidateClicked::BUTTON_LEFT; break; } scoped_ptr args( input_ime::OnCandidateClicked::Create(engine_id, candidate_id, button_enum)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnCandidateClicked::kEventName, args.Pass()); } virtual void OnMenuItemActivated(const std::string& engine_id, const std::string& menu_id) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; scoped_ptr args( input_ime::OnMenuItemActivated::Create(engine_id, menu_id)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnMenuItemActivated::kEventName, args.Pass()); } virtual void OnSurroundingTextChanged(const std::string& engine_id, const std::string& text, int cursor_pos, int anchor_pos) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; input_ime::OnSurroundingTextChanged::SurroundingInfo info; info.text = text; info.focus = cursor_pos; info.anchor = anchor_pos; scoped_ptr args( input_ime::OnSurroundingTextChanged::Create(engine_id, info)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnSurroundingTextChanged::kEventName, args.Pass()); } virtual void OnReset(const std::string& engine_id) OVERRIDE { if (profile_ == NULL || extension_id_.empty()) return; scoped_ptr args(input_ime::OnReset::Create(engine_id)); DispatchEventToExtension(profile_, extension_id_, input_ime::OnReset::kEventName, args.Pass()); } private: bool HasKeyEventListener() const { return extensions::EventRouter::Get(profile_) ->ExtensionHasEventListener(extension_id_, input_ime::OnKeyEvent::kEventName); } Profile* profile_; std::string extension_id_; DISALLOW_COPY_AND_ASSIGN(ImeObserver); }; } // namespace chromeos namespace extensions { InputImeEventRouter* InputImeEventRouter::GetInstance() { return Singleton::get(); } bool InputImeEventRouter::RegisterIme( const std::string& extension_id, const extensions::InputComponentInfo& component) { #if defined(USE_X11) VLOG(1) << "RegisterIme: " << extension_id << " id: " << component.id; std::vector layouts; layouts.assign(component.layouts.begin(), component.layouts.end()); std::vector languages; languages.assign(component.languages.begin(), component.languages.end()); // Ideally Observer should be per (extension_id + Profile), and multiple // InputMethodEngine can share one Observer. But it would become tricky // to maintain an internal map for observers which does nearly nothing // but just make sure they can properly deleted. // Making Obesrver per InputMethodEngine can make things cleaner. Profile* profile = ProfileManager::GetActiveUserProfile(); scoped_ptr observer( new chromeos::ImeObserver(profile, extension_id)); chromeos::InputMethodEngine* engine = new chromeos::InputMethodEngine(); engine->Initialize(observer.Pass(), component.name.c_str(), extension_id.c_str(), component.id.c_str(), languages, layouts, component.options_page_url, component.input_view_url); profile_engine_map_[profile][extension_id][component.id] = engine; return true; #else // TODO(spang): IME support under ozone. NOTIMPLEMENTED(); return false; #endif } void InputImeEventRouter::UnregisterAllImes(const std::string& extension_id) { Profile* profile = ProfileManager::GetActiveUserProfile(); ProfileEngineMap::iterator extension_map = profile_engine_map_.find(profile); if (extension_map == profile_engine_map_.end()) return; ExtensionMap::iterator engine_map = extension_map->second.find(extension_id); if (engine_map == extension_map->second.end()) return; STLDeleteContainerPairSecondPointers(engine_map->second.begin(), engine_map->second.end()); extension_map->second.erase(extension_id); profile_engine_map_.erase(profile); } InputMethodEngineInterface* InputImeEventRouter::GetEngine( const std::string& extension_id, const std::string& engine_id) { // IME can only work on active user profile. Profile* profile = ProfileManager::GetActiveUserProfile(); ProfileEngineMap::const_iterator extension_map = profile_engine_map_.find(profile); if (extension_map == profile_engine_map_.end()) return NULL; ExtensionMap::const_iterator engine_map = extension_map->second.find(extension_id); if (engine_map == extension_map->second.end()) return NULL; EngineMap::const_iterator engine = engine_map->second.find(engine_id); if (engine == engine_map->second.end()) return NULL; return engine->second; } InputMethodEngineInterface* InputImeEventRouter::GetActiveEngine( const std::string& extension_id) { // IME can only work on active user profile. Profile* profile = ProfileManager::GetActiveUserProfile(); ProfileEngineMap::const_iterator extension_map = profile_engine_map_.find(profile); if (extension_map == profile_engine_map_.end()) return NULL; ExtensionMap::const_iterator engine_map = extension_map->second.find(extension_id); if (engine_map == extension_map->second.end()) return NULL; for (EngineMap::const_iterator i = engine_map->second.begin(); i != engine_map->second.end(); ++i) { if (i->second->IsActive()) return i->second; } return NULL; } void InputImeEventRouter::OnKeyEventHandled( const std::string& extension_id, const std::string& request_id, bool handled) { RequestMap::iterator request = request_map_.find(request_id); if (request == request_map_.end()) { LOG(ERROR) << "Request ID not found: " << request_id; return; } std::string engine_id = request->second.first; chromeos::input_method::KeyEventHandle* key_data = request->second.second; request_map_.erase(request); InputMethodEngineInterface* engine = GetEngine(extension_id, engine_id); if (!engine) { LOG(ERROR) << "Engine does not exist: " << engine_id; return; } engine->KeyEventDone(key_data, handled); } std::string InputImeEventRouter::AddRequest( const std::string& engine_id, chromeos::input_method::KeyEventHandle* key_data) { std::string request_id = base::IntToString(next_request_id_); ++next_request_id_; request_map_[request_id] = std::make_pair(engine_id, key_data); return request_id; } InputImeEventRouter::InputImeEventRouter() : next_request_id_(1) { } InputImeEventRouter::~InputImeEventRouter() {} bool InputImeSetCompositionFunction::RunSync() { InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { SetResult(new base::FundamentalValue(false)); return true; } scoped_ptr parent_params( SetComposition::Params::Create(*args_)); const SetComposition::Params::Parameters& params = parent_params->parameters; std::vector segments; if (params.segments) { const std::vector >& segments_args = *params.segments; for (size_t i = 0; i < segments_args.size(); ++i) { EXTENSION_FUNCTION_VALIDATE( segments_args[i]->style != SetComposition::Params::Parameters::SegmentsType::STYLE_NONE); segments.push_back(InputMethodEngineInterface::SegmentInfo()); segments.back().start = segments_args[i]->start; segments.back().end = segments_args[i]->end; if (segments_args[i]->style == SetComposition::Params::Parameters::SegmentsType::STYLE_UNDERLINE) { segments.back().style = InputMethodEngineInterface::SEGMENT_STYLE_UNDERLINE; } else { segments.back().style = InputMethodEngineInterface::SEGMENT_STYLE_DOUBLE_UNDERLINE; } } } int selection_start = params.selection_start ? *params.selection_start : params.cursor; int selection_end = params.selection_end ? *params.selection_end : params.cursor; SetResult(new base::FundamentalValue( engine->SetComposition(params.context_id, params.text.c_str(), selection_start, selection_end, params.cursor, segments, &error_))); return true; } bool InputImeClearCompositionFunction::RunSync() { InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { SetResult(new base::FundamentalValue(false)); return true; } scoped_ptr parent_params( ClearComposition::Params::Create(*args_)); const ClearComposition::Params::Parameters& params = parent_params->parameters; SetResult(new base::FundamentalValue( engine->ClearComposition(params.context_id, &error_))); return true; } bool InputImeCommitTextFunction::RunSync() { // TODO(zork): Support committing when not active. InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { SetResult(new base::FundamentalValue(false)); return true; } scoped_ptr parent_params( CommitText::Params::Create(*args_)); const CommitText::Params::Parameters& params = parent_params->parameters; SetResult(new base::FundamentalValue( engine->CommitText(params.context_id, params.text.c_str(), &error_))); return true; } bool InputImeHideInputViewFunction::RunAsync() { InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { return true; } engine->HideInputView(); return true; } bool InputImeSendKeyEventsFunction::RunAsync() { scoped_ptr parent_params( SendKeyEvents::Params::Create(*args_)); const SendKeyEvents::Params::Parameters& params = parent_params->parameters; chromeos::InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { error_ = kErrorEngineNotAvailable; return false; } const std::vector >& key_data = params.key_data; std::vector key_data_out; for (size_t i = 0; i < key_data.size(); ++i) { chromeos::InputMethodEngineInterface::KeyboardEvent event; event.type = input_ime::KeyboardEvent::ToString(key_data[i]->type); event.key = key_data[i]->key; event.code = key_data[i]->code; event.key_code = key_data[i]->key_code.get() ? *(key_data[i]->key_code) : 0; if (key_data[i]->alt_key) event.alt_key = *(key_data[i]->alt_key); if (key_data[i]->ctrl_key) event.ctrl_key = *(key_data[i]->ctrl_key); if (key_data[i]->shift_key) event.shift_key = *(key_data[i]->shift_key); if (key_data[i]->caps_lock) event.caps_lock = *(key_data[i]->caps_lock); key_data_out.push_back(event); } engine->SendKeyEvents(params.context_id, key_data_out); return true; } bool InputImeSetCandidateWindowPropertiesFunction::RunSync() { scoped_ptr parent_params( SetCandidateWindowProperties::Params::Create(*args_)); const SetCandidateWindowProperties::Params::Parameters& params = parent_params->parameters; InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetEngine(extension_id(), params.engine_id); if (!engine) { SetResult(new base::FundamentalValue(false)); return true; } const SetCandidateWindowProperties::Params::Parameters::Properties& properties = params.properties; if (properties.visible && !engine->SetCandidateWindowVisible(*properties.visible, &error_)) { SetResult(new base::FundamentalValue(false)); return true; } InputMethodEngineInterface::CandidateWindowProperty properties_out = engine->GetCandidateWindowProperty(); bool modified = false; if (properties.cursor_visible) { properties_out.is_cursor_visible = *properties.cursor_visible; modified = true; } if (properties.vertical) { properties_out.is_vertical = *properties.vertical; modified = true; } if (properties.page_size) { properties_out.page_size = *properties.page_size; modified = true; } if (properties.window_position == SetCandidateWindowProperties::Params::Parameters::Properties:: WINDOW_POSITION_COMPOSITION) { properties_out.show_window_at_composition = true; modified = true; } else if (properties.window_position == SetCandidateWindowProperties::Params::Parameters::Properties:: WINDOW_POSITION_CURSOR) { properties_out.show_window_at_composition = false; modified = true; } if (properties.auxiliary_text) { properties_out.auxiliary_text = *properties.auxiliary_text; modified = true; } if (properties.auxiliary_text_visible) { properties_out.is_auxiliary_text_visible = *properties.auxiliary_text_visible; modified = true; } if (modified) { engine->SetCandidateWindowProperty(properties_out); } SetResult(new base::FundamentalValue(true)); return true; } bool InputImeSetCandidatesFunction::RunSync() { InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { SetResult(new base::FundamentalValue(false)); return true; } scoped_ptr parent_params( SetCandidates::Params::Create(*args_)); const SetCandidates::Params::Parameters& params = parent_params->parameters; std::vector candidates_out; const std::vector >& candidates_in = params.candidates; for (size_t i = 0; i < candidates_in.size(); ++i) { candidates_out.push_back(InputMethodEngineInterface::Candidate()); candidates_out.back().value = candidates_in[i]->candidate; candidates_out.back().id = candidates_in[i]->id; if (candidates_in[i]->label) candidates_out.back().label = *candidates_in[i]->label; if (candidates_in[i]->annotation) candidates_out.back().annotation = *candidates_in[i]->annotation; if (candidates_in[i]->usage) { candidates_out.back().usage.title = candidates_in[i]->usage->title; candidates_out.back().usage.body = candidates_in[i]->usage->body; } } SetResult(new base::FundamentalValue( engine->SetCandidates(params.context_id, candidates_out, &error_))); return true; } bool InputImeSetCursorPositionFunction::RunSync() { InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id()); if (!engine) { SetResult(new base::FundamentalValue(false)); return true; } scoped_ptr parent_params( SetCursorPosition::Params::Create(*args_)); const SetCursorPosition::Params::Parameters& params = parent_params->parameters; SetResult(new base::FundamentalValue( engine->SetCursorPosition(params.context_id, params.candidate_id, &error_))); return true; } bool InputImeSetMenuItemsFunction::RunSync() { scoped_ptr parent_params( SetMenuItems::Params::Create(*args_)); const SetMenuItems::Params::Parameters& params = parent_params->parameters; InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetEngine(extension_id(), params.engine_id); if (!engine) { error_ = kErrorEngineNotAvailable; return false; } const std::vector >& items = params.items; std::vector items_out; for (size_t i = 0; i < items.size(); ++i) { items_out.push_back(InputMethodEngineInterface::MenuItem()); SetMenuItemToMenu(*items[i], &items_out.back()); } if (!engine->SetMenuItems(items_out)) error_ = kErrorSetMenuItemsFail; return true; } bool InputImeUpdateMenuItemsFunction::RunSync() { scoped_ptr parent_params( UpdateMenuItems::Params::Create(*args_)); const UpdateMenuItems::Params::Parameters& params = parent_params->parameters; InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetEngine(extension_id(), params.engine_id); if (!engine) { error_ = kErrorEngineNotAvailable; return false; } const std::vector >& items = params.items; std::vector items_out; for (size_t i = 0; i < items.size(); ++i) { items_out.push_back(InputMethodEngineInterface::MenuItem()); SetMenuItemToMenu(*items[i], &items_out.back()); } if (!engine->UpdateMenuItems(items_out)) error_ = kErrorUpdateMenuItemsFail; return true; } bool InputImeDeleteSurroundingTextFunction::RunSync() { scoped_ptr parent_params( DeleteSurroundingText::Params::Create(*args_)); const DeleteSurroundingText::Params::Parameters& params = parent_params->parameters; InputMethodEngineInterface* engine = InputImeEventRouter::GetInstance()->GetEngine(extension_id(), params.engine_id); if (!engine) { error_ = kErrorEngineNotAvailable; return false; } engine->DeleteSurroundingText(params.context_id, params.offset, params.length, &error_); return true; } bool InputImeKeyEventHandledFunction::RunAsync() { scoped_ptr params( KeyEventHandled::Params::Create(*args_)); InputImeEventRouter::GetInstance()->OnKeyEventHandled( extension_id(), params->request_id, params->response); return true; } 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::OnActivate::kEventName); event_router->RegisterObserver(this, input_ime::OnFocus::kEventName); } InputImeAPI::~InputImeAPI() { EventRouter::Get(browser_context_)->UnregisterObserver(this); } static base::LazyInstance > g_factory = LAZY_INSTANCE_INITIALIZER; // static BrowserContextKeyedAPIFactory* InputImeAPI::GetFactoryInstance() { return g_factory.Pointer(); } void InputImeAPI::OnExtensionLoaded(content::BrowserContext* browser_context, const Extension* extension) { const std::vector* input_components = extensions::InputComponents::GetInputComponents(extension); if (!input_components) return; for (std::vector::const_iterator component = input_components->begin(); component != input_components->end(); ++component) { if (component->type == extensions::INPUT_COMPONENT_TYPE_IME) { // Don't pass profile_ to register ime, instead always use // GetActiveUserProfile. It is because: // The original profile for login screen is called signin profile. // And the active profile is the incognito profile based on signin // profile. So if |profile_| is signin profile, we need to make sure // the router/observer runs under its incognito profile, because the // component extensions were installed under its incognito profile. input_ime_event_router()->RegisterIme(extension->id(), *component); } } } void InputImeAPI::OnExtensionUnloaded(content::BrowserContext* browser_context, const Extension* extension, UnloadedExtensionInfo::Reason reason) { const std::vector* input_components = extensions::InputComponents::GetInputComponents(extension); if (!input_components) return; if (input_components->size() > 0) input_ime_event_router()->UnregisterAllImes(extension->id()); } void InputImeAPI::OnListenerAdded(const EventListenerInfo& details) { InputMethodEngineInterface* engine = input_ime_event_router()->GetActiveEngine(details.extension_id); if (engine) engine->NotifyImeReady(); } InputImeEventRouter* InputImeAPI::input_ime_event_router() { return InputImeEventRouter::GetInstance(); } } // namespace extensions