// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/fling_controller.h"

#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "build/chromecast_buildflags.h"
#include "content/browser/renderer_host/input/gesture_event_queue.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/blink/fling_booster.h"
#include "ui/events/gestures/physics_based_fling_curve.h"

#if BUILDFLAG(IS_WIN)
#include "ui/display/win/test/scoped_screen_win.h"
#endif

using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseWheelEvent;
using ui::PhysicsBasedFlingCurve;

namespace {
constexpr double kFrameDelta = 1000.0 / 60.0;
}  // namespace

namespace content {

class FakeFlingController : public FlingController {
 public:
  FakeFlingController(FlingControllerEventSenderClient* event_sender_client,
                      FlingControllerSchedulerClient* scheduler_client,
                      const Config& config)
      : FlingController(event_sender_client, scheduler_client, config) {}
};

class FlingControllerTest : public FlingControllerEventSenderClient,
                            public FlingControllerSchedulerClient,
                            public testing::TestWithParam<bool> {
 public:
  // testing::Test
  FlingControllerTest()
      : needs_begin_frame_for_fling_progress_(GetParam()),
        task_environment_(base::test::TaskEnvironment::MainThreadType::UI) {}

  FlingControllerTest(const FlingControllerTest&) = delete;
  FlingControllerTest& operator=(const FlingControllerTest&) = delete;

  ~FlingControllerTest() override {}

  void SetUp() override {
    fling_controller_ = std::make_unique<FakeFlingController>(
        this, this, FlingController::Config());
    fling_controller_->set_clock_for_testing(&mock_clock_);
    AdvanceTime();
  }

  // FlingControllerEventSenderClient
  void SendGeneratedWheelEvent(
      const MouseWheelEventWithLatencyInfo& wheel_event) override {
    wheel_event_count_++;
    last_sent_wheel_ = wheel_event.event;
    first_wheel_event_sent_ = true;

    if (wheel_event.event.momentum_phase == WebMouseWheelEvent::kPhaseEnded)
      first_wheel_event_sent_ = false;
  }
  void SendGeneratedGestureScrollEvents(
      const GestureEventWithLatencyInfo& gesture_event) override {
    fling_controller_->ObserveAndMaybeConsumeGestureEvent(gesture_event);
    sent_scroll_gesture_count_++;
    last_sent_gesture_ = gesture_event.event;
  }

  gfx::Size GetRootWidgetViewportSize() override {
    return gfx::Size(1920, 1080);
  }

  // FlingControllerSchedulerClient
  void ScheduleFlingProgress(
      base::WeakPtr<FlingController> fling_controller) override {
    DCHECK(!scheduled_next_fling_progress_);
    scheduled_next_fling_progress_ = true;
  }
  void DidStopFlingingOnBrowser(
      base::WeakPtr<FlingController> fling_controller) override {
    notified_client_after_fling_stop_ = true;
  }
  bool NeedsBeginFrameForFlingProgress() override {
    return needs_begin_frame_for_fling_progress_;
  }

  void SimulateFlingStart(blink::WebGestureDevice source_device,
                          const gfx::Vector2dF& velocity,
                          bool wait_before_processing = true) {
    scheduled_next_fling_progress_ = false;
    sent_scroll_gesture_count_ = 0;
    WebGestureEvent fling_start(WebInputEvent::Type::kGestureFlingStart, 0,
                                NowTicks(), source_device);
    fling_start.data.fling_start.velocity_x = velocity.x();
    fling_start.data.fling_start.velocity_y = velocity.y();
    GestureEventWithLatencyInfo fling_start_with_latency(fling_start);
    if (wait_before_processing) {
      // Wait for up to one frame before processing the event.
      AdvanceTime(base::RandInt(0, static_cast<int>(kFrameDelta)));
    }
    fling_controller_->ObserveAndMaybeConsumeGestureEvent(
        fling_start_with_latency);
  }

  void SimulateScrollBegin(blink::WebGestureDevice source_device,
                           const gfx::Vector2dF& delta) {
    WebGestureEvent scroll_begin(WebInputEvent::Type::kGestureScrollBegin, 0,
                                 NowTicks(), 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.inertial_phase =
        WebGestureEvent::InertialPhaseState::kNonMomentum;
    scroll_begin.data.scroll_begin.delta_hint_units =
        ui::ScrollGranularity::kScrollByPrecisePixel;
    GestureEventWithLatencyInfo scroll_begin_with_latency(scroll_begin);

    fling_controller_->ObserveAndMaybeConsumeGestureEvent(
        scroll_begin_with_latency);
  }

  void SimulateScrollUpdate(blink::WebGestureDevice source_device,
                            const gfx::Vector2dF& delta) {
    WebGestureEvent scroll_update(WebInputEvent::Type::kGestureScrollUpdate, 0,
                                  NowTicks(), 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.velocity_x = delta.x();
    scroll_update.data.scroll_update.velocity_y = delta.y();
    scroll_update.data.scroll_update.inertial_phase =
        WebGestureEvent::InertialPhaseState::kNonMomentum;
    scroll_update.data.scroll_update.delta_units =
        ui::ScrollGranularity::kScrollByPrecisePixel;
    GestureEventWithLatencyInfo scroll_update_with_latency(scroll_update);

    fling_controller_->ObserveAndMaybeConsumeGestureEvent(
        scroll_update_with_latency);
  }

  void SimulateFlingCancel(blink::WebGestureDevice source_device) {
    notified_client_after_fling_stop_ = false;
    WebGestureEvent fling_cancel(WebInputEvent::Type::kGestureFlingCancel, 0,
                                 NowTicks(), source_device);
    // autoscroll fling cancel doesn't allow fling boosting.
    if (source_device == blink::WebGestureDevice::kSyntheticAutoscroll)
      fling_cancel.data.fling_cancel.prevent_boosting = true;
    GestureEventWithLatencyInfo fling_cancel_with_latency(fling_cancel);
    fling_controller_->ObserveAndMaybeConsumeGestureEvent(
        fling_cancel_with_latency);
  }

  void ProgressFling(base::TimeTicks current_time) {
    DCHECK(scheduled_next_fling_progress_);
    scheduled_next_fling_progress_ = false;
    fling_controller_->ProgressFling(current_time);
  }

  bool FlingInProgress() { return fling_controller_->fling_in_progress(); }

  void AdvanceTime(double time_delta_ms = kFrameDelta) {
    mock_clock_.Advance(base::Milliseconds(time_delta_ms));
  }

  base::TimeTicks NowTicks() const { return mock_clock_.NowTicks(); }

  float CompleteFlingAndAccumulateScrollDelta() {
    float total_scroll_delta = 0.f;

    // Sometimes SendGeneratedGestureScrollEvents is not run because fling is
    // not advanced. This is due to the first |FlingScheduler::OnAnimationStep|
    // call having the time of the last frame before AddAnimationObserver.
    // Please see comment in |FlingController::ProgressFling|. This leaves the
    // last_sent_gesture as a GSE. We therefore don't accrue delta in this case
    if (last_sent_gesture_.GetType() !=
        WebInputEvent::Type::kGestureScrollEnd) {
      DCHECK(last_sent_gesture_.GetType() ==
             WebInputEvent::Type::kGestureScrollUpdate);
      total_scroll_delta += last_sent_gesture_.data.scroll_update.delta_x;
    }

    while (true) {
      AdvanceTime();
      ProgressFling(NowTicks());
      if (last_sent_gesture_.GetType() ==
          WebInputEvent::Type::kGestureScrollEnd) {
        break;
      } else {
        DCHECK(last_sent_gesture_.GetType() ==
               WebInputEvent::Type::kGestureScrollUpdate);
        total_scroll_delta += last_sent_gesture_.data.scroll_update.delta_x;
      }
    }

    return total_scroll_delta;
  }

 protected:
  std::unique_ptr<FakeFlingController> fling_controller_;
  int wheel_event_count_ = 0;
  WebMouseWheelEvent last_sent_wheel_;
  WebGestureEvent last_sent_gesture_;
  bool scheduled_next_fling_progress_ = false;
  bool notified_client_after_fling_stop_ = false;
  bool first_wheel_event_sent_ = false;
  int sent_scroll_gesture_count_ = 0;
#if BUILDFLAG(IS_WIN)
  // This is necessary for static methods of `display::ScreenWin`.
  display::win::test::ScopedScreenWin scoped_screen_win_;
#endif

 private:
  base::SimpleTestTickClock mock_clock_;

  // This determines whether the platform ticks fling animations using
  // SetNeedsBeginFrame (i.e. WebView). If true, we should avoid calling
  // ProgressFling immediately after a FlingStart since this will match the
  // behavior in FlingController::ProcessGestureFlingStart. See
  // https://crrev.com/c/1181521.
  bool needs_begin_frame_for_fling_progress_;
  base::test::TaskEnvironment task_environment_;
};

INSTANTIATE_TEST_SUITE_P(All, FlingControllerTest, testing::Bool());

TEST_P(FlingControllerTest,
       ControllerSendsWheelEndOnTouchpadFlingWithZeroVelocity) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchpad, gfx::Vector2dF());
  // The controller doesn't start a fling and sends a wheel end event
  // immediately.
  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);
}

