DevTools: cut host and port from webSocketDebuggerUrl in addition to ws:// prefix
[chromium-blink-merge.git] / content / browser / renderer_host / input / touch_emulator.cc
blobd22065e91996143003c35fcd4809b769bcc31ea9
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.
5 #include "content/browser/renderer_host/input/touch_emulator.h"
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/base_event_utils.h"
15 #include "ui/events/blink/blink_event_util.h"
16 #include "ui/events/gesture_detection/gesture_provider_config_helper.h"
17 #include "ui/gfx/image/image.h"
18 #include "ui/gfx/screen.h"
20 using blink::WebGestureEvent;
21 using blink::WebInputEvent;
22 using blink::WebKeyboardEvent;
23 using blink::WebMouseEvent;
24 using blink::WebMouseWheelEvent;
25 using blink::WebTouchEvent;
26 using blink::WebTouchPoint;
28 namespace content {
30 namespace {
32 ui::GestureProvider::Config GetEmulatorGestureProviderConfig(
33 ui::GestureProviderConfigType config_type) {
34 ui::GestureProvider::Config config =
35 ui::GetGestureProviderConfig(config_type);
36 config.gesture_begin_end_types_enabled = false;
37 config.gesture_detector_config.swipe_enabled = false;
38 config.gesture_detector_config.two_finger_tap_enabled = false;
39 return config;
42 // Time between two consecutive mouse moves, during which second mouse move
43 // is not converted to touch.
44 const double kMouseMoveDropIntervalSeconds = 5.f / 1000;
46 } // namespace
48 TouchEmulator::TouchEmulator(TouchEmulatorClient* client)
49 : client_(client),
50 gesture_provider_config_type_(
51 ui::GestureProviderConfigType::CURRENT_PLATFORM),
52 double_tap_enabled_(true),
53 emulated_stream_active_sequence_count_(0),
54 native_stream_active_sequence_count_(0) {
55 DCHECK(client_);
56 ResetState();
58 bool use_2x = gfx::Screen::GetNativeScreen()->
59 GetPrimaryDisplay().device_scale_factor() > 1.5f;
60 float cursor_scale_factor = use_2x ? 2.f : 1.f;
61 cursor_size_ = InitCursorFromResource(&touch_cursor_,
62 cursor_scale_factor,
63 use_2x ? IDR_DEVTOOLS_TOUCH_CURSOR_ICON_2X :
64 IDR_DEVTOOLS_TOUCH_CURSOR_ICON);
65 InitCursorFromResource(&pinch_cursor_,
66 cursor_scale_factor,
67 use_2x ? IDR_DEVTOOLS_PINCH_CURSOR_ICON_2X :
68 IDR_DEVTOOLS_PINCH_CURSOR_ICON);
70 WebCursor::CursorInfo cursor_info;
71 cursor_info.type = blink::WebCursorInfo::TypePointer;
72 pointer_cursor_.InitFromCursorInfo(cursor_info);
75 TouchEmulator::~TouchEmulator() {
76 // We cannot cleanup properly in destructor, as we need roundtrip to the
77 // renderer for ack. Instead, the owner should call Disable, and only
78 // destroy this object when renderer is dead.
81 void TouchEmulator::ResetState() {
82 last_mouse_event_was_move_ = false;
83 last_mouse_move_timestamp_ = 0;
84 mouse_pressed_ = false;
85 shift_pressed_ = false;
86 suppress_next_fling_cancel_ = false;
87 pinch_scale_ = 1.f;
88 pinch_gesture_active_ = false;
91 void TouchEmulator::Enable(ui::GestureProviderConfigType config_type) {
92 if (!gesture_provider_ || gesture_provider_config_type_ != config_type) {
93 gesture_provider_config_type_ = config_type;
94 gesture_provider_.reset(new ui::FilteredGestureProvider(
95 GetEmulatorGestureProviderConfig(config_type), this));
96 // TODO(dgozman): Use synthetic secondary touch to support multi-touch.
97 gesture_provider_->SetMultiTouchZoomSupportEnabled(false);
98 gesture_provider_->SetDoubleTapSupportForPageEnabled(double_tap_enabled_);
100 UpdateCursor();
103 void TouchEmulator::Disable() {
104 if (!enabled())
105 return;
107 CancelTouch();
108 gesture_provider_.reset();
109 UpdateCursor();
110 ResetState();
113 void TouchEmulator::SetDoubleTapSupportForPageEnabled(bool enabled) {
114 double_tap_enabled_ = enabled;
115 if (gesture_provider_)
116 gesture_provider_->SetDoubleTapSupportForPageEnabled(enabled);
119 gfx::SizeF TouchEmulator::InitCursorFromResource(
120 WebCursor* cursor, float scale, int resource_id) {
121 gfx::Image& cursor_image =
122 content::GetContentClient()->GetNativeImageNamed(resource_id);
123 WebCursor::CursorInfo cursor_info;
124 cursor_info.type = blink::WebCursorInfo::TypeCustom;
125 cursor_info.image_scale_factor = scale;
126 cursor_info.custom_image = cursor_image.AsBitmap();
127 cursor_info.hotspot =
128 gfx::Point(cursor_image.Width() / 2, cursor_image.Height() / 2);
129 #if defined(OS_WIN)
130 cursor_info.external_handle = 0;
131 #endif
133 cursor->InitFromCursorInfo(cursor_info);
134 return gfx::ScaleSize(cursor_image.Size(), 1.f / scale);
137 bool TouchEmulator::HandleMouseEvent(const WebMouseEvent& mouse_event) {
138 if (!enabled())
139 return false;
141 if (mouse_event.button == WebMouseEvent::ButtonRight &&
142 mouse_event.type == WebInputEvent::MouseDown) {
143 client_->ShowContextMenuAtPoint(gfx::Point(mouse_event.x, mouse_event.y));
146 if (mouse_event.button != WebMouseEvent::ButtonLeft)
147 return true;
149 if (mouse_event.type == WebInputEvent::MouseMove) {
150 if (last_mouse_event_was_move_ &&
151 mouse_event.timeStampSeconds < last_mouse_move_timestamp_ +
152 kMouseMoveDropIntervalSeconds)
153 return true;
155 last_mouse_event_was_move_ = true;
156 last_mouse_move_timestamp_ = mouse_event.timeStampSeconds;
157 } else {
158 last_mouse_event_was_move_ = false;
161 if (mouse_event.type == WebInputEvent::MouseDown)
162 mouse_pressed_ = true;
163 else if (mouse_event.type == WebInputEvent::MouseUp)
164 mouse_pressed_ = false;
166 UpdateShiftPressed((mouse_event.modifiers & WebInputEvent::ShiftKey) != 0);
168 if (mouse_event.type != WebInputEvent::MouseDown &&
169 mouse_event.type != WebInputEvent::MouseMove &&
170 mouse_event.type != WebInputEvent::MouseUp) {
171 return true;
174 FillTouchEventAndPoint(mouse_event);
175 HandleEmulatedTouchEvent(touch_event_);
177 // Do not pass mouse events to the renderer.
178 return true;
181 bool TouchEmulator::HandleMouseWheelEvent(const WebMouseWheelEvent& event) {
182 if (!enabled())
183 return false;
185 // Send mouse wheel for easy scrolling when there is no active touch.
186 return emulated_stream_active_sequence_count_ > 0;
189 bool TouchEmulator::HandleKeyboardEvent(const WebKeyboardEvent& event) {
190 if (!enabled())
191 return false;
193 if (!UpdateShiftPressed((event.modifiers & WebInputEvent::ShiftKey) != 0))
194 return false;
196 if (!mouse_pressed_)
197 return false;
199 // Note: The necessary pinch events will be lazily inserted by
200 // |OnGestureEvent| depending on the state of |shift_pressed_|, using the
201 // scroll stream as the event driver.
202 if (shift_pressed_) {
203 // TODO(dgozman): Add secondary touch point and set anchor.
204 } else {
205 // TODO(dgozman): Remove secondary touch point and anchor.
208 // Never block keyboard events.
209 return false;
212 bool TouchEmulator::HandleTouchEvent(const blink::WebTouchEvent& event) {
213 // Block native event when emulated touch stream is active.
214 if (emulated_stream_active_sequence_count_)
215 return true;
217 bool is_sequence_start = WebTouchEventTraits::IsTouchSequenceStart(event);
218 // Do not allow middle-sequence event to pass through, if start was blocked.
219 if (!native_stream_active_sequence_count_ && !is_sequence_start)
220 return true;
222 if (is_sequence_start)
223 native_stream_active_sequence_count_++;
224 return false;
227 void TouchEmulator::HandleEmulatedTouchEvent(blink::WebTouchEvent event) {
228 DCHECK(gesture_provider_);
229 event.uniqueTouchEventId = ui::GetNextTouchEventId();
230 auto result = gesture_provider_->OnTouchEvent(MotionEventWeb(event));
231 if (!result.succeeded)
232 return;
234 const bool event_consumed = true;
235 // Block emulated event when emulated native stream is active.
236 if (native_stream_active_sequence_count_) {
237 gesture_provider_->OnTouchEventAck(event.uniqueTouchEventId,
238 event_consumed);
239 return;
242 bool is_sequence_start = WebTouchEventTraits::IsTouchSequenceStart(event);
243 // Do not allow middle-sequence event to pass through, if start was blocked.
244 if (!emulated_stream_active_sequence_count_ && !is_sequence_start) {
245 gesture_provider_->OnTouchEventAck(event.uniqueTouchEventId,
246 event_consumed);
247 return;
250 if (is_sequence_start)
251 emulated_stream_active_sequence_count_++;
253 event.causesScrollingIfUncanceled = result.did_generate_scroll;
254 client_->ForwardEmulatedTouchEvent(event);
257 bool TouchEmulator::HandleTouchEventAck(
258 const blink::WebTouchEvent& event, InputEventAckState ack_result) {
259 bool is_sequence_end = WebTouchEventTraits::IsTouchSequenceEnd(event);
260 if (emulated_stream_active_sequence_count_) {
261 if (is_sequence_end)
262 emulated_stream_active_sequence_count_--;
264 const bool event_consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
265 if (gesture_provider_)
266 gesture_provider_->OnTouchEventAck(event.uniqueTouchEventId,
267 event_consumed);
268 return true;
271 // We may have not seen native touch sequence start (when created in the
272 // middle of a sequence), so don't decrement sequence count below zero.
273 if (is_sequence_end && native_stream_active_sequence_count_)
274 native_stream_active_sequence_count_--;
275 return false;
278 void TouchEmulator::OnGestureEvent(const ui::GestureEventData& gesture) {
279 WebGestureEvent gesture_event =
280 ui::CreateWebGestureEventFromGestureEventData(gesture);
282 switch (gesture_event.type) {
283 case WebInputEvent::Undefined:
284 NOTREACHED() << "Undefined WebInputEvent type";
285 // Bail without sending the junk event to the client.
286 return;
288 case WebInputEvent::GestureScrollBegin:
289 client_->ForwardGestureEvent(gesture_event);
290 // PinchBegin must always follow ScrollBegin.
291 if (InPinchGestureMode())
292 PinchBegin(gesture_event);
293 break;
295 case WebInputEvent::GestureScrollUpdate:
296 if (InPinchGestureMode()) {
297 // Convert scrolls to pinches while shift is pressed.
298 if (!pinch_gesture_active_)
299 PinchBegin(gesture_event);
300 else
301 PinchUpdate(gesture_event);
302 } else {
303 // Pass scroll update further. If shift was released, end the pinch.
304 if (pinch_gesture_active_)
305 PinchEnd(gesture_event);
306 client_->ForwardGestureEvent(gesture_event);
308 break;
310 case WebInputEvent::GestureScrollEnd:
311 // PinchEnd must precede ScrollEnd.
312 if (pinch_gesture_active_)
313 PinchEnd(gesture_event);
314 client_->ForwardGestureEvent(gesture_event);
315 break;
317 case WebInputEvent::GestureFlingStart:
318 // PinchEnd must precede FlingStart.
319 if (pinch_gesture_active_)
320 PinchEnd(gesture_event);
321 if (InPinchGestureMode()) {
322 // No fling in pinch mode. Forward scroll end instead of fling start.
323 suppress_next_fling_cancel_ = true;
324 ScrollEnd(gesture_event);
325 } else {
326 suppress_next_fling_cancel_ = false;
327 client_->ForwardGestureEvent(gesture_event);
329 break;
331 case WebInputEvent::GestureFlingCancel:
332 // If fling start was suppressed, we should not send fling cancel either.
333 if (!suppress_next_fling_cancel_)
334 client_->ForwardGestureEvent(gesture_event);
335 suppress_next_fling_cancel_ = false;
336 break;
338 default:
339 // Everything else goes through.
340 client_->ForwardGestureEvent(gesture_event);
344 void TouchEmulator::CancelTouch() {
345 if (!emulated_stream_active_sequence_count_ || !enabled())
346 return;
348 WebTouchEventTraits::ResetTypeAndTouchStates(
349 WebInputEvent::TouchCancel,
350 (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(),
351 &touch_event_);
352 DCHECK(gesture_provider_);
353 if (gesture_provider_->GetCurrentDownEvent())
354 HandleEmulatedTouchEvent(touch_event_);
357 void TouchEmulator::UpdateCursor() {
358 if (!enabled())
359 client_->SetCursor(pointer_cursor_);
360 else
361 client_->SetCursor(InPinchGestureMode() ? pinch_cursor_ : touch_cursor_);
364 bool TouchEmulator::UpdateShiftPressed(bool shift_pressed) {
365 if (shift_pressed_ == shift_pressed)
366 return false;
367 shift_pressed_ = shift_pressed;
368 UpdateCursor();
369 return true;
372 void TouchEmulator::PinchBegin(const WebGestureEvent& event) {
373 DCHECK(InPinchGestureMode());
374 DCHECK(!pinch_gesture_active_);
375 pinch_gesture_active_ = true;
376 pinch_anchor_ = gfx::Point(event.x, event.y);
377 pinch_scale_ = 1.f;
378 FillPinchEvent(event);
379 pinch_event_.type = WebInputEvent::GesturePinchBegin;
380 client_->ForwardGestureEvent(pinch_event_);
383 void TouchEmulator::PinchUpdate(const WebGestureEvent& event) {
384 DCHECK(pinch_gesture_active_);
385 int dy = pinch_anchor_.y() - event.y;
386 float scale = exp(dy * 0.002f);
387 FillPinchEvent(event);
388 pinch_event_.type = WebInputEvent::GesturePinchUpdate;
389 pinch_event_.data.pinchUpdate.scale = scale / pinch_scale_;
390 client_->ForwardGestureEvent(pinch_event_);
391 pinch_scale_ = scale;
394 void TouchEmulator::PinchEnd(const WebGestureEvent& event) {
395 DCHECK(pinch_gesture_active_);
396 pinch_gesture_active_ = false;
397 FillPinchEvent(event);
398 pinch_event_.type = WebInputEvent::GesturePinchEnd;
399 client_->ForwardGestureEvent(pinch_event_);
402 void TouchEmulator::FillPinchEvent(const WebInputEvent& event) {
403 pinch_event_.timeStampSeconds = event.timeStampSeconds;
404 pinch_event_.modifiers = event.modifiers;
405 pinch_event_.sourceDevice = blink::WebGestureDeviceTouchscreen;
406 pinch_event_.x = pinch_anchor_.x();
407 pinch_event_.y = pinch_anchor_.y();
410 void TouchEmulator::ScrollEnd(const WebGestureEvent& event) {
411 WebGestureEvent scroll_event;
412 scroll_event.timeStampSeconds = event.timeStampSeconds;
413 scroll_event.modifiers = event.modifiers;
414 scroll_event.sourceDevice = blink::WebGestureDeviceTouchscreen;
415 scroll_event.type = WebInputEvent::GestureScrollEnd;
416 client_->ForwardGestureEvent(scroll_event);
419 void TouchEmulator::FillTouchEventAndPoint(const WebMouseEvent& mouse_event) {
420 WebInputEvent::Type eventType;
421 switch (mouse_event.type) {
422 case WebInputEvent::MouseDown:
423 eventType = WebInputEvent::TouchStart;
424 break;
425 case WebInputEvent::MouseMove:
426 eventType = WebInputEvent::TouchMove;
427 break;
428 case WebInputEvent::MouseUp:
429 eventType = WebInputEvent::TouchEnd;
430 break;
431 default:
432 eventType = WebInputEvent::Undefined;
433 NOTREACHED() << "Invalid event for touch emulation: " << mouse_event.type;
435 touch_event_.touchesLength = 1;
436 touch_event_.modifiers = mouse_event.modifiers;
437 WebTouchEventTraits::ResetTypeAndTouchStates(
438 eventType, mouse_event.timeStampSeconds, &touch_event_);
440 WebTouchPoint& point = touch_event_.touches[0];
441 point.id = 0;
442 point.radiusX = 0.5f * cursor_size_.width();
443 point.radiusY = 0.5f * cursor_size_.height();
444 point.force = 1.f;
445 point.rotationAngle = 0.f;
446 point.position.x = mouse_event.x;
447 point.screenPosition.x = mouse_event.globalX;
448 point.position.y = mouse_event.y;
449 point.screenPosition.y = mouse_event.globalY;
452 bool TouchEmulator::InPinchGestureMode() const {
453 return shift_pressed_;
456 } // namespace content