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

#include "components/input/mouse_wheel_event_queue.h"

#include <stddef.h>

#include <memory>
#include <utility>

#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "components/input/timeout_monitor.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/synthetic_web_input_event_builders.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "ui/events/base_event_utils.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseWheelEvent;

namespace input {
namespace {

const float kWheelScrollX = 10;
const float kWheelScrollY = 12;
const float kWheelScrollGlobalX = 50;
const float kWheelScrollGlobalY = 72;

#define EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event)                          \
  EXPECT_EQ(WebInputEvent::Type::kGestureScrollBegin, event->GetType()); \
  EXPECT_EQ(kWheelScrollX, event->PositionInWidget().x());               \
  EXPECT_EQ(kWheelScrollY, event->PositionInWidget().y());               \
  EXPECT_EQ(kWheelScrollGlobalX, event->PositionInScreen().x());         \
  EXPECT_EQ(kWheelScrollGlobalY, event->PositionInScreen().y());         \
  EXPECT_EQ(scroll_units, event->data.scroll_begin.delta_hint_units);

#define EXPECT_GESTURE_SCROLL_BEGIN(event)                         \
  EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);                         \
  EXPECT_FALSE(event->data.scroll_begin.synthetic);                \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kUnknownMomentum, \
            event->data.scroll_begin.inertial_phase);

#define EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(event)          \
  EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);                     \
  EXPECT_FALSE(event->data.scroll_begin.synthetic);            \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kNonMomentum, \
            event->data.scroll_begin.inertial_phase);

#define EXPECT_SYNTHETIC_GESTURE_SCROLL_BEGIN(event)           \
  EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);                     \
  EXPECT_TRUE(event->data.scroll_begin.synthetic);             \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kNonMomentum, \
            event->data.scroll_begin.inertial_phase);

#define EXPECT_INERTIAL_GESTURE_SCROLL_BEGIN(event)         \
  EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);                  \
  EXPECT_FALSE(event->data.scroll_begin.synthetic);         \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum, \
            event->data.scroll_begin.inertial_phase);

#define EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_BEGIN(event) \
  EXPECT_GESTURE_SCROLL_BEGIN_IMPL(event);                    \
  EXPECT_TRUE(event->data.scroll_begin.synthetic);            \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum,   \
            event->data.scroll_begin.inertial_phase);

#define EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event)                          \
  EXPECT_EQ(WebInputEvent::Type::kGestureScrollUpdate, event->GetType()); \
  EXPECT_EQ(scroll_units, event->data.scroll_update.delta_units);         \
  EXPECT_EQ(kWheelScrollX, event->PositionInWidget().x());                \
  EXPECT_EQ(kWheelScrollY, event->PositionInWidget().y());                \
  EXPECT_EQ(kWheelScrollGlobalX, event->PositionInScreen().x());          \
  EXPECT_EQ(kWheelScrollGlobalY, event->PositionInScreen().y());

#define EXPECT_GESTURE_SCROLL_UPDATE(event)                        \
  EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event);                        \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kUnknownMomentum, \
            event->data.scroll_update.inertial_phase);

#define EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(event)         \
  EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event);                    \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kNonMomentum, \
            event->data.scroll_update.inertial_phase);

#define EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(event)        \
  EXPECT_GESTURE_SCROLL_UPDATE_IMPL(event);                 \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum, \
            event->data.scroll_update.inertial_phase);

#define EXPECT_GESTURE_SCROLL_END_IMPL(event)                          \
  EXPECT_EQ(WebInputEvent::Type::kGestureScrollEnd, event->GetType()); \
  EXPECT_EQ(scroll_units, event->data.scroll_end.delta_units);         \
  EXPECT_EQ(kWheelScrollX, event->PositionInWidget().x());             \
  EXPECT_EQ(kWheelScrollY, event->PositionInWidget().y());             \
  EXPECT_EQ(kWheelScrollGlobalX, event->PositionInScreen().x());       \
  EXPECT_EQ(kWheelScrollGlobalY, event->PositionInScreen().y());

