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