Stop deferring gestures in FlingBooster

Today, FlingBooster works by observing gestures during a fling. While it
decides whether or not the fling will be boosted, the incoming gestures
are filtered. This leads to a loss of responsiveness in this period.

In this CL, we radically simplify the FlingBooster/FlingController. We
no longer defer gestures during an active fling. When a FlingCancel is
received, the current fling is immediately stopped. The fling booster
still tracks gestures in the same way but allows them to be dispatched
so that scrolling remains responsive. If a fling occurs and meets all
the existing criteria for boosting, we start a new fling and add the
previous fling's velocity to it.

This change makes scrolling feel more responsive during a fling and also
significantly simplifies the fling and gesture code since we're no
longer modifying the gesture stream.

Change-Id: I47c0466861395f2064c4ce68d300955afd3668ec
Bug: 905593
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1630537
Reviewed-by: Navid Zolghadr <nzolghadr@chromium.org>
Reviewed-by: Timothy Dresser <tdresser@chromium.org>
Reviewed-by: Sadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: Sahel Sharify <sahel@chromium.org>
Auto-Submit: David Bokan <bokan@chromium.org>
Commit-Queue: David Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670141}
diff --git a/content/browser/renderer_host/input/fling_controller.cc b/content/browser/renderer_host/input/fling_controller.cc
index 0d6fda58..4131a5f 100644
--- a/content/browser/renderer_host/input/fling_controller.cc
+++ b/content/browser/renderer_host/input/fling_controller.cc
@@ -10,7 +10,6 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/common/content_client.h"
 #include "ui/events/base_event_utils.h"
-#include "ui/events/blink/fling_booster.h"
 #include "ui/events/gestures/blink/web_gesture_curve_impl.h"
 
 using blink::WebInputEvent;
@@ -52,7 +51,6 @@
           config.touchpad_tap_suppression_config),
       touchscreen_tap_suppression_controller_(
           config.touchscreen_tap_suppression_config),