TEST_P(FlingControllerTest,
       ControllerSendsGSEOnTouchscreenFlingWithZeroVelocity) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen, gfx::Vector2dF());
  // The controller doesn't start a fling and sends a GSE immediately.
  EXPECT_FALSE(FlingInProgress());
  EXPECT_EQ(WebInputEvent::Type::kGestureScrollEnd,
            last_sent_gesture_.GetType());
}

TEST_P(FlingControllerTest, ControllerHandlesTouchpadGestureFling) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
  // Processing GFS will send the first fling progress event if the time delta
  // between the timestamp of the GFS and the time that ProcessGestureFlingStart
  // is called is large enough.
  bool process_GFS_sent_first_event = first_wheel_event_sent_;

  AdvanceTime();
  ProgressFling(NowTicks());

  if (!process_GFS_sent_first_event) {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
  } else {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged,
              last_sent_wheel_.momentum_phase);
  }
  EXPECT_GT(last_sent_wheel_.delta_x, 0.f);

  // The rest of the wheel events must have momentum_phase == KPhaseChanged.
  AdvanceTime();
  ProgressFling(NowTicks());
  EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, last_sent_wheel_.momentum_phase);
  EXPECT_GT(last_sent_wheel_.delta_x, 0.f);

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchpad);
  EXPECT_FALSE(FlingInProgress());
}

