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