-      fling_in_progress_(false),
       clock_(base::DefaultTickClock::GetInstance()),
       weak_ptr_factory_(this) {
   DCHECK(event_sender_client);
@@ -61,21 +59,13 @@
 
 FlingController::~FlingController() = default;
 
-bool FlingController::ShouldForwardForGFCFiltering(
-    const GestureEventWithLatencyInfo& gesture_event) const {
-  if (gesture_event.event.GetType() != WebInputEvent::kGestureFlingCancel)
-    return true;
-
-  if (fling_in_progress_)
-    return !fling_booster_->fling_cancellation_is_deferred();
-
-  return false;
-}
-
-bool FlingController::ShouldForwardForTapSuppression(
+bool FlingController::ObserveAndFilterForTapSuppression(
     const GestureEventWithLatencyInfo& gesture_event) {
   switch (gesture_event.event.GetType()) {
     case WebInputEvent::kGestureFlingCancel:
+      // The controllers' state is affected by the cancel event and assumes
+      // it's actually stopping an ongoing fling.
+      DCHECK(fling_curve_);
       if (gesture_event.event.SourceDevice() ==
           blink::WebGestureDevice::kTouchscreen) {
         touchscreen_tap_suppression_controller_
@@ -84,7 +74,7 @@
                  blink::WebGestureDevice::kTouchpad) {
         touchpad_tap_suppression_controller_.GestureFlingCancelStoppedFling();
       }
-      return true;
+      return false;
     case WebInputEvent::kGestureTapDown:
     case WebInputEvent::kGestureShowPress:
     case WebInputEvent::kGestureTapUnconfirmed:
@@ -96,61 +86,27 @@
     case WebInputEvent::kGestureTwoFingerTap:
       if (gesture_event.event.SourceDevice() ==
           blink::WebGestureDevice::kTouchscreen) {
-        return !touchscreen_tap_suppression_controller_.FilterTapEvent(
+        return touchscreen_tap_suppression_controller_.FilterTapEvent(
             gesture_event);
       }
-      return true;
+      return false;
     default:
-      return true;
+      return false;
   }
 }
 
-bool FlingController::FilterGestureEventForFlingBoosting(
-    const GestureEventWithLatencyInfo& gesture_event) {
-  if (!fling_booster_)
-    return false;
-
-  bool cancel_current_fling;
-  bool should_filter_event = fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_event.event, &cancel_current_fling);
-  if (cancel_current_fling) {
-    CancelCurrentFling();
-  }
-
-  if (should_filter_event) {
-    if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingStart) {
-      UpdateCurrentFlingState(gesture_event.event,
-                              fling_booster_->current_fling_velocity());
-      TRACE_EVENT_INSTANT2("input",
-                           fling_booster_->fling_boosted()
-                               ? "FlingController::FlingBoosted"
-                               : "FlingController::FlingReplaced",
-                           TRACE_EVENT_SCOPE_THREAD, "vx",
-                           fling_booster_->current_fling_velocity().x(), "vy",
-                           fling_booster_->current_fling_velocity().y());
-    } else if (gesture_event.event.GetType() ==
-               WebInputEvent::kGestureFlingCancel) {
-      DCHECK(fling_booster_->fling_cancellation_is_deferred());
-      TRACE_EVENT_INSTANT0("input", "FlingController::FlingBoostStart",
-                           TRACE_EVENT_SCOPE_THREAD);
-    } else if (gesture_event.event.GetType() ==
-                   WebInputEvent::kGestureScrollBegin ||
-               gesture_event.event.GetType() ==
-                   WebInputEvent::kGestureScrollUpdate) {
-      TRACE_EVENT_INSTANT0("input",
-                           "FlingController::ExtendBoostedFlingTimeout",
-                           TRACE_EVENT_SCOPE_THREAD);
-    }
-  }
-
-  return should_filter_event;
-}
-
 bool FlingController::ObserveAndMaybeConsumeGestureEvent(
     const GestureEventWithLatencyInfo& gesture_event) {
-  if (!ShouldForwardForGFCFiltering(gesture_event) ||
-      !ShouldForwardForTapSuppression(gesture_event) ||
-      FilterGestureEventForFlingBoosting(gesture_event))
+  // FlingCancel events arrive when a finger is touched down regardless of
+  // whether there is an ongoing fling. These can affect state so if there's no
+  // on-going fling we should just discard these without letting the rest of
+  // the fling system see it.
+  if (gesture_event.event.GetType() == WebInputEvent::kGestureFlingCancel &&
+      !fling_curve_) {
+    return true;
+  }
+
+  if (ObserveAndFilterForTapSuppression(gesture_event))
     return true;
 
   if (gesture_event.event.GetType() == WebInputEvent::kGestureScrollUpdate) {
@@ -165,6 +121,8 @@
     last_seen_scroll_update_ = base::TimeTicks();
   }
 
+  fling_booster_.ObserveGestureEvent(gesture_event.event);
+
   // fling_controller_ is in charge of handling GFS events and the events are
   // not sent to the renderer, the controller processes the fling and generates
   // fling progress events (wheel events for touchpad and GSU events for
@@ -186,20 +144,15 @@
 
 void FlingController::ProcessGestureFlingStart(
     const GestureEventWithLatencyInfo& gesture_event) {
-  const float vx = gesture_event.event.data.fling_start.velocity_x;
-  const float vy = gesture_event.event.data.fling_start.velocity_y;
-  if (!UpdateCurrentFlingState(gesture_event.event, gfx::Vector2dF(vx, vy)))
+  if (!UpdateCurrentFlingState(gesture_event.event))
     return;
 
-  TRACE_EVENT_ASYNC_BEGIN2("input", kFlingTraceName, this, "vx", vx, "vy", vy);
+  TRACE_EVENT_ASYNC_BEGIN2("input", kFlingTraceName, this, "vx",
+                           current_fling_parameters_.velocity.x(), "vy",
+                           current_fling_parameters_.velocity.y());
 
-  has_fling_animation_started_ = false;
   last_progress_time_ = base::TimeTicks();
-  fling_in_progress_ = true;
-  fling_booster_ = std::make_unique<ui::FlingBooster>(
-      current_fling_parameters_.velocity,
-      current_fling_parameters_.source_device,
-      current_fling_parameters_.modifiers);
+
   // Wait for BeginFrame to call ProgressFling when
   // SetNeedsBeginFrameForFlingProgress is used to progress flings instead of
   // compositor animation observer (happens on Android WebView).
@@ -215,10 +168,8 @@
 
 void FlingController::ProcessGestureFlingCancel(
     const GestureEventWithLatencyInfo& gesture_event) {
-  fling_in_progress_ = false;
-
-  if (fling_curve_)
-    CancelCurrentFling();
+  DCHECK(fling_curve_);
+  EndCurrentFling();
 }
 
 void FlingController::ProgressFling(base::TimeTicks current_time) {
@@ -226,15 +177,8 @@
     return;
 
   TRACE_EVENT_ASYNC_STEP_INTO0("input", kFlingTraceName, this, "ProgressFling");
-  DCHECK(fling_booster_);
-  fling_booster_->set_last_fling_animation_time(
-      (current_time - base::TimeTicks()).InSecondsF());
-  if (fling_booster_->MustCancelDeferredFling()) {
-    CancelCurrentFling();
-    return;
-  }
 
-  if (!has_fling_animation_started_) {
+  if (!first_fling_update_sent()) {
     // Guard against invalid as there are no guarantees fling event and progress
     // timestamps are compatible.
     if (current_fling_parameters_.start_time.is_null()) {
@@ -270,30 +214,27 @@
   bool fling_is_active = fling_curve_->Advance(
       (current_time - current_fling_parameters_.start_time).InSecondsF(),
       current_fling_parameters_.velocity, delta_to_scroll);
-  if (fling_is_active) {
-    if (std::abs(delta_to_scroll.x()) > kMinInertialScrollDelta ||
-        std::abs(delta_to_scroll.y()) > kMinInertialScrollDelta) {
-      GenerateAndSendFlingProgressEvents(delta_to_scroll);
-      has_fling_animation_started_ = true;
-      last_progress_time_ = current_time;
-    }
-    // As long as the fling curve is active, the fling progress must get
-    // scheduled even when the last delta to scroll was zero.
-    ScheduleFlingProgress();
+
+  if (!fling_is_active && current_fling_parameters_.source_device !=
+                              blink::WebGestureDevice::kSyntheticAutoscroll) {
+    EndCurrentFling();
     return;
   }
 
-  if (current_fling_parameters_.source_device !=
-      blink::WebGestureDevice::kSyntheticAutoscroll) {
-    CancelCurrentFling();
+  if (std::abs(delta_to_scroll.x()) > kMinInertialScrollDelta ||
+      std::abs(delta_to_scroll.y()) > kMinInertialScrollDelta) {
+    GenerateAndSendFlingProgressEvents(delta_to_scroll);
+    last_progress_time_ = current_time;
   }
+
+  // As long as the fling curve is active, the fling progress must get
+  // scheduled even when the last delta to scroll was zero.
+  ScheduleFlingProgress();
 }
 
 void FlingController::StopFling() {
-  if (!fling_curve_)
-    return;
-
-  CancelCurrentFling();
+  if (fling_curve_)
+    EndCurrentFling();
 }
 
 void FlingController::GenerateAndSendWheelEvents(
@@ -349,9 +290,8 @@
   switch (current_fling_parameters_.source_device) {
     case blink::WebGestureDevice::kTouchpad: {
       blink::WebMouseWheelEvent::Phase phase =
-          has_fling_animation_started_
-              ? blink::WebMouseWheelEvent::kPhaseChanged
-              : blink::WebMouseWheelEvent::kPhaseBegan;
+          first_fling_update_sent() ? blink::WebMouseWheelEvent::kPhaseChanged
+                                    : blink::WebMouseWheelEvent::kPhaseBegan;
       GenerateAndSendWheelEvents(delta, phase);
       break;
     }
@@ -386,62 +326,27 @@
   }
 }
 
-void FlingController::CancelCurrentFling() {
-  bool had_active_fling = !!fling_curve_;
-  fling_curve_.reset();
-  has_fling_animation_started_ = false;
+void FlingController::EndCurrentFling() {
   last_progress_time_ = base::TimeTicks();
-  fling_in_progress_ = false;
 
-  // Extract the last event filtered by the fling booster if it exists.
-  bool fling_cancellation_is_deferred =
-      fling_booster_ && fling_booster_->fling_cancellation_is_deferred();
-  WebGestureEvent last_fling_boost_event;
-  if (fling_cancellation_is_deferred)
-    last_fling_boost_event = fling_booster_->last_boost_event();
-
-  // Reset the state of the fling.
-  fling_booster_.reset();
   GenerateAndSendFlingEndEvents();
   current_fling_parameters_ = ActiveFlingParameters();
 
-  // Synthesize a GestureScrollBegin, as the original event was suppressed. It
-  // is important to send the GSB after resetting the fling_booster_ otherwise
-  // it will get filtered by the booster again. This is necessary for
-  // touchscreen fling cancelation only, since autoscroll fling cancelation
-  // doesn't get deferred and when the touchpad fling cancelation gets deferred,
-  // the first wheel event after the cancelation will cause a GSB generation.
-  if (fling_cancellation_is_deferred &&
-      last_fling_boost_event.SourceDevice() ==
-          blink::WebGestureDevice::kTouchscreen &&
-      (last_fling_boost_event.GetType() == WebInputEvent::kGestureScrollBegin ||
-       last_fling_boost_event.GetType() ==
-           WebInputEvent::kGestureScrollUpdate)) {
-    WebGestureEvent scroll_begin_event;
-    if (last_fling_boost_event.GetType() ==
-        WebInputEvent::kGestureScrollUpdate) {
-      scroll_begin_event =
-          ui::ScrollBeginFromScrollUpdate(last_fling_boost_event);
-    } else {
-      scroll_begin_event = last_fling_boost_event;
-    }
-    event_sender_client_->SendGeneratedGestureScrollEvents(
-        GestureEventWithLatencyInfo(
-            scroll_begin_event,
-            ui::LatencyInfo(ui::SourceEventType::INERTIAL)));
-  }
-
-  if (had_active_fling) {
+  if (fling_curve_) {
     scheduler_client_->DidStopFlingingOnBrowser(weak_ptr_factory_.GetWeakPtr());
     TRACE_EVENT_ASYNC_END0("input", kFlingTraceName, this);
   }
+
+  fling_curve_.reset();
 }
 
 bool FlingController::UpdateCurrentFlingState(
-    const WebGestureEvent& fling_start_event,
-    const gfx::Vector2dF& velocity) {
+    const WebGestureEvent& fling_start_event) {
   DCHECK_EQ(WebInputEvent::kGestureFlingStart, fling_start_event.GetType());
 
+  const gfx::Vector2dF velocity =
+      fling_booster_.GetVelocityForFlingStart(fling_start_event);
+
   current_fling_parameters_.velocity = velocity;
   current_fling_parameters_.point = fling_start_event.PositionInWidget();
   current_fling_parameters_.global_point = fling_start_event.PositionInScreen();
@@ -460,7 +365,7 @@
 
   if (velocity.IsZero() && fling_start_event.SourceDevice() !=
                                blink::WebGestureDevice::kSyntheticAutoscroll) {
-    CancelCurrentFling();
+    EndCurrentFling();
     return false;
   }
 
@@ -473,10 +378,6 @@
   return true;
 }
 
-bool FlingController::FlingCancellationIsDeferred() const {
-  return fling_booster_ && fling_booster_->fling_cancellation_is_deferred();
-}
-
 gfx::Vector2dF FlingController::CurrentFlingVelocity() const {
   return current_fling_parameters_.velocity;
 }
diff --git a/content/browser/renderer_host/input/fling_controller.h b/content/browser/renderer_host/input/fling_controller.h
index bcb5aec2..a71152d 100644
--- a/content/browser/renderer_host/input/fling_controller.h
+++ b/content/browser/renderer_host/input/fling_controller.h
@@ -8,15 +8,12 @@
 #include "content/browser/renderer_host/input/touchpad_tap_suppression_controller.h"
 #include "content/browser/renderer_host/input/touchscreen_tap_suppression_controller.h"
 #include "content/public/common/input_event_ack_state.h"
+#include "ui/events/blink/fling_booster.h"
 
 namespace blink {
 class WebGestureCurve;
 }
 
-namespace ui {
-class FlingBooster;
-}
-
 namespace content {
 
 class FlingController;
@@ -95,9 +92,7 @@
   void ProcessGestureFlingCancel(
       const GestureEventWithLatencyInfo& gesture_event);
 
-  bool fling_in_progress() const { return fling_in_progress_; }
-
-  bool FlingCancellationIsDeferred() const;
+  bool fling_in_progress() const { return fling_curve_.get(); }
 
   gfx::Vector2dF CurrentFlingVelocity() const;
 
@@ -107,20 +102,11 @@
   void set_clock_for_testing(const base::TickClock* clock) { clock_ = clock; }
 
  protected:
-  std::unique_ptr<ui::FlingBooster> fling_booster_;
+  ui::FlingBooster fling_booster_;
 
  private:
-  // Sub-filter for removing unnecessary GestureFlingCancels.
-  bool ShouldForwardForGFCFiltering(
-      const GestureEventWithLatencyInfo& gesture_event) const;
-
   // Sub-filter for suppressing taps immediately after a GestureFlingCancel.
-  bool ShouldForwardForTapSuppression(
-      const GestureEventWithLatencyInfo& gesture_event);
-
-  // Sub-filter for suppressing gesture events to boost an active fling whenever
-  // possible.
-  bool FilterGestureEventForFlingBoosting(
+  bool ObserveAndFilterForTapSuppression(
       const GestureEventWithLatencyInfo& gesture_event);
 
   void ScheduleFlingProgress();
@@ -145,10 +131,17 @@
 
   void GenerateAndSendFlingEndEvents();
 
-  void CancelCurrentFling();
+  void EndCurrentFling();
 
-  bool UpdateCurrentFlingState(const blink::WebGestureEvent& fling_start_event,
-                               const gfx::Vector2dF& velocity);
+  // Used to update the fling_curve_ state based on the parameters of the fling
+  // start event. Returns true if the fling curve was updated for a valid
+  // fling. Returns false if the parameters should not cause a fling and the
+  // fling_curve_ is not updated.
+  bool UpdateCurrentFlingState(const blink::WebGestureEvent& fling_start_event);
+
+  bool first_fling_update_sent() const {
+    return !last_progress_time_.is_null();
+  }
 
   FlingControllerEventSenderClient* event_sender_client_;
 
@@ -164,18 +157,12 @@
   // canceling tap.
   TouchscreenTapSuppressionController touchscreen_tap_suppression_controller_;
 
-  // Gesture curve of the current active fling.
+  // Gesture curve of the current active fling. nullptr while a fling is not
+  // active.
   std::unique_ptr<blink::WebGestureCurve> fling_curve_;
 
   ActiveFlingParameters current_fling_parameters_;
 
-  // True when a fling is active.
-  bool fling_in_progress_;
-
-  // Whether an active fling has seen a |ProgressFling()| call. This is useful
-  // for determining if the fling start time should be re-initialized.
-  bool has_fling_animation_started_;
-
   // The last time fling progress events were sent.
   base::TimeTicks last_progress_time_;
 
diff --git a/content/browser/renderer_host/input/fling_controller_unittest.cc b/content/browser/renderer_host/input/fling_controller_unittest.cc
index 1405a5ad..c9f2f2d0 100644
--- a/content/browser/renderer_host/input/fling_controller_unittest.cc
+++ b/content/browser/renderer_host/input/fling_controller_unittest.cc
@@ -30,8 +30,6 @@
                       FlingControllerSchedulerClient* scheduler_client,
                       const Config& config)
       : FlingController(event_sender_client, scheduler_client, config) {}
-
-  bool FlingBoosted() const { return fling_booster_->fling_boosted(); }
 };
 
 class FlingControllerTest : public FlingControllerEventSenderClient,
@@ -138,7 +136,6 @@
   }
 
   bool FlingInProgress() { return fling_controller_->fling_in_progress(); }
-  bool FlingBoosted() { return fling_controller_->FlingBoosted(); }
 
   void AdvanceTime(double time_delta_ms = kFrameDelta) {
     mock_clock_.Advance(base::TimeDelta::FromMillisecondsD(time_delta_ms));
@@ -211,17 +208,9 @@
   EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
-  // Now cancel the fling. The GFC will get suppressed by fling booster.
+  // Now cancel the fling.
   SimulateFlingCancel(blink::WebGestureDevice::kTouchpad);
-  EXPECT_TRUE(FlingInProgress());
-
-  // Wait for the boosting timer to expire. The delayed cancelation must work.
-  AdvanceTime(500);
-  ProgressFling(NowTicks());
   EXPECT_FALSE(FlingInProgress());
-  EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, last_sent_wheel_.momentum_phase);
-  EXPECT_EQ(0.f, last_sent_wheel_.delta_x);
-  EXPECT_EQ(0.f, last_sent_wheel_.delta_y);
 }
 
 // Ensure that the start time of a fling is measured from the last received
@@ -261,13 +250,11 @@
             last_sent_gesture_.data.scroll_update.inertial_phase);
   EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);
 
-  // Now cancel the fling. The GFC will get suppressed by fling booster.
+  // Now cancel the fling.
   SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
-  EXPECT_TRUE(FlingInProgress());
+  EXPECT_FALSE(FlingInProgress());
 
-  // Wait for the boosting timer to expire. The delayed cancelation must work.
-  AdvanceTime(500);
-  ProgressFling(NowTicks());
+  // Cancellation should send a GSE.
   EXPECT_FALSE(FlingInProgress());
   EXPECT_EQ(WebInputEvent::kGestureScrollEnd, last_sent_gesture_.GetType());
 }
@@ -370,21 +357,27 @@
   EXPECT_EQ(WebInputEvent::kGestureScrollEnd, last_sent_gesture_.GetType());
 }
 
-TEST_P(FlingControllerTest, GestureFlingCancelsFiltered) {
-  // GFC without previous GFS is dropped.
-  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
-  EXPECT_FALSE(FlingInProgress());
+TEST_P(FlingControllerTest, GestureFlingCancelOutsideFling) {
+  // FlingCancel without a FlingStart doesn't cause issues, doesn't send any
+  // events.
+  {
+    int current_sent_scroll_gesture_count = sent_scroll_gesture_count_;
+    SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
+    EXPECT_FALSE(FlingInProgress());
+    EXPECT_EQ(current_sent_scroll_gesture_count, sent_scroll_gesture_count_);
+  }
 
-  // GFC after previous GFS is filtered by fling booster.
-  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
-                     gfx::Vector2dF(1000, 0));
-  EXPECT_TRUE(FlingInProgress());
-  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
-  EXPECT_TRUE(FlingInProgress());
-
-  // Any other GFC while the fling cancelation is deferred gets filtered.
-  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
-  EXPECT_TRUE(FlingInProgress());
+  // Do a fling and cancel it. Make sure another cancel is also a no-op.
+  {
+    SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
+                       gfx::Vector2dF(1000, 0));
+    AdvanceTime();
+    ProgressFling(NowTicks());
+    SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
+    int current_sent_scroll_gesture_count = sent_scroll_gesture_count_;
+    SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
+    EXPECT_EQ(current_sent_scroll_gesture_count, sent_scroll_gesture_count_);
+  }
 }
 
 TEST_P(FlingControllerTest, GestureFlingNotCancelledBySmallTimeDelta) {
@@ -479,13 +472,7 @@
   EXPECT_GT(wheel_event_count_, 0);
 }
 
-#if defined(OS_LINUX)
-#define MAYBE_ControllerBoostsTouchpadFling \
-  DISABLED_ControllerBoostsTouchpadFling
-#else
-#define MAYBE_ControllerBoostsTouchpadFling ControllerBoostsTouchpadFling
-#endif
-TEST_P(FlingControllerTest, MAYBE_ControllerBoostsTouchpadFling) {
+TEST_P(FlingControllerTest, ControllerBoostsTouchpadFling) {
   SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                      gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
@@ -510,15 +497,14 @@
   EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, last_sent_wheel_.momentum_phase);
   EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
 
-  // Now cancel the fling. The GFC will get suppressed by fling booster.
+  // Now cancel the fling.
   SimulateFlingCancel(blink::WebGestureDevice::kTouchpad);
-  EXPECT_TRUE(FlingInProgress());
+  EXPECT_FALSE(FlingInProgress());
 
   // The second GFS will boost the current active fling.
   SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                      gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
-  EXPECT_TRUE(FlingBoosted());
 }
 
 TEST_P(FlingControllerTest, ControllerBoostsTouchscreenFling) {
@@ -533,15 +519,16 @@
             last_sent_gesture_.data.scroll_update.inertial_phase);
   EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);
 
-  // Now cancel the fling. The GFC will get suppressed by fling booster.
+  // Now cancel the fling.
   SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
-  EXPECT_TRUE(FlingInProgress());
+  EXPECT_FALSE(FlingInProgress());
 
-  // The second GFS will boost the current active fling.
+  // The second GFS can be boosted so it should boost the just deactivated
+  // fling.
   SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                      gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
-  EXPECT_TRUE(FlingBoosted());
+  EXPECT_GT(fling_controller_->CurrentFlingVelocity().x(), 1000);
 }
 
 TEST_P(FlingControllerTest, ControllerNotifiesTheClientAfterFlingStart) {
@@ -549,16 +536,9 @@
                      gfx::Vector2dF(1000, 0));
   EXPECT_TRUE(FlingInProgress());
 
-  // Now cancel the fling. The GFC will get suppressed by fling booster.
+  // Now cancel the fling.
   SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
-  EXPECT_TRUE(FlingInProgress());
-
-  // Wait for the boosting timer to expire. The delayed cancelation must work
-  // and the client must be notified after fling cancelation.
-  AdvanceTime(500);
-  ProgressFling(NowTicks());
   EXPECT_FALSE(FlingInProgress());
-  EXPECT_EQ(WebInputEvent::kGestureScrollEnd, last_sent_gesture_.GetType());
   EXPECT_TRUE(notified_client_after_fling_stop_);
 }
 