// Ensure that the start time of a fling is measured from the last received
// GSU. This ensures that the first progress fling during FlingStart should
// send significant delta. If we're using the FlingStart as the start time, we
// would send none or very little delta.
TEST_P(FlingControllerTest, FlingStartsAtLastScrollUpdate) {
  SimulateScrollUpdate(blink::WebGestureDevice::kTouchscreen,
                       gfx::Vector2dF(1000, 0));
  double time_to_advance_ms = 30.0;
  AdvanceTime(time_to_advance_ms);
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0), /*wait_before_processing=*/false);
  EXPECT_TRUE(FlingInProgress());

  if (NeedsBeginFrameForFlingProgress())
    ProgressFling(NowTicks());

  // We haven't advanced time since the FlingStart. Ensure we still send a
  // significant amount of delta (~0.030sec * 1000pixels/sec) since we should
  // be measuring the time since the last GSU.
  EXPECT_EQ(1, sent_scroll_gesture_count_);
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_NEAR(last_sent_gesture_.data.scroll_update.delta_x, 30.0, 5);
}

// Tests that when a fling is interrupted (e.g. by having reached the end of
// the content), a subsequent fling isn't boosted. An example here would be an
// infinite scroller that loads more content after hitting the scroll extent.
TEST_P(FlingControllerTest, InterruptedFlingIsntBoosted) {
  double time_to_advance_ms = 8.0;

  // Start an ordinary fling.
  {
    AdvanceTime(time_to_advance_ms);
    SimulateScrollBegin(blink::WebGestureDevice::kTouchscreen,
                        gfx::Vector2dF(10, 0));
    SimulateScrollUpdate(blink::WebGestureDevice::kTouchscreen,
                         gfx::Vector2dF(10, 0));
    SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                       gfx::Vector2dF(1000, 0),
                       /*wait_before_processing=*/false);
    ASSERT_TRUE(FlingInProgress());

    if (NeedsBeginFrameForFlingProgress())
      ProgressFling(NowTicks());
  }

  // Stop the fling. This simulates hitting a scroll extent.
  {
    ASSERT_EQ(fling_controller_->CurrentFlingVelocity().x(), 1000);
    fling_controller_->StopFling();
  }

  // Now perform a second fling (e.g. after an infinite scroller loads more
  // content). Ensure it isn't boosted since the previous fling was
  // interrupted.
  {
    AdvanceTime(time_to_advance_ms);
    SimulateScrollBegin(blink::WebGestureDevice::kTouchscreen,
                        gfx::Vector2dF(10, 0));
    SimulateScrollUpdate(blink::WebGestureDevice::kTouchscreen,
                         gfx::Vector2dF(10, 0));
    SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                       gfx::Vector2dF(1000, 0),
                       /*wait_before_processing=*/false);

    if (NeedsBeginFrameForFlingProgress())
      ProgressFling(NowTicks());

    EXPECT_EQ(fling_controller_->CurrentFlingVelocity().x(), 1000)
        << "Fling was boosted but should not have been.";
  }
}

