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