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

#include <linux/input.h>
#include <wayland-server.h>

#include <cmath>
#include <memory>

#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/cursor/mojom/cursor_type.mojom-shared.h"
#include "ui/base/cursor/platform_cursor.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/event.h"
#include "ui/ozone/common/bitmap_cursor_factory.h"
#include "ui/ozone/common/features.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor.h"
#include "ui/ozone/platform/wayland/host/wayland_cursor_factory.h"
#include "ui/ozone/platform/wayland/host/wayland_seat.h"
#include "ui/ozone/platform/wayland/host/wayland_window.h"
#include "ui/ozone/platform/wayland/test/mock_pointer.h"
#include "ui/ozone/platform/wayland/test/mock_surface.h"
#include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h"
#include "ui/ozone/platform/wayland/test/wayland_test.h"
#include "ui/ozone/test/mock_platform_window_delegate.h"
#include "ui/platform_window/platform_window_init_properties.h"

using ::testing::_;
using ::testing::AllOf;
using ::testing::Ge;
using ::testing::Le;
using ::testing::Mock;
using ::testing::Ne;
using ::testing::SaveArg;
using ::testing::Values;

namespace ui {

class WaylandPointerTest : public WaylandTestSimple {
 public:
  void SetUp() override {
    WaylandTestSimple::SetUp();

    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      wl_seat_send_capabilities(server->seat()->resource(),
                                WL_SEAT_CAPABILITY_POINTER);
    });
    ASSERT_TRUE(connection_->seat()->pointer());

    EXPECT_EQ(1u, DeviceDataManager::GetInstance()->GetMouseDevices().size());
    // Wayland doesn't expose touchpad devices separately. They are all
    // WaylandPointers.
    EXPECT_EQ(0u,
              DeviceDataManager::GetInstance()->GetTouchpadDevices().size());
  }

 protected:
  // The auxiliary convenience methods for entering and leaving the pointer into
  // and from the surface.
  void SendEnter(int x, int y) {
    PostToServerAndWait(
        [x, y, surface_id = window_->root_surface()->get_surface_id()](
            wl::TestWaylandServerThread* server) {
          auto* const pointer = server->seat()->pointer()->resource();
          auto* const surface =
              server->GetObject<wl::MockSurface>(surface_id)->resource();

          wl_pointer_send_enter(pointer, server->GetNextSerial(), surface,
                                wl_fixed_from_int(x), wl_fixed_from_int(y));
          wl_pointer_send_frame(pointer);
        });
  }
  void SendEnter() { SendEnter(0, 0); }

  void SendLeave() {
    PostToServerAndWait(
        [surface_id = window_->root_surface()->get_surface_id()](
            wl::TestWaylandServerThread* server) {
          auto* const pointer = server->seat()->pointer()->resource();
          auto* const surface =
              server->GetObject<wl::MockSurface>(surface_id)->resource();

          wl_pointer_send_leave(pointer, server->GetNextSerial(), surface);
          wl_pointer_send_frame(pointer);
        });
  }

  void SendAxisStopEvents() {
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer()->resource();

      wl_pointer_send_axis_stop(pointer, server->GetNextTime(),
                                WL_POINTER_AXIS_VERTICAL_SCROLL);
      wl_pointer_send_axis_stop(pointer, server->GetNextTime(),
                                WL_POINTER_AXIS_HORIZONTAL_SCROLL);
      wl_pointer_send_frame(pointer);
    });
  }

  void SendRightButtonPress() {
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer()->resource();

      wl_pointer_send_button(pointer, server->GetNextSerial(),
                             server->GetNextTime(), BTN_RIGHT,
                             WL_POINTER_BUTTON_STATE_PRESSED);
      wl_pointer_send_frame(pointer);
    });
  }

  void CheckEventType(
      ui::EventType event_type,
      ui::Event* event,
      ui::EventPointerType pointer_type = ui::EventPointerType::kMouse,
      float force = std::numeric_limits<float>::quiet_NaN(),
      float tilt_x = 0.0,
      float tilt_y = 0.0) {
    ASSERT_TRUE(event);
    ASSERT_TRUE(event->IsMouseEvent());

    auto* mouse_event = event->AsMouseEvent();
    EXPECT_EQ(event_type, mouse_event->type());
  }
};

