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

#include <objbase.h>

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/time/time.h"
#include "content/browser/renderer_host/direct_manipulation_helper_win.h"
#include "content/browser/renderer_host/direct_manipulation_test_helper_win.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/win/window_event_target.h"

namespace content {

namespace {

template <typename Interface>
using ComPtr = Microsoft::WRL::ComPtr<Interface>;

template <typename Interface>
using RuntimeFtmBaseClass = Microsoft::WRL::RuntimeClass<
    Microsoft::WRL::RuntimeClassFlags<
        Microsoft::WRL::RuntimeClassType::ClassicCom>,
    Microsoft::WRL::Implements<
        Microsoft::WRL::RuntimeClassFlags<
            Microsoft::WRL::RuntimeClassType::ClassicCom>,
        Microsoft::WRL::FtmBase,
        Interface>>;

class MockDirectManipulationViewport
    : public RuntimeFtmBaseClass<IDirectManipulationViewport> {
 public:
  MockDirectManipulationViewport() = default;

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

  ~MockDirectManipulationViewport() override = default;

  void set_stop_callback(base::OnceClosure callback) {
    stop_callback_ = std::move(callback);
  }
  void set_add_event_handler_callback(base::OnceClosure callback) {
    add_event_handler_callback_ = std::move(callback);
  }
  void set_remove_event_handler_callback(base::OnceClosure callback) {
    remove_event_handler_callback_ = std::move(callback);
  }
  void set_set_contact_callback(base::OnceClosure callback) {
    set_contact_callback_ = std::move(callback);
  }
  void set_abandon_callback(base::OnceClosure callback) {
    abandon_callback_ = std::move(callback);
  }
  void set_zoom_to_rect_callback(base::OnceClosure callback) {
    zoom_to_rect_callback_ = std::move(callback);
  }

  bool WasZoomToRectCalled() {
    bool called = zoom_to_rect_called_;
    zoom_to_rect_called_ = false;
    return called;
  }

  HRESULT STDMETHODCALLTYPE Enable() override { return S_OK; }

  HRESULT STDMETHODCALLTYPE Disable() override { return S_OK; }

  HRESULT STDMETHODCALLTYPE SetContact(_In_ UINT32 pointerId) override {
    if (set_contact_callback_) {
      std::move(set_contact_callback_).Run();
    }
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE ReleaseContact(_In_ UINT32 pointerId) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE ReleaseAllContacts() override { return S_OK; }

  HRESULT STDMETHODCALLTYPE
  GetStatus(_Out_ DIRECTMANIPULATION_STATUS* status) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE GetTag(_In_ REFIID riid,
                                   _COM_Outptr_opt_ void** object,
                                   _Out_opt_ UINT32* id) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE SetTag(_In_opt_ IUnknown* object,
                                   _In_ UINT32 id) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE GetViewportRect(_Out_ RECT* viewport) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  SetViewportRect(_In_ const RECT* viewport) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE ZoomToRect(_In_ const float left,
                                       _In_ const float top,
                                       _In_ const float right,
                                       _In_ const float bottom,
                                       _In_ BOOL animate) override {
    zoom_to_rect_called_ = true;
    if (zoom_to_rect_callback_) {
      std::move(zoom_to_rect_callback_).Run();
    }
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  SetViewportTransform(_In_reads_(point_count) const float* matrix,
                       _In_ DWORD point_count) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  SyncDisplayTransform(_In_reads_(point_count) const float* matrix,
                       _In_ DWORD point_count) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  GetPrimaryContent(_In_ REFIID riid, _COM_Outptr_ void** object) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  AddContent(_In_ IDirectManipulationContent* content) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  RemoveContent(_In_ IDirectManipulationContent* content) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE SetViewportOptions(
      _In_ DIRECTMANIPULATION_VIEWPORT_OPTIONS options) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE AddConfiguration(
      _In_ DIRECTMANIPULATION_CONFIGURATION configuration) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE RemoveConfiguration(
      _In_ DIRECTMANIPULATION_CONFIGURATION configuration) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE ActivateConfiguration(
      _In_ DIRECTMANIPULATION_CONFIGURATION configuration) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE SetManualGesture(
      _In_ DIRECTMANIPULATION_GESTURE_CONFIGURATION configuration) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  SetChaining(_In_ DIRECTMANIPULATION_MOTION_TYPES enabledTypes) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  AddEventHandler(_In_opt_ HWND window,
                  _In_ IDirectManipulationViewportEventHandler* eventHandler,
                  _Out_ DWORD* cookie) override {
    if (add_event_handler_callback_) {
      std::move(add_event_handler_callback_).Run();
    }
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE RemoveEventHandler(_In_ DWORD cookie) override {
    if (remove_event_handler_callback_) {
      std::move(remove_event_handler_callback_).Run();
    }
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  SetInputMode(_In_ DIRECTMANIPULATION_INPUT_MODE mode) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  SetUpdateMode(_In_ DIRECTMANIPULATION_INPUT_MODE mode) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE Stop() override {
    if (stop_callback_) {
      std::move(stop_callback_).Run();
    }
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE Abandon() override {
    if (abandon_callback_) {
      std::move(abandon_callback_).Run();
    }
    return S_OK;
  }

 private:
  bool zoom_to_rect_called_ = false;
  base::OnceClosure stop_callback_;
  base::OnceClosure add_event_handler_callback_;
  base::OnceClosure remove_event_handler_callback_;
  base::OnceClosure set_contact_callback_;
  base::OnceClosure abandon_callback_;
  base::OnceClosure zoom_to_rect_callback_;
};

class MockDirectManipulationUpdateManager
    : public RuntimeFtmBaseClass<IDirectManipulationUpdateManager> {
 public:
  MockDirectManipulationUpdateManager() = default;

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

  ~MockDirectManipulationUpdateManager() override = default;

  void set_update_callback(base::OnceClosure callback) {
    update_callback_ = std::move(callback);
  }

  HRESULT STDMETHODCALLTYPE
  RegisterWaitHandleCallback(HANDLE,
                             IDirectManipulationUpdateHandler*,
                             DWORD* cookie) override {
    *cookie = 123;
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE UnregisterWaitHandleCallback(DWORD) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  Update(IDirectManipulationFrameInfoProvider*) override {
    if (update_callback_) {
      std::move(update_callback_).Run();
    }
    return S_OK;
  }

 private:
  base::OnceClosure update_callback_;
};

class MockDirectManipulationManager
    : public RuntimeFtmBaseClass<IDirectManipulationManager> {
 public:
  explicit MockDirectManipulationManager(
      ComPtr<MockDirectManipulationViewport> viewport)
      : viewport_(std::move(viewport)) {}

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

  ~MockDirectManipulationManager() override = default;

  HRESULT STDMETHODCALLTYPE Activate(HWND) override { return S_OK; }

  HRESULT STDMETHODCALLTYPE CreateContent(IDirectManipulationFrameInfoProvider*,
                                          REFCLSID,
                                          REFIID,
                                          void**) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  CreateViewport(IDirectManipulationFrameInfoProvider*,
                 HWND,
                 REFIID riid,
                 void** object) override {
    return viewport_.CopyTo(riid, object);
  }

  HRESULT STDMETHODCALLTYPE Deactivate(HWND) override { return S_OK; }

  HRESULT STDMETHODCALLTYPE GetUpdateManager(REFIID riid,
                                             void** object) override {
    return update_manager_.CopyTo(riid, object);
  }

  HRESULT STDMETHODCALLTYPE ProcessInput(const MSG*, BOOL*) override {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE
  RegisterHitTestTarget(HWND, HWND, DIRECTMANIPULATION_HITTEST_TYPE) override {
    return S_OK;
  }

  ComPtr<MockDirectManipulationUpdateManager> mock_update_manager() {
    return update_manager_;
  }

 private:
  ComPtr<MockDirectManipulationViewport> viewport_;
  ComPtr<MockDirectManipulationUpdateManager> update_manager_ =
      Microsoft::WRL::Make<MockDirectManipulationUpdateManager>();
};

enum class EventGesture {
  kScrollBegin,
  kScroll,
  kScrollEnd,
  kFlingBegin,
  kFling,
  kFlingEnd,
  kScaleBegin,
  kScale,
  kScaleEnd,
};

struct Event {
  explicit Event(float scale) : gesture_(EventGesture::kScale), scale_(scale) {}

  Event(EventGesture gesture, float scroll_x, float scroll_y)
      : gesture_(gesture), scroll_x_(scroll_x), scroll_y_(scroll_y) {}

  explicit Event(EventGesture gesture) : gesture_(gesture) {}

  EventGesture gesture_;
  float scale_ = 0;
  float scroll_x_ = 0;
  float scroll_y_ = 0;
};

class MockWindowEventTarget : public ui::WindowEventTarget {
 public:
  MockWindowEventTarget() {}

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

  ~MockWindowEventTarget() override {}

  void ApplyPinchZoomScale(float scale) override {
    events_.push_back(Event(scale));
  }

  void ApplyPinchZoomBegin() override {
    events_.push_back(Event(EventGesture::kScaleBegin));
  }

  void ApplyPinchZoomEnd() override {
    events_.push_back(Event(EventGesture::kScaleEnd));
  }

  void ApplyPanGestureScroll(int scroll_x, int scroll_y) override {
    events_.push_back(Event(EventGesture::kScroll, scroll_x, scroll_y));
  }

  void ApplyPanGestureFling(int scroll_x, int scroll_y) override {
    events_.push_back(Event(EventGesture::kFling, scroll_x, scroll_y));
  }

  void ApplyPanGestureScrollBegin(int scroll_x, int scroll_y) override {
    events_.push_back(Event(EventGesture::kScrollBegin, scroll_x, scroll_y));
  }

  void ApplyPanGestureFlingBegin() override {
    events_.push_back(Event(EventGesture::kFlingBegin));
  }

  void ApplyPanGestureFlingEnd() override {
    events_.push_back(Event(EventGesture::kFlingEnd));
  }

  void ApplyPanGestureScrollEnd(bool tranisitioning_to_pinch) override {
    events_.push_back(Event(EventGesture::kScrollEnd));
  }

  std::vector<Event> GetEvents() {
    std::vector<Event> result = events_;
    events_.clear();
    return result;
  }

  // Other Overrides
  LRESULT HandleMouseMessage(unsigned int message,
                             WPARAM w_param,
                             LPARAM l_param,
                             bool* handled) override {
    return S_OK;
  }

  LRESULT HandlePointerMessage(unsigned int message,
                               WPARAM w_param,
                               LPARAM l_param,
                               bool* handled) override {
    return S_OK;
  }

  LRESULT HandleKeyboardMessage(unsigned int message,
                                WPARAM w_param,
                                LPARAM l_param,
                                bool* handled) override {
    return S_OK;
  }

  LRESULT HandleTouchMessage(unsigned int message,
                             WPARAM w_param,
                             LPARAM l_param,
                             bool* handled) override {
    return S_OK;
  }

  LRESULT HandleInputMessage(unsigned int message,
                             WPARAM w_param,
                             LPARAM l_param,
                             bool* handled) override {
    return S_OK;
  }

  LRESULT HandleScrollMessage(unsigned int message,
                              WPARAM w_param,
                              LPARAM l_param,
                              bool* handled) override {
    return S_OK;
  }

  LRESULT HandleNcHitTestMessage(unsigned int message,
                                 WPARAM w_param,
                                 LPARAM l_param,
                                 bool* handled) override {
    return S_OK;
  }

 private:
  std::vector<Event> events_;
};

}  //  namespace

class DirectManipulationUnitTest : public testing::Test {
 public:
  DirectManipulationUnitTest() = default;

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

  ~DirectManipulationUnitTest() override = default;

  void SetUp() override {
    testing::Test::SetUp();
    viewport_ = Microsoft::WRL::Make<MockDirectManipulationViewport>();
    ASSERT_TRUE(viewport_);
    manager_ = Microsoft::WRL::Make<MockDirectManipulationManager>(viewport_);
    ASSERT_TRUE(manager_);
    direct_manipulation_helper_ =
        DirectManipulationHelper::CreateInstanceForTesting(manager_);
    ASSERT_TRUE(direct_manipulation_helper_);
    direct_manipulation_helper_->UpdateEventHandler(nullptr, &event_target_);
    content_ = Microsoft::WRL::Make<MockDirectManipulationContent>();
    ASSERT_TRUE(content_);
  }

  DirectManipulationHelper* GetDirectManipulationHelper() {
    return direct_manipulation_helper_.get();
  }

  base::OnceClosure ResetDirectManipulationHelperCallback() {
    return base::BindOnce(
        &DirectManipulationUnitTest::ResetDirectManipulationHelper,
        base::Unretained(this));
  }

  void RecreateDirectManipulationHelper() {
    ASSERT_FALSE(direct_manipulation_helper_);
    ASSERT_TRUE(manager_);
    direct_manipulation_helper_ =
        DirectManipulationHelper::CreateInstanceForTesting(manager_);
    ASSERT_TRUE(direct_manipulation_helper_);
  }

  std::vector<Event> GetEvents() { return event_target_.GetEvents(); }

  void ViewportStatusChanged(DIRECTMANIPULATION_STATUS current,
                             DIRECTMANIPULATION_STATUS previous) {
    direct_manipulation_helper_->event_handler_->OnViewportStatusChanged(
        viewport_.Get(), current, previous);
  }

  void ContentUpdated(float scale, float scroll_x, float scroll_y) {
    content_->SetContentTransform(scale, scroll_x, scroll_y);
    direct_manipulation_helper_->event_handler_->OnContentUpdated(
        viewport_.Get(), content_.Get());
  }

  bool WasZoomToRectCalled() { return viewport_->WasZoomToRectCalled(); }

  void SetDeviceScaleFactor(float factor) {
    direct_manipulation_helper_->SetDeviceScaleFactorForTesting(factor);
  }

 protected:
  ComPtr<MockDirectManipulationViewport> viewport_;
  ComPtr<MockDirectManipulationManager> manager_;
  ComPtr<MockDirectManipulationContent> content_;
  MockWindowEventTarget event_target_;

 private:
  void ResetDirectManipulationHelper() { direct_manipulation_helper_.reset(); }

  std::unique_ptr<DirectManipulationHelper> direct_manipulation_helper_;
};

TEST_F(DirectManipulationUnitTest, ReceiveSimplePanTransform) {
  if (!GetDirectManipulationHelper())
    return;

  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_READY);
  ContentUpdated(1, 10, 0);

  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);
  EXPECT_EQ(10, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);

  // For next update, should only apply the difference.
  ContentUpdated(1, 15, 0);

  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScroll, events[0].gesture_);
  EXPECT_EQ(5, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);

  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);

  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollEnd, events[0].gesture_);
}

TEST_F(DirectManipulationUnitTest, ReceivePanFling) {
  if (!GetDirectManipulationHelper())
    return;

  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_READY);
  ContentUpdated(1, 10, 0);

  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);
  EXPECT_EQ(10, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);

