Home | History | Annotate | Download | only in input
      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 
     18 using blink::WebGestureEvent;
     19 using blink::WebInputEvent;
     20 using blink::WebKeyboardEvent;
     21 using blink::WebMouseEvent;
     22 using blink::WebMouseWheelEvent;
     23 using blink::WebTouchEvent;
     24 using blink::WebTouchPoint;
     25 
     26 namespace content {
     27 
     28 namespace {
     29 
     30 ui::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.
     42 const double kMouseMoveDropIntervalSeconds = 5.f / 1000;
     43 
     44 } // namespace
     45 
     46 TouchEmulator::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 
     77 TouchEmulator::~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 
     83 void 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 
     94 void TouchEmulator::Enable(bool allow_pinch) {
     95   if (!enabled_) {
     96     enabled_ = true;
     97     ResetState();
     98   }
     99   allow_pinch_ = allow_pinch;
    100   UpdateCursor();
    101 }
    102 
    103 void TouchEmulator::Disable() {
    104   if (!enabled_)
    105     return;
    106 
    107   enabled_ = false;
    108   UpdateCursor();
    109   CancelTouch();
    110 }
    111 
    112 void 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 
    129 bool 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 
    169 bool 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 
    177 bool 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 
    200 bool 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 
    207 void 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 
    273 void 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 
    286 void TouchEmulator::UpdateCursor() {
    287   if (!enabled_)
    288     client_->SetCursor(pointer_cursor_);
    289   else
    290     client_->SetCursor(InPinchGestureMode() ? pinch_cursor_ : touch_cursor_);
    291 }
    292 
    293 bool 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 
    301 void 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 
    312 void 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 
    323 void 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 
    331 void 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 
    339 void 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 
    348 bool 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 
    390 bool TouchEmulator::InPinchGestureMode() const {
    391   return shift_pressed_ && allow_pinch_;
    392 }
    393 
    394 }  // namespace content
    395