TEST_P(FlingControllerTest, ControllerHandlesTouchscreenGestureFling) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());

  // The fling progress will generate and send GSU events with inertial state.
  AdvanceTime();
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
  EXPECT_FALSE(FlingInProgress());

  // Cancellation should send a GSE.
  EXPECT_FALSE(FlingInProgress());
  EXPECT_EQ(WebInputEvent::Type::kGestureScrollEnd,
            last_sent_gesture_.GetType());
}

TEST_P(FlingControllerTest, ControllerSendsWheelEndWhenTouchpadFlingIsOver) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(100, 0));
  EXPECT_TRUE(FlingInProgress());
  // Processing GFS will send the first fling progress event if the time delta
  // between the timestamp of the GFS and the time that ProcessGestureFlingStart
  // is called is large enough.
  bool process_GFS_sent_first_event = first_wheel_event_sent_;

  AdvanceTime();
  ProgressFling(NowTicks());
  if (!process_GFS_sent_first_event) {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
  } else {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged,
              last_sent_wheel_.momentum_phase);
  }
  EXPECT_GT(last_sent_wheel_.delta_x, 0.f);

  AdvanceTime();
  ProgressFling(NowTicks());
  while (FlingInProgress()) {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged,
              last_sent_wheel_.momentum_phase);
    EXPECT_GT(last_sent_wheel_.delta_x, 0.f);
    AdvanceTime();
    ProgressFling(NowTicks());
  }

  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);
}

TEST_P(FlingControllerTest, ControllerSendsGSEWhenTouchscreenFlingIsOver) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(100, 0));
  EXPECT_TRUE(FlingInProgress());

  AdvanceTime();
  ProgressFling(NowTicks());
  while (FlingInProgress()) {
    ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
              last_sent_gesture_.GetType());
    EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
              last_sent_gesture_.data.scroll_update.inertial_phase);
    EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);
    AdvanceTime();
    ProgressFling(NowTicks());
  }

  EXPECT_EQ(WebInputEvent::Type::kGestureScrollEnd,
            last_sent_gesture_.GetType());
}

TEST_P(FlingControllerTest, EarlyTouchpadFlingCancelationOnFlingStop) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
  // Processing GFS will send the first fling progress event if the time delta
  // between the timestamp of the GFS and the time that ProcessGestureFlingStart
  // is called is large enough.
  bool process_GFS_sent_first_event = first_wheel_event_sent_;

  AdvanceTime();
  ProgressFling(NowTicks());
  if (!process_GFS_sent_first_event) {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
  } else {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged,
              last_sent_wheel_.momentum_phase);
  }
  EXPECT_GT(last_sent_wheel_.delta_x, 0.f);

  fling_controller_->StopFling();
  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);
}