  // For next update, should only apply the difference.
  ContentUpdated(1, 15, 0);

  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScroll, events[0].gesture_);
  EXPECT_EQ(5, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);

  // Fling Begin.
  ViewportStatusChanged(DIRECTMANIPULATION_INERTIA, DIRECTMANIPULATION_RUNNING);

  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingBegin, events[0].gesture_);

  ContentUpdated(1, 20, 0);

  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFling, events[0].gesture_);
  EXPECT_EQ(5, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);

  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_INERTIA);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingEnd, events[0].gesture_);
}

TEST_F(DirectManipulationUnitTest, ReceiveSimpleScaleTransform) {
  if (!GetDirectManipulationHelper())
    return;

  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_READY);
  ContentUpdated(1.1f, 0, 0);
  std::vector<Event> events = GetEvents();
  ASSERT_EQ(2u, events.size());
  EXPECT_EQ(EventGesture::kScaleBegin, events[0].gesture_);
  EXPECT_EQ(EventGesture::kScale, events[1].gesture_);
  EXPECT_EQ(1.1f, events[1].scale_);

  // For next update, should only apply the difference.
  ContentUpdated(1.21f, 0, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScale, events[0].gesture_);
  EXPECT_EQ(1.1f, events[0].scale_);

  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScaleEnd, events[0].gesture_);
}

TEST_F(DirectManipulationUnitTest, ReceiveScrollTransformLessThanOne) {
  if (!GetDirectManipulationHelper())
    return;

  // Scroll offset less than 1, should not apply.
  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_READY);
  ContentUpdated(1, 0.1f, 0);
  std::vector<Event> events = GetEvents();
  EXPECT_EQ(0u, events.size());

  // Scroll offset less than 1, should not apply.
  ContentUpdated(1, 0.2f, 0);
  events = GetEvents();
  EXPECT_EQ(0u, events.size());

  // Scroll offset more than 1, should only apply integer part.
  ContentUpdated(1, 1.2f, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);
  EXPECT_EQ(1, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);

  // Scroll offset difference less than 1, should not apply.
  ContentUpdated(1, 1.5f, 0);
  events = GetEvents();
  EXPECT_EQ(0u, events.size());

  // Scroll offset difference more than 1, should only apply integer part.
  ContentUpdated(1, 3.0f, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScroll, events[0].gesture_);
  EXPECT_EQ(2, events[0].scroll_x_);
  EXPECT_EQ(0, events[0].scroll_y_);
}

TEST_F(DirectManipulationUnitTest,
       ReceiveScaleTransformLessThanFloatPointError) {
  if (!GetDirectManipulationHelper())
    return;

  // Scale factor less than float point error, ignore.
  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_READY);
  ContentUpdated(1.000001f, 0, 0);
  std::vector<Event> events = GetEvents();
  EXPECT_EQ(0u, events.size());

  // Scale factor more than float point error, apply.
  ContentUpdated(1.00001f, 0, 0);
  events = GetEvents();
  ASSERT_EQ(2u, events.size());
  EXPECT_EQ(EventGesture::kScaleBegin, events[0].gesture_);
  EXPECT_EQ(EventGesture::kScale, events[1].gesture_);
  EXPECT_EQ(1.00001f, events[1].scale_);

  // Scale factor difference less than float point error, ignore.
  ContentUpdated(1.000011f, 0, 0);
  events = GetEvents();
  EXPECT_EQ(0u, events.size());

  // Scale factor difference more than float point error, apply.
  ContentUpdated(1.000021f, 0, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScale, events[0].gesture_);
  EXPECT_EQ(1.000021f / 1.00001f, events[0].scale_);

  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScaleEnd, events[0].gesture_);
}

TEST_F(DirectManipulationUnitTest, InSameSequenceReceiveBothScrollAndScale) {
  if (!GetDirectManipulationHelper())
    return;

  // Direct Manipulation maybe give incorrect predictions. In this case, we will
  // receive scroll first then scale after.

  // First event is a scroll event.
  ContentUpdated(1.0f, 5, 0);
  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

  // Second event comes with scale factor. Now the scroll offset only noise.
  ContentUpdated(1.00001f, 5, 0);
  events = GetEvents();
  ASSERT_EQ(3u, events.size());
  EXPECT_EQ(EventGesture::kScrollEnd, events[0].gesture_);
  EXPECT_EQ(EventGesture::kScaleBegin, events[1].gesture_);
  EXPECT_EQ(EventGesture::kScale, events[2].gesture_);
}

TEST_F(DirectManipulationUnitTest, InSameSequenceReceiveScaleAfterFling) {
  if (!GetDirectManipulationHelper())
    return;

  // Direct Manipulation maybe give pinch event after fling. In this case, we
  // should end the current sequence first.

  // First event is a scroll event.
  ContentUpdated(1.0f, 5, 0);
  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

  // Fling Begin.
  ViewportStatusChanged(DIRECTMANIPULATION_INERTIA, DIRECTMANIPULATION_RUNNING);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingBegin, events[0].gesture_);

  ContentUpdated(1, 10, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFling, events[0].gesture_);

  // Event comes with scale factor. Now the scroll offset only noise.
  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_INERTIA);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingEnd, events[0].gesture_);

  ContentUpdated(1.00001f, 10, 0);
  events = GetEvents();
  ASSERT_EQ(2u, events.size());
  EXPECT_EQ(EventGesture::kScaleBegin, events[0].gesture_);
  EXPECT_EQ(EventGesture::kScale, events[1].gesture_);
}