void SendAxisEvents(struct wl_resource* resource,
                    uint32_t time_ms,
                    uint32_t axis_source,
                    uint32_t axis,
                    int offset) {
  wl_pointer_send_axis_source(resource, axis_source);
  wl_pointer_send_axis(resource, time_ms, axis, wl_fixed_from_int(offset));
  wl_pointer_send_frame(resource);
}

void SendDiagonalAxisEvents(struct wl_resource* resource,
                            uint32_t time_ms,
                            uint32_t axis_source,
                            int offset_x,
                            int offset_y) {
  wl_pointer_send_axis_source(resource, axis_source);
  wl_pointer_send_axis(resource, time_ms, WL_POINTER_AXIS_VERTICAL_SCROLL,
                       wl_fixed_from_int(offset_y));
  wl_pointer_send_axis(resource, time_ms, WL_POINTER_AXIS_HORIZONTAL_SCROLL,
                       wl_fixed_from_int(offset_x));
  wl_pointer_send_frame(resource);
}

ACTION_P(CloneEvent, ptr) {
  *ptr = arg0->Clone();
}

TEST_F(WaylandPointerTest, Enter) {
  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  SendEnter();

  ASSERT_TRUE(event);
  ASSERT_TRUE(event->IsMouseEvent());
  auto* mouse_event = event->AsMouseEvent();
  EXPECT_EQ(EventType::kMouseEntered, mouse_event->type());
  EXPECT_EQ(0, mouse_event->button_flags());
  EXPECT_EQ(0, mouse_event->changed_button_flags());
  EXPECT_EQ(gfx::PointF(0, 0), mouse_event->location_f());
}

TEST_F(WaylandPointerTest, Leave) {
  MockPlatformWindowDelegate other_delegate;
  gfx::AcceleratedWidget other_widget = gfx::kNullAcceleratedWidget;
  EXPECT_CALL(other_delegate, OnAcceleratedWidgetAvailable(_))
      .WillOnce(SaveArg<0>(&other_widget));

  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(0, 0, 10, 10);
  properties.type = PlatformWindowType::kWindow;
  auto other_window = WaylandWindow::Create(&other_delegate, connection_.get(),
                                            std::move(properties));
  ASSERT_NE(other_widget, gfx::kNullAcceleratedWidget);

  EXPECT_CALL(delegate_, DispatchEvent(_)).Times(2);
  EXPECT_CALL(other_delegate, DispatchEvent(_)).Times(2);

  PostToServerAndWait(
      [surface_id = window_->root_surface()->get_surface_id(),
       other_surface_id = other_window->root_surface()->get_surface_id()](
          wl::TestWaylandServerThread* server) {
        auto* const pointer = server->seat()->pointer()->resource();
        auto* const surface =
            server->GetObject<wl::MockSurface>(surface_id)->resource();
        auto* const other_surface =
            server->GetObject<wl::MockSurface>(other_surface_id)->resource();

        wl_pointer_send_enter(pointer, 1, surface, 0, 0);
        wl_pointer_send_frame(pointer);

        wl_pointer_send_leave(pointer, 2, surface);
        wl_pointer_send_frame(pointer);

        wl_pointer_send_enter(pointer, 3, other_surface, 0, 0);
        wl_pointer_send_frame(pointer);

        wl_pointer_send_button(pointer, 4, 1004, BTN_LEFT,
                               WL_POINTER_BUTTON_STATE_PRESSED);
        wl_pointer_send_frame(pointer);
      });
}

ACTION_P3(CloneEventAndCheckCapture, window, result, ptr) {
  ASSERT_TRUE(window->HasCapture() == result);
  *ptr = arg0->Clone();
}

TEST_F(WaylandPointerTest, Motion) {
  SendEnter();

  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();

    wl_pointer_send_motion(pointer, 1002, wl_fixed_from_double(10.75),
                           wl_fixed_from_double(20.375));
    wl_pointer_send_frame(pointer);
  });

  ASSERT_TRUE(event);
  ASSERT_TRUE(event->IsMouseEvent());
  auto* mouse_event = event->AsMouseEvent();
  EXPECT_EQ(EventType::kMouseMoved, mouse_event->type());
  EXPECT_EQ(0, mouse_event->button_flags());
  EXPECT_EQ(0, mouse_event->changed_button_flags());
  EXPECT_EQ(gfx::PointF(10.75, 20.375), mouse_event->location_f());
  EXPECT_EQ(gfx::PointF(10.75, 20.375), mouse_event->root_location_f());
}

