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