#define EXPECT_GESTURE_SCROLL_END(event)                           \
  EXPECT_GESTURE_SCROLL_END_IMPL(event);                           \
  EXPECT_FALSE(event->data.scroll_end.synthetic);                  \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kUnknownMomentum, \
            event->data.scroll_end.inertial_phase);

#define EXPECT_GESTURE_SCROLL_END_WITH_PHASE(event)            \
  EXPECT_GESTURE_SCROLL_END_IMPL(event);                       \
  EXPECT_FALSE(event->data.scroll_end.synthetic);              \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kNonMomentum, \
            event->data.scroll_end.inertial_phase);

#define EXPECT_SYNTHETIC_GESTURE_SCROLL_END(event)             \
  EXPECT_GESTURE_SCROLL_END_IMPL(event);                       \
  EXPECT_TRUE(event->data.scroll_end.synthetic);               \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kNonMomentum, \
            event->data.scroll_end.inertial_phase);

#define EXPECT_INERTIAL_GESTURE_SCROLL_END(event)           \
  EXPECT_GESTURE_SCROLL_END_IMPL(event);                    \
  EXPECT_FALSE(event->data.scroll_end.synthetic);           \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum, \
            event->data.scroll_end.inertial_phase);

#if BUILDFLAG(IS_CHROMEOS)
#define EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_END(event) \
  EXPECT_GESTURE_SCROLL_END_IMPL(event);                    \
  EXPECT_TRUE(event->data.scroll_end.synthetic);            \
  EXPECT_EQ(WebGestureEvent::InertialPhaseState::kMomentum, \
            event->data.scroll_end.inertial_phase);         \
  EXPECT_TRUE(event->data.scroll_end.generated_by_fling_controller);
#endif

#define EXPECT_MOUSE_WHEEL(event) \
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, event->GetType());

}  // namespace

