1// Copyright 2013 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 "ash/ime/input_method_menu_item.h"
6#include "ash/ime/input_method_menu_manager.h"
7#include "base/bind_helpers.h"
8#include "base/strings/stringprintf.h"
9#include "base/strings/utf_string_conversions.h"
10#include "chrome/browser/extensions/extension_browsertest.h"
11#include "chromeos/ime/component_extension_ime_manager.h"
12#include "chromeos/ime/composition_text.h"
13#include "chromeos/ime/input_method_descriptor.h"
14#include "chromeos/ime/input_method_manager.h"
15#include "content/public/test/browser_test_utils.h"
16#include "content/public/test/test_utils.h"
17#include "extensions/browser/process_manager.h"
18#include "extensions/common/manifest_handlers/background_info.h"
19#include "extensions/test/extension_test_message_listener.h"
20#include "ui/base/ime/chromeos/ime_bridge.h"
21#include "ui/base/ime/chromeos/mock_ime_candidate_window_handler.h"
22#include "ui/base/ime/chromeos/mock_ime_input_context_handler.h"
23#include "ui/events/event.h"
24
25namespace chromeos {
26namespace input_method {
27namespace {
28
29const char kIdentityIMEID[] =
30    "_ext_ime_iafoklpfplgfnoimmaejoeondnjnlcfpIdentityIME";
31const char kToUpperIMEID[] =
32    "_ext_ime_iafoklpfplgfnoimmaejoeondnjnlcfpToUpperIME";
33const char kAPIArgumentIMEID[] =
34    "_ext_ime_iafoklpfplgfnoimmaejoeondnjnlcfpAPIArgumentIME";
35const char kExtensionID[] = "iafoklpfplgfnoimmaejoeondnjnlcfp";
36
37// InputMethod extension should work on 1)normal extension, 2)normal extension
38// in incognito mode 3)component extension.
39enum TestType {
40  kTestTypeNormal = 0,
41  kTestTypeIncognito = 1,
42  kTestTypeComponent = 2,
43};
44
45class InputMethodEngineBrowserTest
46    : public ExtensionBrowserTest,
47      public ::testing::WithParamInterface<TestType> {
48 public:
49  InputMethodEngineBrowserTest()
50      : ExtensionBrowserTest() {}
51  virtual ~InputMethodEngineBrowserTest() {}
52
53  virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
54    ExtensionBrowserTest::SetUpInProcessBrowserTestFixture();
55  }
56
57  virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
58    extension_ = NULL;
59  }
60
61 protected:
62  void LoadTestInputMethod() {
63    // This will load "chrome/test/data/extensions/input_ime"
64    ExtensionTestMessageListener ime_ready_listener("ReadyToUseImeEvent",
65                                                    false);
66    extension_ = LoadExtensionWithType("input_ime", GetParam());
67    ASSERT_TRUE(extension_);
68    ASSERT_TRUE(ime_ready_listener.WaitUntilSatisfied());
69
70    // Extension IMEs are not enabled by default.
71    std::vector<std::string> extension_ime_ids;
72    extension_ime_ids.push_back(kIdentityIMEID);
73    extension_ime_ids.push_back(kToUpperIMEID);
74    extension_ime_ids.push_back(kAPIArgumentIMEID);
75    InputMethodManager::Get()->GetActiveIMEState()->SetEnabledExtensionImes(
76        &extension_ime_ids);
77
78    InputMethodDescriptors extension_imes;
79    InputMethodManager::Get()->GetActiveIMEState()->GetInputMethodExtensions(
80        &extension_imes);
81
82    // Test IME has two input methods, thus InputMethodManager should have two
83    // extension IME.
84    // Note: Even extension is loaded by LoadExtensionAsComponent as above, the
85    // IME does not managed by ComponentExtensionIMEManager or it's id won't
86    // start with __comp__. The component extension IME is whitelisted and
87    // managed by ComponentExtensionIMEManager, but its framework is same as
88    // normal extension IME.
89    EXPECT_EQ(3U, extension_imes.size());
90  }
91
92  const extensions::Extension* LoadExtensionWithType(
93      const std::string& extension_name, TestType type) {
94    switch (type) {
95      case kTestTypeNormal:
96        return LoadExtension(test_data_dir_.AppendASCII(extension_name));
97      case kTestTypeIncognito:
98        return LoadExtensionIncognito(
99            test_data_dir_.AppendASCII(extension_name));
100      case kTestTypeComponent:
101        return LoadExtensionAsComponent(
102            test_data_dir_.AppendASCII(extension_name));
103    }
104    NOTREACHED();
105    return NULL;
106  }
107
108  const extensions::Extension* extension_;
109};
110
111class KeyEventDoneCallback {
112 public:
113  explicit KeyEventDoneCallback(bool expected_argument)
114      : expected_argument_(expected_argument),
115        is_called_(false) {}
116  ~KeyEventDoneCallback() {}
117
118  void Run(bool consumed) {
119    if (consumed == expected_argument_) {
120      base::MessageLoop::current()->Quit();
121      is_called_ = true;
122    }
123  }
124
125  void WaitUntilCalled() {
126    while (!is_called_)
127      content::RunMessageLoop();
128  }
129
130 private:
131  bool expected_argument_;
132  bool is_called_;
133
134  DISALLOW_COPY_AND_ASSIGN(KeyEventDoneCallback);
135};
136
137INSTANTIATE_TEST_CASE_P(InputMethodEngineBrowserTest,
138                        InputMethodEngineBrowserTest,
139                        ::testing::Values(kTestTypeNormal));
140INSTANTIATE_TEST_CASE_P(InputMethodEngineIncognitoBrowserTest,
141                        InputMethodEngineBrowserTest,
142                        ::testing::Values(kTestTypeIncognito));
143INSTANTIATE_TEST_CASE_P(InputMethodEngineComponentExtensionBrowserTest,
144                        InputMethodEngineBrowserTest,
145                        ::testing::Values(kTestTypeComponent));
146
147IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest,
148                       BasicScenarioTest) {
149  LoadTestInputMethod();
150
151  InputMethodManager::Get()->GetActiveIMEState()->ChangeInputMethod(
152      kIdentityIMEID, false /* show_message */);
153
154  scoped_ptr<MockIMEInputContextHandler> mock_input_context(
155      new MockIMEInputContextHandler());
156  scoped_ptr<MockIMECandidateWindowHandler> mock_candidate_window(
157      new MockIMECandidateWindowHandler());
158
159  IMEBridge::Get()->SetInputContextHandler(mock_input_context.get());
160  IMEBridge::Get()->SetCandidateWindowHandler(mock_candidate_window.get());
161
162  IMEEngineHandlerInterface* engine_handler =
163      IMEBridge::Get()->GetCurrentEngineHandler();
164  ASSERT_TRUE(engine_handler);
165
166  // onActivate event should be fired if Enable function is called.
167  ExtensionTestMessageListener activated_listener("onActivate", false);
168  engine_handler->Enable("IdentityIME");
169  ASSERT_TRUE(activated_listener.WaitUntilSatisfied());
170  ASSERT_TRUE(activated_listener.was_satisfied());
171
172  // onFocus event should be fired if FocusIn function is called.
173  ExtensionTestMessageListener focus_listener("onFocus:text", false);
174  IMEEngineHandlerInterface::InputContext context(ui::TEXT_INPUT_TYPE_TEXT,
175                                                  ui::TEXT_INPUT_MODE_DEFAULT);
176  engine_handler->FocusIn(context);
177  ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
178  ASSERT_TRUE(focus_listener.was_satisfied());
179
180  // onKeyEvent should be fired if ProcessKeyEvent is called.
181  KeyEventDoneCallback callback(false);  // EchoBackIME doesn't consume keys.
182  ExtensionTestMessageListener keyevent_listener("onKeyEvent", false);
183  ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, ui::EF_NONE);
184  engine_handler->ProcessKeyEvent(key_event,
185                                  base::Bind(&KeyEventDoneCallback::Run,
186                                             base::Unretained(&callback)));
187  ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
188  ASSERT_TRUE(keyevent_listener.was_satisfied());
189  callback.WaitUntilCalled();
190
191  // onSurroundingTextChange should be fired if SetSurroundingText is called.
192  ExtensionTestMessageListener surrounding_text_listener(
193      "onSurroundingTextChanged", false);
194  engine_handler->SetSurroundingText("text",  // Surrounding text.
195                                     0,  // focused position.
196                                     1);  // anchor position.
197  ASSERT_TRUE(surrounding_text_listener.WaitUntilSatisfied());
198  ASSERT_TRUE(surrounding_text_listener.was_satisfied());
199
200  // onMenuItemActivated should be fired if PropertyActivate is called.
201  ExtensionTestMessageListener property_listener("onMenuItemActivated", false);
202  engine_handler->PropertyActivate("property_name");
203  ASSERT_TRUE(property_listener.WaitUntilSatisfied());
204  ASSERT_TRUE(property_listener.was_satisfied());
205
206  // onReset should be fired if Reset is called.
207  ExtensionTestMessageListener reset_listener("onReset", false);
208  engine_handler->Reset();
209  ASSERT_TRUE(reset_listener.WaitUntilSatisfied());
210  ASSERT_TRUE(reset_listener.was_satisfied());
211
212  // onBlur should be fired if FocusOut is called.
213  ExtensionTestMessageListener blur_listener("onBlur", false);
214  engine_handler->FocusOut();
215  ASSERT_TRUE(blur_listener.WaitUntilSatisfied());
216  ASSERT_TRUE(blur_listener.was_satisfied());
217
218  // onDeactivated should be fired if Disable is called.
219  ExtensionTestMessageListener disabled_listener("onDeactivated", false);
220  engine_handler->Disable();
221  ASSERT_TRUE(disabled_listener.WaitUntilSatisfied());
222  ASSERT_TRUE(disabled_listener.was_satisfied());
223
224  IMEBridge::Get()->SetInputContextHandler(NULL);
225  IMEBridge::Get()->SetCandidateWindowHandler(NULL);
226}
227
228IN_PROC_BROWSER_TEST_P(InputMethodEngineBrowserTest,
229                       APIArgumentTest) {
230  LoadTestInputMethod();
231
232  InputMethodManager::Get()->GetActiveIMEState()->ChangeInputMethod(
233      kAPIArgumentIMEID, false /* show_message */);
234
235  scoped_ptr<MockIMEInputContextHandler> mock_input_context(
236      new MockIMEInputContextHandler());
237  scoped_ptr<MockIMECandidateWindowHandler> mock_candidate_window(
238      new MockIMECandidateWindowHandler());
239
240  IMEBridge::Get()->SetInputContextHandler(mock_input_context.get());
241  IMEBridge::Get()->SetCandidateWindowHandler(mock_candidate_window.get());
242
243  IMEEngineHandlerInterface* engine_handler =
244      IMEBridge::Get()->GetCurrentEngineHandler();
245  ASSERT_TRUE(engine_handler);
246
247  extensions::ExtensionHost* host =
248      extensions::ExtensionSystem::Get(profile())
249          ->process_manager()
250          ->GetBackgroundHostForExtension(extension_->id());
251  ASSERT_TRUE(host);
252
253  engine_handler->Enable("APIArgumentIME");
254  IMEEngineHandlerInterface::InputContext context(ui::TEXT_INPUT_TYPE_TEXT,
255                                                  ui::TEXT_INPUT_MODE_DEFAULT);
256  engine_handler->FocusIn(context);
257
258  {
259    SCOPED_TRACE("KeyDown, Ctrl:No, alt:No, Shift:No, Caps:No");
260    KeyEventDoneCallback callback(false);
261    const std::string expected_value =
262        "onKeyEvent::keydown:a:KeyA:false:false:false:false";
263    ExtensionTestMessageListener keyevent_listener(expected_value, false);
264
265    ui::KeyEvent key_event(
266        ui::ET_KEY_PRESSED, ui::VKEY_A, "KeyA", ui::EF_NONE);
267    engine_handler->ProcessKeyEvent(key_event,
268                                    base::Bind(&KeyEventDoneCallback::Run,
269                                               base::Unretained(&callback)));
270    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
271    EXPECT_TRUE(keyevent_listener.was_satisfied());
272    callback.WaitUntilCalled();
273  }
274  {
275    SCOPED_TRACE("KeyDown, Ctrl:Yes, alt:No, Shift:No, Caps:No");
276    KeyEventDoneCallback callback(false);
277    const std::string expected_value =
278        "onKeyEvent::keydown:a:KeyA:true:false:false:false";
279    ExtensionTestMessageListener keyevent_listener(expected_value, false);
280
281    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
282                           ui::VKEY_A,
283                           "KeyA",
284                           ui::EF_CONTROL_DOWN);
285    engine_handler->ProcessKeyEvent(key_event,
286                                    base::Bind(&KeyEventDoneCallback::Run,
287                                               base::Unretained(&callback)));
288    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
289    EXPECT_TRUE(keyevent_listener.was_satisfied());
290    callback.WaitUntilCalled();
291  }
292  {
293    SCOPED_TRACE("KeyDown, Ctrl:No, alt:Yes, Shift:No, Caps:No");
294    KeyEventDoneCallback callback(false);
295    const std::string expected_value =
296        "onKeyEvent::keydown:a:KeyA:false:true:false:false";
297    ExtensionTestMessageListener keyevent_listener(expected_value, false);
298
299    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
300                           ui::VKEY_A,
301                           "KeyA",
302                           ui::EF_ALT_DOWN);
303    engine_handler->ProcessKeyEvent(key_event,
304                                    base::Bind(&KeyEventDoneCallback::Run,
305                                               base::Unretained(&callback)));
306    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
307    EXPECT_TRUE(keyevent_listener.was_satisfied());
308    callback.WaitUntilCalled();
309  }
310  {
311    SCOPED_TRACE("KeyDown, Ctrl:No, alt:No, Shift:Yes, Caps:No");
312    KeyEventDoneCallback callback(false);
313    const std::string expected_value =
314        "onKeyEvent::keydown:A:KeyA:false:false:true:false";
315    ExtensionTestMessageListener keyevent_listener(expected_value, false);
316
317    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
318                           ui::VKEY_A,
319                           "KeyA",
320                           ui::EF_SHIFT_DOWN);
321    engine_handler->ProcessKeyEvent(key_event,
322                                    base::Bind(&KeyEventDoneCallback::Run,
323                                               base::Unretained(&callback)));
324    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
325    EXPECT_TRUE(keyevent_listener.was_satisfied());
326    callback.WaitUntilCalled();
327  }
328  {
329    SCOPED_TRACE("KeyDown, Ctrl:No, alt:No, Shift:No, Caps:Yes");
330    KeyEventDoneCallback callback(false);
331    const std::string expected_value =
332        "onKeyEvent::keydown:A:KeyA:false:false:false:true";
333    ExtensionTestMessageListener keyevent_listener(expected_value, false);
334
335    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
336                           ui::VKEY_A,
337                           "KeyA",
338                           ui::EF_CAPS_LOCK_DOWN);
339    engine_handler->ProcessKeyEvent(key_event,
340                                    base::Bind(&KeyEventDoneCallback::Run,
341                                               base::Unretained(&callback)));
342    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
343    EXPECT_TRUE(keyevent_listener.was_satisfied());
344    callback.WaitUntilCalled();
345  }
346  {
347    SCOPED_TRACE("KeyDown, Ctrl:Yes, alt:Yes, Shift:No, Caps:No");
348    KeyEventDoneCallback callback(false);
349    const std::string expected_value =
350        "onKeyEvent::keydown:a:KeyA:true:true:false:false";
351    ExtensionTestMessageListener keyevent_listener(expected_value, false);
352
353    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
354                           ui::VKEY_A,
355                           "KeyA",
356                           ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN);
357    engine_handler->ProcessKeyEvent(key_event,
358                                    base::Bind(&KeyEventDoneCallback::Run,
359                                               base::Unretained(&callback)));
360    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
361    EXPECT_TRUE(keyevent_listener.was_satisfied());
362    callback.WaitUntilCalled();
363  }
364  {
365    SCOPED_TRACE("KeyDown, Ctrl:No, alt:No, Shift:Yes, Caps:Yes");
366    KeyEventDoneCallback callback(false);
367    const std::string expected_value =
368        "onKeyEvent::keydown:a:KeyA:false:false:true:true";
369    ExtensionTestMessageListener keyevent_listener(expected_value, false);
370
371    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
372                           ui::VKEY_A,
373                           "KeyA",
374                           ui::EF_SHIFT_DOWN | ui::EF_CAPS_LOCK_DOWN);
375    engine_handler->ProcessKeyEvent(key_event,
376                                    base::Bind(&KeyEventDoneCallback::Run,
377                                               base::Unretained(&callback)));
378    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
379    EXPECT_TRUE(keyevent_listener.was_satisfied());
380    callback.WaitUntilCalled();
381  }
382  // Media keys cases.
383  const struct {
384    ui::KeyboardCode keycode;
385    const char* code;
386    const char* key;
387  } kMediaKeyCases[] = {
388    { ui::VKEY_BROWSER_BACK, "BrowserBack", "HistoryBack" },
389    { ui::VKEY_BROWSER_FORWARD, "BrowserForward", "HistoryForward" },
390    { ui::VKEY_BROWSER_REFRESH, "BrowserRefresh", "BrowserRefresh" },
391    { ui::VKEY_MEDIA_LAUNCH_APP2, "ChromeOSFullscreen", "ChromeOSFullscreen" },
392    { ui::VKEY_MEDIA_LAUNCH_APP1,
393      "ChromeOSSwitchWindow", "ChromeOSSwitchWindow" },
394    { ui::VKEY_BRIGHTNESS_DOWN, "BrightnessDown", "BrightnessDown" },
395    { ui::VKEY_BRIGHTNESS_UP, "BrightnessUp", "BrightnessUp" },
396    { ui::VKEY_VOLUME_MUTE, "VolumeMute", "AudioVolumeMute" },
397    { ui::VKEY_VOLUME_DOWN, "VolumeDown", "AudioVolumeDown" },
398    { ui::VKEY_VOLUME_UP, "VolumeUp", "AudioVolumeUp" },
399    { ui::VKEY_F1, "F1", "HistoryBack" },
400    { ui::VKEY_F2, "F2", "HistoryForward" },
401    { ui::VKEY_F3, "F3", "BrowserRefresh" },
402    { ui::VKEY_F4, "F4", "ChromeOSFullscreen" },
403    { ui::VKEY_F5, "F5", "ChromeOSSwitchWindow" },
404    { ui::VKEY_F6, "F6", "BrightnessDown" },
405    { ui::VKEY_F7, "F7", "BrightnessUp" },
406    { ui::VKEY_F8, "F8", "AudioVolumeMute" },
407    { ui::VKEY_F9, "F9", "AudioVolumeDown" },
408    { ui::VKEY_F10, "F10", "AudioVolumeUp" },
409  };
410  for (size_t i = 0; i < arraysize(kMediaKeyCases); ++i) {
411    SCOPED_TRACE(std::string("KeyDown, ") + kMediaKeyCases[i].code);
412    KeyEventDoneCallback callback(false);
413    const std::string expected_value =
414        base::StringPrintf("onKeyEvent::keydown:%s:%s:false:false:false:false",
415                           kMediaKeyCases[i].key, kMediaKeyCases[i].code);
416    ExtensionTestMessageListener keyevent_listener(expected_value, false);
417
418    ui::KeyEvent key_event(ui::ET_KEY_PRESSED,
419                           kMediaKeyCases[i].keycode,
420                           kMediaKeyCases[i].code,
421                           ui::EF_NONE);
422    engine_handler->ProcessKeyEvent(key_event,
423                                    base::Bind(&KeyEventDoneCallback::Run,
424                                               base::Unretained(&callback)));
425    ASSERT_TRUE(keyevent_listener.WaitUntilSatisfied());
426    EXPECT_TRUE(keyevent_listener.was_satisfied());
427    callback.WaitUntilCalled();
428  }
429  // TODO(nona): Add browser tests for other API as well.
430  {
431    SCOPED_TRACE("commitText test");
432    mock_input_context->Reset();
433    mock_candidate_window->Reset();
434
435    const char commit_text_test_script[] =
436        "chrome.input.ime.commitText({"
437        "  contextID: engineBridge.getFocusedContextID().contextID,"
438        "  text:'COMMIT_TEXT'"
439        "});";
440
441    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
442                                       commit_text_test_script));
443    EXPECT_EQ(1, mock_input_context->commit_text_call_count());
444    EXPECT_EQ("COMMIT_TEXT", mock_input_context->last_commit_text());
445  }
446  {
447    SCOPED_TRACE("sendKeyEvents test");
448    mock_input_context->Reset();
449    mock_candidate_window->Reset();
450
451    const char send_key_events_test_script[] =
452        "chrome.input.ime.sendKeyEvents({"
453        "  contextID: engineBridge.getFocusedContextID().contextID,"
454        "  keyData : [{"
455        "    type : 'keydown',"
456        "    requestId : '0',"
457        "    key : 'z',"
458        "    code : 'KeyZ',"
459        "  },{"
460        "    type : 'keyup',"
461        "    requestId : '1',"
462        "    key : 'z',"
463        "    code : 'KeyZ',"
464        "  }]"
465        "});";
466
467    ExtensionTestMessageListener keyevent_listener_down(
468        std::string("onKeyEvent:") + kExtensionID +
469        ":keydown:z:KeyZ:false:false:false:false",
470        false);
471    ExtensionTestMessageListener keyevent_listener_up(
472        std::string("onKeyEvent:") + kExtensionID +
473        ":keyup:z:KeyZ:false:false:false:false",
474        false);
475
476    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
477                                       send_key_events_test_script));
478
479    ASSERT_TRUE(keyevent_listener_down.WaitUntilSatisfied());
480    EXPECT_TRUE(keyevent_listener_down.was_satisfied());
481    ASSERT_TRUE(keyevent_listener_up.WaitUntilSatisfied());
482    EXPECT_TRUE(keyevent_listener_up.was_satisfied());
483  }
484  {
485    SCOPED_TRACE("sendKeyEvents test with keyCode");
486    mock_input_context->Reset();
487    mock_candidate_window->Reset();
488
489    const char send_key_events_test_script[] =
490        "chrome.input.ime.sendKeyEvents({"
491        "  contextID: engineBridge.getFocusedContextID().contextID,"
492        "  keyData : [{"
493        "    type : 'keydown',"
494        "    requestId : '2',"
495        "    key : 'a',"
496        "    code : 'KeyQ',"
497        "    keyCode : 0x41,"
498        "  },{"
499        "    type : 'keyup',"
500        "    requestId : '3',"
501        "    key : 'a',"
502        "    code : 'KeyQ',"
503        "    keyCode : 0x41,"
504        "  }]"
505        "});";
506
507    ExtensionTestMessageListener keyevent_listener_down(
508        std::string("onKeyEvent:") + kExtensionID +
509        ":keydown:a:KeyQ:false:false:false:false",
510        false);
511    ExtensionTestMessageListener keyevent_listener_up(
512        std::string("onKeyEvent:") + kExtensionID +
513        ":keyup:a:KeyQ:false:false:false:false",
514        false);
515
516    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
517                                       send_key_events_test_script));
518
519    ASSERT_TRUE(keyevent_listener_down.WaitUntilSatisfied());
520    EXPECT_TRUE(keyevent_listener_down.was_satisfied());
521    ASSERT_TRUE(keyevent_listener_up.WaitUntilSatisfied());
522    EXPECT_TRUE(keyevent_listener_up.was_satisfied());
523  }
524  {
525    SCOPED_TRACE("setComposition test");
526    mock_input_context->Reset();
527    mock_candidate_window->Reset();
528
529    const char set_composition_test_script[] =
530        "chrome.input.ime.setComposition({"
531        "  contextID: engineBridge.getFocusedContextID().contextID,"
532        "  text:'COMPOSITION_TEXT',"
533        "  cursor:4,"
534        "  segments : [{"
535        "    start: 0,"
536        "    end: 5,"
537        "    style: 'underline'"
538        "  },{"
539        "    start: 6,"
540        "    end: 10,"
541        "    style: 'doubleUnderline'"
542        "  }]"
543        "});";
544
545    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
546                                       set_composition_test_script));
547    EXPECT_EQ(1, mock_input_context->update_preedit_text_call_count());
548
549    EXPECT_EQ(4U,
550              mock_input_context->last_update_composition_arg().cursor_pos);
551    EXPECT_TRUE(mock_input_context->last_update_composition_arg().is_visible);
552
553    const CompositionText& composition_text =
554        mock_input_context->last_update_composition_arg().composition_text;
555    EXPECT_EQ(base::UTF8ToUTF16("COMPOSITION_TEXT"), composition_text.text());
556    const std::vector<CompositionText::UnderlineAttribute>& underlines =
557        composition_text.underline_attributes();
558
559    ASSERT_EQ(2U, underlines.size());
560    EXPECT_EQ(CompositionText::COMPOSITION_TEXT_UNDERLINE_SINGLE,
561              underlines[0].type);
562    EXPECT_EQ(0U, underlines[0].start_index);
563    EXPECT_EQ(5U, underlines[0].end_index);
564
565    EXPECT_EQ(CompositionText::COMPOSITION_TEXT_UNDERLINE_DOUBLE,
566              underlines[1].type);
567    EXPECT_EQ(6U, underlines[1].start_index);
568    EXPECT_EQ(10U, underlines[1].end_index);
569  }
570  {
571    SCOPED_TRACE("clearComposition test");
572    mock_input_context->Reset();
573    mock_candidate_window->Reset();
574
575    const char commite_text_test_script[] =
576        "chrome.input.ime.clearComposition({"
577        "  contextID: engineBridge.getFocusedContextID().contextID,"
578        "});";
579
580    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
581                                       commite_text_test_script));
582    EXPECT_EQ(1, mock_input_context->update_preedit_text_call_count());
583    EXPECT_FALSE(
584        mock_input_context->last_update_composition_arg().is_visible);
585    const CompositionText& composition_text =
586        mock_input_context->last_update_composition_arg().composition_text;
587    EXPECT_TRUE(composition_text.text().empty());
588  }
589  {
590    SCOPED_TRACE("setCandidateWindowProperties:visibility test");
591    mock_input_context->Reset();
592    mock_candidate_window->Reset();
593
594    const char set_candidate_window_properties_test_script[] =
595        "chrome.input.ime.setCandidateWindowProperties({"
596        "  engineID: engineBridge.getActiveEngineID(),"
597        "  properties: {"
598        "    visible: true,"
599        "  }"
600        "});";
601    ASSERT_TRUE(content::ExecuteScript(
602        host->host_contents(),
603        set_candidate_window_properties_test_script));
604    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
605    EXPECT_TRUE(
606        mock_candidate_window->last_update_lookup_table_arg().is_visible);
607  }
608  {
609    SCOPED_TRACE("setCandidateWindowProperties:cursor_visibility test");
610    mock_input_context->Reset();
611    mock_candidate_window->Reset();
612
613    const char set_candidate_window_properties_test_script[] =
614        "chrome.input.ime.setCandidateWindowProperties({"
615        "  engineID: engineBridge.getActiveEngineID(),"
616        "  properties: {"
617        "    cursorVisible: true,"
618        "  }"
619        "});";
620    ASSERT_TRUE(content::ExecuteScript(
621        host->host_contents(),
622        set_candidate_window_properties_test_script));
623    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
624
625    // window visibility is kept as before.
626    EXPECT_TRUE(
627        mock_candidate_window->last_update_lookup_table_arg().is_visible);
628
629    const ui::CandidateWindow& table =
630        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
631    EXPECT_TRUE(table.is_cursor_visible());
632  }
633  {
634    SCOPED_TRACE("setCandidateWindowProperties:vertical test");
635    mock_input_context->Reset();
636    mock_candidate_window->Reset();
637
638    const char set_candidate_window_properties_test_script[] =
639        "chrome.input.ime.setCandidateWindowProperties({"
640        "  engineID: engineBridge.getActiveEngineID(),"
641        "  properties: {"
642        "    vertical: true,"
643        "  }"
644        "});";
645    ASSERT_TRUE(content::ExecuteScript(
646        host->host_contents(),
647        set_candidate_window_properties_test_script));
648    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
649
650    // window visibility is kept as before.
651    EXPECT_TRUE(
652        mock_candidate_window->last_update_lookup_table_arg().is_visible);
653
654    const ui::CandidateWindow& table =
655        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
656
657    // cursor visibility is kept as before.
658    EXPECT_TRUE(table.is_cursor_visible());
659
660    EXPECT_EQ(ui::CandidateWindow::VERTICAL, table.orientation());
661  }
662  {
663    SCOPED_TRACE("setCandidateWindowProperties:pageSize test");
664    mock_input_context->Reset();
665    mock_candidate_window->Reset();
666
667    const char set_candidate_window_properties_test_script[] =
668        "chrome.input.ime.setCandidateWindowProperties({"
669        "  engineID: engineBridge.getActiveEngineID(),"
670        "  properties: {"
671        "    pageSize: 7,"
672        "  }"
673        "});";
674    ASSERT_TRUE(content::ExecuteScript(
675        host->host_contents(),
676        set_candidate_window_properties_test_script));
677    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
678
679    // window visibility is kept as before.
680    EXPECT_TRUE(
681        mock_candidate_window->last_update_lookup_table_arg().is_visible);
682
683    const ui::CandidateWindow& table =
684        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
685
686    // cursor visibility is kept as before.
687    EXPECT_TRUE(table.is_cursor_visible());
688
689    // oritantation is kept as before.
690    EXPECT_EQ(ui::CandidateWindow::VERTICAL, table.orientation());
691
692    EXPECT_EQ(7U, table.page_size());
693  }
694  {
695    SCOPED_TRACE("setCandidateWindowProperties:auxTextVisibility test");
696    mock_input_context->Reset();
697    mock_candidate_window->Reset();
698
699    const char set_candidate_window_properties_test_script[] =
700        "chrome.input.ime.setCandidateWindowProperties({"
701        "  engineID: engineBridge.getActiveEngineID(),"
702        "  properties: {"
703        "    auxiliaryTextVisible: true"
704        "  }"
705        "});";
706    ASSERT_TRUE(content::ExecuteScript(
707        host->host_contents(),
708        set_candidate_window_properties_test_script));
709    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
710
711    const ui::CandidateWindow& table =
712        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
713    EXPECT_TRUE(table.is_auxiliary_text_visible());
714  }
715  {
716    SCOPED_TRACE("setCandidateWindowProperties:auxText test");
717    mock_input_context->Reset();
718    mock_candidate_window->Reset();
719
720    const char set_candidate_window_properties_test_script[] =
721        "chrome.input.ime.setCandidateWindowProperties({"
722        "  engineID: engineBridge.getActiveEngineID(),"
723        "  properties: {"
724        "    auxiliaryText: 'AUXILIARY_TEXT'"
725        "  }"
726        "});";
727    ASSERT_TRUE(content::ExecuteScript(
728        host->host_contents(),
729        set_candidate_window_properties_test_script));
730    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
731
732    // aux text visibility is kept as before.
733    const ui::CandidateWindow& table =
734        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
735    EXPECT_TRUE(table.is_auxiliary_text_visible());
736    EXPECT_EQ("AUXILIARY_TEXT", table.auxiliary_text());
737  }
738  {
739    SCOPED_TRACE("setCandidates test");
740    mock_input_context->Reset();
741    mock_candidate_window->Reset();
742
743    const char set_candidates_test_script[] =
744        "chrome.input.ime.setCandidates({"
745        "  contextID: engineBridge.getFocusedContextID().contextID,"
746        "  candidates: [{"
747        "    candidate: 'CANDIDATE_1',"
748        "    id: 1,"
749        "    },{"
750        "    candidate: 'CANDIDATE_2',"
751        "    id: 2,"
752        "    label: 'LABEL_2',"
753        "    },{"
754        "    candidate: 'CANDIDATE_3',"
755        "    id: 3,"
756        "    label: 'LABEL_3',"
757        "    annotation: 'ANNOTACTION_3'"
758        "    },{"
759        "    candidate: 'CANDIDATE_4',"
760        "    id: 4,"
761        "    label: 'LABEL_4',"
762        "    annotation: 'ANNOTACTION_4',"
763        "    usage: {"
764        "      title: 'TITLE_4',"
765        "      body: 'BODY_4'"
766        "    }"
767        "  }]"
768        "});";
769    ASSERT_TRUE(content::ExecuteScript(host->host_contents(),
770                                       set_candidates_test_script));
771
772    // window visibility is kept as before.
773    EXPECT_TRUE(
774        mock_candidate_window->last_update_lookup_table_arg().is_visible);
775
776    const ui::CandidateWindow& table =
777        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
778
779    // cursor visibility is kept as before.
780    EXPECT_TRUE(table.is_cursor_visible());
781
782    // oritantation is kept as before.
783    EXPECT_EQ(ui::CandidateWindow::VERTICAL, table.orientation());
784
785    // page size is kept as before.
786    EXPECT_EQ(7U, table.page_size());
787
788    ASSERT_EQ(4U, table.candidates().size());
789
790    EXPECT_EQ(base::UTF8ToUTF16("CANDIDATE_1"),
791              table.candidates().at(0).value);
792
793    EXPECT_EQ(base::UTF8ToUTF16("CANDIDATE_2"),
794              table.candidates().at(1).value);
795    EXPECT_EQ(base::UTF8ToUTF16("LABEL_2"), table.candidates().at(1).label);
796
797    EXPECT_EQ(base::UTF8ToUTF16("CANDIDATE_3"),
798              table.candidates().at(2).value);
799    EXPECT_EQ(base::UTF8ToUTF16("LABEL_3"), table.candidates().at(2).label);
800    EXPECT_EQ(base::UTF8ToUTF16("ANNOTACTION_3"),
801              table.candidates().at(2).annotation);
802
803    EXPECT_EQ(base::UTF8ToUTF16("CANDIDATE_4"),
804              table.candidates().at(3).value);
805    EXPECT_EQ(base::UTF8ToUTF16("LABEL_4"), table.candidates().at(3).label);
806    EXPECT_EQ(base::UTF8ToUTF16("ANNOTACTION_4"),
807              table.candidates().at(3).annotation);
808    EXPECT_EQ(base::UTF8ToUTF16("TITLE_4"),
809              table.candidates().at(3).description_title);
810    EXPECT_EQ(base::UTF8ToUTF16("BODY_4"),
811              table.candidates().at(3).description_body);
812  }
813  {
814    SCOPED_TRACE("setCursorPosition test");
815    mock_input_context->Reset();
816    mock_candidate_window->Reset();
817
818    const char set_cursor_position_test_script[] =
819        "chrome.input.ime.setCursorPosition({"
820        "  contextID: engineBridge.getFocusedContextID().contextID,"
821        "  candidateID: 2"
822        "});";
823    ASSERT_TRUE(content::ExecuteScript(
824        host->host_contents(), set_cursor_position_test_script));
825    EXPECT_EQ(1, mock_candidate_window->update_lookup_table_call_count());
826
827    // window visibility is kept as before.
828    EXPECT_TRUE(
829        mock_candidate_window->last_update_lookup_table_arg().is_visible);
830
831    const ui::CandidateWindow& table =
832        mock_candidate_window->last_update_lookup_table_arg().lookup_table;
833
834    // cursor visibility is kept as before.
835    EXPECT_TRUE(table.is_cursor_visible());
836
837    // oritantation is kept as before.
838    EXPECT_EQ(ui::CandidateWindow::VERTICAL, table.orientation());
839
840    // page size is kept as before.
841    EXPECT_EQ(7U, table.page_size());
842
843    // candidates are same as before.
844    ASSERT_EQ(4U, table.candidates().size());
845
846    // Candidate ID == 2 is 1 in index.
847    EXPECT_EQ(1U, table.cursor_position());
848  }
849  {
850    SCOPED_TRACE("setMenuItem test");
851    mock_input_context->Reset();
852    mock_candidate_window->Reset();
853
854    const char set_menu_item_test_script[] =
855        "chrome.input.ime.setMenuItems({"
856        "  engineID: engineBridge.getActiveEngineID(),"
857        "  items: [{"
858        "    id: 'ID0',"
859        "  },{"
860        "    id: 'ID1',"
861        "    label: 'LABEL1',"
862        "  },{"
863        "    id: 'ID2',"
864        "    label: 'LABEL2',"
865        "    style: 'radio',"
866        "  },{"
867        "    id: 'ID3',"
868        "    label: 'LABEL3',"
869        "    style: 'check',"
870        "    visible: true,"
871        "  },{"
872        "    id: 'ID4',"
873        "    label: 'LABEL4',"
874        "    style: 'separator',"
875        "    visible: true,"
876        "    checked: true"
877        "  }]"
878        "});";
879    ASSERT_TRUE(content::ExecuteScript(
880        host->host_contents(), set_menu_item_test_script));
881
882    const ash::ime::InputMethodMenuItemList& props =
883        ash::ime::InputMethodMenuManager::GetInstance()->
884        GetCurrentInputMethodMenuItemList();
885    ASSERT_EQ(5U, props.size());
886
887    EXPECT_EQ("ID0", props[0].key);
888    EXPECT_EQ("ID1", props[1].key);
889    EXPECT_EQ("ID2", props[2].key);
890    EXPECT_EQ("ID3", props[3].key);
891    EXPECT_EQ("ID4", props[4].key);
892
893    EXPECT_EQ("LABEL1", props[1].label);
894    EXPECT_EQ("LABEL2", props[2].label);
895    EXPECT_EQ("LABEL3", props[3].label);
896    EXPECT_EQ("LABEL4", props[4].label);
897
898    EXPECT_TRUE(props[2].is_selection_item);
899    // TODO(nona): Add tests for style: ["toggle" and "separator"]
900    // and visible:, when implement them.
901
902    EXPECT_TRUE(props[4].is_selection_item_checked);
903  }
904  {
905    SCOPED_TRACE("deleteSurroundingText test");
906    mock_input_context->Reset();
907    mock_candidate_window->Reset();
908
909    const char delete_surrounding_text_test_script[] =
910        "chrome.input.ime.deleteSurroundingText({"
911        "  engineID: engineBridge.getActiveEngineID(),"
912        "  contextID: engineBridge.getFocusedContextID().contextID,"
913        "  offset: 5,"
914        "  length: 3"
915        "});";
916    ASSERT_TRUE(content::ExecuteScript(
917        host->host_contents(), delete_surrounding_text_test_script));
918
919    EXPECT_EQ(1, mock_input_context->delete_surrounding_text_call_count());
920    EXPECT_EQ(5, mock_input_context->last_delete_surrounding_text_arg().offset);
921    EXPECT_EQ(3U,
922              mock_input_context->last_delete_surrounding_text_arg().length);
923  }
924  {
925    SCOPED_TRACE("onFocus test");
926    mock_input_context->Reset();
927    mock_candidate_window->Reset();
928
929    {
930      ExtensionTestMessageListener focus_listener("onFocus:text", false);
931      IMEEngineHandlerInterface::InputContext context(
932          ui::TEXT_INPUT_TYPE_TEXT, ui::TEXT_INPUT_MODE_DEFAULT);
933      engine_handler->FocusIn(context);
934      ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
935      ASSERT_TRUE(focus_listener.was_satisfied());
936    }
937    {
938      ExtensionTestMessageListener focus_listener("onFocus:search", false);
939      IMEEngineHandlerInterface::InputContext context(
940          ui::TEXT_INPUT_TYPE_SEARCH, ui::TEXT_INPUT_MODE_DEFAULT);
941      engine_handler->FocusIn(context);
942      ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
943      ASSERT_TRUE(focus_listener.was_satisfied());
944    }
945    {
946      ExtensionTestMessageListener focus_listener("onFocus:tel", false);
947      IMEEngineHandlerInterface::InputContext context(
948          ui::TEXT_INPUT_TYPE_TELEPHONE, ui::TEXT_INPUT_MODE_DEFAULT);
949      engine_handler->FocusIn(context);
950      ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
951      ASSERT_TRUE(focus_listener.was_satisfied());
952    }
953    {
954      ExtensionTestMessageListener focus_listener("onFocus:url", false);
955      IMEEngineHandlerInterface::InputContext context(
956          ui::TEXT_INPUT_TYPE_URL, ui::TEXT_INPUT_MODE_DEFAULT);
957      engine_handler->FocusIn(context);
958      ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
959      ASSERT_TRUE(focus_listener.was_satisfied());
960    }
961    {
962      ExtensionTestMessageListener focus_listener("onFocus:email", false);
963      IMEEngineHandlerInterface::InputContext context(
964          ui::TEXT_INPUT_TYPE_EMAIL, ui::TEXT_INPUT_MODE_DEFAULT);
965      engine_handler->FocusIn(context);
966      ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
967      ASSERT_TRUE(focus_listener.was_satisfied());
968    }
969    {
970      ExtensionTestMessageListener focus_listener("onFocus:number", false);
971      IMEEngineHandlerInterface::InputContext context(
972          ui::TEXT_INPUT_TYPE_NUMBER, ui::TEXT_INPUT_MODE_DEFAULT);
973      engine_handler->FocusIn(context);
974      ASSERT_TRUE(focus_listener.WaitUntilSatisfied());
975      ASSERT_TRUE(focus_listener.was_satisfied());
976    }
977  }
978
979  IMEBridge::Get()->SetInputContextHandler(NULL);
980  IMEBridge::Get()->SetCandidateWindowHandler(NULL);
981}
982
983}  // namespace
984}  // namespace input_method
985}  // namespace chromeos
986