chromium/content/browser/renderer_host/direct_manipulation_win_unittest.cc

// 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 "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 {

class MockDirectManipulationViewport
    : public Microsoft::WRL::RuntimeClass<
          Microsoft::WRL::RuntimeClassFlags<
              Microsoft::WRL::RuntimeClassType::ClassicCom>,
          Microsoft::WRL::Implements<
              Microsoft::WRL::RuntimeClassFlags<
                  Microsoft::WRL::RuntimeClassType::ClassicCom>,
              Microsoft::WRL::FtmBase,
              IDirectManipulationViewport>> {
 public:
  MockDirectManipulationViewport() {}

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

  ~MockDirectManipulationViewport() override {}

  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 {
    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;
    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 {
    return S_OK;
  }

  HRESULT STDMETHODCALLTYPE RemoveEventHandler(_In_ DWORD cookie) override {
    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 { return S_OK; }

  HRESULT STDMETHODCALLTYPE Abandon() override { return S_OK; }

 private:
  bool zoom_to_rect_called_ = false;
};

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;
  }

  void HandleParentChanged() override {}

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

}  //  namespace

class DirectManipulationUnitTest : public testing::Test {
 public:
  DirectManipulationUnitTest() {
    viewport_ = Microsoft::WRL::Make<MockDirectManipulationViewport>();
    content_ = Microsoft::WRL::Make<MockDirectManipulationContent>();
    direct_manipulation_helper_ =
        DirectManipulationHelper::CreateInstanceForTesting(&event_target_,
                                                           viewport_);
  }

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

  ~DirectManipulationUnitTest() override {}

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

  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);
  }

 private:
  std::unique_ptr<DirectManipulationHelper> direct_manipulation_helper_;
  Microsoft::WRL::ComPtr<MockDirectManipulationViewport> viewport_;
  Microsoft::WRL::ComPtr<MockDirectManipulationContent> content_;
  MockWindowEventTarget event_target_;
};

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

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

  std::vector<Event> events = GetEvents();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingBegin, events[0].gesture_);

  ContentUpdated(1, 20, 0);

  events = GetEvents();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_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();
  EXPECT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

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

  ContentUpdated(1, 10, 0);
  events = GetEvents();
  EXPECT_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();
  EXPECT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kFlingEnd, events[0].gesture_);

  ContentUpdated(1.00001f, 10, 0);
  events = GetEvents();
  EXPECT_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();
  EXPECT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

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

  ContentUpdated(1, 10, 0);
  events = GetEvents();
  EXPECT_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();
  EXPECT_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();
  EXPECT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);

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

  ContentUpdated(1, 10, 0);
  events = GetEvents();
  EXPECT_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();
  EXPECT_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();
  EXPECT_EQ(1u, events.size());
  EXPECT_EQ(EventGesture::kScrollBegin, events[0].gesture_);
  EXPECT_EQ(5, events[0].scroll_x_);
}

}  //  namespace content