Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / input / touch_event_queue_unittest.cc
index 7d2c086..06a3a9f 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 {
@@ -30,19 +35,13 @@ class TouchEventQueueTest : public testing::Test,
         acked_event_count_(0),
         last_acked_event_state_(INPUT_EVENT_ACK_STATE_UNKNOWN),
         slop_length_dips_(0),
+        slop_includes_boundary_(true),
         touch_scrolling_mode_(TouchEventQueue::TOUCH_SCROLLING_MODE_DEFAULT) {}
 
   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 +77,32 @@ 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_;
+    config.touchmove_slop_suppression_region_includes_boundary =
+        slop_includes_boundary_;
+    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) {
+  void SetUpForTouchMoveSlopTesting(double slop_length_dips,
+                                    bool slop_includes_boundary) {
     slop_length_dips_ = slop_length_dips;
-    ResetQueueWithParameters(touch_scrolling_mode_, slop_length_dips_);
+    slop_includes_boundary_ = slop_includes_boundary;
+    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 +140,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 +171,10 @@ class TouchEventQueueTest : public testing::Test,
     SendTouchEvent();
   }
 
+  void AdvanceTouchTime(double seconds) {
+    touch_event_.timeStampSeconds += seconds;
+  }
+
   size_t GetAndResetAckedEventCount() {
     size_t count = acked_event_count_;
     acked_event_count_ = 0;
@@ -169,8 +195,14 @@ class TouchEventQueueTest : public testing::Test,
     queue_->OnHasTouchEventHandlers(has_handlers);
   }
 
-  bool IsTimeoutRunning() {
-    return queue_->IsTimeoutRunningForTesting();
+  void SetAckTimeoutDisabled() { queue_->SetAckTimeoutEnabled(false); }
+
+  bool IsTimeoutEnabled() const { return queue_->ack_timeout_enabled(); }
+
+  bool IsTimeoutRunning() const { return queue_->IsTimeoutRunningForTesting(); }
+
+  bool HasPendingAsyncTouchMove() const {
+    return queue_->HasPendingAsyncTouchMoveForTesting();
   }
 
   size_t queued_event_count() const {
@@ -193,15 +225,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);
   }
 
@@ -216,6 +253,7 @@ class TouchEventQueueTest : public testing::Test,
   scoped_ptr<WebGestureEvent> followup_gesture_event_;
   scoped_ptr<InputEventAckState> sync_ack_result_;
   double slop_length_dips_;
+  bool slop_includes_boundary_;
   TouchEventQueue::TouchScrollingMode touch_scrolling_mode_;
   base::MessageLoopForUI message_loop_;
 };
