| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ppapi/tests/test_input_event.h" |
| |
| #include "ppapi/c/pp_errors.h" |
| #include "ppapi/c/ppb_input_event.h" |
| #include "ppapi/cpp/input_event.h" |
| #include "ppapi/cpp/module.h" |
| #include "ppapi/tests/test_utils.h" |
| #include "ppapi/tests/testing_instance.h" |
| |
| REGISTER_TEST_CASE(InputEvent); |
| |
| namespace { |
| |
| const uint32_t kSpaceChar = 0x20; |
| const char* kSpaceString = " "; |
| const char* kSpaceCode = "Space"; |
| |
| #define FINISHED_WAITING_MESSAGE "TEST_INPUT_EVENT_FINISHED_WAITING" |
| |
| pp::Point GetCenter(const pp::Rect& rect) { |
| return pp::Point( |
| rect.x() + rect.width() / 2, |
| rect.y() + rect.height() / 2); |
| } |
| |
| } // namespace |
| |
| void TestInputEvent::RunTests(const std::string& filter) { |
| RUN_TEST(Events, filter); |
| |
| // The AcceptTouchEvent_N tests should not be run when the filter is empty; |
| // they can only be run one at a time. |
| // TODO(dmichael): Figure out a way to make these run in the same test fixture |
| // instance. |
| if (!ShouldRunAllTests(filter)) { |
| RUN_TEST(AcceptTouchEvent_1, filter); |
| RUN_TEST(AcceptTouchEvent_2, filter); |
| RUN_TEST(AcceptTouchEvent_3, filter); |
| RUN_TEST(AcceptTouchEvent_4, filter); |
| } |
| } |
| |
| TestInputEvent::TestInputEvent(TestingInstance* instance) |
| : TestCase(instance), |
| input_event_interface_(NULL), |
| mouse_input_event_interface_(NULL), |
| wheel_input_event_interface_(NULL), |
| keyboard_input_event_interface_(NULL), |
| touch_input_event_interface_(NULL), |
| nested_event_(instance->pp_instance()), |
| view_rect_(), |
| expected_input_event_(0), |
| received_expected_event_(false), |
| received_finish_message_(false) { |
| } |
| |
| TestInputEvent::~TestInputEvent() { |
| // Remove the special listener that only responds to a |
| // FINISHED_WAITING_MESSAGE string. See Init for where it gets added. |
| std::string js_code; |
| js_code += "var plugin = document.getElementById('plugin');" |
| "plugin.removeEventListener('message'," |
| " plugin.wait_for_messages_handler);" |
| "delete plugin.wait_for_messages_handler;"; |
| instance_->EvalScript(js_code); |
| } |
| |
| bool TestInputEvent::Init() { |
| input_event_interface_ = static_cast<const PPB_InputEvent*>( |
| pp::Module::Get()->GetBrowserInterface(PPB_INPUT_EVENT_INTERFACE)); |
| mouse_input_event_interface_ = static_cast<const PPB_MouseInputEvent*>( |
| pp::Module::Get()->GetBrowserInterface( |
| PPB_MOUSE_INPUT_EVENT_INTERFACE)); |
| wheel_input_event_interface_ = static_cast<const PPB_WheelInputEvent*>( |
| pp::Module::Get()->GetBrowserInterface( |
| PPB_WHEEL_INPUT_EVENT_INTERFACE)); |
| keyboard_input_event_interface_ = static_cast<const PPB_KeyboardInputEvent*>( |
| pp::Module::Get()->GetBrowserInterface( |
| PPB_KEYBOARD_INPUT_EVENT_INTERFACE)); |
| touch_input_event_interface_ = static_cast<const PPB_TouchInputEvent*>( |
| pp::Module::Get()->GetBrowserInterface( |
| PPB_TOUCH_INPUT_EVENT_INTERFACE)); |
| |
| bool success = |
| input_event_interface_ && |
| mouse_input_event_interface_ && |
| wheel_input_event_interface_ && |
| keyboard_input_event_interface_ && |
| touch_input_event_interface_ && |
| CheckTestingInterface(); |
| |
| // Set up a listener for our message that signals that all input events have |
| // been received. |
| std::string js_code; |
| // Note the following code is dependent on some features of test_case.html. |
| // E.g., it is assumed that the DOM element where the plugin is embedded has |
| // an id of 'plugin', and there is a function 'IsTestingMessage' that allows |
| // us to ignore the messages that are intended for use by the testing |
| // framework itself. |
| js_code += "var plugin = document.getElementById('plugin');" |
| "var wait_for_messages_handler = function(message_event) {" |
| " if (!IsTestingMessage(message_event.data) &&" |
| " message_event.data === '" FINISHED_WAITING_MESSAGE "') {" |
| " plugin.postMessage('" FINISHED_WAITING_MESSAGE "');" |
| " }" |
| "};" |
| "plugin.addEventListener('message', wait_for_messages_handler);" |
| // Stash it on the plugin so we can remove it in the destructor. |
| "plugin.wait_for_messages_handler = wait_for_messages_handler;"; |
| instance_->EvalScript(js_code); |
| |
| return success; |
| } |
| |
| pp::InputEvent TestInputEvent::CreateMouseEvent( |
| PP_InputEvent_Type type, |
| PP_InputEvent_MouseButton buttons) { |
| return pp::MouseInputEvent( |
| instance_, |
| type, |
| 100, // time_stamp |
| 0, // modifiers |
| buttons, |
| GetCenter(view_rect_), |
| 1, // click count |
| pp::Point()); // movement |
| } |
| |
| pp::InputEvent TestInputEvent::CreateWheelEvent() { |
| return pp::WheelInputEvent( |
| instance_, |
| 100, // time_stamp |
| 0, // modifiers |
| pp::FloatPoint(1, 2), |
| pp::FloatPoint(3, 4), |
| PP_TRUE); // scroll_by_page |
| } |
| |
| pp::InputEvent TestInputEvent::CreateKeyEvent(PP_InputEvent_Type type, |
| uint32_t key_code, |
| const std::string& code) { |
| return pp::KeyboardInputEvent( |
| instance_, |
| type, |
| 100, // time_stamp |
| 0, // modifiers |
| key_code, |
| pp::Var(), |
| pp::Var(code)); |
| } |
| |
| pp::InputEvent TestInputEvent::CreateCharEvent(const std::string& text) { |
| return pp::KeyboardInputEvent( |
| instance_, |
| PP_INPUTEVENT_TYPE_CHAR, |
| 100, // time_stamp |
| 0, // modifiers |
| 0, // keycode |
| pp::Var(text), |
| pp::Var()); |
| } |
| |
| pp::InputEvent TestInputEvent::CreateTouchEvent(PP_InputEvent_Type type, |
| const pp::FloatPoint& point) { |
| PP_TouchPoint touch_point = PP_MakeTouchPoint(); |
| touch_point.position = point; |
| |
| pp::TouchInputEvent touch_event(instance_, type, 100, 0); |
| touch_event.AddTouchPoint(PP_TOUCHLIST_TYPE_TOUCHES, touch_point); |
| touch_event.AddTouchPoint(PP_TOUCHLIST_TYPE_CHANGEDTOUCHES, touch_point); |
| touch_event.AddTouchPoint(PP_TOUCHLIST_TYPE_TARGETTOUCHES, touch_point); |
| |
| return touch_event; |
| } |
| |
| void TestInputEvent::PostMessageBarrier() { |
| received_finish_message_ = false; |
| instance_->PostMessage(pp::Var(FINISHED_WAITING_MESSAGE)); |
| testing_interface_->RunMessageLoop(instance_->pp_instance()); |
| nested_event_.Wait(); |
| } |
| |
| // Simulates the input event and calls PostMessage to let us know when |
| // we have received all resulting events from the browser. |
| bool TestInputEvent::SimulateInputEvent( |
| const pp::InputEvent& input_event) { |
| expected_input_event_ = pp::InputEvent(input_event.pp_resource()); |
| received_expected_event_ = false; |
| testing_interface_->SimulateInputEvent(instance_->pp_instance(), |
| input_event.pp_resource()); |
| PostMessageBarrier(); |
| return received_expected_event_; |
| } |
| |
| bool TestInputEvent::AreEquivalentEvents(PP_Resource received, |
| PP_Resource expected) { |
| if (!input_event_interface_->IsInputEvent(received) || |
| !input_event_interface_->IsInputEvent(expected)) { |
| return false; |
| } |
| |
| // Test common fields, except modifiers and time stamp, which may be changed |
| // by the browser. |
| int32_t received_type = input_event_interface_->GetType(received); |
| int32_t expected_type = input_event_interface_->GetType(expected); |
| if (received_type != expected_type) { |
| // Allow key down events to match "raw" key down events. |
| if (expected_type != PP_INPUTEVENT_TYPE_KEYDOWN && |
| received_type != PP_INPUTEVENT_TYPE_RAWKEYDOWN) { |
| return false; |
| } |
| } |
| |
| // Test event type-specific fields. |
| switch (input_event_interface_->GetType(received)) { |
| case PP_INPUTEVENT_TYPE_MOUSEDOWN: |
| case PP_INPUTEVENT_TYPE_MOUSEUP: |
| case PP_INPUTEVENT_TYPE_MOUSEMOVE: |
| case PP_INPUTEVENT_TYPE_MOUSEENTER: |
| case PP_INPUTEVENT_TYPE_MOUSELEAVE: |
| // Check mouse fields, except position and movement, which may be |
| // modified by the renderer. |
| return |
| mouse_input_event_interface_->GetButton(received) == |
| mouse_input_event_interface_->GetButton(expected) && |
| mouse_input_event_interface_->GetClickCount(received) == |
| mouse_input_event_interface_->GetClickCount(expected); |
| |
| case PP_INPUTEVENT_TYPE_WHEEL: |
| return |
| pp::FloatPoint(wheel_input_event_interface_->GetDelta(received)) == |
| pp::FloatPoint(wheel_input_event_interface_->GetDelta(expected)) && |
| pp::FloatPoint(wheel_input_event_interface_->GetTicks(received)) == |
| pp::FloatPoint(wheel_input_event_interface_->GetTicks(expected)) && |
| wheel_input_event_interface_->GetScrollByPage(received) == |
| wheel_input_event_interface_->GetScrollByPage(expected); |
| |
| case PP_INPUTEVENT_TYPE_RAWKEYDOWN: |
| case PP_INPUTEVENT_TYPE_KEYDOWN: |
| case PP_INPUTEVENT_TYPE_KEYUP: |
| return |
| keyboard_input_event_interface_->GetKeyCode(received) == |
| keyboard_input_event_interface_->GetKeyCode(expected); |
| |
| case PP_INPUTEVENT_TYPE_CHAR: |
| return |
| keyboard_input_event_interface_->GetKeyCode(received) == |
| keyboard_input_event_interface_->GetKeyCode(expected) && |
| pp::Var(pp::PASS_REF, |
| keyboard_input_event_interface_->GetCharacterText(received)) == |
| pp::Var(pp::PASS_REF, |
| keyboard_input_event_interface_->GetCharacterText(expected)); |
| |
| case PP_INPUTEVENT_TYPE_TOUCHSTART: |
| case PP_INPUTEVENT_TYPE_TOUCHMOVE: |
| case PP_INPUTEVENT_TYPE_TOUCHEND: |
| case PP_INPUTEVENT_TYPE_TOUCHCANCEL: { |
| if (!touch_input_event_interface_->IsTouchInputEvent(received) || |
| !touch_input_event_interface_->IsTouchInputEvent(expected)) |
| return false; |
| |
| uint32_t touch_count = touch_input_event_interface_->GetTouchCount( |
| received, PP_TOUCHLIST_TYPE_TOUCHES); |
| if (touch_count <= 0 || |
| touch_count != touch_input_event_interface_->GetTouchCount(expected, |
| PP_TOUCHLIST_TYPE_TOUCHES)) |
| return false; |
| |
| for (uint32_t i = 0; i < touch_count; ++i) { |
| PP_TouchPoint expected_point = touch_input_event_interface_-> |
| GetTouchByIndex(expected, PP_TOUCHLIST_TYPE_TOUCHES, i); |
| PP_TouchPoint received_point = touch_input_event_interface_-> |
| GetTouchByIndex(received, PP_TOUCHLIST_TYPE_TOUCHES, i); |
| |
| if (expected_point.id != received_point.id || |
| expected_point.radius != received_point.radius || |
| expected_point.rotation_angle != received_point.rotation_angle || |
| expected_point.pressure != received_point.pressure) |
| return false; |
| |
| if (expected_point.position.x != received_point.position.x || |
| expected_point.position.y != received_point.position.y) |
| return false; |
| } |
| return true; |
| } |
| |
| default: |
| break; |
| } |
| |
| return false; |
| } |
| |
| bool TestInputEvent::HandleInputEvent(const pp::InputEvent& input_event) { |
| // Some events may cause extra events to be generated, so look for the |
| // first one that matches. |
| if (!received_expected_event_) { |
| received_expected_event_ = AreEquivalentEvents( |
| input_event.pp_resource(), |
| expected_input_event_.pp_resource()); |
| } |
| // Handle all input events. |
| return true; |
| } |
| |
| void TestInputEvent::HandleMessage(const pp::Var& message_data) { |
| if (message_data.is_string() && |
| (message_data.AsString() == FINISHED_WAITING_MESSAGE)) { |
| testing_interface_->QuitMessageLoop(instance_->pp_instance()); |
| received_finish_message_ = true; |
| nested_event_.Signal(); |
| } |
| } |
| |
| void TestInputEvent::DidChangeView(const pp::View& view) { |
| view_rect_ = view.GetRect(); |
| } |
| |
| std::string TestInputEvent::TestEvents() { |
| // Request all input event classes. |
| input_event_interface_->RequestInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_MOUSE | |
| PP_INPUTEVENT_CLASS_WHEEL | |
| PP_INPUTEVENT_CLASS_KEYBOARD | |
| PP_INPUTEVENT_CLASS_TOUCH); |
| PostMessageBarrier(); |
| |
| // Send the events and check that we received them. |
| ASSERT_TRUE( |
| SimulateInputEvent(CreateMouseEvent(PP_INPUTEVENT_TYPE_MOUSEDOWN, |
| PP_INPUTEVENT_MOUSEBUTTON_LEFT))); |
| ASSERT_TRUE( |
| SimulateInputEvent(CreateWheelEvent())); |
| ASSERT_TRUE( |
| SimulateInputEvent(CreateKeyEvent(PP_INPUTEVENT_TYPE_KEYDOWN, |
| kSpaceChar, kSpaceCode))); |
| ASSERT_TRUE( |
| SimulateInputEvent(CreateCharEvent(kSpaceString))); |
| ASSERT_TRUE(SimulateInputEvent(CreateTouchEvent(PP_INPUTEVENT_TYPE_TOUCHSTART, |
| pp::FloatPoint(12, 23)))); |
| // Request only mouse events. |
| input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_WHEEL | |
| PP_INPUTEVENT_CLASS_KEYBOARD); |
| PostMessageBarrier(); |
| |
| // Check that we only receive mouse events. |
| ASSERT_TRUE( |
| SimulateInputEvent(CreateMouseEvent(PP_INPUTEVENT_TYPE_MOUSEDOWN, |
| PP_INPUTEVENT_MOUSEBUTTON_LEFT))); |
| ASSERT_FALSE( |
| SimulateInputEvent(CreateWheelEvent())); |
| ASSERT_FALSE( |
| SimulateInputEvent(CreateKeyEvent(PP_INPUTEVENT_TYPE_KEYDOWN, |
| kSpaceChar, kSpaceCode))); |
| ASSERT_FALSE( |
| SimulateInputEvent(CreateCharEvent(kSpaceString))); |
| |
| PASS(); |
| } |
| |
| std::string TestInputEvent::TestAcceptTouchEvent_1() { |
| // The browser normally sends touch-events to the renderer only if the page |
| // has touch-event handlers. Since test-case.html does not have any |
| // touch-event handler, it would normally not receive any touch events from |
| // the browser. However, if a plugin in the page does accept touch events, |
| // then the browser should start sending touch-events to the page. In this |
| // test, the plugin simply registers for touch-events. The real test is to |
| // verify that the browser knows to send touch-events to the renderer. |
| // If the plugin is removed from the page, then there are no more touch-event |
| // handlers in the page, and browser stops sending touch-events. So to make |
| // it possible to test this properly, the plugin is not removed from the page |
| // at the end of the test. |
| instance_->set_remove_plugin(false); |
| input_event_interface_->RequestInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_MOUSE | |
| PP_INPUTEVENT_CLASS_WHEEL | |
| PP_INPUTEVENT_CLASS_KEYBOARD | |
| PP_INPUTEVENT_CLASS_TOUCH); |
| PASS(); |
| } |
| |
| std::string TestInputEvent::TestAcceptTouchEvent_2() { |
| // See comment in TestAcceptTouchEvent_1. |
| instance_->set_remove_plugin(false); |
| input_event_interface_->RequestInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_MOUSE | |
| PP_INPUTEVENT_CLASS_WHEEL | |
| PP_INPUTEVENT_CLASS_KEYBOARD | |
| PP_INPUTEVENT_CLASS_TOUCH); |
| input_event_interface_->ClearInputEventRequest(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_TOUCH); |
| PASS(); |
| } |
| |
| std::string TestInputEvent::TestAcceptTouchEvent_3() { |
| // See comment in TestAcceptTouchEvent_1. |
| instance_->set_remove_plugin(false); |
| input_event_interface_->RequestInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_MOUSE | |
| PP_INPUTEVENT_CLASS_WHEEL | |
| PP_INPUTEVENT_CLASS_KEYBOARD); |
| input_event_interface_->RequestFilteringInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_TOUCH); |
| PASS(); |
| } |
| |
| std::string TestInputEvent::TestAcceptTouchEvent_4() { |
| // See comment in TestAcceptTouchEvent_1. |
| instance_->set_remove_plugin(false); |
| input_event_interface_->RequestInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_MOUSE | |
| PP_INPUTEVENT_CLASS_WHEEL | |
| PP_INPUTEVENT_CLASS_KEYBOARD); |
| input_event_interface_->RequestInputEvents(instance_->pp_instance(), |
| PP_INPUTEVENT_CLASS_TOUCH); |
| PASS(); |
| } |