// 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( pp::Module::Get()->GetBrowserInterface(PPB_INPUT_EVENT_INTERFACE)); mouse_input_event_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_MOUSE_INPUT_EVENT_INTERFACE)); wheel_input_event_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_WHEEL_INPUT_EVENT_INTERFACE)); keyboard_input_event_interface_ = static_cast( pp::Module::Get()->GetBrowserInterface( PPB_KEYBOARD_INPUT_EVENT_INTERFACE)); touch_input_event_interface_ = static_cast( 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(); }