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 pp::Var()); 199 } 200 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. 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 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 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 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 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 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