1// Copyright 2014 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/browser/renderer_host/input/touch_emulator.h"
6
7#include "content/browser/renderer_host/input/motion_event_web.h"
8#include "content/browser/renderer_host/input/web_input_event_util.h"
9#include "content/common/input/web_touch_event_traits.h"
10#include "content/public/common/content_client.h"
11#include "content/public/common/content_switches.h"
12#include "grit/content_resources.h"
13#include "third_party/WebKit/public/platform/WebCursorInfo.h"
14#include "ui/events/gesture_detection/gesture_config_helper.h"
15#include "ui/gfx/image/image.h"
16#include "ui/gfx/screen.h"
17
18using blink::WebGestureEvent;
19using blink::WebInputEvent;
20using blink::WebKeyboardEvent;
21using blink::WebMouseEvent;
22using blink::WebMouseWheelEvent;
23using blink::WebTouchEvent;
24using blink::WebTouchPoint;
25
26namespace content {
27
28namespace {
29
30ui::GestureProvider::Config GetGestureProviderConfig() {
31  // TODO(dgozman): Use different configs to emulate mobile/desktop as
32  // requested by renderer.
33  ui::GestureProvider::Config config = ui::DefaultGestureProviderConfig();
34  config.gesture_begin_end_types_enabled = false;
35  config.gesture_detector_config.swipe_enabled = false;
36  config.gesture_detector_config.two_finger_tap_enabled = false;
37  return config;
38}
39
40// Time between two consecutive mouse moves, during which second mouse move
41// is not converted to touch.
42const double kMouseMoveDropIntervalSeconds = 5.f / 1000;
43
44} // namespace
45
46TouchEmulator::TouchEmulator(TouchEmulatorClient* client)
47    : client_(client),
48      gesture_provider_(GetGestureProviderConfig(), this),
49      enabled_(false),
50      allow_pinch_(false) {
51  DCHECK(client_);
52  ResetState();
53
54  bool use_2x = gfx::Screen::GetNativeScreen()->
55      GetPrimaryDisplay().device_scale_factor() > 1.5f;
56  float cursor_scale_factor = use_2x ? 2.f : 1.f;
57  InitCursorFromResource(&touch_cursor_,
58      cursor_scale_factor,
59      use_2x ? IDR_DEVTOOLS_TOUCH_CURSOR_ICON_2X :
60          IDR_DEVTOOLS_TOUCH_CURSOR_ICON);
61  InitCursorFromResource(&pinch_cursor_,
62      cursor_scale_factor,
63      use_2x ? IDR_DEVTOOLS_PINCH_CURSOR_ICON_2X :
64          IDR_DEVTOOLS_PINCH_CURSOR_ICON);
65
66  WebCursor::CursorInfo cursor_info;
67  cursor_info.type = blink::WebCursorInfo::TypePointer;
68  pointer_cursor_.InitFromCursorInfo(cursor_info);
69
70  // TODO(dgozman): Use synthetic secondary touch to support multi-touch.
71  gesture_provider_.SetMultiTouchZoomSupportEnabled(false);
72  // TODO(dgozman): Enable double tap if requested by the renderer.
73  // TODO(dgozman): Don't break double-tap-based pinch with shift handling.
74  gesture_provider_.SetDoubleTapSupportForPlatformEnabled(false);
75}
76
77TouchEmulator::~TouchEmulator() {
78  // We cannot cleanup properly in destructor, as we need roundtrip to the
79  // renderer for ack. Instead, the owner should call Disable, and only
80  // destroy this object when renderer is dead.
81}
82
83void TouchEmulator::ResetState() {
84  last_mouse_event_was_move_ = false;
85  last_mouse_move_timestamp_ = 0;
86  mouse_pressed_ = false;
87  shift_pressed_ = false;
88  touch_active_ = false;
89  suppress_next_fling_cancel_ = false;
90  pinch_scale_ = 1.f;
91  pinch_gesture_active_ = false;
92}
93
94void TouchEmulator::Enable(bool allow_pinch) {
95  if (!enabled_) {
96    enabled_ = true;
97    ResetState();
98  }
99  allow_pinch_ = allow_pinch;
100  UpdateCursor();
101}
102
103void TouchEmulator::Disable() {
104  if (!enabled_)
105    return;
106
107  enabled_ = false;
108  UpdateCursor();
109  CancelTouch();
110}
111
112void TouchEmulator::InitCursorFromResource(
113    WebCursor* cursor, float scale, int resource_id) {
114  gfx::Image& cursor_image =
115      content::GetContentClient()->GetNativeImageNamed(resource_id);
116  WebCursor::CursorInfo cursor_info;
117  cursor_info.type = blink::WebCursorInfo::TypeCustom;
118  cursor_info.image_scale_factor = scale;
119  cursor_info.custom_image = cursor_image.AsBitmap();
120  cursor_info.hotspot =
121      gfx::Point(cursor_image.Width() / 2, cursor_image.Height() / 2);
122#if defined(OS_WIN)
123  cursor_info.external_handle = 0;
124#endif
125
126  cursor->InitFromCursorInfo(cursor_info);
127}
128
129bool TouchEmulator::HandleMouseEvent(const WebMouseEvent& mouse_event) {
130  if (!enabled_)
131    return false;
132
133  if (mouse_event.button == WebMouseEvent::ButtonRight &&
134      mouse_event.type == WebInputEvent::MouseDown) {
135    client_->ShowContextMenuAtPoint(gfx::Point(mouse_event.x, mouse_event.y));
136  }
137
138  if (mouse_event.button != WebMouseEvent::ButtonLeft)
139    return true;
140
141  if (mouse_event.type == WebInputEvent::MouseMove) {
142    if (last_mouse_event_was_move_ &&
143        mouse_event.timeStampSeconds < last_mouse_move_timestamp_ +
144            kMouseMoveDropIntervalSeconds)
145      return true;
146
147    last_mouse_event_was_move_ = true;
148    last_mouse_move_timestamp_ = mouse_event.timeStampSeconds;
149  } else {
150    last_mouse_event_was_move_ = false;
151  }
152
153  if (mouse_event.type == WebInputEvent::MouseDown)
154    mouse_pressed_ = true;
155  else if (mouse_event.type == WebInputEvent::MouseUp)
156    mouse_pressed_ = false;
157
158  UpdateShiftPressed((mouse_event.modifiers & WebInputEvent::ShiftKey) != 0);
159
160  if (FillTouchEventAndPoint(mouse_event) &&
161      gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_))) {
162    client_->ForwardTouchEvent(touch_event_);
163  }
164
165  // Do not pass mouse events to the renderer.
166  return true;
167}
168
169bool TouchEmulator::HandleMouseWheelEvent(const WebMouseWheelEvent& event) {
170  if (!enabled_)
171    return false;
172
173  // Send mouse wheel for easy scrolling when there is no active touch.
174  return touch_active_;
175}
176
177bool TouchEmulator::HandleKeyboardEvent(const WebKeyboardEvent& event) {
178  if (!enabled_)
179    return false;
180
181  if (!UpdateShiftPressed((event.modifiers & WebInputEvent::ShiftKey) != 0))
182    return false;
183
184  if (!mouse_pressed_)
185    return false;
186
187  // Note: The necessary pinch events will be lazily inserted by
188  // |OnGestureEvent| depending on the state of |shift_pressed_|, using the
189  // scroll stream as the event driver.
190  if (shift_pressed_) {
191    // TODO(dgozman): Add secondary touch point and set anchor.
192  } else {
193    // TODO(dgozman): Remove secondary touch point and anchor.
194  }
195
196  // Never block keyboard events.
197  return false;
198}
199
200bool TouchEmulator::HandleTouchEventAck(InputEventAckState ack_result) {
201  const bool event_consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
202  gesture_provider_.OnTouchEventAck(event_consumed);
203  // TODO(dgozman): Disable emulation when real touch events are available.
204  return true;
205}
206
207void TouchEmulator::OnGestureEvent(const ui::GestureEventData& gesture) {
208  WebGestureEvent gesture_event =
209      CreateWebGestureEventFromGestureEventData(gesture);
210
211  switch (gesture_event.type) {
212    case WebInputEvent::Undefined:
213      NOTREACHED() << "Undefined WebInputEvent type";
214      // Bail without sending the junk event to the client.
215      return;
216
217    case WebInputEvent::GestureScrollBegin:
218      client_->ForwardGestureEvent(gesture_event);
219      // PinchBegin must always follow ScrollBegin.
220      if (InPinchGestureMode())
221        PinchBegin(gesture_event);
222      break;
223
224    case WebInputEvent::GestureScrollUpdate:
225      if (InPinchGestureMode()) {
226        // Convert scrolls to pinches while shift is pressed.
227        if (!pinch_gesture_active_)
228          PinchBegin(gesture_event);
229        else
230          PinchUpdate(gesture_event);
231      } else {
232        // Pass scroll update further. If shift was released, end the pinch.
233        if (pinch_gesture_active_)
234          PinchEnd(gesture_event);
235        client_->ForwardGestureEvent(gesture_event);
236      }
237      break;
238
239    case WebInputEvent::GestureScrollEnd:
240      // PinchEnd must precede ScrollEnd.
241      if (pinch_gesture_active_)
242        PinchEnd(gesture_event);
243      client_->ForwardGestureEvent(gesture_event);
244      break;
245
246    case WebInputEvent::GestureFlingStart:
247      // PinchEnd must precede FlingStart.
248      if (pinch_gesture_active_)
249        PinchEnd(gesture_event);
250      if (InPinchGestureMode()) {
251        // No fling in pinch mode. Forward scroll end instead of fling start.
252        suppress_next_fling_cancel_ = true;
253        ScrollEnd(gesture_event);
254      } else {
255        suppress_next_fling_cancel_ = false;
256        client_->ForwardGestureEvent(gesture_event);
257      }
258      break;
259
260    case WebInputEvent::GestureFlingCancel:
261      // If fling start was suppressed, we should not send fling cancel either.
262      if (!suppress_next_fling_cancel_)
263        client_->ForwardGestureEvent(gesture_event);
264      suppress_next_fling_cancel_ = false;
265      break;
266
267    default:
268      // Everything else goes through.
269      client_->ForwardGestureEvent(gesture_event);
270  }
271}
272
273void TouchEmulator::CancelTouch() {
274  if (!touch_active_)
275    return;
276
277  WebTouchEventTraits::ResetTypeAndTouchStates(
278      WebInputEvent::TouchCancel,
279      (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(),
280      &touch_event_);
281  touch_active_ = false;
282  if (gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_)))
283    client_->ForwardTouchEvent(touch_event_);
284}
285
286void TouchEmulator::UpdateCursor() {
287  if (!enabled_)
288    client_->SetCursor(pointer_cursor_);
289  else
290    client_->SetCursor(InPinchGestureMode() ? pinch_cursor_ : touch_cursor_);
291}
292
293bool TouchEmulator::UpdateShiftPressed(bool shift_pressed) {
294  if (shift_pressed_ == shift_pressed)
295    return false;
296  shift_pressed_ = shift_pressed;
297  UpdateCursor();
298  return true;
299}
300
301void TouchEmulator::PinchBegin(const WebGestureEvent& event) {
302  DCHECK(InPinchGestureMode());
303  DCHECK(!pinch_gesture_active_);
304  pinch_gesture_active_ = true;
305  pinch_anchor_ = gfx::Point(event.x, event.y);
306  pinch_scale_ = 1.f;
307  FillPinchEvent(event);
308  pinch_event_.type = WebInputEvent::GesturePinchBegin;
309  client_->ForwardGestureEvent(pinch_event_);
310}
311
312void TouchEmulator::PinchUpdate(const WebGestureEvent& event) {
313  DCHECK(pinch_gesture_active_);
314  int dy = pinch_anchor_.y() - event.y;
315  float scale = exp(dy * 0.002f);
316  FillPinchEvent(event);
317  pinch_event_.type = WebInputEvent::GesturePinchUpdate;
318  pinch_event_.data.pinchUpdate.scale = scale / pinch_scale_;
319  client_->ForwardGestureEvent(pinch_event_);
320  pinch_scale_ = scale;
321}
322
323void TouchEmulator::PinchEnd(const WebGestureEvent& event) {
324  DCHECK(pinch_gesture_active_);
325  pinch_gesture_active_ = false;
326  FillPinchEvent(event);
327  pinch_event_.type = WebInputEvent::GesturePinchEnd;
328  client_->ForwardGestureEvent(pinch_event_);
329}
330
331void TouchEmulator::FillPinchEvent(const WebInputEvent& event) {
332  pinch_event_.timeStampSeconds = event.timeStampSeconds;
333  pinch_event_.modifiers = event.modifiers;
334  pinch_event_.sourceDevice = blink::WebGestureDeviceTouchscreen;
335  pinch_event_.x = pinch_anchor_.x();
336  pinch_event_.y = pinch_anchor_.y();
337}
338
339void TouchEmulator::ScrollEnd(const WebGestureEvent& event) {
340  WebGestureEvent scroll_event;
341  scroll_event.timeStampSeconds = event.timeStampSeconds;
342  scroll_event.modifiers = event.modifiers;
343  scroll_event.sourceDevice = blink::WebGestureDeviceTouchscreen;
344  scroll_event.type = WebInputEvent::GestureScrollEnd;
345  client_->ForwardGestureEvent(scroll_event);
346}
347
348bool TouchEmulator::FillTouchEventAndPoint(const WebMouseEvent& mouse_event) {
349  if (mouse_event.type != WebInputEvent::MouseDown &&
350      mouse_event.type != WebInputEvent::MouseMove &&
351      mouse_event.type != WebInputEvent::MouseUp) {
352    return false;
353  }
354
355  WebInputEvent::Type eventType;
356  switch (mouse_event.type) {
357    case WebInputEvent::MouseDown:
358      eventType = WebInputEvent::TouchStart;
359      touch_active_ = true;
360      break;
361    case WebInputEvent::MouseMove:
362      eventType = WebInputEvent::TouchMove;
363      break;
364    case WebInputEvent::MouseUp:
365      eventType = WebInputEvent::TouchEnd;
366      touch_active_ = false;
367      break;
368    default:
369      eventType = WebInputEvent::Undefined;
370      NOTREACHED();
371  }
372  touch_event_.touchesLength = 1;
373  touch_event_.modifiers = mouse_event.modifiers;
374  WebTouchEventTraits::ResetTypeAndTouchStates(
375      eventType, mouse_event.timeStampSeconds, &touch_event_);
376
377  WebTouchPoint& point = touch_event_.touches[0];
378  point.id = 0;
379  point.radiusX = point.radiusY = 1.f;
380  point.force = 1.f;
381  point.rotationAngle = 0.f;
382  point.position.x = mouse_event.x;
383  point.screenPosition.x = mouse_event.globalX;
384  point.position.y = mouse_event.y;
385  point.screenPosition.y = mouse_event.globalY;
386
387  return true;
388}
389
390bool TouchEmulator::InPinchGestureMode() const {
391  return shift_pressed_ && allow_pinch_;
392}
393
394}  // namespace content
395