Commit 750a80d9 authored by s.singapati's avatar s.singapati Committed by Commit bot

Ensure touchmove changedTouches list only includes moving touches

On Android, WebTouchPoint.state of all touches is set to "StateMoved" thus
included in changedTouches when any touch moves. This patch compares current
touch points position to previous position in TouchMove event and updates state.
This comparision is done after all filtering and potential coalescing
in TouchEventQueue.

BUG=416236

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

Cr-Commit-Position: refs/heads/master@{#314315}
parent 57a1895e
......@@ -45,6 +45,20 @@ bool ShouldTouchTriggerTimeout(const WebTouchEvent& event) {
!WebInputEventTraits::IgnoresAckDisposition(event);
}
// Compare all properties of touch points to determine the state.
bool HasPointChanged(const WebTouchPoint& last_point,
const WebTouchPoint& current_point) {
if (last_point.screenPosition != current_point.screenPosition ||
last_point.position != current_point.position ||
last_point.radiusX != current_point.radiusX ||
last_point.radiusY != current_point.radiusY ||
last_point.rotationAngle != current_point.rotationAngle ||
last_point.force != current_point.force) {
return true;
}
return false;
}
} // namespace
......@@ -96,7 +110,7 @@ class TouchEventQueue::TouchTimeoutHandler {
SetPendingAckState(PENDING_ACK_CANCEL_EVENT);
TouchEventWithLatencyInfo cancel_event =
ObtainCancelEventForTouchEvent(timeout_event_);
touch_queue_->SendTouchEventImmediately(cancel_event);
touch_queue_->SendTouchEventImmediately(&cancel_event);
} else {
SetPendingAckState(PENDING_ACK_NONE);
touch_queue_->UpdateTouchConsumerStates(timeout_event_.event,
......@@ -500,7 +514,7 @@ void TouchEventQueue::ForwardNextEventToRenderer() {
pending_async_touchmove_.Pass();
async_move->event.cancelable = false;
touch_queue_.push_front(new CoalescedWebTouchEvent(*async_move, true));
SendTouchEventImmediately(*async_move);
SendTouchEventImmediately(async_move.get());
return;
}
}
......@@ -514,7 +528,7 @@ void TouchEventQueue::ForwardNextEventToRenderer() {
// A synchronous ack will reset |dispatching_touch_|, in which case
// the touch timeout should not be started.
base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
SendTouchEventImmediately(touch);
SendTouchEventImmediately(&touch);
if (dispatching_touch_ && timeout_handler_)
timeout_handler_->StartIfNecessary(touch);
}
......@@ -674,8 +688,34 @@ scoped_ptr<CoalescedWebTouchEvent> TouchEventQueue::PopTouchEvent() {
}
void TouchEventQueue::SendTouchEventImmediately(
const TouchEventWithLatencyInfo& touch) {
client_->SendTouchEventImmediately(touch);
TouchEventWithLatencyInfo* touch) {
// For touchmove events, compare touch points position from current event
// to last sent event and update touch points state.
if (touch->event.type == WebInputEvent::TouchMove) {
CHECK(last_sent_touchevent_);
for (unsigned int i = 0; i < last_sent_touchevent_->touchesLength; ++i) {
const WebTouchPoint& last_touch_point =
last_sent_touchevent_->touches[i];
// Touches with same id may not have same index in Touches array.
for (unsigned int j = 0; j < touch->event.touchesLength; ++j) {
const WebTouchPoint& current_touchmove_point = touch->event.touches[j];
if (current_touchmove_point.id != last_touch_point.id)
continue;
if (!HasPointChanged(last_touch_point, current_touchmove_point))
touch->event.touches[j].state = WebTouchPoint::StateStationary;
break;
}
}
}
if (last_sent_touchevent_)
*last_sent_touchevent_ = touch->event;
else
last_sent_touchevent_.reset(new WebTouchEvent(touch->event));
client_->SendTouchEventImmediately(*touch);
}
TouchEventQueue::PreFilterResult
......@@ -690,6 +730,8 @@ TouchEventQueue::FilterBeforeForwarding(const WebTouchEvent& event) {
touch_consumer_states_.clear();
send_touch_events_async_ = false;
pending_async_touchmove_.reset();
last_sent_touchevent_.reset();
touch_sequence_start_position_ = gfx::PointF(event.touches[0].position);
drop_remaining_touches_in_sequence_ = false;
if (!has_handlers_) {
......
......@@ -164,8 +164,9 @@ class CONTENT_EXPORT TouchEventQueue {
// Safely pop the head of the queue.
scoped_ptr<CoalescedWebTouchEvent> PopTouchEvent();
// Dispatch |touch| to the client.
void SendTouchEventImmediately(const TouchEventWithLatencyInfo& touch);
// Dispatch |touch| to the client. Before dispatching, updates pointer
// states in touchmove events for pointers that have not changed position.
void SendTouchEventImmediately(TouchEventWithLatencyInfo* touch);
enum PreFilterResult {
ACK_WITH_NO_CONSUMER_EXISTS,
......@@ -236,6 +237,9 @@ class CONTENT_EXPORT TouchEventQueue {
// mode.
const TouchScrollingMode touch_scrolling_mode_;
// Event is saved to compare pointer positions for new touchmove events.
scoped_ptr<blink::WebTouchEvent> last_sent_touchevent_;
DISALLOW_COPY_AND_ASSIGN(TouchEventQueue);
};
......
......@@ -169,6 +169,46 @@ class TouchEventQueueTest : public testing::Test,
SendTouchEvent();
}
void ChangeTouchPointRadius(int index, float radius_x, float radius_y) {
CHECK_GE(index, 0);
CHECK_LT(index, touch_event_.touchesLengthCap);
WebTouchPoint& point = touch_event_.touches[index];
point.radiusX = radius_x;
point.radiusY = radius_y;
touch_event_.touches[index].state = WebTouchPoint::StateMoved;
touch_event_.causesScrollingIfUncanceled = true;
WebTouchEventTraits::ResetType(WebInputEvent::TouchMove,
touch_event_.timeStampSeconds,
&touch_event_);
SendTouchEvent();
}
void ChangeTouchPointRotationAngle(int index, float rotation_angle) {
CHECK_GE(index, 0);
CHECK_LT(index, touch_event_.touchesLengthCap);
WebTouchPoint& point = touch_event_.touches[index];
point.rotationAngle = rotation_angle;
touch_event_.touches[index].state = WebTouchPoint::StateMoved;
touch_event_.causesScrollingIfUncanceled = true;
WebTouchEventTraits::ResetType(WebInputEvent::TouchMove,
touch_event_.timeStampSeconds,
&touch_event_);
SendTouchEvent();
}
void ChangeTouchPointForce(int index, float force) {
CHECK_GE(index, 0);
CHECK_LT(index, touch_event_.touchesLengthCap);
WebTouchPoint& point = touch_event_.touches[index];
point.force = force;
touch_event_.touches[index].state = WebTouchPoint::StateMoved;
touch_event_.causesScrollingIfUncanceled = true;
WebTouchEventTraits::ResetType(WebInputEvent::TouchMove,
touch_event_.timeStampSeconds,
&touch_event_);
SendTouchEvent();
}
void ReleaseTouchPoint(int index) {
touch_event_.ReleasePoint(index);
SendTouchEvent();
......@@ -1894,7 +1934,7 @@ TEST_F(TouchEventQueueTest, AsyncTouchThrottledAfterScroll) {
EXPECT_TRUE(sent_event().cancelable);
EXPECT_FALSE(HasPendingAsyncTouchMove());
EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type);
EXPECT_EQ(WebTouchPoint::StateMoved, sent_event().touches[0].state);
EXPECT_EQ(WebTouchPoint::StateStationary, sent_event().touches[0].state);
EXPECT_EQ(WebTouchPoint::StateMoved, sent_event().touches[1].state);
EXPECT_EQ(1U, queued_event_count());
EXPECT_EQ(1U, GetAndResetSentEventCount());
......@@ -2300,4 +2340,114 @@ TEST_F(TouchEventQueueTest, UnseenTouchPointerIdsNotForwarded) {
EXPECT_EQ(1U, GetAndResetAckedEventCount());
}
// Tests that touch points states are correct in TouchMove events.
TEST_F(TouchEventQueueTest, PointerStatesInTouchMove) {
PressTouchPoint(1, 1);
PressTouchPoint(2, 2);
PressTouchPoint(3, 3);
PressTouchPoint(4, 4);
EXPECT_EQ(4U, queued_event_count());
EXPECT_EQ(1U, GetAndResetSentEventCount());
// Receive ACK for the first three touch-events.
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
EXPECT_EQ(1U, queued_event_count());
// Test current touches state before sending TouchMoves.
const WebTouchEvent& event1 = sent_event();
EXPECT_EQ(WebInputEvent::TouchStart, event1.type);
EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[0].state);
EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[1].state);
EXPECT_EQ(WebTouchPoint::StateStationary, event1.touches[2].state);
EXPECT_EQ(WebTouchPoint::StatePressed, event1.touches[3].state);
// Move x-position for 1st touch, y-position for 2nd touch
// and do not move other touches.
MoveTouchPoints(0, 1.1f, 1.f, 1, 2.f, 20.001f);
MoveTouchPoints(2, 3.f, 3.f, 3, 4.f, 4.f);
EXPECT_EQ(2U, queued_event_count());
// Receive an ACK for the last TouchPress event.
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
// 1st TouchMove is sent. Test for touches state.
const WebTouchEvent& event2 = sent_event();
EXPECT_EQ(WebInputEvent::TouchMove, event2.type);
EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[0].state);
EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[1].state);
EXPECT_EQ(WebTouchPoint::StateStationary, event2.touches[2].state);
EXPECT_EQ(WebTouchPoint::StateStationary, event2.touches[3].state);
// Move only 4th touch but not others.
MoveTouchPoints(0, 1.1f, 1.f, 1, 2.f, 20.001f);
MoveTouchPoints(2, 3.f, 3.f, 3, 4.1f, 4.1f);
// Receive an ACK for previous (1st) TouchMove.
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
// 2nd TouchMove is sent. Test for touches state.
const WebTouchEvent& event3 = sent_event();
EXPECT_EQ(WebInputEvent::TouchMove, event3.type);
EXPECT_EQ(WebTouchPoint::StateStationary, event3.touches[0].state);
EXPECT_EQ(WebTouchPoint::StateStationary, event3.touches[1].state);
EXPECT_EQ(WebTouchPoint::StateStationary, event3.touches[2].state);
EXPECT_EQ(WebTouchPoint::StateMoved, event3.touches[3].state);
}
// Tests that touch point state is correct in TouchMove events
// when point properties other than position changed.
TEST_F(TouchEventQueueTest, PointerStatesWhenOtherThanPositionChanged) {
PressTouchPoint(1, 1);
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
// Default initial radiusX/Y is (1.f, 1.f).
// Default initial rotationAngle is 1.f.
// Default initial force is 1.f.
// Change touch point radius only.
ChangeTouchPointRadius(0, 1.5f, 1.f);
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
// TouchMove is sent. Test for pointer state.
const WebTouchEvent& event1 = sent_event();
EXPECT_EQ(WebInputEvent::TouchMove, event1.type);
EXPECT_EQ(WebTouchPoint::StateMoved, event1.touches[0].state);
// Change touch point force.
ChangeTouchPointForce(0, 0.9f);
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
// TouchMove is sent. Test for pointer state.
const WebTouchEvent& event2 = sent_event();
EXPECT_EQ(WebInputEvent::TouchMove, event2.type);
EXPECT_EQ(WebTouchPoint::StateMoved, event2.touches[0].state);
// Change touch point rotationAngle.
ChangeTouchPointRotationAngle(0, 1.1f);
// TouchMove is sent. Test for pointer state.
const WebTouchEvent& event3 = sent_event();
EXPECT_EQ(WebInputEvent::TouchMove, event3.type);
EXPECT_EQ(WebTouchPoint::StateMoved, event3.touches[0].state);
// TODO(jdduke): Now trying to forward a TouchMove event without really
// changing touch point properties. Update below test with testing for
// rejected TouchMove with ack state INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
// crbug.com/452032. Or should it be in a separte test?.
// Do not change any properties, but use previous values.
MoveTouchPoint(0, 1.f, 1.f);
ChangeTouchPointRadius(0, 1.5f, 1.f);
// Receive an ACK for previous TouchMove.
SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
// TouchMove is sent, but pointer state should be StateStationary.
const WebTouchEvent& event4 = sent_event();
EXPECT_EQ(WebInputEvent::TouchMove, event4.type);
EXPECT_EQ(WebTouchPoint::StateStationary, event4.touches[0].state);
}
} // namespace content
......@@ -177,6 +177,8 @@ int SyntheticWebTouchEvent::PressPoint(float x, float y) {
point.position.y = point.screenPosition.y = y;
point.state = WebTouchPoint::StatePressed;
point.radiusX = point.radiusY = 1.f;
point.rotationAngle = 1.f;
point.force = 1.f;
++touchesLength;
WebTouchEventTraits::ResetType(
WebInputEvent::TouchStart, timeStampSeconds, this);
......
......@@ -139,6 +139,12 @@ bool TouchEventStreamValidator::Validate(const WebTouchEvent& event,
break;
case WebTouchPoint::StateStationary:
// TODO(jdduke): Remove this after implementing TouchMove events
// filtering based on corrected touches state in TouchEventQueue,
// crbug.com/452032.
if (event.type == WebInputEvent::TouchMove)
found_valid_state_for_type = true;
break;
case WebTouchPoint::StateCancelled:
......
......@@ -159,7 +159,12 @@ TEST(TouchEventStreamValidator, InvalidPointStates) {
j <= WebTouchPoint::StateCancelled;
++j) {
event.touches[0].state = static_cast<WebTouchPoint::State>(j);
if (event.touches[0].state == kValidTouchPointStatesForType[i]) {
if (event.touches[0].state == kValidTouchPointStatesForType[i]
// TODO(jdduke): Remove this StateStationary workaround check for
// TouchMove after implementing TouchMove events filtering based on
// corrected touches state in TouchEventQueue, crbug.com/452032.
|| (event.type == WebInputEvent::TouchMove
&& event.touches[0].state == WebTouchPoint::StateStationary)) {
EXPECT_TRUE(validator.Validate(event, &error_msg));
EXPECT_TRUE(error_msg.empty());
} else {
......
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