Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / input / touch_event_queue_unittest.cc
index 7d2c086..90e922b 100644 (file)
@@ -19,8 +19,13 @@ using blink::WebTouchPoint;
 
 namespace content {
 namespace {
-const size_t kDefaultTouchTimeoutDelayMs = 10;
+
+const double kMinSecondsBetweenThrottledTouchmoves = 0.2;
+
+base::TimeDelta DefaultTouchTimeoutDelay() {
+  return base::TimeDelta::FromMilliseconds(1);
 }
+}  // namespace
 
 class TouchEventQueueTest : public testing::Test,
                             public TouchEventQueueClient {
@@ -35,14 +40,7 @@ class TouchEventQueueTest : public testing::Test,
   virtual ~TouchEventQueueTest() {}
 
   // testing::Test
-  virtual void SetUp() OVERRIDE {
-    ResetQueueWithParameters(touch_scrolling_mode_, slop_length_dips_);
-  }
-
-  virtual void SetTouchScrollingMode(TouchEventQueue::TouchScrollingMode mode) {
-    touch_scrolling_mode_ = mode;
-    ResetQueueWithParameters(touch_scrolling_mode_, slop_length_dips_);
-  }
+  virtual void SetUp() OVERRIDE { ResetQueueWithConfig(CreateConfig()); }
 
   virtual void TearDown() OVERRIDE {
     queue_.reset();
@@ -78,14 +76,28 @@ class TouchEventQueueTest : public testing::Test,
   }
 
  protected:
+  TouchEventQueue::Config CreateConfig() {
+    TouchEventQueue::Config config;
+    config.touch_scrolling_mode = touch_scrolling_mode_;
+    config.touchmove_slop_suppression_length_dips = slop_length_dips_;
+    return config;
+  }
 
-  void SetUpForTimeoutTesting(size_t timeout_delay_ms) {
-    queue_->SetAckTimeoutEnabled(true, timeout_delay_ms);
+  void SetTouchScrollingMode(TouchEventQueue::TouchScrollingMode mode) {
+    touch_scrolling_mode_ = mode;
+    ResetQueueWithConfig(CreateConfig());
   }
 
   void SetUpForTouchMoveSlopTesting(double slop_length_dips) {
     slop_length_dips_ = slop_length_dips;
-    ResetQueueWithParameters(touch_scrolling_mode_, slop_length_dips_);
+    ResetQueueWithConfig(CreateConfig());
+  }
+
+  void SetUpForTimeoutTesting(base::TimeDelta timeout_delay) {
+    TouchEventQueue::Config config = CreateConfig();
+    config.touch_ack_timeout_delay = timeout_delay;
+    config.touch_ack_timeout_supported = true;
+    ResetQueueWithConfig(config);
   }
 
   void SendTouchEvent(const WebTouchEvent& event) {
@@ -123,17 +135,22 @@ class TouchEventQueueTest : public testing::Test,
     sync_ack_result_.reset(new InputEventAckState(sync_ack_result));
   }
 
-  void PressTouchPoint(int x, int y) {
+  void PressTouchPoint(float x, float y) {
     touch_event_.PressPoint(x, y);
     SendTouchEvent();
   }
 
-  void MoveTouchPoint(int index, int x, int y) {
+  void MoveTouchPoint(int index, float x, float y) {
     touch_event_.MovePoint(index, x, y);
     SendTouchEvent();
   }
 
-  void MoveTouchPoints(int index0, int x0, int y0, int index1, int x1, int y1) {
+  void MoveTouchPoints(int index0,
+                       float x0,
+                       float y0,
+                       int index1,
+                       float x1,
+                       float y1) {
     touch_event_.MovePoint(index0, x0, y0);
     touch_event_.MovePoint(index1, x1, y1);
     SendTouchEvent();
@@ -149,6 +166,14 @@ class TouchEventQueueTest : public testing::Test,
     SendTouchEvent();
   }
 
+  void AdvanceTouchTime(double seconds) {
+    touch_event_.timeStampSeconds += seconds;
+  }
+
+  void ResetTouchEvent() {
+    touch_event_ = SyntheticWebTouchEvent();
+  }
+
   size_t GetAndResetAckedEventCount() {
     size_t count = acked_event_count_;
     acked_event_count_ = 0;
@@ -169,8 +194,12 @@ class TouchEventQueueTest : public testing::Test,
     queue_->OnHasTouchEventHandlers(has_handlers);
   }
 
-  bool IsTimeoutRunning() {
-    return queue_->IsTimeoutRunningForTesting();
+  void SetAckTimeoutDisabled() { queue_->SetAckTimeoutEnabled(false); }
+
+  bool IsTimeoutRunning() const { return queue_->IsTimeoutRunningForTesting(); }
+
+  bool HasPendingAsyncTouchMove() const {
+    return queue_->HasPendingAsyncTouchMoveForTesting();
   }
 
   size_t queued_event_count() const {
@@ -193,15 +222,20 @@ class TouchEventQueueTest : public testing::Test,
     return last_acked_event_state_;
   }
 
+  static void RunTasksAndWait(base::TimeDelta delay) {
+    base::MessageLoop::current()->PostDelayedTask(
+        FROM_HERE, base::MessageLoop::QuitClosure(), delay);
+    base::MessageLoop::current()->Run();
+  }
+
  private:
   void SendTouchEvent() {
     SendTouchEvent(touch_event_);
     touch_event_.ResetPoints();
   }
 
-  void ResetQueueWithParameters(TouchEventQueue::TouchScrollingMode mode,
-                                double slop_length_dips) {
-    queue_.reset(new TouchEventQueue(this, mode, slop_length_dips));
+  void ResetQueueWithConfig(const TouchEventQueue::Config& config) {
+    queue_.reset(new TouchEventQueue(this, config));
     queue_->OnHasTouchEventHandlers(true);
   }
 
@@ -238,6 +272,7 @@ TEST_F(TouchEventQueueTest, Basic) {
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(WebInputEvent::TouchStart, acked_event().type);
+  EXPECT_TRUE(acked_event().cancelable);
 
   // Receive an ACK for the second touch-event.
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
@@ -245,11 +280,58 @@ TEST_F(TouchEventQueueTest, Basic) {
   EXPECT_EQ(0U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(WebInputEvent::TouchMove, acked_event().type);
+  EXPECT_TRUE(acked_event().cancelable);
+}
+
+// Tests that touch-events with multiple points are queued properly.
+TEST_F(TouchEventQueueTest, BasicMultiTouch) {
+  const size_t kPointerCount = 10;
+  for (size_t i = 0; i < kPointerCount; ++i)
+    PressTouchPoint(i, i);
+
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(kPointerCount, queued_event_count());
+
+  for (size_t i = 0; i < kPointerCount; ++i)
+    MoveTouchPoint(i, 1.f + i, 2.f + i);
+
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  // All moves should coalesce.
+  EXPECT_EQ(kPointerCount + 1, queued_event_count());
+
+  for (size_t i = 0; i < kPointerCount; ++i)
+    ReleaseTouchPoint(kPointerCount - 1 - i);
+
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(kPointerCount * 2 + 1, queued_event_count());
+
+  // Ack all presses.
+  for (size_t i = 0; i < kPointerCount; ++i)
+    SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  EXPECT_EQ(kPointerCount, GetAndResetAckedEventCount());
+  EXPECT_EQ(kPointerCount, GetAndResetSentEventCount());
+
+  // Ack the coalesced move.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(kPointerCount, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Ack all releases.
+  for (size_t i = 0; i < kPointerCount; ++i)
+    SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  EXPECT_EQ(kPointerCount, GetAndResetAckedEventCount());
+  EXPECT_EQ(kPointerCount - 1, GetAndResetSentEventCount());
 }
 
-// Tests that the touch-queue is emptied if a page stops listening for touch
-// events.
-TEST_F(TouchEventQueueTest, QueueFlushedWhenHandlersRemoved) {
+// Tests that the touch-queue continues delivering events for an active pointer
+// after all handlers are removed, but acks new pointers immediately as having
+// no consumer.
+TEST_F(TouchEventQueueTest, NoNewTouchesForwardedAfterHandlersRemoved) {
   OnHasTouchEventHandlers(true);
   EXPECT_EQ(0U, queued_event_count());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
@@ -257,32 +339,51 @@ TEST_F(TouchEventQueueTest, QueueFlushedWhenHandlersRemoved) {
   // Send a touch-press event.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
 
-  ReleaseTouchPoint(0);
-
-  // Events will be queued until the first sent event is ack'ed.
-  for (int i = 5; i < 15; ++i) {
-    PressTouchPoint(1, 1);
-    MoveTouchPoint(0, i, i);
-    ReleaseTouchPoint(0);
-  }
-  EXPECT_EQ(32U, queued_event_count());
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  // Signal that all touch handlers have been removed.
+  OnHasTouchEventHandlers(false);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, queued_event_count());
 
-  // Receive an ACK for the first touch-event. One of the queued touch-event
-  // should be forwarded.
+  // Process the ack for the sent touch, ensuring that it is honored (despite
+  // the touch handler having been removed).
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
-  EXPECT_EQ(31U, queued_event_count());
-  EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
-  EXPECT_EQ(WebInputEvent::TouchStart, acked_event().type);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
 
-  // Flush the queue. The touch-event queue should now be emptied, but none of
-  // the queued touch-events should be sent to the renderer.
-  OnHasTouchEventHandlers(false);
+  // Try forwarding a new pointer. It should be rejected immediately.
+  PressTouchPoint(2, 2);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+
+  // Further events for the pointer without a handler should not be forwarded.
+  MoveTouchPoint(1, 3, 3);
+  ReleaseTouchPoint(1);
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+
+  // Events for the first pointer, that had a handler, should be forwarded, even
+  // if the renderer reports that no handlers exist.
+  MoveTouchPoint(0, 4, 4);
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(31U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_CONSUMED, acked_event_state());
 }
 
 // Tests that addition of a touch handler during a touch sequence will not cause
@@ -326,14 +427,23 @@ TEST_F(TouchEventQueueTest, ActiveSequenceDroppedWhenHandlersRemoved) {
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
 
-  // Touch handle deregistration should flush the queue.
+  // Unregister all touch handlers.
   OnHasTouchEventHandlers(false);
-  EXPECT_EQ(2U, GetAndResetAckedEventCount());
-  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
 
-  // The ack should be ignored as the touch queue is now empty.
-  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  // Repeated registration/unregstration of handlers should have no effect as
+  // we're still awaiting the ack arrival.
+  OnHasTouchEventHandlers(true);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+  OnHasTouchEventHandlers(false);
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // The ack should be flush the queue.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  EXPECT_EQ(2U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, queued_event_count());
 
   // Events should be dropped while there is no touch handler.
@@ -357,6 +467,34 @@ TEST_F(TouchEventQueueTest, ActiveSequenceDroppedWhenHandlersRemoved) {
   EXPECT_EQ(1U, GetAndResetSentEventCount());
 }
 
+// Tests that removal/addition of a touch handler without any intervening
+// touch activity has no affect on touch forwarding.
+TEST_F(TouchEventQueueTest,
+       ActiveSequenceUnaffectedByRepeatedHandlerRemovalAndAddition) {
+  // Send a touch-press event.
+  PressTouchPoint(1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Simulate the case where the touchstart handler removes itself, and adds a
+  // touchmove handler.
+  OnHasTouchEventHandlers(false);
+  OnHasTouchEventHandlers(true);
+
+  // Queue a touch-move event.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // The ack should trigger forwarding of the touchmove, as if no touch
+  // handler registration changes have occurred.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+}
+
 // Tests that touch-events are coalesced properly in the queue.
 TEST_F(TouchEventQueueTest, Coalesce) {
   // Send a touch-press event.
@@ -458,33 +596,23 @@ TEST_F(TouchEventQueueTest, MultiTouch) {
   EXPECT_EQ(WebTouchPoint::StateMoved, event.touches[1].state);
 }
 
-// Tests that if a touch-event queue is destroyed in response to a touch-event
-// in the renderer, then there is no crash when the ACK for that touch-event
-// comes back.
-TEST_F(TouchEventQueueTest, AckAfterQueueFlushed) {
-  // Send some touch-events to the renderer.
+// Tests that the touch-event queue is robust to redundant acks.
+TEST_F(TouchEventQueueTest, SpuriousAcksIgnored) {
+  // Trigger a spurious ack.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Send and ack a touch press.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, queued_event_count());
-
-  MoveTouchPoint(0, 10, 10);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(2U, queued_event_count());
-
-  // Receive an ACK for the press. This should cause the queued touch-move to
-  // be sent to the renderer.
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
-  EXPECT_EQ(1U, GetAndResetSentEventCount());
-  EXPECT_EQ(1U, queued_event_count());
-
-  OnHasTouchEventHandlers(false);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, queued_event_count());
 
-  // Now receive an ACK for the move.
+  // Trigger a spurious ack.
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
-  EXPECT_EQ(0U, GetAndResetSentEventCount());
-  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
 // Tests that touch-move events are not sent to the renderer if the preceding
@@ -546,11 +674,14 @@ TEST_F(TouchEventQueueTest, NoConsumer) {
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(0U, queued_event_count());
 
-  // Send a second press event. Even though the first touch had NO_CONSUMER,
-  // this press event should reach the renderer.
+  // Send a second press event. Even though the first touch press had
+  // NO_CONSUMER, this press event should reach the renderer.
   PressTouchPoint(1, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchStart, acked_event().type);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
 TEST_F(TouchEventQueueTest, ConsumerIgnoreMultiFinger) {
@@ -674,10 +805,10 @@ TEST_F(TouchEventQueueTest, AckWithFollowupEvents) {
   // Create a touch event that will be queued synchronously by a touch ack.
   // Note, this will be triggered by all subsequent touch acks.
   WebTouchEvent followup_event;
-  followup_event.type = WebInputEvent::TouchStart;
+  followup_event.type = WebInputEvent::TouchMove;
   followup_event.touchesLength = 1;
-  followup_event.touches[0].id = 1;
-  followup_event.touches[0].state = WebTouchPoint::StatePressed;
+  followup_event.touches[0].id = 0;
+  followup_event.touches[0].state = WebTouchPoint::StateMoved;
   SetFollowupEvent(followup_event);
 
   // Receive an ACK for the press. This should cause the followup touch-move to
@@ -804,7 +935,8 @@ TEST_F(TouchEventQueueTest, NoTouchBasic) {
 }
 
 // Tests that no TouchEvents are sent to renderer during scrolling.
-TEST_F(TouchEventQueueTest, NoTouchOnScroll) {
+TEST_F(TouchEventQueueTest, TouchCancelOnScroll) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_TOUCHCANCEL);
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -815,13 +947,16 @@ TEST_F(TouchEventQueueTest, NoTouchOnScroll) {
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
 
+  MoveTouchPoint(0, 30, 15);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
   // Queue another TouchStart.
   PressTouchPoint(20, 20);
-  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(3U, queued_event_count());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
   EXPECT_EQ(WebInputEvent::TouchStart, latest_event().type);
 
-  // GestureScrollBegin inserts a synthetic TouchCancel before the TouchStart.
   WebGestureEvent followup_scroll;
   followup_scroll.type = WebInputEvent::GestureScrollBegin;
   SetFollowupEvent(followup_scroll);
@@ -829,7 +964,18 @@ TEST_F(TouchEventQueueTest, NoTouchOnScroll) {
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(2U, queued_event_count());
+  EXPECT_TRUE(sent_event().cancelable);
+  EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type);
+
+  // GestureScrollUpdate inserts a synthetic TouchCancel before the TouchStart.
+  followup_scroll.type = WebInputEvent::GestureScrollUpdate;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(2U, queued_event_count());
   EXPECT_EQ(WebInputEvent::TouchCancel, sent_event().type);
+  EXPECT_FALSE(sent_event().cancelable);
   EXPECT_EQ(WebInputEvent::TouchStart, latest_event().type);
 
   // Acking the TouchCancel will result in dispatch of the next TouchStart.
@@ -879,6 +1025,8 @@ TEST_F(TouchEventQueueTest, NoTouchOnScroll) {
 // Tests that a scroll event will not insert a synthetic TouchCancel if there
 // was no consumer for the current touch sequence.
 TEST_F(TouchEventQueueTest, NoTouchCancelOnScrollIfNoConsumer) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_TOUCHCANCEL);
+
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
@@ -968,7 +1116,7 @@ TEST_F(TouchEventQueueTest, PendingStart) {
 
 // Tests that the touch timeout is started when sending certain touch types.
 TEST_F(TouchEventQueueTest, TouchTimeoutTypes) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Sending a TouchStart will start the timeout.
   PressTouchPoint(0, 1);
@@ -1002,7 +1150,7 @@ TEST_F(TouchEventQueueTest, TouchTimeoutTypes) {
 // disabling touch forwarding until the next TouchStart is received after
 // the timeout events are ack'ed.
 TEST_F(TouchEventQueueTest, TouchTimeoutBasic) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   GetAndResetSentEventCount();
@@ -1013,11 +1161,7 @@ TEST_F(TouchEventQueueTest, TouchTimeoutBasic) {
   EXPECT_TRUE(IsTimeoutRunning());
 
   // Delay the ack.
-  base::MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::MessageLoop::QuitClosure(),
-      base::TimeDelta::FromMilliseconds(kDefaultTouchTimeoutDelayMs * 2));
-  base::MessageLoop::current()->Run();
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
 
   // The timeout should have fired, synthetically ack'ing the timed-out event.
   // TouchEvent forwarding is disabled until the ack is received for the
@@ -1031,6 +1175,8 @@ TEST_F(TouchEventQueueTest, TouchTimeoutBasic) {
   EXPECT_FALSE(IsTimeoutRunning());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(WebInputEvent::TouchCancel, sent_event().type);
+  EXPECT_FALSE(sent_event().cancelable);
 
   // Touch events should not be forwarded until we receive the cancel acks.
   MoveTouchPoint(0, 1, 1);
@@ -1051,12 +1197,14 @@ TEST_F(TouchEventQueueTest, TouchTimeoutBasic) {
   PressTouchPoint(0, 1);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(WebInputEvent::TouchStart, sent_event().type);
+  EXPECT_TRUE(sent_event().cancelable);
 }
 
 // Tests that the timeout is never started if the renderer consumes
 // a TouchEvent from the current touch sequence.
 TEST_F(TouchEventQueueTest, NoTouchTimeoutIfRendererIsConsumingGesture) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
@@ -1087,9 +1235,36 @@ TEST_F(TouchEventQueueTest, NoTouchTimeoutIfRendererIsConsumingGesture) {
   EXPECT_FALSE(IsTimeoutRunning());
 }
 
+// Tests that the timeout is never started if the renderer consumes
+// a TouchEvent from the current touch sequence.
+TEST_F(TouchEventQueueTest, NoTouchTimeoutIfDisabledAfterTouchStart) {
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  ASSERT_TRUE(IsTimeoutRunning());
+
+  // Send the ack immediately. The timeout should not have fired.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now explicitly disable the timeout.
+  SetAckTimeoutDisabled();
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // A TouchMove should not start or trigger the timeout.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
 // Tests that the timeout is never started if the ack is synchronous.
 TEST_F(TouchEventQueueTest, NoTouchTimeoutIfAckIsSynchronous) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   SetSyncAckResult(INPUT_EVENT_ACK_STATE_CONSUMED);
@@ -1098,23 +1273,26 @@ TEST_F(TouchEventQueueTest, NoTouchTimeoutIfAckIsSynchronous) {
   EXPECT_FALSE(IsTimeoutRunning());
 }
 
-// Tests that the timeout is disabled if the touch handler disappears.
-TEST_F(TouchEventQueueTest, TouchTimeoutStoppedIfTouchHandlerRemoved) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+// Tests that the timeout does not fire if explicitly disabled while an event
+// is in-flight.
+TEST_F(TouchEventQueueTest, NoTouchTimeoutIfDisabledWhileTimerIsActive) {
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
   ASSERT_TRUE(IsTimeoutRunning());
 
-  // Unload the touch handler.
-  OnHasTouchEventHandlers(false);
+  // Verify that disabling the timeout also turns off the timer.
+  SetAckTimeoutDisabled();
   EXPECT_FALSE(IsTimeoutRunning());
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
 // Tests that a TouchCancel timeout plays nice when the timed out touch stream
 // turns into a scroll gesture sequence.
 TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGesture) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
@@ -1127,11 +1305,7 @@ TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGesture) {
   SetFollowupEvent(followup_scroll);
 
   // Delay the ack.
-  base::MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::MessageLoop::QuitClosure(),
-      base::TimeDelta::FromMilliseconds(kDefaultTouchTimeoutDelayMs * 2));
-  base::MessageLoop::current()->Run();
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
 
   // The timeout should have fired, disabling touch forwarding until both acks
   // are received, acking the timed out event.
@@ -1173,7 +1347,7 @@ TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGesture) {
 // turns into a scroll gesture sequence, but the original event acks are
 // significantly delayed.
 TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGestureAndDelayedAck) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
@@ -1186,11 +1360,7 @@ TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGestureAndDelayedAck) {
   SetFollowupEvent(followup_scroll);
 
   // Delay the ack.
-  base::MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::MessageLoop::QuitClosure(),
-      base::TimeDelta::FromMilliseconds(kDefaultTouchTimeoutDelayMs * 2));
-  base::MessageLoop::current()->Run();
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
 
   // The timeout should have fired, disabling touch forwarding until both acks
   // are received and acking the timed out event.
@@ -1233,7 +1403,7 @@ TEST_F(TouchEventQueueTest, TouchTimeoutWithFollowupGestureAndDelayedAck) {
 // Tests that a delayed TouchEvent ack will not trigger a TouchCancel timeout if
 // the timed-out event had no consumer.
 TEST_F(TouchEventQueueTest, NoCancelOnTouchTimeoutWithoutConsumer) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
@@ -1242,11 +1412,7 @@ TEST_F(TouchEventQueueTest, NoCancelOnTouchTimeoutWithoutConsumer) {
   EXPECT_TRUE(IsTimeoutRunning());
 
   // Delay the ack.
-  base::MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::MessageLoop::QuitClosure(),
-      base::TimeDelta::FromMilliseconds(kDefaultTouchTimeoutDelayMs * 2));
-  base::MessageLoop::current()->Run();
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
 
   // The timeout should have fired, synthetically ack'ing the timed out event.
   // TouchEvent forwarding is disabled until the original ack is received.
@@ -1273,9 +1439,9 @@ TEST_F(TouchEventQueueTest, NoCancelOnTouchTimeoutWithoutConsumer) {
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
-// Tests that TouchMove's are dropped if within the slop suppression region
-// for an unconsumed TouchStart
-TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithinSlopRegion) {
+// Tests that TouchMove's are dropped if within the boundary-inclusive slop
+// suppression region for an unconsumed TouchStart.
+TEST_F(TouchEventQueueTest, TouchMoveSuppressionIncludingSlopBoundary) {
   const double kSlopLengthDips = 10.;
   const double kHalfSlopLengthDips = kSlopLengthDips / 2;
   SetUpForTouchMoveSlopTesting(kSlopLengthDips);
@@ -1305,14 +1471,24 @@ TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithinSlopRegion) {
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
   EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, acked_event_state());
 
+  MoveTouchPoint(0, -kSlopLengthDips, 0);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, acked_event_state());
+
+  MoveTouchPoint(0, 0, kSlopLengthDips);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NOT_CONSUMED, acked_event_state());
+
   // As soon as a TouchMove exceeds the (Euclidean) distance, no more
   // TouchMove's should be suppressed.
-  // TODO(jdduke): Remove ceil with adoption of floating point touch coords,
-  // crbug/336807.
   const double kFortyFiveDegreeSlopLengthXY =
-      std::ceil(kSlopLengthDips * std::sqrt(2.) / 2.);
-  MoveTouchPoint(0, kFortyFiveDegreeSlopLengthXY + .1,
-                    kFortyFiveDegreeSlopLengthXY + .1);
+      kSlopLengthDips * std::sqrt(2.) / 2.;
+  MoveTouchPoint(0, kFortyFiveDegreeSlopLengthXY + .2,
+                    kFortyFiveDegreeSlopLengthXY + .2);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
@@ -1336,17 +1512,16 @@ TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithinSlopRegion) {
   ASSERT_EQ(2U, GetAndResetAckedEventCount());
   ASSERT_EQ(0U, queued_event_count());
 
-  // The slop region is boundary-exclusive.
-  // TODO(jdduke): Change to inclusive upon resolving crbug.com/336807.
+  // The slop region is boundary-inclusive.
   MoveTouchPoint(0, kSlopLengthDips - 1., 0);
   EXPECT_EQ(0U, queued_event_count());
   EXPECT_EQ(0U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
   MoveTouchPoint(0, kSlopLengthDips, 0);
-  EXPECT_EQ(1U, queued_event_count());
-  EXPECT_EQ(1U, GetAndResetSentEventCount());
-  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
 // Tests that TouchMove's are not dropped within the slop suppression region if
@@ -1370,8 +1545,24 @@ TEST_F(TouchEventQueueTest, NoTouchMoveSuppressionAfterTouchConsumed) {
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
-// Tests that TouchMove's are not dropped due to integral truncation of
-// WebTouchPoint coordinates after DPI scaling.
+// Tests that even very small TouchMove's are not suppressed when suppression is
+// disabled.
+TEST_F(TouchEventQueueTest, NoTouchMoveSuppressionIfDisabled) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Small TouchMove's should not be suppressed.
+  MoveTouchPoint(0, 0.001f, 0.001f);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+}
+
+// Tests that TouchMove's are not dropped due to incorrect handling of DPI
+// scaling.
 TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithDIPScaling) {
   const float kSlopLengthPixels = 7.f;
   const float kDPIScale = 3.f;
@@ -1383,12 +1574,11 @@ TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithDIPScaling) {
   ASSERT_EQ(1U, GetAndResetSentEventCount());
   ASSERT_EQ(1U, GetAndResetAckedEventCount());
 
-  // TouchMove's along the slop boundary should not be suppresed.
-  // TODO(jdduke): These should be suppressed, crbug.com/336807.
+  // TouchMove's along the slop boundary should be suppresed.
   MoveTouchPoint(0, 0, kSlopLengthPixels / kDPIScale);
-  EXPECT_EQ(1U, queued_event_count());
-  EXPECT_EQ(1U, GetAndResetSentEventCount());
-  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
   // Reset the touch sequence.
   ReleaseTouchPoint(0);
@@ -1404,20 +1594,18 @@ TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithDIPScaling) {
   ASSERT_EQ(1U, GetAndResetAckedEventCount());
 
   // TouchMove's outside the region should not be suppressed.
-  const float kPixelCoordOutsideSlopRegion = kSlopLengthPixels + 1.f;
+  const float kPixelCoordOutsideSlopRegion = kSlopLengthPixels + 0.5f;
   MoveTouchPoint(0, 0, kPixelCoordOutsideSlopRegion / kDPIScale);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
-
 // Tests that TouchMove's are not dropped if a secondary pointer is present
 // during any movement.
 TEST_F(TouchEventQueueTest, NoTouchMoveSuppressionAfterMultiTouch) {
   const double kSlopLengthDips = 10.;
   const double kHalfSlopLengthDips = kSlopLengthDips / 2;
-  const double kDoubleSlopLengthDips = 10.;
   SetUpForTouchMoveSlopTesting(kSlopLengthDips);
 
   // Queue a TouchStart.
@@ -1433,13 +1621,13 @@ TEST_F(TouchEventQueueTest, NoTouchMoveSuppressionAfterMultiTouch) {
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
   // Simulate a secondary pointer press.
-  PressTouchPoint(kDoubleSlopLengthDips, 0);
+  PressTouchPoint(kSlopLengthDips, 0);
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(1U, GetAndResetAckedEventCount());
 
   // TouchMove with a secondary pointer should not be suppressed.
-  MoveTouchPoint(1, kDoubleSlopLengthDips, 0);
+  MoveTouchPoint(1, kSlopLengthDips, 0);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
@@ -1459,6 +1647,65 @@ TEST_F(TouchEventQueueTest, NoTouchMoveSuppressionAfterMultiTouch) {
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
 }
 
+// Tests that secondary touch points can be forwarded even if the primary touch
+// point had no consumer.
+TEST_F(TouchEventQueueTest, SecondaryTouchForwardedAfterPrimaryHadNoConsumer) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Events should not be forwarded, as the point had no consumer.
+  MoveTouchPoint(0, 0, 15);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Simulate a secondary pointer press.
+  PressTouchPoint(20, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove with a secondary pointer should not be suppressed.
+  MoveTouchPoint(1, 25, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that no touch points will be forwarded after scrolling begins while no
+// touch points have a consumer.
+TEST_F(TouchEventQueueTest, NoForwardingAfterScrollWithNoTouchConsumers) {
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  WebGestureEvent followup_scroll;
+  followup_scroll.type = WebInputEvent::GestureScrollBegin;
+  SetFollowupEvent(followup_scroll);
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+
+  // The secondary pointer press should not be forwarded.
+  PressTouchPoint(20, 0);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+
+  // Neither should any further touchmoves be forwarded.
+  MoveTouchPoint(1, 25, 0);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS, acked_event_state());
+}
+
 TEST_F(TouchEventQueueTest, SyncTouchMoveDoesntCancelTouchOnScroll) {
  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_SYNC_TOUCHMOVE);
  // Queue a TouchStart.
@@ -1481,33 +1728,502 @@ TEST_F(TouchEventQueueTest, SyncTouchMoveDoesntCancelTouchOnScroll) {
  EXPECT_EQ(0U, queued_event_count());
 }
 
-TEST_F(TouchEventQueueTest, TouchAbsorption) {
- SetTouchScrollingMode(
-     TouchEventQueue::TOUCH_SCROLLING_MODE_ABSORB_TOUCHMOVE);
- // Queue a TouchStart.
- PressTouchPoint(0, 1);
- EXPECT_EQ(1U, GetAndResetSentEventCount());
- SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
- EXPECT_EQ(1U, GetAndResetAckedEventCount());
-
- for (int i = 0; i < 3; ++i) {
-   SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
-                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+TEST_F(TouchEventQueueTest, AsyncTouch) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
 
-   MoveTouchPoint(0, 20, 5);
-   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  for (int i = 0; i < 3; ++i) {
+   SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                       INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+   MoveTouchPoint(0, 10, 5);
+   SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+   EXPECT_FALSE(HasPendingAsyncTouchMove());
+   EXPECT_TRUE(sent_event().cancelable);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_EQ(1U, GetAndResetSentEventCount());
 
-   // Consuming a scroll event prevents the next touch moves from being
-   // dispatched.
+   // Consuming a scroll event will throttle subsequent touchmoves.
    SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
                        INPUT_EVENT_ACK_STATE_CONSUMED);
-   MoveTouchPoint(0, 20, 5);
+   MoveTouchPoint(0, 10, 5);
    SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+   EXPECT_TRUE(HasPendingAsyncTouchMove());
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_EQ(0U, GetAndResetSentEventCount());
- }
+  }
+}
+
+// Ensure that touchmove's are appropriately throttled during a typical
+// scroll sequences that transitions between scrolls consumed and unconsumed.
+TEST_F(TouchEventQueueTest, AsyncTouchThrottledAfterScroll) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
+
+  // Process a TouchStart
+  PressTouchPoint(0, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now send the first touch move and associated GestureScrollBegin.
+  MoveTouchPoint(0, 0, 5);
+  WebGestureEvent followup_scroll;
+  followup_scroll.type = WebInputEvent::GestureScrollBegin;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  SendGestureEventAck(WebInputEvent::GestureScrollBegin,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // Send the second touch move and associated GestureScrollUpdate, but don't
+  // ACK the gesture event yet.
+  MoveTouchPoint(0, 0, 50);
+  followup_scroll.type = WebInputEvent::GestureScrollUpdate;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now queue a second touchmove and verify it's not (yet) dispatched.
+  MoveTouchPoint(0, 0, 100);
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_TRUE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Queuing the final touchend should flush the pending, async touchmove.
+  ReleaseTouchPoint(0);
+  followup_scroll.type = WebInputEvent::GestureScrollEnd;
+  SetFollowupEvent(followup_scroll);
+  EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // Ack the flushed, async touchmove. The ack should not reach the client, but
+  // it should trigger sending of the (now non-cancelable) touchend.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(WebInputEvent::TouchEnd, sent_event().type);
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, queued_event_count());
+
+  // Ack the touchend.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now mark the scrolls as not consumed (which would cause future touchmoves
+  // in the active sequence to be sent if there was one).
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // Start a new touch sequence and verify that throttling has been reset.
+  // Touch moves after the start of scrolling will again be throttled.
+  PressTouchPoint(0, 0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  MoveTouchPoint(0, 0, 5);
+  followup_scroll.type = WebInputEvent::GestureScrollBegin;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, 0, 5);
+  followup_scroll.type = WebInputEvent::GestureScrollUpdate;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, 0, 10);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_TRUE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // As soon as a touchmove exceeds the outer slop region it will be forwarded
+  // immediately.
+  MoveTouchPoint(0, 0, 20);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Subsequent touchmove's should be deferred.
+  MoveTouchPoint(0, 0, 25);
+  EXPECT_TRUE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // The pending touchmove should be flushed with the the new touchmove if
+  // sufficient time has passed.
+  AdvanceTouchTime(kMinSecondsBetweenThrottledTouchmoves + 0.1);
+  MoveTouchPoint(0, 0, 15);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Non-touchmove events should always flush any pending touchmove events.
+  MoveTouchPoint(0, 0, 25);
+  EXPECT_TRUE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  PressTouchPoint(30, 30);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(2U, queued_event_count());
+
+  // Ack'ing the flushed, async touchmove will dispatch the touchstart. Note
+  // that the flushed touchmove's ack will not reach the client (its
+  // constituent events have already been ack'ed).
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(WebInputEvent::TouchStart, sent_event().type);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Ack the touchstart.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Send a secondary touchmove.
+  MoveTouchPoint(1, 0, 25);
+  EXPECT_TRUE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // An unconsumed scroll should resume synchronous touch handling.
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+
+  // The pending touchmove should be coalesced with the next (now synchronous)
+  // touchmove.
+  MoveTouchPoint(0, 0, 25);
+  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::StateMoved, sent_event().touches[1].state);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+
+  // Subsequent touches will queue until the preceding, synchronous touches are
+  // ack'ed.
+  ReleaseTouchPoint(1);
+  EXPECT_EQ(2U, queued_event_count());
+  ReleaseTouchPoint(0);
+  EXPECT_EQ(3U, queued_event_count());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_TRUE(sent_event().cancelable);
+  EXPECT_EQ(WebInputEvent::TouchEnd, sent_event().type);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_TRUE(sent_event().cancelable);
+  EXPECT_EQ(WebInputEvent::TouchEnd, sent_event().type);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Ensure that async touch dispatch and touch ack timeout interactions work
+// appropriately.
+TEST_F(TouchEventQueueTest, AsyncTouchWithAckTimeout) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
+
+  // The touchstart should start the timeout.
+  PressTouchPoint(0, 0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_TRUE(IsTimeoutRunning());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_FALSE(IsTimeoutRunning());
+
+  // The start of a scroll gesture should trigger async touch event dispatch.
+  MoveTouchPoint(0, 1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_TRUE(IsTimeoutRunning());
+  WebGestureEvent followup_scroll;
+  followup_scroll.type = WebInputEvent::GestureScrollBegin;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_CONSUMED);
+
+  // An async touch should fire after the throttling interval has expired, but
+  // it should not start the touch ack timeout.
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_TRUE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  AdvanceTouchTime(kMinSecondsBetweenThrottledTouchmoves + 0.1);
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_FALSE(sent_event().cancelable);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // An unconsumed scroll event will resume synchronous touchmoves, which are
+  // subject to the ack timeout.
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_TRUE(IsTimeoutRunning());
+  EXPECT_TRUE(sent_event().cancelable);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The timeout should fire, disabling touch forwarding until both acks are
+  // received and acking the timed out event.
+  RunTasksAndWait(DefaultTouchTimeoutDelay() * 2);
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  // Ack'ing the original event should trigger a cancel event.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Subsequent touchmove's should not be forwarded, even as the scroll gesture
+  // goes from unconsumed to consumed.
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  MoveTouchPoint(0, 20, 5);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  MoveTouchPoint(0, 25, 5);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+}
+
+// Ensure that if the touch ack for an async touchmove triggers a follow-up
+// touch event, that follow-up touch will be forwarded appropriately.
+TEST_F(TouchEventQueueTest, AsyncTouchWithTouchCancelAfterAck) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
+
+  PressTouchPoint(0, 0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // The start of a scroll gesture should trigger async touch event dispatch.
+  MoveTouchPoint(0, 1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  WebGestureEvent followup_scroll;
+  followup_scroll.type = WebInputEvent::GestureScrollBegin;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+
+  SendGestureEvent(WebInputEvent::GestureScrollUpdate);
+
+  // The async touchmove should be ack'ed immediately, but not forwarded.
+  // However, because the ack triggers a touchcancel, both the pending touch and
+  // the queued touchcancel should be flushed.
+  WebTouchEvent followup_cancel;
+  followup_cancel.type = WebInputEvent::TouchCancel;
+  followup_cancel.touchesLength = 1;
+  followup_cancel.touches[0].state = WebTouchPoint::StateCancelled;
+  SetFollowupEvent(followup_cancel);
+  MoveTouchPoint(0, 5, 5);
+  EXPECT_EQ(2U, queued_event_count());
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_FALSE(HasPendingAsyncTouchMove());
+  EXPECT_EQ(WebInputEvent::TouchMove, acked_event().type);
+  EXPECT_EQ(WebInputEvent::TouchMove, sent_event().type);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // The ack for the async touchmove should not reach the client, as it has
+  // already been ack'ed.
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_FALSE(sent_event().cancelable);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(WebInputEvent::TouchCancel, sent_event().type);
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(WebInputEvent::TouchCancel, acked_event().type);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+}
+
+// Ensure that the async touch is fully reset if the touch sequence restarts
+// without properly terminating.
+TEST_F(TouchEventQueueTest, AsyncTouchWithHardTouchStartReset) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
+
+  PressTouchPoint(0, 0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Trigger async touchmove dispatch.
+  MoveTouchPoint(0, 1, 1);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  WebGestureEvent followup_scroll;
+  followup_scroll.type = WebInputEvent::GestureScrollBegin;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  SendGestureEvent(WebInputEvent::GestureScrollUpdate);
+
+  // The async touchmove should be immediately ack'ed but delivery is deferred.
+  MoveTouchPoint(0, 2, 2);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(WebInputEvent::TouchMove, acked_event().type);
+
+  // The queue should be robust to hard touch restarts with a new touch
+  // sequence. In this case, the deferred async touch should not be flushed
+  // by the new touch sequence.
+  SendGestureEvent(WebInputEvent::GestureScrollEnd);
+  ResetTouchEvent();
+
+  PressTouchPoint(0, 0);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(WebInputEvent::TouchStart, sent_event().type);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+TEST_F(TouchEventQueueTest, TouchAbsorptionWithConsumedFirstMove) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 1);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, 20, 5);
+  SendGestureEvent(blink::WebInputEvent::GestureScrollBegin);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(2U, GetAndResetSentEventCount());
+
+  // Even if the first touchmove event was consumed, subsequent unconsumed
+  // touchmove events should trigger scrolling.
+  MoveTouchPoint(0, 60, 5);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_TRUE(sent_event().cancelable);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  MoveTouchPoint(0, 20, 5);
+  WebGestureEvent followup_scroll;
+  followup_scroll.type = WebInputEvent::GestureScrollUpdate;
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  SendGestureEventAck(WebInputEvent::GestureScrollUpdate,
+                      INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_TRUE(sent_event().cancelable);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+
+  // Touch move event is throttled.
+  MoveTouchPoint(0, 60, 5);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+}
+
+TEST_F(TouchEventQueueTest, UnseenTouchPointerIdsNotForwarded) {
+  SyntheticWebTouchEvent event;
+  event.PressPoint(0, 0);
+  SendTouchEvent(event);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Give the touchmove a previously unseen pointer id; it should not be sent.
+  int press_id = event.touches[0].id;
+  event.MovePoint(0, 1, 1);
+  event.touches[0].id = 7;
+  SendTouchEvent(event);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Give the touchmove a valid id; it should be sent.
+  event.touches[0].id = press_id;
+  SendTouchEvent(event);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Do the same for release.
+  event.ReleasePoint(0);
+  event.touches[0].id = 11;
+  SendTouchEvent(event);
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Give the touchmove a valid id; it should be sent.
+  event.touches[0].id = press_id;
+  SendTouchEvent(event);
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
 }  // namespace content