Commit c2693b64 authored by dgozman@chromium.org's avatar dgozman@chromium.org

Touch emulator: allow multiple touch streams.

    
When both native and emulated touch streams are available,
we should block one stream while another is active.
To achieve this RenderWidgetHostImpl gives TouchEmulator a chance
to handle native touch event, so it can be effectively blocked.
    
BUG=384522

Review URL: https://codereview.chromium.org/375863005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284475 0039d316-1c4b-4281-b951-d872f2087c98
parent 1aca1931
......@@ -47,7 +47,9 @@ TouchEmulator::TouchEmulator(TouchEmulatorClient* client)
: client_(client),
gesture_provider_(GetGestureProviderConfig(), this),
enabled_(false),
allow_pinch_(false) {
allow_pinch_(false),
emulated_stream_active_sequence_count_(0),
native_stream_active_sequence_count_(0) {
DCHECK(client_);
ResetState();
......@@ -85,7 +87,6 @@ void TouchEmulator::ResetState() {
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;
......@@ -155,7 +156,7 @@ bool TouchEmulator::HandleMouseEvent(const WebMouseEvent& mouse_event) {
if (FillTouchEventAndPoint(mouse_event) &&
gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_))) {
client_->ForwardTouchEvent(touch_event_);
ForwardTouchEventToClient();
}
// Do not pass mouse events to the renderer.
......@@ -167,7 +168,7 @@ bool TouchEmulator::HandleMouseWheelEvent(const WebMouseWheelEvent& event) {
return false;
// Send mouse wheel for easy scrolling when there is no active touch.
return touch_active_;
return emulated_stream_active_sequence_count_ > 0;
}
bool TouchEmulator::HandleKeyboardEvent(const WebKeyboardEvent& event) {
......@@ -193,11 +194,59 @@ bool TouchEmulator::HandleKeyboardEvent(const WebKeyboardEvent& event) {
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;
bool TouchEmulator::HandleTouchEvent(const blink::WebTouchEvent& event) {
// Block native event when emulated touch stream is active.
if (emulated_stream_active_sequence_count_)
return true;
bool is_sequence_start = WebTouchEventTraits::IsTouchSequenceStart(event);
// Do not allow middle-sequence event to pass through, if start was blocked.
if (!native_stream_active_sequence_count_ && !is_sequence_start)
return true;
if (is_sequence_start)
native_stream_active_sequence_count_++;
return false;
}
void TouchEmulator::ForwardTouchEventToClient() {
const bool event_consumed = true;
// Block emulated event when emulated native stream is active.
if (native_stream_active_sequence_count_) {
gesture_provider_.OnTouchEventAck(event_consumed);
return;
}
bool is_sequence_start =
WebTouchEventTraits::IsTouchSequenceStart(touch_event_);
// Do not allow middle-sequence event to pass through, if start was blocked.
if (!emulated_stream_active_sequence_count_ && !is_sequence_start) {
gesture_provider_.OnTouchEventAck(event_consumed);
return;
}
if (is_sequence_start)
emulated_stream_active_sequence_count_++;
client_->ForwardEmulatedTouchEvent(touch_event_);
}
bool TouchEmulator::HandleTouchEventAck(
const blink::WebTouchEvent& event, InputEventAckState ack_result) {
bool is_sequence_end = WebTouchEventTraits::IsTouchSequenceEnd(event);
if (emulated_stream_active_sequence_count_) {
if (is_sequence_end)
emulated_stream_active_sequence_count_--;
const bool event_consumed = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
gesture_provider_.OnTouchEventAck(event_consumed);
return true;
}
// We may have not seen native touch sequence start (when created in the
// middle of a sequence), so don't decrement sequence count below zero.
if (is_sequence_end && native_stream_active_sequence_count_)
native_stream_active_sequence_count_--;
return false;
}
void TouchEmulator::OnGestureEvent(const ui::GestureEventData& gesture) {
......@@ -267,16 +316,15 @@ void TouchEmulator::OnGestureEvent(const ui::GestureEventData& gesture) {
}
void TouchEmulator::CancelTouch() {
if (!touch_active_)
if (!emulated_stream_active_sequence_count_)
return;
WebTouchEventTraits::ResetTypeAndTouchStates(
WebInputEvent::TouchCancel,
(base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(),
&touch_event_);
touch_active_ = false;
if (gesture_provider_.OnTouchEvent(MotionEventWeb(touch_event_)))
client_->ForwardTouchEvent(touch_event_);
ForwardTouchEventToClient();
}
void TouchEmulator::UpdateCursor() {
......@@ -352,14 +400,12 @@ bool TouchEmulator::FillTouchEventAndPoint(const WebMouseEvent& mouse_event) {
switch (mouse_event.type) {
case WebInputEvent::MouseDown:
eventType = WebInputEvent::TouchStart;
touch_active_ = true;
break;
case WebInputEvent::MouseMove:
eventType = WebInputEvent::TouchMove;
break;
case WebInputEvent::MouseUp:
eventType = WebInputEvent::TouchEnd;
touch_active_ = false;
break;
default:
eventType = WebInputEvent::Undefined;
......
......@@ -23,15 +23,22 @@ class CONTENT_EXPORT TouchEmulator : public ui::GestureProviderClient {
void Enable(bool allow_pinch);
void Disable();
// Returns |true| if the event was consumed.
// Note that TouchEmulator should always listen to touch events and their acks
// (even in disabled state) to track native stream presence.
bool enabled() const { return enabled_; }
// Returns |true| if the event was consumed. Consumed event should not
// propagate any further.
// 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);
bool HandleTouchEvent(const blink::WebTouchEvent& event);
// Returns |true| if the event ack was consumed. Consumed ack should not
// propagate any further.
bool HandleTouchEventAck(InputEventAckState ack_result);
bool HandleTouchEventAck(const blink::WebTouchEvent& event,
InputEventAckState ack_result);
// Cancel any touches, for example, when focus is lost.
void CancelTouch();
......@@ -59,6 +66,8 @@ class CONTENT_EXPORT TouchEmulator : public ui::GestureProviderClient {
void PinchEnd(const blink::WebGestureEvent& event);
void ScrollEnd(const blink::WebGestureEvent& event);
void ForwardTouchEventToClient();
TouchEmulatorClient* const client_;
ui::FilteredGestureProvider gesture_provider_;
......@@ -83,7 +92,8 @@ class CONTENT_EXPORT TouchEmulator : public ui::GestureProviderClient {
bool shift_pressed_;
blink::WebTouchEvent touch_event_;
bool touch_active_;
int emulated_stream_active_sequence_count_;
int native_stream_active_sequence_count_;
// Whether we should suppress next fling cancel. This may happen when we
// did not send fling start in pinch mode.
......
......@@ -17,7 +17,7 @@ class CONTENT_EXPORT TouchEmulatorClient {
virtual ~TouchEmulatorClient() {}
virtual void ForwardGestureEvent(const blink::WebGestureEvent& event) = 0;
virtual void ForwardTouchEvent(const blink::WebTouchEvent& event) = 0;
virtual void ForwardEmulatedTouchEvent(const blink::WebTouchEvent& event) = 0;
virtual void SetCursor(const WebCursor& cursor) = 0;
};
......
......@@ -71,7 +71,7 @@ class TouchEmulatorTest : public testing::Test,
forwarded_events_.push_back(event.type);
}
virtual void ForwardTouchEvent(
virtual void ForwardEmulatedTouchEvent(
const blink::WebTouchEvent& event) OVERRIDE {
forwarded_events_.push_back(event.type);
EXPECT_EQ(1U, event.touchesLength);
......@@ -79,7 +79,8 @@ class TouchEmulatorTest : public testing::Test,
EXPECT_EQ(last_mouse_y_, event.touches[0].position.y);
int expectedCancelable = event.type != WebInputEvent::TouchCancel;
EXPECT_EQ(expectedCancelable, event.cancelable);
emulator()->HandleTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
emulator()->HandleTouchEventAck(
event, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
}
virtual void SetCursor(const WebCursor& cursor) OVERRIDE {}
......@@ -181,6 +182,66 @@ class TouchEmulatorTest : public testing::Test,
mouse_pressed_ = false;
}
bool TouchStart(int x, int y, bool ack) {
return SendTouchEvent(
WebInputEvent::TouchStart, WebTouchPoint::StatePressed, x, y, ack);
}
bool TouchMove(int x, int y, bool ack) {
return SendTouchEvent(
WebInputEvent::TouchMove, WebTouchPoint::StateMoved, x, y, ack);
}
bool TouchEnd(int x, int y, bool ack) {
return SendTouchEvent(
WebInputEvent::TouchEnd, WebTouchPoint::StateReleased, x, y, ack);
}
WebTouchEvent MakeTouchEvent(WebInputEvent::Type type,
WebTouchPoint::State state, int x, int y) {
WebTouchEvent event;
event.type = type;
event.timeStampSeconds = GetNextEventTimeSeconds();
event.touchesLength = 1;
event.touches[0].id = 0;
event.touches[0].state = state;
event.touches[0].position.x = x;
event.touches[0].position.y = y;
event.touches[0].screenPosition.x = x;
event.touches[0].screenPosition.y = y;
return event;
}
bool SendTouchEvent(WebInputEvent::Type type, WebTouchPoint::State state,
int x, int y, bool ack) {
WebTouchEvent event = MakeTouchEvent(type, state, x, y);
if (emulator()->HandleTouchEvent(event)) {
// Touch event is not forwarded.
return false;
}
if (ack) {
// Can't send ack if there are some pending acks.
DCHECK(!touch_events_to_ack_.size());
// Touch event is forwarded, ack should not be handled by emulator.
EXPECT_FALSE(emulator()->HandleTouchEventAck(
event, INPUT_EVENT_ACK_STATE_CONSUMED));
} else {
touch_events_to_ack_.push_back(event);
}
return true;
}
void AckOldestTouchEvent() {
DCHECK(touch_events_to_ack_.size());
WebTouchEvent event = touch_events_to_ack_[0];
touch_events_to_ack_.erase(touch_events_to_ack_.begin());
// Emulator should not handle ack from native stream.
EXPECT_FALSE(emulator()->HandleTouchEventAck(
event, INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS));
}
private:
scoped_ptr<TouchEmulator> emulator_;
std::vector<WebInputEvent::Type> forwarded_events_;
......@@ -193,6 +254,7 @@ class TouchEmulatorTest : public testing::Test,
bool mouse_pressed_;
int last_mouse_x_;
int last_mouse_y_;
std::vector<WebTouchEvent> touch_events_to_ack_;
base::MessageLoopForUI message_loop_;
};
......@@ -346,4 +408,86 @@ TEST_F(TouchEmulatorTest, MouseWheel) {
EXPECT_TRUE(SendMouseWheelEvent());
}
TEST_F(TouchEmulatorTest, MultipleTouchStreams) {
// Native stream should be blocked while emulated is active.
MouseMove(100, 200);
EXPECT_EQ("", ExpectedEvents());
MouseDown(100, 200);
EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
EXPECT_FALSE(TouchStart(10, 10, true));
EXPECT_FALSE(TouchMove(20, 20, true));
MouseUp(200, 200);
EXPECT_EQ(
"TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate"
" TouchEnd GestureScrollEnd",
ExpectedEvents());
EXPECT_FALSE(TouchEnd(20, 20, true));
// Emulated stream should be blocked while native is active.
EXPECT_TRUE(TouchStart(10, 10, true));
EXPECT_TRUE(TouchMove(20, 20, true));
MouseDown(300, 200);
EXPECT_EQ("", ExpectedEvents());
// Re-enabling in the middle of a touch sequence should not affect this.
emulator()->Disable();
emulator()->Enable(true);
MouseDrag(300, 300);
EXPECT_EQ("", ExpectedEvents());
MouseUp(300, 300);
EXPECT_EQ("", ExpectedEvents());
EXPECT_TRUE(TouchEnd(20, 20, true));
EXPECT_EQ("", ExpectedEvents());
// Late ack for TouchEnd should not mess things up.
EXPECT_TRUE(TouchStart(10, 10, false));
EXPECT_TRUE(TouchMove(20, 20, false));
emulator()->Disable();
EXPECT_TRUE(TouchEnd(20, 20, false));
EXPECT_TRUE(TouchStart(30, 30, false));
AckOldestTouchEvent(); // TouchStart.
emulator()->Enable(true);
AckOldestTouchEvent(); // TouchMove.
AckOldestTouchEvent(); // TouchEnd.
MouseDown(300, 200);
EXPECT_EQ("", ExpectedEvents());
MouseDrag(300, 300);
EXPECT_EQ("", ExpectedEvents());
MouseUp(300, 300);
EXPECT_EQ("", ExpectedEvents());
AckOldestTouchEvent(); // TouchStart.
MouseDown(300, 200);
EXPECT_EQ("", ExpectedEvents());
EXPECT_TRUE(TouchMove(30, 40, true));
EXPECT_TRUE(TouchEnd(30, 40, true));
MouseUp(300, 200);
EXPECT_EQ("", ExpectedEvents());
// Emulation should be back to normal.
MouseDown(100, 200);
EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
MouseUp(200, 200);
EXPECT_EQ(
"TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate"
" TouchEnd GestureScrollEnd",
ExpectedEvents());
}
TEST_F(TouchEmulatorTest, MultipleTouchStreamsLateEnable) {
// Enabling in the middle of native touch sequence should be handled.
// Send artificial late TouchEnd ack, like it is the first thing emulator
// does see.
WebTouchEvent event = MakeTouchEvent(
WebInputEvent::TouchEnd, WebTouchPoint::StateReleased, 10, 10);
EXPECT_FALSE(emulator()->HandleTouchEventAck(
event, INPUT_EVENT_ACK_STATE_CONSUMED));
MouseDown(100, 200);
EXPECT_EQ("TouchStart GestureTapDown", ExpectedEvents());
MouseUp(200, 200);
EXPECT_EQ(
"TouchMove GestureTapCancel GestureScrollBegin GestureScrollUpdate"
" TouchEnd GestureScrollEnd",
ExpectedEvents());
}
} // namespace content
......@@ -961,9 +961,13 @@ void RenderWidgetHostImpl::ForwardGestureEventWithLatencyInfo(
input_router_->SendGestureEvent(gesture_with_latency);
}
void RenderWidgetHostImpl::ForwardTouchEvent(
void RenderWidgetHostImpl::ForwardEmulatedTouchEvent(
const blink::WebTouchEvent& touch_event) {
ForwardTouchEventWithLatencyInfo(touch_event, ui::LatencyInfo());
TRACE_EVENT0("input", "RenderWidgetHostImpl::ForwardEmulatedTouchEvent");
ui::LatencyInfo latency_info =
CreateRWHLatencyInfoIfNotExist(NULL, touch_event.type);
TouchEventWithLatencyInfo touch_with_latency(touch_event, latency_info);
input_router_->SendTouchEvent(touch_with_latency);
}
void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
......@@ -977,6 +981,16 @@ void RenderWidgetHostImpl::ForwardTouchEventWithLatencyInfo(
ui::LatencyInfo latency_info =
CreateRWHLatencyInfoIfNotExist(&ui_latency, touch_event.type);
TouchEventWithLatencyInfo touch_with_latency(touch_event, latency_info);
if (touch_emulator_ &&
touch_emulator_->HandleTouchEvent(touch_with_latency.event)) {
if (view_) {
view_->ProcessAckedTouchEvent(
touch_with_latency, INPUT_EVENT_ACK_STATE_CONSUMED);
}
return;
}
input_router_->SendTouchEvent(touch_with_latency);
}
......@@ -1856,8 +1870,10 @@ void RenderWidgetHostImpl::OnTouchEventAck(
}
ComputeTouchLatency(touch_event.latency);
if (touch_emulator_ && touch_emulator_->HandleTouchEventAck(ack_result))
if (touch_emulator_ &&
touch_emulator_->HandleTouchEventAck(event.event, ack_result)) {
return;
}
if (view_)
view_->ProcessAckedTouchEvent(touch_event, ack_result);
......@@ -1886,6 +1902,13 @@ bool RenderWidgetHostImpl::IgnoreInputEvents() const {
}
bool RenderWidgetHostImpl::ShouldForwardTouchEvent() const {
// It's important that the emulator sees a complete native touch stream,
// allowing it to perform touch filtering as appropriate.
// TODO(dgozman): Remove when touch stream forwarding issues resolved, see
// crbug.com/375940.
if (touch_emulator_ && touch_emulator_->enabled())
return true;
return input_router_->ShouldForwardTouchEvent();
}
......
......@@ -297,10 +297,10 @@ class CONTENT_EXPORT RenderWidgetHostImpl
const blink::WebMouseWheelEvent& wheel_event,
const ui::LatencyInfo& ui_latency);
// TouchEmulatorClient overrides.
// TouchEmulatorClient implementation.
virtual void ForwardGestureEvent(
const blink::WebGestureEvent& gesture_event) OVERRIDE;
virtual void ForwardTouchEvent(
virtual void ForwardEmulatedTouchEvent(
const blink::WebTouchEvent& touch_event) OVERRIDE;
virtual void SetCursor(const WebCursor& cursor) OVERRIDE;
......
......@@ -31,6 +31,20 @@ bool WebTouchEventTraits::IsTouchSequenceStart(const WebTouchEvent& event) {
return AllTouchPointsHaveState(event, blink::WebTouchPoint::StatePressed);
}
bool WebTouchEventTraits::IsTouchSequenceEnd(const WebTouchEvent& event) {
if (event.type != WebInputEvent::TouchEnd &&
event.type != WebInputEvent::TouchCancel)
return false;
if (!event.touchesLength)
return true;
for (size_t i = 0; i < event.touchesLength; ++i) {
if (event.touches[i].state != blink::WebTouchPoint::StateReleased &&
event.touches[i].state != blink::WebTouchPoint::StateCancelled)
return false;
}
return true;
}
void WebTouchEventTraits::ResetType(WebInputEvent::Type type,
double timestamp_sec,
WebTouchEvent* event) {
......
......@@ -22,6 +22,10 @@ class CONTENT_EXPORT WebTouchEventTraits {
// touches to some active touches (the start of a new "touch sequence").
static bool IsTouchSequenceStart(const blink::WebTouchEvent& event);
// Returns whether this event represents a transition from active
// touches to no active touches (the end of a "touch sequence").
static bool IsTouchSequenceEnd(const blink::WebTouchEvent& event);
// Sets the type of |event| to |type|, resetting any other type-specific
// properties and updating the timestamp.
static void ResetType(blink::WebInputEvent::Type type,
......
......@@ -305,10 +305,10 @@ void SimulateTapAt(WebContents* web_contents, const gfx::Point& point) {
touch.PressPoint(point.x(), point.y());
RenderWidgetHostImpl* widget_host =
RenderWidgetHostImpl::From(web_contents->GetRenderViewHost());
widget_host->ForwardTouchEvent(touch);
widget_host->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
touch.timeStampSeconds += kTapDurationSeconds;
touch.ReleasePoint(0);
widget_host->ForwardTouchEvent(touch);
widget_host->ForwardTouchEventWithLatencyInfo(touch, ui::LatencyInfo());
}
void SimulateKeyPress(WebContents* web_contents,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment