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 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 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 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 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 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 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 133 void TestImeInputEvent::DidChangeView(const pp::View& view) { 134 view_rect_ = view.GetRect(); 135 } 136 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 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 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 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 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 } 199 200 void TestImeInputEvent::GetFocusBySimulatingMouseClick() { 201 // For receiving IME events, the plugin DOM node needs to be focused. 202 // The following code is for achieving that by simulating a mouse click event. 203 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 204 PP_INPUTEVENT_CLASS_MOUSE); 205 SimulateInputEvent(pp::MouseInputEvent( 206 instance_, 207 PP_INPUTEVENT_TYPE_MOUSEDOWN, 208 100, // time_stamp 209 0, // modifiers 210 PP_INPUTEVENT_MOUSEBUTTON_LEFT, 211 pp::Point( 212 view_rect_.x() + view_rect_.width() / 2, 213 view_rect_.y() + view_rect_.height() / 2), 214 1, // click count 215 pp::Point())); // movement 216 } 217 218 // Simulates the input event and calls PostMessage to let us know when 219 // we have received all resulting events from the browser. 220 bool TestImeInputEvent::SimulateInputEvent(const pp::InputEvent& input_event) { 221 received_unexpected_event_ = false; 222 received_finish_message_ = false; 223 testing_interface_->SimulateInputEvent(instance_->pp_instance(), 224 input_event.pp_resource()); 225 instance_->PostMessage(pp::Var(FINISHED_WAITING_MESSAGE)); 226 testing_interface_->RunMessageLoop(instance_->pp_instance()); 227 return received_finish_message_ && !received_unexpected_event_; 228 } 229 230 bool TestImeInputEvent::AreEquivalentEvents(PP_Resource received, 231 PP_Resource expected) { 232 if (!input_event_interface_->IsInputEvent(received) || 233 !input_event_interface_->IsInputEvent(expected)) { 234 return false; 235 } 236 237 // Test common fields, except modifiers and time stamp, which may be changed 238 // by the browser. 239 int32_t received_type = input_event_interface_->GetType(received); 240 int32_t expected_type = input_event_interface_->GetType(expected); 241 if (received_type != expected_type) 242 return false; 243 244 // Test event type-specific fields. 245 switch (received_type) { 246 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_START: 247 // COMPOSITION_START does not convey further information. 248 break; 249 250 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_END: 251 case PP_INPUTEVENT_TYPE_IME_TEXT: 252 // For COMPOSITION_END and TEXT, GetText() has meaning. 253 return pp::Var(pp::PASS_REF, 254 ime_input_event_interface_->GetText(received)) == 255 pp::Var(pp::PASS_REF, 256 ime_input_event_interface_->GetText(expected)); 257 258 case PP_INPUTEVENT_TYPE_IME_COMPOSITION_UPDATE: 259 // For COMPOSITION_UPDATE, all fields must be checked. 260 { 261 uint32_t received_segment_number = 262 ime_input_event_interface_->GetSegmentNumber(received); 263 uint32_t expected_segment_number = 264 ime_input_event_interface_->GetSegmentNumber(expected); 265 if (received_segment_number != expected_segment_number) 266 return false; 267 268 // The "<=" is not a bug. i-th segment is represented as the pair of 269 // i-th and (i+1)-th offsets in Pepper IME API. 270 for (uint32_t i = 0; i <= received_segment_number; ++i) { 271 if (ime_input_event_interface_->GetSegmentOffset(received, i) != 272 ime_input_event_interface_->GetSegmentOffset(expected, i)) 273 return false; 274 } 275 276 uint32_t received_selection_start = 0; 277 uint32_t received_selection_end = 0; 278 uint32_t expected_selection_start = 0; 279 uint32_t expected_selection_end = 0; 280 ime_input_event_interface_->GetSelection( 281 received, &received_selection_start, &received_selection_end); 282 ime_input_event_interface_->GetSelection( 283 expected, &expected_selection_start, &expected_selection_end); 284 if (received_selection_start != expected_selection_start || 285 received_selection_end != expected_selection_end) { 286 return true; 287 } 288 289 return pp::Var(pp::PASS_REF, 290 ime_input_event_interface_->GetText(received)) == 291 pp::Var(pp::PASS_REF, 292 ime_input_event_interface_->GetText(expected)) && 293 ime_input_event_interface_->GetTargetSegment(received) == 294 ime_input_event_interface_->GetTargetSegment(expected); 295 } 296 297 case PP_INPUTEVENT_TYPE_CHAR: 298 return 299 keyboard_input_event_interface_->GetKeyCode(received) == 300 keyboard_input_event_interface_->GetKeyCode(expected) && 301 pp::Var(pp::PASS_REF, 302 keyboard_input_event_interface_->GetCharacterText(received)) == 303 pp::Var(pp::PASS_REF, 304 keyboard_input_event_interface_->GetCharacterText(expected)); 305 306 default: 307 break; 308 } 309 return true; 310 } 311 312 std::string TestImeInputEvent::TestImeCommit() { 313 GetFocusBySimulatingMouseClick(); 314 315 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 316 PP_INPUTEVENT_CLASS_KEYBOARD | 317 PP_INPUTEVENT_CLASS_IME); 318 319 std::vector<uint32_t> segments; 320 segments.push_back(0U); 321 segments.push_back(3U); 322 segments.push_back(7U); 323 segments.push_back(11U); 324 pp::InputEvent update_event = CreateImeCompositionUpdateEvent( 325 kCompositionText, segments, 1, std::make_pair(3U, 7U)); 326 327 expected_events_.clear(); 328 expected_events_.push_back(CreateImeCompositionStartEvent()); 329 expected_events_.push_back(update_event); 330 expected_events_.push_back(CreateImeCompositionEndEvent(kCompositionText)); 331 expected_events_.push_back(CreateImeTextEvent(kCompositionText)); 332 333 // Simulate the case when IME successfully committed some text. 334 ASSERT_TRUE(SimulateInputEvent(update_event)); 335 ASSERT_TRUE(SimulateInputEvent(CreateImeTextEvent(kCompositionText))); 336 337 ASSERT_TRUE(expected_events_.empty()); 338 PASS(); 339 } 340 341 std::string TestImeInputEvent::TestImeCancel() { 342 GetFocusBySimulatingMouseClick(); 343 344 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 345 PP_INPUTEVENT_CLASS_KEYBOARD | 346 PP_INPUTEVENT_CLASS_IME); 347 348 std::vector<uint32_t> segments; 349 segments.push_back(0U); 350 segments.push_back(3U); 351 segments.push_back(7U); 352 segments.push_back(11U); 353 pp::InputEvent update_event = CreateImeCompositionUpdateEvent( 354 kCompositionText, segments, 1, std::make_pair(3U, 7U)); 355 356 expected_events_.clear(); 357 expected_events_.push_back(CreateImeCompositionStartEvent()); 358 expected_events_.push_back(update_event); 359 expected_events_.push_back(CreateImeCompositionEndEvent(std::string())); 360 361 // Simulate the case when IME canceled composition. 362 ASSERT_TRUE(SimulateInputEvent(update_event)); 363 ASSERT_TRUE(SimulateInputEvent(CreateImeCompositionEndEvent(std::string()))); 364 365 ASSERT_TRUE(expected_events_.empty()); 366 PASS(); 367 } 368 369 std::string TestImeInputEvent::TestImeUnawareCommit() { 370 GetFocusBySimulatingMouseClick(); 371 372 input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), 373 PP_INPUTEVENT_CLASS_IME); 374 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 375 PP_INPUTEVENT_CLASS_KEYBOARD); 376 377 std::vector<uint32_t> segments; 378 segments.push_back(0U); 379 segments.push_back(3U); 380 segments.push_back(7U); 381 segments.push_back(11U); 382 pp::InputEvent update_event = CreateImeCompositionUpdateEvent( 383 kCompositionText, segments, 1, std::make_pair(3U, 7U)); 384 385 expected_events_.clear(); 386 expected_events_.push_back(CreateCharEvent(kCompositionChar[0])); 387 expected_events_.push_back(CreateCharEvent(kCompositionChar[1])); 388 expected_events_.push_back(CreateCharEvent(kCompositionChar[2])); 389 390 // Test for IME-unaware plugins. Commit event is translated to char events. 391 ASSERT_TRUE(SimulateInputEvent(update_event)); 392 ASSERT_TRUE(SimulateInputEvent(CreateImeTextEvent(kCompositionText))); 393 394 ASSERT_TRUE(expected_events_.empty()); 395 PASS(); 396 } 397 398 399 std::string TestImeInputEvent::TestImeUnawareCancel() { 400 GetFocusBySimulatingMouseClick(); 401 402 input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), 403 PP_INPUTEVENT_CLASS_IME); 404 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 405 PP_INPUTEVENT_CLASS_KEYBOARD); 406 407 std::vector<uint32_t> segments; 408 segments.push_back(0U); 409 segments.push_back(3U); 410 segments.push_back(7U); 411 segments.push_back(11U); 412 pp::InputEvent update_event = CreateImeCompositionUpdateEvent( 413 kCompositionText, segments, 1, std::make_pair(3U, 7U)); 414 415 expected_events_.clear(); 416 417 // Test for IME-unaware plugins. Cancel won't issue any events. 418 ASSERT_TRUE(SimulateInputEvent(update_event)); 419 ASSERT_TRUE(SimulateInputEvent(CreateImeCompositionEndEvent(std::string()))); 420 421 ASSERT_TRUE(expected_events_.empty()); 422 PASS(); 423 } 424 425