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
GetCenter(const pp::Rect & rect)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
RunTests(const std::string & filter)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
TestInputEvent(TestingInstance * instance)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
~TestInputEvent()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
Init()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
CreateMouseEvent(PP_InputEvent_Type type,PP_InputEvent_MouseButton buttons)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
CreateWheelEvent()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
CreateKeyEvent(PP_InputEvent_Type type,uint32_t key_code)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
CreateCharEvent(const std::string & text)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
CreateTouchEvent(PP_InputEvent_Type type,const pp::FloatPoint & point)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
PostMessageBarrier()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.
SimulateInputEvent(const pp::InputEvent & input_event)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
AreEquivalentEvents(PP_Resource received,PP_Resource expected)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
HandleInputEvent(const pp::InputEvent & input_event)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
HandleMessage(const pp::Var & message_data)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
DidChangeView(const pp::View & view)316 void TestInputEvent::DidChangeView(const pp::View& view) {
317 view_rect_ = view.GetRect();
318 }
319
TestEvents()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
TestAcceptTouchEvent_1()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
TestAcceptTouchEvent_2()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
TestAcceptTouchEvent_3()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
TestAcceptTouchEvent_4()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