TEST_F(WaylandPointerTest, MotionDragged) {
  SendEnter();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();

    wl_pointer_send_button(pointer, server->GetNextSerial(),
                           server->GetNextTime(), BTN_MIDDLE,
                           WL_POINTER_BUTTON_STATE_PRESSED);
    wl_pointer_send_frame(pointer);
  });

  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();

    wl_pointer_send_motion(pointer, 1003, wl_fixed_from_int(400),
                           wl_fixed_from_int(500));
    wl_pointer_send_frame(pointer);
  });

  ASSERT_TRUE(event);
  ASSERT_TRUE(event->IsMouseEvent());
  auto* mouse_event = event->AsMouseEvent();
  EXPECT_EQ(EventType::kMouseDragged, mouse_event->type());
  EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, mouse_event->button_flags());
  EXPECT_EQ(0, mouse_event->changed_button_flags());
  EXPECT_EQ(gfx::PointF(400, 500), mouse_event->location_f());
  EXPECT_EQ(gfx::PointF(400, 500), mouse_event->root_location_f());
}

// Verifies whether the platform event source handles all types of axis sources.
// The actual behaviour of each axis source is not tested here.
TEST_F(WaylandPointerTest, AxisSourceTypes) {
  SendEnter();

  std::unique_ptr<Event> event1, event2, event3, event4;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(4)
      .WillOnce(CloneEvent(&event1))
      .WillOnce(CloneEvent(&event2))
      .WillOnce(CloneEvent(&event3))
      .WillOnce(CloneEvent(&event4));

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();

    for (const auto source :
         {WL_POINTER_AXIS_SOURCE_WHEEL, WL_POINTER_AXIS_SOURCE_FINGER,
          WL_POINTER_AXIS_SOURCE_CONTINUOUS,
          WL_POINTER_AXIS_SOURCE_WHEEL_TILT}) {
      SendAxisEvents(pointer, server->GetNextTime(), source,
                     WL_POINTER_AXIS_VERTICAL_SCROLL, rand() % 20);
    }
  });

  ASSERT_TRUE(event1);
  ASSERT_TRUE(event1->IsMouseWheelEvent());
  ASSERT_TRUE(event2);
  ASSERT_TRUE(event2->IsScrollEvent());
  ASSERT_TRUE(event3);
  ASSERT_TRUE(event3->IsScrollEvent());
  ASSERT_TRUE(event4);
  ASSERT_TRUE(event4->IsMouseWheelEvent());
}

TEST_F(WaylandPointerTest, Axis) {
  SendEnter();

  for (uint32_t axis :
       {WL_POINTER_AXIS_VERTICAL_SCROLL, WL_POINTER_AXIS_HORIZONTAL_SCROLL}) {
    for (bool send_axis_source : {false, true}) {
      std::unique_ptr<Event> event;
      EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

      PostToServerAndWait([axis, send_axis_source](
                              wl::TestWaylandServerThread* server) {
        auto* const pointer = server->seat()->pointer()->resource();

        if (send_axis_source) {
          // The axis source event is optional.  When it is not set within the
          // event frame, we assume the mouse wheel.
          wl_pointer_send_axis_source(pointer, WL_POINTER_AXIS_SOURCE_WHEEL);
        }

        // Wayland servers typically send a value of 10 per mouse wheel click.
        wl_pointer_send_axis(pointer, 1003, axis, wl_fixed_from_int(10));
        wl_pointer_send_frame(pointer);
      });

      ASSERT_TRUE(event);
      ASSERT_TRUE(event->IsMouseWheelEvent());
      auto* mouse_wheel_event = event->AsMouseWheelEvent();
      EXPECT_EQ(axis == WL_POINTER_AXIS_VERTICAL_SCROLL
                    ? gfx::Vector2d(0, -MouseWheelEvent::kWheelDelta)
                    : gfx::Vector2d(-MouseWheelEvent::kWheelDelta, 0),
                mouse_wheel_event->offset());
      EXPECT_EQ(gfx::PointF(), mouse_wheel_event->location_f());
      EXPECT_EQ(gfx::PointF(), mouse_wheel_event->root_location_f());
    }
  }
}