TEST_F(DirectManipulationUnitTest, InSameSequenceReceiveScrollAfterFling) {
  if (!GetDirectManipulationHelper())
    return;

  // Direct Manipulation maybe give scroll event after fling. In this case, we
  // should end the current sequence first.

  // First event is a scroll event.
  ContentUpdated(1.0f, 5, 0);
  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

  // Fling Begin.
  ViewportStatusChanged(DIRECTMANIPULATION_INERTIA, DIRECTMANIPULATION_RUNNING);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingBegin, events[0].gesture_);

  ContentUpdated(1, 10, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFling, events[0].gesture_);

  // Fling back to Scroll.
  ViewportStatusChanged(DIRECTMANIPULATION_RUNNING, DIRECTMANIPULATION_INERTIA);
  ContentUpdated(1, 15, 0);
  events = GetEvents();
  ASSERT_EQ(2u, events.size());
  EXPECT_EQ(EventGesture::kFlingEnd, events[0].gesture_);
  EXPECT_EQ(EventGesture::kScrollBegin, events[1].gesture_);
}

TEST_F(DirectManipulationUnitTest,
       ReceiveScaleAfterFlingWithoutViewportStatusChanged) {
  if (!GetDirectManipulationHelper())
    return;

  // We never see this when testing, but still what to test it.

  ContentUpdated(1.0f, 5, 0);
  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

  // Fling Begin.
  ViewportStatusChanged(DIRECTMANIPULATION_INERTIA, DIRECTMANIPULATION_RUNNING);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingBegin, events[0].gesture_);

  ContentUpdated(1, 10, 0);
  events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFling, events[0].gesture_);

  // Event comes with scale factor. But no ViewportStatusChanged.
  ContentUpdated(1.00001f, 10, 0);
  events = GetEvents();
  ASSERT_EQ(3u, events.size());
  EXPECT_EQ(EventGesture::kFlingEnd, events[0].gesture_);
  EXPECT_EQ(EventGesture::kScaleBegin, events[1].gesture_);
  EXPECT_EQ(EventGesture::kScale, events[2].gesture_);
}