class MouseWheelEventQueueTest : public testing::Test,
                                 public MouseWheelEventQueueClient {
 public:
  MouseWheelEventQueueTest()
      : task_environment_(
            base::test::SingleThreadTaskEnvironment::MainThreadType::UI),
        acked_event_count_(0),
        last_acked_event_state_(blink::mojom::InputEventResultState::kUnknown) {
    queue_ = std::make_unique<MouseWheelEventQueue>(this);
  }

  ~MouseWheelEventQueueTest() override = default;

  // MouseWheelEventQueueClient
  void SendMouseWheelEventImmediately(
      const MouseWheelEventWithLatencyInfo& event,
      MouseWheelEventHandledCallback callback,
      DispatchToRendererCallback& dispatch_callback) override {
    WebMouseWheelEvent* cloned_event = new WebMouseWheelEvent();
    std::unique_ptr<WebInputEvent> cloned_event_holder(cloned_event);
    *cloned_event = event.event;
    sent_events_.push_back(std::move(cloned_event_holder));
    // Currently the tests assume the events are always dispatched, to simulate
    // events not being dispatched the `dispatch_callback` should be run with
    // `kNotDispatched`.
    std::move(dispatch_callback)
        .Run(event.event, DispatchToRendererResult::kDispatched);
    callbacks_.emplace_back(base::BindOnce(
        [](MouseWheelEventHandledCallback callback,
           const MouseWheelEventWithLatencyInfo& event,
           blink::mojom::InputEventResultSource ack_source,
           blink::mojom::InputEventResultState ack_result) {
          std::move(callback).Run(event, ack_source, ack_result);
        },
        std::move(callback), event));
  }

  void ForwardGestureEventWithLatencyInfo(
      const blink::WebGestureEvent& event,
      const ui::LatencyInfo& latency_info) override {
    WebGestureEvent* cloned_event = new WebGestureEvent();
    std::unique_ptr<WebInputEvent> cloned_event_holder(cloned_event);
    *cloned_event = event;
    if (event.GetType() == WebInputEvent::Type::kGestureScrollBegin) {
      is_wheel_scroll_in_progress_ = true;
    } else if (event.GetType() == WebInputEvent::Type::kGestureScrollEnd) {
      is_wheel_scroll_in_progress_ = false;
    }
    sent_events_.push_back(std::move(cloned_event_holder));
  }

  void OnMouseWheelEventAck(
      const MouseWheelEventWithLatencyInfo& event,
      blink::mojom::InputEventResultSource ack_source,
      blink::mojom::InputEventResultState ack_result) override {
    ++acked_event_count_;
    last_acked_event_ = event.event;
    last_acked_event_state_ = ack_result;
  }

  bool IsWheelScrollInProgress() override {
    return is_wheel_scroll_in_progress_;
  }

  bool IsAutoscrollInProgress() override { return false; }

 protected:
  using HandleEventCallback =
      base::OnceCallback<void(blink::mojom::InputEventResultSource ack_source,
                              blink::mojom::InputEventResultState ack_result)>;

  size_t queued_event_count() const { return queue_->queued_size(); }

  bool event_in_flight() const { return queue_->event_in_flight(); }

  std::vector<std::unique_ptr<WebInputEvent>>& all_sent_events() {
    return sent_events_;
  }

  const std::unique_ptr<WebInputEvent>& sent_input_event(size_t index) {
    return sent_events_[index];
  }
  const WebGestureEvent* sent_gesture_event(size_t index) {
    return static_cast<WebGestureEvent*>(sent_events_[index].get());
  }

  const WebMouseWheelEvent& acked_event() const { return last_acked_event_; }

  size_t GetAndResetSentEventCount() {
    size_t count = sent_events_.size();
    sent_events_.clear();
    return count;
  }

  size_t GetAndResetAckedEventCount() {
    size_t count = acked_event_count_;
    acked_event_count_ = 0;
    return count;
  }

  void SendMouseWheelEventAck(blink::mojom::InputEventResultState ack_result) {
    std::move(callbacks_.front())
        .Run(blink::mojom::InputEventResultSource::kCompositorThread,
             ack_result);
    callbacks_.pop_front();
  }

  void SendMouseWheel(float x,
                      float y,
                      float global_x,
                      float global_y,
                      float dX,
                      float dY,
                      int modifiers,
                      bool high_precision,
                      blink::WebMouseWheelEvent::Phase phase,
                      blink::WebMouseWheelEvent::Phase momentum_phase,
                      WebInputEvent::RailsMode rails_mode,
                      bool has_synthetic_phase = false) {
    WebMouseWheelEvent event = blink::SyntheticWebMouseWheelEventBuilder::Build(
        x, y, global_x, global_y, dX, dY, modifiers,
        high_precision ? ui::ScrollGranularity::kScrollByPrecisePixel
                       : ui::ScrollGranularity::kScrollByPixel);
    event.phase = phase;
    event.momentum_phase = momentum_phase;
    event.rails_mode = rails_mode;
    event.has_synthetic_phase = has_synthetic_phase;

    {
      ScopedDispatchToRendererCallback dispatch_callback(base::DoNothing());
      queue_->QueueEvent(MouseWheelEventWithLatencyInfo(event),
                         dispatch_callback.callback);
    }
  }
  void SendMouseWheel(float x,
                      float y,
                      float global_x,
                      float global_y,
                      float dX,
                      float dY,
                      int modifiers,
                      bool high_precision,
                      blink::WebMouseWheelEvent::Phase phase,
                      blink::WebMouseWheelEvent::Phase momentum_phase,
                      bool has_synthetic_phase = false) {
    SendMouseWheel(x, y, global_x, global_y, dX, dY, modifiers, high_precision,
                   phase, momentum_phase,
                   WebInputEvent::RailsMode::kRailsModeFree,
                   has_synthetic_phase);
  }

  void SendGestureEvent(WebInputEvent::Type type) {
    WebGestureEvent event(type, WebInputEvent::kNoModifiers,
                          ui::EventTimeForNow(),
                          blink::WebGestureDevice::kTouchscreen);
    queue_->OnGestureScrollEvent(
        GestureEventWithLatencyInfo(event, ui::LatencyInfo()));
  }

  static void RunTasksAndWait(base::TimeDelta delay) {
    base::RunLoop loop;
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, loop.QuitWhenIdleClosure(), delay);
    loop.Run();
  }

  void GestureSendingTest(bool high_precision) {
    const ui::ScrollGranularity scroll_units =
        high_precision ? ui::ScrollGranularity::kScrollByPrecisePixel
                       : ui::ScrollGranularity::kScrollByPixel;
    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 1, 1, 0, high_precision,
                   WebMouseWheelEvent::kPhaseBegan,
                   WebMouseWheelEvent::kPhaseNone);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    // The second mouse wheel should not be sent since one is already in
    // queue.
    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 5, 5, 0, high_precision,
                   WebMouseWheelEvent::kPhaseChanged,
                   WebMouseWheelEvent::kPhaseNone);
    EXPECT_EQ(1U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(0U, GetAndResetSentEventCount());

    // Receive an ACK for the mouse wheel event and release the next
    // mouse wheel event.
    SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
    EXPECT_EQ(0U, queued_event_count());
    EXPECT_TRUE(event_in_flight());
    EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
    EXPECT_EQ(1U, GetAndResetAckedEventCount());
    EXPECT_EQ(3U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
    EXPECT_MOUSE_WHEEL(sent_input_event(2));
    EXPECT_EQ(3U, GetAndResetSentEventCount());
  }

  void PhaseGestureSendingTest(bool high_precision) {
    const ui::ScrollGranularity scroll_units =
        high_precision ? ui::ScrollGranularity::kScrollByPrecisePixel
                       : ui::ScrollGranularity::kScrollByPixel;

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 1, 1, 0, high_precision,
                   WebMouseWheelEvent::kPhaseBegan,
                   WebMouseWheelEvent::kPhaseNone);
    EXPECT_EQ(1U, GetAndResetSentEventCount());
    SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
    EXPECT_EQ(2U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
    EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
    EXPECT_EQ(2U, GetAndResetSentEventCount());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 5, 5, 0, high_precision,
                   WebMouseWheelEvent::kPhaseChanged,
                   WebMouseWheelEvent::kPhaseNone);
    EXPECT_EQ(1U, GetAndResetSentEventCount());
    SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
    EXPECT_EQ(1U, all_sent_events().size());
    EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(0));
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 5, 5, 0, high_precision,
                   WebMouseWheelEvent::kPhaseNone,
                   WebMouseWheelEvent::kPhaseBegan);
    EXPECT_EQ(1U, GetAndResetSentEventCount());
    SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
    // A fling has started, no ScrollEnd/ScrollBegin is sent.
    EXPECT_EQ(1U, all_sent_events().size());
    EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(sent_gesture_event(0));
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 5, 5, 0, high_precision,
                   WebMouseWheelEvent::kPhaseNone,
                   WebMouseWheelEvent::kPhaseChanged);
    EXPECT_EQ(1U, GetAndResetSentEventCount());
    SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
    EXPECT_EQ(1U, all_sent_events().size());
    EXPECT_INERTIAL_GESTURE_SCROLL_UPDATE(sent_gesture_event(0));
    EXPECT_EQ(1U, GetAndResetSentEventCount());

    SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                   kWheelScrollGlobalY, 0, 0, 0, high_precision,
                   WebMouseWheelEvent::kPhaseNone,
                   WebMouseWheelEvent::kPhaseEnded);
    EXPECT_EQ(1U, GetAndResetSentEventCount());
    SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
    // MomentumPhase is ended, the scroll is done, and GSE is sent
    // immediately.
    EXPECT_EQ(1U, all_sent_events().size());
    EXPECT_INERTIAL_GESTURE_SCROLL_END(sent_gesture_event(0));
    EXPECT_EQ(1U, GetAndResetSentEventCount());
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  std::unique_ptr<MouseWheelEventQueue> queue_;
  std::vector<std::unique_ptr<WebInputEvent>> sent_events_;
  base::circular_deque<HandleEventCallback> callbacks_;
  size_t acked_event_count_;
  blink::mojom::InputEventResultState last_acked_event_state_;
  WebMouseWheelEvent last_acked_event_;

 private:
  bool is_wheel_scroll_in_progress_ = false;
};