TEST_F(WaylandPointerTest, AxisFingerAndContinuous) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(ui::kWaylandUnscaledTouchpadScrolling);

  SendEnter();

  for (uint32_t axis :
       {WL_POINTER_AXIS_VERTICAL_SCROLL, WL_POINTER_AXIS_HORIZONTAL_SCROLL}) {
    for (uint32_t source :
         {WL_POINTER_AXIS_SOURCE_FINGER, WL_POINTER_AXIS_SOURCE_CONTINUOUS}) {
      std::unique_ptr<Event> event;
      EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

      PostToServerAndWait([axis, source](wl::TestWaylandServerThread* server) {
        auto* const pointer = server->seat()->pointer()->resource();

        wl_pointer_send_axis_source(pointer, source);
        wl_pointer_send_axis(pointer, 1003, axis, wl_fixed_from_int(10));
        wl_pointer_send_frame(pointer);
      });

      ASSERT_TRUE(event);
      ASSERT_TRUE(event->IsScrollEvent());
      auto* scroll_event = event->AsScrollEvent();
      EXPECT_EQ(EventType::kScroll, scroll_event->type());
      EXPECT_EQ(axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? 0.0f : -10.0f,
                scroll_event->x_offset());
      EXPECT_EQ(axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? -10.0f : 0.0f,
                scroll_event->y_offset());
    }
  }
}

TEST_F(WaylandPointerTest, Axis120) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndEnableFeature(ui::kWaylandUnscaledTouchpadScrolling);

  SendEnter();

  for (uint32_t axis :
       {WL_POINTER_AXIS_VERTICAL_SCROLL, WL_POINTER_AXIS_HORIZONTAL_SCROLL}) {
    for (bool send_axis_source : {false, true}) {
      std::unique_ptr<Event> event;
      EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

      PostToServerAndWait([axis, send_axis_source](
                              wl::TestWaylandServerThread* server) {
        auto* const pointer = server->seat()->pointer()->resource();

        if (send_axis_source) {
          wl_pointer_send_axis_source(pointer, WL_POINTER_AXIS_SOURCE_WHEEL);
        }

        wl_pointer_send_axis(pointer, 1003, axis, wl_fixed_from_int(10));
        if (wl_resource_get_version(pointer) >=
            WL_POINTER_AXIS_VALUE120_SINCE_VERSION) {
          wl_pointer_send_axis_value120(pointer, axis, 120);
        }
        wl_pointer_send_frame(pointer);
      });

      ASSERT_TRUE(event);
      ASSERT_TRUE(event->IsMouseWheelEvent());
      auto* mouse_wheel_event = event->AsMouseWheelEvent();
      EXPECT_EQ(axis == WL_POINTER_AXIS_VERTICAL_SCROLL
                    ? gfx::Vector2d(0, -MouseWheelEvent::kWheelDelta)
                    : gfx::Vector2d(-MouseWheelEvent::kWheelDelta, 0),
                mouse_wheel_event->offset());
      EXPECT_EQ(gfx::PointF(), mouse_wheel_event->location_f());
      EXPECT_EQ(gfx::PointF(), mouse_wheel_event->root_location_f());
    }
  }
}

TEST_F(WaylandPointerTest, AxisLegacyFlagDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(ui::kWaylandUnscaledTouchpadScrolling);

  SendEnter();

  for (uint32_t axis :
       {WL_POINTER_AXIS_VERTICAL_SCROLL, WL_POINTER_AXIS_HORIZONTAL_SCROLL}) {
    for (uint32_t source :
         {WL_POINTER_AXIS_SOURCE_FINGER, WL_POINTER_AXIS_SOURCE_CONTINUOUS}) {
      std::unique_ptr<Event> event;
      EXPECT_CALL(delegate_, DispatchEvent(_)).WillOnce(CloneEvent(&event));

      PostToServerAndWait([axis, source](wl::TestWaylandServerThread* server) {
        auto* const pointer = server->seat()->pointer()->resource();

        wl_pointer_send_axis_source(pointer, source);
        wl_pointer_send_axis(pointer, 1003, axis, wl_fixed_from_int(10));
        wl_pointer_send_frame(pointer);
      });

      ASSERT_TRUE(event);
      ASSERT_TRUE(event->IsScrollEvent());
      auto* scroll_event = event->AsScrollEvent();
      EXPECT_EQ(EventType::kScroll, scroll_event->type());
      EXPECT_EQ(axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? 0.0f : -120.0f,
                scroll_event->x_offset());
      EXPECT_EQ(axis == WL_POINTER_AXIS_VERTICAL_SCROLL ? -120.0f : 0.0f,
                scroll_event->y_offset());
    }
  }
}