TEST_P(FlingControllerTest, EarlyTouchscreenFlingCancelationOnFlingStop) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());

  // progress fling must send GSU events.
  AdvanceTime();
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);

  fling_controller_->StopFling();
  EXPECT_FALSE(FlingInProgress());
  EXPECT_EQ(WebInputEvent::Type::kGestureScrollEnd,
            last_sent_gesture_.GetType());
}

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_);
  }

  // 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) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0), false);
  EXPECT_TRUE(FlingInProgress());
  int current_sent_scroll_gesture_count = sent_scroll_gesture_count_;

  // If we the first progress tick happens too close to the fling_start time,
  // the controller won't send any GSU events, but the fling is still active.
  ProgressFling(NowTicks());
  EXPECT_EQ(current_sent_scroll_gesture_count, sent_scroll_gesture_count_);
  EXPECT_TRUE(FlingInProgress());

  // The rest of the progress flings must advance the fling normally.
  AdvanceTime();
  ProgressFling(NowTicks());
  EXPECT_EQ(blink::WebGestureDevice::kTouchscreen,
            last_sent_gesture_.SourceDevice());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);
}

TEST_P(FlingControllerTest, GestureFlingWithNegativeTimeDelta) {
  base::TimeTicks initial_time = NowTicks();
  AdvanceTime();
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
  int current_sent_scroll_gesture_count = sent_scroll_gesture_count_;

  // If we get a negative time delta, that is, the Progress tick time happens
  // before the fling's start time then we should *not* try progressing the
  // fling.
  ProgressFling(initial_time);
  EXPECT_EQ(current_sent_scroll_gesture_count, sent_scroll_gesture_count_);

  // The rest of the progress flings must advance the fling normally.
  AdvanceTime();
  ProgressFling(NowTicks());
  EXPECT_EQ(blink::WebGestureDevice::kTouchscreen,
            last_sent_gesture_.SourceDevice());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);
}

// Regression test for https://crbug.com/924279
TEST_P(FlingControllerTest, TouchpadFlingWithOldEvent) {
  // Only the code path that uses compositor animation observers is affected.
  if (NeedsBeginFrameForFlingProgress())
    return;

  // Create a fling start event.
  base::TimeTicks event_time = NowTicks();
  WebGestureEvent fling_start(WebInputEvent::Type::kGestureFlingStart, 0,
                              event_time, blink::WebGestureDevice::kTouchpad);
  fling_start.data.fling_start.velocity_x = 0.f;
  fling_start.data.fling_start.velocity_y = -1000.f;
  GestureEventWithLatencyInfo fling_start_with_latency(fling_start);

  // Move time forward. Assume a frame occurs here.
  AdvanceTime(1.f);
  base::TimeTicks last_frame_time = NowTicks();

  // Start the fling animation later, as if there was a delay in event dispatch.
  AdvanceTime(1.f);
  fling_controller_->ProcessGestureFlingStart(fling_start_with_latency);
  EXPECT_TRUE(FlingInProgress());

  // Initial scroll was sent.
  EXPECT_EQ(1, wheel_event_count_);
  wheel_event_count_ = 0;

  // Move time forward a little.
  AdvanceTime(1.f);

  // Simulate the compositor animation observer calling ProgressFling with the
  // last frame time. That frame time is after the event time, but before the
  // animation start time.
  ProgressFling(last_frame_time);

  // No scrolls were sent.
  EXPECT_EQ(0, wheel_event_count_);

  // Additional ProgressFling calls generate scroll events as normal.
  AdvanceTime();
  ProgressFling(NowTicks());
  EXPECT_GT(wheel_event_count_, 0);
}

TEST_P(FlingControllerTest, ControllerBoostsTouchpadFling) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
  // Processing GFS will send the first fling progress event if the time delta
  // between the timestamp of the GFS and the time that ProcessGestureFlingStart
  // is called is large enough.
  bool process_GFS_sent_first_event = first_wheel_event_sent_;

  AdvanceTime();
  ProgressFling(NowTicks());
  if (!process_GFS_sent_first_event) {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseBegan, last_sent_wheel_.momentum_phase);
  } else {
    EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged,
              last_sent_wheel_.momentum_phase);
  }
  EXPECT_GT(last_sent_wheel_.delta_x, 0.f);

  // The rest of the wheel events must have momentum_phase == KPhaseChanged.
  AdvanceTime();
  ProgressFling(NowTicks());
  EXPECT_EQ(WebMouseWheelEvent::kPhaseChanged, last_sent_wheel_.momentum_phase);
  EXPECT_GT(last_sent_wheel_.delta_x, 0.f);

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchpad);
  EXPECT_FALSE(FlingInProgress());

  // The second GFS will boost the current active fling.
  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
}

