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 "ppapi/tests/test_ime_input_event.h"
6
7#include "ppapi/c/pp_errors.h"
8#include "ppapi/c/ppb_input_event.h"
9#include "ppapi/cpp/input_event.h"
10#include "ppapi/cpp/module.h"
11#include "ppapi/tests/test_utils.h"
12#include "ppapi/tests/testing_instance.h"
13
14REGISTER_TEST_CASE(ImeInputEvent);
15
16namespace {
17
18// Japanese Kanji letters
19const char* kCompositionChar[] = {
20    "\xE6\x96\x87",  // An example character of normal unicode.
21    "\xF0\xA0\xAE\x9F", // An example character of surrogate pair.
22    "\xF0\x9F\x98\x81"  // An example character of surrogate pair(emoji).
23};
24
25const char kCompositionText[] = "\xE6\x96\x87\xF0\xA0\xAE\x9F\xF0\x9F\x98\x81";
26
27#define FINISHED_WAITING_MESSAGE "TEST_IME_INPUT_EVENT_FINISHED_WAITING"
28
29}  // namespace
30
31TestImeInputEvent::TestImeInputEvent(TestingInstance* instance)
32    : TestCase(instance),
33      input_event_interface_(NULL),
34      keyboard_input_event_interface_(NULL),
35      ime_input_event_interface_(NULL),
36      received_unexpected_event_(true),
37      received_finish_message_(false) {
38}
39
40TestImeInputEvent::~TestImeInputEvent() {
41  // Remove the special listener that only responds to a
42  // FINISHED_WAITING_MESSAGE string. See Init for where it gets added.
43  std::string js_code;
44  js_code = "var plugin = document.getElementById('plugin');"
45            "plugin.removeEventListener('message',"
46            "                           plugin.wait_for_messages_handler);"
47            "delete plugin.wait_for_messages_handler;";
48  instance_->EvalScript(js_code);
49}
50
51void TestImeInputEvent::RunTests(const std::string& filter) {
52  RUN_TEST(ImeCommit, filter);
53  RUN_TEST(ImeCancel, filter);
54  RUN_TEST(ImeUnawareCommit, filter);
55  RUN_TEST(ImeUnawareCancel, filter);
56}
57
58bool TestImeInputEvent::Init() {
59  input_event_interface_ = static_cast<const PPB_InputEvent*>(
60      pp::Module::Get()->GetBrowserInterface(PPB_INPUT_EVENT_INTERFACE));
61  keyboard_input_event_interface_ =
62      static_cast<const PPB_KeyboardInputEvent*>(
63          pp::Module::Get()->GetBrowserInterface(
64              PPB_KEYBOARD_INPUT_EVENT_INTERFACE));
65  ime_input_event_interface_ = static_cast<const PPB_IMEInputEvent*>(
66      pp::Module::Get()->GetBrowserInterface(
67          PPB_IME_INPUT_EVENT_INTERFACE));
68
69  bool success =
70      input_event_interface_ &&
71      keyboard_input_event_interface_ &&
72      ime_input_event_interface_ &&
73      CheckTestingInterface();
74
75  // Set up a listener for our message that signals that all input events have
76  // been received.
77  // Note the following code is dependent on some features of test_case.html.
78  // E.g., it is assumed that the DOM element where the plugin is embedded has
79  // an id of 'plugin', and there is a function 'IsTestingMessage' that allows
80  // us to ignore the messages that are intended for use by the testing
81  // framework itself.
82  std::string js_code =
83      "var plugin = document.getElementById('plugin');"
84      "var wait_for_messages_handler = function(message_event) {"
85      "  if (!IsTestingMessage(message_event.data) &&"
86      "      message_event.data === '" FINISHED_WAITING_MESSAGE "') {"
87      "    plugin.postMessage('" FINISHED_WAITING_MESSAGE "');"
88      "  }"
89      "};"
90      "plugin.addEventListener('message', wait_for_messages_handler);"
91      // Stash it on the plugin so we can remove it in the destructor.
92      "plugin.wait_for_messages_handler = wait_for_messages_handler;";
93  instance_->EvalScript(js_code);
94
95  return success;
96}
97
98bool TestImeInputEvent::HandleInputEvent(const pp::InputEvent& input_event) {
99  // Check whether the IME related events comes in the expected order.
100  switch (input_event.GetType()) {
101    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
102    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
103    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
104    case PP_INPUTEVENT_TYPE_IME_TEXT:
105    case PP_INPUTEVENT_TYPE_CHAR:
106      if (expected_events_.empty()) {
107        received_unexpected_event_ = true;
108      } else {
109        received_unexpected_event_ =
110          !AreEquivalentEvents(input_event.pp_resource(),
111                               expected_events_.front().pp_resource());
112        expected_events_.erase(expected_events_.begin());
113      }
114      break;
115
116    default:
117      // Don't care for any other input event types for this test.
118      break;
119  }
120
121  // Handle all input events.
122  return true;
123}
124
125void TestImeInputEvent::HandleMessage(const pp::Var& message_data) {
126  if (message_data.is_string() &&
127      (message_data.AsString() == FINISHED_WAITING_MESSAGE)) {
128    testing_interface_->QuitMessageLoop(instance_->pp_instance());
129    received_finish_message_ = true;
130  }
131}
132
133void TestImeInputEvent::DidChangeView(const pp::View& view) {
134  view_rect_ = view.GetRect();
135}
136
137pp::InputEvent TestImeInputEvent::CreateImeCompositionStartEvent() {
138  return pp::IMEInputEvent(
139      instance_,
140      PP_INPUTEVENT_TYPE_IME_COMPOSITION_START,
141      100, // time_stamp
142      pp::Var(""),
143      std::vector<uint32_t>(),
144      -1, // target_segment
145      std::make_pair(0U, 0U) // selection
146  );
147}
148
149pp::InputEvent TestImeInputEvent::CreateImeCompositionUpdateEvent(
150    const std::string& text,
151    const std::vector<uint32_t>& segments,
152    int32_t target_segment,
153    const std::pair<uint32_t, uint32_t>& selection) {
154  return pp::IMEInputEvent(
155      instance_,
156      PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE,
157      100, // time_stamp
158      text,
159      segments,
160      target_segment,
161      selection
162  );
163}
164
165pp::InputEvent TestImeInputEvent::CreateImeCompositionEndEvent(
166    const std::string& text) {
167  return pp::IMEInputEvent(
168      instance_,
169      PP_INPUTEVENT_TYPE_IME_COMPOSITION_END,
170      100, // time_stamp
171      pp::Var(text),
172      std::vector<uint32_t>(),
173      -1, // target_segment
174      std::make_pair(0U, 0U) // selection
175  );
176}
177
178pp::InputEvent TestImeInputEvent::CreateImeTextEvent(const std::string& text) {
179  return pp::IMEInputEvent(
180      instance_,
181      PP_INPUTEVENT_TYPE_IME_TEXT,
182      100, // time_stamp
183      pp::Var(text),
184      std::vector<uint32_t>(),
185      -1, // target_segment
186      std::make_pair(0U, 0U) // selection
187  );
188}
189
190pp::InputEvent TestImeInputEvent::CreateCharEvent(const std::string& text) {
191  return pp::KeyboardInputEvent(
192      instance_,
193      PP_INPUTEVENT_TYPE_CHAR,
194      100,  // time_stamp
195      0,  // modifiers
196      0,  // keycode
197      pp::Var(text),
198      pp::Var());
199}
200
201void TestImeInputEvent::GetFocusBySimulatingMouseClick() {
202  // For receiving IME events, the plugin DOM node needs to be focused.
203  // The following code is for achieving that by simulating a mouse click event.
204  input_event_interface_->RequestInputEvents(instance_->pp_instance(),
205                                             PP_INPUTEVENT_CLASS_MOUSE);
206  SimulateInputEvent(pp::MouseInputEvent(
207      instance_,
208      PP_INPUTEVENT_TYPE_MOUSEDOWN,
209      100,  // time_stamp
210      0,  // modifiers
211      PP_INPUTEVENT_MOUSEBUTTON_LEFT,
212      pp::Point(
213          view_rect_.x() + view_rect_.width() / 2,
214          view_rect_.y() + view_rect_.height() / 2),
215      1,  // click count
216      pp::Point()));  // movement
217}
218
219// Simulates the input event and calls PostMessage to let us know when
220// we have received all resulting events from the browser.
221bool TestImeInputEvent::SimulateInputEvent(const pp::InputEvent& input_event) {
222  received_unexpected_event_ = false;
223  received_finish_message_ = false;
224  testing_interface_->SimulateInputEvent(instance_->pp_instance(),
225                                         input_event.pp_resource());
226  instance_->PostMessage(pp::Var(FINISHED_WAITING_MESSAGE));
227  testing_interface_->RunMessageLoop(instance_->pp_instance());
228  return received_finish_message_ && !received_unexpected_event_;
229}
230
231bool TestImeInputEvent::AreEquivalentEvents(PP_Resource received,
232                                            PP_Resource expected) {
233  if (!input_event_interface_->IsInputEvent(received) ||
234      !input_event_interface_->IsInputEvent(expected)) {
235    return false;
236  }
237
238  // Test common fields, except modifiers and time stamp, which may be changed
239  // by the browser.
240  int32_t received_type = input_event_interface_->GetType(received);
241  int32_t expected_type = input_event_interface_->GetType(expected);
242  if (received_type != expected_type)
243    return false;
244
245  // Test event type-specific fields.
246  switch (received_type) {
247    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START:
248      // COMPOSITION_START does not convey further information.
249      break;
250
251    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END:
252    case PP_INPUTEVENT_TYPE_IME_TEXT:
253      // For COMPOSITION_END and TEXT, GetText() has meaning.
254      return pp::Var(pp::PASS_REF,
255                     ime_input_event_interface_->GetText(received)) ==
256             pp::Var(pp::PASS_REF,
257                     ime_input_event_interface_->GetText(expected));
258
259    case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE:
260      // For COMPOSITION_UPDATE, all fields must be checked.
261      {
262        uint32_t received_segment_number =
263            ime_input_event_interface_->GetSegmentNumber(received);
264        uint32_t expected_segment_number =
265            ime_input_event_interface_->GetSegmentNumber(expected);
266        if (received_segment_number != expected_segment_number)
267          return false;
268
269        // The "<=" is not a bug. i-th segment is represented as the pair of
270        // i-th and (i+1)-th offsets in Pepper IME API.
271        for (uint32_t i = 0; i <= received_segment_number; ++i) {
272          if (ime_input_event_interface_->GetSegmentOffset(received, i) !=
273              ime_input_event_interface_->GetSegmentOffset(expected, i))
274            return false;
275        }
276
277        uint32_t received_selection_start = 0;
278        uint32_t received_selection_end = 0;
279        uint32_t expected_selection_start = 0;
280        uint32_t expected_selection_end = 0;
281        ime_input_event_interface_->GetSelection(
282            received, &received_selection_start, &received_selection_end);
283        ime_input_event_interface_->GetSelection(
284            expected, &expected_selection_start, &expected_selection_end);
285        if (received_selection_start != expected_selection_start ||
286            received_selection_end != expected_selection_end) {
287          return true;
288        }
289
290        return pp::Var(pp::PASS_REF,
291                       ime_input_event_interface_->GetText(received)) ==
292               pp::Var(pp::PASS_REF,
293                       ime_input_event_interface_->GetText(expected)) &&
294               ime_input_event_interface_->GetTargetSegment(received) ==
295               ime_input_event_interface_->GetTargetSegment(expected);
296      }
297
298    case PP_INPUTEVENT_TYPE_CHAR:
299      return
300          keyboard_input_event_interface_->GetKeyCode(received) ==
301          keyboard_input_event_interface_->GetKeyCode(expected) &&
302          pp::Var(pp::PASS_REF,
303              keyboard_input_event_interface_->GetCharacterText(received)) ==
304          pp::Var(pp::PASS_REF,
305              keyboard_input_event_interface_->GetCharacterText(expected));
306
307    default:
308      break;
309  }
310  return true;
311}
312
313std::string TestImeInputEvent::TestImeCommit() {
314  GetFocusBySimulatingMouseClick();
315
316  input_event_interface_->RequestInputEvents(instance_->pp_instance(),
317                                             PP_INPUTEVENT_CLASS_KEYBOARD |
318                                             PP_INPUTEVENT_CLASS_IME);
319
320  std::vector<uint32_t> segments;
321  segments.push_back(0U);
322  segments.push_back(3U);
323  segments.push_back(7U);
324  segments.push_back(11U);
325  pp::InputEvent update_event = CreateImeCompositionUpdateEvent(
326      kCompositionText, segments, 1, std::make_pair(3U, 7U));
327
328  expected_events_.clear();
329  expected_events_.push_back(CreateImeCompositionStartEvent());
330  expected_events_.push_back(update_event);
331  expected_events_.push_back(CreateImeCompositionEndEvent(kCompositionText));
332  expected_events_.push_back(CreateImeTextEvent(kCompositionText));
333
334  // Simulate the case when IME successfully committed some text.
335  ASSERT_TRUE(SimulateInputEvent(update_event));
336  ASSERT_TRUE(SimulateInputEvent(CreateImeTextEvent(kCompositionText)));
337
338  ASSERT_TRUE(expected_events_.empty());
339  PASS();
340}
341
342std::string TestImeInputEvent::TestImeCancel() {
343  GetFocusBySimulatingMouseClick();
344
345  input_event_interface_->RequestInputEvents(instance_->pp_instance(),
346                                             PP_INPUTEVENT_CLASS_KEYBOARD |
347                                             PP_INPUTEVENT_CLASS_IME);
348
349  std::vector<uint32_t> segments;
350  segments.push_back(0U);
351  segments.push_back(3U);
352  segments.push_back(7U);
353  segments.push_back(11U);
354  pp::InputEvent update_event = CreateImeCompositionUpdateEvent(
355      kCompositionText, segments, 1, std::make_pair(3U, 7U));
356
357  expected_events_.clear();
358  expected_events_.push_back(CreateImeCompositionStartEvent());
359  expected_events_.push_back(update_event);
360  expected_events_.push_back(CreateImeCompositionEndEvent(std::string()));
361
362  // Simulate the case when IME canceled composition.
363  ASSERT_TRUE(SimulateInputEvent(update_event));
364  ASSERT_TRUE(SimulateInputEvent(CreateImeCompositionEndEvent(std::string())));
365
366  ASSERT_TRUE(expected_events_.empty());
367  PASS();
368}
369
370std::string TestImeInputEvent::TestImeUnawareCommit() {
371  GetFocusBySimulatingMouseClick();
372
373  input_event_interface_->ClearInputEventRequest(instance_->pp_instance(),
374                                                 PP_INPUTEVENT_CLASS_IME);
375  input_event_interface_->RequestInputEvents(instance_->pp_instance(),
376                                             PP_INPUTEVENT_CLASS_KEYBOARD);
377
378  std::vector<uint32_t> segments;
379  segments.push_back(0U);
380  segments.push_back(3U);
381  segments.push_back(7U);
382  segments.push_back(11U);
383  pp::InputEvent update_event = CreateImeCompositionUpdateEvent(
384      kCompositionText, segments, 1, std::make_pair(3U, 7U));
385
386  expected_events_.clear();
387  expected_events_.push_back(CreateCharEvent(kCompositionChar[0]));
388  expected_events_.push_back(CreateCharEvent(kCompositionChar[1]));
389  expected_events_.push_back(CreateCharEvent(kCompositionChar[2]));
390
391  // Test for IME-unaware plugins. Commit event is translated to char events.
392  ASSERT_TRUE(SimulateInputEvent(update_event));
393  ASSERT_TRUE(SimulateInputEvent(CreateImeTextEvent(kCompositionText)));
394
395  ASSERT_TRUE(expected_events_.empty());
396  PASS();
397}
398
399
400std::string TestImeInputEvent::TestImeUnawareCancel() {
401  GetFocusBySimulatingMouseClick();
402
403  input_event_interface_->ClearInputEventRequest(instance_->pp_instance(),
404                                                 PP_INPUTEVENT_CLASS_IME);
405  input_event_interface_->RequestInputEvents(instance_->pp_instance(),
406                                             PP_INPUTEVENT_CLASS_KEYBOARD);
407
408  std::vector<uint32_t> segments;
409  segments.push_back(0U);
410  segments.push_back(3U);
411  segments.push_back(7U);
412  segments.push_back(11U);
413  pp::InputEvent update_event = CreateImeCompositionUpdateEvent(
414      kCompositionText, segments, 1, std::make_pair(3U, 7U));
415
416  expected_events_.clear();
417
418  // Test for IME-unaware plugins. Cancel won't issue any events.
419  ASSERT_TRUE(SimulateInputEvent(update_event));
420  ASSERT_TRUE(SimulateInputEvent(CreateImeCompositionEndEvent(std::string())));
421
422  ASSERT_TRUE(expected_events_.empty());
423  PASS();
424}
425