TEST_F(DirectManipulationUnitTest,
       ZoomToRectShouldNotBeCalledInEmptyRunningReadySequence) {
  if (!GetDirectManipulationHelper())
    return;

  ContentUpdated(1.0f, 5, 0);

  // Receive first ready when gesture end.
  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
  EXPECT_TRUE(WasZoomToRectCalled());

  // Receive second ready from ZoomToRect.
  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
  EXPECT_FALSE(WasZoomToRectCalled());
}

TEST_F(DirectManipulationUnitTest, HiDPIScroll) {
  if (!GetDirectManipulationHelper())
    return;

  SetDeviceScaleFactor(10.0);
  ContentUpdated(1.0f, 50, 0);
  std::vector<Event> events = GetEvents();
  ASSERT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);
  EXPECT_EQ(5, events[0].scroll_x_);
}

// DirectManipulation COM calls on the UI thread can enter a nested message loop
// while they block on an internal delegate thread. The nested message loop can
// process WM_DESTROY messages that can invalidate pointers on the stack. To
// simulate this, these tests delete the DirectManipulationHelper from a mock
// COM call in each method that makes COM calls. If the methods don't guard
// their stack objects this will cause ASAN errors. Not all of these functions
// will enter a nested message loop in practice, but better safe than sorry.

