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