// Tests that mouse wheel events are queued properly.
TEST_F(MouseWheelEventQueueTest, Basic) {
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // The second mouse wheel should not be sent since one is already in queue.
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 5, 5, 0, false,
                 WebMouseWheelEvent::kPhaseChanged,
                 WebMouseWheelEvent::kPhaseNone);
  EXPECT_EQ(1U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(0U, GetAndResetSentEventCount());

  // Receive an ACK for the first mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());

  // Receive an ACK for the second mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(0U, GetAndResetSentEventCount());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
}

TEST_F(MouseWheelEventQueueTest, GestureSending) {
  GestureSendingTest(false);
}

TEST_F(MouseWheelEventQueueTest, GestureSendingPrecisePixels) {
  GestureSendingTest(true);
}

TEST_F(MouseWheelEventQueueTest, GestureSendingWithPhaseInformation) {
  PhaseGestureSendingTest(false);
}

TEST_F(MouseWheelEventQueueTest,
       GestureSendingWithPhaseInformationPrecisePixels) {
  PhaseGestureSendingTest(true);
}

// Tests that a Wheel event with synthetic momentumn_phase == PhaseEnded that is
// generated by the fling controller properly populates
// scroll_end.data.scroll_end.generated_by_fling_controller.
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(MouseWheelEventQueueTest, WheelEndWithMomentumPhaseEndedInformation) {
  const ui::ScrollGranularity scroll_units =
      ui::ScrollGranularity::kScrollByPrecisePixel;
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, true /* high_precision */,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
  EXPECT_EQ(2U, GetAndResetSentEventCount());

  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 0, 0, 0, true /* high_precision */,
                 WebMouseWheelEvent::kPhaseNone,
                 WebMouseWheelEvent::kPhaseEnded, true /*has_synthetic_phase*/);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);

  EXPECT_EQ(1U, all_sent_events().size());
  EXPECT_SYNTHETIC_INERTIAL_GESTURE_SCROLL_END(sent_gesture_event(0));
  EXPECT_EQ(1U, GetAndResetSentEventCount());
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(MouseWheelEventQueueTest, GestureSendingInterrupted) {
  const ui::ScrollGranularity scroll_units =
      ui::ScrollGranularity::kScrollByPixel;
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(2U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
  EXPECT_EQ(2U, GetAndResetSentEventCount());

  // When a touch based GSB arrives in the
  // middle of wheel scrolling sequence, a synthetic wheel event with zero
  // deltas and phase = |kPhaseEnded| will be sent.
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 0, 0, 0, false,
                 WebMouseWheelEvent::kPhaseEnded,
                 WebMouseWheelEvent::kPhaseNone);
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(1U, GetAndResetAckedEventCount());

  // Ensure that a gesture scroll begin terminates the current scroll event.
  SendGestureEvent(WebInputEvent::Type::kGestureScrollBegin);

  EXPECT_EQ(2U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_END_WITH_PHASE(sent_gesture_event(1));
  EXPECT_EQ(2U, GetAndResetSentEventCount());

  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone);

  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // New mouse wheel events won't cause gestures because a scroll
  // is already in progress by another device.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(0U, all_sent_events().size());

  SendGestureEvent(WebInputEvent::Type::kGestureScrollEnd);
  EXPECT_EQ(0U, all_sent_events().size());

  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone);

  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(2U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
  EXPECT_EQ(2U, GetAndResetSentEventCount());
}

