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_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(InputEvent); 15 16 namespace { 17 18 const uint32_t kSpaceChar = 0x20; 19 const char* kSpaceString = " "; 20 const char* kSpaceCode = "Space"; 21 22 #define FINISHED_WAITING_MESSAGE "TEST_INPUT_EVENT_FINISHED_WAITING" 23 24 pp::Point GetCenter(const pp::Rect& rect) { 25 return pp::Point( 26 rect.x() + rect.width() / 2, 27 rect.y() + rect.height() / 2); 28 } 29 30 } // namespace 31 32 void TestInputEvent::RunTests(const std::string& filter) { 33 RUN_TEST(Events, filter); 34 RUN_TEST(EventsLatencyTracking, filter); 35 36 // The AcceptTouchEvent_N tests should not be run when the filter is empty; 37 // they can only be run one at a time. 38 // TODO(dmichael): Figure out a way to make these run in the same test fixture 39 // instance. 40 if (!ShouldRunAllTests(filter)) { 41 RUN_TEST(AcceptTouchEvent_1, filter); 42 RUN_TEST(AcceptTouchEvent_2, filter); 43 RUN_TEST(AcceptTouchEvent_3, filter); 44 RUN_TEST(AcceptTouchEvent_4, filter); 45 } 46 } 47 48 TestInputEvent::TestInputEvent(TestingInstance* instance) 49 : TestCase(instance), 50 input_event_interface_(NULL), 51 mouse_input_event_interface_(NULL), 52 wheel_input_event_interface_(NULL), 53 keyboard_input_event_interface_(NULL), 54 touch_input_event_interface_(NULL), 55 nested_event_(instance->pp_instance()), 56 view_rect_(), 57 expected_input_event_(0), 58 received_expected_event_(false), 59 received_finish_message_(false), 60 enable_latency_tracking_(false), 61 last_latency_tracking_successful_(false) { 62 } 63 64 TestInputEvent::~TestInputEvent() { 65 // Remove the special listener that only responds to a 66 // FINISHED_WAITING_MESSAGE string. See Init for where it gets added. 67 std::string js_code; 68 js_code += "var plugin = document.getElementById('plugin');" 69 "plugin.removeEventListener('message'," 70 " plugin.wait_for_messages_handler);" 71 "delete plugin.wait_for_messages_handler;"; 72 instance_->EvalScript(js_code); 73 } 74 75 bool TestInputEvent::Init() { 76 input_event_interface_ = static_cast<const PPB_InputEvent*>( 77 pp::Module::Get()->GetBrowserInterface(PPB_INPUT_EVENT_INTERFACE)); 78 input_event_private_interface_ = static_cast<const PPB_InputEvent_Private*>( 79 pp::Module::Get()->GetBrowserInterface(PPB_INPUTEVENT_PRIVATE_INTERFACE)); 80 mouse_input_event_interface_ = static_cast<const PPB_MouseInputEvent*>( 81 pp::Module::Get()->GetBrowserInterface( 82 PPB_MOUSE_INPUT_EVENT_INTERFACE)); 83 wheel_input_event_interface_ = static_cast<const PPB_WheelInputEvent*>( 84 pp::Module::Get()->GetBrowserInterface( 85 PPB_WHEEL_INPUT_EVENT_INTERFACE)); 86 keyboard_input_event_interface_ = static_cast<const PPB_KeyboardInputEvent*>( 87 pp::Module::Get()->GetBrowserInterface( 88 PPB_KEYBOARD_INPUT_EVENT_INTERFACE)); 89 touch_input_event_interface_ = static_cast<const PPB_TouchInputEvent*>( 90 pp::Module::Get()->GetBrowserInterface( 91 PPB_TOUCH_INPUT_EVENT_INTERFACE)); 92 93 bool success = 94 input_event_interface_ && 95 input_event_private_interface_ && 96 mouse_input_event_interface_ && 97 wheel_input_event_interface_ && 98 keyboard_input_event_interface_ && 99 touch_input_event_interface_ && 100 CheckTestingInterface(); 101 102 // Set up a listener for our message that signals that all input events have 103 // been received. 104 std::string js_code; 105 // Note the following code is dependent on some features of test_case.html. 106 // E.g., it is assumed that the DOM element where the plugin is embedded has 107 // an id of 'plugin', and there is a function 'IsTestingMessage' that allows 108 // us to ignore the messages that are intended for use by the testing 109 // framework itself. 110 js_code += "var plugin = document.getElementById('plugin');" 111 "var wait_for_messages_handler = function(message_event) {" 112 " if (!IsTestingMessage(message_event.data) &&" 113 " message_event.data === '" FINISHED_WAITING_MESSAGE "') {" 114 " plugin.postMessage('" FINISHED_WAITING_MESSAGE "');" 115 " }" 116 "};" 117 "plugin.addEventListener('message', wait_for_messages_handler);" 118 // Stash it on the plugin so we can remove it in the destructor. 119 "plugin.wait_for_messages_handler = wait_for_messages_handler;"; 120 instance_->EvalScript(js_code); 121 122 return success; 123 } 124 125 pp::InputEvent TestInputEvent::CreateMouseEvent( 126 PP_InputEvent_Type type, 127 PP_InputEvent_MouseButton buttons) { 128 return pp::MouseInputEvent( 129 instance_, 130 type, 131 100, // time_stamp 132 0, // modifiers 133 buttons, 134 GetCenter(view_rect_), 135 1, // click count 136 pp::Point()); // movement 137 } 138 139 pp::InputEvent TestInputEvent::CreateWheelEvent() { 140 return pp::WheelInputEvent( 141 instance_, 142 100, // time_stamp 143 0, // modifiers 144 pp::FloatPoint(1, 2), 145 pp::FloatPoint(3, 4), 146 PP_TRUE); // scroll_by_page 147 } 148 149 pp::InputEvent TestInputEvent::CreateKeyEvent(PP_InputEvent_Type type, 150 uint32_t key_code, 151 const std::string& code) { 152 return pp::KeyboardInputEvent( 153 instance_, 154 type, 155 100, // time_stamp 156 0, // modifiers 157 key_code, 158 pp::Var(), 159 pp::Var(code)); 160 } 161 162 pp::InputEvent TestInputEvent::CreateCharEvent(const std::string& text) { 163 return pp::KeyboardInputEvent( 164 instance_, 165 PP_INPUTEVENT_TYPE_CHAR, 166 100, // time_stamp 167 0, // modifiers 168 0, // keycode 169 pp::Var(text), 170 pp::Var()); 171 } 172 173 pp::InputEvent TestInputEvent::CreateTouchEvent(PP_InputEvent_Type type, 174 const pp::FloatPoint& point) { 175 PP_TouchPoint touch_point = PP_MakeTouchPoint(); 176 touch_point.position = point; 177 178 pp::TouchInputEvent touch_event(instance_, type, 100, 0); 179 touch_event.AddTouchPoint(PP_TOUCHLIST_TYPE_TOUCHES, touch_point); 180 touch_event.AddTouchPoint(PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, touch_point); 181 touch_event.AddTouchPoint(PP_TOUCHLIST_TYPE_TARGETTOUCHES, touch_point); 182 183 return touch_event; 184 } 185 186 void TestInputEvent::PostMessageBarrier() { 187 received_finish_message_ = false; 188 instance_->PostMessage(pp::Var(FINISHED_WAITING_MESSAGE)); 189 testing_interface_->RunMessageLoop(instance_->pp_instance()); 190 nested_event_.Wait(); 191 } 192 193 // Simulates the input event and calls PostMessage to let us know when 194 // we have received all resulting events from the browser. 195 bool TestInputEvent::SimulateInputEvent( 196 const pp::InputEvent& input_event) { 197 expected_input_event_ = pp::InputEvent(input_event.pp_resource()); 198 received_expected_event_ = false; 199 testing_interface_->SimulateInputEvent(instance_->pp_instance(), 200 input_event.pp_resource()); 201 PostMessageBarrier(); 202 return received_expected_event_; 203 } 204 205 bool TestInputEvent::AreEquivalentEvents(PP_Resource received, 206 PP_Resource expected) { 207 if (!input_event_interface_->IsInputEvent(received) || 208 !input_event_interface_->IsInputEvent(expected)) { 209 return false; 210 } 211 212 // Test common fields, except modifiers and time stamp, which may be changed 213 // by the browser. 214 int32_t received_type = input_event_interface_->GetType(received); 215 int32_t expected_type = input_event_interface_->GetType(expected); 216 if (received_type != expected_type) { 217 // Allow key down events to match "raw" key down events. 218 if (expected_type != PP_INPUTEVENT_TYPE_KEYDOWN && 219 received_type != PP_INPUTEVENT_TYPE_RAWKEYDOWN) { 220 return false; 221 } 222 } 223 224 // Test event type-specific fields. 225 switch (input_event_interface_->GetType(received)) { 226 case PP_INPUTEVENT_TYPE_MOUSEDOWN: 227 case PP_INPUTEVENT_TYPE_MOUSEUP: 228 case PP_INPUTEVENT_TYPE_MOUSEMOVE: 229 case PP_INPUTEVENT_TYPE_MOUSEENTER: 230 case PP_INPUTEVENT_TYPE_MOUSELEAVE: 231 // Check mouse fields, except position and movement, which may be 232 // modified by the renderer. 233 return 234 mouse_input_event_interface_->GetButton(received) == 235 mouse_input_event_interface_->GetButton(expected) && 236 mouse_input_event_interface_->GetClickCount(received) == 237 mouse_input_event_interface_->GetClickCount(expected); 238 239 case PP_INPUTEVENT_TYPE_WHEEL: 240 return 241 pp::FloatPoint(wheel_input_event_interface_->GetDelta(received)) == 242 pp::FloatPoint(wheel_input_event_interface_->GetDelta(expected)) && 243 pp::FloatPoint(wheel_input_event_interface_->GetTicks(received)) == 244 pp::FloatPoint(wheel_input_event_interface_->GetTicks(expected)) && 245 wheel_input_event_interface_->GetScrollByPage(received) == 246 wheel_input_event_interface_->GetScrollByPage(expected); 247 248 case PP_INPUTEVENT_TYPE_RAWKEYDOWN: 249 case PP_INPUTEVENT_TYPE_KEYDOWN: 250 case PP_INPUTEVENT_TYPE_KEYUP: 251 return 252 keyboard_input_event_interface_->GetKeyCode(received) == 253 keyboard_input_event_interface_->GetKeyCode(expected); 254 255 case PP_INPUTEVENT_TYPE_CHAR: 256 return 257 keyboard_input_event_interface_->GetKeyCode(received) == 258 keyboard_input_event_interface_->GetKeyCode(expected) && 259 pp::Var(pp::PASS_REF, 260 keyboard_input_event_interface_->GetCharacterText(received)) == 261 pp::Var(pp::PASS_REF, 262 keyboard_input_event_interface_->GetCharacterText(expected)); 263 264 case PP_INPUTEVENT_TYPE_TOUCHSTART: 265 case PP_INPUTEVENT_TYPE_TOUCHMOVE: 266 case PP_INPUTEVENT_TYPE_TOUCHEND: 267 case PP_INPUTEVENT_TYPE_TOUCHCANCEL: { 268 if (!touch_input_event_interface_->IsTouchInputEvent(received) || 269 !touch_input_event_interface_->IsTouchInputEvent(expected)) 270 return false; 271 272 uint32_t touch_count = touch_input_event_interface_->GetTouchCount( 273 received, PP_TOUCHLIST_TYPE_TOUCHES); 274 if (touch_count <= 0 || 275 touch_count != touch_input_event_interface_->GetTouchCount(expected, 276 PP_TOUCHLIST_TYPE_TOUCHES)) 277 return false; 278 279 for (uint32_t i = 0; i < touch_count; ++i) { 280 PP_TouchPoint expected_point = touch_input_event_interface_-> 281 GetTouchByIndex(expected, PP_TOUCHLIST_TYPE_TOUCHES, i); 282 PP_TouchPoint received_point = touch_input_event_interface_-> 283 GetTouchByIndex(received, PP_TOUCHLIST_TYPE_TOUCHES, i); 284 285 if (expected_point.id != received_point.id || 286 expected_point.radius != received_point.radius || 287 expected_point.rotation_angle != received_point.rotation_angle || 288 expected_point.pressure != received_point.pressure) 289 return false; 290 291 if (expected_point.position.x != received_point.position.x || 292 expected_point.position.y != received_point.position.y) 293 return false; 294 } 295 return true; 296 } 297 298 default: 299 break; 300 } 301 302 return false; 303 } 304 305 bool TestInputEvent::HandleInputEvent(const pp::InputEvent& input_event) { 306 // Some events may cause extra events to be generated, so look for the 307 // first one that matches. 308 if (!received_expected_event_) { 309 received_expected_event_ = AreEquivalentEvents( 310 input_event.pp_resource(), 311 expected_input_event_.pp_resource()); 312 } 313 // Handle all input events. 314 if (enable_latency_tracking_) { 315 pp::InputEventPrivate private_event(input_event); 316 last_latency_tracking_successful_ = private_event.TraceInputLatency(true); 317 } 318 return true; 319 } 320 321 void TestInputEvent::HandleMessage(const pp::Var& message_data) { 322 if (message_data.is_string() && 323 (message_data.AsString() == FINISHED_WAITING_MESSAGE)) { 324 testing_interface_->QuitMessageLoop(instance_->pp_instance()); 325 received_finish_message_ = true; 326 nested_event_.Signal(); 327 } 328 } 329 330 void TestInputEvent::DidChangeView(const pp::View& view) { 331 view_rect_ = view.GetRect(); 332 } 333 334 std::string TestInputEvent::TestEventsLatencyTracking() { 335 enable_latency_tracking_ = true; 336 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 337 PP_INPUTEVENT_CLASS_TOUCH); 338 PostMessageBarrier(); 339 340 ASSERT_TRUE(SimulateInputEvent(CreateTouchEvent(PP_INPUTEVENT_TYPE_TOUCHSTART, 341 pp::FloatPoint(12, 23)))); 342 // Without calling StartTrackingLatency() first, TraceInputLatency() won't 343 // take effect and will return false; 344 ASSERT_FALSE(last_latency_tracking_successful_); 345 346 input_event_private_interface_->StartTrackingLatency( 347 instance_->pp_instance()); 348 349 ASSERT_TRUE(SimulateInputEvent(CreateTouchEvent(PP_INPUTEVENT_TYPE_TOUCHSTART, 350 pp::FloatPoint(12, 23)))); 351 ASSERT_TRUE(last_latency_tracking_successful_); 352 353 PASS(); 354 } 355 356 std::string TestInputEvent::TestEvents() { 357 // Request all input event classes. 358 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 359 PP_INPUTEVENT_CLASS_MOUSE | 360 PP_INPUTEVENT_CLASS_WHEEL | 361 PP_INPUTEVENT_CLASS_KEYBOARD | 362 PP_INPUTEVENT_CLASS_TOUCH); 363 PostMessageBarrier(); 364 365 // Send the events and check that we received them. 366 ASSERT_TRUE( 367 SimulateInputEvent(CreateMouseEvent(PP_INPUTEVENT_TYPE_MOUSEDOWN, 368 PP_INPUTEVENT_MOUSEBUTTON_LEFT))); 369 ASSERT_TRUE( 370 SimulateInputEvent(CreateWheelEvent())); 371 ASSERT_TRUE( 372 SimulateInputEvent(CreateKeyEvent(PP_INPUTEVENT_TYPE_KEYDOWN, 373 kSpaceChar, kSpaceCode))); 374 ASSERT_TRUE( 375 SimulateInputEvent(CreateCharEvent(kSpaceString))); 376 ASSERT_TRUE(SimulateInputEvent(CreateTouchEvent(PP_INPUTEVENT_TYPE_TOUCHSTART, 377 pp::FloatPoint(12, 23)))); 378 // Request only mouse events. 379 input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), 380 PP_INPUTEVENT_CLASS_WHEEL | 381 PP_INPUTEVENT_CLASS_KEYBOARD); 382 PostMessageBarrier(); 383 384 // Check that we only receive mouse events. 385 ASSERT_TRUE( 386 SimulateInputEvent(CreateMouseEvent(PP_INPUTEVENT_TYPE_MOUSEDOWN, 387 PP_INPUTEVENT_MOUSEBUTTON_LEFT))); 388 ASSERT_FALSE( 389 SimulateInputEvent(CreateWheelEvent())); 390 ASSERT_FALSE( 391 SimulateInputEvent(CreateKeyEvent(PP_INPUTEVENT_TYPE_KEYDOWN, 392 kSpaceChar, kSpaceCode))); 393 ASSERT_FALSE( 394 SimulateInputEvent(CreateCharEvent(kSpaceString))); 395 396 PASS(); 397 } 398 399 std::string TestInputEvent::TestAcceptTouchEvent_1() { 400 // The browser normally sends touch-events to the renderer only if the page 401 // has touch-event handlers. Since test-case.html does not have any 402 // touch-event handler, it would normally not receive any touch events from 403 // the browser. However, if a plugin in the page does accept touch events, 404 // then the browser should start sending touch-events to the page. In this 405 // test, the plugin simply registers for touch-events. The real test is to 406 // verify that the browser knows to send touch-events to the renderer. 407 // If the plugin is removed from the page, then there are no more touch-event 408 // handlers in the page, and browser stops sending touch-events. So to make 409 // it possible to test this properly, the plugin is not removed from the page 410 // at the end of the test. 411 instance_->set_remove_plugin(false); 412 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 413 PP_INPUTEVENT_CLASS_MOUSE | 414 PP_INPUTEVENT_CLASS_WHEEL | 415 PP_INPUTEVENT_CLASS_KEYBOARD | 416 PP_INPUTEVENT_CLASS_TOUCH); 417 PASS(); 418 } 419 420 std::string TestInputEvent::TestAcceptTouchEvent_2() { 421 // See comment in TestAcceptTouchEvent_1. 422 instance_->set_remove_plugin(false); 423 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 424 PP_INPUTEVENT_CLASS_MOUSE | 425 PP_INPUTEVENT_CLASS_WHEEL | 426 PP_INPUTEVENT_CLASS_KEYBOARD | 427 PP_INPUTEVENT_CLASS_TOUCH); 428 input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), 429 PP_INPUTEVENT_CLASS_TOUCH); 430 PASS(); 431 } 432 433 std::string TestInputEvent::TestAcceptTouchEvent_3() { 434 // See comment in TestAcceptTouchEvent_1. 435 instance_->set_remove_plugin(false); 436 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 437 PP_INPUTEVENT_CLASS_MOUSE | 438 PP_INPUTEVENT_CLASS_WHEEL | 439 PP_INPUTEVENT_CLASS_KEYBOARD); 440 input_event_interface_->RequestFilteringInputEvents(instance_->pp_instance(), 441 PP_INPUTEVENT_CLASS_TOUCH); 442 PASS(); 443 } 444 445 std::string TestInputEvent::TestAcceptTouchEvent_4() { 446 // See comment in TestAcceptTouchEvent_1. 447 instance_->set_remove_plugin(false); 448 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 449 PP_INPUTEVENT_CLASS_MOUSE | 450 PP_INPUTEVENT_CLASS_WHEEL | 451 PP_INPUTEVENT_CLASS_KEYBOARD); 452 input_event_interface_->RequestInputEvents(instance_->pp_instance(), 453 PP_INPUTEVENT_CLASS_TOUCH); 454 PASS(); 455 } 456