TEST_F(WaylandPointerTest, SetBitmap) {
  SendEnter();

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer();

    EXPECT_CALL(*pointer, SetCursor(nullptr, 0, 0));
  });

  connection_->SetCursorBitmap({}, {}, 1.0);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer();

    Mock::VerifyAndClearExpectations(pointer);
  });

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer();

    EXPECT_CALL(*pointer, SetCursor(Ne(nullptr), 5, 8));
  });

  SkBitmap dummy_cursor;
  dummy_cursor.setInfo(
      SkImageInfo::Make(16, 16, kUnknown_SkColorType, kUnknown_SkAlphaType));
  connection_->SetCursorBitmap({dummy_cursor}, gfx::Point(5, 8), 1.0);

  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer();

    Mock::VerifyAndClearExpectations(pointer);
  });

  SendLeave();
}

// Tests that bitmap is set on pointer focus and the pointer surface respects
// provided scale of the surface image.
TEST_F(WaylandPointerTest, SetBitmapAndScaleOnPointerFocus) {
  for (float scale : {1.0, 1.2, 1.5, 1.75, 2.0, 2.5, 3.0}) {
    gfx::Size size = {static_cast<int>(10 * scale),
                      static_cast<int>(10 * scale)};
    SkBitmap dummy_cursor;
    SkImageInfo info = SkImageInfo::Make(size.width(), size.height(),
                                         SkColorType::kBGRA_8888_SkColorType,
                                         SkAlphaType::kPremul_SkAlphaType);
    dummy_cursor.allocPixels(info, size.width() * 4);

    const gfx::Point hotspot_px = {5, 8};
    const gfx::Point hotspot_dip =
        gfx::ScaleToRoundedPoint(hotspot_px, 1 / scale);

    WaylandCursorFactory cursor_factory(connection_.get());
    auto cursor = cursor_factory.CreateImageCursor(
        mojom::CursorType::kCustom, dummy_cursor, hotspot_px, scale);

    SendEnter(10, 10);

    // Set a cursor.
    wl_resource* surface_resource = nullptr;

    PostToServerAndWait([&surface_resource,
                         hotspot_dip](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer();

      // Allow up to 1 DIP of precision loss.
      EXPECT_CALL(
          *pointer,
          SetCursor(Ne(nullptr),
                    AllOf(Ge(hotspot_dip.x() - 1), Le(hotspot_dip.x() + 1)),
                    AllOf(Ge(hotspot_dip.y() - 1), Le(hotspot_dip.y() + 1))))
          .WillOnce(SaveArg<0>(&surface_resource));
    });

    window_->SetCursor(cursor);
    connection_->Flush();

    SendLeave();

    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer();

      Mock::VerifyAndClearExpectations(pointer);
    });

    ASSERT_TRUE(surface_resource);

    PostToServerAndWait([surface_resource, scale,
                         hotspot_dip](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer();

      auto* mock_pointer_surface =
          wl::MockSurface::FromResource(surface_resource);
      EXPECT_EQ(mock_pointer_surface->buffer_scale(), std::ceil(scale - 0.2f));

      // Update the focus.
      EXPECT_CALL(
          *pointer,
          SetCursor(Ne(nullptr),
                    AllOf(Ge(hotspot_dip.x() - 1), Le(hotspot_dip.x() + 1)),
                    AllOf(Ge(hotspot_dip.y() - 1), Le(hotspot_dip.y() + 1))));
    });

    SendEnter(50, 75);

    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer();

      Mock::VerifyAndClearExpectations(pointer);
    });

    // Reset the focus for the next iteration.
    SendLeave();
  }
}

