1// Copyright 2013 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 "content/common/input/web_input_event_traits.h"
6
7#include <bitset>
8#include <limits>
9
10#include "base/logging.h"
11
12using blink::WebGestureEvent;
13using blink::WebInputEvent;
14using blink::WebKeyboardEvent;
15using blink::WebMouseEvent;
16using blink::WebMouseWheelEvent;
17using blink::WebTouchEvent;
18using std::numeric_limits;
19
20namespace content {
21namespace {
22
23const int kInvalidTouchIndex = -1;
24
25bool CanCoalesce(const WebKeyboardEvent& event_to_coalesce,
26                 const WebKeyboardEvent& event) {
27  return false;
28}
29
30void Coalesce(const WebKeyboardEvent& event_to_coalesce,
31              WebKeyboardEvent* event) {
32  DCHECK(CanCoalesce(event_to_coalesce, *event));
33}
34
35bool CanCoalesce(const WebMouseEvent& event_to_coalesce,
36                 const WebMouseEvent& event) {
37  return event.type == event_to_coalesce.type &&
38         event.type == WebInputEvent::MouseMove;
39}
40
41void Coalesce(const WebMouseEvent& event_to_coalesce, WebMouseEvent* event) {
42  DCHECK(CanCoalesce(event_to_coalesce, *event));
43  // Accumulate movement deltas.
44  int x = event->movementX;
45  int y = event->movementY;
46  *event = event_to_coalesce;
47  event->movementX += x;
48  event->movementY += y;
49}
50
51bool CanCoalesce(const WebMouseWheelEvent& event_to_coalesce,
52                 const WebMouseWheelEvent& event) {
53  return event.modifiers == event_to_coalesce.modifiers &&
54         event.scrollByPage == event_to_coalesce.scrollByPage &&
55         event.phase == event_to_coalesce.phase &&
56         event.momentumPhase == event_to_coalesce.momentumPhase &&
57         event.hasPreciseScrollingDeltas ==
58             event_to_coalesce.hasPreciseScrollingDeltas;
59}
60
61float GetUnacceleratedDelta(float accelerated_delta, float acceleration_ratio) {
62  return accelerated_delta * acceleration_ratio;
63}
64
65float GetAccelerationRatio(float accelerated_delta, float unaccelerated_delta) {
66  if (unaccelerated_delta == 0.f || accelerated_delta == 0.f)
67    return 1.f;
68  return unaccelerated_delta / accelerated_delta;
69}
70
71void Coalesce(const WebMouseWheelEvent& event_to_coalesce,
72              WebMouseWheelEvent* event) {
73  DCHECK(CanCoalesce(event_to_coalesce, *event));
74  float unaccelerated_x =
75      GetUnacceleratedDelta(event->deltaX,
76                            event->accelerationRatioX) +
77      GetUnacceleratedDelta(event_to_coalesce.deltaX,
78                            event_to_coalesce.accelerationRatioX);
79  float unaccelerated_y =
80      GetUnacceleratedDelta(event->deltaY,
81                            event->accelerationRatioY) +
82      GetUnacceleratedDelta(event_to_coalesce.deltaY,
83                            event_to_coalesce.accelerationRatioY);
84  event->deltaX += event_to_coalesce.deltaX;
85  event->deltaY += event_to_coalesce.deltaY;
86  event->wheelTicksX += event_to_coalesce.wheelTicksX;
87  event->wheelTicksY += event_to_coalesce.wheelTicksY;
88  event->accelerationRatioX =
89      GetAccelerationRatio(event->deltaX, unaccelerated_x);
90  event->accelerationRatioY =
91      GetAccelerationRatio(event->deltaY, unaccelerated_y);
92  DCHECK_GE(event_to_coalesce.timeStampSeconds, event->timeStampSeconds);
93  event->timeStampSeconds = event_to_coalesce.timeStampSeconds;
94}
95
96// Returns |kInvalidTouchIndex| iff |event| lacks a touch with an ID of |id|.
97int GetIndexOfTouchID(const WebTouchEvent& event, int id) {
98  for (unsigned i = 0; i < event.touchesLength; ++i) {
99    if (event.touches[i].id == id)
100      return i;
101  }
102  return kInvalidTouchIndex;
103}
104
105bool CanCoalesce(const WebTouchEvent& event_to_coalesce,
106                 const WebTouchEvent& event) {
107  if (event.type != event_to_coalesce.type ||
108      event.type != WebInputEvent::TouchMove ||
109      event.modifiers != event_to_coalesce.modifiers ||
110      event.touchesLength != event_to_coalesce.touchesLength ||
111      event.touchesLength > WebTouchEvent::touchesLengthCap)
112    return false;
113
114  COMPILE_ASSERT(WebTouchEvent::touchesLengthCap <= sizeof(int32_t) * 8U,
115                 suboptimal_touches_length_cap_size);
116  // Ensure that we have a 1-to-1 mapping of pointer ids between touches.
117  std::bitset<WebTouchEvent::touchesLengthCap> unmatched_event_touches(
118      (1 << event.touchesLength) - 1);
119  for (unsigned i = 0; i < event_to_coalesce.touchesLength; ++i) {
120    int event_touch_index =
121        GetIndexOfTouchID(event, event_to_coalesce.touches[i].id);
122    if (event_touch_index == kInvalidTouchIndex)
123      return false;
124    if (!unmatched_event_touches[event_touch_index])
125      return false;
126    unmatched_event_touches[event_touch_index] = false;
127  }
128  return unmatched_event_touches.none();
129}
130
131void Coalesce(const WebTouchEvent& event_to_coalesce, WebTouchEvent* event) {
132  DCHECK(CanCoalesce(event_to_coalesce, *event));
133  // The WebTouchPoints include absolute position information. So it is
134  // sufficient to simply replace the previous event with the new event->
135  // However, it is necessary to make sure that all the points have the
136  // correct state, i.e. the touch-points that moved in the last event, but
137  // didn't change in the current event, will have Stationary state. It is
138  // necessary to change them back to Moved state.
139  WebTouchEvent old_event = *event;
140  *event = event_to_coalesce;
141  for (unsigned i = 0; i < event->touchesLength; ++i) {
142    int i_old = GetIndexOfTouchID(old_event, event->touches[i].id);
143    if (old_event.touches[i_old].state == blink::WebTouchPoint::StateMoved)
144      event->touches[i].state = blink::WebTouchPoint::StateMoved;
145  }
146}
147
148bool CanCoalesce(const WebGestureEvent& event_to_coalesce,
149                 const WebGestureEvent& event) {
150  if (event.type != event_to_coalesce.type ||
151      event.sourceDevice != event_to_coalesce.sourceDevice ||
152      event.modifiers != event_to_coalesce.modifiers)
153    return false;
154
155  if (event.type == WebInputEvent::GestureScrollUpdate)
156    return true;
157
158  // GesturePinchUpdate scales can be combined only if they share a focal point,
159  // e.g., with double-tap drag zoom.
160  if (event.type == WebInputEvent::GesturePinchUpdate &&
161      event.x == event_to_coalesce.x &&
162      event.y == event_to_coalesce.y)
163    return true;
164
165  return false;
166}
167
168void Coalesce(const WebGestureEvent& event_to_coalesce,
169              WebGestureEvent* event) {
170  DCHECK(CanCoalesce(event_to_coalesce, *event));
171  if (event->type == WebInputEvent::GestureScrollUpdate) {
172    event->data.scrollUpdate.deltaX +=
173        event_to_coalesce.data.scrollUpdate.deltaX;
174    event->data.scrollUpdate.deltaY +=
175        event_to_coalesce.data.scrollUpdate.deltaY;
176  } else if (event->type == WebInputEvent::GesturePinchUpdate) {
177    event->data.pinchUpdate.scale *= event_to_coalesce.data.pinchUpdate.scale;
178    // Ensure the scale remains bounded above 0 and below Infinity so that
179    // we can reliably perform operations like log on the values.
180    if (event->data.pinchUpdate.scale < numeric_limits<float>::min())
181      event->data.pinchUpdate.scale = numeric_limits<float>::min();
182    else if (event->data.pinchUpdate.scale > numeric_limits<float>::max())
183      event->data.pinchUpdate.scale = numeric_limits<float>::max();
184  }
185}
186
187struct WebInputEventSize {
188  template <class EventType>
189  bool Execute(WebInputEvent::Type /* type */, size_t* type_size) const {
190    *type_size = sizeof(EventType);
191    return true;
192  }
193};
194
195struct WebInputEventClone {
196  template <class EventType>
197  bool Execute(const WebInputEvent& event,
198               ScopedWebInputEvent* scoped_event) const {
199    DCHECK_EQ(sizeof(EventType), event.size);
200    *scoped_event = ScopedWebInputEvent(
201        new EventType(static_cast<const EventType&>(event)));
202    return true;
203  }
204};
205
206struct WebInputEventDelete {
207  template <class EventType>
208  bool Execute(WebInputEvent* event, bool* /* dummy_var */) const {
209    if (!event)
210      return false;
211    DCHECK_EQ(sizeof(EventType), event->size);
212    delete static_cast<EventType*>(event);
213    return true;
214  }
215};
216
217struct WebInputEventCanCoalesce {
218  template <class EventType>
219  bool Execute(const WebInputEvent& event_to_coalesce,
220               const WebInputEvent* event) const {
221    if (event_to_coalesce.type != event->type)
222      return false;
223    DCHECK_EQ(sizeof(EventType), event->size);
224    DCHECK_EQ(sizeof(EventType), event_to_coalesce.size);
225    return CanCoalesce(static_cast<const EventType&>(event_to_coalesce),
226                       *static_cast<const EventType*>(event));
227  }
228};
229
230struct WebInputEventCoalesce {
231  template <class EventType>
232  bool Execute(const WebInputEvent& event_to_coalesce,
233               WebInputEvent* event) const {
234    Coalesce(static_cast<const EventType&>(event_to_coalesce),
235             static_cast<EventType*>(event));
236    return true;
237  }
238};
239
240template <typename Operator, typename ArgIn, typename ArgOut>
241bool Apply(Operator op,
242           WebInputEvent::Type type,
243           const ArgIn& arg_in,
244           ArgOut* arg_out) {
245  if (WebInputEvent::isMouseEventType(type))
246    return op.template Execute<WebMouseEvent>(arg_in, arg_out);
247  else if (type == WebInputEvent::MouseWheel)
248    return op.template Execute<WebMouseWheelEvent>(arg_in, arg_out);
249  else if (WebInputEvent::isKeyboardEventType(type))
250    return op.template Execute<WebKeyboardEvent>(arg_in, arg_out);
251  else if (WebInputEvent::isTouchEventType(type))
252    return op.template Execute<WebTouchEvent>(arg_in, arg_out);
253  else if (WebInputEvent::isGestureEventType(type))
254    return op.template Execute<WebGestureEvent>(arg_in, arg_out);
255
256  NOTREACHED() << "Unknown webkit event type " << type;
257  return false;
258}
259
260}  // namespace
261
262const char* WebInputEventTraits::GetName(WebInputEvent::Type type) {
263#define CASE_TYPE(t) case WebInputEvent::t:  return #t
264  switch(type) {
265    CASE_TYPE(Undefined);
266    CASE_TYPE(MouseDown);
267    CASE_TYPE(MouseUp);
268    CASE_TYPE(MouseMove);
269    CASE_TYPE(MouseEnter);
270    CASE_TYPE(MouseLeave);
271    CASE_TYPE(ContextMenu);
272    CASE_TYPE(MouseWheel);
273    CASE_TYPE(RawKeyDown);
274    CASE_TYPE(KeyDown);
275    CASE_TYPE(KeyUp);
276    CASE_TYPE(Char);
277    CASE_TYPE(GestureScrollBegin);
278    CASE_TYPE(GestureScrollEnd);
279    CASE_TYPE(GestureScrollUpdate);
280    CASE_TYPE(GestureFlingStart);
281    CASE_TYPE(GestureFlingCancel);
282    CASE_TYPE(GestureShowPress);
283    CASE_TYPE(GestureTap);
284    CASE_TYPE(GestureTapUnconfirmed);
285    CASE_TYPE(GestureTapDown);
286    CASE_TYPE(GestureTapCancel);
287    CASE_TYPE(GestureDoubleTap);
288    CASE_TYPE(GestureTwoFingerTap);
289    CASE_TYPE(GestureLongPress);
290    CASE_TYPE(GestureLongTap);
291    CASE_TYPE(GesturePinchBegin);
292    CASE_TYPE(GesturePinchEnd);
293    CASE_TYPE(GesturePinchUpdate);
294    CASE_TYPE(TouchStart);
295    CASE_TYPE(TouchMove);
296    CASE_TYPE(TouchEnd);
297    CASE_TYPE(TouchCancel);
298    default:
299      // Must include default to let blink::WebInputEvent add new event types
300      // before they're added here.
301      DLOG(WARNING) <<
302          "Unhandled WebInputEvent type in WebInputEventTraits::GetName.\n";
303      break;
304  }
305#undef CASE_TYPE
306  return "";
307}
308
309size_t WebInputEventTraits::GetSize(WebInputEvent::Type type) {
310  size_t size = 0;
311  Apply(WebInputEventSize(), type, type, &size);
312  return size;
313}
314
315ScopedWebInputEvent WebInputEventTraits::Clone(const WebInputEvent& event) {
316  ScopedWebInputEvent scoped_event;
317  Apply(WebInputEventClone(), event.type, event, &scoped_event);
318  return scoped_event.Pass();
319}
320
321void WebInputEventTraits::Delete(WebInputEvent* event) {
322  if (!event)
323    return;
324  bool dummy_var = false;
325  Apply(WebInputEventDelete(), event->type, event, &dummy_var);
326}
327
328bool WebInputEventTraits::CanCoalesce(const WebInputEvent& event_to_coalesce,
329                                      const WebInputEvent& event) {
330  // Early out before casting.
331  if (event_to_coalesce.type != event.type)
332    return false;
333  return Apply(WebInputEventCanCoalesce(),
334               event.type,
335               event_to_coalesce,
336               &event);
337}
338
339void WebInputEventTraits::Coalesce(const WebInputEvent& event_to_coalesce,
340                                   WebInputEvent* event) {
341  DCHECK(event);
342  Apply(WebInputEventCoalesce(), event->type, event_to_coalesce, event);
343}
344
345bool WebInputEventTraits::IgnoresAckDisposition(const WebInputEvent& event) {
346  switch (event.type) {
347    case WebInputEvent::MouseDown:
348    case WebInputEvent::MouseUp:
349    case WebInputEvent::MouseEnter:
350    case WebInputEvent::MouseLeave:
351    case WebInputEvent::ContextMenu:
352    case WebInputEvent::GestureScrollBegin:
353    case WebInputEvent::GestureScrollEnd:
354    case WebInputEvent::GestureShowPress:
355    case WebInputEvent::GestureTapUnconfirmed:
356    case WebInputEvent::GestureTapDown:
357    case WebInputEvent::GestureTapCancel:
358    case WebInputEvent::GesturePinchBegin:
359    case WebInputEvent::GesturePinchEnd:
360    case WebInputEvent::TouchCancel:
361      return true;
362    case WebInputEvent::TouchStart:
363    case WebInputEvent::TouchMove:
364    case WebInputEvent::TouchEnd:
365      return !static_cast<const WebTouchEvent&>(event).cancelable;
366    default:
367      return false;
368  }
369}
370
371}  // namespace content
372