// Copyright 2018 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/touchpad_pinch_event_queue.h"

#include <string>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "components/input/event_with_latency_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/input/input_event_result.mojom-shared.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/latency/latency_info.h"

namespace input {

class MockTouchpadPinchEventQueueClient {
 public:
  MockTouchpadPinchEventQueueClient() = default;
  ~MockTouchpadPinchEventQueueClient() = default;

  // TouchpadPinchEventQueueClient
  MOCK_METHOD1(SendMouseWheelEventForPinchImmediately,
               void(const MouseWheelEventWithLatencyInfo& event));
  MOCK_METHOD3(OnGestureEventForPinchAck,
               void(const GestureEventWithLatencyInfo& event,
                    blink::mojom::InputEventResultSource ack_source,
                    blink::mojom::InputEventResultState ack_result));
};

class TouchpadPinchEventQueueTest : public testing::TestWithParam<bool>,
                                    public TouchpadPinchEventQueueClient {
 protected:
  TouchpadPinchEventQueueTest() {
    queue_ = std::make_unique<TouchpadPinchEventQueue>(this);
  }
  ~TouchpadPinchEventQueueTest() = default;

  void QueueEvent(const blink::WebGestureEvent& event) {
    ScopedDispatchToRendererCallback dispatch_callback(base::DoNothing());
    queue_->QueueEvent(GestureEventWithLatencyInfo(event),
                       dispatch_callback.callback);
  }

  void QueuePinchBegin() {
    blink::WebGestureEvent event(
        blink::WebInputEvent::Type::kGesturePinchBegin,
        blink::WebInputEvent::kNoModifiers,
        blink::WebInputEvent::GetStaticTimeStampForTests(),
        blink::WebGestureDevice::kTouchpad);
    event.SetPositionInWidget(gfx::PointF(1, 1));
    event.SetPositionInScreen(gfx::PointF(1, 1));
    event.SetNeedsWheelEvent(true);
    QueueEvent(event);
  }

  void QueuePinchEnd() {
    blink::WebGestureEvent event(
        blink::WebInputEvent::Type::kGesturePinchEnd,
        blink::WebInputEvent::kNoModifiers,
        blink::WebInputEvent::GetStaticTimeStampForTests(),
        blink::WebGestureDevice::kTouchpad);
    event.SetPositionInWidget(gfx::PointF(1, 1));
    event.SetPositionInScreen(gfx::PointF(1, 1));
    event.SetNeedsWheelEvent(true);
    QueueEvent(event);
  }

  void QueuePinchUpdate(float scale, bool zoom_disabled) {
    blink::WebGestureEvent event(
        blink::WebInputEvent::Type::kGesturePinchUpdate,
        blink::WebInputEvent::kNoModifiers,
        blink::WebInputEvent::GetStaticTimeStampForTests(),
        blink::WebGestureDevice::kTouchpad);
    event.SetPositionInWidget(gfx::PointF(1, 1));
    event.SetPositionInScreen(gfx::PointF(1, 1));
    event.SetNeedsWheelEvent(true);
    event.data.pinch_update.zoom_disabled = zoom_disabled;
    event.data.pinch_update.scale = scale;
    QueueEvent(event);
  }

  void QueueDoubleTap() {
    blink::WebGestureEvent event(
        blink::WebInputEvent::Type::kGestureDoubleTap,
        blink::WebInputEvent::kNoModifiers,
        blink::WebInputEvent::GetStaticTimeStampForTests(),
        blink::WebGestureDevice::kTouchpad);
    event.SetPositionInWidget(gfx::PointF(1, 1));
    event.SetPositionInScreen(gfx::PointF(1, 1));
    event.data.tap.tap_count = 1;
    event.SetNeedsWheelEvent(true);
    QueueEvent(event);
  }

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

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

  void SendMouseWheelEventForPinchImmediately(
      const blink::WebGestureEvent& pinch_event,
      const MouseWheelEventWithLatencyInfo& event,
      MouseWheelEventHandledCallback callback,
      DispatchToRendererCallback& dispatch_callback) override {
    mock_client_.SendMouseWheelEventForPinchImmediately(event);
    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 OnGestureEventForPinchAck(
      const GestureEventWithLatencyInfo& event,
      blink::mojom::InputEventResultSource ack_source,
      blink::mojom::InputEventResultState ack_result) override {
    mock_client_.OnGestureEventForPinchAck(event, ack_source, ack_result);
  }

  testing::StrictMock<MockTouchpadPinchEventQueueClient> mock_client_;
  std::unique_ptr<TouchpadPinchEventQueue> queue_;
  base::circular_deque<HandleEventCallback> callbacks_;
};

MATCHER_P(EventHasType,
          type,
          std::string(negation ? "does not have" : "has") + " type " +
              ::testing::PrintToString(type)) {
  return arg.event.GetType() == type;
}

MATCHER_P(EventHasPhase,
          phase,
          std::string(negation ? "does not have" : "has") + " phase " +
              ::testing::PrintToString(phase)) {
  return arg.event.phase == phase;
}

MATCHER_P(EventHasScale,
          expected_scale,
          std::string(negation ? "does not have" : "has") + " scale " +
              ::testing::PrintToString(expected_scale)) {
  const float actual_scale = exp(arg.event.delta_y / 100.0f);
  return ::testing::Matches(::testing::FloatEq(expected_scale))(actual_scale);
}

MATCHER(EventHasCtrlModifier,
        std::string(negation ? "does not have" : "has") + " control modifier") {
  return (arg.event.GetModifiers() & blink::WebInputEvent::kControlKey) != 0;
}

MATCHER(EventIsBlocking,
        std::string(negation ? "is not" : "is") + " blocking") {
  return arg.event.dispatch_type ==
         blink::WebInputEvent::DispatchType::kBlocking;
}

// Ensure that when the queue receives a touchpad pinch sequence, it sends a
// synthetic mouse wheel event and acks the pinch events back to the client.
TEST_F(TouchpadPinchEventQueueTest, Basic) {
  ::testing::InSequence sequence;
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure the queue sends the wheel events with phase information.
TEST_F(TouchpadPinchEventQueueTest, MouseWheelPhase) {
  ::testing::InSequence sequence;
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  EventHasPhase(blink::WebMouseWheelEvent::kPhaseBegan)));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  EventHasPhase(blink::WebMouseWheelEvent::kPhaseChanged)));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  testing::_, testing::_));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  EventHasPhase(blink::WebMouseWheelEvent::kPhaseEnded)));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure that if the renderer consumes the synthetic wheel event, the ack of
// the GesturePinchUpdate reflects this.
TEST_F(TouchpadPinchEventQueueTest, Consumed) {
  ::testing::InSequence sequence;
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kMainThread,
                  blink::mojom::InputEventResultState::kConsumed));

  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))));

  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
                    blink::mojom::InputEventResultState::kConsumed);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure that the queue sends wheel events for updates with |zoom_disabled| as