TEST_F(WaylandPointerTest, FlingVertical) {
  SendEnter(50, 75);

  SendRightButtonPress();

  std::unique_ptr<Event> event1, event2, event3;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(3)
      .WillOnce(CloneEvent(&event1))
      .WillOnce(CloneEvent(&event2))
      .WillOnce(CloneEvent(&event3));

  // Send two axis events.
  for (int n = 0; n < 2; ++n) {
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer()->resource();

      SendAxisEvents(pointer, server->GetNextTime(),
                     WL_POINTER_AXIS_SOURCE_FINGER,
                     WL_POINTER_AXIS_VERTICAL_SCROLL, 10);
    });

    // Advance time to emulate delay between events and allow the fling gesture
    // to be recognised.
    task_environment_.FastForwardBy(base::Milliseconds(1));
  }

  // axis_stop event which should trigger fling scroll.
  SendAxisStopEvents();

  // Usual axis events should follow before the fling event.
  ASSERT_TRUE(event1);
  ASSERT_TRUE(event1->IsScrollEvent());
  ASSERT_TRUE(event2);
  ASSERT_TRUE(event2->IsScrollEvent());

  // The third dispatched event should be FLING_START.
  ASSERT_TRUE(event3);
  ASSERT_TRUE(event3->IsScrollEvent());
  auto* scroll_event = event3->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingStart, scroll_event->type());
  EXPECT_EQ(gfx::PointF(50, 75), scroll_event->location_f());
  EXPECT_EQ(0.0f, scroll_event->x_offset());
  EXPECT_EQ(0.0f, scroll_event->x_offset_ordinal());
  // Initial vertical velocity depends on the implementation outside of
  // WaylandPointer, but it should be negative value based on the direction of
  // recent two axis events.
  EXPECT_GT(0.0f, scroll_event->y_offset());
  EXPECT_GT(0.0f, scroll_event->y_offset_ordinal());
}

TEST_F(WaylandPointerTest, FlingHorizontal) {
  SendEnter(50, 75);

  SendRightButtonPress();

  std::unique_ptr<Event> event1, event2, event3;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(3)
      .WillOnce(CloneEvent(&event1))
      .WillOnce(CloneEvent(&event2))
      .WillOnce(CloneEvent(&event3));

  // Send two axis events.
  for (int n = 0; n < 2; ++n) {
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer()->resource();

      SendAxisEvents(pointer, server->GetNextTime(),
                     WL_POINTER_AXIS_SOURCE_FINGER,
                     WL_POINTER_AXIS_HORIZONTAL_SCROLL, 10);
    });

    // Advance time to emulate delay between events and allow the fling gesture
    // to be recognised.
    task_environment_.FastForwardBy(base::Milliseconds(1));
  }

  // axis_stop event which should trigger fling scroll.
  SendAxisStopEvents();

  // Usual axis events should follow before the fling event.
  ASSERT_TRUE(event1);
  ASSERT_TRUE(event1->IsScrollEvent());
  ASSERT_TRUE(event2);
  ASSERT_TRUE(event2->IsScrollEvent());

  // The third dispatched event should be FLING_START.
  ASSERT_TRUE(event3);
  ASSERT_TRUE(event3->IsScrollEvent());
  auto* scroll_event = event3->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingStart, scroll_event->type());
  EXPECT_EQ(gfx::PointF(50, 75), scroll_event->location_f());
  EXPECT_EQ(0.0f, scroll_event->y_offset());
  EXPECT_EQ(0.0f, scroll_event->y_offset_ordinal());
  // Initial horizontal velocity depends on the implementation outside of
  // WaylandPointer, but it should be negative value based on the direction of
  // recent two axis events.
  EXPECT_GT(0.0f, scroll_event->x_offset());
  EXPECT_GT(0.0f, scroll_event->x_offset_ordinal());
}

