910e62b5创建于 1月15日历史提交
// Copyright 2013 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/common/input/synthetic_gesture_controller.h"

#include <stddef.h>
#include <stdint.h>

#include <array>
#include <memory>
#include <utility>

#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "content/common/input/synthetic_gesture.h"
#include "content/common/input/synthetic_gesture_target.h"
#include "content/common/input/synthetic_pinch_gesture.h"
#include "content/common/input/synthetic_pinch_gesture_params.h"
#include "content/common/input/synthetic_pointer_action.h"
#include "content/common/input/synthetic_smooth_drag_gesture.h"
#include "content/common/input/synthetic_smooth_drag_gesture_params.h"
#include "content/common/input/synthetic_smooth_move_gesture.h"
#include "content/common/input/synthetic_smooth_scroll_gesture.h"
#include "content/common/input/synthetic_smooth_scroll_gesture_params.h"
#include "content/common/input/synthetic_tap_gesture.h"
#include "content/common/input/synthetic_tap_gesture_params.h"
#include "content/common/input/synthetic_touchpad_pinch_gesture.h"
#include "content/common/input/synthetic_touchscreen_pinch_gesture.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/input/web_input_event.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/geometry/vector2d_f.h"

using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using blink::WebTouchPoint;

namespace content {

namespace {

const int kFlushInputRateInMs = 16;
const int kPointerAssumedStoppedTimeMs = 43;
const float kTouchSlopInDips = 7.0f;
const float kMinScalingSpanInDips = 27.5f;
const int kTouchPointersLength = 16;
const int kMouseWheelTickMultiplier = 0;

enum TouchGestureType { TOUCH_SCROLL, TOUCH_DRAG };

WebTouchPoint::State ToWebTouchPointState(
    SyntheticPointerActionParams::PointerActionType action_type) {
  switch (action_type) {
    case SyntheticPointerActionParams::PointerActionType::PRESS:
      return WebTouchPoint::State::kStatePressed;
    case SyntheticPointerActionParams::PointerActionType::MOVE:
      return WebTouchPoint::State::kStateMoved;
    case SyntheticPointerActionParams::PointerActionType::RELEASE:
      return WebTouchPoint::State::kStateReleased;
    case SyntheticPointerActionParams::PointerActionType::CANCEL:
      return WebTouchPoint::State::kStateCancelled;
    case SyntheticPointerActionParams::PointerActionType::IDLE:
      return WebTouchPoint::State::kStateStationary;
    case SyntheticPointerActionParams::PointerActionType::LEAVE:
    case SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED:
      NOTREACHED()
          << "Invalid SyntheticPointerActionParams::PointerActionType.";
  }
  NOTREACHED() << "Invalid SyntheticPointerActionParams::PointerActionType.";
}

WebInputEvent::Type ToWebMouseEventType(
    SyntheticPointerActionParams::PointerActionType action_type) {
  switch (action_type) {
    case SyntheticPointerActionParams::PointerActionType::PRESS:
      return WebInputEvent::Type::kMouseDown;
    case SyntheticPointerActionParams::PointerActionType::MOVE:
      return WebInputEvent::Type::kMouseMove;
    case SyntheticPointerActionParams::PointerActionType::RELEASE:
      return WebInputEvent::Type::kMouseUp;
    case SyntheticPointerActionParams::PointerActionType::LEAVE:
      return WebInputEvent::Type::kMouseLeave;
    case SyntheticPointerActionParams::PointerActionType::CANCEL:
    case SyntheticPointerActionParams::PointerActionType::IDLE:
    case SyntheticPointerActionParams::PointerActionType::NOT_INITIALIZED:
      NOTREACHED()
          << "Invalid SyntheticPointerActionParams::PointerActionType.";
  }
  NOTREACHED() << "Invalid SyntheticPointerActionParams::PointerActionType.";
}

WebInputEvent::Type WebTouchPointStateToEventType(
    blink::WebTouchPoint::State state) {
  switch (state) {
    case blink::WebTouchPoint::State::kStateReleased:
      return WebInputEvent::Type::kTouchEnd;
    case blink::WebTouchPoint::State::kStatePressed:
      return WebInputEvent::Type::kTouchStart;
    case blink::WebTouchPoint::State::kStateMoved:
      return WebInputEvent::Type::kTouchMove;
    case blink::WebTouchPoint::State::kStateCancelled:
      return WebInputEvent::Type::kTouchCancel;
    default:
      return WebInputEvent::Type::kUndefined;
  }
}

class MockGestureParams : public SyntheticGestureParams {
 public:
  GestureType GetGestureType() const override {
    return SyntheticGestureParams::TAP_GESTURE;
  }
};

class MockSyntheticGesture : public SyntheticGestureBase<MockGestureParams> {
 public:
  MockSyntheticGesture(bool* finished, int num_steps)
      : SyntheticGestureBase(MockGestureParams()),
        finished_(finished),
        num_steps_(num_steps),
        step_count_(0) {
    *finished_ = false;
  }
  ~MockSyntheticGesture() override {}

  Result ForwardInputEvents(const base::TimeTicks& timestamp,
                            SyntheticGestureTarget* target) override {
    step_count_++;
    if (step_count_ == num_steps_) {
      *finished_ = true;
      return SyntheticGesture::GESTURE_FINISHED;
    } else if (step_count_ > num_steps_) {
      *finished_ = true;
      // Return arbitrary failure.
      return SyntheticGesture::GESTURE_SOURCE_TYPE_NOT_IMPLEMENTED;
    }

    return SyntheticGesture::GESTURE_RUNNING;
  }

 protected:
  raw_ptr<bool> finished_;
  int num_steps_;
  int step_count_;
};

class MockSyntheticGestureTarget : public SyntheticGestureTarget {
 public:
  MockSyntheticGestureTarget()
      : flush_requested_(false),
        pointer_assumed_stopped_time_ms_(kPointerAssumedStoppedTimeMs) {}
  ~MockSyntheticGestureTarget() override {}

  // SyntheticGestureTarget:
  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    if (!(event.GetModifiers() & blink::WebInputEvent::kFromDebugger)) {
      all_from_debugger_ = false;
    }
  }

  void GetVSyncParameters(base::TimeTicks& timebase,
                          base::TimeDelta& interval) const override {
    timebase = base::TimeTicks();
    interval = base::Microseconds(16667);
  }

  content::mojom::GestureSourceType GetDefaultSyntheticGestureSourceType()
      const override {
    return content::mojom::GestureSourceType::kTouchInput;
  }

  base::TimeDelta PointerAssumedStoppedTime() const override {
    return base::Milliseconds(pointer_assumed_stopped_time_ms_);
  }

  void set_pointer_assumed_stopped_time_ms(int time_ms) {
    pointer_assumed_stopped_time_ms_ = time_ms;
  }

  float GetTouchSlopInDips() const override { return kTouchSlopInDips; }
  float GetSpanSlopInDips() const override { return 2 * kTouchSlopInDips; }

  int GetMouseWheelMinimumGranularity() const override {
    return kMouseWheelTickMultiplier;
  }

  float GetMinScalingSpanInDips() const override {
    return kMinScalingSpanInDips;
  }

  bool flush_requested() const { return flush_requested_; }
  void ClearFlushRequest() { flush_requested_ = false; }

  void WaitForTargetAck(SyntheticGestureParams::GestureType type,
                        content::mojom::GestureSourceType source,
                        base::OnceClosure callback) const override {
    // Must resolve synchronously since FlushInputUntilComplete will try the
    // next gesture after this one.
    std::move(callback).Run();
  }

  bool all_from_debugger() { return all_from_debugger_; }

 private:
  bool flush_requested_;

  int pointer_assumed_stopped_time_ms_;
  bool all_from_debugger_ = true;
};

class MockMoveGestureTarget : public MockSyntheticGestureTarget {
 public:
  MockMoveGestureTarget()
      : total_abs_move_distance_length_(0),
        granularity_(ui::ScrollGranularity::kScrollByPixel) {}
  ~MockMoveGestureTarget() override {}

  gfx::Vector2dF start_to_end_distance() const {
    return start_to_end_distance_;
  }
  float total_abs_move_distance_length() const {
    return total_abs_move_distance_length_;
  }

  ui::ScrollGranularity granularity() const { return granularity_; }

 protected:
  gfx::Vector2dF start_to_end_distance_;
  float total_abs_move_distance_length_;
  ui::ScrollGranularity granularity_;
};

class MockScrollMouseTarget : public MockMoveGestureTarget {
 public:
  MockScrollMouseTarget() {}
  ~MockScrollMouseTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_EQ(event.GetType(), WebInputEvent::Type::kMouseWheel);
    const WebMouseWheelEvent& mouse_wheel_event =
        static_cast<const WebMouseWheelEvent&>(event);
    gfx::Vector2dF delta(mouse_wheel_event.delta_x, mouse_wheel_event.delta_y);
    start_to_end_distance_ += delta;
    total_abs_move_distance_length_ += delta.Length();
    granularity_ = mouse_wheel_event.delta_units;
  }
};

class MockMoveTouchTarget : public MockMoveGestureTarget {
 public:
  MockMoveTouchTarget() : started_(false) {}
  ~MockMoveTouchTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_TRUE(WebInputEvent::IsTouchEventType(event.GetType()));
    const WebTouchEvent& touch_event = static_cast<const WebTouchEvent&>(event);
    ASSERT_EQ(touch_event.touches_length, 1U);

    if (!started_) {
      ASSERT_EQ(touch_event.GetType(), WebInputEvent::Type::kTouchStart);
      start_ = touch_event.touches[0].PositionInWidget();
      last_touch_point_ = start_;
      started_ = true;
    } else {
      ASSERT_NE(touch_event.GetType(), WebInputEvent::Type::kTouchStart);
      ASSERT_NE(touch_event.GetType(), WebInputEvent::Type::kTouchCancel);

      gfx::PointF touch_point(touch_event.touches[0].PositionInWidget());
      gfx::Vector2dF delta = touch_point - last_touch_point_;
      total_abs_move_distance_length_ += delta.Length();

      if (touch_event.GetType() == WebInputEvent::Type::kTouchEnd)
        start_to_end_distance_ = touch_point - start_;

      last_touch_point_ = touch_point;
    }
  }

 protected:
  gfx::PointF start_;
  gfx::PointF last_touch_point_;
  bool started_;
};

class MockFlingGestureTarget : public MockMoveGestureTarget {
 public:
  MockFlingGestureTarget() : fling_velocity_x_(0), fling_velocity_y_(0) {}
  ~MockFlingGestureTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    if (event.GetType() == WebInputEvent::Type::kGestureFlingStart) {
      const blink::WebGestureEvent& gesture_event =
          static_cast<const blink::WebGestureEvent&>(event);
      fling_velocity_x_ = gesture_event.data.fling_start.velocity_x;
      fling_velocity_y_ = gesture_event.data.fling_start.velocity_y;
    }
  }

  float fling_velocity_x() const { return fling_velocity_x_; }
  float fling_velocity_y() const { return fling_velocity_y_; }

 private:
  float fling_velocity_x_;
  float fling_velocity_y_;
};

class MockDragMouseTarget : public MockMoveGestureTarget {
 public:
  MockDragMouseTarget() : started_(false) {}
  ~MockDragMouseTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_TRUE(WebInputEvent::IsMouseEventType(event.GetType()));
    const WebMouseEvent& mouse_event = static_cast<const WebMouseEvent&>(event);
    if (!started_) {
      EXPECT_EQ(mouse_event.button, WebMouseEvent::Button::kLeft);
      EXPECT_EQ(mouse_event.click_count, 1);
      EXPECT_EQ(mouse_event.GetType(), WebInputEvent::Type::kMouseDown);
      start_ = mouse_event.PositionInWidget();
      last_mouse_point_ = start_;
      started_ = true;
    } else {
      EXPECT_EQ(mouse_event.button, WebMouseEvent::Button::kLeft);
      ASSERT_NE(mouse_event.GetType(), WebInputEvent::Type::kMouseDown);

      gfx::PointF mouse_point(mouse_event.PositionInWidget());
      gfx::Vector2dF delta = mouse_point - last_mouse_point_;
      total_abs_move_distance_length_ += delta.Length();
      if (mouse_event.GetType() == WebInputEvent::Type::kMouseUp)
        start_to_end_distance_ = mouse_point - start_;
      last_mouse_point_ = mouse_point;
    }
  }

 private:
  bool started_;
  gfx::PointF start_, last_mouse_point_;
};

class MockSyntheticTouchscreenPinchTouchTarget
    : public MockSyntheticGestureTarget {
 public:
  enum ZoomDirection {
    ZOOM_DIRECTION_UNKNOWN,
    ZOOM_IN,
    ZOOM_OUT
  };

  MockSyntheticTouchscreenPinchTouchTarget() = default;
  ~MockSyntheticTouchscreenPinchTouchTarget() override = default;

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_TRUE(WebInputEvent::IsTouchEventType(event.GetType()));
    const WebTouchEvent& touch_event = static_cast<const WebTouchEvent&>(event);
    if ((touch_event.GetType() == WebInputEvent::Type::kTouchStart &&
         !received_first_touch_start_) ||
        (touch_event.GetType() == WebInputEvent::Type::kTouchEnd &&
         received_first_touch_end_)) {
      ASSERT_EQ(touch_event.touches_length, 1U);
    } else {
      ASSERT_EQ(touch_event.touches_length, 2U);
    }

    if (!started_) {
      ASSERT_EQ(touch_event.GetType(), WebInputEvent::Type::kTouchStart);

      if (received_first_touch_start_) {
        start_0_ = gfx::PointF(touch_event.touches[0].PositionInWidget());
        start_1_ = gfx::PointF(touch_event.touches[1].PositionInWidget());
        last_pointer_distance_ = (start_0_ - start_1_).Length();
        initial_pointer_distance_ = last_pointer_distance_;
        EXPECT_GE(initial_pointer_distance_, GetMinScalingSpanInDips());
        started_ = true;
      }
    } else {
      ASSERT_NE(touch_event.GetType(), WebInputEvent::Type::kTouchStart);
      ASSERT_NE(touch_event.GetType(), WebInputEvent::Type::kTouchCancel);

      if (!received_first_touch_end_) {
        gfx::PointF current_0 =
            gfx::PointF(touch_event.touches[0].PositionInWidget());
        gfx::PointF current_1 =
            gfx::PointF(touch_event.touches[1].PositionInWidget());

        float pointer_distance = (current_0 - current_1).Length();

        if (last_pointer_distance_ != pointer_distance) {
          if (zoom_direction_ == ZOOM_DIRECTION_UNKNOWN) {
            zoom_direction_ =
                ComputeZoomDirection(last_pointer_distance_, pointer_distance);
          } else {
            EXPECT_EQ(
                zoom_direction_,
                ComputeZoomDirection(last_pointer_distance_, pointer_distance));
          }
        }

        last_pointer_distance_ = pointer_distance;
      }
    }

    received_first_touch_start_ |=
        touch_event.GetType() == WebInputEvent::Type::kTouchStart;

    received_first_touch_end_ |=
        touch_event.GetType() == WebInputEvent::Type::kTouchEnd;
  }

  content::mojom::GestureSourceType GetDefaultSyntheticGestureSourceType()
      const override {
    return content::mojom::GestureSourceType::kTouchInput;
  }

  ZoomDirection zoom_direction() const { return zoom_direction_; }

  float ComputeScaleFactor() const {
    switch (zoom_direction_) {
      case ZOOM_IN:
        return last_pointer_distance_ /
               (initial_pointer_distance_ + GetSpanSlopInDips());
      case ZOOM_OUT:
        return last_pointer_distance_ /
               (initial_pointer_distance_ - GetSpanSlopInDips());
      case ZOOM_DIRECTION_UNKNOWN:
        return 1.0f;
      default:
        NOTREACHED();
    }
  }

 private:
  ZoomDirection ComputeZoomDirection(float last_pointer_distance,
                                     float current_pointer_distance) {
    DCHECK_NE(last_pointer_distance, current_pointer_distance);
    return last_pointer_distance < current_pointer_distance ? ZOOM_IN
                                                            : ZOOM_OUT;
  }

  float initial_pointer_distance_ = 0.f;
  float last_pointer_distance_ = 0.f;
  ZoomDirection zoom_direction_ = ZOOM_DIRECTION_UNKNOWN;
  gfx::PointF start_0_;
  gfx::PointF start_1_;
  // Starting and ending pinch zoom involves two events. Much of the unit test
  // code applies only when both fingers are pressed. These two booleans allow
  // us to do the required bookkeeping to ensure that we know when to do work
  // that depends on both fingers being pressed.
  bool received_first_touch_start_ = false;
  bool received_first_touch_end_ = false;
  bool started_ = false;
};

class MockSyntheticTouchpadPinchTouchTarget
    : public MockSyntheticGestureTarget {
 public:
  enum ZoomDirection { ZOOM_DIRECTION_UNKNOWN, ZOOM_IN, ZOOM_OUT };

  MockSyntheticTouchpadPinchTouchTarget()
      : zoom_direction_(ZOOM_DIRECTION_UNKNOWN),
        started_(false),
        ended_(false),
        scale_factor_(1.0f) {}
  ~MockSyntheticTouchpadPinchTouchTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    EXPECT_TRUE(WebInputEvent::IsGestureEventType(event.GetType()));
    const blink::WebGestureEvent& gesture_event =
        static_cast<const blink::WebGestureEvent&>(event);

    if (gesture_event.GetType() == WebInputEvent::Type::kGesturePinchBegin) {
      EXPECT_FALSE(started_);
      EXPECT_FALSE(ended_);
      started_ = true;
    } else if (gesture_event.GetType() ==
               WebInputEvent::Type::kGesturePinchEnd) {
      EXPECT_TRUE(started_);
      EXPECT_FALSE(ended_);
      ended_ = true;
    } else {
      EXPECT_EQ(WebInputEvent::Type::kGesturePinchUpdate,
                gesture_event.GetType());
      EXPECT_TRUE(started_);
      EXPECT_FALSE(ended_);
      const float scale = gesture_event.data.pinch_update.scale;
      if (scale != 1.0f) {
        if (zoom_direction_ == ZOOM_DIRECTION_UNKNOWN) {
          zoom_direction_ = scale > 1.0f ? ZOOM_IN : ZOOM_OUT;
        } else if (zoom_direction_ == ZOOM_IN) {
          EXPECT_GT(scale, 1.0f);
        } else {
          EXPECT_EQ(ZOOM_OUT, zoom_direction_);
          EXPECT_LT(scale, 1.0f);
        }

        scale_factor_ *= scale;
      }
    }
  }

  content::mojom::GestureSourceType GetDefaultSyntheticGestureSourceType()
      const override {
    return content::mojom::GestureSourceType::kMouseInput;
  }

  ZoomDirection zoom_direction() const { return zoom_direction_; }

  float scale_factor() const { return scale_factor_; }

 private:
  ZoomDirection zoom_direction_;
  bool started_;
  bool ended_;
  float scale_factor_;
};

class MockSyntheticTapGestureTarget : public MockSyntheticGestureTarget {
 public:
  MockSyntheticTapGestureTarget() : state_(NOT_STARTED) {}
  ~MockSyntheticTapGestureTarget() override {}

  bool GestureFinished() const { return state_ == FINISHED; }
  gfx::PointF position() const { return position_; }
  base::TimeDelta GetDuration() const { return stop_time_ - start_time_; }

 protected:
  enum GestureState {
    NOT_STARTED,
    STARTED,
    FINISHED
  };

  gfx::PointF position_;
  base::TimeDelta start_time_;
  base::TimeDelta stop_time_;
  GestureState state_;
};

class MockSyntheticTapTouchTarget : public MockSyntheticTapGestureTarget {
 public:
  MockSyntheticTapTouchTarget() {}
  ~MockSyntheticTapTouchTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_TRUE(WebInputEvent::IsTouchEventType(event.GetType()));
    const WebTouchEvent& touch_event = static_cast<const WebTouchEvent&>(event);
    ASSERT_EQ(touch_event.touches_length, 1U);

    switch (state_) {
      case NOT_STARTED:
        EXPECT_EQ(touch_event.GetType(), WebInputEvent::Type::kTouchStart);
        position_ = gfx::PointF(touch_event.touches[0].PositionInWidget());
        start_time_ = touch_event.TimeStamp().since_origin();
        state_ = STARTED;
        break;
      case STARTED:
        EXPECT_EQ(touch_event.GetType(), WebInputEvent::Type::kTouchEnd);
        EXPECT_EQ(position_,
                  gfx::PointF(touch_event.touches[0].PositionInWidget()));
        stop_time_ = touch_event.TimeStamp().since_origin();
        state_ = FINISHED;
        break;
      case FINISHED:
        EXPECT_FALSE(true);
        break;
    }
  }
};

class MockSyntheticTapMouseTarget : public MockSyntheticTapGestureTarget {
 public:
  MockSyntheticTapMouseTarget() {}
  ~MockSyntheticTapMouseTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_TRUE(WebInputEvent::IsMouseEventType(event.GetType()));
    const WebMouseEvent& mouse_event = static_cast<const WebMouseEvent&>(event);

    switch (state_) {
      case NOT_STARTED:
        EXPECT_EQ(mouse_event.GetType(), WebInputEvent::Type::kMouseDown);
        EXPECT_EQ(mouse_event.button, WebMouseEvent::Button::kLeft);
        EXPECT_EQ(mouse_event.click_count, 1);
        position_ = gfx::PointF(mouse_event.PositionInWidget());
        start_time_ = mouse_event.TimeStamp().since_origin();
        state_ = STARTED;
        break;
      case STARTED:
        EXPECT_EQ(mouse_event.GetType(), WebInputEvent::Type::kMouseUp);
        EXPECT_EQ(mouse_event.button, WebMouseEvent::Button::kLeft);
        EXPECT_EQ(mouse_event.click_count, 1);
        EXPECT_EQ(position_, gfx::PointF(mouse_event.PositionInWidget()));
        stop_time_ = mouse_event.TimeStamp().since_origin();
        state_ = FINISHED;
        break;
      case FINISHED:
        EXPECT_FALSE(true);
        break;
    }
  }
};

class MockSyntheticPointerActionTarget : public MockSyntheticGestureTarget {
 public:
  MockSyntheticPointerActionTarget() : num_dispatched_pointer_actions_(0) {}
  ~MockSyntheticPointerActionTarget() override {}

  WebInputEvent::Type type() const { return type_; }
  int num_dispatched_pointer_actions() const {
    return num_dispatched_pointer_actions_;
  }
  void reset_num_dispatched_pointer_actions() {
    num_dispatched_pointer_actions_ = 0;
  }

 protected:
  WebInputEvent::Type type_;
  int num_dispatched_pointer_actions_;
};

class MockSyntheticPointerTouchActionTarget
    : public MockSyntheticPointerActionTarget {
 public:
  MockSyntheticPointerTouchActionTarget() {}
  ~MockSyntheticPointerTouchActionTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    DCHECK(WebInputEvent::IsTouchEventType(event.GetType()));
    const WebTouchEvent& touch_event = static_cast<const WebTouchEvent&>(event);
    type_ = touch_event.GetType();
    for (size_t i = 0; i < WebTouchEvent::kTouchesLengthCap; ++i) {
      if (WebTouchPointStateToEventType(touch_event.touches[i].state) != type_)
        continue;
      indexes_[num_dispatched_pointer_actions_] = i;
      positions_[num_dispatched_pointer_actions_] =
          gfx::PointF(touch_event.touches[i].PositionInWidget());
      states_[num_dispatched_pointer_actions_] = touch_event.touches[i].state;
      num_dispatched_pointer_actions_++;
    }
  }

  testing::AssertionResult SyntheticTouchActionDispatchedCorrectly(
      const SyntheticPointerActionParams& param,
      int index,
      int touch_index) {
    if (param.pointer_action_type() ==
            SyntheticPointerActionParams::PointerActionType::PRESS ||
        param.pointer_action_type() ==
            SyntheticPointerActionParams::PointerActionType::MOVE) {
      if (indexes_[index] != touch_index) {
        return testing::AssertionFailure()
               << "Pointer index at index " << index << " was "
               << indexes_[index] << ", expected " << touch_index << ".";
      }

      if (positions_[index] != param.position()) {
        return testing::AssertionFailure()
               << "Pointer position at index " << index << " was "
               << positions_[index].ToString() << ", expected "
               << param.position().ToString() << ".";
      }
    }

    if (states_[index] != ToWebTouchPointState(param.pointer_action_type())) {
      return testing::AssertionFailure()
             << "Pointer states at index " << index << " was " << states_[index]
             << ", expected "
             << ToWebTouchPointState(param.pointer_action_type()) << ".";
    }
    return testing::AssertionSuccess();
  }

  testing::AssertionResult SyntheticTouchActionListDispatchedCorrectly(
      const std::vector<SyntheticPointerActionParams>& params_list,
      int start_index,
      base::span<int> index_array) {
    testing::AssertionResult result = testing::AssertionSuccess();
    for (size_t i = 0; i < params_list.size(); ++i) {
      if (params_list[i].pointer_action_type() !=
          SyntheticPointerActionParams::PointerActionType::IDLE)
        result = SyntheticTouchActionDispatchedCorrectly(
            params_list[i], start_index + i, index_array[i]);
      if (result == testing::AssertionFailure())
        return result;
    }
    return testing::AssertionSuccess();
  }

 private:
  std::array<gfx::PointF, kTouchPointersLength> positions_;
  std::array<int, kTouchPointersLength> indexes_;
  std::array<WebTouchPoint::State, kTouchPointersLength> states_;
};

class MockSyntheticPointerMouseActionTarget
    : public MockSyntheticPointerActionTarget {
 public:
  MockSyntheticPointerMouseActionTarget() {}
  ~MockSyntheticPointerMouseActionTarget() override {}

  void DispatchInputEventToPlatform(const WebInputEvent& event) override {
    MockSyntheticGestureTarget::DispatchInputEventToPlatform(event);
    ASSERT_TRUE(WebInputEvent::IsMouseEventType(event.GetType()));
    const WebMouseEvent& mouse_event = static_cast<const WebMouseEvent&>(event);
    type_ = mouse_event.GetType();
    position_ = gfx::PointF(mouse_event.PositionInWidget());
    clickCount_ = mouse_event.click_count;
    button_ = mouse_event.button;
    num_dispatched_pointer_actions_++;
  }

  testing::AssertionResult SyntheticMouseActionDispatchedCorrectly(
      const SyntheticPointerActionParams& param,
      int click_count,
      SyntheticPointerActionParams::Button button =
          SyntheticPointerActionParams::Button::NO_BUTTON) {
    if (type_ != ToWebMouseEventType(param.pointer_action_type())) {
      return testing::AssertionFailure()
             << "Pointer type was " << WebInputEvent::GetName(type_)
             << ", expected " << WebInputEvent::GetName(ToWebMouseEventType(
             param.pointer_action_type())) << ".";
    }

    if (clickCount_ != click_count) {
      return testing::AssertionFailure() << "Pointer click count was "
                                         << clickCount_ << ", expected "
                                         << click_count << ".";
    }

    if (button_ != WebMouseEvent::Button::kNoButton) {
      if (param.pointer_action_type() ==
              SyntheticPointerActionParams::PointerActionType::PRESS ||
          param.pointer_action_type() ==
              SyntheticPointerActionParams::PointerActionType::RELEASE) {
        if (clickCount_ != 1) {
          return testing::AssertionFailure() << "Pointer click count was "
                                             << clickCount_ << ", expected 1.";
        }
      }

      if (param.pointer_action_type() ==
          SyntheticPointerActionParams::PointerActionType::MOVE) {
        if (clickCount_ != 0) {
          return testing::AssertionFailure() << "Pointer click count was "
                                             << clickCount_ << ", expected 0.";
        }
      }

      if (button_ !=
          SyntheticPointerActionParams::GetWebMouseEventButton(button)) {
        return testing::AssertionFailure()
               << "Pointer button was " << static_cast<int>(button_)
               << ", expected " << static_cast<int>(button) << ".";
      }
    }

    if ((param.pointer_action_type() ==
             SyntheticPointerActionParams::PointerActionType::PRESS ||
         param.pointer_action_type() ==
             SyntheticPointerActionParams::PointerActionType::MOVE) &&
        position_ != param.position()) {
      return testing::AssertionFailure()
             << "Pointer position was " << position_.ToString() << ", expected "
             << param.position().ToString() << ".";
    }
    return testing::AssertionSuccess();
  }

 private:
  gfx::PointF position_;
  int clickCount_;
  WebMouseEvent::Button button_;
};

class DummySyntheticGestureControllerDelegate
    : public SyntheticGestureController::Delegate {
 public:
  DummySyntheticGestureControllerDelegate() = default;

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

  ~DummySyntheticGestureControllerDelegate() override = default;

 private:
  // SyntheticGestureController::Delegate:
  bool HasGestureStopped() override { return true; }
  bool IsHidden() const override { return false; }
};

}  // namespace

class SyntheticGestureControllerTestBase {
 public:
  SyntheticGestureControllerTestBase() {}
  ~SyntheticGestureControllerTestBase() {}

 protected:
  template <typename MockGestureTarget>
  void CreateControllerAndTarget() {
    target_ = new MockGestureTarget();
    controller_ = std::make_unique<SyntheticGestureController>(
        &delegate_, std::unique_ptr<SyntheticGestureTarget>(target_),
        base::SingleThreadTaskRunner::GetCurrentDefault());
  }

  void QueueSyntheticGesture(std::unique_ptr<SyntheticGesture> gesture) {
    controller_->QueueSyntheticGesture(
        std::move(gesture),
        base::BindOnce(
            &SyntheticGestureControllerTestBase::OnSyntheticGestureCompleted,
            base::Unretained(this)));
  }

  void FlushInputUntilComplete() {
    // Start the timer explicitly here, since the test does not need to
    // wait for begin-frame to start the timer.
    controller_->StartOrUpdateTimer();
    do
      time_ += base::Milliseconds(kFlushInputRateInMs);
    while (controller_->DispatchNextEvent(time_));
  }

  void OnSyntheticGestureCompleted(SyntheticGesture::Result result) {
    DCHECK_NE(result, SyntheticGesture::GESTURE_RUNNING);
    if (result == SyntheticGesture::GESTURE_FINISHED)
      num_success_++;
    else
      num_failure_++;
  }

  bool DispatchTimerRunning() const {
    return controller_->dispatch_timer_.IsRunning();
  }

  base::TimeDelta GetTotalTime() const { return time_ - start_time_; }

  base::test::TaskEnvironment env_;
  raw_ptr<MockSyntheticGestureTarget, DanglingUntriaged> target_;
  DummySyntheticGestureControllerDelegate delegate_;
  std::unique_ptr<SyntheticGestureController> controller_;
  base::TimeTicks start_time_;
  base::TimeTicks time_;
  int num_success_;
  int num_failure_;
};

class SyntheticGestureControllerTest
    : public SyntheticGestureControllerTestBase,
      public testing::Test {
 protected:
  void SetUp() override {
    start_time_ = base::TimeTicks::Now();
    time_ = start_time_;
    num_success_ = 0;
    num_failure_ = 0;
  }

  void TearDown() override {
    controller_.reset();
    target_ = nullptr;
    time_ = base::TimeTicks();
  }
};

class SyntheticGestureControllerTestWithParam
    : public SyntheticGestureControllerTestBase,
      public testing::TestWithParam<bool> {
 protected:
  void SetUp() override {
    start_time_ = base::TimeTicks::Now();
    time_ = start_time_;
    num_success_ = 0;
    num_failure_ = 0;
  }

  void TearDown() override {
    controller_.reset();
    target_ = nullptr;
    time_ = base::TimeTicks();
  }
};

TEST_F(SyntheticGestureControllerTest, SingleGesture) {
  CreateControllerAndTarget<MockSyntheticGestureTarget>();

  bool finished = false;
  std::unique_ptr<MockSyntheticGesture> gesture(
      new MockSyntheticGesture(&finished, 3));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_TRUE(finished);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
}

TEST_F(SyntheticGestureControllerTest, GestureFailed) {
  CreateControllerAndTarget<MockSyntheticGestureTarget>();

  bool finished = false;
  std::unique_ptr<MockSyntheticGesture> gesture(
      new MockSyntheticGesture(&finished, 0));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_TRUE(finished);
  EXPECT_EQ(1, num_failure_);
  EXPECT_EQ(0, num_success_);
}

TEST_F(SyntheticGestureControllerTest, SuccessiveGestures) {
  CreateControllerAndTarget<MockSyntheticGestureTarget>();

  bool finished_1 = false;
  std::unique_ptr<MockSyntheticGesture> gesture_1(
      new MockSyntheticGesture(&finished_1, 2));
  bool finished_2 = false;
  std::unique_ptr<MockSyntheticGesture> gesture_2(
      new MockSyntheticGesture(&finished_2, 4));

  // Queue first gesture and wait for it to finish
  QueueSyntheticGesture(std::move(gesture_1));
  FlushInputUntilComplete();

  EXPECT_TRUE(finished_1);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);

  // Queue second gesture.
  QueueSyntheticGesture(std::move(gesture_2));
  FlushInputUntilComplete();

  EXPECT_TRUE(finished_2);
  EXPECT_EQ(2, num_success_);
  EXPECT_EQ(0, num_failure_);
}

TEST_F(SyntheticGestureControllerTest, TwoGesturesInFlight) {
  CreateControllerAndTarget<MockSyntheticGestureTarget>();

  bool finished_1 = false;
  std::unique_ptr<MockSyntheticGesture> gesture_1(
      new MockSyntheticGesture(&finished_1, 2));
  bool finished_2 = false;
  std::unique_ptr<MockSyntheticGesture> gesture_2(
      new MockSyntheticGesture(&finished_2, 4));

  QueueSyntheticGesture(std::move(gesture_1));
  QueueSyntheticGesture(std::move(gesture_2));
  FlushInputUntilComplete();

  EXPECT_TRUE(finished_1);
  EXPECT_TRUE(finished_2);

  EXPECT_EQ(2, num_success_);
  EXPECT_EQ(0, num_failure_);
}