TEST_P(FlingControllerTest, ControllerBoostsTouchscreenFling) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
  // Fling progress must send GSU events.
  AdvanceTime();
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
  EXPECT_FALSE(FlingInProgress());

  // 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_GT(fling_controller_->CurrentFlingVelocity().x(), 1000);
}

// Ensure that once a fling finishes, the next fling doesn't get boosted.
TEST_P(FlingControllerTest, ControllerDoesntBoostFinishedFling) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());
  AdvanceTime();
  ProgressFling(NowTicks());

  // Fast forward so that the fling ends.
  double time_to_advance_ms = 1000.0;
  AdvanceTime(time_to_advance_ms);
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollEnd,
            last_sent_gesture_.GetType())
      << "Unexpected Last Sent Gesture: "
      << WebInputEvent::GetName(last_sent_gesture_.GetType());
  EXPECT_EQ(fling_controller_->CurrentFlingVelocity().x(), 0);
  EXPECT_FALSE(FlingInProgress());

  // Now send a new fling that would have been boosted had it occurred during
  // the previous fling. Ensure it isn't boosted.
  AdvanceTime();
  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0), /*wait_before_processing=*/false);
  EXPECT_TRUE(FlingInProgress());
  EXPECT_EQ(fling_controller_->CurrentFlingVelocity().x(), 1000);
}

TEST_P(FlingControllerTest, ControllerNotifiesTheClientAfterFlingStart) {
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
  EXPECT_FALSE(FlingInProgress());
  EXPECT_TRUE(notified_client_after_fling_stop_);
}

TEST_P(FlingControllerTest, MiddleClickAutoScrollFling) {
  SimulateFlingStart(blink::WebGestureDevice::kSyntheticAutoscroll,
                     gfx::Vector2dF(1000, 0));
  EXPECT_TRUE(FlingInProgress());

  AdvanceTime();
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);

  // Now send a new fling with different velocity and without sending a fling
  // cancel event, the new fling should always replace the old one even when
  // they are in the same direction.
  SimulateFlingStart(blink::WebGestureDevice::kSyntheticAutoscroll,
                     gfx::Vector2dF(2000, 0));
  EXPECT_TRUE(FlingInProgress());
  EXPECT_EQ(fling_controller_->CurrentFlingVelocity().x(), 2000);

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kSyntheticAutoscroll);
  EXPECT_FALSE(FlingInProgress());
}

// Ensure that the fling controller does not start a fling if the last touchpad
// wheel event was consumed.
TEST_P(FlingControllerTest, NoFlingStartAfterWheelEventConsumed) {
  // First ensure that a fling can start after a not consumed wheel event.
  fling_controller_->OnWheelEventAck(
      MouseWheelEventWithLatencyInfo(),
      blink::mojom::InputEventResultSource::kCompositorThread,
      blink::mojom::InputEventResultState::kNotConsumed);

  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(1000, 0));
  ASSERT_TRUE(FlingInProgress());

  // Cancel the first fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchpad);
  EXPECT_FALSE(FlingInProgress());

  // Now test that a consumed touchpad wheel event results in no fling.
  fling_controller_->OnWheelEventAck(
      MouseWheelEventWithLatencyInfo(),
      blink::mojom::InputEventResultSource::kCompositorThread,
      blink::mojom::InputEventResultState::kConsumed);

  SimulateFlingStart(blink::WebGestureDevice::kTouchpad,
                     gfx::Vector2dF(1000, 0));
  EXPECT_FALSE(FlingInProgress());
}

class FlingControllerWithPhysicsBasedFlingTest : public FlingControllerTest {
 public:
  // testing::Test
  FlingControllerWithPhysicsBasedFlingTest() {
    scoped_feature_list_.InitAndEnableFeature(
        features::kExperimentalFlingAnimation);
  }

  FlingControllerWithPhysicsBasedFlingTest(
      const FlingControllerWithPhysicsBasedFlingTest&) = delete;
  FlingControllerWithPhysicsBasedFlingTest& operator=(
      const FlingControllerWithPhysicsBasedFlingTest&) = delete;