TEST_F(WaylandPointerTest, FlingCancel) {
  SendEnter(50, 75);
  SendRightButtonPress();
  std::unique_ptr<Event> event1, event2, event3, event4, event5;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(5)
      .WillOnce(CloneEvent(&event1))
      .WillOnce(CloneEvent(&event2))
      .WillOnce(CloneEvent(&event3))
      .WillOnce(CloneEvent(&event4))
      .WillOnce(CloneEvent(&event5));

  // Send two axis events.
  for (int n = 0; n < 2; ++n) {
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer()->resource();
      SendAxisEvents(pointer, server->GetNextTime(),
                     WL_POINTER_AXIS_SOURCE_FINGER,
                     WL_POINTER_AXIS_VERTICAL_SCROLL, 10);
    });
    // Advance time to emulate delay between events and allow the fling gesture
    // to be recognised.
    task_environment_.FastForwardBy(base::Milliseconds(1));
  }

  // axis_stop event which should trigger FLING_START.
  SendAxisStopEvents();

  // Another axis scroll event is added. In Linux, this must lead to a
  // FLING_CANCEL being triggered before a usual scroll event occurs because a
  // FLING_START was triggered beforehand.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();
    SendAxisEvents(pointer, server->GetNextTime(),
                   WL_POINTER_AXIS_SOURCE_FINGER,
                   WL_POINTER_AXIS_VERTICAL_SCROLL, 10);
  });

  // Advance time to emulate delay between events and allow the fling gesture
  // to be recognised.
  task_environment_.FastForwardBy(base::Milliseconds(1));
  // Two usual axis events should follow before the fling event.
  ASSERT_TRUE(event1);
  ASSERT_TRUE(event1->IsScrollEvent());
  EXPECT_EQ(EventType::kScroll, event1->type());

  ASSERT_TRUE(event2);
  ASSERT_TRUE(event2->IsScrollEvent());
  EXPECT_EQ(EventType::kScroll, event2->type());

  // The 3rd event will be fling start with vertical velocity.
  ASSERT_TRUE(event3);
  ASSERT_TRUE(event3->IsScrollEvent());
  auto* fling_start_event = event3->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingStart, fling_start_event->type());
  EXPECT_EQ(0.0f, fling_start_event->x_offset());
  EXPECT_GT(0.0f, fling_start_event->y_offset());
  EXPECT_EQ(0.0f, fling_start_event->x_offset_ordinal());
  EXPECT_GT(0.0f, fling_start_event->y_offset_ordinal());

  // The 4th event should be FLING_CANCEL.
  ASSERT_TRUE(event4);
  ASSERT_TRUE(event4->IsScrollEvent());
  auto* fling_cancel_event = event4->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingCancel, fling_cancel_event->type());
  EXPECT_EQ(gfx::PointF(50, 75), fling_cancel_event->location_f());
  EXPECT_EQ(0.0f, fling_cancel_event->x_offset());
  EXPECT_EQ(0.0f, fling_cancel_event->y_offset());
  EXPECT_EQ(0.0f, fling_cancel_event->x_offset_ordinal());
  EXPECT_EQ(0.0f, fling_cancel_event->y_offset_ordinal());

  // The 5th event will be yet another axis event.
  ASSERT_TRUE(event5);
  ASSERT_TRUE(event5);
  EXPECT_EQ(EventType::kScroll, event5->type());
}

TEST_F(WaylandPointerTest, FlingDiagonal) {
  SendEnter(50, 75);

  SendRightButtonPress();

  std::unique_ptr<Event> event1, event2, event3;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(3)
      .WillOnce(CloneEvent(&event1))
      .WillOnce(CloneEvent(&event2))
      .WillOnce(CloneEvent(&event3));

  // Send two axis events that notify scrolls both in vertical and horizontal.
  for (int n = 0; n < 2; ++n) {
    PostToServerAndWait([](wl::TestWaylandServerThread* server) {
      auto* const pointer = server->seat()->pointer()->resource();

      SendDiagonalAxisEvents(pointer, server->GetNextTime(),
                             WL_POINTER_AXIS_SOURCE_FINGER, 20, 10);
    });

    // Advance time to emulate delay between events and allow the fling gesture
    // to be recognised.
    task_environment_.FastForwardBy(base::Milliseconds(1));
  }

  // axis_stop event which should trigger fling scroll.
  SendAxisStopEvents();

  // Usual axis events should follow before the fling event.
  ASSERT_TRUE(event1);
  ASSERT_TRUE(event1->IsScrollEvent());
  ASSERT_TRUE(event2);
  ASSERT_TRUE(event2->IsScrollEvent());

  // The third dispatched event should be FLING_START.
  ASSERT_TRUE(event3);
  ASSERT_TRUE(event3->IsScrollEvent());
  auto* scroll_event = event3->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingStart, scroll_event->type());
  EXPECT_EQ(gfx::PointF(50, 75), scroll_event->location_f());
  // Check the offset direction. It should non-zero in both axes.
  EXPECT_GT(0.0f, scroll_event->x_offset());
  EXPECT_GT(0.0f, scroll_event->y_offset());
  EXPECT_GT(0.0f, scroll_event->x_offset_ordinal());
  EXPECT_GT(0.0f, scroll_event->y_offset_ordinal());
  // Horizontal offset should be larger than vertical one, given the scroll
  // offset in each direction.
  EXPECT_GT(std::abs(scroll_event->x_offset()),
            std::abs(scroll_event->y_offset()));
  EXPECT_GT(std::abs(scroll_event->x_offset_ordinal()),
            std::abs(scroll_event->y_offset_ordinal()));
}