TEST_F(SyntheticGestureControllerTest, GestureCompletedOnDidFlushInput) {
  CreateControllerAndTarget<MockSyntheticGestureTarget>();

  bool finished_1, finished_2;
  std::unique_ptr<MockSyntheticGesture> gesture_1(
      new MockSyntheticGesture(&finished_1, 2));
  std::unique_ptr<MockSyntheticGesture> gesture_2(
      new MockSyntheticGesture(&finished_2, 4));

  QueueSyntheticGesture(std::move(gesture_1));
  QueueSyntheticGesture(std::move(gesture_2));

  FlushInputUntilComplete();
  EXPECT_EQ(2, num_success_);
}

gfx::Vector2d AddTouchSlopToVector(const gfx::Vector2dF& vector,
                                   SyntheticGestureTarget* target) {
  const int kTouchSlop = target->GetTouchSlopInDips();

  int x = vector.x();
  if (x > 0)
    x += kTouchSlop;
  else if (x < 0)
    x -= kTouchSlop;

  int y = vector.y();
  if (y > 0)
    y += kTouchSlop;
  else if (y < 0)
    y -= kTouchSlop;

  return gfx::Vector2d(x, y);
}

TEST_P(SyntheticGestureControllerTestWithParam,
       SingleMoveGestureTouchVertical) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  if (GetParam() == TOUCH_DRAG) {
    params.add_slop = false;
  }
  params.start_point.SetPoint(89, 32);
  params.distances.push_back(gfx::Vector2d(0, 123));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  if (GetParam() == TOUCH_SCROLL) {
    EXPECT_EQ(AddTouchSlopToVector(params.distances[0], target_),
              scroll_target->start_to_end_distance());
  } else {
    EXPECT_EQ(params.distances[0], scroll_target->start_to_end_distance());
  }
}

TEST_P(SyntheticGestureControllerTestWithParam,
       SingleScrollGestureTouchHorizontal) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  if (GetParam() == TOUCH_DRAG) {
    params.add_slop = false;
  }
  params.start_point.SetPoint(12, -23);
  params.distances.push_back(gfx::Vector2d(-234, 0));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  if (GetParam() == TOUCH_SCROLL) {
    EXPECT_EQ(AddTouchSlopToVector(params.distances[0], target_),
              scroll_target->start_to_end_distance());
  } else {
    EXPECT_EQ(params.distances[0], scroll_target->start_to_end_distance());
  }
}

void CheckIsWithinRangeSingle(float scroll_distance,
                              int target_distance,
                              SyntheticGestureTarget* target) {
  if (target_distance > 0) {
    EXPECT_LE(target_distance, scroll_distance);
    EXPECT_LE(scroll_distance, target_distance + target->GetTouchSlopInDips());
  } else {
    EXPECT_GE(target_distance, scroll_distance);
    EXPECT_GE(scroll_distance, target_distance - target->GetTouchSlopInDips());
  }
}

void CheckSingleScrollDistanceIsWithinRange(
    const gfx::Vector2dF& scroll_distance,
    const gfx::Vector2dF& target_distance,
    SyntheticGestureTarget* target) {
  CheckIsWithinRangeSingle(scroll_distance.x(), target_distance.x(), target);
  CheckIsWithinRangeSingle(scroll_distance.y(), target_distance.y(), target);
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureTouchDiagonal) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  params.start_point.SetPoint(0, 7);
  params.distances.push_back(gfx::Vector2d(413, -83));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  CheckSingleScrollDistanceIsWithinRange(
      scroll_target->start_to_end_distance(), params.distances[0], target_);
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureTouchLongStop) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  // Create a smooth scroll with a short distance and set the pointer assumed
  // stopped time high, so that the stopping should dominate the time the
  // gesture is active.
  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  params.start_point.SetPoint(-98, -23);
  params.distances.push_back(gfx::Vector2d(21, -12));
  params.prevent_fling = true;
  target_->set_pointer_assumed_stopped_time_ms(543);

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  CheckSingleScrollDistanceIsWithinRange(
      scroll_target->start_to_end_distance(), params.distances[0], target_);
  EXPECT_GE(GetTotalTime(), target_->PointerAssumedStoppedTime());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureTouchFling) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  // Create a smooth scroll with a short distance and set the pointer assumed
  // stopped time high. Disable 'prevent_fling' and check that the gesture
  // finishes without waiting before it stops.
  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  params.start_point.SetPoint(-89, 78);
  params.distances.push_back(gfx::Vector2d(-43, 19));
  params.prevent_fling = false;

  target_->set_pointer_assumed_stopped_time_ms(543);

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  CheckSingleScrollDistanceIsWithinRange(
      scroll_target->start_to_end_distance(), params.distances[0], target_);
  EXPECT_LE(GetTotalTime(), target_->PointerAssumedStoppedTime());
}

TEST_P(SyntheticGestureControllerTestWithParam,
       SingleScrollGestureTouchZeroDistance) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  if (GetParam() == TOUCH_DRAG) {
    params.add_slop = false;
  }
  params.start_point.SetPoint(-32, 43);
  params.distances.push_back(gfx::Vector2d(0, 0));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(gfx::Vector2dF(0, 0), scroll_target->start_to_end_distance());
}

TEST_P(SyntheticGestureControllerTestWithParam,
       SingleScrollGestureTouchFromDebugger) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.from_devtools_debugger = true;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  if (GetParam() == TOUCH_DRAG) {
    params.add_slop = false;
  }
  params.start_point.SetPoint(89, 32);
  params.distances.push_back(gfx::Vector2d(0, 123));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureMouseVertical) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(432, 89);
  params.distances.push_back(gfx::Vector2d(0, -234));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.distances[0], scroll_target->start_to_end_distance());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureMouseHorizontal) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(90, 12);
  params.distances.push_back(gfx::Vector2d(345, 0));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.distances[0], scroll_target->start_to_end_distance());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureMouseDiagonal) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(90, 12);
  params.distances.push_back(gfx::Vector2d(-194, 303));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.distances[0], scroll_target->start_to_end_distance());
}

TEST_F(SyntheticGestureControllerTest, MultiScrollGestureMouse) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(90, 12);
  params.distances.push_back(gfx::Vector2d(-129, 212));
  params.distances.push_back(gfx::Vector2d(8, -9));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.distances[0] + params.distances[1],
            scroll_target->start_to_end_distance());
}

TEST_F(SyntheticGestureControllerTest, MultiScrollGestureMouseHorizontal) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(90, 12);
  params.distances.push_back(gfx::Vector2d(-129, 0));
  params.distances.push_back(gfx::Vector2d(79, 0));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  // This check only works for horizontal or vertical scrolls because of
  // floating point precision issues with diagonal scrolls.
  EXPECT_FLOAT_EQ(params.distances[0].Length() + params.distances[1].Length(),
                  scroll_target->total_abs_move_distance_length());
  EXPECT_FLOAT_EQ((params.distances[0] + params.distances[1]).x(),
                  scroll_target->start_to_end_distance().x());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureTouchpadSwipe) {
  CreateControllerAndTarget<MockFlingGestureTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(39, 86);
  params.distances.push_back(gfx::Vector2d(0, -132));
  params.fling_velocity_x = 800;
  params.fling_velocity_y = -1000;
  params.prevent_fling = false;

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockFlingGestureTarget* swipe_target =
      static_cast<MockFlingGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.fling_velocity_x, swipe_target->fling_velocity_x());
  EXPECT_EQ(params.fling_velocity_y, swipe_target->fling_velocity_y());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureMousePreciseScroll) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(39, 86);
  params.distances.push_back(gfx::Vector2d(0, -132));
  params.granularity = ui::ScrollGranularity::kScrollByPrecisePixel;

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.granularity, scroll_target->granularity());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureMouseScrollByPage) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(39, 86);
  params.distances.push_back(gfx::Vector2d(0, -132));
  params.granularity = ui::ScrollGranularity::kScrollByPage;

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(params.granularity, scroll_target->granularity());
}

TEST_F(SyntheticGestureControllerTest, SingleScrollGestureMouseFromDebugger) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.from_devtools_debugger = true;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_WHEEL_INPUT;
  params.start_point.SetPoint(432, 89);
  params.distances.push_back(gfx::Vector2d(0, -234));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

void CheckIsWithinRangeMulti(float scroll_distance,
                             int target_distance,
                             SyntheticGestureTarget* target) {
  if (target_distance > 0) {
    EXPECT_GE(scroll_distance, target_distance - target->GetTouchSlopInDips());
    EXPECT_LE(scroll_distance, target_distance + target->GetTouchSlopInDips());
  } else {
    EXPECT_LE(scroll_distance, target_distance + target->GetTouchSlopInDips());
    EXPECT_GE(scroll_distance, target_distance - target->GetTouchSlopInDips());
  }
}