TEST_F(MouseWheelEventQueueTest, GestureRailScrolling) {
  const ui::ScrollGranularity scroll_units =
      ui::ScrollGranularity::kScrollByPixel;
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone,
                 WebInputEvent::RailsMode::kRailsModeHorizontal);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(2U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
  EXPECT_EQ(1U, sent_gesture_event(1)->data.scroll_update.delta_x);
  EXPECT_EQ(0U, sent_gesture_event(1)->data.scroll_update.delta_y);
  EXPECT_EQ(2U, GetAndResetSentEventCount());

  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseChanged,
                 WebMouseWheelEvent::kPhaseNone,
                 WebInputEvent::RailsMode::kRailsModeVertical);

  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  size_t scroll_update_index = 0;
  EXPECT_EQ(1U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(0));

  EXPECT_EQ(
      0U, sent_gesture_event(scroll_update_index)->data.scroll_update.delta_x);
  EXPECT_EQ(
      1U, sent_gesture_event(scroll_update_index)->data.scroll_update.delta_y);
  EXPECT_EQ(1U, GetAndResetSentEventCount());
}

TEST_F(MouseWheelEventQueueTest, WheelScrollLatching) {
  const ui::ScrollGranularity scroll_units =
      ui::ScrollGranularity::kScrollByPixel;
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone,
                 WebInputEvent::RailsMode::kRailsModeVertical);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());
  EXPECT_EQ(2U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_BEGIN_WITH_PHASE(sent_gesture_event(0));
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(1));
  EXPECT_EQ(0U, sent_gesture_event(1)->data.scroll_update.delta_x);
  EXPECT_EQ(1U, sent_gesture_event(1)->data.scroll_update.delta_y);
  EXPECT_EQ(2U, GetAndResetSentEventCount());

  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 1, 1, 0, false,
                 WebMouseWheelEvent::kPhaseChanged,
                 WebMouseWheelEvent::kPhaseNone,
                 WebInputEvent::RailsMode::kRailsModeVertical);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());

  // Scroll latching: no new scroll begin expected.
  EXPECT_EQ(1U, all_sent_events().size());
  EXPECT_GESTURE_SCROLL_UPDATE_WITH_PHASE(sent_gesture_event(0));
  EXPECT_EQ(0U, sent_gesture_event(0)->data.scroll_update.delta_x);
  EXPECT_EQ(1U, sent_gesture_event(0)->data.scroll_update.delta_y);
  EXPECT_EQ(1U, GetAndResetSentEventCount());
}

