1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/extensions/api/input_ime/input_ime_api.h"
6 
7 #include <stddef.h>
8 
9 #include <memory>
10 #include <utility>
11 
12 #include "ash/public/cpp/keyboard/keyboard_config.h"
13 #include "base/feature_list.h"
14 #include "base/macros.h"
15 #include "base/strings/stringprintf.h"
16 #include "chrome/browser/chromeos/input_method/assistive_window_properties.h"
17 #include "chrome/browser/chromeos/input_method/input_host_helper.h"
18 #include "chrome/browser/chromeos/input_method/input_method_engine.h"
19 #include "chrome/browser/chromeos/input_method/native_input_method_engine.h"
20 #include "chrome/browser/chromeos/login/lock/screen_locker.h"
21 #include "chrome/browser/chromeos/login/session/user_session_manager.h"
22 #include "chrome/browser/chromeos/profiles/profile_helper.h"
23 #include "chrome/browser/profiles/profile_manager.h"
24 #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h"
25 #include "chrome/common/extensions/api/input_ime.h"
26 #include "chrome/common/extensions/api/input_method_private.h"
27 #include "chromeos/constants/chromeos_features.h"
28 #include "extensions/browser/extension_registry.h"
29 #include "extensions/browser/process_manager.h"
30 #include "extensions/common/manifest_handlers/background_info.h"
31 #include "ui/base/ime/chromeos/component_extension_ime_manager.h"
32 #include "ui/base/ime/chromeos/extension_ime_util.h"
33 #include "ui/base/ime/chromeos/ime_engine_handler_interface.h"
34 #include "ui/base/ime/chromeos/input_method_manager.h"
35 #include "ui/base/ui_base_features.h"
36 
37 namespace input_ime = extensions::api::input_ime;
38 namespace input_method_private = extensions::api::input_method_private;
39 namespace DeleteSurroundingText =
40     extensions::api::input_ime::DeleteSurroundingText;
41 namespace UpdateMenuItems = extensions::api::input_ime::UpdateMenuItems;
42 namespace HideInputView = extensions::api::input_ime::HideInputView;
43 namespace SetMenuItems = extensions::api::input_ime::SetMenuItems;
44 namespace SetCursorPosition = extensions::api::input_ime::SetCursorPosition;
45 namespace SetCandidates = extensions::api::input_ime::SetCandidates;
46 namespace SetCandidateWindowProperties =
47     extensions::api::input_ime::SetCandidateWindowProperties;
48 namespace SetAssistiveWindowProperties =
49     extensions::api::input_ime::SetAssistiveWindowProperties;
50 namespace SetAssistiveWindowButtonHighlighted =
51     extensions::api::input_ime::SetAssistiveWindowButtonHighlighted;
52 namespace ClearComposition = extensions::api::input_ime::ClearComposition;
53 namespace OnCompositionBoundsChanged =
54     extensions::api::input_method_private::OnCompositionBoundsChanged;
55 namespace NotifyImeMenuItemActivated =
56     extensions::api::input_method_private::NotifyImeMenuItemActivated;
57 namespace OnScreenProjectionChanged =
58     extensions::api::input_method_private::OnScreenProjectionChanged;
59 namespace SetSelectionRange =
60     extensions::api::input_method_private::SetSelectionRange;
61 namespace FinishComposingText =
62     extensions::api::input_method_private::FinishComposingText;
63 using chromeos::InputMethodEngine;
64 using chromeos::InputMethodEngineBase;
65 using ui::IMEEngineHandlerInterface;
66 
67 namespace {
68 const char kErrorEngineNotAvailable[] = "The engine is not available.";
69 const char kErrorSetMenuItemsFail[] = "Could not create menu items.";
70 const char kErrorUpdateMenuItemsFail[] = "Could not update menu items.";
71 const char kErrorEngineNotActive[] = "The engine is not active.";
72 const char kErrorRouterNotAvailable[] = "The router is not available.";
73 
SetMenuItemToMenu(const input_ime::MenuItem & input,chromeos::input_method::InputMethodManager::MenuItem * out)74 void SetMenuItemToMenu(
75     const input_ime::MenuItem& input,
76     chromeos::input_method::InputMethodManager::MenuItem* out) {
77   out->modified = 0;
78   out->id = input.id;
79   if (input.label) {
80     out->modified |= InputMethodEngine::MENU_ITEM_MODIFIED_LABEL;
81     out->label = *input.label;
82   }
83 
84   if (input.style != input_ime::MENU_ITEM_STYLE_NONE) {
85     out->modified |= InputMethodEngine::MENU_ITEM_MODIFIED_STYLE;
86     out->style =
87         static_cast<chromeos::input_method::InputMethodManager::MenuItemStyle>(
88             input.style);
89   }
90 
91   if (input.visible)
92     out->modified |= InputMethodEngine::MENU_ITEM_MODIFIED_VISIBLE;
93   out->visible = input.visible ? *input.visible : true;
94 
95   if (input.checked)
96     out->modified |= InputMethodEngine::MENU_ITEM_MODIFIED_CHECKED;
97   out->checked = input.checked ? *input.checked : false;
98 
99   if (input.enabled)
100     out->modified |= InputMethodEngine::MENU_ITEM_MODIFIED_ENABLED;
101   out->enabled = input.enabled ? *input.enabled : true;
102 }
103 
GetKeyboardConfig()104 keyboard::KeyboardConfig GetKeyboardConfig() {
105   return ChromeKeyboardControllerClient::Get()->GetKeyboardConfig();
106 }
107 
ConvertAssistiveWindowType(input_ime::AssistiveWindowType type)108 ui::ime::AssistiveWindowType ConvertAssistiveWindowType(
109     input_ime::AssistiveWindowType type) {
110   switch (type) {
111     case input_ime::ASSISTIVE_WINDOW_TYPE_NONE:
112       return ui::ime::AssistiveWindowType::kNone;
113     case input_ime::ASSISTIVE_WINDOW_TYPE_UNDO:
114       return ui::ime::AssistiveWindowType::kUndoWindow;
115   }
116 }
117 
ConvertAssistiveWindowButtonId(input_ime::AssistiveWindowButton id)118 ui::ime::ButtonId ConvertAssistiveWindowButtonId(
119     input_ime::AssistiveWindowButton id) {
120   switch (id) {
121     case input_ime::ASSISTIVE_WINDOW_BUTTON_ADDTODICTIONARY:
122       return ui::ime::ButtonId::kAddToDictionary;
123     case input_ime::ASSISTIVE_WINDOW_BUTTON_UNDO:
124       return ui::ime::ButtonId::kUndo;
125     case input_ime::ASSISTIVE_WINDOW_BUTTON_NONE:
126       return ui::ime::ButtonId::kNone;
127   }
128 }
129 
ConvertAssistiveWindowButton(const ui::ime::ButtonId id)130 input_ime::AssistiveWindowButton ConvertAssistiveWindowButton(
131     const ui::ime::ButtonId id) {
132   switch (id) {
133     case ui::ime::ButtonId::kNone:
134     case ui::ime::ButtonId::kSmartInputsSettingLink:
135     case ui::ime::ButtonId::kSuggestion:
136     case ui::ime::ButtonId::kLearnMore:
137       return input_ime::ASSISTIVE_WINDOW_BUTTON_NONE;
138     case ui::ime::ButtonId::kUndo:
139       return input_ime::ASSISTIVE_WINDOW_BUTTON_UNDO;
140     case ui::ime::ButtonId::kAddToDictionary:
141       return input_ime::ASSISTIVE_WINDOW_BUTTON_ADDTODICTIONARY;
142   }
143 }
144 
ConvertAssistiveWindowType(const ui::ime::AssistiveWindowType & type)145 input_ime::AssistiveWindowType ConvertAssistiveWindowType(
146     const ui::ime::AssistiveWindowType& type) {
147   switch (type) {
148     case ui::ime::AssistiveWindowType::kNone:
149     case ui::ime::AssistiveWindowType::kEmojiSuggestion:
150     case ui::ime::AssistiveWindowType::kPersonalInfoSuggestion:
151       return input_ime::AssistiveWindowType::ASSISTIVE_WINDOW_TYPE_NONE;
152     case ui::ime::AssistiveWindowType::kUndoWindow:
153       return input_ime::AssistiveWindowType::ASSISTIVE_WINDOW_TYPE_UNDO;
154   }
155 }
156 
157 class ImeObserverChromeOS : public ui::ImeObserver {
158  public:
ImeObserverChromeOS(const std::string & extension_id,Profile * profile)159   ImeObserverChromeOS(const std::string& extension_id, Profile* profile)
160       : ImeObserver(extension_id, profile) {}
161 
162   ~ImeObserverChromeOS() override = default;
163 
164   // chromeos::InputMethodEngineBase::Observer overrides.
OnInputContextUpdate(const IMEEngineHandlerInterface::InputContext & context)165   void OnInputContextUpdate(
166       const IMEEngineHandlerInterface::InputContext& context) override {
167     if (extension_id_.empty() ||
168         !HasListener(input_ime::OnInputContextUpdate::kEventName))
169       return;
170 
171     input_ime::InputContext context_value;
172     context_value.context_id = context.id;
173     context_value.type =
174         input_ime::ParseInputContextType(ConvertInputContextType(context));
175 
176     std::unique_ptr<base::ListValue> args(
177         input_ime::OnInputContextUpdate::Create(context_value));
178 
179     DispatchEventToExtension(
180         extensions::events::INPUT_IME_ON_INPUT_CONTEXT_UPDATE,
181         input_ime::OnInputContextUpdate::kEventName, std::move(args));
182   }
183 
OnCandidateClicked(const std::string & component_id,int candidate_id,InputMethodEngineBase::MouseButtonEvent button)184   void OnCandidateClicked(
185       const std::string& component_id,
186       int candidate_id,
187       InputMethodEngineBase::MouseButtonEvent button) override {
188     if (extension_id_.empty() ||
189         !HasListener(input_ime::OnCandidateClicked::kEventName))
190       return;
191 
192     input_ime::MouseButton button_enum = input_ime::MOUSE_BUTTON_NONE;
193     switch (button) {
194       case InputMethodEngineBase::MOUSE_BUTTON_MIDDLE:
195         button_enum = input_ime::MOUSE_BUTTON_MIDDLE;
196         break;
197 
198       case InputMethodEngineBase::MOUSE_BUTTON_RIGHT:
199         button_enum = input_ime::MOUSE_BUTTON_RIGHT;
200         break;
201 
202       case InputMethodEngineBase::MOUSE_BUTTON_LEFT:
203       // Default to left.
204       default:
205         button_enum = input_ime::MOUSE_BUTTON_LEFT;
206         break;
207     }
208 
209     std::unique_ptr<base::ListValue> args(input_ime::OnCandidateClicked::Create(
210         component_id, candidate_id, button_enum));
211 
212     DispatchEventToExtension(extensions::events::INPUT_IME_ON_CANDIDATE_CLICKED,
213                              input_ime::OnCandidateClicked::kEventName,
214                              std::move(args));
215   }
216 
OnMenuItemActivated(const std::string & component_id,const std::string & menu_id)217   void OnMenuItemActivated(const std::string& component_id,
218                            const std::string& menu_id) override {
219     if (extension_id_.empty() ||
220         !HasListener(input_ime::OnMenuItemActivated::kEventName))
221       return;
222 
223     std::unique_ptr<base::ListValue> args(
224         input_ime::OnMenuItemActivated::Create(component_id, menu_id));
225 
226     DispatchEventToExtension(
227         extensions::events::INPUT_IME_ON_MENU_ITEM_ACTIVATED,
228         input_ime::OnMenuItemActivated::kEventName, std::move(args));
229   }
230 
OnScreenProjectionChanged(bool is_projected)231   void OnScreenProjectionChanged(bool is_projected) override {
232     if (extension_id_.empty() ||
233         !HasListener(OnScreenProjectionChanged::kEventName)) {
234       return;
235     }
236     // Note: this is a private API event.
237     std::unique_ptr<base::ListValue> args(new base::ListValue());
238     args->Append(std::make_unique<base::Value>(is_projected));
239 
240     DispatchEventToExtension(
241         extensions::events::INPUT_METHOD_PRIVATE_ON_SCREEN_PROJECTION_CHANGED,
242         OnScreenProjectionChanged::kEventName, std::move(args));
243   }
244 
OnCompositionBoundsChanged(const std::vector<gfx::Rect> & bounds)245   void OnCompositionBoundsChanged(
246       const std::vector<gfx::Rect>& bounds) override {
247     if (extension_id_.empty() ||
248         !HasListener(OnCompositionBoundsChanged::kEventName))
249       return;
250 
251     // Note: this is a private API event.
252     auto bounds_list = std::make_unique<base::ListValue>();
253     for (auto bound : bounds) {
254       auto bounds_value = std::make_unique<base::DictionaryValue>();
255       bounds_value->SetInteger("x", bound.x());
256       bounds_value->SetInteger("y", bound.y());
257       bounds_value->SetInteger("w", bound.width());
258       bounds_value->SetInteger("h", bound.height());
259       bounds_list->Append(std::move(bounds_value));
260     }
261 
262     if (bounds_list->GetSize() <= 0)
263       return;
264     std::unique_ptr<base::ListValue> args(new base::ListValue());
265 
266     // The old extension code uses the first parameter to get the bounds of the
267     // first composition character, so for backward compatibility, add it here.
268     base::Value* first_value = NULL;
269     if (bounds_list->Get(0, &first_value))
270       args->Append(first_value->CreateDeepCopy());
271     args->Append(std::move(bounds_list));
272 
273     DispatchEventToExtension(
274         extensions::events::INPUT_METHOD_PRIVATE_ON_COMPOSITION_BOUNDS_CHANGED,
275         OnCompositionBoundsChanged::kEventName, std::move(args));
276   }
277 
OnFocus(const IMEEngineHandlerInterface::InputContext & context)278   void OnFocus(
279       const IMEEngineHandlerInterface::InputContext& context) override {
280     if (extension_id_.empty())
281       return;
282 
283     // There is both a public and private OnFocus event. The private OnFocus
284     // event is only for ChromeOS and contains additional information about pen
285     // inputs. We ensure that we only trigger one OnFocus event.
286     if (ExtensionHasListener(input_method_private::OnFocus::kEventName)) {
287       input_method_private::InputContext input_context;
288       input_context.context_id = context.id;
289       input_context.type = input_method_private::ParseInputContextType(
290           ConvertInputContextType(context));
291       input_context.mode = input_method_private::ParseInputModeType(
292           ConvertInputContextMode(context));
293       input_context.auto_correct = ConvertInputContextAutoCorrect(context);
294       input_context.auto_complete = ConvertInputContextAutoComplete(context);
295       input_context.auto_capitalize =
296           ConvertInputContextAutoCapitalize(context.flags);
297       input_context.spell_check = ConvertInputContextSpellCheck(context);
298       input_context.has_been_password = ConvertHasBeenPassword(context);
299       input_context.should_do_learning = context.should_do_learning;
300       input_context.focus_reason = input_method_private::ParseFocusReason(
301           ConvertInputContextFocusReason(context));
302 
303       // Populate app key for private OnFocus.
304       // TODO(b/163645900): Add app type later.
305       chromeos::input_host_helper::InputAssociatedHost host;
306       chromeos::input_host_helper::PopulateInputHost(&host);
307       input_context.app_key = std::make_unique<std::string>(host.app_key);
308 
309       std::unique_ptr<base::ListValue> args(
310           input_method_private::OnFocus::Create(input_context));
311 
312       DispatchEventToExtension(
313           extensions::events::INPUT_METHOD_PRIVATE_ON_FOCUS,
314           input_method_private::OnFocus::kEventName, std::move(args));
315       return;
316     }
317 
318     ImeObserver::OnFocus(context);
319   }
320 
OnAssistiveWindowButtonClicked(const ui::ime::AssistiveWindowButton & button)321   void OnAssistiveWindowButtonClicked(
322       const ui::ime::AssistiveWindowButton& button) override {
323     if (extension_id_.empty() ||
324         !HasListener(input_ime::OnAssistiveWindowButtonClicked::kEventName)) {
325       return;
326     }
327     input_ime::OnAssistiveWindowButtonClicked::Details details;
328     details.button_id = ConvertAssistiveWindowButton(button.id);
329     details.window_type = ConvertAssistiveWindowType(button.window_type);
330 
331     std::unique_ptr<base::ListValue> args(
332         input_ime::OnAssistiveWindowButtonClicked::Create(details));
333     DispatchEventToExtension(
334         extensions::events::INPUT_IME_ON_ASSISTIVE_WINDOW_BUTTON_CLICKED,
335         input_ime::OnAssistiveWindowButtonClicked::kEventName, std::move(args));
336   }
337 
OnSuggestionsChanged(const std::vector<std::string> & suggestions)338   void OnSuggestionsChanged(
339       const std::vector<std::string>& suggestions) override {
340     std::unique_ptr<base::ListValue> args(
341         input_method_private::OnSuggestionsChanged::Create(suggestions));
342     DispatchEventToExtension(
343         extensions::events::INPUT_IME_ON_SUGGESTIONS_CHANGED,
344         input_method_private::OnSuggestionsChanged::kEventName,
345         std::move(args));
346   }
347 
OnInputMethodOptionsChanged(const std::string & engine_id)348   void OnInputMethodOptionsChanged(const std::string& engine_id) override {
349     std::unique_ptr<base::ListValue> args(
350         input_method_private::OnInputMethodOptionsChanged::Create(engine_id));
351     DispatchEventToExtension(
352         extensions::events::INPUT_IME_ON_INPUT_METHOD_OPTIONS_CHANGED,
353         input_method_private::OnInputMethodOptionsChanged::kEventName,
354         std::move(args));
355   }
356 
357  private:
358   // ui::ImeObserver overrides.
DispatchEventToExtension(extensions::events::HistogramValue histogram_value,const std::string & event_name,std::unique_ptr<base::ListValue> args)359   void DispatchEventToExtension(
360       extensions::events::HistogramValue histogram_value,
361       const std::string& event_name,
362       std::unique_ptr<base::ListValue> args) override {
363     if (event_name == input_ime::OnActivate::kEventName) {
364       // Send onActivate event regardless of it's listened by the IME.
365       auto event = std::make_unique<extensions::Event>(
366           histogram_value, event_name, std::move(args), profile_);
367       extensions::EventRouter::Get(profile_)->DispatchEventWithLazyListener(
368           extension_id_, std::move(event));
369       return;
370     }
371 
372     // For suspended IME extension (e.g. XKB extension), don't awake it by IME
373     // events except onActivate. The IME extension should be awake by other
374     // events (e.g. runtime.onMessage) from its other pages.
375     // This is to save memory for steady state Chrome OS on which the users
376     // don't want any IME features.
377     extensions::ExtensionRegistry* extension_registry =
378         extensions::ExtensionRegistry::Get(profile_);
379     if (extension_registry) {
380       const extensions::Extension* extension =
381           extension_registry->GetExtensionById(
382               extension_id_, extensions::ExtensionRegistry::ENABLED);
383       if (!extension)
384         return;
385       extensions::ProcessManager* process_manager =
386           extensions::ProcessManager::Get(profile_);
387       if (extensions::BackgroundInfo::HasBackgroundPage(extension) &&
388           !process_manager->GetBackgroundHostForExtension(extension_id_)) {
389         return;
390       }
391     }
392 
393     auto event = std::make_unique<extensions::Event>(
394         histogram_value, event_name, std::move(args), profile_);
395     extensions::EventRouter::Get(profile_)
396         ->DispatchEventToExtension(extension_id_, std::move(event));
397   }
398 
399   // The component IME extensions need to know the current screen type (e.g.
400   // lock screen, login screen, etc.) so that its on-screen keyboard page
401   // won't open new windows/pages. See crbug.com/395621.
GetCurrentScreenType()402   std::string GetCurrentScreenType() override {
403     switch (chromeos::input_method::InputMethodManager::Get()
404                 ->GetActiveIMEState()
405                 ->GetUIStyle()) {
406       case chromeos::input_method::InputMethodManager::UIStyle::kLogin:
407         return "login";
408       case chromeos::input_method::InputMethodManager::UIStyle::kSecondaryLogin:
409         return "secondary-login";
410       case chromeos::input_method::InputMethodManager::UIStyle::kLock:
411         return "lock";
412       case chromeos::input_method::InputMethodManager::UIStyle::kNormal:
413         return "normal";
414     }
415   }
416 
ConvertInputContextFocusReason(ui::IMEEngineHandlerInterface::InputContext input_context)417   std::string ConvertInputContextFocusReason(
418       ui::IMEEngineHandlerInterface::InputContext input_context) {
419     switch (input_context.focus_reason) {
420       case ui::TextInputClient::FOCUS_REASON_NONE:
421         return "";
422       case ui::TextInputClient::FOCUS_REASON_MOUSE:
423         return "mouse";
424       case ui::TextInputClient::FOCUS_REASON_TOUCH:
425         return "touch";
426       case ui::TextInputClient::FOCUS_REASON_PEN:
427         return "pen";
428       case ui::TextInputClient::FOCUS_REASON_OTHER:
429         return "other";
430     }
431   }
432 
ConvertInputContextAutoCorrect(ui::IMEEngineHandlerInterface::InputContext input_context)433   bool ConvertInputContextAutoCorrect(
434       ui::IMEEngineHandlerInterface::InputContext input_context) override {
435     if (!GetKeyboardConfig().auto_correct)
436       return false;
437     return ImeObserver::ConvertInputContextAutoCorrect(input_context);
438   }
439 
ConvertInputContextAutoComplete(ui::IMEEngineHandlerInterface::InputContext input_context)440   bool ConvertInputContextAutoComplete(
441       ui::IMEEngineHandlerInterface::InputContext input_context) override {
442     if (!GetKeyboardConfig().auto_complete)
443       return false;
444     return ImeObserver::ConvertInputContextAutoComplete(input_context);
445   }
446 
ConvertInputContextAutoCapitalize(int flags)447   input_method_private::AutoCapitalizeType ConvertInputContextAutoCapitalize(
448       int flags) {
449     if (!GetKeyboardConfig().auto_capitalize)
450       return input_method_private::AUTO_CAPITALIZE_TYPE_OFF;
451     if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_NONE)
452       return input_method_private::AUTO_CAPITALIZE_TYPE_OFF;
453     if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_CHARACTERS)
454       return input_method_private::AUTO_CAPITALIZE_TYPE_CHARACTERS;
455     if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_WORDS)
456       return input_method_private::AUTO_CAPITALIZE_TYPE_WORDS;
457     if (flags & ui::TEXT_INPUT_FLAG_AUTOCAPITALIZE_SENTENCES)
458       return input_method_private::AUTO_CAPITALIZE_TYPE_SENTENCES;
459 
460     // Autocapitalize flag may be missing for native text fields.
461     // See https://crbug.com/1002713.
462     return input_method_private::AUTO_CAPITALIZE_TYPE_NONE;
463   }
464 
ConvertInputContextSpellCheck(ui::IMEEngineHandlerInterface::InputContext input_context)465   bool ConvertInputContextSpellCheck(
466       ui::IMEEngineHandlerInterface::InputContext input_context) override {
467     if (!GetKeyboardConfig().spell_check)
468       return false;
469     return ImeObserver::ConvertInputContextSpellCheck(input_context);
470   }
471 
ConvertHasBeenPassword(ui::IMEEngineHandlerInterface::InputContext input_context)472   bool ConvertHasBeenPassword(
473       ui::IMEEngineHandlerInterface::InputContext input_context) {
474     return input_context.flags & ui::TEXT_INPUT_FLAG_HAS_BEEN_PASSWORD;
475   }
476 
ConvertInputContextMode(ui::IMEEngineHandlerInterface::InputContext input_context)477   std::string ConvertInputContextMode(
478       ui::IMEEngineHandlerInterface::InputContext input_context) {
479     std::string input_mode_type = "none";  // default to nothing
480     switch (input_context.mode) {
481       case ui::TEXT_INPUT_MODE_SEARCH:
482         input_mode_type = "search";
483         break;
484       case ui::TEXT_INPUT_MODE_TEL:
485         input_mode_type = "tel";
486         break;
487       case ui::TEXT_INPUT_MODE_URL:
488         input_mode_type = "url";
489         break;
490       case ui::TEXT_INPUT_MODE_EMAIL:
491         input_mode_type = "email";
492         break;
493       case ui::TEXT_INPUT_MODE_NUMERIC:
494         input_mode_type = "numeric";
495         break;
496       case ui::TEXT_INPUT_MODE_DECIMAL:
497         input_mode_type = "decimal";
498         break;
499       case ui::TEXT_INPUT_MODE_NONE:
500         input_mode_type = "noKeyboard";
501         break;
502       case ui::TEXT_INPUT_MODE_TEXT:
503         input_mode_type = "text";
504         break;
505       default:
506         input_mode_type = "";
507         break;
508     }
509     return input_mode_type;
510   }
511 
512   DISALLOW_COPY_AND_ASSIGN(ImeObserverChromeOS);
513 };
514 
515 }  // namespace
516 
517 namespace extensions {
518 
GetEngineIfActive(Profile * profile,const std::string & extension_id,std::string * error)519 InputMethodEngine* GetEngineIfActive(Profile* profile,
520                                      const std::string& extension_id,
521                                      std::string* error) {
522   InputImeEventRouter* event_router = GetInputImeEventRouter(profile);
523   DCHECK(event_router) << kErrorRouterNotAvailable;
524   InputMethodEngine* engine = static_cast<InputMethodEngine*>(
525       event_router->GetEngineIfActive(extension_id, error));
526   return engine;
527 }
528 
GetEngine(content::BrowserContext * browser_context,const std::string & extension_id,std::string * error)529 InputMethodEngine* GetEngine(content::BrowserContext* browser_context,
530                              const std::string& extension_id,
531                              std::string* error) {
532   Profile* profile = Profile::FromBrowserContext(browser_context);
533   InputImeEventRouter* event_router = GetInputImeEventRouter(profile);
534   DCHECK(event_router) << kErrorRouterNotAvailable;
535   InputMethodEngine* engine =
536       static_cast<InputMethodEngine*>(event_router->GetEngine(extension_id));
537   DCHECK(engine) << kErrorEngineNotAvailable;
538   if (!engine)
539     *error = kErrorEngineNotAvailable;
540   return engine;
541 }
542 
InputImeEventRouter(Profile * profile)543 InputImeEventRouter::InputImeEventRouter(Profile* profile)
544     : InputImeEventRouterBase(profile) {}
545 
546 InputImeEventRouter::~InputImeEventRouter() = default;
547 
RegisterImeExtension(const std::string & extension_id,const std::vector<InputComponentInfo> & input_components)548 bool InputImeEventRouter::RegisterImeExtension(
549     const std::string& extension_id,
550     const std::vector<InputComponentInfo>& input_components) {
551   VLOG(1) << "RegisterImeExtension: " << extension_id;
552 
553   if (engine_map_[extension_id])
554     return false;
555 
556   chromeos::input_method::InputMethodManager* manager =
557       chromeos::input_method::InputMethodManager::Get();
558   chromeos::ComponentExtensionIMEManager* comp_ext_ime_manager =
559       manager->GetComponentExtensionIMEManager();
560 
561   chromeos::input_method::InputMethodDescriptors descriptors;
562   // Only creates descriptors for 3rd party IME extension, because the
563   // descriptors for component IME extensions are managed by InputMethodUtil.
564   if (!comp_ext_ime_manager->IsAllowlistedExtension(extension_id)) {
565     for (const auto& component : input_components) {
566       DCHECK(component.type == INPUT_COMPONENT_TYPE_IME);
567 
568       std::vector<std::string> layouts;
569       layouts.assign(component.layouts.begin(), component.layouts.end());
570       std::vector<std::string> languages;
571       languages.assign(component.languages.begin(), component.languages.end());
572 
573       const std::string& input_method_id =
574           chromeos::extension_ime_util::GetInputMethodID(extension_id,
575                                                          component.id);
576       descriptors.push_back(chromeos::input_method::InputMethodDescriptor(
577           input_method_id,
578           component.name,
579           std::string(),  // TODO(uekawa): Set short name.
580           layouts,
581           languages,
582           false,  // 3rd party IMEs are always not for login.
583           component.options_page_url,
584           component.input_view_url));
585     }
586   }
587 
588   Profile* profile = GetProfile();
589   // TODO(https://crbug.com/1140236): Investigate whether profile selection
590   // is really needed.
591   bool is_login = false;
592   // When Chrome starts with the Login screen, sometimes active IME State was
593   // not set yet. It's asynchronous process. So we need a fallback for that.
594   scoped_refptr<chromeos::input_method::InputMethodManager::State>
595       active_state = chromeos::input_method::InputMethodManager::Get()
596                          ->GetActiveIMEState();
597   if (active_state) {
598     is_login = active_state->GetUIStyle() ==
599                chromeos::input_method::InputMethodManager::UIStyle::kLogin;
600   } else {
601     is_login = chromeos::ProfileHelper::IsSigninProfile(profile);
602   }
603 
604   if (is_login && profile->HasPrimaryOTRProfile()) {
605     profile = profile->GetPrimaryOTRProfile();
606   }
607 
608   auto observer = std::make_unique<ImeObserverChromeOS>(extension_id, profile);
609   auto engine = extension_id == "jkghodnilhceideoidjikpgommlajknk"
610                     ? std::make_unique<chromeos::NativeInputMethodEngine>()
611                     : std::make_unique<chromeos::InputMethodEngine>();
612   engine->Initialize(std::move(observer), extension_id.c_str(), profile);
613   engine_map_[extension_id] = std::move(engine);
614 
615   chromeos::UserSessionManager::GetInstance()
616       ->GetDefaultIMEState(profile)
617       ->AddInputMethodExtension(extension_id, descriptors,
618                                 engine_map_[extension_id].get());
619 
620   return true;
621 }
622 
UnregisterAllImes(const std::string & extension_id)623 void InputImeEventRouter::UnregisterAllImes(const std::string& extension_id) {
624   auto it = engine_map_.find(extension_id);
625   if (it != engine_map_.end()) {
626     chromeos::input_method::InputMethodManager::Get()
627         ->GetActiveIMEState()
628         ->RemoveInputMethodExtension(extension_id);
629     engine_map_.erase(it);
630   }
631 }
632 
GetEngine(const std::string & extension_id)633 InputMethodEngine* InputImeEventRouter::GetEngine(
634     const std::string& extension_id) {
635   auto it = engine_map_.find(extension_id);
636   if (it == engine_map_.end()) {
637     LOG(ERROR) << kErrorEngineNotAvailable << " extension id: " << extension_id;
638     return nullptr;
639   } else {
640     return it->second.get();
641   }
642 }
643 
GetEngineIfActive(const std::string & extension_id,std::string * error)644 InputMethodEngineBase* InputImeEventRouter::GetEngineIfActive(
645     const std::string& extension_id,
646     std::string* error) {
647   auto it = engine_map_.find(extension_id);
648   if (it == engine_map_.end()) {
649     LOG(ERROR) << kErrorEngineNotAvailable << " extension id: " << extension_id;
650     *error = kErrorEngineNotAvailable;
651     return nullptr;
652   } else if (it->second->IsActive()) {
653     return it->second.get();
654   } else {
655     LOG(WARNING) << kErrorEngineNotActive << " extension id: " << extension_id;
656     *error = kErrorEngineNotActive;
657     return nullptr;
658   }
659 }
660 
Run()661 ExtensionFunction::ResponseAction InputImeClearCompositionFunction::Run() {
662   std::string error;
663   InputMethodEngine* engine = GetEngineIfActive(
664       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
665   if (!engine) {
666     return RespondNow(Error(InformativeError(error, static_function_name())));
667   }
668 
669   std::unique_ptr<ClearComposition::Params> parent_params(
670       ClearComposition::Params::Create(*args_));
671   const ClearComposition::Params::Parameters& params =
672       parent_params->parameters;
673 
674   bool success = engine->ClearComposition(params.context_id, &error);
675   std::unique_ptr<base::ListValue> results =
676       std::make_unique<base::ListValue>();
677   results->Append(std::make_unique<base::Value>(success));
678   return RespondNow(success
679                         ? ArgumentList(std::move(results))
680                         : ErrorWithArguments(
681                               std::move(results),
682                               InformativeError(error, static_function_name())));
683 }
684 
Run()685 ExtensionFunction::ResponseAction InputImeHideInputViewFunction::Run() {
686   std::string error;
687   InputMethodEngine* engine = GetEngineIfActive(
688       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
689   if (!engine)
690     return RespondNow(Error(InformativeError(error, static_function_name())));
691   engine->HideInputView();
692   return RespondNow(NoArguments());
693 }
694 
695 ExtensionFunction::ResponseAction
Run()696 InputImeSetAssistiveWindowPropertiesFunction::Run() {
697   std::string error;
698   InputMethodEngine* engine = GetEngineIfActive(
699       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
700   if (!engine) {
701     return RespondNow(Error(InformativeError(error, static_function_name())));
702   }
703   std::unique_ptr<SetAssistiveWindowProperties::Params> parent_params(
704       SetAssistiveWindowProperties::Params::Create(*args_));
705   const SetAssistiveWindowProperties::Params::Parameters& params =
706       parent_params->parameters;
707   const input_ime::AssistiveWindowProperties& window = params.properties;
708   chromeos::AssistiveWindowProperties assistive_window;
709 
710   assistive_window.visible = window.visible;
711   assistive_window.type = ConvertAssistiveWindowType(window.type);
712   if (window.announce_string)
713     assistive_window.announce_string = *window.announce_string;
714 
715   engine->SetAssistiveWindowProperties(params.context_id, assistive_window,
716                                        &error);
717   if (!error.empty())
718     return RespondNow(Error(InformativeError(error, static_function_name())));
719   return RespondNow(OneArgument(base::Value(true)));
720 }
721 
722 ExtensionFunction::ResponseAction
Run()723 InputImeSetAssistiveWindowButtonHighlightedFunction::Run() {
724   std::string error;
725   InputMethodEngine* engine = GetEngineIfActive(
726       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
727   if (!engine) {
728     return RespondNow(Error(InformativeError(error, static_function_name())));
729   }
730   std::unique_ptr<SetAssistiveWindowButtonHighlighted::Params> parent_params(
731       SetAssistiveWindowButtonHighlighted::Params::Create(*args_));
732   const SetAssistiveWindowButtonHighlighted::Params::Parameters& params =
733       parent_params->parameters;
734   ui::ime::AssistiveWindowButton button;
735 
736   button.id = ConvertAssistiveWindowButtonId(params.button_id);
737   button.window_type = ConvertAssistiveWindowType(params.window_type);
738   if (params.announce_string)
739     button.announce_string = *params.announce_string;
740 
741   engine->SetButtonHighlighted(params.context_id, button, params.highlighted,
742                                &error);
743   if (!error.empty())
744     return RespondNow(Error(InformativeError(error, static_function_name())));
745 
746   return RespondNow(NoArguments());
747 }
748 
749 ExtensionFunction::ResponseAction
Run()750 InputImeSetCandidateWindowPropertiesFunction::Run() {
751   std::unique_ptr<SetCandidateWindowProperties::Params> parent_params(
752       SetCandidateWindowProperties::Params::Create(*args_));
753   const SetCandidateWindowProperties::Params::Parameters&
754       params = parent_params->parameters;
755 
756   std::string error;
757   InputMethodEngine* engine =
758       GetEngine(browser_context(), extension_id(), &error);
759   if (!engine) {
760     return RespondNow(Error(InformativeError(error, static_function_name())));
761   }
762 
763   const SetCandidateWindowProperties::Params::Parameters::Properties&
764       properties = params.properties;
765 
766   if (properties.visible &&
767       !engine->SetCandidateWindowVisible(*properties.visible, &error)) {
768     std::unique_ptr<base::ListValue> results =
769         std::make_unique<base::ListValue>();
770     results->Append(std::make_unique<base::Value>(false));
771     return RespondNow(ErrorWithArguments(
772         std::move(results), InformativeError(error, static_function_name())));
773   }
774 
775   InputMethodEngine::CandidateWindowProperty properties_out =
776       engine->GetCandidateWindowProperty(params.engine_id);
777   bool modified = false;
778 
779   if (properties.cursor_visible) {
780     properties_out.is_cursor_visible = *properties.cursor_visible;
781     modified = true;
782   }
783 
784   if (properties.vertical) {
785     properties_out.is_vertical = *properties.vertical;
786     modified = true;
787   }
788 
789   if (properties.page_size) {
790     properties_out.page_size = *properties.page_size;
791     modified = true;
792   }
793 
794   if (properties.window_position == input_ime::WINDOW_POSITION_COMPOSITION) {
795     properties_out.show_window_at_composition = true;
796     modified = true;
797   } else if (properties.window_position == input_ime::WINDOW_POSITION_CURSOR) {
798     properties_out.show_window_at_composition = false;
799     modified = true;
800   }
801 
802   if (properties.auxiliary_text) {
803     properties_out.auxiliary_text = *properties.auxiliary_text;
804     modified = true;
805   }
806 
807   if (properties.auxiliary_text_visible) {
808     properties_out.is_auxiliary_text_visible =
809         *properties.auxiliary_text_visible;
810     modified = true;
811   }
812 
813   if (properties.current_candidate_index) {
814     properties_out.current_candidate_index =
815         *properties.current_candidate_index;
816     modified = true;
817   }
818 
819   if (properties.total_candidates) {
820     properties_out.total_candidates = *properties.total_candidates;
821     modified = true;
822   }
823 
824   if (modified) {
825     engine->SetCandidateWindowProperty(params.engine_id, properties_out);
826   }
827 
828   return RespondNow(OneArgument(base::Value(true)));
829 }
830 
Run()831 ExtensionFunction::ResponseAction InputImeSetCandidatesFunction::Run() {
832   std::string error;
833   InputMethodEngine* engine = GetEngineIfActive(
834       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
835   if (!engine) {
836     return RespondNow(Error(InformativeError(error, static_function_name())));
837   }
838 
839   std::unique_ptr<SetCandidates::Params> parent_params(
840       SetCandidates::Params::Create(*args_));
841   const SetCandidates::Params::Parameters& params =
842       parent_params->parameters;
843 
844   std::vector<InputMethodEngine::Candidate> candidates_out;
845   for (const auto& candidate_in : params.candidates) {
846     candidates_out.emplace_back();
847     candidates_out.back().value = candidate_in.candidate;
848     candidates_out.back().id = candidate_in.id;
849     if (candidate_in.label)
850       candidates_out.back().label = *candidate_in.label;
851     if (candidate_in.annotation)
852       candidates_out.back().annotation = *candidate_in.annotation;
853     if (candidate_in.usage) {
854       candidates_out.back().usage.title = candidate_in.usage->title;
855       candidates_out.back().usage.body = candidate_in.usage->body;
856     }
857   }
858 
859   bool success =
860       engine->SetCandidates(params.context_id, candidates_out, &error);
861   std::unique_ptr<base::ListValue> results =
862       std::make_unique<base::ListValue>();
863   results->Append(std::make_unique<base::Value>(success));
864   return RespondNow(success
865                         ? ArgumentList(std::move(results))
866                         : ErrorWithArguments(
867                               std::move(results),
868                               InformativeError(error, static_function_name())));
869 }
870 
Run()871 ExtensionFunction::ResponseAction InputImeSetCursorPositionFunction::Run() {
872   std::string error;
873   InputMethodEngine* engine = GetEngineIfActive(
874       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
875   if (!engine) {
876     return RespondNow(Error(InformativeError(error, static_function_name())));
877   }
878 
879   std::unique_ptr<SetCursorPosition::Params> parent_params(
880       SetCursorPosition::Params::Create(*args_));
881   const SetCursorPosition::Params::Parameters& params =
882       parent_params->parameters;
883 
884   bool success =
885       engine->SetCursorPosition(params.context_id, params.candidate_id, &error);
886   std::unique_ptr<base::ListValue> results =
887       std::make_unique<base::ListValue>();
888   results->Append(std::make_unique<base::Value>(success));
889   return RespondNow(success
890                         ? ArgumentList(std::move(results))
891                         : ErrorWithArguments(
892                               std::move(results),
893                               InformativeError(error, static_function_name())));
894 }
895 
Run()896 ExtensionFunction::ResponseAction InputImeSetMenuItemsFunction::Run() {
897   std::unique_ptr<SetMenuItems::Params> parent_params(
898       SetMenuItems::Params::Create(*args_));
899   const input_ime::MenuParameters& params = parent_params->parameters;
900 
901   std::string error;
902   InputMethodEngine* engine =
903       GetEngine(browser_context(), extension_id(), &error);
904   if (!engine) {
905     return RespondNow(Error(InformativeError(error, static_function_name())));
906   }
907 
908   std::vector<chromeos::input_method::InputMethodManager::MenuItem> items_out;
909   for (const input_ime::MenuItem& item_in : params.items) {
910     items_out.emplace_back();
911     SetMenuItemToMenu(item_in, &items_out.back());
912   }
913 
914   if (!engine->SetMenuItems(items_out, &error)) {
915     return RespondNow(Error(InformativeError(
916         base::StringPrintf("%s %s", kErrorSetMenuItemsFail, error.c_str()),
917         static_function_name())));
918   }
919   return RespondNow(NoArguments());
920 }
921 
Run()922 ExtensionFunction::ResponseAction InputImeUpdateMenuItemsFunction::Run() {
923   std::unique_ptr<UpdateMenuItems::Params> parent_params(
924       UpdateMenuItems::Params::Create(*args_));
925   const input_ime::MenuParameters& params = parent_params->parameters;
926 
927   std::string error;
928   InputMethodEngine* engine =
929       GetEngine(browser_context(), extension_id(), &error);
930   if (!engine) {
931     return RespondNow(Error(InformativeError(error, static_function_name())));
932   }
933 
934   std::vector<chromeos::input_method::InputMethodManager::MenuItem> items_out;
935   for (const input_ime::MenuItem& item_in : params.items) {
936     items_out.emplace_back();
937     SetMenuItemToMenu(item_in, &items_out.back());
938   }
939 
940   if (!engine->UpdateMenuItems(items_out, &error)) {
941     return RespondNow(Error(InformativeError(
942         base::StringPrintf("%s %s", kErrorUpdateMenuItemsFail, error.c_str()),
943         static_function_name())));
944   }
945   return RespondNow(NoArguments());
946 }
947 
Run()948 ExtensionFunction::ResponseAction InputImeDeleteSurroundingTextFunction::Run() {
949   std::unique_ptr<DeleteSurroundingText::Params> parent_params(
950       DeleteSurroundingText::Params::Create(*args_));
951   const DeleteSurroundingText::Params::Parameters& params =
952       parent_params->parameters;
953 
954   std::string error;
955   InputMethodEngine* engine =
956       GetEngine(browser_context(), extension_id(), &error);
957   if (!engine) {
958     return RespondNow(Error(InformativeError(error, static_function_name())));
959   }
960 
961   engine->DeleteSurroundingText(params.context_id, params.offset, params.length,
962                                 &error);
963   return RespondNow(
964       error.empty() ? NoArguments()
965                     : Error(InformativeError(error, static_function_name())));
966 }
967 
968 ExtensionFunction::ResponseAction
Run()969 InputMethodPrivateFinishComposingTextFunction::Run() {
970   std::string error;
971   InputMethodEngine* engine = GetEngineIfActive(
972       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
973   if (!engine)
974     return RespondNow(Error(InformativeError(error, static_function_name())));
975   std::unique_ptr<FinishComposingText::Params> parent_params(
976       FinishComposingText::Params::Create(*args_));
977   const FinishComposingText::Params::Parameters& params =
978       parent_params->parameters;
979   engine->FinishComposingText(params.context_id, &error);
980   return RespondNow(
981       error.empty() ? NoArguments()
982                     : Error(InformativeError(error, static_function_name())));
983 }
984 
985 ExtensionFunction::ResponseAction
Run()986 InputMethodPrivateNotifyImeMenuItemActivatedFunction::Run() {
987   chromeos::input_method::InputMethodDescriptor current_input_method =
988       chromeos::input_method::InputMethodManager::Get()
989           ->GetActiveIMEState()
990           ->GetCurrentInputMethod();
991   std::string active_extension_id =
992       chromeos::extension_ime_util::GetExtensionIDFromInputMethodID(
993           current_input_method.id());
994   std::string error;
995   InputMethodEngine* engine =
996       GetEngineIfActive(Profile::FromBrowserContext(browser_context()),
997                         active_extension_id, &error);
998   if (!engine)
999     return RespondNow(Error(InformativeError(error, static_function_name())));
1000 
1001   std::unique_ptr<NotifyImeMenuItemActivated::Params> params(
1002       NotifyImeMenuItemActivated::Params::Create(*args_));
1003   if (params->engine_id != engine->GetActiveComponentId())
1004     return RespondNow(
1005         Error(InformativeError(kErrorEngineNotActive, static_function_name())));
1006   engine->PropertyActivate(params->name);
1007   return RespondNow(NoArguments());
1008 }
1009 
1010 ExtensionFunction::ResponseAction
Run()1011 InputMethodPrivateGetCompositionBoundsFunction::Run() {
1012   std::string error;
1013   InputMethodEngine* engine = GetEngineIfActive(
1014       Profile::FromBrowserContext(browser_context()), extension_id(), &error);
1015   if (!engine)
1016     return RespondNow(Error(InformativeError(error, static_function_name())));
1017 
1018   auto bounds_list = std::make_unique<base::ListValue>();
1019   for (const auto& bounds : engine->composition_bounds()) {
1020     auto bounds_value = std::make_unique<base::DictionaryValue>();
1021     bounds_value->SetInteger("x", bounds.x());
1022     bounds_value->SetInteger("y", bounds.y());
1023     bounds_value->SetInteger("w", bounds.width());
1024     bounds_value->SetInteger("h", bounds.height());
1025     bounds_list->Append(std::move(bounds_value));
1026   }
1027 
1028   return RespondNow(
1029       OneArgument(base::Value::FromUniquePtrValue(std::move(bounds_list))));
1030 }
1031 
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)1032 void InputImeAPI::OnExtensionLoaded(content::BrowserContext* browser_context,
1033                                     const Extension* extension) {
1034   const std::vector<InputComponentInfo>* input_components =
1035       InputComponents::GetInputComponents(extension);
1036   InputImeEventRouter* event_router =
1037       GetInputImeEventRouter(Profile::FromBrowserContext(browser_context));
1038   if (input_components && event_router) {
1039     if (extension->id() == event_router->GetUnloadedExtensionId()) {
1040       // After the 1st-party IME extension being unloaded unexpectedly,
1041       // we don't unregister the IME entries so after the extension being
1042       // reloaded we should reactivate the engine so that the IME extension
1043       // can receive the onActivate event to recover itself upon the
1044       // unexpected unload.
1045       std::string error;
1046       InputMethodEngineBase* engine =
1047           event_router->GetEngineIfActive(extension->id(), &error);
1048       DCHECK(engine) << error;
1049       // When extension is unloaded unexpectedly and reloaded, OS doesn't pass
1050       // details.browser_context value in OnListenerAdded callback. So we need
1051       // to reactivate engine here.
1052       if (engine)
1053         engine->Enable(engine->GetActiveComponentId());
1054       event_router->SetUnloadedExtensionId("");
1055     } else {
1056       event_router->RegisterImeExtension(extension->id(), *input_components);
1057     }
1058   }
1059 }
1060 
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionReason reason)1061 void InputImeAPI::OnExtensionUnloaded(content::BrowserContext* browser_context,
1062                                       const Extension* extension,
1063                                       UnloadedExtensionReason reason) {
1064   const std::vector<InputComponentInfo>* input_components =
1065       InputComponents::GetInputComponents(extension);
1066   if (!input_components || input_components->empty())
1067     return;
1068   InputImeEventRouter* event_router =
1069       GetInputImeEventRouter(Profile::FromBrowserContext(browser_context));
1070   if (!event_router)
1071     return;
1072   chromeos::input_method::InputMethodManager* manager =
1073       chromeos::input_method::InputMethodManager::Get();
1074   chromeos::ComponentExtensionIMEManager* comp_ext_ime_manager =
1075       manager->GetComponentExtensionIMEManager();
1076 
1077   if (comp_ext_ime_manager->IsAllowlistedExtension(extension->id())) {
1078     // Since the first party ime is not allow to uninstall, and when it's
1079     // unloaded unexpectedly, OS will recover the extension at once.
1080     // So should not unregister the IMEs. Otherwise the IME icons on the
1081     // desktop shelf will disappear. see bugs: 775507,788247,786273,761714.
1082     // But still need to unload keyboard container document. Since ime extension
1083     // need to re-render the document when it's recovered.
1084     auto* keyboard_client = ChromeKeyboardControllerClient::Get();
1085     if (keyboard_client->is_keyboard_enabled()) {
1086       // Keyboard controller "Reload" method only reload current page when the
1087       // url is changed. So we need unload the current page first. Then next
1088       // engine->Enable() can refresh the inputview page correctly.
1089       // Empties the content url and reload the controller to unload the
1090       // current page.
1091       // TODO(wuyingbing): Should add a new method to unload the document.
1092       manager->GetActiveIMEState()->DisableInputView();
1093       keyboard_client->ReloadKeyboardIfNeeded();
1094     }
1095     event_router->SetUnloadedExtensionId(extension->id());
1096   } else {
1097     event_router->UnregisterAllImes(extension->id());
1098   }
1099 }
1100 
OnListenerAdded(const EventListenerInfo & details)1101 void InputImeAPI::OnListenerAdded(const EventListenerInfo& details) {
1102   if (!details.browser_context)
1103     return;
1104   std::string error;
1105   InputMethodEngine* engine =
1106       GetEngineIfActive(Profile::FromBrowserContext(details.browser_context),
1107                         details.extension_id, &error);
1108   // Notifies the IME extension for IME ready with onActivate/onFocus events.
1109   if (engine)
1110     engine->Enable(engine->GetActiveComponentId());
1111 }
1112 
1113 }  // namespace extensions
1114