• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
14 REGISTER_TEST_CASE(ImeInputEvent);
15 
16 namespace {
17 
18 // Japanese Kanji letters
19 const 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 
25 const 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 
TestImeInputEvent(TestingInstance * instance)31 TestImeInputEvent::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 
~TestImeInputEvent()40 TestImeInputEvent::~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 
RunTests(const std::string & filter)51 void 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 
Init()58 bool 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 
HandleInputEvent(const pp::InputEvent & input_event)98 bool 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 
HandleMessage(const pp::Var & message_data)125 void 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 
DidChangeView(const pp::View & view)133 void TestImeInputEvent::DidChangeView(const pp::View& view) {
134   view_rect_ = view.GetRect();
135 }
136 
CreateImeCompositionStartEvent()137 pp::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 
CreateImeCompositionUpdateEvent(const std::string & text,const std::vector<uint32_t> & segments,int32_t target_segment,const std::pair<uint32_t,uint32_t> & selection)149 pp::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 
CreateImeCompositionEndEvent(const std::string & text)165 pp::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 
CreateImeTextEvent(const std::string & text)178 pp::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 
CreateCharEvent(const std::string & text)190 pp::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 
GetFocusBySimulatingMouseClick()201 void 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.
SimulateInputEvent(const pp::InputEvent & input_event)221 bool 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 
AreEquivalentEvents(PP_Resource received,PP_Resource expected)231 bool 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 
TestImeCommit()313 std::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 
TestImeCancel()342 std::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 
TestImeUnawareCommit()370 std::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 
TestImeUnawareCancel()400 std::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