void CheckMultiScrollDistanceIsWithinRange(
    const gfx::Vector2dF& scroll_distance,
    const gfx::Vector2dF& target_distance,
    SyntheticGestureTarget* target) {
  CheckIsWithinRangeMulti(scroll_distance.x(), target_distance.x(), target);
  CheckIsWithinRangeMulti(scroll_distance.y(), target_distance.y(), target);
}

TEST_F(SyntheticGestureControllerTest, MultiScrollGestureTouch) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  params.start_point.SetPoint(8, -13);
  params.distances.push_back(gfx::Vector2d(234, 133));
  params.distances.push_back(gfx::Vector2d(-9, 78));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  CheckMultiScrollDistanceIsWithinRange(
      scroll_target->start_to_end_distance(),
      params.distances[0] + params.distances[1],
      target_);
}

TEST_P(SyntheticGestureControllerTestWithParam,
       MultiScrollGestureTouchVertical) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::TOUCH_INPUT;
  if (GetParam() == TOUCH_DRAG) {
    params.add_slop = false;
  }
  params.start_point.SetPoint(234, -13);
  params.distances.push_back(gfx::Vector2d(0, 133));
  params.distances.push_back(gfx::Vector2d(0, 78));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* scroll_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  if (GetParam() == TOUCH_SCROLL) {
    EXPECT_FLOAT_EQ(params.distances[0].Length() +
                        params.distances[1].Length() +
                        target_->GetTouchSlopInDips(),
                    scroll_target->total_abs_move_distance_length());
  EXPECT_EQ(AddTouchSlopToVector(params.distances[0] + params.distances[1],
                                 target_),
            scroll_target->start_to_end_distance());
  } else {
    EXPECT_FLOAT_EQ(params.distances[0].Length() + params.distances[1].Length(),
                    scroll_target->total_abs_move_distance_length());
    EXPECT_EQ(params.distances[0] + params.distances[1],
              scroll_target->start_to_end_distance());
  }
}

INSTANTIATE_TEST_SUITE_P(Single,
                         SyntheticGestureControllerTestWithParam,
                         testing::Values(TOUCH_SCROLL, TOUCH_DRAG));

TEST_F(SyntheticGestureControllerTest, SingleDragGestureMouseDiagonal) {
  CreateControllerAndTarget<MockDragMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT;
  params.start_point.SetPoint(0, 7);
  params.distances.push_back(gfx::Vector2d(413, -83));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* drag_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(drag_target->start_to_end_distance(), params.distances[0]);
}

TEST_F(SyntheticGestureControllerTest, SingleDragGestureMouseZeroDistance) {
  CreateControllerAndTarget<MockDragMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT;
  params.start_point.SetPoint(-32, 43);
  params.distances.push_back(gfx::Vector2d(0, 0));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* drag_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(gfx::Vector2dF(0, 0), drag_target->start_to_end_distance());
}

TEST_F(SyntheticGestureControllerTest, MultiDragGestureMouse) {
  CreateControllerAndTarget<MockDragMouseTarget>();

  SyntheticSmoothMoveGestureParams params;
  params.input_type = SyntheticSmoothMoveGestureParams::MOUSE_DRAG_INPUT;
  params.start_point.SetPoint(8, -13);
  params.distances.push_back(gfx::Vector2d(234, 133));
  params.distances.push_back(gfx::Vector2d(-9, 78));

  std::unique_ptr<SyntheticSmoothMoveGesture> gesture(
      new SyntheticSmoothMoveGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockMoveGestureTarget* drag_target =
      static_cast<MockMoveGestureTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(drag_target->start_to_end_distance(),
            params.distances[0] + params.distances[1]);
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothDragTestUsingSingleMouseDrag) {
  CreateControllerAndTarget<MockDragMouseTarget>();

  SyntheticSmoothDragGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.distances.push_back(gfx::Vector2d(234, 133));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothDragGesture> gesture(
      new SyntheticSmoothDragGesture(params));
  gesture->DidQueue(controller_->GetWeakPtr());
  const base::TimeTicks timestamp;
  gesture->ForwardInputEvents(timestamp, target_);
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothDragTestUsingSingleMouseDragFromDebugger) {
  CreateControllerAndTarget<MockDragMouseTarget>();

  SyntheticSmoothDragGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.distances.push_back(gfx::Vector2d(234, 133));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothDragGesture> gesture(
      new SyntheticSmoothDragGesture(params));
  gesture->DidQueue(controller_->GetWeakPtr());
  const base::TimeTicks timestamp;
  gesture->ForwardInputEvents(timestamp, target_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothDragTestUsingSingleTouchDrag) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothDragGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.start_point.SetPoint(89, 32);
  params.distances.push_back(gfx::Vector2d(0, 123));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothDragGesture> gesture(
      new SyntheticSmoothDragGesture(params));
  gesture->DidQueue(controller_->GetWeakPtr());
  const base::TimeTicks timestamp;
  gesture->ForwardInputEvents(timestamp, target_);
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothDragTestUsingSingleTouchDragFromDebugger) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothDragGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.start_point.SetPoint(89, 32);
  params.distances.push_back(gfx::Vector2d(0, 123));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothDragGesture> gesture(
      new SyntheticSmoothDragGesture(params));
  gesture->DidQueue(controller_->GetWeakPtr());
  const base::TimeTicks timestamp;
  gesture->ForwardInputEvents(timestamp, target_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothScrollTestUsingSingleTouchScroll) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothScrollGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;

  std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
      new SyntheticSmoothScrollGesture(params));
  gesture->DidQueue(controller_->GetWeakPtr());
  const base::TimeTicks timestamp;
  gesture->ForwardInputEvents(timestamp, target_);
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothScrollTestUsingSingleTouchScrollFromDebugger) {
  CreateControllerAndTarget<MockMoveTouchTarget>();

  SyntheticSmoothScrollGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.anchor.SetPoint(432, 89);
  params.distances.push_back(gfx::Vector2d(0, -234));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
      new SyntheticSmoothScrollGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothScrollTestUsingSingleMouseScroll) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothScrollGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.anchor.SetPoint(432, 89);
  params.distances.push_back(gfx::Vector2d(0, -234));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
      new SyntheticSmoothScrollGesture(params));
  gesture->DidQueue(controller_->GetWeakPtr());
  const base::TimeTicks timestamp;
  gesture->ForwardInputEvents(timestamp, target_);
}

