| // 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(); | |
| } |