// well.
TEST_F(TouchpadPinchEventQueueTest, ZoomDisabled) {
  ::testing::InSequence sequence;
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));

  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))));

  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, true);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

TEST_F(TouchpadPinchEventQueueTest, MultipleSequences) {
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored))
      .Times(2);
  EXPECT_CALL(mock_client_, SendMouseWheelEventForPinchImmediately(testing::_))
      .Times(4);
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists))
      .Times(2);
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored))
      .Times(2);

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure we can queue additional pinch event sequences while the queue is
// waiting for a wheel event ack.
TEST_F(TouchpadPinchEventQueueTest, MultipleQueuedSequences) {
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_, SendMouseWheelEventForPinchImmediately(testing::_));
  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);

  testing::Mock::VerifyAndClearExpectations(&mock_client_);

  QueuePinchEnd();

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  QueuePinchEnd();

  // No calls since we're still waiting on the ack for the first wheel event.
  testing::Mock::VerifyAndClearExpectations(&mock_client_);

  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_, SendMouseWheelEventForPinchImmediately(testing::_))
      .Times(2);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  // ACK for end event.
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);

  // After acking the first wheel event, the queue continues.
  testing::Mock::VerifyAndClearExpectations(&mock_client_);

  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_, SendMouseWheelEventForPinchImmediately(testing::_));
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  // ACK for end event.
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure the queue handles pinch event sequences with multiple updates.
TEST_F(TouchpadPinchEventQueueTest, MultipleUpdatesInSequence) {
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  // Only first wheel event is cancelable.
  // Here the second and the third wheel events are not blocking because we
  // ack the first wheel event as unconsumed.
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))))
      .Times(3);
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored))
      .Times(2);
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure the queue coalesces pinch update events.
TEST_F(TouchpadPinchEventQueueTest, MultipleUpdatesCoalesced) {
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  // Only the first wheel event is cancelable.
  // Here the second wheel is not blocking because we ack the first wheel
  // event as unconsumed.
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))))
      .Times(2);
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kCompositorThread,
                  blink::mojom::InputEventResultState::kNoConsumerExists));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  // The queue will send the first wheel event for this first update.
  QueuePinchUpdate(1.23, false);
  // Before the first wheel event is acked, queue another update.
  QueuePinchUpdate(1.23, false);
  // Queue a third update. This will be coalesced with the second update which
  // is currently in the queue.
  QueuePinchUpdate(1.23, false);
  QueuePinchEnd();

  // Ack for the wheel event corresponding to the first update.
  SendWheelEventAck(blink::mojom::InputEventResultSource::kCompositorThread,
                    blink::mojom::InputEventResultState::kNoConsumerExists);
  // Ack for the wheel event corresponding to the second and third updates.
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
  // ACK for end event.
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
  EXPECT_FALSE(queue_->has_pending());
}