TEST_F(DirectManipulationUnitTest, DestroyDuringOnAnimationStep) {
  manager_->mock_update_manager()->set_update_callback(
      ResetDirectManipulationHelperCallback());
  GetDirectManipulationHelper()->OnAnimationStep(base::TimeTicks::Now());
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

TEST_F(DirectManipulationUnitTest, DestroyDuringUpdateEventHandler) {
  ASSERT_TRUE(GetDirectManipulationHelper()->HasEventHandlerForTesting());
  viewport_->set_remove_event_handler_callback(
      ResetDirectManipulationHelperCallback());
  GetDirectManipulationHelper()->UpdateEventHandler(nullptr, nullptr);
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);

  RecreateDirectManipulationHelper();
  ASSERT_FALSE(GetDirectManipulationHelper()->HasEventHandlerForTesting());
  viewport_->set_add_event_handler_callback(
      ResetDirectManipulationHelperCallback());
  GetDirectManipulationHelper()->UpdateEventHandler(nullptr, &event_target_);
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

TEST_F(DirectManipulationUnitTest, DestroyDuringSetSizeInPixels) {
  viewport_->set_stop_callback(ResetDirectManipulationHelperCallback());
  GetDirectManipulationHelper()->SetSizeInPixels(gfx::Size(2000, 2000));
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

TEST_F(DirectManipulationUnitTest, DestroyDuringOnPointerHitTest) {
  ASSERT_TRUE(GetDirectManipulationHelper()->HasEventHandlerForTesting());
  viewport_->set_set_contact_callback(ResetDirectManipulationHelperCallback());
  GetDirectManipulationHelper()->OnPointerHitTest(0, PT_TOUCHPAD);
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

TEST_F(DirectManipulationUnitTest, DestroyDuringDestroy) {
  // OnCompositingShuttingDown can call Destroy(), which makes COM calls that
  // could enter a nested event loop. Those could process WM_DESTROY messages
  // that delete the DirectManipulationHelper, causing the destructor to
  // re-enter Destroy().
  viewport_->set_abandon_callback(ResetDirectManipulationHelperCallback());
  GetDirectManipulationHelper()->OnCompositingShuttingDown(
      GetDirectManipulationHelper()->compositor());
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

TEST_F(DirectManipulationUnitTest, DestroyDuringZoomToRect) {
  // ZoomToRect is triggered from
  // DirectManipulationEventHandler::OnViewportStatusChanged when there's a
  // content transform.
  ContentUpdated(1.1f, 0, 0);
  viewport_->set_zoom_to_rect_callback(ResetDirectManipulationHelperCallback());
  ViewportStatusChanged(DIRECTMANIPULATION_READY, DIRECTMANIPULATION_RUNNING);
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

TEST_F(DirectManipulationUnitTest, DestroyDuringGetContentTransform) {
  // GetContentTransform is triggered from
  // DirectManipulationEventHandler::OnContentUpdated.
  content_->set_get_content_transform_callback(
      ResetDirectManipulationHelperCallback());
  ContentUpdated(1.1f, 0, 0);
  EXPECT_EQ(GetDirectManipulationHelper(), nullptr);
}

}  //  namespace content
