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