diff options
author | dgozman@chromium.org <dgozman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-15 09:00:07 +0000 |
---|---|---|
committer | dgozman@chromium.org <dgozman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-04-15 09:00:07 +0000 |
commit | 471eeb53f64b1e576a42579a8cfd3f3376eef57e (patch) | |
tree | fa99f8a889fe00de7ec2fc083db8acb511ca7325 | |
parent | f203be26cd1e55f6b520da214731855ca2fafdba (diff) | |
download | chromium_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.cc | 371 | ||||
-rw-r--r-- | content/browser/renderer_host/input/touch_emulator.h | 100 | ||||
-rw-r--r-- | content/browser/renderer_host/input/touch_emulator_client.h | 26 | ||||
-rw-r--r-- | content/browser/renderer_host/input/touch_emulator_unittest.cc | 318 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host_impl.cc | 50 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host_impl.h | 14 | ||||
-rw-r--r-- | content/browser/renderer_host/render_widget_host_unittest.cc | 251 | ||||
-rw-r--r-- | content/browser/resources/devtools/devtools_pinch_cursor.png | bin | 0 -> 138 bytes | |||
-rw-r--r-- | content/browser/resources/devtools/devtools_touch_cursor.png | bin | 0 -> 343 bytes | |||
-rw-r--r-- | content/child/blink_platform_impl.cc | 1 | ||||
-rw-r--r-- | content/common/view_messages.h | 6 | ||||
-rw-r--r-- | content/content_browser.gypi | 3 | ||||
-rw-r--r-- | content/content_resources.grd | 2 | ||||
-rw-r--r-- | content/content_tests.gypi | 1 | ||||
-rw-r--r-- | content/renderer/devtools/devtools_agent.cc | 7 | ||||
-rw-r--r-- | content/renderer/devtools/devtools_agent.h | 1 |
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 Binary files differnew file mode 100644 index 0000000..8f057f0 --- /dev/null +++ b/content/browser/resources/devtools/devtools_pinch_cursor.png diff --git a/content/browser/resources/devtools/devtools_touch_cursor.png b/content/browser/resources/devtools/devtools_touch_cursor.png Binary files differnew file mode 100644 index 0000000..f8d8a01 --- /dev/null +++ b/content/browser/resources/devtools/devtools_touch_cursor.png 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); |