// The case tested here should not actually occur in a good implementation of
// the Wayland protocol.
TEST_F(WaylandPointerTest, FlingVelocityWithoutLeadingAxis) {
  SendEnter(50, 75);

  SendRightButtonPress();

  std::unique_ptr<Event> event;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(1)
      .WillOnce(CloneEvent(&event));

  // Send axis_stop event without leading axis event.
  SendAxisStopEvents();

  // We expect only a FLING_START event.
  ASSERT_TRUE(event);
  ASSERT_TRUE(event->IsScrollEvent());
  auto* scroll_event = event->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingStart, scroll_event->type());

  // Check the offset direction. It should be zero in both axes.
  EXPECT_EQ(0.0f, scroll_event->x_offset());
  EXPECT_EQ(0.0f, scroll_event->y_offset());
  EXPECT_EQ(0.0f, scroll_event->x_offset_ordinal());
  EXPECT_EQ(0.0f, scroll_event->y_offset_ordinal());
}

TEST_F(WaylandPointerTest, FrameTargetUseAfterFreeOnSyncWindowClose) {
  SendEnter(50, 50);

  // Frame 1: axis_stop -> FlingStart is queued and drained (1 event). This
  // sets is_fling_active_=true so the next finger-scroll frame will queue 2
  // events (FlingCancel + Scroll).
  SendAxisStopEvents();

  // Simulate a popup widget closing from inside its own event handler.
  bool destroyed = false;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .WillRepeatedly([this, &destroyed](Event* event) {
        if (!destroyed) {
          destroyed = true;
          window_.reset();
        }
      });

  // Frame 2: finger axis -> ProcessPointerScrollData() pushes 2 events
  // [FlingCancel, Scroll] into pointer_frames_, then OnPointerFrameEvent()
  // drains both against the cached `target`. The first dispatch frees
  // `target`; the second dispatch should be dismissed.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();
    SendAxisEvents(pointer, server->GetNextTime(),
                   WL_POINTER_AXIS_SOURCE_FINGER,
                   WL_POINTER_AXIS_VERTICAL_SCROLL, 10);
  });

  EXPECT_TRUE(destroyed);

  // The test fixture's window has been torn down mid-test.
  DisableSyncOnTearDown();
}

TEST_F(WaylandPointerTest, FlingVelocityWithSingleLeadingAxis) {
  SendEnter(50, 75);

  SendRightButtonPress();

  std::unique_ptr<Event> event1, event2;
  EXPECT_CALL(delegate_, DispatchEvent(_))
      .Times(2)
      .WillOnce(CloneEvent(&event1))
      .WillOnce(CloneEvent(&event2));

  // Send a single axis event.
  PostToServerAndWait([](wl::TestWaylandServerThread* server) {
    auto* const pointer = server->seat()->pointer()->resource();

    SendDiagonalAxisEvents(pointer, server->GetNextTime(),
                           WL_POINTER_AXIS_SOURCE_FINGER, -20, 10);
  });

  // Advance time to emulate delay between events and allow the fling gesture
  // to be recognised.
  task_environment_.FastForwardBy(base::Milliseconds(1));

  // One axis event should follow before the fling event.
  SendAxisStopEvents();
  ASSERT_TRUE(event1);
  ASSERT_TRUE(event1->IsScrollEvent());

  // We expect a FLING_START event.
  ASSERT_TRUE(event2);
  ASSERT_TRUE(event2->IsScrollEvent());
  auto* scroll_event2 = event2->AsScrollEvent();
  EXPECT_EQ(EventType::kScrollFlingStart, scroll_event2->type());

  // Check the offset direction. Horizontal axis should be negative. Vertical
  // axis should be positive.
  EXPECT_LT(0.0f, scroll_event2->x_offset());
  EXPECT_GT(0.0f, scroll_event2->y_offset());
  EXPECT_LT(0.0f, scroll_event2->x_offset_ordinal());
  EXPECT_GT(0.0f, scroll_event2->y_offset_ordinal());
}

}  // namespace ui