@@ -238,6 +276,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,6 +284,7 @@ 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 the touch-queue is emptied if a page stops listening for touch
@@ -546,11 +586,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) {
@@ -804,7 +847,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());
@@ -830,6 +874,7 @@ TEST_F(TouchEventQueueTest, NoTouchOnScroll) {
   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 +924,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 +1015,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 +1049,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 +1060,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 +1074,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 +1096,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 +1134,38 @@ 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_TRUE(IsTimeoutEnabled());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  // Now explicitly disable the timeout.
+  SetAckTimeoutDisabled();
+  EXPECT_FALSE(IsTimeoutRunning());
+  EXPECT_FALSE(IsTimeoutEnabled());
+
+  // 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);
@@ -1099,8 +1175,8 @@ TEST_F(TouchEventQueueTest, NoTouchTimeoutIfAckIsSynchronous) {
 }
 
 // Tests that the timeout is disabled if the touch handler disappears.
-TEST_F(TouchEventQueueTest, TouchTimeoutStoppedIfTouchHandlerRemoved) {
-  SetUpForTimeoutTesting(kDefaultTouchTimeoutDelayMs);
+TEST_F(TouchEventQueueTest, NoTouchTimeoutIfTouchHandlerRemoved) {
+  SetUpForTimeoutTesting(DefaultTouchTimeoutDelay());
 
   // Queue a TouchStart.
   PressTouchPoint(0, 1);
@@ -1111,10 +1187,26 @@ TEST_F(TouchEventQueueTest, TouchTimeoutStoppedIfTouchHandlerRemoved) {
   EXPECT_FALSE(IsTimeoutRunning());
 }
 
+// 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());
+
+  // 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 +1219,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 +1261,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 +1274,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 +1317,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 +1326,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,12 +1353,13 @@ 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);
+  const bool slop_includes_boundary = true;
+  SetUpForTouchMoveSlopTesting(kSlopLengthDips, slop_includes_boundary);
 
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
@@ -1305,14 +1386,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 +1427,60 @@ 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(0U, queued_event_count());
+  EXPECT_EQ(0U, GetAndResetSentEventCount());
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+}
+
+// Tests that TouchMove's are dropped if within the boundary-exclusive slop
+// suppression region for an unconsumed TouchStart.
+TEST_F(TouchEventQueueTest, TouchMoveSuppressionExcludingSlopBoundary) {
+  const double kSlopLengthDips = 10.;
+  const double kHalfSlopLengthDips = kSlopLengthDips / 2;
+  const bool slop_includes_boundary = false;
+  SetUpForTouchMoveSlopTesting(kSlopLengthDips, slop_includes_boundary);
+
+  // Queue a TouchStart.
+  PressTouchPoint(0, 0);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  ASSERT_EQ(1U, GetAndResetSentEventCount());
+  ASSERT_EQ(1U, GetAndResetAckedEventCount());
+
+  // TouchMove's within the region should be suppressed.
+  MoveTouchPoint(0, 0, kHalfSlopLengthDips);
+  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, kSlopLengthDips - 0.2f, 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());
+
+  // As soon as a TouchMove reaches the (Euclidean) slop distance, no more
+  // TouchMove's should be suppressed.
+  MoveTouchPoint(0, kSlopLengthDips, 0);
   EXPECT_EQ(1U, queued_event_count());
   EXPECT_EQ(1U, GetAndResetSentEventCount());
   EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
+
+  MoveTouchPoint(0, kHalfSlopLengthDips, 0);
+  EXPECT_EQ(1U, queued_event_count());
+  EXPECT_EQ(1U, GetAndResetSentEventCount());
+  EXPECT_EQ(0U, GetAndResetAckedEventCount());
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  EXPECT_EQ(1U, GetAndResetAckedEventCount());
 }
 
 // Tests that TouchMove's are not dropped within the slop suppression region if
@@ -1354,7 +1488,8 @@ TEST_F(TouchEventQueueTest, TouchMoveSuppressionWithinSlopRegion) {
 TEST_F(TouchEventQueueTest, NoTouchMoveSuppressionAfterTouchConsumed) {
   const double kSlopLengthDips = 10.;
   const double kHalfSlopLengthDips = kSlopLengthDips / 2;
-  SetUpForTouchMoveSlopTesting(kSlopLengthDips);
+  const bool slop_includes_boundary = true;
+  SetUpForTouchMoveSlopTesting(kSlopLengthDips, slop_includes_boundary);
 
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
@@ -1370,12 +1505,14 @@ 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 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;
-  SetUpForTouchMoveSlopTesting(kSlopLengthPixels / kDPIScale);
+  const bool slop_includes_boundary = true;
+  SetUpForTouchMoveSlopTesting(kSlopLengthPixels / kDPIScale,
+                               slop_includes_boundary);
 
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
@@ -1383,12 +1520,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,21 +1540,20 @@ 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);
+  const bool slop_includes_boundary = true;
+  SetUpForTouchMoveSlopTesting(kSlopLengthDips, slop_includes_boundary);
 
   // Queue a TouchStart.
   PressTouchPoint(0, 0);
@@ -1433,13 +1568,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 +1594,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 +1675,355 @@ 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());
+TEST_F(TouchEventQueueTest, AsyncTouch) {
+  SetTouchScrollingMode(TouchEventQueue::TOUCH_SCROLLING_MODE_ASYNC_TOUCHMOVE);
 
- for (int i = 0; i < 3; ++i) {
+  // 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, 20, 5);
+   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,
+  // but don't ACK the gesture event yet.
+  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());
+
+  // Now queue a second touchmove and verify it's not (yet) dispatched.
+  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());
+
+  // Queuing the final touchend should flush the pending, async touchmove.
+  ReleaseTouchPoint(0);
+  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 scroll as not consumed (which would cause future
+  // touchmoves in the active sequence to be sent if there was one).
+  SendGestureEventAck(WebInputEvent::GestureScrollBegin,
+      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);
+  SetFollowupEvent(followup_scroll);
+  SendTouchEventAck(INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
+  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());
+
+  // 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 asnc 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());
 }
 
 }  // namespace content