input_ime_api.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright (c) 2012 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 "base/json/json_writer.h"
8#include "base/lazy_instance.h"
9#include "base/stl_util.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/values.h"
12#include "chrome/browser/chromeos/input_method/input_method_engine.h"
13#include "chrome/browser/extensions/event_router.h"
14#include "chrome/browser/extensions/extension_function_registry.h"
15#include "chrome/browser/extensions/extension_input_module_constants.h"
16#include "chrome/browser/extensions/extension_system.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/common/chrome_notification_types.h"
19#include "chrome/common/extensions/api/input_ime/input_components_handler.h"
20#include "content/public/browser/notification_details.h"
21#include "content/public/browser/notification_source.h"
22
23namespace keys = extension_input_module_constants;
24
25namespace {
26
27const char kStyleNone[] = "none";
28const char kStyleCheck[] = "check";
29const char kStyleRadio[] = "radio";
30const char kStyleSeparator[] = "separator";
31const char kWindowPositionComposition[] = "composition";
32
33const char kErrorEngineNotAvailable[] = "Engine is not available";
34const char kErrorBadCandidateList[] = "Invalid candidate list provided";
35const char kErrorSetMenuItemsFail[] = "Could not create menu Items";
36const char kErrorUpdateMenuItemsFail[] = "Could not update menu Items";
37
38bool ReadMenuItems(
39    base::ListValue* menu_items,
40    std::vector<chromeos::InputMethodEngine::MenuItem>* output) {
41  for (size_t i = 0; i < menu_items->GetSize(); ++i) {
42    base::DictionaryValue* item_dict;
43    if (!menu_items->GetDictionary(i, &item_dict)) {
44      return false;
45    }
46
47    std::string id;
48    std::string label;
49    chromeos::InputMethodEngine::MenuItemStyle style =
50        chromeos::InputMethodEngine::MENU_ITEM_STYLE_NONE;
51    bool visible = true;
52    bool enabled = true;
53    bool checked = false;
54    std::string icon;
55    chromeos::InputMethodEngine::KeyboardEvent shortcut_key;
56
57    unsigned int modified = 0;
58
59    if (!item_dict->GetString(keys::kIdKey, &id)) {
60      return false;
61    }
62
63    if (item_dict->HasKey(keys::kLabelKey)) {
64      if (!item_dict->GetString(keys::kLabelKey, &label)) {
65        return false;
66      }
67      modified |= chromeos::InputMethodEngine::MENU_ITEM_MODIFIED_LABEL;
68    }
69    if (item_dict->HasKey(keys::kStyleKey)) {
70      std::string style_string;
71      if (!item_dict->GetString(keys::kStyleKey, &style_string)) {
72        return false;
73      }
74
75      if (style_string == kStyleNone) {
76        style = chromeos::InputMethodEngine::MENU_ITEM_STYLE_NONE;
77      } else if (style_string == kStyleCheck) {
78        style = chromeos::InputMethodEngine::MENU_ITEM_STYLE_CHECK;
79      } else if (style_string == kStyleRadio) {
80        style = chromeos::InputMethodEngine::MENU_ITEM_STYLE_RADIO;
81      } else if (style_string == kStyleSeparator) {
82        style = chromeos::InputMethodEngine::MENU_ITEM_STYLE_SEPARATOR;
83      } else {
84        return false;
85      }
86      modified |= chromeos::InputMethodEngine::MENU_ITEM_MODIFIED_STYLE;
87    }
88
89    if (item_dict->HasKey(keys::kVisibleKey)) {
90      if (!item_dict->GetBoolean(keys::kVisibleKey, &visible)) {
91        return false;
92      }
93      modified |= chromeos::InputMethodEngine::MENU_ITEM_MODIFIED_VISIBLE;
94    }
95
96    if (item_dict->HasKey(keys::kCheckedKey)) {
97      if (!item_dict->GetBoolean(keys::kCheckedKey, &checked)) {
98        return false;
99      }
100      modified |= chromeos::InputMethodEngine::MENU_ITEM_MODIFIED_CHECKED;
101    }
102
103    if (item_dict->HasKey(keys::kEnabledKey)) {
104      if (!item_dict->GetBoolean(keys::kEnabledKey, &enabled)) {
105        return false;
106      }
107      modified |= chromeos::InputMethodEngine::MENU_ITEM_MODIFIED_ENABLED;
108    }
109
110    output->push_back(chromeos::InputMethodEngine::MenuItem());
111    output->back().id = id;
112    output->back().label = label;
113    output->back().style = style;
114    output->back().visible = visible;
115    output->back().enabled = enabled;
116    output->back().checked = checked;
117
118    output->back().modified = modified;
119
120    if (item_dict->HasKey(keys::kItemsKey)) {
121      base::ListValue* sub_list;
122      if (!item_dict->GetList(keys::kItemsKey, &sub_list)) {
123        return false;
124      }
125
126      if (!ReadMenuItems(sub_list, &(output->back().children))) {
127        return false;
128      }
129    }
130  }
131
132  return true;
133}
134
135static void DispatchEventToExtension(Profile* profile,
136                                     const std::string& extension_id,
137                                     const std::string& event_name,
138                                     scoped_ptr<base::ListValue> args) {
139  scoped_ptr<extensions::Event> event(new extensions::Event(
140      event_name, args.Pass()));
141  event->restrict_to_profile = profile;
142  extensions::ExtensionSystem::Get(profile)->event_router()->
143      DispatchEventToExtension(extension_id, event.Pass());
144}
145
146}  // namespace
147
148namespace events {
149
150const char kOnActivate[] = "input.ime.onActivate";
151const char kOnDeactivated[] = "input.ime.onDeactivated";
152const char kOnFocus[] = "input.ime.onFocus";
153const char kOnBlur[] = "input.ime.onBlur";
154const char kOnInputContextUpdate[] = "input.ime.onInputContextUpdate";
155const char kOnKeyEvent[] = "input.ime.onKeyEvent";
156const char kOnCandidateClicked[] = "input.ime.onCandidateClicked";
157const char kOnMenuItemActivated[] = "input.ime.onMenuItemActivated";
158const char kOnSurroundingTextChanged[] = "input.ime.onSurroundingTextChanged";
159const char kOnReset[] = "input.ime.onReset";
160
161}  // namespace events
162
163namespace chromeos {
164class ImeObserver : public chromeos::InputMethodEngine::Observer {
165 public:
166  ImeObserver(Profile* profile, const std::string& extension_id,
167              const std::string& engine_id) :
168    profile_(profile),
169    extension_id_(extension_id),
170    engine_id_(engine_id) {
171  }
172
173  virtual ~ImeObserver() {}
174
175  virtual void OnActivate(const std::string& engine_id) OVERRIDE {
176    if (profile_ == NULL || extension_id_.empty())
177      return;
178
179    scoped_ptr<base::ListValue> args(new base::ListValue());
180    args->Append(Value::CreateStringValue(engine_id));
181
182    DispatchEventToExtension(profile_, extension_id_,
183                             events::kOnActivate, args.Pass());
184  }
185
186  virtual void OnDeactivated(const std::string& engine_id) OVERRIDE {
187    if (profile_ == NULL || extension_id_.empty())
188      return;
189
190    scoped_ptr<base::ListValue> args(new base::ListValue());
191    args->Append(Value::CreateStringValue(engine_id));
192
193    DispatchEventToExtension(profile_, extension_id_,
194                             events::kOnDeactivated, args.Pass());
195  }
196
197  virtual void OnFocus(
198      const InputMethodEngine::InputContext& context) OVERRIDE {
199    if (profile_ == NULL || extension_id_.empty())
200      return;
201
202    base::DictionaryValue* dict = new base::DictionaryValue();
203    dict->SetInteger("contextID", context.id);
204    dict->SetString("type", context.type);
205
206    scoped_ptr<base::ListValue> args(new base::ListValue());
207    args->Append(dict);
208
209    DispatchEventToExtension(profile_, extension_id_,
210                             events::kOnFocus, args.Pass());
211  }
212
213  virtual void OnBlur(int context_id) OVERRIDE {
214    if (profile_ == NULL || extension_id_.empty())
215      return;
216
217    scoped_ptr<base::ListValue> args(new base::ListValue());
218    args->Append(Value::CreateIntegerValue(context_id));
219
220    DispatchEventToExtension(profile_, extension_id_,
221                             events::kOnBlur, args.Pass());
222  }
223
224  virtual void OnInputContextUpdate(
225      const InputMethodEngine::InputContext& context) OVERRIDE {
226    if (profile_ == NULL || extension_id_.empty())
227      return;
228
229    base::DictionaryValue* dict = new base::DictionaryValue();
230    dict->SetInteger("contextID", context.id);
231    dict->SetString("type", context.type);
232
233    scoped_ptr<base::ListValue> args(new base::ListValue());
234    args->Append(dict);
235
236    DispatchEventToExtension(profile_, extension_id_,
237                             events::kOnInputContextUpdate, args.Pass());
238  }
239
240  virtual void OnKeyEvent(
241      const std::string& engine_id,
242      const InputMethodEngine::KeyboardEvent& event,
243      chromeos::input_method::KeyEventHandle* key_data) OVERRIDE {
244    if (profile_ == NULL || extension_id_.empty())
245      return;
246
247    std::string request_id =
248        extensions::InputImeEventRouter::GetInstance()->AddRequest(engine_id,
249                                                                   key_data);
250
251    base::DictionaryValue* dict = new base::DictionaryValue();
252    dict->SetString("type", event.type);
253    dict->SetString("requestId", request_id);
254    dict->SetString("key", event.key);
255    dict->SetString("code", event.code);
256    dict->SetBoolean("altKey", event.alt_key);
257    dict->SetBoolean("ctrlKey", event.ctrl_key);
258    dict->SetBoolean("shiftKey", event.shift_key);
259    dict->SetBoolean("capsLock", event.caps_lock);
260
261    scoped_ptr<base::ListValue> args(new base::ListValue());
262    args->Append(Value::CreateStringValue(engine_id));
263    args->Append(dict);
264
265    DispatchEventToExtension(profile_, extension_id_,
266                             events::kOnKeyEvent, args.Pass());
267  }
268
269  virtual void OnCandidateClicked(
270      const std::string& engine_id,
271      int candidate_id,
272      chromeos::InputMethodEngine::MouseButtonEvent button) OVERRIDE {
273    if (profile_ == NULL || extension_id_.empty())
274      return;
275
276    scoped_ptr<base::ListValue> args(new base::ListValue());
277    args->Append(Value::CreateStringValue(engine_id));
278    args->Append(Value::CreateIntegerValue(candidate_id));
279    switch (button) {
280      case chromeos::InputMethodEngine::MOUSE_BUTTON_MIDDLE:
281        args->Append(Value::CreateStringValue("middle"));
282        break;
283
284      case chromeos::InputMethodEngine::MOUSE_BUTTON_RIGHT:
285        args->Append(Value::CreateStringValue("right"));
286        break;
287
288      case chromeos::InputMethodEngine::MOUSE_BUTTON_LEFT:
289      // Default to left.
290      default:
291        args->Append(Value::CreateStringValue("left"));
292        break;
293    }
294
295    DispatchEventToExtension(profile_, extension_id_,
296                             events::kOnCandidateClicked, args.Pass());
297  }
298
299  virtual void OnMenuItemActivated(const std::string& engine_id,
300                                   const std::string& menu_id) OVERRIDE {
301    if (profile_ == NULL || extension_id_.empty())
302      return;
303
304    scoped_ptr<base::ListValue> args(new base::ListValue());
305    args->Append(Value::CreateStringValue(engine_id));
306    args->Append(Value::CreateStringValue(menu_id));
307
308    DispatchEventToExtension(profile_, extension_id_,
309                             events::kOnMenuItemActivated, args.Pass());
310  }
311
312  virtual void OnSurroundingTextChanged(const std::string& engine_id,
313                                        const std::string& text,
314                                        int cursor_pos,
315                                        int anchor_pos) OVERRIDE {
316    if (profile_ == NULL || extension_id_.empty())
317      return;
318    base::DictionaryValue* dict = new base::DictionaryValue();
319    dict->SetString("text", text);
320    dict->SetInteger("focus", cursor_pos);
321    dict->SetInteger("anchor", anchor_pos);
322
323    scoped_ptr<ListValue> args(new base::ListValue);
324    args->Append(Value::CreateStringValue(engine_id));
325    args->Append(dict);
326
327    DispatchEventToExtension(profile_, extension_id_,
328                             events::kOnSurroundingTextChanged, args.Pass());
329  }
330
331  virtual void OnReset(const std::string& engine_id) OVERRIDE {
332    if (profile_ == NULL || extension_id_.empty())
333      return;
334    scoped_ptr<base::ListValue> args(new base::ListValue());
335    args->Append(Value::CreateStringValue(engine_id));
336
337    DispatchEventToExtension(profile_, extension_id_,
338                             events::kOnReset, args.Pass());
339  }
340
341 private:
342  Profile* profile_;
343  std::string extension_id_;
344  std::string engine_id_;
345
346  DISALLOW_COPY_AND_ASSIGN(ImeObserver);
347};
348
349}  // namespace chromeos
350
351namespace extensions {
352
353InputImeEventRouter*
354InputImeEventRouter::GetInstance() {
355  return Singleton<InputImeEventRouter>::get();
356}
357
358#if defined(OS_CHROMEOS)
359bool InputImeEventRouter::RegisterIme(
360    Profile* profile,
361    const std::string& extension_id,
362    const extensions::InputComponentInfo& component) {
363  VLOG(1) << "RegisterIme: " << extension_id << " id: " << component.id;
364
365  std::map<std::string, chromeos::InputMethodEngine*>& engine_map =
366      engines_[extension_id];
367
368  std::map<std::string, chromeos::InputMethodEngine*>::iterator engine_ix =
369      engine_map.find(component.id);
370  if (engine_ix != engine_map.end()) {
371    return false;
372  }
373
374  std::string error;
375  chromeos::ImeObserver* observer = new chromeos::ImeObserver(profile,
376                                                              extension_id,
377                                                              component.id);
378  std::vector<std::string> layouts;
379  layouts.assign(component.layouts.begin(), component.layouts.end());
380
381  chromeos::InputMethodEngine* engine =
382      chromeos::InputMethodEngine::CreateEngine(
383          observer, component.name.c_str(), extension_id.c_str(),
384          component.id.c_str(), component.description.c_str(),
385          component.language.c_str(), layouts, component.options_page_url,
386          &error);
387  if (!engine) {
388    delete observer;
389    LOG(ERROR) << "RegisterIme: " << error;
390    return false;
391  }
392
393  engine_map[component.id] = engine;
394
395  std::map<std::string, chromeos::ImeObserver*>& observer_list =
396      observers_[extension_id];
397
398  observer_list[component.id] = observer;
399
400  return true;
401}
402
403void InputImeEventRouter::UnregisterAllImes(
404    Profile* profile, const std::string& extension_id) {
405  std::map<std::string,
406           std::map<std::string,
407                    chromeos::InputMethodEngine*> >::iterator engine_map =
408      engines_.find(extension_id);
409  if (engine_map != engines_.end()) {
410    STLDeleteContainerPairSecondPointers(engine_map->second.begin(),
411                                         engine_map->second.end());
412    engines_.erase(engine_map);
413  }
414
415  std::map<std::string,
416           std::map<std::string,
417                    chromeos::ImeObserver*> >::iterator observer_list =
418      observers_.find(extension_id);
419  if (observer_list != observers_.end()) {
420    STLDeleteContainerPairSecondPointers(observer_list->second.begin(),
421                                         observer_list->second.end());
422    observers_.erase(observer_list);
423  }
424}
425#endif
426
427chromeos::InputMethodEngine* InputImeEventRouter::GetEngine(
428    const std::string& extension_id, const std::string& engine_id) {
429  std::map<std::string,
430           std::map<std::string, chromeos::InputMethodEngine*> >::const_iterator
431               engine_list = engines_.find(extension_id);
432  if (engine_list != engines_.end()) {
433    std::map<std::string, chromeos::InputMethodEngine*>::const_iterator
434        engine_ix = engine_list->second.find(engine_id);
435    if (engine_ix != engine_list->second.end()) {
436      return engine_ix->second;
437    }
438  }
439  return NULL;
440}
441
442chromeos::InputMethodEngine* InputImeEventRouter::GetActiveEngine(
443    const std::string& extension_id) {
444  std::map<std::string,
445           std::map<std::string, chromeos::InputMethodEngine*> >::const_iterator
446               engine_list = engines_.find(extension_id);
447  if (engine_list != engines_.end()) {
448    std::map<std::string, chromeos::InputMethodEngine*>::const_iterator
449        engine_ix;
450    for (engine_ix = engine_list->second.begin();
451         engine_ix != engine_list->second.end();
452         ++engine_ix) {
453      if (engine_ix->second->IsActive()) {
454        return engine_ix->second;
455      }
456    }
457  }
458  return NULL;
459}
460
461void InputImeEventRouter::OnKeyEventHandled(
462    const std::string& extension_id,
463    const std::string& request_id,
464    bool handled) {
465  RequestMap::iterator request = request_map_.find(request_id);
466  if (request == request_map_.end()) {
467    LOG(ERROR) << "Request ID not found: " << request_id;
468    return;
469  }
470
471  std::string engine_id = request->second.first;
472  chromeos::input_method::KeyEventHandle* key_data = request->second.second;
473  request_map_.erase(request);
474
475  chromeos::InputMethodEngine* engine = GetEngine(extension_id, engine_id);
476  if (!engine) {
477    LOG(ERROR) << "Engine does not exist: " << engine_id;
478    return;
479  }
480
481  engine->KeyEventDone(key_data, handled);
482}
483
484std::string InputImeEventRouter::AddRequest(
485    const std::string& engine_id,
486    chromeos::input_method::KeyEventHandle* key_data) {
487  std::string request_id = base::IntToString(next_request_id_);
488  ++next_request_id_;
489
490  request_map_[request_id] = std::make_pair(engine_id, key_data);
491
492  return request_id;
493}
494
495InputImeEventRouter::InputImeEventRouter()
496  : next_request_id_(1) {
497}
498
499InputImeEventRouter::~InputImeEventRouter() {}
500
501bool SetCompositionFunction::RunImpl() {
502  chromeos::InputMethodEngine* engine =
503      InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id());
504  if (!engine) {
505    SetResult(Value::CreateBooleanValue(false));
506    return true;
507  }
508
509  base::DictionaryValue* args;
510  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
511  int context_id;
512  std::string text;
513  int selection_start;
514  int selection_end;
515  int cursor;
516  std::vector<chromeos::InputMethodEngine::SegmentInfo> segments;
517
518  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kContextIdKey,
519                                               &context_id));
520  EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kTextKey, &text));
521  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kCursorKey, &cursor));
522  if (args->HasKey(keys::kSelectionStartKey)) {
523    EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kSelectionStartKey,
524                                                 &selection_start));
525  } else {
526    selection_start = cursor;
527  }
528  if (args->HasKey(keys::kSelectionEndKey)) {
529    EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kSelectionEndKey,
530                                                 &selection_end));
531  } else {
532    selection_end = cursor;
533  }
534
535  if (args->HasKey(keys::kSegmentsKey)) {
536    base::ListValue* segment_list = NULL;
537    EXTENSION_FUNCTION_VALIDATE(args->GetList(keys::kSegmentsKey,
538                                              &segment_list));
539
540    for (size_t i = 0; i < segment_list->GetSize(); ++i) {
541      base::DictionaryValue* segment = NULL;
542      if (!segment_list->GetDictionary(i, &segment))
543        continue;
544
545      int start;
546      int end;
547      std::string style;
548
549      EXTENSION_FUNCTION_VALIDATE(segment->GetInteger(keys::kStartKey,
550                                                      &start));
551      EXTENSION_FUNCTION_VALIDATE(segment->GetInteger(keys::kEndKey, &end));
552      EXTENSION_FUNCTION_VALIDATE(segment->GetString(keys::kStyleKey, &style));
553
554      segments.push_back(chromeos::InputMethodEngine::SegmentInfo());
555      segments.back().start = start;
556      segments.back().end = end;
557      if (style == keys::kStyleUnderline) {
558        segments.back().style =
559            chromeos::InputMethodEngine::SEGMENT_STYLE_UNDERLINE;
560      } else if (style == keys::kStyleDoubleUnderline) {
561        segments.back().style =
562            chromeos::InputMethodEngine::SEGMENT_STYLE_DOUBLE_UNDERLINE;
563      }
564    }
565  }
566
567  if (engine->SetComposition(context_id, text.c_str(), selection_start,
568                             selection_end, cursor, segments, &error_)) {
569    SetResult(Value::CreateBooleanValue(true));
570  } else {
571    SetResult(Value::CreateBooleanValue(false));
572  }
573  return true;
574}
575
576bool ClearCompositionFunction::RunImpl() {
577  chromeos::InputMethodEngine* engine =
578      InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id());
579  if (!engine) {
580    SetResult(Value::CreateBooleanValue(false));
581    return true;
582  }
583
584  base::DictionaryValue* args;
585  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
586  int context_id;
587
588  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kContextIdKey,
589                                               &context_id));
590
591  if (engine->ClearComposition(context_id, &error_)) {
592    SetResult(Value::CreateBooleanValue(true));
593  } else {
594    SetResult(Value::CreateBooleanValue(false));
595  }
596  return true;
597}
598
599bool CommitTextFunction::RunImpl() {
600  // TODO(zork): Support committing when not active.
601  chromeos::InputMethodEngine* engine =
602      InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id());
603  if (!engine) {
604    SetResult(Value::CreateBooleanValue(false));
605    return true;
606  }
607
608  base::DictionaryValue* args;
609  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
610  int context_id;
611  std::string text;
612
613  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kContextIdKey,
614                                                     &context_id));
615  EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kTextKey, &text));
616
617  if (engine->CommitText(context_id, text.c_str(), &error_)) {
618    SetResult(Value::CreateBooleanValue(true));
619  } else {
620    SetResult(Value::CreateBooleanValue(false));
621  }
622  return true;
623}
624
625bool SetCandidateWindowPropertiesFunction::RunImpl() {
626  base::DictionaryValue* args;
627  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
628
629  std::string engine_id;
630  EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kEngineIdKey, &engine_id));
631
632  chromeos::InputMethodEngine* engine =
633      InputImeEventRouter::GetInstance()->GetEngine(extension_id(), engine_id);
634  if (!engine) {
635    SetResult(Value::CreateBooleanValue(false));
636    return true;
637  }
638
639  base::DictionaryValue* properties;
640  EXTENSION_FUNCTION_VALIDATE(args->GetDictionary(keys::kPropertiesKey,
641                                                  &properties));
642
643  if (properties->HasKey(keys::kVisibleKey)) {
644    bool visible;
645    EXTENSION_FUNCTION_VALIDATE(properties->GetBoolean(keys::kVisibleKey,
646                                                       &visible));
647    if (!engine->SetCandidateWindowVisible(visible, &error_)) {
648      SetResult(Value::CreateBooleanValue(false));
649      return true;
650    }
651  }
652
653  if (properties->HasKey(keys::kCursorVisibleKey)) {
654    bool visible;
655    EXTENSION_FUNCTION_VALIDATE(properties->GetBoolean(keys::kCursorVisibleKey,
656                                                       &visible));
657    engine->SetCandidateWindowCursorVisible(visible);
658  }
659
660  if (properties->HasKey(keys::kVerticalKey)) {
661    bool vertical;
662    EXTENSION_FUNCTION_VALIDATE(properties->GetBoolean(keys::kVerticalKey,
663                                                       &vertical));
664    engine->SetCandidateWindowVertical(vertical);
665  }
666
667  if (properties->HasKey(keys::kPageSizeKey)) {
668    int page_size;
669    EXTENSION_FUNCTION_VALIDATE(properties->GetInteger(keys::kPageSizeKey,
670                                                       &page_size));
671    engine->SetCandidateWindowPageSize(page_size);
672  }
673
674  if (properties->HasKey(keys::kAuxiliaryTextKey)) {
675    std::string aux_text;
676    EXTENSION_FUNCTION_VALIDATE(properties->GetString(keys::kAuxiliaryTextKey,
677                                                      &aux_text));
678    engine->SetCandidateWindowAuxText(aux_text.c_str());
679  }
680
681  if (properties->HasKey(keys::kAuxiliaryTextVisibleKey)) {
682    bool visible;
683    EXTENSION_FUNCTION_VALIDATE(properties->GetBoolean(
684        keys::kAuxiliaryTextVisibleKey,
685        &visible));
686    engine->SetCandidateWindowAuxTextVisible(visible);
687  }
688
689  if (properties->HasKey(keys::kWindowPositionKey)) {
690    // TODO(nona): Switch to scheme compiler. (crbug.com/235552)
691    chromeos::InputMethodEngine::CandidateWindowPosition window_position =
692      chromeos::InputMethodEngine::WINDOW_POS_CURSOR;
693    std::string position_in_str;
694    EXTENSION_FUNCTION_VALIDATE(properties->GetString(
695        keys::kWindowPositionKey,
696        &position_in_str));
697    window_position = (position_in_str == kWindowPositionComposition) ?
698        chromeos::InputMethodEngine::WINDOW_POS_COMPOSITTION :
699        chromeos::InputMethodEngine::WINDOW_POS_CURSOR;
700    engine->SetCandidateWindowPosition(window_position);
701  }
702
703  SetResult(Value::CreateBooleanValue(true));
704
705  return true;
706}
707
708#if defined(OS_CHROMEOS)
709bool SetCandidatesFunction::ReadCandidates(
710    base::ListValue* candidates,
711    std::vector<chromeos::InputMethodEngine::Candidate>* output) {
712  for (size_t i = 0; i < candidates->GetSize(); ++i) {
713    base::DictionaryValue* candidate_dict;
714    EXTENSION_FUNCTION_VALIDATE(candidates->GetDictionary(i, &candidate_dict));
715
716    std::string candidate;
717    int id;
718    std::string label;
719    std::string annotation;
720    chromeos::InputMethodEngine::UsageEntry usage_entry;
721
722    EXTENSION_FUNCTION_VALIDATE(candidate_dict->GetString(keys::kCandidateKey,
723                                                          &candidate));
724    EXTENSION_FUNCTION_VALIDATE(candidate_dict->GetInteger(keys::kIdKey, &id));
725
726    if (candidate_dict->HasKey(keys::kLabelKey)) {
727      EXTENSION_FUNCTION_VALIDATE(candidate_dict->GetString(keys::kLabelKey,
728                                                            &label));
729    }
730    if (candidate_dict->HasKey(keys::kAnnotationKey)) {
731      EXTENSION_FUNCTION_VALIDATE(candidate_dict->GetString(
732          keys::kAnnotationKey,
733          &annotation));
734    }
735
736    if (candidate_dict->HasKey(keys::kUsageKey)) {
737      base::DictionaryValue* usage_dict;
738      EXTENSION_FUNCTION_VALIDATE(candidate_dict->GetDictionary(keys::kUsageKey,
739                                                                &usage_dict));
740      EXTENSION_FUNCTION_VALIDATE(usage_dict->GetString(keys::kUsageTitleKey,
741                                                        &usage_entry.title));
742      EXTENSION_FUNCTION_VALIDATE(usage_dict->GetString(keys::kUsageBodyKey,
743                                                        &usage_entry.body));
744    }
745
746    output->push_back(chromeos::InputMethodEngine::Candidate());
747    output->back().value = candidate;
748    output->back().id = id;
749    output->back().label = label;
750    output->back().annotation = annotation;
751    output->back().usage = usage_entry;
752
753    if (candidate_dict->HasKey(keys::kCandidatesKey)) {
754      base::ListValue* sub_list;
755      EXTENSION_FUNCTION_VALIDATE(candidate_dict->GetList(keys::kCandidatesKey,
756                                                          &sub_list));
757      if (!ReadCandidates(sub_list, &(output->back().candidates))) {
758        error_ = kErrorBadCandidateList;
759        return false;
760      }
761    }
762  }
763
764  return true;
765}
766
767bool SetCandidatesFunction::RunImpl() {
768  chromeos::InputMethodEngine* engine =
769      InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id());
770  if (!engine) {
771    SetResult(Value::CreateBooleanValue(false));
772    return true;
773  }
774
775  base::DictionaryValue* args;
776  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
777
778  int context_id;
779  std::vector<chromeos::InputMethodEngine::Candidate> candidates;
780
781  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kContextIdKey,
782                                                     &context_id));
783
784  base::ListValue* candidate_list;
785  EXTENSION_FUNCTION_VALIDATE(args->GetList(keys::kCandidatesKey,
786                                            &candidate_list));
787  if (!ReadCandidates(candidate_list, &candidates)) {
788    error_ = kErrorBadCandidateList;
789    return false;
790  }
791
792  std::string error;
793  if (engine->SetCandidates(context_id, candidates, &error_)) {
794    SetResult(Value::CreateBooleanValue(true));
795  } else {
796    SetResult(Value::CreateBooleanValue(false));
797  }
798  return true;
799}
800
801bool SetCursorPositionFunction::RunImpl() {
802  chromeos::InputMethodEngine* engine =
803      InputImeEventRouter::GetInstance()->GetActiveEngine(extension_id());
804  if (!engine) {
805    SetResult(Value::CreateBooleanValue(false));
806    return true;
807  }
808
809  base::DictionaryValue* args;
810  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
811  int context_id;
812  int candidate_id;
813
814  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kContextIdKey,
815                                               &context_id));
816  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kCandidateIdKey,
817                                               &candidate_id));
818
819  if (engine->SetCursorPosition(context_id, candidate_id, &error_)) {
820    SetResult(Value::CreateBooleanValue(true));
821  } else {
822    SetResult(Value::CreateBooleanValue(false));
823  }
824  return true;
825}
826
827bool SetMenuItemsFunction::RunImpl() {
828  base::DictionaryValue* args;
829  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
830
831  std::string engine_id;
832  EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kEngineIdKey, &engine_id));
833
834  chromeos::InputMethodEngine* engine =
835      InputImeEventRouter::GetInstance()->GetEngine(extension_id(), engine_id);
836  if (!engine) {
837    error_ = kErrorEngineNotAvailable;
838    return false;
839  }
840
841  base::ListValue* items;
842  EXTENSION_FUNCTION_VALIDATE(args->GetList(keys::kItemsKey, &items));
843
844  std::vector<chromeos::InputMethodEngine::MenuItem> menu_items;
845  EXTENSION_FUNCTION_VALIDATE(ReadMenuItems(items, &menu_items));
846
847  if (!engine->SetMenuItems(menu_items)) {
848    error_ = kErrorSetMenuItemsFail;
849  }
850  return true;
851}
852
853bool UpdateMenuItemsFunction::RunImpl() {
854  base::DictionaryValue* args;
855  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
856
857  std::string engine_id;
858  EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kEngineIdKey, &engine_id));
859
860  chromeos::InputMethodEngine* engine =
861      InputImeEventRouter::GetInstance()->GetEngine(extension_id(), engine_id);
862  if (!engine) {
863    error_ = kErrorEngineNotAvailable;
864    return false;
865  }
866
867  base::ListValue* items;
868  EXTENSION_FUNCTION_VALIDATE(args->GetList(keys::kItemsKey, &items));
869
870  std::vector<chromeos::InputMethodEngine::MenuItem> menu_items;
871  EXTENSION_FUNCTION_VALIDATE(ReadMenuItems(items, &menu_items));
872
873  if (!engine->UpdateMenuItems(menu_items)) {
874    error_ = kErrorUpdateMenuItemsFail;
875  }
876  return true;
877}
878
879bool DeleteSurroundingTextFunction::RunImpl() {
880  base::DictionaryValue* args;
881  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));
882
883  std::string engine_id;
884  EXTENSION_FUNCTION_VALIDATE(args->GetString(keys::kEngineIdKey, &engine_id));
885
886  chromeos::InputMethodEngine* engine =
887      InputImeEventRouter::GetInstance()->GetEngine(extension_id(), engine_id);
888  if (!engine) {
889    error_ = kErrorEngineNotAvailable;
890    return false;
891  }
892
893  int context_id = 0;
894  int offset = 0;
895  int length = 0;
896  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kContextIdKey,
897                                               &context_id));
898  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kOffsetKey, &offset));
899  EXTENSION_FUNCTION_VALIDATE(args->GetInteger(keys::kLengthKey, &length));
900
901  engine->DeleteSurroundingText(context_id, offset, length, &error_);
902  return true;
903}
904
905bool KeyEventHandled::RunImpl() {
906  std::string request_id_str;
907  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &request_id_str));
908
909  bool handled = false;
910  EXTENSION_FUNCTION_VALIDATE(args_->GetBoolean(1, &handled));
911
912  InputImeEventRouter::GetInstance()->OnKeyEventHandled(
913      extension_id(), request_id_str, handled);
914
915  return true;
916}
917#endif
918
919InputImeAPI::InputImeAPI(Profile* profile)
920    : profile_(profile) {
921  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
922                 content::Source<Profile>(profile));
923  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
924                 content::Source<Profile>(profile));
925
926  ExtensionFunctionRegistry* registry =
927      ExtensionFunctionRegistry::GetInstance();
928  registry->RegisterFunction<SetCompositionFunction>();
929  registry->RegisterFunction<ClearCompositionFunction>();
930  registry->RegisterFunction<CommitTextFunction>();
931  registry->RegisterFunction<SetCandidateWindowPropertiesFunction>();
932  registry->RegisterFunction<SetCandidatesFunction>();
933  registry->RegisterFunction<SetCursorPositionFunction>();
934  registry->RegisterFunction<SetMenuItemsFunction>();
935  registry->RegisterFunction<UpdateMenuItemsFunction>();
936  registry->RegisterFunction<DeleteSurroundingTextFunction>();
937  registry->RegisterFunction<KeyEventHandled>();
938}
939
940InputImeAPI::~InputImeAPI() {
941}
942
943static base::LazyInstance<ProfileKeyedAPIFactory<InputImeAPI> >
944g_factory = LAZY_INSTANCE_INITIALIZER;
945
946// static
947ProfileKeyedAPIFactory<InputImeAPI>* InputImeAPI::GetFactoryInstance() {
948  return &g_factory.Get();
949}
950
951void InputImeAPI::Observe(int type,
952                          const content::NotificationSource& source,
953                          const content::NotificationDetails& details) {
954  if (type == chrome::NOTIFICATION_EXTENSION_LOADED) {
955    const Extension* extension =
956        content::Details<const Extension>(details).ptr();
957    const std::vector<InputComponentInfo>* input_components =
958        extensions::InputComponents::GetInputComponents(extension);
959    if (!input_components)
960      return;
961    for (std::vector<extensions::InputComponentInfo>::const_iterator component =
962        input_components->begin(); component != input_components->end();
963        ++component) {
964      if (component->type == extensions::INPUT_COMPONENT_TYPE_IME) {
965        input_ime_event_router()->RegisterIme(
966            profile_, extension->id(), *component);
967      }
968    }
969  } else if (type == chrome::NOTIFICATION_EXTENSION_UNLOADED) {
970    const Extension* extension =
971        content::Details<const UnloadedExtensionInfo>(details)->extension;
972    const std::vector<InputComponentInfo>* input_components =
973        extensions::InputComponents::GetInputComponents(extension);
974    if (!input_components)
975      return;
976    if (input_components->size() > 0) {
977      input_ime_event_router()->UnregisterAllImes(profile_, extension->id());
978    }
979  }
980}
981
982InputImeEventRouter* InputImeAPI::input_ime_event_router() {
983  return InputImeEventRouter::GetInstance();
984}
985
986}  // namespace extensions
987