  ~FlingControllerWithPhysicsBasedFlingTest() override = default;

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(All,
                         FlingControllerWithPhysicsBasedFlingTest,
                         testing::Bool());

// Ensure the bounding distance for boosted physics based flings is increased
// by a factor of the boost_multiplier and default multiplier
TEST_P(FlingControllerWithPhysicsBasedFlingTest,
       ControllerBoostsTouchscreenFling) {
  // We use a velocity of 4500 in this test because it yields a scroll delta
  // that is greater than viewport * boost_multiplier * kDefaultBoundsMultiplier

  // Android, Chromecast and iOS use Mobile fling curve so they are ignored
  // for this test
  bool use_mobile_fling_curve = false;
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_IOS)
  use_mobile_fling_curve = true;
#endif
  if (use_mobile_fling_curve)
    return;

  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(4500, 0));
  EXPECT_TRUE(FlingInProgress());
  // Fling progress must send GSU events.
  AdvanceTime();
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollUpdate,
            last_sent_gesture_.GetType());
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,
            last_sent_gesture_.data.scroll_update.inertial_phase);
  EXPECT_GT(last_sent_gesture_.data.scroll_update.delta_x, 0.f);

  // Now cancel the fling.
  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);
  EXPECT_FALSE(FlingInProgress());

  // The second GFS can be boosted so it should boost the just deactivated
  // fling. To test that the correct bounds scale is used, the scroll delta
  // is accumulated after each frame.
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(4500, 0));
  EXPECT_TRUE(FlingInProgress());
  if (NeedsBeginFrameForFlingProgress())
    ProgressFling(NowTicks());

  float total_scroll_delta = CompleteFlingAndAccumulateScrollDelta();

  // We expect the scroll delta to be the viewport * [boost_multiplier = 2] *
  // multiplier
  float expected_delta =
      2 * PhysicsBasedFlingCurve::default_bounds_multiplier_for_testing() *
      GetRootWidgetViewportSize().width();
  EXPECT_EQ(ceilf(total_scroll_delta), roundf(expected_delta));
}

// Ensure that once a fling finishes, the next fling has a boost_multiplier of 1
TEST_P(FlingControllerWithPhysicsBasedFlingTest,
       ControllerDoesntBoostFinishedFling) {
  // Android, Chromecast and iOS use Mobile fling curve so they are ignored
  // for this test
  bool use_mobile_fling_curve = false;
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CASTOS) || BUILDFLAG(IS_IOS)
  use_mobile_fling_curve = true;
#endif
  if (use_mobile_fling_curve)
    return;
  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(1000, 0), /*wait_before_processing=*/true);
  EXPECT_TRUE(FlingInProgress());
  AdvanceTime();
  ProgressFling(NowTicks());

  // Fast forward so that the fling ends.
  double time_to_advance_ms = 1000.0;
  AdvanceTime(time_to_advance_ms);
  ProgressFling(NowTicks());
  ASSERT_EQ(WebInputEvent::Type::kGestureScrollEnd,
            last_sent_gesture_.GetType())
      << "Unexpected Last Sent Gesture: "
      << WebInputEvent::GetName(last_sent_gesture_.GetType());
  EXPECT_EQ(fling_controller_->CurrentFlingVelocity().x(), 0);
  EXPECT_FALSE(FlingInProgress());

  // Now send a new fling, ensure boost_multiplier is 1
  AdvanceTime();
  SimulateFlingCancel(blink::WebGestureDevice::kTouchscreen);

  SimulateFlingStart(blink::WebGestureDevice::kTouchscreen,
                     gfx::Vector2dF(10000, 0));
  EXPECT_TRUE(FlingInProgress());
  if (NeedsBeginFrameForFlingProgress())
    ProgressFling(NowTicks());

  float total_scroll_delta = CompleteFlingAndAccumulateScrollDelta();

  // We expect the scroll delta to be the viewport * [boost_multiplier = 1] *
  // multiplier
  float expected_delta =
      PhysicsBasedFlingCurve::default_bounds_multiplier_for_testing() *
      GetRootWidgetViewportSize().width();
  EXPECT_EQ(ceilf(total_scroll_delta), roundf(expected_delta));
}

}  // namespace content