@@ -580,7 +560,7 @@
   SimulateFlingStart(blink::WebGestureDevice::kSyntheticAutoscroll,
                      gfx::Vector2dF(2000, 0));
   EXPECT_TRUE(FlingInProgress());
-  EXPECT_FALSE(FlingBoosted());
+  EXPECT_EQ(fling_controller_->CurrentFlingVelocity().x(), 2000);
 
   // Now cancel the fling. The GFC won't get suppressed by fling booster since
   // autoscroll fling doesn't have boosting.
diff --git a/content/browser/renderer_host/input/gesture_event_queue.cc b/content/browser/renderer_host/input/gesture_event_queue.cc
index d266c05..c925ba5 100644
--- a/content/browser/renderer_host/input/gesture_event_queue.cc
+++ b/content/browser/renderer_host/input/gesture_event_queue.cc
@@ -75,10 +75,6 @@
   fling_controller_.StopFling();
 }
 
-bool GestureEventQueue::FlingCancellationIsDeferred() const {
-  return fling_controller_.FlingCancellationIsDeferred();
-}
-
 gfx::Vector2dF GestureEventQueue::CurrentFlingVelocity() const {
   return fling_controller_.CurrentFlingVelocity();
 }
diff --git a/content/browser/renderer_host/input/gesture_event_queue.h b/content/browser/renderer_host/input/gesture_event_queue.h
index 48976bcf..c279724 100644
--- a/content/browser/renderer_host/input/gesture_event_queue.h
+++ b/content/browser/renderer_host/input/gesture_event_queue.h
@@ -118,8 +118,6 @@
   // Calls |fling_controller_.StopFling| to halt an active fling if such exists.
   void StopFling();
 
-  bool FlingCancellationIsDeferred() const;
-
   gfx::Vector2dF CurrentFlingVelocity() const;
 
   void set_debounce_interval_time_ms_for_testing(int interval_ms) {
diff --git a/content/browser/renderer_host/input/gesture_event_queue_unittest.cc b/content/browser/renderer_host/input/gesture_event_queue_unittest.cc
index 496beb3a..ff22573 100644
--- a/content/browser/renderer_host/input/gesture_event_queue_unittest.cc
+++ b/content/browser/renderer_host/input/gesture_event_queue_unittest.cc
@@ -215,9 +215,6 @@
   }
 
   bool FlingInProgress() { return queue()->FlingInProgressForTest(); }
