1 // Copyright 2017 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 "third_party/blink/renderer/core/input/pointer_event_manager.h"
6
7 #include <limits>
8
9 #include "third_party/blink/renderer/core/dom/document.h"
10 #include "third_party/blink/renderer/core/dom/events/native_event_listener.h"
11 #include "third_party/blink/renderer/core/html/html_element.h"
12 #include "third_party/blink/renderer/core/input/event_handler.h"
13 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
14 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
15 #include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
16
17 namespace blink {
18
19 namespace {
20 class CheckPointerEventListenerCallback final : public NativeEventListener {
21 public:
Invoke(ExecutionContext *,Event * event)22 void Invoke(ExecutionContext*, Event* event) override {
23 const String pointer_type = ((PointerEvent*)event)->pointerType();
24 if (pointer_type == "mouse")
25 mouse_event_received_count_++;
26 else if (pointer_type == "touch")
27 touch_event_received_count_++;
28 else if (pointer_type == "pen")
29 pen_event_received_count_++;
30 }
31
mouseEventCount() const32 int mouseEventCount() const { return mouse_event_received_count_; }
touchEventCount() const33 int touchEventCount() const { return touch_event_received_count_; }
penEventCount() const34 int penEventCount() const { return pen_event_received_count_; }
35
36 private:
37 int mouse_event_received_count_ = 0;
38 int touch_event_received_count_ = 0;
39 int pen_event_received_count_ = 0;
40 };
41
42 class PointerEventCoordinateListenerCallback final
43 : public NativeEventListener {
44 public:
Create()45 static PointerEventCoordinateListenerCallback* Create() {
46 return MakeGarbageCollected<PointerEventCoordinateListenerCallback>();
47 }
48
Invoke(ExecutionContext *,Event * event)49 void Invoke(ExecutionContext*, Event* event) override {
50 const PointerEvent* pointer_event = (PointerEvent*)event;
51 last_client_x_ = pointer_event->clientX();
52 last_client_y_ = pointer_event->clientY();
53 last_page_x_ = pointer_event->pageX();
54 last_page_y_ = pointer_event->pageY();
55 last_screen_x_ = pointer_event->screenX();
56 last_screen_y_ = pointer_event->screenY();
57 last_width_ = pointer_event->width();
58 last_height_ = pointer_event->height();
59 last_movement_x_ = pointer_event->movementX();
60 last_movement_y_ = pointer_event->movementY();
61 }
62
63 double last_client_x_ = 0;
64 double last_client_y_ = 0;
65 double last_page_x_ = 0;
66 double last_page_y_ = 0;
67 double last_screen_x_ = 0;
68 double last_screen_y_ = 0;
69 double last_width_ = 0;
70 double last_height_ = 0;
71 double last_movement_x_ = 0;
72 double last_movement_y_ = 0;
73 };
74
75 } // namespace
76
77 class PointerEventManagerTest : public SimTest {
78 protected:
GetEventHandler()79 EventHandler& GetEventHandler() {
80 return GetDocument().GetFrame()->GetEventHandler();
81 }
CreateTestPointerEvent(WebInputEvent::Type type,WebPointerProperties::PointerType pointer_type,gfx::PointF position_in_widget=gfx::PointF (100,100),gfx::PointF position_in_screen=gfx::PointF (100,100),int movement_x=0,int movement_y=0,float width=1,float height=1)82 WebPointerEvent CreateTestPointerEvent(
83 WebInputEvent::Type type,
84 WebPointerProperties::PointerType pointer_type,
85 gfx::PointF position_in_widget = gfx::PointF(100, 100),
86 gfx::PointF position_in_screen = gfx::PointF(100, 100),
87 int movement_x = 0,
88 int movement_y = 0,
89 float width = 1,
90 float height = 1) {
91 WebPointerEvent event(
92 type,
93 WebPointerProperties(
94 1, pointer_type, WebPointerProperties::Button::kLeft,
95 position_in_widget, position_in_screen, movement_x, movement_y),
96 width, height);
97 return event;
98 }
CreateTestMouseEvent(WebInputEvent::Type type,const gfx::PointF & coordinates)99 WebMouseEvent CreateTestMouseEvent(WebInputEvent::Type type,
100 const gfx::PointF& coordinates) {
101 WebMouseEvent event(type, coordinates, coordinates,
102 WebPointerProperties::Button::kLeft, 0,
103 WebInputEvent::kLeftButtonDown,
104 WebInputEvent::GetStaticTimeStampForTests());
105 event.SetFrameScale(1);
106 return event;
107 }
108 };
109
TEST_F(PointerEventManagerTest,HasPointerCapture)110 TEST_F(PointerEventManagerTest, HasPointerCapture) {
111 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
112 SimRequest request("https://example.com/test.html", "text/html");
113 LoadURL("https://example.com/test.html");
114 request.Complete(
115 "<body style='padding: 0px; width: 400px; height: 400px;'>"
116 "</body>");
117 ASSERT_FALSE(GetDocument().body()->hasPointerCapture(4));
118 ASSERT_FALSE(GetDocument().body()->hasPointerCapture(
119 std::numeric_limits<PointerId>::max()));
120 ASSERT_FALSE(GetDocument().body()->hasPointerCapture(0));
121 ASSERT_FALSE(GetDocument().body()->hasPointerCapture(-1));
122 ASSERT_FALSE(
123 GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
124
125 ExceptionState exception(nullptr, ExceptionState::kExecutionContext, "", "");
126
127 GetEventHandler().HandleMousePressEvent(
128 CreateTestMouseEvent(WebInputEvent::kMouseDown, gfx::PointF(100, 100)));
129
130 ASSERT_FALSE(
131 GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
132
133 GetDocument().body()->setPointerCapture(PointerEventFactory::kMouseId,
134 exception);
135 ASSERT_TRUE(
136 GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
137
138 GetEventHandler().HandleMouseMoveEvent(
139 CreateTestMouseEvent(WebInputEvent::kMouseMove, gfx::PointF(200, 200)),
140 Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
141
142 ASSERT_TRUE(
143 GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
144
145 GetDocument().body()->releasePointerCapture(PointerEventFactory::kMouseId,
146 exception);
147 ASSERT_FALSE(
148 GetDocument().body()->hasPointerCapture(PointerEventFactory::kMouseId));
149 }
150
TEST_F(PointerEventManagerTest,PointerCancelsOfAllTypes)151 TEST_F(PointerEventManagerTest, PointerCancelsOfAllTypes) {
152 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
153 SimRequest request("https://example.com/test.html", "text/html");
154 LoadURL("https://example.com/test.html");
155 request.Complete(
156 "<body style='padding: 0px; width: 400px; height: 400px;'>"
157 "<div draggable='true' style='width: 150px; height: 150px;'></div>"
158 "</body>");
159 auto* callback = MakeGarbageCollected<CheckPointerEventListenerCallback>();
160 GetDocument().body()->addEventListener(event_type_names::kPointercancel,
161 callback);
162
163 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
164 CreateTestPointerEvent(WebInputEvent::kPointerDown,
165 WebPointerProperties::PointerType::kTouch),
166 {}, {}));
167
168 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
169 CreateTestPointerEvent(WebInputEvent::kPointerDown,
170 WebPointerProperties::PointerType::kPen),
171 {}, {}));
172
173 GetEventHandler().HandleMousePressEvent(
174 CreateTestMouseEvent(WebInputEvent::kMouseDown, gfx::PointF(100, 100)));
175
176 ASSERT_EQ(callback->mouseEventCount(), 0);
177 ASSERT_EQ(callback->touchEventCount(), 0);
178 ASSERT_EQ(callback->penEventCount(), 0);
179
180 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
181 CreateTestPointerEvent(WebInputEvent::kPointerCausedUaAction,
182 WebPointerProperties::PointerType::kPen),
183 {}, {}));
184 ASSERT_EQ(callback->mouseEventCount(), 0);
185 ASSERT_EQ(callback->touchEventCount(), 1);
186 ASSERT_EQ(callback->penEventCount(), 1);
187
188 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
189 CreateTestPointerEvent(WebInputEvent::kPointerCausedUaAction,
190 WebPointerProperties::PointerType::kTouch),
191 {}, {}));
192 ASSERT_EQ(callback->mouseEventCount(), 0);
193 ASSERT_EQ(callback->touchEventCount(), 1);
194 ASSERT_EQ(callback->penEventCount(), 1);
195
196 GetEventHandler().HandleMouseMoveEvent(
197 CreateTestMouseEvent(WebInputEvent::kMouseMove, gfx::PointF(200, 200)),
198 Vector<WebMouseEvent>(), Vector<WebMouseEvent>());
199
200 ASSERT_EQ(callback->mouseEventCount(), 1);
201 ASSERT_EQ(callback->touchEventCount(), 1);
202 ASSERT_EQ(callback->penEventCount(), 1);
203 }
204
TEST_F(PointerEventManagerTest,PointerEventCoordinates)205 TEST_F(PointerEventManagerTest, PointerEventCoordinates) {
206 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
207 SimRequest request("https://example.com/test.html", "text/html");
208 LoadURL("https://example.com/test.html");
209 request.Complete(
210 "<body style='padding: 0px; width: 400px; height: 400px;'>"
211 "</body>");
212 WebView().SetPageScaleFactor(2);
213 PointerEventCoordinateListenerCallback* callback =
214 PointerEventCoordinateListenerCallback::Create();
215 GetDocument().body()->addEventListener(event_type_names::kPointerdown,
216 callback);
217
218 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
219 CreateTestPointerEvent(WebInputEvent::kPointerDown,
220 WebPointerProperties::PointerType::kTouch,
221 gfx::PointF(150, 200), gfx::PointF(100, 50), 10,
222 10, 16, 24),
223 {}, {}));
224
225 ASSERT_EQ(callback->last_client_x_, 75);
226 ASSERT_EQ(callback->last_client_y_, 100);
227 ASSERT_EQ(callback->last_page_x_, 75);
228 ASSERT_EQ(callback->last_page_y_, 100);
229 ASSERT_EQ(callback->last_screen_x_, 100);
230 ASSERT_EQ(callback->last_screen_y_, 50);
231 ASSERT_EQ(callback->last_width_, 8);
232 ASSERT_EQ(callback->last_height_, 12);
233 ASSERT_EQ(callback->last_movement_x_, 10);
234 ASSERT_EQ(callback->last_movement_y_, 10);
235 }
236
TEST_F(PointerEventManagerTest,PointerEventMovements)237 TEST_F(PointerEventManagerTest, PointerEventMovements) {
238 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
239 SimRequest request("https://example.com/test.html", "text/html");
240 LoadURL("https://example.com/test.html");
241 request.Complete(
242 "<body style='padding: 0px; width: 400px; height: 400px;'>"
243 "</body>");
244 PointerEventCoordinateListenerCallback* callback =
245 PointerEventCoordinateListenerCallback::Create();
246 GetDocument().body()->addEventListener(event_type_names::kPointermove,
247 callback);
248
249 {
250 // Turn on the flag for test.
251 ScopedConsolidatedMovementXYForTest scoped_feature(true);
252
253 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
254 CreateTestPointerEvent(WebInputEvent::kPointerMove,
255 WebPointerProperties::PointerType::kMouse,
256 gfx::PointF(150, 210), gfx::PointF(100, 50), 10,
257 10),
258 {}, {}));
259 // The first pointermove event has movement_x/y 0.
260 ASSERT_EQ(callback->last_screen_x_, 100);
261 ASSERT_EQ(callback->last_screen_y_, 50);
262 ASSERT_EQ(callback->last_movement_x_, 0);
263 ASSERT_EQ(callback->last_movement_y_, 0);
264
265 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
266 CreateTestPointerEvent(WebInputEvent::kPointerMove,
267 WebPointerProperties::PointerType::kMouse,
268 gfx::PointF(150, 200), gfx::PointF(132, 29), 10,
269 10),
270 {}, {}));
271 // pointermove event movement = event.screenX/Y - last_event.screenX/Y.
272 ASSERT_EQ(callback->last_screen_x_, 132);
273 ASSERT_EQ(callback->last_screen_y_, 29);
274 ASSERT_EQ(callback->last_movement_x_, 32);
275 ASSERT_EQ(callback->last_movement_y_, -21);
276
277 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
278 CreateTestPointerEvent(WebInputEvent::kPointerMove,
279 WebPointerProperties::PointerType::kMouse,
280 gfx::PointF(150, 210), gfx::PointF(113.8, 32.7),
281 10, 10),
282 {}, {}));
283 // fractional screen coordinates result in fractional movement.
284 ASSERT_FLOAT_EQ(callback->last_screen_x_, 113.8);
285 ASSERT_FLOAT_EQ(callback->last_screen_y_, 32.7);
286 // TODO(eirage): These should be float value once mouse_event.idl change.
287 ASSERT_FLOAT_EQ(callback->last_movement_x_, -19);
288 ASSERT_FLOAT_EQ(callback->last_movement_y_, 3);
289 }
290
291 {
292 // When flag is off, movementX/Y follows the value in WebPointerProperties.
293 ScopedConsolidatedMovementXYForTest scoped_feature(false);
294
295 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
296 CreateTestPointerEvent(WebInputEvent::kPointerMove,
297 WebPointerProperties::PointerType::kMouse,
298 gfx::PointF(150, 210), gfx::PointF(100, 16.25),
299 1024, -8765),
300 {}, {}));
301 ASSERT_EQ(callback->last_screen_x_, 100);
302 ASSERT_EQ(callback->last_screen_y_, 16.25);
303 ASSERT_EQ(callback->last_movement_x_, 1024);
304 ASSERT_EQ(callback->last_movement_y_, -8765);
305 }
306 }
307
308 // Test that we are not losing fractions when truncating movements.
TEST_F(PointerEventManagerTest,PointerEventSmallFractionMovements)309 TEST_F(PointerEventManagerTest, PointerEventSmallFractionMovements) {
310 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
311 SimRequest request("https://example.com/test.html", "text/html");
312 LoadURL("https://example.com/test.html");
313 request.Complete(
314 "<body style='padding: 0px; width: 400px; height: 400px;'>"
315 "</body>");
316 PointerEventCoordinateListenerCallback* callback =
317 PointerEventCoordinateListenerCallback::Create();
318 GetDocument().body()->addEventListener(event_type_names::kPointermove,
319 callback);
320
321 // Turn on the flag for test.
322 ScopedConsolidatedMovementXYForTest scoped_feature(true);
323
324 WebPointerEvent pointer_event = CreateTestPointerEvent(
325 WebInputEvent::kPointerMove, WebPointerProperties::PointerType::kMouse,
326 gfx::PointF(150, 210), gfx::PointF(113.8, 32.7), 0, 0);
327 WebView().MainFrameWidget()->HandleInputEvent(
328 WebCoalescedInputEvent(pointer_event));
329 ASSERT_FLOAT_EQ(callback->last_movement_x_, 0);
330 ASSERT_FLOAT_EQ(callback->last_movement_y_, 0);
331
332 pointer_event.SetPositionInScreen(113.4, 32.9);
333 WebView().MainFrameWidget()->HandleInputEvent(
334 WebCoalescedInputEvent(pointer_event));
335 ASSERT_FLOAT_EQ(callback->last_movement_x_, 0);
336 ASSERT_FLOAT_EQ(callback->last_movement_y_, 0);
337
338 pointer_event.SetPositionInScreen(113.0, 33.1);
339 WebView().MainFrameWidget()->HandleInputEvent(
340 WebCoalescedInputEvent(pointer_event));
341 ASSERT_FLOAT_EQ(callback->last_movement_x_, 0);
342 ASSERT_FLOAT_EQ(callback->last_movement_y_, 1);
343
344 pointer_event.SetPositionInScreen(112.6, 33.3);
345 WebView().MainFrameWidget()->HandleInputEvent(
346 WebCoalescedInputEvent(pointer_event));
347 ASSERT_FLOAT_EQ(callback->last_movement_x_, -1);
348 ASSERT_FLOAT_EQ(callback->last_movement_y_, 0);
349 }
350
TEST_F(PointerEventManagerTest,PointerRawUpdateMovements)351 TEST_F(PointerEventManagerTest, PointerRawUpdateMovements) {
352 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
353 SimRequest request("https://example.com/test.html", "text/html");
354 LoadURL("https://example.com/test.html");
355 request.Complete(
356 "<body style='padding: 0px; width: 400px; height: 400px;'>"
357 "</body>");
358 PointerEventCoordinateListenerCallback* callback =
359 PointerEventCoordinateListenerCallback::Create();
360 GetDocument().body()->addEventListener(event_type_names::kPointermove,
361 callback);
362 GetDocument().body()->addEventListener(event_type_names::kPointerrawupdate,
363 callback);
364
365 // Turn on the flag for test.
366 ScopedConsolidatedMovementXYForTest scoped_feature(true);
367
368 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
369 CreateTestPointerEvent(WebInputEvent::kPointerRawUpdate,
370 WebPointerProperties::PointerType::kMouse,
371 gfx::PointF(150, 210), gfx::PointF(100, 50), 10,
372 10),
373 {}, {}));
374 // The first pointerrawupdate event has movement_x/y 0.
375 ASSERT_EQ(callback->last_screen_x_, 100);
376 ASSERT_EQ(callback->last_screen_y_, 50);
377 ASSERT_EQ(callback->last_movement_x_, 0);
378 ASSERT_EQ(callback->last_movement_y_, 0);
379
380 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
381 CreateTestPointerEvent(WebInputEvent::kPointerRawUpdate,
382 WebPointerProperties::PointerType::kMouse,
383 gfx::PointF(150, 200), gfx::PointF(132, 29), 10,
384 10),
385 {}, {}));
386 // pointerrawupdate event movement = event.screenX/Y - last_event.screenX/Y.
387 ASSERT_EQ(callback->last_screen_x_, 132);
388 ASSERT_EQ(callback->last_screen_y_, 29);
389 ASSERT_EQ(callback->last_movement_x_, 32);
390 ASSERT_EQ(callback->last_movement_y_, -21);
391
392 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
393 CreateTestPointerEvent(WebInputEvent::kPointerMove,
394 WebPointerProperties::PointerType::kMouse,
395 gfx::PointF(150, 200), gfx::PointF(144, 30), 10,
396 10),
397 {}, {}));
398 // First pointermove, have 0 movements.
399 ASSERT_EQ(callback->last_screen_x_, 144);
400 ASSERT_EQ(callback->last_screen_y_, 30);
401 ASSERT_EQ(callback->last_movement_x_, 0);
402 ASSERT_EQ(callback->last_movement_y_, 0);
403
404 WebView().MainFrameWidget()->HandleInputEvent(WebCoalescedInputEvent(
405 CreateTestPointerEvent(WebInputEvent::kPointerRawUpdate,
406 WebPointerProperties::PointerType::kMouse,
407 gfx::PointF(150, 200), gfx::PointF(142, 32), 10,
408 10),
409 {}, {}));
410 // pointerrawupdate event's movement is independent from pointermoves.
411 ASSERT_EQ(callback->last_screen_x_, 142);
412 ASSERT_EQ(callback->last_screen_y_, 32);
413 ASSERT_EQ(callback->last_movement_x_, 10);
414 ASSERT_EQ(callback->last_movement_y_, 3);
415 }
416
TEST_F(PointerEventManagerTest,PointerUnadjustedMovement)417 TEST_F(PointerEventManagerTest, PointerUnadjustedMovement) {
418 WebView().MainFrameWidget()->Resize(WebSize(400, 400));
419 SimRequest request("https://example.com/test.html", "text/html");
420 LoadURL("https://example.com/test.html");
421 request.Complete(
422 "<body style='padding: 0px; width: 400px; height: 400px;'>"
423 "</body>");
424 PointerEventCoordinateListenerCallback* callback =
425 PointerEventCoordinateListenerCallback::Create();
426 GetDocument().body()->addEventListener(event_type_names::kPointermove,
427 callback);
428
429 WebPointerEvent event = CreateTestPointerEvent(
430 WebInputEvent::kPointerMove, WebPointerProperties::PointerType::kMouse,
431 gfx::PointF(150, 210), gfx::PointF(100, 50), 120, -321);
432 event.is_raw_movement_event = true;
433 WebView().MainFrameWidget()->HandleInputEvent(
434 WebCoalescedInputEvent(event, {}, {}));
435
436 // If is_raw_movement_event is true, PE use the raw movement value from
437 // movement_x/y.
438 ASSERT_EQ(callback->last_screen_x_, 100);
439 ASSERT_EQ(callback->last_screen_y_, 50);
440 ASSERT_EQ(callback->last_movement_x_, 120);
441 ASSERT_EQ(callback->last_movement_y_, -321);
442 }
443
444 } // namespace blink
445