// Ensure the queue handles pinch event sequences with multiple canceled
// updates.
TEST_F(TouchpadPinchEventQueueTest, MultipleCanceledUpdatesInSequence) {
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())))
      .Times(3);
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kMainThread,
                  blink::mojom::InputEventResultState::kConsumed))
      .Times(3);
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
                    blink::mojom::InputEventResultState::kConsumed);
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
                    blink::mojom::InputEventResultState::kConsumed);
  QueuePinchUpdate(1.23, false);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
                    blink::mojom::InputEventResultState::kConsumed);
  QueuePinchEnd();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

// Ensure that when the queue receives a touchpad double tap, it sends a
// synthetic mouse wheel event and acks the double tap back to the client.
TEST_F(TouchpadPinchEventQueueTest, DoubleTap) {
  ::testing::InSequence sequence;
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), EventIsBlocking(),
                  EventHasPhase(blink::WebMouseWheelEvent::kPhaseNone),
                  EventHasScale(1.0f))));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGestureDoubleTap),
                  blink::mojom::InputEventResultSource::kMainThread,
                  blink::mojom::InputEventResultState::kConsumed));

  QueueDoubleTap();
  SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
                    blink::mojom::InputEventResultState::kConsumed);
}

// Ensure that ACKs are only processed when they match the event that is
// currently awaiting an ACK.
TEST_F(TouchpadPinchEventQueueTest, IgnoreNonMatchingEvents) {
  ::testing::InSequence sequence;
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchBegin),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));
  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(
                  ::testing::AllOf(EventHasCtrlModifier(), EventIsBlocking())));
  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchUpdate),
                  blink::mojom::InputEventResultSource::kMainThread,
                  blink::mojom::InputEventResultState::kConsumed));

  EXPECT_CALL(mock_client_,
              SendMouseWheelEventForPinchImmediately(::testing::AllOf(
                  EventHasCtrlModifier(), ::testing::Not(EventIsBlocking()))));

  EXPECT_CALL(mock_client_,
              OnGestureEventForPinchAck(
                  EventHasType(blink::WebInputEvent::Type::kGesturePinchEnd),
                  blink::mojom::InputEventResultSource::kBrowser,
                  blink::mojom::InputEventResultState::kIgnored));

  QueuePinchBegin();
  QueuePinchUpdate(1.23, false);
  QueuePinchEnd();

  SendWheelEventAck(blink::mojom::InputEventResultSource::kMainThread,
                    blink::mojom::InputEventResultState::kConsumed);
  SendWheelEventAck(blink::mojom::InputEventResultSource::kBrowser,
                    blink::mojom::InputEventResultState::kIgnored);
}

}  // namespace input