-  bool FlingCancellationIsDeferred() {
-    return queue()->FlingCancellationIsDeferred();
-  }
 
   GestureEventQueue* queue() const {
     return queue_.get();
@@ -476,7 +473,7 @@
 
 // Test that the fling cancelling tap down event and its following tap get
 // suppressed when tap suppression is enabled.
-TEST_F(GestureEventQueueTest, TapGetsSuppressedAfterTapDownCancellsFling) {
+TEST_F(GestureEventQueueTest, TapGetsSuppressedAfterTapDownCancelsFling) {
   SetUpForTapSuppression(400);
   // The velocity of the event must be large enough to make sure that the fling
   // is still active when the tap down happens.
@@ -492,7 +489,6 @@
   // fling cancel event is not sent to the renderer.
   SimulateGestureEvent(WebInputEvent::kGestureFlingCancel,
                        blink::WebGestureDevice::kTouchscreen);
-  EXPECT_TRUE(FlingCancellationIsDeferred());
   EXPECT_EQ(0U, GetAndResetSentGestureEventCount());
   EXPECT_EQ(0U, GestureEventQueueSize());
   RunUntilIdle();
diff --git a/content/browser/renderer_host/input/input_router.h b/content/browser/renderer_host/input/input_router.h
index 3a32def..ed482871 100644
--- a/content/browser/renderer_host/input/input_router.h
+++ b/content/browser/renderer_host/input/input_router.h
@@ -91,9 +91,6 @@
   // Used to stop an active fling if such exists.
   virtual void StopFling() = 0;
 
-  // Used to check if a fling cancellation is deferred due to boosting or not.
-  virtual bool FlingCancellationIsDeferred() = 0;
-
   // Called when a set-touch-action message is received from the renderer
   // for a touch start event that is currently in flight.
   virtual void OnSetTouchAction(cc::TouchAction touch_action) = 0;
diff --git a/content/browser/renderer_host/input/input_router_impl.cc b/content/browser/renderer_host/input/input_router_impl.cc
index 0eaa9de..b0b6cde 100644
--- a/content/browser/renderer_host/input/input_router_impl.cc
+++ b/content/browser/renderer_host/input/input_router_impl.cc
@@ -140,8 +140,7 @@
 
 void InputRouterImpl::SendGestureEvent(
     const GestureEventWithLatencyInfo& original_gesture_event) {
-  input_stream_validator_.Validate(original_gesture_event.event,
-                                   FlingCancellationIsDeferred());
+  input_stream_validator_.Validate(original_gesture_event.event);
 
   GestureEventWithLatencyInfo gesture_event(original_gesture_event);
 
@@ -261,10 +260,6 @@
   gesture_event_queue_.StopFling();
 }
 
-bool InputRouterImpl::FlingCancellationIsDeferred() {
-  return gesture_event_queue_.FlingCancellationIsDeferred();
-}
-
 void InputRouterImpl::ProcessDeferredGestureEventQueue() {
   GestureEventQueue::GestureQueue deferred_gesture_events =
       gesture_event_queue_.TakeDeferredEvents();
diff --git a/content/browser/renderer_host/input/input_router_impl.h b/content/browser/renderer_host/input/input_router_impl.h
index 745bac2..b4b8f4da 100644
--- a/content/browser/renderer_host/input/input_router_impl.h
+++ b/content/browser/renderer_host/input/input_router_impl.h
@@ -86,7 +86,6 @@
   void BindHost(mojom::WidgetInputHandlerHostRequest request,
                 bool frame_handler) override;
   void StopFling() override;
-  bool FlingCancellationIsDeferred() override;
   void OnSetTouchAction(cc::TouchAction touch_action) override;
   void ForceSetTouchActionAuto() override;
 
diff --git a/content/browser/renderer_host/input/mock_input_router.cc b/content/browser/renderer_host/input/mock_input_router.cc
index 142067c..d2890cbe 100644
--- a/content/browser/renderer_host/input/mock_input_router.cc
+++ b/content/browser/renderer_host/input/mock_input_router.cc
@@ -45,10 +45,6 @@
   return cc::kTouchActionAuto;
 }
 
-bool MockInputRouter::FlingCancellationIsDeferred() {
-  return false;
-}
-
 void MockInputRouter::OnHasTouchEventHandlers(bool has_handlers) {
   has_handlers_ = has_handlers;
 }
diff --git a/content/browser/renderer_host/input/mock_input_router.h b/content/browser/renderer_host/input/mock_input_router.h
index 31ca101..9bc5dab 100644
--- a/content/browser/renderer_host/input/mock_input_router.h
+++ b/content/browser/renderer_host/input/mock_input_router.h
@@ -47,7 +47,6 @@
   void BindHost(mojom::WidgetInputHandlerHostRequest request,
                 bool frame_handler) override {}
   void StopFling() override {}
-  bool FlingCancellationIsDeferred() override;
   void OnSetTouchAction(cc::TouchAction touch_action) override {}
   void ForceSetTouchActionAuto() override {}
   void OnHasTouchEventHandlers(bool has_handlers) override;
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index ac57699..2bd19c8 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1304,18 +1304,8 @@
 
   bool scroll_update_needs_wrapping = false;
   if (gesture_event.GetType() == blink::WebInputEvent::kGestureScrollBegin) {
-    // When a user starts scrolling while a fling is active, the GSB will arrive
-    // when is_in_gesture_scroll_[gesture_event.SourceDevice()] is still true.
-    // This is because the fling controller defers handling the GFC event
-    // arrived before the GSB and doesn't send a GSE to end the fling; Instead,
-    // it waits for a second GFS to arrive and boost the current active fling if
-    // possible. While GFC handling is deferred the controller suppresses the
-    // GSB and GSU events instead of sending them to the renderer and continues
-    // to progress the fling. So, the renderer doesn't receive two GSB events
-    // without any GSE in between.
-    DCHECK(!is_in_gesture_scroll_[static_cast<int>(
-               gesture_event.SourceDevice())] ||
-           FlingCancellationIsDeferred());
+    DCHECK(
+        !is_in_gesture_scroll_[static_cast<int>(gesture_event.SourceDevice())]);
     is_in_gesture_scroll_[static_cast<int>(gesture_event.SourceDevice())] =
         true;
   } else if (gesture_event.GetType() ==
@@ -3089,10 +3079,6 @@
   input_router_->StopFling();
 }
 
-bool RenderWidgetHostImpl::FlingCancellationIsDeferred() const {
-  return input_router_->FlingCancellationIsDeferred();
-}
-
 void RenderWidgetHostImpl::SetScreenOrientationForTesting(
     uint16_t angle,
     ScreenOrientationValues type) {
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index 77ec094..30f9aee 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -699,7 +699,6 @@
 
   void ProgressFlingIfNeeded(base::TimeTicks current_time);
   void StopFling();
-  bool FlingCancellationIsDeferred() const;
   void SetNeedsBeginFrameForFlingProgress();
 
   // The RenderWidgetHostImpl will keep showing the old page (for a while) after
diff --git a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
index d665ce6..0023fe4 100644
--- a/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
+++ b/content/browser/renderer_host/render_widget_host_view_aura_unittest.cc
@@ -2209,11 +2209,13 @@
   view_->OnScrollEvent(&scroll2);
   base::RunLoop().RunUntilIdle();
 
-  EXPECT_TRUE(widget_host_->FlingCancellationIsDeferred());
   events = GetAndResetDispatchedMessages();
-  EXPECT_EQ("MouseWheel", GetMessageNames(events));
+  EXPECT_EQ("MouseWheel GestureScrollEnd MouseWheel", GetMessageNames(events));
   wheel_event = static_cast<const WebMouseWheelEvent*>(
       events[0]->ToEvent()->Event()->web_event.get());
+  EXPECT_EQ(WebMouseWheelEvent::kPhaseEnded, wheel_event->momentum_phase);
+  wheel_event = static_cast<const WebMouseWheelEvent*>(
+      events[2]->ToEvent()->Event()->web_event.get());
   EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, wheel_event->phase);
 }
 
diff --git a/content/common/input/gesture_event_stream_validator.cc b/content/common/input/gesture_event_stream_validator.cc
index 52b6082..00748c2 100644
--- a/content/common/input/gesture_event_stream_validator.cc
+++ b/content/common/input/gesture_event_stream_validator.cc
@@ -23,7 +23,6 @@
 
 bool GestureEventStreamValidator::Validate(
     const blink::WebGestureEvent& event,
-    const bool fling_cancellation_is_deferred,
     std::string* error_msg) {
   DCHECK(error_msg);
   error_msg->clear();
@@ -33,7 +32,7 @@
   }
   switch (event.GetType()) {
     case WebInputEvent::kGestureScrollBegin:
-      if (scrolling_ && !fling_cancellation_is_deferred)
+      if (scrolling_)
         error_msg->append("Scroll begin during scroll\n");
       if (pinching_)
         error_msg->append("Scroll begin during pinch\n");
@@ -114,10 +113,4 @@
   return error_msg->empty();
 }
 
-bool GestureEventStreamValidator::Validate(const blink::WebGestureEvent& event,
-                                           std::string* error_msg) {
-  return Validate(event, /* fling_cancellation_is_deferred = */ false,
-                  error_msg);
-}
-
 }  // namespace content
diff --git a/content/common/input/gesture_event_stream_validator.h b/content/common/input/gesture_event_stream_validator.h
index 49303e1..5d8ae8f3 100644
--- a/content/common/input/gesture_event_stream_validator.h
+++ b/content/common/input/gesture_event_stream_validator.h
@@ -24,9 +24,6 @@
 
   // If |event| is valid for the current stream, returns true.
   // Otherwise, returns false with a corresponding error message.
-  bool Validate(const blink::WebGestureEvent& event,
-                const bool fling_cancellation_is_deferred,
-                std::string* error_msg);
   bool Validate(const blink::WebGestureEvent& event, std::string* error_msg);
 
  private:
diff --git a/content/common/input/input_event_stream_validator.cc b/content/common/input/input_event_stream_validator.cc
index 237a333..731aa1f4 100644
--- a/content/common/input/input_event_stream_validator.cc
+++ b/content/common/input/input_event_stream_validator.cc
@@ -25,28 +25,24 @@
 InputEventStreamValidator::~InputEventStreamValidator() {
 }
 
-void InputEventStreamValidator::Validate(
-    const WebInputEvent& event,
-    const bool fling_cancellation_is_deferred /* = false */) {
+void InputEventStreamValidator::Validate(const WebInputEvent& event) {
   if (!enabled_)
     return;
 
-  DCHECK(ValidateImpl(event, fling_cancellation_is_deferred, &error_msg_))
+  DCHECK(ValidateImpl(event, &error_msg_))
       << error_msg_
       << "\nInvalid Event: " << ui::WebInputEventTraits::ToString(event);
 }
 
 bool InputEventStreamValidator::ValidateImpl(
     const blink::WebInputEvent& event,
-    const bool fling_cancellation_is_deferred,
     std::string* error_msg) {
   DCHECK(error_msg);
   if (WebInputEvent::IsGestureEventType(event.GetType())) {
     const WebGestureEvent& gesture = static_cast<const WebGestureEvent&>(event);
     // TODO(jdduke): Validate touchpad gesture streams.
     if (gesture.SourceDevice() == blink::WebGestureDevice::kTouchscreen)
-      return gesture_validator_.Validate(
-          gesture, fling_cancellation_is_deferred, error_msg);
+      return gesture_validator_.Validate(gesture, error_msg);
   } else if (WebInputEvent::IsTouchEventType(event.GetType())) {
     const WebTouchEvent& touch = static_cast<const WebTouchEvent&>(event);
     return touch_validator_.Validate(touch, error_msg);
diff --git a/content/common/input/input_event_stream_validator.h b/content/common/input/input_event_stream_validator.h
index 7bf2238e..6bf8625 100644
--- a/content/common/input/input_event_stream_validator.h
+++ b/content/common/input/input_event_stream_validator.h
@@ -24,12 +24,10 @@
   InputEventStreamValidator();
   ~InputEventStreamValidator();
 
-  void Validate(const blink::WebInputEvent&,
-                const bool fling_cancellation_is_deferred = false);
+  void Validate(const blink::WebInputEvent&);
 
  private:
   bool ValidateImpl(const blink::WebInputEvent&,
-                    const bool fling_cancellation_is_deferred,
                     std::string* error_msg);
 
   GestureEventStreamValidator gesture_validator_;
diff --git a/ui/events/blink/fling_booster.cc b/ui/events/blink/fling_booster.cc
index 2fa2ae2..39ab3ba7 100644
--- a/ui/events/blink/fling_booster.cc
+++ b/ui/events/blink/fling_booster.cc
@@ -20,176 +20,153 @@
 // ticks, scrolls or flings of sufficient velocity relative to the current fling
 // are received. The default value on Android native views is 40ms, but we use a
 // slightly increased value to accomodate small IPC message delays.
-const double kFlingBoostTimeoutDelaySeconds = 0.05;
+constexpr base::TimeDelta kFlingBoostTimeoutDelay =
+    base::TimeDelta::FromSecondsD(0.05);
 }  // namespace
 
 namespace ui {
 
-FlingBooster::FlingBooster(const gfx::Vector2dF& fling_velocity,
-                           blink::WebGestureDevice source_device,
-                           int modifiers)
-    : current_fling_velocity_(fling_velocity),
-      source_device_(source_device),
-      modifiers_(modifiers),
-      deferred_fling_cancel_time_seconds_(0),
-      last_fling_animate_time_seconds_(0),
-      fling_boosted_(false) {}
+gfx::Vector2dF FlingBooster::GetVelocityForFlingStart(
+    const blink::WebGestureEvent& fling_start) {
+  DCHECK_EQ(blink::WebInputEvent::kGestureFlingStart, fling_start.GetType());
+  gfx::Vector2dF velocity(fling_start.data.fling_start.velocity_x,
+                          fling_start.data.fling_start.velocity_y);
 
-bool FlingBooster::FilterGestureEventForFlingBoosting(
-    const WebGestureEvent& gesture_event,
-    bool* out_cancel_current_fling) {
-  DCHECK(out_cancel_current_fling);
-  *out_cancel_current_fling = false;
+  if (ShouldBoostFling(fling_start))
+    velocity += current_fling_velocity_;
 
-  if (gesture_event.GetType() == WebInputEvent::kGestureFlingCancel) {
-    if (gesture_event.data.fling_cancel.prevent_boosting)
-      return false;
+  Reset();
 
-    if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
-      return false;
+  current_fling_velocity_ = velocity;
+  source_device_ = fling_start.SourceDevice();
+  modifiers_ = fling_start.GetModifiers();
 
-    deferred_fling_cancel_time_seconds_ =
-        gesture_event.TimeStamp().since_origin().InSecondsF() +
-        kFlingBoostTimeoutDelaySeconds;
-    return true;
-  }
+  return current_fling_velocity_;
+}
 
-  // A fling is either inactive or is "free spinning", i.e., has yet to be
-  // interrupted by a touch gesture, in which case there is nothing to filter.
-  if (!deferred_fling_cancel_time_seconds_)
-    return false;
+void FlingBooster::ObserveGestureEvent(const WebGestureEvent& gesture_event) {
+  if (current_fling_velocity_.IsZero())
+    return;
 
-  // Gestures from a different source should immediately interrupt the fling.
-  if (gesture_event.SourceDevice() != source_device_) {
-    *out_cancel_current_fling = true;
-    return false;
-  }
+  // Gestures from a different source should prevent boosting.
+  if (gesture_event.SourceDevice() != source_device_)
+    Reset();
 
   switch (gesture_event.GetType()) {
-    case WebInputEvent::kGestureTapCancel:
-    case WebInputEvent::kGestureTapDown:
-      return false;
-
-    case WebInputEvent::kGestureScrollBegin:
-      // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
-      // determine if the ScrollBegin should immediately cancel the fling.
-      ExtendBoostedFlingTimeout(gesture_event);
-      return true;
-
+    case WebInputEvent::kGestureScrollBegin: {
+      cutoff_time_for_boost_ =
+          gesture_event.TimeStamp() + kFlingBoostTimeoutDelay;
+      break;
+    }
     case WebInputEvent::kGestureScrollUpdate: {
       if (gesture_event.data.scroll_update.inertial_phase ==
           WebGestureEvent::InertialPhaseState::kMomentum) {
-        // GSU events in momentum phase are generated by FlingController to
-        // progress fling and should not interfere with fling boosting.
-        return false;
+        return;
       }
 
-      if (ShouldSuppressScrollForFlingBoosting(gesture_event)) {
-        ExtendBoostedFlingTimeout(gesture_event);
-        return true;
+      if (cutoff_time_for_boost_.is_null())
+        return;
+
+      if (gesture_event.TimeStamp() > cutoff_time_for_boost_) {
+        Reset();
+        return;
       }
 
-      *out_cancel_current_fling = true;
-      return false;
+      // If the user scrolls in a direction counter to the current scroll, don't
+      // boost.
+      gfx::Vector2dF delta(gesture_event.data.scroll_update.delta_x,
+                           gesture_event.data.scroll_update.delta_y);
+      if (gfx::DotProduct(current_fling_velocity_, delta) <= 0) {
+        Reset();
+        return;
+      }
+
+      // Scrolls must be of sufficient velocity to maintain the active fling.
+      // Unfortunately we can't simply use the velocity_x|y fields on the
+      // gesture event because they're not populated when converting from
+      // Android's MotionEvents.
+      if (!previous_boosting_scroll_timestamp_.is_null()) {
+        const double time_since_last_boost_event =
+            (gesture_event.TimeStamp() - previous_boosting_scroll_timestamp_)
+                .InSecondsF();
+        if (time_since_last_boost_event >= 0.001) {
+          const gfx::Vector2dF scroll_velocity =
+              gfx::ScaleVector2d(delta, 1. / time_since_last_boost_event);
+          if (scroll_velocity.LengthSquared() <
+              kMinBoostTouchScrollSpeedSquare) {
+            Reset();
+            return;
+          }
+        }
+      }
+
+      previous_boosting_scroll_timestamp_ = gesture_event.TimeStamp();
+      cutoff_time_for_boost_ =
+          gesture_event.TimeStamp() + kFlingBoostTimeoutDelay;
+      break;
     }
-
-    case WebInputEvent::kGestureScrollEnd:
-      // Clear the last fling boost event *prior* to fling cancellation,
-      // preventing insertion of a synthetic GestureScrollBegin.
-      last_fling_boost_event_ = WebGestureEvent();
-      *out_cancel_current_fling = true;
-      return true;
-
-    case WebInputEvent::kGestureFlingStart: {
-      DCHECK_EQ(source_device_, gesture_event.SourceDevice());
-      gfx::Vector2dF new_fling_velocity(
-          gesture_event.data.fling_start.velocity_x,
-          gesture_event.data.fling_start.velocity_y);
-      DCHECK(!new_fling_velocity.IsZero());
-
-      fling_boosted_ = ShouldBoostFling(gesture_event);
-      if (fling_boosted_)
-        current_fling_velocity_ += new_fling_velocity;
-      else
-        current_fling_velocity_ = new_fling_velocity;
-
-      deferred_fling_cancel_time_seconds_ = 0;
-      last_fling_boost_event_ = WebGestureEvent();
-      return true;
+    case WebInputEvent::kGestureScrollEnd: {
+      previous_boosting_scroll_timestamp_ = base::TimeTicks();
+      break;
     }
+    case WebInputEvent::kGestureFlingCancel: {
+      if (gesture_event.data.fling_cancel.prevent_boosting) {
+        Reset();
+        return;
+      }
 
+      previous_boosting_scroll_timestamp_ = base::TimeTicks();
+      cutoff_time_for_boost_ =
+          gesture_event.TimeStamp() + kFlingBoostTimeoutDelay;
+      break;
+    }
     default:
-      // All other types of gestures (taps, presses, etc...) will complete the
-      // deferred fling cancellation.
-      *out_cancel_current_fling = true;
-      return false;
+      break;
   }
 }
 
-bool FlingBooster::MustCancelDeferredFling() const {
-  return deferred_fling_cancel_time_seconds_ &&
-         last_fling_animate_time_seconds_ > deferred_fling_cancel_time_seconds_;
-}
-
 bool FlingBooster::ShouldBoostFling(const WebGestureEvent& fling_start_event) {
   DCHECK_EQ(WebInputEvent::kGestureFlingStart, fling_start_event.GetType());
-
-  gfx::Vector2dF new_fling_velocity(
-      fling_start_event.data.fling_start.velocity_x,
-      fling_start_event.data.fling_start.velocity_y);
-
-  if (gfx::DotProduct(current_fling_velocity_, new_fling_velocity) <= 0)
+  if (current_fling_velocity_.IsZero())
     return false;
 
-  if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
-    return false;
-
-  if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
+  if (source_device_ != fling_start_event.SourceDevice())
     return false;
 
   if (modifiers_ != fling_start_event.GetModifiers())
     return false;
 
-  return true;
-}
-
-bool FlingBooster::ShouldSuppressScrollForFlingBoosting(
-    const WebGestureEvent& scroll_update_event) {
-  DCHECK_EQ(WebInputEvent::kGestureScrollUpdate, scroll_update_event.GetType());
-
-  gfx::Vector2dF dx(scroll_update_event.data.scroll_update.delta_x,
-                    scroll_update_event.data.scroll_update.delta_y);
-  if (gfx::DotProduct(current_fling_velocity_, dx) <= 0)
+  if (cutoff_time_for_boost_.is_null())
     return false;
 
-  const double time_since_last_fling_animate = std::max(
-      0.0, scroll_update_event.TimeStamp().since_origin().InSecondsF() -
-               last_fling_animate_time_seconds_);
-  if (time_since_last_fling_animate > kFlingBoostTimeoutDelaySeconds)
+  if (fling_start_event.TimeStamp() > cutoff_time_for_boost_)
     return false;
 
-  const double time_since_last_boost_event =
-      (scroll_update_event.TimeStamp() - last_fling_boost_event_.TimeStamp())
-          .InSecondsF();
-  if (time_since_last_boost_event < 0.001)
-    return true;
+  gfx::Vector2dF new_fling_velocity(
+      fling_start_event.data.fling_start.velocity_x,
+      fling_start_event.data.fling_start.velocity_y);
 
-  // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
-  // The scroll must be of sufficient velocity to maintain the active fling.
-  const gfx::Vector2dF scroll_velocity =
-      gfx::ScaleVector2d(dx, 1. / time_since_last_boost_event);
-  if (scroll_velocity.LengthSquared() < kMinBoostTouchScrollSpeedSquare)
+  if (gfx::DotProduct(current_fling_velocity_, new_fling_velocity) <= 0) {
     return false;
+  }
+
+  if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare) {
+    return false;
+  }
+
+  if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare) {
+    return false;
+  }
 
   return true;
 }
 
-void FlingBooster::ExtendBoostedFlingTimeout(
-    const blink::WebGestureEvent& event) {
-  deferred_fling_cancel_time_seconds_ =
-      event.TimeStamp().since_origin().InSecondsF() +
-      kFlingBoostTimeoutDelaySeconds;
-  last_fling_boost_event_ = event;
+void FlingBooster::Reset() {
+  cutoff_time_for_boost_ = base::TimeTicks();
+  current_fling_velocity_ = gfx::Vector2dF();
+  source_device_ = blink::WebGestureDevice::kUninitialized;
+  modifiers_ = 0;
+  previous_boosting_scroll_timestamp_ = base::TimeTicks();
 }
 
 }  // namespace ui
diff --git a/ui/events/blink/fling_booster.h b/ui/events/blink/fling_booster.h
index 21f2554..112bd0a7 100644
--- a/ui/events/blink/fling_booster.h
+++ b/ui/events/blink/fling_booster.h
@@ -9,76 +9,46 @@
 
 namespace ui {
 
-// TODO(dcheng): This class should probably be using base::TimeTicks internally.
+// This class is used to track fling state and provide "fling boosting".
+// Boosting is a feature where successive flings can repeatedly increase the
+// fling velocity so that users can scroll through long documents. This
+// boosting logic occurs only in certain circumstances so we track the state
+// and conditions in this class. The FlingController will request the velocity
+// for all flings from this class; if FlingBooster decides the fling should be
+// boosted it'll add the new fling's velocity to the previous one's.
 class FlingBooster {
  public:
-  FlingBooster(const gfx::Vector2dF& fling_velocity,
-               blink::WebGestureDevice source_device,
-               int modifiers);
+  FlingBooster() = default;
 
-  // Returns true if the event should be suppressed due to to an active,
-  // boost-enabled fling, in which case further processing should cease.
-  bool FilterGestureEventForFlingBoosting(
-      const blink::WebGestureEvent& gesture_event,
-      bool* out_cancel_current_fling);
-
-  bool MustCancelDeferredFling() const;
-
-  void set_last_fling_animation_time(double last_fling_animate_time_seconds) {
-    last_fling_animate_time_seconds_ = last_fling_animate_time_seconds;
-  }
-
-  gfx::Vector2dF current_fling_velocity() const {
-    return current_fling_velocity_;
-  }
-
-  void set_current_fling_velocity(const gfx::Vector2dF& fling_velocity) {
-    current_fling_velocity_ = fling_velocity;
-  }
-
-  bool fling_boosted() const { return fling_boosted_; }
-
-  bool fling_cancellation_is_deferred() const {
-    return !!deferred_fling_cancel_time_seconds_;
-  }
-
-  blink::WebGestureEvent last_boost_event() const {
-    return last_fling_boost_event_;
-  }
+  gfx::Vector2dF GetVelocityForFlingStart(
+      const blink::WebGestureEvent& gesture_start);
+  void ObserveGestureEvent(const blink::WebGestureEvent& gesture_event);
 
  private:
   bool ShouldBoostFling(const blink::WebGestureEvent& fling_start_event);
 
-  bool ShouldSuppressScrollForFlingBoosting(
-      const blink::WebGestureEvent& scroll_update_event);
+  void Reset();
 
-  // Set a time in the future after which a boost-enabled fling will terminate
-  // without further momentum from the user.
-  void ExtendBoostedFlingTimeout(const blink::WebGestureEvent& event);
+  // When non-null, the current gesture stream is being considered for
+  // boosting. If a fling hasn't occurred by this time, we won't cause a boost.
+  // Note, however, that we'll extend this time as we see scroll updates.
+  base::TimeTicks cutoff_time_for_boost_;
 
+  // Tracks velocity at fling start of the currently ongoing fling. When a new
+  // fling is started and we decide to boost, we'll add this velocity to it.
   gfx::Vector2dF current_fling_velocity_;
 
-  // These store the current active fling source device and modifiers since a
-  // new fling start event must have the same source device and modifiers to be
-  // able to boost the active fling.
-  blink::WebGestureDevice source_device_;
-  int modifiers_;
+  // These store the current active fling source device and modifier keys (e.g.
+  // Ctrl) since a new fling start event must have the same source device and
+  // modifiers to be able to boost the active fling.
+  blink::WebGestureDevice source_device_ =
+      blink::WebGestureDevice::kUninitialized;
+  int modifiers_ = 0;
 
-  // Time at which an active fling should expire due to a deferred cancellation
-  // event.
-  double deferred_fling_cancel_time_seconds_;
-
-  // Time at which the last fling animation has happened.
-  double last_fling_animate_time_seconds_;
-
-  // Whether the current active fling is boosted or replaced by a new fling
-  // start event.
-  bool fling_boosted_;
-
-  // The last event that extended the lifetime of the boosted fling. If the
-  // event was a scroll gesture, a GestureScrollBegin needs to be inserted if
-  // the fling terminates.
-  blink::WebGestureEvent last_fling_boost_event_;
+  // Track the last timestamp we've seen a scroll update that we're evaluating
+  // as a boost. This is used to calculate the velocity; if it's to slow we'll
+  // avoid boosting.
+  base::TimeTicks previous_boosting_scroll_timestamp_;
 
   DISALLOW_COPY_AND_ASSIGN(FlingBooster);
 };
diff --git a/ui/events/blink/fling_booster_unittest.cc b/ui/events/blink/fling_booster_unittest.cc
index 0bdc3ab..fce6110 100644
--- a/ui/events/blink/fling_booster_unittest.cc
+++ b/ui/events/blink/fling_booster_unittest.cc
@@ -11,294 +11,238 @@
 #include "ui/events/base_event_utils.h"
 #include "ui/events/event_modifiers.h"
 
+using base::TimeDelta;
 using blink::WebGestureDevice;
 using blink::WebGestureEvent;
 using blink::WebInputEvent;
+using gfx::Vector2dF;
 
 namespace ui {
 namespace test {
 
+static constexpr TimeDelta kEventDelta = TimeDelta::FromMilliseconds(10);
+
+// Constants from fling_booster.cc
+static constexpr double kMinBoostScrollSpeed = 150.;
+static constexpr double kMinBoostFlingSpeed = 350.;
+static constexpr base::TimeDelta kFlingBoostTimeoutDelay =
+    base::TimeDelta::FromSecondsD(0.05);
+
 class FlingBoosterTest : public testing::Test {
  public:
-  FlingBoosterTest() : delta_time_(base::TimeDelta::FromMilliseconds(10)) {
-    gesture_scroll_event_.SetSourceDevice(
-        blink::WebGestureDevice::kTouchscreen);
-  }
+  FlingBoosterTest() = default;
 
-  WebGestureEvent CreateFlingStart(base::TimeTicks timestamp,
-                                   WebGestureDevice source_device,
-                                   const gfx::Vector2dF& velocity,
-                                   int modifiers) {
+  WebGestureEvent CreateFlingStart(
+      const gfx::Vector2dF& velocity,
+      int modifiers = 0,
+      WebGestureDevice source_device = WebGestureDevice::kTouchscreen) {
     WebGestureEvent fling_start(WebInputEvent::kGestureFlingStart, modifiers,
-                                timestamp, source_device);
+                                event_time_, source_device);
     fling_start.data.fling_start.velocity_x = velocity.x();
     fling_start.data.fling_start.velocity_y = velocity.y();
     return fling_start;
   }
 
-  WebGestureEvent CreateFlingCancel(base::TimeTicks timestamp,
-                                    WebGestureDevice source_device) {
+  WebGestureEvent CreateFlingCancel(
+      WebGestureDevice source_device = WebGestureDevice::kTouchscreen) {
     WebGestureEvent fling_cancel(WebInputEvent::kGestureFlingCancel, 0,
-                                 timestamp, source_device);
+                                 event_time_, source_device);
     return fling_cancel;
   }
 
-  void StartFirstFling() {
-    event_time_ = base::TimeTicks() + delta_time_;
-    fling_booster_.reset(new FlingBooster(
-        gfx::Vector2dF(1000, 1000), blink::WebGestureDevice::kTouchscreen, 0));
-    fling_booster_->set_last_fling_animation_time(
-        EventTimeStampToSeconds(event_time_));
+  WebGestureEvent CreateScrollBegin(
+      gfx::Vector2dF delta,
+      WebGestureDevice source_device = WebGestureDevice::kTouchscreen) {
+    WebGestureEvent scroll_begin(WebInputEvent::kGestureScrollBegin, 0,
+                                 event_time_, source_device);
+    scroll_begin.data.scroll_begin.delta_x_hint = delta.x();
+    scroll_begin.data.scroll_begin.delta_y_hint = delta.y();
+    scroll_begin.data.scroll_begin.delta_hint_units =
+        ui::input_types::ScrollGranularity::kScrollByPrecisePixel;
+    return scroll_begin;
   }
 
-  void CancelFling() {
-    WebGestureEvent fling_cancel_event =
-        CreateFlingCancel(event_time_, blink::WebGestureDevice::kTouchscreen);
-    bool cancel_current_fling;
-    EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-        fling_cancel_event, &cancel_current_fling));
-    EXPECT_FALSE(cancel_current_fling);
-    EXPECT_TRUE(fling_booster_->fling_cancellation_is_deferred());
+  WebGestureEvent CreateScrollUpdate(
+      gfx::Vector2dF delta,
+      WebGestureDevice source_device = WebGestureDevice::kTouchscreen) {
+    WebGestureEvent scroll_update(WebInputEvent::kGestureScrollUpdate, 0,
+                                  event_time_, source_device);
+    scroll_update.data.scroll_update.delta_x = delta.x();
+    scroll_update.data.scroll_update.delta_y = delta.y();
+    scroll_update.data.scroll_update.delta_units =
+        ui::input_types::ScrollGranularity::kScrollByPrecisePixel;
+    return scroll_update;
+  }
+
+  Vector2dF DeltaFromVelocity(Vector2dF velocity, TimeDelta delta) {
+    float delta_seconds = static_cast<float>(delta.InSecondsF());
+    Vector2dF out = velocity;
+    out.Scale(1.f / delta_seconds);
+    return out;
+  }
+
+  Vector2dF SendFlingStart(WebGestureEvent event) {
+    DCHECK_EQ(WebInputEvent::kGestureFlingStart, event.GetType());
+
+    // The event will first be observed, then the FlingController will request
+    // a possibly boosted velocity.
+    fling_booster_.ObserveGestureEvent(event);
+    return fling_booster_.GetVelocityForFlingStart(event);
+  }
+
+  // Simulates the gesture scroll stream for a scroll that should create a
+  // boost.
+  void SimulateBoostingScroll() {
+    event_time_ += kEventDelta;
+    fling_booster_.ObserveGestureEvent(CreateFlingCancel());
+    fling_booster_.ObserveGestureEvent(CreateScrollBegin(Vector2dF(0, 1)));
+
+    // GestureScrollUpdates in the same direction and at sufficient speed should
+    // be considered boosting. First GSU speed is ignored since we need 2 to
+    // determine velocity.
+    event_time_ += kEventDelta;
+    fling_booster_.ObserveGestureEvent(CreateScrollUpdate(Vector2dF(0, 1)));
+    event_time_ += kEventDelta;
+    fling_booster_.ObserveGestureEvent(CreateScrollUpdate(
+        DeltaFromVelocity(Vector2dF(0, kMinBoostScrollSpeed), kEventDelta)));
   }
 
  protected:
-  std::unique_ptr<FlingBooster> fling_booster_;
-  base::TimeDelta delta_time_;
-  base::TimeTicks event_time_;
-  WebGestureEvent gesture_scroll_event_;
+  base::TimeTicks event_time_ =
+      base::TimeTicks() + TimeDelta::FromSeconds(100000);
+  FlingBooster fling_booster_;
 };
 
-TEST_F(FlingBoosterTest, FlingBoost) {
-  StartFirstFling();
+TEST_F(FlingBoosterTest, FlingBoostBasic) {
+  Vector2dF fling_velocity;
 
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  EXPECT_EQ(Vector2dF(0, 1000), fling_velocity)
+      << "First fling shouldn't be boosted";
 
-  // The GestureScrollBegin should be swallowed by the fling when a fling
-  // cancellation is deferred.
-  gesture_scroll_event_.SetTimeStamp(event_time_);
-  gesture_scroll_event_.SetType(WebInputEvent::kGestureScrollBegin);
-  bool cancel_current_fling;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_scroll_event_, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
+  SimulateBoostingScroll();
 
-  // Animate calls within the deferred cancellation window should continue.
-  event_time_ += delta_time_;
-  fling_booster_->set_last_fling_animation_time(
-      EventTimeStampToSeconds(event_time_));
-  EXPECT_FALSE(fling_booster_->MustCancelDeferredFling());
-
-  // GestureScrollUpdates in the same direction and at sufficient speed should
-  // be swallowed by the fling.
-  gesture_scroll_event_.SetTimeStamp(event_time_);
-  gesture_scroll_event_.SetType(WebInputEvent::kGestureScrollUpdate);
-  gesture_scroll_event_.data.scroll_update.delta_x = 100;
-  gesture_scroll_event_.data.scroll_update.delta_y = 100;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_scroll_event_, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
-
-  // Animate calls within the deferred cancellation window should continue.
-  event_time_ += delta_time_;
-  fling_booster_->set_last_fling_animation_time(
-      EventTimeStampToSeconds(event_time_));
-  EXPECT_FALSE(fling_booster_->MustCancelDeferredFling());
-
-  // GestureFlingStart in the same direction and at sufficient speed should
-  // boost the active fling.
-  WebGestureEvent fling_start_event =
-      CreateFlingStart(event_time_, blink::WebGestureDevice::kTouchscreen,
-                       gfx::Vector2dF(1000, 1000), 0);
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      fling_start_event, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
-  EXPECT_EQ(gfx::Vector2dF(2000, 2000),
-            fling_booster_->current_fling_velocity());
-  EXPECT_TRUE(fling_booster_->fling_boosted());
-
-  // Animate calls within the deferred cancellation window should continue.
-  event_time_ += delta_time_;
-  fling_booster_->set_last_fling_animation_time(
-      EventTimeStampToSeconds(event_time_));
-  EXPECT_FALSE(fling_booster_->MustCancelDeferredFling());
-
-  // GestureFlingCancel should terminate the fling if no boosting gestures are
-  // received within the timeout window.
-  CancelFling();
-  event_time_ += base::TimeDelta::FromMilliseconds(100);
-  fling_booster_->set_last_fling_animation_time(
-      EventTimeStampToSeconds(event_time_));
-  EXPECT_TRUE(fling_booster_->MustCancelDeferredFling());
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 2000)));
+  EXPECT_EQ(Vector2dF(0, 3000), fling_velocity)
+      << "FlingStart with ongoing fling should be boosted";
 }
 
 TEST_F(FlingBoosterTest, NoFlingBoostIfScrollDelayed) {
-  StartFirstFling();
+  Vector2dF fling_velocity;
 
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  SimulateBoostingScroll();
 
-  // The GestureScrollBegin should be swallowed by the fling when a fling
-  // cancellation is deferred.
-  gesture_scroll_event_.SetTimeStamp(event_time_);
-  gesture_scroll_event_.SetType(WebInputEvent::kGestureScrollBegin);
-  bool cancel_current_fling;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_scroll_event_, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
+  // Delay longer than the timeout and ensure we don't boost.
+  event_time_ += kFlingBoostTimeoutDelay + TimeDelta::FromMilliseconds(1);
+  fling_booster_.ObserveGestureEvent(CreateScrollUpdate(Vector2dF(0, 10000)));
 
-  // If no GestureScrollUpdate or GestureFlingStart is received within the
-  // timeout window, the fling should be cancelled and scrolling should resume.
-  event_time_ += base::TimeDelta::FromMilliseconds(100);
-  fling_booster_->set_last_fling_animation_time(
-      EventTimeStampToSeconds(event_time_));
-  EXPECT_TRUE(fling_booster_->MustCancelDeferredFling());
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 2000)));
+  EXPECT_EQ(Vector2dF(0, 2000), fling_velocity)
+      << "ScrollUpdate delayed longer than boosting timeout; fling shouldn't "
+         "be boosted.";
 }
 
-TEST_F(FlingBoosterTest, NoFlingBoostIfNotAnimated) {
-  StartFirstFling();
+TEST_F(FlingBoosterTest, NoFlingBoostIfBoostTooSlow) {
+  Vector2dF fling_velocity;
 
-  // Animate fling once.
-  event_time_ += delta_time_;
-  fling_booster_->set_last_fling_animation_time(
-      EventTimeStampToSeconds(event_time_));
-  EXPECT_FALSE(fling_booster_->MustCancelDeferredFling());
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  SimulateBoostingScroll();
 
-  // Cancel the fling after long delay of no animate. The fling cancellation
-  // should be deferred to allow fling boosting events to arrive.
-  event_time_ += base::TimeDelta::FromMilliseconds(100);
-  CancelFling();
+  auto new_velocity = Vector2dF(0, kMinBoostFlingSpeed - 1);
+  fling_velocity = SendFlingStart(CreateFlingStart(new_velocity));
+  EXPECT_EQ(new_velocity, fling_velocity)
+      << "Boosting FlingStart too slow; fling shouldn't be boosted.";
+}
 
-  // The GestureScrollBegin should be swallowed by the fling when a fling
-  // cancellation is deferred.
-  gesture_scroll_event_.SetTimeStamp(event_time_);
-  gesture_scroll_event_.SetType(WebInputEvent::kGestureScrollBegin);
-  bool cancel_current_fling;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_scroll_event_, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
+TEST_F(FlingBoosterTest, NoFlingBoostIfCurrentVelocityTooSlow) {
+  Vector2dF fling_velocity;
 
-  // Should exit scroll boosting on GestureScrollUpdate due to long delay since
-  // last animate and cancel old fling. The scroll update event shouldn't get
-  // filtered.
-  gesture_scroll_event_.SetType(WebInputEvent::kGestureScrollUpdate);
-  gesture_scroll_event_.data.scroll_update.delta_y = 100;
-  EXPECT_FALSE(fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_scroll_event_, &cancel_current_fling));
-  EXPECT_TRUE(cancel_current_fling);
+  fling_velocity =
+      SendFlingStart(CreateFlingStart(Vector2dF(0, kMinBoostFlingSpeed - 1)));
+
+  SimulateBoostingScroll();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 2000)));
+  EXPECT_EQ(Vector2dF(0, 2000), fling_velocity)
+      << "Existing fling too slow and shouldn't be boosted.";
 }
 
 TEST_F(FlingBoosterTest, NoFlingBoostIfFlingInDifferentDirection) {
-  StartFirstFling();
+  Vector2dF fling_velocity;
 
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  SimulateBoostingScroll();
 
-  // If the new fling is orthogonal to the existing fling, no boosting should
-  // take place, with the new fling replacing the old.
-  WebGestureEvent fling_start_event =
-      CreateFlingStart(event_time_, blink::WebGestureDevice::kTouchscreen,
-                       gfx::Vector2dF(-1000, -1000), 0);
-  bool cancel_current_fling;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      fling_start_event, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
-  EXPECT_EQ(gfx::Vector2dF(-1000, -1000),
-            fling_booster_->current_fling_velocity());
-  EXPECT_FALSE(fling_booster_->fling_boosted());
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(1000, 0)));
+  EXPECT_EQ(Vector2dF(1000, 0), fling_velocity)
+      << "Fling isn't in same direction, shouldn't boost.";
 }
 
 TEST_F(FlingBoosterTest, NoFlingBoostIfScrollInDifferentDirection) {
-  StartFirstFling();
+  Vector2dF fling_velocity;
 
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  SimulateBoostingScroll();
 
-  // If the GestureScrollUpdate is in a different direction than the fling,
-  // the fling should be cancelled and the update event shouldn't get filtered.
-  gesture_scroll_event_.SetTimeStamp(event_time_);
-  gesture_scroll_event_.SetType(WebInputEvent::kGestureScrollUpdate);
-  gesture_scroll_event_.data.scroll_update.delta_x = -100;
-  bool cancel_current_fling;
-  EXPECT_FALSE(fling_booster_->FilterGestureEventForFlingBoosting(
-      gesture_scroll_event_, &cancel_current_fling));
-  EXPECT_TRUE(cancel_current_fling);
-}
+  // Start a new scroll in an orthogonal direction and fling in the direction
+  // of the original fling.
+  event_time_ += kEventDelta;
+  fling_booster_.ObserveGestureEvent(CreateScrollUpdate(Vector2dF(1000, 0)));
 