#if BUILDFLAG(IS_MAC)
TEST_F(MouseWheelEventQueueTest, DoNotSwapXYForShiftScroll) {
  // Send an event with shift modifier, zero value for delta X, and no direction
  // for |rails_mode|. Do not swap the scroll direction.
  SendMouseWheel(kWheelScrollX, kWheelScrollY, kWheelScrollGlobalX,
                 kWheelScrollGlobalY, 0.0, 1.0, WebInputEvent::kShiftKey, false,
                 WebMouseWheelEvent::kPhaseBegan,
                 WebMouseWheelEvent::kPhaseNone,
                 WebInputEvent::RailsMode::kRailsModeFree);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_TRUE(event_in_flight());
  EXPECT_EQ(1U, GetAndResetSentEventCount());

  // Receive an ACK for the mouse wheel event.
  SendMouseWheelEventAck(blink::mojom::InputEventResultState::kNotConsumed);
  EXPECT_EQ(0U, queued_event_count());
  EXPECT_FALSE(event_in_flight());
  EXPECT_EQ(WebInputEvent::Type::kMouseWheel, acked_event().GetType());
  EXPECT_EQ(1U, GetAndResetAckedEventCount());

  EXPECT_EQ(2U, all_sent_events().size());
  EXPECT_EQ(0U, sent_gesture_event(1)->data.scroll_update.delta_x);
  EXPECT_EQ(1U, sent_gesture_event(1)->data.scroll_update.delta_y);
  EXPECT_EQ(2U, GetAndResetSentEventCount());
}
#endif
}  // namespace input