TEST_F(SyntheticGestureControllerTest,
       SyntheticSmoothScrollTestUsingSingleMouseScrollFromDebugger) {
  CreateControllerAndTarget<MockScrollMouseTarget>();

  SyntheticSmoothScrollGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.anchor.SetPoint(432, 89);
  params.distances.push_back(gfx::Vector2d(0, -234));
  params.speed_in_pixels_s = 800;

  std::unique_ptr<SyntheticSmoothScrollGesture> gesture(
      new SyntheticSmoothScrollGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest,
       TouchscreenTouchpadPinchGestureTouchZoomIn) {
  CreateControllerAndTarget<MockSyntheticTouchscreenPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticTouchscreenPinchGesture> gesture(
      new SyntheticTouchscreenPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTouchscreenPinchTouchTarget* pinch_target =
      static_cast<MockSyntheticTouchscreenPinchTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pinch_target->zoom_direction(),
            MockSyntheticTouchscreenPinchTouchTarget::ZOOM_IN);
  EXPECT_FLOAT_EQ(params.scale_factor, pinch_target->ComputeScaleFactor());
}

TEST_F(SyntheticGestureControllerTest,
       TouchscreenTouchpadPinchGestureTouchZoomOut) {
  CreateControllerAndTarget<MockSyntheticTouchscreenPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.scale_factor = 0.4f;
  params.anchor.SetPoint(-12, 93);

  std::unique_ptr<SyntheticTouchscreenPinchGesture> gesture(
      new SyntheticTouchscreenPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTouchscreenPinchTouchTarget* pinch_target =
      static_cast<MockSyntheticTouchscreenPinchTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pinch_target->zoom_direction(),
            MockSyntheticTouchscreenPinchTouchTarget::ZOOM_OUT);
  EXPECT_FLOAT_EQ(params.scale_factor, pinch_target->ComputeScaleFactor());
}

TEST_F(SyntheticGestureControllerTest,
       TouchscreenTouchpadPinchGestureTouchNoScaling) {
  CreateControllerAndTarget<MockSyntheticTouchscreenPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.scale_factor = 1.0f;

  std::unique_ptr<SyntheticTouchscreenPinchGesture> gesture(
      new SyntheticTouchscreenPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTouchscreenPinchTouchTarget* pinch_target =
      static_cast<MockSyntheticTouchscreenPinchTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pinch_target->zoom_direction(),
            MockSyntheticTouchscreenPinchTouchTarget::ZOOM_DIRECTION_UNKNOWN);
  EXPECT_EQ(params.scale_factor, pinch_target->ComputeScaleFactor());
}

TEST_F(SyntheticGestureControllerTest,
       TouchscreenTouchpadPinchGestureTouchFromDebugger) {
  CreateControllerAndTarget<MockSyntheticTouchscreenPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticTouchscreenPinchGesture> gesture(
      new SyntheticTouchscreenPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest, TouchpadPinchGestureTouchZoomIn) {
  CreateControllerAndTarget<MockSyntheticTouchpadPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticTouchpadPinchGesture> gesture(
      new SyntheticTouchpadPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTouchpadPinchTouchTarget* pinch_target =
      static_cast<MockSyntheticTouchpadPinchTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pinch_target->zoom_direction(),
            MockSyntheticTouchpadPinchTouchTarget::ZOOM_IN);
  EXPECT_FLOAT_EQ(params.scale_factor, pinch_target->scale_factor());
}

TEST_F(SyntheticGestureControllerTest, TouchpadPinchGestureTouchZoomOut) {
  CreateControllerAndTarget<MockSyntheticTouchpadPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.scale_factor = 0.4f;
  params.anchor.SetPoint(-12, 93);

  std::unique_ptr<SyntheticTouchpadPinchGesture> gesture(
      new SyntheticTouchpadPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTouchpadPinchTouchTarget* pinch_target =
      static_cast<MockSyntheticTouchpadPinchTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pinch_target->zoom_direction(),
            MockSyntheticTouchpadPinchTouchTarget::ZOOM_OUT);
  EXPECT_FLOAT_EQ(params.scale_factor, pinch_target->scale_factor());
}

TEST_F(SyntheticGestureControllerTest, TouchpadPinchGestureTouchNoScaling) {
  CreateControllerAndTarget<MockSyntheticTouchpadPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.scale_factor = 1.0f;

  std::unique_ptr<SyntheticTouchpadPinchGesture> gesture(
      new SyntheticTouchpadPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTouchpadPinchTouchTarget* pinch_target =
      static_cast<MockSyntheticTouchpadPinchTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pinch_target->zoom_direction(),
            MockSyntheticTouchpadPinchTouchTarget::ZOOM_DIRECTION_UNKNOWN);
  EXPECT_EQ(params.scale_factor, pinch_target->scale_factor());
}

TEST_F(SyntheticGestureControllerTest, TouchpadPinchGestureTouchFromDebugger) {
  CreateControllerAndTarget<MockSyntheticTouchpadPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticTouchpadPinchGesture> gesture(
      new SyntheticTouchpadPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

// Ensure that if SyntheticPinchGesture is instantiated with TOUCH_INPUT it
// correctly creates a touchscreen gesture.
TEST_F(SyntheticGestureControllerTest, PinchGestureExplicitTouch) {
  CreateControllerAndTarget<MockSyntheticTouchscreenPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticPinchGesture> gesture(
      new SyntheticPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  // Gesture target will fail expectations if the wrong underlying
  // SyntheticPinch*Gesture was instantiated.
}

// Ensure that if SyntheticPinchGesture is instantiated with MOUSE_INPUT it
// correctly creates a touchpad gesture.
TEST_F(SyntheticGestureControllerTest, PinchGestureExplicitMouse) {
  CreateControllerAndTarget<MockSyntheticTouchpadPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticPinchGesture> gesture(
      new SyntheticPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  // Gesture target will fail expectations if the wrong underlying
  // SyntheticPinch*Gesture was instantiated.
}

// Ensure that if SyntheticPinchGesture is instantiated with DEFAULT_INPUT it
// correctly creates a touchscreen gesture for a touchscreen controller.
TEST_F(SyntheticGestureControllerTest, PinchGestureDefaultTouch) {
  CreateControllerAndTarget<MockSyntheticTouchscreenPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kDefaultInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticPinchGesture> gesture(
      new SyntheticPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  // Gesture target will fail expectations if the wrong underlying
  // SyntheticPinch*Gesture was instantiated.
}

// Ensure that if SyntheticPinchGesture is instantiated with DEFAULT_INPUT it
// correctly creates a touchpad gesture for a touchpad controller.
TEST_F(SyntheticGestureControllerTest, PinchGestureDefaultMouse) {
  CreateControllerAndTarget<MockSyntheticTouchpadPinchTouchTarget>();

  SyntheticPinchGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kDefaultInput;
  params.scale_factor = 2.3f;
  params.anchor.SetPoint(54, 89);

  std::unique_ptr<SyntheticPinchGesture> gesture(
      new SyntheticPinchGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  // Gesture target will fail expectations if the wrong underlying
  // SyntheticPinch*Gesture was instantiated.
}

TEST_F(SyntheticGestureControllerTest, TapGestureTouch) {
  CreateControllerAndTarget<MockSyntheticTapTouchTarget>();

  SyntheticTapGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.duration_ms = 123;
  params.position.SetPoint(87, -124);

  std::unique_ptr<SyntheticTapGesture> gesture(new SyntheticTapGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTapTouchTarget* tap_target =
      static_cast<MockSyntheticTapTouchTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(tap_target->GestureFinished());
  EXPECT_EQ(tap_target->position(), params.position);
  EXPECT_EQ(tap_target->GetDuration().InMilliseconds(), params.duration_ms);
  EXPECT_GE(GetTotalTime(), base::Milliseconds(params.duration_ms));
}

TEST_F(SyntheticGestureControllerTest, TapGestureTouchFromDebugger) {
  CreateControllerAndTarget<MockSyntheticTapTouchTarget>();

  SyntheticTapGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.duration_ms = 123;
  params.position.SetPoint(87, -124);

  std::unique_ptr<SyntheticTapGesture> gesture(new SyntheticTapGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest, TapGestureMouse) {
  CreateControllerAndTarget<MockSyntheticTapMouseTarget>();

  SyntheticTapGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.duration_ms = 79;
  params.position.SetPoint(98, 123);

  std::unique_ptr<SyntheticTapGesture> gesture(new SyntheticTapGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticTapMouseTarget* tap_target =
      static_cast<MockSyntheticTapMouseTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(tap_target->GestureFinished());
  EXPECT_EQ(tap_target->position(), params.position);
  EXPECT_EQ(tap_target->GetDuration().InMilliseconds(), params.duration_ms);
  EXPECT_GE(GetTotalTime(), base::Milliseconds(params.duration_ms));
}

TEST_F(SyntheticGestureControllerTest, TapGestureMouseFromDebugger) {
  CreateControllerAndTarget<MockSyntheticTapMouseTarget>();

  SyntheticTapGestureParams params;
  params.from_devtools_debugger = true;
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  params.duration_ms = 79;
  params.position.SetPoint(98, 123);

  std::unique_ptr<SyntheticTapGesture> gesture(new SyntheticTapGesture(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_TRUE(target_->all_from_debugger());
}

TEST_F(SyntheticGestureControllerTest, PointerTouchAction) {
  CreateControllerAndTarget<MockSyntheticPointerTouchActionTarget>();

  // First, send two touch presses for finger 0 and finger 1.
  SyntheticPointerActionListParams::ParamList param_list;
  SyntheticPointerActionParams param0 = SyntheticPointerActionParams(
      SyntheticPointerActionParams::PointerActionType::PRESS);
  SyntheticPointerActionParams param1 = SyntheticPointerActionParams(
      SyntheticPointerActionParams::PointerActionType::PRESS);
  param0.set_position(gfx::PointF(54, 89));
  param0.set_pointer_id(0);
  param1.set_position(gfx::PointF(79, 132));
  param1.set_pointer_id(1);
  param_list.push_back(param0);
  param_list.push_back(param1);
  SyntheticPointerActionListParams params(param_list);
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  std::unique_ptr<SyntheticPointerAction> gesture(
      new SyntheticPointerAction(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticPointerTouchActionTarget* pointer_touch_target =
      static_cast<MockSyntheticPointerTouchActionTarget*>(target_);
  int index_array[2] = {0, 1};
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_touch_target->num_dispatched_pointer_actions(), 2);
  EXPECT_TRUE(pointer_touch_target->SyntheticTouchActionListDispatchedCorrectly(
      param_list, 0, index_array));

  // Second, send a touch release for finger 0, a touch move for finger 1.
  param0.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::RELEASE);
  param1.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::MOVE);
  param1.set_position(gfx::PointF(183, 239));
  param_list.clear();
  param_list.push_back(param0);
  param_list.push_back(param1);
  params.PushPointerActionParamsList(param_list);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_touch_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  index_array[1] = 0;
  EXPECT_EQ(2, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_touch_target->num_dispatched_pointer_actions(), 4);
  EXPECT_TRUE(pointer_touch_target->SyntheticTouchActionListDispatchedCorrectly(
      param_list, 2, index_array));

  // Third, send a touch release for finger 1.
  param1.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::RELEASE);
  param_list.clear();
  param_list.push_back(param1);
  params.PushPointerActionParamsList(param_list);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_touch_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(3, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_touch_target->num_dispatched_pointer_actions(), 5);
  EXPECT_TRUE(pointer_touch_target->SyntheticTouchActionListDispatchedCorrectly(
      param_list, 4, index_array));
}

TEST_F(SyntheticGestureControllerTest, PointerMouseAction) {
  CreateControllerAndTarget<MockSyntheticPointerMouseActionTarget>();

  // First, send a mouse move.
  SyntheticPointerActionListParams::ParamList param_list;
  SyntheticPointerActionParams param = SyntheticPointerActionParams(
      SyntheticPointerActionParams::PointerActionType::MOVE);

  param.set_position(gfx::PointF(54, 89));
  SyntheticPointerActionListParams params;
  params.PushPointerActionParams(param);
  params.gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  std::unique_ptr<SyntheticPointerAction> gesture(
      new SyntheticPointerAction(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticPointerMouseActionTarget* pointer_mouse_target =
      static_cast<MockSyntheticPointerMouseActionTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_mouse_target->num_dispatched_pointer_actions(), 1);
  EXPECT_TRUE(
      pointer_mouse_target->SyntheticMouseActionDispatchedCorrectly(param, 0));

  // Second, send a mouse press.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::PRESS);
  param.set_position(gfx::PointF(183, 239));
  param.set_button(SyntheticPointerActionParams::Button::LEFT);
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_mouse_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(2, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_mouse_target->num_dispatched_pointer_actions(), 2);
  EXPECT_TRUE(pointer_mouse_target->SyntheticMouseActionDispatchedCorrectly(
      param, 1, SyntheticPointerActionParams::Button::LEFT));

  // Third, send a mouse move.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::MOVE);
  param.set_position(gfx::PointF(254, 279));
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_mouse_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(3, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_mouse_target->num_dispatched_pointer_actions(), 3);
  EXPECT_TRUE(pointer_mouse_target->SyntheticMouseActionDispatchedCorrectly(
      param, 0, SyntheticPointerActionParams::Button::LEFT));

  // Fourth, send a mouse release.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::RELEASE);
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_mouse_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(4, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_mouse_target->num_dispatched_pointer_actions(), 4);
  EXPECT_TRUE(pointer_mouse_target->SyntheticMouseActionDispatchedCorrectly(
      param, 1, SyntheticPointerActionParams::Button::LEFT));
}

TEST_F(SyntheticGestureControllerTest, PointerPenAction) {
  CreateControllerAndTarget<MockSyntheticPointerMouseActionTarget>();

  // First, send a pen move.
  SyntheticPointerActionListParams::ParamList param_list;
  SyntheticPointerActionParams param = SyntheticPointerActionParams(
      SyntheticPointerActionParams::PointerActionType::MOVE);

  param.set_position(gfx::PointF(54, 89));
  SyntheticPointerActionListParams params;
  params.PushPointerActionParams(param);
  params.gesture_source_type = content::mojom::GestureSourceType::kPenInput;
  std::unique_ptr<SyntheticPointerAction> gesture(
      new SyntheticPointerAction(params));
  QueueSyntheticGesture(std::move(gesture));
  FlushInputUntilComplete();

  MockSyntheticPointerMouseActionTarget* pointer_pen_target =
      static_cast<MockSyntheticPointerMouseActionTarget*>(target_);
  EXPECT_EQ(1, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_pen_target->num_dispatched_pointer_actions(), 1);
  EXPECT_TRUE(
      pointer_pen_target->SyntheticMouseActionDispatchedCorrectly(param, 0));

  // Second, send a pen press.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::PRESS);
  param.set_button(SyntheticPointerActionParams::Button::LEFT);
  param.set_position(gfx::PointF(183, 239));
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_pen_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(2, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_pen_target->num_dispatched_pointer_actions(), 2);
  EXPECT_TRUE(pointer_pen_target->SyntheticMouseActionDispatchedCorrectly(
      param, 1, SyntheticPointerActionParams::Button::LEFT));

  // Third, send a pen move.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::MOVE);
  param.set_position(gfx::PointF(254, 279));
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_pen_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(3, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_pen_target->num_dispatched_pointer_actions(), 3);
  EXPECT_TRUE(pointer_pen_target->SyntheticMouseActionDispatchedCorrectly(
      param, 0, SyntheticPointerActionParams::Button::LEFT));

  // Fourth, send a pen release.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::RELEASE);
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_pen_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(4, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_pen_target->num_dispatched_pointer_actions(), 4);
  EXPECT_TRUE(pointer_pen_target->SyntheticMouseActionDispatchedCorrectly(
      param, 1, SyntheticPointerActionParams::Button::LEFT));

  // Fifth, send a pen leave.
  param.set_pointer_action_type(
      SyntheticPointerActionParams::PointerActionType::LEAVE);
  params.PushPointerActionParams(param);
  gesture = std::make_unique<SyntheticPointerAction>(params);
  QueueSyntheticGesture(std::move(gesture));
  pointer_pen_target->reset_num_dispatched_pointer_actions();
  FlushInputUntilComplete();

  EXPECT_EQ(5, num_success_);
  EXPECT_EQ(0, num_failure_);
  EXPECT_EQ(pointer_pen_target->num_dispatched_pointer_actions(), 5);
  EXPECT_TRUE(
      pointer_pen_target->SyntheticMouseActionDispatchedCorrectly(param, 0));
}

class MockSyntheticGestureTargetManualAck : public MockSyntheticGestureTarget {
 public:
  void WaitForTargetAck(SyntheticGestureParams::GestureType type,
                        content::mojom::GestureSourceType source,
                        base::OnceClosure callback) const override {
    if (manually_ack_)
      target_ack_ = std::move(callback);
    else
      std::move(callback).Run();
  }
  bool HasOutstandingAck() const { return !target_ack_.is_null(); }
  void InvokeAck() {
    DCHECK(HasOutstandingAck());
    std::move(target_ack_).Run();
  }
  void SetManuallyAck(bool manually_ack) { manually_ack_ = manually_ack; }

 private:
  mutable base::OnceClosure target_ack_;
  bool manually_ack_ = true;
};

// Ensure the first time a gesture is queued, we wait for a renderer ACK before
// starting the gesture. Following gestures should start immediately. This test
// the renderer_known_to_be_initialized_ bit in the controller.
TEST_F(SyntheticGestureControllerTest, WaitForRendererInitialization) {
  CreateControllerAndTarget<MockSyntheticGestureTargetManualAck>();

  auto* target = static_cast<MockSyntheticGestureTargetManualAck*>(target_);

  EXPECT_FALSE(target->HasOutstandingAck());

  SyntheticTapGestureParams params;
  params.gesture_source_type = content::mojom::GestureSourceType::kTouchInput;
  params.duration_ms = 123;
  params.position.SetPoint(87, -124);

  // Queue the first gesture.
  {
    auto gesture = std::make_unique<SyntheticTapGesture>(params);
    QueueSyntheticGesture(std::move(gesture));

    // We should have received a WaitForTargetAck and the dispatch timer won't
    // start until that's ACK'd.
    EXPECT_TRUE(target->HasOutstandingAck());
    EXPECT_FALSE(DispatchTimerRunning());

    target->InvokeAck();

    // The timer should now be running.
    EXPECT_FALSE(target->HasOutstandingAck());
    EXPECT_TRUE(DispatchTimerRunning());
  }

  // Finish the gesture.
  {
    target->SetManuallyAck(false);
    FlushInputUntilComplete();
    target->SetManuallyAck(true);
    EXPECT_FALSE(DispatchTimerRunning());
  }

  // Queue the second gesture.
  {
    auto gesture = std::make_unique<SyntheticTapGesture>(params);
    QueueSyntheticGesture(std::move(gesture));

    // This time, because we've already sent a gesuture to the renderer,
    // there's no need to wait for an ACK before starting the dispatch timer.
    EXPECT_FALSE(target->HasOutstandingAck());
    EXPECT_TRUE(DispatchTimerRunning());
  }
}

}  // namespace content