-TEST_F(FlingBoosterTest, NoFlingBoostIfFlingTooSlow) {
-  StartFirstFling();
-
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
-
-  // If the new fling velocity is too small, no boosting should take place, with
-  // the new fling replacing the old.
-  WebGestureEvent fling_start_event =
-      CreateFlingStart(event_time_, blink::WebGestureDevice::kTouchscreen,
-                       gfx::Vector2dF(100, 100), 0);
-  bool cancel_current_fling;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      fling_start_event, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
-  EXPECT_EQ(gfx::Vector2dF(100, 100), fling_booster_->current_fling_velocity());
-  EXPECT_FALSE(fling_booster_->fling_boosted());
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 2000)));
+  EXPECT_EQ(Vector2dF(0, 2000), fling_velocity)
+      << "Scrolling in an orthogonal direction should prevent boosting, even "
+         "if the fling is in the original direction.";
 }
 
 TEST_F(FlingBoosterTest, NoFlingBoostIfPreventBoostingFlagIsSet) {
-  StartFirstFling();
+  WebGestureEvent fling_start = CreateFlingStart(Vector2dF(0, 1000));
 
-  // The fling cancellation should not be deferred because of prevent boosting
-  // flag set.
-  WebGestureEvent fling_cancel_event =
-      CreateFlingCancel(event_time_, blink::WebGestureDevice::kTouchscreen);
-  fling_cancel_event.data.fling_cancel.prevent_boosting = true;
-  bool cancel_current_fling;
-  EXPECT_FALSE(fling_booster_->FilterGestureEventForFlingBoosting(
-      fling_cancel_event, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
-  EXPECT_FALSE(fling_booster_->fling_cancellation_is_deferred());
+  Vector2dF fling_velocity = SendFlingStart(fling_start);
+
+  // Start a new scroll.
+  event_time_ += kEventDelta;
+  WebGestureEvent cancel_event = CreateFlingCancel();
+  cancel_event.data.fling_cancel.prevent_boosting = true;
+  fling_booster_.ObserveGestureEvent(cancel_event);
+  fling_booster_.ObserveGestureEvent(CreateScrollBegin(Vector2dF(0, 1)));
+
+  // GestureScrollUpdates in the same direction and at sufficient speed should
+  // be considered boosting. However, since the prevent_boosting flag was set,
+  // we shouldn't boost.
+  event_time_ += kEventDelta;
+  fling_booster_.ObserveGestureEvent(CreateScrollUpdate(Vector2dF(0, 10000)));
+  event_time_ += kEventDelta;
+  fling_booster_.ObserveGestureEvent(CreateScrollUpdate(Vector2dF(0, 10000)));
+
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 2000)));
+  EXPECT_EQ(Vector2dF(0, 2000), fling_velocity)
+      << "prevent_boosting on FlingCancel should avoid boosting a subsequent "
+         "FlingStart";
 }
 
 TEST_F(FlingBoosterTest, NoFlingBoostIfDifferentFlingModifiers) {
-  StartFirstFling();
+  Vector2dF fling_velocity;
 
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  SimulateBoostingScroll();
 
-  // GestureFlingStart with different modifiers should replace the old fling.
-  WebGestureEvent fling_start_event =
-      CreateFlingStart(event_time_, blink::WebGestureDevice::kTouchscreen,
-                       gfx::Vector2dF(500, 500), MODIFIER_SHIFT);
-  bool cancel_current_fling;
-  EXPECT_TRUE(fling_booster_->FilterGestureEventForFlingBoosting(
-      fling_start_event, &cancel_current_fling));
-  EXPECT_FALSE(cancel_current_fling);
-  EXPECT_EQ(gfx::Vector2dF(500, 500), fling_booster_->current_fling_velocity());
-  EXPECT_FALSE(fling_booster_->fling_boosted());
+  fling_velocity =
+      SendFlingStart(CreateFlingStart(Vector2dF(0, 2000), MODIFIER_SHIFT));
+  EXPECT_EQ(Vector2dF(0, 2000), fling_velocity)
+      << "Changed modifier keys should prevent boost.";
 }
 
 TEST_F(FlingBoosterTest, NoFlingBoostIfDifferentFlingSourceDevices) {
-  StartFirstFling();
+  Vector2dF fling_velocity;
 
-  // The fling cancellation should be deferred to allow fling boosting events to
-  // arrive.
-  CancelFling();
+  fling_velocity = SendFlingStart(CreateFlingStart(Vector2dF(0, 1000)));
+  SimulateBoostingScroll();
 
-  // GestureFlingStart with different source device should not get filtered by
-  // fling_booster.
-  WebGestureEvent fling_start_event =
-      CreateFlingStart(event_time_, blink::WebGestureDevice::kTouchpad,
-                       gfx::Vector2dF(500, 500), 0);
-  bool cancel_current_fling;
-  EXPECT_FALSE(fling_booster_->FilterGestureEventForFlingBoosting(
-      fling_start_event, &cancel_current_fling));
-  EXPECT_TRUE(cancel_current_fling);
+  fling_velocity = SendFlingStart(
+      CreateFlingStart(Vector2dF(0, 1000), 0, WebGestureDevice::kTouchpad));
+  EXPECT_EQ(Vector2dF(0, 1000), fling_velocity)
+      << "Changed modifier keys should prevent boost.";
 }
 
 }  // namespace test