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/grit/content_resources.h"
     11 #include "content/public/common/content_client.h"
     12 #include "content/public/common/content_switches.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       emulated_stream_active_sequence_count_(0),
     51       native_stream_active_sequence_count_(0) {
     52   DCHECK(client_);
     53   ResetState();
     54 
     55   bool use_2x = gfx::Screen::GetNativeScreen()->
     56       GetPrimaryDisplay().device_scale_factor() > 1.5f;
     57   float cursor_scale_factor = use_2x ? 2.f : 1.f;
     58   cursor_size_ = InitCursorFromResource(&touch_cursor_,
     59       cursor_scale_factor,
     60       use_2x ? IDR_DEVTOOLS_TOUCH_CURSOR_ICON_2X :
     61           IDR_DEVTOOLS_TOUCH_CURSOR_ICON);
     62   InitCursorFromResource(&pinch_cursor_,
     63       cursor_scale_factor,
     64       use_2x ? IDR_DEVTOOLS_PINCH_CURSOR_ICON_2X :
     65           IDR_DEVTOOLS_PINCH_CURSOR_ICON);
     66 
     67   WebCursor::CursorInfo cursor_info;
     68   cursor_info.type = blink::WebCursorInfo::TypePointer;
     69   pointer_cursor_.InitFromCursorInfo(cursor_info);
     70 
     71   // TODO(dgozman): Use synthetic secondary touch to support multi-touch.
     72   gesture_provider_.SetMultiTouchZoomSupportEnabled(false);
     73   // TODO(dgozman): Enable double tap if requested by the renderer.
     74   // TODO(dgozman): Don't break double-tap-based pinch with shift handling.
     75   gesture_provider_.SetDoubleTapSupportForPlatformEnabled(false);
     76 }
     77 
     78 TouchEmulator::~TouchEmulator() {
     79   // We cannot cleanup properly in destructor, as we need roundtrip to the
     80   // renderer for ack. Instead, the owner should call Disable, and only
     81   // destroy this object when renderer is dead.
     82 }
     83 
     84 void TouchEmulator::ResetState() {
     85   last_mouse_event_was_move_ = false;
     86   last_mouse_move_timestamp_ = 0;
     87   mouse_pressed_ = false;
     88   shift_pressed_ = false;
     89   suppress_next_fling_cancel_ = false;
     90   pinch_scale_ = 1.f;
     91   pinch_gesture_active_ = false;
     92 }
     93 
     94 void TouchEmulator::Enable() {
     95   if (!enabled_) {
     96     enabled_ = true;
     97     ResetState();
     98   }
     99   UpdateCursor();
    100 }
    101 
    102 void TouchEmulator::Disable() {
    103   if (!enabled_)
    104     return;
    105 
    106   enabled_ = false;
    107   UpdateCursor();
    108   CancelTouch();
    109 }
    110 
    111 gfx::SizeF TouchEmulator::InitCursorFromResource(
    112     WebCursor* cursor, float scale, int resource_id) {
    113   gfx::Image& cursor_image =
    114       content::GetContentClient()->GetNativeImageNamed(resource_id);
    115   WebCursor::CursorInfo cursor_info;
    116   cursor_info.type = blink::WebCursorInfo::TypeCustom;
    117   cursor_info.image_scale_factor = scale;
    118   cursor_info.custom_image = cursor_image.AsBitmap();
    119   cursor_info.hotspot =
    120       gfx::Point(cursor_image.Width() / 2, cursor_image.Height() / 2);
    121 #if defined(OS_WIN)
    122   cursor_info.external_handle = 0;
    123 #endif
    124 
    125   cursor->InitFromCursorInfo(cursor_info);
    126   return gfx::ScaleSize(cursor_image.Size(), 1.f / scale);
    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     ForwardTouchEventToClient();
    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 emulated_stream_active_sequence_count_ > 0;
    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::HandleTouchEvent(const blink::WebTouchEvent& event) {
    201   // Block native event when emulated touch stream is active.
    202   if (emulated_stream_active_sequence_count_)
    203     return true;
    204 
    205   bool is_sequence_start = WebTouchEventTraits::IsTouchSequenceStart(event);
    206   // Do not allow middle-sequence event to pass through, if start was blocked.
    207   if (!native_stream_active_sequence_count_ && !is_sequence_start)
    208     return true;
    209 
    210   if (is_sequence_start)
    211     native_stream_active_sequence_count_++;
    212   return false;
    213 }
    214 
    215 void TouchEmulator::ForwardTouchEventToClient() {
    216   const bool event_consumed = true;
    217   // Block emulated event when emulated native stream is active.
    218   if (native_stream_active_sequence_count_) {
    219     gesture_provider_.OnTouchEventAck(event_consumed);
    220     return;
    221   }
    222 
    223   bool is_sequence_start =
    224       WebTouchEventTraits::IsTouchSequenceStart(touch_event_);
    225   // Do not allow middle-sequence event to pass through, if start was blocked.
    226   if (!emulated_stream_active_sequence_count_ && !is_sequence_start) {
    227     gesture_provider_.OnTouchEventAck(event_consumed);
    228     return;
    229   }
    230 
    231   if (is_sequence_start)
    232     emulated_stream_active_sequence_count_++;
    233   client_->ForwardEmulatedTouchEvent(touch_event_);
    234 }
    235 
    236 bool TouchEmulator::HandleTouchEventAck(
    237     const blink::WebTouchEvent& event, InputEventAckState ack_result) {
    238   bool is_sequence_end = WebTouchEventTraits::IsTouchSequenceEnd(event);
    239   if (emulated_stream_active_sequence_count_) {
    240     if (is_sequence_end)
    241       emulated_stream_active_sequence_count_--;
    242 
    243     const bool event_consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
    244     gesture_provider_.OnTouchEventAck(event_consumed);
    245     return true;
    246   }
    247 
    248   // We may have not seen native touch sequence start (when created in the
    249   // middle of a sequence), so don't decrement sequence count below zero.
    250   if (is_sequence_end && native_stream_active_sequence_count_)
    251     native_stream_active_sequence_count_--;
    252   return false;
    253 }
    254 
    255 void TouchEmulator::OnGestureEvent(const ui::GestureEventData& gesture) {
    256   WebGestureEvent gesture_event =
    257       CreateWebGestureEventFromGestureEventData(gesture);
    258 
    259   switch (gesture_event.type) {
    260     case WebInputEvent::Undefined:
    261       NOTREACHED() << "Undefined WebInputEvent type";
    262       // Bail without sending the junk event to the client.
    263       return;
    264 
    265     case WebInputEvent::GestureScrollBegin:
    266       client_->ForwardGestureEvent(gesture_event);
    267       // PinchBegin must always follow ScrollBegin.
    268       if (InPinchGestureMode())
    269         PinchBegin(gesture_event);
    270       break;
    271 
    272     case WebInputEvent::GestureScrollUpdate:
    273       if (InPinchGestureMode()) {
    274         // Convert scrolls to pinches while shift is pressed.
    275         if (!pinch_gesture_active_)
    276           PinchBegin(gesture_event);
    277         else
    278           PinchUpdate(gesture_event);
    279       } else {
    280         // Pass scroll update further. If shift was released, end the pinch.
    281         if (pinch_gesture_active_)
    282           PinchEnd(gesture_event);
    283         client_->ForwardGestureEvent(gesture_event);
    284       }
    285       break;
    286 
    287     case WebInputEvent::GestureScrollEnd:
    288       // PinchEnd must precede ScrollEnd.
    289       if (pinch_gesture_active_)
    290         PinchEnd(gesture_event);
    291       client_->ForwardGestureEvent(gesture_event);
    292       break;
    293 
    294     case WebInputEvent::GestureFlingStart:
    295       // PinchEnd must precede FlingStart.
    296       if (pinch_gesture_active_)
    297         PinchEnd(gesture_event);
    298       if (InPinchGestureMode()) {
    299         // No fling in pinch mode. Forward scroll end instead of fling start.
    300         suppress_next_fling_cancel_ = true;
    301         ScrollEnd(gesture_event);
    302       } else {
    303         suppress_next_fling_cancel_ = false;
    304         client_->ForwardGestureEvent(gesture_event);
    305       }
    306       break;
    307 
    308     case WebInputEvent::GestureFlingCancel:
    309       // If fling start was suppressed, we should not send fling cancel either.
    310       if (!suppress_next_fling_cancel_)
    311         client_->ForwardGestureEvent(gesture_event);
    312       suppress_next_fling_cancel_ = false;
    313       break;
    314 
    315     default:
    316       // Everything else goes through.
    317       client_->ForwardGestureEvent(gesture_event);
    318   }
    319 }
    320 
    321 void TouchEmulator::CancelTouch() {
    322   if (!emulated_stream_active_sequence_count_)
    323     return;
    324 
    325   WebTouchEventTraits::ResetTypeAndTouchStates(
    326       WebInputEvent::TouchCancel,
    327       (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(),
    328       &touch_event_);
    329   if (gesture_provider_.GetCurrentDownEvent() &&
    330       gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_)))
    331     ForwardTouchEventToClient();
    332 }
    333 
    334 void TouchEmulator::UpdateCursor() {
    335   if (!enabled_)
    336     client_->SetCursor(pointer_cursor_);
    337   else
    338     client_->SetCursor(InPinchGestureMode() ? pinch_cursor_ : touch_cursor_);
    339 }
    340 
    341 bool TouchEmulator::UpdateShiftPressed(bool shift_pressed) {
    342   if (shift_pressed_ == shift_pressed)
    343     return false;
    344   shift_pressed_ = shift_pressed;
    345   UpdateCursor();
    346   return true;
    347 }
    348 
    349 void TouchEmulator::PinchBegin(const WebGestureEvent& event) {
    350   DCHECK(InPinchGestureMode());
    351   DCHECK(!pinch_gesture_active_);
    352   pinch_gesture_active_ = true;
    353   pinch_anchor_ = gfx::Point(event.x, event.y);
    354   pinch_scale_ = 1.f;
    355   FillPinchEvent(event);
    356   pinch_event_.type = WebInputEvent::GesturePinchBegin;
    357   client_->ForwardGestureEvent(pinch_event_);
    358 }
    359 
    360 void TouchEmulator::PinchUpdate(const WebGestureEvent& event) {
    361   DCHECK(pinch_gesture_active_);
    362   int dy = pinch_anchor_.y() - event.y;
    363   float scale = exp(dy * 0.002f);
    364   FillPinchEvent(event);
    365   pinch_event_.type = WebInputEvent::GesturePinchUpdate;
    366   pinch_event_.data.pinchUpdate.scale = scale / pinch_scale_;
    367   client_->ForwardGestureEvent(pinch_event_);
    368   pinch_scale_ = scale;
    369 }
    370 
    371 void TouchEmulator::PinchEnd(const WebGestureEvent& event) {
    372   DCHECK(pinch_gesture_active_);
    373   pinch_gesture_active_ = false;
    374   FillPinchEvent(event);
    375   pinch_event_.type = WebInputEvent::GesturePinchEnd;
    376   client_->ForwardGestureEvent(pinch_event_);
    377 }
    378 
    379 void TouchEmulator::FillPinchEvent(const WebInputEvent& event) {
    380   pinch_event_.timeStampSeconds = event.timeStampSeconds;
    381   pinch_event_.modifiers = event.modifiers;
    382   pinch_event_.sourceDevice = blink::WebGestureDeviceTouchscreen;
    383   pinch_event_.x = pinch_anchor_.x();
    384   pinch_event_.y = pinch_anchor_.y();
    385 }
    386 
    387 void TouchEmulator::ScrollEnd(const WebGestureEvent& event) {
    388   WebGestureEvent scroll_event;
    389   scroll_event.timeStampSeconds = event.timeStampSeconds;
    390   scroll_event.modifiers = event.modifiers;
    391   scroll_event.sourceDevice = blink::WebGestureDeviceTouchscreen;
    392   scroll_event.type = WebInputEvent::GestureScrollEnd;
    393   client_->ForwardGestureEvent(scroll_event);
    394 }
    395 
    396 bool TouchEmulator::FillTouchEventAndPoint(const WebMouseEvent& mouse_event) {
    397   if (mouse_event.type != WebInputEvent::MouseDown &&
    398       mouse_event.type != WebInputEvent::MouseMove &&
    399       mouse_event.type != WebInputEvent::MouseUp) {
    400     return false;
    401   }
    402 
    403   WebInputEvent::Type eventType;
    404   switch (mouse_event.type) {
    405     case WebInputEvent::MouseDown:
    406       eventType = WebInputEvent::TouchStart;
    407       break;
    408     case WebInputEvent::MouseMove:
    409       eventType = WebInputEvent::TouchMove;
    410       break;
    411     case WebInputEvent::MouseUp:
    412       eventType = WebInputEvent::TouchEnd;
    413       break;
    414     default:
    415       eventType = WebInputEvent::Undefined;
    416       NOTREACHED();
    417   }
    418   touch_event_.touchesLength = 1;
    419   touch_event_.modifiers = mouse_event.modifiers;
    420   WebTouchEventTraits::ResetTypeAndTouchStates(
    421       eventType, mouse_event.timeStampSeconds, &touch_event_);
    422 
    423   WebTouchPoint& point = touch_event_.touches[0];
    424   point.id = 0;
    425   point.radiusX = 0.5f * cursor_size_.width();
    426   point.radiusY = 0.5f * cursor_size_.height();
    427   point.force = 1.f;
    428   point.rotationAngle = 0.f;
    429   point.position.x = mouse_event.x;
    430   point.screenPosition.x = mouse_event.globalX;
    431   point.position.y = mouse_event.y;
    432   point.screenPosition.y = mouse_event.globalY;
    433 
    434   return true;
    435 }
    436 
    437 bool TouchEmulator::InPinchGestureMode() const {
    438   return shift_pressed_;
    439 }
    440 
    441 }  // namespace content
    442