summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordgozman@chromium.org <dgozman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-15 09:00:07 +0000
committerdgozman@chromium.org <dgozman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-15 09:00:07 +0000
commit471eeb53f64b1e576a42579a8cfd3f3376eef57e (patch)
treefa99f8a889fe00de7ec2fc083db8acb511ca7325
parentf203be26cd1e55f6b520da214731855ca2fafdba (diff)
downloadchromium_src-471eeb53f64b1e576a42579a8cfd3f3376eef57e.zip
chromium_src-471eeb53f64b1e576a42579a8cfd3f3376eef57e.tar.gz
chromium_src-471eeb53f64b1e576a42579a8cfd3f3376eef57e.tar.bz2
Revert 263647 "Revert 263644 "[DevTools] Touch emulation in cont..."
Static initializers issue was fixed in r263789. > Revert 263644 "[DevTools] Touch emulation in content." > > Reason for revert: broke > http://build.chromium.org/p/chromium/builders/Linux%20x64/builds/63653/ > by introducing two new static-initializers into the chrome binary: > # velocity_tracker.cc ui::(anonymous namespace)::LeastSquaresVelocityTrackerStrategy::HORIZON > # velocity_tracker.cc ui::(anonymous namespace)::ASSUME_POINTER_STOPPED_TIME > > > [DevTools] Touch emulation in content. > > > > When renderer requests touch emulation using mouse, host creates > > a TouchEmulator object and passes mouse, mouse wheel and keyboard events > > to emulator before sending them to renderer. > > > > Emulator completely blocks mouse-related events, and instead generates > > touch events. Those touch events are handled over to gesture provider > > after being acked by renderer. Resulting gestures are sent to the renderer. > > > > When shift is pressed, scroll gestures are converted to pinch gestures, so > > user can scale the page. This is required because multitouch is not yet > > supported by emulator. > > > > BUG=337142 > > > > Review URL: https://codereview.chromium.org/138163016 > > TBR=dgozman@chromium.org > > Review URL: https://codereview.chromium.org/237353003 TBR=fischman@chromium.org Review URL: https://codereview.chromium.org/236063014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@263806 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--content/browser/renderer_host/input/touch_emulator.cc371
-rw-r--r--content/browser/renderer_host/input/touch_emulator.h100
-rw-r--r--content/browser/renderer_host/input/touch_emulator_client.h26
-rw-r--r--content/browser/renderer_host/input/touch_emulator_unittest.cc318
-rw-r--r--content/browser/renderer_host/render_widget_host_impl.cc50
-rw-r--r--content/browser/renderer_host/render_widget_host_impl.h14
-rw-r--r--content/browser/renderer_host/render_widget_host_unittest.cc251
-rw-r--r--content/browser/resources/devtools/devtools_pinch_cursor.pngbin0 -> 138 bytes
-rw-r--r--content/browser/resources/devtools/devtools_touch_cursor.pngbin0 -> 343 bytes
-rw-r--r--content/child/blink_platform_impl.cc1
-rw-r--r--content/common/view_messages.h6
-rw-r--r--content/content_browser.gypi3
-rw-r--r--content/content_resources.grd2
-rw-r--r--content/content_tests.gypi1
-rw-r--r--content/renderer/devtools/devtools_agent.cc7
-rw-r--r--content/renderer/devtools/devtools_agent.h1
16 files changed, 1138 insertions, 13 deletions
diff --git a/content/browser/renderer_host/input/touch_emulator.cc b/content/browser/renderer_host/input/touch_emulator.cc
new file mode 100644
index 0000000..b0d43e2
--- /dev/null
+++ b/content/browser/renderer_host/input/touch_emulator.cc
@@ -0,0 +1,371 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/renderer_host/input/touch_emulator.h"
+
+#include "content/browser/renderer_host/input/motion_event_web.h"
+#include "content/browser/renderer_host/input/web_input_event_util.h"
+#include "content/public/common/content_client.h"
+#include "content/public/common/content_switches.h"
+#include "grit/content_resources.h"
+#include "third_party/WebKit/public/platform/WebCursorInfo.h"
+#include "ui/events/gesture_detection/gesture_config_helper.h"
+#include "ui/gfx/image/image.h"
+
+using blink::WebGestureEvent;
+using blink::WebInputEvent;
+using blink::WebKeyboardEvent;
+using blink::WebMouseEvent;
+using blink::WebMouseWheelEvent;
+using blink::WebTouchEvent;
+using blink::WebTouchPoint;
+
+namespace content {
+
+namespace {
+
+ui::GestureProvider::Config GetGestureProviderConfig() {
+ // TODO(dgozman): Use different configs to emulate mobile/desktop as
+ // requested by renderer.
+ ui::GestureProvider::Config config = ui::DefaultGestureProviderConfig();
+ config.gesture_begin_end_types_enabled = false;
+ return config;
+}
+
+// Time between two consecutive mouse moves, during which second mouse move
+// is not converted to touch.
+const double kMouseMoveDropIntervalSeconds = 5.f / 1000;
+
+} // namespace
+
+TouchEmulator::TouchEmulator(TouchEmulatorClient* client)
+ : client_(client),
+ gesture_provider_(GetGestureProviderConfig(), this),
+ enabled_(false),
+ allow_pinch_(false) {
+ DCHECK(client_);
+ ResetState();
+
+ InitCursorFromResource(&touch_cursor_, IDR_DEVTOOLS_TOUCH_CURSOR_ICON);
+ InitCursorFromResource(&pinch_cursor_, IDR_DEVTOOLS_PINCH_CURSOR_ICON);
+
+ WebCursor::CursorInfo cursor_info;
+ cursor_info.type = blink::WebCursorInfo::TypePointer;
+ pointer_cursor_.InitFromCursorInfo(cursor_info);
+
+ // TODO(dgozman): Use synthetic secondary touch to support multi-touch.
+ gesture_provider_.SetMultiTouchSupportEnabled(false);
+ // TODO(dgozman): Enable double tap if requested by the renderer.
+ // TODO(dgozman): Don't break double-tap-based pinch with shift handling.
+ gesture_provider_.SetDoubleTapSupportForPlatformEnabled(false);
+}
+
+TouchEmulator::~TouchEmulator() {
+ // We cannot cleanup properly in destructor, as we need roundtrip to the
+ // renderer for ack. Instead, the owner should call Disable, and only
+ // destroy this object when renderer is dead.
+}
+
+void TouchEmulator::ResetState() {
+ last_mouse_event_was_move_ = false;
+ last_mouse_move_timestamp_ = 0;
+ mouse_pressed_ = false;
+ shift_pressed_ = false;
+ touch_active_ = false;
+ suppress_next_fling_cancel_ = false;
+ pinch_scale_ = 1.f;
+ pinch_gesture_active_ = false;
+}
+
+void TouchEmulator::Enable(bool allow_pinch) {
+ if (!enabled_) {
+ enabled_ = true;
+ ResetState();
+ }
+ allow_pinch_ = allow_pinch;
+ UpdateCursor();
+}
+
+void TouchEmulator::Disable() {
+ if (!enabled_)
+ return;
+
+ enabled_ = false;
+ UpdateCursor();
+ CancelTouch();
+}
+
+void TouchEmulator::InitCursorFromResource(WebCursor* cursor, int resource_id) {
+ gfx::Image& cursor_image =
+ content::GetContentClient()->GetNativeImageNamed(resource_id);
+ WebCursor::CursorInfo cursor_info;
+ cursor_info.type = blink::WebCursorInfo::TypeCustom;
+ // TODO(dgozman): Add HiDPI cursors.
+ cursor_info.image_scale_factor = 1.f;
+ cursor_info.custom_image = cursor_image.AsBitmap();
+ cursor_info.hotspot =
+ gfx::Point(cursor_image.Width() / 2, cursor_image.Height() / 2);
+#if defined(OS_WIN)
+ cursor_info.external_handle = 0;
+#endif
+
+ cursor->InitFromCursorInfo(cursor_info);
+}
+
+bool TouchEmulator::HandleMouseEvent(const WebMouseEvent& mouse_event) {
+ if (!enabled_)
+ return false;
+
+ if (mouse_event.button != WebMouseEvent::ButtonLeft)
+ return true;
+
+ if (mouse_event.type == WebInputEvent::MouseMove) {
+ if (last_mouse_event_was_move_ &&
+ mouse_event.timeStampSeconds < last_mouse_move_timestamp_ +
+ kMouseMoveDropIntervalSeconds)
+ return true;
+
+ last_mouse_event_was_move_ = true;
+ last_mouse_move_timestamp_ = mouse_event.timeStampSeconds;
+ } else {
+ last_mouse_event_was_move_ = false;
+ }
+
+ if (mouse_event.type == WebInputEvent::MouseDown)
+ mouse_pressed_ = true;
+ else if (mouse_event.type == WebInputEvent::MouseUp)
+ mouse_pressed_ = false;
+
+ UpdateShiftPressed((mouse_event.modifiers & WebInputEvent::ShiftKey) != 0);
+
+ if (FillTouchEventAndPoint(mouse_event) &&
+ gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_))) {
+ client_->ForwardTouchEvent(touch_event_);
+ }
+
+ // Do not pass mouse events to the renderer.
+ return true;
+}
+
+bool TouchEmulator::HandleMouseWheelEvent(const WebMouseWheelEvent& event) {
+ if (!enabled_)
+ return false;
+
+ // No mouse wheel events for the renderer.
+ return true;
+}
+
+bool TouchEmulator::HandleKeyboardEvent(const WebKeyboardEvent& event) {
+ if (!enabled_)
+ return false;
+
+ if (!UpdateShiftPressed((event.modifiers & WebInputEvent::ShiftKey) != 0))
+ return false;
+
+ if (!mouse_pressed_)
+ return false;
+
+ // Note: The necessary pinch events will be lazily inserted by
+ // |OnGestureEvent| depending on the state of |shift_pressed_|, using the
+ // scroll stream as the event driver.
+ if (shift_pressed_) {
+ // TODO(dgozman): Add secondary touch point and set anchor.
+ } else {
+ // TODO(dgozman): Remove secondary touch point and anchor.
+ }
+
+ // Never block keyboard events.
+ return false;
+}
+
+bool TouchEmulator::HandleTouchEventAck(InputEventAckState ack_result) {
+ const bool event_consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
+ gesture_provider_.OnTouchEventAck(event_consumed);
+ // TODO(dgozman): Disable emulation when real touch events are available.
+ return true;
+}
+
+void TouchEmulator::OnGestureEvent(const ui::GestureEventData& gesture) {
+ WebGestureEvent gesture_event =
+ CreateWebGestureEventFromGestureEventData(gesture);
+
+ switch (gesture_event.type) {
+ case WebInputEvent::GestureScrollBegin:
+ client_->ForwardGestureEvent(gesture_event);
+ // PinchBegin must always follow ScrollBegin.
+ if (InPinchGestureMode())
+ PinchBegin(gesture_event);
+ break;
+
+ case WebInputEvent::GestureScrollUpdate:
+ if (InPinchGestureMode()) {
+ // Convert scrolls to pinches while shift is pressed.
+ if (!pinch_gesture_active_)
+ PinchBegin(gesture_event);
+ else
+ PinchUpdate(gesture_event);
+ } else {
+ // Pass scroll update further. If shift was released, end the pinch.
+ if (pinch_gesture_active_)
+ PinchEnd(gesture_event);
+ client_->ForwardGestureEvent(gesture_event);
+ }
+ break;
+
+ case WebInputEvent::GestureScrollEnd:
+ // PinchEnd must precede ScrollEnd.
+ if (pinch_gesture_active_)
+ PinchEnd(gesture_event);
+ client_->ForwardGestureEvent(gesture_event);
+ break;
+
+ case WebInputEvent::GestureFlingStart:
+ // PinchEnd must precede FlingStart.
+ if (pinch_gesture_active_)
+ PinchEnd(gesture_event);
+ if (InPinchGestureMode()) {
+ // No fling in pinch mode. Forward scroll end instead of fling start.
+ suppress_next_fling_cancel_ = true;
+ ScrollEnd(gesture_event);
+ } else {
+ suppress_next_fling_cancel_ = false;
+ client_->ForwardGestureEvent(gesture_event);
+ }
+ break;
+
+ case WebInputEvent::GestureFlingCancel:
+ // If fling start was suppressed, we should not send fling cancel either.
+ if (!suppress_next_fling_cancel_)
+ client_->ForwardGestureEvent(gesture_event);
+ suppress_next_fling_cancel_ = false;
+ break;
+
+ default:
+ // Everything else goes through.
+ client_->ForwardGestureEvent(gesture_event);
+ }
+}
+
+void TouchEmulator::CancelTouch() {
+ if (!touch_active_)
+ return;
+
+ touch_event_.timeStampSeconds =
+ (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
+ touch_event_.type = WebInputEvent::TouchCancel;
+ touch_event_.touches[0].state = WebTouchPoint::StateCancelled;
+ touch_active_ = false;
+ if (gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_)))
+ client_->ForwardTouchEvent(touch_event_);
+}
+
+void TouchEmulator::UpdateCursor() {
+ if (!enabled_)
+ client_->SetCursor(pointer_cursor_);
+ else
+ client_->SetCursor(InPinchGestureMode() ? pinch_cursor_ : touch_cursor_);
+}
+
+bool TouchEmulator::UpdateShiftPressed(bool shift_pressed) {
+ if (shift_pressed_ == shift_pressed)
+ return false;
+ shift_pressed_ = shift_pressed;
+ UpdateCursor();
+ return true;
+}
+
+void TouchEmulator::PinchBegin(const WebGestureEvent& event) {
+ DCHECK(InPinchGestureMode());
+ DCHECK(!pinch_gesture_active_);
+ pinch_gesture_active_ = true;
+ pinch_anchor_ = gfx::Point(event.x, event.y);
+ pinch_scale_ = 1.f;
+ FillPinchEvent(event);
+ pinch_event_.type = WebInputEvent::GesturePinchBegin;
+ client_->ForwardGestureEvent(pinch_event_);
+}
+
+void TouchEmulator::PinchUpdate(const WebGestureEvent& event) {
+ DCHECK(pinch_gesture_active_);
+ int dy = pinch_anchor_.y() - event.y;
+ float scale = exp(dy * 0.002f);
+ FillPinchEvent(event);
+ pinch_event_.type = WebInputEvent::GesturePinchUpdate;
+ pinch_event_.data.pinchUpdate.scale = scale / pinch_scale_;
+ client_->ForwardGestureEvent(pinch_event_);
+ pinch_scale_ = scale;
+}
+
+void TouchEmulator::PinchEnd(const WebGestureEvent& event) {
+ DCHECK(pinch_gesture_active_);
+ pinch_gesture_active_ = false;
+ FillPinchEvent(event);
+ pinch_event_.type = WebInputEvent::GesturePinchEnd;
+ client_->ForwardGestureEvent(pinch_event_);
+}
+
+void TouchEmulator::FillPinchEvent(const WebInputEvent& event) {
+ pinch_event_.timeStampSeconds = event.timeStampSeconds;
+ pinch_event_.modifiers = event.modifiers;
+ pinch_event_.sourceDevice = blink::WebGestureEvent::Touchscreen;
+ pinch_event_.x = pinch_anchor_.x();
+ pinch_event_.y = pinch_anchor_.y();
+}
+
+void TouchEmulator::ScrollEnd(const WebGestureEvent& event) {
+ WebGestureEvent scroll_event;
+ scroll_event.timeStampSeconds = event.timeStampSeconds;
+ scroll_event.modifiers = event.modifiers;
+ scroll_event.sourceDevice = blink::WebGestureEvent::Touchscreen;
+ scroll_event.type = WebInputEvent::GestureScrollEnd;
+ client_->ForwardGestureEvent(scroll_event);
+}
+
+bool TouchEmulator::FillTouchEventAndPoint(const WebMouseEvent& mouse_event) {
+ if (mouse_event.type != WebInputEvent::MouseDown &&
+ mouse_event.type != WebInputEvent::MouseMove &&
+ mouse_event.type != WebInputEvent::MouseUp) {
+ return false;
+ }
+
+ touch_event_.touchesLength = 1;
+ touch_event_.timeStampSeconds = mouse_event.timeStampSeconds;
+ touch_event_.modifiers = mouse_event.modifiers;
+
+ WebTouchPoint& point = touch_event_.touches[0];
+ point.id = 0;
+ point.radiusX = point.radiusY = 1.f;
+ point.force = 1.f;
+ point.rotationAngle = 0.f;
+ point.position.x = mouse_event.x;
+ point.screenPosition.x = mouse_event.globalX;
+ point.position.y = mouse_event.y;
+ point.screenPosition.y = mouse_event.globalY;
+
+ switch (mouse_event.type) {
+ case WebInputEvent::MouseDown:
+ touch_event_.type = WebInputEvent::TouchStart;
+ touch_active_ = true;
+ point.state = WebTouchPoint::StatePressed;
+ break;
+ case WebInputEvent::MouseMove:
+ touch_event_.type = WebInputEvent::TouchMove;
+ point.state = WebTouchPoint::StateMoved;
+ break;
+ case WebInputEvent::MouseUp:
+ touch_event_.type = WebInputEvent::TouchEnd;
+ touch_active_ = false;
+ point.state = WebTouchPoint::StateReleased;
+ break;
+ default:
+ NOTREACHED();
+ }
+ return true;
+}
+
+bool TouchEmulator::InPinchGestureMode() const {
+ return shift_pressed_ && allow_pinch_;
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/input/touch_emulator.h b/content/browser/renderer_host/input/touch_emulator.h
new file mode 100644
index 0000000..b4b9e93
--- /dev/null
+++ b/content/browser/renderer_host/input/touch_emulator.h
@@ -0,0 +1,100 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_H_
+
+#include "content/browser/renderer_host/input/touch_emulator_client.h"
+#include "content/common/cursors/webcursor.h"
+#include "content/port/common/input_event_ack_state.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+#include "ui/events/gesture_detection/filtered_gesture_provider.h"
+
+namespace content {
+
+// Emulates touch input with mouse and keyboard.
+class CONTENT_EXPORT TouchEmulator : public ui::GestureProviderClient {
+ public:
+ explicit TouchEmulator(TouchEmulatorClient* client);
+ virtual ~TouchEmulator();
+
+ void Enable(bool allow_pinch);
+ void Disable();
+
+ // Returns |true| if the event was consumed.
+ // TODO(dgozman): maybe pass latency info together with events.
+ bool HandleMouseEvent(const blink::WebMouseEvent& event);
+ bool HandleMouseWheelEvent(const blink::WebMouseWheelEvent& event);
+ bool HandleKeyboardEvent(const blink::WebKeyboardEvent& event);
+
+ // Returns |true| if the event ack was consumed. Consumed ack should not
+ // propagate any further.
+ bool HandleTouchEventAck(InputEventAckState ack_result);
+
+ // Cancel any touches, for example, when focus is lost.
+ void CancelTouch();
+
+ private:
+ // ui::GestureProviderClient implementation.
+ virtual void OnGestureEvent(const ui::GestureEventData& gesture) OVERRIDE;
+
+ void InitCursorFromResource(WebCursor* cursor, int resource_id);
+ void ResetState();
+ void UpdateCursor();
+ bool UpdateShiftPressed(bool shift_pressed);
+
+ // Whether we should convert scrolls into pinches.
+ bool InPinchGestureMode() const;
+
+ bool FillTouchEventAndPoint(const blink::WebMouseEvent& mouse_event);
+ void FillPinchEvent(const blink::WebInputEvent& event);
+
+ // The following methods generate and pass gesture events to the renderer.
+ void PinchBegin(const blink::WebGestureEvent& event);
+ void PinchUpdate(const blink::WebGestureEvent& event);
+ void PinchEnd(const blink::WebGestureEvent& event);
+ void ScrollEnd(const blink::WebGestureEvent& event);
+
+ TouchEmulatorClient* const client_;
+ ui::FilteredGestureProvider gesture_provider_;
+
+ // Disabled emulator does only process touch acks left from previous
+ // emulation. It does not intercept any events.
+ bool enabled_;
+ bool allow_pinch_;
+
+ // While emulation is on, default cursor is touch. Pressing shift changes
+ // cursor to the pinch one.
+ WebCursor pointer_cursor_;
+ WebCursor touch_cursor_;
+ WebCursor pinch_cursor_;
+
+ // These are used to drop extra mouse move events coming too quickly, so
+ // we don't handle too much touches in gesture provider.
+ bool last_mouse_event_was_move_;
+ double last_mouse_move_timestamp_;
+
+ bool mouse_pressed_;
+ bool shift_pressed_;
+
+ blink::WebTouchEvent touch_event_;
+ bool touch_active_;
+
+ // Whether we should suppress next fling cancel. This may happen when we
+ // did not send fling start in pinch mode.
+ bool suppress_next_fling_cancel_;
+
+ blink::WebGestureEvent pinch_event_;
+ // Point which does not move while pinch-zooming.
+ gfx::Point pinch_anchor_;
+ // The cumulative scale change from the start of pinch gesture.
+ float pinch_scale_;
+ bool pinch_gesture_active_;
+
+ DISALLOW_COPY_AND_ASSIGN(TouchEmulator);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_H_
diff --git a/content/browser/renderer_host/input/touch_emulator_client.h b/content/browser/renderer_host/input/touch_emulator_client.h
new file mode 100644
index 0000000..92f382f
--- /dev/null
+++ b/content/browser/renderer_host/input/touch_emulator_client.h
@@ -0,0 +1,26 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_CLIENT_H_
+#define CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_CLIENT_H_
+
+#include "content/common/content_export.h"
+#include "content/common/cursors/webcursor.h"
+#include "third_party/WebKit/public/web/WebInputEvent.h"
+
+namespace content {
+
+// Emulates touch input with mouse and keyboard.
+class CONTENT_EXPORT TouchEmulatorClient {
+ public:
+ virtual ~TouchEmulatorClient() {}
+
+ virtual void ForwardGestureEvent(const blink::WebGestureEvent& event) = 0;
+ virtual void ForwardTouchEvent(const blink::WebTouchEvent& event) = 0;
+ virtual void SetCursor(const WebCursor& cursor) = 0;
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_RENDERER_HOST_INPUT_TOUCH_EMULATOR_CLIENT_H_
diff --git a/content/browser/renderer_host/input/touch_emulator_unittest.cc b/content/browser/renderer_host/input/touch_emulator_unittest.cc
new file mode 100644
index 0000000..7593fb7
--- /dev/null
+++ b/content/browser/renderer_host/input/touch_emulator_unittest.cc
@@ -0,0 +1,318 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/time/time.h"
+#include "content/browser/renderer_host/input/touch_emulator.h"
+#include "content/browser/renderer_host/input/touch_emulator_client.h"
+#include "content/common/input/web_input_event_traits.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/gesture_detection/gesture_config_helper.h"
+
+#if defined(USE_AURA)
+#include "ui/aura/env.h"
+#include "ui/aura/test/test_screen.h"
+#endif
+
+using blink::WebGestureEvent;
+using blink::WebInputEvent;
+using blink::WebKeyboardEvent;
+using blink::WebMouseEvent;
+using blink::WebTouchEvent;
+using blink::WebTouchPoint;
+
+namespace content {
+
+class TouchEmulatorTest : public testing::Test,
+ public TouchEmulatorClient {
+ public:
+ TouchEmulatorTest()
+ : shift_pressed_(false),
+ mouse_pressed_(false),
+ last_mouse_x_(-1),
+ last_mouse_y_(-1) {
+ last_event_time_seconds_ =
+ (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
+ event_time_delta_seconds_ = 0.1;
+ }
+
+ virtual ~TouchEmulatorTest() {}
+
+ // testing::Test
+ virtual void SetUp() OVERRIDE {
+#if defined(USE_AURA)
+ aura::Env::CreateInstance();
+ screen_.reset(aura::TestScreen::Create());
+ gfx::Screen::SetScreenInstance(gfx::SCREEN_TYPE_NATIVE, screen_.get());
+#endif
+
+ emulator_.reset(new TouchEmulator(this));
+ emulator_->Enable(true /* allow_pinch */);
+ }
+
+ virtual void TearDown() OVERRIDE {
+ emulator_->Disable();
+ EXPECT_EQ("", ExpectedEvents());
+
+#if defined(USE_AURA)
+ aura::Env::DeleteInstance();
+ screen_.reset();
+#endif
+ }
+
+ virtual void ForwardGestureEvent(
+ const blink::WebGestureEvent& event) OVERRIDE {
+ forwarded_events_.push_back(event.type);
+ }
+
+ virtual void ForwardTouchEvent(
+ const blink::WebTouchEvent& event) OVERRIDE {
+ forwarded_events_.push_back(event.type);
+ EXPECT_EQ(1U, event.touchesLength);
+ EXPECT_EQ(last_mouse_x_, event.touches[0].position.x);
+ EXPECT_EQ(last_mouse_y_, event.touches[0].position.y);
+ emulator()->HandleTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+ }
+
+ virtual void SetCursor(const WebCursor& cursor) OVERRIDE {}
+
+ protected:
+ TouchEmulator* emulator() const {
+ return emulator_.get();
+ }
+
+ int modifiers() const {
+ return shift_pressed_ ? WebInputEvent::ShiftKey : 0;
+ }
+
+ std::string ExpectedEvents() {
+ std::string result;
+ for (size_t i = 0; i < forwarded_events_.size(); ++i) {
+ if (i != 0)
+ result += " ";
+ result += WebInputEventTraits::GetName(forwarded_events_[i]);
+ }
+ forwarded_events_.clear();
+ return result;
+ }
+
+ double GetNextEventTimeSeconds() {
+ last_event_time_seconds_ += event_time_delta_seconds_;
+ return last_event_time_seconds_;
+ }
+
+ void set_event_time_delta_seconds_(double delta) {
+ event_time_delta_seconds_ = delta;
+ }
+
+ void SendKeyboardEvent(WebInputEvent::Type type) {
+ WebKeyboardEvent event;
+ event.timeStampSeconds = GetNextEventTimeSeconds();
+ event.type = type;
+ event.modifiers = modifiers();
+ emulator()->HandleKeyboardEvent(event);
+ }
+
+ void PressShift() {
+ DCHECK(!shift_pressed_);
+ shift_pressed_ = true;
+ SendKeyboardEvent(WebInputEvent::KeyDown);
+ }
+
+ void ReleaseShift() {
+ DCHECK(shift_pressed_);
+ shift_pressed_ = false;
+ SendKeyboardEvent(WebInputEvent::KeyUp);
+ }
+
+ void SendMouseEvent(WebInputEvent::Type type, int x, int y) {
+ WebMouseEvent event;
+ event.timeStampSeconds = GetNextEventTimeSeconds();
+ event.type = type;
+ event.button = mouse_pressed_ ? WebMouseEvent::ButtonLeft :
+ WebMouseEvent::ButtonNone;
+ event.modifiers = modifiers();
+ last_mouse_x_ = x;
+ last_mouse_y_ = y;
+ event.x = event.windowX = event.globalX = x;
+ event.y = event.windowY = event.globalY = y;
+ emulator()->HandleMouseEvent(event);
+ }
+
+ void MouseDown(int x, int y) {
+ DCHECK(!mouse_pressed_);
+ if (x != last_mouse_x_ || y != last_mouse_y_)
+ SendMouseEvent(WebInputEvent::MouseMove, x, y);
+ mouse_pressed_ = true;
+ SendMouseEvent(WebInputEvent::MouseDown, x, y);
+ }
+
+ void MouseDrag(int x, int y) {
+ DCHECK(mouse_pressed_);
+ SendMouseEvent(WebInputEvent::MouseMove, x, y);
+ }
+
+ void MouseMove(int x, int y) {
+ DCHECK(!mouse_pressed_);
+ SendMouseEvent(WebInputEvent::MouseMove, x, y);
+ }
+
+ void MouseUp(int x, int y) {
+ DCHECK(mouse_pressed_);
+ if (x != last_mouse_x_ || y != last_mouse_y_)
+ SendMouseEvent(WebInputEvent::MouseMove, x, y);
+ SendMouseEvent(WebInputEvent::MouseUp, x, y);
+ mouse_pressed_ = false;
+ }
+
+ private:
+ scoped_ptr<TouchEmulator> emulator_;
+ std::vector<WebInputEvent::Type> forwarded_events_;
+#if defined(USE_AURA)
+ scoped_ptr<gfx::Screen> screen_;
+#endif
+ double last_event_time_seconds_;
+ double event_time_delta_seconds_;
+ bool shift_pressed_;
+ bool mouse_pressed_;
+ int last_mouse_x_;
+ int last_mouse_y_;
+ base::MessageLoopForUI message_loop_;
+};
+
+
+TEST_F(TouchEmulatorTest, NoTouches) {
+ MouseMove(100, 200);
+ MouseMove(300, 300);
+ EXPECT_EQ("", ExpectedEvents());
+}
+
+TEST_F(TouchEmulatorTest, Touch) {
+ MouseMove(100, 200);
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDown(100, 200);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+ MouseUp(200, 200);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate"
+ " TouchEnd GestureScrollEnd",
+ ExpectedEvents());
+}
+
+TEST_F(TouchEmulatorTest, MultipleTouches) {
+ MouseMove(100, 200);
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDown(100, 200);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+ MouseUp(200, 200);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate"
+ " TouchEnd GestureScrollEnd",
+ ExpectedEvents());
+ MouseMove(300, 200);
+ MouseMove(200, 200);
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDown(300, 200);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+ MouseDrag(300, 300);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ ExpectedEvents());
+ MouseDrag(300, 400);
+ EXPECT_EQ("TouchMove GestureScrollUpdate", ExpectedEvents());
+ MouseUp(300, 500);
+ EXPECT_EQ(
+ "TouchMove GestureScrollUpdate TouchEnd GestureScrollEnd",
+ ExpectedEvents());
+}
+
+TEST_F(TouchEmulatorTest, Pinch) {
+ MouseDown(100, 200);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+ MouseDrag(200, 200);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ ExpectedEvents());
+ PressShift();
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDrag(300, 200);
+ EXPECT_EQ("TouchMove GesturePinchBegin", ExpectedEvents());
+ ReleaseShift();
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDrag(400, 200);
+ EXPECT_EQ(
+ "TouchMove GesturePinchEnd GestureScrollUpdate",
+ ExpectedEvents());
+ MouseUp(400, 200);
+ EXPECT_EQ("TouchEnd GestureScrollEnd", ExpectedEvents());
+}
+
+TEST_F(TouchEmulatorTest, DisableAndReenable) {
+ MouseDown(100, 200);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+ MouseDrag(200, 200);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ ExpectedEvents());
+ PressShift();
+ MouseDrag(300, 200);
+ EXPECT_EQ("TouchMove GesturePinchBegin", ExpectedEvents());
+
+ // Disable while pinch is in progress.
+ emulator()->Disable();
+ EXPECT_EQ("TouchCancel GesturePinchEnd GestureScrollEnd", ExpectedEvents());
+ MouseUp(300, 200);
+ ReleaseShift();
+ MouseMove(300, 300);
+ EXPECT_EQ("", ExpectedEvents());
+
+ emulator()->Enable(true /* allow_pinch */);
+ MouseDown(300, 300);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+ MouseDrag(300, 400);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ ExpectedEvents());
+
+ // Disable while scroll is in progress.
+ emulator()->Disable();
+ EXPECT_EQ("TouchCancel GestureScrollEnd", ExpectedEvents());
+}
+
+TEST_F(TouchEmulatorTest, MouseMovesDropped) {
+ MouseMove(100, 200);
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDown(100, 200);
+ EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
+
+ // Mouse move after mouse down is never dropped.
+ set_event_time_delta_seconds_(0.001);
+ MouseDrag(200, 200);
+ EXPECT_EQ(
+ "TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ ExpectedEvents());
+
+ // The following mouse moves are dropped.
+ MouseDrag(300, 200);
+ EXPECT_EQ("", ExpectedEvents());
+ MouseDrag(350, 200);
+ EXPECT_EQ("", ExpectedEvents());
+
+ // Dispatching again.
+ set_event_time_delta_seconds_(0.1);
+ MouseDrag(400, 200);
+ EXPECT_EQ(
+ "TouchMove GestureScrollUpdate",
+ ExpectedEvents());
+ MouseUp(400, 200);
+ EXPECT_EQ(
+ "TouchEnd GestureScrollEnd",
+ ExpectedEvents());
+}
+
+} // namespace content
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 32856f9..fc2b0ae 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -38,6 +38,7 @@
#include "content/browser/renderer_host/input/synthetic_gesture_controller.h"
#include "content/browser/renderer_host/input/synthetic_gesture_target.h"
#include "content/browser/renderer_host/input/timeout_monitor.h"
+#include "content/browser/renderer_host/input/touch_emulator.h"
#include "content/browser/renderer_host/overscroll_controller.h"
#include "content/browser/renderer_host/render_process_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
@@ -239,6 +240,8 @@ RenderWidgetHostImpl::RenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
input_router_.reset(new InputRouterImpl(process_, this, this, routing_id_));
+ touch_emulator_.reset();
+
#if defined(USE_AURA)
bool overscroll_enabled = CommandLine::ForCurrentProcess()->
GetSwitchValueASCII(switches::kOverscrollHistoryNavigation) != "0";
@@ -479,6 +482,8 @@ bool RenderWidgetHostImpl::OnMessageReceived(const IPC::Message &msg) {
IPC_MESSAGE_HANDLER(ViewHostMsg_Focus, OnFocus)
IPC_MESSAGE_HANDLER(ViewHostMsg_Blur, OnBlur)
IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnSetCursor)
+ IPC_MESSAGE_HANDLER(ViewHostMsg_SetTouchEventEmulationEnabled,
+ OnSetTouchEventEmulationEnabled)
IPC_MESSAGE_HANDLER(ViewHostMsg_TextInputTypeChanged,
OnTextInputTypeChanged)
IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCancelComposition,
@@ -677,10 +682,16 @@ void RenderWidgetHostImpl::Blur() {
if (overscroll_controller_)
overscroll_controller_->Cancel();
+ if (touch_emulator_)
+ touch_emulator_->CancelTouch();
+
Send(new InputMsg_SetFocus(routing_id_, false));
}
void RenderWidgetHostImpl::LostCapture() {
+ if (touch_emulator_)
+ touch_emulator_->CancelTouch();
+
Send(new InputMsg_MouseCaptureLost(routing_id_));
}
@@ -984,6 +995,9 @@ void RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(
if (IgnoreInputEvents())
return;
+ if (touch_emulator_ && touch_emulator_->HandleMouseEvent(mouse_event))
+ return;
+
input_router_->SendMouseEvent(MouseEventWithLatencyInfo(mouse_event,
latency_info));
}
@@ -1007,6 +1021,9 @@ void RenderWidgetHostImpl::ForwardWheelEventWithLatencyInfo(
if (IgnoreInputEvents())
return;
+ if (touch_emulator_ && touch_emulator_->HandleMouseWheelEvent(wheel_event))
+ return;
+
input_router_->SendWheelEvent(MouseWheelEventWithLatencyInfo(wheel_event,
latency_info));
}
@@ -1056,6 +1073,11 @@ void RenderWidgetHostImpl::ForwardGestureEventWithLatencyInfo(
input_router_->SendGestureEvent(gesture_with_latency);
}
+void RenderWidgetHostImpl::ForwardTouchEvent(
+ const blink::WebTouchEvent& touch_event) {
+ ForwardTouchEventWithLatencyInfo(touch_event, ui::LatencyInfo());
+}
+
void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
const blink::WebTouchEvent& touch_event,
const ui::LatencyInfo& ui_latency) {
@@ -1131,6 +1153,9 @@ void RenderWidgetHostImpl::ForwardKeyboardEvent(
suppress_next_char_events_ = false;
}
+ if (touch_emulator_ && touch_emulator_->HandleKeyboardEvent(key_event))
+ return;
+
input_router_->SendKeyboardEvent(
key_event,
CreateRWHLatencyInfoIfNotExist(NULL, key_event.type),
@@ -1151,6 +1176,12 @@ void RenderWidgetHostImpl::QueueSyntheticGesture(
}
}
+void RenderWidgetHostImpl::SetCursor(const WebCursor& cursor) {
+ if (!view_)
+ return;
+ view_->UpdateCursor(cursor);
+}
+
void RenderWidgetHostImpl::SendCursorVisibilityState(bool is_visible) {
Send(new InputMsg_CursorVisibilityChange(GetRoutingID(), is_visible));
}
@@ -1815,10 +1846,19 @@ void RenderWidgetHostImpl::OnBlur() {
}
void RenderWidgetHostImpl::OnSetCursor(const WebCursor& cursor) {
- if (!view_) {
- return;
+ SetCursor(cursor);
+}
+
+void RenderWidgetHostImpl::OnSetTouchEventEmulationEnabled(
+ bool enabled, bool allow_pinch) {
+ if (enabled) {
+ if (!touch_emulator_)
+ touch_emulator_.reset(new TouchEmulator(this));
+ touch_emulator_->Enable(allow_pinch);
+ } else {
+ if (touch_emulator_)
+ touch_emulator_->Disable();
}
- view_->UpdateCursor(cursor);
}
void RenderWidgetHostImpl::OnTextInputTypeChanged(
@@ -2156,6 +2196,10 @@ void RenderWidgetHostImpl::OnTouchEventAck(
ui::INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT, 0, 0);
}
ComputeTouchLatency(touch_event.latency);
+
+ if (touch_emulator_ && touch_emulator_->HandleTouchEventAck(ack_result))
+ return;
+
if (view_)
view_->ProcessAckedTouchEvent(touch_event, ack_result);
}
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 1707018..6fb3203 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -26,6 +26,7 @@
#include "content/browser/renderer_host/input/input_ack_handler.h"
#include "content/browser/renderer_host/input/input_router_client.h"
#include "content/browser/renderer_host/input/synthetic_gesture.h"
+#include "content/browser/renderer_host/input/touch_emulator_client.h"
#include "content/common/input/synthetic_gesture_packet.h"
#include "content/common/view_message_enums.h"
#include "content/port/browser/event_with_latency_info.h"
@@ -84,6 +85,7 @@ class RenderWidgetHostDelegate;
class RenderWidgetHostViewPort;
class SyntheticGestureController;
class TimeoutMonitor;
+class TouchEmulator;
class WebCursor;
struct EditCommand;
@@ -92,6 +94,7 @@ struct EditCommand;
class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
public InputRouterClient,
public InputAckHandler,
+ public TouchEmulatorClient,
public IPC::Listener {
public:
// routing_id can be MSG_ROUTING_NONE, in which case the next available
@@ -298,7 +301,6 @@ class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
// Forwards the given message to the renderer. These are called by the view
// when it has received a message.
- void ForwardGestureEvent(const blink::WebGestureEvent& gesture_event);
void ForwardGestureEventWithLatencyInfo(
const blink::WebGestureEvent& gesture_event,
const ui::LatencyInfo& ui_latency);
@@ -312,6 +314,13 @@ class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
const blink::WebMouseWheelEvent& wheel_event,
const ui::LatencyInfo& ui_latency);
+ // TouchEmulatorClient overrides.
+ virtual void ForwardGestureEvent(
+ const blink::WebGestureEvent& gesture_event) OVERRIDE;
+ virtual void ForwardTouchEvent(
+ const blink::WebTouchEvent& touch_event) OVERRIDE;
+ virtual void SetCursor(const WebCursor& cursor) OVERRIDE;
+
// Queues a synthetic gesture for testing purposes. Invokes the on_complete
// callback when the gesture is finished running.
void QueueSyntheticGesture(
@@ -658,6 +667,7 @@ class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
virtual void OnFocus();
virtual void OnBlur();
void OnSetCursor(const WebCursor& cursor);
+ void OnSetTouchEventEmulationEnabled(bool enabled, bool allow_pinch);
void OnTextInputTypeChanged(ui::TextInputType type,
ui::TextInputMode input_mode,
bool can_compose_inline);
@@ -903,6 +913,8 @@ class CONTENT_EXPORT RenderWidgetHostImpl : virtual public RenderWidgetHost,
scoped_ptr<SyntheticGestureController> synthetic_gesture_controller_;
+ scoped_ptr<TouchEmulator> touch_emulator_;
+
// Receives and handles all input events.
scoped_ptr<InputRouter> input_router_;
diff --git a/content/browser/renderer_host/render_widget_host_unittest.cc b/content/browser/renderer_host/render_widget_host_unittest.cc
index 4dc6d24..08a26e8 100644
--- a/content/browser/renderer_host/render_widget_host_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_unittest.cc
@@ -48,6 +48,7 @@ using base::TimeDelta;
using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebKeyboardEvent;
+using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;
@@ -199,6 +200,7 @@ class MockRenderWidgetHost : public RenderWidgetHostImpl {
: RenderWidgetHostImpl(delegate, process, routing_id, false),
unresponsive_timer_fired_(false) {
input_router_impl_ = static_cast<InputRouterImpl*>(input_router_.get());
+ acked_touch_event_type_ = blink::WebInputEvent::Undefined;
}
// Allow poking at a few private members.
@@ -261,6 +263,18 @@ class MockRenderWidgetHost : public RenderWidgetHostImpl {
return touch_event_queue().empty();
}
+ virtual void OnTouchEventAck(
+ const TouchEventWithLatencyInfo& event,
+ InputEventAckState ack_result) OVERRIDE {
+ // Sniff touch acks.
+ acked_touch_event_type_ = event.event.type;
+ RenderWidgetHostImpl::OnTouchEventAck(event, ack_result);
+ }
+
+ WebInputEvent::Type acked_touch_event_type() const {
+ return acked_touch_event_type_;
+ }
+
bool ScrollStateIsContentScrolling() const {
return scroll_state() == OverscrollController::STATE_CONTENT_SCROLLING;
}
@@ -321,6 +335,7 @@ class MockRenderWidgetHost : public RenderWidgetHostImpl {
private:
bool unresponsive_timer_fired_;
+ WebInputEvent::Type acked_touch_event_type_;
// |input_router_impl_| and |mock_input_router_| are owned by
// RenderWidgetHostImpl. The handles below are provided for convenience so
@@ -582,7 +597,10 @@ class RenderWidgetHostTest : public testing::Test {
RenderWidgetHostTest()
: process_(NULL),
handle_key_press_event_(false),
- handle_mouse_event_(false) {
+ handle_mouse_event_(false),
+ simulated_event_time_delta_seconds_(0) {
+ last_simulated_event_time_seconds_ =
+ (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF();
}
virtual ~RenderWidgetHostTest() {
}
@@ -645,10 +663,20 @@ class RenderWidgetHostTest : public testing::Test {
host_->OnMessageReceived(*response);
}
+ double GetNextSimulatedEventTimeSeconds() {
+ last_simulated_event_time_seconds_ += simulated_event_time_delta_seconds_;
+ return last_simulated_event_time_seconds_;
+ }
+
void SimulateKeyboardEvent(WebInputEvent::Type type) {
- WebKeyboardEvent event = SyntheticWebKeyboardEventBuilder::Build(type);
- NativeWebKeyboardEvent native_event;
- memcpy(&native_event, &event, sizeof(event));
+ SimulateKeyboardEvent(type, 0);
+ }
+
+ void SimulateKeyboardEvent(WebInputEvent::Type type, int modifiers) {
+ WebKeyboardEvent event = SyntheticWebKeyboardEventBuilder::Build(type);
+ event.modifiers = modifiers;
+ NativeWebKeyboardEvent native_event;
+ memcpy(&native_event, &event, sizeof(event));
host_->ForwardKeyboardEvent(native_event);
}
@@ -679,11 +707,17 @@ class RenderWidgetHostTest : public testing::Test {
}
void SimulateMouseMove(int x, int y, int modifiers) {
- host_->ForwardMouseEvent(
- SyntheticWebMouseEventBuilder::Build(WebInputEvent::MouseMove,
- x,
- y,
- modifiers));
+ SimulateMouseEvent(WebInputEvent::MouseMove, x, y, modifiers, false);
+ }
+
+ void SimulateMouseEvent(
+ WebInputEvent::Type type, int x, int y, int modifiers, bool pressed) {
+ WebMouseEvent event =
+ SyntheticWebMouseEventBuilder::Build(type, x, y, modifiers);
+ if (pressed)
+ event.button = WebMouseEvent::ButtonLeft;
+ event.timeStampSeconds = GetNextSimulatedEventTimeSeconds();
+ host_->ForwardMouseEvent(event);
}
void SimulateWheelEventWithPhase(WebMouseWheelEvent::Phase phase) {
@@ -788,6 +822,8 @@ class RenderWidgetHostTest : public testing::Test {
scoped_ptr<gfx::Screen> screen_;
bool handle_key_press_event_;
bool handle_mouse_event_;
+ double last_simulated_event_time_seconds_;
+ double simulated_event_time_delta_seconds_;
private:
SyntheticWebTouchEvent touch_event_;
@@ -2381,6 +2417,203 @@ TEST_F(RenderWidgetHostTest, OverscrollResetsOnBlur) {
process_->sink().ClearMessages();
}
+std::string GetInputMessageTypes(RenderWidgetHostProcess* process) {
+ const WebInputEvent* event = NULL;
+ ui::LatencyInfo latency_info;
+ bool is_keyboard_shortcut;
+ std::string result;
+ for (size_t i = 0; i < process->sink().message_count(); ++i) {
+ const IPC::Message *message = process->sink().GetMessageAt(i);
+ EXPECT_EQ(InputMsg_HandleInputEvent::ID, message->type());
+ EXPECT_TRUE(InputMsg_HandleInputEvent::Read(
+ message, &event, &latency_info, &is_keyboard_shortcut));
+ if (i != 0)
+ result += " ";
+ result += WebInputEventTraits::GetName(event->type);
+ }
+ process->sink().ClearMessages();
+ return result;
+}
+
+TEST_F(RenderWidgetHostTest, TouchEmulator) {
+ simulated_event_time_delta_seconds_ = 0.1;
+ host_->DisableGestureDebounce();
+ // Immediately ack all touches instead of sending them to the renderer.
+ host_->OnMessageReceived(ViewHostMsg_HasTouchEventHandlers(0, false));
+ host_->OnMessageReceived(
+ ViewHostMsg_SetTouchEventEmulationEnabled(0, true, true));
+ process_->sink().ClearMessages();
+ view_->set_bounds(gfx::Rect(0, 0, 400, 200));
+ view_->Show();
+
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 10, 0, false);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Mouse press becomes touch start which in turn becomes tap.
+ SimulateMouseEvent(WebInputEvent::MouseDown, 10, 10, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchStart, host_->acked_touch_event_type());
+ EXPECT_EQ("GestureTapDown", GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureTapDown,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+ // Mouse drag generates touch move, cancels tap and starts scroll.
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 30, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ(
+ "GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureTapCancel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Mouse drag with shift becomes pinch.
+ SimulateMouseEvent(
+ WebInputEvent::MouseMove, 10, 40, WebInputEvent::ShiftKey, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ("GesturePinchBegin",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GesturePinchBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateMouseEvent(
+ WebInputEvent::MouseMove, 10, 50, WebInputEvent::ShiftKey, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ("GesturePinchUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GesturePinchUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Mouse drag without shift becomes scroll again.
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 60, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ("GesturePinchEnd GestureScrollUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 70, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ("GestureScrollUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateMouseEvent(WebInputEvent::MouseUp, 10, 70, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchEnd, host_->acked_touch_event_type());
+ EXPECT_EQ("GestureScrollEnd", GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Mouse move does nothing.
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 80, 0, false);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Another mouse down continues scroll.
+ SimulateMouseEvent(WebInputEvent::MouseDown, 10, 80, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchStart, host_->acked_touch_event_type());
+ EXPECT_EQ("GestureTapDown", GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureTapDown,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 100, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ(
+ "GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureTapCancel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Another pinch.
+ SimulateMouseEvent(
+ WebInputEvent::MouseMove, 10, 110, WebInputEvent::ShiftKey, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ("GesturePinchBegin",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GesturePinchBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ SimulateMouseEvent(
+ WebInputEvent::MouseMove, 10, 120, WebInputEvent::ShiftKey, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ("GesturePinchUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GesturePinchUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Turn off emulation during a pinch.
+ host_->OnMessageReceived(
+ ViewHostMsg_SetTouchEventEmulationEnabled(0, false, false));
+ EXPECT_EQ(WebInputEvent::TouchCancel, host_->acked_touch_event_type());
+ EXPECT_EQ("GesturePinchEnd GestureScrollEnd",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GesturePinchEnd,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Mouse event should pass untouched.
+ SimulateMouseEvent(
+ WebInputEvent::MouseMove, 10, 10, WebInputEvent::ShiftKey, true);
+ EXPECT_EQ("MouseMove", GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::MouseMove,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Turn on emulation.
+ host_->OnMessageReceived(
+ ViewHostMsg_SetTouchEventEmulationEnabled(0, true, true));
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Another touch.
+ SimulateMouseEvent(WebInputEvent::MouseDown, 10, 10, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchStart, host_->acked_touch_event_type());
+ EXPECT_EQ("GestureTapDown", GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureTapDown,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+
+ // Scroll.
+ SimulateMouseEvent(WebInputEvent::MouseMove, 10, 30, 0, true);
+ EXPECT_EQ(WebInputEvent::TouchMove, host_->acked_touch_event_type());
+ EXPECT_EQ(
+ "GestureTapCancel GestureScrollBegin GestureScrollUpdate",
+ GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureTapCancel,
+ INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollBegin,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ SendInputEventACK(WebInputEvent::GestureScrollUpdate,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+
+ // Turn off emulation during a scroll.
+ host_->OnMessageReceived(
+ ViewHostMsg_SetTouchEventEmulationEnabled(0, false, false));
+ EXPECT_EQ(WebInputEvent::TouchCancel, host_->acked_touch_event_type());
+
+ EXPECT_EQ("GestureScrollEnd", GetInputMessageTypes(process_));
+ SendInputEventACK(WebInputEvent::GestureScrollEnd,
+ INPUT_EVENT_ACK_STATE_CONSUMED);
+ EXPECT_EQ(0U, process_->sink().message_count());
+}
+
#define TEST_InputRouterRoutes_NOARGS(INPUTMSG) \
TEST_F(RenderWidgetHostTest, InputRouterRoutes##INPUTMSG) { \
host_->SetupForInputRouterTest(); \
diff --git a/content/browser/resources/devtools/devtools_pinch_cursor.png b/content/browser/resources/devtools/devtools_pinch_cursor.png
new file mode 100644
index 0000000..8f057f0
--- /dev/null
+++ b/content/browser/resources/devtools/devtools_pinch_cursor.png
Binary files differ
diff --git a/content/browser/resources/devtools/devtools_touch_cursor.png b/content/browser/resources/devtools/devtools_touch_cursor.png
new file mode 100644
index 0000000..f8d8a01
--- /dev/null
+++ b/content/browser/resources/devtools/devtools_touch_cursor.png
Binary files differ
diff --git a/content/child/blink_platform_impl.cc b/content/child/blink_platform_impl.cc
index 9b2898e..237d2c8 100644
--- a/content/child/blink_platform_impl.cc
+++ b/content/child/blink_platform_impl.cc
@@ -701,6 +701,7 @@ const DataResource kDataResources[] = {
{ "generatePassword", IDR_PASSWORD_GENERATION_ICON, ui::SCALE_FACTOR_100P },
{ "generatePasswordHover",
IDR_PASSWORD_GENERATION_ICON_HOVER, ui::SCALE_FACTOR_100P },
+ // TODO(dgozman): remove this after moving to content-based touch emulation.
{ "syntheticTouchCursor",
IDR_SYNTHETIC_TOUCH_CURSOR, ui::SCALE_FACTOR_100P },
};
diff --git a/content/common/view_messages.h b/content/common/view_messages.h
index 67dac64..5bb763a 100644
--- a/content/common/view_messages.h
+++ b/content/common/view_messages.h
@@ -1234,6 +1234,12 @@ IPC_MESSAGE_ROUTED1(ViewHostMsg_FocusedNodeChanged,
IPC_MESSAGE_ROUTED1(ViewHostMsg_SetCursor, content::WebCursor)
+// Message sent from renderer requesting touch emulation using mouse.
+// Shift-scrolling should be converted to pinch, if |allow_pinch| is true.
+IPC_MESSAGE_ROUTED2(ViewHostMsg_SetTouchEventEmulationEnabled,
+ bool /* enabled */,
+ bool /* allow_pinch */)
+
// Used to set a cookie. The cookie is set asynchronously, but will be
// available to a subsequent ViewHostMsg_GetCookies request.
IPC_MESSAGE_CONTROL4(ViewHostMsg_SetCookie,
diff --git a/content/content_browser.gypi b/content/content_browser.gypi
index c0ea977d..cbd0d38 100644
--- a/content/content_browser.gypi
+++ b/content/content_browser.gypi
@@ -932,6 +932,9 @@
'browser/renderer_host/input/tap_suppression_controller_client.h',
'browser/renderer_host/input/timeout_monitor.cc',
'browser/renderer_host/input/timeout_monitor.h',
+ 'browser/renderer_host/input/touch_emulator.cc',
+ 'browser/renderer_host/input/touch_emulator.h',
+ 'browser/renderer_host/input/touch_emulator_client.h',
'browser/renderer_host/input/touch_event_queue.cc',
'browser/renderer_host/input/touch_event_queue.h',
'browser/renderer_host/input/touch_action_filter.cc',
diff --git a/content/content_resources.grd b/content/content_resources.grd
index e689fc6..408aa66 100644
--- a/content/content_resources.grd
+++ b/content/content_resources.grd
@@ -14,6 +14,8 @@
<include name="IDR_ACCESSIBILITY_HTML" file="browser/resources/accessibility/accessibility.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_ACCESSIBILITY_CSS" file="browser/resources/accessibility/accessibility.css" type="BINDATA" />
<include name="IDR_ACCESSIBILITY_JS" file="browser/resources/accessibility/accessibility.js" flattenhtml="true" type="BINDATA" />
+ <include name="IDR_DEVTOOLS_PINCH_CURSOR_ICON" file="browser/resources/devtools/devtools_pinch_cursor.png" type="BINDATA" />
+ <include name="IDR_DEVTOOLS_TOUCH_CURSOR_ICON" file="browser/resources/devtools/devtools_touch_cursor.png" type="BINDATA" />
<include name="IDR_GPU_INTERNALS_HTML" file="browser/resources/gpu/gpu_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
<include name="IDR_GPU_INTERNALS_JS" file="browser/resources/gpu/gpu_internals.js" flattenhtml="true" type="BINDATA" />
<include name="IDR_INDEXED_DB_INTERNALS_HTML" file="browser/resources/indexed_db/indexeddb_internals.html" flattenhtml="true" allowexternalscript="true" type="BINDATA" />
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index c398612..ddc9d3e 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -479,6 +479,7 @@
'browser/renderer_host/input/synthetic_gesture_controller_unittest.cc',
'browser/renderer_host/input/tap_suppression_controller_unittest.cc',
'browser/renderer_host/input/touch_action_filter_unittest.cc',
+ 'browser/renderer_host/input/touch_emulator_unittest.cc',
'browser/renderer_host/input/touch_event_queue_unittest.cc',
'browser/renderer_host/media/audio_input_device_manager_unittest.cc',
'browser/renderer_host/media/audio_renderer_host_unittest.cc',
diff --git a/content/renderer/devtools/devtools_agent.cc b/content/renderer/devtools/devtools_agent.cc
index 38b4b5e..3033733 100644
--- a/content/renderer/devtools/devtools_agent.cc
+++ b/content/renderer/devtools/devtools_agent.cc
@@ -251,6 +251,13 @@ void DevToolsAgent::disableDeviceEmulation() {
impl->DisableScreenMetricsEmulation();
}
+void DevToolsAgent::setTouchEventEmulationEnabled(
+ bool enabled, bool allow_pinch) {
+ RenderViewImpl* impl = static_cast<RenderViewImpl*>(render_view());
+ impl->Send(new ViewHostMsg_SetTouchEventEmulationEnabled(
+ impl->routing_id(), enabled, allow_pinch));
+}
+
#if defined(USE_TCMALLOC) && !defined(OS_WIN)
static void AllocationVisitor(void* data, const void* ptr) {
typedef blink::WebDevToolsAgentClient::AllocatedObjectVisitor Visitor;
diff --git a/content/renderer/devtools/devtools_agent.h b/content/renderer/devtools/devtools_agent.h
index fed4a68..9d58d97 100644
--- a/content/renderer/devtools/devtools_agent.h
+++ b/content/renderer/devtools/devtools_agent.h
@@ -76,6 +76,7 @@ class DevToolsAgent : public RenderViewObserver,
virtual void enableDeviceEmulation(
const blink::WebDeviceEmulationParams& params);
virtual void disableDeviceEmulation();
+ virtual void setTouchEventEmulationEnabled(bool enabled, bool allow_pinch);
void OnAttach();
void OnReattach(const std::string& agent_state);